@heyama/pay-react-native 1.0.4 → 1.0.5
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/assets/logos/airtel.png +0 -0
- package/assets/logos/at.png +0 -0
- package/assets/logos/telecel.png +0 -0
- package/assets/logos/tnm.png +0 -0
- package/assets/logos/vodacom.png +0 -0
- package/dist/checkout-core/hooks/use-checkout-controller.js +11 -5
- package/dist/checkout-core/messages/en.d.ts +17 -6
- package/dist/checkout-core/messages/en.js +18 -7
- package/dist/checkout-core/messages/fr.js +18 -7
- package/dist/checkout-core/models/checkout.constants.d.ts +3 -3
- package/dist/checkout-core/models/checkout.constants.js +25 -2
- package/dist/checkout-core/models/checkout.enum.d.ts +3 -1
- package/dist/checkout-core/models/checkout.enum.js +2 -0
- package/dist/checkout-core/models/checkout.interface.d.ts +39 -2
- package/dist/checkout-core/models/checkout.view.utils.d.ts +24 -3
- package/dist/checkout-core/models/checkout.view.utils.js +47 -2
- package/dist/checkout-core/models/domain.enum.d.ts +21 -1
- package/dist/checkout-core/models/domain.enum.js +20 -0
- package/dist/components/SheetDial.d.ts +7 -0
- package/dist/components/SheetDial.js +36 -0
- package/dist/components/SheetSimulator.js +2 -2
- package/dist/components/SheetWaiting.d.ts +2 -2
- package/dist/components/SheetWaiting.js +12 -11
- package/dist/models/sheet.constants.js +10 -0
- package/dist/models/sheet.utils.d.ts +0 -5
- package/dist/models/sheet.utils.js +0 -9
- package/package.json +1 -1
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
2
|
-
import { useFormatter, useTranslations } from 'use-intl';
|
|
2
|
+
import { useFormatter, useLocale, useTranslations } from 'use-intl';
|
|
3
3
|
import { COPIED_MS, FLOW_TRAITS, OTP_MAX_LENGTH, OTP_PATTERN, } from "../models/checkout.constants.js";
|
|
4
4
|
import { CardFieldsMessageEnum, CheckoutScreenEnum, CheckoutTabEnum, CheckoutToastToneEnum, MethodFlowEnum, MethodStateEnum, PayRefusalEnum, } from "../models/checkout.enum.js";
|
|
5
5
|
import { CountryEnum } from "../models/domain.enum.js";
|
|
6
6
|
import { cardLabel } from "../models/checkout.card.utils.js";
|
|
7
7
|
import { formatMoney } from "../models/checkout.money.utils.js";
|
|
8
8
|
import { maskE164 } from "../models/checkout.phone.utils.js";
|
|
9
|
-
import { countriesOf, deriveCheckout, initialUi, logoOf, pickedPatch, screenOf, waitLeftOf, } from "../models/checkout.view.utils.js";
|
|
9
|
+
import { countriesOf, deriveCheckout, dialHelpOf, initialUi, logoOf, pickedPatch, screenOf, waitLeftOf, } from "../models/checkout.view.utils.js";
|
|
10
10
|
import { useNow, useReturnCountdown, useToast } from "./use-checkout-timers.js";
|
|
11
11
|
import { usePhoneNumber } from "./use-phone-number.js";
|
|
12
12
|
/** Before the payment has loaded, or for a link that matches none. */
|
|
@@ -34,6 +34,7 @@ const NO_PAYMENT_UI = {
|
|
|
34
34
|
export function useCheckoutController(view, { clues, mutations, platform }) {
|
|
35
35
|
const t = useTranslations('checkout');
|
|
36
36
|
const format = useFormatter();
|
|
37
|
+
const lang = useLocale();
|
|
37
38
|
const [patch, setPatch] = useState({});
|
|
38
39
|
const [helpOpen, setHelpOpen] = useState(false);
|
|
39
40
|
const [copied, setCopied] = useState(false);
|
|
@@ -302,12 +303,16 @@ export function useCheckoutController(view, { clues, mutations, platform }) {
|
|
|
302
303
|
pickerOpen: !suggestion,
|
|
303
304
|
});
|
|
304
305
|
},
|
|
305
|
-
|
|
306
|
+
// Only offered on a waiting attempt's steps.
|
|
307
|
+
cannotDialCode: (code) => {
|
|
308
|
+
toast.show(t('toast.dial', { code, method: attempt.methodName }), CheckoutToastToneEnum.INFO, logoOf(attempt));
|
|
309
|
+
},
|
|
310
|
+
sendAgain: () => {
|
|
306
311
|
void mutations
|
|
307
312
|
.resend({ paymentId: view.id })
|
|
308
313
|
.then((result) => result.ok
|
|
309
|
-
? toast.show(t('toast.
|
|
310
|
-
: toast.show(t('toast.
|
|
314
|
+
? toast.show(t('toast.sent_again', { phone: maskE164(attempt.phone) }), CheckoutToastToneEnum.SUCCESS)
|
|
315
|
+
: toast.show(t('toast.send_again_wait'), CheckoutToastToneEnum.INFO))
|
|
311
316
|
.catch(() => toast.show(t('toast.failed'), CheckoutToastToneEnum.WARNING));
|
|
312
317
|
},
|
|
313
318
|
cancel: () => {
|
|
@@ -340,6 +345,7 @@ export function useCheckoutController(view, { clues, mutations, platform }) {
|
|
|
340
345
|
derived,
|
|
341
346
|
phone,
|
|
342
347
|
waitLeft,
|
|
348
|
+
dialHelp: dialHelpOf(attempt, lang),
|
|
343
349
|
returnLeft: countdown.left,
|
|
344
350
|
helpOpen,
|
|
345
351
|
toast: toast.toast,
|
|
@@ -59,6 +59,14 @@ declare const _default: {
|
|
|
59
59
|
bj: string;
|
|
60
60
|
tg: string;
|
|
61
61
|
bf: string;
|
|
62
|
+
cd: string;
|
|
63
|
+
ga: string;
|
|
64
|
+
gh: string;
|
|
65
|
+
mw: string;
|
|
66
|
+
rw: string;
|
|
67
|
+
sl: string;
|
|
68
|
+
ug: string;
|
|
69
|
+
zm: string;
|
|
62
70
|
intl: string;
|
|
63
71
|
};
|
|
64
72
|
country: string;
|
|
@@ -73,15 +81,17 @@ declare const _default: {
|
|
|
73
81
|
loading: string;
|
|
74
82
|
confirm_title: string;
|
|
75
83
|
sent_to: string;
|
|
84
|
+
manual_to: string;
|
|
76
85
|
remaining: string;
|
|
77
86
|
steps: {
|
|
78
87
|
first: string;
|
|
79
88
|
last: string;
|
|
80
89
|
};
|
|
81
90
|
no_prompt: string;
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
91
|
+
send_again_hint: string;
|
|
92
|
+
dial_pending: string;
|
|
93
|
+
send_again: string;
|
|
94
|
+
send_again_in: string;
|
|
85
95
|
cancel: string;
|
|
86
96
|
checking_title: string;
|
|
87
97
|
checking_with: string;
|
|
@@ -100,6 +110,7 @@ declare const _default: {
|
|
|
100
110
|
wrong_amount: string;
|
|
101
111
|
approved_late: string;
|
|
102
112
|
reversed: string;
|
|
113
|
+
approved_all: string;
|
|
103
114
|
};
|
|
104
115
|
failure: {
|
|
105
116
|
declined: {
|
|
@@ -196,7 +207,7 @@ declare const _default: {
|
|
|
196
207
|
approve_title: string;
|
|
197
208
|
approve_body: string;
|
|
198
209
|
expires_in: string;
|
|
199
|
-
|
|
210
|
+
manual_body: string;
|
|
200
211
|
done_in: string;
|
|
201
212
|
close_title: string;
|
|
202
213
|
close_body: string;
|
|
@@ -237,8 +248,8 @@ declare const _default: {
|
|
|
237
248
|
soon: string;
|
|
238
249
|
no_answer: string;
|
|
239
250
|
failed: string;
|
|
240
|
-
|
|
241
|
-
|
|
251
|
+
sent_again: string;
|
|
252
|
+
send_again_wait: string;
|
|
242
253
|
preview: string;
|
|
243
254
|
dial: string;
|
|
244
255
|
};
|
|
@@ -50,6 +50,14 @@ export default {
|
|
|
50
50
|
bj: 'Benin',
|
|
51
51
|
tg: 'Togo',
|
|
52
52
|
bf: 'Burkina Faso',
|
|
53
|
+
cd: 'DR Congo',
|
|
54
|
+
ga: 'Gabon',
|
|
55
|
+
gh: 'Ghana',
|
|
56
|
+
mw: 'Malawi',
|
|
57
|
+
rw: 'Rwanda',
|
|
58
|
+
sl: 'Sierra Leone',
|
|
59
|
+
ug: 'Uganda',
|
|
60
|
+
zm: 'Zambia',
|
|
53
61
|
intl: 'International',
|
|
54
62
|
},
|
|
55
63
|
country: 'Country',
|
|
@@ -64,15 +72,17 @@ export default {
|
|
|
64
72
|
loading: 'Loading the payment',
|
|
65
73
|
confirm_title: 'Confirm the payment on your phone',
|
|
66
74
|
sent_to: 'We sent a {method} request to',
|
|
75
|
+
manual_to: 'Approve your {method} payment on',
|
|
67
76
|
remaining: 'left to approve',
|
|
68
77
|
steps: {
|
|
69
78
|
first: 'Unlock your phone and open the request',
|
|
70
79
|
last: 'Enter your PIN to approve',
|
|
71
80
|
},
|
|
72
81
|
no_prompt: "Didn't get the request?",
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
82
|
+
send_again_hint: 'Send it again to your phone',
|
|
83
|
+
dial_pending: 'Dial this code to see pending payments',
|
|
84
|
+
send_again: 'Send again',
|
|
85
|
+
send_again_in: 'Send again in {clock}',
|
|
76
86
|
cancel: 'Cancel payment',
|
|
77
87
|
checking_title: 'Confirming your payment',
|
|
78
88
|
checking_with: '{method} is checking your code for',
|
|
@@ -91,6 +101,7 @@ export default {
|
|
|
91
101
|
wrong_amount: 'Approve a wrong amount',
|
|
92
102
|
approved_late: 'Approve late',
|
|
93
103
|
reversed: 'Approve, then reverse',
|
|
104
|
+
approved_all: 'Approve every request',
|
|
94
105
|
},
|
|
95
106
|
failure: {
|
|
96
107
|
declined: {
|
|
@@ -187,7 +198,7 @@ export default {
|
|
|
187
198
|
approve_title: 'Approve on your phone',
|
|
188
199
|
approve_body: 'Enter your {method} PIN in the request sent to {phone}.',
|
|
189
200
|
expires_in: 'Expires in',
|
|
190
|
-
|
|
201
|
+
manual_body: 'Approve your {method} payment on {phone}.',
|
|
191
202
|
done_in: 'Done ({seconds})',
|
|
192
203
|
close_title: 'Close the payment?',
|
|
193
204
|
close_body: 'The request is still on your phone. If you approve it, the payment goes through.',
|
|
@@ -197,7 +208,7 @@ export default {
|
|
|
197
208
|
faqs: {
|
|
198
209
|
not_received: {
|
|
199
210
|
q: "I didn't get the request on my phone",
|
|
200
|
-
a: 'Check that your phone is on and has network
|
|
211
|
+
a: 'Check that your phone is on and has network, then send it again from the waiting screen. After the last request, that screen shows what to dial if your network has a code.',
|
|
201
212
|
},
|
|
202
213
|
debited: {
|
|
203
214
|
q: 'I was debited but the page says failed',
|
|
@@ -228,8 +239,8 @@ export default {
|
|
|
228
239
|
soon: '{method} is coming soon',
|
|
229
240
|
no_answer: 'No answer yet: try again or pick another method',
|
|
230
241
|
failed: 'Something went wrong. Try again.',
|
|
231
|
-
|
|
232
|
-
|
|
242
|
+
sent_again: 'Request sent again to {phone}',
|
|
243
|
+
send_again_wait: 'Wait a few seconds before asking again.',
|
|
233
244
|
preview: 'Preview only: nothing is paid here',
|
|
234
245
|
dial: 'Dial {code} from your {method} phone.',
|
|
235
246
|
},
|
|
@@ -51,6 +51,14 @@ const messages = {
|
|
|
51
51
|
bj: 'Bénin',
|
|
52
52
|
tg: 'Togo',
|
|
53
53
|
bf: 'Burkina Faso',
|
|
54
|
+
cd: 'RD Congo',
|
|
55
|
+
ga: 'Gabon',
|
|
56
|
+
gh: 'Ghana',
|
|
57
|
+
mw: 'Malawi',
|
|
58
|
+
rw: 'Rwanda',
|
|
59
|
+
sl: 'Sierra Leone',
|
|
60
|
+
ug: 'Ouganda',
|
|
61
|
+
zm: 'Zambie',
|
|
54
62
|
intl: 'International',
|
|
55
63
|
},
|
|
56
64
|
country: 'Pays',
|
|
@@ -65,15 +73,17 @@ const messages = {
|
|
|
65
73
|
loading: 'Chargement du paiement',
|
|
66
74
|
confirm_title: 'Confirmez le paiement sur votre téléphone',
|
|
67
75
|
sent_to: 'Demande {method} envoyée au',
|
|
76
|
+
manual_to: 'Validez votre paiement {method} sur le',
|
|
68
77
|
remaining: 'pour valider',
|
|
69
78
|
steps: {
|
|
70
79
|
first: 'Déverrouillez votre téléphone et ouvrez la demande',
|
|
71
80
|
last: 'Saisissez votre code secret pour valider',
|
|
72
81
|
},
|
|
73
82
|
no_prompt: 'Pas reçu la demande ?',
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
83
|
+
send_again_hint: 'Renvoyez-la sur votre téléphone',
|
|
84
|
+
dial_pending: 'Composez ce code pour voir les paiements en attente',
|
|
85
|
+
send_again: 'Renvoyer',
|
|
86
|
+
send_again_in: 'Renvoyer dans {clock}',
|
|
77
87
|
cancel: 'Annuler le paiement',
|
|
78
88
|
checking_title: 'Confirmation du paiement',
|
|
79
89
|
checking_with: '{method} vérifie votre code pour le',
|
|
@@ -92,6 +102,7 @@ const messages = {
|
|
|
92
102
|
wrong_amount: 'Valider un mauvais montant',
|
|
93
103
|
approved_late: 'Valider en retard',
|
|
94
104
|
reversed: 'Valider puis annuler',
|
|
105
|
+
approved_all: 'Valider toutes les demandes',
|
|
95
106
|
},
|
|
96
107
|
failure: {
|
|
97
108
|
declined: {
|
|
@@ -188,7 +199,7 @@ const messages = {
|
|
|
188
199
|
approve_title: 'Approuvez sur votre téléphone',
|
|
189
200
|
approve_body: 'Composez votre code secret {method} dans la fenêtre reçue au {phone}.',
|
|
190
201
|
expires_in: 'Expire dans',
|
|
191
|
-
|
|
202
|
+
manual_body: 'Validez votre paiement {method} sur le {phone}.',
|
|
192
203
|
done_in: 'Terminé ({seconds})',
|
|
193
204
|
close_title: 'Fermer le paiement ?',
|
|
194
205
|
close_body: 'La demande est toujours sur votre téléphone. Si vous la validez, le paiement passe.',
|
|
@@ -198,7 +209,7 @@ const messages = {
|
|
|
198
209
|
faqs: {
|
|
199
210
|
not_received: {
|
|
200
211
|
q: "Je n'ai pas reçu la demande",
|
|
201
|
-
a: "Vérifiez que votre téléphone est allumé et capte le réseau
|
|
212
|
+
a: "Vérifiez que votre téléphone est allumé et capte le réseau, puis renvoyez-la depuis l'écran d'attente. Après la dernière demande, cet écran indique quoi composer si votre réseau a un code.",
|
|
202
213
|
},
|
|
203
214
|
debited: {
|
|
204
215
|
q: "J'ai été débité mais la page indique un échec",
|
|
@@ -229,8 +240,8 @@ const messages = {
|
|
|
229
240
|
soon: '{method} arrive bientôt',
|
|
230
241
|
no_answer: 'Pas encore de réponse : réessayez ou choisissez un autre moyen',
|
|
231
242
|
failed: 'Une erreur est survenue. Réessayez.',
|
|
232
|
-
|
|
233
|
-
|
|
243
|
+
sent_again: 'Demande renvoyée au {phone}',
|
|
244
|
+
send_again_wait: 'Patientez quelques secondes avant de la renvoyer.',
|
|
234
245
|
preview: 'Aperçu seulement : rien n’est payé ici',
|
|
235
246
|
dial: 'Composez {code} depuis votre téléphone {method}.',
|
|
236
247
|
},
|
|
@@ -11,8 +11,8 @@ export declare const TOAST_MS = 2400;
|
|
|
11
11
|
export declare const COPIED_MS = 1800;
|
|
12
12
|
/** The last seconds of the waiting clock turn red. */
|
|
13
13
|
export declare const URGENT_SECONDS = 30;
|
|
14
|
-
/** A prompt can be sent again this long after the last one (the backend's `
|
|
15
|
-
export declare const
|
|
14
|
+
/** A prompt can be sent again this long after the last one (the backend's `SEND_AGAIN_AFTER_MS`). */
|
|
15
|
+
export declare const SEND_AGAIN_SECONDS = 30;
|
|
16
16
|
/** The card fields' bones until their page says how tall it is, on the web and in the sheet. */
|
|
17
17
|
export declare const CARD_FIELDS_HEIGHT = 190;
|
|
18
18
|
/** The only sides a brand tile is drawn at, in pixels: beside a line of text, in a list row, on the waiting screen. */
|
|
@@ -35,7 +35,7 @@ export declare const MEDIA_LINE = "rgb(120 124 132 / 0.35)";
|
|
|
35
35
|
*/
|
|
36
36
|
export declare const CHECKOUT_COLORS: Record<CheckoutThemeEnum, CheckoutColors>;
|
|
37
37
|
/** International dial code of each Mobile Money country. */
|
|
38
|
-
export declare const DIAL_CODES:
|
|
38
|
+
export declare const DIAL_CODES: Record<Exclude<CountryEnum, CountryEnum.INTERNATIONAL>, string>;
|
|
39
39
|
/** Greyed on the Card tab until a provider for them is coded. */
|
|
40
40
|
export declare const COMING_SOON_WALLETS: Array<ComingSoonWallet>;
|
|
41
41
|
/** The card tile: a generic card, whatever networks the provider takes. */
|
|
@@ -10,8 +10,8 @@ export const TOAST_MS = 2400;
|
|
|
10
10
|
export const COPIED_MS = 1800;
|
|
11
11
|
/** The last seconds of the waiting clock turn red. */
|
|
12
12
|
export const URGENT_SECONDS = 30;
|
|
13
|
-
/** A prompt can be sent again this long after the last one (the backend's `
|
|
14
|
-
export const
|
|
13
|
+
/** A prompt can be sent again this long after the last one (the backend's `SEND_AGAIN_AFTER_MS`). */
|
|
14
|
+
export const SEND_AGAIN_SECONDS = 30;
|
|
15
15
|
/** The card fields' bones until their page says how tall it is, on the web and in the sheet. */
|
|
16
16
|
export const CARD_FIELDS_HEIGHT = 190;
|
|
17
17
|
/** The only sides a brand tile is drawn at, in pixels: beside a line of text, in a list row, on the waiting screen. */
|
|
@@ -86,6 +86,14 @@ export const DIAL_CODES = {
|
|
|
86
86
|
[CountryEnum.BENIN]: '229',
|
|
87
87
|
[CountryEnum.TOGO]: '228',
|
|
88
88
|
[CountryEnum.BURKINA_FASO]: '226',
|
|
89
|
+
[CountryEnum.DR_CONGO]: '243',
|
|
90
|
+
[CountryEnum.GABON]: '241',
|
|
91
|
+
[CountryEnum.GHANA]: '233',
|
|
92
|
+
[CountryEnum.MALAWI]: '265',
|
|
93
|
+
[CountryEnum.RWANDA]: '250',
|
|
94
|
+
[CountryEnum.SIERRA_LEONE]: '232',
|
|
95
|
+
[CountryEnum.UGANDA]: '256',
|
|
96
|
+
[CountryEnum.ZAMBIA]: '260',
|
|
89
97
|
};
|
|
90
98
|
/** Greyed on the Card tab until a provider for them is coded. */
|
|
91
99
|
export const COMING_SOON_WALLETS = [
|
|
@@ -132,6 +140,14 @@ export const CURRENCY_DECIMALS = {
|
|
|
132
140
|
[CurrencyEnum.XOF]: 0,
|
|
133
141
|
[CurrencyEnum.EUR]: 2,
|
|
134
142
|
[CurrencyEnum.USD]: 2,
|
|
143
|
+
[CurrencyEnum.GHS]: 2,
|
|
144
|
+
[CurrencyEnum.MWK]: 2,
|
|
145
|
+
[CurrencyEnum.UGX]: 0,
|
|
146
|
+
// Whole units, not ISO's two: Vodacom refuses decimals.
|
|
147
|
+
[CurrencyEnum.CDF]: 0,
|
|
148
|
+
[CurrencyEnum.SLE]: 2,
|
|
149
|
+
[CurrencyEnum.RWF]: 0,
|
|
150
|
+
[CurrencyEnum.ZMW]: 2,
|
|
135
151
|
};
|
|
136
152
|
/** What follows the amount: francs CFA read the same in both zones. */
|
|
137
153
|
export const CURRENCY_LABEL = {
|
|
@@ -139,4 +155,11 @@ export const CURRENCY_LABEL = {
|
|
|
139
155
|
[CurrencyEnum.XOF]: 'FCFA',
|
|
140
156
|
[CurrencyEnum.EUR]: 'EUR',
|
|
141
157
|
[CurrencyEnum.USD]: 'USD',
|
|
158
|
+
[CurrencyEnum.GHS]: 'GHS',
|
|
159
|
+
[CurrencyEnum.MWK]: 'MWK',
|
|
160
|
+
[CurrencyEnum.UGX]: 'UGX',
|
|
161
|
+
[CurrencyEnum.CDF]: 'CDF',
|
|
162
|
+
[CurrencyEnum.SLE]: 'SLE',
|
|
163
|
+
[CurrencyEnum.RWF]: 'RWF',
|
|
164
|
+
[CurrencyEnum.ZMW]: 'ZMW',
|
|
142
165
|
};
|
|
@@ -92,7 +92,9 @@ export declare enum SimulatorOutcomeEnum {
|
|
|
92
92
|
PROVIDER_ERROR = "provider_error",
|
|
93
93
|
WRONG_AMOUNT = "wrong_amount",
|
|
94
94
|
APPROVED_LATE = "approved_late",
|
|
95
|
-
REVERSED = "reversed"
|
|
95
|
+
REVERSED = "reversed",
|
|
96
|
+
/** A success on every send: a duplicate. Offered once there are several. */
|
|
97
|
+
APPROVED_ALL = "approved_all"
|
|
96
98
|
}
|
|
97
99
|
export declare enum PhoneErrorEnum {
|
|
98
100
|
START = "start",
|
|
@@ -104,6 +104,8 @@ export var SimulatorOutcomeEnum;
|
|
|
104
104
|
SimulatorOutcomeEnum["WRONG_AMOUNT"] = "wrong_amount";
|
|
105
105
|
SimulatorOutcomeEnum["APPROVED_LATE"] = "approved_late";
|
|
106
106
|
SimulatorOutcomeEnum["REVERSED"] = "reversed";
|
|
107
|
+
/** A success on every send: a duplicate. Offered once there are several. */
|
|
108
|
+
SimulatorOutcomeEnum["APPROVED_ALL"] = "approved_all";
|
|
107
109
|
})(SimulatorOutcomeEnum || (SimulatorOutcomeEnum = {}));
|
|
108
110
|
export var PhoneErrorEnum;
|
|
109
111
|
(function (PhoneErrorEnum) {
|
|
@@ -44,6 +44,19 @@ export interface CardSummary {
|
|
|
44
44
|
brand: string;
|
|
45
45
|
last4: string;
|
|
46
46
|
}
|
|
47
|
+
/** How to approve by dialling, as the provider said it, in both languages. */
|
|
48
|
+
export interface AttemptDial {
|
|
49
|
+
title: {
|
|
50
|
+
en: string;
|
|
51
|
+
fr: string;
|
|
52
|
+
};
|
|
53
|
+
/** The whole code, to dial in one tap. */
|
|
54
|
+
quickLink: string | null;
|
|
55
|
+
steps: {
|
|
56
|
+
en: Array<string>;
|
|
57
|
+
fr: Array<string>;
|
|
58
|
+
};
|
|
59
|
+
}
|
|
47
60
|
export interface CheckoutAttempt {
|
|
48
61
|
reference: string;
|
|
49
62
|
method: string;
|
|
@@ -61,6 +74,26 @@ export interface CheckoutAttempt {
|
|
|
61
74
|
startedAt: number;
|
|
62
75
|
/** A test attempt the Simulator took: the tester picks its outcome. */
|
|
63
76
|
simulated: boolean;
|
|
77
|
+
/** When the latest request went out: "Didn't get it?" counts from it. */
|
|
78
|
+
lastSentAt: number;
|
|
79
|
+
/** New requests the payer can still ask for. */
|
|
80
|
+
sendsLeft: number;
|
|
81
|
+
/** The network's prompt never shows by itself: the payer dials at once. */
|
|
82
|
+
manual: boolean;
|
|
83
|
+
dial: Array<AttemptDial>;
|
|
84
|
+
}
|
|
85
|
+
/** One way to approve by dialling, in the payer's language. */
|
|
86
|
+
export interface DialChannel {
|
|
87
|
+
title: string;
|
|
88
|
+
quickLink: string | null;
|
|
89
|
+
steps: Array<string>;
|
|
90
|
+
}
|
|
91
|
+
/** What the waiting screen offers a payer with no prompt, once it is time to. */
|
|
92
|
+
export interface DialHelp {
|
|
93
|
+
/** USSD steps: after the last new request, or at once for a `manual` network. */
|
|
94
|
+
channels: Array<DialChannel>;
|
|
95
|
+
/** The method's own code to see pending payments, when there are no steps. */
|
|
96
|
+
pendingCode: string | null;
|
|
64
97
|
}
|
|
65
98
|
export interface CheckoutPayer {
|
|
66
99
|
name: string | null;
|
|
@@ -226,8 +259,10 @@ export interface CheckoutCoreActions {
|
|
|
226
259
|
cannotDial: () => void;
|
|
227
260
|
/** Leaves the failure screen for the suggested method, or the list. */
|
|
228
261
|
useAnother: () => void;
|
|
229
|
-
/**
|
|
230
|
-
|
|
262
|
+
/** The phone could not open its dialler on a waiting attempt's code: dial it by hand. */
|
|
263
|
+
cannotDialCode: (code: string) => void;
|
|
264
|
+
/** "Didn't get it?": a new request. */
|
|
265
|
+
sendAgain: () => void;
|
|
231
266
|
cancel: () => void;
|
|
232
267
|
returnNow: () => void;
|
|
233
268
|
stayHere: () => void;
|
|
@@ -253,6 +288,8 @@ export interface CheckoutCore {
|
|
|
253
288
|
phone: PhoneNumber;
|
|
254
289
|
/** Seconds left on the waiting screen's clock. */
|
|
255
290
|
waitLeft: number;
|
|
291
|
+
/** Steps and codes the waiting screen offers now. */
|
|
292
|
+
dialHelp: DialHelp;
|
|
256
293
|
/** Seconds before the success screen sends the payer back, null once stopped. */
|
|
257
294
|
returnLeft: number | null;
|
|
258
295
|
helpOpen: boolean;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { CheckoutScreenEnum } from './checkout.enum.js';
|
|
1
|
+
import { CheckoutScreenEnum, SimulatorOutcomeEnum } from './checkout.enum.js';
|
|
2
2
|
import { CountryEnum } from './domain.enum.js';
|
|
3
|
-
import type { ProviderLogoEnum } from './domain.enum.js';
|
|
4
|
-
import type { CheckoutApp, CheckoutDerived, CheckoutMethod, CheckoutPreviewApp, CheckoutUi, CheckoutView, CountryClues, PhoneStatus, SupportContact } from './checkout.interface.js';
|
|
3
|
+
import type { LangEnum, ProviderLogoEnum } from './domain.enum.js';
|
|
4
|
+
import type { CheckoutApp, CheckoutAttempt, CheckoutDerived, CheckoutMethod, CheckoutPreviewApp, CheckoutUi, CheckoutView, CountryClues, DialHelp, PhoneStatus, SupportContact } from './checkout.interface.js';
|
|
5
5
|
/** Countries with Mobile Money methods, in the order their first method comes. */
|
|
6
6
|
export declare function countriesOf(methods: Array<CheckoutMethod>): Array<CountryEnum>;
|
|
7
7
|
/** The country whose dial code starts this E.164 number. */
|
|
@@ -30,6 +30,27 @@ export declare function logoOf(of: Pick<CheckoutMethod, 'flow' | 'logos'>): Prov
|
|
|
30
30
|
export declare function deriveCheckout(view: CheckoutView, ui: CheckoutUi, phone: PhoneStatus): CheckoutDerived;
|
|
31
31
|
/** Seconds left to approve an attempt started at `startedAt`. */
|
|
32
32
|
export declare function waitLeftOf(startedAt: number, now: number): number;
|
|
33
|
+
/** The payer can still ask for a new request of this attempt. */
|
|
34
|
+
export declare const canSendAgain: (attempt: Pick<CheckoutAttempt, "sendsLeft">) => boolean;
|
|
35
|
+
/**
|
|
36
|
+
* What the waiting screen offers a payer with no prompt. USSD is the last
|
|
37
|
+
* resort: it shows after the last new request, or at once when the network's
|
|
38
|
+
* prompt never shows by itself, in the payer's language; a channel that only
|
|
39
|
+
* differs in its other language or by letter case shows once. The method's own
|
|
40
|
+
* code to see pending payments takes over when there are no steps to show.
|
|
41
|
+
* Nothing for a flow with no prompt.
|
|
42
|
+
*/
|
|
43
|
+
export declare function dialHelpOf(attempt: CheckoutAttempt | null, lang: LangEnum): DialHelp;
|
|
44
|
+
/**
|
|
45
|
+
* What the Simulator offers the tester: what the flow can do, and a success on
|
|
46
|
+
* every send only once the payer asked again.
|
|
47
|
+
*/
|
|
48
|
+
export declare function simulatorOutcomesOf(attempt: CheckoutAttempt): Array<SimulatorOutcomeEnum>;
|
|
49
|
+
/**
|
|
50
|
+
* Seconds before the prompt can be sent again: 30 from the last time it went
|
|
51
|
+
* out, by the attempt or by `sentAt`, the second of the wait a sheet just asked at.
|
|
52
|
+
*/
|
|
53
|
+
export declare function sendAgainLeftOf(attempt: CheckoutAttempt, waitLeft: number, sentAt?: number): number;
|
|
33
54
|
/** How payers reach the app's support, when it set any. */
|
|
34
55
|
export declare function supportContactsOf(app: CheckoutApp): Array<SupportContact>;
|
|
35
56
|
/** A pending test payment for the Branding tab's preview, branded as typed. */
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { CARD_LOGO, DIAL_CODES, OTP_PATTERN, PREVIEW_AMOUNT, PREVIEW_ORDER_REF, WAIT_SECONDS, } from "./checkout.constants.js";
|
|
2
|
-
import { CheckoutPaymentStatusEnum, CheckoutScreenEnum, CheckoutTabEnum, MethodFlowEnum, MethodKindEnum, MethodStateEnum, SupportChannelEnum, } from "./checkout.enum.js";
|
|
1
|
+
import { CARD_LOGO, DIAL_CODES, FLOW_TRAITS, OTP_PATTERN, PREVIEW_AMOUNT, PREVIEW_ORDER_REF, SEND_AGAIN_SECONDS, WAIT_SECONDS, } from "./checkout.constants.js";
|
|
2
|
+
import { CheckoutPaymentStatusEnum, CheckoutScreenEnum, CheckoutTabEnum, MethodFlowEnum, MethodKindEnum, MethodStateEnum, SimulatorOutcomeEnum, SupportChannelEnum, } from "./checkout.enum.js";
|
|
3
3
|
import { AttemptStatusEnum, CountryEnum, CurrencyEnum } from "./domain.enum.js";
|
|
4
4
|
/** Countries with Mobile Money methods, in the order their first method comes. */
|
|
5
5
|
export function countriesOf(methods) {
|
|
@@ -162,6 +162,51 @@ export function waitLeftOf(startedAt, now) {
|
|
|
162
162
|
const elapsed = Math.floor((now - startedAt) / 1000);
|
|
163
163
|
return Math.min(WAIT_SECONDS, Math.max(0, WAIT_SECONDS - elapsed));
|
|
164
164
|
}
|
|
165
|
+
/** The payer can still ask for a new request of this attempt. */
|
|
166
|
+
export const canSendAgain = (attempt) => attempt.sendsLeft > 0;
|
|
167
|
+
/**
|
|
168
|
+
* What the waiting screen offers a payer with no prompt. USSD is the last
|
|
169
|
+
* resort: it shows after the last new request, or at once when the network's
|
|
170
|
+
* prompt never shows by itself, in the payer's language; a channel that only
|
|
171
|
+
* differs in its other language or by letter case shows once. The method's own
|
|
172
|
+
* code to see pending payments takes over when there are no steps to show.
|
|
173
|
+
* Nothing for a flow with no prompt.
|
|
174
|
+
*/
|
|
175
|
+
export function dialHelpOf(attempt, lang) {
|
|
176
|
+
if (!attempt || !FLOW_TRAITS[attempt.flow].prompt) {
|
|
177
|
+
return { channels: [], pendingCode: null };
|
|
178
|
+
}
|
|
179
|
+
const sendAgain = canSendAgain(attempt);
|
|
180
|
+
const channels = new Map();
|
|
181
|
+
for (const { title, quickLink, steps } of sendAgain ? [] : attempt.dial) {
|
|
182
|
+
const key = [quickLink, ...steps[lang]].join('|').toLowerCase();
|
|
183
|
+
channels.set(key, { title: title[lang], quickLink, steps: steps[lang] });
|
|
184
|
+
}
|
|
185
|
+
return {
|
|
186
|
+
channels: [...channels.values()],
|
|
187
|
+
pendingCode: !sendAgain && channels.size === 0 ? attempt.pendingCode : null,
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
/** The payer asked for a new request since Pay. */
|
|
191
|
+
const askedAgain = (attempt) => attempt.lastSentAt > attempt.startedAt;
|
|
192
|
+
/**
|
|
193
|
+
* What the Simulator offers the tester: what the flow can do, and a success on
|
|
194
|
+
* every send only once the payer asked again.
|
|
195
|
+
*/
|
|
196
|
+
export function simulatorOutcomesOf(attempt) {
|
|
197
|
+
const { hiddenOutcomes } = FLOW_TRAITS[attempt.flow];
|
|
198
|
+
return Object.values(SimulatorOutcomeEnum).filter((outcome) => !hiddenOutcomes.includes(outcome) &&
|
|
199
|
+
(outcome !== SimulatorOutcomeEnum.APPROVED_ALL || askedAgain(attempt)));
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Seconds before the prompt can be sent again: 30 from the last time it went
|
|
203
|
+
* out, by the attempt or by `sentAt`, the second of the wait a sheet just asked at.
|
|
204
|
+
*/
|
|
205
|
+
export function sendAgainLeftOf(attempt, waitLeft, sentAt = 0) {
|
|
206
|
+
const lastSent = Math.floor((attempt.lastSentAt - attempt.startedAt) / 1000);
|
|
207
|
+
const waited = WAIT_SECONDS - waitLeft - Math.max(sentAt, lastSent);
|
|
208
|
+
return Math.max(0, SEND_AGAIN_SECONDS - waited);
|
|
209
|
+
}
|
|
165
210
|
/** How payers reach the app's support, when it set any. */
|
|
166
211
|
export function supportContactsOf(app) {
|
|
167
212
|
const contacts = [];
|
|
@@ -7,6 +7,11 @@ export declare enum ProviderLogoEnum {
|
|
|
7
7
|
MOOV = "moov",
|
|
8
8
|
WAVE = "wave",
|
|
9
9
|
MIXX = "mixx",
|
|
10
|
+
AIRTEL = "airtel",
|
|
11
|
+
AT = "at",
|
|
12
|
+
TELECEL = "telecel",
|
|
13
|
+
TNM = "tnm",
|
|
14
|
+
VODACOM = "vodacom",
|
|
10
15
|
VISA = "visa",
|
|
11
16
|
MASTERCARD = "mastercard",
|
|
12
17
|
PAYPAL = "paypal",
|
|
@@ -22,13 +27,28 @@ export declare enum CountryEnum {
|
|
|
22
27
|
BENIN = "bj",
|
|
23
28
|
TOGO = "tg",
|
|
24
29
|
BURKINA_FASO = "bf",
|
|
30
|
+
DR_CONGO = "cd",
|
|
31
|
+
GABON = "ga",
|
|
32
|
+
GHANA = "gh",
|
|
33
|
+
MALAWI = "mw",
|
|
34
|
+
RWANDA = "rw",
|
|
35
|
+
SIERRA_LEONE = "sl",
|
|
36
|
+
UGANDA = "ug",
|
|
37
|
+
ZAMBIA = "zm",
|
|
25
38
|
INTERNATIONAL = "intl"
|
|
26
39
|
}
|
|
27
40
|
export declare enum CurrencyEnum {
|
|
28
41
|
XAF = "XAF",
|
|
29
42
|
XOF = "XOF",
|
|
30
43
|
EUR = "EUR",
|
|
31
|
-
USD = "USD"
|
|
44
|
+
USD = "USD",
|
|
45
|
+
GHS = "GHS",
|
|
46
|
+
MWK = "MWK",
|
|
47
|
+
UGX = "UGX",
|
|
48
|
+
CDF = "CDF",
|
|
49
|
+
SLE = "SLE",
|
|
50
|
+
RWF = "RWF",
|
|
51
|
+
ZMW = "ZMW"
|
|
32
52
|
}
|
|
33
53
|
/** Why an attempt failed. The first five are the payer's and count toward the lock. */
|
|
34
54
|
export declare enum FailureReasonEnum {
|
|
@@ -8,6 +8,11 @@ export var ProviderLogoEnum;
|
|
|
8
8
|
ProviderLogoEnum["MOOV"] = "moov";
|
|
9
9
|
ProviderLogoEnum["WAVE"] = "wave";
|
|
10
10
|
ProviderLogoEnum["MIXX"] = "mixx";
|
|
11
|
+
ProviderLogoEnum["AIRTEL"] = "airtel";
|
|
12
|
+
ProviderLogoEnum["AT"] = "at";
|
|
13
|
+
ProviderLogoEnum["TELECEL"] = "telecel";
|
|
14
|
+
ProviderLogoEnum["TNM"] = "tnm";
|
|
15
|
+
ProviderLogoEnum["VODACOM"] = "vodacom";
|
|
11
16
|
ProviderLogoEnum["VISA"] = "visa";
|
|
12
17
|
ProviderLogoEnum["MASTERCARD"] = "mastercard";
|
|
13
18
|
ProviderLogoEnum["PAYPAL"] = "paypal";
|
|
@@ -24,6 +29,14 @@ export var CountryEnum;
|
|
|
24
29
|
CountryEnum["BENIN"] = "bj";
|
|
25
30
|
CountryEnum["TOGO"] = "tg";
|
|
26
31
|
CountryEnum["BURKINA_FASO"] = "bf";
|
|
32
|
+
CountryEnum["DR_CONGO"] = "cd";
|
|
33
|
+
CountryEnum["GABON"] = "ga";
|
|
34
|
+
CountryEnum["GHANA"] = "gh";
|
|
35
|
+
CountryEnum["MALAWI"] = "mw";
|
|
36
|
+
CountryEnum["RWANDA"] = "rw";
|
|
37
|
+
CountryEnum["SIERRA_LEONE"] = "sl";
|
|
38
|
+
CountryEnum["UGANDA"] = "ug";
|
|
39
|
+
CountryEnum["ZAMBIA"] = "zm";
|
|
27
40
|
CountryEnum["INTERNATIONAL"] = "intl";
|
|
28
41
|
})(CountryEnum || (CountryEnum = {}));
|
|
29
42
|
export var CurrencyEnum;
|
|
@@ -32,6 +45,13 @@ export var CurrencyEnum;
|
|
|
32
45
|
CurrencyEnum["XOF"] = "XOF";
|
|
33
46
|
CurrencyEnum["EUR"] = "EUR";
|
|
34
47
|
CurrencyEnum["USD"] = "USD";
|
|
48
|
+
CurrencyEnum["GHS"] = "GHS";
|
|
49
|
+
CurrencyEnum["MWK"] = "MWK";
|
|
50
|
+
CurrencyEnum["UGX"] = "UGX";
|
|
51
|
+
CurrencyEnum["CDF"] = "CDF";
|
|
52
|
+
CurrencyEnum["SLE"] = "SLE";
|
|
53
|
+
CurrencyEnum["RWF"] = "RWF";
|
|
54
|
+
CurrencyEnum["ZMW"] = "ZMW";
|
|
35
55
|
})(CurrencyEnum || (CurrencyEnum = {}));
|
|
36
56
|
/** Why an attempt failed. The first five are the payer's and count toward the lock. */
|
|
37
57
|
export var FailureReasonEnum;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { SheetScreenProps } from '../models/sheet.interface.js';
|
|
2
|
+
/**
|
|
3
|
+
* A payer with no prompt and no new request left: the network's USSD steps, or the
|
|
4
|
+
* method's code to see pending payments, with one tap to dial where a code is
|
|
5
|
+
* given (on Android; on iOS it says to dial it by hand).
|
|
6
|
+
*/
|
|
7
|
+
export declare function SheetDial({ core }: SheetScreenProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { Linking, Platform, StyleSheet, View } from 'react-native';
|
|
3
|
+
import { useTranslations } from 'use-intl';
|
|
4
|
+
import { CheckoutButtonVariantEnum } from "../checkout-core/models/checkout.enum.js";
|
|
5
|
+
import { telHref } from "../checkout-core/models/checkout.phone.utils.js";
|
|
6
|
+
import { useSheetTheme } from "../hooks/use-sheet-theme.js";
|
|
7
|
+
import { FigtreeEnum } from "../models/sheet.enum.js";
|
|
8
|
+
import { SheetButton } from "./SheetButton.js";
|
|
9
|
+
import { SheetText } from "./SheetText.js";
|
|
10
|
+
/**
|
|
11
|
+
* A payer with no prompt and no new request left: the network's USSD steps, or the
|
|
12
|
+
* method's code to see pending payments, with one tap to dial where a code is
|
|
13
|
+
* given (on Android; on iOS it says to dial it by hand).
|
|
14
|
+
*/
|
|
15
|
+
export function SheetDial({ core }) {
|
|
16
|
+
const t = useTranslations('checkout');
|
|
17
|
+
const { colors } = useSheetTheme();
|
|
18
|
+
const { channels, pendingCode } = core.dialHelp;
|
|
19
|
+
const dial = (code) => {
|
|
20
|
+
// The iPhone's Phone app refuses a tel: link with * or #: say to dial it by hand.
|
|
21
|
+
if (Platform.OS === 'ios') {
|
|
22
|
+
core.actions.cannotDialCode(code);
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
Linking.openURL(telHref(code)).catch(() => {
|
|
26
|
+
core.actions.cannotDialCode(code);
|
|
27
|
+
});
|
|
28
|
+
};
|
|
29
|
+
return (_jsxs(_Fragment, { children: [channels.map(({ title, steps, quickLink }) => (_jsxs(View, { style: styles.block, children: [_jsx(SheetText, { weight: FigtreeEnum.BOLD, accessibilityRole: "header", children: title }), quickLink && (_jsx(SheetButton, { variant: CheckoutButtonVariantEnum.SECONDARY, label: t('sheet.dial', { code: quickLink }), onPress: () => dial(quickLink) })), steps.map((step, index) => (_jsxs(View, { style: styles.step, children: [_jsxs(SheetText, { weight: FigtreeEnum.BOLD, style: styles.number, children: [index + 1, "."] }), _jsx(SheetText, { paragraph: true, size: 14, style: styles.text, children: step })] }, step)))] }, steps.join()))), pendingCode && (_jsxs(View, { style: styles.block, children: [_jsx(SheetText, { weight: FigtreeEnum.BOLD, children: t('no_prompt') }), _jsx(SheetText, { paragraph: true, size: 14, color: colors.muted, children: t('dial_pending') }), _jsx(SheetButton, { variant: CheckoutButtonVariantEnum.SECONDARY, label: t('sheet.dial', { code: pendingCode }), onPress: () => dial(pendingCode) })] }))] }));
|
|
30
|
+
}
|
|
31
|
+
const styles = StyleSheet.create({
|
|
32
|
+
block: { marginTop: 18, gap: 10 },
|
|
33
|
+
step: { flexDirection: 'row', gap: 8 },
|
|
34
|
+
number: { width: 20 },
|
|
35
|
+
text: { flex: 1 },
|
|
36
|
+
});
|
|
@@ -2,7 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { StyleSheet, TouchableOpacity, View } from 'react-native';
|
|
3
3
|
import { useTranslations } from 'use-intl';
|
|
4
4
|
import { FLOW_TRAITS, SIMULATOR_NO_ANSWER_SUFFIX, } from "../checkout-core/models/checkout.constants.js";
|
|
5
|
-
import {
|
|
5
|
+
import { simulatorOutcomesOf } from "../checkout-core/models/checkout.view.utils.js";
|
|
6
6
|
import { useSheetTheme } from "../hooks/use-sheet-theme.js";
|
|
7
7
|
import { FigtreeEnum } from "../models/sheet.enum.js";
|
|
8
8
|
import { SheetText } from "./SheetText.js";
|
|
@@ -14,7 +14,7 @@ export function SheetSimulator({ core }) {
|
|
|
14
14
|
const t = useTranslations('checkout');
|
|
15
15
|
const { colors } = useSheetTheme();
|
|
16
16
|
const traits = FLOW_TRAITS[core.view.attempt.flow];
|
|
17
|
-
const outcomes =
|
|
17
|
+
const outcomes = simulatorOutcomesOf(core.view.attempt);
|
|
18
18
|
return (_jsxs(View, { accessibilityLabel: t('simulator.title'), style: [
|
|
19
19
|
styles.panel,
|
|
20
20
|
{ backgroundColor: colors.warnSoft, borderColor: colors.warnLine },
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { SheetScreenProps } from '../models/sheet.interface.js';
|
|
2
2
|
/**
|
|
3
3
|
* Waiting for the payer to approve on their phone: what to do, the method
|
|
4
|
-
* and the clock,
|
|
5
|
-
* It moves on by itself.
|
|
4
|
+
* and the clock, Send again once 30 seconds have passed, the network's USSD steps
|
|
5
|
+
* once no new request is left, and another method. It moves on by itself.
|
|
6
6
|
*/
|
|
7
7
|
export declare function SheetWaiting({ core }: SheetScreenProps): import("react").JSX.Element;
|
|
@@ -6,10 +6,11 @@ import { FLOW_TRAITS, URGENT_SECONDS, WAIT_SECONDS, } from "../checkout-core/mod
|
|
|
6
6
|
import { CheckoutButtonVariantEnum } from "../checkout-core/models/checkout.enum.js";
|
|
7
7
|
import { formatClock, formatMoney, } from "../checkout-core/models/checkout.money.utils.js";
|
|
8
8
|
import { maskE164 } from "../checkout-core/models/checkout.phone.utils.js";
|
|
9
|
+
import { canSendAgain, sendAgainLeftOf, } from "../checkout-core/models/checkout.view.utils.js";
|
|
9
10
|
import { useSheetTheme } from "../hooks/use-sheet-theme.js";
|
|
10
11
|
import { FigtreeEnum } from "../models/sheet.enum.js";
|
|
11
|
-
import { resendLeftOf } from "../models/sheet.utils.js";
|
|
12
12
|
import { SheetButton } from "./SheetButton.js";
|
|
13
|
+
import { SheetDial } from "./SheetDial.js";
|
|
13
14
|
import { SheetLayout } from "./SheetLayout.js";
|
|
14
15
|
import { SheetRow } from "./SheetRow.js";
|
|
15
16
|
import { SheetSimulator } from "./SheetSimulator.js";
|
|
@@ -17,36 +18,36 @@ import { SheetText } from "./SheetText.js";
|
|
|
17
18
|
const NO_BREAK_SPACE = '\u00a0';
|
|
18
19
|
/**
|
|
19
20
|
* Waiting for the payer to approve on their phone: what to do, the method
|
|
20
|
-
* and the clock,
|
|
21
|
-
* It moves on by itself.
|
|
21
|
+
* and the clock, Send again once 30 seconds have passed, the network's USSD steps
|
|
22
|
+
* once no new request is left, and another method. It moves on by itself.
|
|
22
23
|
*/
|
|
23
24
|
export function SheetWaiting({ core }) {
|
|
24
25
|
const t = useTranslations('checkout');
|
|
25
26
|
const { colors, brand } = useSheetTheme();
|
|
26
27
|
const { view, waitLeft, actions } = core;
|
|
27
28
|
const attempt = view.attempt;
|
|
28
|
-
// A payment code or a card needs nothing more from the payer: no prompt to
|
|
29
|
+
// A payment code or a card needs nothing more from the payer: no prompt to send again.
|
|
29
30
|
const { prompt } = FLOW_TRAITS[attempt.flow];
|
|
30
31
|
/** The second of the wait the prompt was last sent at. */
|
|
31
32
|
const [sentAt, setSentAt] = useState(0);
|
|
32
|
-
const
|
|
33
|
+
const sendAgainIn = sendAgainLeftOf(attempt, waitLeft, sentAt);
|
|
33
34
|
// The number never breaks across lines.
|
|
34
35
|
const phone = attempt.phone
|
|
35
36
|
? maskE164(attempt.phone).replace(/ /g, NO_BREAK_SPACE)
|
|
36
37
|
: '';
|
|
37
|
-
return (_jsx(SheetLayout, { foot: _jsxs(_Fragment, { children: [prompt && (_jsx(SheetButton, { variant: CheckoutButtonVariantEnum.SECONDARY, label:
|
|
38
|
-
? t('
|
|
39
|
-
: t('
|
|
38
|
+
return (_jsx(SheetLayout, { foot: _jsxs(_Fragment, { children: [prompt && canSendAgain(attempt) && (_jsx(SheetButton, { variant: CheckoutButtonVariantEnum.SECONDARY, label: sendAgainIn > 0
|
|
39
|
+
? t('send_again_in', { clock: formatClock(sendAgainIn) })
|
|
40
|
+
: t('send_again'), disabled: sendAgainIn > 0, onPress: () => {
|
|
40
41
|
setSentAt(WAIT_SECONDS - waitLeft);
|
|
41
|
-
actions.
|
|
42
|
+
actions.sendAgain();
|
|
42
43
|
} })), _jsx(Pressable, { onPress: actions.useAnother, accessibilityRole: "button", style: styles.link, children: _jsx(SheetText, { weight: FigtreeEnum.SEMI_BOLD, style: styles.center, children: t('use_another') }) })] }), children: _jsxs(View, { style: styles.waiting, children: [_jsxs(View, { style: styles.pulse, children: [_jsx(View, { style: [styles.halo, styles.outer, { backgroundColor: brand }] }), _jsx(View, { style: [styles.halo, styles.inner, { backgroundColor: brand }] }), _jsx(View, { testID: "icon-phone", style: [styles.phone, { borderColor: brand }], children: _jsx(View, { style: [styles.speaker, { backgroundColor: brand }] }) })] }), _jsx(SheetText, { weight: FigtreeEnum.EXTRA_BOLD, size: 21, accessibilityRole: "header", style: [styles.center, styles.title], children: prompt ? t('sheet.approve_title') : t('checking_title') }), _jsx(SheetText, { paragraph: true, size: 14, color: colors.muted, style: styles.center, children: attempt.phone
|
|
43
44
|
? prompt
|
|
44
|
-
? t('sheet.approve_body', {
|
|
45
|
+
? t(attempt.manual ? 'sheet.manual_body' : 'sheet.approve_body', {
|
|
45
46
|
method: attempt.methodName,
|
|
46
47
|
phone,
|
|
47
48
|
})
|
|
48
49
|
: `${t('checking_with', { method: attempt.methodName })} ${phone}`
|
|
49
|
-
: t('checking_card', { method: attempt.methodName }) }), _jsxs(View, { style: [styles.rows, { borderColor: colors.line }], children: [_jsx(SheetRow, { label: attempt.methodName, children: formatMoney(view.amount, view.currency) }), _jsx(SheetRow, { label: t('sheet.expires_in'), urgent: waitLeft <= URGENT_SECONDS, children: formatClock(waitLeft) })] }), attempt.simulated && _jsx(SheetSimulator, { core: core })] }) }));
|
|
50
|
+
: t('checking_card', { method: attempt.methodName }) }), _jsxs(View, { style: [styles.rows, { borderColor: colors.line }], children: [_jsx(SheetRow, { label: attempt.methodName, children: formatMoney(view.amount, view.currency) }), _jsx(SheetRow, { label: t('sheet.expires_in'), urgent: waitLeft <= URGENT_SECONDS, children: formatClock(waitLeft) })] }), attempt.simulated && _jsx(SheetSimulator, { core: core }), _jsx(SheetDial, { core: core })] }) }));
|
|
50
51
|
}
|
|
51
52
|
const styles = StyleSheet.create({
|
|
52
53
|
waiting: { paddingHorizontal: 18, paddingTop: 30 },
|
|
@@ -9,6 +9,11 @@ import logoGooglePay from '../../assets/logos/google-pay.png';
|
|
|
9
9
|
import logoMastercard from '../../assets/logos/mastercard.png';
|
|
10
10
|
import logoMixx from '../../assets/logos/mixx.png';
|
|
11
11
|
import logoMoov from '../../assets/logos/moov.png';
|
|
12
|
+
import logoAirtel from '../../assets/logos/airtel.png';
|
|
13
|
+
import logoAt from '../../assets/logos/at.png';
|
|
14
|
+
import logoTelecel from '../../assets/logos/telecel.png';
|
|
15
|
+
import logoTnm from '../../assets/logos/tnm.png';
|
|
16
|
+
import logoVodacom from '../../assets/logos/vodacom.png';
|
|
12
17
|
import logoMtn from '../../assets/logos/mtn.png';
|
|
13
18
|
import logoOrange from '../../assets/logos/orange.png';
|
|
14
19
|
import logoPaypal from '../../assets/logos/paypal.png';
|
|
@@ -54,6 +59,11 @@ export const PROVIDER_LOGOS = {
|
|
|
54
59
|
[ProviderLogoEnum.MOOV]: logoMoov,
|
|
55
60
|
[ProviderLogoEnum.WAVE]: logoWave,
|
|
56
61
|
[ProviderLogoEnum.MIXX]: logoMixx,
|
|
62
|
+
[ProviderLogoEnum.AIRTEL]: logoAirtel,
|
|
63
|
+
[ProviderLogoEnum.AT]: logoAt,
|
|
64
|
+
[ProviderLogoEnum.TELECEL]: logoTelecel,
|
|
65
|
+
[ProviderLogoEnum.TNM]: logoTnm,
|
|
66
|
+
[ProviderLogoEnum.VODACOM]: logoVodacom,
|
|
57
67
|
[ProviderLogoEnum.VISA]: logoVisa,
|
|
58
68
|
[ProviderLogoEnum.MASTERCARD]: logoMastercard,
|
|
59
69
|
[ProviderLogoEnum.PAYPAL]: logoPaypal,
|
|
@@ -11,11 +11,6 @@ export declare function sheetTheme(payment: CheckoutThemeEnum | null, scheme: st
|
|
|
11
11
|
export declare function sheetResult(view: CheckoutView | null | undefined, failed: boolean): PaymentSheetResult;
|
|
12
12
|
/** `cm` → 🇨🇲: the country's flag, as the phone draws it. */
|
|
13
13
|
export declare function flagOf(country: CountryEnum): string;
|
|
14
|
-
/**
|
|
15
|
-
* Seconds before the prompt can be sent again, from the waiting clock
|
|
16
|
-
* (`waitLeft`) and the second of the wait it was last sent at (0: Pay).
|
|
17
|
-
*/
|
|
18
|
-
export declare function resendLeftOf(waitLeft: number, sentAt: number): number;
|
|
19
14
|
/**
|
|
20
15
|
* The card fields page the sheet loads: English under `/en`, French the bare
|
|
21
16
|
* path asking for French (as the checkout link does), with the payment, the
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { RESEND_SECONDS, WAIT_SECONDS, } from "../checkout-core/models/checkout.constants.js";
|
|
2
1
|
import { CardFieldsMessageEnum, CheckoutPaymentStatusEnum, CheckoutThemeEnum, PayRefusalEnum, } from "../checkout-core/models/checkout.enum.js";
|
|
3
2
|
import { AttemptStatusEnum, LangEnum, } from "../checkout-core/models/domain.enum.js";
|
|
4
3
|
import { SHEET_FLICK_SPEED, SHEET_SWIPE_DISTANCE } from "./sheet.constants.js";
|
|
@@ -36,14 +35,6 @@ export function sheetResult(view, failed) {
|
|
|
36
35
|
export function flagOf(country) {
|
|
37
36
|
return String.fromCodePoint(...[...country.toUpperCase()].map((letter) => 0x1f1e6 + letter.charCodeAt(0) - 'A'.charCodeAt(0)));
|
|
38
37
|
}
|
|
39
|
-
/**
|
|
40
|
-
* Seconds before the prompt can be sent again, from the waiting clock
|
|
41
|
-
* (`waitLeft`) and the second of the wait it was last sent at (0: Pay).
|
|
42
|
-
*/
|
|
43
|
-
export function resendLeftOf(waitLeft, sentAt) {
|
|
44
|
-
const waited = WAIT_SECONDS - waitLeft - sentAt;
|
|
45
|
-
return Math.max(0, RESEND_SECONDS - waited);
|
|
46
|
-
}
|
|
47
38
|
/**
|
|
48
39
|
* The card fields page the sheet loads: English under `/en`, French the bare
|
|
49
40
|
* path asking for French (as the checkout link does), with the payment, the
|