Deploy Ladill Events / deploy (push) Successful in 1m25s
Use the shared Inline sheet, hold busy until payment UI opens, and avoid locking page scroll during the handoff.
636 lines
28 KiB
PHP
636 lines
28 KiB
PHP
{{--
|
|
Payment checkout shell.
|
|
|
|
Audiences:
|
|
- buyer (default): public ticket buyers, donors, shoppers — self-serve checkout
|
|
- operator: signed-in staff flows such as wallet top-up
|
|
|
|
Optional overrides: $sheetTitle, $sheetSubtitle, $sheetFooter, $sheetAria, $iframeTitle
|
|
Requires Alpine ancestor with:
|
|
- showSheet (bool)
|
|
- checkoutUrl (string) — authorization / waiting URL
|
|
- accessCode (string, optional) — Paystack initialize access_code for Inline
|
|
- publicKey (string, optional)
|
|
- returnUrl (string, optional) — where to go after Inline onSuccess (?reference=)
|
|
|
|
checkout.paystack.com cannot be iframed (X-Frame-Options: SAMEORIGIN).
|
|
|
|
Paystack Inline owns its secure payment popup. We do not place a Ladill
|
|
loading sheet in front of it: that created a needless first screen before
|
|
the checkout appeared. The Ladill bottomsheet/modal is reserved for
|
|
same-origin waiting pages (such as MoMo) and recoverable errors.
|
|
--}}
|
|
@php
|
|
$audience = $audience ?? 'buyer';
|
|
$defaults = match ($audience) {
|
|
'operator' => [
|
|
'title' => 'Complete payment',
|
|
'subtitle' => null,
|
|
'aria' => 'Complete payment',
|
|
'iframe' => 'Payment checkout',
|
|
'footer' => null,
|
|
],
|
|
default => [
|
|
'title' => 'Complete your payment',
|
|
'subtitle' => 'Pay securely. You\'ll get a confirmation when it succeeds.',
|
|
'aria' => 'Complete your payment',
|
|
'iframe' => 'Secure payment checkout',
|
|
'footer' => 'Your payment is processed securely. Do not close this window until finished.',
|
|
],
|
|
};
|
|
$sheetTitle = $sheetTitle ?? $defaults['title'];
|
|
$sheetSubtitle = $sheetSubtitle ?? $defaults['subtitle'];
|
|
$sheetAria = $sheetAria ?? $defaults['aria'];
|
|
$iframeTitle = $iframeTitle ?? $defaults['iframe'];
|
|
$sheetFooter = $sheetFooter ?? $defaults['footer'];
|
|
@endphp
|
|
@once
|
|
<script>
|
|
(function () {
|
|
if (window.__ladillPayCheckoutBootstrapped) return;
|
|
window.__ladillPayCheckoutBootstrapped = true;
|
|
|
|
var INLINE_SRC = 'https://js.paystack.co/v2/inline.js';
|
|
var inlineLoading = null;
|
|
var activePopup = null;
|
|
|
|
function isFrameable(url) {
|
|
if (!url) return false;
|
|
try {
|
|
return new URL(url, window.location.href).origin === window.location.origin;
|
|
} catch (e) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
function isPaystackCheckoutUrl(url) {
|
|
if (!url) return false;
|
|
try {
|
|
var host = new URL(url, window.location.href).hostname.toLowerCase();
|
|
return host === 'checkout.paystack.com' || host.endsWith('.paystack.com');
|
|
} catch (e) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
function accessCodeFromUrl(url) {
|
|
if (!url || !isPaystackCheckoutUrl(url)) return '';
|
|
try {
|
|
var path = new URL(url, window.location.href).pathname.replace(/^\/+/, '');
|
|
var code = path.split('/')[0] || '';
|
|
return /^[A-Za-z0-9_-]{6,}$/.test(code) ? code : '';
|
|
} catch (e) {
|
|
return '';
|
|
}
|
|
}
|
|
|
|
function resolveAccessCode(accessCode, checkoutUrl) {
|
|
var code = (accessCode || '').toString().trim();
|
|
if (code) return code;
|
|
return accessCodeFromUrl(checkoutUrl);
|
|
}
|
|
|
|
function loadInlineJs() {
|
|
if (window.PaystackPop) {
|
|
return Promise.resolve(window.PaystackPop);
|
|
}
|
|
if (inlineLoading) return inlineLoading;
|
|
inlineLoading = new Promise(function (resolve, reject) {
|
|
var existing = document.querySelector('script[data-ladill-paystack-inline]');
|
|
if (existing) {
|
|
if (window.PaystackPop) {
|
|
resolve(window.PaystackPop);
|
|
return;
|
|
}
|
|
existing.addEventListener('load', function () { resolve(window.PaystackPop); });
|
|
existing.addEventListener('error', function () { reject(new Error('Paystack script failed')); });
|
|
return;
|
|
}
|
|
var script = document.createElement('script');
|
|
script.src = INLINE_SRC;
|
|
script.async = true;
|
|
script.dataset.ladillPaystackInline = '1';
|
|
script.onload = function () { resolve(window.PaystackPop); };
|
|
script.onerror = function () {
|
|
inlineLoading = null;
|
|
reject(new Error('Paystack script failed'));
|
|
};
|
|
document.head.appendChild(script);
|
|
});
|
|
return inlineLoading;
|
|
}
|
|
|
|
function buildReturnUrl(returnUrl, reference) {
|
|
if (!returnUrl || !reference) return '';
|
|
try {
|
|
var u = new URL(returnUrl, window.location.href);
|
|
u.searchParams.set('reference', reference);
|
|
return u.toString();
|
|
} catch (e) {
|
|
var join = returnUrl.indexOf('?') >= 0 ? '&' : '?';
|
|
return returnUrl + join + 'reference=' + encodeURIComponent(reference);
|
|
}
|
|
}
|
|
|
|
function cancelInline() {
|
|
try {
|
|
if (activePopup && typeof activePopup.cancelTransaction === 'function') {
|
|
activePopup.cancelTransaction();
|
|
}
|
|
} catch (e) {}
|
|
activePopup = null;
|
|
}
|
|
|
|
// Fullscreen permission for Paystack's own iframes (do not reparent them).
|
|
// Only write attributes when they actually change — writing always re-fires
|
|
// MutationObservers and freezes the page (wallet include path).
|
|
function applyIframePermissions(iframe) {
|
|
if (!iframe || !iframe.setAttribute) return;
|
|
try {
|
|
var cur = (iframe.getAttribute('allow') || '').trim();
|
|
var need = ['payment *', 'fullscreen *', 'clipboard-read *', 'clipboard-write *', 'publickey-credentials-get *'];
|
|
var parts = cur ? cur.split(';').map(function (p) { return p.trim(); }).filter(Boolean) : [];
|
|
var lower = parts.map(function (p) { return p.toLowerCase(); });
|
|
need.forEach(function (perm) {
|
|
var base = perm.split(' ')[0].toLowerCase();
|
|
if (!lower.some(function (p) { return p === base || p.indexOf(base + ' ') === 0; })) {
|
|
parts.push(perm);
|
|
}
|
|
});
|
|
var next = parts.join('; ');
|
|
if (cur !== next) {
|
|
iframe.setAttribute('allow', next);
|
|
}
|
|
if (!iframe.hasAttribute('allowfullscreen')) {
|
|
iframe.setAttribute('allowfullscreen', '');
|
|
}
|
|
} catch (e) {}
|
|
}
|
|
|
|
function ensurePaystackIframePermissions(root) {
|
|
try {
|
|
var scope = root || document;
|
|
var nodes = scope.querySelectorAll ? scope.querySelectorAll('iframe') : [];
|
|
for (var i = 0; i < nodes.length; i++) {
|
|
var iframe = nodes[i];
|
|
var id = (iframe.id || '').toLowerCase();
|
|
var src = '';
|
|
try { src = (iframe.getAttribute('src') || '').toLowerCase(); } catch (e) {}
|
|
if (id.indexOf('inline-') === 0 || id.indexOf('embed-') === 0 || src.indexOf('paystack') !== -1) {
|
|
applyIframePermissions(iframe);
|
|
}
|
|
}
|
|
} catch (e) {}
|
|
}
|
|
|
|
function installIframeAllowHook() {
|
|
if (window.__ladillIframeAllowHook) return;
|
|
window.__ladillIframeAllowHook = true;
|
|
try {
|
|
var orig = Document.prototype.createElement;
|
|
Document.prototype.createElement = function (tagName, options) {
|
|
var el = options !== undefined ? orig.call(this, tagName, options) : orig.call(this, tagName);
|
|
try {
|
|
if (String(tagName).toLowerCase() === 'iframe') {
|
|
applyIframePermissions(el);
|
|
}
|
|
} catch (e) {}
|
|
return el;
|
|
};
|
|
} catch (e) {}
|
|
}
|
|
|
|
/**
|
|
* Arm iframe permission helpers only while a payment is starting.
|
|
* Do not install on every wallet/page load — observing style/class + always
|
|
* rewriting allow caused an infinite MutationObserver loop (page unresponsive).
|
|
*/
|
|
function watchPaystackIframes() {
|
|
if (window.__ladillPaystackIframeWatch) return;
|
|
window.__ladillPaystackIframeWatch = true;
|
|
installIframeAllowHook();
|
|
ensurePaystackIframePermissions(document);
|
|
try {
|
|
var obs = new MutationObserver(function (mutations) {
|
|
for (var i = 0; i < mutations.length; i++) {
|
|
var m = mutations[i];
|
|
// childList only — never re-enter on attribute writes.
|
|
if (!m.addedNodes || !m.addedNodes.length) continue;
|
|
for (var n = 0; n < m.addedNodes.length; n++) {
|
|
var node = m.addedNodes[n];
|
|
if (!node || node.nodeType !== 1) continue;
|
|
if (node.tagName === 'IFRAME') {
|
|
applyIframePermissions(node);
|
|
} else if (node.querySelectorAll) {
|
|
var nested = node.querySelectorAll('iframe');
|
|
for (var k = 0; k < nested.length; k++) {
|
|
applyIframePermissions(nested[k]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
obs.observe(document.documentElement || document.body, {
|
|
childList: true,
|
|
subtree: true,
|
|
});
|
|
} catch (e) {}
|
|
}
|
|
|
|
function emitPayEvent(name, detail) {
|
|
try {
|
|
window.dispatchEvent(new CustomEvent(name, { detail: detail || {} }));
|
|
} catch (e) {}
|
|
}
|
|
|
|
function createStoreDefinition() {
|
|
return {
|
|
frameable: false,
|
|
inline: false,
|
|
ready: false,
|
|
launching: false,
|
|
error: '',
|
|
_launchToken: 0,
|
|
_activeCode: '',
|
|
_openedEmitted: false,
|
|
shellVisible() {
|
|
return !!(this.frameable || this.error);
|
|
},
|
|
reset() {
|
|
this.frameable = false;
|
|
this.inline = false;
|
|
this.ready = false;
|
|
this.launching = false;
|
|
this.error = '';
|
|
this._activeCode = '';
|
|
this._openedEmitted = false;
|
|
cancelInline();
|
|
},
|
|
markOpened() {
|
|
if (this._openedEmitted) return;
|
|
this._openedEmitted = true;
|
|
this.launching = false;
|
|
emitPayEvent('ladill-pay-opened');
|
|
},
|
|
markFailed(message) {
|
|
this.launching = false;
|
|
this._activeCode = '';
|
|
this._openedEmitted = false;
|
|
this.error = message || 'Could not open secure payment.';
|
|
emitPayEvent('ladill-pay-error', { message: this.error });
|
|
},
|
|
sync(show, url, accessCode, returnUrl) {
|
|
url = (url || '').toString();
|
|
accessCode = (accessCode || '').toString();
|
|
returnUrl = (returnUrl || '').toString();
|
|
|
|
if (!show) {
|
|
this._launchToken += 1;
|
|
this.reset();
|
|
return;
|
|
}
|
|
|
|
this.ready = !!(url || accessCode);
|
|
// Keep a previous recoverable error only until a new attempt supplies payload.
|
|
if (url || accessCode) {
|
|
this.error = '';
|
|
}
|
|
|
|
if (!url && !accessCode) {
|
|
// Waiting for parent to set access_code / checkout_url after fetch.
|
|
this.frameable = false;
|
|
this.inline = false;
|
|
this.launching = true;
|
|
this._activeCode = '';
|
|
this._openedEmitted = false;
|
|
return;
|
|
}
|
|
|
|
if (isFrameable(url)) {
|
|
this.frameable = true;
|
|
this.inline = false;
|
|
this.launching = false;
|
|
this._activeCode = '';
|
|
this._openedEmitted = false;
|
|
cancelInline();
|
|
// Same-origin waiting UI is our shell — it is "open" for busy-state release.
|
|
emitPayEvent('ladill-pay-opened');
|
|
return;
|
|
}
|
|
|
|
var code = resolveAccessCode(accessCode, url);
|
|
if (code) {
|
|
this.frameable = false;
|
|
this.inline = true;
|
|
if (this._activeCode === code && (this.launching || activePopup)) {
|
|
return;
|
|
}
|
|
this.launchInline(code, returnUrl);
|
|
return;
|
|
}
|
|
|
|
this.frameable = false;
|
|
this.inline = false;
|
|
this.markFailed(
|
|
isPaystackCheckoutUrl(url)
|
|
? 'Could not start in-app payment. Please try again.'
|
|
: 'Secure checkout could not be opened in-page. Please try again.'
|
|
);
|
|
},
|
|
launchInline(accessCode, returnUrl) {
|
|
var store = this;
|
|
var token = ++this._launchToken;
|
|
this._activeCode = accessCode;
|
|
this.launching = true;
|
|
this.error = '';
|
|
this.inline = true;
|
|
this.frameable = false;
|
|
this._openedEmitted = false;
|
|
|
|
watchPaystackIframes();
|
|
|
|
// Ensure Paystack script is fully ready before resumeTransaction so we
|
|
// do not paint an empty popup shell for a beat (the pre-modal flash).
|
|
loadInlineJs().then(function (PaystackPop) {
|
|
if (token !== store._launchToken) return;
|
|
if (!PaystackPop) throw new Error('Paystack unavailable');
|
|
|
|
cancelInline();
|
|
|
|
var popup = new PaystackPop();
|
|
activePopup = popup;
|
|
ensurePaystackIframePermissions(document);
|
|
|
|
popup.resumeTransaction(accessCode, {
|
|
onLoad: function () {
|
|
if (token !== store._launchToken) return;
|
|
store.markOpened();
|
|
ensurePaystackIframePermissions(document);
|
|
},
|
|
onSuccess: function (transaction) {
|
|
if (token !== store._launchToken) return;
|
|
store.launching = false;
|
|
activePopup = null;
|
|
var reference = (transaction && (transaction.reference || transaction.trxref)) || '';
|
|
var dest = buildReturnUrl(returnUrl, reference);
|
|
if (dest) {
|
|
window.location.assign(dest);
|
|
return;
|
|
}
|
|
window.location.reload();
|
|
},
|
|
onCancel: function () {
|
|
if (token !== store._launchToken) return;
|
|
store.launching = false;
|
|
store._activeCode = '';
|
|
store._openedEmitted = false;
|
|
activePopup = null;
|
|
emitPayEvent('ladill-pay-cancelled');
|
|
},
|
|
onError: function (err) {
|
|
if (token !== store._launchToken) return;
|
|
activePopup = null;
|
|
store.markFailed((err && err.message) ? String(err.message) : 'Could not open secure payment.');
|
|
},
|
|
});
|
|
|
|
// Safety: if onLoad is slow, treat a live checkout iframe as open.
|
|
var polls = 0;
|
|
var pollId = setInterval(function () {
|
|
if (token !== store._launchToken) {
|
|
clearInterval(pollId);
|
|
return;
|
|
}
|
|
ensurePaystackIframePermissions(document);
|
|
var live = document.querySelector(
|
|
'iframe[id^="inline-checkout"], iframe[id^="embed-checkout"]'
|
|
);
|
|
if (live) {
|
|
store.markOpened();
|
|
clearInterval(pollId);
|
|
}
|
|
polls += 1;
|
|
if (polls >= 50) {
|
|
clearInterval(pollId);
|
|
if (store.launching && token === store._launchToken) {
|
|
store.markFailed('Payment UI did not open. Please try again.');
|
|
}
|
|
}
|
|
}, 100);
|
|
}).catch(function (err) {
|
|
if (token !== store._launchToken) return;
|
|
store.markFailed((err && err.message) ? String(err.message) : 'Could not load payment script.');
|
|
});
|
|
},
|
|
};
|
|
}
|
|
|
|
function ensureStore() {
|
|
if (!window.Alpine || typeof window.Alpine.store !== 'function') return null;
|
|
try {
|
|
var existing = window.Alpine.store('ladillPayCheckout');
|
|
if (existing) return existing;
|
|
} catch (e) {}
|
|
window.Alpine.store('ladillPayCheckout', createStoreDefinition());
|
|
return window.Alpine.store('ladillPayCheckout');
|
|
}
|
|
|
|
document.addEventListener('alpine:init', function () {
|
|
ensureStore();
|
|
});
|
|
if (window.Alpine && window.Alpine.version) {
|
|
ensureStore();
|
|
}
|
|
|
|
// Do not watch iframes or preload Paystack on every page that includes this
|
|
// partial (e.g. wallet). That froze the tab. prepare()/launchInline arm it.
|
|
|
|
window.LadillPayCheckout = {
|
|
isFrameable: isFrameable,
|
|
isPaystackCheckoutUrl: isPaystackCheckoutUrl,
|
|
accessCodeFromUrl: accessCodeFromUrl,
|
|
resolveAccessCode: resolveAccessCode,
|
|
prepare: function () {
|
|
// Warm script + hooks only when user is about to pay.
|
|
installIframeAllowHook();
|
|
loadInlineJs().catch(function () {});
|
|
return null;
|
|
},
|
|
cancel: function () {
|
|
cancelInline();
|
|
var store = ensureStore();
|
|
if (store) store.reset();
|
|
},
|
|
open: function () { return null; },
|
|
ensureStore: ensureStore,
|
|
};
|
|
})();
|
|
</script>
|
|
@endonce
|
|
<template x-teleport="body">
|
|
{{-- Inherit showSheet / checkoutUrl / accessCode / returnUrl from parent Alpine scope. --}}
|
|
<div
|
|
x-init="
|
|
window.LadillPayCheckout && window.LadillPayCheckout.ensureStore();
|
|
// Drop parent busy flags only when payment UI is actually up (or sheet closed).
|
|
// Prevents the form/button from flashing back while Paystack is still booting.
|
|
const releaseBusy = () => {
|
|
try {
|
|
if (typeof loading !== 'undefined' && loading) loading = false;
|
|
if (typeof renewLoading !== 'undefined' && renewLoading) renewLoading = false;
|
|
} catch (e) {}
|
|
};
|
|
const onPayOpened = () => releaseBusy();
|
|
const onPayCancelled = () => { showSheet = false; releaseBusy(); };
|
|
const onPayError = () => releaseBusy();
|
|
window.addEventListener('ladill-pay-opened', onPayOpened);
|
|
window.addEventListener('ladill-pay-cancelled', onPayCancelled);
|
|
window.addEventListener('ladill-pay-error', onPayError);
|
|
const runSync = () => {
|
|
const store = $store.ladillPayCheckout;
|
|
if (!store) return;
|
|
store.sync(
|
|
!!showSheet,
|
|
(typeof checkoutUrl === 'undefined' || checkoutUrl === null) ? '' : String(checkoutUrl),
|
|
(typeof accessCode === 'undefined' || accessCode === null) ? '' : String(accessCode),
|
|
(typeof returnUrl === 'undefined' || returnUrl === null) ? '' : String(returnUrl)
|
|
);
|
|
};
|
|
$watch('showSheet', (value) => {
|
|
runSync();
|
|
if (!value) releaseBusy();
|
|
});
|
|
$watch('checkoutUrl', () => runSync());
|
|
$watch('accessCode', () => runSync());
|
|
$watch('returnUrl', () => runSync());
|
|
$nextTick(() => runSync());
|
|
"
|
|
x-show="showSheet && $store.ladillPayCheckout && ($store.ladillPayCheckout.frameable || $store.ladillPayCheckout.error)"
|
|
x-cloak
|
|
data-ladill-pay-sheet
|
|
style="display: none"
|
|
x-effect="
|
|
// Only lock page scroll when OUR shell is visible — never during pure
|
|
// Paystack Inline handoff (that scrollbar jump was the pre-modal flash).
|
|
const lock = !!(showSheet && $store.ladillPayCheckout && ($store.ladillPayCheckout.frameable || $store.ladillPayCheckout.error));
|
|
document.documentElement.style.overflow = lock ? 'hidden' : '';
|
|
"
|
|
@keydown.escape.window="
|
|
if (!showSheet) return;
|
|
// While Paystack Inline is the active UI, Escape is handled by Paystack.
|
|
if ($store.ladillPayCheckout && $store.ladillPayCheckout.inline && !$store.ladillPayCheckout.launching && !$store.ladillPayCheckout.error) return;
|
|
showSheet = false;
|
|
window.LadillPayCheckout?.cancel?.();
|
|
"
|
|
@ladill-pay-cancelled.window="showSheet = false"
|
|
class="fixed inset-0 z-[9999]"
|
|
role="dialog"
|
|
aria-modal="true"
|
|
:aria-hidden="(!(showSheet && $store.ladillPayCheckout && ($store.ladillPayCheckout.frameable || $store.ladillPayCheckout.error))).toString()"
|
|
aria-label="{{ $sheetAria }}">
|
|
|
|
{{--
|
|
Ladill bottomsheet (mobile) / modal (desktop) — ONLY for:
|
|
- same-origin waiting pages (MoMo etc.)
|
|
- recoverable errors
|
|
Paystack Inline opens its own secure payment popup; this shell stays
|
|
hidden during launch so nothing flashes in front of it.
|
|
--}}
|
|
<div x-show="$store.ladillPayCheckout && ($store.ladillPayCheckout.frameable || $store.ladillPayCheckout.error)"
|
|
x-cloak
|
|
class="pointer-events-auto absolute inset-0 flex items-end justify-center md:items-center md:p-6"
|
|
data-ladill-pay-shell>
|
|
<div class="absolute inset-0 bg-black/60"
|
|
data-ladill-pay-backdrop
|
|
@click="showSheet = false; window.LadillPayCheckout?.cancel?.()">
|
|
</div>
|
|
|
|
<div data-ladill-pay-panel
|
|
data-paystack-live="0"
|
|
class="relative flex w-full flex-col overflow-hidden rounded-t-2xl bg-white shadow-2xl md:max-w-lg md:rounded-2xl"
|
|
style="height: min(90dvh, 720px); max-height: 90dvh; padding-bottom: env(safe-area-inset-bottom, 0px)"
|
|
x-transition:enter="transition duration-300 ease-out md:duration-200"
|
|
x-transition:enter-start="translate-y-full opacity-0 md:translate-y-0 md:scale-95"
|
|
x-transition:enter-end="translate-y-0 opacity-100 md:scale-100"
|
|
x-transition:leave="transition duration-200 ease-in md:duration-150"
|
|
x-transition:leave-start="translate-y-0 opacity-100 md:scale-100"
|
|
x-transition:leave-end="translate-y-full opacity-0 md:translate-y-0 md:scale-95"
|
|
@click.stop>
|
|
|
|
<div class="relative flex shrink-0 flex-col gap-0.5 border-b border-slate-100 px-4 pb-3 pt-5 md:px-5 md:pt-3"
|
|
style="padding-top: max(1.25rem, env(safe-area-inset-top, 0px))"
|
|
data-ladill-pay-header>
|
|
<div class="absolute left-1/2 top-2 h-1 w-10 -translate-x-1/2 rounded-full bg-slate-200 md:hidden" data-ladill-pay-handle aria-hidden="true"></div>
|
|
<div class="flex items-start justify-between gap-3">
|
|
<div class="min-w-0">
|
|
<p class="text-sm font-semibold text-slate-800">{{ $sheetTitle }}</p>
|
|
@if (! empty($sheetSubtitle))
|
|
<p class="mt-0.5 text-xs leading-snug text-slate-500">{{ $sheetSubtitle }}</p>
|
|
@endif
|
|
</div>
|
|
<button type="button"
|
|
x-ref="checkoutClose"
|
|
x-init="
|
|
$watch(
|
|
() => !!(showSheet && $store.ladillPayCheckout && ($store.ladillPayCheckout.frameable || $store.ladillPayCheckout.error)),
|
|
(open) => { if (open) $nextTick(() => $refs.checkoutClose?.focus()); }
|
|
)
|
|
"
|
|
@click="showSheet = false; window.LadillPayCheckout?.cancel?.()"
|
|
class="shrink-0 rounded-full p-1.5 text-slate-400 transition hover:bg-slate-100 hover:text-slate-700"
|
|
aria-label="Close">
|
|
<svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" aria-hidden="true">
|
|
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18 18 6M6 6l12 12"/>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
{{-- Markers kept for tests / callers that query live close chrome. --}}
|
|
<div class="sr-only" data-ladill-pay-header-minimal data-ladill-pay-close-live aria-hidden="true"></div>
|
|
<div class="sr-only" data-ladill-pay-mount data-ladill-pay-body aria-hidden="true"></div>
|
|
|
|
<div class="min-h-0 flex-1 overflow-y-auto overscroll-contain">
|
|
<iframe x-show="$store.ladillPayCheckout && $store.ladillPayCheckout.frameable"
|
|
:src="showSheet && $store.ladillPayCheckout && $store.ladillPayCheckout.frameable ? checkoutUrl : ''"
|
|
class="h-full min-h-[60dvh] w-full border-0 md:min-h-0"
|
|
style="min-height: 28rem"
|
|
allow="payment *; fullscreen *; clipboard-read *; clipboard-write *"
|
|
title="{{ $iframeTitle }}"></iframe>
|
|
|
|
<div x-show="$store.ladillPayCheckout && $store.ladillPayCheckout.error"
|
|
class="flex min-h-[40dvh] flex-col items-center justify-center gap-4 px-6 py-10 text-center md:min-h-[20rem] md:px-8 md:py-12"
|
|
data-ladill-pay-error>
|
|
<div class="flex h-12 w-12 items-center justify-center rounded-full bg-red-50 text-red-500" 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="M12 9v3.75m9-.75a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9 3.75h.008v.008H12v-.008Z"/>
|
|
</svg>
|
|
</div>
|
|
<div class="max-w-xs space-y-1 md:max-w-sm">
|
|
<p class="text-sm font-semibold text-slate-800" data-ladill-pay-status-title>Checkout unavailable</p>
|
|
<p class="text-xs leading-snug text-slate-500" x-text="$store.ladillPayCheckout.error"></p>
|
|
</div>
|
|
<button type="button"
|
|
data-ladill-pay-retry
|
|
@click="window.LadillPayCheckout?.ensureStore()?.sync(true, (typeof checkoutUrl==='undefined'||checkoutUrl===null)?'':String(checkoutUrl), (typeof accessCode==='undefined'||accessCode===null)?'':String(accessCode), (typeof returnUrl==='undefined'||returnUrl===null)?'':String(returnUrl))"
|
|
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">
|
|
Try again
|
|
</button>
|
|
<button type="button"
|
|
@click="showSheet = false; window.LadillPayCheckout?.cancel?.()"
|
|
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 md:px-5"
|
|
data-ladill-pay-footer>
|
|
{{ $sheetFooter }}
|
|
</p>
|
|
@endif
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|