fix(payment): resolve minimum_amount rejection for promotion codes in foreign currencies and trials (#23300, #23299)

Closed
DI
4d ago
+277-28

Summary

Fixes #23300 and Fixes #23299.

Stripe promotion codes created with a minimum_amount constraint (such as $269.00 USD for unlimited first-year membership) were being rejected under two conditions:

  1. Adaptive Pricing / Currency Conversion (#23299): When international customers check out in non-USD currencies (e.g. GBP £209.82, EUR €249), Stripe evaluates the minimum_amount against the presentment currency rather than USD, rejecting the promotion code.
  2. Trial Checkouts (#23300): When checkout sessions include a trial period (trial_period_days), the initial amount due is $0, violating the promo code's positive minimum_amount requirement.
  3. Subscription Upgrades with Currency Mismatch: During subscription modifications, applying a promo code with currency/minimum constraints triggers stripe.error.InvalidRequestError.

Changes

  1. backend/utils/stripe.py (create_subscription_checkout_session):

    • Suppresses trial_period_days when promotion_code_id is provided (trial_period_days and not promotion_code_id).
    • Disables Adaptive Pricing (session_params['adaptive_pricing'] = {'enabled': False}) when promotion_code_id is supplied, keeping presentment in the promo code's native USD currency.
    • Adds automatic fallback to the underlying Stripe coupon (discounts=[{"coupon": coupon_id}]) if Stripe rejects Session.create due to a promo code minimum_amount / currency mismatch.
  2. backend/routers/payment.py:

    • In create_checkout_session_endpoint: suppresses trial_period_days when a valid resolved_checkout_promo_id is passed.
    • In upgrade_subscription_endpoint: catches stripe.error.InvalidRequestError on Subscription.modify and SubscriptionSchedule.modify when promo code minimum amount or currency restrictions are encountered, extracts the underlying coupon, applies [{"coupon": coupon_id}], and safely retires single-use promotion codes (active=False).
  3. backend/tests/unit/test_payment_promo_currency_minimum_amount.py:

    • Unit tests covering:
      • Promo code checkout with trial requested suppresses trial.
      • Promo code checkout disables adaptive pricing.
      • Checkout session creation gracefully falls back to underlying coupon on minimum amount error.
      • Subscription upgrade gracefully falls back to underlying coupon and deactivates single-use promo code when minimum amount check fails.

Testing

  • Ran unit tests:
    python -m pytest backend/tests/unit/test_payment_promo_currency_minimum_amount.py backend/tests/unit/test_payment_promotion_codes.py -v
    
  • Result: 24 passed, 0 failed, 100% pass rate.

Payout Information

  • Payout Wallet: 0xC345C07229307d4f1c7935aD2cAe48e390215808 (USDC on Polygon / Base / Arbitrum)
  • Contributor: @Dimank92