@openreceive/react 0.4.3 → 0.4.6

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/README.md CHANGED
@@ -1,6 +1,30 @@
1
1
  # @openreceive/react
2
2
 
3
- React checkout components for OpenReceive (`<Checkout>`, `useCheckout`, `PaymentWizard`).
3
+ Add Bitcoin Lightning checkout to your React app with a ready-to-use
4
+ `<Checkout>` component, or build your own payment screen with `useCheckout`
5
+ and `PaymentWizard`. Let customers choose a payment method, scan a QR code,
6
+ and follow payment status without building the checkout flow from scratch.
7
+
8
+ OpenReceive supports optional swaps from **USDT, USDC, SOL, and ETH** through
9
+ a configured swap provider. The provider converts the payment to **BTC over
10
+ Lightning**, which settles into the merchant's connected wallet. Available
11
+ assets and networks depend on the provider; swaps are optional.
12
+
13
+ Pair this browser package with an OpenReceive server integration. Your server
14
+ authorizes the order, sets the amount, configures swaps, and verifies
15
+ settlement; wallet and provider credentials stay on the server.
16
+
17
+ ## Install
18
+
19
+ Use Node.js 22 or later for package tooling and React 18 or later.
20
+
21
+ ```sh
22
+ npm install @openreceive/react
23
+ ```
24
+
25
+ Follow the [frontend checkout guide](https://github.com/openreceive/openreceive/blob/master/docs/guides/frontend-checkout.md)
26
+ to connect the component to your server routes. Use the server-side payment
27
+ hook to fulfill orders; browser callbacks update the interface.
4
28
 
5
29
  ## Mount
6
30
 
@@ -20,7 +44,7 @@ export function Pay() {
20
44
  Pass `reference` to let the component create the checkout (create mode), or pass
21
45
  a `checkout` snapshot to render one your server already created. Prop names,
22
46
  defaults, and the full surface are shared across the wrappers — see
23
- `docs/internal/wrapper-parity.md` in the repository.
47
+ [frontend checkout guide](https://github.com/openreceive/openreceive/blob/master/docs/guides/frontend-checkout.md).
24
48
 
25
49
  Event handlers (`onCopy`, `onOpenWallet`, `onState`, `onSettled`,
26
50
  `onProviderCopy`, `onStartOver`, `onError`) are ordinary props. React receives
@@ -31,11 +55,12 @@ framework values rather than DOM `CustomEvent`s — `onState` gets the
31
55
  and returns the view model plus the copy/open actions. Create mode belongs to
32
56
  `<Checkout>`, so the hook takes no create options.
33
57
 
34
- ## Icon assets
58
+ ## Images
35
59
 
36
- The checkout loads its payment-method icons by URL at runtime; your app must
37
- serve them where the resolution lands. See
38
- [Icon assets in `@openreceive/browser`](https://github.com/openreceive/openreceive/blob/master/packages/js/browser/README.md#icon-assets)
39
- for the per-bundler recipes.
60
+ Everything the checkout draws — payment-method icons, wallet logos, pay
61
+ tutorials — ships inside the JavaScript; nothing to copy, serve or configure,
62
+ under any bundler. If your Content-Security-Policy has a strict `img-src`,
63
+ allow `data:`. See
64
+ [Images in `@openreceive/browser`](https://github.com/openreceive/openreceive/blob/master/packages/js/browser/README.md#images).
40
65
 
41
66
  Part of [OpenReceive](https://openreceive.org). Start with the [Node quickstart](https://github.com/openreceive/openreceive/blob/master/docs/guides/quickstart-node.md); the full API is in the [API reference](https://github.com/openreceive/openreceive/blob/master/docs/guides/api-reference.md).
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as React from 'react';
2
- import { CheckoutComponentProps, CheckoutState, QrEncoder, BrowserLoggerOption, CheckoutStatusRefresh, Status, SwapRefundStaging, CheckoutSnapshot, AssetUrlResolver, ThemePreference, BrowserLogContext, CheckoutInvoiceSnapshot, ResolvedTheme, ThemeModel, CheckoutPhase, CheckoutStatusModel, UnixSeconds, BrowserLogger, TransactionDetailsSource } from '@openreceive/browser/headless';
2
+ import { CheckoutComponentProps, CheckoutState, QrEncoder, BrowserLoggerOption, CheckoutStatusRefresh, Status, SwapRefundStaging, CheckoutSnapshot, ThemePreference, BrowserLogContext, CheckoutInvoiceSnapshot, ResolvedTheme, ThemeModel, CheckoutPhase, CheckoutStatusModel, UnixSeconds, BrowserLogger, TransactionDetailsSource } from '@openreceive/browser/headless';
3
3
  export { TransactionDetailsSource, resolveTransactionDetailRows } from '@openreceive/browser/headless';
4
4
 
5
5
  interface CheckoutData {
@@ -46,6 +46,8 @@ interface UseCheckoutOptions extends CheckoutData, Omit<CheckoutEventHandlers, "
46
46
  * polling — there is nowhere to poll.
47
47
  */
48
48
  readonly prefix?: string;
49
+ /** Header name the page's `<meta name="csrf-token">` is sent under; default `X-CSRF-Token`. */
50
+ readonly csrfHeader?: string;
49
51
  readonly polling?: boolean;
50
52
  readonly pollIntervalMs?: number;
51
53
  }
@@ -162,8 +164,6 @@ interface CheckoutProps extends CheckoutComponentProps, CheckoutEventHandlers, O
162
164
  * `useCheckout` model.
163
165
  */
164
166
  readonly children?: CheckoutChildren;
165
- /** Passed through to the payment wizard. See {@link PaymentWizardProps.resolveAssetUrl}. */
166
- readonly resolveAssetUrl?: AssetUrlResolver;
167
167
  /**
168
168
  * Lock the checkout to one theme. Wins over the stored preference and any
169
169
  * ancestor ThemeScope, and hides the toggle: a host embedding the checkout in
@@ -233,25 +233,12 @@ interface PaymentWizardProps {
233
233
  */
234
234
  readonly prefix?: string;
235
235
  readonly fetch?: typeof globalThis.fetch;
236
+ /** Header name the page's `<meta name="csrf-token">` is sent under; default `X-CSRF-Token`. */
237
+ readonly csrfHeader?: string;
236
238
  readonly clipboard?: Pick<Clipboard, "writeText">;
237
239
  readonly qrEncoder?: QrEncoder;
238
240
  /** Base URL of an external bolt11 decoder; omitted, no "Decode" link is rendered. */
239
241
  readonly decodeLinkUrl?: string;
240
- /**
241
- * Rewrite a packaged asset path (`assets/provider-icons/strike.png`,
242
- * `assets/pay_tutorials/strike-1.webp`) into a URL this host can serve. The
243
- * packaged URLs only resolve under Vite/Rollup; every other bundler needs
244
- * this, or the provider logos and pay tutorials come out as dead `file://`
245
- * links. The payment-method icons are compiled in as `data:` URIs and need
246
- * nothing; given a resolver they go through it too (`assets/icons/btc.svg`).
247
- * See docs/guides/provider-registry.md.
248
- */
249
- readonly resolveAssetUrl?: AssetUrlResolver;
250
- /**
251
- * The string form of the same seam: where this host serves the packages'
252
- * `dist/assets` trees. `resolveAssetUrl` wins when both are set.
253
- */
254
- readonly assetBaseUrl?: string;
255
242
  /**
256
243
  * The two-step refund, from whoever owns the checkout controller —
257
244
  * `<Checkout>` passes its `useCheckout` model. Given one, the wizard stages
package/dist/index.js CHANGED
@@ -47,6 +47,7 @@ function useCheckoutSession(options) {
47
47
  },
48
48
  prefix: () => optionsRef.current.swap?.prefix(),
49
49
  fetch: () => optionsRef.current.swap?.fetch(),
50
+ csrfHeader: () => optionsRef.current.swap?.csrfHeader?.(),
50
51
  onStarted: (invoice) => optionsRef.current.swap?.onStarted?.(invoice)
51
52
  },
52
53
  get logger() {
@@ -769,6 +770,7 @@ function useCheckout(options) {
769
770
  refreshStatus: async (reference) => await refreshStatusRef.current?.(reference) ?? null
770
771
  } : {},
771
772
  ...prefix === void 0 ? {} : { prefix },
773
+ ...options.csrfHeader === void 0 ? {} : { csrfHeader: options.csrfHeader },
772
774
  pollIntervalMs: options.pollIntervalMs,
773
775
  // Omit logger when unset so @openreceive/browser attaches its default console sink.
774
776
  // Pass `false` through to disable; wrap custom sinks so inline host callbacks stay stable.
@@ -796,7 +798,7 @@ function useCheckout(options) {
796
798
  controller.stop();
797
799
  if (controllerRef.current === controller) controllerRef.current = null;
798
800
  };
799
- }, [checkoutIdentity, polls, prefix, options.pollIntervalMs]);
801
+ }, [checkoutIdentity, polls, prefix, options.csrfHeader, options.pollIntervalMs]);
800
802
  const publicStatus = deriveStatus(state);
801
803
  const richStatus = createCheckoutStatusModel2(state);
802
804
  React6.useEffect(() => {
@@ -944,7 +946,6 @@ import {
944
946
  buildMethodGridEntries,
945
947
  checkoutLabels as checkoutLabels5,
946
948
  copyInvoice as copyInvoiceHelper3,
947
- createAssetBaseUrlResolver,
948
949
  createMethodGridDisplay,
949
950
  createPaymentWizardController,
950
951
  createPaymentWizardModel,
@@ -957,14 +958,15 @@ import {
957
958
  getPaymentMethodIcon,
958
959
  getSwapOptionIcon,
959
960
  getWizardEmptyMessage,
961
+ loadPayTutorialImages,
960
962
  networkButtonClasses,
961
963
  networkCheckClasses,
962
964
  networkMobileRevealClasses,
963
965
  networkSummaryIconClasses,
964
966
  orClasses as orClasses6,
965
967
  paymentMethods,
966
- resolveWizardSelection,
967
968
  requestSwapRefund,
969
+ resolveWizardSelection,
968
970
  selectCurrentSwapInvoice,
969
971
  swapAssetMatchesRoute,
970
972
  swapOptionLimitMessage as swapOptionLimitMessage2
@@ -1142,10 +1144,12 @@ function ProviderTutorialModal(options) {
1142
1144
  {
1143
1145
  className: orClasses4.tutorialFrame
1144
1146
  },
1145
- React7.createElement("img", {
1146
- alt: tutorial?.caption ?? "",
1147
+ // No `<img>` until the lazily loaded screenshot is in hand: an
1148
+ // empty src is a request for the page itself.
1149
+ tutorial?.image === void 0 ? null : React7.createElement("img", {
1150
+ alt: tutorial.caption,
1147
1151
  className: orClasses4.tutorialImage,
1148
- src: tutorial?.image ?? ""
1152
+ src: tutorial.image
1149
1153
  })
1150
1154
  ),
1151
1155
  React7.createElement(
@@ -2020,6 +2024,7 @@ function PaymentWizard(props) {
2020
2024
  },
2021
2025
  prefix: () => props.prefix,
2022
2026
  fetch: () => fetcher,
2027
+ csrfHeader: () => props.csrfHeader,
2023
2028
  onStarted: (invoice) => props.onSwapStarted?.(invoice)
2024
2029
  },
2025
2030
  ...props.logger === void 0 ? {} : { logger: props.logger },
@@ -2073,6 +2078,7 @@ function PaymentWizard(props) {
2073
2078
  ) : await requestSwapRefund({
2074
2079
  fetch: fetcher,
2075
2080
  prefix,
2081
+ ...props.csrfHeader === void 0 ? {} : { csrfHeader: props.csrfHeader },
2076
2082
  reference,
2077
2083
  paymentHash: resolveAttemptPaymentHash(
2078
2084
  [startedSwapInvoice, ...checkout?.invoices ?? []],
@@ -2090,6 +2096,7 @@ function PaymentWizard(props) {
2090
2096
  },
2091
2097
  [
2092
2098
  props.prefix,
2099
+ props.csrfHeader,
2093
2100
  reference,
2094
2101
  fetcher,
2095
2102
  props.onError,
@@ -2107,14 +2114,25 @@ function PaymentWizard(props) {
2107
2114
  );
2108
2115
  const model = createPaymentWizardModel(selection);
2109
2116
  const { wizard } = model;
2110
- const resolveAssetUrl = props.resolveAssetUrl ?? (props.assetBaseUrl === void 0 || props.assetBaseUrl.trim() === "" ? void 0 : createAssetBaseUrlResolver(props.assetBaseUrl));
2117
+ const tutorialOpen = activeTutorial !== null;
2118
+ const [tutorialImagesReady, setTutorialImagesReady] = React9.useState(false);
2119
+ React9.useEffect(() => {
2120
+ if (!tutorialOpen || tutorialImagesReady) return;
2121
+ let cancelled = false;
2122
+ loadPayTutorialImages().then(
2123
+ () => {
2124
+ if (!cancelled) setTutorialImagesReady(true);
2125
+ },
2126
+ () => void 0
2127
+ );
2128
+ return () => {
2129
+ cancelled = true;
2130
+ };
2131
+ }, [tutorialOpen, tutorialImagesReady]);
2111
2132
  const routeAssetDisplays = createWizardRouteAssetDisplays(model.routeAssets, {
2112
- selectedRoute: model.selectedRoute,
2113
- ...resolveAssetUrl === void 0 ? {} : { resolveAssetUrl }
2114
- });
2115
- const routeDisplays = createWizardRouteDisplays(wizard.routes, {
2116
- ...resolveAssetUrl === void 0 ? {} : { resolveAssetUrl }
2133
+ selectedRoute: model.selectedRoute
2117
2134
  });
2135
+ const routeDisplays = createWizardRouteDisplays(wizard.routes);
2118
2136
  const showRoutePicker = routeAssetDisplays.length > 0 && (model.selectedRoute === null || routeDisplays.length === 0);
2119
2137
  const activeTutorialProvider = activeTutorial === null ? void 0 : routeDisplays.flatMap((route) => route.providers).find((provider) => provider.id === activeTutorial.providerId);
2120
2138
  const swapAssetOptions = swapOptions.enabled ? swapOptions.options.filter((option) => option.provider.length > 0) : [];
@@ -2258,8 +2276,7 @@ function PaymentWizard(props) {
2258
2276
  void props.onRequestLightning?.();
2259
2277
  }
2260
2278
  },
2261
- onContinueSwap: selectSwapAsset,
2262
- ...resolveAssetUrl === void 0 ? {} : { resolveAssetUrl }
2279
+ onContinueSwap: selectSwapAsset
2263
2280
  }) : null,
2264
2281
  // On the grid the note is a section of its own, under the tiles: there is no
2265
2282
  // body to put it in until a method is chosen.
@@ -2532,10 +2549,14 @@ function renderCompactPaymentMethodSelector(options) {
2532
2549
  React9.createElement("img", {
2533
2550
  alt: "",
2534
2551
  className: orClasses6.methodNetworkIcon,
2535
- src: getNetworkIcon(option.network_label, options.resolveAssetUrl)
2552
+ src: getNetworkIcon(option.network_label)
2536
2553
  })
2537
2554
  ),
2538
- React9.createElement("span", { className: "truncate" }, option.network_label),
2555
+ React9.createElement(
2556
+ "span",
2557
+ { className: orClasses6.methodNetworkLabel },
2558
+ option.network_label
2559
+ ),
2539
2560
  optionSelected ? React9.createElement(
2540
2561
  "span",
2541
2562
  {
@@ -2594,7 +2615,7 @@ function renderCompactPaymentMethodSelector(options) {
2594
2615
  React9.createElement(
2595
2616
  "div",
2596
2617
  {
2597
- className: orClasses6.wizardBody,
2618
+ className: orClasses6.methodBody,
2598
2619
  "aria-labelledby": "payment-method-heading"
2599
2620
  },
2600
2621
  React9.createElement(
@@ -2627,7 +2648,7 @@ function renderCompactPaymentMethodSelector(options) {
2627
2648
  React9.createElement("img", {
2628
2649
  alt: "",
2629
2650
  className: orClasses6.methodIcon,
2630
- src: getPaymentMethodIcon(method.id, options.resolveAssetUrl)
2651
+ src: getPaymentMethodIcon(method.id)
2631
2652
  })
2632
2653
  ),
2633
2654
  React9.createElement(
@@ -2693,7 +2714,7 @@ function renderCompactPaymentMethodSelector(options) {
2693
2714
  }) : React9.createElement("img", {
2694
2715
  alt: "",
2695
2716
  className: orClasses6.methodIcon,
2696
- src: getSwapOptionIcon(displayOption, options.resolveAssetUrl)
2717
+ src: getSwapOptionIcon(displayOption)
2697
2718
  })
2698
2719
  ),
2699
2720
  React9.createElement(
@@ -2920,6 +2941,7 @@ function CheckoutSnapshotMode(props) {
2920
2941
  function CheckoutCreate(props) {
2921
2942
  const reference = props.reference;
2922
2943
  const resolvedPrefix = props.prefix ?? OPENRECEIVE_DEFAULT_PREFIX;
2944
+ const csrfHeader = props.csrfHeader;
2923
2945
  const {
2924
2946
  onError,
2925
2947
  metadata,
@@ -2952,6 +2974,7 @@ function CheckoutCreate(props) {
2952
2974
  requestCheckout: (id) => requestCheckout({
2953
2975
  prefix: resolvedPrefix,
2954
2976
  reference: id,
2977
+ ...csrfHeader === void 0 ? {} : { csrfHeader },
2955
2978
  ...metadataRef.current === void 0 ? {} : { metadata: metadataRef.current },
2956
2979
  ...createFetchRef.current === void 0 ? {} : { fetch: createFetchRef.current }
2957
2980
  }),
@@ -2974,11 +2997,13 @@ function CheckoutCreate(props) {
2974
2997
  prepareCheckout({
2975
2998
  prefix: resolvedPrefix,
2976
2999
  reference,
3000
+ ...csrfHeader === void 0 ? {} : { csrfHeader },
2977
3001
  ...createFetchRef.current === void 0 ? {} : { fetch: createFetchRef.current }
2978
3002
  }).then(
2979
3003
  (checkout) => resumePaymentHash === void 0 ? checkout : resumeSwapAttempt({
2980
3004
  fetch: createFetchRef.current ?? globalThis.fetch,
2981
3005
  prefix: resolvedPrefix,
3006
+ ...csrfHeader === void 0 ? {} : { csrfHeader },
2982
3007
  reference,
2983
3008
  paymentHash: resumePaymentHash,
2984
3009
  snapshot: checkout
@@ -2996,7 +3021,7 @@ function CheckoutCreate(props) {
2996
3021
  return () => {
2997
3022
  cancelled = true;
2998
3023
  };
2999
- }, [reference, resolvedPrefix, resumePaymentHash, attempt]);
3024
+ }, [reference, resolvedPrefix, csrfHeader, resumePaymentHash, attempt]);
3000
3025
  const onSwapStarted = React10.useCallback(
3001
3026
  (invoice) => {
3002
3027
  setCreated((current) => {
@@ -3085,6 +3110,7 @@ function CheckoutView(props) {
3085
3110
  // The dispatcher and CheckoutCreate both resolve this before rendering the
3086
3111
  // view, so the default below only guards a direct CheckoutView call.
3087
3112
  prefix = OPENRECEIVE_DEFAULT_PREFIX,
3113
+ csrfHeader,
3088
3114
  metadata: _metadata,
3089
3115
  createFetch: _createFetch,
3090
3116
  // Not `_`-prefixed like its neighbours: the view reads it, to infer
@@ -3121,8 +3147,6 @@ function CheckoutView(props) {
3121
3147
  components,
3122
3148
  classNames,
3123
3149
  children,
3124
- resolveAssetUrl,
3125
- assetBaseUrl,
3126
3150
  className,
3127
3151
  ...sectionProps
3128
3152
  } = props;
@@ -3134,6 +3158,7 @@ function CheckoutView(props) {
3134
3158
  onError,
3135
3159
  refreshStatus,
3136
3160
  prefix,
3161
+ csrfHeader,
3137
3162
  onState,
3138
3163
  onSettled,
3139
3164
  polling,
@@ -3374,6 +3399,7 @@ function CheckoutView(props) {
3374
3399
  onError,
3375
3400
  onSwapFocusChange: setSwapFocused,
3376
3401
  prefix,
3402
+ csrfHeader,
3377
3403
  qrEncoder,
3378
3404
  decodeLinkUrl,
3379
3405
  logContext: getCheckoutLogContext({
@@ -3385,8 +3411,6 @@ function CheckoutView(props) {
3385
3411
  onCopy,
3386
3412
  onRequestLightning,
3387
3413
  onSwapStarted,
3388
- resolveAssetUrl,
3389
- assetBaseUrl,
3390
3414
  // Whether a payer who closes the tab has a URL to come back
3391
3415
  // to. Explicit wins — only the host knows about a per-order
3392
3416
  // route of its own; otherwise infer it from the two props that