@mj-biz-apps/orders-server 5.1.0 → 5.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 (42) hide show
  1. package/README.md +7 -0
  2. package/dist/CheckoutServerExtension.d.ts +96 -0
  3. package/dist/CheckoutServerExtension.d.ts.map +1 -0
  4. package/dist/CheckoutServerExtension.js +554 -0
  5. package/dist/CheckoutServerExtension.js.map +1 -0
  6. package/dist/__tests__/CheckoutServerExtension.test.d.ts +2 -0
  7. package/dist/__tests__/CheckoutServerExtension.test.d.ts.map +1 -0
  8. package/dist/__tests__/CheckoutServerExtension.test.js +257 -0
  9. package/dist/__tests__/CheckoutServerExtension.test.js.map +1 -0
  10. package/dist/__tests__/checkout-edge-policy.test.d.ts +2 -0
  11. package/dist/__tests__/checkout-edge-policy.test.d.ts.map +1 -0
  12. package/dist/__tests__/checkout-edge-policy.test.js +70 -0
  13. package/dist/__tests__/checkout-edge-policy.test.js.map +1 -0
  14. package/dist/__tests__/checkout-host-page.test.d.ts +2 -0
  15. package/dist/__tests__/checkout-host-page.test.d.ts.map +1 -0
  16. package/dist/__tests__/checkout-host-page.test.js +86 -0
  17. package/dist/__tests__/checkout-host-page.test.js.map +1 -0
  18. package/dist/__tests__/server-extensions-manifest.test.d.ts +2 -0
  19. package/dist/__tests__/server-extensions-manifest.test.d.ts.map +1 -0
  20. package/dist/__tests__/server-extensions-manifest.test.js +29 -0
  21. package/dist/__tests__/server-extensions-manifest.test.js.map +1 -0
  22. package/dist/checkout-edge-policy.d.ts +44 -0
  23. package/dist/checkout-edge-policy.d.ts.map +1 -0
  24. package/dist/checkout-edge-policy.js +84 -0
  25. package/dist/checkout-edge-policy.js.map +1 -0
  26. package/dist/checkout-host-page.d.ts +43 -0
  27. package/dist/checkout-host-page.d.ts.map +1 -0
  28. package/dist/checkout-host-page.js +445 -0
  29. package/dist/checkout-host-page.js.map +1 -0
  30. package/dist/generated/generated.d.ts +189 -173
  31. package/dist/generated/generated.d.ts.map +1 -1
  32. package/dist/generated/generated.js +1152 -1947
  33. package/dist/generated/generated.js.map +1 -1
  34. package/dist/index.d.ts +2 -0
  35. package/dist/index.d.ts.map +1 -1
  36. package/dist/index.js +13 -1
  37. package/dist/index.js.map +1 -1
  38. package/dist/server-extensions-manifest.d.ts +14 -0
  39. package/dist/server-extensions-manifest.d.ts.map +1 -0
  40. package/dist/server-extensions-manifest.js +15 -0
  41. package/dist/server-extensions-manifest.js.map +1 -0
  42. package/package.json +30 -13
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Pure helpers for the anonymous checkout edge: slug validation and origin allowlist.
3
+ * Extracted so unit tests do not have to boot Express or the ClassFactory.
4
+ */
5
+ /** POST route suffixes that must never be treated as a distribution slug. */
6
+ export const CHECKOUT_RESERVED_SLUGS = new Set([
7
+ 'initialize',
8
+ 'draft',
9
+ 'payment-intent',
10
+ 'complete',
11
+ ]);
12
+ const SLUG_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
13
+ /**
14
+ * Public distribution slugs: short, URL-safe, not colliding with the POST verbs
15
+ * mounted on the same root.
16
+ */
17
+ export function isValidCheckoutSlug(slug) {
18
+ if (!slug || slug.length > 128) {
19
+ return false;
20
+ }
21
+ if (CHECKOUT_RESERVED_SLUGS.has(slug.toLowerCase())) {
22
+ return false;
23
+ }
24
+ return SLUG_PATTERN.test(slug);
25
+ }
26
+ /**
27
+ * True when the browser Origin is the same host:port as this request (the
28
+ * first-party `GET /checkout/:slug` page posting back to MJAPI).
29
+ */
30
+ export function isSameOrigin(origin, hostHeader) {
31
+ if (!origin || !hostHeader) {
32
+ return false;
33
+ }
34
+ try {
35
+ const url = new URL(origin);
36
+ return url.host.toLowerCase() === hostHeader.toLowerCase();
37
+ }
38
+ catch {
39
+ return false;
40
+ }
41
+ }
42
+ /**
43
+ * Widget `allowedOrigins` is a **cross-origin embed** allowlist. Same-origin
44
+ * posts from the MJAPI-hosted public page always pass. When the allowlist is
45
+ * absent or empty, any origin is allowed (the distribution slug remains the
46
+ * access control).
47
+ */
48
+ /**
49
+ * Client IP for rate limiting and Turnstile.
50
+ *
51
+ * `TrustedProxyHops` is the number of reverse proxies that append to
52
+ * `X-Forwarded-For`. 0 (the default) ignores XFF entirely and uses the socket
53
+ * address — the leftmost XFF hop is client-supplied and must never key a
54
+ * security decision. When hops is N, the Nth-from-the-right entry is used
55
+ * (the address the outermost trusted proxy observed).
56
+ */
57
+ export function resolveClientIp(req, trustedProxyHops = 0) {
58
+ const socket = req.socket?.remoteAddress ?? 'unknown';
59
+ if (!Number.isInteger(trustedProxyHops) || trustedProxyHops < 1) {
60
+ return socket;
61
+ }
62
+ const fwd = req.headers['x-forwarded-for'];
63
+ const raw = Array.isArray(fwd) ? fwd.join(',') : typeof fwd === 'string' ? fwd : undefined;
64
+ if (!raw) {
65
+ return socket;
66
+ }
67
+ const hops = raw.split(',').map((h) => h.trim()).filter((h) => h.length > 0);
68
+ const idx = hops.length - trustedProxyHops;
69
+ if (idx < 0 || idx >= hops.length) {
70
+ return socket;
71
+ }
72
+ return hops[idx];
73
+ }
74
+ export function originAllowed(origin, policy, hostHeader) {
75
+ if (isSameOrigin(origin, hostHeader)) {
76
+ return true;
77
+ }
78
+ if (!policy.allowedOrigins || policy.allowedOrigins.length === 0) {
79
+ return true;
80
+ }
81
+ const normalized = origin.replace(/\/+$/, '').toLowerCase();
82
+ return policy.allowedOrigins.some((allowed) => allowed.replace(/\/+$/, '').toLowerCase() === normalized);
83
+ }
84
+ //# sourceMappingURL=checkout-edge-policy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"checkout-edge-policy.js","sourceRoot":"","sources":["../src/checkout-edge-policy.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Public checkout host page — a self-contained HTML document that drives
3
+ * `OrdersCheckoutEdge` POSTs from a browser with no Explorer shell and no
4
+ * custom-element bundle.
5
+ *
6
+ * All per-request values reach the boot script via HTML-escaped `data-*`
7
+ * attributes (Forms host-page XSS rule): nothing attacker-controlled is
8
+ * interpolated into the inline `<script>`.
9
+ */
10
+ export interface CheckoutHostPageOptions {
11
+ /** Distribution slug from `GET /checkout/:slug`. */
12
+ slug: string;
13
+ /**
14
+ * URL prefix the boot script POSTs to (the extension RootPath, e.g. `/checkout`).
15
+ * Relative so the page works behind whatever public origin MJAPI is served on.
16
+ */
17
+ apiRoot: string;
18
+ /** Browser tab title before initialize returns the widget name. */
19
+ pageTitle?: string;
20
+ /** Per-response CSP nonce. Required so a caller cannot emit a nonce-less page. */
21
+ cspNonce: string;
22
+ /**
23
+ * Relative URL of the `<mj-orders-checkout>` Angular Element bundle.
24
+ * When set, the page hosts the reusable Angular widget (customUI, introspected
25
+ * extension fields). When omitted, a vanilla fallback form is used.
26
+ */
27
+ elementSrc?: string;
28
+ }
29
+ export interface CheckoutHostErrorOptions {
30
+ message: string;
31
+ pageTitle?: string;
32
+ /** Per-response CSP nonce. Required so a caller cannot emit a nonce-less page. */
33
+ cspNonce: string;
34
+ }
35
+ /** Headers for the public checkout host / error pages (CSP is nonce-based). */
36
+ export declare function checkoutHostSecurityHeaders(nonce: string): Record<string, string>;
37
+ /** Escape a string for safe insertion into HTML text content. */
38
+ export declare function escapeHtml(value: string): string;
39
+ /** Escape a string for safe insertion into a double-quoted HTML attribute. */
40
+ export declare function escapeAttr(value: string): string;
41
+ export declare function renderCheckoutHostPage(options: CheckoutHostPageOptions): string;
42
+ export declare function renderCheckoutHostErrorPage(options: CheckoutHostErrorOptions): string;
43
+ //# sourceMappingURL=checkout-host-page.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"checkout-host-page.d.ts","sourceRoot":"","sources":["../src/checkout-host-page.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,MAAM,WAAW,uBAAuB;IACpC,oDAAoD;IACpD,IAAI,EAAE,MAAM,CAAC;IACb;;;OAGG;IACH,OAAO,EAAE,MAAM,CAAC;IAChB,mEAAmE;IACnE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,kFAAkF;IAClF,QAAQ,EAAE,MAAM,CAAC;IACjB;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,wBAAwB;IACrC,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,kFAAkF;IAClF,QAAQ,EAAE,MAAM,CAAC;CACpB;AAED,+EAA+E;AAC/E,wBAAgB,2BAA2B,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAoBjF;AAED,iEAAiE;AACjE,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAKhD;AAED,8EAA8E;AAC9E,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEhD;AAED,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,uBAAuB,GAAG,MAAM,CAwC/E;AAED,wBAAgB,2BAA2B,CAAC,OAAO,EAAE,wBAAwB,GAAG,MAAM,CAmBrF"}
@@ -0,0 +1,445 @@
1
+ /**
2
+ * Public checkout host page — a self-contained HTML document that drives
3
+ * `OrdersCheckoutEdge` POSTs from a browser with no Explorer shell and no
4
+ * custom-element bundle.
5
+ *
6
+ * All per-request values reach the boot script via HTML-escaped `data-*`
7
+ * attributes (Forms host-page XSS rule): nothing attacker-controlled is
8
+ * interpolated into the inline `<script>`.
9
+ */
10
+ /** Headers for the public checkout host / error pages (CSP is nonce-based). */
11
+ export function checkoutHostSecurityHeaders(nonce) {
12
+ const n = nonce.replace(/[^A-Za-z0-9+/=_-]/g, '');
13
+ const csp = [
14
+ "default-src 'none'",
15
+ "base-uri 'none'",
16
+ "form-action 'none'",
17
+ "frame-ancestors 'none'",
18
+ `script-src 'nonce-${n}' 'self' https://js.stripe.com`,
19
+ `style-src 'nonce-${n}' 'self'`,
20
+ "img-src 'self' data: https://*.stripe.com",
21
+ 'frame-src https://js.stripe.com https://*.js.stripe.com https://hooks.stripe.com https://*.stripe.com https://*.hcaptcha.com https://newassets.hcaptcha.com',
22
+ "connect-src 'self' https://api.stripe.com https://m.stripe.network",
23
+ ].join('; ');
24
+ return {
25
+ 'Content-Security-Policy': csp,
26
+ 'X-Frame-Options': 'DENY',
27
+ 'Referrer-Policy': 'no-referrer',
28
+ 'X-Content-Type-Options': 'nosniff',
29
+ 'Cache-Control': 'no-store',
30
+ };
31
+ }
32
+ /** Escape a string for safe insertion into HTML text content. */
33
+ export function escapeHtml(value) {
34
+ return value
35
+ .replace(/&/g, '&amp;')
36
+ .replace(/</g, '&lt;')
37
+ .replace(/>/g, '&gt;');
38
+ }
39
+ /** Escape a string for safe insertion into a double-quoted HTML attribute. */
40
+ export function escapeAttr(value) {
41
+ return escapeHtml(value).replace(/"/g, '&quot;');
42
+ }
43
+ export function renderCheckoutHostPage(options) {
44
+ const title = escapeHtml(options.pageTitle ?? 'Checkout');
45
+ const slug = escapeAttr(options.slug);
46
+ const apiRoot = escapeAttr(options.apiRoot);
47
+ const nonceAttr = ` nonce="${escapeAttr(options.cspNonce)}"`;
48
+ const elementSrc = options.elementSrc ? escapeAttr(options.elementSrc) : '';
49
+ if (elementSrc) {
50
+ return `<!doctype html>
51
+ <html lang="en">
52
+ <head>
53
+ <meta charset="utf-8" />
54
+ <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
55
+ <meta name="robots" content="noindex" />
56
+ <title>${title}</title>
57
+ <style${nonceAttr}>${PAGE_CSS}</style>
58
+ <script type="module" src="${elementSrc}"${nonceAttr}></script>
59
+ </head>
60
+ <body>
61
+ <main class="mj-co">
62
+ <mj-orders-checkout slug="${slug}" api-root="${apiRoot}" csp-nonce="${escapeAttr(options.cspNonce)}"></mj-orders-checkout>
63
+ </main>
64
+ </body>
65
+ </html>`;
66
+ }
67
+ return `<!doctype html>
68
+ <html lang="en">
69
+ <head>
70
+ <meta charset="utf-8" />
71
+ <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
72
+ <meta name="robots" content="noindex" />
73
+ <title>${title}</title>
74
+ <style${nonceAttr}>${PAGE_CSS}</style>
75
+ </head>
76
+ <body>
77
+ <main class="mj-co" id="mj-co" data-slug="${slug}" data-api-root="${apiRoot}">
78
+ <p class="mj-co__status" role="status">Loading checkout…</p>
79
+ </main>
80
+ <script${nonceAttr}>${BOOT_SCRIPT}</script>
81
+ </body>
82
+ </html>`;
83
+ }
84
+ export function renderCheckoutHostErrorPage(options) {
85
+ const title = escapeHtml(options.pageTitle ?? 'Checkout unavailable');
86
+ const message = escapeHtml(options.message);
87
+ const nonceAttr = ` nonce="${escapeAttr(options.cspNonce)}"`;
88
+ return `<!doctype html>
89
+ <html lang="en">
90
+ <head>
91
+ <meta charset="utf-8" />
92
+ <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
93
+ <meta name="robots" content="noindex" />
94
+ <title>${title}</title>
95
+ <style${nonceAttr}>${PAGE_CSS}</style>
96
+ </head>
97
+ <body>
98
+ <main class="mj-co">
99
+ <p class="mj-co__error" role="alert">${message}</p>
100
+ </main>
101
+ </body>
102
+ </html>`;
103
+ }
104
+ const PAGE_CSS = `
105
+ :root { color-scheme: light dark; }
106
+ * { box-sizing: border-box; }
107
+ html, body { margin: 0; padding: 0; min-height: 100%; }
108
+ body {
109
+ background: var(--mj-bg, #f8fafc);
110
+ color: var(--mj-text, #0f172a);
111
+ font-family: var(--mj-font-body, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif);
112
+ -webkit-text-size-adjust: 100%;
113
+ min-height: 100vh;
114
+ }
115
+ .mj-co {
116
+ max-width: 36rem;
117
+ margin: 0 auto;
118
+ padding: clamp(1.5rem, 5vw, 3rem) clamp(1rem, 4vw, 2rem);
119
+ }
120
+ .mj-co__status, .mj-co__error, .mj-co__ok {
121
+ font-size: 1.0625rem;
122
+ line-height: 1.5;
123
+ }
124
+ .mj-co__error { color: var(--mj-error, #b3261e); }
125
+ .mj-co__ok { color: var(--mj-ok, #0f7b3b); }
126
+ .mj-co h1 { font-size: 1.5rem; margin: 0 0 0.5rem; }
127
+ .mj-co p { margin: 0 0 1rem; }
128
+ .mj-co label { display: block; font-weight: 600; margin: 0.75rem 0 0.25rem; }
129
+ .mj-co input, .mj-co select, .mj-co textarea {
130
+ width: 100%;
131
+ padding: 0.5rem 0.65rem;
132
+ border: 1px solid var(--mj-border, #c9cdd4);
133
+ border-radius: 6px;
134
+ font: inherit;
135
+ background: var(--mj-surface, #fff);
136
+ color: inherit;
137
+ }
138
+ .mj-co button {
139
+ margin-top: 1.25rem;
140
+ padding: 0.65rem 1.1rem;
141
+ border: 0;
142
+ border-radius: 6px;
143
+ background: var(--mj-primary, #2563eb);
144
+ color: #fff;
145
+ font: inherit;
146
+ font-weight: 600;
147
+ cursor: pointer;
148
+ }
149
+ .mj-co button[disabled] { opacity: 0.6; cursor: not-allowed; }
150
+ .mj-co__total { font-weight: 600; margin-top: 1rem; }
151
+ #mj-co-card { margin-top: 1rem; }
152
+ `;
153
+ /**
154
+ * Static boot script. Per-request values are read from `#mj-co` data-* attributes
155
+ * at runtime — never spliced into this string.
156
+ */
157
+ const BOOT_SCRIPT = `
158
+ (function () {
159
+ var host = document.getElementById('mj-co');
160
+ var slug = host.getAttribute('data-slug') || '';
161
+ var apiRoot = (host.getAttribute('data-api-root') || '/checkout').replace(/\\/+$/, '');
162
+
163
+ function showError(msg) {
164
+ host.innerHTML = '';
165
+ var p = document.createElement('p');
166
+ p.className = 'mj-co__error';
167
+ p.setAttribute('role', 'alert');
168
+ p.textContent = msg;
169
+ host.appendChild(p);
170
+ }
171
+ function setStatus(msg) {
172
+ var el = host.querySelector('.mj-co__status');
173
+ if (el) el.textContent = msg;
174
+ }
175
+ function clientKey() {
176
+ var storageKey = 'mj-checkout-key:' + slug;
177
+ try {
178
+ var existing = sessionStorage.getItem(storageKey);
179
+ if (existing) return existing;
180
+ } catch (e) {}
181
+ var key;
182
+ if (window.crypto && crypto.randomUUID) {
183
+ key = crypto.randomUUID();
184
+ } else if (window.crypto && crypto.getRandomValues) {
185
+ var b = new Uint8Array(16);
186
+ crypto.getRandomValues(b);
187
+ b[6] = (b[6] & 0x0f) | 0x40;
188
+ b[8] = (b[8] & 0x3f) | 0x80;
189
+ var hex = [];
190
+ for (var i = 0; i < 16; i++) hex.push(('0' + b[i].toString(16)).slice(-2));
191
+ key = hex.slice(0, 4).join('') + '-' + hex.slice(4, 6).join('') + '-' + hex.slice(6, 8).join('') + '-' + hex.slice(8, 10).join('') + '-' + hex.slice(10, 16).join('');
192
+ } else {
193
+ showError('Checkout requires a secure random source. Open this page over HTTPS.');
194
+ return '';
195
+ }
196
+ try { sessionStorage.setItem(storageKey, key); } catch (e) {}
197
+ return key;
198
+ }
199
+ function post(path, body) {
200
+ return fetch(apiRoot + path, {
201
+ method: 'POST',
202
+ headers: { 'Content-Type': 'application/json' },
203
+ credentials: 'same-origin',
204
+ body: JSON.stringify(body)
205
+ }).then(function (res) {
206
+ return res.json().then(function (json) {
207
+ json._httpStatus = res.status;
208
+ return json;
209
+ });
210
+ });
211
+ }
212
+ function fieldValue(el) {
213
+ if (el.type === 'checkbox') return el.checked;
214
+ if (el.type === 'number') return el.value === '' ? '' : Number(el.value);
215
+ return el.value;
216
+ }
217
+ function loadStripe(pk) {
218
+ return new Promise(function (resolve, reject) {
219
+ // Reuse the same Stripe instance that created the Card Element. A second
220
+ // Stripe(pk) object cannot confirm that element (Stripe.js throws).
221
+ if (stripe) { resolve(stripe); return; }
222
+ function inst() {
223
+ stripe = window.Stripe(pk);
224
+ resolve(stripe);
225
+ }
226
+ if (window.Stripe) { inst(); return; }
227
+ var s = document.createElement('script');
228
+ s.src = 'https://js.stripe.com/v3/';
229
+ s.onload = function () {
230
+ if (!window.Stripe) { reject(new Error('Stripe.js did not load')); return; }
231
+ inst();
232
+ };
233
+ s.onerror = function () { reject(new Error('Could not load Stripe.js')); };
234
+ document.head.appendChild(s);
235
+ });
236
+ }
237
+
238
+ if (!slug) {
239
+ showError('This checkout link is missing its reference. Please check the link and try again.');
240
+ return;
241
+ }
242
+
243
+ var sessionKey = clientKey();
244
+ if (!sessionKey) return;
245
+ var sessionId = '';
246
+ var config = {};
247
+ var stripe = null;
248
+ var cardElement = null;
249
+
250
+ post('/initialize', { slug: slug, clientSessionKey: sessionKey }).then(function (init) {
251
+ if (!init || !init.Success) {
252
+ showError((init && init.ErrorMessage) || 'This checkout is not available.');
253
+ return;
254
+ }
255
+ sessionId = init.SessionID;
256
+ config = init.Configuration || {};
257
+ var productId = config.productId;
258
+ if (!productId) {
259
+ showError('This checkout is not configured with a product.');
260
+ return;
261
+ }
262
+
263
+ host.innerHTML = '';
264
+ var h1 = document.createElement('h1');
265
+ h1.textContent = config.title || init.WidgetName || 'Checkout';
266
+ host.appendChild(h1);
267
+ if (config.description) {
268
+ var desc = document.createElement('p');
269
+ desc.textContent = config.description;
270
+ host.appendChild(desc);
271
+ }
272
+
273
+ var form = document.createElement('form');
274
+ form.setAttribute('novalidate', 'novalidate');
275
+
276
+ function addField(name, label, type, required, placeholder, options) {
277
+ var lab = document.createElement('label');
278
+ lab.setAttribute('for', 'mj-co-' + name);
279
+ lab.textContent = label + (required ? ' *' : '');
280
+ form.appendChild(lab);
281
+ var input;
282
+ if (type === 'select' && options && options.length) {
283
+ input = document.createElement('select');
284
+ var blank = document.createElement('option');
285
+ blank.value = '';
286
+ blank.textContent = '';
287
+ input.appendChild(blank);
288
+ options.forEach(function (opt) {
289
+ var o = document.createElement('option');
290
+ if (typeof opt === 'object' && opt) {
291
+ o.value = String(opt.value);
292
+ o.textContent = String(opt.label);
293
+ } else {
294
+ o.value = String(opt);
295
+ o.textContent = String(opt);
296
+ }
297
+ input.appendChild(o);
298
+ });
299
+ } else if (type === 'textarea') {
300
+ input = document.createElement('textarea');
301
+ input.rows = 3;
302
+ } else if (type === 'boolean') {
303
+ input = document.createElement('input');
304
+ input.type = 'checkbox';
305
+ } else {
306
+ input = document.createElement('input');
307
+ input.type = type === 'number' ? 'number' : (type === 'date' ? 'date' : 'text');
308
+ }
309
+ input.id = 'mj-co-' + name;
310
+ input.name = name;
311
+ if (placeholder && input.placeholder !== undefined) input.placeholder = placeholder;
312
+ if (required && input.type !== 'checkbox') input.required = true;
313
+ form.appendChild(input);
314
+ return input;
315
+ }
316
+
317
+ addField('email', 'Email', 'text', true, 'you@example.com');
318
+ var qtyInput = null;
319
+ if (config.allowQuantity !== false) {
320
+ qtyInput = addField('quantity', 'Quantity', 'number', true, '1');
321
+ qtyInput.min = '1';
322
+ qtyInput.value = '1';
323
+ if (config.maxQuantity) qtyInput.max = String(config.maxQuantity);
324
+ }
325
+
326
+ var extFields = Array.isArray(config.extensionFields) ? config.extensionFields : [];
327
+ extFields.forEach(function (f) {
328
+ if (!f || !f.name || f.name === 'email') return;
329
+ addField(f.name, f.label || f.name, f.type || 'text', !!f.required, f.placeholder || '', f.options);
330
+ });
331
+
332
+ var cardMount = document.createElement('div');
333
+ cardMount.id = 'mj-co-card';
334
+ form.appendChild(cardMount);
335
+
336
+ var totalEl = document.createElement('p');
337
+ totalEl.className = 'mj-co__total';
338
+ form.appendChild(totalEl);
339
+
340
+ var status = document.createElement('p');
341
+ status.className = 'mj-co__status';
342
+ status.setAttribute('role', 'status');
343
+ form.appendChild(status);
344
+
345
+ var submit = document.createElement('button');
346
+ submit.type = 'submit';
347
+ submit.textContent = 'Continue';
348
+ form.appendChild(submit);
349
+ host.appendChild(form);
350
+
351
+ form.addEventListener('submit', function (ev) {
352
+ ev.preventDefault();
353
+ submit.disabled = true;
354
+ status.textContent = 'Pricing…';
355
+ var emailEl = form.elements.namedItem('email');
356
+ var email = emailEl && 'value' in emailEl ? String(emailEl.value || '').trim() : '';
357
+ var qty = qtyInput ? Math.max(1, parseInt(qtyInput.value, 10) || 1) : 1;
358
+ var extensionFields = {};
359
+ extFields.forEach(function (f) {
360
+ if (!f || !f.name) return;
361
+ var el = form.elements.namedItem(f.name);
362
+ if (el && 'type' in el) extensionFields[f.name] = fieldValue(el);
363
+ });
364
+ var line = { ProductID: productId, Quantity: qty };
365
+ if (Object.keys(extensionFields).length) {
366
+ line.ExtensionData = { EntityName: config.extensionEntityName, Fields: extensionFields };
367
+ }
368
+ post('/draft', {
369
+ sessionId: sessionId,
370
+ clientSessionKey: sessionKey,
371
+ email: email,
372
+ lines: [line]
373
+ }).then(function (draft) {
374
+ if (!draft || !draft.Success) {
375
+ throw new Error((draft && draft.ErrorMessage) || 'Could not price this checkout.');
376
+ }
377
+ var total = typeof draft.TotalGross === 'number' ? draft.TotalGross : 0;
378
+ totalEl.textContent = 'Total: ' + total.toFixed(2);
379
+ if (!draft.RequiresPayment) {
380
+ status.textContent = 'Completing…';
381
+ return post('/complete', { sessionId: sessionId, clientSessionKey: sessionKey });
382
+ }
383
+ status.textContent = 'Opening payment…';
384
+ return post('/payment-intent', { sessionId: sessionId, clientSessionKey: sessionKey }).then(function (intent) {
385
+ if (!intent || !intent.Success || !intent.ClientSecret) {
386
+ throw new Error((intent && intent.ErrorMessage) || 'Could not start payment.');
387
+ }
388
+ var pk = config.stripePublishableKey;
389
+ if (!pk) {
390
+ throw new Error('This checkout requires card payment. Configure stripePublishableKey on the widget, or embed the checkout widget on a site that already loads Stripe.');
391
+ }
392
+ return loadStripe(pk).then(function (stripeInst) {
393
+ stripe = stripeInst;
394
+ if (!cardElement) {
395
+ var elements = stripe.elements();
396
+ cardElement = elements.create('card');
397
+ cardElement.mount('#mj-co-card');
398
+ status.textContent = 'Enter card details and submit again to pay.';
399
+ submit.textContent = 'Pay';
400
+ submit.disabled = false;
401
+ form.dataset.payReady = '1';
402
+ return { _waitForCard: true };
403
+ }
404
+ status.textContent = 'Confirming payment…';
405
+ return stripe.confirmCardPayment(intent.ClientSecret, {
406
+ payment_method: { card: cardElement, billing_details: { email: email } }
407
+ }).then(function (result) {
408
+ if (result.error) throw new Error(result.error.message || 'Payment failed.');
409
+ status.textContent = 'Completing…';
410
+ return post('/complete', { sessionId: sessionId, clientSessionKey: sessionKey });
411
+ });
412
+ });
413
+ });
414
+ }).then(function (done) {
415
+ if (!done || done._waitForCard) return;
416
+ if (!done.Success) {
417
+ throw new Error(done.ErrorMessage || 'Could not complete checkout.');
418
+ }
419
+ host.innerHTML = '';
420
+ var ok = document.createElement('p');
421
+ ok.className = 'mj-co__ok';
422
+ ok.setAttribute('role', 'status');
423
+ ok.textContent = config.successMessage || ('Thank you. Order ' + (done.OrderNumber || '') + ' is confirmed.');
424
+ host.appendChild(ok);
425
+ if (config.redirectUrl) {
426
+ window.location.href = config.redirectUrl;
427
+ }
428
+ }).catch(function (err) {
429
+ status.textContent = '';
430
+ var existing = form.querySelector('.mj-co__error');
431
+ if (existing) existing.remove();
432
+ var p = document.createElement('p');
433
+ p.className = 'mj-co__error';
434
+ p.setAttribute('role', 'alert');
435
+ p.textContent = err && err.message ? err.message : 'Checkout failed.';
436
+ form.insertBefore(p, submit);
437
+ submit.disabled = false;
438
+ });
439
+ });
440
+ }).catch(function () {
441
+ showError('Checkout is temporarily unavailable. Please try again.');
442
+ });
443
+ })();
444
+ `;
445
+ //# sourceMappingURL=checkout-host-page.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"checkout-host-page.js","sourceRoot":"","sources":["../src/checkout-host-page.ts"],"names":[],"mappings":"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"}