Skip to content

[dev] [claudfuen] feat/pentest-subscription-billing#2220

Open
github-actions[bot] wants to merge 7 commits intomainfrom
feat/pentest-subscription-billing
Open

[dev] [claudfuen] feat/pentest-subscription-billing#2220
github-actions[bot] wants to merge 7 commits intomainfrom
feat/pentest-subscription-billing

Conversation

@github-actions
Copy link
Contributor

@github-actions github-actions bot commented Mar 4, 2026

Summary

  • Add Stripe subscription billing for penetration testing (subscribe, overage charges, usage tracking)
  • Gate the penetration tests page behind an active subscription (ModuleGate paywall)
  • Add billing page UI with subscription management, usage progress bar, and payment method section
  • Add Stripe webhook handler for subscription lifecycle events (renewals, cancellations, payment failures)
  • Preauthorize billing before creating a run (prevents orphaned runs on billing failure)
  • Overage confirmation dialog when all included runs are exhausted

Environment Variables

Three new env vars are required for pentest billing to function. All are optional — the app won't crash without them, but billing features will return descriptive errors.

Variable Purpose Example
STRIPE_PENTEST_SUBSCRIPTION_PRICE_ID Stripe Price ID for the monthly pentest subscription plan price_1T6vZa...
STRIPE_PENTEST_OVERAGE_PRICE_ID Stripe Price ID for per-run overage charges price_1T6va1...
STRIPE_PENTEST_WEBHOOK_SECRET Webhook signing secret for the /api/webhooks/stripe-pentest endpoint whsec_...

Stripe setup

  1. Create a recurring price (e.g. $99/month) → set as STRIPE_PENTEST_SUBSCRIPTION_PRICE_ID
  2. Create a one-time price for overage runs (e.g. $49) → set as STRIPE_PENTEST_OVERAGE_PRICE_ID
  3. Create a webhook endpoint pointing to <app-url>/api/webhooks/stripe-pentest listening for:
    • customer.subscription.updated
    • customer.subscription.deleted
    • invoice.payment_failed
  4. Copy the webhook signing secret → set as STRIPE_PENTEST_WEBHOOK_SECRET

Test plan

  • Unsubscribed org sees ModuleGate paywall on penetration tests page
  • "Get started" button redirects to Stripe Checkout
  • After checkout, subscription is active and usage shows "0/N runs"
  • Creating a run within limits succeeds without overage prompt
  • Creating a run over limit shows overage confirmation dialog
  • Confirming overage charges the card and creates the run
  • Cancelling overage dismisses the dialog without creating a run
  • Billing page shows correct status badges (Active/Cancelled/Past due/Not subscribed)
  • "Manage" button opens Stripe billing portal
  • Webhook correctly handles subscription updates, deletions, and payment failures

🤖 Generated with Claude Code

claudfuen and others added 5 commits March 3, 2026 21:44
Icons (AppShellWrapper — rail):
- Compliance: CertificateCheck → Badge (certification badge)
- Trust:      CloudAuditing   → Globe (public/global trust center)
- Security:   Security        → ManageProtection (protection management)

Icons (AppSidebar — compliance sidebar):
- Auditor View:  TaskComplete → DocumentSigned  (auditors sign off)
- Controls:      Security     → SettingsAdjust  (adjustable params, no dupe)
- Documents:     Catalog      → FolderDetails   (folder of docs)
- People:        Group        → UserMultiple     (standard multi-user)
- Risks:         Warning      → Scale           (weighing risk)
- Vendors:       ShoppingBag  → Partnership     (business partners)
- Questionnaire: Document     → DocumentTasks   (task-filled form)
- Integrations:  Integration  → Connect         (connecting systems)
- Cloud Tests:   Chemistry    → TestTool        (test tooling)

Overview:
- Replace <DraggableCards> with a plain 2-column grid
- Delete DraggableCards.tsx, SortableCard.tsx, useCardOrder.ts

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The id returned by the create API is the provider run ID stored
in providerRunId, not the internal ptr... primary key.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Show a locked marketing state when the org has no active pentest
subscription. Includes a reusable ModuleGate component in @comp/ui
with dark chrome-frame preview, and an animated pen-test demo that
cycles through agents and reveals findings with severity badges.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@vercel
Copy link

vercel bot commented Mar 4, 2026

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

Project Deployment Actions Updated (UTC)
app Ready Ready Preview, Comment Mar 4, 2026 9:34pm
1 Skipped Deployment
Project Deployment Actions Updated (UTC)
portal Skipped Skipped Mar 4, 2026 9:34pm

Request Review

@cursor
Copy link

cursor bot commented Mar 4, 2026

PR Summary

High Risk
Touches Stripe subscription and off-session charging logic and tightens authorization to admin/owner only, so mistakes could block access or incorrectly charge customers. Also changes subscription run limits and billing/checkout UX, which impacts production revenue flows.

Overview
Penetration testing is now subscription-gated with clearer billing/usage UX. The pentest page fetches PentestSubscription + run usage and either shows a locked upsell (ModuleGate + new PentestPreviewAnimation) or the run table with a usage indicator; creating a run now warns/requires confirmation when it will incur an overage charge.

Billing flow is hardened and moved earlier in the run lifecycle. Server actions now require org admin/owner (not just membership), block duplicate active subscriptions, add preauthorizePentestRun() to validate subscription + (if over limit) charge Stripe before creating the run using a client-stable nonce/idempotency key and improved payment-method fallback, and add getPentestPricing() for dynamic price display.

Billing page and Stripe webhook coverage are expanded. The billing page is reworked into a subscriptions table (active/cancelled/past_due states, usage/renewal, manage/fix payment CTAs) with an expanded pentest usage section; Stripe pentest webhook now has comprehensive tests for checkout completion and subscription update/delete events.

Data/API contract tweaks. PentestSubscription.includedRunsPerPeriod default changes from 3 to 1, period counting uses < currentPeriodEnd, and openapi.json is updated (new internal bulk automation failure notification endpoint, evidence submission delete, GitHub repos listing, and minor pentest webhook/payload schema adjustments).

Written by Cursor Bugbot for commit 1b06c7a. This will update automatically on new commits. Configure here.

- Add preauthorize billing check before run creation (prevents unbilled runs)
- Add dynamic pricing from Stripe (subscription + overage prices)
- Add usage tracking in pentest page header and create dialog
- Add overage confirmation dialog when included runs are exhausted
- Add billing page with subscription table, usage details, and payment method
- Add role-based auth (admin/owner only) for billing server actions
- Add duplicate subscription guard in subscribeToPentestPlan
- Add webhook unit tests (10 cases covering all event types)
- Fix period boundary queries (lte → lt) to prevent double-counting
- Fix payment method lookup to check subscription PM before customer PM
- Fix PaymentIntent config (off_session instead of conflicting options)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix animation interval spawning duplicate reset timers (added pausing flag)
- Use stable nonce ref to prevent duplicate overage charges on retry
- Remove dead getPentestUsage function and PentestUsage interface

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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.

});
if (existingSub?.status === 'active') {
throw new Error('Organization already has an active pentest subscription.');
}
Copy link

Choose a reason for hiding this comment

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

Duplicate Stripe subscription possible for past-due status

High Severity

The duplicate-subscription guard in subscribeToPentestPlan only blocks active status, but past_due subscriptions are not blocked. The pentest page checks hasActiveSubscription (only true for active), so a past_due subscriber sees the "Get started" button which calls subscribeToPentestPlan. This creates a brand-new Stripe subscription while the old past_due one still exists. The DB record is overwritten via upsert, so the old subscription becomes untracked — the customer gets billed twice by Stripe with no way to cancel the orphaned subscription through the app.

Additional Locations (1)

Fix in Cursor Fix in Web

const authResult = await preauthorizePentestRun(organizationId, nonceRef.current);
if (!authResult.authorized) {
throw new Error(authResult.error ?? 'Billing authorization failed.');
}
Copy link

Choose a reason for hiding this comment

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

Pre-creation billing check introduces overage race condition

Medium Severity

Moving billing authorization before run creation introduces a TOCTOU race. Two concurrent requests can both call preauthorizePentestRun, both see the same runsThisPeriod count below the included limit, and both get authorized: true without overage. Both then create runs, exceeding the included limit without any overage charge. The previous post-creation check didn't have this issue because the run already existed in the DB when the count was performed.

Additional Locations (1)

Fix in Cursor Fix in Web

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant