React Native
The @wajub/react-native package, a ready-made payment sheet for your React Native app.
The React Native SDK lets you take Mobile Money, card and wallet payments in your app, with a
ready-made payment sheet. present() opens it and resolves once, when it closes.
@wajub/react-native
Stable · GAnpm
- Version
- 1.1.1
- Runtime
- React 18+, React Native 0.74+
- Frameworks
- Android and iOS
Covers
- Payments
- Mobile Money
- Cards
Install
npm install @wajub/react-native @react-native-community/netinfo @react-native-async-storage/async-storagenpm install @stripe/stripe-react-nativecd ios && pod install| Peer | Range | Needed |
|---|---|---|
react | >=18 | Always |
react-native | >=0.74 | Always |
@react-native-community/netinfo | >=11.0.0 | Always |
@react-native-async-storage/async-storage | >=1.17.0 | Always |
@stripe/stripe-react-native | >=0.38.0 | Optional, for Stripe cards. Without it the sheet does not offer them, see Cards |
@stripe/stripe-react-native sets its own minimum iOS and Android versions, which become yours
when you install it. No provider is needed at the root of your app: usePayment() works in any
component.
Quick start
- Your server creates the payment with its secret key and hands the app
the
authorization_token. - The app calls
present()with that token. Render thePaymentSheetelement the hook returns in the same component. - Your server confirms the payment, with the
payment.succeededwebhook orGET /payments/{id}, before it delivers. The result in the app is what the payer saw, not proof of payment.
import { Button } from 'react-native';
import { usePayment } from '@wajub/react-native';
function CheckoutScreen({ authorizationToken }: { authorizationToken: string }) {
const { present, PaymentSheet } = usePayment();
const pay = async () => {
const result = await present({
sessionToken: authorizationToken,
returnUrl: 'https://shop.example.com/app/payment-return', // optional, a universal/app link
});
switch (result.status) {
case 'complete':
// Paid on the device. Deliver once your server has confirmed it.
return showReceipt(result.payment.id);
case 'processing':
// Closed while a payment was running: an approval may still land.
return showPending();
case 'failed':
return showError(result.error.message);
case 'cancelled':
// result.reason: 'payer_cancelled', 'dismissed' or 'host_gone'
return;
}
};
return (
<>
<Button title="Pay" onPress={pay} />
{PaymentSheet}
</>
);
}present() resolves exactly once, with the value onComplete receives. If the component that
called the hook unmounts while the sheet is open, it still resolves: cancelled with host_gone
before any attempt, processing once one started, complete after a success. Calling it while a
sheet is open rejects with a WajubError whose code is sheet_already_presented, and leaves the
open sheet alone.
The PaymentSheet from usePayment is an element, render it
usePayment() returns { present, PaymentSheet }, where PaymentSheet is an element or null.
Put {PaymentSheet} in your JSX: <PaymentSheet /> throws, because it is not a component.
Options
Everything goes in the one object present() takes, the callbacks of the next section included.
| Option | Type | Default | What it does |
|---|---|---|---|
sessionToken | string | Required | The payment's authorization_token |
locale | string | The payment's, else the device's | 'fr' or 'en'. Anything else reads English |
appearance | Appearance | None | The sheet's look, see Appearance |
loader | 'skeleton' or 'spinner' | 'skeleton' | What shows while the payment loads |
readyTimeout | number, ms | 15000 | Past it, a sheet still loading shows an error with Retry and onLoadError gets ready_timeout. 0 disables it |
returnUrl | string | None | An https universal or app link the provider's page sends the payer back to. Without it, the payer comes back to the app by hand |
mobileMoney.country | string, ISO-2 | The payer's country when offered, else the first one | The country the form starts on |
mobileMoney.countryLocked | boolean | false | Hides the country selector, when country is offered |
mobileMoney.operatorMode | 'choose' or 'detect' | 'choose' | 'detect' hides the operator list while the number tells the operator |
cancelOnDismiss | boolean | true | Closing the sheet before any attempt cancels the payment. false keeps it open, so you can present() it again |
autoCloseSuccess | number, ms | 3000 | How long the success screen stays. 0 keeps it until the payer closes it |
stripe.appPublishableKey | string | None | Your app's own Stripe key, if your app also takes its own Stripe payments, see Cards |
Callbacks and states
All optional, all called on the JavaScript thread, in this order for one payment: onReady, then
onStateChange as the payer moves on, onPaymentStarted and onRedirect when they apply,
onSuccess, onError or onCancel, and onComplete last, exactly once. A callback that throws is
logged, and the sheet carries on.
| Callback | Receives | When |
|---|---|---|
onReady | { sessionId, environment, amount, currency, methods } | The first screen is drawn, also for a payment already paid or ended. environment is 'sandbox' or 'live', methods the payment methods shown |
onStateChange | { state, method } | After onReady, then on every state change. method is mobile_money, card, wallet or unknown |
onLoadError | WajubError | The sheet cannot offer a payment: session_not_found, session_terminal_expired, session_terminal_cancelled, session_terminal_failed, load_error, ready_timeout or no_payment_method |
onPaymentStarted | { method, channel } | The payment request is accepted. From now on, closing the sheet resolves processing |
onRedirect | { url, method: 'GET' } | Right before a provider page opens in the system browser |
onSuccess | PaymentSuccess | The payment succeeded. method is unknown when it was paid before the sheet opened |
onError | WajubError | Every failure the payer sees, retryable ones included, field validation excluded |
onCancel | { reason, redirectUrl } | The sheet closes as cancelled |
onComplete | CheckoutResult | The sheet closes: what present() resolves with |
onStateChange reports one of these states: INITIATED (method list), COLLECTING_DETAILS (a
method's form), PROCESSING (Pay pressed), APPROVAL_REQUIRED, USSD_REQUIRED,
PAYER_OTP_REQUIRED (the code the payer generates), OTP_REQUIRED, PIN_REQUIRED,
PHONE_REQUIRED, BIRTHDAY_REQUIRED, ADDRESS_REQUIRED, 3DS_REQUIRED, VERIFYING, PENDING,
SUCCESS, FAILED, EXPIRED and CANCELLED. The screen behind each one
is on Mobile SDKs.
Results
present() resolves with one of four results. Switch on status.
status | When | Carries |
|---|---|---|
complete | The sheet closed after the success screen, also for a payment paid before it opened | payment (PaymentSuccess), transaction |
processing | The sheet closed while a payment was under way. The sheet never shows a failure it is not sure of: money may still move, so confirm on your server | transaction, and instruction when the payer had one |
failed | The sheet closed on a failure, an expired payment, a load error, or a payment cancelled elsewhere (payment_cancelled) | error, transaction or null |
cancelled | Cancel payment, a close before any attempt, or the host unmounting first | reason (payer_cancelled, dismissed or host_gone), redirectUrl |
PaymentSuccess carries id (Wajub's payment id, what GET /payments/{id} takes), reference
(yours), amount (what the payer paid, fee included, in major units), currency, method,
status: 'success', timestamp, and receipt_url when there is a receipt.
A killed app cannot resolve a promise. At the next launch, ask your server for the payment's status.
Cancelling and closing
The payer closes the sheet with ✕ in its header, Android's back button, or Close on a final screen. There is no swipe-down.
| The payer closes the sheet | Result |
|---|---|
| While the payment loads | cancelled, dismissed |
| On the method list or a form, before any attempt or after Try again | cancelled, dismissed. The payment is cancelled, unless cancelOnDismiss is false |
| While a payment is under way: the payer approves, confirms a step or waits | processing. The payment keeps running: confirm it on your server |
| On the failure screen | failed, with the error the payer saw. The payment is cancelled when it still can be, unless cancelOnDismiss is false |
| On the success screen | complete |
| With Cancel payment | cancelled, payer_cancelled. The payment is cancelled for good |
Cancel payment shows under the method list and the forms, never while a payment is under way. The rules are on Mobile SDKs.
Appearance
appearance sets the sheet's look: a preset, a colour scheme and the values you want to change.
await present({
sessionToken,
appearance: {
theme: 'stripe', // a preset: 'stripe', 'night', 'flat', 'none'; or 'light', 'dark', 'auto', 'system'
colorScheme: 'auto', // follows the phone, live
variables: { colorPrimary: '#0F766E', borderRadius: '12px', fontFamily: 'Inter' },
buttonTextColor: '#FFFFFF',
},
});- Colour scheme:
colorSchemeislight,darkorauto, which follows the phone.inherit: truefollows your app's colour scheme instead. - Branding: what you leave unset comes from the merchant's branding in the Dashboard, and a white-label merchant hides "Secured by Wajub".
- Fonts:
fontFamilymust be a font your app ships, else the platform's font is used. - Labels:
labels: 'above', the default, draws each label above its field;'floating'rests it inside the empty field. - Motion:
disableAnimations: true, or the phone's reduce-motion setting, stops the animations.
Every key and preset is on Appearance.
Mobile Money
One form covers every Mobile Money operator of the payment: the payer picks the country and the operator and types the number, formatted as it is typed. The number's prefix preselects its operator, and a number of another offered operator is refused before anything is sent. After Pay, "Approve the payment on your phone" shows the number and the operator's instruction.
mobileMoney.country starts the form on a country, countryLocked hides the selector, and
operatorMode: 'detect' keeps the operator list hidden while the number tells the operator. More
on Mobile SDKs.
Cards
Stripe cards need the optional @stripe/stripe-react-native peer; no other Stripe setup is needed.
If your app also takes its own Stripe payments, pass your app's key in
stripe: { appPublishableKey } so your own Stripe payments keep working.
Other cards open a secure page of the payment provider in the system browser. In sandbox, the sheet shows a test card form.
Confirmation steps
When a payment needs one more step after Pay, the sheet shows it natively: a one-time code, the card's PIN, a phone number, a date of birth or a billing address, with the attempts left after a wrong answer. In sandbox, a card that asks for 3-D Secure gets a code sent by email. The steps and their states are on Mobile SDKs.
Errors
Every failure is a WajubError, a subclass of Error. onLoadError, onError and a failed
result hand one over. Its fields are in snake_case.
| Field | What it is for |
|---|---|
type | api_error, authentication_error, invalid_request_error, payment_error or rate_limit_error |
code | What went wrong, to branch on |
message | For the payer, in the sheet's language |
decline_code | On a decline, its reason |
retryable | Whether another attempt can work |
param | The field at fault, when there is one |
correlation_id | The request id to quote to Wajub support |
retry_after_seconds | The wait on rate_limited |
block_reference | On shield_blocked, the reference to quote to Wajub support |
import { WajubError } from '@wajub/react-native';
try {
const result = await present({
sessionToken,
onError: (error) => log('wajub', error.code, error.correlation_id),
});
if (result.status === 'failed') {
// insufficient_funds, payment_attempt_ended, session_terminal_expired, ready_timeout…
report(result.error.code, result.error.retryable);
}
} catch (error) {
if (error instanceof WajubError && error.code === 'sheet_already_presented') {
// A sheet is already open.
}
}Codes worth branching on: insufficient_funds, payment_declined, payment_attempt_ended
(refused, nothing was charged), payment_failed, payment_cancelled, session_expired,
shield_blocked, merchant_restricted, rate_limited, session_terminal_expired,
unsupported_action, redirect_unavailable and sheet_already_presented. Every code is on
Mobile SDKs.
Your own UI
The sheet's screens are not exported. An app that draws its own payment screens uses the
lower-level WajubSession, which createSession(token) returns and the
package's README documents.
Privacy
To make the payment, the SDK sends Wajub what the payer types. For fraud prevention, it also sends basic device information and a random install identifier, which is not the advertising identifier. What to declare in the App Store privacy labels and in Google Play's data safety form, category by category, is on Mobile SDKs.
Testing
A payment your server creates with a sandbox key puts the whole flow in sandbox, and the sheet shows a "TEST MODE" badge. The test numbers and cards are on Test scenarios.
Related pages
- Mobile SDKsStates, results and error codes of the payment sheet.
- Create a paymentThe server call that produces the token.
- WebhooksThe confirmation that ships the order.
- React Native recipeMobile Money in a React Native app, end to end.
- FlutterThe SDK for Flutter apps.
- AndroidThe SDK for Android apps, in Kotlin and Compose.
- iOSThe SDK for iOS apps, in Swift.