Saltar al contenido principal

Ecommerce Adapter Architecture

Source-backed reference for apps/backend/src/ecommerce/.

Current provider status in code:

  • Shopify: implemented
  • WooCommerce: not yet implemented (port and factory pattern are ready)

Scope

The ecommerce module handles:

  • OAuth connection lifecycle
  • outbound sync (products, inventory, collections)
  • inbound order ingestion (webhooks + polling fallback)
  • sync logging and connection diagnostics

Key files:

  • application/ecommerce.service.ts
  • domain/ecommerce-provider.port.ts
  • infrastructure/ecommerce-provider.factory.ts
  • infrastructure/shopify/*
  • interfaces/ecommerce.controller.ts

Hexagonal mapping

Domain layer

  • ecommerce-provider.port.ts: provider contract
  • ecommerce-connection.domain.ts: repository contracts and records

Domain expresses what the module needs from a provider without HTTP/SDK specifics.

Application layer

  • EcommerceService: orchestration for connect/sync/poll/webhook ingestion/logging
  • listeners under application/listeners/: event-driven sync triggers

Infrastructure layer

  • EcommerceConnectionRepository (Kysely persistence)
  • EcommerceProviderFactory (provider-name -> adapter mapping)
  • EcommercePollingProcessor (BullMQ queue worker)
  • ShopifyAdapter + ShopifyWebhookController

Interfaces layer

  • EcommerceController for authenticated operational endpoints
  • public Shopify webhook and OAuth callback endpoints
  • DTO/query parsing

Provider port and extension pattern

EcommerceProviderPort is the stable contract for all adapters:

  • OAuth URL and code exchange
  • webhook registration and signature verification
  • product and collection push
  • inventory level update
  • order pull and acknowledge
  • webhook payload parsing

EcommerceProviderFactory currently maps:

  • shopify -> ShopifyAdapter

To add WooCommerce, implement a WooCommerce adapter that satisfies EcommerceProviderPort and register it in the factory. Application flow remains unchanged.


Runtime flows

1) OAuth connection

  1. GET /ecommerce/oauth/url returns provider authorization URL (state includes business + provider context).
  2. Provider redirects to GET /ecommerce/oauth/callback.
  3. Service exchanges code for token, attempts webhook registration, encrypts credentials, stores connection.
  4. Polling repeat job is registered.
  5. Backend redirects to PWA settings route.

2) Outbound sync

Event-driven:

  • product sync listener -> syncProduct
  • inventory sync listener -> syncInventoryLevel
  • collection sync listener -> syncCollection

Manual:

  • POST /ecommerce/sync/products
  • POST /ecommerce/sync/collections

Batch pushes run paginated loops and emit summary sync logs.

3) Inbound orders

Webhook path:

  • POST /ecommerce/webhooks/shopify
  • requires raw request body for HMAC verification (express.raw route behavior)
  • validates HMAC using stored webhook secret
  • parses payload via adapter
  • ingests deduplicated order

Polling fallback:

  • BullMQ queue ecommerce-polling, job poll
  • pulls orders since lastSyncedAt ?? createdAt
  • ingests + acknowledges orders
  • updates lastSyncedAt

Persistence responsibilities

EcommerceConnectionRepository handles:

  • connection create/read/update/delete
  • product mapping upsert and lookup
  • collection mapping upsert and lookup
  • ingested-order deduplication guard
  • sync log persistence and pagination
  • acknowledgement timestamps and connection status/sync timestamps

Credential data is encrypted at rest through CryptoService.


Queue and polling behavior

Polling queue:

  • queue name: ecommerce-polling
  • repeat job: poll
  • repeat interval: every 15 minutes
  • repeat job id pattern: ecommerce-poll-{businessId}

Worker:

  • processor: EcommercePollingProcessor
  • startup dedup uses repeatable job.key (not job.id) to avoid accidental deletion of valid jobs
  • failures are rethrown for BullMQ retry handling

Manual poll:

  • POST /ecommerce/sync/poll enqueues one immediate poll job
  • GET /ecommerce/debug/poll runs poll synchronously for debug workflows

API surface (high-level)

Authenticated:

  • GET /ecommerce/connection
  • GET /ecommerce/oauth/url
  • GET /ecommerce/sync/log
  • POST /ecommerce/sync/products
  • POST /ecommerce/sync/collections
  • POST /ecommerce/sync/poll
  • GET /ecommerce/debug/poll
  • GET /ecommerce/debug/scopes
  • DELETE /ecommerce/connection

Public:

  • GET /ecommerce/oauth/callback
  • POST /ecommerce/webhooks/shopify

Most authenticated endpoints are scoped by businessId query param.


Operational pitfalls

  • Webhook registration can fail during connect; polling fallback is still used.
  • Webhook controller intentionally returns 200 { ok: false } for app-side errors (non-auth) to prevent provider retry storms.
  • Polling jobs are deduplicated by stable repeat jobId (ecommerce-poll-{businessId}).
  • disconnect is idempotent: returns success even when no connection exists.
  • Scope debugging endpoint (/ecommerce/debug/scopes) is intended for operational troubleshooting, not merchant workflows.
  • Provider factory currently maps only shopify; unsupported provider names fail fast with Unknown e-commerce provider.