BWH Payments

BWH Payments

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

Gateway reference

Every gateway is a Single DocType whose controller extends both Document and PaymentGatewayBase:

from frappe.model.document import Document

from bwh_payments.base_class import PaymentGatewayBase


class AcmePayGatewaySettings(Document, PaymentGatewayBase): ...
Method Required Called when
create_session ✅ A Gateway Payment Request is created
get_payment_status ✅ An app checks a payment without waiting for a webhook
refund_payment ✅ A refund is issued
handle_webhook ✅ The gateway posts to the webhook endpoint
cancel_session Your app calls release_if_unpaid() on an open checkout
get_gateway_name Never by BWH Payments. It's for your own use.

Rules for every method

  • Money is in major units. 49.99 means $49.99. Convert with the currency helpers below, and use the same conversion for charges and refunds.
  • Statuses are BWH Payments statuses, already mapped from the gateway's own wording: Pending, Paid, Not Paid, Cancelled, Expired.
  • To report failure, throw. Use frappe.throw with a message a person can act on.

create_session

def create_session(
	self,
	amount: float,
	currency: str,
	reference: str | None = None,
	customer: dict | None = None,
) -> dict

Opens a hosted checkout. Runs when a Gateway Payment Request is saved without a gateway session, which normally means once, on insert.

Parameters

Name Type Description
amount float Amount to charge, in major units
currency str ISO 4217 code, for example USD or KWD
reference str The Gateway Payment Request name. Send it as the gateway's merchant reference.
customer dict The shopper. See Customer below. Any field may be empty.

Returns

Key Required Description
session_id ✅ The gateway's id for this checkout, stored as the request's order_ref. It must be the id the gateway's webhooks and status endpoint use.
redirect_url ✅ The hosted checkout URL the shopper is sent to
success_url Where the shopper returns after paying
cancel_url Where the shopper returns after cancelling
failure_url Where the shopper returns after a failed payment

Throw when the currency isn't supported, a required setting is missing, or the gateway refuses to open a checkout. The request isn't saved, and your message is raised to the app that created it.


get_payment_status

def get_payment_status(self, session_id: str) -> str

Reads the current status from the gateway's API. It is called:

  • when the shopper returns to the site
  • by your own scheduled job, if you run one to settle Pending payments no one confirmed
  • by release_if_unpaid(), before an open checkout is cancelled

For gateways with no trustworthy webhook, this is the only source of truth.

Parameters

Name Type Description
session_id str The session_id your create_session returned

Returns one of Pending, Paid, Not Paid, Cancelled, Expired.

  • Map anything you don't recognise to Pending.
  • Return Paid only when the money has actually been captured.
  • Only a request that is still Pending changes. Once it's Paid, Cancelled, Not Paid or Expired, a later answer is ignored.

refund_payment

def refund_payment(self, session_id: str, amount: float, currency: str | None = None) -> dict

Refunds part or all of a paid checkout. Before your method runs, BWH Payments has already:

  • checked the payment is refundable
  • checked amount isn't more than what's left
  • skipped a refund Payment Entry (or an amendment of one) that was already refunded

Parameters

Name Type Description
session_id str The checkout to refund
amount float Amount to refund, in major units
currency str The payment's currency

Returns

Key Description
refund_id The gateway's refund id. Every partial refund's id is kept for reconciliation.
status Informational: the gateway's own refund status, for example succeeded or pending
amount Informational: the amount refunded, in major units. The refunded total is increased by the amount requested, not this value.

Throw when the gateway refuses the refund. The payment's refund total and status are left unchanged.


handle_webhook

def handle_webhook(self, payload: bytes, headers: dict) -> dict

Verifies a delivery to the webhook endpoint and says what it means.

Parameters

Name Type Description
payload bytes The raw request body, exactly as received. Verify signatures against this.
headers dict Request headers, with title-case names such as X-Acme-Signature

Returns

Return Meaning HTTP response
(throws) Verification failed 400
{} Genuine, but not something to act on 200
{"session_id", "status", "event_id"} Apply status to the checkout with this session_id 200

event_id must stay the same when the gateway retries a delivery, and differ between events. A repeated event_id is ignored.

See Webhooks and security.


cancel_session

def cancel_session(self, session_id: str) -> bool

Optional. The default returns False.

Cancels an open checkout. Called by release_if_unpaid(). Return True only when the gateway confirms the checkout can no longer be paid. On any gateway error, return False instead of throwing.

If this returns False, the payment stays Pending until the gateway settles it, and release_if_unpaid() returns False.


get_gateway_name

def get_gateway_name(self) -> str

Optional. The default is the class name. Returns a human-readable name, for example "Acme Pay". BWH Payments doesn't call it. It's there for your own code.


Customer

The customer argument to create_session:

{
	"ref": "CUST-0001",  # linked customer id, or ""
	"email": "shopper@example.com",  # or ""
	"phone": "+15550100",  # or ""
	"name": {"forenames": "Ada", "surname": "Lovelace"},
	"address": {"line1": "1 Main St", "city": "Dubai", "country": "United Arab Emirates"},  # or None
}

Guest checkouts often leave fields empty. Drop the empty ones before you send the dict to the gateway.

Currency helpers

from bwh_payments.currency import (
	from_minor_units,
	get_minor_unit_exponent,
	to_minor_units,
	validate_transaction_currency,
)
Helper Example Use it to
to_minor_units(amount, currency) (49.99, "USD") → 4999, (12.345, "KWD") → 12345 Send amounts to gateways that bill in cents
from_minor_units(amount, currency) (4999, "USD") → 49.99 Read the gateway's echoed amounts
get_minor_unit_exponent(currency) "KWD" → 3, "JPY" → 0, "USD" → 2 Format major-unit strings, e.g. f"{amount:.{exponent}f}"
validate_transaction_currency(currency) Throws for an unknown code Check the currency exists on the site before calling out

Exponents follow ISO 4217. They aren't read from the site's Currency settings.

URL helper

from bwh_payments.bwh_payments.utils import get_localised_url

get_localised_url(url) swaps a leading language segment, such as /en/, for the shopper's current language, so they return to the page in their own language. Use it on every return URL you send the gateway.

Last updated 3 weeks ago
Was this helpful?
Thanks!