@az54/react
v1.0.3
Published
Composants React pour l'intégration de formulaires de paiement Mobile Money AZ54
Maintainers
Readme
AZ54 Payment SDK
SDK JavaScript/TypeScript pour intégrer des formulaires de paiement et de transfert Mobile Money dans vos applications React.
📦 Installation
npm install @az54/react
# ou
yarn add @az54/react
# ou
bun add @az54/react🚀 Démarrage rapide
1. Importer les styles
// Dans votre fichier main.tsx ou App.tsx
import "@az54/react/styles.css";2. Configurer le Provider
import { AZ54Provider } from "@az54/react";
function App() {
return (
<AZ54Provider baseUrl="https://api.az54.com">
{/* Votre application */}
</AZ54Provider>
);
}3. Utiliser un formulaire
import { PayinForm } from "@az54/react";
function CheckoutPage() {
const handleSubmit = async (data) => {
// Envoyer les données à votre backend
await fetch("/api/payments/initiate", {
method: "POST",
body: JSON.stringify(data),
});
};
return <PayinForm onSubmit={handleSubmit} />;
}📋 Packages
| Package | Description |
| ---------------------------------------------------------- | --------------------------------------- |
| @az54/core | Client API et types TypeScript |
| @az54/react | Composants React (formulaires, boutons) |
🎯 Composants disponibles
Payment (Payin) - Recevoir des paiements
<PayinForm>- Formulaire de réception de paiement<PayinButton>- Bouton + modal de paiement<PayinModal>- Modal wrapper
Payout - Envoyer des transferts
<PayoutForm>- Formulaire d'envoi de transfert<PayoutButton>- Bouton + modal de transfert<PayoutModal>- Modal wrapper
🔧 API Reference
<AZ54Provider>
Provider obligatoire pour configurer le SDK.
`<AZ54Provider baseUrl="https://api.merchant.io" // URL de votre API timeout={30000} // Timeout des requêtes en ms
{children} `
Props:
baseUrl(string, optionnel) - URL de base de l'APItimeout(number, optionnel) - Timeout des requêtes en mschildren(ReactNode, requis) - Contenu de l'app
<PayinForm> - Recevoir un paiement
Formulaire intelligent en 4 étapes pour recevoir des paiements Mobile Money.
<PayinForm
onSubmit={(data) => console.log(data)}
onError={(error) => console.error(error)}
onCancel={() => console.log("Annulé")}
onStepChange={(step) => console.log("Étape:", step)}
// Valeurs par défaut (modifiables)
defaultPhoneNumber="+22507123456"
defaultCurrency="XOF"
// Valeurs fixes (masquent les étapes)
fixedPhoneNumber="+22507123456" // Skip l'étape téléphone
fixedCurrency="XOF" // Skip l'étape devise
fixedAmount={5000} // Montant non modifiable
// Personnalisation UI
theme={{ primary: "#00D68F" }}
locale="fr"
showDescription={true}
showEmail={true}
// État
isSubmitting={false}
className="my-form"
/>Exemple avec limites personnalisées:
<PayinForm
onSubmit={handleSubmit}
minAmount={10} // Force un minimum de 10, même si le système permet moins
maxAmount={1000} // Force un maximum de 1000, même si le système permet plus
/>Props principales:
| Prop | Type | Description |
| --------------------- | ------------------------------------------------- | -------------------------------------------------------------------- |
| onSubmit | (data: PayinFormData) => void | Promise<void> | Requis - Callback lors de la soumission |
| onError | (error: Error) => void | Callback en cas d'erreur |
| onCancel | () => void | Callback lors de l'annulation |
| onStepChange | (step: number) => void | Callback au changement d'étape |
| onAmountChange | (amount: number) => void | Callback quand le montant change |
| fixedPhoneNumber | string | Numéro fixe → skip l'étape 1 |
| fixedCurrency | string | Devise fixe → skip l'étape 2 |
| fixedAmount | number | Montant fixe → champ non éditable |
| minAmount | number | Montant minimum (doit être ≥ au minimum système) |
| maxAmount | number | Montant maximum (doit être ≤ au maximum système) |
| paymentMethodLabel | string | Intitulé du bloc « méthode de paiement » (défaut: "Vous payez avec") |
| showDescription | boolean | Afficher le champ description (défaut: true) |
| showEmail | boolean | Afficher le champ email (défaut: true) |
| showProgress | boolean | Afficher l'indicateur de progression (défaut: true) |
| theme | PaymentFormTheme | Personnalisation du thème |
| isSubmitting | boolean | État de chargement externe |
| isNextDisabled | boolean | Désactiver le bouton Suivant/Confirmer (défaut: false) |
| renderBeforeAmount | () => ReactNode | Contenu custom avant le champ montant |
| renderAfterAmount | () => ReactNode | Contenu custom après le champ montant |
| renderAmountSummary | (amount: number, currency: string) => ReactNode | Remplace le récapitulatif par défaut |
💡 Méthode de paiement: au-dessus du champ montant, l'étape finale rappelle l'opérateur retenu (logo + nom), le numéro Mobile Money formaté et la devise. Le récapitulatif final ne répète donc plus ces informations et ne conserve que le montant.
Données retournées par onSubmit:
interface PayinFormData {
orderId: string; // "order-550e8400-e29b-41d4-a716-446655440000" (généré automatiquement)
phoneNumber: string; // "+22507123456"
country: string; // "Côte d'Ivoire"
currency: string; // "XOF"
operatorCode: string; // "orange_ci"
operatorName: string; // "Orange Money"
workflowType: "provider_auth" | "pre_authorized" | "redirect_url";
amount: number; // 5000
otp?: string; // "1234" (pour Orange CI)
description?: string;
email?: string;
}💡 Note: L'
orderIdest généré automatiquement côté client pour éviter les race conditions. Il suit le formatorder-<UUID v4>(tiret, pas underscore : certains providers comme Intouch rejettent tout identifiant contenant un_) et garantit l'unicité de chaque transaction.
<PayoutForm> - Envoyer un transfert
Formulaire intelligent en 4 étapes pour envoyer des transferts Mobile Money.
<PayoutForm
onSubmit={(data) => console.log(data)}
onError={(error) => console.error(error)}
fixedPhoneNumber="+22507123456"
fixedCurrency="XOF"
// Spécifique aux payouts
showSummary={false} // Masquer le récapitulatif final
disableSubmit={insufficientBalance} // Bloquer l'envoi (ex: solde insuffisant)
theme={{ primary: "#00D68F" }}
isSubmitting={false}
/>Props principales:
| Prop | Type | Description |
| -------------------- | ------------------------------------------------- | --------------------------------------------------------------------------------------------------- |
| onSubmit | (data: PayoutFormData) => void \| Promise<void> | Requis - Callback lors de la soumission |
| onError | (error: Error) => void | Callback en cas d'erreur |
| onCancel | () => void | Callback lors de l'annulation |
| onStepChange | (step: number) => void | Callback au changement d'étape |
| fixedPhoneNumber | string | Numéro fixe → skip l'étape 1 |
| fixedCurrency | string | Devise fixe → skip l'étape 2 |
| fixedAmount | number | Montant fixe → champ non éditable |
| minAmount | number | Montant minimum (doit être ≥ au minimum système) |
| maxAmount | number | Montant maximum (doit être ≤ au maximum système) |
| paymentMethodLabel | string | Intitulé du bloc « méthode de paiement » (défaut: "Envoi vers") |
| showDescription | boolean | Afficher le champ description (défaut: true) |
| showEmail | boolean | Afficher le champ email (défaut: true) |
| showSummary | boolean | Afficher le récapitulatif (défaut: true) |
| showProgress | boolean | Afficher l'indicateur de progression (défaut: true) |
| theme | PaymentFormTheme | Personnalisation du thème |
| isSubmitting | boolean | État de chargement externe |
| disableSubmit | boolean | Désactiver le bouton de soumission, ex: solde insuffisant détecté côté consommateur (défaut: false) |
| renderBeforeAmount | (ctx: PayoutAmountContext) => ReactNode | Contenu custom avant le champ montant |
| renderAfterAmount | (ctx: PayoutAmountContext) => ReactNode | Contenu custom après le champ montant |
⚠️
<PayoutForm>n'a pas les mêmes props que<PayinForm>pour tout ce qui touche au montant : pas deisNextDisabled(utilisezdisableSubmit), pas deonAmountChange, pas derenderAmountSummary, pas dedefaultAmount. EtrenderBeforeAmount/renderAfterAmountreçoivent un argumentctx: PayoutAmountContext(amount,minAmount,maxAmount,operatorCode, …) là où la versionPayinFormne prend aucun argument.
Exemple — bloquer l'envoi sur solde insuffisant:
<PayoutForm
onSubmit={handlePayout}
renderAfterAmount={({ amount }) => (
<BalancePreview amount={amount} balance={merchantBalance} />
)}
disableSubmit={merchantBalance < amount}
/>→ renderAfterAmount lit le montant courant via ctx.amount pour afficher un aperçu de solde ; disableSubmit bloque le bouton "Confirmer" tant que le solde est insuffisant — sans ça, React ignore silencieusement une prop inconnue et le payout part quand même.
Données retournées:
interface PayoutFormData {
orderId: string; // "order-550e8400-e29b-41d4-a716-446655440000" (généré automatiquement)
phoneNumber: string; // Destinataire
country: string;
currency: string;
operatorCode: string;
operatorName: string;
workflowType: WorkflowType;
amount: number;
otp?: string;
description?: string;
email?: string;
}<PayinButton> & <PayoutButton>
Boutons qui ouvrent les formulaires dans une modal.
// Bouton de paiement
<PayinButton
buttonText="Payer maintenant"
buttonVariant="default" // 'default' | 'outline' | 'ghost'
buttonSize="lg" // 'sm' | 'default' | 'lg'
buttonClassName="my-btn"
modalTitle="Paiement sécurisé"
modalClassName="my-modal"
disabled={false}
// + toutes les props de PayinForm
onSubmit={handleSubmit}
fixedAmount={5000}
/>
// Bouton de transfert
<PayoutButton
buttonText="Envoyer de l'argent"
modalTitle="Transfert d'argent"
onSubmit={handlePayout}
/>🎨 Personnalisation du thème
Le SDK utilise CSS Layers et CSS Custom Properties pour une personnalisation totale.
Via le prop theme
const customTheme = {
// Couleurs
primary: "#22c55e", // Couleur principale (boutons, accents)
primaryForeground: "#ffffff", // Texte sur fond primary
background: "#ffffff", // Fond du formulaire
foreground: "#0f172a", // Texte principal
muted: "#f1f5f9", // Fond secondaire (cartes)
mutedForeground: "#64748b", // Texte secondaire
border: "#e2e8f0", // Bordures
destructive: "#ef4444", // Couleur d'erreur
// Typographie
borderRadius: "0.5rem", // Arrondi des coins
fontFamily: "Inter, sans-serif",
// Layout (nouveau!)
padding: "1.5rem", // Padding du formulaire
maxWidth: "28rem", // Largeur max
margin: "0 auto", // Centrage
};
<PayinForm theme={customTheme} onSubmit={handleSubmit} />;Via Tailwind (sans !important)
<PayinForm
className="p-0 max-w-full" // ✅ Fonctionne directement
onSubmit={handleSubmit}
/>Via CSS
/* Votre fichier CSS */
.az54-payment-form {
padding: 0;
max-width: 600px;
--az54-primary: #10b981;
}Pourquoi ça marche ? Tous les styles du SDK sont dans @layer az54-sdk, ce qui leur donne la priorité la plus basse. Vos styles gagnent toujours automatiquement!
📘 Cas d'usage
1. Paiement simple (utilisateur anonyme)
<PayinForm onSubmit={handleSubmit} />
→ L'utilisateur saisit tout : téléphone, devise, opérateur, montant
2. Paiement avec téléphone pré-rempli (utilisateur connecté)
<PayinForm
defaultPhoneNumber={user.phoneNumber}
onSubmit={handleSubmit}
/>
→ Le téléphone est pré-rempli mais modifiable
3. Paiement avec téléphone fixe (utilisateur connecté)
<PayinForm
fixedPhoneNumber={user.phoneNumber}
onSubmit={handleSubmit}
/>
→ Skip l'étape téléphone, commence directement à la devise
4. Paiement avec montant fixe (e-commerce)
<PayinForm
fixedPhoneNumber={user.phoneNumber}
fixedCurrency="XOF"
fixedAmount={cart.total}
onSubmit={handleSubmit}
/>
→ L'utilisateur choisit uniquement l'opérateur et saisit l'OTP
5. Bouton de paiement rapide
<PayinButton
buttonText="Payer 5 000 XOF"
buttonSize="lg"
fixedAmount={5000}
fixedCurrency="XOF"
onSubmit={handleSubmit}
/>
6. Paiement avec contenu custom (promo code, bonus)
<PayinForm
onSubmit={handleSubmit}
fixedPhoneNumber={user.phoneNumber}
renderAfterAmount={() => (
<>
<PromoCodeSection
onPromoValidated={handlePromoValidated}
bonus={referralBonus}
/>
{referralBonus && (
<TotalSummary
baseAmount={amount}
bonus={referralBonus}
currency={currency}
/>
)}
</>
)}
renderAmountSummary={(amount, currency) => (
<CustomSummary amount={amount} currency={currency} bonus={bonus} />
)}
/>→ Injecte du contenu custom (codes promo, bonus) autour du champ montant
7. Transfert d'argent (payout)
<PayoutForm
fixedCurrency="XOF"
showSummary={false}
onSubmit={handlePayout}
/>
8. Transfert avec bénéficiaire connu
<PayoutForm
fixedPhoneNumber={beneficiary.phoneNumber}
fixedCurrency="XOF"
onSubmit={handlePayout}
/>
🪝 Hooks (usage avancé)
usePayinCorridors() - Récupérer les corridors de paiement
`import { usePayinCorridors } from '@az54/react';
function MyComponent() { const { corridors, isLoading, fetchCorridors, error } = usePayinCorridors();
const handlePhoneChange = async (phone: string) => { try { const data = await fetchCorridors(phone); console.log('Pays:', data.country.name); console.log('Devises:', data.currencies); console.log('Opérateurs:', data.operators); } catch (err) { console.error('Erreur:', err); } };
return ( <input type="tel" onChange={(e) => handlePhoneChange(e.target.value)} /> ); }`
data.operatorscontient une entrée par couple (opérateur, devise). Un mêmecodepeut donc apparaître plusieurs fois, une fois par devise qu'il sert.minAmountMajor/maxAmountMajor(exprimés en unité MAJEURE decurrency, alignée sur l'unité dePayinFormData.amount/PayoutFormData.amount) etworkflowTypene valent que pour cette devise : filtrez sur la devise retenue avant de les lire. Les formulairesPayinForm/PayoutFormle font déjà.
usePayoutCorridors() - Récupérer les corridors de payout
`import { usePayoutCorridors } from '@az54/react';
function MyComponent() { const { corridors, isLoading, fetchCorridors } = usePayoutCorridors();
// Même API que usePayinCorridors }`
useCountries() - Liste des pays supportés
`import { useCountries } from '@az54/react';
function CountrySelector() { const { countries, isLoading } = useCountries();
// Chargement automatique au mount
return ( {countries.map(country => (
🔐 Intégration Backend
⚠️ Important: Le SDK ne fait PAS d'appel de paiement direct. Il collecte uniquement les informations et les retourne via onSubmit.
Architecture recommandée
┌─────────────┐ 1. Formulaire ┌──────────────┐
│ Frontend │ ──────────────────> │ Votre API │
│ (SDK) │ │ (Backend) │
└─────────────┘ └──────────────┘
│
│ 2. Initier paiement
│ (clé secrète)
▼
┌──────────────┐
│ API AZ54 │
└──────────────┘
│
│ 3. Webhook
▼
┌──────────────┐
│ Votre API │
│ (Webhook) │
└──────────────┘❓ FAQ
Quelle est la différence entre Payment et Payout ?
- Payment (Payin) : Recevoir de l'argent (client → marchand)
- Payout : Envoyer de l'argent (marchand → bénéficiaire)
Puis-je utiliser le SDK sans backend ?
Non, vous devez avoir un backend pour appeler l'API AZ54 avec votre clé secrète. Le SDK collecte uniquement les données.
Comment gérer les OTP pour Orange CI ?
Le SDK détecte automatiquement si l'opérateur nécessite un OTP et affiche le champ correspondant. L'OTP est inclus dans les données retournées par onSubmit.
Comment personnaliser entièrement le style ?
Vous pouvez soit utiliser la prop theme, soit surcharger les classes CSS du SDK en important votre propre CSS après @az54/react/styles.css.
Le SDK fonctionne-t-il avec Next.js ?
Oui, mais assurez-vous d'utiliser 'use client' pour les composants qui utilisent le SDK (car il utilise des hooks React).
