Unify payment sheet to one POS design across all apps.
Deploy Ladill Merchant / deploy (push) Successful in 1m44s

Loading uses the standard white bottomsheet/modal; once Paystack Inline is
ready the host shell goes transparent so only Paystack's card shows on the
backdrop — no oversized second white frame on Merchant/Give/etc.
This commit is contained in:
isaacclad
2026-07-21 23:15:56 +00:00
parent 99d3053508
commit 131df28d99
@@ -233,6 +233,7 @@
s.setProperty('visibility', 'visible', 'important'); s.setProperty('visibility', 'visible', 'important');
s.setProperty('display', 'block', 'important'); s.setProperty('display', 'block', 'important');
s.setProperty('background', 'transparent', 'important'); s.setProperty('background', 'transparent', 'important');
s.setProperty('background-color', 'transparent', 'important');
s.setProperty('max-width', 'none', 'important'); s.setProperty('max-width', 'none', 'important');
s.setProperty('max-height', 'none', 'important'); s.setProperty('max-height', 'none', 'important');
applyIframePermissions(iframe); applyIframePermissions(iframe);
@@ -681,7 +682,15 @@
overflow: hidden; overflow: hidden;
background: #fff; background: #fff;
} }
/* Paystack draws its own modal chrome inside the iframe — give it the full body. */ /* 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-panel][data-paystack-live="1"] [data-ladill-pay-mount] {
background: transparent !important;
}
/* 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[data-ladill-pay-contained],
[data-ladill-pay-mount] iframe[id^="inline-checkout"], [data-ladill-pay-mount] iframe[id^="inline-checkout"],
[data-ladill-pay-mount] iframe[id^="embed-checkout"] { [data-ladill-pay-mount] iframe[id^="embed-checkout"] {
@@ -701,14 +710,13 @@
z-index: 2 !important; z-index: 2 !important;
visibility: visible !important; visibility: visible !important;
display: block !important; display: block !important;
background: #fff !important; background: transparent !important;
color-scheme: light !important; color-scheme: light !important;
} }
/* If a Paystack frame escapes the mount, pin it over the panel body instead of full viewport. */ /* If a Paystack frame escapes the mount, pin it over the mount box. */
html.ladill-pay-inline-active body > iframe[id^="inline-checkout"], 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"] {
position: fixed !important; position: fixed !important;
/* Fallback rect; JS updates these CSS vars from the mount box. */
left: var(--ladill-pay-left, 0) !important; left: var(--ladill-pay-left, 0) !important;
top: var(--ladill-pay-top, 0) !important; top: var(--ladill-pay-top, 0) !important;
width: var(--ladill-pay-width, 100%) !important; width: var(--ladill-pay-width, 100%) !important;
@@ -721,7 +729,7 @@
z-index: 10000 !important; z-index: 10000 !important;
visibility: visible !important; visibility: visible !important;
display: block !important; display: block !important;
background: #fff !important; background: transparent !important;
} }
</style> </style>
@endonce @endonce
@@ -771,17 +779,12 @@
</div> </div>
{{-- {{--
Single payment surface (same pattern as POS): ONE design everywhere (POS / Merchant / Mini / Give / Invoice / Events):
- Mobile: bottom sheet - Loading / error / same-origin: white bottomsheet (mobile) or modal (desktop)
- Desktop: centered modal, wide enough for Paystack channel sidebar - Paystack Inline ready: transparent shell so Paystack's own card is the only surface
- When Inline is ready: minimal chrome so Paystack fills the body (no double header/footer) on the dimmed backdrop (no second white frame / double modal)
--}} --}}
<div data-ladill-pay-panel <div data-ladill-pay-panel
class="relative flex w-full flex-col overflow-hidden rounded-t-2xl bg-white shadow-2xl md:rounded-2xl"
:class="($store.ladillPayCheckout && $store.ladillPayCheckout.inline && !$store.ladillPayCheckout.launching && !$store.ladillPayCheckout.error)
? 'md:max-w-3xl'
: 'md:max-w-lg'"
style="height: min(92dvh, 760px); max-height: 92dvh; padding-bottom: env(safe-area-inset-bottom, 0px)"
x-show="showSheet" x-show="showSheet"
x-transition:enter="transition duration-300 ease-out md:duration-200" 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-start="translate-y-full opacity-0 md:translate-y-0 md:scale-95"
@@ -789,9 +792,30 @@
x-transition:leave="transition duration-200 ease-in md:duration-150" 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-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" 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"
: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);'"
@click.stop> @click.stop>
{{-- Full title header only while loading / error / same-origin frame --}} {{-- 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 --}}
<div x-show="!$store.ladillPayCheckout <div x-show="!$store.ladillPayCheckout
|| $store.ladillPayCheckout.frameable || $store.ladillPayCheckout.frameable
|| $store.ladillPayCheckout.launching || $store.ladillPayCheckout.launching
@@ -821,46 +845,28 @@
</div> </div>
</div> </div>
{{-- Minimal chrome once Paystack owns the body (one close, no second title stack) --}} {{-- Body: spinner while starting; Paystack fills this when ready --}}
<div x-show="$store.ladillPayCheckout <div class="relative min-h-0 flex-1 overflow-hidden overscroll-contain"
&& $store.ladillPayCheckout.inline
&& !$store.ladillPayCheckout.launching
&& !$store.ladillPayCheckout.error
&& !$store.ladillPayCheckout.frameable"
class="relative z-20 flex shrink-0 items-center justify-between px-3 pb-1 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"
@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: Paystack mounts here (same area as the spinner). --}}
<div class="relative min-h-0 flex-1 overflow-hidden overscroll-contain bg-white"
data-ladill-pay-body data-ladill-pay-body
style="min-height: 22rem"> :class="($store.ladillPayCheckout && $store.ladillPayCheckout.inline && !$store.ladillPayCheckout.launching && !$store.ladillPayCheckout.error && !$store.ladillPayCheckout.frameable)
{{-- Same-origin waiting pages (e.g. MoMo status) --}} ? 'bg-transparent'
: 'bg-white'"
style="min-height: 20rem">
<iframe x-show="$store.ladillPayCheckout && $store.ladillPayCheckout.frameable" <iframe x-show="$store.ladillPayCheckout && $store.ladillPayCheckout.frameable"
:src="showSheet && $store.ladillPayCheckout && $store.ladillPayCheckout.frameable ? checkoutUrl : ''" :src="showSheet && $store.ladillPayCheckout && $store.ladillPayCheckout.frameable ? checkoutUrl : ''"
class="absolute inset-0 h-full w-full border-0" class="absolute inset-0 h-full w-full border-0"
allow="payment *; fullscreen *; clipboard-read *; clipboard-write *" allow="payment *; fullscreen *; clipboard-read *; clipboard-write *"
title="{{ $iframeTitle }}"></iframe> title="{{ $iframeTitle }}"></iframe>
{{-- Paystack Inline checkout iframe is reparented into this mount --}}
<div x-show="!$store.ladillPayCheckout || !$store.ladillPayCheckout.frameable" <div x-show="!$store.ladillPayCheckout || !$store.ladillPayCheckout.frameable"
data-ladill-pay-mount data-ladill-pay-mount
id="ladill-paystack-mount" id="ladill-paystack-mount"
class="absolute inset-0 bg-white" class="absolute inset-0"
:class="($store.ladillPayCheckout && $store.ladillPayCheckout.inline && !$store.ladillPayCheckout.launching && !$store.ladillPayCheckout.error)
? 'bg-transparent'
: 'bg-white'"
aria-hidden="true"></div> aria-hidden="true"></div>
{{-- Loading / error only hides once Paystack has loaded inside the mount --}}
<div x-show="(!$store.ladillPayCheckout || !$store.ladillPayCheckout.frameable) <div x-show="(!$store.ladillPayCheckout || !$store.ladillPayCheckout.frameable)
&& (!$store.ladillPayCheckout && (!$store.ladillPayCheckout
|| $store.ladillPayCheckout.launching || $store.ladillPayCheckout.launching
@@ -909,7 +915,6 @@
</div> </div>
</div> </div>
{{-- Footer only before Paystack owns the body (avoids double trust messaging) --}}
@if (! empty($sheetFooter)) @if (! empty($sheetFooter))
<p x-show="!$store.ladillPayCheckout <p x-show="!$store.ladillPayCheckout
|| $store.ladillPayCheckout.frameable || $store.ladillPayCheckout.frameable