feat(billing): regional prices only in the local currency, with a standard-price opt-out

Merged
RI
4d ago
+413-83

Summary

Follow-up to #3834 (regional discount on Solo).

  • Currency must match the region. A regional price is set for one market, so it is only sold in the local currency of the country granting it, or in USD where Stripe does not sell that currency (e.g. Ghana, Tunisia). An org already billed in USD in France now sees and pays the full price. The rule is shared (isRegionalCurrency / priceTierForCurrency) and applied in:
    • private/plan_prices: an org pinned to another currency gets the full prices, no discount; discount.currencies lists the currencies the discount is sold in.
    • checkout: if the checkout currency is not regional, the full price is sold.
    • trial auto-charge: billed in the customer's currency (USD until one is pinned).
    • webhook guard: a subscription billed in another market's currency moves to the full price.
  • Opt-out on the plans page. Under the badge, "Not in France? Pay the standard price" shows the full price and sends standardPrice: true to checkout, so buyers with a foreign card or address are not charged a correction right after paying. "Show the 30% regional price" undoes it (kept per browser session). The page also drops the discount when the selected currency does not qualify.
  • Default currency. Falls back to the billing address or card country when the IP country is unknown, the same countries the discount uses (previously the discount could say France while prices stayed in USD).
  • Requirement text now says the standard price is charged otherwise.
  • Public price catalog. GET /private/plan_prices/public (no auth) returns the same shape as plan_prices for logged-out pages such as the website: EE account, visitor IP country only, no org or customer fields. It reuses the per-tier cache, so it never hits Stripe per request, and sets Cache-Control: private, max-age=300 because the answer depends on the visitor's country.

Test plan

  • bun run typecheck, bun run lint
  • Unit tests: regional-price, plan-prices, stripe-redirects (checkout: FR in EUR gets p70, FR in USD and opt-out get the full price), stripe-trial-plan, stripe-revenue-movement, stripe-emulator
  • private-error-cases: public catalog answers without auth, the org route still requires it
  • CI e2e

🤖 Generated with Claude Code


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need.

Summary by CodeRabbit

  • New Features
    • Eligible customers can choose between regional and standard plan pricing. Plan pages show an option to switch when a regional price is available, and checkout reflects the selected price.
    • Regional pricing is shown when the customer’s country and billing currency qualify. The regional price may require a payment card and billing address from an eligible country.
  • Bug Fixes
    • Pricing now falls back to the standard rate when the regional price does not match the applicable country or currency.