PSP Integration
This guide describes the recommended pattern for building a Payment Service Provider (PSP) app that integrates with the Commerce app.
Overview
PSP apps handle payment processing for Commerce carts. The core responsibility is:
- Accept a payment from the customer (via your PSP's API)
- Create a Hantera Payment actor with an authorization
- Link the payment to the cart and complete it
Each PSP has its own flow (redirect, iframe, client-side SDK, etc.), but the cart completion step is the same.
The Payment Flow
Exposing a Checkout Ingress
PSP apps typically expose a checkout ingress at:
POST /ingress/commerce/carts/{cartId}/payment/{provider}This ingress receives the cart ID and returns whatever the storefront needs to initiate the payment flow (redirect URL, client token, iframe snippet, etc.).
Completing the Cart
When your PSP confirms the payment is authorized, complete the cart with these steps:
Step 1: Create a Payment Actor
let paymentResult = messageActor (
'payment'
'new'
[{
type = 'create'
body = {
providerKey = 'your-provider'
currencyCode = 'SEK'
externalReference = externalPaymentId
commands = [{
type = 'createAuthorization'
authorizationNumber = externalPaymentId
amount = orderAmount
authorizationState = 'successful'
},{
type = 'generatePaymentNumberByPrefix'
prefix = 'YOUR-PREFIX'
}]
}
}]
)Key fields:
providerKey— identifies your PSP (e.g.,'stripe','kustom')externalReference— the ID from your PSP's APIamount— the authorized amount in the cart's currency
Step 2: Link Payment and Complete Cart
let completeResult = messageActor (
'ticket'
cartId
[{
type = 'applyCommands'
body = {
commands = [{
type = 'createRelation'
relationKey = 'payments'
nodeId = paymentResult.actorId
}]
}
},{
type = 'complete'
}]
)The createRelation links the payment to the cart. The complete message transitions the cart to completed state and triggers the cart-to-order rule, which creates the actual order.
Important
Always create the payment and link it before completing the cart. The order creation rules may need the payment data.
Reacting to Cart Changes
If your PSP needs to stay synchronized with the cart (e.g., updating order amounts in an external checkout), you can listen to the OnCartMutation custom hook. This hook fires after every successful cart render and provides the full rendered cart data.
See Hooks for details on how to listen and react to cart renders.
Handling Captures
For PSPs that require explicit capture (as opposed to direct charges), implement a rule that listens for OnPaymentCapture:
param input: OnPaymentCapture
from input.payment.providerKey match
'your-provider' |>
from {
effect = 'scheduleJob'
definition = 'apps/your-app/capturePayment'
at = now
parameters = {
externalReference = input.payment.externalReference
amount = input.capture.amount
}
}The capture job then calls your PSP's capture API.
Idempotency
PSP integrations should be idempotent:
- Payment creation: Check for existing payments by
externalReferencebefore creating new ones - Cart completion: If the cart is already completed, the
completemessage is a no-op - Webhooks: PSPs may send the same webhook multiple times
See Also
- Payment Actor — Payment actor reference
- Custom Hooks — How triggerHook works