Skip to content

feat: add PayU India gateway - #26

Merged
harshtandiya merged 10 commits into
v1-stablefrom
feat/payu-gateway
Oct 14, 2026
Merged

harshtandiya merged 10 commits into
v1-stablefrom
feat/payu-gateway

Conversation

@harshtandiya

Copy link
Copy Markdown

Summary

Adds PayU India (payu.in) next to Razorpay. Callers keep using create_payment, refund and webhooks unchanged.

  • PayU Gateway Settings Single, plus services/payu/{base,live,stub}.py (raw HTTP to postservice.php, no SDK).
  • Checkout is a browser form POST. create_session makes no PayU call: order_url is our guest start_checkout page, which renders the signed, escaped, auto-submitting form.
  • PayU returns the shopper by a cross-site POST, which carries no Lax cookies. return_from_payu ignores the body, drops the sid=Guest cookies Frappe queued, and redirects to the stored success or failure URL. The shopper stays signed in.
  • Status comes from verify_payment (unmappedstatus). Refunds use cancel_refund_transaction and settle through check_action_status.
  • Payment webhooks are checked with the reverse hash (with our own key, and additionalCharges when present). Refund webhooks are unsigned, so the refund is read back from PayU and the body is ignored.
  • INR only.
  • Shared fix: record_external_refund refuses a refund with no amount, instead of recording it as 0.

Tests

  • 31 new stub-driven tests, including a spine test through webhook.handle.
  • Full suite: 211 OK (5 skipped) on a local bench.
  • pre-commit is clean. The leak check found no PayU profile, settings or Integration Requests after the run.
  • Mutating either hash formula fails the tests.

Manual PayU sandbox check (live client is not tested)

  1. create_payment → order_url → pay with a test card.
  2. Land on redirect_to and still be signed in.
  3. The webhook marks the request Paid.
  4. A partial refund settles through the refund webhook.
  5. Confirm the check_action_status reply shape and its amount field (amt or amount).
  6. Confirm that a refund reply always carries request_id.
  7. Confirm whether the account validates with the Salt v2 or the v1 salt.
  8. Confirm that a firstname with punctuation is accepted.

PayU checkout is a browser form POST, so create_session makes no PayU
call: order_url is our start_checkout page, which renders the signed
form. PayU returns the shopper by a cross-site POST, so surl/furl go
through return_from_payu, which drops the sid=Guest cookies and
redirects to the stored URL.

Status, refunds and refund status use postservice.php commands over raw
HTTP. Payment webhooks are checked with the reverse hash; refund
webhooks are unsigned, so the refund is read back from PayU.

record_external_refund now refuses a refund with no amount instead of
recording it as zero.
@github-actions

github-actions Bot commented Oct 14, 2026 •

Copy link
Copy Markdown

Backend test coverage: 83%

Commit b7ce093. Test modules are excluded.

Coverage by file
Name Stmts Miss Cover
bwh_payments/__init__.py 1 1 0%
bwh_payments/api.py 40 0 100%
bwh_payments/base_class.py 47 3 94%
bwh_payments/bwh_payments/checkout.py 25 4 84%
bwh_payments/bwh_payments/doctype/gateway_payment_request/gateway_payment_request.py 200 18 91%
bwh_payments/bwh_payments/doctype/gateway_refund/gateway_refund.py 46 1 98%
bwh_payments/bwh_payments/doctype/payment_gateway_profile/payment_gateway_profile.py 27 3 89%
bwh_payments/bwh_payments/doctype/payu_gateway_settings/payu_gateway_settings.py 143 5 97%
bwh_payments/bwh_payments/doctype/razorpay_gateway_settings/razorpay_gateway_settings.py 124 4 97%
bwh_payments/bwh_payments/doctype/stripe_gateway_settings/stripe_gateway_settings.py 79 9 89%
bwh_payments/bwh_payments/doctype/tabby_gateway_settings/tabby_gateway_settings.py 133 11 92%
bwh_payments/bwh_payments/doctype/telr_gateway_settings/telr_gateway_settings.py 61 6 90%
bwh_payments/bwh_payments/utils.py 49 9 82%
bwh_payments/bwh_payments/webhook.py 96 8 92%
bwh_payments/currency.py 18 1 94%
bwh_payments/hooks.py 11 0 100%
bwh_payments/migrate/frappe_payments.py 51 43 16%
bwh_payments/services/__init__.py 3 0 100%
bwh_payments/services/payu/base.py 22 0 100%
bwh_payments/services/payu/live.py 53 36 32%
bwh_payments/services/payu/stub.py 76 2 97%
bwh_payments/services/razorpay/base.py 15 0 100%
bwh_payments/services/razorpay/live.py 61 39 36%
bwh_payments/services/razorpay/stub.py 68 1 99%
bwh_payments/services/stripe/base.py 15 0 100%
bwh_payments/services/stripe/live.py 17 7 59%
bwh_payments/services/stripe/stub.py 63 3 95%
bwh_payments/services/tabby/base.py 31 0 100%
bwh_payments/services/tabby/live.py 59 39 34%
bwh_payments/services/tabby/stub.py 52 1 98%
bwh_payments/services/telr/base.py 18 0 100%
bwh_payments/services/telr/live.py 91 72 21%
bwh_payments/services/telr/stub.py 43 2 95%
bwh_payments/testing.py 70 1 99%
TOTAL 1908 329 83%

19 empty files skipped.

@harshtandiya
harshtandiya merged commit 091f6c8 into v1-stable Oct 14, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant