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,386 @@
1
+ import { Ionicons } from "@expo/vector-icons";
2
+ import { StyleSheet, Text, TextInput, View } from "react-native";
3
+ import OTPInput from "./OTPInput";
4
+
5
+ type CardPaymentProps = {
6
+ cardNumber: string;
7
+ setCardNumber: (value: string) => void;
8
+ expiryDate: string;
9
+ setExpiryDate: (value: string) => void;
10
+ cvv: string;
11
+ message?: string;
12
+ setCvv: (value: string) => void;
13
+ cardcontinue: boolean;
14
+ showOtp: boolean;
15
+ handleOTPComplete: (otp: string) => void;
16
+ handleOTPChange: (otp: string) => void;
17
+ handlePinChange: (pin: string) => void;
18
+ formatCardNumber: (value: string) => string;
19
+ formatExpiry: (value: string) => string;
20
+ };
21
+
22
+ export default function CardPayment({
23
+ cardNumber,
24
+ message,
25
+ setCardNumber,
26
+ expiryDate,
27
+ setExpiryDate,
28
+ cvv,
29
+ setCvv,
30
+ cardcontinue,
31
+ showOtp,
32
+ handleOTPComplete,
33
+ handleOTPChange,
34
+ handlePinChange,
35
+ formatCardNumber,
36
+ formatExpiry,
37
+ }: CardPaymentProps) {
38
+ if (cardcontinue) {
39
+ return (
40
+ <View>
41
+ {showOtp ? (
42
+ <View style={styles.otpContainer}>
43
+ {message && <Text style={styles.inputLabel}>{message}</Text>}
44
+ <Text style={styles.inputLabel}>Enter OTP</Text>
45
+ <OTPInput
46
+ length={6}
47
+ onComplete={handleOTPComplete}
48
+ onChangeOTP={handleOTPChange}
49
+ autoFocus={true}
50
+ />
51
+ </View>
52
+ ) : (
53
+ <View style={styles.otpContainer}>
54
+ <Text style={styles.inputLabel}>Enter PIN</Text>
55
+ <OTPInput
56
+ length={4}
57
+ onComplete={handleOTPComplete}
58
+ onChangeOTP={handlePinChange}
59
+ autoFocus={true}
60
+ secureTextEntry={true}
61
+ />
62
+ </View>
63
+ )}
64
+ </View>
65
+ );
66
+ }
67
+
68
+ return (
69
+ <View>
70
+ <View style={styles.inputGroup}>
71
+ <Text style={styles.inputLabel}>Card Number</Text>
72
+ <View style={styles.inputWrapper}>
73
+ <TextInput
74
+ value={cardNumber}
75
+ onChangeText={(text) => setCardNumber(formatCardNumber(text))}
76
+ placeholder="0000 0000 0000 0000"
77
+ maxLength={19}
78
+ keyboardType="number-pad"
79
+ style={styles.input}
80
+ />
81
+ <Ionicons
82
+ name="card-outline"
83
+ size={20}
84
+ color="#9CA3AF"
85
+ style={styles.inputIcon}
86
+ />
87
+ </View>
88
+ </View>
89
+
90
+ <View style={styles.row}>
91
+ <View style={styles.flex1}>
92
+ <Text style={styles.inputLabel}>Expiry Date</Text>
93
+ <TextInput
94
+ value={expiryDate}
95
+ onChangeText={(text) => setExpiryDate(formatExpiry(text))}
96
+ placeholder="MM/YY"
97
+ maxLength={5}
98
+ keyboardType="number-pad"
99
+ style={styles.input}
100
+ />
101
+ </View>
102
+ <View style={styles.flex1}>
103
+ <Text style={styles.inputLabel}>CVV</Text>
104
+ <TextInput
105
+ value={cvv}
106
+ onChangeText={(text) => setCvv(text.replace(/\D/g, "").slice(0, 3))}
107
+ placeholder="123"
108
+ maxLength={3}
109
+ keyboardType="number-pad"
110
+ secureTextEntry
111
+ style={styles.input}
112
+ />
113
+ </View>
114
+ </View>
115
+
116
+ <View style={styles.secureBox}>
117
+ <Ionicons name="lock-closed" size={16} color="#6B7280" />
118
+ <Text style={styles.secureText}>
119
+ Your payment is secured with 256-bit SSL encryption
120
+ </Text>
121
+ </View>
122
+ </View>
123
+ );
124
+ }
125
+
126
+ const styles = StyleSheet.create({
127
+ overlay: {
128
+ flex: 1,
129
+ backgroundColor: "rgba(0,0,0,0.5)",
130
+ justifyContent: "center",
131
+ },
132
+ modalContainer: {
133
+ position: "relative",
134
+ width: "90%",
135
+ alignSelf: "center",
136
+ borderRadius: 20,
137
+ height: "80%",
138
+ backgroundColor: "white",
139
+ },
140
+
141
+ otpContainer: {
142
+ width: "100%",
143
+ marginHorizontal: "auto",
144
+ marginTop: 20,
145
+ marginBottom: 40,
146
+ },
147
+ header: {
148
+ backgroundColor: "#6B21A8",
149
+ padding: 16,
150
+ borderTopLeftRadius: 20,
151
+ borderTopRightRadius: 20,
152
+ },
153
+ pinWrapper: {
154
+ flexDirection: "row",
155
+ alignItems: "center",
156
+ },
157
+ eyeButton: {
158
+ paddingHorizontal: 10,
159
+ },
160
+ eyeText: {
161
+ color: "#007AFF",
162
+ },
163
+
164
+ headerTop: {
165
+ flexDirection: "row",
166
+ justifyContent: "space-between",
167
+ alignItems: "flex-start",
168
+ marginBottom: 16,
169
+ },
170
+ headerTitle: { color: "white", fontSize: 24, fontWeight: "bold" },
171
+ headerSubtitle: { color: "#E0E7FF", fontSize: 14, marginTop: 4 },
172
+ closeBtn: {
173
+ backgroundColor: "rgba(255,255,255,0.2)",
174
+ borderRadius: 999,
175
+ padding: 8,
176
+ },
177
+ amountBox: {
178
+ backgroundColor: "rgba(255,255,255,0.2)",
179
+ borderRadius: 12,
180
+ padding: 8,
181
+ },
182
+ amountLabel: { color: "#E0E7FF", fontSize: 12, marginBottom: 4 },
183
+ amountText: { color: "white", fontSize: 20, fontWeight: "bold" },
184
+ scrollContainer: { flex: 1, marginBottom: 16 },
185
+ body: {
186
+ padding: 24,
187
+ backgroundColor: "white",
188
+ borderBottomLeftRadius: 20,
189
+ borderBottomRightRadius: 20,
190
+ },
191
+ paymentMethodsContainer: {
192
+ flexDirection: "row",
193
+ marginBottom: 24,
194
+ backgroundColor: "#F3F4F6",
195
+ padding: 4,
196
+ borderRadius: 12,
197
+ },
198
+ paymentMethod: {
199
+ flex: 1,
200
+ flexDirection: "row",
201
+ alignItems: "center",
202
+ justifyContent: "center",
203
+ paddingVertical: 12,
204
+ borderRadius: 12,
205
+ marginHorizontal: 4,
206
+ },
207
+ paymentMethodSelected: { backgroundColor: "white" },
208
+ paymentMethodText: { fontSize: 14, color: "#4B5563", marginLeft: 8 },
209
+ paymentMethodTextSelected: { color: "#4F46E5", fontWeight: "600" },
210
+ inputGroup: { marginBottom: 16 },
211
+ inputLabel: {
212
+ fontSize: 14,
213
+ fontWeight: "500",
214
+ color: "#374151",
215
+ marginBottom: 6,
216
+ },
217
+ inputWrapper: { position: "relative" },
218
+ input: {
219
+ width: "100%",
220
+ paddingHorizontal: 16,
221
+ paddingVertical: 12,
222
+ borderWidth: 1,
223
+ borderColor: "#D1D5DB",
224
+ borderRadius: 12,
225
+ backgroundColor: "white",
226
+ },
227
+ inputIcon: { position: "absolute", right: 16, top: 14 },
228
+ row: { flexDirection: "row", gap: 16, marginBottom: 12 },
229
+ flex1: { flex: 1 },
230
+ secureBox: {
231
+ flexDirection: "row",
232
+ alignItems: "center",
233
+ backgroundColor: "#F9FAFB",
234
+ padding: 12,
235
+ borderRadius: 12,
236
+ gap: 8,
237
+ marginBottom: 16,
238
+ },
239
+ secureText: { fontSize: 12, color: "#4B5563", flex: 1 },
240
+ transferBox: {
241
+ backgroundColor: "#EFF6FF",
242
+ borderWidth: 1,
243
+ borderColor: "#BFDBFE",
244
+ borderRadius: 12,
245
+ padding: 8,
246
+ marginBottom: 8,
247
+ },
248
+ transferText: {
249
+ fontSize: 14,
250
+ color: "#1E3A8A",
251
+ fontWeight: "500",
252
+ marginBottom: 12,
253
+ },
254
+ transferDetail: {
255
+ backgroundColor: "white",
256
+ borderRadius: 12,
257
+ padding: 8,
258
+ marginBottom: 12,
259
+ },
260
+ transferLabel: { fontSize: 10, color: "#4B5563", marginBottom: 4 },
261
+ transferValue: { fontWeight: "600", color: "#111827" },
262
+ accountNumber: {
263
+ fontWeight: "600",
264
+ color: "#111827",
265
+ fontSize: 16,
266
+ letterSpacing: 2,
267
+ flex: 1,
268
+ },
269
+ transferNote: {
270
+ backgroundColor: "#FEF3C7",
271
+ borderWidth: 1,
272
+ borderColor: "#FDE68A",
273
+ borderRadius: 12,
274
+ padding: 8,
275
+ },
276
+ transferNoteText: { fontSize: 14, color: "#78350F" },
277
+ dropdown: {
278
+ width: "100%",
279
+ paddingHorizontal: 16,
280
+ paddingVertical: 12,
281
+ borderWidth: 1,
282
+ borderColor: "#D1D5DB",
283
+ borderRadius: 12,
284
+ flexDirection: "row",
285
+ justifyContent: "space-between",
286
+ alignItems: "center",
287
+ },
288
+ dropdownText: { color: "#111827" },
289
+ dropdownPlaceholder: { color: "#9CA3AF" },
290
+ dropdownList: {
291
+ marginTop: 8,
292
+ backgroundColor: "white",
293
+ borderWidth: 1,
294
+ borderColor: "#E5E7EB",
295
+ borderRadius: 12,
296
+ overflow: "hidden",
297
+ maxHeight: 240,
298
+ },
299
+ dropdownItem: {
300
+ paddingVertical: 12,
301
+ paddingHorizontal: 16,
302
+ borderBottomWidth: 1,
303
+ borderBottomColor: "#F3F4F6",
304
+ },
305
+ dropdownItemText: { color: "#111827" },
306
+
307
+ ussdContainer: {
308
+ backgroundColor: "#E0E7FF", // bg-indigo-50
309
+ borderWidth: 1,
310
+ borderColor: "#C7D2FE", // border-indigo-200
311
+ borderRadius: 12, // rounded-xl
312
+ padding: 16, // p-4
313
+ marginBottom: 16,
314
+ },
315
+ ussdTitle: {
316
+ fontSize: 14, // text-sm
317
+ color: "#1E3A8A", // text-indigo-900
318
+ fontWeight: "500", // font-medium
319
+ marginBottom: 12, // mb-3
320
+ },
321
+ ussdCodeContainer: {
322
+ backgroundColor: "white", // bg-white
323
+ borderRadius: 12, // rounded-xl
324
+ padding: 16, // p-4
325
+ marginBottom: 12, // mb-3
326
+ },
327
+ ussdCodeRow: {
328
+ flexDirection: "row",
329
+ justifyContent: "space-between",
330
+ alignItems: "center",
331
+ },
332
+ ussdCodeText: {
333
+ fontFamily: "monospace", // font-mono
334
+ fontSize: 16, // text-base
335
+ fontWeight: "700", // font-bold
336
+ color: "#111827", // text-gray-900
337
+ flex: 1,
338
+ },
339
+ ussdCopyBtn: {
340
+ padding: 8, // p-2
341
+ },
342
+ ussdStepsLabel: {
343
+ fontSize: 14, // text-sm
344
+ color: "#1E3A8A", // text-indigo-900
345
+ fontWeight: "600", // font-semibold
346
+ marginBottom: 8, // mb-2
347
+ },
348
+ ussdStepsText: {
349
+ fontSize: 14, // text-sm
350
+ color: "#1E40AF", // text-indigo-800
351
+ },
352
+ payButton: {
353
+ display: "flex",
354
+ flexDirection: "row",
355
+ justifyContent: "center",
356
+ gap: 8,
357
+ width: "100%",
358
+ paddingVertical: 16, // py-4
359
+ borderRadius: 12, // rounded-xl
360
+ marginTop: 8, // mt-2
361
+ alignItems: "center",
362
+ },
363
+ payButtonActive: {
364
+ backgroundColor: "#4F46E5", // bg-indigo-600
365
+ },
366
+ payButtonDisabled: {
367
+ backgroundColor: "#D1D5DB", // bg-gray-300
368
+ },
369
+ payButtonText: {
370
+ color: "white",
371
+ fontWeight: "600",
372
+ fontSize: 18, // text-lg
373
+ textAlign: "center",
374
+ },
375
+ footer: {
376
+ flexDirection: "row",
377
+ justifyContent: "center",
378
+ alignItems: "center",
379
+ marginTop: 8, // mt-2
380
+ gap: 8, // supported in RN 0.71+, acts like Tailwind gap-2
381
+ },
382
+ footerText: {
383
+ fontSize: 14, // text-sm
384
+ color: "#6B7280", // text-gray-500
385
+ },
386
+ });
@@ -0,0 +1,204 @@
1
+ import React, { useEffect, useState } from "react";
2
+ import {
3
+ NativeSyntheticEvent,
4
+ StyleSheet,
5
+ TextInput,
6
+ TextInputKeyPressEventData,
7
+ View,
8
+ } from "react-native";
9
+
10
+ type OTPInputProps = {
11
+ length?: number;
12
+ onComplete?: (otp: string) => void;
13
+ onChangeOTP?: (otp: string) => void;
14
+ autoFocus?: boolean;
15
+ secureTextEntry?: boolean;
16
+ disabled?: boolean;
17
+ inputStyle?: object;
18
+ containerStyle?: object;
19
+ focusedInputStyle?: object;
20
+ };
21
+
22
+ export type OTPInputRef = {
23
+ clear: () => void;
24
+ };
25
+
26
+ export default function OTPInput({
27
+ length = 6,
28
+ onComplete,
29
+ onChangeOTP,
30
+ autoFocus = true,
31
+ secureTextEntry = false,
32
+ disabled = false,
33
+ inputStyle,
34
+ containerStyle,
35
+ focusedInputStyle,
36
+ }: OTPInputProps) {
37
+ const [otp, setOtp] = useState<string[]>(Array(length).fill(""));
38
+ const [focusedIndex, setFocusedIndex] = useState<number | null>(
39
+ autoFocus ? 0 : null,
40
+ );
41
+ const ref = React.useRef<{ clear: () => void } | null>(null);
42
+ const inputRefs = React.useRef<Array<TextInput | null>>([]);
43
+
44
+ // Focus first input on mount if autoFocus is true
45
+ useEffect(() => {
46
+ if (autoFocus && inputRefs.current[0]) {
47
+ inputRefs.current[0].focus();
48
+ }
49
+ }, [autoFocus]);
50
+
51
+ // Notify parent of OTP changes
52
+ useEffect(() => {
53
+ const otpString = otp.join("");
54
+ onChangeOTP?.(otpString);
55
+
56
+ // Call onComplete when all digits are filled
57
+ if (otpString.length === length && !otpString.includes("")) {
58
+ onComplete?.(otpString);
59
+ }
60
+ }, [otp, length, onComplete, onChangeOTP]);
61
+
62
+ const handleChangeText = (text: string, index: number) => {
63
+ // Only allow numbers
64
+ const sanitizedText = text.replace(/[^0-9]/g, "");
65
+
66
+ if (sanitizedText === "") {
67
+ // Handle delete
68
+ const newOtp = [...otp];
69
+ newOtp[index] = "";
70
+ setOtp(newOtp);
71
+ return;
72
+ }
73
+
74
+ // Handle paste (multiple characters)
75
+ if (sanitizedText.length > 1) {
76
+ const pastedDigits = sanitizedText.slice(0, length).split("");
77
+ const newOtp = [...otp];
78
+
79
+ pastedDigits.forEach((digit, i) => {
80
+ if (index + i < length) {
81
+ newOtp[index + i] = digit;
82
+ }
83
+ });
84
+
85
+ setOtp(newOtp);
86
+
87
+ // Focus the next empty input or the last input
88
+ const nextIndex = Math.min(index + pastedDigits.length, length - 1);
89
+ inputRefs.current[nextIndex]?.focus();
90
+ return;
91
+ }
92
+
93
+ // Handle single digit input
94
+ const newOtp = [...otp];
95
+ newOtp[index] = sanitizedText;
96
+ setOtp(newOtp);
97
+
98
+ // Auto-focus next input
99
+ if (index < length - 1 && sanitizedText !== "") {
100
+ inputRefs.current[index + 1]?.focus();
101
+ }
102
+ };
103
+
104
+ const handleKeyPress = (
105
+ e: NativeSyntheticEvent<TextInputKeyPressEventData>,
106
+ index: number,
107
+ ) => {
108
+ if (e.nativeEvent.key === "Backspace") {
109
+ if (otp[index] === "") {
110
+ // If current input is empty, focus previous input
111
+ if (index > 0) {
112
+ inputRefs.current[index - 1]?.focus();
113
+ }
114
+ } else {
115
+ // Clear current input
116
+ const newOtp = [...otp];
117
+ newOtp[index] = "";
118
+ setOtp(newOtp);
119
+ }
120
+ }
121
+ };
122
+
123
+ const handleFocus = (index: number) => {
124
+ setFocusedIndex(index);
125
+ };
126
+
127
+ const handleBlur = () => {
128
+ setFocusedIndex(null);
129
+ };
130
+
131
+ // Method to clear OTP (can be called from parent component)
132
+ const clearOTP = () => {
133
+ setOtp(Array(length).fill(""));
134
+ inputRefs.current[0]?.focus();
135
+ };
136
+
137
+ // Expose clearOTP method
138
+ React.useImperativeHandle(ref, () => ({
139
+ clear: clearOTP,
140
+ }));
141
+
142
+ return (
143
+ <View style={[styles.container, containerStyle]}>
144
+ {Array(length)
145
+ .fill(0)
146
+ .map((_, index) => (
147
+ <TextInput
148
+ key={index}
149
+ ref={(ref) => {
150
+ inputRefs.current[index] = ref;
151
+ }}
152
+ style={[
153
+ styles.input,
154
+ inputStyle,
155
+ focusedIndex === index && styles.focusedInput,
156
+ focusedIndex === index && focusedInputStyle,
157
+ disabled && styles.disabledInput,
158
+ ]}
159
+ value={otp[index]}
160
+ onChangeText={(text) => handleChangeText(text, index)}
161
+ onKeyPress={(e) => handleKeyPress(e, index)}
162
+ onFocus={() => handleFocus(index)}
163
+ onBlur={handleBlur}
164
+ keyboardType="number-pad"
165
+ maxLength={1}
166
+ selectTextOnFocus
167
+ editable={!disabled}
168
+ secureTextEntry={secureTextEntry}
169
+ textContentType="oneTimeCode"
170
+ autoComplete="sms-otp"
171
+ />
172
+ ))}
173
+ </View>
174
+ );
175
+ }
176
+
177
+ const styles = StyleSheet.create({
178
+ container: {
179
+ flexDirection: "row",
180
+ justifyContent: "center",
181
+ alignItems: "center",
182
+ gap: 2,
183
+ },
184
+ input: {
185
+ width: 40,
186
+ height: 50,
187
+ borderWidth: 1,
188
+ borderColor: "#D1D5DB",
189
+ borderRadius: 10,
190
+ textAlign: "center",
191
+ fontSize: 24,
192
+ fontWeight: "400",
193
+ backgroundColor: "#FFFFFF",
194
+ color: "#111827",
195
+ },
196
+ focusedInput: {
197
+ borderColor: "#3B82F6",
198
+ borderWidth: 2,
199
+ },
200
+ disabledInput: {
201
+ backgroundColor: "#F3F4F6",
202
+ color: "#9CA3AF",
203
+ },
204
+ });
@@ -0,0 +1,99 @@
1
+ import { Ionicons } from "@expo/vector-icons";
2
+ import { StyleSheet, Text, TouchableOpacity, View } from "react-native";
3
+
4
+ type PaymentMethod = {
5
+ id: string;
6
+ name: string;
7
+ icon: any;
8
+ };
9
+
10
+ type PaymentMethodSelectorProps = {
11
+ methods: PaymentMethod[];
12
+ selectedMethod: string;
13
+ onSelectMethod: (methodId: string) => void;
14
+ };
15
+
16
+ export default function PaymentMethodSelector({
17
+ methods,
18
+ selectedMethod,
19
+ onSelectMethod,
20
+ }: PaymentMethodSelectorProps) {
21
+ return (
22
+ <View style={styles.container}>
23
+ {methods.map((method) => (
24
+ <TouchableOpacity
25
+ key={method.id}
26
+ onPress={() => onSelectMethod(method.id)}
27
+ style={[
28
+ styles.method,
29
+ selectedMethod === method.id && styles.methodSelected,
30
+ ]}
31
+ >
32
+ <Ionicons
33
+ name={method.icon}
34
+ size={20}
35
+ color={selectedMethod === method.id ? "#4F46E5" : "#6B7280"}
36
+ />
37
+ <Text
38
+ style={[
39
+ styles.methodText,
40
+ selectedMethod === method.id && styles.methodTextSelected,
41
+ ]}
42
+ >
43
+ {method.name}
44
+ </Text>
45
+ </TouchableOpacity>
46
+ ))}
47
+ </View>
48
+ );
49
+ }
50
+
51
+ const styles = StyleSheet.create({
52
+ container: {
53
+ flexDirection: "row",
54
+ gap: 10,
55
+ marginBottom: 24,
56
+ },
57
+ method: {
58
+ flex: 1,
59
+ flexDirection: "row",
60
+ alignItems: "center",
61
+ justifyContent: "center",
62
+ gap: 6,
63
+ paddingVertical: 16,
64
+ paddingHorizontal: 10,
65
+ borderWidth: 2,
66
+ borderColor: "#E5E7EB",
67
+ borderRadius: 14,
68
+ backgroundColor: "#FFFFFF",
69
+ shadowColor: "#000",
70
+ shadowOffset: {
71
+ width: 0,
72
+ height: 1,
73
+ },
74
+ shadowOpacity: 0.05,
75
+ shadowRadius: 2,
76
+ elevation: 1,
77
+ },
78
+ methodSelected: {
79
+ borderColor: "#4F46E5",
80
+ backgroundColor: "#EEF2FF",
81
+ shadowColor: "#4F46E5",
82
+ shadowOffset: {
83
+ width: 0,
84
+ height: 2,
85
+ },
86
+ shadowOpacity: 0.1,
87
+ shadowRadius: 4,
88
+ elevation: 2,
89
+ },
90
+ methodText: {
91
+ fontSize: 14,
92
+ fontWeight: "600",
93
+ color: "#6B7280",
94
+ },
95
+ methodTextSelected: {
96
+ color: "#4F46E5",
97
+ fontWeight: "700",
98
+ },
99
+ });