fix: skip pay call if invoice already paid after finalize (#13924)
## Description When a customer downgrades from Enterprise to Business, they may retain unused Stripe credit balance. During an AI credits topup, Stripe::Invoice.finalize_invoice auto-applies that credit balance to the invoice. If the credit balance fully covers the invoice amount, Stripe marks it as paid immediately upon finalization. Calling Stripe::Invoice.pay on an already-paid invoice throws an error, breaking the topup flow. This fix retrieves the invoice status after finalization and skips the pay call if Stripe has already settled it via credits. ## Type of change Please delete options that are not relevant. - [ ] Bug fix (non-breaking change which fixes an issue) ## How Has This Been Tested? Tested against Stripe test mode with the following scenarios: - Full credit balance payment: Customer has enough Stripe credit balance to cover the entire invoice. Invoice is marked paid after finalize_invoice — pay is correctly skipped. Credits are fulfilled successfully. - Partial credit balance payment: Customer has some Stripe credit balance but not enough to cover the full amount. Invoice remains open after finalization — pay is called and charges the remaining amount to the default payment method. Credits are fulfilled successfully. - Zero credit balance (normal payment): Customer has no Stripe credit balance. Invoice remains open after finalization — pay charges the full amount. Credits are fulfilled successfully. ## Checklist: - [ ] My code follows the style guidelines of this project - [ ] I have performed a self-review of my code - [ ] I have commented on my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [ ] My changes generate no new warnings - [ ] I have added tests that prove my fix is effective or that my feature works - [ ] New and existing unit tests pass locally with my changes - [ ] Any dependent changes have been merged and published in downstream modules
This commit is contained in:
committed by
GitHub
parent
44a7a13117
commit
04acc16609
@@ -281,6 +281,7 @@ RSpec.describe 'Enterprise Billing APIs', type: :request do
|
||||
allow(Stripe::Invoice).to receive(:create).and_return(stripe_invoice)
|
||||
allow(Stripe::InvoiceItem).to receive(:create)
|
||||
allow(Stripe::Invoice).to receive(:finalize_invoice)
|
||||
allow(Stripe::Invoice).to receive(:retrieve).and_return(Struct.new(:status).new('open'))
|
||||
allow(Stripe::Invoice).to receive(:pay)
|
||||
allow(Stripe::Billing::CreditGrant).to receive(:create)
|
||||
end
|
||||
|
||||
@@ -24,6 +24,7 @@ describe Enterprise::Billing::TopupCheckoutService do
|
||||
allow(Stripe::Invoice).to receive(:create).and_return(stripe_invoice)
|
||||
allow(Stripe::InvoiceItem).to receive(:create)
|
||||
allow(Stripe::Invoice).to receive(:finalize_invoice)
|
||||
allow(Stripe::Invoice).to receive(:retrieve).and_return(Struct.new(:status).new('open'))
|
||||
allow(Stripe::Invoice).to receive(:pay)
|
||||
allow(Stripe::Billing::CreditGrant).to receive(:create)
|
||||
end
|
||||
@@ -58,5 +59,19 @@ describe Enterprise::Billing::TopupCheckoutService do
|
||||
expect(error.message).to eq(I18n.t('errors.topup.plan_not_eligible'))
|
||||
end
|
||||
end
|
||||
|
||||
it 'calls pay when invoice is open after finalization' do
|
||||
service.create_checkout_session(credits: 1000)
|
||||
|
||||
expect(Stripe::Invoice).to have_received(:pay).with('inv_test123')
|
||||
end
|
||||
|
||||
it 'skips pay when invoice is already paid via Stripe credits' do
|
||||
allow(Stripe::Invoice).to receive(:retrieve).and_return(Struct.new(:status).new('paid'))
|
||||
|
||||
service.create_checkout_session(credits: 1000)
|
||||
|
||||
expect(Stripe::Invoice).not_to have_received(:pay)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user