@sunbay/checkout-sdk-js 0.0.1 → 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +72 -0
- package/dist/index.cjs +181 -147
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +250 -218
- package/dist/index.d.ts +250 -218
- package/dist/index.global.js +181 -147
- package/dist/index.global.js.map +1 -1
- package/dist/index.mjs +181 -147
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -2
package/dist/index.mjs
CHANGED
|
@@ -37,8 +37,9 @@ var MainFrameTransport = class {
|
|
|
37
37
|
this.eventHandlers = /* @__PURE__ */ new Map();
|
|
38
38
|
this.helloHandler = null;
|
|
39
39
|
/**
|
|
40
|
-
*
|
|
41
|
-
* main
|
|
40
|
+
* Runtime id negotiated during the handshake; empty string before injection. Outbound request
|
|
41
|
+
* envelopes are filled from it; the main endpoint only processes requests whose runtimeId
|
|
42
|
+
* matches its own, and an empty string is silently dropped.
|
|
42
43
|
*/
|
|
43
44
|
this.runtimeId = "";
|
|
44
45
|
this.destroyed = false;
|
|
@@ -51,9 +52,9 @@ var MainFrameTransport = class {
|
|
|
51
52
|
window.addEventListener("message", this.messageListener);
|
|
52
53
|
}
|
|
53
54
|
/**
|
|
54
|
-
*
|
|
55
|
+
* Create the hidden, 0-size main iframe and append it to document.body.
|
|
55
56
|
*
|
|
56
|
-
* @returns
|
|
57
|
+
* @returns The mounted iframe element.
|
|
57
58
|
* @example
|
|
58
59
|
* const iframe = transport.createHiddenIframe();
|
|
59
60
|
*/
|
|
@@ -75,11 +76,11 @@ var MainFrameTransport = class {
|
|
|
75
76
|
return iframe;
|
|
76
77
|
}
|
|
77
78
|
/**
|
|
78
|
-
*
|
|
79
|
+
* Send one request envelope and pair the response by requestId.
|
|
79
80
|
*
|
|
80
|
-
* @param name -
|
|
81
|
-
* @param payload -
|
|
82
|
-
* @returns
|
|
81
|
+
* @param name - Command name.
|
|
82
|
+
* @param payload - Command payload (JSON-serializable).
|
|
83
|
+
* @returns The response data; on failure (ok:false / timeout / destroy) rejects with a CheckoutError.
|
|
83
84
|
* @example
|
|
84
85
|
* const session = await transport.request('getSession', {});
|
|
85
86
|
*/
|
|
@@ -120,10 +121,10 @@ var MainFrameTransport = class {
|
|
|
120
121
|
});
|
|
121
122
|
}
|
|
122
123
|
/**
|
|
123
|
-
*
|
|
124
|
+
* Register an event handler (dispatches the payload of kind:'event' envelopes).
|
|
124
125
|
*
|
|
125
|
-
* @param event -
|
|
126
|
-
* @param handler -
|
|
126
|
+
* @param event - Event name.
|
|
127
|
+
* @param handler - Callback.
|
|
127
128
|
*/
|
|
128
129
|
on(event, handler) {
|
|
129
130
|
let set = this.eventHandlers.get(event);
|
|
@@ -134,10 +135,10 @@ var MainFrameTransport = class {
|
|
|
134
135
|
set.add(handler);
|
|
135
136
|
}
|
|
136
137
|
/**
|
|
137
|
-
*
|
|
138
|
+
* Unregister an event handler.
|
|
138
139
|
*
|
|
139
|
-
* @param event -
|
|
140
|
-
* @param handler -
|
|
140
|
+
* @param event - Event name.
|
|
141
|
+
* @param handler - The previously registered callback.
|
|
141
142
|
*/
|
|
142
143
|
off(event, handler) {
|
|
143
144
|
const set = this.eventHandlers.get(event);
|
|
@@ -149,24 +150,26 @@ var MainFrameTransport = class {
|
|
|
149
150
|
}
|
|
150
151
|
}
|
|
151
152
|
/**
|
|
152
|
-
*
|
|
153
|
+
* Register the handshake (RuntimeHello) callback. The hello phase has no checkoutId yet and is
|
|
154
|
+
* handled separately.
|
|
153
155
|
*
|
|
154
|
-
* @param handler -
|
|
156
|
+
* @param handler - Called when a hello is received.
|
|
155
157
|
*/
|
|
156
158
|
onHello(handler) {
|
|
157
159
|
this.helloHandler = handler;
|
|
158
160
|
}
|
|
159
161
|
/**
|
|
160
|
-
*
|
|
161
|
-
*
|
|
162
|
+
* Inject the runtime id negotiated during the handshake. Must be called before sending the first
|
|
163
|
+
* request (initialize), otherwise the outbound envelope's runtimeId is an empty string, gets
|
|
164
|
+
* silently dropped by the main endpoint, and the request times out.
|
|
162
165
|
*
|
|
163
|
-
* @param runtimeId -
|
|
166
|
+
* @param runtimeId - The runtimeId from RuntimeHello.
|
|
164
167
|
*/
|
|
165
168
|
setRuntimeId(runtimeId) {
|
|
166
169
|
this.runtimeId = runtimeId;
|
|
167
170
|
}
|
|
168
171
|
/**
|
|
169
|
-
*
|
|
172
|
+
* Destroy: remove the iframe, clear listeners, and reject pending requests with INSTANCE_DESTROYED.
|
|
170
173
|
*/
|
|
171
174
|
destroy() {
|
|
172
175
|
if (this.destroyed) {
|
|
@@ -186,7 +189,7 @@ var MainFrameTransport = class {
|
|
|
186
189
|
}
|
|
187
190
|
this.iframe = null;
|
|
188
191
|
}
|
|
189
|
-
/**
|
|
192
|
+
/** Resolve the timeout by command name. */
|
|
190
193
|
resolveTimeout(name) {
|
|
191
194
|
if (name === "initialize") {
|
|
192
195
|
return this.timeouts.initialize;
|
|
@@ -194,20 +197,22 @@ var MainFrameTransport = class {
|
|
|
194
197
|
return this.timeouts.default;
|
|
195
198
|
}
|
|
196
199
|
/**
|
|
197
|
-
*
|
|
200
|
+
* Point-to-point port relay (parent-window relay, Stripe controller model).
|
|
198
201
|
*
|
|
199
|
-
*
|
|
200
|
-
* transfer MessagePort
|
|
201
|
-
*
|
|
202
|
-
*
|
|
203
|
-
*
|
|
204
|
-
*
|
|
202
|
+
* Sibling iframes (main and the various Element iframes) cannot obtain each other's window
|
|
203
|
+
* references, so they cannot transfer a MessagePort directly. This layer (the merchant page,
|
|
204
|
+
* the only party that can reach both ends and knows their origin) does the following when it
|
|
205
|
+
* receives an `element-online` from some Element iframe:
|
|
206
|
+
* 1. Create a `MessageChannel`;
|
|
207
|
+
* 2. `port1` → main iframe (`relay-port`, targetOrigin=hostOrigin);
|
|
208
|
+
* 3. `port2` → the originating Element iframe (i.e. `event.source`, targetOrigin=hostOrigin).
|
|
205
209
|
*
|
|
206
|
-
*
|
|
207
|
-
*
|
|
210
|
+
* Both ends' origin is hostOrigin, so targetOrigin is always correct (and not dropped across
|
|
211
|
+
* origins). This layer only carries **empty ports** and never touches card values (which are
|
|
212
|
+
* transmitted end-to-end after the port is established).
|
|
208
213
|
*
|
|
209
|
-
* @param event
|
|
210
|
-
* @returns
|
|
214
|
+
* @param event Inbound message (origin already verified as hostOrigin)
|
|
215
|
+
* @returns Whether it was handled as element-online (if so, the caller should return)
|
|
211
216
|
*/
|
|
212
217
|
tryHandleElementOnline(event) {
|
|
213
218
|
const data = event.data;
|
|
@@ -236,7 +241,7 @@ var MainFrameTransport = class {
|
|
|
236
241
|
elementWin.postMessage(relayMsg, this.hostOrigin, [channel.port2]);
|
|
237
242
|
return true;
|
|
238
243
|
}
|
|
239
|
-
/**
|
|
244
|
+
/** Handle an inbound message: filter origin, recognize relay / hello / Envelope and dispatch. */
|
|
240
245
|
handleMessage(event) {
|
|
241
246
|
if (this.destroyed) {
|
|
242
247
|
return;
|
|
@@ -277,7 +282,7 @@ var MainFrameTransport = class {
|
|
|
277
282
|
this.handleEvent(envelope);
|
|
278
283
|
}
|
|
279
284
|
}
|
|
280
|
-
/**
|
|
285
|
+
/** Dispatch a response to the paired pending request. */
|
|
281
286
|
handleResponse(envelope) {
|
|
282
287
|
const entry = this.pending.get(envelope.requestId);
|
|
283
288
|
if (!entry) {
|
|
@@ -291,7 +296,7 @@ var MainFrameTransport = class {
|
|
|
291
296
|
entry.reject(envelope.error);
|
|
292
297
|
}
|
|
293
298
|
}
|
|
294
|
-
/**
|
|
299
|
+
/** Dispatch an event to the registered handlers. */
|
|
295
300
|
handleEvent(envelope) {
|
|
296
301
|
const set = this.eventHandlers.get(envelope.name);
|
|
297
302
|
if (!set) {
|
|
@@ -337,17 +342,18 @@ var CheckoutElement = class _CheckoutElement {
|
|
|
337
342
|
this.status = "created";
|
|
338
343
|
this.container = null;
|
|
339
344
|
this.iframe = null;
|
|
340
|
-
/** mount
|
|
345
|
+
/** Event handlers temporarily subscribed during mount (unbound after resolve/reject). */
|
|
341
346
|
this.readyHandler = null;
|
|
342
347
|
this.loadErrorHandler = null;
|
|
343
|
-
/**
|
|
348
|
+
/** expressCheckout only: the persistently subscribed expressAvailabilityChange handler (unbound on destroy). */
|
|
344
349
|
this.availabilityHandler = null;
|
|
345
350
|
/**
|
|
346
|
-
* iframe
|
|
347
|
-
*
|
|
351
|
+
* iframe auto-height: the window 'message' listener that stays resident during mount (unbound
|
|
352
|
+
* when the iframe disappears/is destroyed). Keeps a reference so removeEventListener can be
|
|
353
|
+
* called; null means not listening.
|
|
348
354
|
*/
|
|
349
355
|
this.resizeHandler = null;
|
|
350
|
-
/**
|
|
356
|
+
/** The most recently applied iframe height (px), used for deduplication to avoid redundant style writes; null means not yet set. */
|
|
351
357
|
this.lastHeight = null;
|
|
352
358
|
this.elementId = opts.elementId;
|
|
353
359
|
this.type = opts.type;
|
|
@@ -362,26 +368,28 @@ var CheckoutElement = class _CheckoutElement {
|
|
|
362
368
|
this.installInputActions();
|
|
363
369
|
this.installExpressAvailability();
|
|
364
370
|
}
|
|
365
|
-
/**
|
|
371
|
+
/** The current status (read-only snapshot, for external/test observation). */
|
|
366
372
|
getStatus() {
|
|
367
373
|
return this.status;
|
|
368
374
|
}
|
|
369
375
|
/**
|
|
370
|
-
*
|
|
376
|
+
* expressCheckout only: return the current snapshot of each wallet's availability (always
|
|
377
|
+
* undefined for other types).
|
|
371
378
|
*
|
|
372
|
-
*
|
|
379
|
+
* The initial value comes from elementReady's availableWallets, then is updated by
|
|
380
|
+
* expressAvailabilityChange.
|
|
373
381
|
*
|
|
374
|
-
* @returns
|
|
382
|
+
* @returns The availability snapshot; undefined when not yet ready or non-express.
|
|
375
383
|
*/
|
|
376
384
|
getAvailableWallets() {
|
|
377
385
|
return this.availableWallets;
|
|
378
386
|
}
|
|
379
387
|
/**
|
|
380
|
-
*
|
|
388
|
+
* Mount at the merchant DOM target: insert a visible iframe, send createElement, await elementReady.
|
|
381
389
|
*
|
|
382
|
-
* @param selectorOrEl - CSS
|
|
383
|
-
* @returns elementReady
|
|
384
|
-
* @throws
|
|
390
|
+
* @param selectorOrEl - A CSS selector or an HTMLElement container; the container must be unique and empty.
|
|
391
|
+
* @returns Resolves after elementReady; on failure removes the node and rejects (loadError / INVALID_OPTIONS).
|
|
392
|
+
* @throws INSTANCE_DESTROYED when already destroyed.
|
|
385
393
|
* @example
|
|
386
394
|
* await element.mount('#card-field');
|
|
387
395
|
*/
|
|
@@ -420,6 +428,7 @@ var CheckoutElement = class _CheckoutElement {
|
|
|
420
428
|
iframe.style.border = "0";
|
|
421
429
|
iframe.style.width = "100%";
|
|
422
430
|
iframe.style.display = "block";
|
|
431
|
+
iframe.setAttribute("scrolling", "no");
|
|
423
432
|
container.appendChild(iframe);
|
|
424
433
|
this.iframe = iframe;
|
|
425
434
|
this.installResizeListener();
|
|
@@ -479,10 +488,10 @@ var CheckoutElement = class _CheckoutElement {
|
|
|
479
488
|
});
|
|
480
489
|
}
|
|
481
490
|
/**
|
|
482
|
-
*
|
|
491
|
+
* Unmount: send destroyElement, remove the iframe; idempotent; keeps the handle and configuration.
|
|
483
492
|
*
|
|
484
|
-
* @returns
|
|
485
|
-
* @throws
|
|
493
|
+
* @returns Resolves on completion (resolves directly when not mounted).
|
|
494
|
+
* @throws INSTANCE_DESTROYED when already destroyed.
|
|
486
495
|
*/
|
|
487
496
|
async unmount() {
|
|
488
497
|
this.assertAlive();
|
|
@@ -498,7 +507,7 @@ var CheckoutElement = class _CheckoutElement {
|
|
|
498
507
|
}
|
|
499
508
|
}
|
|
500
509
|
/**
|
|
501
|
-
*
|
|
510
|
+
* Destroy the handle: idempotent; remove the iframe; afterwards any non-destroy/off call throws INSTANCE_DESTROYED.
|
|
502
511
|
*/
|
|
503
512
|
destroy() {
|
|
504
513
|
if (this.status === "destroyed") {
|
|
@@ -522,11 +531,11 @@ var CheckoutElement = class _CheckoutElement {
|
|
|
522
531
|
this.onDestroy?.(this.elementId);
|
|
523
532
|
}
|
|
524
533
|
/**
|
|
525
|
-
*
|
|
534
|
+
* Update the Element configuration: send updateElement; illegal fields are rejected wholesale by the hosted side.
|
|
526
535
|
*
|
|
527
|
-
* @param patch -
|
|
528
|
-
* @returns
|
|
529
|
-
* @throws
|
|
536
|
+
* @param patch - Configuration delta.
|
|
537
|
+
* @returns Resolves after successfully writing back the local options.
|
|
538
|
+
* @throws INSTANCE_DESTROYED when already destroyed.
|
|
530
539
|
*/
|
|
531
540
|
async update(patch) {
|
|
532
541
|
this.assertAlive();
|
|
@@ -540,26 +549,26 @@ var CheckoutElement = class _CheckoutElement {
|
|
|
540
549
|
}
|
|
541
550
|
}
|
|
542
551
|
/**
|
|
543
|
-
*
|
|
552
|
+
* Register an event handler (passed through to the transport, dispatched by event name).
|
|
544
553
|
*
|
|
545
|
-
* @param event -
|
|
546
|
-
* @param handler -
|
|
547
|
-
* @throws
|
|
554
|
+
* @param event - Event name.
|
|
555
|
+
* @param handler - Callback.
|
|
556
|
+
* @throws INSTANCE_DESTROYED when already destroyed.
|
|
548
557
|
*/
|
|
549
558
|
on(event, handler) {
|
|
550
559
|
this.assertAlive();
|
|
551
560
|
this.transport.on(event, handler);
|
|
552
561
|
}
|
|
553
562
|
/**
|
|
554
|
-
*
|
|
563
|
+
* Unregister an event handler (still safe to call after destruction).
|
|
555
564
|
*
|
|
556
|
-
* @param event -
|
|
557
|
-
* @param handler -
|
|
565
|
+
* @param event - Event name.
|
|
566
|
+
* @param handler - The previously registered callback.
|
|
558
567
|
*/
|
|
559
568
|
off(event, handler) {
|
|
560
569
|
this.transport.off(event, handler);
|
|
561
570
|
}
|
|
562
|
-
/**
|
|
571
|
+
/** Install input-class action methods (automatically called after construction). */
|
|
563
572
|
installInputActions() {
|
|
564
573
|
if (!INPUT_TYPES.includes(this.type)) {
|
|
565
574
|
return;
|
|
@@ -569,10 +578,10 @@ var CheckoutElement = class _CheckoutElement {
|
|
|
569
578
|
this.clear = () => this.sendAction("clear");
|
|
570
579
|
}
|
|
571
580
|
/**
|
|
572
|
-
*
|
|
581
|
+
* expressCheckout only: persistently subscribe to expressAvailabilityChange and update the local availability snapshot.
|
|
573
582
|
*
|
|
574
|
-
*
|
|
575
|
-
*
|
|
583
|
+
* This event payload has no elementId; express is a singleton (guaranteed by CheckoutInstance),
|
|
584
|
+
* so there is no need to filter by elementId. Non-express types do not subscribe.
|
|
576
585
|
*/
|
|
577
586
|
installExpressAvailability() {
|
|
578
587
|
if (this.type !== "expressCheckout") {
|
|
@@ -590,18 +599,18 @@ var CheckoutElement = class _CheckoutElement {
|
|
|
590
599
|
this.availabilityHandler = handler;
|
|
591
600
|
this.transport.on("expressAvailabilityChange", handler);
|
|
592
601
|
}
|
|
593
|
-
/**
|
|
602
|
+
/** Send one input-class action command (synchronously throws INSTANCE_DESTROYED after destruction). */
|
|
594
603
|
sendAction(name) {
|
|
595
604
|
this.assertAlive();
|
|
596
605
|
return this.transport.request(name, { elementId: this.elementId }).then(() => void 0);
|
|
597
606
|
}
|
|
598
|
-
/**
|
|
607
|
+
/** Throw INSTANCE_DESTROYED if already destroyed. */
|
|
599
608
|
assertAlive() {
|
|
600
609
|
if (this.status === "destroyed") {
|
|
601
610
|
throw makeError2(ErrorCode2.INSTANCE_DESTROYED, "Checkout instance has been destroyed", this.elementId);
|
|
602
611
|
}
|
|
603
612
|
}
|
|
604
|
-
/**
|
|
613
|
+
/** Resolve the mount container (the selector must uniquely match a single element). */
|
|
605
614
|
resolveContainer(selectorOrEl) {
|
|
606
615
|
if (typeof selectorOrEl !== "string") {
|
|
607
616
|
return selectorOrEl;
|
|
@@ -614,26 +623,34 @@ var CheckoutElement = class _CheckoutElement {
|
|
|
614
623
|
return node instanceof HTMLElement ? node : null;
|
|
615
624
|
}
|
|
616
625
|
/**
|
|
617
|
-
*
|
|
618
|
-
*
|
|
619
|
-
*
|
|
620
|
-
* `&appearance=<encodeURIComponent(JSON.stringify(appearance))
|
|
621
|
-
*
|
|
622
|
-
*
|
|
623
|
-
*
|
|
626
|
+
* Resolve the iframe src: split fields go to /sdk/field/, the rest to /sdk/element/, with
|
|
627
|
+
* checkoutId and elementId; if an initial colorScheme is provided, append `&colorScheme=<value>`
|
|
628
|
+
* (no flash on the first frame); if the initial appearance contains variables or rules, append
|
|
629
|
+
* `&appearance=<encodeURIComponent(JSON.stringify(appearance))>` (takes effect on the first
|
|
630
|
+
* frame, no flash). An appearance containing only colorScheme (no variables/rules) is not
|
|
631
|
+
* appended, to avoid an empty appearance string. For expressCheckout, additionally append the
|
|
632
|
+
* wallet toggles / per-wallet buttonType / global buttonHeight (see {@link resolveExpressQuery}).
|
|
624
633
|
*
|
|
625
|
-
* elementId
|
|
626
|
-
*
|
|
634
|
+
* elementId is sent with the src so the hosted child echoes it verbatim in iframe-resize
|
|
635
|
+
* messages, letting the parent window dispatch the height per handle among multiple iframes on
|
|
636
|
+
* the same page.
|
|
627
637
|
*/
|
|
628
638
|
resolveSrc() {
|
|
629
639
|
const segment = FIELD_TYPES.includes(this.type) ? "field" : "element";
|
|
630
640
|
let query = `?checkoutId=${encodeURIComponent(this.checkoutId)}`;
|
|
631
641
|
query += `&elementId=${encodeURIComponent(this.elementId)}`;
|
|
642
|
+
if (this.type === "address") {
|
|
643
|
+
const mode = this.options?.mode;
|
|
644
|
+
if (mode) {
|
|
645
|
+
query += `&mode=${encodeURIComponent(mode)}`;
|
|
646
|
+
}
|
|
647
|
+
}
|
|
632
648
|
if (this.colorScheme) {
|
|
633
649
|
query += `&colorScheme=${encodeURIComponent(this.colorScheme)}`;
|
|
634
650
|
}
|
|
635
|
-
|
|
636
|
-
|
|
651
|
+
const effectiveAppearance = this.perElementAppearance() ?? this.appearance;
|
|
652
|
+
if (effectiveAppearance && (effectiveAppearance.variables || effectiveAppearance.rules)) {
|
|
653
|
+
query += `&appearance=${encodeURIComponent(JSON.stringify(effectiveAppearance))}`;
|
|
637
654
|
}
|
|
638
655
|
if (this.type === "expressCheckout") {
|
|
639
656
|
query += this.resolveExpressQuery();
|
|
@@ -641,16 +658,26 @@ var CheckoutElement = class _CheckoutElement {
|
|
|
641
658
|
return `${this.hostOrigin}/sdk/${segment}/${this.type}${query}`;
|
|
642
659
|
}
|
|
643
660
|
/**
|
|
644
|
-
*
|
|
645
|
-
*
|
|
646
|
-
*
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
661
|
+
* Extract a per-element appearance (from the Element's own options, i.e. the second
|
|
662
|
+
* argument of create(type, options)). Returns undefined when absent. Used so per-element
|
|
663
|
+
* customization (e.g. CardFieldElementOptions.appearance) is honored on the first frame.
|
|
664
|
+
*/
|
|
665
|
+
perElementAppearance() {
|
|
666
|
+
const opts = this.options;
|
|
667
|
+
return opts?.appearance;
|
|
668
|
+
}
|
|
669
|
+
/**
|
|
670
|
+
* Build the expressCheckout-specific query fragment (only called for expressCheckout):
|
|
671
|
+
* - a wallet explicitly set to `false` → `&applePay=false` / `&googlePay=false` (force hidden,
|
|
672
|
+
* preserving the existing toggle semantics; `true` / absent → automatic, no toggle appended);
|
|
673
|
+
* - a wallet configured in object form containing `buttonType` → `&applePayButtonType=<v>` /
|
|
674
|
+
* `&googlePayButtonType=<v>` (boolean toggle / absent → not appended);
|
|
675
|
+
* - global `buttonHeight` (a finite number) → `&buttonHeight=<n>`.
|
|
650
676
|
*
|
|
651
|
-
*
|
|
677
|
+
* Appended only when there is a value; empty strings are not appended. This fragment never
|
|
678
|
+
* touches card values and only carries display configuration.
|
|
652
679
|
*
|
|
653
|
-
* @returns
|
|
680
|
+
* @returns A query fragment starting with `&` (returns an empty string when nothing is appendable).
|
|
654
681
|
*/
|
|
655
682
|
resolveExpressQuery() {
|
|
656
683
|
const options = this.options;
|
|
@@ -678,13 +705,14 @@ var CheckoutElement = class _CheckoutElement {
|
|
|
678
705
|
return query;
|
|
679
706
|
}
|
|
680
707
|
/**
|
|
681
|
-
*
|
|
682
|
-
* `contentWindow`
|
|
683
|
-
*
|
|
708
|
+
* Install the iframe auto-height listener: subscribe to window 'message', only trust a valid
|
|
709
|
+
* {@link IframeResizeMessage} from this iframe's `contentWindow` with origin === hostOrigin, and
|
|
710
|
+
* when the elementId matches this handle, adjust the outer iframe's height to the content's real
|
|
711
|
+
* height (width stays 100%).
|
|
684
712
|
*
|
|
685
|
-
*
|
|
686
|
-
*
|
|
687
|
-
*
|
|
713
|
+
* Security: first verify `event.source === iframe.contentWindow` (the key source check), then
|
|
714
|
+
* verify `event.origin === hostOrigin` (a stricter same-origin check), to prevent cross-origin forgery.
|
|
715
|
+
* Height deduplication: skip if identical to the last one, to avoid redundant style writes.
|
|
688
716
|
*/
|
|
689
717
|
installResizeListener() {
|
|
690
718
|
const handler = (event) => {
|
|
@@ -713,7 +741,7 @@ var CheckoutElement = class _CheckoutElement {
|
|
|
713
741
|
this.resizeHandler = handler;
|
|
714
742
|
window.addEventListener("message", handler);
|
|
715
743
|
}
|
|
716
|
-
/**
|
|
744
|
+
/** Unbind the iframe auto-height listener and reset the deduplication state (idempotent). */
|
|
717
745
|
teardownResizeListener() {
|
|
718
746
|
if (this.resizeHandler) {
|
|
719
747
|
window.removeEventListener("message", this.resizeHandler);
|
|
@@ -721,7 +749,7 @@ var CheckoutElement = class _CheckoutElement {
|
|
|
721
749
|
}
|
|
722
750
|
this.lastHeight = null;
|
|
723
751
|
}
|
|
724
|
-
/**
|
|
752
|
+
/** Remove the iframe DOM node (if present) and unbind the auto-height listener. */
|
|
725
753
|
removeIframe() {
|
|
726
754
|
this.teardownResizeListener();
|
|
727
755
|
if (this.iframe && this.iframe.parentNode) {
|
|
@@ -729,7 +757,7 @@ var CheckoutElement = class _CheckoutElement {
|
|
|
729
757
|
}
|
|
730
758
|
this.iframe = null;
|
|
731
759
|
}
|
|
732
|
-
/**
|
|
760
|
+
/** Factory: equivalent to `new CheckoutElement(opts)` (a unified entry for CheckoutInstance). */
|
|
733
761
|
static create(opts) {
|
|
734
762
|
return new _CheckoutElement(opts);
|
|
735
763
|
}
|
|
@@ -754,14 +782,16 @@ function makeError3(code, message, category = "configuration") {
|
|
|
754
782
|
}
|
|
755
783
|
var CheckoutInstance = class {
|
|
756
784
|
constructor(opts) {
|
|
757
|
-
/**
|
|
785
|
+
/** Created Elements (indexed by elementId). */
|
|
758
786
|
/**
|
|
759
|
-
*
|
|
760
|
-
*
|
|
761
|
-
* {@link
|
|
787
|
+
* The **single source of truth** for the mutual-exclusion registry: elementId → the created
|
|
788
|
+
* CheckoutElement. Mutual-exclusion decisions are derived live from this Map (see
|
|
789
|
+
* {@link assertNoConflict}); an element's `destroy()` removes itself from this Map via
|
|
790
|
+
* {@link releaseElement} to free the slot (allowing a mutually-exclusive Element of the same
|
|
791
|
+
* kind to be recreated after destruction).
|
|
762
792
|
*/
|
|
763
793
|
this.elements = /* @__PURE__ */ new Map();
|
|
764
|
-
/**
|
|
794
|
+
/** The current pending confirm (at most one at a time). */
|
|
765
795
|
this.pendingConfirm = null;
|
|
766
796
|
this.destroyed = false;
|
|
767
797
|
this.checkoutId = opts.checkoutId;
|
|
@@ -777,17 +807,17 @@ var CheckoutInstance = class {
|
|
|
777
807
|
this.transport.on("paymentResult", this.paymentResultHandler);
|
|
778
808
|
}
|
|
779
809
|
/**
|
|
780
|
-
*
|
|
810
|
+
* Create an Element handle (not auto-mounted; a mutual-exclusion conflict throws ELEMENT_CONFLICT).
|
|
781
811
|
*
|
|
782
|
-
*
|
|
783
|
-
* -
|
|
784
|
-
* -
|
|
785
|
-
* - `expressCheckout`
|
|
812
|
+
* Mutual-exclusion rules (spec 6):
|
|
813
|
+
* - The combined `card` and any split field (cardNumber/cardExpiry/cardCvc) are mutually exclusive.
|
|
814
|
+
* - An address with the same `mode` (billing/shipping) cannot be duplicated.
|
|
815
|
+
* - `expressCheckout` is a singleton.
|
|
786
816
|
*
|
|
787
|
-
* @param type - Element
|
|
788
|
-
* @param options -
|
|
789
|
-
* @returns
|
|
790
|
-
* @throws
|
|
817
|
+
* @param type - Element type.
|
|
818
|
+
* @param options - Initial Element configuration.
|
|
819
|
+
* @returns The newly created CheckoutElement.
|
|
820
|
+
* @throws INSTANCE_DESTROYED when already destroyed; ELEMENT_CONFLICT on a mutual-exclusion conflict.
|
|
791
821
|
*/
|
|
792
822
|
create(type, options) {
|
|
793
823
|
this.assertAlive();
|
|
@@ -809,12 +839,13 @@ var CheckoutInstance = class {
|
|
|
809
839
|
return element;
|
|
810
840
|
}
|
|
811
841
|
/**
|
|
812
|
-
*
|
|
813
|
-
*
|
|
842
|
+
* Initiate a payment confirmation. Generates an operationId, sends the confirm command to obtain
|
|
843
|
+
* accepted, and the returned Promise is fulfilled by a later paymentResult event with the same
|
|
844
|
+
* operationId. Calling again while in progress → CHECKOUT_BUSY.
|
|
814
845
|
*
|
|
815
|
-
* @param opts -
|
|
816
|
-
* @returns
|
|
817
|
-
* @throws
|
|
846
|
+
* @param opts - Optional parameters such as billing/shipping address, name, email, returnUrl.
|
|
847
|
+
* @returns The terminal PaymentOutcome.
|
|
848
|
+
* @throws rejects INSTANCE_DESTROYED when destroyed; rejects CHECKOUT_BUSY when in progress.
|
|
818
849
|
*/
|
|
819
850
|
confirm(opts) {
|
|
820
851
|
if (this.destroyed) {
|
|
@@ -840,10 +871,10 @@ var CheckoutInstance = class {
|
|
|
840
871
|
});
|
|
841
872
|
}
|
|
842
873
|
/**
|
|
843
|
-
*
|
|
874
|
+
* Get the latest session view (passes through the getSession command and writes back the local snapshot).
|
|
844
875
|
*
|
|
845
|
-
* @returns
|
|
846
|
-
* @throws
|
|
876
|
+
* @returns The latest PublicSessionView.
|
|
877
|
+
* @throws rejects INSTANCE_DESTROYED when destroyed.
|
|
847
878
|
*/
|
|
848
879
|
async getSession() {
|
|
849
880
|
this.assertAlive();
|
|
@@ -852,10 +883,10 @@ var CheckoutInstance = class {
|
|
|
852
883
|
return resp;
|
|
853
884
|
}
|
|
854
885
|
/**
|
|
855
|
-
*
|
|
886
|
+
* Refresh the session (passes through the refresh command and writes back the local snapshot).
|
|
856
887
|
*
|
|
857
|
-
* @returns
|
|
858
|
-
* @throws
|
|
888
|
+
* @returns The refreshed PublicSessionView.
|
|
889
|
+
* @throws rejects INSTANCE_DESTROYED when destroyed.
|
|
859
890
|
*/
|
|
860
891
|
async refresh() {
|
|
861
892
|
this.assertAlive();
|
|
@@ -864,20 +895,20 @@ var CheckoutInstance = class {
|
|
|
864
895
|
return resp;
|
|
865
896
|
}
|
|
866
897
|
/**
|
|
867
|
-
*
|
|
898
|
+
* Query the payment status (returns TxnStatus as-is, no transformation).
|
|
868
899
|
*
|
|
869
|
-
* @returns txnStatus
|
|
870
|
-
* @throws
|
|
900
|
+
* @returns txnStatus (may include txnId).
|
|
901
|
+
* @throws rejects INSTANCE_DESTROYED when destroyed.
|
|
871
902
|
*/
|
|
872
903
|
async getPaymentStatus() {
|
|
873
904
|
this.assertAlive();
|
|
874
905
|
return await this.transport.request("getPaymentStatus", {});
|
|
875
906
|
}
|
|
876
907
|
/**
|
|
877
|
-
*
|
|
908
|
+
* Update the checkout appearance/locale (does not change the session or the amount).
|
|
878
909
|
*
|
|
879
|
-
* @param patch - locale / appearance
|
|
880
|
-
* @throws
|
|
910
|
+
* @param patch - locale / appearance delta.
|
|
911
|
+
* @throws rejects INSTANCE_DESTROYED when destroyed.
|
|
881
912
|
*/
|
|
882
913
|
async updateCheckout(patch) {
|
|
883
914
|
this.assertAlive();
|
|
@@ -885,29 +916,30 @@ var CheckoutInstance = class {
|
|
|
885
916
|
await this.transport.request("updateCheckout", payload);
|
|
886
917
|
}
|
|
887
918
|
/**
|
|
888
|
-
*
|
|
919
|
+
* Register a Checkout-level event handler (passes through the transport).
|
|
889
920
|
*
|
|
890
|
-
* @param event -
|
|
891
|
-
* @param handler -
|
|
892
|
-
* @throws
|
|
921
|
+
* @param event - Event name.
|
|
922
|
+
* @param handler - Callback.
|
|
923
|
+
* @throws INSTANCE_DESTROYED when destroyed.
|
|
893
924
|
*/
|
|
894
925
|
on(event, handler) {
|
|
895
926
|
this.assertAlive();
|
|
896
927
|
this.transport.on(event, handler);
|
|
897
928
|
}
|
|
898
929
|
/**
|
|
899
|
-
*
|
|
930
|
+
* Unregister an event handler (safe to call after destruction).
|
|
900
931
|
*
|
|
901
|
-
* @param event -
|
|
902
|
-
* @param handler -
|
|
932
|
+
* @param event - Event name.
|
|
933
|
+
* @param handler - The previously registered callback.
|
|
903
934
|
*/
|
|
904
935
|
off(event, handler) {
|
|
905
936
|
this.transport.off(event, handler);
|
|
906
937
|
}
|
|
907
938
|
/**
|
|
908
|
-
*
|
|
909
|
-
*
|
|
910
|
-
*
|
|
939
|
+
* Destroy the instance: idempotent. Releases all Elements, unbinds the paymentResult listener,
|
|
940
|
+
* rejects the pending confirm with INSTANCE_DESTROYED, sends the destroy command and destroys
|
|
941
|
+
* the transport. Any late paymentResult afterwards is ignored and does not overwrite an already
|
|
942
|
+
* settled Promise.
|
|
911
943
|
*/
|
|
912
944
|
destroy() {
|
|
913
945
|
if (this.destroyed) {
|
|
@@ -928,13 +960,13 @@ var CheckoutInstance = class {
|
|
|
928
960
|
void Promise.resolve(this.transport.request("destroy", {})).catch(() => void 0);
|
|
929
961
|
this.transport.destroy();
|
|
930
962
|
}
|
|
931
|
-
/**
|
|
963
|
+
/** Throw INSTANCE_DESTROYED if already destroyed. */
|
|
932
964
|
assertAlive() {
|
|
933
965
|
if (this.destroyed) {
|
|
934
966
|
throw makeError3(ErrorCode3.INSTANCE_DESTROYED, "Checkout instance has been destroyed", "runtime");
|
|
935
967
|
}
|
|
936
968
|
}
|
|
937
|
-
/**
|
|
969
|
+
/** Mutual-exclusion check: throws ELEMENT_CONFLICT on conflict (derived live from the elements Map, does not mutate the registry). */
|
|
938
970
|
assertNoConflict(type, options) {
|
|
939
971
|
const existing = [...this.elements.values()];
|
|
940
972
|
const has = (t) => existing.some((e) => e.type === t);
|
|
@@ -984,9 +1016,10 @@ var CheckoutInstance = class {
|
|
|
984
1016
|
}
|
|
985
1017
|
}
|
|
986
1018
|
/**
|
|
987
|
-
*
|
|
988
|
-
*
|
|
989
|
-
*
|
|
1019
|
+
* Element `destroy()` callback: remove this elementId from the mutual-exclusion registry (the
|
|
1020
|
+
* elements Map) to free the slot. Idempotent; when the whole instance is destroyed (destroyed
|
|
1021
|
+
* already set) it goes through the unified `elements.clear()` path, so this is a no-op here,
|
|
1022
|
+
* avoiding concurrent modification while iterating elements to call element.destroy().
|
|
990
1023
|
*/
|
|
991
1024
|
releaseElement(elementId) {
|
|
992
1025
|
if (this.destroyed) {
|
|
@@ -994,7 +1027,7 @@ var CheckoutInstance = class {
|
|
|
994
1027
|
}
|
|
995
1028
|
this.elements.delete(elementId);
|
|
996
1029
|
}
|
|
997
|
-
/**
|
|
1030
|
+
/** Handle the paymentResult event: fulfill the pending confirm by operationId; late/mismatched ones are ignored. */
|
|
998
1031
|
handlePaymentResult(payload) {
|
|
999
1032
|
if (this.destroyed) {
|
|
1000
1033
|
return;
|
|
@@ -1021,9 +1054,10 @@ var CheckoutInstance = class {
|
|
|
1021
1054
|
// src/config/env.ts
|
|
1022
1055
|
var HOST_ORIGINS = {
|
|
1023
1056
|
dev: "https://checkout.sunbay-dev.com",
|
|
1057
|
+
test: "https://checkout.sunbay-uat.us",
|
|
1024
1058
|
prod: "https://checkout.sunbay.us"
|
|
1025
1059
|
};
|
|
1026
|
-
function resolveHostOrigin(env = "
|
|
1060
|
+
function resolveHostOrigin(env = "prod", override) {
|
|
1027
1061
|
if (override) {
|
|
1028
1062
|
return override;
|
|
1029
1063
|
}
|
|
@@ -1122,7 +1156,7 @@ if (typeof window !== "undefined") {
|
|
|
1122
1156
|
const w = window;
|
|
1123
1157
|
w.SunbayCheckout = { ...w.SunbayCheckout ?? {}, createCheckout };
|
|
1124
1158
|
}
|
|
1125
|
-
var version = true ? "0.0.
|
|
1159
|
+
var version = true ? "0.0.3" : "0.0.0";
|
|
1126
1160
|
export {
|
|
1127
1161
|
CheckoutElement,
|
|
1128
1162
|
CheckoutInstance,
|