This document follows three paths that cross the most important boundaries. File
names are the current implementation, not Graphify's historical src/... paths.
#1. Provider payment request
Example: an integration creates a Stripe PaymentIntent or Mercado Pago Order.
Diagram source
sequenceDiagram
participant AUT as Application under test
participant GC as GatewayController
participant GS as GatewayService
participant P as ProviderPlugin
participant PS as PaymentsService
participant DB as PostgreSQL
AUT->>GC: Provider path + bearer token
GC->>GS: handle(path, request)
GS->>DB: Resolve application credential
GS->>P: Match route
GS->>DB: Resolve scenario and start trace
GS->>P: Validate request
GS->>PS: Execute canonical command
PS->>DB: Lock/idempotency/state/ledger/outbox transaction
PS->>P: toWire(canonical view)
P-->>AUT: Provider-shaped response
Step by step:
apps/api/src/modules/gateway/gateway.controller.tscatches the provider path. A provider prefix is optional; the credential decides the provider and tenant.gateway.service.tsresolves the application throughApplicationsService.resolveByAccessTokenand rejects a disagreeing prefix.plugin.routesmaps the real provider path topayment.create,payment.update,payment.confirm,payment.capture,payment.cancel,payment.getorpayment.refund.ScenariosService.resolvechooses the application's scenario or theX-Emulator-Scenariooverride. Trace events record this decision after redaction.plugin.validatechecks the provider contract. Nothing payment-related has been written yet.GatewayServiceapplies scenario latency, timeout or HTTP error.plugin.toCanonicalcreates anIntentCommand;PaymentsService.executechecks the provider's idempotency rule and enters the single write path.IdempotencyService.runtakes a transaction-scoped PostgreSQL advisory lock, replays the stored outcome when appropriate, or runs the write transaction.- Payment state, transitions, ledger postings, provider resource and owed webhook row are committed together.
plugin.toWirerenders the provider response. Core refusals are translated byplugin.errors.shape()at the gateway boundary.
To modify this flow, first decide whether the rule is provider-specific
(apps/api/src/providers/<id>/) or universal (core/, gateway, payments, ledger or
webhooks). A provider-specific if in a core service is an architecture defect.
#2. Buyer completes a checkout
The checkout link is a capability. Authentication is optional: it adds wallet payment, while card/transfer/ticket remain available anonymously.
Diagram source
sequenceDiagram
participant B as Buyer browser
participant N as Checkout Nitro
participant C as CheckoutService
participant P as PaymentsService
participant DB as PostgreSQL
B->>N: GET provider checkout URL
N->>C: GET /checkout/:provider/:resourceId
C->>DB: Load resource, intent, optional customer session
C-->>B: Amount, methods, own balance only
B->>N: Confirm selected method
N->>C: POST /checkout/.../confirm
C->>C: Require customer only for wallet method
C->>P: completeFromCheckout()
P->>DB: Lock, transition, accounting, outbox
C-->>B: Updated checkout view
Relevant files:
- Browser pages:
apps/checkout/app/pages/mercadopago/checkout/[resourceId].vueandapps/checkout/app/pages/stripe/c/pay/[resourceId].vue. - Provider kits:
apps/checkout/providers/<id>/Checkout.vueandtheme.css. - Nitro proxy:
apps/checkout/server/api/checkout/[provider]/[resourceId]/. - API surface:
apps/api/src/modules/checkout/checkout.controller.tsandcheckout.service.ts. - Shared write:
apps/api/src/modules/payments/payments.service.ts.
For a wallet method, CheckoutService resolves a valid customer session, takes
the walletKey from that identity and checks the person's own balance. A
customerId from the request body is never trusted. For other methods,
fundingSource is external and the ledger uses system cash rather than a
customer wallet.
PaymentsService.completeFromCheckout takes an advisory lock and refreshes the
intent inside it. That refresh is essential: without it, concurrent clicks can
read stale identity-map state and charge more than once.
#3. Webhook delivery
Diagram source
sequenceDiagram
participant PS as PaymentsService
participant DB as PostgreSQL
participant OD as OutboxDispatcher
participant Q as Transport (Postgres, Mongo or Redis)
participant W as Worker handler
participant I as Integration webhook
PS->>DB: Commit payment + webhook_event
OD->>DB: Claim due rows (SKIP LOCKED)
OD->>Q: Hand over the delivery
Q->>W: Dispatch with retries
W->>DB: Read event and application
W->>W: Plugin signs payload
W->>I: POST allow-listed callback
W->>DB: Record attempt and final status
WebhooksService.schedulecreatesWebhookEventEntitywith the payment's transaction manager. If either write fails, neither commits.OutboxDispatcher.sweepclaims due or stale rows withFOR UPDATE SKIP LOCKED, stampsqueued_at, then hands them to the transport. Under thepostgresdriver it does not run at all: that driver reads the same rows, and two claimers on one table is a race.- A failed
enqueueloses no notification; the stale sweep can claim the row again. - The handler in
apps/api/src/worker.tschecksRUNTIME_OUTBOUND_ENABLED, URL protocol, and that the host resolves to a public address (WEBHOOK_ALLOWED_HOSTSbeing the exceptions). - The provider plugin supplies topic, payload and signature. The worker supplies transport and retry policy.
WebhookAttemptEntityrecords HTTP status, duration or error. Scenarios may delay, duplicate or deliberately mis-sign an event.
The delivery model is at-least-once. Integrations must be idempotent; duplicate delivery is both possible operationally and intentionally testable.
#Control-plane requests
Control-plane controllers declare RolesGuard, and application-scoped ones also
declare ApplicationScopeGuard. Console browser requests go through
apps/console/server/api/; apps/console/server/utils/api.ts forwards the
signed-in person's token from a sealed cookie. Sorting, filtering and search are
validated by the endpoint's allow-list and always return { data, meta }.
PayPal approval ends in approved, without a debit or hold. Merchant authorization
or capture is a separate request; see PayPal fidelity.