@applite/duticotac-react
v0.0.11
Published
React UI components for Duticotac payments (Shadcn-based)
Readme
@applite/duticotac-react
Composants React pour integrer les paiements Duticotac dans vos applications. Construit avec Tailwind CSS et compatible avec les tokens Shadcn UI.
Ce package fournit une experience de paiement complete : selection du provider, saisie du numero de telephone, code OTP, suivi en temps reel de la transaction, et affichage du resultat.
Table des matieres
- Installation
- Prerequis
- Demarrage rapide
- Hook useDuticotac
- Flux de paiement
- Composants
- Utilitaires
- Integration avec Shadcn UI
- Providers supportes
- Validation du telephone
- Gestion des erreurs
- Comportement du polling
- API Reference complete
- Exemples avances
- Scripts
Installation
pnpm add @applite/duticotac-reactC'est tout ! Le SDK core (@applite/duticotac) est inclus et re-exporte automatiquement. Vous n'avez pas besoin de l'installer separement.
Prerequis
- React >= 18
- Tailwind CSS configure dans votre projet
- Les composants utilisent les tokens de couleur Shadcn (
primary,muted-foreground,border,destructive, etc.). Si vous utilisez Shadcn UI, tout fonctionne directement. Sinon, definissez ces variables CSS dans votre theme Tailwind.
Demarrage rapide
La maniere recommandee d'utiliser ce package est le hook useDuticotac. Il fournit un dialog responsive integre (modal sur desktop, bottom sheet sur mobile) et une API imperative simple :
import { DuticotacSDK, useDuticotac } from "@applite/duticotac-react";
const sdk = new DuticotacSDK({ apiKey: "your_api_key" });
function CheckoutPage() {
const { pay, Dialog } = useDuticotac({
sdk,
getTransactionId: async () => {
const res = await fetch("/api/create-transaction", { method: "POST" });
const data = await res.json();
return data.transactionId;
},
});
return (
<>
<button onClick={() => pay({
amount: 5000,
onSuccess: (tx) => console.log("Paiement confirme :", tx),
})}>
Acheter — 5 000 FCFA
</button>
{Dialog}
</>
);
}Tous les types et classes du SDK core (
DuticotacSDK,DuticotacError, types, etc.) sont disponibles directement depuis@applite/duticotac-react. Pas besoin d'importer depuis@applite/duticotac.
Pour un controle plus fin (modal custom, Shadcn Dialog, etc.), utilisez directement le composant
DuticotacPaymentModalavec sa proprenderModal.
Hook useDuticotac
La maniere la plus simple d'integrer Duticotac. Le hook retourne une fonction pay() et un element Dialog avec un dialog responsive integre (modal centre sur desktop, bottom sheet sur mobile).
import { DuticotacSDK, useDuticotac } from "@applite/duticotac-react";
const sdk = new DuticotacSDK({ apiKey: "your_api_key" });
function CheckoutPage() {
const { pay, Dialog } = useDuticotac({
sdk,
providers: ["OM_CI", "MTN_CI", "MOOV_CI", "WAVE_CI"],
getTransactionId: async () => {
const res = await fetch("/api/create-transaction", { method: "POST" });
const data = await res.json();
return data.transactionId;
},
name: "Jean Dupont",
email: "[email protected]",
});
return (
<>
<button onClick={() => pay({
amount: 5000,
productReference: "premium-monthly",
onSuccess: (tx) => {
console.log("Paiement confirme :", tx);
},
})}>
Acheter — 5 000 FCFA
</button>
{Dialog}
</>
);
}API du hook
const { pay, Dialog, isOpen, close } = useDuticotac(config);config (UseDuticotacConfig)
| Champ | Type | Requis | Description |
|-------|------|--------|-------------|
| sdk | DuticotacSDK | Oui | Instance du SDK |
| getTransactionId | () => Promise<string> | Oui | Generateur d'ID transaction |
| providers | PaymentProvider[] | Non | Providers disponibles (defaut: OM, MTN, MOOV, WAVE) |
| productReference | string | Non | Reference produit par defaut |
| name | string | Non | Nom du client par defaut |
| email | string | Non | Email du client par defaut |
| customerId | string | Non | ID du client |
| kolaboReference | string | Non | Reference Kolabo |
| app | CoreApp | Non | Application source |
| platform | PlatformType | Non | Plateforme |
| initialPhone | string | Non | Pre-remplir le telephone |
| pollTimeout | number | Non | Timeout polling en ms (defaut: 90000) |
| title | string | Non | Titre du dialog (defaut: "Paiement") |
Retour
| Champ | Type | Description |
|-------|------|-------------|
| pay | (options: PayOptions) => void | Ouvre le dialog de paiement |
| Dialog | ReactNode | Element JSX a rendre une fois dans l'arbre |
| isOpen | boolean | Etat du dialog |
| close | () => void | Fermer le dialog programmatiquement |
PayOptions (argument de pay())
| Champ | Type | Requis | Description |
|-------|------|--------|-------------|
| amount | number | Oui | Montant en FCFA |
| providers | PaymentProvider[] | Non | Override les providers |
| productReference | string | Non | Override la reference produit |
| name | string | Non | Override le nom |
| email | string | Non | Override l'email |
| customerId | string | Non | Override l'ID client |
| successMessage | string | Non | Message de succes personnalise |
| onSuccess | (tx: TransactionModel) => void | Non | Callback de succes |
| onClose | () => void | Non | Callback de fermeture |
Comportement responsive
Le dialog s'adapte automatiquement :
| Ecran | Comportement | |-------|-------------| | Desktop (> 640px) | Modal centre, max-width 440px, animation scale | | Mobile (<= 640px) | Bottom sheet, glisse depuis le bas, coins arrondis en haut, poignee de drag |
Les deux modes incluent : fermeture par backdrop click, touche Escape, verrouillage du scroll, et transitions CSS fluides.
Flux de paiement
Le DuticotacPaymentModal gere automatiquement les 4 etapes du paiement :
1. FORMULAIRE 2. CONFIRMATION 3. SUCCES / ERREUR
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ [OM] [MTN] │ │ Spinner │ │ [OK] │
│ [MOOV] [WAVE]│ ───> │ │ ────> │ Paiement │
│ │ │ Confirmez │ │ reussi ! │
│ Tel: 07... │ │ sur votre │ │ │
│ OTP: ____ │ │ telephone... │ │ [Fermer] │
│ │ │ │ │ │
│ [Payer 5000] │ │ 42s... │ │ — ou — │
└──────────────┘ └──────────────┘ │ │
│ [X] │
│ Echec du │
│ paiement │
│ [Reessayer] │
└──────────────┘Etape 1 — Formulaire : L'utilisateur choisit un provider, entre son numero de telephone, et son code OTP (Orange Money uniquement).
Etape 2 — Confirmation : Le SDK appelle mobileMoney.cashout() puis transaction.poll(). Si le provider retourne une paymentUrl (Wave), un nouvel onglet s'ouvre. Le polling est intelligent : il pause quand l'onglet est cache et reprend instantanement quand l'utilisateur revient.
Etape 3 — Resultat : Succes (avec callback onSuccess) ou erreur (avec bouton "Reessayer").
Composants
DuticotacPaymentModal
Le composant principal qui orchestre tout le flux de paiement.
import { DuticotacPaymentModal } from "@applite/duticotac-react";Props
| Prop | Type | Requis | Defaut | Description |
|------|------|--------|--------|-------------|
| open | boolean | Oui | — | Controle la visibilite du modal |
| onClose | () => void | Oui | — | Appele quand le modal se ferme |
| sdk | DuticotacSDK | Oui | — | Instance du SDK Duticotac |
| amount | number | Oui | — | Montant du paiement en FCFA |
| getTransactionId | () => Promise<string> | Oui | — | Fonction async retournant un ID de transaction unique |
| providers | PaymentProvider[] | Non | ["OM_CI", "MTN_CI", "MOOV_CI", "WAVE_CI"] | Providers de paiement disponibles |
| productReference | string | Non | — | Reference du produit/offre |
| onSuccess | (tx: TransactionModel) => void | Non | — | Appele quand le paiement est confirme |
| initialPhone | string | Non | "" | Pre-remplir le numero de telephone |
| name | string | Non | "Duticotac App" | Nom du client |
| email | string | Non | "" | Email du client |
| customerId | string | Non | — | ID du client |
| kolaboReference | string | Non | — | Reference Kolabo partenaire |
| app | CoreApp | Non | — | Application source ("XORAIA", "MONSMSPRO", "FREE") |
| platform | PlatformType | Non | — | Plateforme ("STORE", "DUTICOTAC", etc.) |
| title | string | Non | "Paiement" | Titre du modal |
| successMessage | string | Non | "Le paiement a ete effectue avec succes." | Message affiche apres succes |
| pollTimeout | number | Non | 90000 | Timeout du polling en ms |
| className | string | Non | — | Classes CSS additionnelles |
| renderModal | (props) => ReactNode | Non | — | Render prop pour utiliser votre propre modal |
ResponsiveDialog
Dialog responsive qui s'adapte automatiquement : modal centre sur desktop, bottom sheet sur mobile. Utilise en interne par useDuticotac, mais peut etre utilise independamment.
import { ResponsiveDialog } from "@applite/duticotac-react";
<ResponsiveDialog
open={isOpen}
onClose={() => setOpen(false)}
title="Mon dialog"
>
<p>Contenu du dialog</p>
</ResponsiveDialog>Props
| Prop | Type | Requis | Description |
|------|------|--------|-------------|
| open | boolean | Oui | Visibilite du dialog |
| onClose | () => void | Oui | Callback de fermeture |
| title | string | Oui | Titre affiche dans le header |
| children | ReactNode | Oui | Contenu du dialog |
| className | string | Non | Classes CSS additionnelles |
Fonctionnalites
- Desktop (> 640px) : Modal centre, max-width 440px, animation d'echelle
- Mobile (<= 640px) : Bottom sheet, slide-up, coins arrondis en haut, poignee de drag
- Rendu via
createPortaldansdocument.body - Fermeture : backdrop click, touche Escape
- Verrouillage du scroll body quand ouvert
- Transitions CSS fluides (250ms)
ProviderSelector
Grille de selection des providers de paiement avec logos et indicateur de selection.
import { ProviderSelector } from "@applite/duticotac-react";
function MyForm() {
const [provider, setProvider] = useState<PaymentProvider>("OM_CI");
return (
<ProviderSelector
providers={["OM_CI", "MTN_CI", "MOOV_CI", "WAVE_CI"]}
value={provider}
onChange={setProvider}
disabled={isLoading}
className="my-4"
/>
);
}Props
| Prop | Type | Requis | Description |
|------|------|--------|-------------|
| providers | PaymentProvider[] | Oui | Liste des providers a afficher |
| value | PaymentProvider | Oui | Provider actuellement selectionne |
| onChange | (provider: PaymentProvider) => void | Oui | Callback de changement |
| disabled | boolean | Non | Desactiver la selection |
| className | string | Non | Classes CSS additionnelles |
PhoneInput
Champ de saisie de numero de telephone avec prefixe +225 (Cote d'Ivoire).
import { PhoneInput } from "@applite/duticotac-react";
<PhoneInput
value={phone}
onChange={setPhone}
label="Numero de telephone"
placeholder="07 01 02 03 04"
error={phoneError}
disabled={isLoading}
autoFocus
/>Props
| Prop | Type | Requis | Defaut | Description |
|------|------|--------|--------|-------------|
| value | string | Oui | — | Valeur du champ (sans prefixe +225) |
| onChange | (value: string) => void | Oui | — | Callback de changement |
| label | string | Non | "Numero de telephone" | Label du champ |
| placeholder | string | Non | "07 01 02 03 04" | Placeholder |
| error | string \| null | Non | — | Message d'erreur a afficher |
| disabled | boolean | Non | — | Desactiver le champ |
| autoFocus | boolean | Non | — | Focus automatique |
| className | string | Non | — | Classes CSS additionnelles |
OtpInput
Saisie de code OTP a 4 chiffres. Utilise pour Orange Money (OM_CI). Gere automatiquement le focus entre les champs, le collage, et la navigation au clavier.
import { OtpInput } from "@applite/duticotac-react";
<OtpInput
value={otp}
onChange={setOtp}
length={4}
disabled={isLoading}
/>Props
| Prop | Type | Requis | Defaut | Description |
|------|------|--------|--------|-------------|
| value | string | Oui | — | Code OTP saisi |
| onChange | (value: string) => void | Oui | — | Callback de changement |
| length | number | Non | 4 | Nombre de chiffres |
| disabled | boolean | Non | — | Desactiver la saisie |
| className | string | Non | — | Classes CSS additionnelles |
Fonctionnalites :
- Auto-focus vers le champ suivant apres saisie d'un chiffre
- Retour au champ precedent sur Backspace
- Navigation avec les fleches gauche/droite
- Support du copier-coller (colle automatiquement les 4 chiffres)
- Instruction integree : "Faites #144*82# pour recevoir le code"
TransactionStatus
Affiche l'etat de la transaction : spinner de polling, succes, ou erreur. Utilise en interne par DuticotacPaymentModal, mais peut etre utilise independamment.
import { TransactionStatus } from "@applite/duticotac-react";
// Polling en cours
<TransactionStatus
status="polling"
elapsedSeconds={42}
paymentUrl="https://wave.com/pay/..."
onOpenPaymentUrl={() => window.open(url, "_blank")}
onClose={handleClose}
/>
// Succes
<TransactionStatus
status="success"
message="500 credits ajoutes a votre compte."
onClose={handleClose}
/>
// Erreur
<TransactionStatus
status="error"
errorMessage="Le delai de confirmation a expire."
onRetry={handleRetry}
onClose={handleClose}
/>Props
| Prop | Type | Requis | Description |
|------|------|--------|-------------|
| status | "polling" \| "success" \| "error" | Oui | Etat actuel |
| elapsedSeconds | number | Non | Secondes ecoulees (affiche pendant le polling) |
| message | string | Non | Message de succes |
| errorMessage | string | Non | Message d'erreur |
| paymentUrl | string \| null | Non | URL de paiement externe (Wave) |
| onRetry | () => void | Non | Callback du bouton "Reessayer" |
| onClose | () => void | Non | Callback du bouton "Fermer" |
| onOpenPaymentUrl | () => void | Non | Callback pour ouvrir l'URL de paiement |
| className | string | Non | Classes CSS additionnelles |
Utilitaires
Validation du telephone
import { validatePhone, normalizePhone, getPhoneRegex, needsOtp } from "@applite/duticotac-react";
// Valider un numero pour un provider specifique
const error = validatePhone("0701020304", "OM_CI");
// null si valide, message d'erreur sinon
// Normaliser en format international
normalizePhone("0701020304"); // "+2250701020304"
// Obtenir la regex de validation
getPhoneRegex("OM_CI"); // /^(\+225)(07)[0-9]{8}$/
getPhoneRegex("MTN_CI"); // /^(\+225)(05)[0-9]{8}$/
getPhoneRegex("MOOV_CI");// /^(\+225)(01)[0-9]{8}$/
// Verifier si l'OTP est requis
needsOtp("OM_CI"); // true
needsOtp("MTN_CI"); // false
needsOtp("WAVE_CI"); // falseFormatage
import { formatCFA } from "@applite/duticotac-react";
formatCFA(5000); // "5 000"
formatCFA(1500000); // "1 500 000"Classes CSS
import { cn } from "@applite/duticotac-react";
cn("px-4 py-2", isActive && "bg-primary", className);
// Merge intelligent des classes Tailwind (via clsx + tailwind-merge)Integration avec Shadcn UI
Avec Shadcn Dialog
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog";
import { DuticotacPaymentModal } from "@applite/duticotac-react";
<DuticotacPaymentModal
open={open}
onClose={() => setOpen(false)}
sdk={sdk}
amount={5000}
getTransactionId={getTransactionId}
renderModal={({ open, onClose, title, children }) => (
<Dialog open={open} onOpenChange={(v) => { if (!v) onClose(); }}>
<DialogContent className="sm:max-w-md">
<DialogHeader>
<DialogTitle>{title}</DialogTitle>
</DialogHeader>
{children}
</DialogContent>
</Dialog>
)}
/>Avec Shadcn Drawer (mobile)
import { Drawer, DrawerContent, DrawerHeader, DrawerTitle } from "@/components/ui/drawer";
<DuticotacPaymentModal
open={open}
onClose={() => setOpen(false)}
sdk={sdk}
amount={5000}
getTransactionId={getTransactionId}
renderModal={({ open, onClose, title, children }) => (
<Drawer open={open} onOpenChange={(v) => { if (!v) onClose(); }}>
<DrawerContent>
<DrawerHeader>
<DrawerTitle>{title}</DrawerTitle>
</DrawerHeader>
<div className="px-4 pb-6">{children}</div>
</DrawerContent>
</Drawer>
)}
/>Sans Shadcn (modal integre)
Si vous ne passez pas de renderModal, un modal overlay simple est utilise automatiquement. Aucune dependance externe requise.
Providers supportes
| Code | Nom | OTP requis | Prefixe telephone | Particularite |
|------|-----|------------|-------------------|---------------|
| OM_CI | Orange Money | Oui (4 chiffres) | 07 | L'utilisateur fait #144*82# pour obtenir le code OTP |
| MTN_CI | MTN MoMo | Non | 05 | Confirmation sur le telephone |
| MOOV_CI | Moov (Flooz) | Non | 01 | Confirmation sur le telephone |
| WAVE_CI | Wave | Non | 07, 05, 01 | Ouvre un onglet navigateur pour le paiement |
| CREDIT_CARD | Carte de credit | Non | — | A venir |
| CASH | Especes | Non | — | Paiement en main propre |
| IAP | Achat Integre | Non | — | In-App Purchase (mobile) |
Validation du telephone
Chaque provider a des regles de validation specifiques basees sur les prefixes telephoniques de Cote d'Ivoire :
| Provider | Regex | Exemples valides |
|----------|-------|------------------|
| Orange Money (OM_CI) | +225 07 XX XX XX XX | +225 07 01 02 03 04 |
| MTN (MTN_CI) | +225 05 XX XX XX XX | +225 05 01 02 03 04 |
| Moov (MOOV_CI) | +225 01 XX XX XX XX | +225 01 01 02 03 04 |
| Wave (WAVE_CI) | +225 (07\|05\|01) XX XX XX XX | Accepte tous les prefixes |
Gestion des erreurs
Le modal gere automatiquement les erreurs du SDK et affiche des messages en francais :
| Code erreur | Message affiche |
|-------------|-----------------|
| otp-required | Code OTP requis pour les paiements Orange Money |
| ref-or-idFromClient-required | Reference ou IDFromClient requis |
| payment-failed | Echec du paiement |
| payment-cancelled | Paiement annule |
| polling-timeout | Temps d'attente de la transaction expire |
| phone-required | Numero de telephone requis |
| no-api-key | Cle API non fournie |
| app-not-found | Application non trouvee dans AppLite UI |
| unknown-error | Une erreur inconnue est survenue |
En cas d'erreur, l'utilisateur peut cliquer sur "Reessayer" pour revenir au formulaire.
Comportement du polling
Apres l'appel cashout, le composant suit le statut de la transaction via sdk.transaction.poll() :
- Backoff exponentiel : 1s → 2s → 3s → 5s → 8s (meme strategie que le SDK Dart)
- Timeout : 90 secondes par defaut (configurable via
pollTimeout) - Tab-aware : Le polling pause quand l'onglet est cache (utilisateur sur Wave ou sur son telephone) et reprend immediatement quand l'onglet redevient visible
- Compteur : Un indicateur "Verification en cours... 42s" est affiche
- Wave : Si le provider retourne une
paymentUrl, un nouvel onglet s'ouvre automatiquement avec un bouton de fallback si le popup est bloque - Etats terminaux :
CONFIRMED→ ecran de succes + callbackonSuccessCANCELLED→ ecran d'erreur ("Paiement annule")FAILED→ ecran d'erreur ("Echec du paiement")
API Reference complete
Tout est disponible depuis un seul import @applite/duticotac-react :
import {
// SDK core (re-exporte depuis @applite/duticotac)
DuticotacSDK,
DuticotacError,
getProviderName,
getProviderLogo,
getErrorMessage,
// Hook (recommande)
useDuticotac,
// Composants
DuticotacPaymentModal,
ResponsiveDialog,
ProviderSelector,
PhoneInput,
OtpInput,
TransactionStatus,
// Utilitaires
cn,
formatCFA,
validatePhone,
normalizePhone,
getPhoneRegex,
needsOtp,
} from "@applite/duticotac-react";
import type {
// Types SDK
PaymentProvider, // "OM_CI" | "MTN_CI" | "MOOV_CI" | "WAVE_CI" | "CREDIT_CARD" | "CASH" | "IAP"
PlatformType, // "STORE" | "TRANSPORT" | "RESTAURATION" | "MULTI_SERVICE" | "E_LEARNING" | "DUTICOTAC"
CoreApp, // "XORAIA" | "MONSMSPRO" | "FREE"
TransactionModel,
TransactionStatus as TransactionStatusType,
// Types des props
UseDuticotacConfig,
UseDuticotacReturn,
PayOptions,
DuticotacPaymentModalProps,
ResponsiveDialogProps,
ProviderSelectorProps,
PhoneInputProps,
OtpInputProps,
TransactionStatusProps,
} from "@applite/duticotac-react";Exemples avances
Avec un backend personnalise
function PaymentPage({ offer, apiKey }: { offer: Offer; apiKey: string }) {
const [open, setOpen] = useState(false);
const sdk = useMemo(() => new DuticotacSDK({ apiKey }), [apiKey]);
const getTransactionId = async () => {
const res = await fetch("/api/payments/init", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ offerId: offer.id }),
});
const { transactionId } = await res.json();
return transactionId;
};
return (
<DuticotacPaymentModal
open={open}
onClose={() => setOpen(false)}
sdk={sdk}
amount={offer.price}
getTransactionId={getTransactionId}
productReference={offer.ref}
providers={["OM_CI", "MTN_CI", "WAVE_CI"]}
pollTimeout={120_000}
onSuccess={(tx) => {
toast.success(`Paiement confirme ! Ref: ${tx.ref}`);
router.push("/dashboard");
}}
/>
);
}Composants individuels (sans le modal)
Vous pouvez utiliser les composants independamment pour construire votre propre flux :
import {
ProviderSelector,
PhoneInput,
OtpInput,
TransactionStatus,
needsOtp,
validatePhone,
} from "@applite/duticotac-react";
function CustomPaymentForm() {
const [provider, setProvider] = useState<PaymentProvider>("OM_CI");
const [phone, setPhone] = useState("");
const [otp, setOtp] = useState("");
return (
<form>
<ProviderSelector
providers={["OM_CI", "MTN_CI", "WAVE_CI"]}
value={provider}
onChange={(p) => {
setProvider(p);
if (!needsOtp(p)) setOtp("");
}}
/>
<PhoneInput
value={phone}
onChange={setPhone}
error={validatePhone(phone, provider)}
/>
{needsOtp(provider) && (
<OtpInput value={otp} onChange={setOtp} />
)}
<button type="submit">Payer</button>
</form>
);
}Personnalisation du style
Tous les composants acceptent une prop className pour ajouter des classes Tailwind :
<ProviderSelector
providers={providers}
value={selected}
onChange={setSelected}
className="gap-4 grid-cols-2" // Override la grille
/>
<PhoneInput
value={phone}
onChange={setPhone}
className="mb-6" // Espacement custom
/>Scripts
| Commande | Description |
|----------|-------------|
| pnpm build | Build CJS + ESM avec declarations de types |
| pnpm dev | Mode watch pour le developpement |
| pnpm type-check | Verification des types TypeScript |
| pnpm lint | Lint du code source |
