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,415 @@
1
+ // import { AntDesign } from "@expo/vector-icons";
2
+ // import { useTheme } from "@react-navigation/core";
3
+ // import React, { ReactElement, useEffect, useRef } from "react";
4
+ // import {
5
+ // Animated,
6
+ // Easing,
7
+ // FlatList,
8
+ // Modal,
9
+ // PanResponder,
10
+ // Pressable,
11
+ // StyleSheet,
12
+ // TouchableOpacity,
13
+ // View,
14
+ // } from "react-native";
15
+ // import { ThemedText } from "../themed-text";
16
+ // import { Spinner } from "../ui/buttons/spinner";
17
+ // interface FilterItem {
18
+ // key: string;
19
+ // render: () => ReactElement | null;
20
+ // }
21
+ // interface PurchaseModalProps {
22
+ // visible: boolean;
23
+ // loading?: boolean;
24
+ // title?: string;
25
+ // description?: string;
26
+ // filters: FilterItem[];
27
+ // onClose: () => void;
28
+ // onApply?: () => void;
29
+ // onReset?: () => void;
30
+ // }
31
+ // const PurchaseModal: React.FC<PurchaseModalProps> = ({
32
+ // visible,
33
+ // title = "Buy Airtime",
34
+ // description,
35
+ // filters,
36
+ // loading,
37
+ // onClose,
38
+ // onApply,
39
+ // onReset,
40
+ // }) => {
41
+ // const { dark } = useTheme();
42
+ // const translateY = useRef(new Animated.Value(400)).current;
43
+ // const backdropOpacity = useRef(new Animated.Value(0)).current;
44
+ // const [internalVisible, setInternalVisible] = React.useState(visible);
45
+ // useEffect(() => {
46
+ // if (visible) {
47
+ // setInternalVisible(true);
48
+ // // 🔑 Reset position BEFORE animating
49
+ // translateY.setValue(400);
50
+ // backdropOpacity.setValue(0);
51
+ // Animated.parallel([
52
+ // Animated.timing(translateY, {
53
+ // toValue: 0,
54
+ // duration: 350,
55
+ // easing: Easing.out(Easing.ease),
56
+ // useNativeDriver: true,
57
+ // }),
58
+ // Animated.timing(backdropOpacity, {
59
+ // toValue: 1,
60
+ // duration: 250,
61
+ // useNativeDriver: true,
62
+ // }),
63
+ // ]).start();
64
+ // } else {
65
+ // Animated.parallel([
66
+ // Animated.timing(translateY, {
67
+ // toValue: 400,
68
+ // duration: 250,
69
+ // easing: Easing.in(Easing.ease),
70
+ // useNativeDriver: true,
71
+ // }),
72
+ // Animated.timing(backdropOpacity, {
73
+ // toValue: 0,
74
+ // duration: 200,
75
+ // useNativeDriver: true,
76
+ // }),
77
+ // ]).start(() => {
78
+ // setInternalVisible(false);
79
+ // });
80
+ // }
81
+ // }, [visible]);
82
+ // const DRAG_CLOSE_THRESHOLD = 100;
83
+ // const panResponder = useRef(
84
+ // PanResponder.create({
85
+ // onMoveShouldSetPanResponder: (_, gestureState) =>
86
+ // gestureState.dy > 5 || gestureState.dy < -5, // detect upward drag too
87
+ // onPanResponderGrant: () => {
88
+ // // set current position as offset
89
+ // translateY.extractOffset();
90
+ // },
91
+ // onPanResponderMove: (_, gestureState) => {
92
+ // translateY.setValue(gestureState.dy); // smooth follow
93
+ // },
94
+ // onPanResponderRelease: (_, gestureState) => {
95
+ // translateY.flattenOffset(); // combine offset & value
96
+ // if (gestureState.dy > DRAG_CLOSE_THRESHOLD || gestureState.vy > 1.2) {
97
+ // // close sheet
98
+ // Animated.timing(translateY, {
99
+ // toValue: 400,
100
+ // duration: 200,
101
+ // easing: Easing.in(Easing.ease),
102
+ // useNativeDriver: true,
103
+ // }).start(() => onClose());
104
+ // } else {
105
+ // // snap back
106
+ // Animated.spring(translateY, {
107
+ // toValue: 0,
108
+ // tension: 120,
109
+ // friction: 12,
110
+ // useNativeDriver: true,
111
+ // }).start();
112
+ // }
113
+ // },
114
+ // }),
115
+ // ).current;
116
+ // if (!internalVisible) return null;
117
+ // return (
118
+ // <Modal transparent visible animationType="none">
119
+ // {/* Backdrop */}
120
+ // <Animated.View style={[styles.backdrop, { opacity: backdropOpacity }]}>
121
+ // <Pressable style={StyleSheet.absoluteFill} onPress={onClose} />
122
+ // </Animated.View>
123
+ // {/* Bottom Sheet */}
124
+ // <Animated.View
125
+ // {...panResponder.panHandlers}
126
+ // style={[
127
+ // styles.bottomSheet,
128
+ // {
129
+ // backgroundColor: dark ? "#121212" : "#fff",
130
+ // transform: [{ translateY }],
131
+ // },
132
+ // ]}
133
+ // >
134
+ // {/* Handle */}
135
+ // <TouchableOpacity style={styles.handle} onPress={onClose} />
136
+ // {/* Header */}
137
+ // <View style={styles.header}>
138
+ // <ThemedText style={styles.title}>{title}</ThemedText>
139
+ // <TouchableOpacity onPress={onClose}>
140
+ // <AntDesign
141
+ // name="close-circle"
142
+ // size={18}
143
+ // color={dark ? "#fff" : "#333740"}
144
+ // />
145
+ // </TouchableOpacity>
146
+ // </View>
147
+ // <ThemedText className="text-sm text-gray-500 mb-4">
148
+ // {description}
149
+ // </ThemedText>
150
+ // {/* Content */}
151
+ // <FlatList
152
+ // data={filters}
153
+ // keyExtractor={(item) => item.key}
154
+ // showsVerticalScrollIndicator={false}
155
+ // contentContainerStyle={{ paddingBottom: 120, height: "100%" }}
156
+ // renderItem={({ item }) => item.render()}
157
+ // />
158
+ // {/* Actions */}
159
+ // <View style={styles.actions}>
160
+ // <TouchableOpacity onPress={onReset} style={styles.resetButton}>
161
+ // <ThemedText>Cancel</ThemedText>
162
+ // </TouchableOpacity>
163
+ // <TouchableOpacity
164
+ // onPress={onApply}
165
+ // style={styles.applyButton}
166
+ // className="flex flex-row justify-center items-center gap-2"
167
+ // >
168
+ // <ThemedText style={{ color: "#fff" }}>Proceed</ThemedText>
169
+ // {loading && <Spinner size={18} color="#fff" />}
170
+ // </TouchableOpacity>
171
+ // </View>
172
+ // </Animated.View>
173
+ // </Modal>
174
+ // );
175
+ // };
176
+ // export default PurchaseModal;
177
+ // const styles = StyleSheet.create({
178
+ // backdrop: {
179
+ // ...StyleSheet.absoluteFillObject,
180
+ // backgroundColor: "rgba(0,0,0,0.4)",
181
+ // },
182
+ // bottomSheet: {
183
+ // position: "absolute",
184
+ // bottom: 0,
185
+ // width: "100%",
186
+ // height: "80%",
187
+ // paddingHorizontal: 10,
188
+ // paddingVertical: 16,
189
+ // borderTopLeftRadius: 24,
190
+ // borderTopRightRadius: 24,
191
+ // },
192
+ // handle: {
193
+ // width: 40,
194
+ // height: 5,
195
+ // borderRadius: 10,
196
+ // backgroundColor: "#ccc",
197
+ // alignSelf: "center",
198
+ // marginBottom: 12,
199
+ // },
200
+ // header: {
201
+ // flexDirection: "row",
202
+ // justifyContent: "space-between",
203
+ // alignItems: "center",
204
+ // paddingHorizontal: 16,
205
+ // marginBottom: 12,
206
+ // },
207
+ // title: {
208
+ // fontFamily: "PoppinsSemiBold",
209
+ // fontSize: 20,
210
+ // },
211
+ // actions: {
212
+ // position: "absolute",
213
+ // bottom: 24,
214
+ // left: 16,
215
+ // right: 16,
216
+ // flexDirection: "row",
217
+ // gap: 12,
218
+ // },
219
+ // resetButton: {
220
+ // flex: 1,
221
+ // borderWidth: 1,
222
+ // borderColor: "#9ca3af",
223
+ // backgroundColor: "#fff",
224
+ // paddingVertical: 10,
225
+ // borderRadius: 10,
226
+ // alignItems: "center",
227
+ // },
228
+ // applyButton: {
229
+ // flex: 1,
230
+ // paddingVertical: 10,
231
+ // borderRadius: 10,
232
+ // alignItems: "center",
233
+ // backgroundColor: "#027DFF",
234
+ // },
235
+ // });
236
+ import { AntDesign } from "@expo/vector-icons";
237
+ import { useTheme } from "@react-navigation/core";
238
+ import React, { useEffect, useRef, useState } from "react";
239
+ import { Animated, Easing, FlatList, Modal, PanResponder, Pressable, StyleSheet, TouchableOpacity, View, } from "react-native";
240
+ import { ThemedText } from "../themed-text";
241
+ import { Spinner } from "../ui/buttons/spinner";
242
+ const PurchaseModal = ({ visible, title = "Buy Airtime", description, filters, loading, onClose, onApply, onReset, }) => {
243
+ const { dark } = useTheme();
244
+ const translateY = useRef(new Animated.Value(400)).current;
245
+ const backdropOpacity = useRef(new Animated.Value(0)).current;
246
+ const [internalVisible, setInternalVisible] = useState(visible);
247
+ /* ---------------- Animation ---------------- */
248
+ useEffect(() => {
249
+ if (visible) {
250
+ setInternalVisible(true);
251
+ translateY.setValue(400);
252
+ backdropOpacity.setValue(0);
253
+ Animated.parallel([
254
+ Animated.timing(translateY, {
255
+ toValue: 0,
256
+ duration: 350,
257
+ easing: Easing.out(Easing.ease),
258
+ useNativeDriver: true,
259
+ }),
260
+ Animated.timing(backdropOpacity, {
261
+ toValue: 1,
262
+ duration: 250,
263
+ useNativeDriver: true,
264
+ }),
265
+ ]).start();
266
+ }
267
+ else {
268
+ Animated.parallel([
269
+ Animated.timing(translateY, {
270
+ toValue: 400,
271
+ duration: 250,
272
+ easing: Easing.in(Easing.ease),
273
+ useNativeDriver: true,
274
+ }),
275
+ Animated.timing(backdropOpacity, {
276
+ toValue: 0,
277
+ duration: 200,
278
+ useNativeDriver: true,
279
+ }),
280
+ ]).start(() => setInternalVisible(false));
281
+ }
282
+ }, [visible]);
283
+ /* ---------------- PanResponder (Handle only) ---------------- */
284
+ const panResponder = useRef(PanResponder.create({
285
+ onMoveShouldSetPanResponder: (_, gesture) => Math.abs(gesture.dy) > 8,
286
+ onPanResponderMove: (_, gesture) => {
287
+ if (gesture.dy > 0) {
288
+ translateY.setValue(gesture.dy);
289
+ }
290
+ },
291
+ onPanResponderRelease: (_, gesture) => {
292
+ if (gesture.dy > 120 || gesture.vy > 1.2) {
293
+ Animated.timing(translateY, {
294
+ toValue: 400,
295
+ duration: 200,
296
+ easing: Easing.in(Easing.ease),
297
+ useNativeDriver: true,
298
+ }).start(onClose);
299
+ }
300
+ else {
301
+ Animated.spring(translateY, {
302
+ toValue: 0,
303
+ tension: 120,
304
+ friction: 12,
305
+ useNativeDriver: true,
306
+ }).start();
307
+ }
308
+ },
309
+ })).current;
310
+ if (!internalVisible)
311
+ return null;
312
+ return (<Modal transparent visible animationType="none">
313
+ {/* Backdrop */}
314
+ <Animated.View style={[styles.backdrop, { opacity: backdropOpacity }]}>
315
+ <Pressable style={StyleSheet.absoluteFill} onPress={onClose}/>
316
+ </Animated.View>
317
+
318
+ {/* Bottom Sheet */}
319
+ <Animated.View style={[
320
+ styles.bottomSheet,
321
+ {
322
+ backgroundColor: dark ? "#333740" : "#fff",
323
+ transform: [{ translateY }],
324
+ },
325
+ ]}>
326
+ {/* Handle (Drag here only) */}
327
+ <TouchableOpacity {...panResponder.panHandlers} style={styles.handle}/>
328
+
329
+ {/* Header */}
330
+ <View style={styles.header}>
331
+ <ThemedText style={styles.title}>{title}</ThemedText>
332
+ <TouchableOpacity onPress={onClose}>
333
+ <AntDesign name="close-circle" size={18} color={dark ? "#fff" : "#333740"}/>
334
+ </TouchableOpacity>
335
+ </View>
336
+
337
+ {description && (<ThemedText className="text-sm text-gray-500 mb-4">
338
+ {description}
339
+ </ThemedText>)}
340
+
341
+ {/* Content (NOW SCROLLABLE ✅) */}
342
+ <FlatList data={filters} keyExtractor={(item) => item.key} showsVerticalScrollIndicator={false} contentContainerStyle={{ paddingBottom: 140 }} renderItem={({ item }) => item.render()}/>
343
+
344
+ {/* Actions */}
345
+ <View style={styles.actions}>
346
+ <TouchableOpacity onPress={onReset} style={styles.resetButton}>
347
+ <ThemedText>Cancel</ThemedText>
348
+ </TouchableOpacity>
349
+
350
+ <TouchableOpacity onPress={onApply} style={styles.applyButton} className="flex-row justify-center items-center gap-2">
351
+ <ThemedText style={{ color: "#fff" }}>Proceed</ThemedText>
352
+ {loading && <Spinner size={18} color="#fff"/>}
353
+ </TouchableOpacity>
354
+ </View>
355
+ </Animated.View>
356
+ </Modal>);
357
+ };
358
+ export default PurchaseModal;
359
+ /* ---------------- Styles ---------------- */
360
+ const styles = StyleSheet.create({
361
+ backdrop: Object.assign(Object.assign({}, StyleSheet.absoluteFillObject), { backgroundColor: "rgba(0,0,0,0.4)" }),
362
+ bottomSheet: {
363
+ position: "absolute",
364
+ bottom: 0,
365
+ width: "100%",
366
+ height: "80%",
367
+ paddingHorizontal: 10,
368
+ paddingVertical: 16,
369
+ borderTopLeftRadius: 24,
370
+ borderTopRightRadius: 24,
371
+ },
372
+ handle: {
373
+ width: 40,
374
+ height: 5,
375
+ borderRadius: 10,
376
+ backgroundColor: "#ccc",
377
+ alignSelf: "center",
378
+ marginBottom: 12,
379
+ },
380
+ header: {
381
+ flexDirection: "row",
382
+ justifyContent: "space-between",
383
+ alignItems: "center",
384
+ paddingHorizontal: 16,
385
+ marginBottom: 12,
386
+ },
387
+ title: {
388
+ fontFamily: "PoppinsSemiBold",
389
+ fontSize: 20,
390
+ },
391
+ actions: {
392
+ position: "absolute",
393
+ bottom: 24,
394
+ left: 16,
395
+ right: 16,
396
+ flexDirection: "row",
397
+ gap: 12,
398
+ },
399
+ resetButton: {
400
+ flex: 1,
401
+ borderWidth: 1,
402
+ borderColor: "#9ca3af",
403
+ backgroundColor: "#fff",
404
+ paddingVertical: 10,
405
+ borderRadius: 10,
406
+ alignItems: "center",
407
+ },
408
+ applyButton: {
409
+ flex: 1,
410
+ paddingVertical: 10,
411
+ borderRadius: 10,
412
+ alignItems: "center",
413
+ backgroundColor: "#027DFF",
414
+ },
415
+ });
@@ -0,0 +1,16 @@
1
+ type TransferPaymentProps = {
2
+ loading: boolean;
3
+ amount: number;
4
+ transferDetail: {
5
+ bankName: string;
6
+ accountNumber: string;
7
+ accountName: string;
8
+ };
9
+ timeLeft: number;
10
+ copied: boolean;
11
+ handleCopy: (text: string) => void;
12
+ formatAmount: (value: number) => string;
13
+ formatTime: (seconds: number) => string;
14
+ };
15
+ export default function TransferPayment({ loading, amount, transferDetail, timeLeft, copied, handleCopy, formatAmount, formatTime, }: TransferPaymentProps): import("react").JSX.Element;
16
+ export {};