BWH Payments

BWH Payments

Open in ChatGPT
Ask ChatGPT about this page
Open in Claude
Ask Claude about this page

Return pages

After paying, or giving up, the shopper is sent back to your site. BWH Payments doesn't include these pages. You build them in your own app, then enter their URLs in each gateway's settings.

You need two pages:

Page Enter it as It should
Confirmation Success URL (Telr: Authorised URL) Confirm the payment with the gateway, then show Paid, or We're confirming your payment while it's still Pending
Payment failed Failure URL, Cancelled URL (Telr: Declined URL, Cancelled URL) Say the payment didn't go through, and offer a retry or another payment method

Use full https:// URLs, for example https://shop.example.com/en/payment/confirmation.

What the gateway adds to the URL

Each gateway sends the shopper back with a query parameter that identifies the payment:

Gateway Parameter Holds
Stripe session_id The gateway session id, which is the request's order_ref
Razorpay reference_id The Gateway Payment Request's name
Telr reference_id The Gateway Payment Request's name
Tabby reference_id The Gateway Payment Request's name

Razorpay and Tabby add their own parameters as well, which you can ignore. Your page should accept either value, and look the request up by order_ref first, then by name.

Confirm the payment

The redirect proves nothing: anyone can open your success URL. Ask the gateway instead, with sync_status(), from a POST request.

# your_app/api.py
import frappe
from frappe import _


@frappe.whitelist(allow_guest=True, methods=["POST"])
def confirm_payment(reference: str) -> dict:
	name = frappe.db.get_value(
		"Gateway Payment Request", {"order_ref": reference}, "name"
	) or frappe.db.exists("Gateway Payment Request", reference)
	if not name:
		frappe.throw(_("Payment not found"), frappe.DoesNotExistError)

	payment_request = frappe.get_doc("Gateway Payment Request", name)
	validate_shopper_owns(payment_request)  # your check: is ref_docname theirs?

	if payment_request.status == "Pending":
		payment_request.sync_status()

	return {"status": payment_request.status}

Call it when the page loads:

const params = new URLSearchParams(window.location.search);
const reference = params.get('session_id') || params.get('reference_id');

const response = await frappe.call({
	method: 'your_app.api.confirm_payment',
	args: { reference },
	type: 'POST',
});
showPaymentStatus(response.message.status);

If the status is still Pending

The shopper can arrive before the gateway has settled the payment. Show a "confirming your payment" state, and call confirm_payment again after a few seconds. The webhook usually settles it shortly afterwards.

Some shoppers never come back to your site, for example when they close the tab after paying. Run a scheduled job that calls sync_status() on requests that are still Pending, so those payments settle too. Telr has no trustworthy webhook, and Tabby captures payments only on a status check, so both depend on this.

Language

For Razorpay, Telr and Tabby, a language segment at the start of your URL's path is switched to the shopper's current language. https://shop.example.com/en/payment/confirmation becomes /ar/payment/confirmation for an Arabic session. Stripe uses your URLs exactly as entered.

Next steps

Last updated 3 weeks ago
Was this helpful?
Thanks!