Describe the bug
The student-facing billing page (/lms/billing/<type>/<slug>, served by
apps/lms/lms/public/frontend/src/pages/Billing.vue) injects the Razorpay
checkout script unconditionally in onMounted, regardless of which
payment gateway the operator has configured in LMS Settings.payment_gateway.
// apps/lms/lms/public/frontend/src/pages/Billing.vue (Billing.vue line 75)
onMounted(() => {
const script = document.createElement('script')
script.src = `https://checkout.razorpay.com/v1/checkout.js`
document.body.appendChild(script)
if (user.data?.name) {
access.submit()
}
})
This is problematic for two reasons:
- It pollutes the Network tab with a 404 red error
(https://checkout-static-next.razorpay.com/build/undefined) on every
billing page load, because the script's auto-loader fetches a
path-derived bundle that ends up as undefined when no key is set.
- It implicitly assumes Razorpay as the default gateway, which
is a problem for any operator who configures a different gateway
(Stripe, Paymob, Braintree, etc.). The payments app in Frappe
supports several gateways, but this Billing.vue script injection is
hardcoded to one specific provider.
The relevant backend code (apps/lms/lms/lms/payments.py) actually
DOES support arbitrary gateways via LMS Settings.payment_gateway →
payments.utils.get_payment_gateway_controller() — it's just the
frontend Billing.vue that hardcodes Razorpay.
To Reproduce
- Install LMS via the
frappe_docker stack.
- Configure
LMS Settings.payment_gateway to "Stripe" (or any
non-Razorpay gateway, including Paymob, Braintree, etc. from
the payments app).
- Configure
Stripe Settings with your test API keys.
- Open
https://<your-site>/lms/billing/course/<slug> as a logged-in
student.
- Open the Network tab in DevTools.
- Observe: a 404 red error on
https://checkout-static-next.razorpay.com/build/undefined (this
is the Razorpay script's auto-loaded bundle with an undefined
path component).
- No visible functional impact on the page itself — the script
loads, fails with 404 silently, and the page continues. But it's
a confusing artifact that suggests something is wrong.
Expected behavior
The Billing.vue should only inject the Razorpay checkout script if
LMS Settings.payment_gateway is set to "Razorpay" (or similar).
For other gateways (Stripe, Paymob, Braintree, etc.), the frontend
should either skip the script entirely or inject the relevant
gateway's checkout SDK (e.g. https://js.stripe.com/v3/ for Stripe).
Additional context
This is independent of (but related to) the PermissionError bug
filed at #2457. The backend has 2-3 issues in the same checkout
flow; the frontend has at least this one Razorpay hardcoding plus
the URL [object Object] bug when the redirect fails (filed at #2459).
We have a minimal workaround in our fork (Fisiocyl) — a small
nginx sub_filter that injects a JS shim into the HTML before the
LMS bundle, which neuters document.createElement('script') for
Razorpay URLs. Happy to share if useful, but the real fix lives in
the upstream.
Describe the bug
The student-facing billing page (
/lms/billing/<type>/<slug>, served byapps/lms/lms/public/frontend/src/pages/Billing.vue) injects the Razorpaycheckout script unconditionally in
onMounted, regardless of whichpayment gateway the operator has configured in
LMS Settings.payment_gateway.This is problematic for two reasons:
(
https://checkout-static-next.razorpay.com/build/undefined) on everybilling page load, because the script's auto-loader fetches a
path-derived bundle that ends up as
undefinedwhen no key is set.is a problem for any operator who configures a different gateway
(Stripe, Paymob, Braintree, etc.). The
paymentsapp in Frappesupports several gateways, but this Billing.vue script injection is
hardcoded to one specific provider.
The relevant backend code (
apps/lms/lms/lms/payments.py) actuallyDOES support arbitrary gateways via
LMS Settings.payment_gateway→payments.utils.get_payment_gateway_controller()— it's just thefrontend Billing.vue that hardcodes Razorpay.
To Reproduce
frappe_dockerstack.LMS Settings.payment_gatewayto "Stripe" (or anynon-Razorpay gateway, including Paymob, Braintree, etc. from
the
paymentsapp).Stripe Settingswith your test API keys.https://<your-site>/lms/billing/course/<slug>as a logged-instudent.
https://checkout-static-next.razorpay.com/build/undefined(thisis the Razorpay script's auto-loaded bundle with an
undefinedpath component).
loads, fails with 404 silently, and the page continues. But it's
a confusing artifact that suggests something is wrong.
Expected behavior
The Billing.vue should only inject the Razorpay checkout script if
LMS Settings.payment_gatewayis set to "Razorpay" (or similar).For other gateways (Stripe, Paymob, Braintree, etc.), the frontend
should either skip the script entirely or inject the relevant
gateway's checkout SDK (e.g.
https://js.stripe.com/v3/for Stripe).Additional context
This is independent of (but related to) the PermissionError bug
filed at #2457. The backend has 2-3 issues in the same checkout
flow; the frontend has at least this one Razorpay hardcoding plus
the URL [object Object] bug when the redirect fails (filed at #2459).
We have a minimal workaround in our fork (Fisiocyl) — a small
nginx sub_filter that injects a JS shim into the HTML before the
LMS bundle, which neuters
document.createElement('script')forRazorpay URLs. Happy to share if useful, but the real fix lives in
the upstream.