Fix Paystack sheet: stop iframe embeds that Paystack blocks.
Deploy Ladill Hosting / deploy (push) Successful in 49s
Deploy Ladill Hosting / deploy (push) Successful in 49s
Paystack checkout.paystack.com returns X-Frame-Options: SAMEORIGIN, so the shared sheet opened empty. Keep mobile sheet/desktop modal chrome, launch checkout in a named window with a Continue CTA fallback, and escape payment-return via window.opener or top.
This commit is contained in:
@@ -8,8 +8,10 @@
|
||||
Optional overrides: $sheetTitle, $sheetSubtitle, $sheetFooter, $sheetAria, $iframeTitle
|
||||
Requires Alpine ancestor with: showSheet (bool), checkoutUrl (string).
|
||||
|
||||
Loads the provider authorization URL in-frame (verified via production Events/POS).
|
||||
MoMo and other non-frameable providers should redirect instead of opening this sheet.
|
||||
Paystack (checkout.paystack.com) sends X-Frame-Options: SAMEORIGIN and cannot be
|
||||
embedded. Same-origin URLs (e.g. MoMo waiting pages) still load in an iframe.
|
||||
External checkouts open in a named window, with an in-sheet Continue CTA as a
|
||||
user-gesture fallback when the popup is blocked after an async fetch.
|
||||
--}}
|
||||
@php
|
||||
$audience = $audience ?? 'buyer';
|
||||
@@ -35,10 +37,101 @@
|
||||
$iframeTitle = $iframeTitle ?? $defaults['iframe'];
|
||||
$sheetFooter = $sheetFooter ?? $defaults['footer'];
|
||||
@endphp
|
||||
@once
|
||||
<script>
|
||||
(function () {
|
||||
if (window.LadillPayCheckout) return;
|
||||
|
||||
function isFrameable(url) {
|
||||
if (!url) return false;
|
||||
try {
|
||||
return new URL(url, window.location.href).origin === window.location.origin;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function openCheckout(url) {
|
||||
if (!url) return null;
|
||||
try {
|
||||
// Named window (no noopener) so payment-return can redirect window.opener.
|
||||
return window.open(url, 'ladill_pay_checkout', 'width=480,height=720');
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function ensureStore() {
|
||||
if (!window.Alpine || typeof window.Alpine.store !== 'function') return null;
|
||||
if (window.Alpine.store('ladillPayCheckout')) {
|
||||
return window.Alpine.store('ladillPayCheckout');
|
||||
}
|
||||
window.Alpine.store('ladillPayCheckout', {
|
||||
frameable: false,
|
||||
popupBlocked: false,
|
||||
launchKey: '',
|
||||
reset() {
|
||||
this.frameable = false;
|
||||
this.popupBlocked = false;
|
||||
this.launchKey = '';
|
||||
},
|
||||
sync(show, url) {
|
||||
if (!show) {
|
||||
this.reset();
|
||||
return;
|
||||
}
|
||||
if (!url) return;
|
||||
this.frameable = isFrameable(url);
|
||||
if (this.frameable) {
|
||||
this.popupBlocked = false;
|
||||
return;
|
||||
}
|
||||
if (this.launchKey === url) return;
|
||||
this.launchKey = url;
|
||||
var popup = openCheckout(url);
|
||||
this.popupBlocked = !(popup && !popup.closed);
|
||||
},
|
||||
continueTo(url) {
|
||||
if (!url) return;
|
||||
var popup = openCheckout(url);
|
||||
this.popupBlocked = !(popup && !popup.closed);
|
||||
if (this.popupBlocked) {
|
||||
window.location.assign(url);
|
||||
}
|
||||
},
|
||||
});
|
||||
return window.Alpine.store('ladillPayCheckout');
|
||||
}
|
||||
|
||||
function onAlpineReady() {
|
||||
ensureStore();
|
||||
}
|
||||
|
||||
if (window.Alpine) {
|
||||
onAlpineReady();
|
||||
}
|
||||
document.addEventListener('alpine:init', onAlpineReady);
|
||||
|
||||
window.LadillPayCheckout = {
|
||||
isFrameable: isFrameable,
|
||||
open: openCheckout,
|
||||
ensureStore: ensureStore,
|
||||
};
|
||||
})();
|
||||
</script>
|
||||
@endonce
|
||||
<template x-teleport="body">
|
||||
<div x-show="showSheet"
|
||||
x-cloak
|
||||
x-effect="document.documentElement.style.overflow = showSheet ? 'hidden' : ''"
|
||||
x-effect="
|
||||
document.documentElement.style.overflow = showSheet ? 'hidden' : '';
|
||||
if (typeof window.LadillPayCheckout !== 'undefined') {
|
||||
window.LadillPayCheckout.ensureStore();
|
||||
}
|
||||
if ($store.ladillPayCheckout) {
|
||||
$store.ladillPayCheckout.sync(showSheet, checkoutUrl);
|
||||
}
|
||||
"
|
||||
@keydown.escape.window="if (showSheet) showSheet = false"
|
||||
class="fixed inset-0 z-[9999] flex items-end justify-center md:items-center md:p-6"
|
||||
role="dialog"
|
||||
@@ -90,10 +183,37 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="min-h-0 flex-1 overflow-y-auto overscroll-contain">
|
||||
<iframe :src="showSheet ? checkoutUrl : ''"
|
||||
<iframe x-show="$store.ladillPayCheckout?.frameable"
|
||||
:src="showSheet && $store.ladillPayCheckout?.frameable ? checkoutUrl : ''"
|
||||
class="h-full min-h-[60dvh] w-full border-0"
|
||||
allow="payment *"
|
||||
title="{{ $iframeTitle }}"></iframe>
|
||||
<div x-show="!$store.ladillPayCheckout?.frameable"
|
||||
class="flex min-h-[60dvh] flex-col items-center justify-center gap-4 px-6 py-10 text-center">
|
||||
<div class="flex h-12 w-12 items-center justify-center rounded-full bg-indigo-50 text-indigo-600" aria-hidden="true">
|
||||
<svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke-width="1.75" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M16.5 10.5V6.75a4.5 4.5 0 1 0-9 0v3.75m-.75 11.25h10.5a2.25 2.25 0 0 0 2.25-2.25v-6.75a2.25 2.25 0 0 0-2.25-2.25H6.75a2.25 2.25 0 0 0-2.25 2.25v6.75a2.25 2.25 0 0 0 2.25 2.25Z"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="max-w-xs space-y-1">
|
||||
<p class="text-sm font-semibold text-slate-800"
|
||||
x-text="$store.ladillPayCheckout?.popupBlocked ? 'Continue in a secure payment window' : 'Complete payment in the secure window'"></p>
|
||||
<p class="text-xs leading-snug text-slate-500">
|
||||
<span x-show="!$store.ladillPayCheckout?.popupBlocked">A Paystack window should be open. If you do not see it, tap continue below.</span>
|
||||
<span x-show="$store.ladillPayCheckout?.popupBlocked">Your browser blocked the popup. Tap continue to open Paystack.</span>
|
||||
</p>
|
||||
</div>
|
||||
<button type="button"
|
||||
@click="$store.ladillPayCheckout?.continueTo(checkoutUrl)"
|
||||
class="inline-flex w-full max-w-xs items-center justify-center rounded-xl bg-indigo-600 px-4 py-3 text-sm font-semibold text-white hover:bg-indigo-700">
|
||||
Continue to Paystack
|
||||
</button>
|
||||
<button type="button"
|
||||
@click="showSheet = false"
|
||||
class="text-xs font-medium text-slate-500 hover:text-slate-700">
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@if (! empty($sheetFooter))
|
||||
<p class="shrink-0 border-t border-slate-100 bg-slate-50 px-4 py-2 text-center text-[11px] text-slate-500">
|
||||
@@ -131,11 +251,38 @@
|
||||
</button>
|
||||
</div>
|
||||
<div class="min-h-0 flex-1 overflow-y-auto overscroll-contain">
|
||||
<iframe :src="showSheet ? checkoutUrl : ''"
|
||||
<iframe x-show="$store.ladillPayCheckout?.frameable"
|
||||
:src="showSheet && $store.ladillPayCheckout?.frameable ? checkoutUrl : ''"
|
||||
class="h-full min-h-0 w-full border-0"
|
||||
style="min-height: 28rem"
|
||||
allow="payment *"
|
||||
title="{{ $iframeTitle }}"></iframe>
|
||||
<div x-show="!$store.ladillPayCheckout?.frameable"
|
||||
class="flex h-full min-h-[28rem] flex-col items-center justify-center gap-4 px-8 py-12 text-center">
|
||||
<div class="flex h-12 w-12 items-center justify-center rounded-full bg-indigo-50 text-indigo-600" aria-hidden="true">
|
||||
<svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke-width="1.75" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M16.5 10.5V6.75a4.5 4.5 0 1 0-9 0v3.75m-.75 11.25h10.5a2.25 2.25 0 0 0 2.25-2.25v-6.75a2.25 2.25 0 0 0-2.25-2.25H6.75a2.25 2.25 0 0 0-2.25 2.25v6.75a2.25 2.25 0 0 0 2.25 2.25Z"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="max-w-sm space-y-1">
|
||||
<p class="text-sm font-semibold text-slate-800"
|
||||
x-text="$store.ladillPayCheckout?.popupBlocked ? 'Continue in a secure payment window' : 'Complete payment in the secure window'"></p>
|
||||
<p class="text-xs leading-snug text-slate-500">
|
||||
<span x-show="!$store.ladillPayCheckout?.popupBlocked">A Paystack window should be open. If you do not see it, click continue below.</span>
|
||||
<span x-show="$store.ladillPayCheckout?.popupBlocked">Your browser blocked the popup. Click continue to open Paystack.</span>
|
||||
</p>
|
||||
</div>
|
||||
<button type="button"
|
||||
@click="$store.ladillPayCheckout?.continueTo(checkoutUrl)"
|
||||
class="inline-flex w-full max-w-xs items-center justify-center rounded-xl bg-indigo-600 px-4 py-3 text-sm font-semibold text-white hover:bg-indigo-700">
|
||||
Continue to Paystack
|
||||
</button>
|
||||
<button type="button"
|
||||
@click="showSheet = false"
|
||||
class="text-xs font-medium text-slate-500 hover:text-slate-700">
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@if (! empty($sheetFooter))
|
||||
<p class="shrink-0 border-t border-slate-100 bg-slate-50 px-5 py-2 text-center text-[11px] text-slate-500">
|
||||
|
||||
Reference in New Issue
Block a user