@heyama/pay-react-native 0.0.0-stage → 1.0.1
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/LICENSE +21 -0
- package/README.md +99 -2
- package/assets/fonts/Figtree-Bold.ttf +0 -0
- package/assets/fonts/Figtree-ExtraBold.ttf +0 -0
- package/assets/fonts/Figtree-Medium.ttf +0 -0
- package/assets/fonts/Figtree-Regular.ttf +0 -0
- package/assets/fonts/Figtree-SemiBold.ttf +0 -0
- package/assets/fonts/OFL.txt +93 -0
- package/assets/logos/apple-pay.png +0 -0
- package/assets/logos/card.png +0 -0
- package/assets/logos/google-pay.png +0 -0
- package/assets/logos/mastercard.png +0 -0
- package/assets/logos/mixx.png +0 -0
- package/assets/logos/moov.png +0 -0
- package/assets/logos/mtn.png +0 -0
- package/assets/logos/orange.png +0 -0
- package/assets/logos/paypal.png +0 -0
- package/assets/logos/visa.png +0 -0
- package/assets/logos/wave.png +0 -0
- package/dist/checkout-core/hooks/use-checkout-controller.d.ts +9 -0
- package/dist/checkout-core/hooks/use-checkout-controller.js +319 -0
- package/dist/checkout-core/hooks/use-checkout-timers.d.ts +15 -0
- package/dist/checkout-core/hooks/use-checkout-timers.js +44 -0
- package/dist/checkout-core/hooks/use-phone-number.d.ts +7 -0
- package/dist/checkout-core/hooks/use-phone-number.js +82 -0
- package/dist/checkout-core/messages/en.d.ts +253 -0
- package/dist/checkout-core/messages/en.js +243 -0
- package/dist/checkout-core/messages/fr.d.ts +4 -0
- package/dist/checkout-core/messages/fr.js +245 -0
- package/dist/checkout-core/models/checkout.card.utils.d.ts +22 -0
- package/dist/checkout-core/models/checkout.card.utils.js +101 -0
- package/dist/checkout-core/models/checkout.constants.d.ts +56 -0
- package/dist/checkout-core/models/checkout.constants.js +142 -0
- package/dist/checkout-core/models/checkout.enum.d.ts +133 -0
- package/dist/checkout-core/models/checkout.enum.js +150 -0
- package/dist/checkout-core/models/checkout.interface.d.ts +417 -0
- package/dist/checkout-core/models/checkout.interface.js +1 -0
- package/dist/checkout-core/models/checkout.money.utils.d.ts +7 -0
- package/dist/checkout-core/models/checkout.money.utils.js +21 -0
- package/dist/checkout-core/models/checkout.phone.utils.d.ts +33 -0
- package/dist/checkout-core/models/checkout.phone.utils.js +159 -0
- package/dist/checkout-core/models/checkout.view.utils.d.ts +34 -0
- package/dist/checkout-core/models/checkout.view.utils.js +205 -0
- package/dist/checkout-core/models/domain.enum.d.ts +56 -0
- package/dist/checkout-core/models/domain.enum.js +62 -0
- package/dist/components/BrissotoPayProvider.d.ts +6 -0
- package/dist/components/BrissotoPayProvider.js +41 -0
- package/dist/components/PaymentSheetModal.d.ts +8 -0
- package/dist/components/PaymentSheetModal.js +77 -0
- package/dist/components/SheetBone.d.ts +3 -0
- package/dist/components/SheetBone.js +12 -0
- package/dist/components/SheetButton.d.ts +3 -0
- package/dist/components/SheetButton.js +33 -0
- package/dist/components/SheetCardFields.d.ts +8 -0
- package/dist/components/SheetCardFields.js +66 -0
- package/dist/components/SheetChosenMethod.d.ts +3 -0
- package/dist/components/SheetChosenMethod.js +60 -0
- package/dist/components/SheetCodeField.d.ts +8 -0
- package/dist/components/SheetCodeField.js +97 -0
- package/dist/components/SheetContent.d.ts +3 -0
- package/dist/components/SheetContent.js +106 -0
- package/dist/components/SheetFailed.d.ts +3 -0
- package/dist/components/SheetFailed.js +43 -0
- package/dist/components/SheetForm.d.ts +8 -0
- package/dist/components/SheetForm.js +101 -0
- package/dist/components/SheetHeader.d.ts +6 -0
- package/dist/components/SheetHeader.js +69 -0
- package/dist/components/SheetIcon.d.ts +3 -0
- package/dist/components/SheetIcon.js +70 -0
- package/dist/components/SheetLayout.d.ts +9 -0
- package/dist/components/SheetLayout.js +48 -0
- package/dist/components/SheetLogo.d.ts +3 -0
- package/dist/components/SheetLogo.js +20 -0
- package/dist/components/SheetMessage.d.ts +3 -0
- package/dist/components/SheetMessage.js +15 -0
- package/dist/components/SheetMethodCard.d.ts +3 -0
- package/dist/components/SheetMethodCard.js +32 -0
- package/dist/components/SheetPhoneField.d.ts +3 -0
- package/dist/components/SheetPhoneField.js +58 -0
- package/dist/components/SheetRow.d.ts +3 -0
- package/dist/components/SheetRow.js +28 -0
- package/dist/components/SheetSimulator.d.ts +6 -0
- package/dist/components/SheetSimulator.js +39 -0
- package/dist/components/SheetSkeleton.d.ts +5 -0
- package/dist/components/SheetSkeleton.js +23 -0
- package/dist/components/SheetSuccess.d.ts +3 -0
- package/dist/components/SheetSuccess.js +51 -0
- package/dist/components/SheetSummary.d.ts +3 -0
- package/dist/components/SheetSummary.js +32 -0
- package/dist/components/SheetTerminal.d.ts +3 -0
- package/dist/components/SheetTerminal.js +21 -0
- package/dist/components/SheetText.d.ts +5 -0
- package/dist/components/SheetText.js +25 -0
- package/dist/components/SheetToast.d.ts +3 -0
- package/dist/components/SheetToast.js +24 -0
- package/dist/components/SheetWaiting.d.ts +7 -0
- package/dist/components/SheetWaiting.js +77 -0
- package/dist/hooks/use-native-platform.d.ts +10 -0
- package/dist/hooks/use-native-platform.js +47 -0
- package/dist/hooks/use-payment-sheet.d.ts +4 -0
- package/dist/hooks/use-payment-sheet.js +10 -0
- package/dist/hooks/use-sheet-motion.d.ts +7 -0
- package/dist/hooks/use-sheet-motion.js +62 -0
- package/dist/hooks/use-sheet-resize.d.ts +6 -0
- package/dist/hooks/use-sheet-resize.js +15 -0
- package/dist/hooks/use-sheet-theme.d.ts +4 -0
- package/dist/hooks/use-sheet-theme.js +12 -0
- package/dist/hooks/use-watched-payment.d.ts +6 -0
- package/dist/hooks/use-watched-payment.js +17 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +2 -0
- package/dist/models/sheet.constants.d.ts +39 -0
- package/dist/models/sheet.constants.js +78 -0
- package/dist/models/sheet.enum.d.ts +16 -0
- package/dist/models/sheet.enum.js +18 -0
- package/dist/models/sheet.interface.d.ts +232 -0
- package/dist/models/sheet.interface.js +1 -0
- package/dist/models/sheet.utils.d.ts +43 -0
- package/dist/models/sheet.utils.js +132 -0
- package/dist/services/checkout.service.d.ts +7 -0
- package/dist/services/checkout.service.js +39 -0
- package/package.json +58 -4
|
@@ -0,0 +1,205 @@
|
|
|
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";
|
|
3
|
+
import { AttemptStatusEnum, CountryEnum, CurrencyEnum } from "./domain.enum.js";
|
|
4
|
+
/** Countries with Mobile Money methods, in the order their first method comes. */
|
|
5
|
+
export function countriesOf(methods) {
|
|
6
|
+
const countries = methods
|
|
7
|
+
.filter((method) => method.kind === MethodKindEnum.MOBILE_MONEY)
|
|
8
|
+
.map((method) => method.country);
|
|
9
|
+
return [...new Set(countries)];
|
|
10
|
+
}
|
|
11
|
+
/** The country whose dial code starts this E.164 number. */
|
|
12
|
+
export function countryOfPhone(phone) {
|
|
13
|
+
const found = Object.entries(DIAL_CODES).find(([, dial]) => phone.startsWith(`+${dial}`));
|
|
14
|
+
return found ? found[0] : null;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* The payer's country (docs/v1-plan.md, Checkout): the app's hint, then the
|
|
18
|
+
* phone's dial code, then the request's IP, then the browser's language
|
|
19
|
+
* (`fr-CM`). Only countries this payment can be paid in count; with none, the
|
|
20
|
+
* first country the gateway serves, for a page that has nothing to offer.
|
|
21
|
+
*/
|
|
22
|
+
export function pickCountry(view, clues, countries) {
|
|
23
|
+
const fromLanguages = clues.languages.map((tag) => tag.split('-')[1]?.toLowerCase());
|
|
24
|
+
const candidates = [
|
|
25
|
+
view.country,
|
|
26
|
+
view.payer.phone ? countryOfPhone(view.payer.phone) : null,
|
|
27
|
+
clues.ipCountry?.toLowerCase(),
|
|
28
|
+
...fromLanguages,
|
|
29
|
+
];
|
|
30
|
+
const found = candidates.find((candidate) => countries.includes(candidate));
|
|
31
|
+
return (found ??
|
|
32
|
+
countries.at(0) ??
|
|
33
|
+
Object.values(CountryEnum)[0]);
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* How the page opens: on the app's preferred method when it can be paid with,
|
|
37
|
+
* else on its preferred tab, with the list open.
|
|
38
|
+
*/
|
|
39
|
+
export function initialUi(view, clues) {
|
|
40
|
+
const countries = countriesOf(view.methods);
|
|
41
|
+
const preferred = view.methods.find((method) => method.id === view.preferredMethod &&
|
|
42
|
+
method.state === MethodStateEnum.AVAILABLE);
|
|
43
|
+
const base = {
|
|
44
|
+
tab: view.preferredTab ?? CheckoutTabEnum.MOBILE_MONEY,
|
|
45
|
+
country: pickCountry(view, clues, countries),
|
|
46
|
+
method: null,
|
|
47
|
+
code: '',
|
|
48
|
+
cardComplete: false,
|
|
49
|
+
cardPaying: false,
|
|
50
|
+
pickerOpen: true,
|
|
51
|
+
summaryOpen: false,
|
|
52
|
+
submitting: false,
|
|
53
|
+
dismissed: null,
|
|
54
|
+
redirecting: false,
|
|
55
|
+
};
|
|
56
|
+
if (preferred?.kind !== MethodKindEnum.MOBILE_MONEY)
|
|
57
|
+
return base;
|
|
58
|
+
return {
|
|
59
|
+
...base,
|
|
60
|
+
tab: CheckoutTabEnum.MOBILE_MONEY,
|
|
61
|
+
country: preferred.country,
|
|
62
|
+
method: preferred.id,
|
|
63
|
+
pickerOpen: false,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
const TERMINAL_SCREEN = {
|
|
67
|
+
[CheckoutPaymentStatusEnum.PAID]: CheckoutScreenEnum.SUCCESS,
|
|
68
|
+
[CheckoutPaymentStatusEnum.EXPIRED]: CheckoutScreenEnum.EXPIRED,
|
|
69
|
+
[CheckoutPaymentStatusEnum.CANCELLED]: CheckoutScreenEnum.CANCELLED,
|
|
70
|
+
[CheckoutPaymentStatusEnum.LOCKED]: CheckoutScreenEnum.LOCKED,
|
|
71
|
+
};
|
|
72
|
+
/** Which screen shows: the payment's status first, then what the payer is doing. */
|
|
73
|
+
export function screenOf(view, ui) {
|
|
74
|
+
if (view === undefined)
|
|
75
|
+
return CheckoutScreenEnum.LOADING;
|
|
76
|
+
if (view === null)
|
|
77
|
+
return CheckoutScreenEnum.INVALID;
|
|
78
|
+
if (ui.redirecting)
|
|
79
|
+
return CheckoutScreenEnum.REDIRECT;
|
|
80
|
+
const terminal = TERMINAL_SCREEN[view.status];
|
|
81
|
+
if (terminal)
|
|
82
|
+
return terminal;
|
|
83
|
+
const { attempt } = view;
|
|
84
|
+
// A card's fields page is still paying: the form stays until it says how it went.
|
|
85
|
+
const shown = attempt && attempt.reference !== ui.dismissed && !ui.cardPaying
|
|
86
|
+
? attempt.status
|
|
87
|
+
: null;
|
|
88
|
+
if (shown === AttemptStatusEnum.PENDING)
|
|
89
|
+
return CheckoutScreenEnum.WAITING;
|
|
90
|
+
// The app offers nothing any more: no form to fill, no other method to try.
|
|
91
|
+
if (view.methods.length === 0)
|
|
92
|
+
return CheckoutScreenEnum.NO_METHOD;
|
|
93
|
+
if (shown === AttemptStatusEnum.FAILED)
|
|
94
|
+
return CheckoutScreenEnum.FAILED;
|
|
95
|
+
return ui.submitting ? CheckoutScreenEnum.SUBMITTING : CheckoutScreenEnum.FORM;
|
|
96
|
+
}
|
|
97
|
+
/** The brand of a Mobile Money method, which a number's first digits can reveal. */
|
|
98
|
+
export function brandOf(method) {
|
|
99
|
+
return method.logos[0];
|
|
100
|
+
}
|
|
101
|
+
/** Everything the picked method needs is in: the card, or the number and its payment code. */
|
|
102
|
+
function readyToPay(method, ui, phone) {
|
|
103
|
+
if (method.flow === MethodFlowEnum.CARD_FIELDS)
|
|
104
|
+
return ui.cardComplete;
|
|
105
|
+
return (phone.valid &&
|
|
106
|
+
(method.flow !== MethodFlowEnum.OTP || OTP_PATTERN.test(ui.code)));
|
|
107
|
+
}
|
|
108
|
+
/** The tile of a method or an attempt: the generic card for cards, whatever networks they take. */
|
|
109
|
+
export function logoOf(of) {
|
|
110
|
+
return of.flow === MethodFlowEnum.CARD_FIELDS ? CARD_LOGO : of.logos[0];
|
|
111
|
+
}
|
|
112
|
+
/** Works out what the screens show from the payment, the page and the number typed. */
|
|
113
|
+
export function deriveCheckout(view, ui, phone) {
|
|
114
|
+
const wallets = view.methods.filter((method) => method.kind === MethodKindEnum.MOBILE_MONEY &&
|
|
115
|
+
method.country === ui.country);
|
|
116
|
+
const cards = view.methods.filter((method) => method.kind === MethodKindEnum.CARD);
|
|
117
|
+
const onCard = ui.tab === CheckoutTabEnum.CARD;
|
|
118
|
+
const picked = (onCard ? cards : wallets).find((method) => method.id === ui.method);
|
|
119
|
+
const selected = !ui.pickerOpen && picked?.state === MethodStateEnum.AVAILABLE
|
|
120
|
+
? picked
|
|
121
|
+
: null;
|
|
122
|
+
const detected = phone.valid
|
|
123
|
+
? (wallets.find((method) => method.state === MethodStateEnum.AVAILABLE &&
|
|
124
|
+
brandOf(method) === phone.operator) ?? null)
|
|
125
|
+
: null;
|
|
126
|
+
const failed = view.methods.find((method) => method.id === view.attempt?.method);
|
|
127
|
+
const suggestion = view.methods.find((method) => method.kind === MethodKindEnum.MOBILE_MONEY &&
|
|
128
|
+
method.state === MethodStateEnum.AVAILABLE &&
|
|
129
|
+
method.country === failed?.country &&
|
|
130
|
+
method.id !== failed.id) ?? null;
|
|
131
|
+
return {
|
|
132
|
+
screen: screenOf(view, ui),
|
|
133
|
+
countries: countriesOf(view.methods),
|
|
134
|
+
wallets,
|
|
135
|
+
cards,
|
|
136
|
+
selected,
|
|
137
|
+
canPay: !!selected &&
|
|
138
|
+
readyToPay(selected, ui, phone) &&
|
|
139
|
+
!ui.submitting &&
|
|
140
|
+
!ui.cardPaying,
|
|
141
|
+
// Only a Mobile Money method has a number to belong to another one.
|
|
142
|
+
mismatch: selected && !onCard && detected && detected.id !== selected.id
|
|
143
|
+
? detected
|
|
144
|
+
: null,
|
|
145
|
+
suggestion,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
/** Seconds left to approve an attempt started at `startedAt`. */
|
|
149
|
+
export function waitLeftOf(startedAt, now) {
|
|
150
|
+
const elapsed = Math.floor((now - startedAt) / 1000);
|
|
151
|
+
return Math.min(WAIT_SECONDS, Math.max(0, WAIT_SECONDS - elapsed));
|
|
152
|
+
}
|
|
153
|
+
/** How payers reach the app's support, when it set any. */
|
|
154
|
+
export function supportContactsOf(app) {
|
|
155
|
+
const contacts = [];
|
|
156
|
+
if (app.supportWhatsapp) {
|
|
157
|
+
contacts.push({
|
|
158
|
+
channel: SupportChannelEnum.WHATSAPP,
|
|
159
|
+
value: app.supportWhatsapp,
|
|
160
|
+
href: `https://wa.me/${app.supportWhatsapp.slice(1)}`,
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
if (app.supportEmail) {
|
|
164
|
+
contacts.push({
|
|
165
|
+
channel: SupportChannelEnum.EMAIL,
|
|
166
|
+
value: app.supportEmail,
|
|
167
|
+
href: `mailto:${app.supportEmail}`,
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
return contacts;
|
|
171
|
+
}
|
|
172
|
+
/** A pending test payment for the Branding tab's preview, branded as typed. */
|
|
173
|
+
export function previewView(app, methods) {
|
|
174
|
+
return {
|
|
175
|
+
id: 'preview',
|
|
176
|
+
status: CheckoutPaymentStatusEnum.PENDING,
|
|
177
|
+
test: false,
|
|
178
|
+
amount: PREVIEW_AMOUNT,
|
|
179
|
+
currency: CurrencyEnum.XAF,
|
|
180
|
+
orderRef: PREVIEW_ORDER_REF,
|
|
181
|
+
items: [],
|
|
182
|
+
returnUrl: '',
|
|
183
|
+
expiresAt: 0,
|
|
184
|
+
paidAt: null,
|
|
185
|
+
payer: { name: null, phone: null, email: null },
|
|
186
|
+
country: null,
|
|
187
|
+
language: null,
|
|
188
|
+
theme: null,
|
|
189
|
+
preferredMethod: null,
|
|
190
|
+
preferredTab: null,
|
|
191
|
+
attemptsLeft: 0,
|
|
192
|
+
maxAttempts: 0,
|
|
193
|
+
// The preview offers no card: nothing loads from the site.
|
|
194
|
+
siteUrl: '',
|
|
195
|
+
app: {
|
|
196
|
+
...app,
|
|
197
|
+
termsUrl: null,
|
|
198
|
+
privacyUrl: null,
|
|
199
|
+
supportEmail: null,
|
|
200
|
+
supportWhatsapp: null,
|
|
201
|
+
},
|
|
202
|
+
methods,
|
|
203
|
+
attempt: null,
|
|
204
|
+
};
|
|
205
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/** Domain enums the checkout core needs; `src/models` re-exports them for the rest of the app. */
|
|
2
|
+
/** Values are the file names in `public/logo/`, without the extension. */
|
|
3
|
+
/** A brand tile in `public/logo/`: one per brand a payer recognises (see `docs/design-system.md`, Logos). */
|
|
4
|
+
export declare enum ProviderLogoEnum {
|
|
5
|
+
ORANGE = "orange",
|
|
6
|
+
MTN = "mtn",
|
|
7
|
+
MOOV = "moov",
|
|
8
|
+
WAVE = "wave",
|
|
9
|
+
MIXX = "mixx",
|
|
10
|
+
VISA = "visa",
|
|
11
|
+
MASTERCARD = "mastercard",
|
|
12
|
+
PAYPAL = "paypal",
|
|
13
|
+
CARD = "card",
|
|
14
|
+
APPLE_PAY = "apple-pay",
|
|
15
|
+
GOOGLE_PAY = "google-pay"
|
|
16
|
+
}
|
|
17
|
+
/** Declared in the order the Providers page lists its country groups. */
|
|
18
|
+
export declare enum CountryEnum {
|
|
19
|
+
CAMEROON = "cm",
|
|
20
|
+
COTE_D_IVOIRE = "ci",
|
|
21
|
+
SENEGAL = "sn",
|
|
22
|
+
BENIN = "bj",
|
|
23
|
+
TOGO = "tg",
|
|
24
|
+
BURKINA_FASO = "bf",
|
|
25
|
+
INTERNATIONAL = "intl"
|
|
26
|
+
}
|
|
27
|
+
export declare enum CurrencyEnum {
|
|
28
|
+
XAF = "XAF",
|
|
29
|
+
XOF = "XOF",
|
|
30
|
+
EUR = "EUR",
|
|
31
|
+
USD = "USD"
|
|
32
|
+
}
|
|
33
|
+
/** Why an attempt failed. The first five are the payer's and count toward the lock. */
|
|
34
|
+
export declare enum FailureReasonEnum {
|
|
35
|
+
DECLINED = "declined",
|
|
36
|
+
INSUFFICIENT_FUNDS = "insufficient_funds",
|
|
37
|
+
/** No mobile money account on this number. */
|
|
38
|
+
NO_ACCOUNT = "no_account",
|
|
39
|
+
WRONG_PIN = "wrong_pin",
|
|
40
|
+
/** An OTP method: the payment code was wrong or had expired. */
|
|
41
|
+
WRONG_CODE = "wrong_code",
|
|
42
|
+
PROVIDER_ERROR = "provider_error",
|
|
43
|
+
TIMEOUT = "timeout",
|
|
44
|
+
MISMATCH = "mismatch"
|
|
45
|
+
}
|
|
46
|
+
/** GLOSSARY: Attempt. */
|
|
47
|
+
export declare enum AttemptStatusEnum {
|
|
48
|
+
PENDING = "pending",
|
|
49
|
+
SUCCEEDED = "succeeded",
|
|
50
|
+
FAILED = "failed"
|
|
51
|
+
}
|
|
52
|
+
/** Interface languages. Values are the URL prefix and the saved cookie value. */
|
|
53
|
+
export declare enum LangEnum {
|
|
54
|
+
FR = "fr",
|
|
55
|
+
EN = "en"
|
|
56
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/** Domain enums the checkout core needs; `src/models` re-exports them for the rest of the app. */
|
|
2
|
+
/** Values are the file names in `public/logo/`, without the extension. */
|
|
3
|
+
/** A brand tile in `public/logo/`: one per brand a payer recognises (see `docs/design-system.md`, Logos). */
|
|
4
|
+
export var ProviderLogoEnum;
|
|
5
|
+
(function (ProviderLogoEnum) {
|
|
6
|
+
ProviderLogoEnum["ORANGE"] = "orange";
|
|
7
|
+
ProviderLogoEnum["MTN"] = "mtn";
|
|
8
|
+
ProviderLogoEnum["MOOV"] = "moov";
|
|
9
|
+
ProviderLogoEnum["WAVE"] = "wave";
|
|
10
|
+
ProviderLogoEnum["MIXX"] = "mixx";
|
|
11
|
+
ProviderLogoEnum["VISA"] = "visa";
|
|
12
|
+
ProviderLogoEnum["MASTERCARD"] = "mastercard";
|
|
13
|
+
ProviderLogoEnum["PAYPAL"] = "paypal";
|
|
14
|
+
ProviderLogoEnum["CARD"] = "card";
|
|
15
|
+
ProviderLogoEnum["APPLE_PAY"] = "apple-pay";
|
|
16
|
+
ProviderLogoEnum["GOOGLE_PAY"] = "google-pay";
|
|
17
|
+
})(ProviderLogoEnum || (ProviderLogoEnum = {}));
|
|
18
|
+
/** Declared in the order the Providers page lists its country groups. */
|
|
19
|
+
export var CountryEnum;
|
|
20
|
+
(function (CountryEnum) {
|
|
21
|
+
CountryEnum["CAMEROON"] = "cm";
|
|
22
|
+
CountryEnum["COTE_D_IVOIRE"] = "ci";
|
|
23
|
+
CountryEnum["SENEGAL"] = "sn";
|
|
24
|
+
CountryEnum["BENIN"] = "bj";
|
|
25
|
+
CountryEnum["TOGO"] = "tg";
|
|
26
|
+
CountryEnum["BURKINA_FASO"] = "bf";
|
|
27
|
+
CountryEnum["INTERNATIONAL"] = "intl";
|
|
28
|
+
})(CountryEnum || (CountryEnum = {}));
|
|
29
|
+
export var CurrencyEnum;
|
|
30
|
+
(function (CurrencyEnum) {
|
|
31
|
+
CurrencyEnum["XAF"] = "XAF";
|
|
32
|
+
CurrencyEnum["XOF"] = "XOF";
|
|
33
|
+
CurrencyEnum["EUR"] = "EUR";
|
|
34
|
+
CurrencyEnum["USD"] = "USD";
|
|
35
|
+
})(CurrencyEnum || (CurrencyEnum = {}));
|
|
36
|
+
/** Why an attempt failed. The first five are the payer's and count toward the lock. */
|
|
37
|
+
export var FailureReasonEnum;
|
|
38
|
+
(function (FailureReasonEnum) {
|
|
39
|
+
FailureReasonEnum["DECLINED"] = "declined";
|
|
40
|
+
FailureReasonEnum["INSUFFICIENT_FUNDS"] = "insufficient_funds";
|
|
41
|
+
/** No mobile money account on this number. */
|
|
42
|
+
FailureReasonEnum["NO_ACCOUNT"] = "no_account";
|
|
43
|
+
FailureReasonEnum["WRONG_PIN"] = "wrong_pin";
|
|
44
|
+
/** An OTP method: the payment code was wrong or had expired. */
|
|
45
|
+
FailureReasonEnum["WRONG_CODE"] = "wrong_code";
|
|
46
|
+
FailureReasonEnum["PROVIDER_ERROR"] = "provider_error";
|
|
47
|
+
FailureReasonEnum["TIMEOUT"] = "timeout";
|
|
48
|
+
FailureReasonEnum["MISMATCH"] = "mismatch";
|
|
49
|
+
})(FailureReasonEnum || (FailureReasonEnum = {}));
|
|
50
|
+
/** GLOSSARY: Attempt. */
|
|
51
|
+
export var AttemptStatusEnum;
|
|
52
|
+
(function (AttemptStatusEnum) {
|
|
53
|
+
AttemptStatusEnum["PENDING"] = "pending";
|
|
54
|
+
AttemptStatusEnum["SUCCEEDED"] = "succeeded";
|
|
55
|
+
AttemptStatusEnum["FAILED"] = "failed";
|
|
56
|
+
})(AttemptStatusEnum || (AttemptStatusEnum = {}));
|
|
57
|
+
/** Interface languages. Values are the URL prefix and the saved cookie value. */
|
|
58
|
+
export var LangEnum;
|
|
59
|
+
(function (LangEnum) {
|
|
60
|
+
LangEnum["FR"] = "fr";
|
|
61
|
+
LangEnum["EN"] = "en";
|
|
62
|
+
})(LangEnum || (LangEnum = {}));
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { BrissotoPayProviderProps } from '../models/sheet.interface.js';
|
|
2
|
+
/**
|
|
3
|
+
* Around the app: holds the connection to Brissoto Pay and the one payment
|
|
4
|
+
* sheet `usePaymentSheet().presentPayment` opens.
|
|
5
|
+
*/
|
|
6
|
+
export declare function BrissotoPayProvider({ url, children, }: BrissotoPayProviderProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { ConvexReactClient } from 'convex/react';
|
|
3
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
4
|
+
import { PaymentSheetContext } from "../hooks/use-payment-sheet.js";
|
|
5
|
+
import { DEFAULT_URL } from "../models/sheet.constants.js";
|
|
6
|
+
import { convexCheckoutCalls } from "../services/checkout.service.js";
|
|
7
|
+
import { PaymentSheetModal } from "./PaymentSheetModal.js";
|
|
8
|
+
/**
|
|
9
|
+
* Around the app: holds the connection to Brissoto Pay and the one payment
|
|
10
|
+
* sheet `usePaymentSheet().presentPayment` opens.
|
|
11
|
+
*/
|
|
12
|
+
export function BrissotoPayProvider({ url = DEFAULT_URL, children, }) {
|
|
13
|
+
const [client, setClient] = useState(null);
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
const next = new ConvexReactClient(url, { unsavedChangesWarning: false });
|
|
16
|
+
setClient(next);
|
|
17
|
+
return () => void next.close();
|
|
18
|
+
}, [url]);
|
|
19
|
+
const calls = useMemo(() => client && convexCheckoutCalls(client), [client]);
|
|
20
|
+
const [request, setRequest] = useState(null);
|
|
21
|
+
/** Sheets opened, so each gets a fresh one. */
|
|
22
|
+
const [count, setCount] = useState(0);
|
|
23
|
+
const busy = useRef(false);
|
|
24
|
+
const presentPayment = useCallback(({ paymentId }) => {
|
|
25
|
+
if (busy.current) {
|
|
26
|
+
return Promise.reject(new Error('A payment sheet is already open.'));
|
|
27
|
+
}
|
|
28
|
+
busy.current = true;
|
|
29
|
+
return new Promise((resolve) => {
|
|
30
|
+
setCount((previous) => previous + 1);
|
|
31
|
+
setRequest({ paymentId, open: true, resolve });
|
|
32
|
+
});
|
|
33
|
+
}, []);
|
|
34
|
+
const sheet = useMemo(() => ({ presentPayment }), [presentPayment]);
|
|
35
|
+
const close = (closing, result) => {
|
|
36
|
+
busy.current = false;
|
|
37
|
+
setRequest({ ...closing, open: false });
|
|
38
|
+
closing.resolve(result);
|
|
39
|
+
};
|
|
40
|
+
return (_jsxs(PaymentSheetContext, { value: sheet, children: [children, calls && request && (_jsx(PaymentSheetModal, { calls: calls, request: request, onClose: (result) => close(request, result) }, count))] }));
|
|
41
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { PaymentSheetModalProps } from '../models/sheet.interface.js';
|
|
2
|
+
/**
|
|
3
|
+
* One payment in a bottom sheet over the dimmed app, as tall as what it
|
|
4
|
+
* shows, in the payment's language and theme, else the device's. Close,
|
|
5
|
+
* a swipe down, a tap on the app behind, back or escape leave it; leaving
|
|
6
|
+
* while a prompt waits on the phone asks first, and resolves `pending`.
|
|
7
|
+
*/
|
|
8
|
+
export declare function PaymentSheetModal({ calls, request, onClose, }: PaymentSheetModalProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useFonts } from 'expo-font';
|
|
3
|
+
import { useMemo, useState } from 'react';
|
|
4
|
+
import { Alert, Animated, KeyboardAvoidingView, Modal, Pressable, StyleSheet, View, useColorScheme, } from 'react-native';
|
|
5
|
+
import { IntlProvider, createTranslator } from 'use-intl';
|
|
6
|
+
import { CHECKOUT_COLORS, INVALID_BRAND_DARK, INVALID_BRAND_LIGHT, } from "../checkout-core/models/checkout.constants.js";
|
|
7
|
+
import { CheckoutThemeEnum } from "../checkout-core/models/checkout.enum.js";
|
|
8
|
+
import { useSheetMotion } from "../hooks/use-sheet-motion.js";
|
|
9
|
+
import { useWatchedPayment } from "../hooks/use-watched-payment.js";
|
|
10
|
+
import { SheetThemeContext } from "../hooks/use-sheet-theme.js";
|
|
11
|
+
import { FIGTREE_FILES, MESSAGES, TIME_ZONE, } from "../models/sheet.constants.js";
|
|
12
|
+
import { sheetLanguage, sheetResult, sheetTheme, } from "../models/sheet.utils.js";
|
|
13
|
+
import { SheetContent } from "./SheetContent.js";
|
|
14
|
+
/**
|
|
15
|
+
* One payment in a bottom sheet over the dimmed app, as tall as what it
|
|
16
|
+
* shows, in the payment's language and theme, else the device's. Close,
|
|
17
|
+
* a swipe down, a tap on the app behind, back or escape leave it; leaving
|
|
18
|
+
* while a prompt waits on the phone asks first, and resolves `pending`.
|
|
19
|
+
*/
|
|
20
|
+
export function PaymentSheetModal({ calls, request, onClose, }) {
|
|
21
|
+
const watched = useWatchedPayment(calls, request.open ? request.paymentId : null);
|
|
22
|
+
const { view } = watched;
|
|
23
|
+
const scheme = useColorScheme();
|
|
24
|
+
const [deviceLocale] = useState(() => Intl.DateTimeFormat().resolvedOptions().locale);
|
|
25
|
+
const [fontsLoaded, fontsError] = useFonts(FIGTREE_FILES);
|
|
26
|
+
const lang = sheetLanguage(view?.language ?? null, deviceLocale);
|
|
27
|
+
const dark = sheetTheme(view?.theme ?? null, scheme) === CheckoutThemeEnum.DARK;
|
|
28
|
+
const color = view?.app.color;
|
|
29
|
+
const theme = useMemo(() => ({
|
|
30
|
+
colors: CHECKOUT_COLORS[dark ? CheckoutThemeEnum.DARK : CheckoutThemeEnum.LIGHT],
|
|
31
|
+
brand: color ?? (dark ? INVALID_BRAND_DARK : INVALID_BRAND_LIGHT),
|
|
32
|
+
dark,
|
|
33
|
+
}), [dark, color]);
|
|
34
|
+
const close = () => {
|
|
35
|
+
const result = sheetResult(view, watched.failed);
|
|
36
|
+
if (result.status !== 'pending') {
|
|
37
|
+
onClose(result);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
// A prompt still waits: approved later, the payment goes through.
|
|
41
|
+
const t = createTranslator({
|
|
42
|
+
locale: lang,
|
|
43
|
+
messages: MESSAGES[lang],
|
|
44
|
+
namespace: 'checkout.sheet',
|
|
45
|
+
});
|
|
46
|
+
Alert.alert(t('close_title'), t('close_body'), [
|
|
47
|
+
{ text: t('keep_waiting'), style: 'cancel' },
|
|
48
|
+
{
|
|
49
|
+
text: MESSAGES[lang].checkout.close,
|
|
50
|
+
style: 'destructive',
|
|
51
|
+
onPress: () => onClose(result),
|
|
52
|
+
},
|
|
53
|
+
]);
|
|
54
|
+
};
|
|
55
|
+
const motion = useSheetMotion(request.open, close);
|
|
56
|
+
return (_jsx(Modal, { testID: "payment-sheet-modal", visible: motion.shown, transparent: true, animationType: "none", statusBarTranslucent: true, onRequestClose: close, children: _jsxs(View, { style: [styles.screen, !request.open && styles.leaving], children: [_jsx(Pressable, { testID: "payment-sheet-backdrop", accessible: false, onPress: close, style: StyleSheet.absoluteFill, children: _jsx(Animated.View, { style: [
|
|
57
|
+
StyleSheet.absoluteFill,
|
|
58
|
+
styles.dim,
|
|
59
|
+
{ opacity: motion.dim },
|
|
60
|
+
] }) }), _jsx(Animated.View, { style: [
|
|
61
|
+
styles.stage,
|
|
62
|
+
{ transform: [{ translateY: motion.translateY }] },
|
|
63
|
+
], children: _jsx(View, { style: [styles.stage, { paddingTop: motion.topGap }], children: _jsxs(KeyboardAvoidingView, { behavior: "padding", style: styles.sheet, children: [_jsx(View, { testID: "payment-sheet-floor", style: [
|
|
64
|
+
styles.floor,
|
|
65
|
+
{ backgroundColor: theme.colors.surface },
|
|
66
|
+
] }), _jsx(IntlProvider, { locale: lang, messages: MESSAGES[lang], timeZone: TIME_ZONE, children: _jsx(SheetThemeContext, { value: theme, children: _jsx(SheetContent, { calls: calls, watched: watched, fontsReady: fontsLoaded || fontsError !== null, deviceLocale: deviceLocale, onClose: close, dragHandlers: motion.dragHandlers }) }) })] }) }) })] }) }));
|
|
67
|
+
}
|
|
68
|
+
const styles = StyleSheet.create({
|
|
69
|
+
screen: { flex: 1 },
|
|
70
|
+
leaving: { pointerEvents: 'none' },
|
|
71
|
+
dim: { backgroundColor: 'black' },
|
|
72
|
+
// The app's dim shows through around the sheet.
|
|
73
|
+
stage: { flex: 1, justifyContent: 'flex-end', pointerEvents: 'box-none' },
|
|
74
|
+
sheet: { flexShrink: 1 },
|
|
75
|
+
// Under the sheet's rounded top, down to the screen's bottom.
|
|
76
|
+
floor: { position: 'absolute', top: 14, right: 0, bottom: 0, left: 0 },
|
|
77
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { StyleSheet, View } from 'react-native';
|
|
3
|
+
import { useSheetTheme } from "../hooks/use-sheet-theme.js";
|
|
4
|
+
/** A rounded block standing in for what is loading, sized like it. */
|
|
5
|
+
export function SheetBone({ width, height, radius = 6 }) {
|
|
6
|
+
const { colors } = useSheetTheme();
|
|
7
|
+
return (_jsx(View, { style: [
|
|
8
|
+
styles.bone,
|
|
9
|
+
{ width, height, borderRadius: radius, backgroundColor: colors.fill },
|
|
10
|
+
] }));
|
|
11
|
+
}
|
|
12
|
+
const styles = StyleSheet.create({ bone: { flex: 0 } });
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { SheetButtonProps } from '../models/sheet.interface.js';
|
|
2
|
+
/** The sheet's buttons: filled in the app colour for the main action, outlined for the rest. */
|
|
3
|
+
export declare function SheetButton({ label, onPress, variant, busy, disabled, inactive, }: SheetButtonProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { ActivityIndicator, StyleSheet, TouchableOpacity } from 'react-native';
|
|
3
|
+
import { CheckoutButtonVariantEnum } from "../checkout-core/models/checkout.enum.js";
|
|
4
|
+
import { useSheetTheme } from "../hooks/use-sheet-theme.js";
|
|
5
|
+
import { FigtreeEnum } from "../models/sheet.enum.js";
|
|
6
|
+
import { SheetText } from "./SheetText.js";
|
|
7
|
+
/** The sheet's buttons: filled in the app colour for the main action, outlined for the rest. */
|
|
8
|
+
export function SheetButton({ label, onPress, variant = CheckoutButtonVariantEnum.PRIMARY, busy = false, disabled = false, inactive = false, }) {
|
|
9
|
+
const { colors, brand } = useSheetTheme();
|
|
10
|
+
const primary = variant === CheckoutButtonVariantEnum.PRIMARY;
|
|
11
|
+
const ink = primary ? 'white' : colors.ink;
|
|
12
|
+
const off = disabled || busy;
|
|
13
|
+
return (_jsxs(TouchableOpacity, { onPress: onPress, disabled: off, activeOpacity: 0.8, accessibilityRole: "button", accessibilityState: { disabled: off, busy }, style: [
|
|
14
|
+
styles.button,
|
|
15
|
+
primary
|
|
16
|
+
? { backgroundColor: brand }
|
|
17
|
+
: { borderWidth: 1, borderColor: colors.line3 },
|
|
18
|
+
(disabled || inactive) && styles.disabled,
|
|
19
|
+
], children: [busy && _jsx(ActivityIndicator, { color: ink }), _jsx(SheetText, { weight: FigtreeEnum.BOLD, size: 16, color: ink, style: styles.label, children: label })] }));
|
|
20
|
+
}
|
|
21
|
+
const styles = StyleSheet.create({
|
|
22
|
+
button: {
|
|
23
|
+
minHeight: 50,
|
|
24
|
+
borderRadius: 14,
|
|
25
|
+
flexDirection: 'row',
|
|
26
|
+
alignItems: 'center',
|
|
27
|
+
justifyContent: 'center',
|
|
28
|
+
gap: 10,
|
|
29
|
+
paddingHorizontal: 16,
|
|
30
|
+
},
|
|
31
|
+
label: { textAlign: 'center', fontVariant: ['tabular-nums'] },
|
|
32
|
+
disabled: { opacity: 0.5 },
|
|
33
|
+
});
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { SheetCardFieldsProps } from '../models/sheet.interface.js';
|
|
2
|
+
/**
|
|
3
|
+
* A card method: its provider's card fields page, and only it, in a WebView
|
|
4
|
+
* sized to it. The card is typed there and goes from there to the provider;
|
|
5
|
+
* the sheet only hears, from that page's origin, how it went. The labels and
|
|
6
|
+
* bones stand in until the page says how tall it is.
|
|
7
|
+
*/
|
|
8
|
+
export declare function SheetCardFields({ core, bridge }: SheetCardFieldsProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useMemo } from 'react';
|
|
3
|
+
import { StyleSheet, View } from 'react-native';
|
|
4
|
+
import { WebView } from 'react-native-webview';
|
|
5
|
+
import { useLocale, useTranslations } from 'use-intl';
|
|
6
|
+
import { CARD_FIELDS_HEIGHT } from "../checkout-core/models/checkout.constants.js";
|
|
7
|
+
import { useSheetTheme } from "../hooks/use-sheet-theme.js";
|
|
8
|
+
import { FigtreeEnum } from "../models/sheet.enum.js";
|
|
9
|
+
import { cardFieldsBridge, cardFieldsMessageOf, cardFieldsUrl, isCardFieldsPage, originOf, } from "../models/sheet.utils.js";
|
|
10
|
+
import { SheetBone } from "./SheetBone.js";
|
|
11
|
+
import { SheetText } from "./SheetText.js";
|
|
12
|
+
/** Every load goes through `mayLoad`: none is handed to the phone's browser. */
|
|
13
|
+
const ANY_ORIGIN = ['*'];
|
|
14
|
+
/**
|
|
15
|
+
* A card method: its provider's card fields page, and only it, in a WebView
|
|
16
|
+
* sized to it. The card is typed there and goes from there to the provider;
|
|
17
|
+
* the sheet only hears, from that page's origin, how it went. The labels and
|
|
18
|
+
* bones stand in until the page says how tall it is.
|
|
19
|
+
*/
|
|
20
|
+
export function SheetCardFields({ core, bridge }) {
|
|
21
|
+
const t = useTranslations('checkout.card_fields');
|
|
22
|
+
const lang = useLocale();
|
|
23
|
+
const { colors, dark } = useSheetTheme();
|
|
24
|
+
const { view, derived, cardHeight } = core;
|
|
25
|
+
// Shown only while a card method is picked, and they all come with their page.
|
|
26
|
+
const method = derived.selected;
|
|
27
|
+
const uri = cardFieldsUrl({
|
|
28
|
+
siteUrl: view.siteUrl,
|
|
29
|
+
path: method.cardFieldsPath,
|
|
30
|
+
paymentId: view.id,
|
|
31
|
+
method: method.id,
|
|
32
|
+
color: view.app.color,
|
|
33
|
+
dark,
|
|
34
|
+
lang,
|
|
35
|
+
});
|
|
36
|
+
const source = useMemo(() => ({ uri }), [uri]);
|
|
37
|
+
const loaded = cardHeight !== null;
|
|
38
|
+
// The page itself, in either language; the provider's own frames inside it.
|
|
39
|
+
const mayLoad = (request) => request.isTopFrame === false || isCardFieldsPage(request.url, uri);
|
|
40
|
+
// Only what comes from the card fields page's origin reaches the core.
|
|
41
|
+
const hear = ({ nativeEvent }) => {
|
|
42
|
+
if (originOf(nativeEvent.url) !== originOf(uri))
|
|
43
|
+
return;
|
|
44
|
+
const message = cardFieldsMessageOf(nativeEvent.data);
|
|
45
|
+
if (message)
|
|
46
|
+
bridge.receive(message);
|
|
47
|
+
};
|
|
48
|
+
const label = (text) => (_jsx(SheetText, { weight: FigtreeEnum.BOLD, size: 13, style: styles.label, children: text }));
|
|
49
|
+
return (_jsxs(View, { style: styles.section, children: [!loaded && (_jsxs(View, { style: styles.bones, children: [_jsxs(View, { children: [label(t('number')), _jsx(SheetBone, { width: "100%", height: 48, radius: 13 })] }), _jsxs(View, { style: styles.row, children: [_jsxs(View, { style: styles.grow, children: [label(t('expiry')), _jsx(SheetBone, { width: "100%", height: 48, radius: 13 })] }), _jsxs(View, { style: styles.grow, children: [label(t('cvc')), _jsx(SheetBone, { width: "100%", height: 48, radius: 13 })] })] })] })), _jsx(WebView, { ref: bridge.page, source: source, accessibilityLabel: t('title'), originWhitelist: ANY_ORIGIN, onShouldStartLoadWithRequest: mayLoad, injectedJavaScriptBeforeContentLoaded: cardFieldsBridge(), onMessage: hear, setSupportMultipleWindows: false, scrollEnabled: false, style: [
|
|
50
|
+
styles.page,
|
|
51
|
+
{
|
|
52
|
+
height: cardHeight ?? CARD_FIELDS_HEIGHT,
|
|
53
|
+
backgroundColor: colors.surface,
|
|
54
|
+
},
|
|
55
|
+
!loaded && styles.hidden,
|
|
56
|
+
] })] }));
|
|
57
|
+
}
|
|
58
|
+
const styles = StyleSheet.create({
|
|
59
|
+
section: { marginTop: 14 },
|
|
60
|
+
bones: { gap: 14, height: CARD_FIELDS_HEIGHT },
|
|
61
|
+
label: { marginBottom: 8 },
|
|
62
|
+
row: { flexDirection: 'row', gap: 12 },
|
|
63
|
+
grow: { flex: 1 },
|
|
64
|
+
page: { width: '100%' },
|
|
65
|
+
hidden: { position: 'absolute', top: 0, left: 0, right: 0, opacity: 0 },
|
|
66
|
+
});
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { ChosenMethodProps } from '../models/sheet.interface.js';
|
|
2
|
+
/** The method picked, on one row, with Change back to the list: as on the web checkout. */
|
|
3
|
+
export declare function SheetChosenMethod({ logo, name, sub, onChange, }: ChosenMethodProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { StyleSheet, TouchableOpacity, View } from 'react-native';
|
|
3
|
+
import { useTranslations } from 'use-intl';
|
|
4
|
+
import { LOGO_SIZE } from "../checkout-core/models/checkout.constants.js";
|
|
5
|
+
import { useSheetTheme } from "../hooks/use-sheet-theme.js";
|
|
6
|
+
import { FigtreeEnum } from "../models/sheet.enum.js";
|
|
7
|
+
import { SheetLogo } from "./SheetLogo.js";
|
|
8
|
+
import { SheetText } from "./SheetText.js";
|
|
9
|
+
/** The method picked, on one row, with Change back to the list: as on the web checkout. */
|
|
10
|
+
export function SheetChosenMethod({ logo, name, sub, onChange, }) {
|
|
11
|
+
const t = useTranslations('checkout');
|
|
12
|
+
const { colors, brand } = useSheetTheme();
|
|
13
|
+
return (_jsxs(View, { testID: "chosen-method", style: [
|
|
14
|
+
styles.row,
|
|
15
|
+
{ borderColor: colors.line2, backgroundColor: colors.surface },
|
|
16
|
+
], children: [_jsxs(View, { children: [_jsx(SheetLogo, { logo: logo, size: LOGO_SIZE.ROW }), _jsx(View, { style: [
|
|
17
|
+
styles.badge,
|
|
18
|
+
{ borderColor: colors.surface, backgroundColor: brand },
|
|
19
|
+
], children: _jsx(View, { style: styles.check }) })] }), _jsxs(View, { style: styles.text, children: [_jsx(SheetText, { weight: FigtreeEnum.SEMI_BOLD, size: 14, numberOfLines: 1, children: name }), _jsx(SheetText, { size: 12, color: colors.muted, numberOfLines: 1, children: sub })] }), _jsx(TouchableOpacity, { onPress: onChange, activeOpacity: 0.7, accessibilityRole: "button", accessibilityLabel: t('change'), hitSlop: 6, style: styles.change, children: _jsx(SheetText, { weight: FigtreeEnum.BOLD, size: 12, color: brand, children: t('change') }) })] }));
|
|
20
|
+
}
|
|
21
|
+
const styles = StyleSheet.create({
|
|
22
|
+
row: {
|
|
23
|
+
flexDirection: 'row',
|
|
24
|
+
alignItems: 'center',
|
|
25
|
+
gap: 12,
|
|
26
|
+
height: 48,
|
|
27
|
+
marginTop: 12,
|
|
28
|
+
borderWidth: 1,
|
|
29
|
+
borderRadius: 12,
|
|
30
|
+
paddingLeft: 8,
|
|
31
|
+
paddingRight: 6,
|
|
32
|
+
},
|
|
33
|
+
badge: {
|
|
34
|
+
position: 'absolute',
|
|
35
|
+
right: -4,
|
|
36
|
+
bottom: -4,
|
|
37
|
+
width: 16,
|
|
38
|
+
height: 16,
|
|
39
|
+
alignItems: 'center',
|
|
40
|
+
justifyContent: 'center',
|
|
41
|
+
borderWidth: 2,
|
|
42
|
+
borderRadius: 8,
|
|
43
|
+
},
|
|
44
|
+
check: {
|
|
45
|
+
width: 3.5,
|
|
46
|
+
height: 6.5,
|
|
47
|
+
marginTop: -1,
|
|
48
|
+
borderColor: 'white',
|
|
49
|
+
borderRightWidth: 1.6,
|
|
50
|
+
borderBottomWidth: 1.6,
|
|
51
|
+
transform: [{ rotate: '45deg' }],
|
|
52
|
+
},
|
|
53
|
+
text: { flex: 1, minWidth: 0 },
|
|
54
|
+
change: {
|
|
55
|
+
height: 32,
|
|
56
|
+
justifyContent: 'center',
|
|
57
|
+
borderRadius: 8,
|
|
58
|
+
paddingHorizontal: 10,
|
|
59
|
+
},
|
|
60
|
+
});
|