feat(payment): charge card-country regional price at checkout - #1045
Merged
Conversation
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$.
|
Tick the box to add this pull request to the merge queue (same as
|
This was referenced Aug 19, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 productcodeand interval, falling back to the original price on any uncertainty (not found, already matching, no active sibling) so a subscription is never blocked.webhooks.pysetup_intent.succeeded— reads the card country from the attached payment method and swaps in the regional price beforecustomer.subscribe. Any Stripe/network error keeps the checked-out price. Product type and trial logic are unaffected (the swap preserves the productcode).graphql.pyStripePriceNode.region— exposes the price'sregionmetadata 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_incompletepath has no card yet and is a follow-up.Tests
country_to_region,resolve_regional_price_id) is unit-tested and passes standalone (14/14)._card_country,_regional_price_id) is mock-tested — no live Stripe or DB.Deploy note
Ship together with the website
feat/regional-price-displayPR. 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 +regionmetadata. The USD prices must carry the samecodeas their BRL counterparts (same product or separate — only thecodeneeds to match) and aregiontag.