[Agent] Issue #397: add checkoutsessioncompleted event handl #404

Open
pook wants to merge 49 commits from agent-task/397 into main
Owner

Closes #397

Changes

feat: issue #397 add-checkoutsessioncompleted-event-handl (agent task liancebot397)

Acceptance Criteria

Add checkout.session.completed event handling to the existing webhook endpoint. On successful checkout: extract client_reference_id as user ID, Stripe customer ID, and subscription ID from the session. Create subscription record in database with status='active', linked to the user. Map the line item price to a plan tier using the config from #386. Wrap database operations in Prisma $transaction. Add test with mock checkout.session.completed event payload. This is the critical missing piece between the checkout session creation (#307, #343) and the subscription guard (#388) — without it, paying customers never get access.


Generated by CEO Planner (priority: 2)

Tokens: 18 in / 10343 out

Closes #397 ## Changes feat: issue #397 add-checkoutsessioncompleted-event-handl (agent task liancebot397) ## Acceptance Criteria Add checkout.session.completed event handling to the existing webhook endpoint. On successful checkout: extract client_reference_id as user ID, Stripe customer ID, and subscription ID from the session. Create subscription record in database with status='active', linked to the user. Map the line item price to a plan tier using the config from #386. Wrap database operations in Prisma $transaction. Add test with mock checkout.session.completed event payload. This is the critical missing piece between the checkout session creation (#307, #343) and the subscription guard (#388) — without it, paying customers never get access. --- *Generated by CEO Planner (priority: 2)* Tokens: 18 in / 10343 out
feat: issue #397 add-checkoutsessioncompleted-event-handl (agent task liancebot397)
Some checks failed
CI Quality Gate / Lint / Typecheck / Test / Build (pull_request) Has been cancelled
e901e1a515
Author
Owner

PR #404 Review: Checkout Session Completed Webhook (Issue #397)

Type check

npx tsc --noEmit (via bun run typecheck): PASS — all three workspace packages compile cleanly.

Tests

bun test checkout-completed.test.ts: PASS — 11 tests, 24 assertions, 0 failures.


Criteria Assessment

# Criterion Verdict Notes
1 Extracts customer ID and subscription ID from session object PASS Customer ID extracted from session.customer (handles both string and expanded object). Subscription ID extracted from session.subscription (same polymorphic handling). User ID resolved via client_reference_id with fallback to metadata.userId.
2 Updates user record in database with active subscription status PASS User record updated with resolved plan and stripeCustomerId inside a transaction. Subscription record upserted (insert if new, update if existing) with status, plan, and currentPeriodEnd. Plan resolved from actual Stripe price ID via priceIdToPlan(), not from metadata.
3 Handles missing user gracefully PASS Early return when userId is null/undefined (no client_reference_id and no metadata.userId). Early return when subscription is null. Both paths tested.
4 Idempotent (safe to receive same event twice) PASS Checks for existing subscription record by stripeSubId before deciding insert vs. update. If subscription already exists, updates in place rather than creating a duplicate. All DB operations wrapped in a single transaction for atomicity.

Additional observations

Improvements over prior implementation:

  • Wraps all DB writes in db.transaction() — the old code did not, risking partial writes on failure.
  • Uses client_reference_id (set at session creation) as primary user identifier instead of relying solely on metadata, which is more reliable.
  • Resolves plan from Stripe price ID rather than trusting metadata, preventing plan mismatch if metadata is stale or tampered.
  • Extracted priceIdToPlan() as a reusable exported function, reducing duplication with mapStripePlan().

No issues found. Code is clean, well-tested, and meets all four acceptance criteria.

## PR #404 Review: Checkout Session Completed Webhook (Issue #397) ### Type check `npx tsc --noEmit` (via `bun run typecheck`): **PASS** — all three workspace packages compile cleanly. ### Tests `bun test checkout-completed.test.ts`: **PASS** — 11 tests, 24 assertions, 0 failures. --- ### Criteria Assessment | # | Criterion | Verdict | Notes | |---|-----------|---------|-------| | 1 | Extracts customer ID and subscription ID from session object | **PASS** | Customer ID extracted from `session.customer` (handles both string and expanded object). Subscription ID extracted from `session.subscription` (same polymorphic handling). User ID resolved via `client_reference_id` with fallback to `metadata.userId`. | | 2 | Updates user record in database with active subscription status | **PASS** | User record updated with resolved `plan` and `stripeCustomerId` inside a transaction. Subscription record upserted (insert if new, update if existing) with `status`, `plan`, and `currentPeriodEnd`. Plan resolved from actual Stripe price ID via `priceIdToPlan()`, not from metadata. | | 3 | Handles missing user gracefully | **PASS** | Early return when `userId` is null/undefined (no `client_reference_id` and no `metadata.userId`). Early return when `subscription` is null. Both paths tested. | | 4 | Idempotent (safe to receive same event twice) | **PASS** | Checks for existing subscription record by `stripeSubId` before deciding insert vs. update. If subscription already exists, updates in place rather than creating a duplicate. All DB operations wrapped in a single transaction for atomicity. | ### Additional observations **Improvements over prior implementation:** - Wraps all DB writes in `db.transaction()` — the old code did not, risking partial writes on failure. - Uses `client_reference_id` (set at session creation) as primary user identifier instead of relying solely on metadata, which is more reliable. - Resolves plan from Stripe price ID rather than trusting metadata, preventing plan mismatch if metadata is stale or tampered. - Extracted `priceIdToPlan()` as a reusable exported function, reducing duplication with `mapStripePlan()`. **No issues found.** Code is clean, well-tested, and meets all four acceptance criteria.
Some checks failed
CI Quality Gate / Lint / Typecheck / Test / Build (pull_request) Has been cancelled
This pull request has changes conflicting with the target branch.
  • .forgejo/workflows/ci.yml
  • bun.lock
  • package.json
  • packages/api/src/db/schema.ts
  • packages/api/src/index.ts
  • packages/api/src/middleware/rate-limit.ts
  • packages/api/src/middleware/security-headers.ts
  • packages/api/src/routes/generate-tos.ts
  • packages/api/src/routes/generate.ts
  • packages/api/src/routes/health.ts
  • packages/api/src/routes/questionnaire.ts
  • packages/api/src/services/document-generator.ts
  • packages/api/src/services/llm.ts
  • packages/api/src/templates/index.ts
  • packages/api/tsconfig.json
  • packages/shared/src/types.ts
  • packages/web/src/app/questionnaire/page.tsx
  • packages/web/src/components/documents/DocumentList.tsx
  • packages/web/src/components/questionnaire/ReviewStep.tsx
View command line instructions

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u origin agent-task/397:agent-task/397
git switch agent-task/397
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
pook/compliancebot!404
No description provided.