Force Paystack checkout into the sheet mount on first insert.
Deploy Ladill POS / deploy (push) Successful in 45s
Deploy Ladill POS / deploy (push) Successful in 45s
Redirect first iframe append into the Ladill sheet body and keep absolute fill styles applied so checkout is not a second full-screen modal.
This commit is contained in:
@@ -13,7 +13,7 @@
|
||||
- publicKey (string, optional) — unused by resumeTransaction; kept for callers
|
||||
- returnUrl (string, optional) — where to go after Inline onSuccess (?reference=)
|
||||
|
||||
Paystack checkout.paystack.com cannot be iframed (X-Frame-Options: SAMEORIGIN).
|
||||
checkout.paystack.com cannot be iframed (X-Frame-Options: SAMEORIGIN).
|
||||
Primary path: Paystack Inline JS (PaystackPop.resumeTransaction) — in-page overlay,
|
||||
not a separate browser tab. Same-origin URLs (e.g. MoMo waiting) still use iframe.
|
||||
--}}
|
||||
@@ -148,14 +148,139 @@
|
||||
setPaystackContainActive(false);
|
||||
}
|
||||
|
||||
// Paystack Inline defaults to a full-viewport modal (body append + position:fixed).
|
||||
// We contain it inside our bottomsheet/modal mount so checkout loads where the
|
||||
// spinner is — not as a second modal on top of Ladill chrome.
|
||||
// Paystack Inline always body-appends a full-viewport modal (position:fixed 100%).
|
||||
// Strategy:
|
||||
// 1) On FIRST append only, put checkout iframe into our sheet mount (no later reparent
|
||||
// — reparent after load reloads the frame and breaks Paystack).
|
||||
// 2) Force position:absolute + fill mount (fixed+100% always paints the whole viewport
|
||||
// even when the node is nested under the sheet).
|
||||
// 3) Continuously re-apply styles (Paystack rewrites style on open/animate).
|
||||
// 4) Hide Paystack's own dimmer; Ladill sheet owns backdrop + chrome.
|
||||
var PAYSTACK_IFRAME_ALLOW = 'payment *; fullscreen *; clipboard-read *; clipboard-write *; publickey-credentials-get *';
|
||||
var PAYSTACK_ALLOW_FEATURES = ['payment', 'fullscreen', 'clipboard-read', 'clipboard-write', 'publickey-credentials-get'];
|
||||
var activeMountEl = null;
|
||||
var activeBodyEl = null;
|
||||
var pinRafId = 0;
|
||||
var lastPinRect = null;
|
||||
var nativeAppendChild = Node.prototype.appendChild;
|
||||
var nativeInsertBefore = Node.prototype.insertBefore;
|
||||
|
||||
function isElementShown(el) {
|
||||
if (!el || el.nodeType !== 1) return false;
|
||||
if (el.getAttribute && el.getAttribute('aria-hidden') === 'true') return false;
|
||||
var p = el;
|
||||
while (p && p.nodeType === 1) {
|
||||
try {
|
||||
var s = window.getComputedStyle(p);
|
||||
if (s.display === 'none' || s.visibility === 'hidden') return false;
|
||||
} catch (e) {}
|
||||
p = p.parentElement;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function resolveOpenSheet() {
|
||||
var sheets = document.querySelectorAll('[data-ladill-pay-sheet]');
|
||||
var fallback = null;
|
||||
for (var i = 0; i < sheets.length; i++) {
|
||||
var sheet = sheets[i];
|
||||
if (!fallback) fallback = sheet;
|
||||
if (sheet.getAttribute('aria-hidden') !== 'true') return sheet;
|
||||
}
|
||||
for (var j = 0; j < sheets.length; j++) {
|
||||
if (isElementShown(sheets[j])) return sheets[j];
|
||||
}
|
||||
return fallback;
|
||||
}
|
||||
|
||||
function resolvePaystackMount() {
|
||||
var sheet = resolveOpenSheet();
|
||||
if (sheet) {
|
||||
var mount = sheet.querySelector('[data-ladill-pay-mount]');
|
||||
if (mount) return mount;
|
||||
var body = sheet.querySelector('[data-ladill-pay-body]');
|
||||
if (body) return body;
|
||||
}
|
||||
return document.querySelector('[data-ladill-pay-mount]')
|
||||
|| document.querySelector('[data-ladill-pay-body]');
|
||||
}
|
||||
|
||||
function getPaystackMount() {
|
||||
return document.querySelector('[data-ladill-pay-mount]');
|
||||
if (activeMountEl && activeMountEl.isConnected) return activeMountEl;
|
||||
return resolvePaystackMount();
|
||||
}
|
||||
|
||||
function getPaystackBody() {
|
||||
if (activeBodyEl && activeBodyEl.isConnected) return activeBodyEl;
|
||||
var mount = getPaystackMount();
|
||||
if (mount) {
|
||||
var body = mount.closest('[data-ladill-pay-body]');
|
||||
if (body) return body;
|
||||
}
|
||||
var sheet = resolveOpenSheet();
|
||||
return sheet ? sheet.querySelector('[data-ladill-pay-body]') : document.querySelector('[data-ladill-pay-body]');
|
||||
}
|
||||
|
||||
function armPaystackMount() {
|
||||
var sheet = resolveOpenSheet();
|
||||
activeMountEl = sheet
|
||||
? (sheet.querySelector('[data-ladill-pay-mount]') || sheet.querySelector('[data-ladill-pay-body]'))
|
||||
: resolvePaystackMount();
|
||||
activeBodyEl = sheet
|
||||
? sheet.querySelector('[data-ladill-pay-body]')
|
||||
: getPaystackBody();
|
||||
// Keep the mount measurable while we pin (Alpine x-show can set display:none).
|
||||
try {
|
||||
if (activeMountEl) {
|
||||
activeMountEl.style.setProperty('display', 'block', 'important');
|
||||
activeMountEl.style.setProperty('visibility', 'visible', 'important');
|
||||
activeMountEl.style.setProperty('pointer-events', 'none', 'important');
|
||||
}
|
||||
} catch (e) {}
|
||||
return activeMountEl;
|
||||
}
|
||||
|
||||
function measurePinTarget() {
|
||||
var el = getPaystackMount() || getPaystackBody();
|
||||
if (!el) return null;
|
||||
var rect = el.getBoundingClientRect();
|
||||
if (rect.width >= 40 && rect.height >= 40) {
|
||||
return rect;
|
||||
}
|
||||
// Mount may still be display:none — fall back to the white body panel.
|
||||
var body = getPaystackBody();
|
||||
if (body && body !== el) {
|
||||
rect = body.getBoundingClientRect();
|
||||
if (rect.width >= 40 && rect.height >= 40) return rect;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function waitForPinTarget(timeoutMs) {
|
||||
timeoutMs = timeoutMs || 1200;
|
||||
return new Promise(function (resolve) {
|
||||
var start = Date.now();
|
||||
var tick = function () {
|
||||
armPaystackMount();
|
||||
var rect = measurePinTarget();
|
||||
if (rect) {
|
||||
syncPaystackMountBox();
|
||||
resolve(rect);
|
||||
return;
|
||||
}
|
||||
if (Date.now() - start >= timeoutMs) {
|
||||
syncPaystackMountBox();
|
||||
resolve(null);
|
||||
return;
|
||||
}
|
||||
if (typeof requestAnimationFrame === 'function') {
|
||||
requestAnimationFrame(tick);
|
||||
} else {
|
||||
setTimeout(tick, 16);
|
||||
}
|
||||
};
|
||||
tick();
|
||||
});
|
||||
}
|
||||
|
||||
function isPaystackCheckoutFrame(iframe) {
|
||||
@@ -213,12 +338,46 @@
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
function styleContainedCheckout(iframe) {
|
||||
function isCheckoutFrame(iframe) {
|
||||
if (!iframe || !iframe.tagName || iframe.tagName.toUpperCase() !== 'IFRAME') return false;
|
||||
var id = (iframe.id || '');
|
||||
return id.indexOf('inline-checkout') === 0
|
||||
|| id.indexOf('embed-checkout') === 0
|
||||
|| iframe.hasAttribute('data-ladill-pay-contained');
|
||||
}
|
||||
|
||||
function styleMountAsContainer(mount) {
|
||||
if (!mount) return;
|
||||
try {
|
||||
// Create a fixed containing block so position:fixed descendants still
|
||||
// cannot escape to the viewport (transform + isolation).
|
||||
var s = mount.style;
|
||||
s.setProperty('position', 'absolute', 'important');
|
||||
s.setProperty('inset', '0', 'important');
|
||||
s.setProperty('left', '0', 'important');
|
||||
s.setProperty('top', '0', 'important');
|
||||
s.setProperty('right', '0', 'important');
|
||||
s.setProperty('bottom', '0', 'important');
|
||||
s.setProperty('width', '100%', 'important');
|
||||
s.setProperty('height', '100%', 'important');
|
||||
s.setProperty('overflow', 'hidden', 'important');
|
||||
s.setProperty('display', 'block', 'important');
|
||||
s.setProperty('visibility', 'visible', 'important');
|
||||
s.setProperty('background', '#fff', 'important');
|
||||
s.setProperty('transform', 'translateZ(0)', 'important');
|
||||
s.setProperty('isolation', 'isolate', 'important');
|
||||
s.setProperty('contain', 'layout size', 'important');
|
||||
s.setProperty('z-index', '1', 'important');
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
function styleCheckoutInMount(iframe) {
|
||||
if (!iframe) return;
|
||||
try {
|
||||
iframe.setAttribute('data-ladill-pay-contained', '1');
|
||||
// Force embed layout; Paystack sets position:fixed inline without !important.
|
||||
iframe.setAttribute('data-ladill-pay-mode', 'mount');
|
||||
var s = iframe.style;
|
||||
// Absolute fill of mount — NOT position:fixed 100% (that always paints viewport).
|
||||
s.setProperty('position', 'absolute', 'important');
|
||||
s.setProperty('left', '0', 'important');
|
||||
s.setProperty('top', '0', 'important');
|
||||
@@ -226,20 +385,81 @@
|
||||
s.setProperty('bottom', '0', 'important');
|
||||
s.setProperty('width', '100%', 'important');
|
||||
s.setProperty('height', '100%', 'important');
|
||||
s.setProperty('max-width', 'none', 'important');
|
||||
s.setProperty('max-height', 'none', 'important');
|
||||
s.setProperty('margin', '0', 'important');
|
||||
s.setProperty('padding', '0', 'important');
|
||||
s.setProperty('border', '0', 'important');
|
||||
s.setProperty('border-radius', '0', 'important');
|
||||
s.setProperty('z-index', '2', 'important');
|
||||
s.setProperty('visibility', 'visible', 'important');
|
||||
s.setProperty('display', 'block', 'important');
|
||||
s.setProperty('background', 'transparent', 'important');
|
||||
s.setProperty('background-color', 'transparent', 'important');
|
||||
s.setProperty('max-width', 'none', 'important');
|
||||
s.setProperty('max-height', 'none', 'important');
|
||||
s.setProperty('opacity', '1', 'important');
|
||||
s.setProperty('background', '#fff', 'important');
|
||||
s.setProperty('background-color', '#fff', 'important');
|
||||
s.setProperty('pointer-events', 'auto', 'important');
|
||||
s.setProperty('color-scheme', 'light', 'important');
|
||||
s.setProperty('transform', 'none', 'important');
|
||||
applyIframePermissions(iframe);
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
function styleCheckoutPinnedToRect(iframe, rect) {
|
||||
if (!iframe || !rect) return;
|
||||
try {
|
||||
iframe.setAttribute('data-ladill-pay-contained', '1');
|
||||
iframe.setAttribute('data-ladill-pay-mode', 'pin');
|
||||
var s = iframe.style;
|
||||
var left = Math.max(0, Math.round(rect.left));
|
||||
var top = Math.max(0, Math.round(rect.top));
|
||||
var width = Math.max(0, Math.round(rect.width));
|
||||
var height = Math.max(0, Math.round(rect.height));
|
||||
// Direct pixels — CSS vars in setProperty are unreliable across browsers.
|
||||
s.setProperty('position', 'fixed', 'important');
|
||||
s.setProperty('left', left + 'px', 'important');
|
||||
s.setProperty('top', top + 'px', 'important');
|
||||
s.setProperty('right', 'auto', 'important');
|
||||
s.setProperty('bottom', 'auto', 'important');
|
||||
s.setProperty('width', width + 'px', 'important');
|
||||
s.setProperty('height', height + 'px', 'important');
|
||||
s.setProperty('max-width', 'none', 'important');
|
||||
s.setProperty('max-height', 'none', 'important');
|
||||
s.setProperty('margin', '0', 'important');
|
||||
s.setProperty('padding', '0', 'important');
|
||||
s.setProperty('border', '0', 'important');
|
||||
s.setProperty('border-radius', '0', 'important');
|
||||
s.setProperty('z-index', '10050', 'important');
|
||||
s.setProperty('visibility', 'visible', 'important');
|
||||
s.setProperty('display', 'block', 'important');
|
||||
s.setProperty('opacity', '1', 'important');
|
||||
s.setProperty('background', '#fff', 'important');
|
||||
s.setProperty('background-color', '#fff', 'important');
|
||||
s.setProperty('pointer-events', 'auto', 'important');
|
||||
s.setProperty('color-scheme', 'light', 'important');
|
||||
s.setProperty('transform', 'none', 'important');
|
||||
applyIframePermissions(iframe);
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
function pinCheckoutIframe(iframe) {
|
||||
if (!iframe || !window.__ladillContainPaystack) return;
|
||||
var mount = getPaystackMount();
|
||||
if (mount) {
|
||||
styleMountAsContainer(mount);
|
||||
// Prefer living inside the mount (absolute fill). Only first-append
|
||||
// redirects into mount; if already elsewhere, pin with fixed+pixels.
|
||||
if (iframe.parentNode === mount) {
|
||||
styleCheckoutInMount(iframe);
|
||||
return;
|
||||
}
|
||||
}
|
||||
var rect = measurePinTarget() || lastPinRect;
|
||||
if (rect) {
|
||||
lastPinRect = rect;
|
||||
styleCheckoutPinnedToRect(iframe, rect);
|
||||
}
|
||||
}
|
||||
|
||||
function hidePaystackBackground(iframe) {
|
||||
if (!iframe) return;
|
||||
try {
|
||||
@@ -249,6 +469,12 @@
|
||||
s.setProperty('visibility', 'hidden', 'important');
|
||||
s.setProperty('pointer-events', 'none', 'important');
|
||||
s.setProperty('opacity', '0', 'important');
|
||||
s.setProperty('z-index', '-1', 'important');
|
||||
s.setProperty('width', '0', 'important');
|
||||
s.setProperty('height', '0', 'important');
|
||||
s.setProperty('left', '-9999px', 'important');
|
||||
s.setProperty('top', '-9999px', 'important');
|
||||
s.setProperty('position', 'fixed', 'important');
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
@@ -258,41 +484,28 @@
|
||||
hidePaystackBackground(iframe);
|
||||
return;
|
||||
}
|
||||
if (!isPaystackCheckoutFrame(iframe) && !isPaystackIframe(iframe)) return;
|
||||
if (isPaystackBackgroundFrame(iframe)) return;
|
||||
|
||||
var mount = getPaystackMount();
|
||||
if (!mount) return;
|
||||
|
||||
// Prefer checkout frames; skip stray non-checkout paystack frames if unsure.
|
||||
if (!isPaystackCheckoutFrame(iframe) && (iframe.id || '').indexOf('inline-') !== 0) {
|
||||
applyIframePermissions(iframe);
|
||||
if (!isCheckoutFrame(iframe) && !isPaystackCheckoutFrame(iframe)) {
|
||||
if (isPaystackIframe(iframe)) applyIframePermissions(iframe);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (iframe.parentNode !== mount) {
|
||||
mount.appendChild(iframe);
|
||||
}
|
||||
styleContainedCheckout(iframe);
|
||||
} catch (e) {
|
||||
// If reparent fails mid-load, still try absolute restyle in place.
|
||||
styleContainedCheckout(iframe);
|
||||
}
|
||||
pinCheckoutIframe(iframe);
|
||||
}
|
||||
|
||||
function ensurePaystackContained(root) {
|
||||
if (!window.__ladillContainPaystack) return;
|
||||
try {
|
||||
var scope = root || document;
|
||||
var nodes = scope.querySelectorAll ? scope.querySelectorAll('iframe') : [];
|
||||
for (var i = 0; i < nodes.length; i++) {
|
||||
containPaystackIframe(nodes[i]);
|
||||
}
|
||||
// Paystack may leave backgrounds on body outside scope walks.
|
||||
syncPaystackMountBox();
|
||||
var bgs = document.querySelectorAll('iframe[id^="inline-background"]');
|
||||
for (var j = 0; j < bgs.length; j++) {
|
||||
hidePaystackBackground(bgs[j]);
|
||||
for (var j = 0; j < bgs.length; j++) hidePaystackBackground(bgs[j]);
|
||||
|
||||
var checks = document.querySelectorAll(
|
||||
'iframe[id^="inline-checkout"], iframe[id^="embed-checkout"], iframe[data-ladill-pay-contained]'
|
||||
);
|
||||
for (var k = 0; k < checks.length; k++) pinCheckoutIframe(checks[k]);
|
||||
|
||||
if (root && root !== document && root.querySelectorAll) {
|
||||
var nested = root.querySelectorAll('iframe');
|
||||
for (var i = 0; i < nested.length; i++) containPaystackIframe(nested[i]);
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
@@ -303,9 +516,7 @@
|
||||
var nodes = scope.querySelectorAll ? scope.querySelectorAll('iframe') : [];
|
||||
for (var i = 0; i < nodes.length; i++) {
|
||||
var iframe = nodes[i];
|
||||
var src = '';
|
||||
try { src = (iframe.getAttribute('src') || '').trim(); } catch (e) {}
|
||||
if (isPaystackIframe(iframe) || !src) {
|
||||
if (isPaystackIframe(iframe) || isCheckoutFrame(iframe)) {
|
||||
applyIframePermissions(iframe);
|
||||
}
|
||||
}
|
||||
@@ -315,10 +526,17 @@
|
||||
|
||||
function syncPaystackMountBox() {
|
||||
try {
|
||||
var mount = getPaystackMount();
|
||||
if (!mount || !window.__ladillContainPaystack) return;
|
||||
var rect = mount.getBoundingClientRect();
|
||||
if (!window.__ladillContainPaystack) return;
|
||||
var rect = measurePinTarget();
|
||||
var root = document.documentElement;
|
||||
if (!rect) {
|
||||
root.style.setProperty('--ladill-pay-left', '0px');
|
||||
root.style.setProperty('--ladill-pay-top', '0px');
|
||||
root.style.setProperty('--ladill-pay-width', '0px');
|
||||
root.style.setProperty('--ladill-pay-height', '0px');
|
||||
return;
|
||||
}
|
||||
lastPinRect = rect;
|
||||
root.style.setProperty('--ladill-pay-left', Math.max(0, Math.round(rect.left)) + 'px');
|
||||
root.style.setProperty('--ladill-pay-top', Math.max(0, Math.round(rect.top)) + 'px');
|
||||
root.style.setProperty('--ladill-pay-width', Math.max(0, Math.round(rect.width)) + 'px');
|
||||
@@ -326,22 +544,78 @@
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
function startPinLoop() {
|
||||
stopPinLoop();
|
||||
var tick = function () {
|
||||
if (!window.__ladillContainPaystack) {
|
||||
pinRafId = 0;
|
||||
return;
|
||||
}
|
||||
ensurePaystackContained(document);
|
||||
pinRafId = typeof requestAnimationFrame === 'function'
|
||||
? requestAnimationFrame(tick)
|
||||
: setTimeout(tick, 32);
|
||||
};
|
||||
tick();
|
||||
}
|
||||
|
||||
function stopPinLoop() {
|
||||
if (!pinRafId) return;
|
||||
if (typeof cancelAnimationFrame === 'function') {
|
||||
cancelAnimationFrame(pinRafId);
|
||||
} else {
|
||||
clearTimeout(pinRafId);
|
||||
}
|
||||
pinRafId = 0;
|
||||
}
|
||||
|
||||
function setPaystackContainActive(active) {
|
||||
window.__ladillContainPaystack = !!active;
|
||||
try {
|
||||
document.documentElement.classList.toggle('ladill-pay-inline-active', !!active);
|
||||
} catch (e) {}
|
||||
if (active) {
|
||||
armPaystackMount();
|
||||
styleMountAsContainer(getPaystackMount());
|
||||
installIframeAllowHook();
|
||||
installAppendHook();
|
||||
ensurePaystackContained(document);
|
||||
syncPaystackMountBox();
|
||||
startPinLoop();
|
||||
if (!window.__ladillPayMountResizeBound) {
|
||||
window.__ladillPayMountResizeBound = true;
|
||||
window.addEventListener('resize', syncPaystackMountBox, { passive: true });
|
||||
window.addEventListener('orientationchange', syncPaystackMountBox, { passive: true });
|
||||
window.addEventListener('resize', function () {
|
||||
syncPaystackMountBox();
|
||||
ensurePaystackContained(document);
|
||||
}, { passive: true });
|
||||
window.addEventListener('orientationchange', function () {
|
||||
setTimeout(function () {
|
||||
syncPaystackMountBox();
|
||||
ensurePaystackContained(document);
|
||||
}, 100);
|
||||
}, { passive: true });
|
||||
window.addEventListener('scroll', function () {
|
||||
if (window.__ladillContainPaystack) {
|
||||
syncPaystackMountBox();
|
||||
ensurePaystackContained(document);
|
||||
}
|
||||
}, { passive: true, capture: true });
|
||||
}
|
||||
} else {
|
||||
stopPinLoop();
|
||||
lastPinRect = null;
|
||||
try {
|
||||
if (activeMountEl) {
|
||||
activeMountEl.style.removeProperty('display');
|
||||
activeMountEl.style.removeProperty('visibility');
|
||||
activeMountEl.style.removeProperty('pointer-events');
|
||||
activeMountEl.style.removeProperty('transform');
|
||||
activeMountEl.style.removeProperty('isolation');
|
||||
activeMountEl.style.removeProperty('contain');
|
||||
}
|
||||
} catch (e) {}
|
||||
activeMountEl = null;
|
||||
activeBodyEl = null;
|
||||
try {
|
||||
var root = document.documentElement;
|
||||
root.style.removeProperty('--ladill-pay-left');
|
||||
@@ -373,48 +647,63 @@
|
||||
if (window.__ladillAppendHook) return;
|
||||
window.__ladillAppendHook = true;
|
||||
try {
|
||||
var origAppend = Node.prototype.appendChild;
|
||||
Node.prototype.appendChild = function (child) {
|
||||
try {
|
||||
if (window.__ladillContainPaystack && child && child.tagName === 'IFRAME') {
|
||||
if (isPaystackBackgroundFrame(child)) {
|
||||
var appendedBg = origAppend.call(this, child);
|
||||
var bg = nativeAppendChild.call(this, child);
|
||||
hidePaystackBackground(child);
|
||||
return appendedBg;
|
||||
return bg;
|
||||
}
|
||||
if (isPaystackCheckoutFrame(child) || ((child.id || '').indexOf('inline-checkout') === 0)) {
|
||||
if (isCheckoutFrame(child) || isPaystackCheckoutFrame(child)
|
||||
|| (child.id || '').indexOf('inline-checkout') === 0
|
||||
|| (child.id || '').indexOf('embed-checkout') === 0) {
|
||||
var mount = getPaystackMount();
|
||||
if (mount) {
|
||||
var appended = origAppend.call(mount, child);
|
||||
styleContainedCheckout(child);
|
||||
return appended;
|
||||
styleMountAsContainer(mount);
|
||||
// FIRST insert goes into mount — avoids later reparent reload.
|
||||
var mounted = nativeAppendChild.call(mount, child);
|
||||
styleCheckoutInMount(child);
|
||||
return mounted;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {}
|
||||
return origAppend.call(this, child);
|
||||
var result = nativeAppendChild.call(this, child);
|
||||
try {
|
||||
if (window.__ladillContainPaystack && child && child.tagName === 'IFRAME') {
|
||||
containPaystackIframe(child);
|
||||
}
|
||||
} catch (e2) {}
|
||||
return result;
|
||||
};
|
||||
|
||||
var origInsert = Node.prototype.insertBefore;
|
||||
Node.prototype.insertBefore = function (child, ref) {
|
||||
try {
|
||||
if (window.__ladillContainPaystack && child && child.tagName === 'IFRAME') {
|
||||
if (isPaystackBackgroundFrame(child)) {
|
||||
var insertedBg = origInsert.call(this, child, ref);
|
||||
var bg = nativeInsertBefore.call(this, child, ref);
|
||||
hidePaystackBackground(child);
|
||||
return insertedBg;
|
||||
return bg;
|
||||
}
|
||||
if (isPaystackCheckoutFrame(child)) {
|
||||
if (isCheckoutFrame(child) || isPaystackCheckoutFrame(child)) {
|
||||
var mount = getPaystackMount();
|
||||
if (mount) {
|
||||
var inserted = origAppend.call(mount, child);
|
||||
styleContainedCheckout(child);
|
||||
return inserted;
|
||||
styleMountAsContainer(mount);
|
||||
var mounted = nativeAppendChild.call(mount, child);
|
||||
styleCheckoutInMount(child);
|
||||
return mounted;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {}
|
||||
return origInsert.call(this, child, ref);
|
||||
var result = nativeInsertBefore.call(this, child, ref);
|
||||
try {
|
||||
if (window.__ladillContainPaystack && child && child.tagName === 'IFRAME') {
|
||||
containPaystackIframe(child);
|
||||
}
|
||||
} catch (e2) {}
|
||||
return result;
|
||||
};
|
||||
} catch (e) {}
|
||||
}
|
||||
@@ -556,75 +845,90 @@
|
||||
this._activeCode = accessCode;
|
||||
this.launching = true;
|
||||
this.error = '';
|
||||
// Arm containment before Paystack creates iframes so the first
|
||||
// body.appendChild is redirected into our sheet mount.
|
||||
setPaystackContainActive(true);
|
||||
watchPaystackIframes();
|
||||
loadInlineJs().then(function (PaystackPop) {
|
||||
// Wait for the bottomsheet/modal body to lay out, then open Paystack
|
||||
// and pin its body iframe over that loading area (no reparent).
|
||||
var start = function () {
|
||||
if (token !== store._launchToken) return;
|
||||
if (!PaystackPop) throw new Error('Paystack unavailable');
|
||||
try {
|
||||
if (activePopup && typeof activePopup.cancelTransaction === 'function') {
|
||||
activePopup.cancelTransaction();
|
||||
}
|
||||
} catch (e) {}
|
||||
activePopup = null;
|
||||
armPaystackMount();
|
||||
setPaystackContainActive(true);
|
||||
var popup = new PaystackPop();
|
||||
activePopup = popup;
|
||||
popup.resumeTransaction(accessCode, {
|
||||
onLoad: function () {
|
||||
if (token !== store._launchToken) return;
|
||||
store.launching = false;
|
||||
ensurePaystackIframePermissions(document);
|
||||
ensurePaystackContained(document);
|
||||
// After Alpine hides the loading overlay, re-measure mount and re-pin iframe.
|
||||
setTimeout(function () {
|
||||
ensurePaystackContained(document);
|
||||
syncPaystackMountBox();
|
||||
}, 0);
|
||||
setTimeout(syncPaystackMountBox, 120);
|
||||
setTimeout(syncPaystackMountBox, 400);
|
||||
},
|
||||
onSuccess: function (transaction) {
|
||||
if (token !== store._launchToken) return;
|
||||
store.launching = false;
|
||||
setPaystackContainActive(false);
|
||||
var reference = (transaction && (transaction.reference || transaction.trxref)) || '';
|
||||
var dest = buildReturnUrl(returnUrl, reference);
|
||||
if (dest) {
|
||||
window.location.assign(dest);
|
||||
return;
|
||||
watchPaystackIframes();
|
||||
waitForPinTarget(1200).then(function () {
|
||||
if (token !== store._launchToken) return;
|
||||
return loadInlineJs();
|
||||
}).then(function (PaystackPop) {
|
||||
if (token !== store._launchToken) return;
|
||||
if (!PaystackPop) throw new Error('Paystack unavailable');
|
||||
try {
|
||||
if (activePopup && typeof activePopup.cancelTransaction === 'function') {
|
||||
activePopup.cancelTransaction();
|
||||
}
|
||||
window.location.reload();
|
||||
},
|
||||
onCancel: function () {
|
||||
if (token !== store._launchToken) return;
|
||||
store.launching = false;
|
||||
store._activeCode = '';
|
||||
activePopup = null;
|
||||
setPaystackContainActive(false);
|
||||
window.dispatchEvent(new CustomEvent('ladill-pay-cancelled'));
|
||||
},
|
||||
onError: function (err) {
|
||||
if (token !== store._launchToken) return;
|
||||
store.launching = false;
|
||||
store._activeCode = '';
|
||||
setPaystackContainActive(false);
|
||||
store.error = (err && err.message) ? String(err.message) : 'Could not open secure payment.';
|
||||
},
|
||||
} catch (e) {}
|
||||
activePopup = null;
|
||||
armPaystackMount();
|
||||
setPaystackContainActive(true);
|
||||
syncPaystackMountBox();
|
||||
// PaystackPop constructor appends iframes to document.body.
|
||||
// We leave them there and pin checkout over our sheet body.
|
||||
var popup = new PaystackPop();
|
||||
activePopup = popup;
|
||||
ensurePaystackContained(document);
|
||||
popup.resumeTransaction(accessCode, {
|
||||
onLoad: function () {
|
||||
if (token !== store._launchToken) return;
|
||||
store.launching = false;
|
||||
ensurePaystackIframePermissions(document);
|
||||
ensurePaystackContained(document);
|
||||
// Alpine swaps header/spinner; re-measure then re-pin.
|
||||
setTimeout(function () { ensurePaystackContained(document); }, 0);
|
||||
setTimeout(function () { ensurePaystackContained(document); }, 50);
|
||||
setTimeout(function () { ensurePaystackContained(document); }, 150);
|
||||
setTimeout(function () { ensurePaystackContained(document); }, 400);
|
||||
},
|
||||
onSuccess: function (transaction) {
|
||||
if (token !== store._launchToken) return;
|
||||
store.launching = false;
|
||||
setPaystackContainActive(false);
|
||||
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 = '';
|
||||
activePopup = null;
|
||||
setPaystackContainActive(false);
|
||||
window.dispatchEvent(new CustomEvent('ladill-pay-cancelled'));
|
||||
},
|
||||
onError: function (err) {
|
||||
if (token !== store._launchToken) return;
|
||||
store.launching = false;
|
||||
store._activeCode = '';
|
||||
setPaystackContainActive(false);
|
||||
store.error = (err && err.message) ? String(err.message) : 'Could not open secure payment.';
|
||||
},
|
||||
});
|
||||
setTimeout(function () { ensurePaystackContained(document); }, 0);
|
||||
setTimeout(function () { ensurePaystackContained(document); }, 50);
|
||||
setTimeout(function () { ensurePaystackContained(document); }, 200);
|
||||
setTimeout(function () { ensurePaystackContained(document); }, 500);
|
||||
}).catch(function (err) {
|
||||
if (token !== store._launchToken) return;
|
||||
store.launching = false;
|
||||
store._activeCode = '';
|
||||
setPaystackContainActive(false);
|
||||
store.error = (err && err.message) ? String(err.message) : 'Could not load payment script.';
|
||||
});
|
||||
// Paystack often inserts frames a tick after resumeTransaction.
|
||||
setTimeout(function () { ensurePaystackContained(document); }, 0);
|
||||
setTimeout(function () { ensurePaystackContained(document); }, 50);
|
||||
setTimeout(function () { ensurePaystackContained(document); }, 200);
|
||||
}).catch(function (err) {
|
||||
if (token !== store._launchToken) return;
|
||||
store.launching = false;
|
||||
store._activeCode = '';
|
||||
setPaystackContainActive(false);
|
||||
store.error = (err && err.message) ? String(err.message) : 'Could not load payment script.';
|
||||
});
|
||||
};
|
||||
if (typeof requestAnimationFrame === 'function') {
|
||||
requestAnimationFrame(function () { requestAnimationFrame(start); });
|
||||
} else {
|
||||
setTimeout(start, 32);
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -667,33 +971,52 @@
|
||||
})();
|
||||
</script>
|
||||
@endonce
|
||||
{{-- Override Paystack's full-viewport popup styles so checkout sits in our sheet mount. --}}
|
||||
{{-- Keep Paystack checkout inside the Ladill sheet loading area. --}}
|
||||
@once('ladill-paystack-contain-css')
|
||||
<style>
|
||||
html.ladill-pay-inline-active iframe[id^="inline-background"] {
|
||||
/* Hide Paystack's own full-screen dimmer — Ladill backdrop owns that. */
|
||||
html.ladill-pay-inline-active iframe[id^="inline-background"],
|
||||
iframe[data-ladill-pay-bg-hidden] {
|
||||
display: none !important;
|
||||
visibility: hidden !important;
|
||||
pointer-events: none !important;
|
||||
opacity: 0 !important;
|
||||
width: 0 !important;
|
||||
height: 0 !important;
|
||||
z-index: -1 !important;
|
||||
left: -9999px !important;
|
||||
top: -9999px !important;
|
||||
position: fixed !important;
|
||||
}
|
||||
|
||||
[data-ladill-pay-mount] {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
overflow: hidden;
|
||||
background: #fff;
|
||||
min-height: 12rem;
|
||||
/* Containing block so fixed children cannot escape the sheet body. */
|
||||
transform: translateZ(0);
|
||||
isolation: isolate;
|
||||
}
|
||||
/* When Paystack is live the shell is transparent so only Paystack's own card shows. */
|
||||
html.ladill-pay-inline-active [data-ladill-pay-panel][data-paystack-live="1"] {
|
||||
background: transparent !important;
|
||||
box-shadow: none !important;
|
||||
html.ladill-pay-inline-active [data-ladill-pay-mount] {
|
||||
display: block !important;
|
||||
visibility: visible !important;
|
||||
pointer-events: auto !important;
|
||||
}
|
||||
html.ladill-pay-inline-active [data-ladill-pay-panel][data-paystack-live="1"] [data-ladill-pay-mount] {
|
||||
background: transparent !important;
|
||||
html.ladill-pay-inline-active [data-ladill-pay-panel] {
|
||||
/* Ensure panel is a stacking context above page content. */
|
||||
z-index: 1;
|
||||
}
|
||||
/* Paystack draws its own modal chrome inside the iframe — fill the mount. */
|
||||
[data-ladill-pay-mount] iframe[data-ladill-pay-contained],
|
||||
[data-ladill-pay-mount] iframe[id^="inline-checkout"],
|
||||
[data-ladill-pay-mount] iframe[id^="embed-checkout"] {
|
||||
html.ladill-pay-inline-active [data-ladill-pay-sheet] {
|
||||
z-index: 9999 !important;
|
||||
}
|
||||
|
||||
/* Preferred: iframe lives inside the mount and fills it. */
|
||||
html.ladill-pay-inline-active [data-ladill-pay-mount] iframe[id^="inline-checkout"],
|
||||
html.ladill-pay-inline-active [data-ladill-pay-mount] iframe[id^="embed-checkout"],
|
||||
html.ladill-pay-inline-active [data-ladill-pay-mount] iframe[data-ladill-pay-contained],
|
||||
html.ladill-pay-inline-active iframe[data-ladill-pay-mode="mount"] {
|
||||
position: absolute !important;
|
||||
left: 0 !important;
|
||||
top: 0 !important;
|
||||
@@ -710,26 +1033,43 @@
|
||||
z-index: 2 !important;
|
||||
visibility: visible !important;
|
||||
display: block !important;
|
||||
background: transparent !important;
|
||||
opacity: 1 !important;
|
||||
background: #fff !important;
|
||||
background-color: #fff !important;
|
||||
color-scheme: light !important;
|
||||
pointer-events: auto !important;
|
||||
transform: none !important;
|
||||
}
|
||||
/* If a Paystack frame escapes the mount, pin it over the mount box. */
|
||||
|
||||
/*
|
||||
Fallback when the frame stays on body: pin with measured pixels.
|
||||
Default 0×0 until JS measures the sheet body (never flash full viewport).
|
||||
*/
|
||||
html.ladill-pay-inline-active body > iframe[id^="inline-checkout"],
|
||||
html.ladill-pay-inline-active body > iframe[id^="embed-checkout"] {
|
||||
html.ladill-pay-inline-active body > iframe[id^="embed-checkout"],
|
||||
html.ladill-pay-inline-active iframe[data-ladill-pay-mode="pin"] {
|
||||
position: fixed !important;
|
||||
left: var(--ladill-pay-left, 0) !important;
|
||||
top: var(--ladill-pay-top, 0) !important;
|
||||
width: var(--ladill-pay-width, 100%) !important;
|
||||
height: var(--ladill-pay-height, 100%) !important;
|
||||
left: var(--ladill-pay-left, 0px) !important;
|
||||
top: var(--ladill-pay-top, 0px) !important;
|
||||
right: auto !important;
|
||||
bottom: auto !important;
|
||||
width: var(--ladill-pay-width, 0px) !important;
|
||||
height: var(--ladill-pay-height, 0px) !important;
|
||||
max-width: none !important;
|
||||
max-height: none !important;
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
border: 0 !important;
|
||||
border-radius: 0 !important;
|
||||
z-index: 10000 !important;
|
||||
z-index: 10050 !important;
|
||||
visibility: visible !important;
|
||||
display: block !important;
|
||||
background: transparent !important;
|
||||
opacity: 1 !important;
|
||||
background: #fff !important;
|
||||
background-color: #fff !important;
|
||||
color-scheme: light !important;
|
||||
pointer-events: auto !important;
|
||||
transform: none !important;
|
||||
}
|
||||
</style>
|
||||
@endonce
|
||||
@@ -779,10 +1119,10 @@
|
||||
</div>
|
||||
|
||||
{{--
|
||||
ONE design everywhere (POS / Merchant / Mini / Give / Invoice / Events):
|
||||
- Loading / error / same-origin: white bottomsheet (mobile) or modal (desktop)
|
||||
- Paystack Inline ready: transparent shell so Paystack's own card is the only surface
|
||||
on the dimmed backdrop (no second white frame / double modal)
|
||||
ONE surface everywhere (POS / Merchant / Mini / Give / Invoice / Events):
|
||||
- Mobile: bottom sheet
|
||||
- Desktop: centered modal
|
||||
- Paystack Inline fills the sheet body loading area (not a second full-screen modal)
|
||||
--}}
|
||||
<div data-ladill-pay-panel
|
||||
x-show="showSheet"
|
||||
@@ -792,30 +1132,15 @@
|
||||
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"
|
||||
class="relative flex w-full flex-col overflow-hidden"
|
||||
class="relative flex w-full flex-col overflow-hidden rounded-t-2xl bg-white shadow-2xl md:rounded-2xl"
|
||||
:data-paystack-live="($store.ladillPayCheckout && $store.ladillPayCheckout.inline && !$store.ladillPayCheckout.launching && !$store.ladillPayCheckout.error && !$store.ladillPayCheckout.frameable) ? '1' : '0'"
|
||||
:class="($store.ladillPayCheckout && $store.ladillPayCheckout.inline && !$store.ladillPayCheckout.launching && !$store.ladillPayCheckout.error && !$store.ladillPayCheckout.frameable)
|
||||
? 'max-w-none rounded-none bg-transparent shadow-none md:w-[min(100vw,720px)]'
|
||||
: 'rounded-t-2xl bg-white shadow-2xl md:max-w-lg md:rounded-2xl'"
|
||||
:style="($store.ladillPayCheckout && $store.ladillPayCheckout.inline && !$store.ladillPayCheckout.launching && !$store.ladillPayCheckout.error && !$store.ladillPayCheckout.frameable)
|
||||
? 'height: min(90dvh, 640px); max-height: 90dvh; width: min(100vw, 720px); padding-bottom: 0;'
|
||||
: 'height: min(90dvh, 720px); max-height: 90dvh; padding-bottom: env(safe-area-inset-bottom, 0px);'"
|
||||
? 'md:max-w-3xl'
|
||||
: 'md:max-w-lg'"
|
||||
style="height: min(90dvh, 720px); max-height: 90dvh; padding-bottom: env(safe-area-inset-bottom, 0px)"
|
||||
@click.stop>
|
||||
|
||||
{{-- Floating close when Paystack owns the surface (Paystack has no host close for us) --}}
|
||||
<button type="button"
|
||||
x-show="$store.ladillPayCheckout && $store.ladillPayCheckout.inline && !$store.ladillPayCheckout.launching && !$store.ladillPayCheckout.error && !$store.ladillPayCheckout.frameable"
|
||||
@click="showSheet = false; window.LadillPayCheckout?.cancel?.()"
|
||||
class="absolute right-3 top-3 z-30 flex h-9 w-9 items-center justify-center rounded-full bg-white/95 text-slate-600 shadow-md ring-1 ring-slate-200/80 backdrop-blur hover:bg-white hover:text-slate-900 md:right-0 md:-mr-12 md:top-0 md:bg-white"
|
||||
style="top: max(0.75rem, env(safe-area-inset-top, 0px))"
|
||||
data-ladill-pay-close-live
|
||||
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>
|
||||
|
||||
{{-- Standard POS header: loading, error, or same-origin waiting page --}}
|
||||
{{-- Full header while loading / error / same-origin; compact bar once Paystack fills the body --}}
|
||||
<div x-show="!$store.ladillPayCheckout
|
||||
|| $store.ladillPayCheckout.frameable
|
||||
|| $store.ladillPayCheckout.launching
|
||||
@@ -845,26 +1170,42 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div x-show="$store.ladillPayCheckout
|
||||
&& $store.ladillPayCheckout.inline
|
||||
&& !$store.ladillPayCheckout.launching
|
||||
&& !$store.ladillPayCheckout.error
|
||||
&& !$store.ladillPayCheckout.frameable"
|
||||
class="relative z-20 flex shrink-0 items-center justify-between border-b border-slate-100 px-3 pb-2 pt-3 md:px-4 md:pt-2"
|
||||
style="padding-top: max(0.75rem, env(safe-area-inset-top, 0px))"
|
||||
data-ladill-pay-header-minimal>
|
||||
<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>
|
||||
<p class="pl-1 text-xs font-medium text-slate-500">{{ $sheetTitle }}</p>
|
||||
<button type="button"
|
||||
data-ladill-pay-close-live
|
||||
@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>
|
||||
|
||||
{{-- Body: spinner while starting; Paystack fills this when ready --}}
|
||||
<div class="relative min-h-0 flex-1 overflow-hidden overscroll-contain"
|
||||
<div class="relative min-h-0 flex-1 overflow-hidden overscroll-contain bg-white"
|
||||
data-ladill-pay-body
|
||||
:class="($store.ladillPayCheckout && $store.ladillPayCheckout.inline && !$store.ladillPayCheckout.launching && !$store.ladillPayCheckout.error && !$store.ladillPayCheckout.frameable)
|
||||
? 'bg-transparent'
|
||||
: 'bg-white'"
|
||||
style="min-height: 20rem">
|
||||
style="min-height: 22rem">
|
||||
<iframe x-show="$store.ladillPayCheckout && $store.ladillPayCheckout.frameable"
|
||||
:src="showSheet && $store.ladillPayCheckout && $store.ladillPayCheckout.frameable ? checkoutUrl : ''"
|
||||
class="absolute inset-0 h-full w-full border-0"
|
||||
allow="payment *; fullscreen *; clipboard-read *; clipboard-write *"
|
||||
title="{{ $iframeTitle }}"></iframe>
|
||||
|
||||
{{-- Pin target for Paystack: keep in layout while inline checkout is active. --}}
|
||||
<div x-show="!$store.ladillPayCheckout || !$store.ladillPayCheckout.frameable"
|
||||
data-ladill-pay-mount
|
||||
id="ladill-paystack-mount"
|
||||
class="absolute inset-0"
|
||||
:class="($store.ladillPayCheckout && $store.ladillPayCheckout.inline && !$store.ladillPayCheckout.launching && !$store.ladillPayCheckout.error)
|
||||
? 'bg-transparent'
|
||||
: 'bg-white'"
|
||||
class="absolute inset-0 bg-white"
|
||||
style="min-height: 12rem"
|
||||
aria-hidden="true"></div>
|
||||
|
||||
<div x-show="(!$store.ladillPayCheckout || !$store.ladillPayCheckout.frameable)
|
||||
|
||||
Reference in New Issue
Block a user