-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Fix: Preserve offer code discount for installments when code is deleted/expired/maxed (#1410) #3066
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Fix: Preserve offer code discount for installments when code is deleted/expired/maxed (#1410) #3066
Conversation
…leted/expired/maxed
| if discount_applies_to_next_charge? | ||
| if original_purchase.purchase_offer_code_discount.present? | ||
| original_discount = original_purchase.purchase_offer_code_discount | ||
| purchase.offer_code = original_purchase.offer_code | ||
| purchase.build_purchase_offer_code_discount( | ||
| offer_code: original_discount.offer_code, | ||
| offer_code_amount: original_discount.offer_code_amount, | ||
| offer_code_is_percent: original_discount.offer_code_is_percent, | ||
| pre_discount_minimum_price_cents: original_discount.pre_discount_minimum_price_cents, | ||
| duration_in_months: original_discount.duration_in_months | ||
| ) | ||
| elsif original_purchase.offer_code.present? | ||
| purchase.offer_code = original_purchase.offer_code | ||
| end | ||
| end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the Core fix - we check if original_purchase.purchase_offer_code_discount exists and copy all fields to the new purchase. Falls back to live offer code for legacy purchases that don't have cached data.
| return nil if offer_code&.deleted? && !include_deleted | ||
|
|
||
| if has_cached_offer_code? | ||
| code = purchase_offer_code_discount.offer_code.code | ||
| code = purchase_offer_code_discount.offer_code&.code | ||
| purchase_offer_code_discount.offer_code_is_percent ? | ||
| OfferCode.new(amount_percentage: purchase_offer_code_discount.offer_code_amount, code:) : | ||
| OfferCode.new(amount_cents: purchase_offer_code_discount.offer_code_amount, code:) | ||
| elsif offer_code&.deleted? && !include_deleted | ||
| nil |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reordered the logic - check cached data first, then check if offer code is deleted. Previously we'd return nil for deleted codes before even checking if we had cached data.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This test directly covers the reported issue #1410 - offer code with max_purchase_count=1, verify quantity_left <= 0 after first purchase, then confirm discount still applies to next installment.
| if discount_applies_to_next_charge? | ||
| if original_purchase.purchase_offer_code_discount.present? | ||
| original_discount = original_purchase.purchase_offer_code_discount | ||
| purchase.offer_code = original_purchase.offer_code |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Setting purchase.offer_code even when it might be nil/deleted - this is intentional for audit trail, the actual discount comes from the cached data.
|
@EmCousin _a Hey, I have updated the logic as per previous discussion #2257 (comment). Could you please review? |
|
@EmCousin light ping on this. |
Issue: #1410
Description
Problem
When an offer code hits its max usage limit (or gets deleted/expires) between installment charges, subsequent payments lose the discount even though the customer legitimately used the code on their first payment.
Solution
Instead of checking the live offer code on each charge, we now copy the discount data from the original purchase's
purchase_offer_code_discountrecord. This data was already being stored - we just weren't using it for subsequent charges.Two changes:
Subscription#build_purchase- copies cached discount data to new purchasesPurchase#original_offer_code- prioritizes cached data over live offer code lookupTest Results
Added tests for:
Checklist
AI Disclosure
Claude Opus 4.5 was used via Claude Code was used for implementation and test writing.