Skip to content

Conversation

@icecrasher321
Copy link
Collaborator

Summary

  • Run from block should call preprocess executions for billing checks
  • Duplicate active subs should loudly error
  • Logger should hit billing actor similar to preprocess
  • Consolidate into helpers for dup checks, block/unblock users

Type of Change

  • Bug fix
  • Other: Code Improvement

Testing

Tested manually

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)

@vercel
Copy link

vercel bot commented Feb 1, 2026

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

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
docs Skipped Skipped Feb 1, 2026 8:03pm

Request Review

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Feb 1, 2026

Greptile Overview

Greptile Summary

This PR strengthens billing integrity by adding duplicate subscription checks, ensuring billing actor consistency, and extending billing enforcement to run-from-block executions.

Key Changes

  • Run-from-block billing enforcement: execute-from-block route now calls preprocessExecution to enforce billing checks, usage limits, and rate limits before execution
  • Duplicate subscription prevention: Added hasActiveSubscription checks across checkout, subscription transfer, and organization creation flows to prevent multiple active subscriptions on the same referenceId
  • Logger billing consistency: ExecutionLogger now uses workspace billed account resolution (via getWorkspaceBilledAccountUserId) matching the preprocessing logic
  • Consolidated blocking helpers: Created blockOrgMembers and unblockOrgMembers functions to centralize organization member blocking/unblocking logic
  • Enhanced dispute handling: Dispute webhook now blocks all org members (not just owner) and handles warning_closed status for unblocking
  • Organization deletion safety: Subscription deletion now checks for other active subscriptions before deleting organizations

Architecture Impact

The changes consolidate billing logic into reusable helpers and ensure consistent billing actor resolution across execution paths. The duplicate subscription checks create multiple defense layers at authorization, checkout, transfer, and organization creation points.

Confidence Score: 5/5

  • Safe to merge - improves billing integrity with defensive checks
  • All changes are defensive improvements to billing logic. The duplicate subscription checks prevent edge cases, billing actor consistency ensures correct cost attribution, and the run-from-block enforcement closes a bypass. Code is well-structured with proper error handling.
  • No files require special attention

Important Files Changed

Filename Overview
apps/sim/app/api/workflows/[id]/execute-from-block/route.ts Added preprocessExecution call to enforce billing checks, rate limits, and usage limits for run-from-block executions
apps/sim/lib/logs/execution/logger.ts Updated to use workspace billed account for cost tracking, matching billing actor logic from preprocessing
apps/sim/lib/billing/organizations/membership.ts Added helper functions blockOrgMembers, unblockOrgMembers, and getOrgMemberIds to centralize organization member blocking logic
apps/sim/lib/billing/authorization.ts Added duplicate subscription check in authorization to prevent multiple active subscriptions on same referenceId
apps/sim/lib/billing/webhooks/invoices.ts Refactored to use blockOrgMembers and unblockOrgMembers helpers for consistent member blocking on payment failures

Sequence Diagram

sequenceDiagram
    participant Client
    participant ExecuteAPI as Execute-from-Block API
    participant Preprocessing as preprocessExecution
    participant Billing as Billing Module
    participant Logger as ExecutionLogger
    participant DB as Database

    Client->>ExecuteAPI: POST /workflows/{id}/execute-from-block
    ExecuteAPI->>Preprocessing: Check billing & limits
    Preprocessing->>DB: Fetch workflow record
    DB-->>Preprocessing: Workflow details
    Preprocessing->>Billing: Get workspace billed account
    Billing-->>Preprocessing: actorUserId
    Preprocessing->>Billing: Check usage limits
    Billing-->>Preprocessing: Usage check result
    alt Usage exceeded or blocked
        Preprocessing-->>ExecuteAPI: Error (402/403)
        ExecuteAPI-->>Client: Execution blocked
    else Checks passed
        Preprocessing-->>ExecuteAPI: Success with actorUserId
        ExecuteAPI->>ExecuteAPI: Execute workflow
        ExecuteAPI->>Logger: Log execution with costs
        Logger->>Billing: Get workspace billed account
        Billing-->>Logger: billingUserId
        Logger->>DB: Update userStats (billing actor)
        DB-->>Logger: Updated
        Logger-->>ExecuteAPI: Logged
        ExecuteAPI-->>Client: Execution result
    end
Loading

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

5 files reviewed, no comments

Edit Code Review Agent Settings | Greptile

Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.

.where(and(inArray(userStats.userId, memberIds), eq(userStats.billingBlockedReason, reason)))

return memberIds.length
}
Copy link

Choose a reason for hiding this comment

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

Return value doesn't reflect actual unblocked member count

Low Severity

The unblockOrgMembers function returns memberIds.length (total org members), but the SQL update only affects members where billingBlockedReason matches the provided reason. This means the return value doesn't reflect how many users were actually unblocked. The memberCount value logged in disputes.ts and elsewhere will be misleading, potentially causing confusion during incident response or auditing.

Fix in Cursor Fix in Web

organizationId: orgId,
memberCount,
status: dispute.status,
})
Copy link

Choose a reason for hiding this comment

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

Org members may stay blocked after dispute won

Medium Severity

The handleDisputeClosed function unblocks Pro users unconditionally (line 110), but uses unblockOrgMembers(orgId, 'dispute') for org members which only unblocks those with billingBlockedReason='dispute'. If a payment failure occurs while a dispute is pending, blockOrgMembers overwrites the reason to 'payment_failed'. When the dispute is later won, org members won't be unblocked because their reason no longer matches 'dispute'. The old code unblocked the owner unconditionally, matching the Pro user behavior.

Fix in Cursor Fix in Web

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.

2 participants