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.
Files changed (79) hide show
  1. package/lib/module/FountainPayProvider.js +5 -0
  2. package/lib/module/FountainPayProvider.js.map +1 -1
  3. package/lib/module/core/api/index.js +59 -0
  4. package/lib/module/core/api/index.js.map +1 -1
  5. package/lib/module/core/types/index.js +32 -0
  6. package/lib/module/core/types/index.js.map +1 -1
  7. package/lib/module/engine/FPEngine.js +9 -0
  8. package/lib/module/engine/FPEngine.js.map +1 -1
  9. package/lib/module/hooks/useLocation.js +66 -0
  10. package/lib/module/hooks/useLocation.js.map +1 -0
  11. package/lib/module/ui/components/ConfirmScreen.js +43 -51
  12. package/lib/module/ui/components/ConfirmScreen.js.map +1 -1
  13. package/lib/module/ui/components/RecurringToggle.js +94 -0
  14. package/lib/module/ui/components/RecurringToggle.js.map +1 -0
  15. package/lib/module/ui/modals/FPShell.js +19 -0
  16. package/lib/module/ui/modals/FPShell.js.map +1 -1
  17. package/lib/module/ui/screens/BillsScreen.js +186 -0
  18. package/lib/module/ui/screens/BillsScreen.js.map +1 -0
  19. package/lib/module/ui/screens/ResultScreen.js +113 -28
  20. package/lib/module/ui/screens/ResultScreen.js.map +1 -1
  21. package/lib/module/ui/screens/SendScreen.js +85 -16
  22. package/lib/module/ui/screens/SendScreen.js.map +1 -1
  23. package/lib/module/ui/screens/sub/billPayment/AirtimeScreen.js +257 -0
  24. package/lib/module/ui/screens/sub/billPayment/AirtimeScreen.js.map +1 -0
  25. package/lib/module/ui/screens/sub/billPayment/CableScreen.js +264 -0
  26. package/lib/module/ui/screens/sub/billPayment/CableScreen.js.map +1 -0
  27. package/lib/module/ui/screens/sub/billPayment/DataScreen.js +273 -0
  28. package/lib/module/ui/screens/sub/billPayment/DataScreen.js.map +1 -0
  29. package/lib/module/ui/screens/sub/billPayment/ElectricityScreen.js +337 -0
  30. package/lib/module/ui/screens/sub/billPayment/ElectricityScreen.js.map +1 -0
  31. package/lib/module/ui/screens/sub/sendPayment/TransferSubScreen.js +1 -1
  32. package/lib/module/ui/screens/sub/sendPayment/TransferSubScreen.js.map +1 -1
  33. package/lib/typescript/src/FountainPayProvider.d.ts.map +1 -1
  34. package/lib/typescript/src/core/api/index.d.ts +52 -63
  35. package/lib/typescript/src/core/api/index.d.ts.map +1 -1
  36. package/lib/typescript/src/core/types/index.d.ts +159 -6
  37. package/lib/typescript/src/core/types/index.d.ts.map +1 -1
  38. package/lib/typescript/src/engine/FPEngine.d.ts +4 -2
  39. package/lib/typescript/src/engine/FPEngine.d.ts.map +1 -1
  40. package/lib/typescript/src/hooks/useLocation.d.ts +12 -0
  41. package/lib/typescript/src/hooks/useLocation.d.ts.map +1 -0
  42. package/lib/typescript/src/index.d.ts +1 -1
  43. package/lib/typescript/src/index.d.ts.map +1 -1
  44. package/lib/typescript/src/ui/components/ConfirmScreen.d.ts +25 -4
  45. package/lib/typescript/src/ui/components/ConfirmScreen.d.ts.map +1 -1
  46. package/lib/typescript/src/ui/components/RecurringToggle.d.ts +7 -0
  47. package/lib/typescript/src/ui/components/RecurringToggle.d.ts.map +1 -0
  48. package/lib/typescript/src/ui/modals/FPShell.d.ts.map +1 -1
  49. package/lib/typescript/src/ui/screens/BillsScreen.d.ts +10 -0
  50. package/lib/typescript/src/ui/screens/BillsScreen.d.ts.map +1 -0
  51. package/lib/typescript/src/ui/screens/ResultScreen.d.ts +20 -3
  52. package/lib/typescript/src/ui/screens/ResultScreen.d.ts.map +1 -1
  53. package/lib/typescript/src/ui/screens/SendScreen.d.ts.map +1 -1
  54. package/lib/typescript/src/ui/screens/sub/billPayment/AirtimeScreen.d.ts +15 -0
  55. package/lib/typescript/src/ui/screens/sub/billPayment/AirtimeScreen.d.ts.map +1 -0
  56. package/lib/typescript/src/ui/screens/sub/billPayment/CableScreen.d.ts +14 -0
  57. package/lib/typescript/src/ui/screens/sub/billPayment/CableScreen.d.ts.map +1 -0
  58. package/lib/typescript/src/ui/screens/sub/billPayment/DataScreen.d.ts +14 -0
  59. package/lib/typescript/src/ui/screens/sub/billPayment/DataScreen.d.ts.map +1 -0
  60. package/lib/typescript/src/ui/screens/sub/billPayment/ElectricityScreen.d.ts +16 -0
  61. package/lib/typescript/src/ui/screens/sub/billPayment/ElectricityScreen.d.ts.map +1 -0
  62. package/package.json +2 -2
  63. package/src/FountainPayProvider.tsx +7 -0
  64. package/src/core/api/index.ts +149 -27
  65. package/src/core/types/index.ts +194 -11
  66. package/src/engine/FPEngine.ts +12 -1
  67. package/src/hooks/useLocation.ts +81 -0
  68. package/src/index.ts +9 -1
  69. package/src/ui/components/ConfirmScreen.tsx +47 -54
  70. package/src/ui/components/RecurringToggle.tsx +106 -0
  71. package/src/ui/modals/FPShell.tsx +26 -3
  72. package/src/ui/screens/BillsScreen.tsx +197 -0
  73. package/src/ui/screens/ResultScreen.tsx +129 -28
  74. package/src/ui/screens/SendScreen.tsx +124 -68
  75. package/src/ui/screens/sub/billPayment/AirtimeScreen.tsx +252 -0
  76. package/src/ui/screens/sub/billPayment/CableScreen.tsx +274 -0
  77. package/src/ui/screens/sub/billPayment/DataScreen.tsx +263 -0
  78. package/src/ui/screens/sub/billPayment/ElectricityScreen.tsx +344 -0
  79. package/src/ui/screens/sub/sendPayment/TransferSubScreen.tsx +1 -1
@@ -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
+ };
@@ -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
- recipient:
95
- | FPInternalTransferRecipient
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: string;
104
- sender_type: 'USER' | 'AGENT';
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?: false;
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: FPSendPaymentRequest | FPSendWalletPaymentRequest | null
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
+ }
@@ -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
- } from './core/types';
37
+ FPBillCategory,
38
+ FPNetworkCode,
39
+ FPMeterType,
40
+ FPNetworkOperator,
41
+ FPDataPlan,
42
+ FPBillProvider,
43
+ FPBillTariff,
44
+ FPBillTransaction,
45
+ } from './core/types';