Skip to content

260 better checkout flow#261

Merged
InfinityBowman merged 6 commits into
mainfrom
260-better-checkout-flow
Jan 9, 2026
Merged

260 better checkout flow#261
InfinityBowman merged 6 commits into
mainfrom
260-better-checkout-flow

Conversation

@InfinityBowman
Copy link
Copy Markdown
Owner

@InfinityBowman InfinityBowman commented Jan 9, 2026

Summary by CodeRabbit

Release Notes

  • New Features

    • Promo code validation and application now available in checkout
    • Usage metrics (projects and collaborators) displayed in billing settings
    • Enhanced error messages for Stripe payment interactions
    • Automatic billing address collection during checkout
  • Style

    • Updated navigation icons throughout reconciliation interface

✏️ Tip: You can customize this high-level summary in your review settings.

@InfinityBowman InfinityBowman linked an issue Jan 9, 2026 that may be closed by this pull request
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jan 9, 2026

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

📝 Walkthrough

Walkthrough

This PR introduces a comprehensive Stripe-first billing redesign featuring KV-backed subscription caching, stateless plan resolution, config-driven entitlements, promo code validation, and usage metrics endpoints. The implementation spans shared error handling, backend billing routes, frontend UI components, and middleware integration with webhook-driven cache invalidation.

Changes

Cohort / File(s) Summary
Billing Core Infrastructure
packages/workers/src/lib/subscriptionCache.js, packages/workers/src/lib/stripeSubscription.js, packages/workers/src/lib/priceToPlan.js, packages/workers/src/lib/billingResolver.js, packages/workers/src/lib/__tests__/*
New KV-backed cache layer with TTL, Stripe subscription fetcher with fallback, price-to-plan mapping utilities, and plan resolution with dual-path testing. Includes unit and integration test scaffolding.
Billing API Endpoints
packages/workers/src/routes/billing/checkout.js, packages/workers/src/routes/billing/subscription.js
New POST /validate-coupon endpoint for promo code validation; new GET /usage endpoint returning project and collaborator counts per organization.
Shared Error & Pricing Configuration
packages/shared/src/errors/stripe.ts, packages/shared/src/errors/index.ts, packages/shared/src/plans/pricing.json, packages/shared/src/plans/access.js
New Stripe error mapping module with user-friendly messages and type guards; centralized pricing configuration and feature/usage limit access functions.
Middleware & Webhook Integration
packages/workers/src/middleware/requireEntitlement.js, packages/workers/src/routes/billing/webhooks.js, packages/workers/src/auth/config.js
Updated entitlement checks to use Stripe-backed plan resolution; webhook handlers for subscription cache invalidation; Stripe checkout config expanded for promo codes and address collection.
Frontend Billing Components
packages/web/src/components/billing/SubscriptionCard.jsx, packages/web/src/components/settings/pages/BillingSettings.jsx
URL route updates; usage data fetching via API with TanStack Query integration and fallback defaults; subscription/usage refetch on successful checkout.
Frontend Query Management
packages/web/src/lib/queryKeys.js
New billing.usage query key for caching organization usage metrics.
Frontend Reconciliation Icons
packages/web/src/components/project/reconcile-tab/ReconciliationWithPdf.jsx, packages/web/src/components/project/reconcile-tab/amstar2-reconcile/ChecklistReconciliation.jsx, packages/web/src/components/project/reconcile-tab/amstar2-reconcile/Footer.jsx, packages/web/src/components/project/reconcile-tab/robins-i-reconcile/*
Replaced AiOutline arrow icons with Fi-prefixed variants across reconciliation UI components.
Documentation & Planning
packages/docs/README.md, packages/docs/plans/flowglad-features-plan.md, packages/docs/plans/phase-4-embedded-checkout.md
Added clarification notes; comprehensive feature plan for Stripe-first billing redesign; Phase 4 plan for embedded Stripe Checkout flow with phased rollout strategy.
Test Updates
packages/workers/src/routes/__tests__/pdfs.test.js
Updated error code expectation from FILE_INVALID_TYPE to VALIDATION_FIELD_INVALID_FORMAT.

Sequence Diagram(s)

sequenceDiagram
    participant Client as Frontend
    participant API as Backend API
    participant KV as KV Cache
    participant Stripe as Stripe API
    participant DB as Database

    Client->>API: GET /api/billing/usage
    API->>DB: Query projects & collaborators for org
    DB-->>API: Return counts
    API-->>Client: { projects, collaborators }

    Client->>API: resolve plan for entitlements
    API->>KV: Check subscription cache
    alt Cache Hit
        KV-->>API: Cached subscription
    else Cache Miss
        API->>Stripe: Fetch subscription by customer ID
        Stripe-->>API: Active subscription
        API->>KV: Store with TTL
    end
    API->>API: Map price ID to plan tier
    API-->>Client: Plan tier + entitlements
Loading
sequenceDiagram
    participant Client as Frontend
    participant Checkout as Checkout Route
    participant Stripe as Stripe API
    participant Webhook as Webhook Handler
    participant KV as KV Cache

    Client->>Checkout: POST /validate-coupon { code }
    Checkout->>Stripe: Query promotionCodes
    Stripe-->>Checkout: Promo details or error
    Checkout-->>Client: { valid, discount, duration }

    Stripe->>Webhook: Event (subscription.created/updated)
    Webhook->>KV: invalidateSubscription(customerId)
    KV-->>Webhook: Cache cleared
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~65 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 inconclusive)
Check name Status Explanation Resolution
Title check ❓ Inconclusive The title '260 better checkout flow' is vague and generic, using the non-descriptive term 'better' without clarifying what specific improvements are being made to the checkout flow. Revise the title to be more specific and descriptive. For example: 'Implement Stripe-first stateless billing with KV cache and embedded checkout' or 'Redesign billing system with Stripe subscriptions and promo code support'.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed Docstring coverage is 91.67% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@cloudflare-workers-and-pages
Copy link
Copy Markdown

cloudflare-workers-and-pages Bot commented Jan 9, 2026

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
corates 57d88a3 Commit Preview URL Jan 09 2026, 05:49 PM

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🤖 Fix all issues with AI agents
In @packages/docs/plans/flowglad-features-plan.md:
- Around line 780-792: The PRICE_TO_PLAN mapping currently references
process.env (e.g., process.env.STRIPE_PRICE_TEAM_MONTHLY) which is incorrect for
Cloudflare Workers; update the docs to show using the Workers environment
bindings (e.g., the c.env or binding names) instead of process.env, and replace
or annotate every occurrence in the example for PRICE_TO_PLAN so it reads that
price IDs come from the Workers bindings (or pass them into the module) —
mention the exact symbol PRICE_TO_PLAN and show that values should be read from
the Worker binding names (or injected config) rather than process.env.

In @packages/docs/README.md:
- Line 5: The phrase "AI generated plans and audits" should use a hyphenated
compound modifier; update the README entry that currently reads "audits/ and
plans/ are for AI generated plans and audits of the codebase and features" to
"audits/ and plans/ are for AI-generated plans and audits of the codebase and
features" so that "AI-generated" correctly modifies "plans and audits."

In @packages/shared/src/errors/stripe.ts:
- Around line 78-97: The type guards isStripeCardError and
isStripeRateLimitError use wrong string literals; update them to check for
Stripe's actual error type values ('card_error' and 'rate_limit_error') instead
of 'StripeCardError'/'StripeRateLimitError', or alternatively import the Stripe
SDK and switch the guards to instanceof checks against
Stripe.errors.StripeCardError and Stripe.errors.StripeRateLimitError (choose one
approach and apply it to both functions).

In @packages/workers/src/routes/billing/checkout.js:
- Around line 23-32: Replace the manual body parsing and ad-hoc checks in the
billingCheckoutRoutes.post('/validate-coupon') handler with the validateRequest
middleware using a schema (e.g., validateCouponSchema) defined in the validation
config (validateCouponSchema = z.object({ code: z.string().min(1, 'Please enter
a promo code').trim() })). Attach validateRequest(validateCouponSchema) to the
route before the async handler, remove the c.req.json() and manual string/trim
checks, and read the validated value (from the request context provided by
validateRequest) inside the handler to continue the existing coupon validation
logic.
- Around line 39-41: Update the Stripe API version in the other billing route so
all Stripe clients use the same version; locate the Stripe client instantiation
(the new Stripe(...) call) in packages/workers/src/routes/billing/invoices.js
and change its apiVersion option from '2024-06-20' to '2025-11-17.clover' so it
matches the instantiation in packages/workers/src/routes/billing/checkout.js
(const stripe = new Stripe(..., { apiVersion: '2025-11-17.clover' })).
🧹 Nitpick comments (4)
packages/workers/src/routes/billing/checkout.js (1)

23-23: Consider adding rate limiting to prevent abuse.

The /validate-coupon endpoint lacks rate limiting, unlike the other checkout routes that use billingCheckoutRateLimit. This could allow attackers to enumerate valid promo codes or abuse the Stripe API.

🛡️ Suggested fix
-billingCheckoutRoutes.post('/validate-coupon', requireAuth, async c => {
+billingCheckoutRoutes.post('/validate-coupon', billingCheckoutRateLimit, requireAuth, async c => {
packages/docs/plans/phase-4-embedded-checkout.md (1)

39-56: Add language specifiers to ASCII diagram code blocks.

The code blocks for architecture diagrams should have a language specifier (e.g., text or plaintext) to satisfy linting and improve rendering consistency.

📝 Suggested fix
-```
+```text
 User clicks "Subscribe"
     |
     v
packages/web/src/components/settings/pages/BillingSettings.jsx (1)

101-101: Consider adding a comment explaining the side-effect initialization.

The useMembers() call without using its return value may confuse future maintainers. A brief comment would clarify intent.

📝 Suggested comment
-  useMembers(); // Initialize members context
+  // Initialize members context for side effects (populates shared state used by child components)
+  useMembers();
packages/docs/plans/flowglad-features-plan.md (1)

1190-1245: Add language specifiers to migration task code blocks.

Several code blocks in the migration plan section are missing language specifiers, which affects linting and rendering.

📝 Suggested fix example
-```
+```text
 Morning:
 - [ ] Create KV namespace...

Apply similar fix to code blocks at lines 1207, 1221, 1235, and 1413.

Comment on lines +780 to +792
const PRICE_TO_PLAN = {
// Team Monthly
[process.env.STRIPE_PRICE_TEAM_MONTHLY]: 'team',
// Team Yearly
[process.env.STRIPE_PRICE_TEAM_YEARLY]: 'team',
// Unlimited Team Monthly
[process.env.STRIPE_PRICE_UNLIMITED_MONTHLY]: 'unlimited_team',
// Unlimited Team Yearly
[process.env.STRIPE_PRICE_UNLIMITED_YEARLY]: 'unlimited_team',
// Starter Team (if exists)
[process.env.STRIPE_PRICE_STARTER_MONTHLY]: 'starter_team',
[process.env.STRIPE_PRICE_STARTER_YEARLY]: 'starter_team',
};
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Note: The PRICE_TO_PLAN mapping uses process.env which won't work in Cloudflare Workers.

The code sample shows process.env.STRIPE_PRICE_TEAM_MONTHLY but Cloudflare Workers use c.env or bindings, not process.env. Update the documentation to reflect the Workers environment.

📝 Suggested documentation fix
-// Price IDs from Stripe Dashboard
-// Format: price_xxx for live, price_test_xxx for test mode
-const PRICE_TO_PLAN = {
-  // Team Monthly
-  [process.env.STRIPE_PRICE_TEAM_MONTHLY]: 'team',
+// Price IDs from Stripe Dashboard (passed via env binding)
+// Format: price_xxx for live, price_test_xxx for test mode
+export function createPriceToPlanMap(env) {
+  return {
+    // Team Monthly
+    [env.STRIPE_PRICE_TEAM_MONTHLY]: 'team',
+    // ... rest
+  };
+}

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In @packages/docs/plans/flowglad-features-plan.md around lines 780 - 792, The
PRICE_TO_PLAN mapping currently references process.env (e.g.,
process.env.STRIPE_PRICE_TEAM_MONTHLY) which is incorrect for Cloudflare
Workers; update the docs to show using the Workers environment bindings (e.g.,
the c.env or binding names) instead of process.env, and replace or annotate
every occurrence in the example for PRICE_TO_PLAN so it reads that price IDs
come from the Workers bindings (or pass them into the module) — mention the
exact symbol PRICE_TO_PLAN and show that values should be read from the Worker
binding names (or injected config) rather than process.env.

Comment thread packages/docs/README.md

This package contains the VitePress documentation site for CoRATES.

Note the `audits/` and `plans/` are for AI generated plans and audits of the codebase and features.
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Fix hyphenation in compound modifier.

"AI generated" should be hyphenated when used as a compound adjective modifying "plans and audits."

📝 Proposed fix
-Note the `audits/` and `plans/` are for AI generated plans and audits of the codebase and features.
+Note the `audits/` and `plans/` are for AI-generated plans and audits of the codebase and features.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
Note the `audits/` and `plans/` are for AI generated plans and audits of the codebase and features.
Note the `audits/` and `plans/` are for AI-generated plans and audits of the codebase and features.
🧰 Tools
🪛 LanguageTool

[grammar] ~5-~5: Use a hyphen to join words.
Context: ...te the audits/ and plans/ are for AI generated plans and audits of the codeba...

(QB_NEW_EN_HYPHEN)

🤖 Prompt for AI Agents
In @packages/docs/README.md at line 5, The phrase "AI generated plans and
audits" should use a hyphenated compound modifier; update the README entry that
currently reads "audits/ and plans/ are for AI generated plans and audits of the
codebase and features" to "audits/ and plans/ are for AI-generated plans and
audits of the codebase and features" so that "AI-generated" correctly modifies
"plans and audits."

Comment on lines +78 to +97
export function isStripeCardError(error: unknown): boolean {
return (
typeof error === 'object' &&
error !== null &&
'type' in error &&
(error as { type: string }).type === 'StripeCardError'
);
}

/**
* Check if an error is a Stripe rate limit error
*/
export function isStripeRateLimitError(error: unknown): boolean {
return (
typeof error === 'object' &&
error !== null &&
'type' in error &&
(error as { type: string }).type === 'StripeRateLimitError'
);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🌐 Web query:

Stripe Node.js SDK v20 error type StripeCardError StripeRateLimitError

💡 Result:

Short answer: In stripe-node v20 the API still exposes specific Stripe error classes (e.g., StripeCardError, StripeRateLimitError) under Stripe.errors, and Stripe's error objects also include a type/code you can inspect (e.g., "card_error" / "rate_limit"). Catch them with try/catch and either check instanceof Stripe.errors. or inspect err.type / err.code.

Example (Node.js / v20):

  • Using instanceof:
    try {
    await stripe.paymentIntents.create({...});
    } catch (err) {
    if (err instanceof Stripe.errors.StripeCardError) {
    // handle card/payment error
    } else if (err instanceof Stripe.errors.StripeRateLimitError) {
    // handle rate limit
    } else {
    // other Stripe errors
    }
    }

  • Or inspect properties:
    catch (err) {
    if (err.type === 'card_error') { /* ... / }
    if (err.type === 'rate_limit_error') { /
    ... */ }
    }

Sources: Stripe Error handling docs and stripe-node error-handling docs. [1][2]

Sources
[1] Stripe — Error handling (official docs).
[2] stripe-node — Error Handling (GitHub / wiki).


Fix incorrect Stripe error type strings.

The type guards check for 'StripeCardError' and 'StripeRateLimitError', but the actual Stripe SDK error type property values are 'card_error' and 'rate_limit_error' respectively. These functions will never match real Stripe errors. Either use instanceof checks with Stripe.errors.StripeCardError and Stripe.errors.StripeRateLimitError, or correct the type string comparisons to use the actual error type values.

🤖 Prompt for AI Agents
In @packages/shared/src/errors/stripe.ts around lines 78 - 97, The type guards
isStripeCardError and isStripeRateLimitError use wrong string literals; update
them to check for Stripe's actual error type values ('card_error' and
'rate_limit_error') instead of 'StripeCardError'/'StripeRateLimitError', or
alternatively import the Stripe SDK and switch the guards to instanceof checks
against Stripe.errors.StripeCardError and Stripe.errors.StripeRateLimitError
(choose one approach and apply it to both functions).

Comment on lines +23 to +32
billingCheckoutRoutes.post('/validate-coupon', requireAuth, async c => {
const logger = createLogger({ c, service: 'billing', env: c.env });

try {
const body = await c.req.json();
const { code } = body;

if (!code || typeof code !== 'string' || code.trim().length === 0) {
return c.json({ valid: false, error: 'Please enter a promo code' });
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Use validateRequest middleware instead of manual body parsing.

As per coding guidelines, request bodies should be validated using validateRequest middleware rather than manual validation. This ensures consistent validation behavior across routes.

♻️ Suggested refactor

First, add a validation schema (in your validation config):

// In validation config
export const validateCouponSchema = z.object({
  code: z.string().min(1, 'Please enter a promo code').trim(),
});

Then refactor the route:

-billingCheckoutRoutes.post('/validate-coupon', requireAuth, async c => {
+billingCheckoutRoutes.post('/validate-coupon', requireAuth, validateRequest(validateCouponSchema), async c => {
   const logger = createLogger({ c, service: 'billing', env: c.env });

   try {
-    const body = await c.req.json();
-    const { code } = body;
-
-    if (!code || typeof code !== 'string' || code.trim().length === 0) {
-      return c.json({ valid: false, error: 'Please enter a promo code' });
-    }
+    const { code } = c.req.valid('json');

Based on learnings, validateRequest middleware should always be used for request body validation in API routes.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In @packages/workers/src/routes/billing/checkout.js around lines 23 - 32,
Replace the manual body parsing and ad-hoc checks in the
billingCheckoutRoutes.post('/validate-coupon') handler with the validateRequest
middleware using a schema (e.g., validateCouponSchema) defined in the validation
config (validateCouponSchema = z.object({ code: z.string().min(1, 'Please enter
a promo code').trim() })). Attach validateRequest(validateCouponSchema) to the
route before the async handler, remove the c.req.json() and manual string/trim
checks, and read the validated value (from the request context provided by
validateRequest) inside the handler to continue the existing coupon validation
logic.

Comment on lines +39 to +41
const stripe = new Stripe(c.env.STRIPE_SECRET_KEY, {
apiVersion: '2025-11-17.clover',
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check Stripe API version usage across the codebase
rg -n "apiVersion" --type js --type ts -A 1 -B 1

Repository: InfinityBowman/corates

Length of output: 6581


🏁 Script executed:

#!/bin/bash
# Check for all Stripe instantiations to see version patterns
rg -n "new Stripe" --type js --type ts -A 2

Repository: InfinityBowman/corates

Length of output: 3753


🏁 Script executed:

#!/bin/bash
# Check the specific file for context
cat -n packages/workers/src/routes/billing/checkout.js | head -50

Repository: InfinityBowman/corates

Length of output: 2228


🌐 Web query:

Stripe API version 2025-11-17.clover valid

💡 Result:

Yes — 2025-11-17.clover is a valid Stripe API version (a Clover release). Use it by sending the request header Stripe-Version: 2025-11-17.clover (or upgrade your account in Workbench). [1][2]

Sources:

  • Clover overview (Stripe changelog). [1]
  • Example changelog entry showing usage and Stripe-Version header. [2]

🌐 Web query:

Stripe API versioning format clover suffix

💡 Result:

Stripe now uses date-based releases with a named “major” suffix (e.g., 2025-09-30.clover). The format is:

  • YYYY-MM-DD. — the date is the release epoch and the suffix (like clover) is the major release name. Monthly updates under the same major keep the same suffix; a new major name indicates a breaking-change release. [1][2]

References:

  • Stripe changelog / "Clover" release notes. [1]
  • Stripe API Versioning documentation (explains YYYY-MM-DD. and major vs monthly releases). [2]

Standardize Stripe API version across all routes.

The API version 2025-11-17.clover is valid and current. However, packages/workers/src/routes/billing/invoices.js uses the older version 2024-06-20. Align all Stripe client instantiations to use the same API version (2025-11-17.clover) for consistent behavior across billing operations.

🤖 Prompt for AI Agents
In @packages/workers/src/routes/billing/checkout.js around lines 39 - 41, Update
the Stripe API version in the other billing route so all Stripe clients use the
same version; locate the Stripe client instantiation (the new Stripe(...) call)
in packages/workers/src/routes/billing/invoices.js and change its apiVersion
option from '2024-06-20' to '2025-11-17.clover' so it matches the instantiation
in packages/workers/src/routes/billing/checkout.js (const stripe = new
Stripe(..., { apiVersion: '2025-11-17.clover' })).

@InfinityBowman InfinityBowman merged commit 2ab42d9 into main Jan 9, 2026
4 checks passed
@InfinityBowman InfinityBowman deleted the 260-better-checkout-flow branch January 9, 2026 17:50
@coderabbitai coderabbitai Bot mentioned this pull request Mar 21, 2026
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.

Better checkout flow

1 participant