@canopypay/checkout-sdk 0.2.0 → 0.3.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.
@@ -1 +1 @@
1
- "use strict";(()=>{(function(){"use strict";if(typeof window=="undefined")return;let f="width=460,height=640",m=2500,y="Pay with Canopy",h=500,g="canopy",v="cnpy_pk_live_";function b(){let e=document.currentScript;if(e&&e.src)try{return new URL(e.src).origin}catch(o){}let n=document.getElementsByTagName("script");for(let o=0;o<n.length;o+=1){let r=n[o].src;if(r&&/\/v1\/(?:[^/]+\/)?canopy\.js(?:[?#].*)?$/.test(r))try{return new URL(r).origin}catch(t){}}return null}function k(e){let n;try{n=new URL(e)}catch(t){return{ok:!1,code:"canopy_origin_invalid"}}if(!((n.pathname===""||n.pathname==="/")&&n.search===""&&n.hash===""))return{ok:!1,code:"canopy_origin_not_bare"};let r=n.hostname==="localhost"||n.hostname==="127.0.0.1";return n.protocol!=="https:"&&!r?{ok:!1,code:"canopy_origin_insecure"}:{ok:!0,origin:n.origin}}function _(e){if(e!==void 0)return k(e);let n=b();return n?{ok:!0,origin:n}:{ok:!1,code:"canopy_origin_undiscoverable"}}function c(e){let n=new URL("/customer-pay",e.origin);return n.searchParams.set("merchant",e.merchant),n.hash="cap="+encodeURIComponent(e.capability||""),n.toString()}function i(e,n,o){if(!e.destroyed)try{e.onEvent(Object.assign({type:n},o))}catch(r){}}function l(e){e.handshakeTimer&&(clearTimeout(e.handshakeTimer),e.handshakeTimer=null),e.resizeObserver&&(e.resizeObserver.disconnect(),e.resizeObserver=null),e.frame&&(e.frame.parentNode&&e.frame.parentNode.removeChild(e.frame),e.frame=null),e.lastHeight=null}function E(e){e.popupPollTimer&&clearInterval(e.popupPollTimer),e.popupPollTimer=setInterval(function(){try{e.popupWin&&e.popupWin.closed&&(e.popupPollTimer&&clearInterval(e.popupPollTimer),e.popupPollTimer=null,e.popupWin=null,i(e,"close",{via:"popup"}))}catch(n){e.popupPollTimer&&clearInterval(e.popupPollTimer),e.popupPollTimer=null}},h)}function p(e,n){if(!e.destroyed){if(!n){i(e,"error",{code:"popup_blocked"});return}if(!e.capability){try{n.close()}catch(o){}i(e,"error",{code:"capability_unavailable"});return}try{n.location.href=c(e)}catch(o){i(e,"error",{code:"popup_navigation_failed"});return}e.popupWin=n,E(e)}}function T(e){let n=document.createElement("button");n.type="button",n.textContent=e.fallbackLabel;let o=n.style;o.background="#1A7A4C",o.color="#FFFFFF",o.border="none",o.borderRadius="8px",o.height="48px",o.padding="0 20px",o.fontSize="16px",o.fontFamily="inherit",o.cursor="pointer",o.maxWidth="100%",o.overflow="hidden",o.textOverflow="ellipsis",o.whiteSpace="nowrap",o.display="inline-block",n.addEventListener("click",function(){let r=window.open("","canopy_checkout",f);p(e,r)}),e.node.appendChild(n),e.fallbackButton=n,i(e,"fallback_offered",{surface:"popup",reason:"iframe_unavailable"})}function w(e){let n=document.createElement("iframe");n.src=c(e),n.style.width="100%",n.style.border="none",e.node.appendChild(n),e.frame=n,typeof ResizeObserver!="undefined"&&(e.resizeObserver=new ResizeObserver(function(o){for(let r=0;r<o.length;r+=1){let t=Math.round(o[r].contentRect.height);t>0&&t!==e.lastHeight&&(e.lastHeight=t,i(e,"resize",{height:t}))}}),e.resizeObserver.observe(n)),e.handshakeTimer=setTimeout(function(){e.handshakeTimer=null,i(e,"surface_unavailable",{attempted:"iframe"}),l(e),e.surface==="auto"?T(e):i(e,"error",{code:"iframe_blocked"})},e.handshakeTimeoutMs)}function O(e,n){if(n.origin!==e.origin)return;let o=n.data;if(!(!o||o.source!==g)){if(o.type==="ready"){e.handshakeTimer&&(clearTimeout(e.handshakeTimer),e.handshakeTimer=null),i(e,"ready");return}if(o.type==="resize"&&typeof o.height=="number"&&e.frame){e.frame.style.height=o.height+"px";return}if(o.type==="paid"){i(e,"paid",o);return}if(o.type==="close"){i(e,"close");return}}}let s=new WeakMap;function L(e){if(typeof e.merchant!="string"||e.merchant.indexOf(v)!==0){try{(e.onEvent||function(){})({type:"error",code:"invalid_merchant_key"})}catch(a){}return{openNow:function(){},destroy:function(){}}}let n=_(e.canopyOrigin);if(!n.ok){try{(e.onEvent||function(){})({type:"error",code:n.code})}catch(a){}return{openNow:function(){},destroy:function(){}}}let o=typeof e.target=="string"?document.querySelector(e.target):e.target;if(!o)return{openNow:function(){},destroy:function(){}};let r=s.get(o);if(r)return r;let t={node:o,capability:e.capability,merchant:e.merchant,origin:n.origin,surface:e.surface||"popup",handshakeTimeoutMs:e.handshakeTimeoutMs||m,fallbackLabel:e.fallbackLabel||y,onEvent:e.onEvent||function(){},destroyed:!1,messageListener:function(){},handshakeTimer:null,resizeObserver:null,frame:null,fallbackButton:null,lastHeight:null,popupWin:null,popupPollTimer:null};t.messageListener=function(a){O(t,a)},window.addEventListener("message",t.messageListener),(t.surface==="iframe"||t.surface==="auto")&&w(t);let d={openNow:function(a){p(t,a)},destroy:function(){t.destroyed||(t.destroyed=!0,window.removeEventListener("message",t.messageListener),l(t),t.popupPollTimer&&(clearInterval(t.popupPollTimer),t.popupPollTimer=null),t.fallbackButton&&(t.fallbackButton.parentNode&&t.fallbackButton.parentNode.removeChild(t.fallbackButton),t.fallbackButton=null),s.delete(o))}};return s.set(o,d),d}let u=window;u.Canopy&&u.Canopy.__version==="2"||(u.Canopy={mount:L,__version:"2"})})();})();
1
+ "use strict";(()=>{(function(){"use strict";if(typeof window=="undefined")return;let f="width=460,height=640",y=2500,m="Pay with Canopy",h=500,g=420,v="canopy",b="cnpy_pk_live_";function k(){let e=document.currentScript;if(e&&e.src)try{return new URL(e.src).origin}catch(o){}let n=document.getElementsByTagName("script");for(let o=0;o<n.length;o+=1){let r=n[o].src;if(r&&/\/v1\/(?:[^/]+\/)?canopy\.js(?:[?#].*)?$/.test(r))try{return new URL(r).origin}catch(t){}}return null}function _(e){let n;try{n=new URL(e)}catch(t){return{ok:!1,code:"canopy_origin_invalid"}}if(!((n.pathname===""||n.pathname==="/")&&n.search===""&&n.hash===""))return{ok:!1,code:"canopy_origin_not_bare"};let r=n.hostname==="localhost"||n.hostname==="127.0.0.1";return n.protocol!=="https:"&&!r?{ok:!1,code:"canopy_origin_insecure"}:{ok:!0,origin:n.origin}}function E(e){if(e!==void 0)return _(e);let n=k();return n?{ok:!0,origin:n}:{ok:!1,code:"canopy_origin_undiscoverable"}}function c(e){let n=new URL("/customer-pay",e.origin);return n.searchParams.set("merchant",e.merchant),n.hash="cap="+encodeURIComponent(e.capability||""),n.toString()}function i(e,n,o){if(!e.destroyed)try{e.onEvent(Object.assign({type:n},o))}catch(r){}}function l(e){e.handshakeTimer&&(clearTimeout(e.handshakeTimer),e.handshakeTimer=null),e.resizeObserver&&(e.resizeObserver.disconnect(),e.resizeObserver=null),e.frame&&(e.frame.parentNode&&e.frame.parentNode.removeChild(e.frame),e.frame=null),e.lastHeight=null}function T(e){e.popupPollTimer&&clearInterval(e.popupPollTimer),e.popupPollTimer=setInterval(function(){try{e.popupWin&&e.popupWin.closed&&(e.popupPollTimer&&clearInterval(e.popupPollTimer),e.popupPollTimer=null,e.popupWin=null,i(e,"close",{via:"popup"}))}catch(n){e.popupPollTimer&&clearInterval(e.popupPollTimer),e.popupPollTimer=null}},h)}function p(e,n){if(!e.destroyed){if(!n){i(e,"error",{code:"popup_blocked"});return}if(!e.capability){try{n.close()}catch(o){}i(e,"error",{code:"capability_unavailable"});return}try{n.location.href=c(e)}catch(o){i(e,"error",{code:"popup_navigation_failed"});return}e.popupWin=n,T(e)}}function w(e){let n=document.createElement("button");n.type="button",n.textContent=e.fallbackLabel;let o=n.style;o.background="#1A7A4C",o.color="#FFFFFF",o.border="none",o.borderRadius="8px",o.height="48px",o.padding="0 20px",o.fontSize="16px",o.fontFamily="inherit",o.cursor="pointer",o.maxWidth="100%",o.overflow="hidden",o.textOverflow="ellipsis",o.whiteSpace="nowrap",o.display="inline-block",n.addEventListener("click",function(){let r=window.open("","canopy_checkout",f);p(e,r)}),e.node.appendChild(n),e.fallbackButton=n,i(e,"fallback_offered",{surface:"popup",reason:"iframe_unavailable"})}function O(e){let n=document.createElement("iframe");n.src=c(e),n.style.width="100%",n.style.border="none",n.setAttribute("allow","clipboard-write"),n.setAttribute("title","Canopy checkout"),n.style.height=g+"px",e.node.appendChild(n),e.frame=n,typeof ResizeObserver!="undefined"&&(e.resizeObserver=new ResizeObserver(function(o){for(let r=0;r<o.length;r+=1){let t=Math.round(o[r].contentRect.height);t>0&&t!==e.lastHeight&&(e.lastHeight=t,i(e,"resize",{height:t}))}}),e.resizeObserver.observe(n)),e.handshakeTimer=setTimeout(function(){e.handshakeTimer=null,i(e,"surface_unavailable",{attempted:"iframe"}),l(e),e.surface==="auto"?w(e):i(e,"error",{code:"iframe_blocked"})},e.handshakeTimeoutMs)}function L(e,n){if(n.origin!==e.origin)return;let o=n.data;if(!(!o||o.source!==v)){if(o.type==="ready"){e.handshakeTimer&&(clearTimeout(e.handshakeTimer),e.handshakeTimer=null),i(e,"ready");return}if(o.type==="resize"&&typeof o.height=="number"&&e.frame){e.frame.style.height=o.height+"px";return}if(o.type==="paid"){i(e,"paid",o);return}if(o.type==="close"){i(e,"close");return}}}let s=new WeakMap;function R(e){if(typeof e.merchant!="string"||e.merchant.indexOf(b)!==0){try{(e.onEvent||function(){})({type:"error",code:"invalid_merchant_key"})}catch(a){}return{openNow:function(){},destroy:function(){}}}let n=E(e.canopyOrigin);if(!n.ok){try{(e.onEvent||function(){})({type:"error",code:n.code})}catch(a){}return{openNow:function(){},destroy:function(){}}}let o=typeof e.target=="string"?document.querySelector(e.target):e.target;if(!o)return{openNow:function(){},destroy:function(){}};let r=s.get(o);if(r)return r;let t={node:o,capability:e.capability,merchant:e.merchant,origin:n.origin,surface:e.surface||"popup",handshakeTimeoutMs:e.handshakeTimeoutMs||y,fallbackLabel:e.fallbackLabel||m,onEvent:e.onEvent||function(){},destroyed:!1,messageListener:function(){},handshakeTimer:null,resizeObserver:null,frame:null,fallbackButton:null,lastHeight:null,popupWin:null,popupPollTimer:null};t.messageListener=function(a){L(t,a)},window.addEventListener("message",t.messageListener),(t.surface==="iframe"||t.surface==="auto")&&O(t);let d={openNow:function(a){p(t,a)},destroy:function(){t.destroyed||(t.destroyed=!0,window.removeEventListener("message",t.messageListener),l(t),t.popupPollTimer&&(clearInterval(t.popupPollTimer),t.popupPollTimer=null),t.fallbackButton&&(t.fallbackButton.parentNode&&t.fallbackButton.parentNode.removeChild(t.fallbackButton),t.fallbackButton=null),s.delete(o))}};return s.set(o,d),d}let u=window;u.Canopy&&u.Canopy.__version==="3"||(u.Canopy={mount:R,__version:"3"})})();})();
package/dist/index.cjs CHANGED
@@ -26,7 +26,7 @@ __export(src_exports, {
26
26
  module.exports = __toCommonJS(src_exports);
27
27
 
28
28
  // src/loader.ts
29
- var LOADER_VERSION = "2";
29
+ var LOADER_VERSION = "3";
30
30
  (function() {
31
31
  "use strict";
32
32
  if (typeof window === "undefined") return;
@@ -34,6 +34,7 @@ var LOADER_VERSION = "2";
34
34
  const DEFAULT_HANDSHAKE_TIMEOUT_MS = 2500;
35
35
  const DEFAULT_FALLBACK_LABEL = "Pay with Canopy";
36
36
  const POPUP_POLL_INTERVAL_MS = 500;
37
+ const INITIAL_FRAME_HEIGHT_PX = 420;
37
38
  const MESSAGE_SOURCE = "canopy";
38
39
  const PUBLISHABLE_KEY_PREFIX = "cnpy_pk_live_";
39
40
  function resolveOrigin() {
@@ -186,6 +187,9 @@ var LOADER_VERSION = "2";
186
187
  iframe.src = buildCheckoutUrl(internal);
187
188
  iframe.style.width = "100%";
188
189
  iframe.style.border = "none";
190
+ iframe.setAttribute("allow", "clipboard-write");
191
+ iframe.setAttribute("title", "Canopy checkout");
192
+ iframe.style.height = INITIAL_FRAME_HEIGHT_PX + "px";
189
193
  internal.node.appendChild(iframe);
190
194
  internal.frame = iframe;
191
195
  if (typeof ResizeObserver !== "undefined") {
package/dist/index.d.cts CHANGED
@@ -1,7 +1,7 @@
1
1
  /**
2
- * Canopy checkout SDK loader (Phase 23 Plan 08, SDK-01 / SDK-04 / API-03).
2
+ * Canopy checkout SDK loader.
3
3
  *
4
- * The whole merchant integration, measured (`sdk-loader-contract.md`):
4
+ * The whole merchant integration:
5
5
  *
6
6
  * ```html
7
7
  * <script src="https://canopy.example/v1/canopy.js"></script>
@@ -13,7 +13,7 @@
13
13
  * capability,
14
14
  * merchant: "cnpy_pk_live_abc123",
15
15
  * onEvent: (e) => {
16
- * // SDK-04: `paid` is advisory only -- it drives UI, never fulfilment.
16
+ * // `paid` is advisory only -- it drives UI, never fulfilment.
17
17
  * // Fulfilment reads a webhook or a server-side status call.
18
18
  * if (e.type === "paid") showBanner("Payment sent — we'll confirm shortly");
19
19
  * },
@@ -29,25 +29,16 @@
29
29
  *
30
30
  * One entry point (`mount`), one option surface (`surface: "popup" | "iframe"
31
31
  * | "auto"`, default `"popup"`), seven events (`ready`, `resize`, `paid`,
32
- * `error`, `close`, `surface_unavailable`, `fallback_offered`). See
33
- * `.claude/skills/spike-findings-Canopy/references/sdk-loader-contract.md`
34
- * and `.claude/skills/spike-findings-Canopy/references/embed-surface-selection.md`
35
- * for the full contract this file implements.
36
- *
37
- * Bump LOADER_VERSION whenever this file's runtime behaviour changes.
38
- * `scripts/build-checkout-sdk.ts` extracts this exact literal by regex from
39
- * this source file (not the built bundle) so the immutable serving path
40
- * (`/v1/{version}/canopy.js`) and this loader's own `Canopy.__version`
41
- * self-report are always the same value by construction.
32
+ * `error`, `close`, `surface_unavailable`, `fallback_offered`).
33
+ */
34
+ /**
35
+ * `close` is POPUP-ONLY. It reports that the payer closed the checkout
36
+ * window, which the loader detects by polling `popupWin.closed`. An inline
37
+ * (`surface: "iframe"`) embed has nothing to close -- the merchant owns the
38
+ * container and removes it themselves -- so no `close` is ever emitted
39
+ * there, and none is synthesised to make the two surfaces look alike.
42
40
  *
43
- * Phase 31 Plan 02 (DIST-01, DIST-04): the five types below are hoisted
44
- * above the IIFE and `export type`-ed so `packages/checkout-sdk/src/index.ts`
45
- * (the npm entry) can re-export a real merchant-facing surface instead of
46
- * shipping a 13-byte `export { }` declaration file. This is byte-safe --
47
- * type-only exports are erased before esbuild picks its format wrapper, so
48
- * the IIFE build this script tag serves is unaffected (verified sha
49
- * `644fbe336e875f4f` both ways, spike 100 exp2/exp3). `Internal` stays
50
- * inside the IIFE: it is not part of the merchant-facing surface.
41
+ * Every other event reaches both surfaces.
51
42
  */
52
43
  type CanopyEventType = "ready" | "resize" | "paid" | "error" | "close" | "surface_unavailable" | "fallback_offered";
53
44
  type CanopyEvent = {
@@ -64,14 +55,14 @@ type MountOptions = {
64
55
  handshakeTimeoutMs?: number;
65
56
  fallbackLabel?: string;
66
57
  /**
67
- * Phase 31 Plan 05 (DIST-02): the Canopy origin `mount()` builds the
68
- * checkout URL against and pins the `postMessage` listener to. A
58
+ * The Canopy origin `mount()` builds the checkout URL against and pins
59
+ * the `postMessage` listener to. A
69
60
  * `<script src="https://.../v1/canopy.js">` tag can be discovered
70
61
  * automatically; a bundled npm consumer has no such tag, so it MUST state
71
62
  * this explicitly or `mount()` refuses with `canopy_origin_undiscoverable`.
72
63
  * There is no default -- baking one in would turn that loud, diagnosable
73
64
  * refusal into a silent wrong-origin checkout for every sandbox or
74
- * self-hosted integration (31-RESEARCH.md Pitfall 5). Must be a bare
65
+ * self-hosted integration. Must be a bare
75
66
  * origin (no path, search, or hash) and `https`, except for the
76
67
  * `localhost` / `127.0.0.1` development affordance. Wins over a present
77
68
  * script tag when both are available.
@@ -83,18 +74,7 @@ type CanopyHandle = {
83
74
  destroy: () => void;
84
75
  };
85
76
 
86
- /**
87
- * Canopy checkout SDK npm entry (Phase 31 Plan 02, DIST-01 / DIST-04).
88
- *
89
- * This is the `npm i @canopypay/checkout-sdk` consumer surface -- it delegates
90
- * to `./loader`'s IIFE (imported here for its side effect of installing
91
- * `window.Canopy`) rather than re-implementing `mount()`. A second
92
- * implementation of the security-relevant origin pinning and the
93
- * publishable-key prefix check would fork exactly the mechanism DIST-01
94
- * exists to keep singular. See
95
- * `.claude/skills/spike-findings-Canopy/references/sdk-package-distribution.md`
96
- * § "How to Build It" step 2 for the verified shape this file follows.
97
- */
77
+ /** Canopy checkout SDK -- the `npm i @canopypay/checkout-sdk` entry point. */
98
78
 
99
79
  declare global {
100
80
  interface Window {
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  /**
2
- * Canopy checkout SDK loader (Phase 23 Plan 08, SDK-01 / SDK-04 / API-03).
2
+ * Canopy checkout SDK loader.
3
3
  *
4
- * The whole merchant integration, measured (`sdk-loader-contract.md`):
4
+ * The whole merchant integration:
5
5
  *
6
6
  * ```html
7
7
  * <script src="https://canopy.example/v1/canopy.js"></script>
@@ -13,7 +13,7 @@
13
13
  * capability,
14
14
  * merchant: "cnpy_pk_live_abc123",
15
15
  * onEvent: (e) => {
16
- * // SDK-04: `paid` is advisory only -- it drives UI, never fulfilment.
16
+ * // `paid` is advisory only -- it drives UI, never fulfilment.
17
17
  * // Fulfilment reads a webhook or a server-side status call.
18
18
  * if (e.type === "paid") showBanner("Payment sent — we'll confirm shortly");
19
19
  * },
@@ -29,25 +29,16 @@
29
29
  *
30
30
  * One entry point (`mount`), one option surface (`surface: "popup" | "iframe"
31
31
  * | "auto"`, default `"popup"`), seven events (`ready`, `resize`, `paid`,
32
- * `error`, `close`, `surface_unavailable`, `fallback_offered`). See
33
- * `.claude/skills/spike-findings-Canopy/references/sdk-loader-contract.md`
34
- * and `.claude/skills/spike-findings-Canopy/references/embed-surface-selection.md`
35
- * for the full contract this file implements.
36
- *
37
- * Bump LOADER_VERSION whenever this file's runtime behaviour changes.
38
- * `scripts/build-checkout-sdk.ts` extracts this exact literal by regex from
39
- * this source file (not the built bundle) so the immutable serving path
40
- * (`/v1/{version}/canopy.js`) and this loader's own `Canopy.__version`
41
- * self-report are always the same value by construction.
32
+ * `error`, `close`, `surface_unavailable`, `fallback_offered`).
33
+ */
34
+ /**
35
+ * `close` is POPUP-ONLY. It reports that the payer closed the checkout
36
+ * window, which the loader detects by polling `popupWin.closed`. An inline
37
+ * (`surface: "iframe"`) embed has nothing to close -- the merchant owns the
38
+ * container and removes it themselves -- so no `close` is ever emitted
39
+ * there, and none is synthesised to make the two surfaces look alike.
42
40
  *
43
- * Phase 31 Plan 02 (DIST-01, DIST-04): the five types below are hoisted
44
- * above the IIFE and `export type`-ed so `packages/checkout-sdk/src/index.ts`
45
- * (the npm entry) can re-export a real merchant-facing surface instead of
46
- * shipping a 13-byte `export { }` declaration file. This is byte-safe --
47
- * type-only exports are erased before esbuild picks its format wrapper, so
48
- * the IIFE build this script tag serves is unaffected (verified sha
49
- * `644fbe336e875f4f` both ways, spike 100 exp2/exp3). `Internal` stays
50
- * inside the IIFE: it is not part of the merchant-facing surface.
41
+ * Every other event reaches both surfaces.
51
42
  */
52
43
  type CanopyEventType = "ready" | "resize" | "paid" | "error" | "close" | "surface_unavailable" | "fallback_offered";
53
44
  type CanopyEvent = {
@@ -64,14 +55,14 @@ type MountOptions = {
64
55
  handshakeTimeoutMs?: number;
65
56
  fallbackLabel?: string;
66
57
  /**
67
- * Phase 31 Plan 05 (DIST-02): the Canopy origin `mount()` builds the
68
- * checkout URL against and pins the `postMessage` listener to. A
58
+ * The Canopy origin `mount()` builds the checkout URL against and pins
59
+ * the `postMessage` listener to. A
69
60
  * `<script src="https://.../v1/canopy.js">` tag can be discovered
70
61
  * automatically; a bundled npm consumer has no such tag, so it MUST state
71
62
  * this explicitly or `mount()` refuses with `canopy_origin_undiscoverable`.
72
63
  * There is no default -- baking one in would turn that loud, diagnosable
73
64
  * refusal into a silent wrong-origin checkout for every sandbox or
74
- * self-hosted integration (31-RESEARCH.md Pitfall 5). Must be a bare
65
+ * self-hosted integration. Must be a bare
75
66
  * origin (no path, search, or hash) and `https`, except for the
76
67
  * `localhost` / `127.0.0.1` development affordance. Wins over a present
77
68
  * script tag when both are available.
@@ -83,18 +74,7 @@ type CanopyHandle = {
83
74
  destroy: () => void;
84
75
  };
85
76
 
86
- /**
87
- * Canopy checkout SDK npm entry (Phase 31 Plan 02, DIST-01 / DIST-04).
88
- *
89
- * This is the `npm i @canopypay/checkout-sdk` consumer surface -- it delegates
90
- * to `./loader`'s IIFE (imported here for its side effect of installing
91
- * `window.Canopy`) rather than re-implementing `mount()`. A second
92
- * implementation of the security-relevant origin pinning and the
93
- * publishable-key prefix check would fork exactly the mechanism DIST-01
94
- * exists to keep singular. See
95
- * `.claude/skills/spike-findings-Canopy/references/sdk-package-distribution.md`
96
- * § "How to Build It" step 2 for the verified shape this file follows.
97
- */
77
+ /** Canopy checkout SDK -- the `npm i @canopypay/checkout-sdk` entry point. */
98
78
 
99
79
  declare global {
100
80
  interface Window {
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // src/loader.ts
2
- var LOADER_VERSION = "2";
2
+ var LOADER_VERSION = "3";
3
3
  (function() {
4
4
  "use strict";
5
5
  if (typeof window === "undefined") return;
@@ -7,6 +7,7 @@ var LOADER_VERSION = "2";
7
7
  const DEFAULT_HANDSHAKE_TIMEOUT_MS = 2500;
8
8
  const DEFAULT_FALLBACK_LABEL = "Pay with Canopy";
9
9
  const POPUP_POLL_INTERVAL_MS = 500;
10
+ const INITIAL_FRAME_HEIGHT_PX = 420;
10
11
  const MESSAGE_SOURCE = "canopy";
11
12
  const PUBLISHABLE_KEY_PREFIX = "cnpy_pk_live_";
12
13
  function resolveOrigin() {
@@ -159,6 +160,9 @@ var LOADER_VERSION = "2";
159
160
  iframe.src = buildCheckoutUrl(internal);
160
161
  iframe.style.width = "100%";
161
162
  iframe.style.border = "none";
163
+ iframe.setAttribute("allow", "clipboard-write");
164
+ iframe.setAttribute("title", "Canopy checkout");
165
+ iframe.style.height = INITIAL_FRAME_HEIGHT_PX + "px";
162
166
  internal.node.appendChild(iframe);
163
167
  internal.frame = iframe;
164
168
  if (typeof ResizeObserver !== "undefined") {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@canopypay/checkout-sdk",
3
3
  "private": false,
4
- "version": "0.2.0",
4
+ "version": "0.3.0",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
7
7
  "module": "./dist/index.js",
@@ -26,10 +26,11 @@
26
26
  ],
27
27
  "scripts": {
28
28
  "build": "tsup --config tsup.config.ts",
29
- "build:npm": "tsup --config tsup.npm.config.ts"
29
+ "build:npm": "tsup --config tsup.npm.config.ts",
30
+ "prepublishOnly": "npm --prefix ../.. run sdk:build && npm run build:npm"
30
31
  },
31
32
  "canopy": {
32
- "loaderVersion": "2",
33
+ "loaderVersion": "3",
33
34
  "apiVersion": "2026-09-01"
34
35
  },
35
36
  "publishConfig": {