diff --git a/clients/.changeset/weak-maps-occur.md b/clients/.changeset/weak-maps-occur.md new file mode 100644 index 0000000000..517b356709 --- /dev/null +++ b/clients/.changeset/weak-maps-occur.md @@ -0,0 +1,5 @@ +--- +'@polar-sh/checkout': patch +--- + +Prevent embed to be closed while checkout is processing payment diff --git a/clients/apps/web/src/components/Checkout/CheckoutForm.tsx b/clients/apps/web/src/components/Checkout/CheckoutForm.tsx index 5939177671..e426338ded 100644 --- a/clients/apps/web/src/components/Checkout/CheckoutForm.tsx +++ b/clients/apps/web/src/components/Checkout/CheckoutForm.tsx @@ -791,6 +791,15 @@ const StripeCheckoutForm = (props: CheckoutFormProps) => { setLoading(true) + if (checkout.embed_origin) { + PolarEmbedCheckout.postMessage( + { + event: 'confirmed', + }, + checkout.embed_origin, + ) + } + if (!checkout.is_payment_form_required) { let updatedCheckout: CheckoutPublicConfirmed setLoadingLabel('Processing order') diff --git a/clients/examples/checkout-embed/index.html b/clients/examples/checkout-embed/index.html index 1490dfcf42..ff7ba7a141 100644 --- a/clients/examples/checkout-embed/index.html +++ b/clients/examples/checkout-embed/index.html @@ -11,11 +11,11 @@

My Product

- Purchase (light mode) - Purchase (dark mode) diff --git a/clients/packages/checkout/src/embed.ts b/clients/packages/checkout/src/embed.ts index 590b770b12..f894b244fc 100644 --- a/clients/packages/checkout/src/embed.ts +++ b/clients/packages/checkout/src/embed.ts @@ -14,6 +14,15 @@ interface EmbedCheckoutMessageClose { event: 'close' } +/** + * Message sent to the parent window when the checkout is confirmed. + * + * At that point, the parent window shouldn't allow to close the checkout. + */ +interface EmbedCheckoutMessageConfirmed { + event: 'confirmed' +} + /** * Message sent to the parent window when the checkout is successfully completed. * @@ -31,6 +40,7 @@ interface EmbedCheckoutMessageSuccess { type EmbedCheckoutMessage = | EmbedCheckoutMessageLoaded | EmbedCheckoutMessageClose + | EmbedCheckoutMessageConfirmed | EmbedCheckoutMessageSuccess const isEmbedCheckoutMessage = ( @@ -46,16 +56,19 @@ class EmbedCheckout { private iframe: HTMLIFrameElement private loader: HTMLDivElement private loaded: boolean + private closable: boolean private eventTarget: EventTarget public constructor(iframe: HTMLIFrameElement, loader: HTMLDivElement) { this.iframe = iframe this.loader = loader this.loaded = false + this.closable = true this.eventTarget = new EventTarget() this.initWindowListener() this.addEventListener('loaded', this.loadedListener.bind(this)) this.addEventListener('close', this.closeListener.bind(this)) + this.addEventListener('confirmed', this.confirmedListener.bind(this)) this.addEventListener('success', this.successListener.bind(this)) } @@ -206,6 +219,11 @@ class EmbedCheckout { listener: (event: CustomEvent) => void, options?: AddEventListenerOptions | boolean, ): void + public addEventListener( + type: 'confirmed', + listener: (event: CustomEvent) => void, + options?: AddEventListenerOptions | boolean, + ): void public addEventListener( type: 'success', listener: (event: CustomEvent) => void, @@ -233,6 +251,10 @@ class EmbedCheckout { type: 'close', listener: (event: CustomEvent) => void, ): void + public removeEventListener( + type: 'confirmed', + listener: (event: CustomEvent) => void, + ): void public removeEventListener( type: 'success', listener: (event: CustomEvent) => void, @@ -276,7 +298,23 @@ class EmbedCheckout { if (event.defaultPrevented) { return } - this.close() + if (this.closable) { + this.close() + } + } + + /** + * Default listener for the `confirmed` event. + * + * This listener will set a flag to prevent the parent window from closing the embedded checkout. + */ + private confirmedListener( + event: CustomEvent, + ): void { + if (event.defaultPrevented) { + return + } + this.closable = false } /** @@ -290,6 +328,7 @@ class EmbedCheckout { if (event.defaultPrevented) { return } + this.closable = true if (event.detail.redirect) { window.location.href = event.detail.successURL }