Visitar URL original
fix(billing): settle a free payer's run after they subscribe mid-run by waleedlatif1 · Pull Request #9498 · simstudioai/sim · GitHub
Skip to content

fix(billing): settle a free payer's run after they subscribe mid-run - #9498

Open
waleedlatif1 wants to merge 1 commit into
stagingfrom
fix/billing-period-plan-change
Open

waleedlatif1 wants to merge 1 commit into
stagingfrom
fix/billing-period-plan-change

Conversation

@waleedlatif1

Copy link
Copy Markdown
Collaborator

Summary

  • A run admitted for a free payer records its spend in the open default period. If the payer subscribes mid-run, the cost callback's threshold check compares that frozen period with the new subscription's period, throws billing_period_mismatch, and answers a retryable 503 on every attempt. The usage is already recorded, but the callback is never acknowledged, so the worker spends its whole retry budget and leaves the charge unconfirmed
  • checkAndBillPayerOverageThreshold now treats spend in the default period as having no threshold to settle (no subscription period ever spans it), which is what a free payer's callback already resolved to before the upgrade
  • Adds isDefaultBillingPeriod next to defaultBillingPeriod, comparing dates so envelopes without a period source are covered too

Type of Change

  • Bug fix

Testing

  • New update-cost integration cases (direct-v1 and attribution-v1): free payer's first callback, payer subscribes, next callback settles (200), replay is a duplicate (409), spend stays in the default period. Both fail with 503 on the pre-fix code
  • bun run test:integration update-cost/route.integration, vitest run lib/billing app/api/billing, apps/sim type-check

Checklist

  • Code follows project style guidelines
  • Self-reviewed my changes
  • Tests added/updated and passing
  • No new warnings introduced
  • I confirm that I have read and agree to the terms outlined in the Contributor License Agreement (CLA)

🤖 Generated with Claude Code

@vercel

vercel Bot commented Oct 31, 2026 •

Copy link
Copy Markdown

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

1 Skipped Deployment
Project Deployment Actions Updated
docs Skipped Skipped Oct 31, 2026 4:39am UTC

Request Review

@cubic-dev-ai cubic-dev-ai 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.

1 issue found across 3 files

Confidence score: 3/5

  • In apps/sim/lib/billing/threshold-billing.ts, the guard treats an open period as no subscription, so a paid or entitled subscription with missing billing dates can be serialized with the default period while the callback is acknowledged. Distinguish missing billing dates from having no subscription.
Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="apps/sim/lib/billing/threshold-billing.ts">

<violation number="1" location="apps/sim/lib/billing/threshold-billing.ts:222">
P1: This guard conflates an open period with having no subscription. A paid or entitled subscription with missing billing dates is serialized with the default period, so this line acknowledges the callback without resolving or settling its overage instead of raising the existing retryable `required_state_missing` error; only bypass the check after confirming that the payer has no subscription.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Turn on auto-fix | Re-trigger cubic

): Promise<ThresholdSettlementOutcome | undefined> {
// Spend admitted without a subscription stays in the open period, which no subscription the
// payer gains later ever spans, so it has no threshold to settle.
if (options.expectedBillingPeriod && isDefaultBillingPeriod(options.expectedBillingPeriod)) {

@cubic-dev-ai cubic-dev-ai Bot Oct 31, 2026 •

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.

P1: This guard conflates an open period with having no subscription. A paid or entitled subscription with missing billing dates is serialized with the default period, so this line acknowledges the callback without resolving or settling its overage instead of raising the existing retryable required_state_missing error; only bypass the check after confirming that the payer has no subscription.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At apps/sim/lib/billing/threshold-billing.ts, line 222:

<comment>This guard conflates an open period with having no subscription. A paid or entitled subscription with missing billing dates is serialized with the default period, so this line acknowledges the callback without resolving or settling its overage instead of raising the existing retryable `required_state_missing` error; only bypass the check after confirming that the payer has no subscription.</comment>

<file context>
@@ -216,6 +217,11 @@ export async function checkAndBillPayerOverageThreshold(
 ): Promise<ThresholdSettlementOutcome | undefined> {
+  // Spend admitted without a subscription stays in the open period, which no subscription the
+  // payer gains later ever spans, so it has no threshold to settle.
+  if (options.expectedBillingPeriod && isDefaultBillingPeriod(options.expectedBillingPeriod)) {
+    return noOp(options, 'no-subscription')
+  }
</file context>
Fix with cubic

@greptile-apps

greptile-apps Bot commented Oct 31, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerView in GreptileConfidence Score: 4/5

[Medium impact] The change reaches cost callbacks in a bounded billing workflow.

The billing fix appears sound, but the exported helper must meet the repository’s documentation requirement before merging.

Findings

  1. P2 The exported helper lacks documentation ▶

Summary

Fixes cost callbacks for runs admitted while the payer was free, then continued after the payer subscribed.

  • Cost callbacks for free runs still settle after the payer subscribes.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart TD
  A[Run admitted while payer is free] --> B[Spend uses the open default period]
  B --> C[Payer subscribes mid-run]
  C --> D[Callback records more cumulative spend]
  D --> E[Default period recognized]
  E --> F[Skip subscription threshold settlement]
  F --> G[Return 200 for increased spend or 409 for replay]
Loading

Reviews (1) · Last reviewed commit: "fix(billing): settle a free payer's run ..." · Reviewed by Greptile

}
}

export function isDefaultBillingPeriod(period: { start: Date; end: Date }): boolean {

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.

P2 The exported helper lacks documentation

isDefaultBillingPeriod is a new exported API without TSDoc. The repository requires TSDoc for exported APIs, so this must be addressed before merging. Add a short comment explaining that it recognizes the open fallback period by its dates, without requiring a source field.

Suggested change
export function isDefaultBillingPeriod(period: { start: Date; end: Date }): boolean {
/** Recognizes the open fallback billing period by its dates, without requiring a source field. */
export function isDefaultBillingPeriod(period: { start: Date; end: Date }): boolean {

Context Used: CLAUDE.md (source)

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

This branch was previously deployed

1 inactive deployment
Preview — 51198df5 Deployed Oct 31, 2026 by vercel[bot]
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.

1 participant