Skip to content

feat(architecture): define country and provider plugin system #142

Description

@carvalab

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

  1. Define the core contracts and event envelopes.
  2. Add the outbox and connector-account model.
  3. Replace hardcoded payment capability handling.
  4. Implement the Argentina payment and fiscal contracts.
  5. Build the hosted broker polling foundation.
  6. Implement PedidosYa or Uber Eats delivery polling.
  7. Add delivery commands and lifecycle reconciliation.
  8. Implement the signed manifest and permission model.
  9. Add the external plugin utilityProcess ABI.
  10. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions