Sandbox & go-live

How to test every part of the integration without moving money, what to check before your first real payment, and what to do when something does not behave.

The two environments

Sandbox is a complete second YallaPay, not a flag on the live one. It has its own signup, its own database, its own keys and its own checkout, backed by the payment provider's test system. The two never share data, which is what makes "is this real money?" answerable by looking at a key prefix.

Environments
LiveSandbox
Hosthttps://yallapay.nethttps://sandbox.yallapay.net
Dashboardhttps://yallapay.net/loginhttps://sandbox.yallapay.net/login
Keyssk_live_, pk_live_sk_test_, pk_test_
Sessionscs_live_…cs_test_…
MoneyRealNone. Test cards only.
ApprovalFull KYCOn request to support
Keys and hosts must match.

A test key sent to the live host, or a live key sent to the sandbox host, is refused with a message naming the correct host. The same is true for sessions: a cs_test_ id means nothing to the live API.

Pointing the SDK at the sandbox

Each SDK takes an environment at configure time. Use the sandbox environment with a pk_test_ key in debug builds, and the live environment with a pk_live_ key in release builds. Tie the choice to your build type so a release can never ship pointed at the sandbox.

YallaPay.configure(
    publishableKey = if (BuildConfig.DEBUG) "pk_test_…" else "pk_live_…",
    returnUrl      = "myapp://yallapay/return",
    environment    = if (BuildConfig.DEBUG) Environment.SANDBOX else Environment.LIVE,
)
#if DEBUG
YallaPay.configure(publishableKey: "pk_test_…", returnURL: "myapp://yallapay/return", environment: .sandbox)
#else
YallaPay.configure(publishableKey: "pk_live_…", returnURL: "myapp://yallapay/return", environment: .live)
#endif
await YallaPay.configure(
  publishableKey: kReleaseMode ? 'pk_live_…' : 'pk_test_…',
  returnUrl: 'myapp://yallapay/return',
  environment: kReleaseMode ? YallaPayEnvironment.live : YallaPayEnvironment.sandbox,
);

Your server needs the same switch: the sandbox secret key and the sandbox host in your staging configuration, the live pair in production.

Test cards

On the sandbox checkout, use one of these. Where an expiry or CVV is listed, enter it exactly; a card outside this set, or one of these with a different expiry, is declined by the test system with a message that does not explain why.

Sandbox cards
BrandNumberExpiryCVVOutcome
Visa 4508 7500 1574 1019 any future date any 3 digits Succeeds
MasterCard 5123 4500 0000 0008 01/39 100 Succeeds
MasterCard 2223 0000 0000 0007 01/39 100 Succeeds
AMEX 3456 789012 34564 04/37 1000 Declines

The name on the card can be anything with two words, such as "test test". 3-D Secure on the sandbox shows a test page with an authentication result selector; leave it on the success option and submit. The same list is shown on your sandbox dashboard under Apps & Return URLs, and it can change when the test system's card set changes.

Apple Pay and Google Pay cannot be exercised on the sandbox from a simulator or emulator. Test them on a real device against the live environment with a small real payment that you refund afterwards.

End-to-end test checklist

Work through these on the sandbox, in order. Each one exercises a piece that can fail silently in production.

  1. Keys and host agree Create a session with your sandbox secret key against the sandbox host. Expect 201 and a cs_test_ id. Then send the same request to the live host and expect 401 with a message naming the sandbox: that proves the two are really separate.
  2. The publishable key is really read-only Create a session with the publishable key and expect 401 insufficient_permissions. Read the session with the publishable key and confirm the response carries no net_amount, purpose or external_id.
  3. Return targets are enforced Create a session with a return URL whose scheme you have not registered. Expect 422 with param: "return_url". Register the scheme and try again.
  4. A complete payment through the app Run your app in a debug build, start a payment, pay with a succeeding test card, complete 3-D Secure. The SDK must return Completed with a transaction id, and the session must read back as paid.
  5. The webhook arrived and verified Check your server logs for the webhook, confirm the signature check passed, and confirm the order was marked paid exactly once. Then deliver the same payload again by hand and confirm it was ignored.
  6. Cancel Start a payment and close the sheet. The SDK must return Canceled, nothing should be charged, and your app should let the customer try again.
  7. Decline Pay with the declining test card. The checkout shows the decline and lets the customer try another card; if they close the sheet instead, the SDK returns Failed with paymentFailed and your app shows a useful message.
  8. Interruption Start a payment, and while the sheet is open, kill the app from the task switcher or with adb shell am force-stop. Complete the payment in the sheet, then relaunch the app. Your launch-time check must report the completed payment. Skipping this test is how integrations lose payments.
  9. Expiry Create a session with expires_in: 300, wait six minutes, then try to pay it. The SDK must return Failed with sessionExpired, and your app should create a fresh session.
  10. Reconciliation Disable your webhook endpoint in the dashboard, make a payment, and confirm your reconciliation job marks the order paid within its interval. Re-enable the endpoint.

Go-live checklist

  • Live account status is Approved.
  • Release builds use the live environment and a pk_live_ key; the switch is tied to the build type, not to a value someone has to remember to change.
  • Production server configuration holds the live secret key and the live host.
  • Return targets for every production app and site are registered on the live account, with the same schemes the release builds declare.
  • The live webhook endpoint is saved, active, reachable over https, and has passed a Send test.
  • The webhook handler verifies signatures, is idempotent, checks amounts, and responds within a few seconds.
  • The reconciliation job runs in production.
  • Interrupted-payment recovery runs on every app launch.
  • The secret key is in a secrets manager and is not in the repository, the app, or a chat history.
  • One real payment of a small amount has been made from a release build and refunded from the dashboard.

Troubleshooting

Symptoms and causes
You seeIt means
merchant_not_approved when creating a session The account is not approved yet, or is approved by YallaPay and awaiting activation by the payment provider. The message says which. Nothing to change in code; on the sandbox, ask support to approve the account.
validation_error on return_url The scheme or host is not registered, is disabled, or the URL is http. Register it under Apps & Return URLs and use https for websites.
401 with a message about the sandbox or live host The key and the host belong to different environments. Check both the key prefix and the base URL in the environment that made the call.
The customer paid, but the SDK returned Canceled The return scheme differs between what the app declares, what is registered, and what the session was created with. All three must be identical.
The customer paid, and the SDK returned Failed with a network error The device could not reach YallaPay after checkout closed. The payment may have succeeded. Call sessionStatus again, and rely on the webhook. Never show "payment failed" on a network error.
The sheet opens but never returns to the app On Android, the manifest placeholder is missing or differs from the return URL. On iOS, the return URL was given with a reserved or malformed scheme; configure() reports this at launch. On Flutter, one of the two platform steps was skipped.
The webhook signature never verifies Almost always a re-encoded body. Read the raw bytes before JSON parsing. Also check that the server clock is within five minutes of real time, and that you are using the full secret key of the same environment the payment was made in.
The webhook arrives twice Expected occasionally. Your handler must be idempotent on transaction id.
No webhook arrives Check the endpoint is active under Webhooks and the delivery log there for your server's response. If a store id was passed, the endpoint with that id must be active and approved. Reconciliation covers the gap meanwhile.
The test card is declined Use a card from the table above with its exact expiry and CVV. Any other card, including real ones, is declined on the sandbox.
Apple Pay or Google Pay buttons are missing They appear only when the device supports the wallet and has a card enrolled. Simulators and emulators never show them.

Still stuck? Email [email protected] with the session id, the environment, and the SDK version. Never include your secret key.