@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.
Files changed (122) 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 +253 -0
  27. package/dist/checkout-core/messages/en.js +243 -0
  28. package/dist/checkout-core/messages/fr.d.ts +4 -0
  29. package/dist/checkout-core/messages/fr.js +245 -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 +133 -0
  35. package/dist/checkout-core/models/checkout.enum.js +150 -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 +77 -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/SheetChosenMethod.d.ts +3 -0
  57. package/dist/components/SheetChosenMethod.js +60 -0
  58. package/dist/components/SheetCodeField.d.ts +8 -0
  59. package/dist/components/SheetCodeField.js +97 -0
  60. package/dist/components/SheetContent.d.ts +3 -0
  61. package/dist/components/SheetContent.js +106 -0
  62. package/dist/components/SheetFailed.d.ts +3 -0
  63. package/dist/components/SheetFailed.js +43 -0
  64. package/dist/components/SheetForm.d.ts +8 -0
  65. package/dist/components/SheetForm.js +101 -0
  66. package/dist/components/SheetHeader.d.ts +6 -0
  67. package/dist/components/SheetHeader.js +69 -0
  68. package/dist/components/SheetIcon.d.ts +3 -0
  69. package/dist/components/SheetIcon.js +70 -0
  70. package/dist/components/SheetLayout.d.ts +9 -0
  71. package/dist/components/SheetLayout.js +48 -0
  72. package/dist/components/SheetLogo.d.ts +3 -0
  73. package/dist/components/SheetLogo.js +20 -0
  74. package/dist/components/SheetMessage.d.ts +3 -0
  75. package/dist/components/SheetMessage.js +15 -0
  76. package/dist/components/SheetMethodCard.d.ts +3 -0
  77. package/dist/components/SheetMethodCard.js +32 -0
  78. package/dist/components/SheetPhoneField.d.ts +3 -0
  79. package/dist/components/SheetPhoneField.js +58 -0
  80. package/dist/components/SheetRow.d.ts +3 -0
  81. package/dist/components/SheetRow.js +28 -0
  82. package/dist/components/SheetSimulator.d.ts +6 -0
  83. package/dist/components/SheetSimulator.js +39 -0
  84. package/dist/components/SheetSkeleton.d.ts +5 -0
  85. package/dist/components/SheetSkeleton.js +23 -0
  86. package/dist/components/SheetSuccess.d.ts +3 -0
  87. package/dist/components/SheetSuccess.js +51 -0
  88. package/dist/components/SheetSummary.d.ts +3 -0
  89. package/dist/components/SheetSummary.js +32 -0
  90. package/dist/components/SheetTerminal.d.ts +3 -0
  91. package/dist/components/SheetTerminal.js +21 -0
  92. package/dist/components/SheetText.d.ts +5 -0
  93. package/dist/components/SheetText.js +25 -0
  94. package/dist/components/SheetToast.d.ts +3 -0
  95. package/dist/components/SheetToast.js +24 -0
  96. package/dist/components/SheetWaiting.d.ts +7 -0
  97. package/dist/components/SheetWaiting.js +77 -0
  98. package/dist/hooks/use-native-platform.d.ts +10 -0
  99. package/dist/hooks/use-native-platform.js +47 -0
  100. package/dist/hooks/use-payment-sheet.d.ts +4 -0
  101. package/dist/hooks/use-payment-sheet.js +10 -0
  102. package/dist/hooks/use-sheet-motion.d.ts +7 -0
  103. package/dist/hooks/use-sheet-motion.js +62 -0
  104. package/dist/hooks/use-sheet-resize.d.ts +6 -0
  105. package/dist/hooks/use-sheet-resize.js +15 -0
  106. package/dist/hooks/use-sheet-theme.d.ts +4 -0
  107. package/dist/hooks/use-sheet-theme.js +12 -0
  108. package/dist/hooks/use-watched-payment.d.ts +6 -0
  109. package/dist/hooks/use-watched-payment.js +17 -0
  110. package/dist/index.d.ts +3 -0
  111. package/dist/index.js +2 -0
  112. package/dist/models/sheet.constants.d.ts +39 -0
  113. package/dist/models/sheet.constants.js +78 -0
  114. package/dist/models/sheet.enum.d.ts +16 -0
  115. package/dist/models/sheet.enum.js +18 -0
  116. package/dist/models/sheet.interface.d.ts +232 -0
  117. package/dist/models/sheet.interface.js +1 -0
  118. package/dist/models/sheet.utils.d.ts +43 -0
  119. package/dist/models/sheet.utils.js +132 -0
  120. package/dist/services/checkout.service.d.ts +7 -0
  121. package/dist/services/checkout.service.js +39 -0
  122. package/package.json +58 -4
@@ -0,0 +1,32 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { StyleSheet, TouchableOpacity, View } from 'react-native';
3
+ import { LOGO_SIZE } from "../checkout-core/models/checkout.constants.js";
4
+ import { useSheetTheme } from "../hooks/use-sheet-theme.js";
5
+ import { FigtreeEnum } from "../models/sheet.enum.js";
6
+ import { SheetLogo } from "./SheetLogo.js";
7
+ import { SheetText } from "./SheetText.js";
8
+ /** One method of the grid: logo and radio dot, then its name and network, or why it is greyed. Picked, it becomes the chosen row. */
9
+ export function SheetMethodCard({ logo, name, sub, muted, onSelect, }) {
10
+ const { colors } = useSheetTheme();
11
+ return (_jsxs(TouchableOpacity, { onPress: onSelect, disabled: muted, activeOpacity: 0.7, accessibilityRole: "radio", accessibilityLabel: `${name}, ${sub}`, accessibilityState: { checked: false, disabled: muted }, style: [styles.card, { borderColor: colors.line2 }], children: [_jsxs(View, { style: styles.top, children: [_jsx(SheetLogo, { logo: logo, size: LOGO_SIZE.ROW, muted: muted }), _jsx(View, { style: [
12
+ styles.radio,
13
+ { borderColor: muted ? colors.line2 : colors.line4 },
14
+ ] })] }), _jsx(SheetText, { weight: FigtreeEnum.BOLD, size: 15, numberOfLines: 1, color: muted ? colors.muted : colors.ink, children: name }), _jsx(SheetText, { size: 12.5, numberOfLines: 1, color: muted ? colors.warnInk : colors.muted, children: sub })] }));
15
+ }
16
+ const styles = StyleSheet.create({
17
+ card: {
18
+ flex: 1,
19
+ minWidth: 0,
20
+ borderWidth: 1,
21
+ borderRadius: 14,
22
+ padding: 12,
23
+ gap: 2,
24
+ },
25
+ top: {
26
+ flexDirection: 'row',
27
+ alignItems: 'flex-start',
28
+ justifyContent: 'space-between',
29
+ marginBottom: 10,
30
+ },
31
+ radio: { width: 20, height: 20, borderRadius: 10, borderWidth: 1.5 },
32
+ });
@@ -0,0 +1,3 @@
1
+ import type { SheetPhoneFieldProps } from '../models/sheet.interface.js';
2
+ /** The Mobile Money number on the phone keyboard: the country's dial code, the number grouped as typed, its check. */
3
+ export declare function SheetPhoneField({ core, phoneRef }: SheetPhoneFieldProps): import("react").JSX.Element;
@@ -0,0 +1,58 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Platform, Pressable, StyleSheet, View } from 'react-native';
3
+ import { useTranslations } from 'use-intl';
4
+ import { useSheetTheme } from "../hooks/use-sheet-theme.js";
5
+ import { FigtreeEnum } from "../models/sheet.enum.js";
6
+ import { flagOf } from "../models/sheet.utils.js";
7
+ import { SheetLogo } from "./SheetLogo.js";
8
+ import { SheetInput, SheetText } from "./SheetText.js";
9
+ /** The Mobile Money number on the phone keyboard: the country's dial code, the number grouped as typed, its check. */
10
+ export function SheetPhoneField({ core, phoneRef }) {
11
+ const t = useTranslations('checkout');
12
+ const { colors, brand } = useSheetTheme();
13
+ const { ui, derived, phone, actions } = core;
14
+ const { error } = phone.status;
15
+ const { mismatch, selected } = derived;
16
+ return (_jsxs(View, { style: styles.section, children: [_jsx(SheetText, { weight: FigtreeEnum.BOLD, style: styles.label, children: t('phone_label') }), _jsxs(View, { testID: "phone-field", style: [
17
+ styles.field,
18
+ {
19
+ borderColor: error
20
+ ? colors.danger
21
+ : phone.focused
22
+ ? brand
23
+ : colors.line3,
24
+ },
25
+ ], children: [_jsxs(View, { style: [styles.dial, { borderColor: colors.line2 }], children: [_jsx(SheetText, { size: 16, children: flagOf(ui.country) }), _jsx(SheetText, { weight: FigtreeEnum.SEMI_BOLD, size: 16, children: `+${phone.dial}` })] }), _jsx(SheetInput, { ref: phoneRef, accessibilityLabel: t('phone_label'), keyboardType: "phone-pad", textContentType: "telephoneNumber", autoComplete: "tel",
26
+ // iOS's number keypads have no Go key: asking for one adds a bar above them.
27
+ returnKeyType: Platform.OS === 'android' ? 'go' : undefined, placeholder: phone.placeholder, value: phone.value, onChangeText: (text) => void phone.type(text, text.length), onFocus: phone.onFocus, onBlur: phone.onBlur, onSubmitEditing: actions.pay, editable: !ui.submitting, style: styles.input })] }), _jsx(View, { accessibilityLiveRegion: "polite", style: styles.message, children: error ? (_jsx(SheetText, { weight: FigtreeEnum.MEDIUM, size: 13, color: colors.danger, children: t(`phone_errors.${error}`) })) : mismatch ? (_jsxs(View, { style: styles.mismatch, children: [_jsx(SheetLogo, { logo: mismatch.logos[0] }), _jsx(SheetText, { size: 13, color: colors.ink2, style: styles.grow, children: t('mismatch', { method: mismatch.name }) }), _jsx(Pressable, { onPress: actions.switchToDetected, accessibilityRole: "button", hitSlop: 6, style: [styles.switch, { borderColor: colors.line3 }], children: _jsx(SheetText, { weight: FigtreeEnum.SEMI_BOLD, size: 13, children: t('switch_to') }) })] })) : (selected && (_jsx(SheetText, { size: 13, color: colors.muted, children: t('helper', { method: selected.name }) }))) })] }));
28
+ }
29
+ const styles = StyleSheet.create({
30
+ section: { marginTop: 18 },
31
+ label: { marginBottom: 8 },
32
+ field: {
33
+ flexDirection: 'row',
34
+ alignItems: 'center',
35
+ height: 50,
36
+ borderWidth: 1,
37
+ borderRadius: 13,
38
+ overflow: 'hidden',
39
+ },
40
+ dial: {
41
+ flexDirection: 'row',
42
+ alignItems: 'center',
43
+ gap: 6,
44
+ height: '100%',
45
+ paddingHorizontal: 12,
46
+ borderRightWidth: 1,
47
+ },
48
+ input: { flex: 1, height: '100%', paddingHorizontal: 12 },
49
+ message: { minHeight: 22, marginTop: 8 },
50
+ mismatch: { flexDirection: 'row', alignItems: 'center', gap: 8 },
51
+ grow: { flex: 1 },
52
+ switch: {
53
+ borderWidth: 1,
54
+ borderRadius: 8,
55
+ paddingHorizontal: 12,
56
+ paddingVertical: 6,
57
+ },
58
+ });
@@ -0,0 +1,3 @@
1
+ import type { SheetRowProps } from '../models/sheet.interface.js';
2
+ /** A label on the left, its value on the right, a hairline under them. */
3
+ export declare function SheetRow({ label, children, urgent }: SheetRowProps): import("react").JSX.Element;
@@ -0,0 +1,28 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { StyleSheet, View } from 'react-native';
3
+ import { useSheetTheme } from "../hooks/use-sheet-theme.js";
4
+ import { FigtreeEnum } from "../models/sheet.enum.js";
5
+ import { SheetText } from "./SheetText.js";
6
+ /** A label on the left, its value on the right, a hairline under them. */
7
+ export function SheetRow({ label, children, urgent = false }) {
8
+ const { colors } = useSheetTheme();
9
+ return (_jsxs(View, { style: [styles.row, { borderColor: colors.line }], children: [_jsx(SheetText, { color: colors.muted, children: label }), _jsx(View, { style: styles.value, children: typeof children === 'string' ? (_jsx(SheetText, { weight: FigtreeEnum.SEMI_BOLD, color: urgent ? colors.danger : colors.ink, style: styles.figures, children: children })) : (children) })] }));
10
+ }
11
+ const styles = StyleSheet.create({
12
+ row: {
13
+ flexDirection: 'row',
14
+ alignItems: 'center',
15
+ justifyContent: 'space-between',
16
+ gap: 12,
17
+ minHeight: 44,
18
+ paddingVertical: 11,
19
+ borderBottomWidth: 1,
20
+ },
21
+ value: {
22
+ flexShrink: 1,
23
+ flexDirection: 'row',
24
+ alignItems: 'center',
25
+ gap: 8,
26
+ },
27
+ figures: { fontVariant: ['tabular-nums'], textAlign: 'right' },
28
+ });
@@ -0,0 +1,6 @@
1
+ import type { SheetScreenProps } from '../models/sheet.interface.js';
2
+ /**
3
+ * On a test payment the Simulator took, the tester plays the payer's phone,
4
+ * or makes the provider misbehave. Never on a live payment.
5
+ */
6
+ export declare function SheetSimulator({ core }: SheetScreenProps): import("react").JSX.Element;
@@ -0,0 +1,39 @@
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 { FLOW_TRAITS, SIMULATOR_NO_ANSWER_SUFFIX, } from "../checkout-core/models/checkout.constants.js";
5
+ import { SimulatorOutcomeEnum } from "../checkout-core/models/checkout.enum.js";
6
+ import { useSheetTheme } from "../hooks/use-sheet-theme.js";
7
+ import { FigtreeEnum } from "../models/sheet.enum.js";
8
+ import { SheetText } from "./SheetText.js";
9
+ /**
10
+ * On a test payment the Simulator took, the tester plays the payer's phone,
11
+ * or makes the provider misbehave. Never on a live payment.
12
+ */
13
+ export function SheetSimulator({ core }) {
14
+ const t = useTranslations('checkout');
15
+ const { colors } = useSheetTheme();
16
+ const traits = FLOW_TRAITS[core.view.attempt.flow];
17
+ const outcomes = Object.values(SimulatorOutcomeEnum).filter((outcome) => !traits.hiddenOutcomes.includes(outcome));
18
+ return (_jsxs(View, { accessibilityLabel: t('simulator.title'), style: [
19
+ styles.panel,
20
+ { backgroundColor: colors.warnSoft, borderColor: colors.warnLine },
21
+ ], children: [_jsx(SheetText, { weight: FigtreeEnum.EXTRA_BOLD, size: 12, color: colors.warnInk2, style: styles.title, children: t('simulator.title') }), _jsxs(SheetText, { size: 13, color: colors.warnInk2, style: styles.body, children: [t('simulator.body'), traits.prompt &&
22
+ ` ${t('simulator.no_answer', { suffix: SIMULATOR_NO_ANSWER_SUFFIX })}`] }), _jsx(View, { style: styles.outcomes, children: outcomes.map((outcome) => (_jsx(TouchableOpacity, { onPress: () => core.actions.simulate(outcome), activeOpacity: 0.7, accessibilityRole: "button", style: [
23
+ styles.outcome,
24
+ { backgroundColor: colors.surface, borderColor: colors.warnLine },
25
+ ], children: _jsx(SheetText, { weight: FigtreeEnum.SEMI_BOLD, size: 13, children: t(`simulator.${outcome}`) }) }, outcome))) })] }));
26
+ }
27
+ const styles = StyleSheet.create({
28
+ panel: { borderWidth: 1, borderRadius: 12, padding: 14, marginTop: 18 },
29
+ title: { letterSpacing: 0.7, textTransform: 'uppercase' },
30
+ body: { marginTop: 4, marginBottom: 12 },
31
+ outcomes: { flexDirection: 'row', flexWrap: 'wrap', gap: 8 },
32
+ outcome: {
33
+ minHeight: 36,
34
+ justifyContent: 'center',
35
+ borderWidth: 1,
36
+ borderRadius: 8,
37
+ paddingHorizontal: 12,
38
+ },
39
+ });
@@ -0,0 +1,5 @@
1
+ /**
2
+ * The summary while the payment loads, bone for bone: its label and the
3
+ * amount.
4
+ */
5
+ export declare function SheetSkeleton(): import("react").JSX.Element;
@@ -0,0 +1,23 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { StyleSheet, View } from 'react-native';
3
+ import { useTranslations } from 'use-intl';
4
+ import { useSheetTheme } from "../hooks/use-sheet-theme.js";
5
+ import { SheetBone } from "./SheetBone.js";
6
+ /**
7
+ * The summary while the payment loads, bone for bone: its label and the
8
+ * amount.
9
+ */
10
+ export function SheetSkeleton() {
11
+ const t = useTranslations('checkout');
12
+ const { colors } = useSheetTheme();
13
+ return (_jsxs(View, { accessibilityRole: "progressbar", accessibilityLabel: t('loading'), style: [styles.summary, { borderColor: colors.line }], children: [_jsx(SheetBone, { width: 96, height: 13 }), _jsx(View, { style: styles.amount, children: _jsx(SheetBone, { width: 168, height: 34 }) })] }));
14
+ }
15
+ const styles = StyleSheet.create({
16
+ summary: {
17
+ paddingHorizontal: 18,
18
+ paddingVertical: 16,
19
+ borderBottomWidth: 1,
20
+ gap: 8,
21
+ },
22
+ amount: { paddingVertical: 2 },
23
+ });
@@ -0,0 +1,3 @@
1
+ import type { SheetClosingProps } from '../models/sheet.interface.js';
2
+ /** Paid: the check, the amount and the receipt; Done closes, as the countdown on it does by itself. */
3
+ export declare function SheetSuccess({ core, onClose }: SheetClosingProps): import("react").JSX.Element;
@@ -0,0 +1,51 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { StyleSheet, View } from 'react-native';
3
+ import { useFormatter, useTranslations } from 'use-intl';
4
+ import { CURRENCY_LABEL } from "../checkout-core/models/checkout.constants.js";
5
+ import { cardLabel } from "../checkout-core/models/checkout.card.utils.js";
6
+ import { formatAmount } from "../checkout-core/models/checkout.money.utils.js";
7
+ import { maskE164 } from "../checkout-core/models/checkout.phone.utils.js";
8
+ import { logoOf } from "../checkout-core/models/checkout.view.utils.js";
9
+ import { useSheetTheme } from "../hooks/use-sheet-theme.js";
10
+ import { FigtreeEnum, SheetIconEnum } from "../models/sheet.enum.js";
11
+ import { SheetButton } from "./SheetButton.js";
12
+ import { SheetIcon } from "./SheetIcon.js";
13
+ import { SheetLayout } from "./SheetLayout.js";
14
+ import { SheetLogo } from "./SheetLogo.js";
15
+ import { SheetRow } from "./SheetRow.js";
16
+ import { SheetText } from "./SheetText.js";
17
+ /** Paid: the check, the amount and the receipt; Done closes, as the countdown on it does by itself. */
18
+ export function SheetSuccess({ core, onClose }) {
19
+ const t = useTranslations('checkout');
20
+ const format = useFormatter();
21
+ const { colors } = useSheetTheme();
22
+ const { view, returnLeft } = core;
23
+ // Only a status check pays a payment: there is always an attempt.
24
+ const attempt = view.attempt;
25
+ return (_jsx(SheetLayout, { foot: _jsx(SheetButton
26
+ // Nothing stops the countdown in the sheet: it always runs here.
27
+ , {
28
+ // Nothing stops the countdown in the sheet: it always runs here.
29
+ label: t('sheet.done_in', { seconds: returnLeft }), onPress: onClose }), children: _jsxs(View, { style: styles.success, children: [_jsx(View, { style: styles.icon, children: _jsx(SheetIcon, { kind: SheetIconEnum.CHECK, color: colors.surface, background: colors.successInk, size: 64 }) }), _jsx(SheetText, { weight: FigtreeEnum.EXTRA_BOLD, size: 22, accessibilityRole: "header", style: [styles.center, styles.title], children: t('success_title') }), _jsxs(View, { style: styles.amountRow, children: [_jsx(SheetText, { weight: FigtreeEnum.EXTRA_BOLD, size: 34, style: styles.amount, children: formatAmount(view.amount, view.currency) }), _jsx(SheetText, { weight: FigtreeEnum.SEMI_BOLD, size: 16, color: colors.ink2, children: CURRENCY_LABEL[view.currency] })] }), _jsx(SheetText, { color: colors.muted, style: styles.center, children: t('paid_to', { app: view.app.name }) }), _jsxs(View, { style: [styles.rows, { borderColor: colors.line }], children: [_jsxs(SheetRow, { label: t('method'), children: [_jsx(SheetLogo, { logo: logoOf(attempt) }), _jsx(SheetText, { weight: FigtreeEnum.SEMI_BOLD, children: attempt.methodName })] }), attempt.phone && (_jsx(SheetRow, { label: t('number'), children: maskE164(attempt.phone) })), attempt.card && (_jsx(SheetRow, { label: t('card'), children: cardLabel(attempt.card) })), _jsx(SheetRow, { label: t('date'), children: format.dateTime(view.paidAt, {
30
+ day: 'numeric',
31
+ month: 'short',
32
+ year: 'numeric',
33
+ hour: '2-digit',
34
+ minute: '2-digit',
35
+ }) }), _jsx(SheetRow, { label: t('reference'), children: attempt.reference })] })] }) }));
36
+ }
37
+ const styles = StyleSheet.create({
38
+ success: { paddingHorizontal: 18, paddingTop: 30 },
39
+ icon: { alignItems: 'center' },
40
+ center: { textAlign: 'center' },
41
+ title: { marginTop: 18, letterSpacing: -0.4 },
42
+ amountRow: {
43
+ flexDirection: 'row',
44
+ alignItems: 'baseline',
45
+ justifyContent: 'center',
46
+ gap: 6,
47
+ marginTop: 6,
48
+ },
49
+ amount: { letterSpacing: -0.85, fontVariant: ['tabular-nums'] },
50
+ rows: { borderTopWidth: 1, marginTop: 22 },
51
+ });
@@ -0,0 +1,3 @@
1
+ import type { SheetSummaryProps } from '../models/sheet.interface.js';
2
+ /** The amount to pay. The order and its lines stay on the web checkout. */
3
+ export declare function SheetSummary({ view }: SheetSummaryProps): import("react").JSX.Element;
@@ -0,0 +1,32 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { StyleSheet, View } from 'react-native';
3
+ import { useTranslations } from 'use-intl';
4
+ import { CURRENCY_LABEL } from "../checkout-core/models/checkout.constants.js";
5
+ import { formatAmount } from "../checkout-core/models/checkout.money.utils.js";
6
+ import { useSheetTheme } from "../hooks/use-sheet-theme.js";
7
+ import { FigtreeEnum } from "../models/sheet.enum.js";
8
+ import { SheetText } from "./SheetText.js";
9
+ /** The amount to pay. The order and its lines stay on the web checkout. */
10
+ export function SheetSummary({ view }) {
11
+ const t = useTranslations('checkout');
12
+ const { colors } = useSheetTheme();
13
+ const { amount, currency } = view;
14
+ return (_jsxs(View, { style: [styles.summary, { borderColor: colors.line }], children: [_jsx(SheetText, { size: 13, color: colors.muted, children: t('total_label') }), _jsxs(View, { style: styles.amountRow, children: [_jsx(SheetText, { weight: FigtreeEnum.EXTRA_BOLD, size: 34, style: styles.amount, children: formatAmount(amount, currency) }), _jsx(SheetText, { weight: FigtreeEnum.SEMI_BOLD, size: 16, color: colors.ink2, children: CURRENCY_LABEL[currency] })] })] }));
15
+ }
16
+ const styles = StyleSheet.create({
17
+ summary: {
18
+ paddingHorizontal: 18,
19
+ paddingVertical: 16,
20
+ borderBottomWidth: 1,
21
+ },
22
+ amountRow: {
23
+ flexDirection: 'row',
24
+ alignItems: 'baseline',
25
+ gap: 6,
26
+ marginTop: 2,
27
+ },
28
+ amount: {
29
+ letterSpacing: -0.85,
30
+ fontVariant: ['tabular-nums'],
31
+ },
32
+ });
@@ -0,0 +1,3 @@
1
+ import type { SheetTerminalProps } from '../models/sheet.interface.js';
2
+ /** A payment that cannot be paid any more, said plainly, and the way back to the app. */
3
+ export declare function SheetTerminal({ core, kind, onClose }: SheetTerminalProps): import("react").JSX.Element;
@@ -0,0 +1,21 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useTranslations } from 'use-intl';
3
+ import { TerminalKindEnum } from "../checkout-core/models/checkout.enum.js";
4
+ import { SheetIconEnum } from "../models/sheet.enum.js";
5
+ import { SheetButton } from "./SheetButton.js";
6
+ import { SheetLayout } from "./SheetLayout.js";
7
+ import { SheetMessage } from "./SheetMessage.js";
8
+ const COPY_KEY = {
9
+ [TerminalKindEnum.EXPIRED]: 'expired',
10
+ [TerminalKindEnum.CANCELLED]: 'cancelled',
11
+ [TerminalKindEnum.LOCKED]: 'locked_screen',
12
+ [TerminalKindEnum.NO_METHOD]: 'no_method',
13
+ };
14
+ /** A payment that cannot be paid any more, said plainly, and the way back to the app. */
15
+ export function SheetTerminal({ core, kind, onClose }) {
16
+ const t = useTranslations('checkout');
17
+ const app = core.view.app.name;
18
+ const key = COPY_KEY[kind];
19
+ const locked = kind === TerminalKindEnum.LOCKED;
20
+ return (_jsx(SheetLayout, { foot: _jsx(SheetButton, { label: t('return_to', { app }), onPress: onClose }), children: _jsx(SheetMessage, { title: t(`${key}.title`), body: t(`${key}.body`, { app }), icon: locked ? SheetIconEnum.LOCK : SheetIconEnum.CLOCK, danger: locked }) }));
21
+ }
@@ -0,0 +1,5 @@
1
+ import type { SheetInputProps, SheetTextProps } from '../models/sheet.interface.js';
2
+ /** Every text of the sheet: Figtree, in the sheet's ink unless told otherwise. */
3
+ export declare function SheetText({ weight, size, color, style, ...props }: SheetTextProps): import("react").JSX.Element;
4
+ /** Every field of the sheet: Figtree at 16, so iOS never zooms, in the sheet's ink. */
5
+ export declare function SheetInput({ style, ...props }: SheetInputProps): import("react").JSX.Element;
@@ -0,0 +1,25 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { Text, TextInput } from 'react-native';
3
+ import { useSheetTheme } from "../hooks/use-sheet-theme.js";
4
+ import { FigtreeEnum } from "../models/sheet.enum.js";
5
+ /** Every text of the sheet: Figtree, in the sheet's ink unless told otherwise. */
6
+ export function SheetText({ weight = FigtreeEnum.REGULAR, size = 14, color, style, ...props }) {
7
+ const { colors } = useSheetTheme();
8
+ return (_jsx(Text, { ...props, style: [
9
+ { fontFamily: weight, fontSize: size, color: color ?? colors.ink },
10
+ style,
11
+ ] }));
12
+ }
13
+ /** Every field of the sheet: Figtree at 16, so iOS never zooms, in the sheet's ink. */
14
+ export function SheetInput({ style, ...props }) {
15
+ const { colors } = useSheetTheme();
16
+ return (_jsx(TextInput, { placeholderTextColor: colors.faint, selectionColor: colors.ink, ...props, style: [
17
+ {
18
+ fontFamily: FigtreeEnum.MEDIUM,
19
+ fontSize: 16,
20
+ color: colors.ink,
21
+ fontVariant: ['tabular-nums'],
22
+ },
23
+ style,
24
+ ] }));
25
+ }
@@ -0,0 +1,3 @@
1
+ import type { SheetToastProps } from '../models/sheet.interface.js';
2
+ /** One short message at a time, over the top of the sheet. */
3
+ export declare function SheetToast({ message }: SheetToastProps): import("react").JSX.Element | null;
@@ -0,0 +1,24 @@
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
+ import { FigtreeEnum } from "../models/sheet.enum.js";
5
+ import { SheetText } from "./SheetText.js";
6
+ /** One short message at a time, over the top of the sheet. */
7
+ export function SheetToast({ message }) {
8
+ const { colors } = useSheetTheme();
9
+ if (!message)
10
+ return null;
11
+ return (_jsx(View, { pointerEvents: "none", accessibilityLiveRegion: "polite", accessibilityRole: "alert", style: [styles.toast, { backgroundColor: colors.ink }], children: _jsx(SheetText, { weight: FigtreeEnum.SEMI_BOLD, size: 13.5, color: colors.surface, style: styles.text, children: message }) }));
12
+ }
13
+ const styles = StyleSheet.create({
14
+ toast: {
15
+ position: 'absolute',
16
+ top: 56,
17
+ left: 18,
18
+ right: 18,
19
+ borderRadius: 12,
20
+ paddingHorizontal: 14,
21
+ paddingVertical: 11,
22
+ },
23
+ text: { textAlign: 'center' },
24
+ });
@@ -0,0 +1,7 @@
1
+ import type { SheetScreenProps } from '../models/sheet.interface.js';
2
+ /**
3
+ * Waiting for the payer to approve on their phone: what to do, the method
4
+ * and the clock, Resend once 30 seconds have passed, and another method.
5
+ * It moves on by itself.
6
+ */
7
+ export declare function SheetWaiting({ core }: SheetScreenProps): import("react").JSX.Element;
@@ -0,0 +1,77 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useState } from 'react';
3
+ import { Pressable, StyleSheet, View } from 'react-native';
4
+ import { useTranslations } from 'use-intl';
5
+ import { FLOW_TRAITS, URGENT_SECONDS, WAIT_SECONDS, } from "../checkout-core/models/checkout.constants.js";
6
+ import { CheckoutButtonVariantEnum } from "../checkout-core/models/checkout.enum.js";
7
+ import { formatClock, formatMoney, } from "../checkout-core/models/checkout.money.utils.js";
8
+ import { maskE164 } from "../checkout-core/models/checkout.phone.utils.js";
9
+ import { useSheetTheme } from "../hooks/use-sheet-theme.js";
10
+ import { FigtreeEnum } from "../models/sheet.enum.js";
11
+ import { resendLeftOf } from "../models/sheet.utils.js";
12
+ import { SheetButton } from "./SheetButton.js";
13
+ import { SheetLayout } from "./SheetLayout.js";
14
+ import { SheetRow } from "./SheetRow.js";
15
+ import { SheetSimulator } from "./SheetSimulator.js";
16
+ import { SheetText } from "./SheetText.js";
17
+ const NO_BREAK_SPACE = '\u00a0';
18
+ /**
19
+ * Waiting for the payer to approve on their phone: what to do, the method
20
+ * and the clock, Resend once 30 seconds have passed, and another method.
21
+ * It moves on by itself.
22
+ */
23
+ export function SheetWaiting({ core }) {
24
+ const t = useTranslations('checkout');
25
+ const { colors, brand } = useSheetTheme();
26
+ const { view, waitLeft, actions } = core;
27
+ const attempt = view.attempt;
28
+ // A payment code or a card needs nothing more from the payer: no prompt to resend.
29
+ const { prompt } = FLOW_TRAITS[attempt.flow];
30
+ /** The second of the wait the prompt was last sent at. */
31
+ const [sentAt, setSentAt] = useState(0);
32
+ const resendLeft = resendLeftOf(waitLeft, sentAt);
33
+ // The number never breaks across lines.
34
+ const phone = attempt.phone
35
+ ? maskE164(attempt.phone).replace(/ /g, NO_BREAK_SPACE)
36
+ : '';
37
+ return (_jsx(SheetLayout, { foot: _jsxs(_Fragment, { children: [prompt && (_jsx(SheetButton, { variant: CheckoutButtonVariantEnum.SECONDARY, label: resendLeft > 0
38
+ ? t('sheet.resend_in', { clock: formatClock(resendLeft) })
39
+ : t('resend'), disabled: resendLeft > 0, onPress: () => {
40
+ setSentAt(WAIT_SECONDS - waitLeft);
41
+ actions.resend();
42
+ } })), _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, { size: 14, color: colors.muted, style: styles.center, children: attempt.phone
43
+ ? prompt
44
+ ? t('sheet.approve_body', {
45
+ method: attempt.methodName,
46
+ phone,
47
+ })
48
+ : `${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
+ }
51
+ const styles = StyleSheet.create({
52
+ waiting: { paddingHorizontal: 18, paddingTop: 30 },
53
+ pulse: {
54
+ alignSelf: 'center',
55
+ width: 128,
56
+ height: 128,
57
+ alignItems: 'center',
58
+ justifyContent: 'center',
59
+ },
60
+ halo: { position: 'absolute' },
61
+ outer: { width: 128, height: 128, borderRadius: 64, opacity: 0.06 },
62
+ inner: { width: 96, height: 96, borderRadius: 48, opacity: 0.12 },
63
+ phone: {
64
+ width: 24,
65
+ height: 38,
66
+ borderWidth: 2.5,
67
+ borderRadius: 6,
68
+ alignItems: 'center',
69
+ justifyContent: 'flex-end',
70
+ paddingBottom: 4,
71
+ },
72
+ speaker: { width: 6, height: 2.5, borderRadius: 1.25 },
73
+ title: { marginTop: 22, marginBottom: 8, letterSpacing: -0.4 },
74
+ center: { textAlign: 'center' },
75
+ rows: { borderTopWidth: 1, marginTop: 22 },
76
+ link: { minHeight: 40, justifyContent: 'center' },
77
+ });
@@ -0,0 +1,10 @@
1
+ import type { RefObject } from 'react';
2
+ import type { TextInputInstance } from 'react-native';
3
+ import type { NativePlatform } from '../models/sheet.interface.js';
4
+ /**
5
+ * What only the phone does for the checkout core. Leaving for the app is
6
+ * closing the sheet: `presentPayment` resolves, nothing opens the return URL.
7
+ * The card fields page talks through its WebView (`cardFields`). One stable
8
+ * object for the sheet's life.
9
+ */
10
+ export declare function useNativePlatform(onLeave: () => void, phoneField: RefObject<TextInputInstance | null>): NativePlatform;
@@ -0,0 +1,47 @@
1
+ import { useEffect, useRef, useState } from 'react';
2
+ import { AppState, Share } from 'react-native';
3
+ import { cardFieldsSubmit } from "../models/sheet.utils.js";
4
+ /**
5
+ * What only the phone does for the checkout core. Leaving for the app is
6
+ * closing the sheet: `presentPayment` resolves, nothing opens the return URL.
7
+ * The card fields page talks through its WebView (`cardFields`). One stable
8
+ * object for the sheet's life.
9
+ */
10
+ export function useNativePlatform(onLeave, phoneField) {
11
+ const leave = useRef(onLeave);
12
+ useEffect(() => {
13
+ leave.current = onLeave;
14
+ }, [onLeave]);
15
+ const cardPage = useRef(null);
16
+ const [cardListeners] = useState(() => new Set());
17
+ const [platform] = useState(() => ({
18
+ onReturn: (listener) => {
19
+ const subscription = AppState.addEventListener('change', (state) => {
20
+ if (state === 'active')
21
+ listener();
22
+ });
23
+ return () => subscription.remove();
24
+ },
25
+ // No clipboard in React Native itself: the share sheet offers Copy.
26
+ copyText: (text) => {
27
+ void Share.share({ message: text }).catch(() => undefined);
28
+ },
29
+ openUrl: () => leave.current(),
30
+ onCardMessage: (listener) => {
31
+ cardListeners.add(listener);
32
+ return () => void cardListeners.delete(listener);
33
+ },
34
+ // Pay is pressed on the form, where a picked card always shows its page.
35
+ submitCard: () => cardPage.current.injectJavaScript(cardFieldsSubmit()),
36
+ saveReceipt: (fileName, text) => {
37
+ void Share.share({ title: fileName, message: text }).catch(() => undefined);
38
+ },
39
+ // A wrong number on Pay: the keyboard comes up on it.
40
+ onPhoneRejected: () => phoneField.current?.focus(),
41
+ }));
42
+ const [cardFields] = useState(() => ({
43
+ page: cardPage,
44
+ receive: (message) => cardListeners.forEach((listener) => listener(message)),
45
+ }));
46
+ return { platform, cardFields };
47
+ }
@@ -0,0 +1,4 @@
1
+ import type { PaymentSheet } from '../models/sheet.interface.js';
2
+ export declare const PaymentSheetContext: import("react").Context<PaymentSheet | null>;
3
+ /** `presentPayment`, from the `BrissotoPayProvider` around the app. */
4
+ export declare function usePaymentSheet(): PaymentSheet;
@@ -0,0 +1,10 @@
1
+ import { createContext, use } from 'react';
2
+ export const PaymentSheetContext = createContext(null);
3
+ /** `presentPayment`, from the `BrissotoPayProvider` around the app. */
4
+ export function usePaymentSheet() {
5
+ const sheet = use(PaymentSheetContext);
6
+ if (!sheet) {
7
+ throw new Error('usePaymentSheet needs a BrissotoPayProvider around the app.');
8
+ }
9
+ return sheet;
10
+ }
@@ -0,0 +1,7 @@
1
+ import type { SheetMotion } from '../models/sheet.interface.js';
2
+ /**
3
+ * The bottom sheet's moves, in plain React Native: it slides up from below
4
+ * the screen, follows a finger dragging its top down, springs back or
5
+ * `onSwipe`s when let go, and slides away once `open` turns false.
6
+ */
7
+ export declare function useSheetMotion(open: boolean, onSwipe: () => void): SheetMotion;
@@ -0,0 +1,62 @@
1
+ import { useEffect, useMemo, useRef, useState } from 'react';
2
+ import { Animated, Easing, PanResponder, useWindowDimensions, } from 'react-native';
3
+ import { SHEET_DIM_OPACITY, SHEET_SLIDE_MS, SHEET_TOP_GAP, } from "../models/sheet.constants.js";
4
+ import { sheetSwipeCloses } from "../models/sheet.utils.js";
5
+ /**
6
+ * The bottom sheet's moves, in plain React Native: it slides up from below
7
+ * the screen, follows a finger dragging its top down, springs back or
8
+ * `onSwipe`s when let go, and slides away once `open` turns false.
9
+ */
10
+ export function useSheetMotion(open, onSwipe) {
11
+ const { height } = useWindowDimensions();
12
+ const [slide] = useState(() => new Animated.Value(height));
13
+ const [drag] = useState(() => new Animated.Value(0));
14
+ const [shown, setShown] = useState(open);
15
+ const swiped = useRef(onSwipe);
16
+ useEffect(() => {
17
+ swiped.current = onSwipe;
18
+ }, [onSwipe]);
19
+ useEffect(() => {
20
+ Animated.timing(slide, {
21
+ toValue: open ? 0 : height,
22
+ duration: SHEET_SLIDE_MS,
23
+ easing: Easing.out(Easing.cubic),
24
+ useNativeDriver: true,
25
+ }).start(() => {
26
+ if (!open)
27
+ setShown(false);
28
+ });
29
+ }, [open, height, slide]);
30
+ const [dragHandlers] = useState(() => {
31
+ const settle = () => Animated.spring(drag, {
32
+ toValue: 0,
33
+ bounciness: 0,
34
+ useNativeDriver: true,
35
+ }).start();
36
+ return PanResponder.create({
37
+ // The top holds only close, which takes its own taps.
38
+ onStartShouldSetPanResponder: () => true,
39
+ onPanResponderMove: (_event, { dy }) => drag.setValue(Math.max(0, dy)),
40
+ onPanResponderRelease: (_event, { dy, vy }) => {
41
+ // Closing asks first while a prompt waits: the sheet comes back up.
42
+ if (sheetSwipeCloses(dy, vy))
43
+ swiped.current();
44
+ settle();
45
+ },
46
+ onPanResponderTerminate: settle,
47
+ }).panHandlers;
48
+ });
49
+ const [translateY] = useState(() => Animated.add(slide, drag));
50
+ const dim = useMemo(() => translateY.interpolate({
51
+ inputRange: [0, height],
52
+ outputRange: [SHEET_DIM_OPACITY, 0],
53
+ extrapolate: 'clamp',
54
+ }), [translateY, height]);
55
+ return {
56
+ shown,
57
+ translateY,
58
+ dim,
59
+ dragHandlers,
60
+ topGap: height * SHEET_TOP_GAP,
61
+ };
62
+ }