react-native-fpay 0.3.5 → 0.3.7

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 (155) hide show
  1. package/lib/module/FountainPayProvider.js +108 -18
  2. package/lib/module/FountainPayProvider.js.map +1 -1
  3. package/lib/module/core/api/client.js +13 -1
  4. package/lib/module/core/api/client.js.map +1 -1
  5. package/lib/module/core/api/index.js +66 -24
  6. package/lib/module/core/api/index.js.map +1 -1
  7. package/lib/module/engine/BLEReceiverService.js.map +1 -1
  8. package/lib/module/engine/BLESenderService.js.map +1 -1
  9. package/lib/module/engine/FPEngine.js +164 -43
  10. package/lib/module/engine/FPEngine.js.map +1 -1
  11. package/lib/module/engine/NearbyUsersService.js +1 -1
  12. package/lib/module/engine/NearbyUsersService.js.map +1 -1
  13. package/lib/module/index.js +1 -0
  14. package/lib/module/index.js.map +1 -1
  15. package/lib/module/store/FPStore.js +155 -0
  16. package/lib/module/store/FPStore.js.map +1 -0
  17. package/lib/module/ui/components/AnimatedDots.js +3 -3
  18. package/lib/module/ui/components/AnimatedDots.js.map +1 -1
  19. package/lib/module/ui/components/ConfirmScreen.js +137 -123
  20. package/lib/module/ui/components/ConfirmScreen.js.map +1 -1
  21. package/lib/module/ui/components/FPButton.js.map +1 -1
  22. package/lib/module/ui/components/LoadingAnimation/InLoading.js +3 -3
  23. package/lib/module/ui/components/LoadingAnimation/InLoading.js.map +1 -1
  24. package/lib/module/ui/components/LoadingAnimation/index.js +3 -3
  25. package/lib/module/ui/components/LoadingAnimation/index.js.map +1 -1
  26. package/lib/module/ui/components/OtpInput/OTPInputView.js +11 -11
  27. package/lib/module/ui/components/OtpInput/OTPInputView.js.map +1 -1
  28. package/lib/module/ui/components/OtpInput/Styles.js +1 -1
  29. package/lib/module/ui/components/OtpInput/helpers/codeToArray.js +1 -1
  30. package/lib/module/ui/components/OtpInput/helpers/device.js.map +1 -1
  31. package/lib/module/ui/components/OtpInput/helpers/styles.js.map +1 -1
  32. package/lib/module/ui/components/OtpInput/index.js +3 -1
  33. package/lib/module/ui/components/OtpInput/index.js.map +1 -1
  34. package/lib/module/ui/components/PulseAnimation.js +2 -2
  35. package/lib/module/ui/components/PulseAnimation.js.map +1 -1
  36. package/lib/module/ui/modals/FPPaymentRequestModal.js +8 -7
  37. package/lib/module/ui/modals/FPPaymentRequestModal.js.map +1 -1
  38. package/lib/module/ui/modals/FPShell.js +2 -0
  39. package/lib/module/ui/modals/FPShell.js.map +1 -1
  40. package/lib/module/ui/screens/ReceiveScreen.js +8 -9
  41. package/lib/module/ui/screens/ReceiveScreen.js.map +1 -1
  42. package/lib/module/ui/screens/SendScreen.js +43 -94
  43. package/lib/module/ui/screens/SendScreen.js.map +1 -1
  44. package/lib/module/ui/screens/styles.js +15 -3
  45. package/lib/module/ui/screens/styles.js.map +1 -1
  46. package/lib/module/ui/screens/sub/receivePayment/Nfc/index.js +4 -4
  47. package/lib/module/ui/screens/sub/receivePayment/Nfc/index.js.map +1 -1
  48. package/lib/module/ui/screens/sub/receivePayment/Qr/index.js +5 -5
  49. package/lib/module/ui/screens/sub/receivePayment/Qr/index.js.map +1 -1
  50. package/lib/module/ui/screens/sub/receivePayment/Transfer/index.js +10 -11
  51. package/lib/module/ui/screens/sub/receivePayment/Transfer/index.js.map +1 -1
  52. package/lib/module/ui/screens/sub/sendPayment/BluetoothSubScreen.js +31 -8
  53. package/lib/module/ui/screens/sub/sendPayment/BluetoothSubScreen.js.map +1 -1
  54. package/lib/module/ui/screens/sub/sendPayment/NFCSubScreen.js +12 -8
  55. package/lib/module/ui/screens/sub/sendPayment/NFCSubScreen.js.map +1 -1
  56. package/lib/module/ui/screens/sub/sendPayment/NQRSubScreen.js +17 -5
  57. package/lib/module/ui/screens/sub/sendPayment/NQRSubScreen.js.map +1 -1
  58. package/lib/module/ui/screens/sub/sendPayment/ProximitySubScreen.js +67 -35
  59. package/lib/module/ui/screens/sub/sendPayment/ProximitySubScreen.js.map +1 -1
  60. package/lib/module/ui/screens/sub/sendPayment/TransferSubScreen.js +110 -34
  61. package/lib/module/ui/screens/sub/sendPayment/TransferSubScreen.js.map +1 -1
  62. package/lib/module/ui/theme/index.js.map +1 -1
  63. package/lib/typescript/src/FountainPayProvider.d.ts +1 -1
  64. package/lib/typescript/src/FountainPayProvider.d.ts.map +1 -1
  65. package/lib/typescript/src/core/api/client.d.ts.map +1 -1
  66. package/lib/typescript/src/core/api/index.d.ts +42 -26
  67. package/lib/typescript/src/core/api/index.d.ts.map +1 -1
  68. package/lib/typescript/src/core/types/index.d.ts +53 -28
  69. package/lib/typescript/src/core/types/index.d.ts.map +1 -1
  70. package/lib/typescript/src/engine/BLEReceiverService.d.ts +2 -0
  71. package/lib/typescript/src/engine/BLEReceiverService.d.ts.map +1 -1
  72. package/lib/typescript/src/engine/BLESenderService.d.ts.map +1 -1
  73. package/lib/typescript/src/engine/FPEngine.d.ts +5 -3
  74. package/lib/typescript/src/engine/FPEngine.d.ts.map +1 -1
  75. package/lib/typescript/src/engine/useIsForeground.d.ts.map +1 -1
  76. package/lib/typescript/src/index.d.ts +2 -1
  77. package/lib/typescript/src/index.d.ts.map +1 -1
  78. package/lib/typescript/src/store/FPStore.d.ts +60 -0
  79. package/lib/typescript/src/store/FPStore.d.ts.map +1 -0
  80. package/lib/typescript/src/ui/components/AnimatedDots.d.ts.map +1 -1
  81. package/lib/typescript/src/ui/components/ConfirmScreen.d.ts +5 -5
  82. package/lib/typescript/src/ui/components/ConfirmScreen.d.ts.map +1 -1
  83. package/lib/typescript/src/ui/components/FPButton.d.ts +1 -1
  84. package/lib/typescript/src/ui/components/FPButton.d.ts.map +1 -1
  85. package/lib/typescript/src/ui/components/LoadingAnimation/InLoading.d.ts.map +1 -1
  86. package/lib/typescript/src/ui/components/LoadingAnimation/index.d.ts.map +1 -1
  87. package/lib/typescript/src/ui/components/OtpInput/OTPInputView.d.ts.map +1 -1
  88. package/lib/typescript/src/ui/components/OtpInput/helpers/codeToArray.d.ts.map +1 -1
  89. package/lib/typescript/src/ui/components/OtpInput/helpers/device.d.ts.map +1 -1
  90. package/lib/typescript/src/ui/components/OtpInput/helpers/styles.d.ts.map +1 -1
  91. package/lib/typescript/src/ui/components/OtpInput/helpers/types.d.ts +1 -2
  92. package/lib/typescript/src/ui/components/OtpInput/helpers/types.d.ts.map +1 -1
  93. package/lib/typescript/src/ui/components/OtpInput/index.d.ts +3 -1
  94. package/lib/typescript/src/ui/components/OtpInput/index.d.ts.map +1 -1
  95. package/lib/typescript/src/ui/components/PulseAnimation.d.ts.map +1 -1
  96. package/lib/typescript/src/ui/modals/FPPaymentRequestModal.d.ts.map +1 -1
  97. package/lib/typescript/src/ui/modals/FPShell.d.ts.map +1 -1
  98. package/lib/typescript/src/ui/screens/ReceiveScreen.d.ts +1 -1
  99. package/lib/typescript/src/ui/screens/ReceiveScreen.d.ts.map +1 -1
  100. package/lib/typescript/src/ui/screens/SendScreen.d.ts +1 -1
  101. package/lib/typescript/src/ui/screens/SendScreen.d.ts.map +1 -1
  102. package/lib/typescript/src/ui/screens/styles.d.ts +197 -0
  103. package/lib/typescript/src/ui/screens/styles.d.ts.map +1 -1
  104. package/lib/typescript/src/ui/screens/sub/receivePayment/Nfc/index.d.ts.map +1 -1
  105. package/lib/typescript/src/ui/screens/sub/receivePayment/Qr/index.d.ts.map +1 -1
  106. package/lib/typescript/src/ui/screens/sub/receivePayment/Transfer/index.d.ts.map +1 -1
  107. package/lib/typescript/src/ui/screens/sub/sendPayment/BluetoothSubScreen.d.ts.map +1 -1
  108. package/lib/typescript/src/ui/screens/sub/sendPayment/NFCSubScreen.d.ts +1 -1
  109. package/lib/typescript/src/ui/screens/sub/sendPayment/NFCSubScreen.d.ts.map +1 -1
  110. package/lib/typescript/src/ui/screens/sub/sendPayment/NQRSubScreen.d.ts +1 -1
  111. package/lib/typescript/src/ui/screens/sub/sendPayment/NQRSubScreen.d.ts.map +1 -1
  112. package/lib/typescript/src/ui/screens/sub/sendPayment/ProximitySubScreen.d.ts +1 -1
  113. package/lib/typescript/src/ui/screens/sub/sendPayment/ProximitySubScreen.d.ts.map +1 -1
  114. package/lib/typescript/src/ui/screens/sub/sendPayment/TransferSubScreen.d.ts +1 -1
  115. package/lib/typescript/src/ui/screens/sub/sendPayment/TransferSubScreen.d.ts.map +1 -1
  116. package/lib/typescript/src/ui/theme/index.d.ts.map +1 -1
  117. package/package.json +4 -4
  118. package/src/FountainPayProvider.tsx +163 -24
  119. package/src/core/api/client.ts +26 -4
  120. package/src/core/api/index.ts +170 -49
  121. package/src/core/types/index.ts +81 -48
  122. package/src/engine/BLEReceiverService.ts +86 -28
  123. package/src/engine/BLESenderService.ts +133 -69
  124. package/src/engine/FPEngine.ts +316 -97
  125. package/src/engine/NearbyUsersService.ts +6 -6
  126. package/src/engine/useIsForeground.ts +12 -12
  127. package/src/index.ts +10 -4
  128. package/src/store/FPStore.ts +216 -0
  129. package/src/ui/components/AnimatedDots.tsx +4 -5
  130. package/src/ui/components/ConfirmScreen.tsx +182 -169
  131. package/src/ui/components/FPButton.tsx +50 -9
  132. package/src/ui/components/LoadingAnimation/InLoading.tsx +23 -27
  133. package/src/ui/components/LoadingAnimation/index.tsx +3 -7
  134. package/src/ui/components/OtpInput/OTPInputView.tsx +254 -205
  135. package/src/ui/components/OtpInput/Styles.ts +1 -1
  136. package/src/ui/components/OtpInput/helpers/codeToArray.ts +2 -2
  137. package/src/ui/components/OtpInput/helpers/device.ts +4 -3
  138. package/src/ui/components/OtpInput/helpers/styles.ts +13 -14
  139. package/src/ui/components/OtpInput/helpers/types.ts +83 -79
  140. package/src/ui/components/OtpInput/index.tsx +18 -15
  141. package/src/ui/components/PulseAnimation.tsx +3 -5
  142. package/src/ui/modals/FPPaymentRequestModal.tsx +111 -28
  143. package/src/ui/modals/FPShell.tsx +60 -34
  144. package/src/ui/screens/ReceiveScreen.tsx +245 -84
  145. package/src/ui/screens/SendScreen.tsx +419 -167
  146. package/src/ui/screens/styles.ts +17 -5
  147. package/src/ui/screens/sub/receivePayment/Nfc/index.tsx +17 -25
  148. package/src/ui/screens/sub/receivePayment/Qr/index.tsx +21 -20
  149. package/src/ui/screens/sub/receivePayment/Transfer/index.tsx +34 -28
  150. package/src/ui/screens/sub/sendPayment/BluetoothSubScreen.tsx +135 -67
  151. package/src/ui/screens/sub/sendPayment/NFCSubScreen.tsx +188 -112
  152. package/src/ui/screens/sub/sendPayment/NQRSubScreen.tsx +102 -69
  153. package/src/ui/screens/sub/sendPayment/ProximitySubScreen.tsx +225 -99
  154. package/src/ui/screens/sub/sendPayment/TransferSubScreen.tsx +209 -89
  155. package/src/ui/theme/index.ts +14 -2
@@ -2,24 +2,35 @@
2
2
  // Orchestrates BLEReceiverService, BLESenderService, proximity, and the event bus.
3
3
  // This replaces AlwaysOnPaymentListener entirely — all its logic lives here.
4
4
 
5
- import { AppState, type AppStateStatus, Vibration, Platform } from 'react-native';
5
+ import {
6
+ AppState,
7
+ type AppStateStatus,
8
+ Vibration,
9
+ Platform,
10
+ } from 'react-native';
6
11
  import AsyncStorage from '@react-native-async-storage/async-storage';
7
12
  import Geolocation from '@react-native-community/geolocation';
8
13
  import { initClient } from '../core/api/client';
9
- import { proximityAPI, walletAPI } from '../core/api';
14
+ import { proximityAPI, accountAPI } from '../core/api';
10
15
  import type {
11
- FPUserInfo,
12
- FPSDKOptions,
13
- FPCallbacks,
16
+ FPUserInfo,
17
+ FPSDKOptions,
18
+ FPCallbacks,
14
19
  FPBluetoothPaymentRequest,
15
- FPVirtualAccount,
16
- FPGenerateAccountRequest,
17
20
  FPError,
18
21
  FintechDevice,
19
22
  FPTransferRecipient,
23
+ FPAccount,
20
24
  } from '../core/types';
21
- import BLEReceiverService, { type BLEPaymentRequest, type BLEPaymentResponse } from './BLEReceiverService';
22
- import BLESenderService, { type BLESendPaymentRequest, type BLESendPaymentResponse } from './BLESenderService';
25
+ import BLEReceiverService, {
26
+ type BLEPaymentRequest,
27
+ type BLEPaymentResponse,
28
+ } from './BLEReceiverService';
29
+ import BLESenderService, {
30
+ type BLESendPaymentRequest,
31
+ type BLESendPaymentResponse,
32
+ } from './BLESenderService';
33
+ import { getFPStore, isBalanceCacheValid } from '../store/FPStore';
23
34
 
24
35
  // ── Module-level singleton state ──────────────────────────────
25
36
  let _apiKey: string | null = null;
@@ -33,15 +44,21 @@ let _proximitySessionId: string | null = null;
33
44
  let _proximityInterval: ReturnType<typeof setInterval> | null = null;
34
45
  let _appStateSub: ReturnType<typeof AppState.addEventListener> | null = null;
35
46
 
47
+ const PROXIMITY_THRESHOLD_METERS = 100;
48
+
36
49
  // ── Internal event bus ────────────────────────────────────────
37
50
  // Used to communicate between FPEngine and the UI layer
38
51
  // without any React coupling.
39
52
 
40
- type FPEventName = 'incoming_payment_request' | 'show_send' | 'show_receive';
53
+ type FPEventName =
54
+ | 'incoming_payment_request'
55
+ | 'show_send'
56
+ | 'show_receive'
57
+ | 'close_send';
41
58
  const _listeners = new Map<FPEventName, Set<Function>>();
42
59
 
43
60
  export function _emitEvent(event: FPEventName, data?: unknown): void {
44
- _listeners.get(event)?.forEach(fn => fn(data));
61
+ _listeners.get(event)?.forEach((fn) => fn(data));
45
62
  }
46
63
 
47
64
  export function _onEvent(event: FPEventName, fn: Function): () => void {
@@ -53,45 +70,120 @@ export function _onEvent(event: FPEventName, fn: Function): () => void {
53
70
  // ── Proximity helpers ─────────────────────────────────────────
54
71
 
55
72
  function _clearProximityInterval(): void {
56
- if (_proximityInterval) { clearInterval(_proximityInterval); _proximityInterval = null; }
73
+ if (_proximityInterval) {
74
+ clearInterval(_proximityInterval);
75
+ _proximityInterval = null;
76
+ }
57
77
  }
58
78
 
59
79
  function _getLocation(): Promise<{ lat: number; lng: number }> {
60
80
  return new Promise((resolve, reject) => {
61
81
  Geolocation.getCurrentPosition(
62
- p => resolve({ lat: p.coords.latitude, lng: p.coords.longitude }),
63
- e => reject({ code: 'LOCATION_DENIED', message: e.message } as FPError),
82
+ (p) => resolve({ lat: p.coords.latitude, lng: p.coords.longitude }),
83
+ (e) => reject({ code: 'LOCATION_DENIED', message: e.message } as FPError),
64
84
  { enableHighAccuracy: true, timeout: 8000, maximumAge: 10000 }
65
85
  );
66
86
  });
67
87
  }
68
88
 
69
89
  async function _startProximityBroadcast(): Promise<void> {
70
- if (!_user || !_account) return;
90
+ const store = getFPStore();
91
+ const psspId = store.psspId;
92
+ if (!_user || !psspId) {
93
+ console.warn('[FPay Engine] Proximity skipped — no user or psspId');
94
+ return;
95
+ }
96
+
71
97
  try {
72
98
  const loc = await _getLocation();
73
- const session = await proximityAPI.broadcast({
74
- latitude: loc.lat, longitude: loc.lng,
75
- user: _user?.name || "",
76
- email: _user?.email || "",
77
- accountName: _account?.accountName || "",
78
- accountNumber: _account?.accountNumber || "",
79
- bankCode: _account?.bankCode || "",
80
- bankName: _account?.bankName || "",
99
+
100
+ // Check if we've moved more than 100m from last broadcast position
101
+ const lastPos = store.lastBroadcastPosition;
102
+ if (lastPos) {
103
+ const distance = _getDistanceMeters(
104
+ lastPos.latitude,
105
+ lastPos.longitude,
106
+ loc.lat,
107
+ loc.lng
108
+ );
109
+
110
+ if (distance < PROXIMITY_THRESHOLD_METERS) {
111
+ console.log(
112
+ `[FPay Engine] Within ${Math.round(distance)}m of last broadcast — skipping`
113
+ );
114
+ return; // Not moved enough — no need to broadcast
115
+ }
116
+
117
+ console.log(
118
+ `[FPay Engine] Moved ${Math.round(distance)}m — broadcasting new position`
119
+ );
120
+ }
121
+
122
+ // Broadcast new position
123
+ const session = await proximityAPI.broadcast(psspId, {
124
+ latitude: loc.lat,
125
+ longitude: loc.lng,
126
+ user_type: 'VIRTUAL_ACCOUNT_USER',
81
127
  });
128
+
129
+ // Save new position to store
130
+ store.setLastBroadcastPosition({ latitude: loc.lat, longitude: loc.lng });
82
131
  _proximitySessionId = session.sessionId;
132
+
133
+ console.log(
134
+ '[FPay Engine] Proximity broadcast active at:',
135
+ loc.lat,
136
+ loc.lng
137
+ );
138
+
139
+ // Start heartbeat interval
83
140
  _clearProximityInterval();
84
141
  _proximityInterval = setInterval(async () => {
85
142
  if (!_proximitySessionId) return;
86
143
  try {
87
- const l = await _getLocation();
88
- await proximityAPI.heartbeat(_proximitySessionId, l.lat, l.lng);
89
- } catch { /* silent — heartbeat failure is non-fatal */ }
144
+ const current = await _getLocation();
145
+ const saved = getFPStore().lastBroadcastPosition;
146
+
147
+ if (saved) {
148
+ const moved = _getDistanceMeters(
149
+ saved.latitude,
150
+ saved.longitude,
151
+ current.lat,
152
+ current.lng
153
+ );
154
+
155
+ if (moved >= PROXIMITY_THRESHOLD_METERS) {
156
+ // Moved significantly — broadcast new position
157
+ console.log(
158
+ `[FPay Engine] Heartbeat: moved ${Math.round(moved)}m — re-broadcasting`
159
+ );
160
+ await proximityAPI.broadcast(psspId, {
161
+ latitude: current.lat,
162
+ longitude: current.lng,
163
+ user_type: 'VIRTUAL_ACCOUNT_USER',
164
+ });
165
+ getFPStore().setLastBroadcastPosition({
166
+ latitude: current.lat,
167
+ longitude: current.lng,
168
+ });
169
+ } else {
170
+ // Just send heartbeat to keep session alive
171
+ await proximityAPI.heartbeat(
172
+ _proximitySessionId,
173
+ current.lat,
174
+ current.lng
175
+ );
176
+ }
177
+ }
178
+ } catch {
179
+ /* silent */
180
+ }
90
181
  }, 15000);
91
- console.log('[FPay Engine] Proximity broadcast active');
92
182
  } catch (err) {
93
- // Non-fatal — app still works without proximity
94
- console.warn('[FPay Engine] Proximity unavailable:', (err as FPError).message);
183
+ console.warn(
184
+ '[FPay Engine] Proximity unavailable:',
185
+ (err as FPError).message
186
+ );
95
187
  }
96
188
  }
97
189
 
@@ -134,10 +226,11 @@ async function _validateRequest(req: BLEPaymentRequest): Promise<boolean> {
134
226
  try {
135
227
  const raw = await AsyncStorage.getItem('@fp_recent_requests');
136
228
  const recent: BLEPaymentRequest[] = raw ? JSON.parse(raw) : [];
137
- const isDuplicate = recent.some(r =>
138
- r.senderId === req.senderId &&
139
- r.amount === req.amount &&
140
- Math.abs(r.timestamp - req.timestamp) < 60000 // within 1 minute
229
+ const isDuplicate = recent.some(
230
+ (r) =>
231
+ r.senderId === req.senderId &&
232
+ r.amount === req.amount &&
233
+ Math.abs(r.timestamp - req.timestamp) < 60000 // within 1 minute
141
234
  );
142
235
  // Store and trim to last 50
143
236
  await AsyncStorage.setItem(
@@ -157,13 +250,23 @@ async function _validateRequest(req: BLEPaymentRequest): Promise<boolean> {
157
250
  // This is the core of AlwaysOnPaymentListener.handleIncomingPaymentRequest()
158
251
  // It fires automatically when BLEReceiverService receives a write event.
159
252
 
160
- async function _handleIncomingRequest(req: BLEPaymentRequest, deviceId: string): Promise<void> {
161
- console.log('[FPay Engine] Incoming payment request from:', req.senderName, req.currency + req.amount);
253
+ async function _handleIncomingRequest(
254
+ req: BLEPaymentRequest,
255
+ deviceId: string
256
+ ): Promise<void> {
257
+ console.log(
258
+ '[FPay Engine] Incoming payment request from:',
259
+ req.senderName,
260
+ req.currency + req.amount
261
+ );
162
262
 
163
263
  const isValid = await _validateRequest(req);
164
264
  if (!isValid) {
165
265
  // Auto-decline invalid requests silently
166
- await BLEReceiverService.sendPaymentResponse({ accepted: false, timestamp: Date.now() }).catch(() => {});
266
+ await BLEReceiverService.sendPaymentResponse({
267
+ accepted: false,
268
+ timestamp: Date.now(),
269
+ }).catch(() => {});
167
270
  return;
168
271
  }
169
272
 
@@ -174,15 +277,16 @@ async function _handleIncomingRequest(req: BLEPaymentRequest, deviceId: string):
174
277
  const fpRequest: FPBluetoothPaymentRequest = {
175
278
  requestId: req.senderId + '_' + req.timestamp,
176
279
  sender: {
177
- name: req.senderName,
280
+ firstName: req.senderName,
281
+ lastName: '',
178
282
  phone: req.senderPhone,
179
283
  email: req.senderEmail,
180
284
  userId: req.senderId,
181
285
  deviceId,
182
286
  deviceName: req.senderName,
183
287
  },
184
- amount: req.amount,
185
- currency: req.currency,
288
+ amount: req.amount,
289
+ currency: req.currency,
186
290
  timestamp: new Date(req.timestamp).toISOString(),
187
291
  };
188
292
 
@@ -193,10 +297,90 @@ async function _handleIncomingRequest(req: BLEPaymentRequest, deviceId: string):
193
297
  _emitEvent('incoming_payment_request', fpRequest);
194
298
  }
195
299
 
300
+ async function _resolveAccount(
301
+ user: FPUserInfo,
302
+ callbacks: FPCallbacks
303
+ ): Promise<void> {
304
+ const store = getFPStore();
305
+ // const existingPsspId = store.psspId; // ← read before clearing
306
+
307
+ // // Now safe to clear stale data
308
+ // store.clearUser();
309
+ // store.clearAccount();
310
+ // store.clearBalance();
311
+ const psspId = store.psspId;
312
+
313
+ try {
314
+ if (psspId) {
315
+ // ← use the saved value, not store.psspId
316
+ console.log('[FPay Engine] Existing PSSP id found — fetching account...');
317
+ try {
318
+ const account = await accountAPI.getAccount(psspId);
319
+ store.setAccount(account);
320
+ store.setUser(user);
321
+ callbacks.onAccountReady?.(account);
322
+ _fetchBalance();
323
+ return;
324
+ } catch (err) {
325
+ console.warn(
326
+ '[FPay Engine] Existing account fetch failed — creating new...'
327
+ );
328
+ }
329
+ }
330
+
331
+ // Create new account
332
+ const account: any = await accountAPI.generate(user);
333
+ if (account.status) {
334
+ store.setPsspId(account.id);
335
+ store.setAccount(account);
336
+ store.setUser(user);
337
+ callbacks.onAccountReady?.(account);
338
+ _fetchBalance();
339
+ }
340
+ } catch (err: any) {
341
+ callbacks.onAccountError?.(err, user);
342
+ }
343
+ }
344
+
345
+ async function _fetchBalance(): Promise<void> {
346
+ const { psspId } = getFPStore();
347
+ if (!psspId) return;
348
+
349
+ if (isBalanceCacheValid()) {
350
+ console.log('[FPay Engine] Balance cache valid — skipping fetch.');
351
+ return;
352
+ }
353
+
354
+ try {
355
+ const { balance } = await accountAPI.getBalance(psspId);
356
+ getFPStore().setBalance(balance);
357
+ console.log('[FPay Engine] Balance fetched:', balance);
358
+ } catch (err) {
359
+ console.warn('[FPay Engine] Balance fetch failed (non-fatal):', err);
360
+ }
361
+ }
362
+
363
+ function _getDistanceMeters(
364
+ lat1: number,
365
+ lng1: number,
366
+ lat2: number,
367
+ lng2: number
368
+ ): number {
369
+ const R = 6371000; // Earth radius in meters
370
+ const toRad = (deg: number) => (deg * Math.PI) / 180;
371
+ const dLat = toRad(lat2 - lat1);
372
+ const dLng = toRad(lng2 - lng1);
373
+ const a =
374
+ Math.sin(dLat / 2) * Math.sin(dLat / 2) +
375
+ Math.cos(toRad(lat1)) *
376
+ Math.cos(toRad(lat2)) *
377
+ Math.sin(dLng / 2) *
378
+ Math.sin(dLng / 2);
379
+ return R * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
380
+ }
196
381
  // ── FPEngine public API ───────────────────────────────────────
197
382
 
198
383
  export const FPEngine = {
199
-
200
384
  // Called by pay.initializeSDK()
201
385
  // Mirrors AlwaysOnPaymentListener.initializeAlwaysOnService()
202
386
  async initialize(
@@ -205,55 +389,59 @@ export const FPEngine = {
205
389
  options: FPSDKOptions = {},
206
390
  callbacks: FPCallbacks = {}
207
391
  ): Promise<void> {
208
- _apiKey = apiKey;
209
- _user = user;
210
- _options = options;
392
+ _apiKey = apiKey;
393
+ _user = user;
394
+ _options = options;
211
395
  _callbacks = callbacks;
212
396
 
213
- // Boot the HTTP client
214
- initClient(apiKey, { baseUrl: options.baseUrl, environment: options.environment });
397
+ initClient(apiKey, {
398
+ baseUrl: options.baseUrl,
399
+ environment: options.environment,
400
+ });
215
401
 
216
- // Initialize BLE receiver with this user's info
217
- const displayName = options.bluetoothDisplayName ?? user.name;
402
+ const displayName =
403
+ options.bluetoothDisplayName ?? `${user.firstName} ${user.lastName}`;
218
404
  await BLEReceiverService.initializeWithUserInfo(
219
- user.userId ?? user.userId,
405
+ user.userId ?? '',
220
406
  displayName,
221
407
  'FountainPay'
222
408
  );
223
409
 
224
- // Start BLE advertising + proximity broadcast in parallel.
225
- // Both are non-fatal — SDK still works if either fails.
226
- await Promise.allSettled([
227
- BLEReceiverService.startAdvertising(),
228
- _startProximityBroadcast(),
229
- ]);
410
+ // Start BLE advertising immediately doesn't need psspId
411
+ await BLEReceiverService.startAdvertising().catch((err) =>
412
+ console.warn('[FPay Engine] BLE advertising failed (non-fatal):', err)
413
+ );
230
414
 
231
- // Initialize BLE sender (central) — used when user sends payments via BT
232
- await BLESenderService.initialize().catch(err =>
415
+ await BLESenderService.initialize().catch((err) =>
233
416
  console.warn('[FPay Engine] BLE sender init failed (non-fatal):', err)
234
417
  );
235
418
 
236
- // Resume broadcasting when app returns to foreground
237
- _appStateSub = AppState.addEventListener('change', (next: AppStateStatus) => {
238
- if (next === 'active' && _isReady) {
239
- _startProximityBroadcast();
240
- BLEReceiverService.startAdvertising().catch(() => {});
241
- } else if (next === 'background') {
242
- _clearProximityInterval();
419
+ _appStateSub = AppState.addEventListener(
420
+ 'change',
421
+ (next: AppStateStatus) => {
422
+ if (next === 'active' && _isReady) {
423
+ _startProximityBroadcast();
424
+ BLEReceiverService.startAdvertising().catch(() => {});
425
+ } else if (next === 'background') {
426
+ _clearProximityInterval();
427
+ }
243
428
  }
244
- });
429
+ );
245
430
 
246
431
  _isReady = true;
247
432
  console.log('[FPay Engine] Initialized. Advertising as:', displayName);
433
+
434
+ // Resolve account first — sets psspId in store
435
+ // Then broadcast proximity — needs psspId to be set
436
+ await _resolveAccount(user, callbacks);
437
+ await _startProximityBroadcast(); // ← moved here, after psspId is available
248
438
  },
249
439
 
250
- async setAccountDetails(account: FPTransferRecipient): Promise<void>{
440
+ async setAccountDetails(account: FPTransferRecipient): Promise<void> {
251
441
  _account = account;
252
442
  },
253
443
 
254
-
255
-
256
- async deviceConnection (deviceId: string): Promise<void>{
444
+ async deviceConnection(deviceId: string): Promise<void> {
257
445
  await BLESenderService.connectToDevice(deviceId);
258
446
  },
259
447
 
@@ -275,43 +463,61 @@ export const FPEngine = {
275
463
 
276
464
  // Called by FPPaymentRequestModal when user taps "Accept"
277
465
  // Mirrors AlwaysOnPaymentListener.handleAcceptPayment()
278
- async acceptPaymentRequest(request: FPBluetoothPaymentRequest): Promise<void> {
466
+ async acceptPaymentRequest(
467
+ request: FPBluetoothPaymentRequest
468
+ ): Promise<void> {
279
469
  try {
280
- const transactionId = 'txn_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
470
+ const transactionId =
471
+ 'txn_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
472
+ const store = getFPStore();
473
+ const account = getFPStore().account;
281
474
 
282
- // Build response same shape as your original:
283
- // { accepted, transactionId, timestamp, accountDetails, requestTimestamp }
475
+ // Build BLE response with receiver's wallet details
284
476
  const response: BLEPaymentResponse = {
285
477
  accepted: true,
286
478
  transactionId,
287
479
  timestamp: Date.now(),
288
- accountDetails: _account ? {
289
- accountName: _account?.accountName,
290
- accountNumber: _account?.accountNumber,
291
- bankName: _account?.bankName,
292
- bankCode: _account?.bankCode,
293
- } : undefined,
294
- // requestTimestamp is echoed back so the sender's polling can match it
295
480
  requestTimestamp: new Date(request.timestamp).getTime(),
481
+ accountDetails: account
482
+ ? {
483
+ accountName: account.accountName,
484
+ accountNumber: account.accountNumber,
485
+ bankName: account.bankName ?? '',
486
+ bankCode: account.bankCode ?? '',
487
+ userId: store.psspId ?? undefined,
488
+ type: 'USER' as const,
489
+ }
490
+ : undefined,
296
491
  };
297
-
298
- // Write response to the BLE characteristic — sender is polling this
492
+ console.log('[FPay Engine] _account at accept time:', _account);
493
+ console.log(
494
+ '[FPay Engine] store account at accept time:',
495
+ getFPStore().account
496
+ );
497
+ console.log('[FPay Engine] response at accept time:', response);
498
+ // Send response back to sender via BLE
299
499
  await BLEReceiverService.sendPaymentResponse(response);
300
500
  console.log('[FPay Engine] Acceptance response sent to sender');
301
501
 
302
- // Notify host app
502
+ // Notify host app that payment is incoming
503
+ // Status is 'pending' until sender confirms transfer on their end
303
504
  const tx = {
304
- id: transactionId,
505
+ id: transactionId,
305
506
  reference: transactionId,
306
- type: 'credit' as const,
307
- channel: 'bluetooth' as const,
308
- amount: request.amount,
309
- currency: request.currency,
310
- status: 'successful' as const,
311
- sender: request.sender,
507
+ type: 'credit' as const,
508
+ channel: 'bluetooth' as const,
509
+ amount: request.amount,
510
+ currency: request.currency,
511
+ status: 'pending' as const, // ← pending until sender processes
512
+ sender: request.sender,
312
513
  createdAt: new Date().toISOString(),
313
514
  };
314
515
  _callbacks.onPaymentReceived?.(tx);
516
+
517
+ // Refresh balance after a delay to give sender time to process
518
+ setTimeout(() => {
519
+ _fetchBalance();
520
+ }, 5000); // ← 5s delay to allow sender's transfer to settle
315
521
  } catch (err) {
316
522
  _callbacks.onError?.(err as FPError);
317
523
  }
@@ -319,9 +525,14 @@ export const FPEngine = {
319
525
 
320
526
  // Called by FPPaymentRequestModal when user taps "Decline"
321
527
  // Mirrors AlwaysOnPaymentListener.handleDeclinePayment()
322
- async declinePaymentRequest(request: FPBluetoothPaymentRequest): Promise<void> {
528
+ async declinePaymentRequest(
529
+ request: FPBluetoothPaymentRequest
530
+ ): Promise<void> {
323
531
  try {
324
- await BLEReceiverService.sendPaymentResponse({ accepted: false, timestamp: Date.now() });
532
+ await BLEReceiverService.sendPaymentResponse({
533
+ accepted: false,
534
+ timestamp: Date.now(),
535
+ });
325
536
  console.log('[FPay Engine] Decline response sent to sender');
326
537
  } catch {
327
538
  // Non-fatal — decline even if response send fails
@@ -351,21 +562,29 @@ export const FPEngine = {
351
562
 
352
563
  // ── Other SDK actions ──────────────────────────────────────
353
564
 
354
- async generateAccount(req: FPGenerateAccountRequest): Promise<FPVirtualAccount> {
355
- return walletAPI.generate(req);
565
+ async generateAccount(req: FPUserInfo): Promise<FPAccount> {
566
+ return accountAPI.generate(req);
567
+ },
568
+
569
+ async refreshBalance(): Promise<void> {
570
+ await _fetchBalance();
571
+ },
572
+
573
+ closeSend(): void {
574
+ _emitEvent('close_send');
356
575
  },
357
576
 
358
577
  showSend(amount: number, currency: string): void {
359
- _emitEvent('show_send', {amount, currency });
578
+ _emitEvent('show_send', { amount, currency });
360
579
  },
361
580
 
362
581
  showReceive(amount?: number, currency?: string): void {
363
582
  _emitEvent('show_receive', { amount, currency });
364
583
  },
365
584
 
366
- getUser: () => _user,
367
- getAccount: () => _account,
368
- isReady: () => _isReady,
585
+ getUser: () => _user,
586
+ getAccount: () => _account,
587
+ isReady: () => _isReady,
369
588
  getCallbacks: () => _callbacks,
370
589
 
371
590
  async destroy(): Promise<void> {
@@ -24,9 +24,9 @@ class NearbyUsersService {
24
24
  Geolocation.getCurrentPosition(
25
25
  async (position) => {
26
26
  const { latitude, longitude } = position.coords;
27
-
27
+
28
28
  const authToken = await this.getAuthToken();
29
-
29
+
30
30
  await fetch(`${this.API_URL}/api/users/location`, {
31
31
  method: 'POST',
32
32
  headers: {
@@ -48,10 +48,10 @@ class NearbyUsersService {
48
48
  // Get list of nearby users from server
49
49
  async getNearbyUsers(): Promise<NearbyUser[]> {
50
50
  const authToken = await this.getAuthToken();
51
-
51
+
52
52
  const response = await fetch(`${this.API_URL}/api/users/nearby?radius=50`, {
53
53
  headers: {
54
- 'Authorization': `Bearer ${authToken}`,
54
+ Authorization: `Bearer ${authToken}`,
55
55
  },
56
56
  });
57
57
 
@@ -69,7 +69,7 @@ class NearbyUsersService {
69
69
  currency: string = 'USD'
70
70
  ) {
71
71
  const authToken = await this.getAuthToken();
72
-
72
+
73
73
  const response = await fetch(`${this.API_URL}/api/payments/request`, {
74
74
  method: 'POST',
75
75
  headers: {
@@ -103,4 +103,4 @@ class NearbyUsersService {
103
103
  }
104
104
  }
105
105
 
106
- export default new NearbyUsersService();
106
+ export default new NearbyUsersService();
@@ -1,18 +1,18 @@
1
- import { useState } from 'react'
2
- import { useEffect } from 'react'
3
- import type { AppStateStatus } from 'react-native'
4
- import { AppState } from 'react-native'
1
+ import { useState } from 'react';
2
+ import { useEffect } from 'react';
3
+ import type { AppStateStatus } from 'react-native';
4
+ import { AppState } from 'react-native';
5
5
 
6
6
  export const useIsForeground = (): boolean => {
7
- const [isForeground, setIsForeground] = useState(true)
7
+ const [isForeground, setIsForeground] = useState(true);
8
8
 
9
9
  useEffect(() => {
10
10
  const onChange = (state: AppStateStatus): void => {
11
- setIsForeground(state === 'active')
12
- }
13
- const listener = AppState.addEventListener('change', onChange)
14
- return () => listener.remove()
15
- }, [setIsForeground])
11
+ setIsForeground(state === 'active');
12
+ };
13
+ const listener = AppState.addEventListener('change', onChange);
14
+ return () => listener.remove();
15
+ }, [setIsForeground]);
16
16
 
17
- return isForeground
18
- }
17
+ return isForeground;
18
+ };
package/src/index.ts CHANGED
@@ -5,10 +5,17 @@
5
5
  // ─────────────────────────────────────────────
6
6
 
7
7
  // The one hook
8
- export { useFountainPay } from './useFountainPay';
8
+ export { useFountainPay } from './useFountainPay';
9
9
 
10
10
  // The one provider (mount at app root)
11
- export { FountainPayProvider } from './FountainPayProvider';
11
+ export { FountainPayProvider } from './FountainPayProvider';
12
+
13
+ export {
14
+ useFPStore,
15
+ getFPStore,
16
+ isBanksCacheValid,
17
+ isTokenValid,
18
+ } from './store/FPStore';
12
19
 
13
20
  // All TypeScript types (for type-safe usage)
14
21
  export type {
@@ -18,8 +25,7 @@ export type {
18
25
  FPCallbacks,
19
26
  FPCurrency,
20
27
  FPTransaction,
21
- FPVirtualAccount,
22
- FPGenerateAccountRequest,
28
+ FPAccount,
23
29
  FPBluetoothPaymentRequest,
24
30
  FPProximityPeer,
25
31
  FPNQRData,