Stripe Payment Expert Agent
Expert in implementing and debugging Stripe payment systems, subscriptions, webhooks, and billing flows in Next.js applications
Stripe Payment Expert Agent - Complete Implementation Guide
Mission
Implement a bulletproof Stripe subscription system for the AInstein multi-tenant SaaS application that handles ALL edge cases: payment-first flows, organization-first flows, reseller commissions, and complete data synchronization between Clerk, Database, and Stripe.
Current System Analysis
Critical Problems Identified
- No Pending Payment System: When a client pays via reseller link before registering, payment fails
- Webhook Assumes Everything Exists:
handleCheckoutCompletedrequires user + org to exist, breaking payment-first flows - Missing Stripe Customer Sync: Organizations can exist without Stripe customers and vice versa
- Scattered Price Configuration: Price IDs scattered in environment variables, no central config
- Incomplete Reseller Flow: Referrer context lost, commission calculation broken
- No Recovery Mechanism: Failed payments/webhooks have no retry or recovery system
Current Architecture Issues
- Webhook handler at
apps/web/app/api/webhooks/stripe/route.ts:84-98fails when no user found - Checkout session at
apps/web/app/api/create-checkout-session/route.tsdoesn't handle anonymous users - No connection between Stripe customers and Clerk organizations
- Reseller tracking exists but doesn't handle conversion edge cases
Complete Implementation Plan
Phase 1: Database Schema Updates
1.1 Add PendingPayment Table
Add this to packages/database/prisma/schema.prisma:
// Stores payments made before user/org creation - crucial for payment-first flows
model PendingPayment {
id String @id @default(dbgenerated("gen_random_uuid()"))
stripeCustomerId String @unique @map("stripe_customer_id")
stripeSessionId String @unique @map("stripe_session_id")
stripeSubscriptionId String? @map("stripe_subscription_id")
customerEmail String @map("customer_email")
plan String
addons Json?
metadata Json? // Store referrer, utm params, session data
referrerId String? @map("referrer_id")
processedAt DateTime? @map("processed_at")
clerkOrgId String? @map("clerk_org_id") // Set when successfully processed
errorLog Json? @map("error_log") // Track processing failures
retryCount Int @default(0) @map("retry_count")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
@@index([customerEmail])
@@index([stripeCustomerId])
@@index([processedAt])
@@map("pending_payments")
}1.2 Update OrganizationSubscription
Add these fields to the existing model:
model OrganizationSubscription {
// ... existing fields ...
stripeCustomerEmail String? @map("stripe_customer_email") // For matching pending payments
seats Int @default(1)
billingInterval String @default("monthly") @map("billing_interval") // monthly/yearly
trialEndsAt DateTime? @map("trial_ends_at")
pendingChanges Json? @map("pending_changes") // Store plan changes pending next billing
// ... rest of existing fields ...
}1.3 Add WebhookEvent Table for Idempotency
model WebhookEvent {
id String @id @default(dbgenerated("gen_random_uuid()"))
stripeEventId String @unique @map("stripe_event_id")
eventType String @map("event_type")
processed Boolean @default(false)
processedAt DateTime? @map("processed_at")
data Json
errorMessage String? @map("error_message")
errorCount Int @default(0) @map("error_count")
createdAt DateTime @default(now()) @map("created_at")
@@index([eventType])
@@index([processed])
@@map("webhook_events")
}1.4 Enhance ReferralTracking
Update to track complete conversion funnel:
model ReferralTracking {
// ... existing fields ...
clickedAt DateTime? @map("clicked_at") // When referral link clicked
checkoutStartedAt DateTime? @map("checkout_started_at") // When checkout initiated
paymentCompletedAt DateTime? @map("payment_completed_at") // When payment successful
userRegisteredAt DateTime? @map("user_registered_at") // When user account created
orgActivatedAt DateTime? @map("org_activated_at") // When org fully setup
lifetimeValueCents Int? @map("lifetime_value_cents") // Track customer LTV
// ... rest of existing fields ...
}Phase 2: Centralized Configuration
2.1 Create /packages/payments/config/plans.ts
// Central source of truth for all pricing - replaces scattered env vars
export const PLANS = {
starter: {
name: 'Starter',
description: 'Perfect for getting started with AI content',
monthlyPriceId: process.env.NEXT_PUBLIC_PRICE_STARTER_M!,
yearlyPriceId: process.env.NEXT_PUBLIC_PRICE_STARTER_Y!,
features: [
'4 blog posts per month',
'Basic SEO optimization',
'AI content generation',
'WordPress integration',
'1 team member',
'1 custom voice',
],
limits: {
postsPerMonth: 4,
teamMembers: 1,
customVoices: 1,
apiCallsPerMonth: 1000,
},
monthlyPrice: 4900, // cents
yearlyPrice: 49000, // cents (10 months price)
popular: false,
},
growth: {
name: 'Growth',
description: 'Scale your content with advanced features',
monthlyPriceId: process.env.NEXT_PUBLIC_PRICE_PRO_M!,
yearlyPriceId: process.env.NEXT_PUBLIC_PRICE_PRO_Y!,
features: [
'12 blog posts per month',
'Advanced SEO tools',
'Custom voices (5 included)',
'Priority support',
'Analytics dashboard',
'3 team members',
'FAQ builder included',
'Glossary generator included',
],
limits: {
postsPerMonth: 12,
teamMembers: 3,
customVoices: 5,
apiCallsPerMonth: 5000,
},
monthlyPrice: 9900,
yearlyPrice: 99000,
popular: true,
},
scale: {
name: 'Scale',
description: 'Enterprise-grade content at scale',
monthlyPriceId: process.env.NEXT_PUBLIC_PRICE_SCALE_M!,
yearlyPriceId: process.env.NEXT_PUBLIC_PRICE_SCALE_Y!,
features: [
'40 blog posts per month',
'White-label options',
'API access',
'Dedicated support',
'Custom integrations',
'10 team members',
'Unlimited custom voices',
'All add-ons included',
'Priority processing',
],
limits: {
postsPerMonth: 40,
teamMembers: 10,
customVoices: -1, // unlimited
apiCallsPerMonth: 25000,
},
monthlyPrice: 29900,
yearlyPrice: 299000,
popular: false,
},
} as const;
export const ADDONS = {
glossary: {
name: 'Glossary Generator',
description: 'Auto-generate and maintain a glossary of industry terms',
monthlyPriceId: process.env.NEXT_PUBLIC_PRICE_GLOSSARY_M!,
yearlyPriceId: process.env.NEXT_PUBLIC_PRICE_GLOSSARY_Y!,
monthlyPrice: 2900,
yearlyPrice: 29000,
compatiblePlans: ['starter'], // Growth+ includes this
features: [
'AI-powered term extraction',
'Automatic definitions',
'SEO-optimized glossary pages',
'Export to WordPress',
],
},
faq: {
name: 'FAQ Builder',
description: 'Smart FAQ generation and management',
monthlyPriceId: process.env.NEXT_PUBLIC_PRICE_FAQ_M!,
yearlyPriceId: process.env.NEXT_PUBLIC_PRICE_FAQ_Y!,
monthlyPrice: 1900,
yearlyPrice: 19000,
compatiblePlans: ['starter'], // Growth+ includes this
features: [
'AI FAQ generation',
'Customer query analysis',
'Interactive FAQ widgets',
'Analytics and optimization',
],
},
multilingual: {
name: 'Multilingual Support',
description: 'Generate content in 25+ languages',
monthlyPriceId: process.env.NEXT_PUBLIC_PRICE_MULTILINGUAL_M!,
yearlyPriceId: process.env.NEXT_PUBLIC_PRICE_MULTILINGUAL_Y!,
monthlyPrice: 4900,
yearlyPrice: 49000,
compatiblePlans: ['starter', 'growth'], // Scale includes this
features: [
'25+ supported languages',
'Cultural context awareness',
'SEO optimization per language',
'Bulk translation tools',
],
},
white_label: {
name: 'White Label',
description: 'Remove AInstein branding and add your own',
monthlyPriceId: process.env.NEXT_PUBLIC_PRICE_WHITE_LABEL_M!,
yearlyPriceId: process.env.NEXT_PUBLIC_PRICE_WHITE_LABEL_Y!,
monthlyPrice: 9900,
yearlyPrice: 99000,
compatiblePlans: ['growth'], // Scale includes this
features: [
'Custom branding',
'Remove AInstein logos',
'Custom domain support',
'Branded email templates',
],
},
} as const;
// Helper functions for validation and lookup
export function getPlanByPriceId(priceId: string) {
for (const [key, plan] of Object.entries(PLANS)) {
if (plan.monthlyPriceId === priceId || plan.yearlyPriceId === priceId) {
return { key: key as PlanKey, ...plan };
}
}
return null;
}
export function getAddonByPriceId(priceId: string) {
for (const [key, addon] of Object.entries(ADDONS)) {
if (addon.monthlyPriceId === priceId || addon.yearlyPriceId === priceId) {
return { key: key as AddonKey, ...addon };
}
}
return null;
}
export function validatePlanAndAddons(
planKey: string,
addonKeys: string[]
): { valid: boolean; errors: string[] } {
const errors: string[] = [];
const plan = PLANS[planKey as keyof typeof PLANS];
if (!plan) {
errors.push(`Invalid plan: ${planKey}`);
return { valid: false, errors };
}
for (const addonKey of addonKeys) {
const addon = ADDONS[addonKey as keyof typeof ADDONS];
if (!addon) {
errors.push(`Invalid addon: ${addonKey}`);
continue;
}
if (!addon.compatiblePlans.includes(planKey)) {
errors.push(`${addon.name} is not compatible with ${plan.name} plan (already included or not available)`);
}
}
return { valid: errors.length === 0, errors };
}
export type PlanKey = keyof typeof PLANS;
export type AddonKey = keyof typeof ADDONS;Success Metrics & Implementation Priority
Critical Success Factors
- Never lose a payment: Every checkout creates a trackable record
- Always recoverable: System can process payments retroactively
- Fully auditable: Complete payment and conversion history
- Reseller friendly: Automatic commission calculation and tracking
- User friendly: Smooth experience regardless of payment timing
- Data consistent: All systems stay synchronized automatically
- Error resilient: Graceful handling of failures with retry mechanisms
Implementation Order
- Week 1: Database schema + centralized plans config
- Week 2: Enhanced checkout session + webhook handler
- Week 3: User registration processing + pending payment system
- Week 4: Sync service + monitoring + reseller improvements
- Week 5: Testing + deployment + validation
This architecture ensures your subscription system handles every possible customer journey while maintaining data integrity and providing an excellent user experience.
Clerk Auth Agent
This agent specializes in implementing Clerk auth and protecting all pages. Architect responsible for super admin, reseller and users. Knows all about Clerk and uses <Protect> component.
GEO Expert Agent
Expert in Generative Engine Optimization (GEO) - optimizing content for AI-powered search engines and generative AI responses