Integrations
The Integration framework provides a standardized way to connect external services (payment gateways, shipping providers, SMS services, etc.) to Fullfinity.
Overview
Section titled “Overview”Integrations follow a provider-agnostic architecture:
┌─────────────────────────────────────────────────────────────────┐│ CORE MODULES ││ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ││ │ invoicing │ │ sales │ │ portal │ ││ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ ││ │ │ │ ││ └────────────────┼────────────────┘ ││ ▼ ││ ┌────────────────────────────────┐ ││ │ integrations (base) │ ││ │ - Integration model │ ││ │ - IntegrationMapper model │ ││ └────────────────┬───────────────┘ ││ │ ││ ┌────────────────┼────────────────┐ ││ ▼ ▼ ▼ ││ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ││ │payment_stripe│ │shipping_ups │ │ sms_twilio │ ││ │(integration) │ │(integration)│ │(integration)│ ││ └─────────────┘ └─────────────┘ └─────────────┘ │└─────────────────────────────────────────────────────────────────┘Integration Model
Section titled “Integration Model”The base Integration model in the integrations module:
class Integration(Model): _verbose_name = "Integration"
name = Char(description="Integration Name", max_length=100, required=True) description = Text(description="Description") image = File(description="Image") company = ManyToOne("Company", related_name="integrations", on_delete="CASCADE") enabled = Boolean(description="Enabled", default=False) module = ManyToOne("Module", related_name="integrations", on_delete="SET NULL") integration_category = Selection( choices=["Online Payment", "Messaging", "Shipping"], description="Integration Category", hint="Which category this integration belongs to (set from each module's manifest).", ) cron_sync_method = Char(description="Cron Sync Method", max_length=100) last_synced_date = Datetime(description="Last Synced On")Key Fields
Section titled “Key Fields”| Field | Description |
|---|---|
name | Human-readable integration name |
enabled | Whether the integration is active |
module | Reference to the Module that provides this integration |
integration_category | Selection — one of Online Payment, Messaging, Shipping (set from the module manifest) |
cron_sync_method | Method name for scheduled sync jobs |
Computed State
Section titled “Computed State”async def get_state(self): """State is computed from module installation + enabled flag.""" self.state = "Not Installed" await self.fetch_related("module") if self.module and self.module.state == "Installed": self.state = "Installed" if self.enabled: self.state = "Enabled"Creating an Integration Module
Section titled “Creating an Integration Module”1. Module Manifest
Section titled “1. Module Manifest”Integration modules declare their category in manifest.yaml:
name: Stripe Paymentsidentifier: payment_stripeversion: '1.0'category: module_category_integrationsintegration_category: Online Payment # Links to backbone moduledescription: Accept payments via Stripedependencies:- online_payment # Backbone module dependencyicon: CreditCardimage: /static/img/stripe_logo.pngstatic_paths:- imgKey manifest fields for integrations:
| Field | Description |
|---|---|
category | Always module_category_integrations for integration modules |
integration_category | The backbone module this integration extends |
dependencies | Must include the backbone module |
image | Logo shown in integration list |
2. Extend Integration Model
Section titled “2. Extend Integration Model”Create a model that inherits from Integration:
from fullfinity.engine.base import *
class MyServiceIntegration(Model): """Extends Integration with MyService-specific fields.""" __inherit__ = "Integration"
# Provider-specific credential fields api_key = Char( max_length=255, description="API Key", hint="Your MyService API key", company_scoped=True, # Stored per-company ) api_secret = Char( max_length=255, description="API Secret", company_scoped=True, ) sandbox_mode = Boolean( description="Sandbox Mode", default=True, )
async def _is_my_service(self) -> bool: """Check if this integration is MyService.""" await self.fetch_related("module") return self.module and self.module.identifier == "my_service"
async def enable_integration(self): """Validate credentials before enabling.""" if await self._is_my_service(): if not self.api_key: raise UserError("Please enter your API Key before enabling.") await super().enable_integration()3. Provider Detection Pattern
Section titled “3. Provider Detection Pattern”Integration methods use a detection pattern to only process their own integrations:
async def some_method(self, *args, **kwargs): # Check if this integration is ours if not await self._is_my_service(): return await super().some_method(*args, **kwargs)
# Handle our integration-specific logic ...This pattern allows multiple integration modules to extend the same backbone.
Integration Categories
Section titled “Integration Categories”integration_category is a Selection — its value must be one of the choices defined on the
Integration model. Current categories and their backbone modules:
| Category | Backbone Module | Purpose |
|---|---|---|
Online Payment | online_payment | Payment gateways (Stripe, PayPal, Razorpay, Adyen, Mollie, Square) |
Messaging | messaging | Messaging providers (e.g. Twilio) |
Shipping | — | Shipping providers (category reserved) |
To add a new category, extend the integration_category Selection choices on the
Integration model first; only then can a manifest use it.
Integration Mapper
Section titled “Integration Mapper”For syncing data with external systems, use IntegrationMapper:
class IntegrationMapper(Model): _verbose_name = "Integration Mapper"
local_id = Char(description="Document ID", max_length=255, required=True) remote_id = Char(description="Remote ID", max_length=255, required=True) integration = ManyToOne("Integration", related_name="mappings", on_delete="CASCADE") model = Char(description="Model", max_length=255, required=True)Example usage:
# Store mapping after creating remote resourceasync def sync_contact(self, contact): # Create in remote system remote_customer = await self._api_create_customer(contact)
# Store mapping IntegrationMapper = get_model("IntegrationMapper") await IntegrationMapper.create( integration=self.id, model="Contact", local_id=str(contact.id), remote_id=remote_customer["id"], )
# Look up remote IDasync def get_remote_id(self, contact): IntegrationMapper = get_model("IntegrationMapper") mapping = await IntegrationMapper.filter( integration=self.id, model="Contact", local_id=str(contact.id), ).first() return mapping.remote_id if mapping else NoneCron Sync
Section titled “Cron Sync”For scheduled synchronization:
class MyServiceIntegration(Model): __inherit__ = "Integration"
# Set sync method name (called by cron) cron_sync_method = "sync_my_service"
async def sync_my_service(cls): """Cron job: Sync data with MyService.""" env = env_ctx.get() Integration = get_model("Integration")
# Get all enabled MyService integrations integrations = await Integration.filter( module__identifier="my_service", enabled=True, ).all()
for integration in integrations: await integration._do_sync()
async def _do_sync(self): """Perform actual sync logic.""" # Fetch updates from external service # Update local records # Update last_synced_date self.last_synced_date = datetime.now() await self.save()View Extension
Section titled “View Extension”Add provider-specific fields to Integration form:
- data_type: UiView identifier: integration_form_view __inherit__: integration_form_view arch: - type: field name: api_key position: after:enabled properties: visible: Q(module__identifier__eq='my_service') - type: field name: api_secret position: after:api_key properties: widget: Password visible: Q(module__identifier__eq='my_service')Best Practices
Section titled “Best Practices”- Use
company_scoped=Truefor credential fields to store per-company - Validate credentials in
enable_integration()before allowing enable - Use the detection pattern (
_is_my_service()) in all override methods - Store mappings for synced records to avoid duplicates
- Handle API errors gracefully with user-friendly messages
- Log sync operations for debugging
Next Steps
Section titled “Next Steps”- Payment Integrations - Building payment gateway integrations
- Creating Modules - General module development