Testing
Everything on this page uses the same SDK artifact you will ship. No special build, no test-only flag in your app, nothing to remove before release.
In-App Shopping is harder to test than a normal payments integration for one structural reason: the last step happens on a real merchant's checkout page, against that merchant's own payment service provider. This page is about what that means in practice, so you can plan your testing rather than discover the limits during it.
Sandbox mints sandbox virtual cards. They are real cards as far as the integration is concerned, but they are not funded on the card rails, so a real merchant's PSP declines them.
A Sandbox run therefore exercises every step up to and including the card being filled and submitted, and then reads a decline from the merchant's page. That is the expected result, not a misconfiguration.
Because nothing is ever authorized, no transaction lifecycle event is produced in Sandbox. Notification handling is tested separately, below.
To report an outcome without waiting for a transaction, use simulation.
What you verify yourself
All of this runs on your own schedule with Sandbox credentials and environment = OfEnvironment.Sandbox:
| Verify | How |
|---|---|
| Session creation | Create a session from your backend; confirm startSession returns and onError does not fire. |
| Session expiry | Reuse an exchange token, or let a session outlive its 3-hour credential, and confirm onSessionExpired and onError are handled. |
| Checkout detection | Browse to the merchant's checkout; confirm onCheckoutReady fires only once the page is genuinely payable, and onCheckoutExited on navigating away. |
| Order extraction | Call readOrder; confirm onOrderReady returns the items and total you see on screen. |
| Your approval UI | The whole of it. This is your code and has no Open Fabric dependency. |
| Transaction creation | Confirm your backend receives card_fetch_token and payment_attempt_id, and that idempotency behaves as you expect on a retry. This is a real transaction and a real virtual card, minted exactly as in Production. |
| The card handover and automation | Confirm the card is fetched, filled and submitted with nothing visible to the customer. |
onPaymentStatus handling | Confirm your app reacts correctly to the decline the merchant returns. |
onPaymentBlocked | Reach checkout without selecting a required delivery option, then trigger payment. |
onFailure | Hard to provoke deliberately; cover it in your own tests instead. |
The only thing Sandbox cannot give you is a merchant accepting the card.
What a passing Sandbox run looks like
The automation reaches the merchant's submit step, and onPaymentStatus then reports FAILED (the merchant declined the sandbox card), or UNCONFIRMED if the merchant's decline page is not one the configuration recognises.
Both are a pass. What Sandbox proves is that every step before the acceptance decision worked and that your app handled the outcome without dead-ending the customer. A SUCCEEDED in Sandbox would mean something was wrong.
Simulating the payment outcome
Outside Production a virtual card is not funded, so no authorization happens and no transaction outcome is ever produced. Without an outcome, pay waits for its deadline and then reports UNCONFIRMED. SdkConfig.simulation reports an outcome you choose instead.
OfInAppShoppingSdk.init(
application = this,
config = SdkConfig(
environment = OfEnvironment.Sandbox,
simulation = Simulation(paymentStatus = PaymentStatus.SUCCEEDED),
tenantName = "YourBrand",
),
)
Simulation.card decides whether a payment actually runs. This is the choice to make:
card not set | card set | |
|---|---|---|
| What runs | Nothing. No card is fetched, no automation runs, nothing is typed into the merchant's page. | The full automation. The card is filled in place of the virtual card and the merchant's form is submitted. |
| Effect on the merchant | None. The merchant's site is not touched. | The card is entered and submitted on the merchant's site. What follows is decided by the merchant. |
| What is substituted | The whole payment, and the outcome. | The transaction outcome only. |
| Use it to | Build and regression-test your payment screens. | Confirm the automation drives the merchant's checkout correctly. |
What happens on the merchant's side depends on the card you supply and on the merchant's own checkout:
- A card their payment provider rejects produces a decline on their page.
- A card their payment provider accepts produces a charge against that card.
- Whether an order exists, and in what state, is the merchant's behaviour. Some merchants create the order before payment is taken, so an order can exist even after a decline.
Open Fabric does not issue or process the card you supply here and has no record of what the merchant did with it. Check the result on the merchant's side, and plan how to cancel or return anything that completes.
Supplying a card:
simulation = Simulation(
card = CardDetails(
cardNumber = "4111111111111111",
cardName = "YOUR NAME",
expiryMonth = "12",
expiryYear = "30",
cvv = "123",
),
paymentStatus = PaymentStatus.SUCCEEDED,
)
Simulation property | Description |
|---|---|
paymentStatus | The status to report. Defaults to SUCCEEDED. |
thenPaymentStatus | The terminal status reported after a non-terminal paymentStatus. |
detail | Text passed to your listener alongside the status. Defaults to "simulated". |
delayMs | How long to wait before reporting. Defaults to 1000. |
blockedReason | Reports onPaymentBlocked(reason) instead of a status, before any automation runs. |
card | The card to fill on the merchant's checkout. See the table above. |
REQUIRES_ACTION is not terminal, so the SDK reports it, waits delayMs again, then reports thenPaymentStatus.
Everything before the payment runs for real in both cases: the session, checkout detection and order extraction. The merchant's own page verdict is still read when the merchant has one configured, and it is used if it arrives first.
With a card set, paymentStatus is reported whatever the merchant's payment provider did. A configured SUCCEEDED says nothing about whether the card was accepted. Read the merchant's page, or the merchant's own order history, to find that out.
A card set here is compiled into your build and is recoverable from it. It is not secure storage.
When environment is OfEnvironment.Prod the SDK discards the simulation at init, card included. A live integration cannot report an outcome that did not happen.
What the debug flag changes
Three things, all of which is why it must never reach production:
| Effect | Consequence |
|---|---|
| Card fields are no longer hidden during automation | The card number being typed is visible on the merchant page. This applies to a virtual card too. |
| Page captures and automation step events are logged | Useful detail in logcat while integrating. |
| Merchant page console messages are forwarded to logcat | Lets you see the merchant's own page errors. |
debug does not affect simulation. The two are independent.
The SDK does not prevent a release build from setting debug = true. Keeping it false outside testing is yours to enforce.
What Open Fabric sets up on request
Two things are ours rather than yours. Ask your Open Fabric contact for both when you start testing:
| Request | Why you need it |
|---|---|
| Merchant configuration for the merchants you want to test against | Detection, extraction and payment flows are merchant-specific and maintained by Open Fabric. A merchant that has not been onboarded will not detect checkout at all. |
| Simulated transaction lifecycle events on your Sandbox account | Since no Sandbox payment is ever authorized, this is the only way to exercise your notification handling end to end. Open Fabric triggers charge and reversal events against a Sandbox transaction so your backend can consume them for real. |
Testing your status handling
A merchant will not produce every status on demand. Use simulation to report each one, and cover these four cases:
- a non-terminal
REQUIRES_ACTIONfollowed by a terminal status, where your surface must survive the first callback, UNCONFIRMED, which must not render as success or as failure,FAILED, which must leave the customer able to pay with the merchant's own methods,SUCCEEDEDarriving before the settlement event, where your ledger must not move until the event lands.
See Payment outcome for what each status should do.
Going live
A first Production run is the only way to see a payment complete, so treat it as a planned test rather than a launch:
- Confirm
SdkConfig.environmentisOfEnvironment.Prodand you are using Production credentials. See SDK setup. - Confirm your backend settles from transaction lifecycle events, not from
onPaymentStatus. - Agree the live test order with your Open Fabric contact, including which merchant and how the order will be cancelled or returned. It is a real purchase with a real charge.
- Run it, and check that the on-device status and the settlement event agree.
See also Testing and go-live for the platform-wide checklist.
Next: Troubleshooting.