@heyama/pay-react-native 1.0.7 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -6,4 +6,4 @@ import type { CheckoutControllerOptions, CheckoutCore, CheckoutView } from '../m
6
6
  * clipboard, leaving for the app, the card fields page) comes in `platform`.
7
7
  * The language is not here: it comes from the IntlProvider around it.
8
8
  */
9
- export declare function useCheckoutController(view: CheckoutView | null | undefined, { clues, mutations, platform }: CheckoutControllerOptions): CheckoutCore;
9
+ export declare function useCheckoutController(view: CheckoutView | null | undefined, { clues, calls, platform }: CheckoutControllerOptions): CheckoutCore;
@@ -31,8 +31,9 @@ const NO_PAYMENT_UI = {
31
31
  * clipboard, leaving for the app, the card fields page) comes in `platform`.
32
32
  * The language is not here: it comes from the IntlProvider around it.
33
33
  */
34
- export function useCheckoutController(view, { clues, mutations, platform }) {
34
+ export function useCheckoutController(view, { clues, calls, platform }) {
35
35
  const t = useTranslations('checkout');
36
+ const mutations = calls?.mutations ?? null;
36
37
  const format = useFormatter();
37
38
  const lang = useLocale();
38
39
  const [patch, setPatch] = useState({});
@@ -131,12 +132,33 @@ export function useCheckoutController(view, { clues, mutations, platform }) {
131
132
  opened.current = paymentId;
132
133
  void mutations.open({ paymentId });
133
134
  }, [paymentId, mutations]);
134
- // Coming back to the page or the app while waiting: ask the provider now (ADR 0001).
135
+ // Away from the page or the app while waiting, the payment is not followed
136
+ // live. Coming back asks the provider now (ADR 0001), then follows again.
137
+ const [away, setAway] = useState(false);
135
138
  useEffect(() => {
136
139
  if (!waiting || !paymentId || !mutations)
137
140
  return undefined;
138
- return platform.onReturn(() => void mutations.checkNow({ paymentId }));
141
+ // Already away when the wait starts (off to the dialler): not followed until back.
142
+ if (platform.isAway())
143
+ setAway(true);
144
+ const stopLeave = platform.onLeave(() => setAway(true));
145
+ const stopReturn = platform.onReturn(() => void mutations
146
+ .checkNow({ paymentId })
147
+ .catch(() => null)
148
+ .then(() => setAway(false)));
149
+ return () => {
150
+ stopLeave();
151
+ stopReturn();
152
+ setAway(false);
153
+ };
139
154
  }, [waiting, paymentId, mutations, platform]);
155
+ // Followed live only on the waiting screen (ADR 0006).
156
+ const followed = waiting && !away;
157
+ const follow = calls?.follow;
158
+ useEffect(() => {
159
+ follow?.(followed);
160
+ }, [follow, followed]);
161
+ const refresh = calls?.refresh;
140
162
  const goBack = useCallback(() => update({ redirecting: true }), [update]);
141
163
  const countdown = useReturnCountdown(screen === CheckoutScreenEnum.SUCCESS, goBack);
142
164
  const returnUrl = view?.returnUrl;
@@ -161,9 +183,10 @@ export function useCheckoutController(view, { clues, mutations, platform }) {
161
183
  ...(message.complete ? {} : { cardPaying: false }),
162
184
  });
163
185
  break;
164
- // The card is with its provider: the waiting screen follows the checks.
186
+ // The card is with its provider: read the attempt, the waiting screen follows the checks.
165
187
  case CardFieldsMessageEnum.DONE:
166
188
  update({ cardPaying: false });
189
+ refresh?.();
167
190
  break;
168
191
  case CardFieldsMessageEnum.REFUSED:
169
192
  update({ cardPaying: false });
@@ -172,7 +195,7 @@ export function useCheckoutController(view, { clues, mutations, platform }) {
172
195
  }
173
196
  };
174
197
  return platform.onCardMessage(onMessage);
175
- }, [update, showToast, refusals, failedToast, platform]);
198
+ }, [update, showToast, refusals, failedToast, platform, refresh]);
176
199
  /* ---------------------------------------------------------- helpers */
177
200
  const reference = attempt?.reference ?? view?.id ?? '';
178
201
  const comingSoon = (name) => toast.show(t('toast.soon', { method: name }), CheckoutToastToneEnum.INFO);
@@ -0,0 +1,9 @@
1
+ import type { CheckoutConnection, CheckoutPayment } from '../models/checkout.interface.js';
2
+ /**
3
+ * One payment for a checkout (ADR 0006): read once over plain calls, read
4
+ * again after each call that changes it, and followed live only between
5
+ * `follow(true)` and `follow(false)`, which the controller says while the
6
+ * payer is on the waiting screen. Once `paymentId` is cleared (a sheet
7
+ * closing) the last answer stays.
8
+ */
9
+ export declare function useCheckoutPayment(connection: CheckoutConnection, paymentId: string | null): CheckoutPayment;
@@ -0,0 +1,99 @@
1
+ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
2
+ /**
3
+ * One payment for a checkout (ADR 0006): read once over plain calls, read
4
+ * again after each call that changes it, and followed live only between
5
+ * `follow(true)` and `follow(false)`, which the controller says while the
6
+ * payer is on the waiting screen. Once `paymentId` is cleared (a sheet
7
+ * closing) the last answer stays.
8
+ */
9
+ export function useCheckoutPayment(connection, paymentId) {
10
+ const [held, setHeld] = useState({
11
+ view: undefined,
12
+ failed: false,
13
+ });
14
+ const [following, setFollowing] = useState(false);
15
+ /** Opened again after a failure: once, then the calls read over HTTP. */
16
+ const [reopened, setReopened] = useState(0);
17
+ const failures = useRef(0);
18
+ /** Followed live: the connection brings each change, nothing is read again. */
19
+ const followingNow = useRef(false);
20
+ /** The payment held, and the newest answer shown: an older one never replaces it. */
21
+ const holding = useRef(paymentId);
22
+ const counter = useRef(0);
23
+ const shown = useRef(0);
24
+ const show = useCallback((ticket, next) => {
25
+ if (ticket < shown.current)
26
+ return;
27
+ shown.current = ticket;
28
+ setHeld(next);
29
+ }, []);
30
+ const read = useCallback((id) => {
31
+ const ticket = ++counter.current;
32
+ return connection.load(id).then((view) => {
33
+ if (holding.current === id)
34
+ show(ticket, () => ({ view, failed: false }));
35
+ },
36
+ // A failed read keeps what was shown.
37
+ () => {
38
+ if (holding.current === id)
39
+ show(ticket, (previous) => ({ ...previous, failed: true }));
40
+ });
41
+ }, [connection, show]);
42
+ useEffect(() => {
43
+ holding.current = paymentId;
44
+ if (paymentId)
45
+ void read(paymentId);
46
+ }, [paymentId, read]);
47
+ useEffect(() => {
48
+ followingNow.current = following;
49
+ }, [following]);
50
+ useEffect(() => {
51
+ if (!following || !paymentId)
52
+ return undefined;
53
+ return connection.follow(paymentId, (view) => {
54
+ failures.current = 0;
55
+ show(++counter.current, () => ({ view, failed: false }));
56
+ },
57
+ // A broken connection is read over HTTP and opened once more; broken
58
+ // again, it stays closed and the calls read over HTTP.
59
+ () => {
60
+ setHeld((previous) => ({ ...previous, failed: true }));
61
+ failures.current += 1;
62
+ if (failures.current > 1) {
63
+ setFollowing(false);
64
+ return;
65
+ }
66
+ void read(paymentId);
67
+ setReopened((count) => count + 1);
68
+ });
69
+ }, [following, reopened, paymentId, connection, show, read]);
70
+ const mutations = useMemo(() => {
71
+ const { open, ...changing } = connection.mutations;
72
+ /**
73
+ * The call, then the payment read again, then the call's answer. Not
74
+ * read while followed live, nor after a refusal: nothing changed.
75
+ */
76
+ const reading = (call) => async (args) => {
77
+ const result = await call(args);
78
+ const refused = result?.ok === false;
79
+ if (!followingNow.current && !refused)
80
+ await read(args.paymentId);
81
+ return result;
82
+ };
83
+ return {
84
+ open,
85
+ pay: reading(changing.pay),
86
+ payWithCode: reading(changing.payWithCode),
87
+ checkNow: reading(changing.checkNow),
88
+ cancel: reading(changing.cancel),
89
+ resend: reading(changing.resend),
90
+ simulate: reading(changing.simulate),
91
+ };
92
+ }, [connection, read]);
93
+ const refresh = useCallback(() => {
94
+ if (paymentId)
95
+ void read(paymentId);
96
+ }, [paymentId, read]);
97
+ const calls = useMemo(() => ({ mutations, follow: setFollowing, refresh }), [mutations, refresh]);
98
+ return { ...held, calls };
99
+ }
@@ -308,6 +308,10 @@ export interface CheckoutCore {
308
308
  export interface CheckoutPlatform {
309
309
  /** Calls `listener` each time the payer comes back to the page or the app; answers how to stop. */
310
310
  onReturn: (listener: () => void) => () => void;
311
+ /** Calls `listener` each time the payer leaves the page or the app; answers how to stop. */
312
+ onLeave: (listener: () => void) => () => void;
313
+ /** The payer is away from the page or the app right now. */
314
+ isAway: () => boolean;
311
315
  copyText: (text: string) => void;
312
316
  /** Leaves for the app's return URL. */
313
317
  openUrl: (url: string) => void;
@@ -323,9 +327,47 @@ export interface CheckoutPlatform {
323
327
  export interface CheckoutControllerOptions {
324
328
  clues: CountryClues;
325
329
  /** Null on the Branding tab's preview: nothing is paid there. */
326
- mutations: CheckoutMutations | null;
330
+ calls: CheckoutCalls | null;
327
331
  platform: CheckoutPlatform;
328
332
  }
333
+ /** What a checkout asks of the gateway about its payment, from `useCheckoutPayment`. */
334
+ export interface CheckoutCalls {
335
+ /** Each call that changes the payment reads it again before it answers. */
336
+ mutations: CheckoutMutations;
337
+ /** Opens or closes the live connection: open only while the payer is on the waiting screen. */
338
+ follow: (on: boolean) => void;
339
+ /** Reads the payment again: the card fields page started an attempt the checkout did not. */
340
+ refresh: () => void;
341
+ }
342
+ /**
343
+ * How a checkout reaches the gateway (ADR 0006): plain calls, and a live
344
+ * connection to one payment only while the payer waits on it.
345
+ */
346
+ export interface CheckoutConnection {
347
+ /** The payment's checkout view, read once; null: no payment has this id. */
348
+ load: (paymentId: string) => Promise<CheckoutView | null>;
349
+ /**
350
+ * Opens a live connection to one payment: `onView` with each answer,
351
+ * `onError` when it cannot load. Answers how to close it.
352
+ */
353
+ follow: (paymentId: string, onView: (view: CheckoutView | null) => void, onError: () => void) => () => void;
354
+ mutations: CheckoutMutations;
355
+ }
356
+ /** What a checkout last heard of its payment. */
357
+ export interface HeldPayment {
358
+ /** `undefined` while loading, null for a link that matches none. */
359
+ view: CheckoutView | null | undefined;
360
+ /** The last read failed. */
361
+ failed: boolean;
362
+ }
363
+ /** One payment as a checkout holds it, from `useCheckoutPayment`. */
364
+ export interface CheckoutPayment extends HeldPayment {
365
+ calls: CheckoutCalls;
366
+ }
367
+ /** What a gateway call answers when it can refuse (Pay, Send again). */
368
+ export interface MaybeRefused {
369
+ ok?: boolean;
370
+ }
329
371
  /** What Pay answers: refused with a reason, or an attempt started. */
330
372
  export interface PayResult {
331
373
  ok: boolean;
@@ -1,6 +1,7 @@
1
1
  import type { BrissotoPayProviderProps } from '../models/sheet.interface.js';
2
2
  /**
3
- * Around the app: holds the connection to Brissoto Pay and the one payment
4
- * sheet `usePaymentSheet().presentPayment` opens.
3
+ * Around the app: the calls to Brissoto Pay and the one payment sheet
4
+ * `usePaymentSheet().presentPayment` opens. Nothing stays connected: a
5
+ * sheet follows its payment live only while the payer waits on it.
5
6
  */
6
7
  export declare function BrissotoPayProvider({ url, children, }: BrissotoPayProviderProps): import("react").JSX.Element;
@@ -1,22 +1,16 @@
1
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';
2
+ import { useCallback, useMemo, useRef, useState } from 'react';
4
3
  import { PaymentSheetContext } from "../hooks/use-payment-sheet.js";
5
4
  import { DEFAULT_URL } from "../models/sheet.constants.js";
6
- import { convexCheckoutCalls } from "../services/checkout.service.js";
5
+ import { convexCheckoutConnection } from "../services/checkout.service.js";
7
6
  import { PaymentSheetModal } from "./PaymentSheetModal.js";
8
7
  /**
9
- * Around the app: holds the connection to Brissoto Pay and the one payment
10
- * sheet `usePaymentSheet().presentPayment` opens.
8
+ * Around the app: the calls to Brissoto Pay and the one payment sheet
9
+ * `usePaymentSheet().presentPayment` opens. Nothing stays connected: a
10
+ * sheet follows its payment live only while the payer waits on it.
11
11
  */
12
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]);
13
+ const connection = useMemo(() => convexCheckoutConnection(url), [url]);
20
14
  const [request, setRequest] = useState(null);
21
15
  /** Sheets opened, so each gets a fresh one. */
22
16
  const [count, setCount] = useState(0);
@@ -37,5 +31,5 @@ export function BrissotoPayProvider({ url = DEFAULT_URL, children, }) {
37
31
  setRequest({ ...closing, open: false });
38
32
  closing.resolve(result);
39
33
  };
40
- return (_jsxs(PaymentSheetContext, { value: sheet, children: [children, calls && request && (_jsx(PaymentSheetModal, { calls: calls, request: request, onClose: (result) => close(request, result) }, count))] }));
34
+ return (_jsxs(PaymentSheetContext, { value: sheet, children: [children, request && (_jsx(PaymentSheetModal, { connection: connection, request: request, onClose: (result) => close(request, result) }, count))] }));
41
35
  }
@@ -5,4 +5,4 @@ import type { PaymentSheetModalProps } from '../models/sheet.interface.js';
5
5
  * a swipe down, a tap on the app behind, back or escape leave it; leaving
6
6
  * while a prompt waits on the phone asks first, and resolves `pending`.
7
7
  */
8
- export declare function PaymentSheetModal({ calls, request, onClose, }: PaymentSheetModalProps): import("react").JSX.Element;
8
+ export declare function PaymentSheetModal({ connection, request, onClose, }: PaymentSheetModalProps): import("react").JSX.Element;
@@ -4,9 +4,9 @@ import { useMemo, useState } from 'react';
4
4
  import { Alert, Animated, KeyboardAvoidingView, Modal, Pressable, StyleSheet, View, useColorScheme, } from 'react-native';
5
5
  import { IntlProvider, createTranslator } from 'use-intl';
6
6
  import { CHECKOUT_COLORS, INVALID_BRAND_DARK, INVALID_BRAND_LIGHT, } from "../checkout-core/models/checkout.constants.js";
7
+ import { useCheckoutPayment } from "../checkout-core/hooks/use-checkout-payment.js";
7
8
  import { CheckoutThemeEnum } from "../checkout-core/models/checkout.enum.js";
8
9
  import { useSheetMotion } from "../hooks/use-sheet-motion.js";
9
- import { useWatchedPayment } from "../hooks/use-watched-payment.js";
10
10
  import { SheetThemeContext } from "../hooks/use-sheet-theme.js";
11
11
  import { FIGTREE_FILES, MESSAGES, TIME_ZONE, } from "../models/sheet.constants.js";
12
12
  import { sheetLanguage, sheetResult, sheetTheme, } from "../models/sheet.utils.js";
@@ -17,9 +17,9 @@ import { SheetContent } from "./SheetContent.js";
17
17
  * a swipe down, a tap on the app behind, back or escape leave it; leaving
18
18
  * while a prompt waits on the phone asks first, and resolves `pending`.
19
19
  */
20
- export function PaymentSheetModal({ calls, request, onClose, }) {
21
- const watched = useWatchedPayment(calls, request.open ? request.paymentId : null);
22
- const { view } = watched;
20
+ export function PaymentSheetModal({ connection, request, onClose, }) {
21
+ const payment = useCheckoutPayment(connection, request.open ? request.paymentId : null);
22
+ const { view } = payment;
23
23
  const scheme = useColorScheme();
24
24
  const [deviceLocale] = useState(() => Intl.DateTimeFormat().resolvedOptions().locale);
25
25
  const [fontsLoaded, fontsError] = useFonts(FIGTREE_FILES);
@@ -32,7 +32,7 @@ export function PaymentSheetModal({ calls, request, onClose, }) {
32
32
  dark,
33
33
  }), [dark, color]);
34
34
  const close = () => {
35
- const result = sheetResult(view, watched.failed);
35
+ const result = sheetResult(view, payment.failed);
36
36
  if (result.status !== 'pending') {
37
37
  onClose(result);
38
38
  return;
@@ -63,7 +63,7 @@ export function PaymentSheetModal({ calls, request, onClose, }) {
63
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
64
  styles.floor,
65
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 }) }) })] }) }) })] }) }));
66
+ ] }), _jsx(IntlProvider, { locale: lang, messages: MESSAGES[lang], timeZone: TIME_ZONE, children: _jsx(SheetThemeContext, { value: theme, children: _jsx(SheetContent, { payment: payment, fontsReady: fontsLoaded || fontsError !== null, deviceLocale: deviceLocale, onClose: close, dragHandlers: motion.dragHandlers }) }) })] }) }) })] }) }));
67
67
  }
68
68
  const styles = StyleSheet.create({
69
69
  screen: { flex: 1 },
@@ -1,3 +1,3 @@
1
1
  import type { SheetContentProps } from '../models/sheet.interface.js';
2
2
  /** The sheet's screens, driven by the shared checkout controller. */
3
- export declare function SheetContent({ calls, watched, fontsReady, deviceLocale, onClose, dragHandlers, }: SheetContentProps): import("react").JSX.Element;
3
+ export declare function SheetContent({ payment, fontsReady, deviceLocale, onClose, dragHandlers, }: SheetContentProps): import("react").JSX.Element;
@@ -28,7 +28,7 @@ const TERMINAL_KIND = {
28
28
  [CheckoutScreenEnum.NO_METHOD]: TerminalKindEnum.NO_METHOD,
29
29
  };
30
30
  /** The sheet's screens, driven by the shared checkout controller. */
31
- export function SheetContent({ calls, watched, fontsReady, deviceLocale, onClose, dragHandlers, }) {
31
+ export function SheetContent({ payment, fontsReady, deviceLocale, onClose, dragHandlers, }) {
32
32
  const t = useTranslations('checkout');
33
33
  const { colors } = useSheetTheme();
34
34
  const [clues] = useState(() => ({
@@ -37,16 +37,16 @@ export function SheetContent({ calls, watched, fontsReady, deviceLocale, onClose
37
37
  }));
38
38
  const phoneRef = useRef(null);
39
39
  const { platform, cardFields } = useNativePlatform(onClose, phoneRef);
40
- const { view } = watched;
40
+ const { view } = payment;
41
41
  const core = useCheckoutController(view, {
42
42
  clues,
43
- mutations: calls.mutations,
43
+ calls: payment.calls,
44
44
  platform,
45
45
  });
46
46
  const { ui, derived, actions } = core;
47
47
  const { screen } = derived;
48
48
  // A payment that could not load is not loading any more.
49
- const failed = watched.failed && view === undefined;
49
+ const failed = payment.failed && view === undefined;
50
50
  const loading = !fontsReady || (screen === CheckoutScreenEnum.LOADING && !failed);
51
51
  const terminal = TERMINAL_KIND[screen];
52
52
  useSheetResize([
@@ -22,6 +22,15 @@ export function useNativePlatform(onLeave, phoneField) {
22
22
  });
23
23
  return () => subscription.remove();
24
24
  },
25
+ // Inactive (a notification pulled down) is not leaving.
26
+ onLeave: (listener) => {
27
+ const subscription = AppState.addEventListener('change', (state) => {
28
+ if (state === 'background')
29
+ listener();
30
+ });
31
+ return () => subscription.remove();
32
+ },
33
+ isAway: () => AppState.currentState === 'background',
25
34
  // No clipboard in React Native itself: the share sheet offers Copy.
26
35
  copyText: (text) => {
27
36
  void Share.share({ message: text }).catch(() => undefined);
@@ -3,7 +3,7 @@ import type { Animated, DimensionValue, GestureResponderHandlers, StyleProp, Tex
3
3
  import type { WebView } from 'react-native-webview';
4
4
  import type { CheckoutButtonVariantEnum, TerminalKindEnum } from '../checkout-core/models/checkout.enum.js';
5
5
  import type { LangEnum, ProviderLogoEnum } from '../checkout-core/models/domain.enum.js';
6
- import type { CardFieldsMessage, CheckoutColors, CheckoutCore, CheckoutToast, CheckoutMutations, CheckoutPlatform, CheckoutView } from '../checkout-core/models/checkout.interface.js';
6
+ import type { CardFieldsMessage, CheckoutColors, CheckoutCore, CheckoutToast, CheckoutConnection, CheckoutPayment, CheckoutPlatform, CheckoutView } from '../checkout-core/models/checkout.interface.js';
7
7
  import type { FigtreeEnum, SheetIconEnum } from './sheet.enum.js';
8
8
  /**
9
9
  * How the sheet ended. `closed`: the payer closed it with the payment still
@@ -29,15 +29,6 @@ export interface BrissotoPayProviderProps {
29
29
  url?: string;
30
30
  children?: ReactNode;
31
31
  }
32
- /** The checkout's gateway calls, one interface for the sheet whatever client is behind it. */
33
- export interface CheckoutCalls {
34
- /**
35
- * Follows one payment's checkout view live: `onView` with each answer
36
- * (null: no payment has this id), `onError` when it cannot load. Answers how to stop.
37
- */
38
- watch: (paymentId: string, onView: (view: CheckoutView | null) => void, onError: () => void) => () => void;
39
- mutations: CheckoutMutations;
40
- }
41
32
  /** The sheet the app asked for, and how to answer it. */
42
33
  export interface SheetRequest {
43
34
  paymentId: string;
@@ -45,20 +36,13 @@ export interface SheetRequest {
45
36
  open: boolean;
46
37
  resolve: (result: PaymentSheetResult) => void;
47
38
  }
48
- /** A payment as the sheet follows it: undefined while loading, null for an id that matches none. */
49
- export interface WatchedPayment {
50
- view: CheckoutView | null | undefined;
51
- /** It could not load. */
52
- failed: boolean;
53
- }
54
39
  export interface PaymentSheetModalProps {
55
- calls: CheckoutCalls;
40
+ connection: CheckoutConnection;
56
41
  request: SheetRequest;
57
42
  onClose: (result: PaymentSheetResult) => void;
58
43
  }
59
44
  export interface SheetContentProps {
60
- calls: CheckoutCalls;
61
- watched: WatchedPayment;
45
+ payment: CheckoutPayment;
62
46
  /** Fonts still loading: the sheet shows its skeleton. */
63
47
  fontsReady: boolean;
64
48
  deviceLocale: string;
@@ -1,7 +1,8 @@
1
- import type { ConvexReactClient } from 'convex/react';
2
- import type { CheckoutCalls } from '../models/sheet.interface.js';
1
+ import type { CheckoutConnection } from '../checkout-core/models/checkout.interface.js';
3
2
  /**
4
- * The checkout's calls over the Convex client: the same public functions the
5
- * web checkout calls, by path, so the package needs none of the backend's code.
3
+ * The checkout's calls to the gateway at `url` (ADR 0006): plain HTTP calls,
4
+ * by path, to the same public functions the web checkout calls, so the
5
+ * package needs none of the backend's code. A live connection is opened only
6
+ * to follow one payment, and closed with it.
6
7
  */
7
- export declare function convexCheckoutCalls(client: ConvexReactClient): CheckoutCalls;
8
+ export declare function convexCheckoutConnection(url: string): CheckoutConnection;
@@ -1,12 +1,17 @@
1
+ import { ConvexClient, ConvexHttpClient } from 'convex/browser';
1
2
  import { makeFunctionReference } from 'convex/server';
2
3
  import { CHECKOUT_FUNCTIONS } from "../models/sheet.constants.js";
3
4
  /**
4
- * The checkout's calls over the Convex client: the same public functions the
5
- * web checkout calls, by path, so the package needs none of the backend's code.
5
+ * The checkout's calls to the gateway at `url` (ADR 0006): plain HTTP calls,
6
+ * by path, to the same public functions the web checkout calls, so the
7
+ * package needs none of the backend's code. A live connection is opened only
8
+ * to follow one payment, and closed with it.
6
9
  */
7
- export function convexCheckoutCalls(client) {
8
- const mutation = (path) => (args) => client.mutation(makeFunctionReference(path), args);
9
- const action = (path) => (args) => client.action(makeFunctionReference(path), args);
10
+ export function convexCheckoutConnection(url) {
11
+ const http = new ConvexHttpClient(url);
12
+ const mutation = (path) => (args) => http.mutation(makeFunctionReference(path), args);
13
+ const action = (path) => (args) => http.action(makeFunctionReference(path), args);
14
+ const get = makeFunctionReference(CHECKOUT_FUNCTIONS.get);
10
15
  const mutations = {
11
16
  pay: mutation(CHECKOUT_FUNCTIONS.pay),
12
17
  payWithCode: action(CHECKOUT_FUNCTIONS.payWithCode),
@@ -17,22 +22,14 @@ export function convexCheckoutCalls(client) {
17
22
  simulate: mutation(CHECKOUT_FUNCTIONS.simulate),
18
23
  };
19
24
  return {
20
- watch: (paymentId, onView, onError) => {
21
- const watch = client.watchQuery(makeFunctionReference(CHECKOUT_FUNCTIONS.get), { paymentId });
22
- const read = () => {
23
- let view;
24
- try {
25
- view = watch.localQueryResult();
26
- }
27
- catch {
28
- onError();
29
- return;
30
- }
31
- if (view !== undefined)
32
- onView(view);
25
+ load: (paymentId) => http.query(get, { paymentId }),
26
+ follow: (paymentId, onView, onError) => {
27
+ const socket = new ConvexClient(url, { unsavedChangesWarning: false });
28
+ const stop = socket.onUpdate(get, { paymentId }, (view) => onView(view), () => onError());
29
+ return () => {
30
+ stop();
31
+ void socket.close();
33
32
  };
34
- read();
35
- return watch.onUpdate(read);
36
33
  },
37
34
  mutations,
38
35
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@heyama/pay-react-native",
3
- "version": "1.0.7",
3
+ "version": "1.1.0",
4
4
  "description": "Brissoto Pay for Expo and React Native apps: the payment sheet.",
5
5
  "license": "MIT",
6
6
  "sideEffects": false,
@@ -1,6 +0,0 @@
1
- import type { CheckoutCalls, WatchedPayment } from '../models/sheet.interface.js';
2
- /**
3
- * One payment, live, while `paymentId` is set. Once it is cleared (the sheet
4
- * closing) the last answer stays, so the sheet slides away unchanged.
5
- */
6
- export declare function useWatchedPayment(calls: CheckoutCalls, paymentId: string | null): WatchedPayment;
@@ -1,17 +0,0 @@
1
- import { useEffect, useState } from 'react';
2
- /**
3
- * One payment, live, while `paymentId` is set. Once it is cleared (the sheet
4
- * closing) the last answer stays, so the sheet slides away unchanged.
5
- */
6
- export function useWatchedPayment(calls, paymentId) {
7
- const [watched, setWatched] = useState({
8
- view: undefined,
9
- failed: false,
10
- });
11
- useEffect(() => {
12
- if (!paymentId)
13
- return undefined;
14
- return calls.watch(paymentId, (view) => setWatched({ view, failed: false }), () => setWatched((previous) => ({ ...previous, failed: true })));
15
- }, [calls, paymentId]);
16
- return watched;
17
- }