Skip to content

feat(billing): grant prorated Deploy credit top-ups on mid-cycle upgrades#6422

Open
Flo4604 wants to merge 1 commit into
eng-2870-grant-deploy-credits-on-invoice-paidfrom
eng-2871-apply-prorated-credit-top-ups-on-upgrade
Open

feat(billing): grant prorated Deploy credit top-ups on mid-cycle upgrades#6422
Flo4604 wants to merge 1 commit into
eng-2870-grant-deploy-credits-on-invoice-paidfrom
eng-2871-apply-prorated-credit-top-ups-on-upgrade

Conversation

@Flo4604

@Flo4604 Flo4604 commented Jun 11, 2026

Copy link
Copy Markdown
Member

Upgrades: changeDeployPlan uses always_invoice, so a mid-cycle upgrade
charges the prorated fee difference immediately. Paying that invoice triggers
the existing credit grant webhook, whose net-fee summing turns the proration
pair (+new prorated fee, -unused old fee) into exactly the right top-up,
expiring with the current period. No new grant logic needed.

Downgrades keep the period as bought (proration_behavior none): no refund of
the fee difference AND no clawback of the usage credits that fee already
granted. The customer rides out the period at the level they paid for; the
lower fee and its smaller credits start at the next renewal. Direction is
derived from DEPLOY_PLANS order (lowest to highest).

Supporting changes:

  • netDeployFee extracted as a pure function with unit tests covering
    subscribe/renewal, upgrade netting, downgrade netting, unrelated lines,
    multi-line period ends, and both webhook payload shapes.
  • Compute plan modal: footnote describes the immediate charge / keep-credits
    semantics, and the downgrade overage warning now says the current period
    keeps its credits.

ENG-2871

@linear-code

linear-code Bot commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

ENG-2871

@vercel

vercel Bot commented Jun 11, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
dashboard Ready Ready Preview, Comment Jun 19, 2026 9:21am
design Ready Ready Preview, Comment Jun 19, 2026 9:21am

Request Review

@Flo4604

Flo4604 commented Jun 11, 2026

Copy link
Copy Markdown
Member Author

This stack of pull requests is managed by jj-ryu.

@Flo4604 Flo4604 force-pushed the eng-2871-apply-prorated-credit-top-ups-on-upgrade branch from d2d8a6a to c560a00 Compare June 11, 2026 09:08
@Flo4604 Flo4604 force-pushed the dev-stripe-test-clocks branch from f8babdd to 6a9b755 Compare June 11, 2026 10:24
@Flo4604 Flo4604 force-pushed the eng-2871-apply-prorated-credit-top-ups-on-upgrade branch from c560a00 to 4fe1d8e Compare June 11, 2026 10:24
@Flo4604 Flo4604 force-pushed the dev-stripe-test-clocks branch from 6a9b755 to 79f2063 Compare June 11, 2026 10:38
@Flo4604 Flo4604 force-pushed the eng-2871-apply-prorated-credit-top-ups-on-upgrade branch from 4fe1d8e to 263f020 Compare June 11, 2026 10:38
@Flo4604 Flo4604 force-pushed the dev-stripe-test-clocks branch from 7aed52a to f2dd6dd Compare June 15, 2026 13:08
@Flo4604 Flo4604 force-pushed the eng-2871-apply-prorated-credit-top-ups-on-upgrade branch from f3d73b1 to 90233a8 Compare June 15, 2026 13:08
@Flo4604 Flo4604 force-pushed the dev-stripe-test-clocks branch from f2dd6dd to edd63bc Compare June 16, 2026 08:17
@Flo4604 Flo4604 force-pushed the eng-2871-apply-prorated-credit-top-ups-on-upgrade branch from 90233a8 to 7a4870f Compare June 16, 2026 08:17
@Flo4604 Flo4604 force-pushed the dev-stripe-test-clocks branch from edd63bc to 8cb9617 Compare June 16, 2026 10:37
@Flo4604 Flo4604 force-pushed the eng-2871-apply-prorated-credit-top-ups-on-upgrade branch from 7a4870f to 5b7f425 Compare June 16, 2026 10:37
@Flo4604 Flo4604 force-pushed the dev-stripe-test-clocks branch from 8cb9617 to b14c61b Compare June 16, 2026 13:27
@Flo4604 Flo4604 force-pushed the eng-2871-apply-prorated-credit-top-ups-on-upgrade branch from 5b7f425 to 4769cb5 Compare June 16, 2026 13:27
@Flo4604 Flo4604 force-pushed the dev-stripe-test-clocks branch from b14c61b to 08d6847 Compare June 16, 2026 15:11
@Flo4604 Flo4604 force-pushed the eng-2871-apply-prorated-credit-top-ups-on-upgrade branch from 4769cb5 to 995a78c Compare June 16, 2026 15:11
@Flo4604 Flo4604 force-pushed the dev-stripe-test-clocks branch from 08d6847 to ad349bc Compare June 16, 2026 16:23
@Flo4604 Flo4604 force-pushed the eng-2871-apply-prorated-credit-top-ups-on-upgrade branch from 995a78c to d6bb1e4 Compare June 16, 2026 16:23

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ℹ️ No critical issues — two minor labeling/test nits inline.

Reviewed changes — mid-cycle Deploy plan changes now grant the correct usage credits by reusing the existing invoice-paid credit-grant webhook, with netDeployFee extracted as a pure, unit-tested function.

  • Immediate prorated charge on upgradeschangeDeployPlan.ts switches proration_behavior to always_invoice for upgrades and none for downgrades, deriving direction from DEPLOY_PLANS.indexOf (the equal-plan case returns earlier, so < is sufficient).
  • Self-correcting credit top-upnetDeployFee sums the invoice's Deploy plan-fee lines; the upgrade proration pair (+new fee, -unused old fee) nets to exactly the top-up, a downgrade nets negative (no grant, no clawback), and a renewal grants the full fee.
  • Refactor of grantDeployCreditsForInvoice — fee summing, period-end, and plan resolution now flow through netDeployFee; behavior preserved.
  • Unit testsdeployCredits.test.ts covers subscribe/renewal, upgrade netting, downgrade netting, unrelated lines, latest-period-end, and missing price details.
  • Modal copydeploy-product-card.tsx footnote and downgrade overage warning now describe the immediate-charge / keep-current-credits semantics.

I verified the load-bearing Stripe assumptions against current docs: always_invoice creates and collects a proration invoice synchronously, proration lines carry pricing.price_details.price and a period.end equal to the subscription period end (so the top-up expires with the current period), error_if_incomplete throws synchronously on payment failure, and invoice.payment_succeeded fires for the proration invoice. All correct. The money paths net correctly and the grant is idempotent per invoice; the only findings are cosmetic.

Pullfrog  | Fix all ➔Fix 👍s ➔View workflow run | Using Claude Opus𝕏

Comment thread web/apps/dashboard/lib/stripe/deployCredits.ts Outdated
Comment thread web/apps/dashboard/lib/stripe/deployCredits.test.ts Outdated

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ No new issues found.

Reviewed changes — the delta since the prior pullfrog review (3d91734) cleanly addresses both earlier nits via the netDeployFee extraction and added test coverage; the money paths are unchanged and remain correct.

  • Plan labeled from the charge linenetDeployFee now derives plan from the largest-amount fee line (chargeLine via reduce), so an upgrade's credit name and deploy_plan metadata reflect the plan the customer ends up on regardless of Stripe's proration line ordering.
  • Test coverage for the previously-uncovered paths — split the old test into "ignores metered lines" plus a genuine null-pricing case ("ignores lines without price details"), added "labels the plan from the charge line regardless of proration order", and added a discount_amounts coupon test.
  • Behavior-preserving refactorgrantDeployCreditsForInvoice now delegates fee summing, period-end (Math.max), and plan resolution to the pure netDeployFee; the net amount, discount subtraction, and expiry logic are identical to the inlined version.

The chargeLine heuristic is a labeling-only concern — amountCents is still summed independently over all fee lines, so the net top-up math is unaffected by which line wins the reduce. Both prior review threads have been resolved.

Pullfrog  | View workflow run | Using Claude Opus𝕏

…ades

Included usage credits are granted once per period, off the plan fee paid at
the start of it. A mid-cycle upgrade charges more fee right away, but before
this change the credit allowance stayed flat until the next renewal: the
customer paid the higher fee immediately and still hit overage the upgrade was
meant to cover. An upgrade has to buy more headroom now, not next month.

Rather than add a separate top-up path, we lean on the credit-grant webhook
from eng-2870. changeDeployPlan reprices the plan-fee item with always_invoice,
so the upgrade produces a proration invoice immediately. That invoice carries
the pair (+new prorated fee, -unused old fee), and the webhook's net-fee
summing collapses it to exactly the difference, granted as credits that expire
with the current period. Summing the invoice lines instead of reading the
catalog price is what makes this work with no new logic: the same code that
grants on subscribe and renewal grants the right top-up on upgrade.

Downgrades are deliberately the opposite: proration_behavior none, so no refund
of the fee difference and no clawback of the credits that fee already granted.
The alternative, refunding the downgrade, would force us to partially revoke an
existing credit grant to stay consistent, and Stripe credit grants only void in
full. Keeping the period as bought sidesteps that entirely, and it is the fairer
default: the customer rides out the period at the level they paid for, and the
lower fee with its smaller credits starts at the next renewal. Direction is
derived from DEPLOY_PLANS order (lowest to highest), so there is no separate
upgrade flag to keep in sync.

Supporting changes:

- netDeployFee extracted as a pure function with unit tests covering
  subscribe/renewal, upgrade netting, downgrade netting, unrelated lines,
  multi-line period ends, and both webhook payload shapes.
- Compute plan modal: footnote describes the immediate-charge / keep-credits
  semantics, and the downgrade overage warning now says the current period
  keeps its credits.

ENG-2871
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants