fairsure-checkout-sdk 1.0.0

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.
Files changed (40) hide show
  1. package/dist/components/CardPayment.d.ts +18 -0
  2. package/dist/components/CardPayment.js +302 -0
  3. package/dist/components/Checkout.d.ts +9 -0
  4. package/dist/components/Checkout.js +520 -0
  5. package/dist/components/OTPInput.d.ts +17 -0
  6. package/dist/components/OTPInput.js +132 -0
  7. package/dist/components/PaymentMethodSelector.d.ts +12 -0
  8. package/dist/components/PaymentMethodSelector.js +67 -0
  9. package/dist/components/PaystackCheckout.d.ts +13 -0
  10. package/dist/components/PaystackCheckout.js +385 -0
  11. package/dist/components/PurchaseModal.d.ts +17 -0
  12. package/dist/components/PurchaseModal.js +415 -0
  13. package/dist/components/TransferPayment.d.ts +16 -0
  14. package/dist/components/TransferPayment.js +311 -0
  15. package/dist/components/USSDPayment.d.ts +21 -0
  16. package/dist/components/USSDPayment.js +316 -0
  17. package/dist/components/usePaymentCheckout.d.ts +61 -0
  18. package/dist/components/usePaymentCheckout.js +398 -0
  19. package/dist/context/CheckoutProvider.d.ts +5 -0
  20. package/dist/context/CheckoutProvider.js +16 -0
  21. package/dist/hooks/useCheckout.d.ts +1 -0
  22. package/dist/hooks/useCheckout.js +8 -0
  23. package/dist/index.d.ts +2 -0
  24. package/dist/index.js +2 -0
  25. package/dist/types.d.ts +9 -0
  26. package/dist/types.js +1 -0
  27. package/fairsure-checkout-sdk-1.0.0.tgz +0 -0
  28. package/package.json +24 -0
  29. package/src/components/CardPayment.tsx +386 -0
  30. package/src/components/OTPInput.tsx +204 -0
  31. package/src/components/PaymentMethodSelector.tsx +99 -0
  32. package/src/components/PaystackCheckout.tsx +506 -0
  33. package/src/components/TransferPayment.tsx +372 -0
  34. package/src/components/USSDPayment.tsx +422 -0
  35. package/src/components/usePaymentCheckout.ts +441 -0
  36. package/src/context/CheckoutProvider.tsx +37 -0
  37. package/src/hooks/useCheckout.ts +11 -0
  38. package/src/index.ts +2 -0
  39. package/src/types.ts +9 -0
  40. package/tsconfig.json +22 -0
@@ -0,0 +1,520 @@
1
+ // components/PaystackCheckout.tsx
2
+ import { Ionicons } from "@expo/vector-icons";
3
+ import * as Clipboard from "expo-clipboard";
4
+ import React, { useEffect, useState } from "react";
5
+ import { Alert, Modal, ScrollView, StyleSheet, Text, TextInput, TouchableOpacity, View, } from "react-native";
6
+ export default function Checkout({ amount, merchantName = "Merchant", onClose, onSuccess, }) {
7
+ var _a;
8
+ const [selectedMethod, setSelectedMethod] = useState("card");
9
+ const [cardNumber, setCardNumber] = useState("");
10
+ const [expiryDate, setExpiryDate] = useState("");
11
+ const [cvv, setCvv] = useState("");
12
+ const [selectedBank, setSelectedBank] = useState("");
13
+ const [showBankDropdown, setShowBankDropdown] = useState(false);
14
+ const [accountNumber] = useState("1234567890");
15
+ const [copied, setCopied] = useState(false);
16
+ useEffect(() => {
17
+ console.log("method", selectedMethod);
18
+ }, [selectedMethod]);
19
+ const paymentMethods = [
20
+ { id: "card", name: "Card", icon: "card-outline" },
21
+ { id: "transfer", name: "Transfer", icon: "business-outline" },
22
+ { id: "ussd", name: "USSD", icon: "phone-portrait-outline" },
23
+ ];
24
+ const ussdBanks = [
25
+ { code: "gtb", name: "GTBank", ussdCode: `*737*50*${amount}*159#` },
26
+ { code: "access", name: "Access Bank", ussdCode: `*901*${amount}*159#` },
27
+ { code: "zenith", name: "Zenith Bank", ussdCode: `*966*${amount}*159#` },
28
+ { code: "uba", name: "UBA", ussdCode: `*919*${amount}*159#` },
29
+ { code: "first", name: "First Bank", ussdCode: `*894*${amount}*159#` },
30
+ {
31
+ code: "fidelity",
32
+ name: "Fidelity Bank",
33
+ ussdCode: `*770*${amount}*159#`,
34
+ },
35
+ ];
36
+ const formatAmount = (value) => {
37
+ return value.toLocaleString("en-NG", {
38
+ minimumFractionDigits: 2,
39
+ maximumFractionDigits: 2,
40
+ });
41
+ };
42
+ const formatCardNumber = (value) => {
43
+ const v = value.replace(/\s+/g, "").replace(/[^0-9]/gi, "");
44
+ const matches = v.match(/\d{4,16}/g);
45
+ const match = (matches && matches[0]) || "";
46
+ const parts = [];
47
+ for (let i = 0; i < match.length; i += 4) {
48
+ parts.push(match.substring(i, i + 4));
49
+ }
50
+ return parts.length ? parts.join(" ") : value;
51
+ };
52
+ const formatExpiry = (value) => {
53
+ const v = value.replace(/\s+/g, "").replace(/[^0-9]/gi, "");
54
+ if (v.length >= 2) {
55
+ return v.slice(0, 2) + "/" + v.slice(2, 4);
56
+ }
57
+ return v;
58
+ };
59
+ const handleCopy = async (text) => {
60
+ await Clipboard.setStringAsync(text);
61
+ setCopied(true);
62
+ setTimeout(() => setCopied(false), 2000);
63
+ };
64
+ const handlePay = () => {
65
+ const reference = `ref_${Date.now()}`;
66
+ Alert.alert("Payment Successful", `Your payment of ₦${formatAmount(amount)} has been processed successfully.`, [
67
+ {
68
+ text: "OK",
69
+ onPress: () => {
70
+ if (onSuccess)
71
+ onSuccess(reference);
72
+ onClose();
73
+ },
74
+ },
75
+ ]);
76
+ };
77
+ const handleClose = () => {
78
+ setCardNumber("");
79
+ setExpiryDate("");
80
+ setCvv("");
81
+ setSelectedBank("");
82
+ setSelectedMethod("card");
83
+ onClose();
84
+ };
85
+ return (<Modal visible transparent animationType="slide" onRequestClose={handleClose}>
86
+ <View style={styles.overlay}>
87
+ <View style={styles.modalContainer}>
88
+ {/* Header */}
89
+ <View style={styles.header}>
90
+ <View style={styles.headerTop}>
91
+ <View>
92
+ <Text style={styles.headerTitle}>Pay {merchantName}</Text>
93
+ <Text style={styles.headerSubtitle}>Complete your payment</Text>
94
+ </View>
95
+ <TouchableOpacity onPress={handleClose} style={styles.closeBtn}>
96
+ <Ionicons name="close" size={24} color="white"/>
97
+ </TouchableOpacity>
98
+ </View>
99
+
100
+ <View style={styles.amountBox}>
101
+ <Text style={styles.amountLabel}>Amount to pay</Text>
102
+ <Text style={styles.amountText}>₦{formatAmount(amount)}</Text>
103
+ </View>
104
+ </View>
105
+
106
+ <ScrollView style={styles.scrollContainer}>
107
+ <View style={styles.body}>
108
+ {/* Payment Methods */}
109
+ <View style={styles.paymentMethodsContainer}>
110
+ {paymentMethods.map((method) => (<TouchableOpacity key={method.id} onPress={() => setSelectedMethod(method.id)} style={[
111
+ styles.paymentMethod,
112
+ selectedMethod === method.id &&
113
+ styles.paymentMethodSelected,
114
+ ]}>
115
+ <Ionicons name={method.icon} size={20} color={selectedMethod === method.id ? "#4F46E5" : "#6B7280"}/>
116
+ <Text style={[
117
+ styles.paymentMethodText,
118
+ selectedMethod === method.id &&
119
+ styles.paymentMethodTextSelected,
120
+ ]}>
121
+ {method.name}
122
+ </Text>
123
+ </TouchableOpacity>))}
124
+ </View>
125
+
126
+ {/* Card Payment */}
127
+ {selectedMethod === "card" && (<View>
128
+ <View style={styles.inputGroup}>
129
+ <Text style={styles.inputLabel}>Card Number</Text>
130
+ <View style={styles.inputWrapper}>
131
+ <TextInput value={cardNumber} onChangeText={(text) => setCardNumber(formatCardNumber(text))} placeholder="0000 0000 0000 0000" maxLength={19} keyboardType="number-pad" style={styles.input}/>
132
+ <Ionicons name="card-outline" size={20} color="#9CA3AF" style={styles.inputIcon}/>
133
+ </View>
134
+ </View>
135
+
136
+ <View style={styles.row}>
137
+ <View style={styles.flex1}>
138
+ <Text style={styles.inputLabel}>Expiry Date</Text>
139
+ <TextInput value={expiryDate} onChangeText={(text) => setExpiryDate(formatExpiry(text))} placeholder="MM/YY" maxLength={5} keyboardType="number-pad" style={styles.input}/>
140
+ </View>
141
+ <View style={styles.flex1}>
142
+ <Text style={styles.inputLabel}>CVV</Text>
143
+ <TextInput value={cvv} onChangeText={(text) => setCvv(text.replace(/\D/g, "").slice(0, 3))} placeholder="123" maxLength={3} keyboardType="number-pad" secureTextEntry style={styles.input}/>
144
+ </View>
145
+ </View>
146
+
147
+ <View style={styles.secureBox}>
148
+ <Ionicons name="lock-closed" size={16} color="#6B7280"/>
149
+ <Text style={styles.secureText}>
150
+ Your payment is secured with 256-bit SSL encryption
151
+ </Text>
152
+ </View>
153
+ </View>)}
154
+
155
+ {/* Transfer Payment */}
156
+ {selectedMethod === "transfer" && (<View>
157
+ <View style={styles.transferBox}>
158
+ <Text style={styles.transferText}>
159
+ Transfer ₦{formatAmount(amount)} to the account below
160
+ </Text>
161
+
162
+ <View>
163
+ <View style={styles.transferDetail}>
164
+ <Text style={styles.transferLabel}>Bank Name</Text>
165
+ <Text style={styles.transferValue}>Wema Bank</Text>
166
+ </View>
167
+
168
+ <View style={styles.transferDetail}>
169
+ <Text style={styles.transferLabel}>Account Number</Text>
170
+ <View style={styles.row}>
171
+ <Text style={styles.accountNumber}>
172
+ {accountNumber}
173
+ </Text>
174
+ <TouchableOpacity onPress={() => handleCopy(accountNumber)}>
175
+ <Ionicons name={copied ? "checkmark" : "copy-outline"} size={20} color="#4F46E5"/>
176
+ </TouchableOpacity>
177
+ </View>
178
+ </View>
179
+
180
+ <View style={styles.transferDetail}>
181
+ <Text style={styles.transferLabel}>Account Name</Text>
182
+ <Text style={styles.transferValue}>
183
+ Paystack-John Doe
184
+ </Text>
185
+ </View>
186
+ </View>
187
+ </View>
188
+
189
+ <View style={styles.transferNote}>
190
+ <Text style={styles.transferNoteText}>
191
+ <Text style={{ fontWeight: "bold" }}>Note:</Text> This
192
+ account expires in 60 minutes. Payment will be confirmed
193
+ automatically.
194
+ </Text>
195
+ </View>
196
+ </View>)}
197
+
198
+ {/* USSD Payment */}
199
+ {selectedMethod === "ussd" && (<View>
200
+ <View style={styles.inputGroup}>
201
+ <Text style={styles.inputLabel}>Select Your Bank</Text>
202
+ <TouchableOpacity onPress={() => setShowBankDropdown(!showBankDropdown)} style={styles.dropdown}>
203
+ <Text style={selectedBank
204
+ ? styles.dropdownText
205
+ : styles.dropdownPlaceholder}>
206
+ {selectedBank || "Choose your bank"}
207
+ </Text>
208
+ <Ionicons name="chevron-down" size={20} color="#9CA3AF"/>
209
+ </TouchableOpacity>
210
+
211
+ {showBankDropdown && (<View style={styles.dropdownList}>
212
+ <ScrollView style={{ maxHeight: 240 }}>
213
+ {ussdBanks.map((bank) => (<TouchableOpacity key={bank.code} onPress={() => {
214
+ setSelectedBank(bank.name);
215
+ setShowBankDropdown(false);
216
+ }} style={styles.dropdownItem}>
217
+ <Text style={styles.dropdownItemText}>
218
+ {bank.name}
219
+ </Text>
220
+ </TouchableOpacity>))}
221
+ </ScrollView>
222
+ </View>)}
223
+ </View>
224
+
225
+ {selectedBank && (<View style={styles.ussdContainer}>
226
+ <Text style={styles.ussdTitle}>
227
+ Dial the code below to complete payment
228
+ </Text>
229
+
230
+ <View style={styles.ussdCodeContainer}>
231
+ <View style={styles.row}>
232
+ <Text style={styles.ussdCodeText}>
233
+ {(_a = ussdBanks.find((b) => b.name === selectedBank)) === null || _a === void 0 ? void 0 : _a.ussdCode}
234
+ </Text>
235
+ <TouchableOpacity onPress={() => {
236
+ var _a;
237
+ return handleCopy(((_a = ussdBanks.find((b) => b.name === selectedBank)) === null || _a === void 0 ? void 0 : _a.ussdCode) || "");
238
+ }}>
239
+ <Ionicons name={copied ? "checkmark" : "copy-outline"} size={20} color="#4F46E5"/>
240
+ </TouchableOpacity>
241
+ </View>
242
+ </View>
243
+
244
+ <View>
245
+ <Text style={styles.ussdCodeText}>Steps:</Text>
246
+ <Text style={styles.ussdStepsText}>
247
+ 1. Dial the USSD code on your phone{"\n"}
248
+ 2. Follow the prompts on your screen{"\n"}
249
+ 3. Authorize payment with your bank PIN
250
+ </Text>
251
+ </View>
252
+ </View>)}
253
+ </View>)}
254
+
255
+ {/* Pay Button */}
256
+ <TouchableOpacity onPress={handlePay} disabled={(selectedMethod === "card" &&
257
+ (!cardNumber || !expiryDate || !cvv)) ||
258
+ (selectedMethod === "ussd" && !selectedBank)} style={[
259
+ styles.payButton,
260
+ (selectedMethod === "card" &&
261
+ (!cardNumber || !expiryDate || !cvv)) ||
262
+ (selectedMethod === "ussd" && !selectedBank)
263
+ ? styles.payButtonDisabled
264
+ : styles.payButtonActive,
265
+ ]}>
266
+ <Text style={styles.payButtonText}>
267
+ {selectedMethod === "transfer"
268
+ ? "I have sent the money"
269
+ : `Pay ₦${formatAmount(amount)}`}
270
+ </Text>
271
+ </TouchableOpacity>
272
+
273
+ {/* Footer */}
274
+ <View style={styles.footer}>
275
+ <Ionicons name="lock-closed" size={16} color="#9CA3AF"/>
276
+ <Text style={styles.footerText}>Secured by Paystack</Text>
277
+ </View>
278
+ </View>
279
+ </ScrollView>
280
+ </View>
281
+ </View>
282
+ </Modal>);
283
+ }
284
+ const styles = StyleSheet.create({
285
+ overlay: {
286
+ flex: 1,
287
+ backgroundColor: "rgba(0,0,0,0.5)",
288
+ justifyContent: "flex-end",
289
+ },
290
+ modalContainer: {
291
+ width: "90%",
292
+ alignSelf: "center",
293
+ borderRadius: 20,
294
+ height: "85%",
295
+ backgroundColor: "white",
296
+ },
297
+ header: {
298
+ backgroundColor: "#6B21A8",
299
+ padding: 16,
300
+ borderTopLeftRadius: 20,
301
+ borderTopRightRadius: 20,
302
+ },
303
+ headerTop: {
304
+ flexDirection: "row",
305
+ justifyContent: "space-between",
306
+ alignItems: "flex-start",
307
+ marginBottom: 16,
308
+ },
309
+ headerTitle: { color: "white", fontSize: 24, fontWeight: "bold" },
310
+ headerSubtitle: { color: "#E0E7FF", fontSize: 14, marginTop: 4 },
311
+ closeBtn: {
312
+ backgroundColor: "rgba(255,255,255,0.2)",
313
+ borderRadius: 999,
314
+ padding: 8,
315
+ },
316
+ amountBox: {
317
+ backgroundColor: "rgba(255,255,255,0.2)",
318
+ borderRadius: 12,
319
+ padding: 8,
320
+ },
321
+ amountLabel: { color: "#E0E7FF", fontSize: 12, marginBottom: 4 },
322
+ amountText: { color: "white", fontSize: 20, fontWeight: "bold" },
323
+ scrollContainer: { flex: 1 },
324
+ body: {
325
+ padding: 24,
326
+ backgroundColor: "white",
327
+ borderBottomLeftRadius: 20,
328
+ borderBottomRightRadius: 20,
329
+ },
330
+ paymentMethodsContainer: {
331
+ flexDirection: "row",
332
+ marginBottom: 24,
333
+ backgroundColor: "#F3F4F6",
334
+ padding: 4,
335
+ borderRadius: 12,
336
+ },
337
+ paymentMethod: {
338
+ flex: 1,
339
+ flexDirection: "row",
340
+ alignItems: "center",
341
+ justifyContent: "center",
342
+ paddingVertical: 12,
343
+ borderRadius: 12,
344
+ marginHorizontal: 4,
345
+ },
346
+ paymentMethodSelected: { backgroundColor: "white" },
347
+ paymentMethodText: { fontSize: 14, color: "#4B5563", marginLeft: 8 },
348
+ paymentMethodTextSelected: { color: "#4F46E5", fontWeight: "600" },
349
+ inputGroup: { marginBottom: 16 },
350
+ inputLabel: {
351
+ fontSize: 14,
352
+ fontWeight: "500",
353
+ color: "#374151",
354
+ marginBottom: 4,
355
+ },
356
+ inputWrapper: { position: "relative" },
357
+ input: {
358
+ width: "100%",
359
+ paddingHorizontal: 16,
360
+ paddingVertical: 12,
361
+ borderWidth: 1,
362
+ borderColor: "#D1D5DB",
363
+ borderRadius: 12,
364
+ backgroundColor: "white",
365
+ },
366
+ inputIcon: { position: "absolute", right: 16, top: 14 },
367
+ row: { flexDirection: "row", gap: 16 },
368
+ flex1: { flex: 1 },
369
+ secureBox: {
370
+ flexDirection: "row",
371
+ alignItems: "center",
372
+ backgroundColor: "#F9FAFB",
373
+ padding: 12,
374
+ borderRadius: 12,
375
+ gap: 8,
376
+ marginBottom: 16,
377
+ },
378
+ secureText: { fontSize: 12, color: "#4B5563", flex: 1 },
379
+ transferBox: {
380
+ backgroundColor: "#EFF6FF",
381
+ borderWidth: 1,
382
+ borderColor: "#BFDBFE",
383
+ borderRadius: 12,
384
+ padding: 8,
385
+ marginBottom: 8,
386
+ },
387
+ transferText: {
388
+ fontSize: 14,
389
+ color: "#1E3A8A",
390
+ fontWeight: "500",
391
+ marginBottom: 12,
392
+ },
393
+ transferDetail: {
394
+ backgroundColor: "white",
395
+ borderRadius: 12,
396
+ padding: 8,
397
+ marginBottom: 12,
398
+ },
399
+ transferLabel: { fontSize: 10, color: "#4B5563", marginBottom: 4 },
400
+ transferValue: { fontWeight: "600", color: "#111827" },
401
+ accountNumber: {
402
+ fontWeight: "600",
403
+ color: "#111827",
404
+ fontSize: 16,
405
+ letterSpacing: 2,
406
+ flex: 1,
407
+ },
408
+ transferNote: {
409
+ backgroundColor: "#FEF3C7",
410
+ borderWidth: 1,
411
+ borderColor: "#FDE68A",
412
+ borderRadius: 12,
413
+ padding: 8,
414
+ },
415
+ transferNoteText: { fontSize: 14, color: "#78350F" },
416
+ dropdown: {
417
+ width: "100%",
418
+ paddingHorizontal: 16,
419
+ paddingVertical: 12,
420
+ borderWidth: 1,
421
+ borderColor: "#D1D5DB",
422
+ borderRadius: 12,
423
+ flexDirection: "row",
424
+ justifyContent: "space-between",
425
+ alignItems: "center",
426
+ },
427
+ dropdownText: { color: "#111827" },
428
+ dropdownPlaceholder: { color: "#9CA3AF" },
429
+ dropdownList: {
430
+ marginTop: 8,
431
+ backgroundColor: "white",
432
+ borderWidth: 1,
433
+ borderColor: "#E5E7EB",
434
+ borderRadius: 12,
435
+ overflow: "hidden",
436
+ maxHeight: 240,
437
+ },
438
+ dropdownItem: {
439
+ paddingVertical: 12,
440
+ paddingHorizontal: 16,
441
+ borderBottomWidth: 1,
442
+ borderBottomColor: "#F3F4F6",
443
+ },
444
+ dropdownItemText: { color: "#111827" },
445
+ ussdContainer: {
446
+ backgroundColor: "#E0E7FF", // bg-indigo-50
447
+ borderWidth: 1,
448
+ borderColor: "#C7D2FE", // border-indigo-200
449
+ borderRadius: 12, // rounded-xl
450
+ padding: 16, // p-4
451
+ marginBottom: 16,
452
+ },
453
+ ussdTitle: {
454
+ fontSize: 14, // text-sm
455
+ color: "#1E3A8A", // text-indigo-900
456
+ fontWeight: "500", // font-medium
457
+ marginBottom: 12, // mb-3
458
+ },
459
+ ussdCodeContainer: {
460
+ backgroundColor: "white", // bg-white
461
+ borderRadius: 12, // rounded-xl
462
+ padding: 16, // p-4
463
+ marginBottom: 12, // mb-3
464
+ },
465
+ ussdCodeRow: {
466
+ flexDirection: "row",
467
+ justifyContent: "space-between",
468
+ alignItems: "center",
469
+ },
470
+ ussdCodeText: {
471
+ fontFamily: "monospace", // font-mono
472
+ fontSize: 16, // text-base
473
+ fontWeight: "700", // font-bold
474
+ color: "#111827", // text-gray-900
475
+ flex: 1,
476
+ },
477
+ ussdCopyBtn: {
478
+ padding: 8, // p-2
479
+ },
480
+ ussdStepsLabel: {
481
+ fontSize: 14, // text-sm
482
+ color: "#1E3A8A", // text-indigo-900
483
+ fontWeight: "600", // font-semibold
484
+ marginBottom: 8, // mb-2
485
+ },
486
+ ussdStepsText: {
487
+ fontSize: 14, // text-sm
488
+ color: "#1E40AF", // text-indigo-800
489
+ },
490
+ payButton: {
491
+ width: "100%",
492
+ paddingVertical: 16, // py-4
493
+ borderRadius: 12, // rounded-xl
494
+ marginTop: 8, // mt-2
495
+ alignItems: "center",
496
+ },
497
+ payButtonActive: {
498
+ backgroundColor: "#4F46E5", // bg-indigo-600
499
+ },
500
+ payButtonDisabled: {
501
+ backgroundColor: "#D1D5DB", // bg-gray-300
502
+ },
503
+ payButtonText: {
504
+ color: "white",
505
+ fontWeight: "600",
506
+ fontSize: 18, // text-lg
507
+ textAlign: "center",
508
+ },
509
+ footer: {
510
+ flexDirection: "row",
511
+ justifyContent: "center",
512
+ alignItems: "center",
513
+ marginTop: 8, // mt-2
514
+ gap: 8, // supported in RN 0.71+, acts like Tailwind gap-2
515
+ },
516
+ footerText: {
517
+ fontSize: 14, // text-sm
518
+ color: "#6B7280", // text-gray-500
519
+ },
520
+ });
@@ -0,0 +1,17 @@
1
+ import React from "react";
2
+ type OTPInputProps = {
3
+ length?: number;
4
+ onComplete?: (otp: string) => void;
5
+ onChangeOTP?: (otp: string) => void;
6
+ autoFocus?: boolean;
7
+ secureTextEntry?: boolean;
8
+ disabled?: boolean;
9
+ inputStyle?: object;
10
+ containerStyle?: object;
11
+ focusedInputStyle?: object;
12
+ };
13
+ export type OTPInputRef = {
14
+ clear: () => void;
15
+ };
16
+ export default function OTPInput({ length, onComplete, onChangeOTP, autoFocus, secureTextEntry, disabled, inputStyle, containerStyle, focusedInputStyle, }: OTPInputProps): React.JSX.Element;
17
+ export {};
@@ -0,0 +1,132 @@
1
+ import React, { useEffect, useState } from "react";
2
+ import { StyleSheet, TextInput, View, } from "react-native";
3
+ export default function OTPInput({ length = 6, onComplete, onChangeOTP, autoFocus = true, secureTextEntry = false, disabled = false, inputStyle, containerStyle, focusedInputStyle, }) {
4
+ const [otp, setOtp] = useState(Array(length).fill(""));
5
+ const [focusedIndex, setFocusedIndex] = useState(autoFocus ? 0 : null);
6
+ const ref = React.useRef(null);
7
+ const inputRefs = React.useRef([]);
8
+ // Focus first input on mount if autoFocus is true
9
+ useEffect(() => {
10
+ if (autoFocus && inputRefs.current[0]) {
11
+ inputRefs.current[0].focus();
12
+ }
13
+ }, [autoFocus]);
14
+ // Notify parent of OTP changes
15
+ useEffect(() => {
16
+ const otpString = otp.join("");
17
+ onChangeOTP === null || onChangeOTP === void 0 ? void 0 : onChangeOTP(otpString);
18
+ // Call onComplete when all digits are filled
19
+ if (otpString.length === length && !otpString.includes("")) {
20
+ onComplete === null || onComplete === void 0 ? void 0 : onComplete(otpString);
21
+ }
22
+ }, [otp, length, onComplete, onChangeOTP]);
23
+ const handleChangeText = (text, index) => {
24
+ var _a, _b;
25
+ // Only allow numbers
26
+ const sanitizedText = text.replace(/[^0-9]/g, "");
27
+ if (sanitizedText === "") {
28
+ // Handle delete
29
+ const newOtp = [...otp];
30
+ newOtp[index] = "";
31
+ setOtp(newOtp);
32
+ return;
33
+ }
34
+ // Handle paste (multiple characters)
35
+ if (sanitizedText.length > 1) {
36
+ const pastedDigits = sanitizedText.slice(0, length).split("");
37
+ const newOtp = [...otp];
38
+ pastedDigits.forEach((digit, i) => {
39
+ if (index + i < length) {
40
+ newOtp[index + i] = digit;
41
+ }
42
+ });
43
+ setOtp(newOtp);
44
+ // Focus the next empty input or the last input
45
+ const nextIndex = Math.min(index + pastedDigits.length, length - 1);
46
+ (_a = inputRefs.current[nextIndex]) === null || _a === void 0 ? void 0 : _a.focus();
47
+ return;
48
+ }
49
+ // Handle single digit input
50
+ const newOtp = [...otp];
51
+ newOtp[index] = sanitizedText;
52
+ setOtp(newOtp);
53
+ // Auto-focus next input
54
+ if (index < length - 1 && sanitizedText !== "") {
55
+ (_b = inputRefs.current[index + 1]) === null || _b === void 0 ? void 0 : _b.focus();
56
+ }
57
+ };
58
+ const handleKeyPress = (e, index) => {
59
+ var _a;
60
+ if (e.nativeEvent.key === "Backspace") {
61
+ if (otp[index] === "") {
62
+ // If current input is empty, focus previous input
63
+ if (index > 0) {
64
+ (_a = inputRefs.current[index - 1]) === null || _a === void 0 ? void 0 : _a.focus();
65
+ }
66
+ }
67
+ else {
68
+ // Clear current input
69
+ const newOtp = [...otp];
70
+ newOtp[index] = "";
71
+ setOtp(newOtp);
72
+ }
73
+ }
74
+ };
75
+ const handleFocus = (index) => {
76
+ setFocusedIndex(index);
77
+ };
78
+ const handleBlur = () => {
79
+ setFocusedIndex(null);
80
+ };
81
+ // Method to clear OTP (can be called from parent component)
82
+ const clearOTP = () => {
83
+ var _a;
84
+ setOtp(Array(length).fill(""));
85
+ (_a = inputRefs.current[0]) === null || _a === void 0 ? void 0 : _a.focus();
86
+ };
87
+ // Expose clearOTP method
88
+ React.useImperativeHandle(ref, () => ({
89
+ clear: clearOTP,
90
+ }));
91
+ return (<View style={[styles.container, containerStyle]}>
92
+ {Array(length)
93
+ .fill(0)
94
+ .map((_, index) => (<TextInput key={index} ref={(ref) => {
95
+ inputRefs.current[index] = ref;
96
+ }} style={[
97
+ styles.input,
98
+ inputStyle,
99
+ focusedIndex === index && styles.focusedInput,
100
+ focusedIndex === index && focusedInputStyle,
101
+ disabled && styles.disabledInput,
102
+ ]} value={otp[index]} onChangeText={(text) => handleChangeText(text, index)} onKeyPress={(e) => handleKeyPress(e, index)} onFocus={() => handleFocus(index)} onBlur={handleBlur} keyboardType="number-pad" maxLength={1} selectTextOnFocus editable={!disabled} secureTextEntry={secureTextEntry} textContentType="oneTimeCode" autoComplete="sms-otp"/>))}
103
+ </View>);
104
+ }
105
+ const styles = StyleSheet.create({
106
+ container: {
107
+ flexDirection: "row",
108
+ justifyContent: "center",
109
+ alignItems: "center",
110
+ gap: 2,
111
+ },
112
+ input: {
113
+ width: 40,
114
+ height: 50,
115
+ borderWidth: 1,
116
+ borderColor: "#D1D5DB",
117
+ borderRadius: 10,
118
+ textAlign: "center",
119
+ fontSize: 24,
120
+ fontWeight: "400",
121
+ backgroundColor: "#FFFFFF",
122
+ color: "#111827",
123
+ },
124
+ focusedInput: {
125
+ borderColor: "#3B82F6",
126
+ borderWidth: 2,
127
+ },
128
+ disabledInput: {
129
+ backgroundColor: "#F3F4F6",
130
+ color: "#9CA3AF",
131
+ },
132
+ });
@@ -0,0 +1,12 @@
1
+ type PaymentMethod = {
2
+ id: string;
3
+ name: string;
4
+ icon: any;
5
+ };
6
+ type PaymentMethodSelectorProps = {
7
+ methods: PaymentMethod[];
8
+ selectedMethod: string;
9
+ onSelectMethod: (methodId: string) => void;
10
+ };
11
+ export default function PaymentMethodSelector({ methods, selectedMethod, onSelectMethod, }: PaymentMethodSelectorProps): import("react").JSX.Element;
12
+ export {};