react-native-fpay 0.4.29 → 0.4.31
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 +5 -0
- package/lib/module/FountainPayProvider.js.map +1 -1
- package/lib/module/core/api/index.js +59 -0
- package/lib/module/core/api/index.js.map +1 -1
- package/lib/module/core/types/index.js +32 -0
- package/lib/module/core/types/index.js.map +1 -1
- package/lib/module/engine/FPEngine.js +9 -0
- package/lib/module/engine/FPEngine.js.map +1 -1
- package/lib/module/hooks/useLocation.js +66 -0
- package/lib/module/hooks/useLocation.js.map +1 -0
- package/lib/module/ui/components/ConfirmScreen.js +43 -51
- package/lib/module/ui/components/ConfirmScreen.js.map +1 -1
- package/lib/module/ui/components/RecurringToggle.js +94 -0
- package/lib/module/ui/components/RecurringToggle.js.map +1 -0
- package/lib/module/ui/modals/FPShell.js +19 -0
- package/lib/module/ui/modals/FPShell.js.map +1 -1
- package/lib/module/ui/screens/BillsScreen.js +186 -0
- package/lib/module/ui/screens/BillsScreen.js.map +1 -0
- package/lib/module/ui/screens/ResultScreen.js +113 -28
- package/lib/module/ui/screens/ResultScreen.js.map +1 -1
- package/lib/module/ui/screens/SendScreen.js +85 -16
- package/lib/module/ui/screens/SendScreen.js.map +1 -1
- package/lib/module/ui/screens/sub/billPayment/AirtimeScreen.js +257 -0
- package/lib/module/ui/screens/sub/billPayment/AirtimeScreen.js.map +1 -0
- package/lib/module/ui/screens/sub/billPayment/CableScreen.js +264 -0
- package/lib/module/ui/screens/sub/billPayment/CableScreen.js.map +1 -0
- package/lib/module/ui/screens/sub/billPayment/DataScreen.js +273 -0
- package/lib/module/ui/screens/sub/billPayment/DataScreen.js.map +1 -0
- package/lib/module/ui/screens/sub/billPayment/ElectricityScreen.js +337 -0
- package/lib/module/ui/screens/sub/billPayment/ElectricityScreen.js.map +1 -0
- package/lib/module/ui/screens/sub/sendPayment/TransferSubScreen.js +1 -1
- package/lib/module/ui/screens/sub/sendPayment/TransferSubScreen.js.map +1 -1
- package/lib/typescript/src/FountainPayProvider.d.ts.map +1 -1
- package/lib/typescript/src/core/api/index.d.ts +52 -63
- package/lib/typescript/src/core/api/index.d.ts.map +1 -1
- package/lib/typescript/src/core/types/index.d.ts +159 -6
- package/lib/typescript/src/core/types/index.d.ts.map +1 -1
- package/lib/typescript/src/engine/FPEngine.d.ts +4 -2
- package/lib/typescript/src/engine/FPEngine.d.ts.map +1 -1
- package/lib/typescript/src/hooks/useLocation.d.ts +12 -0
- package/lib/typescript/src/hooks/useLocation.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +1 -1
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/ui/components/ConfirmScreen.d.ts +25 -4
- package/lib/typescript/src/ui/components/ConfirmScreen.d.ts.map +1 -1
- package/lib/typescript/src/ui/components/RecurringToggle.d.ts +7 -0
- package/lib/typescript/src/ui/components/RecurringToggle.d.ts.map +1 -0
- package/lib/typescript/src/ui/modals/FPShell.d.ts.map +1 -1
- package/lib/typescript/src/ui/screens/BillsScreen.d.ts +10 -0
- package/lib/typescript/src/ui/screens/BillsScreen.d.ts.map +1 -0
- package/lib/typescript/src/ui/screens/ResultScreen.d.ts +20 -3
- package/lib/typescript/src/ui/screens/ResultScreen.d.ts.map +1 -1
- package/lib/typescript/src/ui/screens/SendScreen.d.ts.map +1 -1
- package/lib/typescript/src/ui/screens/sub/billPayment/AirtimeScreen.d.ts +15 -0
- package/lib/typescript/src/ui/screens/sub/billPayment/AirtimeScreen.d.ts.map +1 -0
- package/lib/typescript/src/ui/screens/sub/billPayment/CableScreen.d.ts +14 -0
- package/lib/typescript/src/ui/screens/sub/billPayment/CableScreen.d.ts.map +1 -0
- package/lib/typescript/src/ui/screens/sub/billPayment/DataScreen.d.ts +14 -0
- package/lib/typescript/src/ui/screens/sub/billPayment/DataScreen.d.ts.map +1 -0
- package/lib/typescript/src/ui/screens/sub/billPayment/ElectricityScreen.d.ts +16 -0
- package/lib/typescript/src/ui/screens/sub/billPayment/ElectricityScreen.d.ts.map +1 -0
- package/package.json +2 -2
- package/src/FountainPayProvider.tsx +7 -0
- package/src/core/api/index.ts +149 -27
- package/src/core/types/index.ts +194 -11
- package/src/engine/FPEngine.ts +12 -1
- package/src/hooks/useLocation.ts +81 -0
- package/src/index.ts +9 -1
- package/src/ui/components/ConfirmScreen.tsx +47 -54
- package/src/ui/components/RecurringToggle.tsx +106 -0
- package/src/ui/modals/FPShell.tsx +26 -3
- package/src/ui/screens/BillsScreen.tsx +197 -0
- package/src/ui/screens/ResultScreen.tsx +129 -28
- package/src/ui/screens/SendScreen.tsx +124 -68
- package/src/ui/screens/sub/billPayment/AirtimeScreen.tsx +252 -0
- package/src/ui/screens/sub/billPayment/CableScreen.tsx +274 -0
- package/src/ui/screens/sub/billPayment/DataScreen.tsx +263 -0
- package/src/ui/screens/sub/billPayment/ElectricityScreen.tsx +344 -0
- package/src/ui/screens/sub/sendPayment/TransferSubScreen.tsx +1 -1
|
@@ -1,11 +1,13 @@
|
|
|
1
|
-
import { useEffect, useRef, useState } from 'react';
|
|
1
|
+
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
2
|
+
import type { FC } from 'react';
|
|
2
3
|
import { Animated, Easing, TouchableOpacity } from 'react-native';
|
|
3
4
|
import Svg, { Path, Circle } from 'react-native-svg';
|
|
4
5
|
import styled from 'styled-components/native';
|
|
5
6
|
import { C, F, R, S } from '../theme';
|
|
6
|
-
import type {
|
|
7
|
-
import { transferAPI } from '../../core/api';
|
|
7
|
+
import type { SubscriptionFreq } from '../../core/types';
|
|
8
|
+
import { subscriptionAPI, transferAPI } from '../../core/api';
|
|
8
9
|
import Gradients from '../components/Gradients';
|
|
10
|
+
import RecurringToggle from '../components/RecurringToggle';
|
|
9
11
|
|
|
10
12
|
// ── Icons ─────────────────────────────────────────────────────
|
|
11
13
|
|
|
@@ -187,7 +189,7 @@ function CountdownRing({ duration }: { duration: number }) {
|
|
|
187
189
|
|
|
188
190
|
// ── Row ──────────────────────────────────────────────────────
|
|
189
191
|
|
|
190
|
-
|
|
192
|
+
const Row: FC<{ label: string; value: string }> = ({ label, value }) => {
|
|
191
193
|
return (
|
|
192
194
|
<RowWrap>
|
|
193
195
|
<RowLabel>{label}</RowLabel>
|
|
@@ -198,37 +200,77 @@ function Row({ label, value }: { label: string; value: string }) {
|
|
|
198
200
|
|
|
199
201
|
// ── Main ─────────────────────────────────────────────────────
|
|
200
202
|
|
|
203
|
+
/**
|
|
204
|
+
* Generalized props — ResultScreen no longer assumes a "recipient" or that
|
|
205
|
+
* the underlying transaction came through transferAPI.
|
|
206
|
+
* - reference: used to poll status (via statusFetcher, defaults to transferAPI.status)
|
|
207
|
+
* - summaryRows: extra rows to render in the detail card (e.g. "To", "Channel")
|
|
208
|
+
* beyond the always-present Reference/Date/Status rows
|
|
209
|
+
* - allowRecurring: SendScreen keeps the existing recurring-payment upsell;
|
|
210
|
+
* domains with no concept of "this recipient" (bills) pass false
|
|
211
|
+
* - statusFetcher: which endpoint to poll — defaults to the shared
|
|
212
|
+
* agencyTransaction-backed transferAPI.status, which bill transactions
|
|
213
|
+
* also live in
|
|
214
|
+
*/
|
|
201
215
|
interface Props {
|
|
202
|
-
|
|
216
|
+
reference: string;
|
|
217
|
+
summaryRows?: { label: string; value: string }[];
|
|
218
|
+
allowRecurring?: boolean;
|
|
219
|
+
statusFetcher?: (reference: string) => Promise<any>;
|
|
203
220
|
onClose: () => void;
|
|
204
221
|
}
|
|
205
222
|
|
|
206
|
-
export function ResultScreen({
|
|
223
|
+
export function ResultScreen({
|
|
224
|
+
reference,
|
|
225
|
+
summaryRows,
|
|
226
|
+
allowRecurring = true,
|
|
227
|
+
statusFetcher = transferAPI.status,
|
|
228
|
+
onClose,
|
|
229
|
+
}: Props) {
|
|
207
230
|
const [loading, setLoading] = useState<boolean>(false);
|
|
208
231
|
const [transactionDetail, setTransactionDetail] = useState<any>(null)
|
|
209
232
|
const opacAnim = useRef(new Animated.Value(1)).current; // ← start visible
|
|
210
233
|
const slideAnim = useRef(new Animated.Value(0)).current; // ← start in place
|
|
211
234
|
const scaleAnim = useRef(new Animated.Value(1)).current; // ← start full size
|
|
212
235
|
|
|
236
|
+
const [recurringEnabled, setRecurringEnabled] = useState(false);
|
|
237
|
+
const [recurringFrequency, setRecurringFrequency] = useState<SubscriptionFreq>('MONTHLY');
|
|
238
|
+
|
|
239
|
+
|
|
213
240
|
const isSuccess = transactionDetail?.status.toLowerCase() === 'successful';
|
|
214
241
|
const accentColor = isSuccess ? C.green : C.red;
|
|
215
242
|
const bgColor = isSuccess ? '#E3FCEF' : '#FFEBE6';
|
|
216
243
|
|
|
244
|
+
const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
245
|
+
const startTimeRef = useRef<number>(Date.now());
|
|
246
|
+
const remainingTimeRef = useRef<number>(COUNTDOWN_SECONDS * 1000);
|
|
247
|
+
const isPausedRef = useRef<boolean>(false);
|
|
248
|
+
|
|
249
|
+
const startTimer = useCallback((duration: number) => {
|
|
250
|
+
if (timerRef.current) clearTimeout(timerRef.current);
|
|
251
|
+
startTimeRef.current = Date.now();
|
|
252
|
+
timerRef.current = setTimeout(onClose, duration);
|
|
253
|
+
}, [onClose]);
|
|
254
|
+
|
|
255
|
+
const pauseTimer = useCallback(() => {
|
|
256
|
+
if (isPausedRef.current) return;
|
|
257
|
+
isPausedRef.current = true;
|
|
258
|
+
if (timerRef.current) clearTimeout(timerRef.current);
|
|
259
|
+
// Calculate how much time is left
|
|
260
|
+
const elapsed = Date.now() - startTimeRef.current;
|
|
261
|
+
remainingTimeRef.current = Math.max(remainingTimeRef.current - elapsed, 0);
|
|
262
|
+
}, []);
|
|
263
|
+
|
|
264
|
+
const resumeTimer = useCallback(() => {
|
|
265
|
+
if (!isPausedRef.current) return;
|
|
266
|
+
isPausedRef.current = false;
|
|
267
|
+
startTimer(remainingTimeRef.current);
|
|
268
|
+
}, [startTimer]);
|
|
269
|
+
|
|
217
270
|
const formatted = `${transactionDetail?.currency || 'NGN'} ${Number(transactionDetail?.amount).toLocaleString('en-NG', {
|
|
218
271
|
minimumFractionDigits: 2,
|
|
219
272
|
})}`;
|
|
220
273
|
|
|
221
|
-
const recipient = transaction?.recipient as any;
|
|
222
|
-
const recipientName = recipient?.accountName ?? recipient?.name ?? '—';
|
|
223
|
-
|
|
224
|
-
const channelLabel: Record<string, string> = {
|
|
225
|
-
transfer: 'Bank Transfer',
|
|
226
|
-
bluetooth: 'Bluetooth',
|
|
227
|
-
proximity: 'Nearby',
|
|
228
|
-
nqr: 'QR Code',
|
|
229
|
-
nfc: 'NFC Tap',
|
|
230
|
-
};
|
|
231
|
-
|
|
232
274
|
const dateStr = transactionDetail?.createdAt
|
|
233
275
|
? new Date(transactionDetail?.createdAt).toLocaleString('en-NG')
|
|
234
276
|
: '—';
|
|
@@ -237,7 +279,7 @@ export function ResultScreen({ transaction, onClose }: Props) {
|
|
|
237
279
|
setLoading(true);
|
|
238
280
|
setTransactionDetail(null);
|
|
239
281
|
try{
|
|
240
|
-
const response = await
|
|
282
|
+
const response = await statusFetcher(reference) as any;
|
|
241
283
|
console.log("Transaction payload: ", response);
|
|
242
284
|
if(response.status){
|
|
243
285
|
setTransactionDetail(response.payload);
|
|
@@ -250,19 +292,70 @@ export function ResultScreen({ transaction, onClose }: Props) {
|
|
|
250
292
|
|
|
251
293
|
}
|
|
252
294
|
|
|
295
|
+
const calculateNextDate=(current: Date | string, frequency: SubscriptionFreq): Date=> {
|
|
296
|
+
const date = new Date(current);
|
|
297
|
+
|
|
298
|
+
switch (frequency) {
|
|
299
|
+
case 'DAILY':
|
|
300
|
+
date.setDate(date.getDate() + 1);
|
|
301
|
+
break;
|
|
302
|
+
case 'WEEKLY':
|
|
303
|
+
date.setDate(date.getDate() + 7);
|
|
304
|
+
break;
|
|
305
|
+
case 'MONTHLY':
|
|
306
|
+
date.setMonth(date.getMonth() + 1);
|
|
307
|
+
break;
|
|
308
|
+
case 'YEARLY':
|
|
309
|
+
date.setFullYear(date.getFullYear() + 1);
|
|
310
|
+
break;
|
|
311
|
+
default:
|
|
312
|
+
throw new Error(`Unknown frequency: ${frequency}`);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
return date;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
const handlePayment = async () => {
|
|
319
|
+
pauseTimer();
|
|
320
|
+
try {
|
|
321
|
+
await subscriptionAPI.create({
|
|
322
|
+
serviceName: summaryRows?.find(r => r.label === 'To')?.value ?? 'Payment',
|
|
323
|
+
serviceCategory: 'Payment', // default category
|
|
324
|
+
amount: transactionDetail.amount, // from your existing payment data
|
|
325
|
+
currency: transactionDetail.currency,
|
|
326
|
+
frequency: recurringFrequency,
|
|
327
|
+
accountNumber: transactionDetail.senderAccountNumber,
|
|
328
|
+
receiverAccountNumber: transactionDetail.destAccountNumber,
|
|
329
|
+
logoUrl: transactionDetail.recipientLogo ?? '',
|
|
330
|
+
nextPaymentDate: calculateNextDate(new Date(), recurringFrequency).toISOString(),
|
|
331
|
+
});
|
|
332
|
+
} catch {
|
|
333
|
+
console.warn('Subscription registration failed silently');
|
|
334
|
+
} finally {
|
|
335
|
+
resumeTimer();
|
|
336
|
+
}
|
|
337
|
+
};
|
|
338
|
+
|
|
253
339
|
|
|
254
340
|
|
|
341
|
+
// useEffect(() => {
|
|
342
|
+
// const timer = setTimeout(onClose, COUNTDOWN_SECONDS * 1000);
|
|
343
|
+
// return () => clearTimeout(timer);
|
|
344
|
+
// }, [onClose]);
|
|
345
|
+
|
|
255
346
|
useEffect(() => {
|
|
256
|
-
|
|
257
|
-
return () =>
|
|
258
|
-
|
|
347
|
+
startTimer(remainingTimeRef.current);
|
|
348
|
+
return () => {
|
|
349
|
+
if (timerRef.current) clearTimeout(timerRef.current);
|
|
350
|
+
};
|
|
351
|
+
}, [startTimer])
|
|
259
352
|
|
|
260
353
|
|
|
261
354
|
useEffect(()=>{
|
|
262
|
-
if(
|
|
355
|
+
if(reference){
|
|
263
356
|
loadTransaction();
|
|
264
357
|
}
|
|
265
|
-
}, [
|
|
358
|
+
}, [reference]);
|
|
266
359
|
|
|
267
360
|
|
|
268
361
|
if (loading && !transactionDetail) {
|
|
@@ -286,13 +379,22 @@ export function ResultScreen({ transaction, onClose }: Props) {
|
|
|
286
379
|
<Amount>{formatted}</Amount>
|
|
287
380
|
|
|
288
381
|
<Card>
|
|
289
|
-
|
|
290
|
-
|
|
382
|
+
{summaryRows?.map((row) => (
|
|
383
|
+
<Row key={row.label} label={row.label} value={row.value} />
|
|
384
|
+
))}
|
|
291
385
|
<Row label="Reference" value={transactionDetail?.reference} />
|
|
292
386
|
<Row label="Date" value={dateStr} />
|
|
293
387
|
<Row label="Status" value={transactionDetail?.status?.toUpperCase()} />
|
|
294
388
|
</Card>
|
|
295
|
-
|
|
389
|
+
{allowRecurring && (
|
|
390
|
+
<RecurringToggle
|
|
391
|
+
onToggle={(enabled, freq) => {
|
|
392
|
+
setRecurringEnabled(enabled);
|
|
393
|
+
setRecurringFrequency(freq);
|
|
394
|
+
handlePayment();
|
|
395
|
+
}}
|
|
396
|
+
/>
|
|
397
|
+
)}
|
|
296
398
|
<Footer>
|
|
297
399
|
<CountdownRing duration={COUNTDOWN_SECONDS * 1000} />
|
|
298
400
|
<CloseBtn onPress={onClose} activeOpacity={0.8}>
|
|
@@ -301,5 +403,4 @@ export function ResultScreen({ transaction, onClose }: Props) {
|
|
|
301
403
|
</Footer>
|
|
302
404
|
</Container>
|
|
303
405
|
);
|
|
304
|
-
}
|
|
305
|
-
|
|
406
|
+
}
|
|
@@ -35,6 +35,7 @@ import ConfirmScreen from '../components/ConfirmScreen';
|
|
|
35
35
|
import LoadingAnimation from '../components/LoadingAnimation';
|
|
36
36
|
import { getFPStore } from '../../store/FPStore';
|
|
37
37
|
import { ResultScreen } from './ResultScreen';
|
|
38
|
+
import { useLocation } from '../../hooks/useLocation';
|
|
38
39
|
|
|
39
40
|
const { height } = Dimensions.get('window');
|
|
40
41
|
|
|
@@ -315,21 +316,22 @@ const SendScreen = ({
|
|
|
315
316
|
const [channel, setChannel] = useState<Channel>('TRANSFER');
|
|
316
317
|
const [loading, setLoading] = useState<boolean>(false);
|
|
317
318
|
const [showConfirmationModal, setShowConfirmationModal] = useState<boolean>(false);
|
|
319
|
+
const { requestLocation } = useLocation();
|
|
318
320
|
|
|
319
321
|
const [completedTx, setCompletedTx] = useState<FPTransaction | null>(null);
|
|
320
322
|
|
|
321
|
-
const [transactionPayload, setTransactionPayload] = useState<
|
|
323
|
+
const [transactionPayload, setTransactionPayload] = useState<FPSendWalletPaymentRequest>({} as FPSendWalletPaymentRequest);
|
|
322
324
|
|
|
323
325
|
const formatted = `${currency} ${amount.toLocaleString('en-NG', { minimumFractionDigits: 2 })}`;
|
|
324
326
|
|
|
325
327
|
// { id: result.reference, reference: result.reference, type: 'debit', channel: 'transfer', amount: result.amount, currency: result.currency, status: result.status, recipient: { accountName: result.recipient.accountName }, createdAt: result.createdAt }
|
|
326
328
|
|
|
327
329
|
const handleContinue = (
|
|
328
|
-
tx:
|
|
330
|
+
tx: FPSendWalletPaymentRequest | null
|
|
329
331
|
) => {
|
|
330
332
|
console.log('Transaction Payload: ', tx);
|
|
331
333
|
setTransactionPayload(
|
|
332
|
-
tx as
|
|
334
|
+
tx as FPSendWalletPaymentRequest
|
|
333
335
|
);
|
|
334
336
|
setShowConfirmationModal(true);
|
|
335
337
|
};
|
|
@@ -341,74 +343,89 @@ const SendScreen = ({
|
|
|
341
343
|
|
|
342
344
|
const handleProcessingTransaction = async (temptId: string) => {
|
|
343
345
|
try {
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
346
|
+
setLoading(true);
|
|
347
|
+
setShowConfirmationModal(false);
|
|
348
|
+
let response: any = null;
|
|
349
|
+
|
|
350
|
+
const recipient = transactionPayload?.recipient as any;
|
|
351
|
+
const isTerminalTransaction = transactionPayload.sender_type === 'AGENT' || recipient.type === 'TERMINAL';
|
|
352
|
+
let movement: { latitude: number; longitude: number } | undefined;
|
|
353
|
+
|
|
354
|
+
if (isTerminalTransaction) {
|
|
355
|
+
// Always fetch fresh location for terminal transactions
|
|
356
|
+
const loc = await requestLocation();
|
|
357
|
+
|
|
358
|
+
if (loc) {
|
|
359
|
+
movement = {
|
|
360
|
+
latitude: loc.latitude,
|
|
361
|
+
longitude: loc.longitude,
|
|
362
|
+
// No address — Django will reverse-geocode
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
const newTransactionPayload ={...transactionPayload, movement}
|
|
367
|
+
switch (newTransactionPayload?.channel) {
|
|
368
|
+
case 'transfer':
|
|
369
|
+
case 'bluetooth':
|
|
370
|
+
case 'proximity': {
|
|
371
|
+
// Internal transfer — recipient has id + type (AGENT/TERMINAL)
|
|
372
|
+
if (recipient?.id && recipient?.type && ['AGENT', 'TERMINAL'].includes(recipient.type)) {
|
|
373
|
+
response = await transferAPI.sendToWallet(
|
|
374
|
+
{
|
|
375
|
+
...newTransactionPayload,
|
|
376
|
+
recipient: {
|
|
377
|
+
account_number: recipient.accountNumber,
|
|
378
|
+
agent_id: recipient.id,
|
|
379
|
+
type: recipient.type,
|
|
380
|
+
terminal_id: recipient?.terminalId || '',
|
|
381
|
+
},
|
|
364
382
|
},
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
383
|
+
temptId
|
|
384
|
+
);
|
|
385
|
+
}
|
|
386
|
+
// Bank transfer — recipient has accountNumber + bankCode
|
|
387
|
+
else if (recipient?.bankCode) {
|
|
388
|
+
response = await transferAPI.sendToBank(
|
|
389
|
+
newTransactionPayload as any,
|
|
390
|
+
temptId
|
|
391
|
+
);
|
|
392
|
+
}
|
|
393
|
+
// Wallet transfer — recipient has accountNumber + userId/agentId
|
|
394
|
+
else {
|
|
395
|
+
response = await transferAPI.sendToWallet(
|
|
396
|
+
newTransactionPayload as FPSendWalletPaymentRequest,
|
|
397
|
+
temptId
|
|
398
|
+
);
|
|
399
|
+
}
|
|
400
|
+
break;
|
|
368
401
|
}
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
response = await
|
|
372
|
-
|
|
402
|
+
|
|
403
|
+
case 'nqr':
|
|
404
|
+
response = await nqrAPI.pay(
|
|
405
|
+
{
|
|
406
|
+
...newTransactionPayload,
|
|
407
|
+
narration: `Send ${formatted} to ${recipient?.accountName ?? ''}`,
|
|
408
|
+
amount: parseFloat(newTransactionPayload.amount)
|
|
409
|
+
},
|
|
373
410
|
temptId
|
|
374
411
|
);
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
response = await
|
|
379
|
-
|
|
412
|
+
break;
|
|
413
|
+
|
|
414
|
+
case 'nfc':
|
|
415
|
+
response = await nfcAPI.pay(
|
|
416
|
+
{
|
|
417
|
+
...newTransactionPayload,
|
|
418
|
+
narration: `Send ${formatted} to ${recipient?.accountName ?? ''}`,
|
|
419
|
+
amount: parseFloat(newTransactionPayload.amount)
|
|
420
|
+
},
|
|
380
421
|
temptId
|
|
381
422
|
);
|
|
382
|
-
|
|
383
|
-
break;
|
|
384
|
-
}
|
|
423
|
+
break;
|
|
385
424
|
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
sender: user ?? undefined,
|
|
391
|
-
narration: `Send ${formatted} to ${recipient?.accountName ?? ''}`,
|
|
392
|
-
},
|
|
393
|
-
temptId
|
|
394
|
-
);
|
|
395
|
-
break;
|
|
396
|
-
|
|
397
|
-
case 'nfc':
|
|
398
|
-
response = await nfcAPI.pay(
|
|
399
|
-
{
|
|
400
|
-
...transactionPayload,
|
|
401
|
-
sender: user ?? undefined,
|
|
402
|
-
narration: `Send ${formatted} to ${recipient?.accountName ?? ''}`,
|
|
403
|
-
},
|
|
404
|
-
temptId
|
|
405
|
-
);
|
|
406
|
-
break;
|
|
407
|
-
|
|
408
|
-
default:
|
|
409
|
-
onError?.({ message: 'Invalid channel' } as FPError);
|
|
410
|
-
return;
|
|
411
|
-
}
|
|
425
|
+
default:
|
|
426
|
+
onError?.({ message: 'Invalid channel' } as FPError);
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
412
429
|
|
|
413
430
|
|
|
414
431
|
if (!response?.status) {
|
|
@@ -431,10 +448,25 @@ const SendScreen = ({
|
|
|
431
448
|
};
|
|
432
449
|
|
|
433
450
|
if (completedTx) {
|
|
451
|
+
const mergedTx = { ...transactionPayload, ...completedTx } as any;
|
|
452
|
+
const recipient = mergedTx?.recipient as any;
|
|
453
|
+
const recipientName = recipient?.accountName ?? recipient?.name ?? '—';
|
|
454
|
+
const channelLabel: Record<string, string> = {
|
|
455
|
+
transfer: 'Bank Transfer',
|
|
456
|
+
bluetooth: 'Bluetooth',
|
|
457
|
+
proximity: 'Nearby',
|
|
458
|
+
nqr: 'QR Code',
|
|
459
|
+
nfc: 'NFC Tap',
|
|
460
|
+
};
|
|
434
461
|
|
|
435
462
|
return (
|
|
436
463
|
<ResultScreen
|
|
437
|
-
|
|
464
|
+
reference={mergedTx.reference}
|
|
465
|
+
summaryRows={[
|
|
466
|
+
{ label: 'To', value: recipientName },
|
|
467
|
+
{ label: 'Channel', value: channelLabel[mergedTx?.channel] ?? mergedTx?.channel },
|
|
468
|
+
]}
|
|
469
|
+
allowRecurring
|
|
438
470
|
onClose={handleResultClose}
|
|
439
471
|
/>
|
|
440
472
|
);
|
|
@@ -549,8 +581,32 @@ const SendScreen = ({
|
|
|
549
581
|
</SheetHeader>
|
|
550
582
|
|
|
551
583
|
<ConfirmScreen
|
|
552
|
-
|
|
553
|
-
|
|
584
|
+
amount={Number(transactionPayload?.amount || 0)}
|
|
585
|
+
currency={transactionPayload?.currency}
|
|
586
|
+
subtitle="Double check the transfer details before you proceed. Please note that successful transfers cannot be reversed."
|
|
587
|
+
summaryRows={(() => {
|
|
588
|
+
const recipient = transactionPayload?.recipient as any;
|
|
589
|
+
const recipientName = recipient?.accountName ?? '';
|
|
590
|
+
const accountNumber = recipient?.accountNumber ?? '';
|
|
591
|
+
const bankName = (recipient as any)?.bankName;
|
|
592
|
+
const rows = [{ label: 'Name', value: recipientName }];
|
|
593
|
+
if (transactionPayload?.isBank) {
|
|
594
|
+
if (transactionPayload?.channel === 'transfer') {
|
|
595
|
+
rows.push({ label: 'Bank', value: bankName });
|
|
596
|
+
rows.push({ label: 'Account No.', value: accountNumber });
|
|
597
|
+
}
|
|
598
|
+
} else {
|
|
599
|
+
rows.push({ label: 'Account Number', value: accountNumber });
|
|
600
|
+
}
|
|
601
|
+
return rows;
|
|
602
|
+
})()}
|
|
603
|
+
validate={(pin: string) =>
|
|
604
|
+
transferAPI.validateTransfer(
|
|
605
|
+
pin,
|
|
606
|
+
getFPStore().psspId || '',
|
|
607
|
+
(transactionPayload?.recipient as any)?.id
|
|
608
|
+
)
|
|
609
|
+
}
|
|
554
610
|
onContinue={handleProcessingTransaction}
|
|
555
611
|
/>
|
|
556
612
|
</SheetScrollView>
|
|
@@ -560,4 +616,4 @@ const SendScreen = ({
|
|
|
560
616
|
);
|
|
561
617
|
};
|
|
562
618
|
|
|
563
|
-
export default SendScreen;
|
|
619
|
+
export default SendScreen;
|