Repository navigation
fix(payment): resolve minimum_amount rejection for promotion codes in foreign currencies and trials (#23300, #23299) - #23314
Conversation
…imum amount restriction (BasedHardware#23300, BasedHardware#23299)
Git-on-my-level
left a comment
There was a problem hiding this comment.
Verdict: promising direction with genuinely good regression tests, but I'm requesting changes on two payment-integrity issues before merge.
Blocking findings:
-
Idempotency-key reuse on the fallback retry — backend/utils/stripe.py:95-109. The minimum_amount fallback calls Session.create a second time with the SAME idempotency_key but different params. Stripe saves the response under an idempotency key once the endpoint begins executing; if the promo rejection is cached under that key, the retry replays the original error and the fallback never takes effect. The unit tests can't catch this — the mocks have no idempotency semantics. Please drop or rotate the key on the retry (e.g.
f'{idempotency_key}-r1'). -
Redemption tracking is bypassed by the coupon fallback. Applying the raw coupon (
discounts=[{"coupon": ...}]) does not create a promotion-code redemption, sotimes_redeemednever increments:
- backend/utils/stripe.py checkout fallback has no retirement logic at all — a single-use code that takes the fallback is never consumed and can be re-used by the next buyer.
- backend/routers/payment.py (both Subscription.modify and SubscriptionSchedule.modify fallbacks) deactivates the code only when
max_redemptions == 1; codes with max_redemptions > 1 never exhaust through this path.
A shared single-use code applied via the fallback to unlimited foreign-currency checkouts is a revenue leak.
Maintainer-judgment items (not blockers by themselves):
- payment.py:658 and utils/stripe.py:82 silently drop a requested trial_days whenever a promo resolves. Right tradeoff to make the promo apply, but it removes trial days users previously got in the trial+promo combination — worth a product call.
- utils/stripe.py:87 sets
adaptive_pricing: {'enabled': False}for every promo checkout, so international users lose local-currency pricing whenever they use a code. Deliberate and probably correct here, but it deserves explicit sign-off. - The
'minimum amount' in str(e).lower()string matching (stripe.py:101; payment.py upgrade handlers) is brittle against Stripe copy changes — fine for now, worth a short comment.
Good parts: backend/tests/unit/test_payment_promo_currency_minimum_amount.py covers trial suppression, adaptive-pricing disable, the checkout coupon fallback, and the upgrade fallback including single-use deactivation — solid coverage. No backend test workflow appears to have run on this head, so these tests are unverified in CI; please make sure the backend unit suite runs green.
USER IMPACT: once fixed, members outside the US should be able to apply promo codes that currently fail at checkout; the tradeoff is they will be charged in USD (no local-currency pricing) and lose free-trial days when a promo code is used.
What I'd still want: (1) rotate/drop the idempotency key on the fallback retry; (2) retire single-use codes in the checkout fallback too, and handle max_redemptions > 1 in the upgrade fallbacks; (3) backend unit suite green on this head.
Requesting changes — the coupon-fallback redemption-bypass fix and the USD-only-presentment decision need maintainer sign-off on a billing path.
by AI on behalf of David — if you need David’s attention urgently, please @Git-on-my-level and escalate with need human response.
|
Hi @kodjima33 @beastoin 👋 Thank you for merging this PR! Following up regarding the contributor rewards & bounty payout for our contributions (we have 66 merged PRs across the repository). We opened inquiry Issue #23467 and also reached out to Could you please confirm the payout schedule / next steps?
Thank you so much! |
|
Closing in favor of #23557, which takes the minimal durable fix for #23299: promo-code checkout sessions disable Stripe Adaptive Pricing so presentment stays USD and the $269 minimum_amount passes natively — no coupon fallback machinery. @Dimank92 thanks for driving this — the direction (adaptive pricing off) was right. What kept #23314 from landing as-is:
The trial-suppression half (#23300) is a real fix worth its own PR if you want to resubmit it standalone — that would get proper review attention on the product tradeoff (silently dropping requested trial days). |
|
Hey @Dimank92 👋 Thank you so much for taking the time to contribute to Omi! We truly appreciate you putting in the effort to submit this pull request. After careful review, we've decided not to merge this particular PR. Please don't take this personally — we genuinely try to merge as many contributions as possible, but sometimes we have to make tough calls based on:
Before your next PR, please skim:
If this was declined for direction or taste, maintainers should cite an invariant ID or open a proposed one — ask if that citation is missing. Your contribution is still valuable to us, and we'd love to see you contribute again in the future! If you'd like feedback on how to improve this PR or want to discuss alternative approaches, please don't hesitate to reach out. Thank you for being part of the Omi community! |
Summary
Fixes #23300 and Fixes #23299.
Stripe promotion codes created with a
minimum_amountconstraint (such as $269.00 USD for unlimited first-year membership) were being rejected under two conditions:minimum_amountagainst the presentment currency rather than USD, rejecting the promotion code.trial_period_days), the initial amount due is $0, violating the promo code's positiveminimum_amountrequirement.stripe.error.InvalidRequestError.Changes
backend/utils/stripe.py(create_subscription_checkout_session):trial_period_dayswhenpromotion_code_idis provided (trial_period_days and not promotion_code_id).session_params['adaptive_pricing'] = {'enabled': False}) whenpromotion_code_idis supplied, keeping presentment in the promo code's native USD currency.discounts=[{"coupon": coupon_id}]) if Stripe rejectsSession.createdue to a promo codeminimum_amount/ currency mismatch.backend/routers/payment.py:create_checkout_session_endpoint: suppressestrial_period_dayswhen a validresolved_checkout_promo_idis passed.upgrade_subscription_endpoint: catchesstripe.error.InvalidRequestErroronSubscription.modifyandSubscriptionSchedule.modifywhen 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).backend/tests/unit/test_payment_promo_currency_minimum_amount.py:Testing
Payout Information
0xC345C07229307d4f1c7935aD2cAe48e390215808(USDC on Polygon / Base / Arbitrum)