Payment architecture
The checkout service orchestrates payment processing by coordinating between payment provider services (Stripe, Adyen, PayNL, etc.) and commercetools. Each payment provider is a standalone service that handles communication with its PSP. The provider only updates the Payment object and its transactions. Order-level actions (confirming an order, handling cancellations) are handled by the checkout service through event subscriptions.
Sequence diagram
Creating a payment
When a payment is needed, the frontend calls the createPayment
mutation. The checkout service then:
- Fetches the order from commercetools
- Creates a Payment object on the order with the selected method and payment interface
- Delegates to the appropriate PSP service to start a transaction
- Returns a redirect URL so the customer can complete payment
The Payment object stores the selected method in
paymentMethodInfo.method and the provider identifier in
paymentMethodInfo.paymentInterface.
Payment states
The checkout service manages payment state transitions through commercetools state machines. Payment providers transition the Payment to one of these states:
| State | Key | Environment variable override |
|---|---|---|
| Initial | PaymentInitial | PAYMENT_STATE_KEY_INITIAL |
| Pending | PaymentPending | PAYMENT_STATE_KEY_PENDING |
| Success | PaymentSuccess | PAYMENT_STATE_KEY_SUCCESS |
| Cancelled | PaymentCancelled | PAYMENT_STATE_KEY_CANCELLED |
| Failure | PaymentFailure | PAYMENT_STATE_KEY_FAILURE |
These state objects are defined in the checkout service's Terraform configuration and looked up by key at runtime. You can override the default keys through environment variables if your project uses different naming.
Event-driven order updates
When a payment state changes, commercetools emits a
PaymentStatusStateTransition message. The checkout service
subscribes to these messages and updates the order accordingly. For
example, when all payments on an order reach PaymentSuccess, the
checkout service transitions the order's payment state to paid.
This event-driven approach means the checkout service reacts to payment outcomes asynchronously rather than polling for status changes. See Messaging & events for how event subscriptions work across all services.
REST endpoints
A payment provider exposes the following API endpoints:
POST /payment-methods: return the payment methods available for the given cart. The cart ID and other context are passed in the request body.
POST /create: create a payment transaction. This starts the payment flow and returns a redirect URL for the customer to complete payment at the PSP.
POST /push (webhook): receive asynchronous payment status updates from the PSP. This endpoint handles webhook callbacks and updates the transaction status in commercetools. See REST endpoints & webhooks for how each provider verifies webhook authenticity.
A payment provider may also expose:
GET /redirect: handle customer redirects back from the PSP after completing payment.
POST /validate-apple-pay-session: validate an Apple Pay merchant session for Apple Pay payments.
API contract
The payment API is defined in an OpenAPI spec at
backend/packages/payment-api/payment-api.yaml. The two primary
endpoints and their types are documented below.
POST /create
Starts a payment transaction and returns a redirect URL for the customer to complete payment at the PSP.
Request body: Transaction
| Field | Type | Required | Description |
|---|---|---|---|
amount | Price | yes | Transaction amount |
customer | object | no | { ipAddress: string } - IPv4 or IPv6 |
orderNumber | string | yes | Human-readable order number |
orderId | string (UUID) | yes | commercetools order ID |
paymentId | string (UUID) | yes | commercetools payment ID |
paymentMethod | string | yes | Payment method code |
paymentMethodArgs | object | no | Provider-specific arguments |
successUrl | string (URL) | yes | Redirect after successful payment |
failureUrl | string (URL) | yes | Redirect after failed payment |
Response body: TransactionResponse
| Field | Type | Description |
|---|---|---|
status | string | Transaction status |
payload.id | string (UUID) | Transaction identifier |
payload.redirectURL | string (URL) | Customer redirect URL |
POST /payment-methods
Returns the payment methods available for the given context (cart, locale, country).
Request body: PaymentMethodArgs
| Field | Type | Required | Description |
|---|---|---|---|
cartId | string (UUID) | no | Cart to check methods for |
orderId | string (UUID) | no | Order to check methods for |
currency | string | yes | e.g. EUR, USD |
country | string | yes | e.g. NL, DE |
locale | string | yes | e.g. en-GB, nl-NL |
device | string | no | web, ios, or android |
Response body: PaymentMethod[]
| Field | Type | Description |
|---|---|---|
id | string | Method identifier |
localizedName | string | Display name for the requested locale |
provider | string | PSP provider key |
options | array | Sub-options, each with id and name |
Shared types
Price
| Field | Type | Description |
|---|---|---|
currency | string | ISO 4217 currency code |
centAmount | number (integer format) | Amount in the smallest currency unit |