Skip to content
This repository was archived by the owner on Jun 3, 2026. It is now read-only.

Support regional Pro pricing#220

Merged
ishaanxgupta merged 2 commits into
mainfrom
feat/regional-pro-pricing
Jun 2, 2026
Merged

Support regional Pro pricing#220
ishaanxgupta merged 2 commits into
mainfrom
feat/regional-pro-pricing

Conversation

@ishaanxgupta
Copy link
Copy Markdown
Contributor

Summary

  • add server-authoritative Pro price options for India and global billing regions
  • accept billing_region in Razorpay checkout and select Rs 99 INR or $3 USD accordingly
  • support an optional RAZORPAY_GLOBAL_PRO_PLAN_ID for global Pro subscriptions, falling back to a USD order when not configured
  • expose regional_prices in public plan metadata and add unit coverage for billing config helpers

Verification

  • python -c "from src.utils import billing; assert billing.plan_price('pro','IN') == {'price_paise': 9900, 'currency': 'INR'}; assert billing.plan_price('pro','GLOBAL') == {'price_paise': 300, 'currency': 'USD'}; assert billing.normalize_billing_region('india') == 'IN'; assert billing.normalize_billing_region('outside') == 'GLOBAL'; assert billing.plan_price_options('pro')['GLOBAL']['currency'] == 'USD'; print('billing region checks passed')"
  • python -m py_compile src\utils\billing.py src\billing\types.py src\billing\service.py src\api\routes\billing.py src\config\settings.py tests\unit\test_billing_config.py

Note: python -m pytest tests\unit\test_billing_config.py could not run in this local shell because pytest is not installed.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jun 2, 2026

Fails
🚫

🔐 This PR modifies sensitive files: src/config/settings.py. These require review by a core maintainer (@ishaanxgupta or @ved015) before merging.

Messages
📖

📝 No CHANGELOG.md update detected. If this PR introduces a user-visible change, please add an entry.

📖

✅ Targeting main. Please squash commits before merging to keep the git history clean.

Generated by 🚫 dangerJS against 0f71b0e

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jun 2, 2026

✅ Staging Deployment Report

Item Value
Branch feat/regional-pro-pricing
Commit 5eb2cd7
Environment Staging
Health http://3.6.255.148:8001/health
API Docs http://3.6.255.148:8001/docs
Smoke Tests success

🟢 Staging is live and healthy! Test your changes at the staging URL above.

Ready to ship? Comment /promote on this PR to merge to main and deploy to production.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jun 2, 2026

🔍 API Schema Diff

---REPORT---


Auto-generated by API Schema Diff workflow

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces regional pricing support for billing plans, allowing different prices and currencies (such as INR for India and USD for global users) to be applied based on the user's billing region. It updates the Razorpay checkout flow to handle region-specific subscription plan IDs and prices, and exposes regional prices in the public plans API. The feedback suggests improving the robustness of normalize_billing_region to handle whitespace-only strings as default inputs and expanding the unit tests to cover these edge cases.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread src/utils/billing.py
Comment on lines +98 to +101
def normalize_billing_region(region: str | None) -> str:
if not region:
return BILLING_REGION_IN
return BILLING_REGION_ALIASES.get(region.strip().upper(), BILLING_REGION_GLOBAL)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The normalize_billing_region function currently defaults to BILLING_REGION_IN only when region is falsy (e.g., None or ""). However, if a whitespace-only string (e.g., " ") is passed, region.strip().upper() becomes "", which is not in BILLING_REGION_ALIASES, causing it to fall back to BILLING_REGION_GLOBAL.

To ensure consistent default behavior for all empty or whitespace-only inputs, we should check if the stripped string is empty before performing the lookup.

Suggested change
def normalize_billing_region(region: str | None) -> str:
if not region:
return BILLING_REGION_IN
return BILLING_REGION_ALIASES.get(region.strip().upper(), BILLING_REGION_GLOBAL)
def normalize_billing_region(region: str | None) -> str:
if not region or not region.strip():
return BILLING_REGION_IN
return BILLING_REGION_ALIASES.get(region.strip().upper(), BILLING_REGION_GLOBAL)

Comment thread tests/unit/test_billing_config.py Outdated
Comment on lines +16 to +19
def test_billing_region_defaults_to_india_and_unknowns_are_global() -> None:
assert billing.normalize_billing_region(None) == billing.BILLING_REGION_IN
assert billing.normalize_billing_region("india") == billing.BILLING_REGION_IN
assert billing.normalize_billing_region("outside-india") == billing.BILLING_REGION_GLOBAL
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Let's expand the unit tests for normalize_billing_region to cover whitespace-only strings and unknown regions (e.g., "UK" or "FR") to ensure they correctly fall back to the global billing region.

Suggested change
def test_billing_region_defaults_to_india_and_unknowns_are_global() -> None:
assert billing.normalize_billing_region(None) == billing.BILLING_REGION_IN
assert billing.normalize_billing_region("india") == billing.BILLING_REGION_IN
assert billing.normalize_billing_region("outside-india") == billing.BILLING_REGION_GLOBAL
def test_billing_region_defaults_to_india_and_unknowns_are_global() -> None:
assert billing.normalize_billing_region(None) == billing.BILLING_REGION_IN
assert billing.normalize_billing_region("") == billing.BILLING_REGION_IN
assert billing.normalize_billing_region(" ") == billing.BILLING_REGION_IN
assert billing.normalize_billing_region("india") == billing.BILLING_REGION_IN
assert billing.normalize_billing_region("outside-india") == billing.BILLING_REGION_GLOBAL
assert billing.normalize_billing_region("UK") == billing.BILLING_REGION_GLOBAL

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Jun 2, 2026

Greptile Summary

This PR adds server-authoritative regional pricing for the Pro plan, supporting ₹99 INR (India) and $3 USD (Global) tiers. The billing_region hint from the client is normalised server-side, the appropriate Razorpay plan or order is selected accordingly, and regional_prices is now surfaced in the public /plans response.

  • normalize_billing_region and plan_price helpers centralise region logic; a missing or unrecognised hint defaults to GLOBAL to avoid undercharging.
  • A new optional RAZORPAY_GLOBAL_PRO_PLAN_ID setting enables recurring USD subscriptions; when absent, checkout falls back to a one-time USD order.
  • regional_prices is added to PlanPublic via plan_price_options, giving clients a canonical per-region price map.

Confidence Score: 5/5

Safe to merge — the regional pricing logic is well-contained and charge amounts are computed server-side, so clients cannot influence the price beyond the normalised region hint.

The core billing path (amount selection, currency, subscription vs. order routing) is correct for both IN and GLOBAL regions. The two observations raised are cosmetic: a falsy-zero guard on the price fallback and a clarifying docstring on an INR-only helper. Neither affects current charge amounts or credit grants.

No files require special attention. The nominal_paise_per_credit helper in src/utils/billing.py is now semantically inconsistent alongside the new USD regional prices, but it is not used for charge calculation.

Important Files Changed

Filename Overview
src/utils/billing.py Adds regional pricing constants, normalize_billing_region, plan_price, and plan_price_options helpers. Defaults None region to GLOBAL. Minor: nominal_paise_per_credit remains INR-hardcoded despite new USD regional pricing.
src/api/routes/billing.py Adds _checkout_package and _pro_plan_id_for_region helpers; threads billing_region through checkout creation and stored notes. Fallback for price_minor_unit uses or which would silently skip an intentional zero value.
src/billing/types.py Adds PlanPricePublic model, regional_prices to PlanPublic, billing_region to CheckoutRequest and VerifyPaymentRequest.
src/billing/service.py Passes regional_prices from plan_price_options into PlanPublic; minimal, safe change.
src/config/settings.py Adds razorpay_global_pro_plan_id optional env var with clear description; clean change.
tests/unit/test_billing_config.py New unit tests covering regional price lookup, normalize_billing_region defaults, and plan_price_options serialization.

Sequence Diagram

sequenceDiagram
    participant Client
    participant BillingRoute as POST /razorpay/order
    participant BillingConfig as billing_config
    participant Settings
    participant Razorpay

    Client->>BillingRoute: "{package_id, billing_region?}"
    BillingRoute->>BillingConfig: normalize_billing_region(billing_region)
    BillingConfig-->>BillingRoute: "IN | GLOBAL"
    BillingRoute->>BillingConfig: plan_price(package_id, region)
    BillingConfig-->>BillingRoute: "{price_minor_unit, currency}"
    BillingRoute->>Settings: razorpay_pro_plan_id / razorpay_global_pro_plan_id
    Settings-->>BillingRoute: plan_id or None

    alt Pro plan + plan_id set
        BillingRoute->>Razorpay: create_subscription(plan_id)
        Razorpay-->>BillingRoute: subscription
        BillingRoute->>BillingRoute: store_checkout(subscription_id, billing_region)
        BillingRoute-->>Client: CheckoutResponse(subscription_id, price_minor_unit, currency)
    else Pro plan GLOBAL + no plan_id, or any other plan
        BillingRoute->>Razorpay: "create_order(amount=price_minor_unit, currency)"
        Razorpay-->>BillingRoute: order
        BillingRoute->>BillingRoute: store_checkout(order_id, billing_region)
        BillingRoute-->>Client: CheckoutResponse(order_id, amount, currency)
    end
Loading

Fix All in Cursor Fix All in Codex Fix All in Claude Code

Reviews (2): Last reviewed commit: "Address regional billing review feedback" | Re-trigger Greptile

Comment thread src/billing/types.py
razorpay_signature: str
razorpay_order_id: Optional[str] = None
razorpay_subscription_id: Optional[str] = None
billing_region: Optional[str] = None
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 billing_region accepted but silently ignored in verify handler

billing_region is added to VerifyPaymentRequest but verify_razorpay_payment never reads request.billing_region — it neither cross-checks it against the stored checkout's billing_region nor uses it for any grant logic. Callers who pass it will see no effect, and the API surface implies a behavior that isn't implemented.

Fix in Cursor Fix in Codex Fix in Claude Code

Comment thread src/utils/billing.py
Comment thread src/utils/billing.py
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jun 2, 2026

✅ Staging Deployment Report

Item Value
Branch feat/regional-pro-pricing
Commit 15df480
Environment Staging
Health http://3.6.255.148:8001/health
API Docs http://3.6.255.148:8001/docs
Smoke Tests success

🟢 Staging is live and healthy! Test your changes at the staging URL above.

Ready to ship? Comment /promote on this PR to merge to main and deploy to production.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jun 2, 2026

🔍 API Schema Diff

---REPORT---


Auto-generated by API Schema Diff workflow

@ishaanxgupta ishaanxgupta merged commit 52aaf6f into main Jun 2, 2026
16 of 17 checks passed
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant