react-native-fpay 0.4.13 → 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.
- package/lib/module/FountainPayProvider.js +79 -6
- package/lib/module/FountainPayProvider.js.map +1 -1
- package/lib/module/core/api/client.js +1 -1
- package/lib/module/core/api/client.js.map +1 -1
- package/lib/module/core/api/index.js +2 -1
- package/lib/module/core/api/index.js.map +1 -1
- package/lib/module/core/types/index.js.map +1 -1
- package/lib/module/engine/BLESenderService.js +32 -7
- package/lib/module/engine/BLESenderService.js.map +1 -1
- package/lib/module/engine/FPEngine.js +10 -0
- package/lib/module/engine/FPEngine.js.map +1 -1
- package/lib/module/ui/components/Gradients/Skeleton.js +54 -0
- package/lib/module/ui/components/Gradients/Skeleton.js.map +1 -0
- package/lib/module/ui/components/Gradients/index.js +85 -0
- package/lib/module/ui/components/Gradients/index.js.map +1 -0
- package/lib/module/ui/screens/ResultScreen.js +18 -5
- package/lib/module/ui/screens/ResultScreen.js.map +1 -1
- package/lib/module/ui/screens/sub/sendPayment/BluetoothSubScreen.js +7 -2
- package/lib/module/ui/screens/sub/sendPayment/BluetoothSubScreen.js.map +1 -1
- package/lib/typescript/src/FountainPayProvider.d.ts +3 -2
- package/lib/typescript/src/FountainPayProvider.d.ts.map +1 -1
- package/lib/typescript/src/core/api/index.d.ts +18 -8
- package/lib/typescript/src/core/api/index.d.ts.map +1 -1
- package/lib/typescript/src/core/types/index.d.ts +6 -4
- package/lib/typescript/src/core/types/index.d.ts.map +1 -1
- package/lib/typescript/src/engine/BLESenderService.d.ts.map +1 -1
- package/lib/typescript/src/engine/FPEngine.d.ts.map +1 -1
- package/lib/typescript/src/ui/components/Gradients/Skeleton.d.ts +3 -0
- package/lib/typescript/src/ui/components/Gradients/Skeleton.d.ts.map +1 -0
- package/lib/typescript/src/ui/components/Gradients/index.d.ts +3 -0
- package/lib/typescript/src/ui/components/Gradients/index.d.ts.map +1 -0
- package/lib/typescript/src/ui/screens/ResultScreen.d.ts.map +1 -1
- package/lib/typescript/src/ui/screens/sub/sendPayment/BluetoothSubScreen.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/FountainPayProvider.tsx +89 -11
- package/src/core/api/client.ts +1 -1
- package/src/core/api/index.ts +21 -2
- package/src/core/types/index.ts +6 -4
- package/src/engine/BLESenderService.ts +44 -17
- package/src/engine/FPEngine.ts +11 -0
- package/src/ui/components/Gradients/Skeleton.tsx +48 -0
- package/src/ui/components/Gradients/index.tsx +94 -0
- package/src/ui/screens/ResultScreen.tsx +23 -6
- package/src/ui/screens/sub/sendPayment/BluetoothSubScreen.tsx +7 -7
|
@@ -63,7 +63,8 @@ export function useFountainPay(): FPInstance {
|
|
|
63
63
|
// ── Provider ──────────────────────────────────────────────────────────────────
|
|
64
64
|
|
|
65
65
|
interface ProviderProps {
|
|
66
|
-
apiKey
|
|
66
|
+
apiKey?: string;
|
|
67
|
+
accessToken?: string;
|
|
67
68
|
options?: FPSDKOptions;
|
|
68
69
|
children: ReactNode;
|
|
69
70
|
onAuthReady?: () => void;
|
|
@@ -72,6 +73,7 @@ interface ProviderProps {
|
|
|
72
73
|
|
|
73
74
|
export function FountainPayProvider({
|
|
74
75
|
apiKey,
|
|
76
|
+
accessToken,
|
|
75
77
|
options,
|
|
76
78
|
children,
|
|
77
79
|
onAuthReady,
|
|
@@ -87,6 +89,9 @@ export function FountainPayProvider({
|
|
|
87
89
|
const account = useFPStore((s) => s.account);
|
|
88
90
|
const user = useFPStore((s) => s.user);
|
|
89
91
|
const balance = useFPStore((s) => s.balance);
|
|
92
|
+
// const resetToken = useFPStore((s) => s.clearAccessToken);
|
|
93
|
+
// const clearUser = useFPStore((s) => s.clearUser);
|
|
94
|
+
|
|
90
95
|
|
|
91
96
|
|
|
92
97
|
useEffect(() => {
|
|
@@ -95,18 +100,91 @@ export function FountainPayProvider({
|
|
|
95
100
|
// Make sure client is initialized before any API call
|
|
96
101
|
console.log('[FountainPay] Provider mounted. HTTP client ready.', options);
|
|
97
102
|
if (!clientBooted.current) {
|
|
98
|
-
|
|
99
|
-
|
|
103
|
+
// For AGENT mode — store the token before booting the client
|
|
104
|
+
// so the request interceptor picks it up immediately
|
|
105
|
+
if (options?.userType === 'AGENT' && accessToken) {
|
|
106
|
+
getFPStore().setAccessToken(accessToken);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
initClient(apiKey ?? '', {
|
|
110
|
+
baseUrl: options?.baseUrl,
|
|
100
111
|
environment: options?.environment,
|
|
101
112
|
});
|
|
113
|
+
|
|
102
114
|
clientBooted.current = true;
|
|
103
|
-
console.log(
|
|
104
|
-
'[FountainPay] Provider mounted. HTTP client ready.: ',
|
|
105
|
-
options
|
|
106
|
-
);
|
|
115
|
+
console.log('[FountainPay] HTTP client ready. Mode:', options?.userType ?? 'USER');
|
|
107
116
|
}
|
|
108
117
|
|
|
109
118
|
const authenticate = async () => {
|
|
119
|
+
// await resetToken();
|
|
120
|
+
// await clearUser();
|
|
121
|
+
const userType = options?.userType ?? 'USER';
|
|
122
|
+
|
|
123
|
+
// ── AGENT mode ────────────────────────────────────────
|
|
124
|
+
if (userType === 'AGENT') {
|
|
125
|
+
if (!accessToken) {
|
|
126
|
+
onAuthError?.('accessToken is required for AGENT mode');
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
try {
|
|
131
|
+
console.log('[FountainPay] Agent mode — validating token...');
|
|
132
|
+
|
|
133
|
+
// Store the token first so the HTTP interceptor picks it up
|
|
134
|
+
getFPStore().setAccessToken(accessToken);
|
|
135
|
+
|
|
136
|
+
// Fetch agent profile from backend
|
|
137
|
+
const response = await authenticateAPI.validateToken();
|
|
138
|
+
if (cancelled) return;
|
|
139
|
+
|
|
140
|
+
if (response.status) {
|
|
141
|
+
const agent = response.payload;
|
|
142
|
+
|
|
143
|
+
// Build FPUserInfo from agent model
|
|
144
|
+
const user: FPUserInfo = {
|
|
145
|
+
firstName: agent.firstName,
|
|
146
|
+
lastName: agent.lastName,
|
|
147
|
+
email: agent.email,
|
|
148
|
+
phone: agent.phone ?? '',
|
|
149
|
+
bvn: agent.bvn,
|
|
150
|
+
userId: agent.id,
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
// Build FPAccount from agent model
|
|
154
|
+
const account: FPAccount = {
|
|
155
|
+
id: agent.id,
|
|
156
|
+
firstName: agent.firstName,
|
|
157
|
+
lastName: agent.lastName,
|
|
158
|
+
phone: agent.phone,
|
|
159
|
+
accountName: `${agent.firstName} ${agent.lastName}`.toUpperCase(),
|
|
160
|
+
accountNumber: agent.accountNumber,
|
|
161
|
+
bankName: agent.bankName,
|
|
162
|
+
bankCode: agent.bankCode, // populate if backend returns it
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
// Store everything — no need to call _resolveAccount
|
|
166
|
+
getFPStore().setUser(user);
|
|
167
|
+
getFPStore().setPsspId(agent.id);
|
|
168
|
+
getFPStore().setAccount(account);
|
|
169
|
+
|
|
170
|
+
setAuthenticated(true);
|
|
171
|
+
console.log('[FountainPay] Agent validated:', agent.firstName, agent.lastName);
|
|
172
|
+
onAuthReady?.();
|
|
173
|
+
} else {
|
|
174
|
+
getFPStore().clearAccessToken();
|
|
175
|
+
setAuthenticated(false, response.message);
|
|
176
|
+
onAuthError?.(response.message);
|
|
177
|
+
}
|
|
178
|
+
} catch (err: any) {
|
|
179
|
+
if (cancelled) return;
|
|
180
|
+
getFPStore().clearAccessToken();
|
|
181
|
+
setAuthenticated(false, err.message);
|
|
182
|
+
onAuthError?.(err.message);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return; // ← don't fall through to PSSP auth
|
|
186
|
+
}
|
|
187
|
+
|
|
110
188
|
if (isTokenValid()) {
|
|
111
189
|
console.log('[FountainPay] Valid token found — skipping auth.');
|
|
112
190
|
setAuthenticated(true);
|
|
@@ -116,7 +194,7 @@ export function FountainPayProvider({
|
|
|
116
194
|
|
|
117
195
|
try {
|
|
118
196
|
console.log('[FountainPay] Verifying API key...');
|
|
119
|
-
const response = await authenticateAPI.login(apiKey);
|
|
197
|
+
const response = await authenticateAPI.login(apiKey || "");
|
|
120
198
|
if (cancelled) return;
|
|
121
199
|
|
|
122
200
|
if (response.status) {
|
|
@@ -152,14 +230,14 @@ export function FountainPayProvider({
|
|
|
152
230
|
return () => {
|
|
153
231
|
cancelled = true;
|
|
154
232
|
};
|
|
155
|
-
}, [apiKey]);
|
|
233
|
+
}, [apiKey, accessToken]);
|
|
156
234
|
|
|
157
235
|
// Build the instance once per provider lifetime.
|
|
158
236
|
// All components that call useFountainPay() share this exact object.
|
|
159
237
|
const instance = useMemo<FPInstance>(() => {
|
|
160
238
|
console.log(
|
|
161
239
|
'[FountainPay] Creating shared FPInstance for apiKey:',
|
|
162
|
-
apiKey.slice(0, 8) + '...'
|
|
240
|
+
apiKey?.toString().slice(0, 8) + '...'
|
|
163
241
|
);
|
|
164
242
|
|
|
165
243
|
return {
|
|
@@ -182,7 +260,7 @@ export function FountainPayProvider({
|
|
|
182
260
|
// options ?? {},
|
|
183
261
|
// callbacks ?? {}
|
|
184
262
|
// );
|
|
185
|
-
await FPEngine.initialize(apiKey, user, options ?? {}, {
|
|
263
|
+
await FPEngine.initialize(apiKey || '', user, options ?? {}, {
|
|
186
264
|
...callbacks,
|
|
187
265
|
onPaymentSent: async (tx) => {
|
|
188
266
|
callbacks?.onPaymentSent?.(tx);
|
package/src/core/api/client.ts
CHANGED
|
@@ -7,7 +7,7 @@ import type { FPError } from '../types';
|
|
|
7
7
|
import { getFPStore } from '../../store/FPStore';
|
|
8
8
|
|
|
9
9
|
const DEFAULT_BASE_URL =
|
|
10
|
-
'https://kenisha-happiest-nan.ngrok-free.dev/sdk/v1/fpip/';
|
|
10
|
+
'https://kenisha-happiest-nan.ngrok-free.dev/modal/sdk/v1/fpip/';
|
|
11
11
|
|
|
12
12
|
let _client: AxiosInstance | null = null;
|
|
13
13
|
let _isRefreshing = false;
|
package/src/core/api/index.ts
CHANGED
|
@@ -11,6 +11,7 @@ import type {
|
|
|
11
11
|
FPAccount,
|
|
12
12
|
FPSendWalletPaymentRequest,
|
|
13
13
|
FPTransactionResponse,
|
|
14
|
+
FPBalance,
|
|
14
15
|
} from '../types';
|
|
15
16
|
|
|
16
17
|
export const healthAPI = {
|
|
@@ -40,6 +41,24 @@ export const authenticateAPI = {
|
|
|
40
41
|
http()
|
|
41
42
|
.post<{ Response: any }>('/verify-otp', { otp, email })
|
|
42
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),
|
|
43
62
|
};
|
|
44
63
|
|
|
45
64
|
export const accountAPI = {
|
|
@@ -71,10 +90,10 @@ export const accountAPI = {
|
|
|
71
90
|
// Get balance using PSSP id
|
|
72
91
|
getBalance: (psspId: string) =>
|
|
73
92
|
http()
|
|
74
|
-
.get<{ payload:
|
|
93
|
+
.get<{ payload: FPBalance }>(
|
|
75
94
|
`/get-balance/${psspId}`
|
|
76
95
|
)
|
|
77
|
-
.then((r) => r.data.payload),
|
|
96
|
+
.then((r) => r.data.payload as FPBalance),
|
|
78
97
|
};
|
|
79
98
|
|
|
80
99
|
export const transferAPI = {
|
package/src/core/types/index.ts
CHANGED
|
@@ -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 {
|
|
@@ -68,9 +70,9 @@ export interface FPParty {
|
|
|
68
70
|
export interface FPBalance {
|
|
69
71
|
balance: number;
|
|
70
72
|
currency: string;
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
73
|
+
dailyLimit: number;
|
|
74
|
+
singleTxnLimit: number;
|
|
75
|
+
maxBalance: number;
|
|
74
76
|
};
|
|
75
77
|
|
|
76
78
|
export interface FPSendPaymentRequest {
|
|
@@ -172,7 +174,7 @@ export interface FPAccount {
|
|
|
172
174
|
phone: string;
|
|
173
175
|
bankName?: string;
|
|
174
176
|
bankCode?: string;
|
|
175
|
-
balance?: FPBalance;
|
|
177
|
+
balance?: FPBalance | null;
|
|
176
178
|
currency?: string;
|
|
177
179
|
}
|
|
178
180
|
|
|
@@ -195,9 +195,10 @@ class BLESenderService {
|
|
|
195
195
|
rawDevice: device,
|
|
196
196
|
};
|
|
197
197
|
} catch (err) {
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
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
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
} catch {
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
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
|
);
|
package/src/engine/FPEngine.ts
CHANGED
|
@@ -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
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
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={
|
|
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
|
|