@heyama/pay-react-native 0.0.0-stage → 1.0.0

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 (120) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +99 -2
  3. package/assets/fonts/Figtree-Bold.ttf +0 -0
  4. package/assets/fonts/Figtree-ExtraBold.ttf +0 -0
  5. package/assets/fonts/Figtree-Medium.ttf +0 -0
  6. package/assets/fonts/Figtree-Regular.ttf +0 -0
  7. package/assets/fonts/Figtree-SemiBold.ttf +0 -0
  8. package/assets/fonts/OFL.txt +93 -0
  9. package/assets/logos/apple-pay.png +0 -0
  10. package/assets/logos/card.png +0 -0
  11. package/assets/logos/google-pay.png +0 -0
  12. package/assets/logos/mastercard.png +0 -0
  13. package/assets/logos/mixx.png +0 -0
  14. package/assets/logos/moov.png +0 -0
  15. package/assets/logos/mtn.png +0 -0
  16. package/assets/logos/orange.png +0 -0
  17. package/assets/logos/paypal.png +0 -0
  18. package/assets/logos/visa.png +0 -0
  19. package/assets/logos/wave.png +0 -0
  20. package/dist/checkout-core/hooks/use-checkout-controller.d.ts +9 -0
  21. package/dist/checkout-core/hooks/use-checkout-controller.js +319 -0
  22. package/dist/checkout-core/hooks/use-checkout-timers.d.ts +15 -0
  23. package/dist/checkout-core/hooks/use-checkout-timers.js +44 -0
  24. package/dist/checkout-core/hooks/use-phone-number.d.ts +7 -0
  25. package/dist/checkout-core/hooks/use-phone-number.js +82 -0
  26. package/dist/checkout-core/messages/en.d.ts +252 -0
  27. package/dist/checkout-core/messages/en.js +242 -0
  28. package/dist/checkout-core/messages/fr.d.ts +4 -0
  29. package/dist/checkout-core/messages/fr.js +244 -0
  30. package/dist/checkout-core/models/checkout.card.utils.d.ts +22 -0
  31. package/dist/checkout-core/models/checkout.card.utils.js +101 -0
  32. package/dist/checkout-core/models/checkout.constants.d.ts +56 -0
  33. package/dist/checkout-core/models/checkout.constants.js +142 -0
  34. package/dist/checkout-core/models/checkout.enum.d.ts +131 -0
  35. package/dist/checkout-core/models/checkout.enum.js +148 -0
  36. package/dist/checkout-core/models/checkout.interface.d.ts +417 -0
  37. package/dist/checkout-core/models/checkout.interface.js +1 -0
  38. package/dist/checkout-core/models/checkout.money.utils.d.ts +7 -0
  39. package/dist/checkout-core/models/checkout.money.utils.js +21 -0
  40. package/dist/checkout-core/models/checkout.phone.utils.d.ts +33 -0
  41. package/dist/checkout-core/models/checkout.phone.utils.js +159 -0
  42. package/dist/checkout-core/models/checkout.view.utils.d.ts +34 -0
  43. package/dist/checkout-core/models/checkout.view.utils.js +205 -0
  44. package/dist/checkout-core/models/domain.enum.d.ts +56 -0
  45. package/dist/checkout-core/models/domain.enum.js +62 -0
  46. package/dist/components/BrissotoPayProvider.d.ts +6 -0
  47. package/dist/components/BrissotoPayProvider.js +41 -0
  48. package/dist/components/PaymentSheetModal.d.ts +8 -0
  49. package/dist/components/PaymentSheetModal.js +72 -0
  50. package/dist/components/SheetBone.d.ts +3 -0
  51. package/dist/components/SheetBone.js +12 -0
  52. package/dist/components/SheetButton.d.ts +3 -0
  53. package/dist/components/SheetButton.js +33 -0
  54. package/dist/components/SheetCardFields.d.ts +8 -0
  55. package/dist/components/SheetCardFields.js +66 -0
  56. package/dist/components/SheetCodeField.d.ts +8 -0
  57. package/dist/components/SheetCodeField.js +95 -0
  58. package/dist/components/SheetContent.d.ts +3 -0
  59. package/dist/components/SheetContent.js +106 -0
  60. package/dist/components/SheetFailed.d.ts +3 -0
  61. package/dist/components/SheetFailed.js +43 -0
  62. package/dist/components/SheetForm.d.ts +7 -0
  63. package/dist/components/SheetForm.js +100 -0
  64. package/dist/components/SheetHeader.d.ts +6 -0
  65. package/dist/components/SheetHeader.js +69 -0
  66. package/dist/components/SheetIcon.d.ts +3 -0
  67. package/dist/components/SheetIcon.js +70 -0
  68. package/dist/components/SheetLayout.d.ts +8 -0
  69. package/dist/components/SheetLayout.js +35 -0
  70. package/dist/components/SheetLogo.d.ts +3 -0
  71. package/dist/components/SheetLogo.js +20 -0
  72. package/dist/components/SheetMessage.d.ts +3 -0
  73. package/dist/components/SheetMessage.js +15 -0
  74. package/dist/components/SheetMethodCard.d.ts +3 -0
  75. package/dist/components/SheetMethodCard.js +39 -0
  76. package/dist/components/SheetPhoneField.d.ts +3 -0
  77. package/dist/components/SheetPhoneField.js +56 -0
  78. package/dist/components/SheetRow.d.ts +3 -0
  79. package/dist/components/SheetRow.js +28 -0
  80. package/dist/components/SheetSimulator.d.ts +6 -0
  81. package/dist/components/SheetSimulator.js +39 -0
  82. package/dist/components/SheetSkeleton.d.ts +5 -0
  83. package/dist/components/SheetSkeleton.js +23 -0
  84. package/dist/components/SheetSuccess.d.ts +3 -0
  85. package/dist/components/SheetSuccess.js +51 -0
  86. package/dist/components/SheetSummary.d.ts +3 -0
  87. package/dist/components/SheetSummary.js +32 -0
  88. package/dist/components/SheetTerminal.d.ts +3 -0
  89. package/dist/components/SheetTerminal.js +21 -0
  90. package/dist/components/SheetText.d.ts +5 -0
  91. package/dist/components/SheetText.js +25 -0
  92. package/dist/components/SheetToast.d.ts +3 -0
  93. package/dist/components/SheetToast.js +24 -0
  94. package/dist/components/SheetWaiting.d.ts +7 -0
  95. package/dist/components/SheetWaiting.js +77 -0
  96. package/dist/hooks/use-native-platform.d.ts +10 -0
  97. package/dist/hooks/use-native-platform.js +47 -0
  98. package/dist/hooks/use-payment-sheet.d.ts +4 -0
  99. package/dist/hooks/use-payment-sheet.js +10 -0
  100. package/dist/hooks/use-sheet-motion.d.ts +7 -0
  101. package/dist/hooks/use-sheet-motion.js +62 -0
  102. package/dist/hooks/use-sheet-resize.d.ts +6 -0
  103. package/dist/hooks/use-sheet-resize.js +15 -0
  104. package/dist/hooks/use-sheet-theme.d.ts +4 -0
  105. package/dist/hooks/use-sheet-theme.js +12 -0
  106. package/dist/hooks/use-watched-payment.d.ts +6 -0
  107. package/dist/hooks/use-watched-payment.js +17 -0
  108. package/dist/index.d.ts +3 -0
  109. package/dist/index.js +2 -0
  110. package/dist/models/sheet.constants.d.ts +39 -0
  111. package/dist/models/sheet.constants.js +78 -0
  112. package/dist/models/sheet.enum.d.ts +16 -0
  113. package/dist/models/sheet.enum.js +18 -0
  114. package/dist/models/sheet.interface.d.ts +225 -0
  115. package/dist/models/sheet.interface.js +1 -0
  116. package/dist/models/sheet.utils.d.ts +43 -0
  117. package/dist/models/sheet.utils.js +132 -0
  118. package/dist/services/checkout.service.d.ts +7 -0
  119. package/dist/services/checkout.service.js +39 -0
  120. package/package.json +58 -4
@@ -0,0 +1,244 @@
1
+ /** Same keys as the English catalog: the compiler flags a missing or extra one. */
2
+ const messages = {
3
+ total_label: 'Montant à payer',
4
+ total: 'Total',
5
+ details: 'Détails',
6
+ hide_details: 'Masquer',
7
+ each: '{price} l’unité',
8
+ pay_with: 'Payer avec',
9
+ unavailable: 'Temporairement indisponible',
10
+ soon: 'Bientôt disponible',
11
+ phone_label: 'Numéro Mobile Money',
12
+ helper: 'Le numéro associé à votre compte {method}',
13
+ phone_errors: {
14
+ start: 'Les numéros mobiles camerounais commencent par 6',
15
+ short: 'Ce numéro est trop court',
16
+ invalid: "Ce numéro n'est pas valide",
17
+ empty: 'Saisissez votre numéro Mobile Money',
18
+ country: "Saisissez un numéro d'un des pays proposés",
19
+ operator: "Ce numéro n'est ni Orange ni MTN",
20
+ },
21
+ otp_label: 'Code de paiement',
22
+ otp_placeholder: 'Saisissez le code',
23
+ otp_help: 'Composez ce code sur votre téléphone {method} pour obtenir un code de paiement, puis saisissez-le ici.',
24
+ card_fields: {
25
+ title: 'Données de la carte',
26
+ number: 'Numéro de carte',
27
+ number_placeholder: '1234 1234 1234 1234',
28
+ expiry: "Date d'expiration",
29
+ expiry_placeholder: 'MM / AA',
30
+ cvc: 'Code de sécurité',
31
+ cvc_placeholder: 'CVC',
32
+ incomplete: "Vérifiez le numéro de carte, la date d'expiration et le code de sécurité.",
33
+ simulator_note: 'Champs de carte du Simulateur : aucune vraie carte débitée.',
34
+ },
35
+ mismatch: 'Ce numéro semble être {method}. Changer ?',
36
+ switch_to: 'Changer',
37
+ pay_short: 'Payer {amount} {currency} avec',
38
+ pay_plain: 'Payer {amount} {currency}',
39
+ sending: 'Envoi de la demande…',
40
+ tab_wallet: 'Mobile Money',
41
+ tab_card: 'Carte & wallet',
42
+ wallets: {
43
+ paypal: { name: 'PayPal', sub: 'Compte PayPal' },
44
+ 'apple-pay': { name: 'Apple Pay', sub: 'iPhone · Mac' },
45
+ 'google-pay': { name: 'Google Pay', sub: 'Android · Chrome' },
46
+ },
47
+ countries: {
48
+ cm: 'Cameroun',
49
+ ci: 'Côte d’Ivoire',
50
+ sn: 'Sénégal',
51
+ bj: 'Bénin',
52
+ tg: 'Togo',
53
+ bf: 'Burkina Faso',
54
+ intl: 'International',
55
+ },
56
+ country: 'Pays',
57
+ change: 'Modifier',
58
+ secured_by: 'Paiement sécurisé par',
59
+ help: "Besoin d'aide ?",
60
+ terms: 'Conditions',
61
+ privacy: 'Confidentialité',
62
+ test_sub: 'Aucun argent réel ne sera débité',
63
+ test_badge: 'Test',
64
+ loading: 'Chargement du paiement',
65
+ confirm_title: 'Confirmez le paiement sur votre téléphone',
66
+ sent_to: 'Demande {method} envoyée au',
67
+ remaining: 'pour valider',
68
+ steps: {
69
+ first: 'Déverrouillez votre téléphone et ouvrez la demande',
70
+ last: 'Saisissez votre code secret pour valider',
71
+ },
72
+ no_prompt: 'Pas reçu la demande ?',
73
+ dial: 'Renvoyez-la, ou composez ce code pour voir les paiements en attente',
74
+ dial_only: 'Renvoyez-la sur votre téléphone',
75
+ resend: 'Renvoyer',
76
+ cancel: 'Annuler le paiement',
77
+ checking_title: 'Confirmation du paiement',
78
+ checking_with: '{method} vérifie votre code pour le',
79
+ checking_card: '{method} vérifie votre carte.',
80
+ simulator: {
81
+ title: 'Simulateur',
82
+ body: 'Paiement de test : choisissez ce qui lui arrive.',
83
+ no_answer: 'Un numéro finissant par {suffix} ne répond pas au lancement.',
84
+ approved: 'Valider',
85
+ declined: 'Refuser',
86
+ insufficient_funds: 'Solde insuffisant',
87
+ no_account: 'Pas de compte',
88
+ wrong_pin: 'Mauvais code secret',
89
+ wrong_code: 'Code de paiement erroné',
90
+ provider_error: 'Erreur du fournisseur',
91
+ wrong_amount: 'Valider un mauvais montant',
92
+ approved_late: 'Valider en retard',
93
+ reversed: 'Valider puis annuler',
94
+ },
95
+ failure: {
96
+ declined: {
97
+ title: 'Paiement refusé',
98
+ body: 'La demande {method} a été refusée sur votre téléphone.',
99
+ next: 'Réessayez, ou payez avec un autre moyen.',
100
+ },
101
+ insufficient_funds: {
102
+ title: 'Solde insuffisant',
103
+ body: 'Votre solde {method} est insuffisant pour ce paiement.',
104
+ next: 'Rechargez votre compte puis réessayez, ou payez avec un autre moyen.',
105
+ },
106
+ no_account: {
107
+ title: 'Aucun compte {method}',
108
+ body: "Ce numéro n'a pas de compte {method}.",
109
+ next: 'Vérifiez le numéro puis réessayez, ou payez avec un autre moyen.',
110
+ },
111
+ wrong_pin: {
112
+ title: 'Code secret incorrect',
113
+ body: "Le code secret saisi pour {method} n'est pas le bon.",
114
+ next: 'Réessayez avec le bon code secret.',
115
+ },
116
+ wrong_code: {
117
+ title: 'Code erroné ou expiré',
118
+ body: "Le code de paiement {method} n'a pas été accepté.",
119
+ next: 'Obtenez un nouveau code et réessayez.',
120
+ },
121
+ provider_error: {
122
+ title: '{method} ne répond pas',
123
+ body: "{method} n'a pas pu traiter le paiement. Le problème ne vient pas de vous.",
124
+ next: 'Réessayez dans un instant, ou payez avec un autre moyen.',
125
+ },
126
+ timeout: {
127
+ title: 'Paiement non confirmé',
128
+ body: "{method} n'a pas confirmé le paiement à temps.",
129
+ next: 'Réessayez, ou payez avec un autre moyen.',
130
+ },
131
+ mismatch: {
132
+ title: 'Paiement à vérifier',
133
+ body: "{method} a signalé un montant différent de celui demandé. Le paiement n'a pas été validé.",
134
+ next: 'Contactez le support avec la référence du paiement avant de réessayer.',
135
+ },
136
+ },
137
+ retry: 'Réessayer',
138
+ retry_code: 'Réessayer avec un nouveau code',
139
+ use_another: 'Utiliser un autre moyen',
140
+ try_with: 'Essayer avec {method}',
141
+ tries_left: 'Tentatives restantes',
142
+ tries_value: '{left} sur {max}',
143
+ not_charged: "Aucun montant n'a été débité de votre compte.",
144
+ success_title: 'Paiement réussi',
145
+ paid_to: 'Payé à {app}',
146
+ amount: 'Montant',
147
+ method: 'Moyen',
148
+ number: 'Numéro',
149
+ card: 'Carte',
150
+ date: 'Date',
151
+ reference: 'Référence',
152
+ copy: 'Copier',
153
+ copied: 'Copié',
154
+ download: 'Télécharger le reçu',
155
+ return_to: 'Retour à {app}',
156
+ return_in: 'Retour à {app} ({seconds})',
157
+ stay: 'Rester sur cette page',
158
+ expired: {
159
+ title: 'Ce lien de paiement a expiré',
160
+ body: 'Retournez sur {app} pour en obtenir un nouveau.',
161
+ },
162
+ cancelled: {
163
+ title: 'Ce paiement a été annulé',
164
+ body: 'Retournez sur {app} pour payer à nouveau.',
165
+ },
166
+ locked_screen: {
167
+ title: 'Trop de tentatives',
168
+ body: 'Pour votre sécurité, ce paiement est suspendu après trop de tentatives infructueuses.',
169
+ },
170
+ invalid: {
171
+ title: "Ce lien n'est pas valide",
172
+ body: "Nous ne trouvons pas ce paiement. Le lien est peut-être incomplet. Rouvrez-le depuis l'application où vous payiez.",
173
+ },
174
+ no_method: {
175
+ title: 'Aucun moyen de paiement pour le moment',
176
+ body: '{app} ne propose aucun moyen de paiement en ce moment. Réessayez plus tard.',
177
+ },
178
+ contact_support: 'Contacter le support',
179
+ get_help: "Obtenir de l'aide",
180
+ returning: 'Retour à {app}…',
181
+ not_redirected: 'Pas redirigé ? Appuyez ici',
182
+ contact_title: 'Contacter le support',
183
+ reference_title: 'Référence du paiement',
184
+ reference_hint: 'Communiquez-la au support pour retrouver votre paiement rapidement.',
185
+ close: 'Fermer',
186
+ sheet: {
187
+ approve_title: 'Approuvez sur votre téléphone',
188
+ approve_body: 'Composez votre code secret {method} dans la fenêtre reçue au {phone}.',
189
+ expires_in: 'Expire dans',
190
+ resend_in: 'Renvoyer dans {clock}',
191
+ done_in: 'Terminé ({seconds})',
192
+ close_title: 'Fermer le paiement ?',
193
+ close_body: 'La demande est toujours sur votre téléphone. Si vous la validez, le paiement passe.',
194
+ keep_waiting: 'Continuer d’attendre',
195
+ dial: 'Composer le {code}',
196
+ },
197
+ faqs: {
198
+ not_received: {
199
+ q: "Je n'ai pas reçu la demande",
200
+ a: "Vérifiez que votre téléphone est allumé et capte le réseau. Vous pouvez aussi composer le code affiché sur l'écran d'attente pour voir et valider les paiements en attente.",
201
+ },
202
+ debited: {
203
+ q: "J'ai été débité mais la page indique un échec",
204
+ a: 'Ne payez pas une deuxième fois. Contactez le support avec votre référence pour que nous le vérifiions.',
205
+ },
206
+ },
207
+ channels: {
208
+ whatsapp: 'WhatsApp',
209
+ email: 'E-mail',
210
+ },
211
+ error: {
212
+ title: "Cette page n'a pas pu s'afficher",
213
+ body: "Rien n'a été débité. Réessayez dans un instant.",
214
+ retry: 'Réessayer',
215
+ },
216
+ refusals: {
217
+ closed: 'Ce paiement ne peut plus être payé.',
218
+ method: "Ce moyen n'est pas disponible pour le moment. Choisissez-en un autre.",
219
+ phone: "Ce numéro n'est pas du pays de ce moyen.",
220
+ code: 'Saisissez le code de paiement que vous avez généré.',
221
+ },
222
+ toast: {
223
+ copied: 'Référence copiée',
224
+ downloaded: 'Reçu téléchargé',
225
+ switched: 'Passé à {method}',
226
+ unavailable: '{method} est temporairement indisponible',
227
+ soon: '{method} arrive bientôt',
228
+ no_answer: 'Pas encore de réponse : réessayez ou choisissez un autre moyen',
229
+ failed: 'Une erreur est survenue. Réessayez.',
230
+ resent: 'Demande renvoyée au {phone}',
231
+ resend_wait: 'Patientez quelques secondes avant de la renvoyer.',
232
+ preview: 'Aperçu seulement : rien n’est payé ici',
233
+ dial: 'Composez {code} depuis votre téléphone {method}.',
234
+ },
235
+ dark_mode: 'Mode sombre',
236
+ light_mode: 'Mode clair',
237
+ language_label: 'Language / Langue',
238
+ receipt_title: 'Brissoto Pay : Paiement réussi',
239
+ meta: {
240
+ title: 'Paiement sécurisé',
241
+ description: 'Payez en toute sécurité par Mobile Money ou carte avec Brissoto Pay.',
242
+ },
243
+ };
244
+ export default messages;
@@ -0,0 +1,22 @@
1
+ import { ProviderLogoEnum } from './domain.enum.js';
2
+ import type { CardEntry, CardFieldsSearch, CardSummary, SimulatorCardFieldsProps } from './checkout.interface.js';
3
+ /** The network of a card number from its first digits (Visa 4, Mastercard 51–55 and 2221–2720), as its logo. */
4
+ export declare function cardBrandOf(number: string): ProviderLogoEnum.VISA | ProviderLogoEnum.MASTERCARD | null;
5
+ /** `4242424242424242` → `4242 4242 4242 4242`, at most 19 digits. */
6
+ export declare function formatCardNumber(value: string): string;
7
+ /** `1228` → `12 / 28`: the month, then the year. */
8
+ export declare function formatExpiry(value: string): string;
9
+ export declare function formatCvc(value: string): string;
10
+ /** A card number of a network we take, of a valid length, that passes Luhn. */
11
+ export declare function isCardNumberValid(number: string): boolean;
12
+ /** A real month, this month or later (`now` decides "this month"). */
13
+ export declare function isExpiryValid(expiry: string, now: Date): boolean;
14
+ export declare function isCvcValid(cvc: string): boolean;
15
+ /** Every field of the card is filled and valid. */
16
+ export declare function isCardComplete(card: CardEntry, now: Date): boolean;
17
+ /** The card fields page's address: the payment, method, app colour and theme, when valid; anything else is dropped. */
18
+ export declare function validateCardFieldsSearch(search: Record<string, unknown>): CardFieldsSearch;
19
+ /** The page's payment, method and look, from its address. */
20
+ export declare function cardFieldsLookOf(search: CardFieldsSearch): SimulatorCardFieldsProps;
21
+ /** "Visa •••• 4242": a paid card, as the receipt and the dashboard show it. */
22
+ export declare function cardLabel(card: CardSummary): string;
@@ -0,0 +1,101 @@
1
+ import { CheckoutThemeEnum } from "./checkout.enum.js";
2
+ import { ProviderLogoEnum } from "./domain.enum.js";
3
+ const NUMBER_LENGTH = { min: 13, max: 19 };
4
+ function digitsOf(value) {
5
+ return value.replace(/\D/g, '');
6
+ }
7
+ /** The network of a card number from its first digits (Visa 4, Mastercard 51–55 and 2221–2720), as its logo. */
8
+ export function cardBrandOf(number) {
9
+ const digits = digitsOf(number);
10
+ if (digits.startsWith('4'))
11
+ return ProviderLogoEnum.VISA;
12
+ const two = Number(digits.slice(0, 2));
13
+ const four = Number(digits.slice(0, 4));
14
+ if ((two >= 51 && two <= 55) || (four >= 2221 && four <= 2720)) {
15
+ return ProviderLogoEnum.MASTERCARD;
16
+ }
17
+ return null;
18
+ }
19
+ /** The Luhn check every card number passes. */
20
+ function passesLuhn(digits) {
21
+ let sum = 0;
22
+ for (let index = 0; index < digits.length; index++) {
23
+ let digit = Number(digits[digits.length - 1 - index]);
24
+ if (index % 2 === 1) {
25
+ digit *= 2;
26
+ if (digit > 9)
27
+ digit -= 9;
28
+ }
29
+ sum += digit;
30
+ }
31
+ return sum % 10 === 0;
32
+ }
33
+ /** `4242424242424242` → `4242 4242 4242 4242`, at most 19 digits. */
34
+ export function formatCardNumber(value) {
35
+ return digitsOf(value)
36
+ .slice(0, NUMBER_LENGTH.max)
37
+ .replace(/(\d{4})(?=\d)/g, '$1 ');
38
+ }
39
+ /** `1228` → `12 / 28`: the month, then the year. */
40
+ export function formatExpiry(value) {
41
+ const digits = digitsOf(value).slice(0, 4);
42
+ return digits.length > 2
43
+ ? `${digits.slice(0, 2)} / ${digits.slice(2)}`
44
+ : digits;
45
+ }
46
+ export function formatCvc(value) {
47
+ return digitsOf(value).slice(0, 4);
48
+ }
49
+ /** A card number of a network we take, of a valid length, that passes Luhn. */
50
+ export function isCardNumberValid(number) {
51
+ const digits = digitsOf(number);
52
+ return (cardBrandOf(digits) !== null &&
53
+ digits.length >= NUMBER_LENGTH.min &&
54
+ digits.length <= NUMBER_LENGTH.max &&
55
+ passesLuhn(digits));
56
+ }
57
+ /** A real month, this month or later (`now` decides "this month"). */
58
+ export function isExpiryValid(expiry, now) {
59
+ const digits = digitsOf(expiry);
60
+ if (digits.length !== 4)
61
+ return false;
62
+ const month = Number(digits.slice(0, 2));
63
+ const year = 2000 + Number(digits.slice(2));
64
+ if (month < 1 || month > 12)
65
+ return false;
66
+ const thisYear = now.getFullYear();
67
+ return year > thisYear || (year === thisYear && month >= now.getMonth() + 1);
68
+ }
69
+ export function isCvcValid(cvc) {
70
+ return /^\d{3,4}$/.test(cvc);
71
+ }
72
+ /** Every field of the card is filled and valid. */
73
+ export function isCardComplete(card, now) {
74
+ return (isCardNumberValid(card.number) &&
75
+ isExpiryValid(card.expiry, now) &&
76
+ isCvcValid(card.cvc));
77
+ }
78
+ const HEX_COLOR = /^#[0-9a-f]{6}$/i;
79
+ /** The card fields page's address: the payment, method, app colour and theme, when valid; anything else is dropped. */
80
+ export function validateCardFieldsSearch(search) {
81
+ const { payment, method, color, theme } = search;
82
+ return {
83
+ ...(typeof payment === 'string' ? { payment } : {}),
84
+ ...(typeof method === 'string' ? { method } : {}),
85
+ ...(typeof color === 'string' && HEX_COLOR.test(color) ? { color } : {}),
86
+ ...(theme === CheckoutThemeEnum.DARK ? { theme } : {}),
87
+ };
88
+ }
89
+ /** The page's payment, method and look, from its address. */
90
+ export function cardFieldsLookOf(search) {
91
+ return {
92
+ paymentId: search.payment ?? null,
93
+ method: search.method ?? null,
94
+ color: search.color ?? null,
95
+ dark: search.theme === CheckoutThemeEnum.DARK,
96
+ };
97
+ }
98
+ /** "Visa •••• 4242": a paid card, as the receipt and the dashboard show it. */
99
+ export function cardLabel(card) {
100
+ return `${card.brand} •••• ${card.last4}`;
101
+ }
@@ -0,0 +1,56 @@
1
+ import { CheckoutThemeEnum, MethodFlowEnum } from './checkout.enum.js';
2
+ import { CountryEnum, CurrencyEnum, ProviderLogoEnum } from './domain.enum.js';
3
+ import type { CheckoutColors, ComingSoonWallet, FlowTraits } from './checkout.interface.js';
4
+ /** The waiting screen gives the payer this long to approve on their phone. */
5
+ export declare const WAIT_SECONDS = 180;
6
+ /** The success screen sends the payer back to the app after this long. */
7
+ export declare const RETURN_SECONDS = 5;
8
+ /** The countdowns move once a second. */
9
+ export declare const TICK_MS = 1000;
10
+ export declare const TOAST_MS = 2400;
11
+ export declare const COPIED_MS = 1800;
12
+ /** The last seconds of the waiting clock turn red. */
13
+ export declare const URGENT_SECONDS = 30;
14
+ /** A prompt can be sent again this long after the last one (the backend's `RESEND_AFTER_MS`). */
15
+ export declare const RESEND_SECONDS = 30;
16
+ /** The card fields' bones until their page says how tall it is, on the web and in the sheet. */
17
+ export declare const CARD_FIELDS_HEIGHT = 190;
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. */
19
+ export declare const LOGO_SIZE: {
20
+ INLINE: number;
21
+ ROW: number;
22
+ HERO: number;
23
+ };
24
+ /** Corner radius of a brand tile, as a share of its side: the same curve at every size. */
25
+ export declare const LOGO_RADIUS_RATIO = 0.23;
26
+ /** Colour the page takes on the "invalid link" screen, which belongs to no app. */
27
+ export declare const INVALID_BRAND_LIGHT = "#111316";
28
+ export declare const INVALID_BRAND_DARK = "#4c515a";
29
+ /** Every image's 1px grey border, in both themes: `--media-line` of `src/styles.css`. */
30
+ export declare const MEDIA_LINE = "rgb(120 124 132 / 0.35)";
31
+ /**
32
+ * The checkout's colours by theme, for the payment sheet, which has no
33
+ * stylesheet. The web page's tokens in `src/features/checkout/checkout.css`
34
+ * are the source: a test keeps the two equal.
35
+ */
36
+ export declare const CHECKOUT_COLORS: Record<CheckoutThemeEnum, CheckoutColors>;
37
+ /** International dial code of each Mobile Money country. */
38
+ export declare const DIAL_CODES: Partial<Record<CountryEnum, string>>;
39
+ /** Greyed on the Card tab until a provider for them is coded. */
40
+ export declare const COMING_SOON_WALLETS: Array<ComingSoonWallet>;
41
+ /** The card tile: a generic card, whatever networks the provider takes. */
42
+ export declare const CARD_LOGO = ProviderLogoEnum.CARD;
43
+ /** The sample order the Branding tab's preview shows. */
44
+ export declare const PREVIEW_AMOUNT = 15000;
45
+ export declare const PREVIEW_ORDER_REF = "HY-20419";
46
+ /** What each way of approving changes on the screens. */
47
+ export declare const FLOW_TRAITS: Record<MethodFlowEnum, FlowTraits>;
48
+ /** A one-time payment code: networks send 4 to 8 digits (the backend's rule). */
49
+ export declare const OTP_MAX_LENGTH = 8;
50
+ export declare const OTP_PATTERN: RegExp;
51
+ /** A test number ending like this gets no answer from the Simulator on start (the backend's). */
52
+ export declare const SIMULATOR_NO_ANSWER_SUFFIX = "0408";
53
+ /** Digits after the decimal point: amounts travel in minor units. */
54
+ export declare const CURRENCY_DECIMALS: Record<CurrencyEnum, number>;
55
+ /** What follows the amount: francs CFA read the same in both zones. */
56
+ export declare const CURRENCY_LABEL: Record<CurrencyEnum, string>;
@@ -0,0 +1,142 @@
1
+ import { CheckoutTabEnum, CheckoutThemeEnum, MethodFlowEnum, SimulatorOutcomeEnum, } from "./checkout.enum.js";
2
+ import { CountryEnum, CurrencyEnum, ProviderLogoEnum } from "./domain.enum.js";
3
+ /** The waiting screen gives the payer this long to approve on their phone. */
4
+ export const WAIT_SECONDS = 180;
5
+ /** The success screen sends the payer back to the app after this long. */
6
+ export const RETURN_SECONDS = 5;
7
+ /** The countdowns move once a second. */
8
+ export const TICK_MS = 1000;
9
+ export const TOAST_MS = 2400;
10
+ export const COPIED_MS = 1800;
11
+ /** The last seconds of the waiting clock turn red. */
12
+ export const URGENT_SECONDS = 30;
13
+ /** A prompt can be sent again this long after the last one (the backend's `RESEND_AFTER_MS`). */
14
+ export const RESEND_SECONDS = 30;
15
+ /** The card fields' bones until their page says how tall it is, on the web and in the sheet. */
16
+ export const CARD_FIELDS_HEIGHT = 190;
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. */
18
+ export const LOGO_SIZE = { INLINE: 20, ROW: 28, HERO: 56 };
19
+ /** Corner radius of a brand tile, as a share of its side: the same curve at every size. */
20
+ export const LOGO_RADIUS_RATIO = 0.23;
21
+ /** Colour the page takes on the "invalid link" screen, which belongs to no app. */
22
+ export const INVALID_BRAND_LIGHT = '#111316';
23
+ export const INVALID_BRAND_DARK = '#4c515a';
24
+ /** Every image's 1px grey border, in both themes: `--media-line` of `src/styles.css`. */
25
+ export const MEDIA_LINE = 'rgb(120 124 132 / 0.35)';
26
+ /**
27
+ * The checkout's colours by theme, for the payment sheet, which has no
28
+ * stylesheet. The web page's tokens in `src/features/checkout/checkout.css`
29
+ * are the source: a test keeps the two equal.
30
+ */
31
+ export const CHECKOUT_COLORS = {
32
+ [CheckoutThemeEnum.LIGHT]: {
33
+ page: '#f2f2f4',
34
+ surface: '#ffffff',
35
+ tabOn: '#ffffff',
36
+ fill: '#efeff0',
37
+ fill2: '#f6f6f7',
38
+ fill3: '#e6e6e8',
39
+ ink: '#111316',
40
+ inkStrong: '#2b2e33',
41
+ ink2: '#474b53',
42
+ muted: '#6b7079',
43
+ faint: '#9b9fa6',
44
+ line: '#ececee',
45
+ line2: '#e3e4e6',
46
+ line3: '#d6d7da',
47
+ line4: '#c4c6ca',
48
+ danger: '#b42318',
49
+ dangerSoft: '#fdeceb',
50
+ warnSoft: '#fff3c4',
51
+ warnLine: '#f0dc8a',
52
+ warnInk: '#8a5a00',
53
+ warnInk2: '#5c4400',
54
+ successInk: '#12784f',
55
+ },
56
+ [CheckoutThemeEnum.DARK]: {
57
+ page: '#0c0d0f',
58
+ surface: '#16181b',
59
+ tabOn: '#2a2d33',
60
+ fill: '#202227',
61
+ fill2: '#1d1f23',
62
+ fill3: '#2a2d33',
63
+ ink: '#f2f3f5',
64
+ inkStrong: '#e4e6ea',
65
+ ink2: '#c3c7ce',
66
+ muted: '#9a9fa9',
67
+ faint: '#6f7580',
68
+ line: '#25282d',
69
+ line2: '#2e3137',
70
+ line3: '#3b3f46',
71
+ line4: '#4c515a',
72
+ danger: '#ff8a80',
73
+ dangerSoft: '#3a1a18',
74
+ warnSoft: '#352c10',
75
+ warnLine: '#5a4a1a',
76
+ warnInk: '#e8b65a',
77
+ warnInk2: '#f0d27a',
78
+ successInk: '#5fd6a2',
79
+ },
80
+ };
81
+ /** International dial code of each Mobile Money country. */
82
+ export const DIAL_CODES = {
83
+ [CountryEnum.CAMEROON]: '237',
84
+ [CountryEnum.COTE_D_IVOIRE]: '225',
85
+ [CountryEnum.SENEGAL]: '221',
86
+ [CountryEnum.BENIN]: '229',
87
+ [CountryEnum.TOGO]: '228',
88
+ [CountryEnum.BURKINA_FASO]: '226',
89
+ };
90
+ /** Greyed on the Card tab until a provider for them is coded. */
91
+ export const COMING_SOON_WALLETS = [
92
+ { id: ProviderLogoEnum.PAYPAL },
93
+ { id: ProviderLogoEnum.APPLE_PAY },
94
+ { id: ProviderLogoEnum.GOOGLE_PAY },
95
+ ];
96
+ /** The card tile: a generic card, whatever networks the provider takes. */
97
+ export const CARD_LOGO = ProviderLogoEnum.CARD;
98
+ /** The sample order the Branding tab's preview shows. */
99
+ export const PREVIEW_AMOUNT = 15000;
100
+ export const PREVIEW_ORDER_REF = 'HY-20419';
101
+ /** What each way of approving changes on the screens. */
102
+ export const FLOW_TRAITS = {
103
+ [MethodFlowEnum.USSD_PUSH]: {
104
+ tab: CheckoutTabEnum.MOBILE_MONEY,
105
+ prompt: true,
106
+ hiddenOutcomes: [SimulatorOutcomeEnum.WRONG_CODE],
107
+ },
108
+ [MethodFlowEnum.OTP]: {
109
+ tab: CheckoutTabEnum.MOBILE_MONEY,
110
+ prompt: false,
111
+ hiddenOutcomes: [SimulatorOutcomeEnum.WRONG_PIN],
112
+ },
113
+ [MethodFlowEnum.CARD_FIELDS]: {
114
+ tab: CheckoutTabEnum.CARD,
115
+ prompt: false,
116
+ // A card has no mobile money account behind it.
117
+ hiddenOutcomes: [
118
+ SimulatorOutcomeEnum.NO_ACCOUNT,
119
+ SimulatorOutcomeEnum.WRONG_PIN,
120
+ SimulatorOutcomeEnum.WRONG_CODE,
121
+ ],
122
+ },
123
+ };
124
+ /** A one-time payment code: networks send 4 to 8 digits (the backend's rule). */
125
+ export const OTP_MAX_LENGTH = 8;
126
+ export const OTP_PATTERN = new RegExp(`^\\d{4,${OTP_MAX_LENGTH}}$`);
127
+ /** A test number ending like this gets no answer from the Simulator on start (the backend's). */
128
+ export const SIMULATOR_NO_ANSWER_SUFFIX = '0408';
129
+ /** Digits after the decimal point: amounts travel in minor units. */
130
+ export const CURRENCY_DECIMALS = {
131
+ [CurrencyEnum.XAF]: 0,
132
+ [CurrencyEnum.XOF]: 0,
133
+ [CurrencyEnum.EUR]: 2,
134
+ [CurrencyEnum.USD]: 2,
135
+ };
136
+ /** What follows the amount: francs CFA read the same in both zones. */
137
+ export const CURRENCY_LABEL = {
138
+ [CurrencyEnum.XAF]: 'FCFA',
139
+ [CurrencyEnum.XOF]: 'FCFA',
140
+ [CurrencyEnum.EUR]: 'EUR',
141
+ [CurrencyEnum.USD]: 'USD',
142
+ };