Skip to content

feat(payment): charge card-country regional price at checkout - #1045

Merged
rdahis merged 1 commit into
stagingfrom
feat/regional-pricing
Aug 5, 2026
Merged

feat(payment): charge card-country regional price at checkout#1045
rdahis merged 1 commit into
stagingfrom
feat/regional-pricing

Conversation

@rdahis

@rdahis rdahis commented Aug 5, 2026

Copy link
Copy Markdown
Member

What

Adds the server-side guardrail for region-aware pricing: BD Pro and the Chatbot sell in BRL for Brazilian cards, and in USD at a Latin-America tier and an international tier for everyone else. The storefront picks the price to show by domain (website PR feat/regional-price-display); this picks the price to charge, from the country of the card the customer actually used — so switching to a cheaper regional domain does not change what they pay.

Changes

  • regional_pricing.py (new) — pure, DB-free logic:
    • country_to_region(iso)br / latam / intl.
    • resolve_regional_price_id(prices, id, region) finds the region-matching price for the same product code and interval, falling back to the original price on any uncertainty (not found, already matching, no active sibling) so a subscription is never blocked.
  • webhooks.py setup_intent.succeeded — reads the card country from the attached payment method and swaps in the regional price before customer.subscribe. Any Stripe/network error keeps the checked-out price. Product type and trial logic are unaffected (the swap preserves the product code).
  • graphql.py StripePriceNode.region — exposes the price's region metadata so the storefront can select the right currency by domain.

Scope

Enforcement covers the trial / SetupIntent path, where the card is known at subscribe time. The non-trial default_incomplete path has no card yet and is a follow-up.

Tests

  • Pure logic (country_to_region, resolve_regional_price_id) is unit-tested and passes standalone (14/14).
  • The webhook glue (_card_country, _regional_price_id) is mock-tested — no live Stripe or DB.

Deploy note

Ship together with the website feat/regional-price-display PR. Enforcing here while the site still shows only BRL would show R$ but charge US$. The website degrades safely to BRL, so website-first or simultaneous is fine; backend-enforcement-first is not.

Assumption

The regional sibling is matched by product code (bd_pro / chatbot) + interval + region metadata. The USD prices must carry the same code as their BRL counterparts (same product or separate — only the code needs to match) and a region tag.

Data Basis sells BD Pro and the Chatbot at different prices by region:
BRL for Brazilian cards, USD at a Latin-America tier and an international
tier for everyone else. The storefront picks the price to *show* by
domain; this adds the server-side guardrail that picks the price to
*charge*, from the country of the card the customer actually used — so
switching to a cheaper regional domain does not change what they pay.

- regional_pricing.py: pure, DB-free logic. country_to_region() maps an
  ISO country to br/latam/intl; resolve_regional_price_id() finds the
  region-matching price for the same product code and interval, falling
  back to the original price on any uncertainty (not found, already
  matching, no active sibling) so a subscription is never blocked.
- setup_intent.succeeded webhook: read the card country from the attached
  payment method and swap in the regional price before subscribing. Any
  Stripe/network error keeps the checked-out price. Trial and product-type
  logic are unaffected — the swap preserves the product code.
- StripePriceNode.region: expose the price's region metadata over GraphQL
  so the storefront can select the right currency by domain.

Enforcement covers the trial/SetupIntent path (where the card is known at
subscribe time). The non-trial default_incomplete path has no card yet and
is left for a follow-up. Pure logic is unit-tested; the webhook glue is
mock-tested (no live Stripe/DB).

Note: deploy together with the website regional-display change — enforcing
here while the site still shows only BRL would show R$ but charge US$.
@mergify

mergify Bot commented Aug 5, 2026

Copy link
Copy Markdown

Tick the box to add this pull request to the merge queue (same as @mergifyio queue).

  • Queue this pull request

@rdahis rdahis self-assigned this Aug 5, 2026
@rdahis
rdahis merged commit e32f836 into staging Aug 5, 2026
5 checks passed
@Winzen Winzen added the staging Indica que o Pull Request está com a branch de destino (base) apontando para staging label Aug 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

staging Indica que o Pull Request está com a branch de destino (base) apontando para staging

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants