Payment Flows
How payments move through your integration: the lifecycle, capabilities, reconciliation, and — most importantly — asynchronous (3-D Secure / redirect) flows and the result callback.
Read first: Overview & Concepts
The cross-cutting rules every integration follows — the five outcomes (
SUCCESS/PENDING/DECLINED/RETRIABLE/UNKNOWN), the response contract (HTTP 200 +status; how LoyaltyPlant interprets your HTTP codes), idempotency, and the error-code catalogue (PaymentErrorCode) — live in the Payments API specification and the SDK guide (which includes the full SDK reference inline), not here. This page covers the flows only.
1. The payment lifecycle
Your integration supports one of two shapes, declared via capabilities.
1.1 Two-phase (CAPTURE)
Authorize first (hold), capture later (settle), or reverse (release).
capturereceives thetransactionReferenceyou returned fromauthorize.amountis optional — omit to capture the full amount, or pass a smaller value for a partial capture.reversevoids an authorization that was never captured.refundreturns funds after capture (supports partial amounts).
1.2 Single-phase (SALE)
One call authorizes and captures.
Refund later with POST /v1/refund (if you declare DIRECT_REFUND).
Declare
CAPTUREorSALE(you may declare both). LoyaltyPlant choosesauthorizefor two-phase andsalefor single-phase based on what you declare.
2. Capabilities & dispatch
GET /v1/capabilities returns the features you support. LoyaltyPlant caches the list and
only calls operations whose capability you declared.
| Capability | Unlocks | Notes |
|---|---|---|
CAPTURE | POST /v1/authorize + POST /v1/capture | Two-phase |
SALE | POST /v1/sale | Single-phase |
DIRECT_REFUND | POST /v1/refund | Refund by transactionReference |
REDIRECT_FLOW | PENDING + redirectUrl from authorize/sale | Enables 3DS/redirect (see §4) |
DIRECT_WEBHOOK | The result callback | Your PSP webhooks you; you call LP back |
STATUS_POLLING | POST /v1/inquire-status | Reconciliation (see §3) |
TOKENIZATION | Card-on-file / one-click | No dedicated endpoint — handled inside authorize/sale using settings |
APPLE_PAY / GOOGLE_PAY | Wallet payments | No dedicated endpoint — handled inside authorize/sale |
Capabilities without their own endpoint.
TOKENIZATION,APPLE_PAY,GOOGLE_PAYdon't add new methods. You "support" them by handling the relevant data inside yourauthorize/saleimplementation (the payment instrument and any tokens arrive via the request and the provisionedsettings). Declaring them tells LoyaltyPlant you can accept those payment types.
reverse has no gating capability — implement it whenever you support CAPTURE.
3. Reconciliation: status inquiry
When an outcome is ambiguous — a timeout, an UNKNOWN, or a PENDING whose callback never
arrived — LoyaltyPlant calls POST /v1/inquire-status (if you declare STATUS_POLLING) to
ask your PSP for the truth.
Return the PSP's current status:
{ "status": "CAPTURED", "transactionReference": "psp_txn_7HagaIn2", "rawStatus": "captured" }
status is one of AUTHORIZED, CAPTURED, VOIDED, REFUNDED, DECLINED, EXPIRED,
PENDING, NOT_FOUND. This is read-only and carries no Idempotency-Key. Implementing
status polling makes your integration far more robust against lost callbacks and network
blips — it is strongly recommended for any integration that uses async flows.
4. Asynchronous flows (3-D Secure / redirect) — the keystone
Some payments can't finish in the single HTTP call from LoyaltyPlant: the customer must complete 3-D Secure or be redirected to a bank/hosted page. This is the part integrations most often get wrong, so read it carefully.
4.1 The shape of an async payment
-
LoyaltyPlant calls
POST /v1/authorize(or/v1/sale). -
Your PSP says "authentication required" and gives you a redirect URL.
-
You return
200 PENDINGwith thatredirectUrland an optionalexpectedTtlSeconds:{
"status": "PENDING",
"transactionReference": "psp_txn_7HagaIn2",
"redirectUrl": "https://acs.issuer-bank.example/3ds/challenge?token=…",
"expectedTtlSeconds": 300
}transactionReferenceis mandatory here — LoyaltyPlant stores it and later matches your callback to this suspended payment by this value.expectedTtlSecondstells LoyaltyPlant how long to wait before treating the payment as timed out (default 15 minutes if omitted).
-
LoyaltyPlant redirects the customer to
redirectUrl. They authenticate. -
Your PSP notifies your service (its own webhook to an endpoint you own — LoyaltyPlant is not involved here).
-
You verify that notification and call LoyaltyPlant back to report the final result (§4.2).
-
LoyaltyPlant resumes the suspended payment and finishes the flow.
To use this flow, declare REDIRECT_FLOW (and DIRECT_WEBHOOK if your PSP webhooks you).
4.2 The result callback
After the payment resolves at your PSP, you report it to LoyaltyPlant with a single call. This callback is not in the set of endpoints you implement — it's an endpoint LoyaltyPlant exposes that you call.
POST https://<payments-service-host>/gate/integration/{integrationId}/result
Authorization: Bearer <inboundToken>
Idempotency-Key: 7c9e6679-7425-40de-944b-e07fc1f90ae7
Content-Type: application/json
{ "status": "SUCCESS", "transactionReference": "psp_txn_7HagaIn2" }
| Item | Value |
|---|---|
| URL | POST {payments-service-host}/gate/integration/{integrationId}/result — the host and your integrationId come from onboarding |
| Auth | Authorization: Bearer <inboundToken> — your inbound token (the one LP gave you for calling back). This is not the outbound token you validate on incoming requests. See General Requirements §4. |
Idempotency-Key | A UUID. Retries are safe (see below). |
| Body | An IntegrationOperationResponse — usually SUCCESS or DECLINED. |
transactionReference | Must match the value you returned in the PENDING response — that's how LoyaltyPlant finds the suspended payment. |
Responses you may get back:
| Code | Meaning |
|---|---|
200 OK | Accepted; the suspended payment was resumed (or had already resumed — a safe replay). |
400 | Invalid payload (e.g. bad JSON, missing transactionReference). |
401 | Invalid inbound token. |
404 | Unknown or inactive integrationId. |
Retries & idempotency. Send an Idempotency-Key and retry the callback until you get a
200. Replays are safe: LoyaltyPlant matches the payment by transactionReference, so a
callback that arrives after the payment already resumed is a no-op. If you never send the
callback, the payment eventually times out (per expectedTtlSeconds) and, if you support
STATUS_POLLING, LoyaltyPlant may reconcile by asking your PSP directly.
4.3 Hosted payment pages
Because your service is publicly reachable, the redirectUrl you return can point at a page
you host — your own hosted payment page, a PSP drop-in/redirect, etc.
LoyaltyPlant simply sends the customer there; you own the page and the PSP acknowledgement.
4.4 Common async mistakes
- ❌ Returning
PENDINGwithout atransactionReference→ LoyaltyPlant can't match your later callback, and the payment hangs until it times out. - ❌ Sending the callback with the outbound token instead of the inbound token →
401. - ❌ A
transactionReferencein the callback that differs from the one in thePENDINGresponse → LoyaltyPlant can't find the payment (logged, no resume). - ❌ Not verifying the PSP webhook signature before trusting it → you may report a forged result. Always verify on your side.
- ❌ Treating a
200 OKfrom the callback as "charge the customer again" on retry — it's idempotent; just stop retrying. - ❌ Declaring
REDIRECT_FLOWbut never implementing the callback → every 3DS payment stalls.
Next: choose your path — SDK Integration Guide → (Java) or API Integration Guide → (any language).