Run the payment
With the transaction approved, hand the credentials to the SDK. It fetches the virtual card, fills the merchant's payment form, and submits — all without the card ever being visible.
Hand the credentials to the SDK
session.pay(
paymentAttemptId = paymentAttemptId,
cardFetchToken = cardFetchToken,
callback = object : PaymentListener {
override fun onPaymentStatus(status: PaymentStatus, detail: String?) {
if (!status.isTerminal) return // e.g. a 3DS challenge is on screen, keep waiting
// Terminal: update your payment surface for this status
}
override fun onPaymentBlocked(message: String) {
// The merchant page needs a user action first (e.g. a delivery
// slot isn't selected) — show the message and let the user retry
}
override fun onFailure(error: OpenFabricError) {
// Automation failed — the merchant's own payment form is restored
}
},
)
| Callback | Fired when |
|---|---|
onPaymentStatus(status, detail) | The automation submitted the payment, and the SDK read the merchant's page for a verdict. May fire more than once; see below. |
onPaymentBlocked(message) | A guard check blocked the flow before any payment step ran. No card was fetched and no payment was attempted. Recoverable. |
onFailure(error) | All payment flows failed and nothing was submitted; the merchant's own payment form has been restored. Terminal. |
pay requires a prior successful readOrder in the same session. That call also prepares the payment steps for the current checkout page.
Read the payment status
PaymentStatus is what the merchant's own page showed after the card was submitted.
| Status | Meaning | Terminal |
|---|---|---|
SUCCEEDED | The merchant confirmed the payment. | Yes |
FAILED | Declined or errored at the merchant. | Yes |
PENDING | Accepted but not settled, e.g. bank-transfer instructions were shown. | Yes |
REQUIRES_ACTION | A 3DS or OTP challenge is on screen. The customer can still finish. | No |
UNCONFIRMED | Nothing the merchant's configuration recognises appeared before the timeout. | Yes |
REQUIRES_ACTION is deliberately not terminal. A challenge is a handoff, not an outcome: the SDK keeps watching the page and calls onPaymentStatus again with the terminal status once the customer completes or abandons it. Always check status.isTerminal before tearing down your payment UI, or a customer who is mid-OTP will lose it.
UNCONFIRMED is not success. It is what the SDK reports when it cannot tell, because the merchant has no payment_status_detection configured, or because their page changed and no configured outcome matched. Treat it as "await the settlement event" and never as payment.
onSuccess(flowId) is removed and replaced by onPaymentStatus(status, detail). There is deliberately no "the script finished" callback any more: that the steps ran said nothing about whether the card was accepted, so a decline and a success looked identical to the host, and an integration wired to onSuccess showed success for both.
If you previously treated onSuccess as submission-confirmed, the equivalent is status.isTerminal && status != PaymentStatus.FAILED, but read Payment outcome before building on it, because neither is authoritative.
detail is free text intended for your logs: the reason a status was reached, when the configuration supplies one. Do not parse it or show it to customers.
What happens on-device
- The SDK calls the Open Fabric Issuer service directly with the
card_fetch_token, authenticated by its session token. Card data never passes through the analysis service or your app. - The issuer returns the card number and CVV encrypted with the RSA-2048 public key the SDK generated at session start. Decryption happens on-device with the private key that never left it.
- The SDK executes the merchant's payment steps — selecting card payment, filling the card fields (hidden from the customer), and submitting the form.
- The SDK then watches the page against the merchant's
payment_status_detectionoutcomes until one matches or the timeout elapses, and reports the result. - The result is reported back to Open Fabric for merchant health monitoring, and to your app via the callback.
Handle failures
Precondition blocked: onPaymentBlocked
Some merchants require a step the customer must complete themselves before payment can run, such as selecting a delivery slot. When such a guard blocks the flow, the SDK fires onPaymentBlocked(message) without fetching a card or attempting a payment:
messageis user-facing — surface it as-is (e.g. a toast) so the customer knows what to fix on the merchant page.- Keep your payment surface open. Once the customer completes the missing step, call
payagain with the samepaymentAttemptIdandcardFetchToken. - Overriding
onPaymentBlockedis optional; the default is a no-op. It does not fall through toonFailure. A blocked precondition is recoverable, and treating it as terminal would tear down a payment surface the customer can still use.
Automation failed — onFailure
onFailure means the automation could not complete and no payment was submitted, typically because the merchant changed their checkout page. The SDK has already restored the merchant's payment form by the time the callback fires:
- Let the customer complete the purchase with the merchant's own payment methods. Never dead-end the customer.
- What you show is yours to decide. The SDK takes no UI action beyond restoring the merchant's form. A brief non-blocking notice works well; so does returning the customer to your own checkout surface. Choose whichever fits your app.
- Failures are reported to Open Fabric automatically and feed the merchant's health monitoring, which triggers configuration repair on our side. No action is needed from you beyond the graceful fallback.
- If the transaction should not proceed at all, handle the cancellation on your backend against the approved transaction.
The decrypted card exists only transiently in SDK memory during form filling and is cleared afterwards. It is never persisted on the device, never shown to the customer (debug = false), never logged, and never sent to your app or backend. This holds on every path, including onFailure, where the form is restored with no card value left in it.
Where to go from here
- Payment outcome: how
PaymentStatusrelates to the settlement event, and which one to update your ledgers from. Read this before wiringonPaymentStatusto anything that moves money. - Transaction lifecycle events (charge, reversal) reach your backend through your existing notification channels — see Notifications.
- For end-to-end testing, see Testing.