Problem or motivation
Flo needs to support country-specific payments, fiscal compliance, and delivery integrations without adding every provider and country directly to the core.
The POS runs behind NAT on a restaurant computer. It cannot be the provider-facing webhook endpoint.
This is a draft reference for Flo's plugin architecture, not a final implementation contract. Plugins will evolve as Flo adds countries, providers, security controls, and marketplace features. I want the team to agree on the direction and the first stable seams before the refactor starts.
Proposed solution
I propose three layers:
Flo core
Stable payment, tax, fiscal, order, delivery, and event contracts.
Local plugin
Flo-owned code runs in-process.
External country code runs in Electron utilityProcess.
Hosted connector
Provider APIs, polling, webhooks, credentials, retries,
reconciliation, and provider-specific payloads.
The POS makes outbound connections and receives normalized events. It never exposes a public provider URL.
Reference architecture:
| Concern |
Reference |
Flo proposal |
| Payment lifecycle |
Vendure |
Explicit initialize, status, settle, cancel, refund, and eligibility operations |
| Country package structure |
Medusa |
Country packages can contain payment, tax, fiscal, delivery, admin, and workflow capabilities |
| Hosted integrations |
Saleor |
Signed manifests, permissions, provider references, and normalized events |
| Fiscal compliance |
Odoo |
Country-owned tax rules, invoice formats, numbering, and fiscal authorization |
| Connector activation |
Shopify |
Install, authorize, configure, test, verify, and activate |
| Local isolation |
Electron |
External local plugins run through utilityProcess and MessagePort |
Core contracts:
PaymentProvider.charge()
-> PaymentConnector.initialize/status/settle/cancel/refund
TaxEngine.compute()
-> TaxEngine.calculate()
InvoiceProvider.issue()
-> FiscalDocumentProvider.issue/retry/cancel
DeliveryAdapter.parseIncomingOrder()
-> DeliveryConnector.poll(cursor)
-> normalized DeliveryEvent
Direct POS webhooks
-> removed
-> broker-owned or approved publisher-owned hosted endpoints
The core owns normalized state, accounting, orders, payments, and fiscal records. Connectors own provider APIs, credentials, external IDs, polling, retries, and payload conversion.
The manifest must declare the plugin ID, publisher, Flo API compatibility, country scope, supported countries, capabilities, execution mode, permissions, configuration schema, provider account requirements, polling hosts, optional hosted routes, allowed outbound hosts, package digest, and signature.
Supported scopes are country, multi_country, and global. The marketplace filters entries by the store's configured country before showing, installing, or activating them.
Argentina examples
Mercado Pago QR:
Flo initializes the payment.
The hosted connector creates the QR payment.
Flo records the payment as pending.
The broker polls the provider status API.
The connector returns paid, failed, expired, or cancelled.
Flo receives the normalized payment event.
The payment record includes the provider reference and idempotency key.
ARCA:
Flo calculates IVA using the Argentina tax engine.
The fiscal provider selects the document type and number.
The provider submits the invoice through the configured ARCA path.
ARCA returns authorization data, including CAE where applicable.
Flo stores the document number, authorization, expiry, QR data, and status.
Failures remain recoverable and auditable.
Tax calculation and fiscal authorization remain separate.
PedidosYa or Uber Eats:
The broker polls for new or changed orders.
The connector stores the cursor and normalizes the order.
Flo receives the order through its outbound connection.
The restaurant accepts or rejects the order.
The kitchen starts preparing the order.
The kitchen marks the order ready.
The broker sends the provider command.
Later polling confirms ready, dispatched, delivered, or cancelled.
The delivery lifecycle is:
received -> accepted -> preparing -> ready -> dispatched -> delivered
received -> rejected
received -> cancelled
accepted -> cancelled
preparing -> cancelled
Commands include accept, deny, start_preparing, ready, dispatched, and cancel.
Events and connector accounts
Plugins listen to normalized Flo events, not raw database changes or provider payloads.
Kitchen marks order ready
-> Flo writes delivery.ready to the outbox
-> dispatcher resolves connectorAccountId
-> broker sends delivery.ready to the provider connector
-> connector calls the provider API
-> polling confirms the provider state
-> Flo records the result
Every event includes eventId, schemaVersion, storeId, aggregateType, aggregateId, connectorAccountId, idempotencyKey, and payload.
The connector account prevents an event from reaching the wrong provider account:
order.source = pedidosya
order.connectorAccountId = pedidosya-ar-store-42
Events use an outbox and are delivered asynchronously. Provider calls do not run inside the POS database transaction.
Polling and hosted HTTP
Polling is an outbound request from the hosted connector:
Broker scheduler
-> hosted connector
-> provider API
-> normalized event
-> Flo outbound connection
-> POS
The POS never listens for provider traffic. If a provider later requires webhooks, the provider calls the Flo broker or an approved publisher-owned hosted service. The manifest can declare serviceBaseUrl, polling, webhookRoutes, and allowedOutboundHosts, but it must not create an unrestricted reverse proxy.
Marketplace and security
The first marketplace version is a signed catalog and controlled installation flow, not a public app store. It supports built-in capabilities, local country packages, hosted payment connectors, hosted delivery connectors, country bundles, and verified publishers.
Installation and activation are separate. Flo verifies the publisher, signature, ABI, permissions, country, and artifact before installation. Activation configures the merchant account, authorizes the provider, tests the connection, and enables the capability.
Vendure and Medusa treat installed plugins as trusted application code. Flo needs stronger isolation for external country code:
- Flo-owned plugins may run in-process.
- External local plugins run in
utilityProcess.
- External plugins use a versioned
MessagePort ABI.
- Plugins cannot access the raw database or Electron APIs.
- Plugins cannot call arbitrary network hosts.
- Permissions are declared and enforced by the host.
- Packages are signed and immutable.
- Hosted connectors are scoped to publisher, store, installation, and connector account.
- Provider credentials remain behind the host or broker.
- Polling and commands are rate-limited.
- Events are deduplicated and idempotent.
- Packages and connectors can be revoked without deleting historical records.
Required refactor areas
| Area |
Current location |
Required change |
| Tax |
main/services/tax.ts |
Replace country switch with registered tax engines |
| Payments |
main/routes/bills.ts |
Validate capabilities, provider references, status, and idempotency |
| Payment UI |
frontend/src/lib/payment-methods.ts |
Load payment capabilities from the backend |
| Cloud transport |
main/services/cloud-sync.ts |
Support normalized connector events and account routing |
| Database |
main/db.ts |
Add plugin, connector, event, outbox, command, and fiscal state |
| POS/KDS |
Existing order and KDS flows |
Accept normalized external orders and lifecycle events |
| Hosted broker |
New hosted service work |
Add polling, provider APIs, credentials, retries, and reconciliation |
| Plugin host |
New Electron integration |
Add manifest validation, utilityProcess, permissions, and lifecycle |
| Tests |
Existing test suite |
Add contract, polling, replay, idempotency, fiscal recovery, and provider tests |
Implementation order
- Define the core contracts and event envelopes.
- Add the outbox and connector-account model.
- Replace hardcoded payment capability handling.
- Implement the Argentina payment and fiscal contracts.
- Build the hosted broker polling foundation.
- Implement PedidosYa or Uber Eats delivery polling.
- Add delivery commands and lifecycle reconciliation.
- Implement the signed manifest and permission model.
- Add the external plugin utilityProcess ABI.
- Add India and other countries using the same contracts.
Acceptance criteria
- Argentina providers can be added without changing core accounting tables.
- ARCA authorization is separate from tax calculation.
- Delivery works through broker polling without a public POS endpoint.
- Delivery supports accepted, preparing, ready, dispatched, delivered, rejected, and cancelled states.
- Provider commands are scoped to the correct connector account.
- Duplicate events do not create duplicate orders, payments, or invoices.
- Polling cursors are durable and replayable.
- External packages cannot run in the Electron main process.
- Undeclared plugin permissions are rejected.
- Hosted connectors cannot access another store's credentials or events.
- India can add UPI, GST, IRN, and delivery providers without changing core contracts.
- Marketplace listings are filtered by installation country.
- Packages can be revoked or disabled without deleting historical business records.
Area
Other: architecture, backend integrations, Electron runtime, hosted connectors, marketplace, payments, tax, fiscal compliance, and delivery.
Problem or motivation
Flo needs to support country-specific payments, fiscal compliance, and delivery integrations without adding every provider and country directly to the core.
The POS runs behind NAT on a restaurant computer. It cannot be the provider-facing webhook endpoint.
This is a draft reference for Flo's plugin architecture, not a final implementation contract. Plugins will evolve as Flo adds countries, providers, security controls, and marketplace features. I want the team to agree on the direction and the first stable seams before the refactor starts.
Proposed solution
I propose three layers:
The POS makes outbound connections and receives normalized events. It never exposes a public provider URL.
Reference architecture:
Core contracts:
The core owns normalized state, accounting, orders, payments, and fiscal records. Connectors own provider APIs, credentials, external IDs, polling, retries, and payload conversion.
The manifest must declare the plugin ID, publisher, Flo API compatibility, country scope, supported countries, capabilities, execution mode, permissions, configuration schema, provider account requirements, polling hosts, optional hosted routes, allowed outbound hosts, package digest, and signature.
Supported scopes are
country,multi_country, andglobal. The marketplace filters entries by the store's configured country before showing, installing, or activating them.Argentina examples
Mercado Pago QR:
The payment record includes the provider reference and idempotency key.
ARCA:
Tax calculation and fiscal authorization remain separate.
PedidosYa or Uber Eats:
The delivery lifecycle is:
Commands include
accept,deny,start_preparing,ready,dispatched, andcancel.Events and connector accounts
Plugins listen to normalized Flo events, not raw database changes or provider payloads.
Every event includes
eventId,schemaVersion,storeId,aggregateType,aggregateId,connectorAccountId,idempotencyKey, andpayload.The connector account prevents an event from reaching the wrong provider account:
Events use an outbox and are delivered asynchronously. Provider calls do not run inside the POS database transaction.
Polling and hosted HTTP
Polling is an outbound request from the hosted connector:
The POS never listens for provider traffic. If a provider later requires webhooks, the provider calls the Flo broker or an approved publisher-owned hosted service. The manifest can declare
serviceBaseUrl,polling,webhookRoutes, andallowedOutboundHosts, but it must not create an unrestricted reverse proxy.Marketplace and security
The first marketplace version is a signed catalog and controlled installation flow, not a public app store. It supports built-in capabilities, local country packages, hosted payment connectors, hosted delivery connectors, country bundles, and verified publishers.
Installation and activation are separate. Flo verifies the publisher, signature, ABI, permissions, country, and artifact before installation. Activation configures the merchant account, authorizes the provider, tests the connection, and enables the capability.
Vendure and Medusa treat installed plugins as trusted application code. Flo needs stronger isolation for external country code:
utilityProcess.MessagePortABI.Required refactor areas
main/services/tax.tsmain/routes/bills.tsfrontend/src/lib/payment-methods.tsmain/services/cloud-sync.tsmain/db.tsImplementation order
Acceptance criteria
Area
Other: architecture, backend integrations, Electron runtime, hosted connectors, marketplace, payments, tax, fiscal compliance, and delivery.