Skip to content

Signed BAA's - #2137

Open
richiemcilroy wants to merge 25 commits into
mainfrom
signed-baa
Open

Signed BAA's#2137
richiemcilroy wants to merge 25 commits into
mainfrom
signed-baa

Conversation

@richiemcilroy

@richiemcilroy richiemcilroy commented Aug 19, 2026

Copy link
Copy Markdown
Member

Add the ability for a user to purchase a signed BAA from Organization Settings

Greptile Summary

Adds organization-level Signed BAA purchasing, signing, PDF generation, email delivery, persistence, download support, and Stripe webhook synchronization. It also adds end-of-period Pro cancellation through seat management and a new security/compliance settings page.

  • Introduces a signed_baas database record and Stripe add-on subscription flow.
  • Generates and emails countersigned PDF agreements and exposes an owner-only download route.
  • Separates Signed BAA subscriptions from Pro entitlement and quota webhook handling.
  • Adds scheduled Pro cancellation and no-credit seat decreases.
  • Adds organization security and compliance UI.

Confidence Score: 1/5

The PR is not safe to merge until active-Pro eligibility, ambiguous Stripe creation recovery, and scheduled-cancellation partial failures are corrected.

The current flow can sell a BAA without the required Pro entitlement, leave a paying customer without an active local agreement after an ambiguous Stripe response, and silently revoke a scheduled Pro cancellation when a later seat update fails.

Files Needing Attention: apps/web/actions/organization/signed-baa.ts, apps/web/actions/organization/update-seat-quantity.ts, apps/web/app/api/webhooks/stripe/route.ts

Security Review

The purchase action does not enforce active Cap Pro status server-side. Because canceled users retain their Stripe identifiers, a former Pro owner can purchase the add-on despite the required base entitlement.

Important Files Changed

Filename Overview
apps/web/actions/organization/signed-baa.ts Implements the purchase workflow but permits canceled Pro users and cannot reconcile an ambiguously successful Stripe creation.
apps/web/actions/organization/update-seat-quantity.ts Adds scheduled cancellation and no-credit decreases, but clearing cancellation before a separate quantity update creates a partial-failure state.
apps/web/app/api/webhooks/stripe/route.ts Separates BAA subscriptions from Pro handling and cascades cancellation, but synchronization cannot recover rows missing the Stripe subscription ID.
apps/web/app/api/settings/billing/baa/download/route.ts Regenerates active agreements for authenticated organization owners with appropriate no-store download headers.
apps/web/lib/baa/generate-signed-baa-pdf.ts Generates a signed PDF from the committed template and stored agreement fields.
packages/database/schema.ts Adds the organization-unique Signed BAA persistence model with matching migration artifacts.
apps/web/app/(org)/dashboard/settings/organization/components/SignedBaaCard.tsx Adds the signing and purchase UI, though its eligibility display inherits the server action's identifier-only entitlement logic.
apps/web/app/(org)/dashboard/settings/organization/components/SeatManagementCard.tsx Adds cancellation confirmation and BAA cancellation messaging, but does not refresh subscription state after a partial server-side failure.
Prompt To Fix All With AI
### Issue 1
apps/web/actions/organization/signed-baa.ts:220-225
**Active Pro entitlement bypassed**

When a former Pro owner retains their Stripe identifiers after subscription deletion, this check treats them as eligible and creates a $99 Signed BAA subscription without an active Cap Pro plan. This violates the add-on's base-plan requirement and allows an agreement to become active independently of Pro.

**How this was verified:** The deletion webhook preserves both Stripe identifiers while this action never checks `stripeSubscriptionStatus` or `userIsPro` before creating the subscription.

### Issue 2
apps/web/actions/organization/signed-baa.ts:341-342
**Ambiguous Stripe success is orphaned**

If Stripe creates the subscription but its response is lost or times out, this catch resets the row to pending without recording the subscription ID and reports that no charge occurred. The webhook only reconciles rows already keyed by that ID, so the customer remains billed while the dashboard provides neither an active BAA nor the signed document.

### Issue 3
apps/web/actions/organization/update-seat-quantity.ts:199-203
**Failed seat change resumes billing**

When a canceling customer changes their seat count and the subsequent Stripe update or local persistence fails, this first update has already cleared `cancel_at_period_end` with no compensation. The requested change fails, but the subscription silently resumes at its old quantity and the client initially continues showing stale cancellation state.

### Issue 4
apps/web/actions/organization/signed-baa.ts:237-240
**Narrative comments duplicate implementation**

This comment restates the processing-row claim expressed by the adjacent update, and the same narrative pattern appears throughout the new billing and webhook code. Repository guidance reserves comments for non-obvious investigative context; duplicating the implementation adds maintenance burden and creates prose that can drift from the behavior.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Reviews (1): Last reviewed commit: "feat(billing): support cancel-to-zero fl..." | Re-trigger Greptile

Greptile also left 4 inline comments on this PR.

Context used (3)

@superagent-security

Copy link
Copy Markdown

🚨 Contributor flagged. Click here for more info: Superagent Dashboard

@socket-security

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Addednpm/​pdf-lib@​1.17.18810010080100

View full report

@socket-security

Copy link
Copy Markdown

Warning

Review the following alerts detected in dependencies.

According to your organization's Security Policy, it is recommended to resolve "Warn" alerts. Learn more about Socket for GitHub.

Action Severity Alert  (click "▶" to expand/collapse)
Warn High
Obfuscated code: npm pdf-lib is 75.0% likely obfuscated

Confidence: 0.75

Location: Package overview

From: apps/web/package.jsonnpm/pdf-lib@1.17.1

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/pdf-lib@1.17.1. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

View full report

Comment on lines +220 to +225
if (!user.stripeCustomerId) {
throw new Error(
"Your organization needs an active Cap Pro subscription before adding the Signed BAA add-on.",
);
}
const customerId = user.stripeCustomerId;

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 security Active Pro entitlement bypassed

When a former Pro owner retains their Stripe identifiers after subscription deletion, this check treats them as eligible and creates a $99 Signed BAA subscription without an active Cap Pro plan. This violates the add-on's base-plan requirement and allows an agreement to become active independently of Pro.

How this was verified: The deletion webhook preserves both Stripe identifiers while this action never checks stripeSubscriptionStatus or userIsPro before creating the subscription.

Knowledge Base Used: Web App (apps/web)

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/web/actions/organization/signed-baa.ts
Line: 220-225

Comment:
**Active Pro entitlement bypassed**

When a former Pro owner retains their Stripe identifiers after subscription deletion, this check treats them as eligible and creates a $99 Signed BAA subscription without an active Cap Pro plan. This violates the add-on's base-plan requirement and allows an agreement to become active independently of Pro.

**How this was verified:** The deletion webhook preserves both Stripe identifiers while this action never checks `stripeSubscriptionStatus` or `userIsPro` before creating the subscription.

**Knowledge Base Used:** [Web App (apps/web)](https://app.greptile.com/cap/-/custom-context/knowledge-base/capsoftware/cap/-/docs/web-app.md)

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Comment on lines +341 to +342
} catch (error) {
await revertToPending();

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 Ambiguous Stripe success is orphaned

If Stripe creates the subscription but its response is lost or times out, this catch resets the row to pending without recording the subscription ID and reports that no charge occurred. The webhook only reconciles rows already keyed by that ID, so the customer remains billed while the dashboard provides neither an active BAA nor the signed document.

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/web/actions/organization/signed-baa.ts
Line: 341-342

Comment:
**Ambiguous Stripe success is orphaned**

If Stripe creates the subscription but its response is lost or times out, this catch resets the row to pending without recording the subscription ID and reports that no charge occurred. The webhook only reconciles rows already keyed by that ID, so the customer remains billed while the dashboard provides neither an active BAA nor the signed document.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Comment on lines +199 to +203
if (subscription.cancel_at_period_end) {
await stripe().subscriptions.update(subscription.id, {
cancel_at_period_end: false,
});
}

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 Failed seat change resumes billing

When a canceling customer changes their seat count and the subsequent Stripe update or local persistence fails, this first update has already cleared cancel_at_period_end with no compensation. The requested change fails, but the subscription silently resumes at its old quantity and the client initially continues showing stale cancellation state.

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/web/actions/organization/update-seat-quantity.ts
Line: 199-203

Comment:
**Failed seat change resumes billing**

When a canceling customer changes their seat count and the subsequent Stripe update or local persistence fails, this first update has already cleared `cancel_at_period_end` with no compensation. The requested change fails, but the subscription silently resumes at its old quantity and the client initially continues showing stale cancellation state.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Comment on lines +237 to +240
const { signatureDataUrl, ...contractFields } = details;
const recordFields = { ...contractFields, signatureData: signatureDataUrl };

// Claim the record so concurrent submissions can never both reach Stripe.

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 Narrative comments duplicate implementation

This comment restates the processing-row claim expressed by the adjacent update, and the same narrative pattern appears throughout the new billing and webhook code. Repository guidance reserves comments for non-obvious investigative context; duplicating the implementation adds maintenance burden and creates prose that can drift from the behavior.

Context Used: AGENTS.md (source)

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/web/actions/organization/signed-baa.ts
Line: 237-240

Comment:
**Narrative comments duplicate implementation**

This comment restates the processing-row claim expressed by the adjacent update, and the same narrative pattern appears throughout the new billing and webhook code. Repository guidance reserves comments for non-obvious investigative context; duplicating the implementation adds maintenance burden and creates prose that can drift from the behavior.

**Context Used:** AGENTS.md ([source](https://github.com/capsoftware/cap/blob/main/AGENTS.md))

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

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!

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