@applite/duticotac-react 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,628 @@
1
+ # @applite/duticotac-react
2
+
3
+ Composants React pour integrer les paiements Duticotac dans vos applications. Construit avec Tailwind CSS et compatible avec les tokens Shadcn UI.
4
+
5
+ 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.
6
+
7
+ ---
8
+
9
+ ## Table des matieres
10
+
11
+ - [Installation](#installation)
12
+ - [Prerequis](#prerequis)
13
+ - [Demarrage rapide](#demarrage-rapide)
14
+ - [Flux de paiement](#flux-de-paiement)
15
+ - [Composants](#composants)
16
+ - [DuticotacPaymentModal](#duticotacpaymentmodal)
17
+ - [ProviderSelector](#providerselector)
18
+ - [PhoneInput](#phoneinput)
19
+ - [OtpInput](#otpinput)
20
+ - [TransactionStatus](#transactionstatus)
21
+ - [Utilitaires](#utilitaires)
22
+ - [Integration avec Shadcn UI](#integration-avec-shadcn-ui)
23
+ - [Providers supportes](#providers-supportes)
24
+ - [Validation du telephone](#validation-du-telephone)
25
+ - [Gestion des erreurs](#gestion-des-erreurs)
26
+ - [Comportement du polling](#comportement-du-polling)
27
+ - [API Reference complete](#api-reference-complete)
28
+ - [Exemples avances](#exemples-avances)
29
+ - [Scripts](#scripts)
30
+
31
+ ---
32
+
33
+ ## Installation
34
+
35
+ ```bash
36
+ pnpm add @applite/duticotac @applite/duticotac-react
37
+ ```
38
+
39
+ Les deux packages sont necessaires : `@applite/duticotac` contient le SDK (logique API, types, models) et `@applite/duticotac-react` contient les composants UI.
40
+
41
+ ## Prerequis
42
+
43
+ - **React** >= 18
44
+ - **Tailwind CSS** configure dans votre projet
45
+ - 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.
46
+
47
+ ---
48
+
49
+ ## Demarrage rapide
50
+
51
+ ```tsx
52
+ import { useState } from "react";
53
+ import { DuticotacSDK } from "@applite/duticotac";
54
+ import { DuticotacPaymentModal } from "@applite/duticotac-react";
55
+
56
+ // Creez l'instance SDK une seule fois (en dehors du composant)
57
+ const sdk = new DuticotacSDK({
58
+ apiKey: "your_api_key",
59
+ appId: "your_app_id", // optionnel
60
+ });
61
+
62
+ function CheckoutPage() {
63
+ const [paymentOpen, setPaymentOpen] = useState(false);
64
+
65
+ return (
66
+ <>
67
+ <button onClick={() => setPaymentOpen(true)}>
68
+ Acheter — 5 000 FCFA
69
+ </button>
70
+
71
+ <DuticotacPaymentModal
72
+ open={paymentOpen}
73
+ onClose={() => setPaymentOpen(false)}
74
+ sdk={sdk}
75
+ amount={5000}
76
+ providers={["OM_CI", "MTN_CI", "MOOV_CI", "WAVE_CI"]}
77
+ getTransactionId={async () => {
78
+ // Appelez votre backend pour generer un ID unique
79
+ const res = await fetch("/api/create-transaction", { method: "POST" });
80
+ const data = await res.json();
81
+ return data.transactionId;
82
+ }}
83
+ productReference="premium-monthly"
84
+ name="Jean Dupont"
85
+ email="jean@example.com"
86
+ onSuccess={(transaction) => {
87
+ console.log("Paiement confirme :", transaction);
88
+ // Rafraichir le solde, rediriger, etc.
89
+ }}
90
+ />
91
+ </>
92
+ );
93
+ }
94
+ ```
95
+
96
+ ---
97
+
98
+ ## Flux de paiement
99
+
100
+ Le `DuticotacPaymentModal` gere automatiquement les 4 etapes du paiement :
101
+
102
+ ```
103
+ 1. FORMULAIRE 2. CONFIRMATION 3. SUCCES / ERREUR
104
+ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐
105
+ │ [OM] [MTN] │ │ Spinner │ │ [OK] │
106
+ │ [MOOV] [WAVE]│ ───> │ │ ────> │ Paiement │
107
+ │ │ │ Confirmez │ │ reussi ! │
108
+ │ Tel: 07... │ │ sur votre │ │ │
109
+ │ OTP: ____ │ │ telephone... │ │ [Fermer] │
110
+ │ │ │ │ │ │
111
+ │ [Payer 5000] │ │ 42s... │ │ — ou — │
112
+ └──────────────┘ └──────────────┘ │ │
113
+ │ [X] │
114
+ │ Echec du │
115
+ │ paiement │
116
+ │ [Reessayer] │
117
+ └──────────────┘
118
+ ```
119
+
120
+ **Etape 1 — Formulaire** : L'utilisateur choisit un provider, entre son numero de telephone, et son code OTP (Orange Money uniquement).
121
+
122
+ **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.
123
+
124
+ **Etape 3 — Resultat** : Succes (avec callback `onSuccess`) ou erreur (avec bouton "Reessayer").
125
+
126
+ ---
127
+
128
+ ## Composants
129
+
130
+ ### `DuticotacPaymentModal`
131
+
132
+ Le composant principal qui orchestre tout le flux de paiement.
133
+
134
+ ```tsx
135
+ import { DuticotacPaymentModal } from "@applite/duticotac-react";
136
+ ```
137
+
138
+ #### Props
139
+
140
+ | Prop | Type | Requis | Defaut | Description |
141
+ |------|------|--------|--------|-------------|
142
+ | `open` | `boolean` | Oui | — | Controle la visibilite du modal |
143
+ | `onClose` | `() => void` | Oui | — | Appele quand le modal se ferme |
144
+ | `sdk` | `DuticotacSDK` | Oui | — | Instance du SDK Duticotac |
145
+ | `amount` | `number` | Oui | — | Montant du paiement en FCFA |
146
+ | `getTransactionId` | `() => Promise<string>` | Oui | — | Fonction async retournant un ID de transaction unique |
147
+ | `providers` | `PaymentProvider[]` | Non | `["OM_CI", "MTN_CI", "MOOV_CI", "WAVE_CI"]` | Providers de paiement disponibles |
148
+ | `productReference` | `string` | Non | — | Reference du produit/offre |
149
+ | `onSuccess` | `(tx: TransactionModel) => void` | Non | — | Appele quand le paiement est confirme |
150
+ | `initialPhone` | `string` | Non | `""` | Pre-remplir le numero de telephone |
151
+ | `name` | `string` | Non | `"Duticotac App"` | Nom du client |
152
+ | `email` | `string` | Non | `""` | Email du client |
153
+ | `customerId` | `string` | Non | — | ID du client |
154
+ | `kolaboReference` | `string` | Non | — | Reference Kolabo partenaire |
155
+ | `app` | `CoreApp` | Non | — | Application source (`"XORAIA"`, `"MONSMSPRO"`, `"FREE"`) |
156
+ | `platform` | `PlatformType` | Non | — | Plateforme (`"STORE"`, `"DUTICOTAC"`, etc.) |
157
+ | `title` | `string` | Non | `"Paiement"` | Titre du modal |
158
+ | `successMessage` | `string` | Non | `"Le paiement a ete effectue avec succes."` | Message affiche apres succes |
159
+ | `pollTimeout` | `number` | Non | `90000` | Timeout du polling en ms |
160
+ | `className` | `string` | Non | — | Classes CSS additionnelles |
161
+ | `renderModal` | `(props) => ReactNode` | Non | — | Render prop pour utiliser votre propre modal |
162
+
163
+ ---
164
+
165
+ ### `ProviderSelector`
166
+
167
+ Grille de selection des providers de paiement avec logos et indicateur de selection.
168
+
169
+ ```tsx
170
+ import { ProviderSelector } from "@applite/duticotac-react";
171
+
172
+ function MyForm() {
173
+ const [provider, setProvider] = useState<PaymentProvider>("OM_CI");
174
+
175
+ return (
176
+ <ProviderSelector
177
+ providers={["OM_CI", "MTN_CI", "MOOV_CI", "WAVE_CI"]}
178
+ value={provider}
179
+ onChange={setProvider}
180
+ disabled={isLoading}
181
+ className="my-4"
182
+ />
183
+ );
184
+ }
185
+ ```
186
+
187
+ #### Props
188
+
189
+ | Prop | Type | Requis | Description |
190
+ |------|------|--------|-------------|
191
+ | `providers` | `PaymentProvider[]` | Oui | Liste des providers a afficher |
192
+ | `value` | `PaymentProvider` | Oui | Provider actuellement selectionne |
193
+ | `onChange` | `(provider: PaymentProvider) => void` | Oui | Callback de changement |
194
+ | `disabled` | `boolean` | Non | Desactiver la selection |
195
+ | `className` | `string` | Non | Classes CSS additionnelles |
196
+
197
+ ---
198
+
199
+ ### `PhoneInput`
200
+
201
+ Champ de saisie de numero de telephone avec prefixe `+225` (Cote d'Ivoire).
202
+
203
+ ```tsx
204
+ import { PhoneInput } from "@applite/duticotac-react";
205
+
206
+ <PhoneInput
207
+ value={phone}
208
+ onChange={setPhone}
209
+ label="Numero de telephone"
210
+ placeholder="07 01 02 03 04"
211
+ error={phoneError}
212
+ disabled={isLoading}
213
+ autoFocus
214
+ />
215
+ ```
216
+
217
+ #### Props
218
+
219
+ | Prop | Type | Requis | Defaut | Description |
220
+ |------|------|--------|--------|-------------|
221
+ | `value` | `string` | Oui | — | Valeur du champ (sans prefixe +225) |
222
+ | `onChange` | `(value: string) => void` | Oui | — | Callback de changement |
223
+ | `label` | `string` | Non | `"Numero de telephone"` | Label du champ |
224
+ | `placeholder` | `string` | Non | `"07 01 02 03 04"` | Placeholder |
225
+ | `error` | `string \| null` | Non | — | Message d'erreur a afficher |
226
+ | `disabled` | `boolean` | Non | — | Desactiver le champ |
227
+ | `autoFocus` | `boolean` | Non | — | Focus automatique |
228
+ | `className` | `string` | Non | — | Classes CSS additionnelles |
229
+
230
+ ---
231
+
232
+ ### `OtpInput`
233
+
234
+ 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.
235
+
236
+ ```tsx
237
+ import { OtpInput } from "@applite/duticotac-react";
238
+
239
+ <OtpInput
240
+ value={otp}
241
+ onChange={setOtp}
242
+ length={4}
243
+ disabled={isLoading}
244
+ />
245
+ ```
246
+
247
+ #### Props
248
+
249
+ | Prop | Type | Requis | Defaut | Description |
250
+ |------|------|--------|--------|-------------|
251
+ | `value` | `string` | Oui | — | Code OTP saisi |
252
+ | `onChange` | `(value: string) => void` | Oui | — | Callback de changement |
253
+ | `length` | `number` | Non | `4` | Nombre de chiffres |
254
+ | `disabled` | `boolean` | Non | — | Desactiver la saisie |
255
+ | `className` | `string` | Non | — | Classes CSS additionnelles |
256
+
257
+ **Fonctionnalites :**
258
+ - Auto-focus vers le champ suivant apres saisie d'un chiffre
259
+ - Retour au champ precedent sur Backspace
260
+ - Navigation avec les fleches gauche/droite
261
+ - Support du copier-coller (colle automatiquement les 4 chiffres)
262
+ - Instruction integree : "Faites **#144*82#** pour recevoir le code"
263
+
264
+ ---
265
+
266
+ ### `TransactionStatus`
267
+
268
+ Affiche l'etat de la transaction : spinner de polling, succes, ou erreur. Utilise en interne par `DuticotacPaymentModal`, mais peut etre utilise independamment.
269
+
270
+ ```tsx
271
+ import { TransactionStatus } from "@applite/duticotac-react";
272
+
273
+ // Polling en cours
274
+ <TransactionStatus
275
+ status="polling"
276
+ elapsedSeconds={42}
277
+ paymentUrl="https://wave.com/pay/..."
278
+ onOpenPaymentUrl={() => window.open(url, "_blank")}
279
+ onClose={handleClose}
280
+ />
281
+
282
+ // Succes
283
+ <TransactionStatus
284
+ status="success"
285
+ message="500 credits ajoutes a votre compte."
286
+ onClose={handleClose}
287
+ />
288
+
289
+ // Erreur
290
+ <TransactionStatus
291
+ status="error"
292
+ errorMessage="Le delai de confirmation a expire."
293
+ onRetry={handleRetry}
294
+ onClose={handleClose}
295
+ />
296
+ ```
297
+
298
+ #### Props
299
+
300
+ | Prop | Type | Requis | Description |
301
+ |------|------|--------|-------------|
302
+ | `status` | `"polling" \| "success" \| "error"` | Oui | Etat actuel |
303
+ | `elapsedSeconds` | `number` | Non | Secondes ecoulees (affiche pendant le polling) |
304
+ | `message` | `string` | Non | Message de succes |
305
+ | `errorMessage` | `string` | Non | Message d'erreur |
306
+ | `paymentUrl` | `string \| null` | Non | URL de paiement externe (Wave) |
307
+ | `onRetry` | `() => void` | Non | Callback du bouton "Reessayer" |
308
+ | `onClose` | `() => void` | Non | Callback du bouton "Fermer" |
309
+ | `onOpenPaymentUrl` | `() => void` | Non | Callback pour ouvrir l'URL de paiement |
310
+ | `className` | `string` | Non | Classes CSS additionnelles |
311
+
312
+ ---
313
+
314
+ ## Utilitaires
315
+
316
+ ### Validation du telephone
317
+
318
+ ```ts
319
+ import { validatePhone, normalizePhone, getPhoneRegex, needsOtp } from "@applite/duticotac-react";
320
+
321
+ // Valider un numero pour un provider specifique
322
+ const error = validatePhone("0701020304", "OM_CI");
323
+ // null si valide, message d'erreur sinon
324
+
325
+ // Normaliser en format international
326
+ normalizePhone("0701020304"); // "+2250701020304"
327
+
328
+ // Obtenir la regex de validation
329
+ getPhoneRegex("OM_CI"); // /^(\+225)(07)[0-9]{8}$/
330
+ getPhoneRegex("MTN_CI"); // /^(\+225)(05)[0-9]{8}$/
331
+ getPhoneRegex("MOOV_CI");// /^(\+225)(01)[0-9]{8}$/
332
+
333
+ // Verifier si l'OTP est requis
334
+ needsOtp("OM_CI"); // true
335
+ needsOtp("MTN_CI"); // false
336
+ needsOtp("WAVE_CI"); // false
337
+ ```
338
+
339
+ ### Formatage
340
+
341
+ ```ts
342
+ import { formatCFA } from "@applite/duticotac-react";
343
+
344
+ formatCFA(5000); // "5 000"
345
+ formatCFA(1500000); // "1 500 000"
346
+ ```
347
+
348
+ ### Classes CSS
349
+
350
+ ```ts
351
+ import { cn } from "@applite/duticotac-react";
352
+
353
+ cn("px-4 py-2", isActive && "bg-primary", className);
354
+ // Merge intelligent des classes Tailwind (via clsx + tailwind-merge)
355
+ ```
356
+
357
+ ---
358
+
359
+ ## Integration avec Shadcn UI
360
+
361
+ ### Avec Shadcn Dialog
362
+
363
+ ```tsx
364
+ import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog";
365
+ import { DuticotacPaymentModal } from "@applite/duticotac-react";
366
+
367
+ <DuticotacPaymentModal
368
+ open={open}
369
+ onClose={() => setOpen(false)}
370
+ sdk={sdk}
371
+ amount={5000}
372
+ getTransactionId={getTransactionId}
373
+ renderModal={({ open, onClose, title, children }) => (
374
+ <Dialog open={open} onOpenChange={(v) => { if (!v) onClose(); }}>
375
+ <DialogContent className="sm:max-w-md">
376
+ <DialogHeader>
377
+ <DialogTitle>{title}</DialogTitle>
378
+ </DialogHeader>
379
+ {children}
380
+ </DialogContent>
381
+ </Dialog>
382
+ )}
383
+ />
384
+ ```
385
+
386
+ ### Avec Shadcn Drawer (mobile)
387
+
388
+ ```tsx
389
+ import { Drawer, DrawerContent, DrawerHeader, DrawerTitle } from "@/components/ui/drawer";
390
+
391
+ <DuticotacPaymentModal
392
+ open={open}
393
+ onClose={() => setOpen(false)}
394
+ sdk={sdk}
395
+ amount={5000}
396
+ getTransactionId={getTransactionId}
397
+ renderModal={({ open, onClose, title, children }) => (
398
+ <Drawer open={open} onOpenChange={(v) => { if (!v) onClose(); }}>
399
+ <DrawerContent>
400
+ <DrawerHeader>
401
+ <DrawerTitle>{title}</DrawerTitle>
402
+ </DrawerHeader>
403
+ <div className="px-4 pb-6">{children}</div>
404
+ </DrawerContent>
405
+ </Drawer>
406
+ )}
407
+ />
408
+ ```
409
+
410
+ ### Sans Shadcn (modal integre)
411
+
412
+ Si vous ne passez pas de `renderModal`, un modal overlay simple est utilise automatiquement. Aucune dependance externe requise.
413
+
414
+ ---
415
+
416
+ ## Providers supportes
417
+
418
+ | Code | Nom | OTP requis | Prefixe telephone | Particularite |
419
+ |------|-----|------------|-------------------|---------------|
420
+ | `OM_CI` | Orange Money | Oui (4 chiffres) | `07` | L'utilisateur fait `#144*82#` pour obtenir le code OTP |
421
+ | `MTN_CI` | MTN MoMo | Non | `05` | Confirmation sur le telephone |
422
+ | `MOOV_CI` | Moov (Flooz) | Non | `01` | Confirmation sur le telephone |
423
+ | `WAVE_CI` | Wave | Non | `07`, `05`, `01` | Ouvre un onglet navigateur pour le paiement |
424
+ | `CREDIT_CARD` | Carte de credit | Non | — | A venir |
425
+ | `CASH` | Especes | Non | — | Paiement en main propre |
426
+ | `IAP` | Achat Integre | Non | — | In-App Purchase (mobile) |
427
+
428
+ ---
429
+
430
+ ## Validation du telephone
431
+
432
+ Chaque provider a des regles de validation specifiques basees sur les prefixes telephoniques de Cote d'Ivoire :
433
+
434
+ | Provider | Regex | Exemples valides |
435
+ |----------|-------|------------------|
436
+ | Orange Money (`OM_CI`) | `+225 07 XX XX XX XX` | +225 07 01 02 03 04 |
437
+ | MTN (`MTN_CI`) | `+225 05 XX XX XX XX` | +225 05 01 02 03 04 |
438
+ | Moov (`MOOV_CI`) | `+225 01 XX XX XX XX` | +225 01 01 02 03 04 |
439
+ | Wave (`WAVE_CI`) | `+225 (07\|05\|01) XX XX XX XX` | Accepte tous les prefixes |
440
+
441
+ ---
442
+
443
+ ## Gestion des erreurs
444
+
445
+ Le modal gere automatiquement les erreurs du SDK et affiche des messages en francais :
446
+
447
+ | Code erreur | Message affiche |
448
+ |-------------|-----------------|
449
+ | `otp-required` | Code OTP requis pour les paiements Orange Money |
450
+ | `ref-or-idFromClient-required` | Reference ou IDFromClient requis |
451
+ | `payment-failed` | Echec du paiement |
452
+ | `payment-cancelled` | Paiement annule |
453
+ | `polling-timeout` | Temps d'attente de la transaction expire |
454
+ | `phone-required` | Numero de telephone requis |
455
+ | `no-api-key` | Cle API non fournie |
456
+ | `app-not-found` | Application non trouvee dans AppLite UI |
457
+ | `unknown-error` | Une erreur inconnue est survenue |
458
+
459
+ En cas d'erreur, l'utilisateur peut cliquer sur **"Reessayer"** pour revenir au formulaire.
460
+
461
+ ---
462
+
463
+ ## Comportement du polling
464
+
465
+ Apres l'appel `cashout`, le composant suit le statut de la transaction via `sdk.transaction.poll()` :
466
+
467
+ - **Backoff exponentiel** : 1s → 2s → 3s → 5s → 8s (meme strategie que le SDK Dart)
468
+ - **Timeout** : 90 secondes par defaut (configurable via `pollTimeout`)
469
+ - **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
470
+ - **Compteur** : Un indicateur "Verification en cours... 42s" est affiche
471
+ - **Wave** : Si le provider retourne une `paymentUrl`, un nouvel onglet s'ouvre automatiquement avec un bouton de fallback si le popup est bloque
472
+ - **Etats terminaux** :
473
+ - `CONFIRMED` → ecran de succes + callback `onSuccess`
474
+ - `CANCELLED` → ecran d'erreur ("Paiement annule")
475
+ - `FAILED` → ecran d'erreur ("Echec du paiement")
476
+
477
+ ---
478
+
479
+ ## API Reference complete
480
+
481
+ ### Types reimportes de `@applite/duticotac`
482
+
483
+ ```ts
484
+ import type {
485
+ PaymentProvider, // "OM_CI" | "MTN_CI" | "MOOV_CI" | "WAVE_CI" | "CREDIT_CARD" | "CASH" | "IAP"
486
+ PlatformType, // "STORE" | "TRANSPORT" | "RESTAURATION" | "MULTI_SERVICE" | "E_LEARNING" | "DUTICOTAC"
487
+ CoreApp, // "XORAIA" | "MONSMSPRO" | "FREE"
488
+ TransactionModel, // Objet transaction complet
489
+ TransactionStatus, // "PENDING" | "CONFIRMED" | "CANCELLED" | "FAILED"
490
+ } from "@applite/duticotac";
491
+ ```
492
+
493
+ ### Exports de `@applite/duticotac-react`
494
+
495
+ ```ts
496
+ // Composants
497
+ export { DuticotacPaymentModal } from "@applite/duticotac-react";
498
+ export { ProviderSelector } from "@applite/duticotac-react";
499
+ export { PhoneInput } from "@applite/duticotac-react";
500
+ export { OtpInput } from "@applite/duticotac-react";
501
+ export { TransactionStatus } from "@applite/duticotac-react";
502
+
503
+ // Types des props
504
+ export type { DuticotacPaymentModalProps } from "@applite/duticotac-react";
505
+ export type { ProviderSelectorProps } from "@applite/duticotac-react";
506
+ export type { PhoneInputProps } from "@applite/duticotac-react";
507
+ export type { OtpInputProps } from "@applite/duticotac-react";
508
+ export type { TransactionStatusProps } from "@applite/duticotac-react";
509
+
510
+ // Utilitaires
511
+ export { cn, formatCFA, validatePhone, normalizePhone, getPhoneRegex, needsOtp } from "@applite/duticotac-react";
512
+ ```
513
+
514
+ ---
515
+
516
+ ## Exemples avances
517
+
518
+ ### Avec un backend personnalise
519
+
520
+ ```tsx
521
+ function PaymentPage({ offer, apiKey }: { offer: Offer; apiKey: string }) {
522
+ const [open, setOpen] = useState(false);
523
+ const sdk = useMemo(() => new DuticotacSDK({ apiKey }), [apiKey]);
524
+
525
+ const getTransactionId = async () => {
526
+ const res = await fetch("/api/payments/init", {
527
+ method: "POST",
528
+ headers: { "Content-Type": "application/json" },
529
+ body: JSON.stringify({ offerId: offer.id }),
530
+ });
531
+ const { transactionId } = await res.json();
532
+ return transactionId;
533
+ };
534
+
535
+ return (
536
+ <DuticotacPaymentModal
537
+ open={open}
538
+ onClose={() => setOpen(false)}
539
+ sdk={sdk}
540
+ amount={offer.price}
541
+ getTransactionId={getTransactionId}
542
+ productReference={offer.ref}
543
+ providers={["OM_CI", "MTN_CI", "WAVE_CI"]}
544
+ pollTimeout={120_000}
545
+ onSuccess={(tx) => {
546
+ toast.success(`Paiement confirme ! Ref: ${tx.ref}`);
547
+ router.push("/dashboard");
548
+ }}
549
+ />
550
+ );
551
+ }
552
+ ```
553
+
554
+ ### Composants individuels (sans le modal)
555
+
556
+ Vous pouvez utiliser les composants independamment pour construire votre propre flux :
557
+
558
+ ```tsx
559
+ import {
560
+ ProviderSelector,
561
+ PhoneInput,
562
+ OtpInput,
563
+ TransactionStatus,
564
+ needsOtp,
565
+ validatePhone,
566
+ } from "@applite/duticotac-react";
567
+
568
+ function CustomPaymentForm() {
569
+ const [provider, setProvider] = useState<PaymentProvider>("OM_CI");
570
+ const [phone, setPhone] = useState("");
571
+ const [otp, setOtp] = useState("");
572
+
573
+ return (
574
+ <form>
575
+ <ProviderSelector
576
+ providers={["OM_CI", "MTN_CI", "WAVE_CI"]}
577
+ value={provider}
578
+ onChange={(p) => {
579
+ setProvider(p);
580
+ if (!needsOtp(p)) setOtp("");
581
+ }}
582
+ />
583
+
584
+ <PhoneInput
585
+ value={phone}
586
+ onChange={setPhone}
587
+ error={validatePhone(phone, provider)}
588
+ />
589
+
590
+ {needsOtp(provider) && (
591
+ <OtpInput value={otp} onChange={setOtp} />
592
+ )}
593
+
594
+ <button type="submit">Payer</button>
595
+ </form>
596
+ );
597
+ }
598
+ ```
599
+
600
+ ### Personnalisation du style
601
+
602
+ Tous les composants acceptent une prop `className` pour ajouter des classes Tailwind :
603
+
604
+ ```tsx
605
+ <ProviderSelector
606
+ providers={providers}
607
+ value={selected}
608
+ onChange={setSelected}
609
+ className="gap-4 grid-cols-2" // Override la grille
610
+ />
611
+
612
+ <PhoneInput
613
+ value={phone}
614
+ onChange={setPhone}
615
+ className="mb-6" // Espacement custom
616
+ />
617
+ ```
618
+
619
+ ---
620
+
621
+ ## Scripts
622
+
623
+ | Commande | Description |
624
+ |----------|-------------|
625
+ | `pnpm build` | Build CJS + ESM avec declarations de types |
626
+ | `pnpm dev` | Mode watch pour le developpement |
627
+ | `pnpm type-check` | Verification des types TypeScript |
628
+ | `pnpm lint` | Lint du code source |