@heyama/pay-react-native 1.0.3 → 1.0.4

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.
@@ -6,7 +6,7 @@ import { CountryEnum } from "../models/domain.enum.js";
6
6
  import { cardLabel } from "../models/checkout.card.utils.js";
7
7
  import { formatMoney } from "../models/checkout.money.utils.js";
8
8
  import { maskE164 } from "../models/checkout.phone.utils.js";
9
- import { countriesOf, deriveCheckout, initialUi, logoOf, screenOf, waitLeftOf, } from "../models/checkout.view.utils.js";
9
+ import { countriesOf, deriveCheckout, initialUi, logoOf, pickedPatch, screenOf, waitLeftOf, } from "../models/checkout.view.utils.js";
10
10
  import { useNow, useReturnCountdown, useToast } from "./use-checkout-timers.js";
11
11
  import { usePhoneNumber } from "./use-phone-number.js";
12
12
  /** Before the payment has loaded, or for a link that matches none. */
@@ -63,6 +63,7 @@ export function useCheckoutController(view, { clues, mutations, platform }) {
63
63
  cards: [],
64
64
  selected: null,
65
65
  canPay: false,
66
+ detected: null,
66
67
  mismatch: null,
67
68
  suggestion: null,
68
69
  };
@@ -87,6 +88,39 @@ export function useCheckoutController(view, { clues, mutations, platform }) {
87
88
  update({ dismissed: attemptReference });
88
89
  showToast(noAnswer, CheckoutToastToneEnum.WARNING);
89
90
  }, [waiting, waitLeft, attemptReference, noAnswer, showToast, update]);
91
+ // A number that tells its method picks it when none is picked: once per
92
+ // number, so Change keeps the list open until the number changes. The app's
93
+ // own number picks without a word. Off the form, or on the Card tab, the
94
+ // number is not looked at: it counts once the payer is back on Mobile Money.
95
+ const detectedMethod = derived.detected;
96
+ const numberE164 = phone.e164;
97
+ const onMobileForm = screen === CheckoutScreenEnum.FORM &&
98
+ ui.tab === CheckoutTabEnum.MOBILE_MONEY;
99
+ const nonePicked = !derived.selected;
100
+ const lastKey = useRef('');
101
+ useEffect(() => {
102
+ if (!onMobileForm)
103
+ return;
104
+ const key = detectedMethod ? `${detectedMethod.id}|${numberE164}` : '';
105
+ if (key === lastKey.current)
106
+ return;
107
+ lastKey.current = key;
108
+ if (!detectedMethod || !nonePicked)
109
+ return;
110
+ update(pickedPatch(detectedMethod.id));
111
+ if (numberE164 !== prefill) {
112
+ showToast(t('toast.switched', { method: detectedMethod.name }), CheckoutToastToneEnum.SUCCESS, logoOf(detectedMethod));
113
+ }
114
+ }, [
115
+ onMobileForm,
116
+ detectedMethod,
117
+ numberE164,
118
+ nonePicked,
119
+ prefill,
120
+ t,
121
+ showToast,
122
+ update,
123
+ ]);
90
124
  const paymentId = view?.id;
91
125
  // A payer opened the link, said once per page (the dashboard's preview has no mutations).
92
126
  const opened = useRef(null);
@@ -192,13 +226,7 @@ export function useCheckoutController(view, { clues, mutations, platform }) {
192
226
  toast.show(method.note ?? t('toast.unavailable', { method: method.name }), CheckoutToastToneEnum.WARNING, logoOf(method));
193
227
  }
194
228
  else {
195
- update({
196
- method: method.id,
197
- pickerOpen: false,
198
- code: '',
199
- cardComplete: false,
200
- methodMissing: 0,
201
- });
229
+ update(pickedPatch(method.id));
202
230
  }
203
231
  },
204
232
  comingSoon,
@@ -202,6 +202,8 @@ export interface CheckoutDerived {
202
202
  /** The picked method, while it can still be used. */
203
203
  selected: CheckoutMethod | null;
204
204
  canPay: boolean;
205
+ /** The available method of the country the typed number belongs to. */
206
+ detected: CheckoutMethod | null;
205
207
  /** The number belongs to another method of the country than the one picked. */
206
208
  mismatch: CheckoutMethod | null;
207
209
  /** After a failure: another method of the same country the payer can use now. */
@@ -22,6 +22,8 @@ export declare function initialUi(view: CheckoutView, clues: CountryClues): Chec
22
22
  export declare function screenOf(view: CheckoutView | null | undefined, ui: CheckoutUi): CheckoutScreenEnum;
23
23
  /** The brand of a Mobile Money method, which a number's first digits can reveal. */
24
24
  export declare function brandOf(method: CheckoutMethod): string;
25
+ /** What the page keeps when a method is picked: the list closes, the rest starts over. */
26
+ export declare function pickedPatch(id: string): Partial<CheckoutUi>;
25
27
  /** The tile of a method or an attempt: the generic card for cards, whatever networks they take. */
26
28
  export declare function logoOf(of: Pick<CheckoutMethod, 'flow' | 'logos'>): ProviderLogoEnum;
27
29
  /** Works out what the screens show from the payment, the page and the number typed. */
@@ -99,6 +99,16 @@ export function screenOf(view, ui) {
99
99
  export function brandOf(method) {
100
100
  return method.logos[0];
101
101
  }
102
+ /** What the page keeps when a method is picked: the list closes, the rest starts over. */
103
+ export function pickedPatch(id) {
104
+ return {
105
+ method: id,
106
+ pickerOpen: false,
107
+ code: '',
108
+ cardComplete: false,
109
+ methodMissing: 0,
110
+ };
111
+ }
102
112
  /** Everything the picked method needs is in: the card, or the number and its payment code. */
103
113
  function readyToPay(method, ui, phone) {
104
114
  if (method.flow === MethodFlowEnum.CARD_FIELDS)
@@ -139,6 +149,7 @@ export function deriveCheckout(view, ui, phone) {
139
149
  readyToPay(selected, ui, phone) &&
140
150
  !ui.submitting &&
141
151
  !ui.cardPaying,
152
+ detected,
142
153
  // Only a Mobile Money method has a number to belong to another one.
143
154
  mismatch: selected && !onCard && detected && detected.id !== selected.id
144
155
  ? detected
@@ -23,7 +23,7 @@ function MethodGrid({ cards, missing }) {
23
23
  for (let index = 0; index < cards.length; index += 2) {
24
24
  rows.push(cards.slice(index, index + 2));
25
25
  }
26
- return (_jsxs(Animated.View, { testID: "method-grid", accessibilityRole: "radiogroup", style: [styles.grid, { transform: [{ translateX: shake }] }], children: [rows.map((row) => (_jsxs(View, { style: styles.gridRow, children: [row.map(({ id, ...card }) => (_jsx(SheetMethodCard, { ...card, missing: missing > 0 }, id))), row.length === 1 && _jsx(View, { style: styles.gridSpacer })] }, row[0].id))), missing > 0 && (_jsx(SheetText, { accessibilityRole: "alert", weight: FigtreeEnum.MEDIUM, size: 13, color: colors.danger, children: t('method_missing') }))] }));
26
+ return (_jsxs(Animated.View, { testID: "method-grid", accessibilityRole: "radiogroup", style: [styles.grid, { transform: [{ translateX: shake }] }], children: [rows.map((row) => (_jsxs(View, { style: styles.gridRow, children: [row.map(({ id, ...card }) => (_jsx(SheetMethodCard, { ...card, missing: missing > 0, alone: cards.length === 1 }, id))), row.length === 1 && cards.length > 1 && (_jsx(View, { style: styles.gridSpacer }))] }, row[0].id))), missing > 0 && (_jsx(SheetText, { accessibilityRole: "alert", weight: FigtreeEnum.MEDIUM, size: 13, color: colors.danger, children: t('method_missing') }))] }));
27
27
  }
28
28
  /**
29
29
  * The form of design A: the country when there are several, the Mobile
@@ -1,3 +1,3 @@
1
1
  import type { MethodCardProps } from '../models/sheet.interface.js';
2
- /** 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. */
3
- export declare function SheetMethodCard({ logo, name, sub, muted, missing, onSelect, }: MethodCardProps): import("react").JSX.Element;
2
+ /** One method of the grid: logo and radio dot, then its name and network, or why it is greyed. Alone, one full-width row. Picked, it becomes the chosen row. */
3
+ export declare function SheetMethodCard({ logo, name, sub, muted, missing, alone, onSelect, }: MethodCardProps): import("react").JSX.Element;
@@ -1,4 +1,4 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { StyleSheet, TouchableOpacity, View } from 'react-native';
3
3
  import { LOGO_SIZE } from "../checkout-core/models/checkout.constants.js";
4
4
  import { useSheetTheme } from "../hooks/use-sheet-theme.js";
@@ -6,16 +6,17 @@ import { FigtreeEnum } from "../models/sheet.enum.js";
6
6
  import { SheetLogo } from "./SheetLogo.js";
7
7
  import { SheetText } from "./SheetText.js";
8
8
  import { FIELD_RADIUS } from "../models/sheet.constants.js";
9
- /** 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. */
10
- export function SheetMethodCard({ logo, name, sub, muted, missing = false, onSelect, }) {
9
+ /** One method of the grid: logo and radio dot, then its name and network, or why it is greyed. Alone, one full-width row. Picked, it becomes the chosen row. */
10
+ export function SheetMethodCard({ logo, name, sub, muted, missing, alone, onSelect, }) {
11
11
  const { colors } = useSheetTheme();
12
12
  const red = missing && !muted;
13
+ const texts = (_jsxs(_Fragment, { children: [_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 })] }));
13
14
  return (_jsxs(TouchableOpacity, { onPress: onSelect, disabled: muted, activeOpacity: 0.7, accessibilityRole: "radio", accessibilityLabel: `${name}, ${sub}`, accessibilityState: { checked: false, disabled: muted }, style: [
14
15
  styles.card,
15
16
  red
16
17
  ? [styles.cardMissing, { borderColor: colors.danger }]
17
18
  : { borderColor: colors.line2 },
18
- ], children: [_jsxs(View, { style: styles.top, children: [_jsx(SheetLogo, { logo: logo, size: LOGO_SIZE.ROW, muted: muted }), _jsx(View, { style: [
19
+ ], children: [_jsxs(View, { style: alone ? styles.row : styles.top, children: [_jsx(SheetLogo, { logo: logo, size: LOGO_SIZE.ROW, muted: muted }), alone && _jsx(View, { style: styles.text, children: texts }), _jsx(View, { style: [
19
20
  styles.radio,
20
21
  {
21
22
  borderColor: muted
@@ -24,7 +25,7 @@ export function SheetMethodCard({ logo, name, sub, muted, missing = false, onSel
24
25
  ? colors.danger
25
26
  : colors.line4,
26
27
  },
27
- ] })] }), _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 })] }));
28
+ ] })] }), !alone && texts] }));
28
29
  }
29
30
  const styles = StyleSheet.create({
30
31
  card: {
@@ -43,5 +44,7 @@ const styles = StyleSheet.create({
43
44
  justifyContent: 'space-between',
44
45
  marginBottom: 10,
45
46
  },
47
+ row: { flexDirection: 'row', alignItems: 'center', gap: 12 },
48
+ text: { flex: 1, minWidth: 0 },
46
49
  radio: { width: 20, height: 20, borderRadius: 10, borderWidth: 1.5 },
47
50
  });
@@ -30,7 +30,7 @@ export function SheetToast({ toast }) {
30
30
  const styles = StyleSheet.create({
31
31
  place: {
32
32
  position: 'absolute',
33
- top: 12,
33
+ top: 24,
34
34
  left: 18,
35
35
  right: 18,
36
36
  alignItems: 'center',
@@ -181,6 +181,8 @@ export interface MethodCardProps {
181
181
  muted: boolean;
182
182
  /** Pay was pressed with no method picked: the card is ringed in red. */
183
183
  missing?: boolean;
184
+ /** The only method of the tab: a full-width row. */
185
+ alone?: boolean;
184
186
  onSelect?: () => void;
185
187
  }
186
188
  export interface ChosenMethodProps {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@heyama/pay-react-native",
3
- "version": "1.0.3",
3
+ "version": "1.0.4",
4
4
  "description": "Brissoto Pay for Expo and React Native apps: the payment sheet.",
5
5
  "license": "MIT",
6
6
  "sideEffects": false,