Keep Mini QR Paystack checkout fully in-app.
Stop form POSTs from 307ing to ladl.link or redirecting away to checkout.paystack.com, open Paystack Inline with access_code, and use the single-chrome sheet so payment stays on the Mini page.
This commit is contained in:
@@ -23,8 +23,13 @@
|
||||
x-data="miniPaymentLanding({
|
||||
payUrl: @js($payUrl),
|
||||
csrf: @js($csrf),
|
||||
amount: @js(old('amount')),
|
||||
amount: @js(old('amount', session('amount'))),
|
||||
errorMsg: @js(session('error')),
|
||||
bootstrapCheckoutUrl: @js(session('checkout_url')),
|
||||
bootstrapAccessCode: @js(session('access_code')),
|
||||
bootstrapPublicKey: @js(session('public_key')),
|
||||
bootstrapCallbackUrl: @js(session('callback_url')),
|
||||
bootstrapProvider: @js(session('provider', 'paystack')),
|
||||
})">
|
||||
|
||||
{{-- Mobile: Ladill Mini branding + fixed payment sheet --}}
|
||||
@@ -123,112 +128,5 @@
|
||||
</main>
|
||||
|
||||
@include('partials.paystack-sheet')
|
||||
|
||||
{{--
|
||||
Safety net: if Alpine/Vite fails to boot, native form POST still works.
|
||||
If Alpine boots but miniPaymentLanding was missing from an old bundle, redefine it.
|
||||
--}}
|
||||
<script>
|
||||
document.addEventListener('alpine:init', () => {
|
||||
const Alpine = window.Alpine;
|
||||
if (!Alpine || typeof Alpine.data !== 'function') return;
|
||||
// Re-register so Pay works even when Blade deploys ahead of an old Vite bundle.
|
||||
Alpine.data('miniPaymentLanding', (config = {}) => ({
|
||||
amount: config.amount ?? '',
|
||||
loading: false,
|
||||
errorMsg: config.errorMsg ?? '',
|
||||
showSheet: false,
|
||||
checkoutUrl: '',
|
||||
accessCode: '',
|
||||
publicKey: '',
|
||||
returnUrl: '',
|
||||
paymentSheetStyle: '',
|
||||
sheetBleedStyle: '',
|
||||
init() {
|
||||
this._syncSheet = () => {
|
||||
if (window.innerWidth >= 768) {
|
||||
this.paymentSheetStyle = '';
|
||||
this.sheetBleedStyle = '';
|
||||
return;
|
||||
}
|
||||
const vp = window.visualViewport;
|
||||
const offset = vp ? Math.max(0, Math.round(window.innerHeight - vp.height - vp.offsetTop)) : 0;
|
||||
const safePad = offset > 0 ? '1.25rem' : 'max(1.25rem, env(safe-area-inset-bottom))';
|
||||
this.paymentSheetStyle = `bottom: ${offset}px; padding-bottom: ${safePad};`;
|
||||
this.sheetBleedStyle = `bottom: ${offset}px;`;
|
||||
};
|
||||
this._onViewportChange = () => this._syncSheet();
|
||||
window.visualViewport?.addEventListener('resize', this._onViewportChange);
|
||||
window.visualViewport?.addEventListener('scroll', this._onViewportChange);
|
||||
if (window.innerWidth < 768) document.documentElement.classList.add('mini-payment-page');
|
||||
this._syncSheet();
|
||||
},
|
||||
async submitPay() {
|
||||
const value = parseFloat(this.amount);
|
||||
if (!value || value <= 0) {
|
||||
this.errorMsg = 'Enter an amount greater than zero.';
|
||||
return;
|
||||
}
|
||||
this.errorMsg = '';
|
||||
this.loading = true;
|
||||
this.checkoutUrl = '';
|
||||
this.accessCode = '';
|
||||
this.publicKey = '';
|
||||
this.returnUrl = '';
|
||||
// Open sheet chrome immediately (mobile bottom sheet / desktop modal).
|
||||
this.showSheet = true;
|
||||
const controller = new AbortController();
|
||||
const timer = setTimeout(() => controller.abort(), 45000);
|
||||
try {
|
||||
const res = await fetch(config.payUrl, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Accept: 'application/json',
|
||||
'X-CSRF-TOKEN': config.csrf,
|
||||
'X-Requested-With': 'XMLHttpRequest',
|
||||
},
|
||||
body: JSON.stringify({ amount: value }),
|
||||
signal: controller.signal,
|
||||
});
|
||||
const data = await res.json().catch(() => ({}));
|
||||
const apiError = typeof data.error === 'string'
|
||||
? data.error
|
||||
: (typeof data.message === 'string' ? data.message : '');
|
||||
if (!res.ok || apiError) {
|
||||
this.showSheet = false;
|
||||
this.errorMsg = apiError || ('Could not start payment (' + res.status + '). Please try again.');
|
||||
return;
|
||||
}
|
||||
if (!data.checkout_url && !data.access_code) {
|
||||
this.showSheet = false;
|
||||
this.errorMsg = 'Could not start payment. Please try again.';
|
||||
return;
|
||||
}
|
||||
// Legacy MoMo waiting page must be full-page — never trap it in the Paystack iframe.
|
||||
const useSheet = data.provider !== 'mtn_momo';
|
||||
if (useSheet) {
|
||||
this.checkoutUrl = data.checkout_url || '';
|
||||
this.accessCode = data.access_code || '';
|
||||
this.publicKey = data.public_key || '';
|
||||
this.returnUrl = data.callback_url || '';
|
||||
} else {
|
||||
this.showSheet = false;
|
||||
window.location.assign(data.checkout_url);
|
||||
return;
|
||||
}
|
||||
} catch (e) {
|
||||
this.showSheet = false;
|
||||
this.errorMsg = e?.name === 'AbortError'
|
||||
? 'Payment is taking too long. Please try again.'
|
||||
: 'Network error. Please try again.';
|
||||
} finally {
|
||||
clearTimeout(timer);
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
}));
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user