@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/dist/index.js ADDED
@@ -0,0 +1,720 @@
1
+ "use client";
2
+ "use strict";
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __export = (target, all) => {
10
+ for (var name in all)
11
+ __defProp(target, name, { get: all[name], enumerable: true });
12
+ };
13
+ var __copyProps = (to, from, except, desc) => {
14
+ if (from && typeof from === "object" || typeof from === "function") {
15
+ for (let key of __getOwnPropNames(from))
16
+ if (!__hasOwnProp.call(to, key) && key !== except)
17
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
18
+ }
19
+ return to;
20
+ };
21
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
22
+ // If the importer is in node compatibility mode or this is not an ESM
23
+ // file that has been converted to a CommonJS file using a Babel-
24
+ // compatible transform (i.e. "__esModule" has not been set), then set
25
+ // "default" to the CommonJS "module.exports" for node compatibility.
26
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
27
+ mod
28
+ ));
29
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
30
+
31
+ // src/index.ts
32
+ var index_exports = {};
33
+ __export(index_exports, {
34
+ DuticotacPaymentModal: () => DuticotacPaymentModal,
35
+ OtpInput: () => OtpInput,
36
+ PhoneInput: () => PhoneInput,
37
+ ProviderSelector: () => ProviderSelector,
38
+ TransactionStatus: () => TransactionStatus,
39
+ cn: () => cn,
40
+ formatCFA: () => formatCFA,
41
+ getPhoneRegex: () => getPhoneRegex,
42
+ needsOtp: () => needsOtp,
43
+ normalizePhone: () => normalizePhone,
44
+ validatePhone: () => validatePhone
45
+ });
46
+ module.exports = __toCommonJS(index_exports);
47
+
48
+ // src/components/payment-modal.tsx
49
+ var React2 = __toESM(require("react"));
50
+ var import_duticotac2 = require("@applite/duticotac");
51
+
52
+ // src/components/provider-selector.tsx
53
+ var import_duticotac = require("@applite/duticotac");
54
+
55
+ // src/utils/cn.ts
56
+ var import_clsx = require("clsx");
57
+ var import_tailwind_merge = require("tailwind-merge");
58
+ function cn(...inputs) {
59
+ return (0, import_tailwind_merge.twMerge)((0, import_clsx.clsx)(inputs));
60
+ }
61
+
62
+ // src/components/provider-selector.tsx
63
+ var import_jsx_runtime = require("react/jsx-runtime");
64
+ function ProviderSelector({
65
+ providers,
66
+ value,
67
+ onChange,
68
+ disabled,
69
+ className
70
+ }) {
71
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: cn("grid gap-2", className), style: {
72
+ gridTemplateColumns: `repeat(${Math.min(providers.length, 4)}, 1fr)`
73
+ }, children: providers.map((provider) => {
74
+ const selected = value === provider;
75
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
76
+ "button",
77
+ {
78
+ type: "button",
79
+ disabled,
80
+ onClick: () => onChange(provider),
81
+ className: cn(
82
+ "relative flex flex-col items-center gap-2 rounded-lg border-2 p-3 transition-all",
83
+ "hover:scale-[1.02] disabled:pointer-events-none disabled:opacity-50",
84
+ selected ? "border-primary bg-primary/5 shadow-sm" : "border-border bg-background hover:border-primary/40"
85
+ ),
86
+ children: [
87
+ selected && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "absolute -right-1.5 -top-1.5 flex h-5 w-5 items-center justify-center rounded-full bg-primary", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { width: "12", height: "12", viewBox: "0 0 24 24", fill: "none", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
88
+ "path",
89
+ {
90
+ d: "M5 12l5 5L20 7",
91
+ stroke: "#fff",
92
+ strokeWidth: "3",
93
+ strokeLinecap: "round",
94
+ strokeLinejoin: "round"
95
+ }
96
+ ) }) }),
97
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
98
+ "img",
99
+ {
100
+ src: (0, import_duticotac.getProviderLogo)(provider),
101
+ alt: (0, import_duticotac.getProviderName)(provider),
102
+ width: 44,
103
+ height: 44,
104
+ className: "rounded-lg object-contain"
105
+ }
106
+ ),
107
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
108
+ "span",
109
+ {
110
+ className: cn(
111
+ "text-center text-xs leading-tight",
112
+ selected ? "font-semibold text-primary" : "font-medium text-muted-foreground"
113
+ ),
114
+ children: (0, import_duticotac.getProviderName)(provider)
115
+ }
116
+ )
117
+ ]
118
+ },
119
+ provider
120
+ );
121
+ }) });
122
+ }
123
+
124
+ // src/components/phone-input.tsx
125
+ var import_jsx_runtime2 = require("react/jsx-runtime");
126
+ function PhoneInput({
127
+ value,
128
+ onChange,
129
+ label = "Numero de telephone",
130
+ placeholder = "07 01 02 03 04",
131
+ error,
132
+ disabled,
133
+ className,
134
+ autoFocus
135
+ }) {
136
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: cn("space-y-1.5", className), children: [
137
+ label && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("label", { className: "block text-sm font-semibold text-foreground", children: label }),
138
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex items-center gap-2", children: [
139
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "flex h-10 items-center rounded-md border border-border bg-muted px-3 text-sm font-medium text-muted-foreground", children: "+225" }),
140
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
141
+ "input",
142
+ {
143
+ type: "tel",
144
+ inputMode: "numeric",
145
+ value,
146
+ onChange: (e) => {
147
+ const cleaned = e.target.value.replace(/[^\d\s]/g, "");
148
+ onChange(cleaned);
149
+ },
150
+ placeholder,
151
+ disabled,
152
+ autoFocus,
153
+ className: cn(
154
+ "h-10 w-full rounded-md border bg-background px-3 text-sm font-medium outline-none transition-colors",
155
+ "placeholder:text-muted-foreground/50",
156
+ "focus:border-primary focus:ring-2 focus:ring-primary/20",
157
+ "disabled:cursor-not-allowed disabled:opacity-50",
158
+ error ? "border-destructive" : "border-border"
159
+ )
160
+ }
161
+ )
162
+ ] }),
163
+ error && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "text-xs font-medium text-destructive", children: error })
164
+ ] });
165
+ }
166
+
167
+ // src/components/otp-input.tsx
168
+ var React = __toESM(require("react"));
169
+ var import_jsx_runtime3 = require("react/jsx-runtime");
170
+ function OtpInput({
171
+ value,
172
+ onChange,
173
+ length = 4,
174
+ disabled,
175
+ className
176
+ }) {
177
+ const inputRefs = React.useRef([]);
178
+ const digits = value.padEnd(length, "").slice(0, length).split("");
179
+ const setDigit = (index, char) => {
180
+ const next = [...digits];
181
+ next[index] = char;
182
+ onChange(next.join(""));
183
+ };
184
+ const handleKeyDown = (index, e) => {
185
+ if (e.key === "Backspace") {
186
+ e.preventDefault();
187
+ if (digits[index]?.trim()) {
188
+ setDigit(index, "");
189
+ } else if (index > 0) {
190
+ setDigit(index - 1, "");
191
+ inputRefs.current[index - 1]?.focus();
192
+ }
193
+ } else if (e.key === "ArrowLeft" && index > 0) {
194
+ inputRefs.current[index - 1]?.focus();
195
+ } else if (e.key === "ArrowRight" && index < length - 1) {
196
+ inputRefs.current[index + 1]?.focus();
197
+ }
198
+ };
199
+ const handleInput = (index, e) => {
200
+ const val = e.target.value;
201
+ const char = val.replace(/\D/g, "").slice(-1);
202
+ if (!char) return;
203
+ setDigit(index, char);
204
+ if (index < length - 1) {
205
+ inputRefs.current[index + 1]?.focus();
206
+ }
207
+ };
208
+ const handlePaste = (e) => {
209
+ e.preventDefault();
210
+ const pasted = e.clipboardData.getData("text").replace(/\D/g, "").slice(0, length);
211
+ if (!pasted) return;
212
+ onChange(pasted.padEnd(length, "").slice(0, length).replace(/ /g, ""));
213
+ const focusIndex = Math.min(pasted.length, length - 1);
214
+ inputRefs.current[focusIndex]?.focus();
215
+ };
216
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: cn("space-y-2", className), children: [
217
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("label", { className: "block text-sm font-semibold text-muted-foreground", children: "Code OTP" }),
218
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "flex justify-center gap-3", onPaste: handlePaste, children: Array.from({ length }).map((_, i) => {
219
+ const filled = !!digits[i]?.trim();
220
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
221
+ "input",
222
+ {
223
+ ref: (el) => {
224
+ inputRefs.current[i] = el;
225
+ },
226
+ type: "text",
227
+ inputMode: "numeric",
228
+ maxLength: 1,
229
+ value: digits[i]?.trim() || "",
230
+ onKeyDown: (e) => handleKeyDown(i, e),
231
+ onInput: (e) => handleInput(i, e),
232
+ onFocus: (e) => e.target.select(),
233
+ disabled,
234
+ className: cn(
235
+ "h-13 w-13 rounded-md border-2 text-center text-xl font-bold outline-none transition-all",
236
+ "focus:border-primary focus:ring-2 focus:ring-primary/20 focus:scale-105",
237
+ "disabled:cursor-not-allowed disabled:opacity-50",
238
+ filled ? "border-primary/30 bg-primary/5" : "border-border bg-background"
239
+ )
240
+ },
241
+ i
242
+ );
243
+ }) }),
244
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("p", { className: "text-center text-xs text-muted-foreground", children: [
245
+ "Faites ",
246
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("strong", { className: "text-foreground", children: "#144*82#" }),
247
+ " pour recevoir le code"
248
+ ] })
249
+ ] });
250
+ }
251
+
252
+ // src/components/transaction-status.tsx
253
+ var import_jsx_runtime4 = require("react/jsx-runtime");
254
+ function Spinner({ className }) {
255
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
256
+ "svg",
257
+ {
258
+ className: cn("animate-spin", className),
259
+ width: "56",
260
+ height: "56",
261
+ viewBox: "0 0 56 56",
262
+ fill: "none",
263
+ children: [
264
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
265
+ "circle",
266
+ {
267
+ cx: "28",
268
+ cy: "28",
269
+ r: "24",
270
+ stroke: "currentColor",
271
+ strokeWidth: "4",
272
+ className: "opacity-20"
273
+ }
274
+ ),
275
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
276
+ "path",
277
+ {
278
+ d: "M28 4a24 24 0 0 1 24 24",
279
+ stroke: "currentColor",
280
+ strokeWidth: "4",
281
+ strokeLinecap: "round",
282
+ className: "text-primary"
283
+ }
284
+ )
285
+ ]
286
+ }
287
+ );
288
+ }
289
+ function TransactionStatus({
290
+ status,
291
+ elapsedSeconds = 0,
292
+ message,
293
+ errorMessage,
294
+ paymentUrl,
295
+ onRetry,
296
+ onClose,
297
+ onOpenPaymentUrl,
298
+ className
299
+ }) {
300
+ if (status === "polling") {
301
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: cn("flex flex-col items-center gap-4 py-8", className), children: [
302
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Spinner, {}),
303
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "space-y-2 text-center", children: [
304
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "text-base font-semibold text-foreground", children: paymentUrl ? "Finalisez le paiement dans l'onglet ouvert..." : "Confirmez le paiement sur votre telephone..." }),
305
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "text-sm text-muted-foreground", children: paymentUrl ? "Completez le paiement dans l'onglet, puis revenez ici." : "Veuillez valider la transaction sur votre telephone." }),
306
+ elapsedSeconds > 0 && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("p", { className: "text-xs text-muted-foreground/70", children: [
307
+ "Verification en cours... ",
308
+ elapsedSeconds,
309
+ "s"
310
+ ] })
311
+ ] }),
312
+ paymentUrl && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "flex flex-col items-center gap-3", children: [
313
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
314
+ "button",
315
+ {
316
+ type: "button",
317
+ onClick: onOpenPaymentUrl,
318
+ className: "inline-flex items-center gap-2 rounded-md bg-primary px-6 py-2.5 text-sm font-semibold text-primary-foreground transition-opacity hover:opacity-90",
319
+ children: [
320
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
321
+ "path",
322
+ {
323
+ d: "M18 13v6a2 2 0 01-2 2H5a2 2 0 01-2-2V8a2 2 0 012-2h6M15 3h6v6M10 14L21 3",
324
+ stroke: "currentColor",
325
+ strokeWidth: "2",
326
+ strokeLinecap: "round",
327
+ strokeLinejoin: "round"
328
+ }
329
+ ) }),
330
+ "Ouvrir Wave pour payer"
331
+ ]
332
+ }
333
+ ),
334
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
335
+ "button",
336
+ {
337
+ type: "button",
338
+ onClick: onClose,
339
+ className: "text-sm text-muted-foreground underline hover:text-foreground",
340
+ children: "Annuler"
341
+ }
342
+ )
343
+ ] })
344
+ ] });
345
+ }
346
+ if (status === "success") {
347
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: cn("flex flex-col items-center gap-4 py-8", className), children: [
348
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "flex h-16 w-16 items-center justify-center rounded-full bg-emerald-500 animate-in zoom-in duration-500", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("svg", { width: "32", height: "32", viewBox: "0 0 24 24", fill: "none", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
349
+ "path",
350
+ {
351
+ d: "M5 12l5 5L20 7",
352
+ stroke: "#fff",
353
+ strokeWidth: "3",
354
+ strokeLinecap: "round",
355
+ strokeLinejoin: "round"
356
+ }
357
+ ) }) }),
358
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "space-y-2 text-center animate-in fade-in slide-in-from-bottom-4 duration-400 delay-150", children: [
359
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "text-lg font-bold text-foreground", children: "Paiement reussi !" }),
360
+ message && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "text-sm text-muted-foreground", children: message })
361
+ ] }),
362
+ onClose && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
363
+ "button",
364
+ {
365
+ type: "button",
366
+ onClick: onClose,
367
+ className: "rounded-md bg-primary px-6 py-2.5 text-sm font-semibold text-primary-foreground transition-opacity hover:opacity-90",
368
+ children: "Fermer"
369
+ }
370
+ )
371
+ ] });
372
+ }
373
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: cn("flex flex-col items-center gap-4 py-8", className), children: [
374
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "flex h-16 w-16 items-center justify-center rounded-full bg-red-500 animate-in zoom-in duration-500", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("svg", { width: "32", height: "32", viewBox: "0 0 24 24", fill: "none", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
375
+ "path",
376
+ {
377
+ d: "M6 18L18 6M6 6l12 12",
378
+ stroke: "#fff",
379
+ strokeWidth: "3",
380
+ strokeLinecap: "round"
381
+ }
382
+ ) }) }),
383
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "space-y-2 text-center animate-in fade-in slide-in-from-bottom-4 duration-400 delay-150", children: [
384
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "text-lg font-bold text-foreground", children: "Echec du paiement" }),
385
+ errorMessage && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "text-sm text-muted-foreground", children: errorMessage })
386
+ ] }),
387
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "flex gap-3", children: [
388
+ onClose && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
389
+ "button",
390
+ {
391
+ type: "button",
392
+ onClick: onClose,
393
+ className: "rounded-md border border-border bg-background px-5 py-2.5 text-sm font-semibold text-foreground transition-colors hover:bg-muted",
394
+ children: "Fermer"
395
+ }
396
+ ),
397
+ onRetry && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
398
+ "button",
399
+ {
400
+ type: "button",
401
+ onClick: onRetry,
402
+ className: "rounded-md bg-primary px-5 py-2.5 text-sm font-semibold text-primary-foreground transition-opacity hover:opacity-90",
403
+ children: "Reessayer"
404
+ }
405
+ )
406
+ ] })
407
+ ] });
408
+ }
409
+
410
+ // src/utils/format.ts
411
+ function formatCFA(n) {
412
+ return new Intl.NumberFormat("fr-FR").format(n);
413
+ }
414
+
415
+ // src/utils/validation.ts
416
+ function getPhoneRegex(provider) {
417
+ switch (provider) {
418
+ case "OM_CI":
419
+ return /^(\+225)(07)[0-9]{8}$/;
420
+ case "MTN_CI":
421
+ return /^(\+225)(05)[0-9]{8}$/;
422
+ case "MOOV_CI":
423
+ return /^(\+225)(01)[0-9]{8}$/;
424
+ default:
425
+ return /^(\+225)(07|05|01)[0-9]{8}$/;
426
+ }
427
+ }
428
+ function validatePhone(phone, provider) {
429
+ if (!phone) return "Num\xE9ro de t\xE9l\xE9phone requis";
430
+ const intl = phone.startsWith("+225") ? phone : `+225${phone}`;
431
+ const cleaned = intl.replace(/\s/g, "");
432
+ if (!getPhoneRegex(provider).test(cleaned)) {
433
+ return "Num\xE9ro invalide pour ce provider";
434
+ }
435
+ return null;
436
+ }
437
+ function normalizePhone(phone) {
438
+ const cleaned = phone.replace(/\s/g, "");
439
+ return cleaned.startsWith("+225") ? cleaned : `+225${cleaned}`;
440
+ }
441
+ function needsOtp(provider) {
442
+ return provider === "OM_CI";
443
+ }
444
+
445
+ // src/components/payment-modal.tsx
446
+ var import_jsx_runtime5 = require("react/jsx-runtime");
447
+ var DEFAULT_PROVIDERS = [
448
+ "OM_CI",
449
+ "MTN_CI",
450
+ "MOOV_CI",
451
+ "WAVE_CI"
452
+ ];
453
+ function DuticotacPaymentModal({
454
+ open,
455
+ onClose,
456
+ onSuccess,
457
+ sdk,
458
+ amount,
459
+ providers = DEFAULT_PROVIDERS,
460
+ getTransactionId,
461
+ productReference,
462
+ initialPhone = "",
463
+ name: customerName,
464
+ email: customerEmail,
465
+ customerId,
466
+ kolaboReference,
467
+ app,
468
+ platform,
469
+ successMessage,
470
+ title = "Paiement",
471
+ pollTimeout = 9e4,
472
+ className,
473
+ renderModal
474
+ }) {
475
+ const [step, setStep] = React2.useState("form");
476
+ const [provider, setProvider] = React2.useState(providers[0]);
477
+ const [phone, setPhone] = React2.useState(initialPhone);
478
+ const [otp, setOtp] = React2.useState("");
479
+ const [phoneError, setPhoneError] = React2.useState(null);
480
+ const [errorMessage, setErrorMessage] = React2.useState("");
481
+ const [paymentUrl, setPaymentUrl] = React2.useState(null);
482
+ const [elapsedSeconds, setElapsedSeconds] = React2.useState(0);
483
+ const [lastTransaction, setLastTransaction] = React2.useState(null);
484
+ const abortRef = React2.useRef(null);
485
+ const elapsedRef = React2.useRef(null);
486
+ const cleanup = React2.useCallback(() => {
487
+ abortRef.current?.abort();
488
+ abortRef.current = null;
489
+ if (elapsedRef.current) {
490
+ clearInterval(elapsedRef.current);
491
+ elapsedRef.current = null;
492
+ }
493
+ }, []);
494
+ React2.useEffect(() => {
495
+ if (open) {
496
+ setStep("form");
497
+ setErrorMessage("");
498
+ setOtp("");
499
+ setPaymentUrl(null);
500
+ setElapsedSeconds(0);
501
+ setPhoneError(null);
502
+ setLastTransaction(null);
503
+ } else {
504
+ cleanup();
505
+ }
506
+ }, [open, cleanup]);
507
+ React2.useEffect(() => () => cleanup(), [cleanup]);
508
+ const isFormValid = phone.replace(/\s/g, "").length >= 8 && (needsOtp(provider) ? otp.length === 4 : true);
509
+ const handleSubmit = async () => {
510
+ const phoneErr = validatePhone(phone, provider);
511
+ if (phoneErr) {
512
+ setPhoneError(phoneErr);
513
+ return;
514
+ }
515
+ setPhoneError(null);
516
+ setStep("confirming");
517
+ setErrorMessage("");
518
+ cleanup();
519
+ const controller = new AbortController();
520
+ abortRef.current = controller;
521
+ try {
522
+ const transactionId = await getTransactionId();
523
+ const result = await sdk.mobileMoney.cashout({
524
+ transactionId,
525
+ ref: productReference,
526
+ phone: normalizePhone(phone),
527
+ name: customerName ?? "Duticotac App",
528
+ email: customerEmail ?? "",
529
+ paymentMethod: provider,
530
+ amount,
531
+ otp: needsOtp(provider) ? otp : void 0,
532
+ customerId,
533
+ kolaboReference,
534
+ app,
535
+ plateform: platform
536
+ });
537
+ const tx = result.data;
538
+ if (tx?.paymentUrl) {
539
+ setPaymentUrl(tx.paymentUrl);
540
+ try {
541
+ window.open(tx.paymentUrl, "_blank");
542
+ } catch {
543
+ }
544
+ }
545
+ const startTime = Date.now();
546
+ setElapsedSeconds(0);
547
+ elapsedRef.current = setInterval(() => {
548
+ setElapsedSeconds(Math.floor((Date.now() - startTime) / 1e3));
549
+ }, 1e3);
550
+ const confirmed = await sdk.transaction.poll(tx.id ?? transactionId, {
551
+ timeoutMs: pollTimeout,
552
+ signal: controller.signal,
553
+ onPoll: () => {
554
+ }
555
+ });
556
+ setLastTransaction(confirmed.data);
557
+ setStep("success");
558
+ onSuccess?.(confirmed.data);
559
+ } catch (e) {
560
+ if (controller.signal.aborted) return;
561
+ if (e instanceof import_duticotac2.DuticotacError) {
562
+ setErrorMessage(e.message);
563
+ } else {
564
+ setErrorMessage(
565
+ (0, import_duticotac2.getErrorMessage)("unknown-error")
566
+ );
567
+ }
568
+ setStep("error");
569
+ }
570
+ };
571
+ const handleRetry = () => {
572
+ setStep("form");
573
+ setErrorMessage("");
574
+ setPaymentUrl(null);
575
+ setElapsedSeconds(0);
576
+ };
577
+ const content = /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: cn("space-y-5", className), children: [
578
+ step === "form" && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
579
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "rounded-lg border-l-4 border-l-primary bg-muted/50 p-4", children: [
580
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "text-xs text-muted-foreground", children: "Montant" }),
581
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("p", { className: "text-xl font-bold text-primary", children: [
582
+ formatCFA(amount),
583
+ " FCFA"
584
+ ] })
585
+ ] }),
586
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "space-y-2", children: [
587
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "text-sm font-semibold text-foreground", children: "Moyen de paiement" }),
588
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
589
+ ProviderSelector,
590
+ {
591
+ providers,
592
+ value: provider,
593
+ onChange: (p) => {
594
+ setProvider(p);
595
+ if (!needsOtp(p)) setOtp("");
596
+ }
597
+ }
598
+ )
599
+ ] }),
600
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
601
+ PhoneInput,
602
+ {
603
+ value: phone,
604
+ onChange: (v) => {
605
+ setPhone(v);
606
+ setPhoneError(null);
607
+ },
608
+ error: phoneError,
609
+ autoFocus: true
610
+ }
611
+ ),
612
+ needsOtp(provider) && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(OtpInput, { value: otp, onChange: setOtp }),
613
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "flex justify-end gap-3 pt-2", children: [
614
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
615
+ "button",
616
+ {
617
+ type: "button",
618
+ onClick: onClose,
619
+ className: "rounded-md border border-border bg-background px-5 py-2.5 text-sm font-semibold text-foreground transition-colors hover:bg-muted",
620
+ children: "Annuler"
621
+ }
622
+ ),
623
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
624
+ "button",
625
+ {
626
+ type: "button",
627
+ onClick: handleSubmit,
628
+ disabled: !isFormValid,
629
+ className: "rounded-md bg-primary px-5 py-2.5 text-sm font-semibold text-primary-foreground transition-opacity hover:opacity-90 disabled:pointer-events-none disabled:opacity-50",
630
+ children: [
631
+ "Payer ",
632
+ formatCFA(amount),
633
+ " FCFA"
634
+ ]
635
+ }
636
+ )
637
+ ] })
638
+ ] }),
639
+ step === "confirming" && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
640
+ TransactionStatus,
641
+ {
642
+ status: "polling",
643
+ elapsedSeconds,
644
+ paymentUrl,
645
+ onOpenPaymentUrl: () => {
646
+ if (paymentUrl) window.open(paymentUrl, "_blank");
647
+ },
648
+ onClose
649
+ }
650
+ ),
651
+ step === "success" && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
652
+ TransactionStatus,
653
+ {
654
+ status: "success",
655
+ message: successMessage ?? "Le paiement a ete effectue avec succes.",
656
+ onClose
657
+ }
658
+ ),
659
+ step === "error" && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
660
+ TransactionStatus,
661
+ {
662
+ status: "error",
663
+ errorMessage,
664
+ onRetry: handleRetry,
665
+ onClose
666
+ }
667
+ )
668
+ ] });
669
+ if (renderModal) {
670
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_jsx_runtime5.Fragment, { children: renderModal({ open, onClose, title, children: content }) });
671
+ }
672
+ if (!open) return null;
673
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "fixed inset-0 z-50 flex items-center justify-center", children: [
674
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
675
+ "div",
676
+ {
677
+ className: "absolute inset-0 bg-black/50",
678
+ onClick: onClose
679
+ }
680
+ ),
681
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "relative z-10 w-full max-w-md rounded-xl border border-border bg-background p-6 shadow-xl", children: [
682
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "mb-4 flex items-center justify-between", children: [
683
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("h2", { className: "text-lg font-bold text-foreground", children: title }),
684
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
685
+ "button",
686
+ {
687
+ type: "button",
688
+ onClick: onClose,
689
+ className: "rounded-md p-1 text-muted-foreground hover:bg-muted hover:text-foreground",
690
+ children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
691
+ "path",
692
+ {
693
+ d: "M6 18L18 6M6 6l12 12",
694
+ stroke: "currentColor",
695
+ strokeWidth: "2",
696
+ strokeLinecap: "round"
697
+ }
698
+ ) })
699
+ }
700
+ )
701
+ ] }),
702
+ content
703
+ ] })
704
+ ] });
705
+ }
706
+ // Annotate the CommonJS export names for ESM import in node:
707
+ 0 && (module.exports = {
708
+ DuticotacPaymentModal,
709
+ OtpInput,
710
+ PhoneInput,
711
+ ProviderSelector,
712
+ TransactionStatus,
713
+ cn,
714
+ formatCFA,
715
+ getPhoneRegex,
716
+ needsOtp,
717
+ normalizePhone,
718
+ validatePhone
719
+ });
720
+ //# sourceMappingURL=index.js.map