Aller au contenu

React Native

Le package @wajub/react-native, une interface de paiement prête à l'emploi pour votre application React Native.

Le SDK React Native vous permet d'encaisser des paiements Mobile Money, par carte et par portefeuille dans votre application, avec une interface de paiement prête à l'emploi. present() l'ouvre et se résout une fois, à sa fermeture.

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-native @react-native-community/netinfo @react-native-async-storage/async-storage
npm install @stripe/stripe-react-native
iOS : lier les modules natifs
cd ios && pod install
Dépendance homologuePlageRequise
react>=18Toujours
react-native>=0.74Toujours
@react-native-community/netinfo>=11.0.0Toujours
@react-native-async-storage/async-storage>=1.17.0Toujours
@stripe/stripe-react-native>=0.38.0Facultative, pour les cartes Stripe. Sans elle, l'interface ne les propose pas, voir Cartes

@stripe/stripe-react-native impose ses propres versions minimales d'iOS et d'Android, qui deviennent les vôtres dès que vous l'installez. Aucun provider n'est nécessaire à la racine de votre application : usePayment() fonctionne dans n'importe quel composant.

Démarrage rapide

  1. Votre serveur crée le paiement avec sa clé secrète et transmet authorization_token à l'application.
  2. L'application appelle present() avec ce jeton. Affichez l'élément PaymentSheet que le hook renvoie dans le même composant.
  3. Votre serveur confirme le paiement, avec le webhook payment.succeeded ou GET /payments/{id}, avant de livrer. Le résultat dans l'application est ce que le payeur a vu, pas une preuve de paiement.
Toute l'intégration
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() se résout exactement une fois, avec la valeur que reçoit onComplete. Si le composant qui a appelé le hook est démonté pendant que l'interface est ouverte, il se résout quand même : cancelled avec host_gone avant toute tentative, processing dès qu'une tentative a commencé, complete après un succès. Un appel pendant qu'une interface est ouverte est rejeté avec une WajubError de code sheet_already_presented, sans toucher à l'interface ouverte.

Options

Tout se passe dans l'unique objet que prend present(), callbacks de la section suivante compris.

OptionTypePar défautRôle
sessionTokenstringObligatoireLe authorization_token du paiement
localestringCelle du paiement, sinon celle de l'appareil'fr' ou 'en'. Toute autre valeur donne l'anglais
appearanceAppearanceAucuneL'apparence de l'interface, voir Apparence
loader'skeleton' ou 'spinner''skeleton'Ce qui s'affiche pendant le chargement du paiement
readyTimeoutnumber, en ms15000Passé ce délai, une interface encore en chargement affiche une erreur avec Réessayer, et onLoadError reçoit ready_timeout. 0 désactive le délai
returnUrlstringAucuneUn lien universel ou d'application https vers lequel la page du prestataire renvoie le payeur. Sans lui, le payeur revient dans l'application par lui-même
mobileMoney.countrystring, ISO-2Le pays du payeur s'il est proposé, sinon le premierLe pays sur lequel le formulaire s'ouvre
mobileMoney.countryLockedbooleanfalseMasque le sélecteur de pays, quand country est proposé
mobileMoney.operatorMode'choose' ou 'detect''choose''detect' masque la liste des opérateurs tant que le numéro suffit à désigner l'opérateur
cancelOnDismissbooleantrueFermer l'interface avant toute tentative annule le paiement. false le laisse ouvert, pour rappeler present()
autoCloseSuccessnumber, en ms3000La durée d'affichage de l'écran de succès. 0 le garde jusqu'à ce que le payeur le ferme
stripe.appPublishableKeystringAucuneLa clé Stripe de votre application, si elle encaisse aussi ses propres paiements Stripe, voir Cartes

Callbacks et états

Tous facultatifs, tous appelés sur le thread JavaScript, dans cet ordre pour un paiement : onReady, puis onStateChange à mesure que le payeur avance, onPaymentStarted et onRedirect quand ils s'appliquent, onSuccess, onError ou onCancel, et onComplete en dernier, une seule fois. Un callback qui lève une erreur est journalisé, et l'interface continue.

CallbackReçoitQuand
onReady{ sessionId, environment, amount, currency, methods }Le premier écran est affiché, y compris pour un paiement déjà payé ou terminé. environment vaut 'sandbox' ou 'live', methods liste les moyens de paiement affichés
onStateChange{ state, method }Après onReady, puis à chaque changement d'état. method vaut mobile_money, card, wallet ou unknown
onLoadErrorWajubErrorL'interface ne peut proposer aucun paiement : session_not_found, session_terminal_expired, session_terminal_cancelled, session_terminal_failed, load_error, ready_timeout ou no_payment_method
onPaymentStarted{ method, channel }La demande de paiement est acceptée. Dès lors, fermer l'interface donne processing
onRedirect{ url, method: 'GET' }Juste avant l'ouverture d'une page de prestataire dans le navigateur du système
onSuccessPaymentSuccessLe paiement a réussi. method vaut unknown s'il était payé avant l'ouverture de l'interface
onErrorWajubErrorChaque échec que voit le payeur, y compris ceux qui permettent un nouvel essai, hors validation des champs
onCancel{ reason, redirectUrl }L'interface se ferme en cancelled
onCompleteCheckoutResultL'interface se ferme : la valeur avec laquelle present() se résout

onStateChange signale l'un de ces états : INITIATED (liste des moyens), COLLECTING_DETAILS (le formulaire d'un moyen), PROCESSING (Payer pressé), APPROVAL_REQUIRED, USSD_REQUIRED, PAYER_OTP_REQUIRED (le code que génère le payeur), OTP_REQUIRED, PIN_REQUIRED, PHONE_REQUIRED, BIRTHDAY_REQUIRED, ADDRESS_REQUIRED, 3DS_REQUIRED, VERIFYING, PENDING, SUCCESS, FAILED, EXPIRED et CANCELLED. L'écran derrière chacun est décrit dans SDKs mobiles.

Résultats

present() se résout avec l'un de quatre résultats. Faites un switch sur status.

statusQuandContenu
completeL'interface s'est fermée après l'écran de succès, y compris pour un paiement payé avant son ouverturepayment (PaymentSuccess), transaction
processingL'interface s'est fermée pendant qu'un paiement était en cours. L'interface n'affiche jamais un échec dont elle n'est pas sûre : de l'argent peut encore bouger, confirmez donc sur votre serveurtransaction, et instruction quand le payeur en avait une
failedL'interface s'est fermée sur un échec, un paiement expiré, une erreur de chargement, ou un paiement annulé ailleurs (payment_cancelled)error, transaction ou null
cancelledAnnuler le paiement, une fermeture avant toute tentative, ou le démontage de l'hôte d'abordreason (payer_cancelled, dismissed ou host_gone), redirectUrl

PaymentSuccess contient id (l'identifiant de paiement de Wajub, celui que prend GET /payments/{id}), reference (la vôtre), amount (ce que le payeur a payé, frais compris, en unité principale), currency, method, status: 'success', timestamp, et receipt_url quand il existe un reçu.

Une application tuée ne peut résoudre aucune promesse. Au lancement suivant, demandez le statut du paiement à votre serveur.

Annuler et fermer

Le payeur ferme l'interface avec ✕ dans son en-tête, le bouton retour d'Android, ou Fermer sur un écran final. Il n'y a pas de glissement vers le bas.

Le payeur ferme l'interfaceRésultat
Pendant le chargement du paiementcancelled, dismissed
Sur la liste des moyens ou un formulaire, avant toute tentative ou après Réessayercancelled, dismissed. Le paiement est annulé, sauf si cancelOnDismiss vaut false
Pendant qu'un paiement est en cours : le payeur approuve, confirme une étape ou attendprocessing. Le paiement continue : confirmez-le sur votre serveur
Sur l'écran d'échecfailed, avec l'erreur que le payeur a vue. Le paiement est annulé s'il peut encore l'être, sauf si cancelOnDismiss vaut false
Sur l'écran de succèscomplete
Avec Annuler le paiementcancelled, payer_cancelled. Le paiement est annulé pour de bon

Annuler le paiement s'affiche sous la liste des moyens et sous les formulaires, jamais pendant qu'un paiement est en cours. Les règles sont dans SDKs mobiles.

Apparence

appearance règle l'aspect de l'interface : un préréglage, un mode de couleur et les valeurs que vous voulez changer.

Préréglages, variables et clés de premier niveau
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',
  },
});
  • Mode de couleur : colorScheme vaut light, dark ou auto, qui suit le téléphone. inherit: true suit plutôt le mode de couleur de votre application.
  • Image de marque : ce que vous laissez vide vient de l'image de marque du marchand dans le Dashboard, et un marchand en marque blanche masque « Sécurisé par Wajub ».
  • Polices : fontFamily doit être une police que votre application embarque, sinon la police de la plateforme est utilisée.
  • Libellés : labels: 'above', la valeur par défaut, place chaque libellé au-dessus de son champ ; 'floating' le place dans le champ vide.
  • Mouvement : disableAnimations: true, ou le réglage de réduction des animations du téléphone, arrête les animations.

Chaque clé et chaque préréglage sont décrits dans Apparence.

Mobile Money

Un seul formulaire couvre tous les opérateurs Mobile Money du paiement : le payeur choisit le pays et l'opérateur et saisit le numéro, mis en forme pendant la saisie. Le préfixe du numéro présélectionne son opérateur, et un numéro d'un autre opérateur proposé est refusé avant tout envoi. Après Payer, « Validez le paiement sur votre téléphone » affiche le numéro et l'instruction de l'opérateur.

mobileMoney.country ouvre le formulaire sur un pays, countryLocked masque le sélecteur, et operatorMode: 'detect' garde la liste des opérateurs masquée tant que le numéro suffit à désigner l'opérateur. Plus de détails dans SDKs mobiles.

Cartes

Les cartes Stripe requièrent la dépendance homologue facultative @stripe/stripe-react-native ; aucune autre configuration Stripe n'est nécessaire. Si votre application encaisse aussi ses propres paiements Stripe, passez la clé de votre application dans stripe: { appPublishableKey } pour que vos propres paiements Stripe continuent de fonctionner.

Les autres cartes ouvrent une page sécurisée du prestataire de paiement dans le navigateur du système. En sandbox, l'interface affiche un formulaire de carte de test.

Étapes de confirmation

Quand un paiement demande une étape de plus après Payer, l'interface l'affiche en natif : un code à usage unique, le code PIN de la carte, un numéro de téléphone, une date de naissance ou une adresse de facturation, avec les essais restants après une mauvaise réponse. En sandbox, une carte qui demande 3-D Secure reçoit un code par e-mail. Les étapes et leurs états sont dans SDKs mobiles.

Erreurs

Chaque échec est une WajubError, une sous-classe d'Error. onLoadError, onError et un résultat failed en transmettent une. Ses champs sont en snake_case.

ChampUtilité
typeapi_error, authentication_error, invalid_request_error, payment_error ou rate_limit_error
codeCe qui s'est passé, pour orienter votre code
messagePour le payeur, dans la langue de l'interface
decline_codeSur un refus, son motif
retryableIndique si un nouvel essai peut aboutir
paramLe champ en cause, quand il y en a un
correlation_idL'identifiant de requête à communiquer au support Wajub
retry_after_secondsL'attente sur rate_limited
block_referenceSur shield_blocked, la référence à communiquer au support Wajub
Où les erreurs vous parviennent
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.
  }
}

Les codes sur lesquels brancher votre code : insufficient_funds, payment_declined, payment_attempt_ended (refusé, rien n'a été débité), payment_failed, payment_cancelled, session_expired, shield_blocked, merchant_restricted, rate_limited, session_terminal_expired, unsupported_action, redirect_unavailable et sheet_already_presented. Chaque code est dans SDKs mobiles.

Votre propre interface

Les écrans de l'interface ne sont pas exportés. Une application qui dessine ses propres écrans de paiement utilise WajubSession, le client de plus bas niveau, que renvoie createSession(token) et que documente le README du paquet.

Confidentialité

Pour effectuer le paiement, le SDK envoie à Wajub ce que saisit le payeur. Pour la prévention de la fraude, il envoie aussi des informations de base sur l'appareil et un identifiant d'installation aléatoire, qui n'est pas l'identifiant publicitaire. Ce qu'il faut déclarer dans les étiquettes de confidentialité de l'App Store et dans le formulaire de sécurité des données de Google Play, catégorie par catégorie, est sur SDKs mobiles.

Tests

Un paiement que votre serveur crée avec une clé de sandbox fait passer tout le parcours en sandbox, et l'interface affiche un badge « MODE TEST ». Les numéros et les cartes de test figurent dans Scénarios de test.

Que pensez-vous de ce contenu ?