react-native-fpay 0.4.31 → 0.4.34

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.
@@ -1,10 +1,3 @@
1
- // ─────────────────────────────────────────────
2
- // Bills Screen
3
- // Renders the category-specific sub-screen
4
- // (Airtime / Data / Electricity / Cable), then
5
- // runs the same Confirm (PIN) → Loading →
6
- // Result flow SendScreen uses, generalized.
7
- // ─────────────────────────────────────────────
8
1
  import { useState } from 'react';
9
2
  import { View, TouchableOpacity, Text } from 'react-native';
10
3
  import styled from 'styled-components/native';
@@ -18,6 +11,7 @@ import { ResultScreen } from './ResultScreen';
18
11
  import LoadingAnimation from '../components/LoadingAnimation';
19
12
  import { billsAPI } from '../../core/api';
20
13
  import { getFPStore } from '../../store/FPStore';
14
+ import { useLocation } from '../../hooks/useLocation';
21
15
  import { C, F, S } from '../theme';
22
16
  import type {
23
17
  FPBillCategory,
@@ -28,6 +22,7 @@ import type {
28
22
  FPUserInfo,
29
23
  FPSummaryRow,
30
24
  } from '../../core/types';
25
+ import type { FPBillPurchaseContext } from '../../core/api';
31
26
 
32
27
  const Container = styled(View)`
33
28
  flex: 1;
@@ -72,14 +67,29 @@ interface Props extends Pick<FPCallbacks, 'onError'> {
72
67
 
73
68
  type FlowStage = 'FORM' | 'CONFIRM' | 'RESULT';
74
69
 
75
- export default function BillsScreen({ category, amount, user, onClose, onError }: Props) {
70
+ export default function BillsScreen({
71
+ category,
72
+ amount,
73
+ user,
74
+ onClose,
75
+ onError,
76
+ }: Props) {
76
77
  const [stage, setStage] = useState<FlowStage>('FORM');
77
78
  const [loading, setLoading] = useState(false);
78
- const [pendingPayload, setPendingPayload] = useState<FPBillPurchaseRequest | null>(null);
79
- const [pendingSummaryRows, setPendingSummaryRows] = useState<FPSummaryRow[]>([]);
80
- const [completedTx, setCompletedTx] = useState<FPBillTransaction | null>(null);
79
+ const [pendingPayload, setPendingPayload] =
80
+ useState<FPBillPurchaseRequest | null>(null);
81
+ const [pendingSummaryRows, setPendingSummaryRows] = useState<FPSummaryRow[]>(
82
+ []
83
+ );
84
+ const [completedTx, setCompletedTx] = useState<FPBillTransaction | null>(
85
+ null
86
+ );
87
+ const { requestLocation } = useLocation();
81
88
 
82
- const handleFormContinue = (payload: FPBillPurchaseRequest, summaryRows: FPSummaryRow[]) => {
89
+ const handleFormContinue = (
90
+ payload: FPBillPurchaseRequest,
91
+ summaryRows: FPSummaryRow[]
92
+ ) => {
83
93
  setPendingPayload(payload);
84
94
  setPendingSummaryRows(summaryRows);
85
95
  setStage('CONFIRM');
@@ -95,26 +105,59 @@ export default function BillsScreen({ category, amount, user, onClose, onError }
95
105
  if (!pendingPayload) return;
96
106
  setLoading(true);
97
107
  try {
108
+ const isTerminalTransaction = !!user?.terminalId;
109
+ let geoLocation: { latitude: number; longitude: number } | undefined;
110
+ if (isTerminalTransaction) {
111
+ const loc = await requestLocation();
112
+ if (loc) {
113
+ geoLocation = { latitude: loc.latitude, longitude: loc.longitude };
114
+ }
115
+ }
116
+
117
+ const ctx: FPBillPurchaseContext = {
118
+ agentId: getFPStore().psspId || '',
119
+ terminalId: user?.terminalId,
120
+ geoLocation,
121
+ currency: 'NGN',
122
+ tnxRef: `bill_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
123
+ forSelf:
124
+ pendingPayload.category === 'ELECTRICITY'
125
+ ? pendingPayload.forSelf
126
+ : true,
127
+ agentName: user ? `${user.firstName} ${user.lastName}` : undefined,
128
+ agentEmail: user?.email,
129
+ agentPhone: user?.phone,
130
+ };
131
+
98
132
  let response;
99
133
  switch (pendingPayload.category) {
100
134
  case 'AIRTIME':
101
- response = await billsAPI.purchaseAirtime(pendingPayload, tempId);
135
+ response = await billsAPI.purchaseAirtime(
136
+ pendingPayload,
137
+ ctx,
138
+ tempId
139
+ );
102
140
  break;
103
141
  case 'DATA':
104
- response = await billsAPI.purchaseData(pendingPayload, tempId);
142
+ response = await billsAPI.purchaseData(pendingPayload, ctx, tempId);
105
143
  break;
106
144
  case 'ELECTRICITY':
107
- response = await billsAPI.purchaseElectricity(pendingPayload, tempId);
145
+ response = await billsAPI.purchaseElectricity(
146
+ pendingPayload,
147
+ ctx,
148
+ tempId
149
+ );
108
150
  break;
109
151
  case 'CABLE':
110
- response = await billsAPI.purchaseCable(pendingPayload, tempId);
152
+ response = await billsAPI.purchaseCable(pendingPayload, ctx, tempId);
111
153
  break;
112
154
  }
113
155
 
114
156
  if (!response?.status) {
115
- onError?.({ code: 'PURCHASE_FAILED', message: response?.message ?? 'Purchase failed' } as FPError);
116
- // Still show the result screen — ResultScreen polls status itself
117
- // and renders the failed state, same pattern SendScreen uses.
157
+ onError?.({
158
+ code: 'PURCHASE_FAILED',
159
+ message: response?.message ?? 'Purchase failed',
160
+ } as FPError);
118
161
  }
119
162
  setCompletedTx(response?.payload ?? null);
120
163
  setStage('RESULT');
@@ -128,9 +171,20 @@ export default function BillsScreen({ category, amount, user, onClose, onError }
128
171
  const renderForm = () => {
129
172
  switch (category) {
130
173
  case 'AIRTIME':
131
- return <AirtimeSubScreen amount={amount} onProcessTransaction={handleFormContinue} onError={onError} />;
174
+ return (
175
+ <AirtimeSubScreen
176
+ amount={amount}
177
+ onProcessTransaction={handleFormContinue}
178
+ onError={onError}
179
+ />
180
+ );
132
181
  case 'DATA':
133
- return <DataSubScreen onProcessTransaction={handleFormContinue} onError={onError} />;
182
+ return (
183
+ <DataSubScreen
184
+ onProcessTransaction={handleFormContinue}
185
+ onError={onError}
186
+ />
187
+ );
134
188
  case 'ELECTRICITY':
135
189
  return (
136
190
  <ElectricitySubScreen
@@ -141,7 +195,12 @@ export default function BillsScreen({ category, amount, user, onClose, onError }
141
195
  />
142
196
  );
143
197
  case 'CABLE':
144
- return <CableSubScreen onProcessTransaction={handleFormContinue} onError={onError} />;
198
+ return (
199
+ <CableSubScreen
200
+ onProcessTransaction={handleFormContinue}
201
+ onError={onError}
202
+ />
203
+ );
145
204
  }
146
205
  };
147
206
 
@@ -154,7 +213,6 @@ export default function BillsScreen({ category, amount, user, onClose, onError }
154
213
  statusFetcher={billsAPI.status}
155
214
  onClose={handleResultClose}
156
215
  />
157
-
158
216
  );
159
217
  }
160
218
 
@@ -187,11 +245,13 @@ export default function BillsScreen({ category, amount, user, onClose, onError }
187
245
  currency="₦"
188
246
  subtitle="Double check the details before you proceed. Please note that successful bill payments cannot be reversed."
189
247
  summaryRows={pendingSummaryRows}
190
- validate={(pin: string) => billsAPI.validateBillPin(pin, getFPStore().psspId || '')}
248
+ validate={(pin: string) =>
249
+ billsAPI.validateBillPin(pin, getFPStore().psspId || '')
250
+ }
191
251
  onContinue={handlePinAuthorized}
192
252
  />
193
253
  </>
194
254
  )}
195
255
  </Container>
196
256
  );
197
- }
257
+ }
@@ -1,5 +1,6 @@
1
1
  import { Text, TouchableOpacity } from 'react-native';
2
2
  import styled from 'styled-components/native';
3
+ import { C } from '../theme';
3
4
 
4
5
  export const ButtonContainer = styled.View`
5
6
  width: 100%;
@@ -84,7 +85,7 @@ export const AccountText = styled.Text`
84
85
  `;
85
86
 
86
87
  export const Label = styled.Text`
87
- color: #fff;
88
+ color: ${C.muted};
88
89
  font-size: 14px;
89
90
  font-weight: 600;
90
91
  margin-bottom: 12px;
@@ -33,12 +33,13 @@ import {
33
33
  StyledTextInput,
34
34
  } from '../../styles';
35
35
  import { getFPStore } from '../../../../store/FPStore';
36
+ import { C } from '../../../theme';
36
37
 
37
38
  const { height } = Dimensions.get('window');
38
39
 
39
40
  const Container = styled(View)`
40
41
  flex: 1;
41
- background-color: #000000;
42
+ background-color: ${C.white};
42
43
  `;
43
44
 
44
45
  const FormContainer = styled(View)`
@@ -66,7 +67,7 @@ const Header = styled(View)`
66
67
 
67
68
  const HeaderButton = styled(TouchableOpacity)`
68
69
  padding: 8px;
69
- background-color: #fff;
70
+ background-color: ${C.surface};
70
71
  width: 40px;
71
72
  height: 40px;
72
73
  border-radius: 50%;
@@ -165,7 +166,7 @@ const SwitchGroup = styled(View)`
165
166
 
166
167
  const SwitchLabel = styled(Text)`
167
168
  font-size: 14px;
168
- color: #fff;
169
+ color: ${C.muted};
169
170
  margin-left: 12px;
170
171
  `;
171
172
 
@@ -308,14 +309,14 @@ export function TransferSubScreen({
308
309
  {/* Header */}
309
310
  <Header>
310
311
  <HeaderButton onPress={onClose}>
311
- <Ionicons name="close" size={24} />
312
+ <Ionicons name="close" size={24} color={C.ink} />
312
313
  </HeaderButton>
313
314
 
314
315
  <HeaderCenter>
315
- <Text style={{ fontWeight: 'bold', fontSize: 16, color: '#FFF' }}>
316
+ <Text style={{ fontWeight: 'bold', fontSize: 16, color: C.ink }}>
316
317
  Bank
317
318
  </Text>
318
- <Text style={{ fontSize: 10, color: '#FFF' }}>Transfer</Text>
319
+ <Text style={{ fontSize: 10, color: C.ink }}>Transfer</Text>
319
320
  </HeaderCenter>
320
321
 
321
322
  <HeaderRight />
@@ -323,11 +324,11 @@ export function TransferSubScreen({
323
324
  <FormContainer>
324
325
  <InputContainer>
325
326
  <SwitchGroup>
326
- <Label>Account Details</Label>
327
+ <Label>{isBank ? 'Bank Account Number' : 'Wallet Number'}</Label>
327
328
  <ListGroup>
328
329
  <SwitchLabel>To Banks</SwitchLabel>
329
330
  <Switch
330
- trackColor={{ false: '#767577', true: '#FFF' }}
331
+ trackColor={{ false: '#767577', true: C.ink }}
331
332
  thumbColor={isBank ? '#003333' : '#f4f3f4'}
332
333
  ios_backgroundColor="#3e3e3e"
333
334
  value={isBank}