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.
- package/lib/module/FountainPayProvider.js +108 -18
- package/lib/module/FountainPayProvider.js.map +1 -1
- package/lib/module/core/api/client.js +13 -1
- package/lib/module/core/api/client.js.map +1 -1
- package/lib/module/core/api/index.js +66 -24
- package/lib/module/core/api/index.js.map +1 -1
- package/lib/module/engine/BLEReceiverService.js.map +1 -1
- package/lib/module/engine/BLESenderService.js.map +1 -1
- package/lib/module/engine/FPEngine.js +164 -43
- package/lib/module/engine/FPEngine.js.map +1 -1
- package/lib/module/engine/NearbyUsersService.js +1 -1
- package/lib/module/engine/NearbyUsersService.js.map +1 -1
- package/lib/module/index.js +1 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/store/FPStore.js +155 -0
- package/lib/module/store/FPStore.js.map +1 -0
- package/lib/module/ui/components/AnimatedDots.js +3 -3
- package/lib/module/ui/components/AnimatedDots.js.map +1 -1
- package/lib/module/ui/components/ConfirmScreen.js +137 -123
- package/lib/module/ui/components/ConfirmScreen.js.map +1 -1
- package/lib/module/ui/components/FPButton.js.map +1 -1
- package/lib/module/ui/components/LoadingAnimation/InLoading.js +3 -3
- package/lib/module/ui/components/LoadingAnimation/InLoading.js.map +1 -1
- package/lib/module/ui/components/LoadingAnimation/index.js +3 -3
- package/lib/module/ui/components/LoadingAnimation/index.js.map +1 -1
- package/lib/module/ui/components/OtpInput/OTPInputView.js +11 -11
- package/lib/module/ui/components/OtpInput/OTPInputView.js.map +1 -1
- package/lib/module/ui/components/OtpInput/Styles.js +1 -1
- package/lib/module/ui/components/OtpInput/helpers/codeToArray.js +1 -1
- package/lib/module/ui/components/OtpInput/helpers/device.js.map +1 -1
- package/lib/module/ui/components/OtpInput/helpers/styles.js.map +1 -1
- package/lib/module/ui/components/OtpInput/index.js +3 -1
- package/lib/module/ui/components/OtpInput/index.js.map +1 -1
- package/lib/module/ui/components/PulseAnimation.js +2 -2
- package/lib/module/ui/components/PulseAnimation.js.map +1 -1
- package/lib/module/ui/modals/FPPaymentRequestModal.js +8 -7
- package/lib/module/ui/modals/FPPaymentRequestModal.js.map +1 -1
- package/lib/module/ui/modals/FPShell.js +2 -0
- package/lib/module/ui/modals/FPShell.js.map +1 -1
- package/lib/module/ui/screens/ReceiveScreen.js +8 -9
- package/lib/module/ui/screens/ReceiveScreen.js.map +1 -1
- package/lib/module/ui/screens/SendScreen.js +43 -94
- package/lib/module/ui/screens/SendScreen.js.map +1 -1
- package/lib/module/ui/screens/styles.js +15 -3
- package/lib/module/ui/screens/styles.js.map +1 -1
- package/lib/module/ui/screens/sub/receivePayment/Nfc/index.js +4 -4
- package/lib/module/ui/screens/sub/receivePayment/Nfc/index.js.map +1 -1
- package/lib/module/ui/screens/sub/receivePayment/Qr/index.js +5 -5
- package/lib/module/ui/screens/sub/receivePayment/Qr/index.js.map +1 -1
- package/lib/module/ui/screens/sub/receivePayment/Transfer/index.js +10 -11
- package/lib/module/ui/screens/sub/receivePayment/Transfer/index.js.map +1 -1
- package/lib/module/ui/screens/sub/sendPayment/BluetoothSubScreen.js +31 -8
- package/lib/module/ui/screens/sub/sendPayment/BluetoothSubScreen.js.map +1 -1
- package/lib/module/ui/screens/sub/sendPayment/NFCSubScreen.js +12 -8
- package/lib/module/ui/screens/sub/sendPayment/NFCSubScreen.js.map +1 -1
- package/lib/module/ui/screens/sub/sendPayment/NQRSubScreen.js +17 -5
- package/lib/module/ui/screens/sub/sendPayment/NQRSubScreen.js.map +1 -1
- package/lib/module/ui/screens/sub/sendPayment/ProximitySubScreen.js +67 -35
- package/lib/module/ui/screens/sub/sendPayment/ProximitySubScreen.js.map +1 -1
- package/lib/module/ui/screens/sub/sendPayment/TransferSubScreen.js +110 -34
- package/lib/module/ui/screens/sub/sendPayment/TransferSubScreen.js.map +1 -1
- package/lib/module/ui/theme/index.js.map +1 -1
- package/lib/typescript/src/FountainPayProvider.d.ts +1 -1
- package/lib/typescript/src/FountainPayProvider.d.ts.map +1 -1
- package/lib/typescript/src/core/api/client.d.ts.map +1 -1
- package/lib/typescript/src/core/api/index.d.ts +42 -26
- package/lib/typescript/src/core/api/index.d.ts.map +1 -1
- package/lib/typescript/src/core/types/index.d.ts +53 -28
- package/lib/typescript/src/core/types/index.d.ts.map +1 -1
- package/lib/typescript/src/engine/BLEReceiverService.d.ts +2 -0
- package/lib/typescript/src/engine/BLEReceiverService.d.ts.map +1 -1
- package/lib/typescript/src/engine/BLESenderService.d.ts.map +1 -1
- package/lib/typescript/src/engine/FPEngine.d.ts +5 -3
- package/lib/typescript/src/engine/FPEngine.d.ts.map +1 -1
- package/lib/typescript/src/engine/useIsForeground.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +2 -1
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/store/FPStore.d.ts +60 -0
- package/lib/typescript/src/store/FPStore.d.ts.map +1 -0
- package/lib/typescript/src/ui/components/AnimatedDots.d.ts.map +1 -1
- package/lib/typescript/src/ui/components/ConfirmScreen.d.ts +5 -5
- package/lib/typescript/src/ui/components/ConfirmScreen.d.ts.map +1 -1
- package/lib/typescript/src/ui/components/FPButton.d.ts +1 -1
- package/lib/typescript/src/ui/components/FPButton.d.ts.map +1 -1
- package/lib/typescript/src/ui/components/LoadingAnimation/InLoading.d.ts.map +1 -1
- package/lib/typescript/src/ui/components/LoadingAnimation/index.d.ts.map +1 -1
- package/lib/typescript/src/ui/components/OtpInput/OTPInputView.d.ts.map +1 -1
- package/lib/typescript/src/ui/components/OtpInput/helpers/codeToArray.d.ts.map +1 -1
- package/lib/typescript/src/ui/components/OtpInput/helpers/device.d.ts.map +1 -1
- package/lib/typescript/src/ui/components/OtpInput/helpers/styles.d.ts.map +1 -1
- package/lib/typescript/src/ui/components/OtpInput/helpers/types.d.ts +1 -2
- package/lib/typescript/src/ui/components/OtpInput/helpers/types.d.ts.map +1 -1
- package/lib/typescript/src/ui/components/OtpInput/index.d.ts +3 -1
- package/lib/typescript/src/ui/components/OtpInput/index.d.ts.map +1 -1
- package/lib/typescript/src/ui/components/PulseAnimation.d.ts.map +1 -1
- package/lib/typescript/src/ui/modals/FPPaymentRequestModal.d.ts.map +1 -1
- package/lib/typescript/src/ui/modals/FPShell.d.ts.map +1 -1
- package/lib/typescript/src/ui/screens/ReceiveScreen.d.ts +1 -1
- package/lib/typescript/src/ui/screens/ReceiveScreen.d.ts.map +1 -1
- package/lib/typescript/src/ui/screens/SendScreen.d.ts +1 -1
- package/lib/typescript/src/ui/screens/SendScreen.d.ts.map +1 -1
- package/lib/typescript/src/ui/screens/styles.d.ts +197 -0
- package/lib/typescript/src/ui/screens/styles.d.ts.map +1 -1
- package/lib/typescript/src/ui/screens/sub/receivePayment/Nfc/index.d.ts.map +1 -1
- package/lib/typescript/src/ui/screens/sub/receivePayment/Qr/index.d.ts.map +1 -1
- package/lib/typescript/src/ui/screens/sub/receivePayment/Transfer/index.d.ts.map +1 -1
- package/lib/typescript/src/ui/screens/sub/sendPayment/BluetoothSubScreen.d.ts.map +1 -1
- package/lib/typescript/src/ui/screens/sub/sendPayment/NFCSubScreen.d.ts +1 -1
- package/lib/typescript/src/ui/screens/sub/sendPayment/NFCSubScreen.d.ts.map +1 -1
- package/lib/typescript/src/ui/screens/sub/sendPayment/NQRSubScreen.d.ts +1 -1
- package/lib/typescript/src/ui/screens/sub/sendPayment/NQRSubScreen.d.ts.map +1 -1
- package/lib/typescript/src/ui/screens/sub/sendPayment/ProximitySubScreen.d.ts +1 -1
- package/lib/typescript/src/ui/screens/sub/sendPayment/ProximitySubScreen.d.ts.map +1 -1
- package/lib/typescript/src/ui/screens/sub/sendPayment/TransferSubScreen.d.ts +1 -1
- package/lib/typescript/src/ui/screens/sub/sendPayment/TransferSubScreen.d.ts.map +1 -1
- package/lib/typescript/src/ui/theme/index.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/FountainPayProvider.tsx +163 -24
- package/src/core/api/client.ts +26 -4
- package/src/core/api/index.ts +170 -49
- package/src/core/types/index.ts +81 -48
- package/src/engine/BLEReceiverService.ts +86 -28
- package/src/engine/BLESenderService.ts +133 -69
- package/src/engine/FPEngine.ts +316 -97
- package/src/engine/NearbyUsersService.ts +6 -6
- package/src/engine/useIsForeground.ts +12 -12
- package/src/index.ts +10 -4
- package/src/store/FPStore.ts +216 -0
- package/src/ui/components/AnimatedDots.tsx +4 -5
- package/src/ui/components/ConfirmScreen.tsx +182 -169
- package/src/ui/components/FPButton.tsx +50 -9
- package/src/ui/components/LoadingAnimation/InLoading.tsx +23 -27
- package/src/ui/components/LoadingAnimation/index.tsx +3 -7
- package/src/ui/components/OtpInput/OTPInputView.tsx +254 -205
- package/src/ui/components/OtpInput/Styles.ts +1 -1
- package/src/ui/components/OtpInput/helpers/codeToArray.ts +2 -2
- package/src/ui/components/OtpInput/helpers/device.ts +4 -3
- package/src/ui/components/OtpInput/helpers/styles.ts +13 -14
- package/src/ui/components/OtpInput/helpers/types.ts +83 -79
- package/src/ui/components/OtpInput/index.tsx +18 -15
- package/src/ui/components/PulseAnimation.tsx +3 -5
- package/src/ui/modals/FPPaymentRequestModal.tsx +111 -28
- package/src/ui/modals/FPShell.tsx +60 -34
- package/src/ui/screens/ReceiveScreen.tsx +245 -84
- package/src/ui/screens/SendScreen.tsx +419 -167
- package/src/ui/screens/styles.ts +17 -5
- package/src/ui/screens/sub/receivePayment/Nfc/index.tsx +17 -25
- package/src/ui/screens/sub/receivePayment/Qr/index.tsx +21 -20
- package/src/ui/screens/sub/receivePayment/Transfer/index.tsx +34 -28
- package/src/ui/screens/sub/sendPayment/BluetoothSubScreen.tsx +135 -67
- package/src/ui/screens/sub/sendPayment/NFCSubScreen.tsx +188 -112
- package/src/ui/screens/sub/sendPayment/NQRSubScreen.tsx +102 -69
- package/src/ui/screens/sub/sendPayment/ProximitySubScreen.tsx +225 -99
- package/src/ui/screens/sub/sendPayment/TransferSubScreen.tsx +209 -89
- package/src/ui/theme/index.ts +14 -2
package/src/engine/FPEngine.ts
CHANGED
|
@@ -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 {
|
|
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,
|
|
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, {
|
|
22
|
-
|
|
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 =
|
|
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) {
|
|
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
|
-
|
|
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
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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
|
|
88
|
-
|
|
89
|
-
|
|
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
|
-
|
|
94
|
-
|
|
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(
|
|
138
|
-
r
|
|
139
|
-
|
|
140
|
-
|
|
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(
|
|
161
|
-
|
|
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({
|
|
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
|
-
|
|
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:
|
|
185
|
-
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
|
|
209
|
-
_user
|
|
210
|
-
_options
|
|
392
|
+
_apiKey = apiKey;
|
|
393
|
+
_user = user;
|
|
394
|
+
_options = options;
|
|
211
395
|
_callbacks = callbacks;
|
|
212
396
|
|
|
213
|
-
|
|
214
|
-
|
|
397
|
+
initClient(apiKey, {
|
|
398
|
+
baseUrl: options.baseUrl,
|
|
399
|
+
environment: options.environment,
|
|
400
|
+
});
|
|
215
401
|
|
|
216
|
-
|
|
217
|
-
|
|
402
|
+
const displayName =
|
|
403
|
+
options.bluetoothDisplayName ?? `${user.firstName} ${user.lastName}`;
|
|
218
404
|
await BLEReceiverService.initializeWithUserInfo(
|
|
219
|
-
user.userId ??
|
|
405
|
+
user.userId ?? '',
|
|
220
406
|
displayName,
|
|
221
407
|
'FountainPay'
|
|
222
408
|
);
|
|
223
409
|
|
|
224
|
-
// Start BLE advertising
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
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
|
-
|
|
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
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
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(
|
|
466
|
+
async acceptPaymentRequest(
|
|
467
|
+
request: FPBluetoothPaymentRequest
|
|
468
|
+
): Promise<void> {
|
|
279
469
|
try {
|
|
280
|
-
const transactionId =
|
|
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
|
|
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
|
-
|
|
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:
|
|
505
|
+
id: transactionId,
|
|
305
506
|
reference: transactionId,
|
|
306
|
-
type:
|
|
307
|
-
channel:
|
|
308
|
-
amount:
|
|
309
|
-
currency:
|
|
310
|
-
status:
|
|
311
|
-
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(
|
|
528
|
+
async declinePaymentRequest(
|
|
529
|
+
request: FPBluetoothPaymentRequest
|
|
530
|
+
): Promise<void> {
|
|
323
531
|
try {
|
|
324
|
-
await BLEReceiverService.sendPaymentResponse({
|
|
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:
|
|
355
|
-
return
|
|
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:
|
|
367
|
-
getAccount:
|
|
368
|
-
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
|
-
|
|
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 }
|
|
8
|
+
export { useFountainPay } from './useFountainPay';
|
|
9
9
|
|
|
10
10
|
// The one provider (mount at app root)
|
|
11
|
-
export { 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
|
-
|
|
22
|
-
FPGenerateAccountRequest,
|
|
28
|
+
FPAccount,
|
|
23
29
|
FPBluetoothPaymentRequest,
|
|
24
30
|
FPProximityPeer,
|
|
25
31
|
FPNQRData,
|