From f09d30c09b67963c52f686c084211cd4560d353d Mon Sep 17 00:00:00 2001 From: isaacclad Date: Thu, 23 Jul 2026 22:36:35 +0000 Subject: [PATCH] Force Paystack checkout into the sheet mount on first insert. Redirect first iframe append into the Ladill sheet body and keep absolute fill styles applied so checkout is not a second full-screen modal. --- .../views/partials/paystack-sheet.blade.php | 705 +++++++++++++----- 1 file changed, 523 insertions(+), 182 deletions(-) diff --git a/resources/views/partials/paystack-sheet.blade.php b/resources/views/partials/paystack-sheet.blade.php index 7baebd8..3c63f5e 100644 --- a/resources/views/partials/paystack-sheet.blade.php +++ b/resources/views/partials/paystack-sheet.blade.php @@ -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 @@ })(); @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') @endonce @@ -779,10 +1119,10 @@ {{-- - 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) --}}
- {{-- Floating close when Paystack owns the surface (Paystack has no host close for us) --}} - - - {{-- Standard POS header: loading, error, or same-origin waiting page --}} + {{-- Full header while loading / error / same-origin; compact bar once Paystack fills the body --}}
+ +

{{ $sheetTitle }}

+ +
+ {{-- Body: spinner while starting; Paystack fills this when ready --}} -
+ style="min-height: 22rem"> + {{-- Pin target for Paystack: keep in layout while inline checkout is active. --}}