react-native-fpay 0.4.33 → 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;
@@ -39,7 +34,6 @@ const Header = styled(View)`
39
34
  align-items: center;
40
35
  justify-content: space-between;
41
36
  padding: ${S.md}px ${S.lg}px;
42
- margin-top: 30px;
43
37
  `;
44
38
 
45
39
  const HeaderTitle = styled(Text)`
@@ -73,14 +67,29 @@ interface Props extends Pick<FPCallbacks, 'onError'> {
73
67
 
74
68
  type FlowStage = 'FORM' | 'CONFIRM' | 'RESULT';
75
69
 
76
- 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) {
77
77
  const [stage, setStage] = useState<FlowStage>('FORM');
78
78
  const [loading, setLoading] = useState(false);
79
- const [pendingPayload, setPendingPayload] = useState<FPBillPurchaseRequest | null>(null);
80
- const [pendingSummaryRows, setPendingSummaryRows] = useState<FPSummaryRow[]>([]);
81
- 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();
82
88
 
83
- const handleFormContinue = (payload: FPBillPurchaseRequest, summaryRows: FPSummaryRow[]) => {
89
+ const handleFormContinue = (
90
+ payload: FPBillPurchaseRequest,
91
+ summaryRows: FPSummaryRow[]
92
+ ) => {
84
93
  setPendingPayload(payload);
85
94
  setPendingSummaryRows(summaryRows);
86
95
  setStage('CONFIRM');
@@ -96,26 +105,59 @@ export default function BillsScreen({ category, amount, user, onClose, onError }
96
105
  if (!pendingPayload) return;
97
106
  setLoading(true);
98
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
+
99
132
  let response;
100
133
  switch (pendingPayload.category) {
101
134
  case 'AIRTIME':
102
- response = await billsAPI.purchaseAirtime(pendingPayload, tempId);
135
+ response = await billsAPI.purchaseAirtime(
136
+ pendingPayload,
137
+ ctx,
138
+ tempId
139
+ );
103
140
  break;
104
141
  case 'DATA':
105
- response = await billsAPI.purchaseData(pendingPayload, tempId);
142
+ response = await billsAPI.purchaseData(pendingPayload, ctx, tempId);
106
143
  break;
107
144
  case 'ELECTRICITY':
108
- response = await billsAPI.purchaseElectricity(pendingPayload, tempId);
145
+ response = await billsAPI.purchaseElectricity(
146
+ pendingPayload,
147
+ ctx,
148
+ tempId
149
+ );
109
150
  break;
110
151
  case 'CABLE':
111
- response = await billsAPI.purchaseCable(pendingPayload, tempId);
152
+ response = await billsAPI.purchaseCable(pendingPayload, ctx, tempId);
112
153
  break;
113
154
  }
114
155
 
115
156
  if (!response?.status) {
116
- onError?.({ code: 'PURCHASE_FAILED', message: response?.message ?? 'Purchase failed' } as FPError);
117
- // Still show the result screen — ResultScreen polls status itself
118
- // and renders the failed state, same pattern SendScreen uses.
157
+ onError?.({
158
+ code: 'PURCHASE_FAILED',
159
+ message: response?.message ?? 'Purchase failed',
160
+ } as FPError);
119
161
  }
120
162
  setCompletedTx(response?.payload ?? null);
121
163
  setStage('RESULT');
@@ -129,9 +171,20 @@ export default function BillsScreen({ category, amount, user, onClose, onError }
129
171
  const renderForm = () => {
130
172
  switch (category) {
131
173
  case 'AIRTIME':
132
- return <AirtimeSubScreen amount={amount} onProcessTransaction={handleFormContinue} onError={onError} />;
174
+ return (
175
+ <AirtimeSubScreen
176
+ amount={amount}
177
+ onProcessTransaction={handleFormContinue}
178
+ onError={onError}
179
+ />
180
+ );
133
181
  case 'DATA':
134
- return <DataSubScreen onProcessTransaction={handleFormContinue} onError={onError} />;
182
+ return (
183
+ <DataSubScreen
184
+ onProcessTransaction={handleFormContinue}
185
+ onError={onError}
186
+ />
187
+ );
135
188
  case 'ELECTRICITY':
136
189
  return (
137
190
  <ElectricitySubScreen
@@ -142,7 +195,12 @@ export default function BillsScreen({ category, amount, user, onClose, onError }
142
195
  />
143
196
  );
144
197
  case 'CABLE':
145
- return <CableSubScreen onProcessTransaction={handleFormContinue} onError={onError} />;
198
+ return (
199
+ <CableSubScreen
200
+ onProcessTransaction={handleFormContinue}
201
+ onError={onError}
202
+ />
203
+ );
146
204
  }
147
205
  };
148
206
 
@@ -155,7 +213,6 @@ export default function BillsScreen({ category, amount, user, onClose, onError }
155
213
  statusFetcher={billsAPI.status}
156
214
  onClose={handleResultClose}
157
215
  />
158
-
159
216
  );
160
217
  }
161
218
 
@@ -188,11 +245,13 @@ export default function BillsScreen({ category, amount, user, onClose, onError }
188
245
  currency="₦"
189
246
  subtitle="Double check the details before you proceed. Please note that successful bill payments cannot be reversed."
190
247
  summaryRows={pendingSummaryRows}
191
- validate={(pin: string) => billsAPI.validateBillPin(pin, getFPStore().psspId || '')}
248
+ validate={(pin: string) =>
249
+ billsAPI.validateBillPin(pin, getFPStore().psspId || '')
250
+ }
192
251
  onContinue={handlePinAuthorized}
193
252
  />
194
253
  </>
195
254
  )}
196
255
  </Container>
197
256
  );
198
- }
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}