Skip to content

[Bug] Billing.vue hardcodes Razorpay checkout script regardless of configured payment gateway #2458

Description

@walidsaleh

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:

  1. 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.
  2. 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

  1. Install LMS via the frappe_docker stack.
  2. Configure LMS Settings.payment_gateway to "Stripe" (or any
    non-Razorpay gateway, including Paymob, Braintree, etc. from
    the payments app).
  3. Configure Stripe Settings with your test API keys.
  4. Open https://<your-site>/lms/billing/course/<slug> as a logged-in
    student.
  5. Open the Network tab in DevTools.
  6. 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).
  7. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions