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:
isaacclad
2026-07-21 21:32:36 +00:00
parent 86d2dcbf0b
commit a50b5c6a44
8 changed files with 275 additions and 249 deletions
+38 -13
View File
@@ -201,6 +201,17 @@ Alpine.data('miniPaymentLanding', (config = {}) => ({
}
this._syncSheet();
// Non-JS form POST fallback flashes checkout payload — open Inline in-page.
if (config.bootstrapCheckoutUrl || config.bootstrapAccessCode) {
this.openInlineCheckout({
checkout_url: config.bootstrapCheckoutUrl || '',
access_code: config.bootstrapAccessCode || '',
public_key: config.bootstrapPublicKey || '',
callback_url: config.bootstrapCallbackUrl || '',
provider: config.bootstrapProvider || 'paystack',
});
}
},
destroy() {
@@ -211,6 +222,30 @@ Alpine.data('miniPaymentLanding', (config = {}) => ({
document.documentElement.classList.remove('mini-payment-page');
},
isSameOrigin(url) {
if (!url) return false;
try {
return new URL(url, window.location.href).origin === window.location.origin;
} catch (e) {
return false;
}
},
openInlineCheckout(data = {}) {
// Never navigate to checkout.paystack.com. Same-origin MoMo waiting can navigate.
if (data.provider === 'mtn_momo' && this.isSameOrigin(data.checkout_url)) {
window.location.assign(data.checkout_url);
return false;
}
this.checkoutUrl = data.checkout_url || '';
this.accessCode = data.access_code || '';
this.publicKey = data.public_key || '';
this.returnUrl = data.callback_url || '';
this.showSheet = true;
return true;
},
async submitPay() {
const value = parseFloat(this.amount);
if (!value || value <= 0) {
@@ -224,8 +259,8 @@ Alpine.data('miniPaymentLanding', (config = {}) => ({
this.accessCode = '';
this.publicKey = '';
this.returnUrl = '';
// Show sheet/modal immediately; Paystack Inline opens in-page from the sheet.
this.showSheet = true;
// Keep sheet closed until we have a checkout payload so Inline syncs once with access_code.
this.showSheet = false;
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), 45000);
@@ -257,17 +292,7 @@ Alpine.data('miniPaymentLanding', (config = {}) => ({
return;
}
// Legacy MoMo waiting page must be full-page — never trap it in the Paystack iframe
// (cross-origin iframe is blank / looks like "Pay did nothing").
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);
if (!this.openInlineCheckout(data)) {
return;
}
} catch (e) {