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
}