Ecommerce Provider Port
Source-backed guide for the e-commerce provider contract in
apps/backend/src/ecommerce/domain/ecommerce-provider.port.ts.
Use this when adding providers or reviewing whether a change belongs in the domain port, application service, infrastructure adapter, or HTTP interface.
Intent
The e-commerce module lets FlowPOS sync products, inventory, collections, and
external orders without making the application layer depend on Shopify-specific
HTTP shapes. EcommerceService depends on EcommerceProviderPort; concrete
platform behavior belongs behind infrastructure adapters.
Current runtime provider status:
shopifyis registered inEcommerceProviderFactorywoocommerceis a reserved enum/status value, but no backend adapter is registered and the connect DTO does not accept it
Hexagonal map
| Layer | Codepaths | Responsibility |
|---|---|---|
| Domain | domain/ecommerce-provider.port.ts, domain/ecommerce-connection.domain.ts | Provider-neutral DTOs and persistence contracts |
| Application | application/ecommerce.service.ts, application/listeners/* | Connection orchestration, outbound sync, inbound ingestion, polling decisions |
| Infrastructure | infrastructure/ecommerce-provider.factory.ts, infrastructure/shopify/*, Kysely repository, polling processor | Provider HTTP calls, webhook security, payload parsing, persistence, queue worker |
| Interfaces | interfaces/ecommerce.controller.ts, provider webhook controllers, DTOs | Authenticated endpoints, public OAuth callback/webhook routes, request parsing |
Port contract
EcommerceProviderPort groups provider responsibilities by workflow:
| Area | Methods | Notes |
|---|---|---|
| Connection | getAuthorizationUrl, exchangeCodeForToken, revokeConnection, registerWebhooks | Current shipped flow is OAuth-oriented because Shopify is the only adapter |
| Products and inventory | pushProduct, pushCollection, updateInventoryLevel | Adapters return external IDs and variant mappings in provider-neutral shapes |
| Orders | pullOrders, acknowledgeOrder | Used by polling fallback after a connection is active |
| Webhook security | verifyWebhookSignature, parseWebhookPayload | HMAC headers and raw payload parsing stay inside adapters |
The port should only include behavior every supported provider can satisfy. Provider-specific authentication, webhook header names, platform API versions, and retry policies are infrastructure concerns.
Factory registration
EcommerceProviderFactory currently maps:
| Provider key | Adapter |
|---|---|
shopify | ShopifyAdapter |
Unsupported provider names fail fast with
Unknown e-commerce provider: (<provider>).
To add another provider:
- implement the adapter under
apps/backend/src/ecommerce/infrastructure/ - satisfy
EcommerceProviderPortwith provider-neutral return values - register the adapter in
EcommerceProviderFactory - register providers/controllers in
EcommerceModuleas needed - expand the interface DTOs so the provider can be selected
- add adapter, webhook, service, and polling tests for the new provider
Do not branch inside EcommerceService for platform-specific payload formats.
Branch in the application layer only when the FlowPOS use case truly differs,
such as connection setup for OAuth vs credential-based providers.
WooCommerce extension boundary
WooCommerce is expected to be credential-based rather than OAuth redirect-based. Do not overload the Shopify OAuth endpoints with Consumer Key / Consumer Secret semantics.
Recommended shape for a future WooCommerce implementation:
- add a credential-based interface endpoint and DTO
- keep WordPress/WooCommerce REST URL validation in the interface/application boundary
- keep Consumer Key / Consumer Secret usage inside the WooCommerce adapter
- register WooCommerce webhooks through adapter code
- map WooCommerce payloads into
EcommerceOrder,EcommerceProduct, andEcommerceCollectiondomain DTOs before returning to the application layer
Until this is implemented, API consumers should treat woocommerce as reserved
forward compatibility.
Common pitfalls
ConnectEcommerceDtoaccepts onlyshopify; adding an enum value alone does not expose a provider.- Shopify
orders/createwebhooks are parsed into orders; the currently registered Shopify inventory webhook does not produce inbound inventory changes. - The repository can store a
saleIdfor an ingested external order, but the current application service does not create FlowPOSsalerecords from Shopify orders. - OAuth state is part of the current Shopify flow. If a provider does not use OAuth, document the interface workflow separately instead of pretending it is the same domain behavior.