React Native
Le package @wajub/react-native, un hook, une interface de paiement et aucune WebView.
@wajub/react-native encaisse les paiements dans votre application React Native. Les champs Mobile
Money sont natifs, les champs de carte proviennent de @stripe/stripe-react-native et le payeur ne voit jamais de WebView.
@wajub/react-native
Stable · GAnpm
- Version
- 1.1.1
- Runtime
- React 18+, React Native 0.74+
- Frameworks
- Android and iOS
Couvre
- Paiements
- Mobile Money
- Cartes
Installer
npm install @wajub/react-nativeIl fournit des sources TypeScript et déclare pusher-js comme seule dépendance réelle. Vous devez
fournir trois dépendances homologues.
| Dépendance homologue | Plage | Utilité |
|---|---|---|
react | >=18 | Toujours |
react-native | >=0.74 | Toujours |
@stripe/stripe-react-native | >=0.38.0 | Paiements par carte. Déclarée facultative |
Stripe est facultatif, Mobile Money fonctionne donc sans lui
Sans @stripe/stripe-react-native, le package s'installe correctement et accepte les paiements
Mobile Money. Seul l'onglet de carte en a besoin. Il nécessite aussi StripeProvider à la racine
de votre application pour fonctionner.
npm install @stripe/stripe-react-nativeObtenir un jeton depuis votre serveur
Aucun élément de ce package n'appelle /payments ni ne détient de clé secrète. Votre backend crée
le paiement et renvoie authorization_token.
const response = await fetch('https://api.yourshop.com/checkout', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ cartId }),
});
const { authorization_token: token } = await response.json();Afficher l'interface de paiement
usePayment fournit une fonction qui ouvre l'interface et une promesse qui renvoie le résultat.
import { WajubProvider, usePayment } from '@wajub/react-native';
function Checkout({ token }: { token: string }) {
const { present, PaymentSheet } = usePayment();
const pay = async () => {
const result = await present({ sessionToken: token });
if ('cancelled' in result) return;
switch (result.status) {
case 'complete':
navigation.navigate('OrderPaid', { ref: result.transaction.reference });
break;
case 'processing':
setInstruction(result.instruction ?? 'Approve the payment on your phone.');
break;
case 'requires_action':
// The sheet already opened the browser for you.
break;
case 'failed':
Alert.alert('Payment failed', result.error.message);
break;
}
};
return (
<>
<Button title="Pay" onPress={pay} />
{PaymentSheet}
</>
);
}
export default function App() {
return (
<WajubProvider>
<Checkout token={token} />
</WajubProvider>
);
}PaymentSheet de usePayment est un élément, affichez-le
usePayment() renvoie { present, PaymentSheet }, où PaymentSheet est déjà un élément affiché,
ou null. Placez {PaymentSheet} dans votre JSX. <PaymentSheet /> génère une erreur, car il ne
s'agit pas d'un composant.
Le package exporte aussi un PaymentSheet au premier niveau. Celui-ci est un composant qui
accepte visible, session, onDismiss et onResult. Deux éléments différents portent le même
nom. Utilisez celui du hook, sauf si vous pilotez vous-même la fenêtre modale.
WajubProvider est facultatif. Sans lui, usePayment utilise la fabrique par défaut et le hook
fonctionne partout. Ajoutez-le pour remplacer la fabrique de session à un seul endroit dans les tests.
Les quatre résultats
PaymentResult est une union discriminée par status. Un switch affine donc chaque branche.
status | Résultat |
|---|---|
complete | Paiement réglé. transaction contient la référence |
processing | Envoyé à l'opérateur. instruction contient le texte à afficher au payeur |
requires_action | 3DS ou redirection bancaire. action_url a déjà été ouverte |
failed | error contient code, decline_code et retryable |
present peut aussi renvoyer { cancelled: true }. C'est pourquoi la garde précède le switch.
Processing est le résultat normal pour Mobile Money
Un paiement Mobile Money renvoie rarement complete depuis l'interface. Le payeur doit encore
approuver sur son téléphone. Vous recevez donc processing avec une instruction, comme composer
un code USSD. Affichez ce texte, puis surveillez le statut ou attendez votre webhook.
Vos propres écrans à la place de l'interface
createSession renvoie le même objet que l'interface de paiement. Utilisez-le si le paiement doit
s'intégrer à un design existant.
import { createSession } from '@wajub/react-native';
const session = createSession(token);
const data = await session.loadSession();
// data.transaction.amount, data.transaction.currency, data.branding, data.locale
const operators = data.channels.filter(
(c) => c.type.toLowerCase() === 'mobile_money' || c.type.toLowerCase() === 'mobile',
);
const result = await session.payMobileMoney({
channel_slug: operators[0].slug,
phone: '+237670000000',
country: 'CM',
});| Méthode | Fonction |
|---|---|
loadSession(forceRefresh?) | Montant, devise, canaux, image de marque et locale. Mis en cache après le premier appel |
getSdkConfig() | Paramètres du prestataire par canal, dont la clé publique Stripe |
payMobileMoney(input) | Envoie le push à l'opérateur |
payCard(slug, paymentMethodId, name?) | Traite une PaymentMethod Stripe déjà créée |
process(channel, data) | Solution brute pour tous les canaux |
handleRedirectAction(result) | Ouvre une URL 3DS ou bancaire dans le navigateur système |
watchStatus(onUpdate, intervalMs?) | S'abonne et renvoie la fonction de désabonnement |
cancel() | Abandonne la session et renvoie l'URL de redirection |
cardChannelSlug() | Canal de carte de la session chargée, s'il existe |
Les champs d'entrée utilisent le snake_case, contrairement aux méthodes
payMobileMoney accepte { channel_slug, phone, country }. result.error.decline_code et
result.action_url conservent également le format d'échange. Seules les méthodes utilisent le
camelCase. Ce choix est volontaire, les payloads reprennent les structures de l'API.
Suivre le résultat
useEffect(() => {
const unsubscribe = session.watchStatus((result) => {
if (result.status === 'complete') setOrderState('paid');
if (result.status === 'failed') setOrderState('failed');
});
return unsubscribe;
}, [session]);Il s'abonne avec Pusher lorsque la session contient des paramètres de temps réel. Sinon, il se replie sur un polling toutes les cinq secondes. Vous n'avez rien à choisir.
Cartes sans l'interface de paiement
La clé publique provient de la session, pas de votre code, car elle diffère entre la sandbox et le mode live, ainsi qu'entre les marchands.
import { StripeProvider } from '@stripe/stripe-react-native';
import { createSession, StripeCardSection } from '@wajub/react-native';
function CardCheckout({ token }: { token: string }) {
const session = useMemo(() => createSession(token), [token]);
const [publishableKey, setKey] = useState<string | null>(null);
const [name, setName] = useState('');
useEffect(() => {
session.loadSession().then(async () => {
const config = await session.getSdkConfig();
const slug = session.cardChannelSlug();
setKey(slug ? (config.channels[slug]?.publishable_key ?? null) : null);
});
}, [session]);
if (!publishableKey) return <ActivityIndicator />;
return (
<StripeProvider publishableKey={publishableKey}>
<StripeCardSection
cardholderName={name}
onCardholderNameChange={setName}
submitting={false}
error={null}
onPay={async (paymentMethodId) => {
const slug = session.cardChannelSlug()!;
const result = await session.payCard(slug, paymentMethodId, name);
await session.handleRedirectAction(result);
}}
/>
</StripeProvider>
);
}handleRedirectAction ouvre le navigateur système, pas une WebView
Le payeur quitte votre application pour Chrome ou Safari, puis revient par votre URL de callback.
Ce comportement est volontaire : de plus en plus de banques refusent les vérifications 3DS dans
une WebView. Vérifiez que le callback sur POST /payments pointe vers un lien profond géré par votre application.
Erreurs
Toute erreur générée par la session est une WajubError. Le résultat failed contient la même
structure sous forme d'objet simple.
import { WajubError } from '@wajub/react-native';
try {
const result = await session.payMobileMoney(input);
if (result.status === 'failed') {
// result.error is a plain WajubErrorShape, not a thrown instance
showRetry(result.error.message, result.error.retryable);
}
} catch (error) {
if (error instanceof WajubError && error.type === 'rate_limit_error') {
showRetryIn(error.retry_after_seconds ?? 60);
return;
}
throw error;
}| Champ | Utilité |
|---|---|
type | api_error, authentication_error, invalid_request_error, payment_error ou rate_limit_error |
code | Motif lisible par la machine |
decline_code | Présent sur une réponse 402, motif du refus du prestataire |
retryable | Indique si une nouvelle tentative doit être proposée |
param | Champ fautif lors d'une erreur de validation |
correlation_id | Valeur à communiquer au support |
retry_after_seconds | Attente imposée par une limite de requêtes |
Tous les champs d'erreur utilisent le snake_case ici et le camelCase dans Flutter
La WajubError de ce package conserve partout le format d'échange : error.retry_after_seconds
et error.decline_code. Le SDK Flutter convertit les mêmes champs en retryAfterSeconds et
declineCode. Le portage d'un handler entre les deux impose de les renommer.
Tests
L'origine de l'API est une constante. Vous ne pouvez donc pas rediriger son URL de base. Configurez plutôt une clé de sandbox sur votre backend. Le jeton généré place tout le flux dans la sandbox, interface comprise. Les numéros qui produisent chaque résultat figurent dans Scénarios de test.
Pour un test unitaire, createSession accepte un second argument, un PayClient, que vous pouvez simuler.
Pages associées
- SDKs mobilesL'architecture commune aux trois SDKs.
- Créer un paiementL'appel serveur qui génère le jeton.
- WebhooksLa confirmation qui déclenche la livraison de la commande.
- Moyens de paiement et canauxTous les opérateurs et slugs de canaux.
- FlutterLa même conception en Dart.
- AndroidLa même conception en Kotlin et Compose.