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
package/src/core/api/index.ts
CHANGED
|
@@ -13,6 +13,18 @@ import {
|
|
|
13
13
|
type FPTransactionResponse,
|
|
14
14
|
type FPBalance,
|
|
15
15
|
type HttpCallResponseFormat,
|
|
16
|
+
type FPNetworkOperator,
|
|
17
|
+
type FPNetworkCode,
|
|
18
|
+
type FPDataPlan,
|
|
19
|
+
type FPBillProvider,
|
|
20
|
+
type FPBillTariff,
|
|
21
|
+
type FPMeterLookupResult,
|
|
22
|
+
type FPMeterType,
|
|
23
|
+
type FPAirtimePurchaseRequest,
|
|
24
|
+
type FPDataPurchaseRequest,
|
|
25
|
+
type FPElectricityPurchaseRequest,
|
|
26
|
+
type FPCablePurchaseRequest,
|
|
27
|
+
type FPBillTransaction,
|
|
16
28
|
BUSINESSID,
|
|
17
29
|
} from '../types';
|
|
18
30
|
|
|
@@ -20,32 +32,32 @@ export const healthAPI = {
|
|
|
20
32
|
ping: () =>
|
|
21
33
|
http()
|
|
22
34
|
.get('/health')
|
|
23
|
-
.then((r) => r.data),
|
|
35
|
+
.then((r: any) => r.data),
|
|
24
36
|
};
|
|
25
37
|
|
|
26
38
|
export const authenticateAPI = {
|
|
27
39
|
login: (appId: string) =>
|
|
28
40
|
http()
|
|
29
41
|
.post('/auth', { user_id: appId })
|
|
30
|
-
.then((r) => r.data),
|
|
42
|
+
.then((r: any) => r.data),
|
|
31
43
|
|
|
32
44
|
profile: () =>
|
|
33
45
|
http()
|
|
34
46
|
.get(`/get-user-details`)
|
|
35
|
-
.then((r) => r.data.payload),
|
|
47
|
+
.then((r: any) => r.data.payload),
|
|
36
48
|
|
|
37
49
|
logout: () =>
|
|
38
50
|
http()
|
|
39
51
|
.post('/auth/logout')
|
|
40
|
-
.then((r) => r.data),
|
|
52
|
+
.then((r: any) => r.data),
|
|
41
53
|
|
|
42
54
|
validateOtp: (otp: string, email: string) =>
|
|
43
55
|
http()
|
|
44
56
|
.post<{ Response: any }>('/verify-otp', { otp, email })
|
|
45
|
-
.then((r) => r.data),
|
|
57
|
+
.then((r: any) => r.data),
|
|
46
58
|
|
|
47
59
|
validateToken: () =>
|
|
48
|
-
http().get<{response: HttpCallResponseFormat}>('/auth/agent/validate-token').then(r => r.data),
|
|
60
|
+
http().get<{response: HttpCallResponseFormat}>('/auth/agent/validate-token').then((r: any) => r.data),
|
|
49
61
|
|
|
50
62
|
sendSmsOtp: async(payload: any)=>
|
|
51
63
|
http()
|
|
@@ -54,22 +66,22 @@ export const authenticateAPI = {
|
|
|
54
66
|
message: string;
|
|
55
67
|
payload:any
|
|
56
68
|
}>("shared/send-sms-otp", payload)
|
|
57
|
-
.then((r)=>r.data),
|
|
69
|
+
.then((r: any)=>r.data),
|
|
58
70
|
|
|
59
71
|
verifyBvn: async() =>
|
|
60
72
|
http()
|
|
61
73
|
.get<{response: HttpCallResponseFormat}>("verify-bvn")
|
|
62
|
-
.then((r)=>r.data),
|
|
74
|
+
.then((r: any)=>r.data),
|
|
63
75
|
|
|
64
76
|
verifySms: async(payload: any)=>
|
|
65
77
|
http()
|
|
66
78
|
.post<{response: HttpCallResponseFormat}>("verify-bvn", payload)
|
|
67
|
-
.then((r)=>r.data),
|
|
79
|
+
.then((r: any)=>r.data),
|
|
68
80
|
|
|
69
81
|
updateProfile: async(payload: any)=>
|
|
70
82
|
http()
|
|
71
83
|
.put<{response: HttpCallResponseFormat}>("update-user-agent", payload)
|
|
72
|
-
.then((r)=>r.data),
|
|
84
|
+
.then((r: any)=>r.data),
|
|
73
85
|
};
|
|
74
86
|
|
|
75
87
|
export const accountAPI = {
|
|
@@ -85,17 +97,17 @@ export const accountAPI = {
|
|
|
85
97
|
nin: user.nin ?? '',
|
|
86
98
|
dateOfBirth: user.dateOfBirth ?? '',
|
|
87
99
|
})
|
|
88
|
-
.then((r) => r.data.payload),
|
|
100
|
+
.then((r: any) => r.data.payload),
|
|
89
101
|
|
|
90
102
|
// Get account details using PSSP id
|
|
91
103
|
getAccount: () =>
|
|
92
104
|
http()
|
|
93
105
|
.get<{ payload: FPAccount }>(`/get-accounts-details`)
|
|
94
|
-
.then((r) => r.data.payload),
|
|
106
|
+
.then((r: any) => r.data.payload),
|
|
95
107
|
|
|
96
108
|
getAgentBalance: () =>
|
|
97
109
|
http().get<{ payload: FPBalance }>(`/get-balance`)
|
|
98
|
-
.then(r => r.data.payload),
|
|
110
|
+
.then((r: any) => r.data.payload),
|
|
99
111
|
|
|
100
112
|
};
|
|
101
113
|
|
|
@@ -103,7 +115,7 @@ export const transferAPI = {
|
|
|
103
115
|
getBanks: () =>
|
|
104
116
|
http()
|
|
105
117
|
.get<FPBankItem[]>('/get-banks')
|
|
106
|
-
.then((r) => r.data),
|
|
118
|
+
.then((r: any) => r.data),
|
|
107
119
|
|
|
108
120
|
verifyAccount: (accountNumber: string, bankCode: string) =>
|
|
109
121
|
http()
|
|
@@ -116,7 +128,7 @@ export const transferAPI = {
|
|
|
116
128
|
account_no: accountNumber,
|
|
117
129
|
institution_code: bankCode,
|
|
118
130
|
})
|
|
119
|
-
.then((r) => r.data),
|
|
131
|
+
.then((r: any) => r.data),
|
|
120
132
|
|
|
121
133
|
verifyWalletAccount: (accountNumber: string) =>
|
|
122
134
|
http()
|
|
@@ -126,16 +138,16 @@ export const transferAPI = {
|
|
|
126
138
|
bankName: string;
|
|
127
139
|
bankCode: string;
|
|
128
140
|
}>('/wallet-name-enquiry', { account_number: accountNumber })
|
|
129
|
-
.then((r) => r.data),
|
|
141
|
+
.then((r: any) => r.data),
|
|
130
142
|
|
|
131
143
|
validateTransfer: (pin: string, userId: string, receiverId: string) =>
|
|
132
144
|
http()
|
|
133
|
-
.post<{ status: boolean; message: string }>('/validate-transaction', {
|
|
145
|
+
.post<{ status: boolean; message: string; payload?: { temp_id: string } }>('/validate-transaction', {
|
|
134
146
|
pin,
|
|
135
147
|
sender_type: 'AGENT',
|
|
136
148
|
receiver_id: receiverId,
|
|
137
149
|
})
|
|
138
|
-
.then((r) => r.data),
|
|
150
|
+
.then((r: any) => r.data),
|
|
139
151
|
|
|
140
152
|
sendToWallet: (payload: any, temptId: string) =>
|
|
141
153
|
http()
|
|
@@ -143,7 +155,7 @@ export const transferAPI = {
|
|
|
143
155
|
...payload,
|
|
144
156
|
temp_id: temptId,
|
|
145
157
|
})
|
|
146
|
-
.then((r) => r.data),
|
|
158
|
+
.then((r: any) => r.data),
|
|
147
159
|
|
|
148
160
|
// Send to external bank account
|
|
149
161
|
sendToBank: (payload: FPSendPaymentRequest, temptId: string) =>
|
|
@@ -152,19 +164,19 @@ export const transferAPI = {
|
|
|
152
164
|
...payload,
|
|
153
165
|
temp_id: temptId,
|
|
154
166
|
})
|
|
155
|
-
.then((r) => r.data),
|
|
167
|
+
.then((r: any) => r.data),
|
|
156
168
|
|
|
157
169
|
status: (reference: string) =>
|
|
158
170
|
http()
|
|
159
171
|
.get<{ status: FPTxStatus; reference: string }>(
|
|
160
172
|
'/get-transaction-status/' + reference
|
|
161
173
|
)
|
|
162
|
-
.then((r) => r.data),
|
|
174
|
+
.then((r: any) => r.data),
|
|
163
175
|
|
|
164
176
|
verify: (reference: string) =>
|
|
165
177
|
http()
|
|
166
178
|
.get<{ FPTransactionResponse: any }>('/transaction/verify/' + reference)
|
|
167
|
-
.then((r) => r.data),
|
|
179
|
+
.then((r: any) => r.data),
|
|
168
180
|
};
|
|
169
181
|
|
|
170
182
|
export const nqrAPI = {
|
|
@@ -176,19 +188,19 @@ export const nqrAPI = {
|
|
|
176
188
|
}) =>
|
|
177
189
|
http()
|
|
178
190
|
.post<FPNQRData>('/generate-nqr', payload)
|
|
179
|
-
.then((r) => r.data),
|
|
191
|
+
.then((r: any) => r.data),
|
|
180
192
|
|
|
181
193
|
pay: (payload: FPSendPaymentRequest, temptId: string) =>
|
|
182
194
|
http()
|
|
183
195
|
.post<FPTransactionResponse>('/pay-nqr', { ...payload, temp_id: temptId })
|
|
184
|
-
.then((r) => r.data),
|
|
196
|
+
.then((r: any) => r.data),
|
|
185
197
|
};
|
|
186
198
|
|
|
187
199
|
export const nfcAPI = {
|
|
188
200
|
pay: (payload: FPSendPaymentRequest, temptId: string) =>
|
|
189
201
|
http()
|
|
190
202
|
.post<FPTransactionResponse>('/pay-nfc', { ...payload, temp_id: temptId })
|
|
191
|
-
.then((r) => r.data),
|
|
203
|
+
.then((r: any) => r.data),
|
|
192
204
|
};
|
|
193
205
|
|
|
194
206
|
export const proximityAPI = {
|
|
@@ -198,7 +210,7 @@ export const proximityAPI = {
|
|
|
198
210
|
) =>
|
|
199
211
|
http()
|
|
200
212
|
.post<{ sessionId: string }>(`/broadcast-proximity/${psspId}`, payload)
|
|
201
|
-
.then((r) => r.data),
|
|
213
|
+
.then((r: any) => r.data),
|
|
202
214
|
|
|
203
215
|
heartbeat: (sessionId: string, lat: number, lng: number) =>
|
|
204
216
|
http().patch(`/broadcast-proximity/${sessionId}`, {
|
|
@@ -216,5 +228,115 @@ export const proximityAPI = {
|
|
|
216
228
|
longitude: lng,
|
|
217
229
|
radius_meters,
|
|
218
230
|
})
|
|
219
|
-
.then((r) => r.data),
|
|
231
|
+
.then((r: any) => r.data),
|
|
220
232
|
};
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
export const subscriptionAPI = {
|
|
236
|
+
create: (
|
|
237
|
+
payload: any
|
|
238
|
+
) =>
|
|
239
|
+
http()
|
|
240
|
+
.post<{ sessionId: string }>(`/subscription/`, payload)
|
|
241
|
+
.then((r: any) => r.data),
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
export const billsAPI = {
|
|
245
|
+
getNetworks: () =>
|
|
246
|
+
http()
|
|
247
|
+
.get<{ status: boolean; payload: FPNetworkOperator[] }>('/bills/networks')
|
|
248
|
+
.then((r: any) => r.data),
|
|
249
|
+
|
|
250
|
+
getDataPlans: (network: FPNetworkCode) =>
|
|
251
|
+
http()
|
|
252
|
+
.get<{ status: boolean; payload: FPDataPlan[] }>('/bills/data-plans', {
|
|
253
|
+
params: { network },
|
|
254
|
+
})
|
|
255
|
+
.then((r: any) => r.data),
|
|
256
|
+
|
|
257
|
+
getDiscos: () =>
|
|
258
|
+
http()
|
|
259
|
+
.get<{ status: boolean; payload: FPBillProvider[] }>('/bills/electricity/discos')
|
|
260
|
+
.then((r: any) => r.data),
|
|
261
|
+
|
|
262
|
+
/** Validates a meter number against a disco before purchase — mirrors
|
|
263
|
+
* the existing CheckMeterNo() lookup in Tapit's BuyPower-backed
|
|
264
|
+
* Electricity screen, generalized behind this SDK's own endpoint. */
|
|
265
|
+
validateMeter: (meterNumber: string, disco: string, meterType: FPMeterType) =>
|
|
266
|
+
http()
|
|
267
|
+
.post<{ status: boolean; message: string; payload: FPMeterLookupResult }>(
|
|
268
|
+
'/bills/electricity/validate-meter',
|
|
269
|
+
{ meter_number: meterNumber, disco, meter_type: meterType }
|
|
270
|
+
)
|
|
271
|
+
.then((r: any) => r.data),
|
|
272
|
+
|
|
273
|
+
getCableProviders: () =>
|
|
274
|
+
http()
|
|
275
|
+
.get<{ status: boolean; payload: FPBillProvider[] }>('/bills/cable/providers')
|
|
276
|
+
.then((r: any) => r.data),
|
|
277
|
+
|
|
278
|
+
getCableTariffs: (provider: string) =>
|
|
279
|
+
http()
|
|
280
|
+
.get<{ status: boolean; payload: FPBillTariff[] }>('/bills/cable/tariffs', {
|
|
281
|
+
params: { provider },
|
|
282
|
+
})
|
|
283
|
+
.then((r: any) => r.data),
|
|
284
|
+
|
|
285
|
+
/** Validates a smartcard/IUC number before purchase. */
|
|
286
|
+
validateSmartcard: (smartcardNumber: string, provider: string) =>
|
|
287
|
+
http()
|
|
288
|
+
.post<{ status: boolean; message: string; payload: FPMeterLookupResult }>(
|
|
289
|
+
'/bills/cable/validate-smartcard',
|
|
290
|
+
{ smartcard_number: smartcardNumber, provider }
|
|
291
|
+
)
|
|
292
|
+
.then((r: any) => r.data),
|
|
293
|
+
|
|
294
|
+
/** Separate from transferAPI.validateTransfer — bills have no transfer
|
|
295
|
+
* recipient, so this hits its own backend endpoint rather than reusing
|
|
296
|
+
* /validate-transaction's receiver_id-shaped contract. One endpoint
|
|
297
|
+
* covers all four categories — the backend doesn't need to know which
|
|
298
|
+
* category is being authorized at PIN-check time. */
|
|
299
|
+
validateBillPin: (pin: string, userId: string) =>
|
|
300
|
+
http()
|
|
301
|
+
.post<{ status: boolean; message: string; payload: { temp_id: string } }>(
|
|
302
|
+
'/bills/validate-pin',
|
|
303
|
+
{ pin, user_id: userId }
|
|
304
|
+
)
|
|
305
|
+
.then((r: any) => r.data),
|
|
306
|
+
|
|
307
|
+
purchaseAirtime: (payload: FPAirtimePurchaseRequest, tempId: string) =>
|
|
308
|
+
http()
|
|
309
|
+
.post<{ status: boolean; message: string; payload: FPBillTransaction }>(
|
|
310
|
+
'/bills/airtime',
|
|
311
|
+
{ ...payload, temp_id: tempId }
|
|
312
|
+
)
|
|
313
|
+
.then((r: any) => r.data),
|
|
314
|
+
|
|
315
|
+
purchaseData: (payload: FPDataPurchaseRequest, tempId: string) =>
|
|
316
|
+
http()
|
|
317
|
+
.post<{ status: boolean; message: string; payload: FPBillTransaction }>(
|
|
318
|
+
'/bills/data',
|
|
319
|
+
{ ...payload, temp_id: tempId }
|
|
320
|
+
)
|
|
321
|
+
.then((r: any) => r.data),
|
|
322
|
+
|
|
323
|
+
purchaseElectricity: (payload: FPElectricityPurchaseRequest, tempId: string) =>
|
|
324
|
+
http()
|
|
325
|
+
.post<{ status: boolean; message: string; payload: FPBillTransaction }>(
|
|
326
|
+
'/bills/electricity',
|
|
327
|
+
{ ...payload, temp_id: tempId }
|
|
328
|
+
)
|
|
329
|
+
.then((r: any) => r.data),
|
|
330
|
+
|
|
331
|
+
purchaseCable: (payload: FPCablePurchaseRequest, tempId: string) =>
|
|
332
|
+
http()
|
|
333
|
+
.post<{ status: boolean; message: string; payload: FPBillTransaction }>(
|
|
334
|
+
'/bills/cable',
|
|
335
|
+
{ ...payload, temp_id: tempId }
|
|
336
|
+
)
|
|
337
|
+
.then((r: any) => r.data),
|
|
338
|
+
|
|
339
|
+
/** Bill transactions live in the same agencyTransaction table as
|
|
340
|
+
* transfers, so the existing status endpoint is reused as-is. */
|
|
341
|
+
status: (reference: string) => transferAPI.status(reference),
|
|
342
|
+
};
|
package/src/core/types/index.ts
CHANGED
|
@@ -14,6 +14,11 @@ export type FPTxStatus =
|
|
|
14
14
|
| 'reversed';
|
|
15
15
|
export type FPTxType = 'debit' | 'credit';
|
|
16
16
|
|
|
17
|
+
export type MovementPayload = {
|
|
18
|
+
latitude: number;
|
|
19
|
+
longitude: number;
|
|
20
|
+
address?: string;
|
|
21
|
+
};
|
|
17
22
|
export interface FPUserInfo {
|
|
18
23
|
firstName: string;
|
|
19
24
|
lastName: string;
|
|
@@ -91,24 +96,21 @@ export interface FPSendPaymentRequest {
|
|
|
91
96
|
isBank?: boolean;
|
|
92
97
|
channel: 'transfer' | 'proximity' | 'bluetooth' | 'nfc' | 'nqr';
|
|
93
98
|
currency?: string;
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
| FPTransferRecipient
|
|
97
|
-
| FPNfcRecipient
|
|
98
|
-
| FPNqrRecipient
|
|
99
|
-
| FPWalletTransferRecipient;
|
|
99
|
+
movement?: MovementPayload;
|
|
100
|
+
recipient: any
|
|
100
101
|
}
|
|
101
102
|
|
|
102
103
|
export interface FPSendWalletPaymentRequest {
|
|
103
|
-
sender_id
|
|
104
|
-
sender_type
|
|
104
|
+
sender_id?: string;
|
|
105
|
+
sender_type?: 'USER' | 'AGENT';
|
|
105
106
|
recipient: any;
|
|
106
107
|
amount: string;
|
|
107
108
|
reference: string;
|
|
108
109
|
narration: string;
|
|
109
110
|
currency?: string;
|
|
110
|
-
isBank?:
|
|
111
|
-
channel: 'transfer' | 'proximity' | 'bluetooth';
|
|
111
|
+
isBank?: boolean;
|
|
112
|
+
channel: 'transfer' | 'proximity' | 'bluetooth' | 'nfc' | 'nqr';
|
|
113
|
+
movement?: MovementPayload;
|
|
112
114
|
}
|
|
113
115
|
|
|
114
116
|
export interface FPNfcTransfer {
|
|
@@ -211,6 +213,11 @@ export interface FPInstance {
|
|
|
211
213
|
initializeSDK: (user: FPUserInfo | null, callbacks?: FPCallbacks) => Promise<void>;
|
|
212
214
|
send: (amount: number, currency: FPCurrency) => void;
|
|
213
215
|
receive: (amount?: number, currency?: FPCurrency) => void;
|
|
216
|
+
/** Open the Bill Payment bottom sheet for a given category and amount —
|
|
217
|
+
* mirrors send(amount, currency). The amount may be adjusted inside the
|
|
218
|
+
* sheet for categories where the user picks a plan/tariff (DATA, CABLE)
|
|
219
|
+
* rather than typing a free amount (AIRTIME, ELECTRICITY). */
|
|
220
|
+
payBill: (amount: number, category: FPBillCategory) => void;
|
|
214
221
|
generateAccountNumber: (user: FPUserInfo) => Promise<FPAccount>;
|
|
215
222
|
refreshBalance: () => Promise<void>;
|
|
216
223
|
listen: () => void;
|
|
@@ -239,7 +246,7 @@ export interface Props {
|
|
|
239
246
|
currency?: FPCurrency;
|
|
240
247
|
onClose: () => void;
|
|
241
248
|
onProcessTransaction?: (
|
|
242
|
-
tx:
|
|
249
|
+
tx: FPSendWalletPaymentRequest | null
|
|
243
250
|
) => void;
|
|
244
251
|
onError?: (err: FPError) => void;
|
|
245
252
|
}
|
|
@@ -252,3 +259,179 @@ export interface ReceiveTransferScreenProps {
|
|
|
252
259
|
onClose?: () => void;
|
|
253
260
|
onPaymentReceived?: (data: any) => void;
|
|
254
261
|
}
|
|
262
|
+
|
|
263
|
+
export type SubscriptionFreq = 'DAILY' | 'WEEKLY' | 'MONTHLY' | 'YEARLY';
|
|
264
|
+
export type SubscriptionStatus = 'ACTIVE' | 'PAUSED' | 'CANCELLED';
|
|
265
|
+
export type PaymentStatus = 'PENDING' | 'SUCCESS' | 'FAILED';
|
|
266
|
+
|
|
267
|
+
export interface Subscription {
|
|
268
|
+
id: string;
|
|
269
|
+
serviceName: string;
|
|
270
|
+
serviceCategory: string;
|
|
271
|
+
amount: number;
|
|
272
|
+
currency: string;
|
|
273
|
+
frequency: SubscriptionFreq;
|
|
274
|
+
nextPaymentDate: string;
|
|
275
|
+
accountNumber: string;
|
|
276
|
+
status: SubscriptionStatus;
|
|
277
|
+
logoUrl?: string;
|
|
278
|
+
description?: string;
|
|
279
|
+
createdAt: string;
|
|
280
|
+
payments?: SubscriptionPayment[];
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
export interface SubscriptionPayment {
|
|
284
|
+
id: string;
|
|
285
|
+
amount: number;
|
|
286
|
+
status: PaymentStatus;
|
|
287
|
+
processedAt?: string;
|
|
288
|
+
failureReason?: string;
|
|
289
|
+
createdAt: string;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
export interface CreateSubscriptionPayload {
|
|
293
|
+
serviceName: string;
|
|
294
|
+
serviceCategory: string;
|
|
295
|
+
amount: number;
|
|
296
|
+
currency: string;
|
|
297
|
+
frequency: SubscriptionFreq;
|
|
298
|
+
nextPaymentDate: string;
|
|
299
|
+
accountNumber: string;
|
|
300
|
+
logoUrl?: string;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// ── Bill Payment ───────────────────────────────────────────────
|
|
304
|
+
// Added to support FPInstance.payBill(). Kept fully separate from
|
|
305
|
+
// FPTransferRecipient / FPSendPaymentRequest — bills have no recipient,
|
|
306
|
+
// so nothing here is unioned into the transfer types above.
|
|
307
|
+
//
|
|
308
|
+
// Provider routing (BuyPower today, Paga later, possibly per-category) is
|
|
309
|
+
// resolved entirely on the backend behind /bills/*. Nothing here encodes
|
|
310
|
+
// which provider services a category — the mobile layer is provider-agnostic
|
|
311
|
+
// by design, same as the rest of the SDK.
|
|
312
|
+
|
|
313
|
+
export type FPBillCategory = 'AIRTIME' | 'DATA' | 'ELECTRICITY' | 'CABLE';
|
|
314
|
+
export type FPNetworkCode = 'MTN' | 'AIRTEL' | 'GLO' | '9MOBILE';
|
|
315
|
+
export type FPMeterType = 'PREPAID' | 'POSTPAID';
|
|
316
|
+
|
|
317
|
+
export interface FPNetworkOperator {
|
|
318
|
+
code: FPNetworkCode;
|
|
319
|
+
displayName: string;
|
|
320
|
+
logoUrl: string;
|
|
321
|
+
brandColor: string;
|
|
322
|
+
isActive: boolean;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
export interface FPDataPlan {
|
|
326
|
+
id: string;
|
|
327
|
+
network: FPNetworkCode;
|
|
328
|
+
label: string; // e.g. "1.5GB"
|
|
329
|
+
validity: string; // e.g. "30 days"
|
|
330
|
+
priceInKobo: number;
|
|
331
|
+
isAvailable: boolean;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/** A disco (electricity) or cable provider, loaded dynamically — same
|
|
335
|
+
* pattern as FPNetworkOperator, just for a different bill category. */
|
|
336
|
+
export interface FPBillProvider {
|
|
337
|
+
code: string; // e.g. "BENIN", "DSTV"
|
|
338
|
+
displayName: string;
|
|
339
|
+
logoUrl?: string;
|
|
340
|
+
isActive: boolean;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/** A purchasable tariff/plan for electricity (band) or cable (bouquet).
|
|
344
|
+
* Distinct from FPDataPlan because the fields that matter differ —
|
|
345
|
+
* electricity tariffs don't have "validity", cable bouquets don't have
|
|
346
|
+
* a meter-type concept. */
|
|
347
|
+
export interface FPBillTariff {
|
|
348
|
+
code: string;
|
|
349
|
+
label: string; // e.g. "DSTV Compact", "Band A"
|
|
350
|
+
priceInKobo: number; // 0 for electricity, where the user enters amount
|
|
351
|
+
isAvailable: boolean;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/** Returned by the meter/smartcard validation lookup, before purchase —
|
|
355
|
+
* mirrors what Electricity/index.tsx's checkMeterNumber() currently
|
|
356
|
+
* resolves locally against BuyPower, now generalized for any category
|
|
357
|
+
* that requires a pre-purchase ownership check. */
|
|
358
|
+
export interface FPMeterLookupResult {
|
|
359
|
+
customerName: string;
|
|
360
|
+
minVendAmountInKobo?: number; // electricity only
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/** Generic shape ConfirmScreen/ResultScreen render — built by the caller
|
|
364
|
+
* (SendScreen builds one from `recipient`, BillsScreen builds one from
|
|
365
|
+
* network/phone/plan/meter) so neither shared component needs to know what
|
|
366
|
+
* a "recipient" or a "bill" is. */
|
|
367
|
+
export interface FPSummaryRow {
|
|
368
|
+
label: string;
|
|
369
|
+
value: string;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// Discriminated union — each category carries only the fields it actually
|
|
373
|
+
// needs, instead of one shape with a pile of optional fields where "which
|
|
374
|
+
// ones matter" depends on a category nobody can see from the type alone.
|
|
375
|
+
|
|
376
|
+
export interface FPAirtimePurchaseRequest {
|
|
377
|
+
category: 'AIRTIME';
|
|
378
|
+
phoneNumber: string;
|
|
379
|
+
network: FPNetworkCode;
|
|
380
|
+
amountInKobo: number;
|
|
381
|
+
idempotencyKey: string;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
export interface FPDataPurchaseRequest {
|
|
385
|
+
category: 'DATA';
|
|
386
|
+
phoneNumber: string;
|
|
387
|
+
network: FPNetworkCode;
|
|
388
|
+
planId: string;
|
|
389
|
+
/** The selected plan's price — included here (not just inferred from
|
|
390
|
+
* planId) so BillsScreen can read amountInKobo generically across every
|
|
391
|
+
* category for the Confirm screen's amount display, the same way
|
|
392
|
+
* AIRTIME/ELECTRICITY/CABLE do. */
|
|
393
|
+
amountInKobo: number;
|
|
394
|
+
idempotencyKey: string;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
export interface FPElectricityPurchaseRequest {
|
|
398
|
+
category: 'ELECTRICITY';
|
|
399
|
+
meterNumber: string;
|
|
400
|
+
disco: string;
|
|
401
|
+
meterType: FPMeterType;
|
|
402
|
+
amountInKobo: number;
|
|
403
|
+
forSelf: boolean;
|
|
404
|
+
phoneNumber: string;
|
|
405
|
+
email: string;
|
|
406
|
+
idempotencyKey: string;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
export interface FPCablePurchaseRequest {
|
|
410
|
+
category: 'CABLE';
|
|
411
|
+
smartcardNumber: string;
|
|
412
|
+
provider: string; // e.g. "DSTV"
|
|
413
|
+
tariffCode: string;
|
|
414
|
+
amountInKobo: number;
|
|
415
|
+
phoneNumber: string;
|
|
416
|
+
idempotencyKey: string;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
export type FPBillPurchaseRequest =
|
|
420
|
+
| FPAirtimePurchaseRequest
|
|
421
|
+
| FPDataPurchaseRequest
|
|
422
|
+
| FPElectricityPurchaseRequest
|
|
423
|
+
| FPCablePurchaseRequest;
|
|
424
|
+
|
|
425
|
+
export interface FPBillTransaction {
|
|
426
|
+
id: string;
|
|
427
|
+
reference: string;
|
|
428
|
+
category: FPBillCategory;
|
|
429
|
+
status: FPTxStatus;
|
|
430
|
+
amount: number;
|
|
431
|
+
currency: FPCurrency;
|
|
432
|
+
identifier: string;
|
|
433
|
+
network?: FPNetworkCode; // AIRTIME/DATA only
|
|
434
|
+
provider?: string; // ELECTRICITY/CABLE only
|
|
435
|
+
planLabel?: string;
|
|
436
|
+
createdAt: string;
|
|
437
|
+
}
|
package/src/engine/FPEngine.ts
CHANGED
|
@@ -22,6 +22,7 @@ import type {
|
|
|
22
22
|
FPTransferRecipient,
|
|
23
23
|
FPAccount,
|
|
24
24
|
FPBalance,
|
|
25
|
+
FPBillCategory,
|
|
25
26
|
} from '../core/types';
|
|
26
27
|
import BLEReceiverService, {
|
|
27
28
|
type BLEPaymentRequest,
|
|
@@ -55,7 +56,9 @@ type FPEventName =
|
|
|
55
56
|
| 'incoming_payment_request'
|
|
56
57
|
| 'show_send'
|
|
57
58
|
| 'show_receive'
|
|
58
|
-
| 'close_send'
|
|
59
|
+
| 'close_send'
|
|
60
|
+
| 'show_bills'
|
|
61
|
+
| 'close_bills';
|
|
59
62
|
const _listeners = new Map<FPEventName, Set<Function>>();
|
|
60
63
|
|
|
61
64
|
export function _emitEvent(event: FPEventName, data?: unknown): void {
|
|
@@ -633,6 +636,14 @@ export const FPEngine = {
|
|
|
633
636
|
_emitEvent('show_receive', { amount, currency });
|
|
634
637
|
},
|
|
635
638
|
|
|
639
|
+
closeBills(): void {
|
|
640
|
+
_emitEvent('close_bills');
|
|
641
|
+
},
|
|
642
|
+
|
|
643
|
+
showBills(amount: number, category: FPBillCategory): void {
|
|
644
|
+
_emitEvent('show_bills', { amount, category });
|
|
645
|
+
},
|
|
646
|
+
|
|
636
647
|
getUser: () => _user,
|
|
637
648
|
getAccount: () => _account,
|
|
638
649
|
isReady: () => _isReady,
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
// hooks/useLocation.ts
|
|
2
|
+
|
|
3
|
+
import { useState, useCallback } from 'react';
|
|
4
|
+
import { Platform, PermissionsAndroid, Alert } from 'react-native';
|
|
5
|
+
import Geolocation from '@react-native-community/geolocation';
|
|
6
|
+
|
|
7
|
+
export type LocationData = {
|
|
8
|
+
latitude: number;
|
|
9
|
+
longitude: number;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export type LocationState = {
|
|
13
|
+
location: LocationData | null;
|
|
14
|
+
loading: boolean;
|
|
15
|
+
error: string | null;
|
|
16
|
+
requestLocation: () => Promise<LocationData | null>;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export function useLocation(): LocationState {
|
|
20
|
+
const [location, setLocation] = useState<LocationData | null>(null);
|
|
21
|
+
const [loading, setLoading] = useState(false);
|
|
22
|
+
const [error, setError] = useState<string | null>(null);
|
|
23
|
+
|
|
24
|
+
const requestAndroidPermission = async (): Promise<boolean> => {
|
|
25
|
+
const granted = await PermissionsAndroid.request(
|
|
26
|
+
PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION,
|
|
27
|
+
{
|
|
28
|
+
title: 'Location Permission',
|
|
29
|
+
message: 'FountainPay needs your location to record where transactions are processed',
|
|
30
|
+
buttonPositive: 'Allow',
|
|
31
|
+
buttonNegative: 'Deny',
|
|
32
|
+
},
|
|
33
|
+
);
|
|
34
|
+
return granted === PermissionsAndroid.RESULTS.GRANTED;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const requestLocation = useCallback((): Promise<LocationData | null> => {
|
|
38
|
+
return new Promise(async (resolve) => {
|
|
39
|
+
setLoading(true);
|
|
40
|
+
setError(null);
|
|
41
|
+
|
|
42
|
+
// Request permission on Android
|
|
43
|
+
if (Platform.OS === 'android') {
|
|
44
|
+
const granted = await requestAndroidPermission();
|
|
45
|
+
if (!granted) {
|
|
46
|
+
setError('Location permission denied');
|
|
47
|
+
setLoading(false);
|
|
48
|
+
resolve(null);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
Geolocation.getCurrentPosition(
|
|
54
|
+
(position) => {
|
|
55
|
+
const data: LocationData = {
|
|
56
|
+
latitude: position.coords.latitude,
|
|
57
|
+
longitude: position.coords.longitude,
|
|
58
|
+
};
|
|
59
|
+
setLocation(data);
|
|
60
|
+
setLoading(false);
|
|
61
|
+
resolve(data);
|
|
62
|
+
},
|
|
63
|
+
(err) => {
|
|
64
|
+
const message = err.message || 'Could not get location';
|
|
65
|
+
setError(message);
|
|
66
|
+
setLoading(false);
|
|
67
|
+
// Resolve null — transfer can still proceed without location
|
|
68
|
+
// Python/Django will omit movement block if null
|
|
69
|
+
resolve(null);
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
enableHighAccuracy: true,
|
|
73
|
+
timeout: 8000, // 8 seconds max — never block transaction
|
|
74
|
+
maximumAge: 0, // always fresh — no cached position
|
|
75
|
+
},
|
|
76
|
+
);
|
|
77
|
+
});
|
|
78
|
+
}, []);
|
|
79
|
+
|
|
80
|
+
return { location, loading, error, requestLocation };
|
|
81
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -34,4 +34,12 @@ export type {
|
|
|
34
34
|
FPError,
|
|
35
35
|
FPTxStatus,
|
|
36
36
|
FPChannel,
|
|
37
|
-
|
|
37
|
+
FPBillCategory,
|
|
38
|
+
FPNetworkCode,
|
|
39
|
+
FPMeterType,
|
|
40
|
+
FPNetworkOperator,
|
|
41
|
+
FPDataPlan,
|
|
42
|
+
FPBillProvider,
|
|
43
|
+
FPBillTariff,
|
|
44
|
+
FPBillTransaction,
|
|
45
|
+
} from './core/types';
|