react-native-fpay 0.4.14 → 0.4.15

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 (44) hide show
  1. package/lib/module/FountainPayProvider.js +79 -6
  2. package/lib/module/FountainPayProvider.js.map +1 -1
  3. package/lib/module/core/api/client.js +1 -1
  4. package/lib/module/core/api/client.js.map +1 -1
  5. package/lib/module/core/api/index.js +2 -1
  6. package/lib/module/core/api/index.js.map +1 -1
  7. package/lib/module/core/types/index.js.map +1 -1
  8. package/lib/module/engine/BLESenderService.js +32 -7
  9. package/lib/module/engine/BLESenderService.js.map +1 -1
  10. package/lib/module/engine/FPEngine.js +10 -0
  11. package/lib/module/engine/FPEngine.js.map +1 -1
  12. package/lib/module/ui/components/Gradients/Skeleton.js +54 -0
  13. package/lib/module/ui/components/Gradients/Skeleton.js.map +1 -0
  14. package/lib/module/ui/components/Gradients/index.js +85 -0
  15. package/lib/module/ui/components/Gradients/index.js.map +1 -0
  16. package/lib/module/ui/screens/ResultScreen.js +18 -5
  17. package/lib/module/ui/screens/ResultScreen.js.map +1 -1
  18. package/lib/module/ui/screens/sub/sendPayment/BluetoothSubScreen.js +7 -2
  19. package/lib/module/ui/screens/sub/sendPayment/BluetoothSubScreen.js.map +1 -1
  20. package/lib/typescript/src/FountainPayProvider.d.ts +3 -2
  21. package/lib/typescript/src/FountainPayProvider.d.ts.map +1 -1
  22. package/lib/typescript/src/core/api/index.d.ts +16 -0
  23. package/lib/typescript/src/core/api/index.d.ts.map +1 -1
  24. package/lib/typescript/src/core/types/index.d.ts +2 -0
  25. package/lib/typescript/src/core/types/index.d.ts.map +1 -1
  26. package/lib/typescript/src/engine/BLESenderService.d.ts.map +1 -1
  27. package/lib/typescript/src/engine/FPEngine.d.ts.map +1 -1
  28. package/lib/typescript/src/ui/components/Gradients/Skeleton.d.ts +3 -0
  29. package/lib/typescript/src/ui/components/Gradients/Skeleton.d.ts.map +1 -0
  30. package/lib/typescript/src/ui/components/Gradients/index.d.ts +3 -0
  31. package/lib/typescript/src/ui/components/Gradients/index.d.ts.map +1 -0
  32. package/lib/typescript/src/ui/screens/ResultScreen.d.ts.map +1 -1
  33. package/lib/typescript/src/ui/screens/sub/sendPayment/BluetoothSubScreen.d.ts.map +1 -1
  34. package/package.json +1 -1
  35. package/src/FountainPayProvider.tsx +89 -11
  36. package/src/core/api/client.ts +1 -1
  37. package/src/core/api/index.ts +18 -0
  38. package/src/core/types/index.ts +2 -0
  39. package/src/engine/BLESenderService.ts +44 -17
  40. package/src/engine/FPEngine.ts +11 -0
  41. package/src/ui/components/Gradients/Skeleton.tsx +48 -0
  42. package/src/ui/components/Gradients/index.tsx +94 -0
  43. package/src/ui/screens/ResultScreen.tsx +23 -6
  44. package/src/ui/screens/sub/sendPayment/BluetoothSubScreen.tsx +7 -7
@@ -41,6 +41,24 @@ export const authenticateAPI = {
41
41
  http()
42
42
  .post<{ Response: any }>('/verify-otp', { otp, email })
43
43
  .then((r) => r.data),
44
+
45
+ validateToken: () =>
46
+ http().get<{
47
+ status: boolean;
48
+ message: string;
49
+ payload: {
50
+ id: string;
51
+ firstName: string;
52
+ lastName: string;
53
+ email: string;
54
+ phone: string;
55
+ bvn?: string;
56
+ accountNumber: string;
57
+ bankName: string;
58
+ bankCode?: string;
59
+ tradeName?: string;
60
+ };
61
+ }>('/auth/agent/validate-token').then(r => r.data),
44
62
  };
45
63
 
46
64
  export const accountAPI = {
@@ -28,6 +28,7 @@ export interface FPSDKOptions {
28
28
  environment?: 'sandbox' | 'production';
29
29
  proximityRadius?: number;
30
30
  bluetoothDisplayName?: string;
31
+ userType?: 'USER' | 'AGENT';
31
32
  }
32
33
 
33
34
  export interface FPTransferRecipient {
@@ -43,6 +44,7 @@ export interface FPWalletTransferRecipient {
43
44
  accountName: string;
44
45
  agentId?: string;
45
46
  userId?: string;
47
+ terminalId?: string;
46
48
  }
47
49
 
48
50
  export interface FPNfcRecipient {
@@ -195,9 +195,10 @@ class BLESenderService {
195
195
  rawDevice: device,
196
196
  };
197
197
  } catch (err) {
198
- try {
199
- await this.bleManager.cancelDeviceConnection(device.id);
200
- } catch {}
198
+ console.log("Read Device Info Error: ", err)
199
+ // try {
200
+ // await this.bleManager.cancelDeviceConnection(device.id);
201
+ // } catch {}
201
202
  return null;
202
203
  }
203
204
  }
@@ -233,10 +234,14 @@ class BLESenderService {
233
234
  ): Promise<BLESendPaymentResponse> {
234
235
  let responseReceived = false;
235
236
 
237
+
236
238
  try {
239
+ this.bleManager.stopDeviceScan();
240
+
237
241
  let device = this.connectedDevices.get(deviceId);
238
242
  if (!device) device = await this.connectToDevice(deviceId);
239
243
 
244
+
240
245
  // Start polling BEFORE writing the request, so we don't miss a fast response
241
246
  const responsePromise = new Promise<BLESendPaymentResponse>(
242
247
  async (resolve, reject) => {
@@ -257,6 +262,9 @@ class BLESenderService {
257
262
  await new Promise((r) => setTimeout(r, 1000));
258
263
  if (responseReceived) break;
259
264
 
265
+ // iOS caches BLE reads — force re-discovery to get fresh values
266
+
267
+
260
268
  console.log(
261
269
  '[FPay BLE Sender] Polling response characteristic...'
262
270
  );
@@ -292,6 +300,7 @@ class BLESenderService {
292
300
  }
293
301
  } catch (pollErr: any) {
294
302
  // errorCode 205 = device disconnected — abort immediately
303
+ console.log('[FPay BLE Sender] Poll error:', pollErr);
295
304
  if (pollErr.errorCode === 205) {
296
305
  reject(new Error('[FPay BLE Sender] Device disconnected'));
297
306
  break;
@@ -314,22 +323,40 @@ class BLESenderService {
314
323
  console.log('[FPay BLE Sender] Writing payment request...');
315
324
 
316
325
  try {
317
- await device.writeCharacteristicWithoutResponseForService(
318
- FP_SERVICE_UUID,
319
- FP_REQUEST_CHAR_UUID,
320
- base64
321
- );
322
- console.log('[FPay BLE Sender] Written without response (preferred)');
323
- } catch {
324
- console.log('[FPay BLE Sender] Retrying with response...');
325
- await device.writeCharacteristicWithResponseForService(
326
- FP_SERVICE_UUID,
327
- FP_REQUEST_CHAR_UUID,
328
- base64
329
- );
330
- console.log('[FPay BLE Sender] Written with response');
326
+ await device.writeCharacteristicWithResponseForService(
327
+ FP_SERVICE_UUID,
328
+ FP_REQUEST_CHAR_UUID,
329
+ base64
330
+ );
331
+ console.log('[FPay BLE Sender] Written with response');
332
+ } catch (e: any) {
333
+ console.log('[FPay BLE Sender] Write with response failed:', e.message);
334
+ console.log('[FPay BLE Sender] Retrying without response...');
335
+ await device.writeCharacteristicWithoutResponseForService(
336
+ FP_SERVICE_UUID,
337
+ FP_REQUEST_CHAR_UUID,
338
+ base64
339
+ );
340
+ console.log('[FPay BLE Sender] Written without response');
331
341
  }
332
342
 
343
+ // try {
344
+ // await device.writeCharacteristicWithoutResponseForService(
345
+ // FP_SERVICE_UUID,
346
+ // FP_REQUEST_CHAR_UUID,
347
+ // base64
348
+ // );
349
+ // console.log('[FPay BLE Sender] Written without response (preferred)');
350
+ // } catch {
351
+ // console.log('[FPay BLE Sender] Retrying with response...');
352
+ // await device.writeCharacteristicWithResponseForService(
353
+ // FP_SERVICE_UUID,
354
+ // FP_REQUEST_CHAR_UUID,
355
+ // base64
356
+ // );
357
+ // console.log('[FPay BLE Sender] Written with response');
358
+ // }
359
+
333
360
  console.log(
334
361
  '[FPay BLE Sender] Request sent. Waiting for accept/decline...'
335
362
  );
@@ -3,6 +3,7 @@
3
3
  // This replaces AlwaysOnPaymentListener entirely — all its logic lives here.
4
4
 
5
5
  import {
6
+ Alert,
6
7
  AppState,
7
8
  type AppStateStatus,
8
9
  Vibration,
@@ -434,6 +435,16 @@ export const FPEngine = {
434
435
  _isReady = true;
435
436
  console.log('[FPay Engine] Initialized. Advertising as:', displayName);
436
437
 
438
+ // AGENT — account already stored by provider, skip resolve
439
+ if (options.userType === 'AGENT') {
440
+ const account = getFPStore().account;
441
+ if (account) {
442
+ callbacks.onAccountReady?.(account);
443
+ _fetchBalance();
444
+ }
445
+ return;
446
+ }
447
+
437
448
  // Resolve account first — sets psspId in store
438
449
  // Then broadcast proximity — needs psspId to be set
439
450
  await _resolveAccount(user, callbacks);
@@ -0,0 +1,48 @@
1
+ import React, { useRef, useEffect } from 'react';
2
+ import { Animated, View, StyleSheet } from 'react-native';
3
+ import LinearGradient from 'react-native-linear-gradient';
4
+
5
+ const Skeleton = ({ width, height, borderRadius = 8, style }: any) => {
6
+ const translateX = useRef(new Animated.Value(-100)).current;
7
+
8
+ useEffect(() => {
9
+ Animated.loop(
10
+ Animated.timing(translateX, {
11
+ toValue: 300,
12
+ duration: 1200,
13
+ useNativeDriver: true,
14
+ })
15
+ ).start();
16
+ }, []);
17
+
18
+ return (
19
+ <View
20
+ style={[
21
+ {
22
+ width,
23
+ height,
24
+ borderRadius,
25
+ backgroundColor: '#e0e0e0',
26
+ overflow: 'hidden',
27
+ },
28
+ style,
29
+ ]}
30
+ >
31
+ <Animated.View
32
+ style={{
33
+ ...StyleSheet.absoluteFillObject,
34
+ transform: [{ translateX }],
35
+ }}
36
+ >
37
+ <LinearGradient
38
+ colors={['#e0e0e0', '#f5f5f5', '#e0e0e0']}
39
+ start={{ x: 0, y: 0 }}
40
+ end={{ x: 1, y: 0 }}
41
+ style={{ flex: 1 }}
42
+ />
43
+ </Animated.View>
44
+ </View>
45
+ );
46
+ };
47
+
48
+ export default Skeleton;
@@ -0,0 +1,94 @@
1
+ import { Animated, View } from "react-native";
2
+ import { C, R, S } from "../../theme";
3
+ import Skeleton from "./Skeleton";
4
+ import styled from "styled-components/native";
5
+
6
+
7
+
8
+ const Container = styled(Animated.View)`
9
+ flex: 1;
10
+ background-color: ${C.white};
11
+ padding: ${S.xxl}px ${S.lg}px ${S.xl}px;
12
+ align-items: center;
13
+ `;
14
+
15
+
16
+ const Card = styled.View`
17
+ width: 100%;
18
+ background-color: ${C.surface};
19
+ border-radius: ${R.xl}px;
20
+ padding: ${S.md}px ${S.lg}px;
21
+ margin-bottom: ${S.xl}px;
22
+ `;
23
+
24
+ const Footer = styled.View`
25
+ flex-direction: row;
26
+ align-items: center;
27
+ margin-top: auto;
28
+ `;
29
+
30
+
31
+ const Gradients = () => {
32
+ return (
33
+ <Container>
34
+ {/* Icon */}
35
+ <Skeleton
36
+ width={80}
37
+ height={80}
38
+ borderRadius={40}
39
+ style={{ alignSelf: 'center', marginBottom: 20 }}
40
+ />
41
+
42
+ {/* Status Text */}
43
+ <Skeleton
44
+ width={200}
45
+ height={20}
46
+ style={{ alignSelf: 'center', marginBottom: 16 }}
47
+ />
48
+
49
+ {/* Amount */}
50
+ <Skeleton
51
+ width={180}
52
+ height={30}
53
+ style={{ alignSelf: 'center', marginBottom: 24 }}
54
+ />
55
+
56
+ {/* Card */}
57
+ <Card>
58
+ {[...Array(5)].map((_, i) => (
59
+ <View
60
+ key={i}
61
+ style={{
62
+ flexDirection: 'row',
63
+ justifyContent: 'space-between',
64
+ marginBottom: 14,
65
+ }}
66
+ >
67
+ <Skeleton width={100} height={14} />
68
+ <Skeleton width={140} height={14} />
69
+ </View>
70
+ ))}
71
+ </Card>
72
+
73
+ {/* Footer */}
74
+ <Footer style={{ marginTop: 30 }}>
75
+ {/* Countdown ring placeholder */}
76
+ <Skeleton
77
+ width={60}
78
+ height={60}
79
+ borderRadius={30}
80
+ style={{ marginBottom: 20 }}
81
+ />
82
+
83
+ {/* Button */}
84
+ <Skeleton
85
+ width={'100%'}
86
+ height={50}
87
+ borderRadius={25}
88
+ />
89
+ </Footer>
90
+ </Container>
91
+ );
92
+ };
93
+
94
+ export default Gradients;
@@ -5,6 +5,7 @@ import styled from 'styled-components/native';
5
5
  import { C, F, R, S } from '../theme';
6
6
  import type { FPTransaction } from '../../core/types';
7
7
  import { transferAPI } from '../../core/api';
8
+ import Gradients from '../components/Gradients';
8
9
 
9
10
  // ── Icons ─────────────────────────────────────────────────────
10
11
 
@@ -203,6 +204,7 @@ interface Props {
203
204
  }
204
205
 
205
206
  export function ResultScreen({ transaction, onClose }: Props) {
207
+ const [loading, setLoading] = useState<boolean>(false);
206
208
  const [transactionDetail, setTransactionDetail] = useState<any>(null)
207
209
  const slideAnim = useRef(new Animated.Value(60)).current;
208
210
  const opacAnim = useRef(new Animated.Value(0)).current;
@@ -232,11 +234,20 @@ export function ResultScreen({ transaction, onClose }: Props) {
232
234
  : '—';
233
235
 
234
236
  const loadTransaction = async()=>{
235
- const response = await transferAPI.status(transaction.reference) as any;
236
- console.log("Transaction payload: ", response);
237
- if(response.status){
238
- setTransactionDetail(response.payload);
237
+ setLoading(true);
238
+ setTransactionDetail(null);
239
+ try{
240
+ const response = await transferAPI.status(transaction.reference) as any;
241
+ console.log("Transaction payload: ", response);
242
+ if(response.status){
243
+ setTransactionDetail(response.payload);
244
+ }
245
+ }catch(err: any){
246
+ console.error("Error loading transactions status")
247
+ }finally{
248
+ setLoading(false);
239
249
  }
250
+
240
251
  }
241
252
 
242
253
  useEffect(() => {
@@ -256,10 +267,16 @@ export function ResultScreen({ transaction, onClose }: Props) {
256
267
  if(transaction){
257
268
  loadTransaction();
258
269
  }
259
- }, [transaction])
270
+ }, [transaction]);
271
+
272
+
273
+ if (loading && !transactionDetail) {
274
+ return <Gradients />;
275
+ }
260
276
 
261
277
  return (
262
278
  <Container style={{ opacity: opacAnim, transform: [{ translateY: slideAnim }] }}>
279
+
263
280
  <IconWrap style={{ transform: [{ scale: scaleAnim }] }}>
264
281
  <IconBg bg={bgColor}>
265
282
  {isSuccess ? <SuccessIcon /> : <FailedIcon />}
@@ -282,7 +299,7 @@ export function ResultScreen({ transaction, onClose }: Props) {
282
299
 
283
300
  <Footer>
284
301
  <CountdownRing duration={COUNTDOWN_SECONDS * 1000} />
285
- <CloseBtn onPress={loadTransaction} activeOpacity={0.8}>
302
+ <CloseBtn onPress={onClose} activeOpacity={0.8}>
286
303
  <CloseBtnText>Close</CloseBtnText>
287
304
  </CloseBtn>
288
305
  </Footer>
@@ -17,15 +17,9 @@ import styled from 'styled-components/native';
17
17
  import Svg, { Path } from 'react-native-svg';
18
18
  import Ionicons from 'react-native-vector-icons/Ionicons';
19
19
 
20
- import { transferAPI } from '../../../../core/api';
21
- import { FPButton } from '../../../components/FPButton';
22
- import { C, R, S, F, shadow } from '../../../theme';
23
20
  import type {
24
- FPCurrency,
25
21
  FPError,
26
- FPSendPaymentRequest,
27
22
  FPSendWalletPaymentRequest,
28
- FPTransaction,
29
23
  FPUserInfo,
30
24
  FintechDevice,
31
25
  Props,
@@ -343,6 +337,8 @@ export function BluetoothSubScreen({
343
337
  request
344
338
  );
345
339
 
340
+ await FPEngine.stopListening();
341
+
346
342
  console.log('Component response: ', response);
347
343
 
348
344
  if (!response.accepted) {
@@ -367,8 +363,10 @@ export function BluetoothSubScreen({
367
363
  recipient: {
368
364
  accountName: response.accountDetails.accountName,
369
365
  accountNumber: response.accountDetails.accountNumber,
370
- userId: response.accountDetails.userId,
366
+ userId: response.accountDetails.userId || "",
367
+ agentId: response.accountDetails.agentId || "",
371
368
  type: response.accountDetails.receiverType ?? 'USER',
369
+ terminalId: response.accountDetails.terminalId || ''
372
370
  },
373
371
  amount: request.amount.toString(),
374
372
  reference: response.transactionId ?? `BT_${Date.now()}`,
@@ -402,6 +400,8 @@ export function BluetoothSubScreen({
402
400
  Alert.alert('Error', 'Failed to send payment request');
403
401
  }
404
402
  onError?.(fp);
403
+ }finally{
404
+ await FPEngine.startListening();
405
405
  }
406
406
  };
407
407