Generate a final invoice for metered usage on subscription cancel#951
Conversation
🤖 Claude Code ReviewPR: #951 I now have a full understanding of the change. Let me write up the review. Code ReviewScope: A single, well-contained change to OverallThis is a solid, minimal fix. It correctly targets only the metered cancel path (licensed subscriptions have no accrued usage, so leaving Observations / minor notes
Correctness / security / performance
Verdict: LGTM. No blocking issues; the notes above are informational. Model: claude-opus-4-8 |
Stripe's CancelSubscription defaults invoice_now to false, so an immediate cancel discarded any metered usage accrued since the last invoice. cancel_metered_subscription now cancels with invoice_now(true), generating a final invoice for the un-invoiced usage. The licensed cancel path is left unchanged: licensed plans bill upfront, so there is no un-invoiced usage to lose. Prorations stay off on both paths, so bare metal usage is never prorated.
4be34df to
b895b0b
Compare
|
Addressed the review's actionable item (test assertion could pass trivially when the creation subscription had no invoice) in b895b0b. Instead of comparing the final invoice against the creation invoice, the test now snapshots the latest invoice immediately before cancellation (reusing the already-fetched I did not add the literal The other two notes (org-delete billing side effect, customer-deletion ordering) are out of scope for this diff, as flagged. |
|
| Branch | claude/invoice-metered-usage-on-cancel |
| Testbed | intel-v1 |
Click to view all benchmark results
| Benchmark | Latency | Benchmark Result microseconds (µs) (Result Δ%) | Upper Boundary microseconds (µs) (Limit %) |
|---|---|---|---|
| Adapter::Json | 📈 view plot 🚷 view threshold | 4.71 µs(+1.46%)Baseline: 4.64 µs | 4.90 µs (96.08%) |
| Adapter::Magic (JSON) | 📈 view plot 🚷 view threshold | 4.58 µs(+1.45%)Baseline: 4.51 µs | 4.72 µs (96.98%) |
| Adapter::Magic (Rust) | 📈 view plot 🚷 view threshold | 25.96 µs(+1.34%)Baseline: 25.62 µs | 26.76 µs (97.02%) |
| Adapter::Rust | 📈 view plot 🚷 view threshold | 3.52 µs(+0.81%)Baseline: 3.49 µs | 3.61 µs (97.64%) |
| Adapter::RustBench | 📈 view plot 🚷 view threshold | 3.52 µs(+0.72%)Baseline: 3.49 µs | 3.60 µs (97.62%) |
Problem
Biller::cancel_metered_subscriptionsent a bareCancelSubscriptionrequest. Stripe'sinvoice_nowflag defaults tofalse, so an immediate cancel silently discarded any metered usage accrued since the last invoice. The customer was never billed for their final usage on either path that cancels a metered subscription: the admin plan DELETE endpoint and organization deletion.Fix
cancel_metered_subscriptionnow cancels withinvoice_now(true), so Stripe generates a final invoice for any un-invoiced metered usage on the way out.The licensed cancel path is unchanged: licensed plans bill upfront, so there is no un-invoiced usage to lose. Prorations stay off (
proratedefaults tofalse) on both paths, so bare metal usage is billed in full and never prorated.Tests
The
metered_subscriptionlifecycle test records usage before exercising the DELETE path. It now captures the creation-time invoice id and asserts that the cancel returns a subscription whoselatest_invoiceis present and different, verifying that the final invoice was generated. Theresubscribe_has_no_trialhelper cancels with no recorded usage, so the gated Stripe test also covers the nothing-to-bill case.Only the invoice ids are held across await points in the test; keeping the full
Subscriptionobjects alive trippedclippy::large_futures.