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:
Tanmay Deep Sharma
2026-03-30 10:37:28 +05:30
committed by GitHub
parent 44a7a13117
commit 04acc16609
3 changed files with 23 additions and 2 deletions

View File

@@ -73,8 +73,13 @@ class Enterprise::Billing::TopupCheckoutService
description: description
)
Stripe::Invoice.finalize_invoice(invoice.id, { auto_advance: false })
Stripe::Invoice.pay(invoice.id)
finalize_and_pay(invoice.id)
end
def finalize_and_pay(invoice_id)
Stripe::Invoice.finalize_invoice(invoice_id, { auto_advance: false })
invoice = Stripe::Invoice.retrieve(invoice_id)
Stripe::Invoice.pay(invoice_id) unless invoice.status == 'paid'
end
def fulfill_credits(credits, topup_option)