Skip to content

feat: add Braintree gateway - #23

Draft
ishad05 wants to merge 3 commits into
bwhtech:v1-stablefrom
ishad05:feat/braintree-gateway
Draft

ishad05 wants to merge 3 commits into
bwhtech:v1-stablefrom
ishad05:feat/braintree-gateway

Conversation

@ishad05

@ishad05 ishad05 commented Oct 12, 2026 •

Copy link
Copy Markdown

Changes

  • Braintree Gateway Settings: Sandbox or Production keys, one merchant account per currency (Braintree Merchant Account table), 3-D Secure, and return URLs. Saving with Enabled checks the keys and every merchant account with Braintree.
  • Checkout page /braintree-checkout: Drop-in 1.44.1 with 3-D Secure. A declined card stays on the page so the shopper can try another.
  • braintree_checkout.pay(session, nonce): charges the request's own amount, once per session even with repeated clicks, then places the order as the shopper, so it shows in their account even if they close the tab.
  • Gateway (services/braintree/{base,live,stub}.py):
    • only currencies with a merchant account are offered at checkout
    • refunds: partial refunds after settlement; before settlement, only a full refund (a void)
    • webhooks are verified, then ignored
  • Listed by api.get_gateways() with the reusable_checkout capability, because the checkout page stays payable until the session is charged.

Testing

  • 263 tests pass, 40 of them new.
  • Braintree Sandbox on a real site:
    • Card 4111 1111 1111 1111 with the 3-D Secure challenge: paid, order placed
    • As a signed-in customer: Sales Order, Sales Invoice and Payment Entry created, order shows in their account
    • Three simultaneous Pay clicks: one charge
    • Declined card: payment stays open for another card
    • Refunds before and after settlement
    • Invalid payment links and wrong keys are refused

Setup

  1. Fill in Braintree Gateway Settings: merchant ID, public key, private key, and one merchant account per currency.
  2. Enable a Payment Gateway Profile named Braintree.
  3. Give the Braintree Mode of Payment a default account for each company.
  4. Optional: register the webhook at /api/method/bwh_payments.bwh_payments.webhook.handle?gateway=Braintree.

Dependency

Built on #7 (PayPal). Review the last commit until #7 merges.

@ishad05
ishad05 marked this pull request as draft October 12, 2026 06:23
@ishad05
ishad05 changed the base branch from develop to v1-stable October 12, 2026 06:27
@ishad05 ishad05 changed the title Feat/braintree gateway feat: add Braintree gateway Oct 12, 2026
@ishad05
ishad05 force-pushed the feat/braintree-gateway branch 2 times, most recently from 914ef38 to da0deaf Compare October 12, 2026 15:47
ishad05 and others added 3 commits October 12, 2026 22:34
A consumer building a gateway settings screen had to keep its own list of
gateways, with each one's description, docs link and settings DocType, so
every new gateway meant a change in each consumer.

- api.get_gateways() returns a GatewayInfo (name, settings_doctype,
  description, docs_url, capabilities, logos) for every gateway Settings
  Single on the site, enabled or not, including another app's gateways.
- The contract gains description, docs_url and capabilities class
  attributes. REUSABLE_CHECKOUT says the checkout link stays payable after
  the shopper leaves it.
- Razorpay, Stripe, Telr and Tabby describe themselves.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PayPal Orders v2 behind the gateway contract, on the base/live/stub
service-client layout:

- PayPal Gateway Settings (Sandbox/Live client credentials, webhook ID,
  return URLs) and services/paypal/{base,live,stub}.py.
- Checkout creates an order and sends the shopper to PayPal's approval
  link; the return page and the CHECKOUT.ORDER.APPROVED webhook both
  capture, and losing that race (ORDER_ALREADY_CAPTURED) counts as paid.
- Webhooks are verified by calling PayPal back with the webhook ID.
- supported_currencies() leaves out what PayPal cannot settle (SAR, AED,
  KWD, BHD, QAR, INR); HUF, JPY and TWD are charged in whole units.
- Refunds go against the completed capture and report processed or
  pending; get_refund_status settles a pending one, get_transaction_ref
  returns the capture, and create_session honours return_url.
- get_gateways() lists PayPal with REUSABLE_CHECKOUT: an approval link
  stays payable until the order is captured or expires.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Braintree has no hosted payment page, so this app serves one at
/braintree-checkout with the Drop-in card form, and create_session points
redirect_url there. The rest of the flow works as for a hosted gateway.

- Braintree Gateway Settings: Sandbox/Production keys, one merchant
  account per currency, 3-D Secure, return URLs; saving with Enabled
  checks the keys and every merchant account.
- braintree_checkout.pay(session, nonce) charges the request's own amount
  once per session, records the sale and places the order as the shopper.
- services/braintree/{base,live,stub}.py: status map, supported_currencies
  from the merchant accounts, refunds (partial after settlement, a full
  void before), get_refund_status, get_transaction_ref, return_url, and
  webhooks verified locally then ignored.
- get_gateways() lists Braintree with REUSABLE_CHECKOUT: the checkout page
  stays payable until the session is charged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@ishad05
ishad05 force-pushed the feat/braintree-gateway branch from da0deaf to dc5e7b3 Compare October 12, 2026 17:10
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