@maytes/checkout-button 1.1.0 → 1.2.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.
Files changed (55) hide show
  1. package/CHANGELOG.md +79 -0
  2. package/README.md +97 -53
  3. package/dist/checkout-button.c640180e.js +156 -0
  4. package/dist/checkout-button.c640180e.js.map +1 -0
  5. package/dist/checkout-button.cjs +280 -145
  6. package/dist/checkout-button.cjs.map +1 -1
  7. package/dist/checkout-button.d.cts +40 -4
  8. package/dist/checkout-button.d.ts +40 -4
  9. package/dist/{checkout-button.f96c49ee.mjs → checkout-button.d6e444ee.mjs} +280 -145
  10. package/dist/checkout-button.d6e444ee.mjs.map +1 -0
  11. package/dist/{checkout-button.178d8394.cjs → checkout-button.d7bc9aea.cjs} +280 -145
  12. package/dist/checkout-button.d7bc9aea.cjs.map +1 -0
  13. package/dist/checkout-button.element.6c530c24.js +155 -0
  14. package/dist/checkout-button.element.6c530c24.js.map +1 -0
  15. package/dist/checkout-button.element.js +155 -0
  16. package/dist/checkout-button.element.js.map +1 -0
  17. package/dist/checkout-button.js +19 -14
  18. package/dist/checkout-button.js.map +1 -1
  19. package/dist/checkout-button.mjs +280 -145
  20. package/dist/checkout-button.mjs.map +1 -1
  21. package/dist/element.cjs +201 -0
  22. package/dist/element.cjs.map +1 -0
  23. package/dist/element.d.cts +44 -0
  24. package/dist/element.d.ts +44 -0
  25. package/dist/element.mjs +180 -0
  26. package/dist/element.mjs.map +1 -0
  27. package/dist/integrity.json +37 -26
  28. package/dist/react.cjs +139 -0
  29. package/dist/react.cjs.map +1 -0
  30. package/dist/react.d.cts +30 -0
  31. package/dist/react.d.ts +30 -0
  32. package/dist/react.mjs +117 -0
  33. package/dist/react.mjs.map +1 -0
  34. package/docs/guides/angular.md +56 -0
  35. package/docs/guides/api.md +108 -0
  36. package/docs/guides/events.md +48 -0
  37. package/docs/guides/launch-behaviour.md +41 -0
  38. package/docs/guides/nextjs.md +49 -0
  39. package/docs/guides/own-button.md +41 -0
  40. package/docs/guides/quickstart-html.md +31 -0
  41. package/docs/guides/quickstart-npm.md +34 -0
  42. package/docs/guides/react.md +88 -0
  43. package/docs/guides/security-csp.md +32 -0
  44. package/docs/guides/server.md +76 -0
  45. package/docs/guides/solid.md +44 -0
  46. package/docs/guides/stripe-payment-element.md +14 -0
  47. package/docs/guides/svelte.md +46 -0
  48. package/docs/guides/troubleshooting.md +17 -0
  49. package/docs/guides/vue-nuxt.md +64 -0
  50. package/docs/guides/web-component.md +109 -0
  51. package/package.json +40 -3
  52. package/dist/checkout-button.178d8394.cjs.map +0 -1
  53. package/dist/checkout-button.811263f9.js +0 -151
  54. package/dist/checkout-button.811263f9.js.map +0 -1
  55. package/dist/checkout-button.f96c49ee.mjs.map +0 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,83 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#28](https://github.com/kttipay/maytes-checkout-button/pull/28) [`a5c64cd`](https://github.com/kttipay/maytes-checkout-button/commit/a5c64cd13d095296c4c49eb3d9478f437802f9e3) Thanks [@kos-maytes](https://github.com/kos-maytes)! - `renderButton()` accepts `radius` and `height` so the Split with Maytes button can match an express-checkout row (for example 6px corners at 48px next to Apple Pay and Google Pay); a button with a `height` keeps its label on one line. Every `maytes:checkout-*` event now carries `detail.instanceId` and `detail.source` (`'button'` or `'api'`), and each instance exposes `maytes.instanceId`, so pages with several instances can tell their events apart. Existing event fields and the default button are unchanged: without `radius`/`height` the button renders exactly as in 1.1. Events that had no `detail` (`opened`, `closed`) now carry `{ instanceId, source }`. TypeScript code that hand-builds a `MaytesSDK` object needs the new `instanceId` member.
8
+
9
+ - [#30](https://github.com/kttipay/maytes-checkout-button/pull/30) [`ac332b7`](https://github.com/kttipay/maytes-checkout-button/commit/ac332b7bf5d361166484e1b657a1a303e22192ef) Thanks [@kos-maytes](https://github.com/kos-maytes)! - Add `<maytes-checkout-button>`, a standard custom element for any front-end stack (plain HTML, Vue/Nuxt, Angular, Svelte/SvelteKit, Solid, React 19). Import `@maytes/checkout-button/element` or load `https://js.maytes.co/v1/checkout-button.element.js`; set `environment` and the other options as attributes and `createCheckout` as a property, and listen for `maytes-opened`, `maytes-closed`, `maytes-redirected` and `maytes-failed` on the element. `open()` starts the checkout from your own button. Configuration mistakes arrive as `maytes-failed` with `reason: 'config'` instead of throwing. `checkout-button.js`, `.mjs` and `.cjs` are unchanged.
10
+
11
+ - [#26](https://github.com/kttipay/maytes-checkout-button/pull/26) [`92ef8c0`](https://github.com/kttipay/maytes-checkout-button/commit/92ef8c009fb74f7639be7e9cb1b2cd8d90a1c2ee) Thanks [@kos-maytes](https://github.com/kos-maytes)! - Add `openCheckout({ mode })` to start the Maytes checkout from the merchant's own button, with the same popup, loader, overlay, events and fallbacks as the rendered button. It resolves with the launch result (`popup`, `redirected`, `failed`, `closed` or `ignored`) and never rejects.
12
+
13
+ No change for existing integrations: the rendered button looks and behaves exactly as before. TypeScript code that builds its own `MaytesSDK` object (for example a test fake) needs the new `openCheckout` member.
14
+
15
+ - [#29](https://github.com/kttipay/maytes-checkout-button/pull/29) [`c62fa53`](https://github.com/kttipay/maytes-checkout-button/commit/c62fa53eec701d71a84e67240ea86a6167aac4c0) Thanks [@kos-maytes](https://github.com/kos-maytes)! - Add React bindings at `@maytes/checkout-button/react` (`MaytesProvider`, `MaytesButton`, `useMaytes`) and `maytes.onBusyChange()`. React is an optional peer dependency (18 or 19); the CDN bundle is unchanged.
16
+
17
+ - [#36](https://github.com/kttipay/maytes-checkout-button/pull/36) [`59adae5`](https://github.com/kttipay/maytes-checkout-button/commit/59adae5dfdb72eb15f6ab4ad0174b9ac693e593f) Thanks [@kos-maytes](https://github.com/kos-maytes)! - `<MaytesButton>` accepts `redirectOverlay`, like `renderButton()`: with it, a same-tab launch shows the Maytes loading overlay while the checkout is created. Off by default.
18
+
19
+ - [#32](https://github.com/kttipay/maytes-checkout-button/pull/32) [`7634c62`](https://github.com/kttipay/maytes-checkout-button/commit/7634c62e007b56db63502322cd77fa553f7a14b5) Thanks [@kos-maytes](https://github.com/kos-maytes)! - Add an opt-in `redirectOverlay` option to `renderButton()` and `openCheckout()`: when the checkout opens in the same tab (redirect mode, phones, blocked popups), the Maytes loading overlay shows while the checkout is created and clears itself after a back-button return. Off by default, so existing integrations are unchanged.
20
+
21
+ ### Patch Changes
22
+
23
+ - [#21](https://github.com/kttipay/maytes-checkout-button/pull/21) [`e87a44b`](https://github.com/kttipay/maytes-checkout-button/commit/e87a44b9649686048fa1e622831d601b8eeed71c) Thanks [@kos-maytes](https://github.com/kos-maytes)! - Fix the checkout button's busy-state spinner not reappearing after a customer's first checkout attempt on a given rendered button. The button now tracks its current icon (logo or spinner) by direct reference instead of re-querying the DOM by class name, so the spinner swap works correctly on every checkout attempt, not just the first.
24
+
25
+ <!-- @hash-sri-start -->
26
+
27
+ **SRI hashes** (use these in `<script integrity="..." crossorigin="anonymous">`):
28
+
29
+ ```
30
+ checkout-button.js sha384-gIQUOEPPO0H+Stb2Ffa40dFxBxGnB40p1+AHttEYor5oZiVKoM+aKagIL3GuNkG1
31
+ checkout-button.mjs sha384-sKFZzp1oSiijC45OFR04FOjFQfgvxawtUp+u/qXvRu5s5ZF8iMSTHaQo5g+VLJ79
32
+ checkout-button.cjs sha384-fkm1nIhsGYEjDw3uc4cjdxui4K8H4HuPnIQ379GP1Y7vtwFC/Dw3M85KopqvjRl1
33
+ checkout-button.element.js sha384-Y3aDrAucY25r7DzSkYJpsjoazd67oBy4WSxbTXBgpaWaT6TOuEOW3PidCw0PeVb9
34
+ ```
35
+
36
+ **SemVer CDN links** (readable production pins; use with the SRI hashes above):
37
+
38
+ - [checkout-button.js](https://js.maytes.co/v1.2.0/checkout-button.js)
39
+ - [checkout-button.mjs](https://js.maytes.co/v1.2.0/checkout-button.mjs)
40
+ - [checkout-button.cjs](https://js.maytes.co/v1.2.0/checkout-button.cjs)
41
+ - [checkout-button.element.js](https://js.maytes.co/v1.2.0/checkout-button.element.js)
42
+
43
+ **Hashed CDN links** (byte-level production pins; use with the SRI hashes above):
44
+
45
+ - [checkout-button.c640180e.js](https://js.maytes.co/checkout-button.c640180e.js)
46
+ - [checkout-button.d6e444ee.mjs](https://js.maytes.co/checkout-button.d6e444ee.mjs)
47
+ - [checkout-button.d7bc9aea.cjs](https://js.maytes.co/checkout-button.d7bc9aea.cjs)
48
+ - [checkout-button.element.6c530c24.js](https://js.maytes.co/checkout-button.element.6c530c24.js)
49
+
50
+ <!-- @hash-sri-end -->
51
+ ## 1.1.1
52
+
53
+ ### Patch Changes
54
+
55
+ - [#16](https://github.com/kttipay/maytes-checkout-button/pull/16) [`e0bd228`](https://github.com/kttipay/maytes-checkout-button/commit/e0bd228f454979c6dd41587cb0e82e9fe735a888) Thanks [@kttipaydiogocavaiar](https://github.com/kttipaydiogocavaiar)! - The popup loading overlay no longer outlives the frame that opened it. When the button runs inside a same-origin iframe, the "Completing checkout with Maytes…" dialog is mounted in the top-level document so it covers the whole merchant page, but only the frame's SDK instance could remove it. The hosted checkout's back, cancel and return exits navigate `window.opener`, which is that frame, so the frame's document was replaced, its popup poll died, and the merchant page stayed covered by a modal dialog with the button unreachable. The overlay now also listens for the frame's `pagehide` and removes itself from the top-level document when the frame navigates away.
56
+
57
+ <!-- @hash-sri-start -->
58
+
59
+ **SRI hashes** (use these in `<script integrity="..." crossorigin="anonymous">`):
60
+
61
+ ```
62
+ checkout-button.js sha384-dfHU/wrTbetBPk5TdX9haImZAq87uFVrlMKMSept0sgtIbTqdYWQi2ml8BF6Gq8N
63
+ checkout-button.mjs sha384-JKjUqX0jqi7UoqqNYGlD97mTCeR0ZshbiLtXhfPQl2w5mQtfmBx+X7W95SjjmkkY
64
+ checkout-button.cjs sha384-oSekYX0Re0jdMsBU6uzxUedTM+Cnl8uF5y0wmZmjUHngtzr8Sdze1MrOSYkGXKJ3
65
+ ```
66
+
67
+ **SemVer CDN links** (readable production pins; use with the SRI hashes above):
68
+
69
+ - [checkout-button.js](https://js.maytes.co/v1.1.1/checkout-button.js)
70
+ - [checkout-button.mjs](https://js.maytes.co/v1.1.1/checkout-button.mjs)
71
+ - [checkout-button.cjs](https://js.maytes.co/v1.1.1/checkout-button.cjs)
72
+
73
+ **Hashed CDN links** (byte-level production pins; use with the SRI hashes above):
74
+
75
+ - [checkout-button.4b780d52.js](https://js.maytes.co/checkout-button.4b780d52.js)
76
+ - [checkout-button.aed57a66.mjs](https://js.maytes.co/checkout-button.aed57a66.mjs)
77
+ - [checkout-button.2aad12c3.cjs](https://js.maytes.co/checkout-button.2aad12c3.cjs)
78
+
79
+ <!-- @hash-sri-end -->
80
+
3
81
  ## 1.1.0
4
82
 
5
83
  ### Minor Changes
@@ -31,6 +109,7 @@ checkout-button.cjs sha384-4c1JuweT47cnpvGlQfv+qQw/3CS+7o2/Dg4qQnLiWM1GDChhHQo0
31
109
  - [checkout-button.178d8394.cjs](https://js.maytes.co/checkout-button.178d8394.cjs)
32
110
 
33
111
  <!-- @hash-sri-end -->
112
+
34
113
  ## 1.0.1
35
114
 
36
115
  ### Patch Changes
package/README.md CHANGED
@@ -5,54 +5,80 @@
5
5
  <h1 align="center">@maytes/checkout-button</h1>
6
6
 
7
7
  <p align="center">
8
- Drop-in <strong>Split with Maytes</strong> button — one script or import, and shoppers can split any checkout with friends.
8
+ The <strong>Split with Maytes</strong> button for your checkout page.<br>
9
+ Shoppers pay their share of the order and invite friends to pay theirs.
9
10
  </p>
10
11
 
11
12
  <p align="center">
12
13
  <a href="https://www.npmjs.com/package/@maytes/checkout-button"><img alt="npm version" src="https://img.shields.io/npm/v/@maytes/checkout-button?color=4A0324&label=npm"></a>
13
- <a href="https://bundlephobia.com/package/@maytes/checkout-button"><img alt="bundle size" src="https://img.shields.io/bundlephobia/minzip/@maytes/checkout-button?color=FE572A&label=gzip"></a>
14
14
  <a href="./CHANGELOG.md"><img alt="provenance" src="https://img.shields.io/badge/npm-provenance-4A0324"></a>
15
15
  <a href="./LICENSE"><img alt="license" src="https://img.shields.io/badge/license-MIT-4A0324"></a>
16
16
  <a href="https://developers.maytes.co/checkout-button"><img alt="docs" src="https://img.shields.io/badge/docs-developers.maytes.co-FE572A"></a>
17
17
  </p>
18
18
 
19
- ---
19
+ <p align="center">
20
+ <a href="#quickstart">Quickstart</a> ·
21
+ <a href="#choose-your-setup">Guides</a> ·
22
+ <a href="#install">Install</a> ·
23
+ <a href="#api">API</a> ·
24
+ <a href="https://developers.maytes.co/checkout-button">Integration guide</a>
25
+ </p>
20
26
 
21
- Renders a button and launches the Maytes-hosted checkout in the top-level window. Default mode opens a centered popup on wide viewports, with a branded loader and automatic redirect fallback on phones or when the popup is blocked; `mode: 'redirect'` opts into a redirect always. Inside an iframe every redirect targets the top-level window.
27
+ When a shopper clicks the button, it asks your server to create a Maytes checkout and opens it in a popup, or in the same tab on phones. Your server captures the payment when Maytes sends the `checkout.authorized` webhook.
22
28
 
23
- **📖 Full integration guide:** [developers.maytes.co/checkout-button](https://developers.maytes.co/checkout-button)
29
+ ## Features
24
30
 
25
- - **Framework-agnostic** — same factory whether it's a `<script>` tag or `import { Maytes }`; no React/Vue/Angular binding to keep in sync.
26
- - **Zero runtime dependencies** — a small, dependency-free bundle; ESM/CJS ship unminified so your bundler can tree-shake it.
27
- - **CSP-safe by construction** — no `eval`, `Function`, or string-form timers; every release is scanned for it before shipping.
28
- - **Evergreen CDN by default** — the recommended `<script>` tag always serves the newest release; pin a SemVer or content-hash URL instead if you'd rather freeze on a tested build.
29
- - **Signed provenance** — every npm release carries a [SLSA](https://slsa.dev) provenance attestation back to this repo's build.
31
+ - **Any stack.** A `<script>` tag or `import { Maytes }`, with [a guide per stack](#choose-your-setup), React components and a web component.
32
+ - **No dependencies.** ESM and CommonJS builds ship unminified, so your bundler tree-shakes them.
33
+ - **CSP-safe.** No `eval`, `Function` or string timers; every release is scanned.
34
+ - **Evergreen CDN.** `/v1/` always serves the newest `1.x`; [pin a version](#install) with SRI to freeze it.
35
+ - **Signed releases.** Every npm release carries [SLSA](https://slsa.dev) provenance.
30
36
 
31
- ## Contents
37
+ ## Quickstart
32
38
 
33
- - [Install](#install)
34
- - [Usage](#usage)
35
- - [API](#api)
36
- - [Mobile app](#mobile-app)
37
- - [Development](#development)
39
+ ```html
40
+ <div id="maytes-button"></div>
41
+
42
+ <script src="https://js.maytes.co/v1/checkout-button.js" crossorigin="anonymous"></script>
43
+ <script>
44
+ const maytes = window.Maytes({
45
+ environment: 'sandbox',
46
+ createCheckout: async () => {
47
+ const res = await fetch('/api/maytes/checkout', { method: 'POST' });
48
+ if (!res.ok) throw new Error('Could not create the Maytes checkout');
49
+ return res.json(); // { checkoutId, checkoutUrl }
50
+ },
51
+ });
52
+
53
+ maytes.renderButton(document.getElementById('maytes-button'), { block: true });
54
+ </script>
55
+ ```
38
56
 
39
- ## Install
57
+ Your server creates the checkout behind `POST /api/maytes/checkout`; [Your server](docs/guides/server.md) shows how. Using a framework? Pick it below.
58
+
59
+ <a id="usage"></a>
60
+
61
+ ## Choose your setup
40
62
 
41
- ### Script tag (CDN)
63
+ - **Frontend:** [HTML](docs/guides/quickstart-html.md) · [npm](docs/guides/quickstart-npm.md) · [React](docs/guides/react.md) · [Next.js](docs/guides/nextjs.md) · [Vue and Nuxt](docs/guides/vue-nuxt.md) · [Angular](docs/guides/angular.md) · [Svelte](docs/guides/svelte.md) · [Solid](docs/guides/solid.md) · [Web component](docs/guides/web-component.md)
64
+ - **Integration:** [Your server](docs/guides/server.md) · [Stripe Payment Element](docs/guides/stripe-payment-element.md) · [Your own button](docs/guides/own-button.md)
42
65
 
43
- | | Evergreen (recommended) | Pinned |
66
+ ## Install
67
+
68
+ | | Evergreen (recommended) | Pinned |
44
69
  |---|---|---|
45
70
  | **Use when** | Default — always get the newest `1.x` release | You'd rather freeze on a tested build |
46
71
  | **Guarantee** | Always current; a bad release reaches you immediately | The bytes you tested are the bytes shipped, forever |
47
72
  | **SRI** | Not possible | Yes |
48
73
 
49
- ### Evergreen (recommended)
50
-
51
74
  ```html
52
75
  <script src="https://js.maytes.co/v1/checkout-button.js"
53
76
  crossorigin="anonymous"></script>
54
77
  ```
55
78
 
79
+ <details>
80
+ <summary><b>Evergreen: CSP and versions</b></summary>
81
+
56
82
  No `integrity` attribute is possible here — the bytes change without notice as new `1.x` releases ship. Trust the origin via CSP instead:
57
83
 
58
84
  ```
@@ -61,19 +87,22 @@ Content-Security-Policy: script-src 'self' https://js.maytes.co;
61
87
 
62
88
  `/v1/` tracks the newest `1.x` release only; it won't jump to a future `2.x` (move to `/v2/checkout-button.js` explicitly once that ships). Poll `https://js.maytes.co/integrity.json` if you want to detect changes yourself.
63
89
 
64
- ### Pinned (SemVer or content hash)
90
+ </details>
91
+
92
+ <details>
93
+ <summary><b>Pinned: SemVer or content hash, with SRI</b></summary>
65
94
 
66
95
  For a guarantee instead of convenience — freeze on a tested build, update on your own schedule:
67
96
 
68
97
  <!-- @cdn-example-start -->
69
98
  ```html
70
- <script src="https://js.maytes.co/v1.1.0/checkout-button.js"
99
+ <script src="https://js.maytes.co/v1.2.0/checkout-button.js"
71
100
  integrity="sha384-…"
72
101
  crossorigin="anonymous"></script>
73
102
  ```
74
103
 
75
104
  ```html
76
- <script src="https://js.maytes.co/checkout-button.811263f9.js"
105
+ <script src="https://js.maytes.co/checkout-button.c640180e.js"
77
106
  integrity="sha384-…"
78
107
  crossorigin="anonymous"></script>
79
108
  ```
@@ -81,60 +110,75 @@ For a guarantee instead of convenience — freeze on a tested build, update on y
81
110
 
82
111
  Both URLs are the same release bytes with the same SRI value — pick either. Full version/hash/SRI history: [`CHANGELOG.md`](./CHANGELOG.md), [releases](https://github.com/kttipay/maytes-checkout-button/releases), `https://js.maytes.co/integrity.json`. Pinned URLs never change once published.
83
112
 
84
- ### npm
113
+ </details>
114
+
115
+ **npm**
85
116
 
86
117
  ```bash
87
118
  npm install @maytes/checkout-button
88
119
  ```
89
120
 
90
- ## Usage
121
+ ESM (`import`) and CommonJS (`require`) builds ship unminified with type declarations; your bundler minifies them. The reasoning behind the CDN channels is in [`docs/cdn-versioning.md`](./docs/cdn-versioning.md).
91
122
 
92
- `Maytes` is a callable factory (Stripe-style). Each call returns an isolated SDK instance.
123
+ ## API
124
+
125
+ `Maytes` is a callable factory, like `Stripe()`. Each call returns an isolated instance.
93
126
 
94
127
  ```ts
95
128
  import { Maytes } from '@maytes/checkout-button';
96
129
 
97
130
  const maytes = Maytes({
131
+ environment: 'sandbox',
98
132
  createCheckout: async () => {
99
- const res = await fetch('/api/maytes/create-checkout', { method: 'POST' });
100
- return res.json(); // { checkoutId: string, checkoutUrl?: string }
133
+ const res = await fetch('/api/maytes/checkout', { method: 'POST' });
134
+ if (!res.ok) throw new Error('Could not create the Maytes checkout');
135
+ return res.json(); // { checkoutId, checkoutUrl }
101
136
  },
102
- environment: 'sandbox',
103
137
  });
104
138
 
105
- const cleanup = maytes.renderButton(document.getElementById('slot'), { block: true });
139
+ maytes.renderButton(document.getElementById('maytes-button')!, { block: true });
140
+ ```
106
141
 
107
- // Opt into same-window redirect instead of the default popup:
108
- maytes.renderButton(document.getElementById('redirect-slot'), { block: true, mode: 'redirect' });
142
+ | Member | Purpose |
143
+ |---|---|
144
+ | `Maytes({ createCheckout, environment })` | Creates an instance. `createCheckout` asks your server for `{ checkoutId, checkoutUrl? }`; `environment` is `'sandbox'` or `'production'`. |
145
+ | `renderButton(container, { label, block, mode, radius, height })` | Renders the button and returns a function that removes it. `mode` is `'popup'` (default) or `'redirect'`; `radius` and `height` fit it next to wallet buttons. |
146
+ | `openCheckout({ mode })` | Starts the checkout from your own button, as a click on the rendered button does. See [Using your own button](docs/guides/own-button.md). |
147
+ | `instanceId` | Read-only id of the instance; every `maytes:checkout-*` event carries it in `event.detail`. |
148
+ | `onBusyChange(listener)` | Calls `listener(true)` when a launch starts and `listener(false)` when it ends; returns a function that stops listening. |
149
+ | `redirectToCheckout({ checkoutId, replace })` | Sends the current tab to a checkout you already created. |
150
+ | `checkoutUrl({ checkoutId })` | Builds the hosted checkout URL. |
151
+ | `destroy()` | Tears down the instance, its buttons, listeners and overlay. Call it on unmount. |
152
+ | `maytes:checkout-opened` / `-closed` / `-redirected` / `-failed` | `CustomEvent`s on `document` describing what the button did. |
109
153
 
110
- // Imperative alternatives:
111
- maytes.redirectToCheckout({ checkoutId });
112
- const url = maytes.checkoutUrl({ checkoutId });
154
+ Style the button from CSS with `--maytes-button-height`, `--maytes-button-radius` and `--maytes-button-font-size`; see the [API reference](docs/guides/api.md#styling-with-css-custom-properties).
113
155
 
114
- // Teardown:
115
- cleanup();
116
- maytes.destroy();
117
- ```
156
+ Full reference: [API](docs/guides/api.md) · [Events](docs/guides/events.md).
118
157
 
119
- Via the script tag, the same factory is available as the global `window.Maytes(...)`.
158
+ ## More guides
120
159
 
121
- ## API
160
+ - **Reference:** [API](docs/guides/api.md) · [Events](docs/guides/events.md) · [Popup, redirect and iframes](docs/guides/launch-behaviour.md) · [Security and CSP](docs/guides/security-csp.md)
161
+ - **Help:** [Troubleshooting](docs/guides/troubleshooting.md)
162
+ - **Internals:** [Technical overview](docs/overview.md) · [CDN versioning decision record](docs/cdn-versioning.md)
122
163
 
123
- | Method | Purpose |
124
- |---|---|
125
- | `Maytes(options)` | Create an SDK instance. `options.createCheckout` mints a checkout server-side; `options.environment` selects the Maytes environment. |
126
- | `renderButton(container, options?)` | Render the button into `container`; returns a cleanup function. Options include `label`, `block`, and `mode: 'redirect' \| 'popup'` (default `'popup'`). |
127
- | `redirectToCheckout(options)` | Launch checkout directly (no button). |
128
- | `checkoutUrl(options)` | Build the hosted checkout URL. |
129
- | `destroy()` | Tear down the instance and its listeners — call this on unmount or when the instance's config/environment changes, not as a reaction to detecting payment success from your own polling (that can tear down a checkout that's still in progress). To remove a single button, use the cleanup function returned by `renderButton()` or hide/disable the button element instead. |
164
+ The same guides ship in the npm package under `docs/guides/` and are published on [developers.maytes.co](https://developers.maytes.co/checkout-button).
165
+
166
+ ## Using an AI coding assistant
167
+
168
+ This repository ships an [Agent Skill](https://github.com/kttipay/maytes-checkout-button/tree/main/skills/maytes-checkout-button) that teaches coding assistants to add Split with Maytes correctly on any stack: credentials stay on your server, `createCheckout` returns `{ checkoutId, checkoutUrl }`, the button is created in browser-only code and destroyed on unmount, and the webhook captures the payment.
130
169
 
131
- ### Inside an iframe?
170
+ In Claude Code:
171
+
172
+ ```
173
+ /plugin marketplace add kttipay/maytes-checkout-button
174
+ /plugin install maytes-checkout-button@maytes
175
+ ```
132
176
 
133
- The hosted checkout must run in the top-level window (its session cookie is refused inside a cross-site frame). If you render the button inside an iframe, the SDK navigates the top-level window; if the browser refuses, it opens a new tab; if both are refused it dispatches `maytes:checkout-failed` with `reason: 'navigation-blocked'`. When the checkout opens in a new tab, no popup poll starts and no `maytes:checkout-opened` / `maytes:checkout-closed` pair fires — you'll only see `maytes:checkout-redirected` with `target: 'tab'`. A sandboxed iframe needs `allow-scripts allow-same-origin allow-top-navigation` (plus `allow-popups allow-popups-to-escape-sandbox` for the tab fallback). Listen to `maytes:checkout-redirected` and read `event.detail.target` (`'self'`, `'top'` or `'tab'`) if your page needs to know where the checkout went. Rendering the button in the top-level page avoids all of this.
177
+ Then ask, for example, "add Split with Maytes to my checkout". Other assistants that support Agent Skills can use the [`skills/maytes-checkout-button`](https://github.com/kttipay/maytes-checkout-button/tree/main/skills/maytes-checkout-button) folder directly. The skill covers only the released API.
134
178
 
135
179
  ## Mobile app
136
180
 
137
- After checkout, shoppers split the cost with friends using Maytes payment links (`app.maytes.co/…`). On a phone with the Maytes app installed, those links open directly in the app (iOS Universal Links / Android App Links); otherwise they open in the browser. Maytes handles this end to end — merchants integrate only the button and configure nothing for the app.
181
+ After checkout, shoppers split the cost with friends using Maytes payment links (`app.maytes.co/…`). With the Maytes app installed, those links open in the app (iOS Universal Links / Android App Links); otherwise in the browser. Maytes handles this end to end — you integrate only the button and configure nothing for the app.
138
182
 
139
183
  ## Development
140
184
 
@@ -146,7 +190,7 @@ npm run test:run
146
190
  npm run build # bundles + SRI hashes + CSP scan
147
191
  ```
148
192
 
149
- Versioning and changelog are managed with [Changesets](./.changeset/README.md). Run `npm run changeset` with your PR. Brand colours are vendored, not hand-written — see [`RELEASING.md`](./RELEASING.md#brand-colours-come-from-the-design-foundation) for how that works.
193
+ Versioning and changelog are managed with [Changesets](./.changeset/README.md). Run `npm run changeset` with your PR. Brand colours are vendored, not hand-written — see [`RELEASING.md`](./RELEASING.md#brand-colours-come-from-the-design-foundation) for how that works. Internals, the end-to-end sequence and behaviour contracts are in [`docs/overview.md`](./docs/overview.md).
150
194
 
151
195
  ## License
152
196
 
@@ -0,0 +1,156 @@
1
+ "use strict";var MaytesCheckoutButton=(()=>{var R=Object.defineProperty;var ve=Object.getOwnPropertyDescriptor;var ke=Object.getOwnPropertyNames;var Ce=Object.prototype.hasOwnProperty;var Ee=(e,t)=>{for(var o in t)R(e,o,{get:t[o],enumerable:!0})},xe=(e,t,o,n)=>{if(t&&typeof t=="object"||typeof t=="function")for(let r of ke(t))!Ce.call(e,r)&&r!==o&&R(e,r,{get:()=>t[r],enumerable:!(n=ve(t,r))||n.enumerable});return e};var Me=e=>xe(R({},"__esModule",{value:!0}),e);var at={};Ee(at,{Maytes:()=>F,MaytesError:()=>c,MaytesErrorCode:()=>s,SDK_VERSION:()=>y});var s={Config:"CONFIG"},c=class extends Error{constructor(t,o){super(o),this.code=t,this.name="MaytesError"}};var G=40,z=55,Se=999;function K(e){return typeof e=="number"&&Number.isInteger(e)}function q(e){let{radius:t,height:o}=e,n={};if(o!==void 0){if(!K(o)||o<G||o>z)throw new c(s.Config,`renderButton({ height }) must be a whole number of pixels from ${G} to ${z}`);n.height=o}if(t!==void 0){let r=o===void 0?Se:Math.floor(o/2);if(!K(t)||t<0||t>r)throw new c(s.Config,`renderButton({ radius }) must be a whole number of pixels from 0 to ${r}`);n.radius=t}return n}function Y(e,t){t.radius!==void 0&&e.style.setProperty("--maytes-button-radius",`${t.radius}px`),t.height!==void 0&&(e.style.setProperty("--maytes-button-height",`${t.height}px`),e.classList.add("maytes-checkout-button--sized"))}var l={brand:{primary:"#4A0324",secondary:"#FE572A"},palette:{burgundy:{700:"#3B021D"},base:{white:"#FFFFFF"}},text:{primaryInverse:"#FFFFFF"}};var mt=l.brand.primary,Z=l.brand.secondary,A="http://www.w3.org/2000/svg",_e=["M392,6.11c-.3-1.61-1.72-2.79-3.38-2.79h-99.97c-1.66,0-3.09,1.19-3.38,2.79l-51.14,275.49c-.36,2.07,1.24,4.03,3.38,4.03h71.07c1.72,0,3.15-1.24,3.38-2.97l5.57-42.24c.23-1.72,1.66-2.97,3.38-2.97h32.45c1.72,0,3.15,1.24,3.38,2.97l5.57,42.24c.23,1.72,1.66,2.97,3.38,2.97h74.46c2.14,0,3.8-1.96,3.38-4.03L392,6.11ZM344.42,174.89h-14.59c-2.07,0-3.62-1.84-3.38-3.86l9.55-74.33c.06-.48.48-.83.95-.83s.89.35.95.83l9.91,74.33c.3,2.02-1.31,3.86-3.38,3.86h0Z","M218.86,3.32h-77.72c-1.48,0-2.85,1.01-3.27,2.44l-25.63,83.65c-.12.35-.41.6-.83.6s-.71-.23-.83-.6L84.9,5.76c-.41-1.42-1.78-2.44-3.27-2.44H3.44c-1.9,0-3.44,1.54-3.44,3.44v275.43c0,1.9,1.54,3.45,3.44,3.45h71.26c1.9,0,3.44-1.54,3.44-3.45v-77.3l-.83-51.44c0-.53.41-.95.95-.95.41,0,.77.3.89.71l14.42,59.62c.35,1.54,1.72,2.61,3.32,2.61h28.42c1.61,0,2.97-1.06,3.32-2.61l14.47-59.79c.12-.3.35-.53.71-.53.41,0,.71.3.71.71l-.42,51.67-.06.06v77.3c0,1.9,1.54,3.44,3.44,3.44h71.37c1.9,0,3.44-1.54,3.44-3.44V6.77c0-1.9-1.54-3.44-3.44-3.44h0Z","M1179.6,147c-9.73-11.8-20.71-21.12-47.76-38.44-27.06-16.91-31.69-23.26-31.69-31.68,0-6.76,3.8-11.39,10.56-11.39,8.19,0,14.18,10.44,15.43,26.28.18,2.14,2.13,3.74,4.27,3.26l63.66-14.12c1.6-.36,2.73-1.9,2.61-3.56-3.97-49.3-35-77.36-86.44-77.36-27.47,0-50.25,8.07-67.63,24.09-17.39,16.01-25.82,36.72-25.82,62.53,0,16.91,4.22,32.09,13.12,45.62,8.9,13.53,25.75,28.3,51.56,44.38,14.77,9.32,24.09,16.49,27.88,21.12,4.21,4.63,6.35,9.31,6.35,14.36,0,6.35-4.63,10.56-11.81,10.56-10.5,0-18.15-11.87-19.28-29.66-.12-2.25-2.31-3.91-4.51-3.26l-63.6,18.68c-1.55.42-2.55,1.84-2.43,3.44,1.78,22.84,11.03,41.41,26.88,55.65,16.49,14.36,36.77,21.53,64.24,21.53s50.32-7.59,67.64-23.26c17.74-15.66,26.64-36.37,26.64-61.28,0-22.01-6.35-40.58-19.87-57.49v-.02Z","M1002.33,3.32h-146.18c-1.9,0-3.44,1.54-3.44,3.44v275.49c0,1.9,1.54,3.44,3.44,3.44h146.18c1.9,0,3.44-1.54,3.44-3.44v-62.05c0-1.9-1.54-3.44-3.44-3.44h-63.3c-1.9,0-3.45-1.54-3.45-3.45v-34.58c0-1.9,1.54-3.44,3.45-3.44v.18h53.63c1.9,0,3.45-1.54,3.45-3.44v-57.84c0-1.9-1.55-3.44-3.45-3.44h-53.63c-1.9,0-3.45-1.54-3.45-3.44v-31.61c0-1.9,1.54-3.44,3.45-3.44h63.3c1.9,0,3.44-1.54,3.44-3.44V6.77c0-1.9-1.54-3.44-3.44-3.44Z","M840.22,6.77c0-1.9-1.54-3.44-3.44-3.44h-175.31c-1.9,0-3.45,1.54-3.45,3.44v62c0,1.9,1.54,3.44,3.45,3.44h41.77c1.9,0,3.44,1.54,3.44,3.44v206.62c0,1.9,1.54,3.45,3.44,3.45h78.14c1.9,0,3.45-1.54,3.45-3.45V75.7c0-1.9,1.54-3.44,3.45-3.44h41.77c1.9,0,3.44-1.54,3.44-3.44h-.12V6.77h-.02Z","M607.71,3.38l-.06-.06c-23.23,0-32.59,48.08-40.22,80.25-1.38,5.82-4.74,18.31-7.75,27.31-4.44,13.26-10.68,30.43-22.21,31.93-18.85,2.47-7.14-39.43-7.14-39.43l21.67-92.98c.84-3.6-1.92-7.02-5.58-7.02h-85.49c-3.12,0-5.64,2.46-5.71,5.64l-1.68,75.09c-.48,21.25,5.64,42.31,18.37,59.37,12.72,17.05,38.81,28,63.12,25.9,52.48-7.08,65.46-70.49,68.4-71.1,3.06-.64,2.56,9.3.84,22.15-2.2,16.42-8.46,32.1-18.52,45.27-5.3,6.95-11.13,12.9-16.55,16.37-32.96,22.51-95.69,9.48-122.4-2.7-3.66-1.68-7.75,1.02-7.75,5.04l16.39,83.98c0,.84.18,1.68.54,2.4,9.48,17.83,53.31,19.33,74.43,17.77,32.18-2.04,100.37-29.35,115.32-122.33,12.17-103.08-1.14-162.91-38.06-162.84l.02-.02Z"];function f(e,t){let o=document.createElementNS(A,"svg");o.setAttribute("xmlns",A),o.setAttribute("viewBox","0 0 1199.47 289.04"),o.style.height=e,o.style.width="auto",o.style.color=t;for(let n of _e){let r=document.createElementNS(A,"path");r.setAttribute("fill","currentColor"),r.setAttribute("d",n),o.appendChild(r)}return o}function p(e){return typeof e=="object"&&e!==null&&e.name==="SecurityError"}function T(){return window.top!==null&&window.top!==window}function L(){if(!T())return null;let e=window.top;try{return e.document,e}catch(t){if(!p(t))throw t;return null}}function J(){if(!T())return window.innerWidth;let e=L();if(e!==null)return e.innerWidth;let t=window.screen?.width;return typeof t=="number"&&t>0?t:window.innerWidth}function X(e,t,o){o?e.location.replace(t):e.location.href=t}function Ie(e){try{e.opener=null}catch(t){if(!p(t))throw t}}function k(e,t){if(!T())return X(window,e,t),{target:"self"};let o;try{return X(window.top,e,t),{target:"top"}}catch(n){if(!p(n))throw n;o=n}try{let n=window.open(e,"_blank");return n!==null?(Ie(n),{target:"tab"}):{target:null,cause:o}}catch(n){return{target:null,cause:n}}}var Q="maytes-overlay-title",D="data-maytes-checkout-button-overlay-styles",Oe="rgba(0, 0, 0, 0.6)";function Re(){let e=L();if(e===null)return null;let t=e.document;return t.body!==null?t:null}function Ae(){return Re()??document}function Te(e){if(e===document||e.querySelector(`style[${D}]`)!==null)return;let t=document.head.querySelector("style[data-maytes-checkout-button]");if(t===null)return;let o=e.createElement("style");o.setAttribute(D,"");let n=t.getAttribute("nonce");n!==null&&o.setAttribute("nonce",n),o.textContent=t.textContent??"",e.head.appendChild(o)}function Le(e,t,o){let n=e.createElement("dialog");n.className="maytes-checkout-overlay",n.setAttribute("aria-labelledby",Q),n.setAttribute("data-maytes-overlay","");let r=e.createElement("div");r.className="maytes-checkout-overlay__content";let d=e.createElement("div");d.setAttribute("aria-hidden","true"),d.appendChild(f("2.5rem",Z)),r.appendChild(d);let i=e.createElement("div");i.className="maytes-checkout-overlay__spinner",i.setAttribute("role","status"),i.setAttribute("aria-live","polite"),i.setAttribute("aria-label","Checkout in progress"),r.appendChild(i);let u=e.createElement("p");if(u.id=Q,u.className="maytes-checkout-overlay__text",u.textContent=o==="popup"?"Completing checkout with Maytes\u2026":"Taking you to Maytes\u2026",r.appendChild(u),o==="popup"){let a=e.createElement("button");a.className="maytes-checkout-overlay__link",a.type="button",a.textContent="Return to Maytes",a.addEventListener("click",()=>{if(t.popupWindow!==null&&!t.popupWindow.closed)try{t.popupWindow.focus()}catch(m){if(!p(m))throw m}}),r.appendChild(a)}return n.appendChild(r),n.addEventListener("cancel",a=>{a.preventDefault(),t.popupWindow!==null&&!t.popupWindow.closed&&t.popupWindow.close(),h(t)}),n}function ee(e){e.style.background=Oe}function U(e,t="popup"){if(e.overlayEl!==null)return;let o=Ae();Te(o);let n=Le(o,e,t);if(e.overlayEl=n,o.body.appendChild(n),o!==document&&De(e),typeof n.showModal=="function")try{n.showModal()}catch(r){console.warn("[maytes/checkout-button] dialog.showModal failed; falling back to inline backdrop",r),n.setAttribute("open",""),ee(n)}else n.setAttribute("open",""),ee(n)}function De(e){let t=()=>h(e);window.addEventListener("pagehide",t),e.overlayDetach=()=>window.removeEventListener("pagehide",t)}function h(e){if(e.overlayEl===null)return;e.overlayDetach?.(),e.overlayDetach=null;let t=e.overlayEl,o=t.ownerDocument;typeof t.close=="function"&&t.open&&t.close(),t.remove(),o!==document&&o.head.querySelector(`style[${D}]`)?.remove(),e.overlayEl=null}var Ue={staging:"https://staging-checkout.maytes.co",sandbox:"https://sandbox-checkout.maytes.co",production:"https://checkout.maytes.co"};function te(e){return e==="sandbox"||e==="production"||e==="staging"}function oe(e,t){if(t!==void 0)return t;let o=Ue[e];if(o===void 0)throw new c(s.Config,`Unknown environment: ${String(e)}`);return o}function ne(e){let t;try{t=new URL(e)}catch{return!1}return t.protocol==="https:"||t.protocol==="http:"}function W(e){let t=e.trim().replace(/\/+$/,""),o;try{o=new URL(t)}catch{throw new c(s.Config,`baseUrl is not a valid URL: ${t}`)}if(o.protocol!=="https:"&&o.protocol!=="http:")throw new c(s.Config,`baseUrl must use http or https; got ${o.protocol}`);return t}function We(e){if(typeof e!="string"||e.trim().length===0)throw new c(s.Config,"checkoutId is required (non-empty string)");return e.trim()}function C(e,t,o){let n=We(o);return`${W(oe(e,t))}/?id=${encodeURIComponent(n)}`}function Ne(e,t){if(typeof window>"u")throw new c(s.Config,"redirectToCheckout requires a browser context (window is undefined)");if(k(e,t).target===null)throw new c(s.Config,"redirectToCheckout could not leave the embedding iframe; call it from a user gesture or from the top-level page")}function re(e,t){return{checkoutUrl(o){return C(e,t,o.checkoutId)},redirectToCheckout(o){let n=C(e,t,o.checkoutId);Ne(n,o.replace===!0)}}}var y="1.2.0";var ie="data-maytes-checkout-button",ae=`style[${ie}="${y}"]`,ce=`__maytes_checkout_button_styles_refs_${y}__`,Pe="#6b8aff",Be=`
2
+ .maytes-checkout-button {
3
+ display: inline-flex;
4
+ align-items: center;
5
+ justify-content: center;
6
+ gap: 0.57em;
7
+ min-height: var(--maytes-button-height, auto);
8
+ padding: 0.71em 1.29em;
9
+ border: none;
10
+ border-radius: var(--maytes-button-radius, 999px);
11
+ background: ${l.brand.primary};
12
+ color: ${l.text.primaryInverse};
13
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
14
+ font-size: var(--maytes-button-font-size, 14px);
15
+ font-weight: 600;
16
+ line-height: 1;
17
+ cursor: pointer;
18
+ user-select: none;
19
+ transition: background-color 120ms ease, opacity 120ms ease;
20
+ }
21
+ .maytes-checkout-button:hover { background: ${l.palette.burgundy[700]}; }
22
+ .maytes-checkout-button:focus-visible {
23
+ outline: 2px solid ${Pe};
24
+ outline-offset: 2px;
25
+ }
26
+ .maytes-checkout-button[aria-disabled='true'] {
27
+ cursor: progress;
28
+ opacity: 0.7;
29
+ }
30
+ .maytes-checkout-button--sized {
31
+ box-sizing: border-box;
32
+ white-space: nowrap;
33
+ }
34
+ .maytes-checkout-button--block {
35
+ display: flex;
36
+ width: 100%;
37
+ }
38
+ .maytes-checkout-button__logo { height: 1em; width: auto; flex: none; }
39
+ .maytes-checkout-button__spinner {
40
+ height: 1em;
41
+ width: 1em;
42
+ border-radius: 50%;
43
+ border: 0.14em solid currentColor;
44
+ border-right-color: transparent;
45
+ animation: maytes-checkout-button-spin 0.7s linear infinite;
46
+ }
47
+ @keyframes maytes-checkout-button-spin {
48
+ to { transform: rotate(360deg); }
49
+ }
50
+ .maytes-checkout-overlay {
51
+ position: fixed;
52
+ inset: 0;
53
+ width: 100vw;
54
+ height: 100vh;
55
+ max-width: 100vw;
56
+ max-height: 100vh;
57
+ margin: 0;
58
+ padding: 0;
59
+ border: none;
60
+ background: transparent;
61
+ z-index: 2147483646;
62
+ font: 500 15px/1.4 -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
63
+ }
64
+ .maytes-checkout-overlay[open] {
65
+ display: flex;
66
+ align-items: center;
67
+ justify-content: center;
68
+ }
69
+ .maytes-checkout-overlay::backdrop {
70
+ background: rgba(0, 0, 0, 0.6);
71
+ backdrop-filter: blur(4px);
72
+ -webkit-backdrop-filter: blur(4px);
73
+ }
74
+ .maytes-checkout-overlay__content {
75
+ display: flex;
76
+ flex-direction: column;
77
+ align-items: center;
78
+ gap: 1.5rem;
79
+ }
80
+ .maytes-checkout-overlay__spinner {
81
+ width: 2.5rem;
82
+ height: 2.5rem;
83
+ border: 4px solid rgba(255, 255, 255, 0.3);
84
+ border-top-color: ${l.brand.secondary};
85
+ border-radius: 50%;
86
+ animation: maytes-checkout-button-spin 0.8s linear infinite;
87
+ }
88
+ .maytes-checkout-overlay__text {
89
+ color: ${l.text.primaryInverse};
90
+ font-size: 1.125rem;
91
+ font-weight: 500;
92
+ margin: 0;
93
+ }
94
+ .maytes-checkout-overlay__link {
95
+ color: rgba(255, 255, 255, 0.7);
96
+ font-size: 0.875rem;
97
+ text-decoration: underline;
98
+ text-underline-offset: 4px;
99
+ background: none;
100
+ border: none;
101
+ cursor: pointer;
102
+ font-family: inherit;
103
+ transition: color 0.15s;
104
+ }
105
+ .maytes-checkout-overlay__link:hover { color: ${l.text.primaryInverse}; }
106
+ @media (prefers-reduced-motion: reduce) {
107
+ .maytes-checkout-overlay__spinner,
108
+ .maytes-checkout-button__spinner { animation: none; }
109
+ }
110
+ `,se=`
111
+ :root { color-scheme: light; }
112
+ html, body { margin: 0; height: 100%; }
113
+ body.maytes-popup-loading {
114
+ display: flex;
115
+ align-items: center;
116
+ justify-content: center;
117
+ min-height: 100vh;
118
+ background: ${l.brand.primary};
119
+ color: ${l.text.primaryInverse};
120
+ font: 500 16px/1.45 -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
121
+ }
122
+ .maytes-popup-loading__content {
123
+ display: flex;
124
+ flex-direction: column;
125
+ align-items: center;
126
+ gap: 1.5rem;
127
+ padding: 2rem;
128
+ text-align: center;
129
+ }
130
+ .maytes-popup-loading__logo { height: 2.75rem; width: auto; }
131
+ .maytes-popup-loading__spinner {
132
+ width: 2.5rem;
133
+ height: 2.5rem;
134
+ border: 4px solid rgba(255, 255, 255, 0.25);
135
+ border-top-color: ${l.brand.secondary};
136
+ border-radius: 50%;
137
+ animation: maytes-checkout-button-spin 0.8s linear infinite;
138
+ }
139
+ @keyframes maytes-checkout-button-spin {
140
+ to { transform: rotate(360deg); }
141
+ }
142
+ .maytes-popup-loading__text { margin: 0; font-size: 1.05rem; font-weight: 500; }
143
+ .maytes-popup-loading__slow {
144
+ margin: 0;
145
+ font-size: 0.9rem;
146
+ font-weight: 400;
147
+ color: rgba(255, 255, 255, 0.75);
148
+ max-width: 22rem;
149
+ }
150
+ .maytes-popup-loading__slow[hidden] { display: none; }
151
+ @media (prefers-reduced-motion: reduce) {
152
+ .maytes-popup-loading__spinner { animation: none; }
153
+ }
154
+ `;function ue(){return window[ce]??0}function le(e){let t=window;t[ce]=e}function de(e){let t=ue()+1;if(le(t),document.head.querySelector(ae)!==null)return;let o=document.createElement("style");o.setAttribute(ie,y),e!==void 0&&e!==""&&o.setAttribute("nonce",e),o.textContent=Be,document.head.appendChild(o)}function pe(){let e=Math.max(0,ue()-1);if(le(e),e>0)return;let t=document.head.querySelector(ae);t!==null&&t.remove()}var N=500,P=800,$e=500,Fe=600,He=8e3;function Ve(e){if(typeof e!="object"||e===null)return!1;let t=e;return!(typeof t.checkoutId!="string"||t.checkoutId.trim().length===0||t.checkoutUrl!==void 0&&(typeof t.checkoutUrl!="string"||t.checkoutUrl.trim().length===0||!ne(t.checkoutUrl)))}function je(e){try{e.focus()}catch(t){if(!p(t))throw t}}function E(e){if(e!==null)try{e.closed||e.close()}catch(t){if(!p(t))throw t}}function Ge(){let e=window.screen?.width??N,t=window.screen?.height??P,o=Math.max(0,Math.round((e-N)/2)),n=Math.max(0,Math.round((t-P)/2));return`popup=yes,width=${N},height=${P},left=${o},top=${n},resizable=yes,scrollbars=yes`}function w(e){e.popupPollHandle!==null&&(clearInterval(e.popupPollHandle),e.popupPollHandle=null)}function ze(e,t){w(e),e.popupPollHandle=setInterval(()=>{let o=e.popupWindow;(o===null||o.closed)&&(w(e),e.popupWindow=null,t())},$e)}function Ke(){return J()<=Fe}function qe(e){return window.open("about:blank",e.popupName,Ge())}function Ye(e,t){e.location.replace(t)}function Ze(e){for(;e.body.firstChild!==null;)e.body.removeChild(e.body.firstChild)}function Xe(e,t){let o=e.document;if(o===null||o.head===null||o.body===null)return;o.title="Maytes checkout",o.documentElement.lang="en";let n=o.createElement("style");t!==void 0&&t!==""&&n.setAttribute("nonce",t),n.textContent=se,o.head.appendChild(n),o.body.classList.add("maytes-popup-loading"),Ze(o);let r=o.createElement("main");r.className="maytes-popup-loading__content",r.setAttribute("role","status"),r.setAttribute("aria-live","polite");let d=f("2.75rem",l.text.primaryInverse);d.setAttribute("class","maytes-popup-loading__logo"),d.setAttribute("aria-hidden","true"),r.appendChild(d);let i=o.createElement("div");i.className="maytes-popup-loading__spinner",i.setAttribute("aria-hidden","true"),r.appendChild(i);let u=o.createElement("p");u.className="maytes-popup-loading__text",u.textContent="Completing checkout with Maytes\u2026",r.appendChild(u);let a=o.createElement("p");a.className="maytes-popup-loading__slow",a.hidden=!0,a.textContent="Still connecting. This can take a moment on slow networks.",r.appendChild(a),o.body.appendChild(r),setTimeout(()=>{a.hidden=!1},He)}function Je(e,t){return{instanceId:e.instanceId,source:t}}function B(e,t,o){let n=o===void 0?{...e,reason:t}:{...e,reason:t,cause:o};document.dispatchEvent(new CustomEvent("maytes:checkout-failed",{detail:n}))}function Qe(e,t,o){let n={...e,url:t,target:o};document.dispatchEvent(new CustomEvent("maytes:checkout-redirected",{detail:n}))}function x(e,t="renderButton"){if(e!=="redirect"&&e!=="popup")throw new c(s.Config,`${t}({ mode }) must be 'redirect' or 'popup'`);return e}function M(e,t="renderButton"){if(e===void 0)return!1;if(typeof e!="boolean")throw new c(s.Config,`${t}({ redirectOverlay }) must be true or false`);return e}function $(e,t){e.busy=t;for(let o of[...e.busyViews])o(t)}function S(e){h(e),$(e,!1)}function me(e,t){E(t),w(e),e.popupWindow===t&&(e.popupWindow=null),S(e)}function et(e){if(e.restoreDetach!==null)return;let t=n=>{n.persisted&&(o(),S(e))},o=()=>{window.removeEventListener("pageshow",t),e.restoreDetach=null};window.addEventListener("pageshow",t),e.restoreDetach=o}function tt(e,t,o,n){let r=k(o,!1);return n&&(r.target==="self"||r.target==="top")?($(e,!1),et(e)):S(e),r.target===null?(console.error("[maytes/checkout-button] could not leave the embedding frame:",r.cause),B(t,"navigation-blocked",r.cause),{outcome:"failed",reason:"navigation-blocked"}):(Qe(t,o,r.target),{outcome:"redirected",target:r.target})}function ot(e,t){let o=qe(e);return o===null?null:(e.popupWindow=o,e.popupNavigated=!1,je(o),Xe(o,e.config.cspNonce),U(e),document.dispatchEvent(new CustomEvent("maytes:checkout-opened",{detail:t})),ze(e,()=>{S(e),document.dispatchEvent(new CustomEvent("maytes:checkout-closed",{detail:t}))}),o)}async function _(e,t,o,n=!1){if(e.busy||e.destroyed)return{outcome:"ignored"};$(e,!0);let r=Je(e,o),d=t==="popup"&&!Ke(),i=d?ot(e,r):null;i===null&&n&&U(e,"redirect");try{let u=await e.config.createCheckout();if(e.destroyed||i!==null&&i.closed)return E(i),{outcome:"closed"};if(!Ve(u)){me(e,i);let m=new c(s.Config,"createCheckout must resolve to { checkoutId: string, checkoutUrl?: http(s) URL }");return console.error(m),B(r,"invalid-shape",m),{outcome:"failed",reason:"invalid-shape"}}let a=u.checkoutUrl??C(e.config.environment,e.config.baseUrl,u.checkoutId);return i!==null?(e.popupNavigated=!0,Ye(i,a),{outcome:"popup"}):(d&&console.warn("[maytes/checkout-button] popup was blocked; redirecting instead"),tt(e,r,a,n))}catch(u){return i!==null&&i.closed?{outcome:"closed"}:(me(e,i),console.error("[maytes/checkout-button] createCheckout failed:",u),B(r,"create-checkout-rejected",u),{outcome:"failed",reason:"create-checkout-rejected"})}}function fe(){let e=f("1em","currentColor");return e.setAttribute("class","maytes-checkout-button__logo"),e.setAttribute("aria-hidden","true"),e}function nt(){let e=document.createElement("span");return e.className="maytes-checkout-button__spinner",e.setAttribute("aria-hidden","true"),e}function he(e,t,o={}){if(e.destroyed)throw new c(s.Config,"renderButton() called on a destroyed Maytes instance");if(!(t instanceof HTMLElement))throw new c(s.Config,"renderButton(container) requires an HTMLElement");let n=q(o),r=M(o.redirectOverlay);de(e.config.cspNonce);let d=o.label??"Split with",i=o.block===!0,u=x(o.mode??"popup"),a=document.createElement("button");a.type="button",a.className=i?"maytes-checkout-button maytes-checkout-button--block":"maytes-checkout-button",Y(a,n),a.setAttribute("aria-label",`${d} Maytes`);let m=document.createTextNode(`${d} `),g=fe();a.appendChild(m),a.appendChild(g);let we=v=>{if(v){a.setAttribute("aria-disabled","true"),a.setAttribute("aria-busy","true");let b=nt();g.replaceWith(b),g=b}else{a.removeAttribute("aria-disabled"),a.removeAttribute("aria-busy");let b=fe();g.replaceWith(b),g=b}},I=!1,H=v=>{I&&(we(v),v||(I=!1))};e.busyViews.add(H);let V=()=>{e.busy||e.destroyed||(I=!0,_(e,u,"button",r))};a.addEventListener("click",V),t.appendChild(a);let j=!1,O=()=>{j||(j=!0,a.removeEventListener("click",V),a.remove(),e.busyViews.delete(H),e.teardowns.delete(O),pe())};return e.teardowns.add(O),O}function ye(){return`${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`}function ge(){if(typeof crypto<"u"&&typeof crypto.randomUUID=="function")try{return crypto.randomUUID()}catch{return ye()}return ye()}function be(e,t){let o={createCheckout:e.createCheckout,environment:e.environment};return t?.baseUrl!==void 0&&(o.baseUrl=t.baseUrl),t?.cspNonce!==void 0&&(o.cspNonce=t.cspNonce),{config:o,busy:!1,busyViews:new Set,instanceId:ge(),overlayEl:null,overlayDetach:null,restoreDetach:null,popupWindow:null,popupNavigated:!1,popupName:`maytes-checkout-${ge()}`,popupPollHandle:null,destroyed:!1,teardowns:new Set}}function rt(e){if(typeof e!="object"||e===null)throw new c(s.Config,"Maytes() requires an options object");if(typeof e.createCheckout!="function")throw new c(s.Config,"Maytes({ createCheckout }) must be an async function returning { checkoutId }");if(!te(e.environment))throw new c(s.Config,"Maytes({ environment }) must be 'sandbox' or 'production'")}function it(e){e!==void 0&&e.baseUrl!==void 0&&W(e.baseUrl)}var F=(e,t)=>{rt(e),it(t);let o=be(e,t),{checkoutUrl:n,redirectToCheckout:r}=re(o.config.environment,o.config.baseUrl);return{instanceId:o.instanceId,renderButton(i,u){return he(o,i,u)},openCheckout(i){if(o.destroyed)throw new c(s.Config,"openCheckout() called on a destroyed Maytes instance");let u=x(i?.mode??"popup","openCheckout"),a=M(i?.redirectOverlay,"openCheckout");return _(o,u,"api",a)},onBusyChange(i){if(o.destroyed)throw new c(s.Config,"onBusyChange() called on a destroyed Maytes instance");return o.busyViews.add(i),()=>{o.busyViews.delete(i)}},redirectToCheckout(i){r(i)},checkoutUrl(i){return n(i)},destroy(){if(!o.destroyed){o.destroyed=!0,w(o),o.restoreDetach?.(),h(o),o.popupNavigated||E(o.popupWindow),o.popupWindow=null,o.busy=!1;for(let i of[...o.teardowns])i();o.teardowns.clear()}}}};typeof window<"u"&&(window.Maytes=F);return Me(at);})();
155
+ window.Maytes = MaytesCheckoutButton.Maytes;
156
+ //# sourceMappingURL=checkout-button.js.map