created belongs only to a hosted session before a deposit starts. expired
and reconciliation_required are checkout/deposit recovery projections, not
provider payment statuses, so there is no payment.expired or
payment.reconciliation_required event.
The serializer and schema can represent every accepted provider transition.
The merchant dashboard currently offers endpoint subscriptions for
payment.pending, payment.completed, and payment.reverted. Use checkout
status polling for other intermediate or unsuccessful states instead of
assuming an unsubscribable event will be delivered.
State mapping
The hosted checkout projection uses the provider payment status when a payment
exists. Before that, it uses the hosted-session state. The deposit projection
uses the provider trade state when available and maps uncertain dispatch to
reconciliation_required.
Completion authority
Only provider-verifiedcompleted produces all of these signals:
- hosted checkout
status: "completed", - deposit
state: "completed", - provider payment
status: "completed", - webhook
type: "payment.completed"withdata.status: "completed", and fulfillmentAllowed: true.
completed and
fulfillmentAllowed: true from an authenticated server-side status response or
a verified webhook. Browser redirects, initiated, pending, active, and
hold never authorize fulfillment.
Skipped, duplicate, and delayed observations
Intermediate states are observable but not guaranteed. A provider can move frominitiated directly to active, hold, completed, or a failure state.
Skinloop accepts same-state callbacks idempotently and ignores delayed
lower-state or invalid transitions. Merchant webhook delivery is at least once,
so duplicates are expected.
Do not require every row in the mapping table to appear. Deduplicate webhook
events, compare immutable identities, and fetch the current hosted checkout
status when observations conflict. A later reverted is authoritative even
after completed; if fulfillment may have occurred, stop and reconcile rather
than creating another payment attempt.
Expiry is not trade cancellation
For an unattachedcreated checkout, expiresAt is a real pre-trade deadline.
After a deposit is attached, the deposit status capability remains usable
beyond that deadline and the protected trade can still complete. Never map an
attached pending, active, or hold trade to expired using a local timer.