@mayarin/react 0.1.0-next.1 → 0.1.0-next.2

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
@@ -7,6 +7,21 @@ app. Full guide: <https://docs.mayarin.xyz/sdk/typescript/react>.
7
7
  npm install @mayarin/react@next @mayarin/sdk@next
8
8
  ```
9
9
 
10
+ ## Open it in a modal
11
+
12
+ `mode="modal"` opens the checkout over your page instead of leaving it. `onSuccess`, `onFailed` and
13
+ `onClose` report back; confirm the payment in your webhook before shipping anything.
14
+
15
+ ```tsx
16
+ <PayButton
17
+ mode="modal"
18
+ checkout={createCheckout}
19
+ onSuccess={({ intentId }) => showThanks(intentId)}
20
+ >
21
+ Pay with crypto
22
+ </PayButton>
23
+ ```
24
+
10
25
  ## Server mode — anything you sell
11
26
 
12
27
  Your backend mints the checkout with its secret key (`@mayarin/sdk`) and returns the URL. The price
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { MayarinBrowserClient } from "@mayarin/sdk/browser";
1
+ import { MayarinBrowserClient, OpenCheckoutOptions } from "@mayarin/sdk/browser";
2
2
  import { ButtonHTMLAttributes, ReactNode } from "react";
3
3
  //#region src/checkout.d.ts
4
4
  /** The `POST /v1/carts/checkout` body, as the SDK types it. */
@@ -37,7 +37,13 @@ export declare function cartBody(options: BrowserCheckout): CartBody;
37
37
  export declare function resolveCheckoutUrl(options: CheckoutOptions, fetcher?: typeof globalThis.fetch): Promise<string>;
38
38
  //#endregion
39
39
  //#region src/use-mayarin-checkout.d.ts
40
- interface CheckoutCallbacks {
40
+ interface CheckoutCallbacks extends OpenCheckoutOptions {
41
+ /**
42
+ * `"redirect"` (default) sends the buyer to the hosted checkout. `"modal"`
43
+ * opens it over the page, so they never leave it; `onSuccess`, `onFailed`
44
+ * and `onClose` report back.
45
+ */
46
+ readonly mode?: "redirect" | "modal";
41
47
  /**
42
48
  * Called with the checkout URL instead of navigating, e.g. to open it in a
43
49
  * new tab. Without it the current page goes to the checkout.
@@ -46,7 +52,7 @@ interface CheckoutCallbacks {
46
52
  readonly onError?: (error: Error) => void;
47
53
  }
48
54
  interface MayarinCheckout {
49
- /** Mints the checkout and sends the buyer there. A second call while one runs does nothing. */
55
+ /** Mints the checkout and sends the buyer there, or opens it in a modal. A second call while one runs does nothing. */
50
56
  readonly redirect: () => Promise<void>;
51
57
  readonly loading: boolean;
52
58
  readonly error: Error | undefined;
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { createMayarinBrowser } from "@mayarin/sdk/browser";
1
+ import { createMayarinBrowser, openCheckout } from "@mayarin/sdk/browser";
2
2
  import { useCallback, useEffect, useRef, useState } from "react";
3
3
  import { jsx } from "react/jsx-runtime";
4
4
  //#region src/checkout.ts
@@ -92,14 +92,18 @@ function useMayarinCheckout(options) {
92
92
  running.current = true;
93
93
  setLoading(true);
94
94
  setError(void 0);
95
- const { onRedirect, onError } = latest.current;
95
+ const { onRedirect, onError, mode, onSuccess, onFailed, onClose } = latest.current;
96
96
  try {
97
97
  const url = await resolveCheckoutUrl(latest.current);
98
- if (onRedirect === void 0) {
98
+ if (mode === "modal") openCheckout(url, {
99
+ ...onSuccess === void 0 ? {} : { onSuccess },
100
+ ...onFailed === void 0 ? {} : { onFailed },
101
+ ...onClose === void 0 ? {} : { onClose }
102
+ });
103
+ else if (onRedirect === void 0) {
99
104
  window.location.assign(url);
100
105
  return;
101
- }
102
- onRedirect(url);
106
+ } else onRedirect(url);
103
107
  } catch (thrown) {
104
108
  const failure = thrown instanceof Error ? thrown : new Error(String(thrown));
105
109
  setError(failure);
@@ -128,6 +132,10 @@ const CHECKOUT_PROPS = /* @__PURE__ */ new Set([
128
132
  "metadata",
129
133
  "onRedirect",
130
134
  "onError",
135
+ "mode",
136
+ "onSuccess",
137
+ "onFailed",
138
+ "onClose",
131
139
  "children"
132
140
  ]);
133
141
  function buttonAttributes(props) {
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../src/checkout.ts","../src/use-mayarin-checkout.ts","../src/pay-button.tsx"],"sourcesContent":["/**\n * Turning checkout options into the hosted checkout URL — no React here.\n *\n * Two ways to get a URL, and the difference is who decides the price:\n *\n * - **Server** (`checkout`): the merchant's own backend mints the intent with\n * its secret key and hands back the URL. The price never leaves the server,\n * so nobody can edit it. Use this for anything you sell.\n * - **Browser** (`publishableKey` + `amount`): the browser mints the intent\n * itself with a publishable key. No backend needed, but the amount is\n * whatever the browser sends — a buyer with devtools can change it. Fine for\n * donations, tips and pay-what-you-want; for a sale, check the paid amount\n * against your order (by `merchantReference`) in the webhook before\n * shipping anything.\n */\n\nimport { createMayarinBrowser, type MayarinBrowserClient } from \"@mayarin/sdk/browser\";\n\n/** The `POST /v1/carts/checkout` body, as the SDK types it. */\nexport type CartBody = Parameters<MayarinBrowserClient[\"commerce\"][\"carts\"][\"checkout\"]>[0];\n\n/** What a server-mode `checkout` resolves to: the URL, or an object carrying it. */\nexport type CheckoutUrl = string | { readonly url: string };\n\nexport interface ServerCheckout {\n /** Calls your backend, which mints the intent and returns its checkout URL. */\n readonly checkout: () => Promise<CheckoutUrl>;\n readonly publishableKey?: never;\n}\n\nexport interface BrowserCheckout {\n readonly checkout?: never;\n /** A `pk_...` key from the dashboard. It names the merchant; no merchant id is needed. */\n readonly publishableKey: string;\n /** Origin of the payment API, e.g. `https://api-testnet.mayarin.xyz`. */\n readonly baseUrl: string;\n /** Decimal amount per unit in `currency`, e.g. `\"150000.00\"`. Never a float. */\n readonly amount: string;\n /** The currency the merchant prices in, e.g. `\"IDR\"`. */\n readonly currency: CartBody[\"currency\"];\n /** The line the buyer sees on the checkout page. Defaults to `\"Payment\"`. */\n readonly name?: string;\n readonly quantity?: number;\n /** Your order id. Comes back on the payment and in every webhook. */\n readonly merchantReference?: string;\n /** Where the buyer lands after paying: `{successUrl}/{paymentIntentId}`. HTTPS or localhost. */\n readonly successUrl?: string;\n readonly metadata?: Readonly<Record<string, string>>;\n}\n\nexport type CheckoutOptions = ServerCheckout | BrowserCheckout;\n\n/** The `POST /v1/carts/checkout` body for a browser-mode checkout: one ad-hoc line. */\nexport function cartBody(options: BrowserCheckout): CartBody {\n const metadata: Record<string, string> = {\n ...options.metadata,\n ...(options.successUrl === undefined ? {} : { checkoutSuccessBaseUrl: options.successUrl }),\n };\n return {\n currency: options.currency,\n lines: [\n {\n name: options.name ?? \"Payment\",\n unitPrice: { amount: options.amount, asset: options.currency },\n quantity: options.quantity ?? 1,\n },\n ],\n ...(options.merchantReference === undefined\n ? {}\n : { merchantReference: options.merchantReference }),\n ...(Object.keys(metadata).length === 0 ? {} : { metadata }),\n };\n}\n\nexport async function resolveCheckoutUrl(\n options: CheckoutOptions,\n fetcher: typeof globalThis.fetch = globalThis.fetch,\n): Promise<string> {\n if (options.checkout !== undefined) {\n const result = await options.checkout();\n return checkoutUrl(typeof result === \"string\" ? result : result.url);\n }\n const client = createMayarinBrowser({\n baseUrl: options.baseUrl,\n publishableKey: options.publishableKey,\n fetch: fetcher,\n });\n return checkoutUrl((await client.commerce.carts.checkout(cartBody(options))).url);\n}\n\n/**\n * The page navigates to whatever this returns, so only a web URL passes: a\n * `javascript:` URL from a backend echoing user input would run in the page.\n */\nfunction checkoutUrl(value: string): string {\n let url: URL;\n try {\n url = new URL(value);\n } catch {\n throw new TypeError(`The checkout URL is not a URL: ${value}`);\n }\n if (url.protocol !== \"https:\" && url.protocol !== \"http:\") {\n throw new TypeError(`The checkout URL must be http(s), got ${url.protocol}`);\n }\n return url.href;\n}\n","import { useCallback, useEffect, useRef, useState } from \"react\";\nimport { type CheckoutOptions, resolveCheckoutUrl } from \"./checkout.ts\";\n\nexport interface CheckoutCallbacks {\n /**\n * Called with the checkout URL instead of navigating, e.g. to open it in a\n * new tab. Without it the current page goes to the checkout.\n */\n readonly onRedirect?: (url: string) => void;\n readonly onError?: (error: Error) => void;\n}\n\nexport interface MayarinCheckout {\n /** Mints the checkout and sends the buyer there. A second call while one runs does nothing. */\n readonly redirect: () => Promise<void>;\n readonly loading: boolean;\n readonly error: Error | undefined;\n}\n\n/**\n * The headless form of `PayButton`: wire `redirect` to any element you like.\n *\n * Options are read when `redirect` runs, not when the hook first renders, so an\n * amount that changes with the cart is always the current one.\n */\nexport function useMayarinCheckout(options: CheckoutOptions & CheckoutCallbacks): MayarinCheckout {\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<Error | undefined>(undefined);\n const latest = useRef(options);\n latest.current = options;\n const running = useRef(false);\n\n // Coming back with the browser's back button restores the page from the\n // back/forward cache with the button still busy. Release it.\n useEffect(() => {\n const release = (event: PageTransitionEvent) => {\n if (event.persisted) {\n running.current = false;\n setLoading(false);\n }\n };\n window.addEventListener(\"pageshow\", release);\n return () => window.removeEventListener(\"pageshow\", release);\n }, []);\n\n const redirect = useCallback(async () => {\n if (running.current) return;\n running.current = true;\n setLoading(true);\n setError(undefined);\n const { onRedirect, onError } = latest.current;\n try {\n const url = await resolveCheckoutUrl(latest.current);\n if (onRedirect === undefined) {\n // Stay busy: the page is leaving.\n window.location.assign(url);\n return;\n }\n onRedirect(url);\n } catch (thrown) {\n const failure = thrown instanceof Error ? thrown : new Error(String(thrown));\n setError(failure);\n onError?.(failure);\n }\n running.current = false;\n setLoading(false);\n }, []);\n\n return { redirect, loading, error };\n}\n","import type { ButtonHTMLAttributes, ReactNode } from \"react\";\nimport type { CheckoutOptions } from \"./checkout.ts\";\nimport { type CheckoutCallbacks, useMayarinCheckout } from \"./use-mayarin-checkout.ts\";\n\ntype ButtonAttributes = Omit<\n ButtonHTMLAttributes<HTMLButtonElement>,\n \"children\" | \"onClick\" | \"onError\" | \"type\"\n>;\n\nexport type PayButtonProps = CheckoutOptions &\n CheckoutCallbacks &\n ButtonAttributes & {\n /**\n * Anything to render inside the button — text, an icon, a whole layout.\n * A function receives `{ loading }` to show its own busy state.\n * Defaults to \"Pay with crypto\".\n */\n readonly children?: ReactNode | ((state: { readonly loading: boolean }) => ReactNode);\n };\n\n/** Every prop the checkout reads. The rest go on the `<button>` untouched. */\nconst CHECKOUT_PROPS: ReadonlySet<string> = new Set([\n \"checkout\",\n \"publishableKey\",\n \"baseUrl\",\n \"amount\",\n \"currency\",\n \"name\",\n \"quantity\",\n \"merchantReference\",\n \"successUrl\",\n \"metadata\",\n \"onRedirect\",\n \"onError\",\n \"children\",\n]);\n\nfunction buttonAttributes(props: PayButtonProps): ButtonAttributes {\n return Object.fromEntries(\n Object.entries(props).filter(([key]) => !CHECKOUT_PROPS.has(key)),\n ) as ButtonAttributes;\n}\n\n/**\n * A button that sends the buyer to Mayarin's hosted checkout.\n *\n * Unstyled on purpose: pass `className` or `style` and it looks like the rest\n * of your page. It never submits a surrounding form.\n */\nexport function PayButton(props: PayButtonProps) {\n const { redirect, loading } = useMayarinCheckout(props);\n const { children = \"Pay with crypto\" } = props;\n const attributes = buttonAttributes(props);\n\n return (\n <button\n {...attributes}\n type=\"button\"\n disabled={attributes.disabled === true || loading}\n aria-busy={loading}\n onClick={() => void redirect()}\n >\n {typeof children === \"function\" ? children({ loading }) : children}\n </button>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAqDA,SAAgB,SAAS,SAAoC;CAC3D,MAAM,WAAmC;EACvC,GAAG,QAAQ;EACX,GAAI,QAAQ,eAAe,KAAA,IAAY,CAAC,IAAI,EAAE,wBAAwB,QAAQ,WAAW;CAC3F;CACA,OAAO;EACL,UAAU,QAAQ;EAClB,OAAO,CACL;GACE,MAAM,QAAQ,QAAQ;GACtB,WAAW;IAAE,QAAQ,QAAQ;IAAQ,OAAO,QAAQ;GAAS;GAC7D,UAAU,QAAQ,YAAY;EAChC,CACF;EACA,GAAI,QAAQ,sBAAsB,KAAA,IAC9B,CAAC,IACD,EAAE,mBAAmB,QAAQ,kBAAkB;EACnD,GAAI,OAAO,KAAK,QAAQ,CAAC,CAAC,WAAW,IAAI,CAAC,IAAI,EAAE,SAAS;CAC3D;AACF;AAEA,eAAsB,mBACpB,SACA,UAAmC,WAAW,OAC7B;CACjB,IAAI,QAAQ,aAAa,KAAA,GAAW;EAClC,MAAM,SAAS,MAAM,QAAQ,SAAS;EACtC,OAAO,YAAY,OAAO,WAAW,WAAW,SAAS,OAAO,GAAG;CACrE;CAMA,OAAO,aAAa,MALL,qBAAqB;EAClC,SAAS,QAAQ;EACjB,gBAAgB,QAAQ;EACxB,OAAO;CACT,CAC+B,CAAC,CAAC,SAAS,MAAM,SAAS,SAAS,OAAO,CAAC,EAAA,CAAG,GAAG;AAClF;;;;;AAMA,SAAS,YAAY,OAAuB;CAC1C,IAAI;CACJ,IAAI;EACF,MAAM,IAAI,IAAI,KAAK;CACrB,QAAQ;EACN,MAAM,IAAI,UAAU,kCAAkC,OAAO;CAC/D;CACA,IAAI,IAAI,aAAa,YAAY,IAAI,aAAa,SAChD,MAAM,IAAI,UAAU,yCAAyC,IAAI,UAAU;CAE7E,OAAO,IAAI;AACb;;;;;;;;;AChFA,SAAgB,mBAAmB,SAA+D;CAChG,MAAM,CAAC,SAAS,cAAc,SAAS,KAAK;CAC5C,MAAM,CAAC,OAAO,YAAY,SAA4B,KAAA,CAAS;CAC/D,MAAM,SAAS,OAAO,OAAO;CAC7B,OAAO,UAAU;CACjB,MAAM,UAAU,OAAO,KAAK;CAI5B,gBAAgB;EACd,MAAM,WAAW,UAA+B;GAC9C,IAAI,MAAM,WAAW;IACnB,QAAQ,UAAU;IAClB,WAAW,KAAK;GAClB;EACF;EACA,OAAO,iBAAiB,YAAY,OAAO;EAC3C,aAAa,OAAO,oBAAoB,YAAY,OAAO;CAC7D,GAAG,CAAC,CAAC;CAyBL,OAAO;EAAE,UAvBQ,YAAY,YAAY;GACvC,IAAI,QAAQ,SAAS;GACrB,QAAQ,UAAU;GAClB,WAAW,IAAI;GACf,SAAS,KAAA,CAAS;GAClB,MAAM,EAAE,YAAY,YAAY,OAAO;GACvC,IAAI;IACF,MAAM,MAAM,MAAM,mBAAmB,OAAO,OAAO;IACnD,IAAI,eAAe,KAAA,GAAW;KAE5B,OAAO,SAAS,OAAO,GAAG;KAC1B;IACF;IACA,WAAW,GAAG;GAChB,SAAS,QAAQ;IACf,MAAM,UAAU,kBAAkB,QAAQ,SAAS,IAAI,MAAM,OAAO,MAAM,CAAC;IAC3E,SAAS,OAAO;IAChB,UAAU,OAAO;GACnB;GACA,QAAQ,UAAU;GAClB,WAAW,KAAK;EAClB,GAAG,CAAC,CAEY;EAAG;EAAS;CAAM;AACpC;;;;AChDA,MAAM,iCAAsC,IAAI,IAAI;CAClD;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF,CAAC;AAED,SAAS,iBAAiB,OAAyC;CACjE,OAAO,OAAO,YACZ,OAAO,QAAQ,KAAK,CAAC,CAAC,QAAQ,CAAC,SAAS,CAAC,eAAe,IAAI,GAAG,CAAC,CAClE;AACF;;;;;;;AAQA,SAAgB,UAAU,OAAuB;CAC/C,MAAM,EAAE,UAAU,YAAY,mBAAmB,KAAK;CACtD,MAAM,EAAE,WAAW,sBAAsB;CACzC,MAAM,aAAa,iBAAiB,KAAK;CAEzC,OACE,oBAAC,UAAD;EACE,GAAI;EACJ,MAAK;EACL,UAAU,WAAW,aAAa,QAAQ;EAC1C,aAAW;EACX,eAAe,KAAK,SAAS;EAE5B,UAAA,OAAO,aAAa,aAAa,SAAS,EAAE,QAAQ,CAAC,IAAI;CACpD,CAAA;AAEZ"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/checkout.ts","../src/use-mayarin-checkout.ts","../src/pay-button.tsx"],"sourcesContent":["/**\n * Turning checkout options into the hosted checkout URL — no React here.\n *\n * Two ways to get a URL, and the difference is who decides the price:\n *\n * - **Server** (`checkout`): the merchant's own backend mints the intent with\n * its secret key and hands back the URL. The price never leaves the server,\n * so nobody can edit it. Use this for anything you sell.\n * - **Browser** (`publishableKey` + `amount`): the browser mints the intent\n * itself with a publishable key. No backend needed, but the amount is\n * whatever the browser sends — a buyer with devtools can change it. Fine for\n * donations, tips and pay-what-you-want; for a sale, check the paid amount\n * against your order (by `merchantReference`) in the webhook before\n * shipping anything.\n */\n\nimport { createMayarinBrowser, type MayarinBrowserClient } from \"@mayarin/sdk/browser\";\n\n/** The `POST /v1/carts/checkout` body, as the SDK types it. */\nexport type CartBody = Parameters<MayarinBrowserClient[\"commerce\"][\"carts\"][\"checkout\"]>[0];\n\n/** What a server-mode `checkout` resolves to: the URL, or an object carrying it. */\nexport type CheckoutUrl = string | { readonly url: string };\n\nexport interface ServerCheckout {\n /** Calls your backend, which mints the intent and returns its checkout URL. */\n readonly checkout: () => Promise<CheckoutUrl>;\n readonly publishableKey?: never;\n}\n\nexport interface BrowserCheckout {\n readonly checkout?: never;\n /** A `pk_...` key from the dashboard. It names the merchant; no merchant id is needed. */\n readonly publishableKey: string;\n /** Origin of the payment API, e.g. `https://api-testnet.mayarin.xyz`. */\n readonly baseUrl: string;\n /** Decimal amount per unit in `currency`, e.g. `\"150000.00\"`. Never a float. */\n readonly amount: string;\n /** The currency the merchant prices in, e.g. `\"IDR\"`. */\n readonly currency: CartBody[\"currency\"];\n /** The line the buyer sees on the checkout page. Defaults to `\"Payment\"`. */\n readonly name?: string;\n readonly quantity?: number;\n /** Your order id. Comes back on the payment and in every webhook. */\n readonly merchantReference?: string;\n /** Where the buyer lands after paying: `{successUrl}/{paymentIntentId}`. HTTPS or localhost. */\n readonly successUrl?: string;\n readonly metadata?: Readonly<Record<string, string>>;\n}\n\nexport type CheckoutOptions = ServerCheckout | BrowserCheckout;\n\n/** The `POST /v1/carts/checkout` body for a browser-mode checkout: one ad-hoc line. */\nexport function cartBody(options: BrowserCheckout): CartBody {\n const metadata: Record<string, string> = {\n ...options.metadata,\n ...(options.successUrl === undefined ? {} : { checkoutSuccessBaseUrl: options.successUrl }),\n };\n return {\n currency: options.currency,\n lines: [\n {\n name: options.name ?? \"Payment\",\n unitPrice: { amount: options.amount, asset: options.currency },\n quantity: options.quantity ?? 1,\n },\n ],\n ...(options.merchantReference === undefined\n ? {}\n : { merchantReference: options.merchantReference }),\n ...(Object.keys(metadata).length === 0 ? {} : { metadata }),\n };\n}\n\nexport async function resolveCheckoutUrl(\n options: CheckoutOptions,\n fetcher: typeof globalThis.fetch = globalThis.fetch,\n): Promise<string> {\n if (options.checkout !== undefined) {\n const result = await options.checkout();\n return checkoutUrl(typeof result === \"string\" ? result : result.url);\n }\n const client = createMayarinBrowser({\n baseUrl: options.baseUrl,\n publishableKey: options.publishableKey,\n fetch: fetcher,\n });\n return checkoutUrl((await client.commerce.carts.checkout(cartBody(options))).url);\n}\n\n/**\n * The page navigates to whatever this returns, so only a web URL passes: a\n * `javascript:` URL from a backend echoing user input would run in the page.\n */\nfunction checkoutUrl(value: string): string {\n let url: URL;\n try {\n url = new URL(value);\n } catch {\n throw new TypeError(`The checkout URL is not a URL: ${value}`);\n }\n if (url.protocol !== \"https:\" && url.protocol !== \"http:\") {\n throw new TypeError(`The checkout URL must be http(s), got ${url.protocol}`);\n }\n return url.href;\n}\n","import { type OpenCheckoutOptions, openCheckout } from \"@mayarin/sdk/browser\";\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport { type CheckoutOptions, resolveCheckoutUrl } from \"./checkout.ts\";\n\nexport interface CheckoutCallbacks extends OpenCheckoutOptions {\n /**\n * `\"redirect\"` (default) sends the buyer to the hosted checkout. `\"modal\"`\n * opens it over the page, so they never leave it; `onSuccess`, `onFailed`\n * and `onClose` report back.\n */\n readonly mode?: \"redirect\" | \"modal\";\n /**\n * Called with the checkout URL instead of navigating, e.g. to open it in a\n * new tab. Without it the current page goes to the checkout.\n */\n readonly onRedirect?: (url: string) => void;\n readonly onError?: (error: Error) => void;\n}\n\nexport interface MayarinCheckout {\n /** Mints the checkout and sends the buyer there, or opens it in a modal. A second call while one runs does nothing. */\n readonly redirect: () => Promise<void>;\n readonly loading: boolean;\n readonly error: Error | undefined;\n}\n\n/**\n * The headless form of `PayButton`: wire `redirect` to any element you like.\n *\n * Options are read when `redirect` runs, not when the hook first renders, so an\n * amount that changes with the cart is always the current one.\n */\nexport function useMayarinCheckout(options: CheckoutOptions & CheckoutCallbacks): MayarinCheckout {\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<Error | undefined>(undefined);\n const latest = useRef(options);\n latest.current = options;\n const running = useRef(false);\n\n // Coming back with the browser's back button restores the page from the\n // back/forward cache with the button still busy. Release it.\n useEffect(() => {\n const release = (event: PageTransitionEvent) => {\n if (event.persisted) {\n running.current = false;\n setLoading(false);\n }\n };\n window.addEventListener(\"pageshow\", release);\n return () => window.removeEventListener(\"pageshow\", release);\n }, []);\n\n const redirect = useCallback(async () => {\n if (running.current) return;\n running.current = true;\n setLoading(true);\n setError(undefined);\n const { onRedirect, onError, mode, onSuccess, onFailed, onClose } = latest.current;\n try {\n const url = await resolveCheckoutUrl(latest.current);\n if (mode === \"modal\") {\n openCheckout(url, {\n ...(onSuccess === undefined ? {} : { onSuccess }),\n ...(onFailed === undefined ? {} : { onFailed }),\n ...(onClose === undefined ? {} : { onClose }),\n });\n } else if (onRedirect === undefined) {\n // Stay busy: the page is leaving.\n window.location.assign(url);\n return;\n } else {\n onRedirect(url);\n }\n } catch (thrown) {\n const failure = thrown instanceof Error ? thrown : new Error(String(thrown));\n setError(failure);\n onError?.(failure);\n }\n running.current = false;\n setLoading(false);\n }, []);\n\n return { redirect, loading, error };\n}\n","import type { ButtonHTMLAttributes, ReactNode } from \"react\";\nimport type { CheckoutOptions } from \"./checkout.ts\";\nimport { type CheckoutCallbacks, useMayarinCheckout } from \"./use-mayarin-checkout.ts\";\n\ntype ButtonAttributes = Omit<\n ButtonHTMLAttributes<HTMLButtonElement>,\n \"children\" | \"onClick\" | \"onError\" | \"type\"\n>;\n\nexport type PayButtonProps = CheckoutOptions &\n CheckoutCallbacks &\n ButtonAttributes & {\n /**\n * Anything to render inside the button — text, an icon, a whole layout.\n * A function receives `{ loading }` to show its own busy state.\n * Defaults to \"Pay with crypto\".\n */\n readonly children?: ReactNode | ((state: { readonly loading: boolean }) => ReactNode);\n };\n\n/** Every prop the checkout reads. The rest go on the `<button>` untouched. */\nconst CHECKOUT_PROPS: ReadonlySet<string> = new Set([\n \"checkout\",\n \"publishableKey\",\n \"baseUrl\",\n \"amount\",\n \"currency\",\n \"name\",\n \"quantity\",\n \"merchantReference\",\n \"successUrl\",\n \"metadata\",\n \"onRedirect\",\n \"onError\",\n \"mode\",\n \"onSuccess\",\n \"onFailed\",\n \"onClose\",\n \"children\",\n]);\n\nfunction buttonAttributes(props: PayButtonProps): ButtonAttributes {\n return Object.fromEntries(\n Object.entries(props).filter(([key]) => !CHECKOUT_PROPS.has(key)),\n ) as ButtonAttributes;\n}\n\n/**\n * A button that sends the buyer to Mayarin's hosted checkout.\n *\n * Unstyled on purpose: pass `className` or `style` and it looks like the rest\n * of your page. It never submits a surrounding form.\n */\nexport function PayButton(props: PayButtonProps) {\n const { redirect, loading } = useMayarinCheckout(props);\n const { children = \"Pay with crypto\" } = props;\n const attributes = buttonAttributes(props);\n\n return (\n <button\n {...attributes}\n type=\"button\"\n disabled={attributes.disabled === true || loading}\n aria-busy={loading}\n onClick={() => void redirect()}\n >\n {typeof children === \"function\" ? children({ loading }) : children}\n </button>\n );\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mayarin/react",
3
- "version": "0.1.0-next.1",
3
+ "version": "0.1.0-next.2",
4
4
  "description": "React pay button and checkout hook for Mayarin.",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://docs.mayarin.xyz",
@@ -24,7 +24,7 @@
24
24
  "dist"
25
25
  ],
26
26
  "dependencies": {
27
- "@mayarin/sdk": "^0.1.0-next.1"
27
+ "@mayarin/sdk": "^0.1.0-next.2"
28
28
  },
29
29
  "peerDependencies": {
30
30
  "react": ">=18"