# Plan: Credit Card Only Payment & Registration Flow

**Created**: 2025-11-04
**Status**: Planning
**Payment Method**: Credit/Debit Cards Only (Instant Payment)
**Objective**: Simple, instant registration flow with immediate site activation after payment

---

## Executive Summary

**Decision**: Credit card payments only
**Why**: Instant payment confirmation = simpler flow, better UX, no waiting period

**Flow**:
1. User pays with card on Stripe → Payment succeeds instantly
2. Stripe redirects to our app `/registration/welcome?session_id=xxx`
3. Webhook creates dealer with `status: 'registration_pending'`
4. User chooses subdomain and fills profile
5. User clicks "Publish" → Site goes live (`status: 'active'`)

**No complexity needed for**:
- ❌ Async payment processing
- ❌ Payment pending states
- ❌ "Waiting for payment" UI
- ❌ Multiple status transitions

---

## Part 1: Status Flow (Simplified)

### 1.1 Status Values (3 Total)

```typescript
"registration_pending"  // Payment cleared, awaiting subdomain selection
"active"               // Fully onboarded and published
"cancelled"            // Subscription cancelled (Stripe webhook)
```

**Optional** (for admin actions):
```typescript
"suspended"  // Admin temporarily disabled account
```

### 1.2 Status Transition Diagram

```
┌─────────────────┐
│ Stripe Checkout │
│ (Card Payment)  │
└────────┬────────┘
         │
         ▼
   payment_status
     = 'paid'
         │
         ▼
   Webhook fires:
   checkout.session
     .completed
         │
         ▼
   Create Dealer
   status =
 'registration_
    pending'
         │
         ▼
  User chooses
   subdomain &
  fills profile
         │
         ▼
   User clicks
    "Publish"
         │
         ▼
   status =
   'active'
```

### 1.3 State Transition Rules

| From                   | To          | Trigger                           | Action                        |
|------------------------|-------------|-----------------------------------|-------------------------------|
| (none)                 | `registration_pending` | checkout.session.completed (card) | Create dealer, send welcome email |
| `registration_pending` | `active`    | User submits "Publish" button     | Publish site, send "live" email |
| `active`               | `cancelled` | customer.subscription.deleted     | Unpublish site                |
| `active`               | `suspended` | Admin action                      | Temporarily disable site      |
| `suspended`            | `active`    | Admin action                      | Re-enable site                |

---

## Part 2: Stripe Configuration

### 2.1 Payment Method Restrictions

**Enable**: Credit/Debit Cards only
**Disable**: ACH, Bank transfers, SEPA, etc.

### 2.2 Update Payment Links

All 4 payment links need:
1. **Redirect URL** to our app (not Stripe success page)
2. **Payment methods** restricted to cards

```bash
# Update all 4 payment links with redirect
stripe payment_links update plink_1SPkoqF5aYvXB94WtV738gPl \
  --after-completion[type]=redirect \
  --after-completion[redirect][url]="http://localhost:3000/registration/welcome?session_id={CHECKOUT_SESSION_ID}" \
  --payment-method-types[]=card

stripe payment_links update plink_1SPkwBF5aYvXB94W41nM2EMq \
  --after-completion[type]=redirect \
  --after-completion[redirect][url]="http://localhost:3000/registration/welcome?session_id={CHECKOUT_SESSION_ID}" \
  --payment-method-types[]=card

stripe payment_links update plink_1SPkwDF5aYvXB94WJPFVBVUC \
  --after-completion[type]=redirect \
  --after-completion[redirect][url]="http://localhost:3000/registration/welcome?session_id={CHECKOUT_SESSION_ID}" \
  --payment-method-types[]=card

stripe payment_links update plink_1SPkwEF5aYvXB94WYBq1RxaJ \
  --after-completion[type]=redirect \
  --after-completion[redirect][url]="http://localhost:3000/registration/welcome?session_id={CHECKOUT_SESSION_ID}" \
  --payment-method-types[]=card
```

**Production URLs**: Replace `http://localhost:3000` with `https://amsoil-dlp.acdev3.com`

---

## Part 3: Webhook Handler

### 3.1 Current State

File: `app/api/webhooks/stripe/route.ts`

**Already handles**:
- ✅ Webhook signature verification
- ✅ Event deduplication via `StripeWebhookEvent` table
- ✅ `checkout.session.completed` event
- ✅ `customer.subscription.updated` event
- ✅ `customer.subscription.deleted` event
- ✅ Idempotent dealer creation

### 3.2 What Needs To Change

**NOTHING!** Current webhook handler already works perfectly for credit cards:

```typescript
case 'checkout.session.completed': {
  const session = event.data.object as Stripe.Checkout.Session

  // Only process if payment succeeded
  if (session.payment_status !== 'paid') {
    console.log('Checkout session not paid yet, skipping')
    return NextResponse.json({ received: true, event: type })
  }

  // Create dealer account (idempotent)
  const result = await createDealerFromCheckout(fullSession)
  // ✅ Dealer created with status: 'registration_pending'
}
```

**Why it works**:
- Credit card payments → `payment_status = 'paid'` immediately
- Dealer created with `status: 'registration_pending'` (from `lib/stripe-webhook-handlers.ts:187`)
- No async payment handling needed

### 3.3 Optional: Simplify Later

We can remove the async payment case if we want to clean up:

```typescript
// This case is not needed for credit cards
case 'checkout.session.async_payment_succeeded': {
  // Can delete this entire case
}
```

But **not urgent** - it doesn't hurt to leave it there.

---

## Part 4: Registration Routes

### 4.1 Route: `/registration/welcome`

**Purpose**: Landing page immediately after Stripe checkout
**File**: `app/registration/welcome/page.tsx` (NEW)

**Functionality**:
```typescript
export default async function RegistrationWelcome({
  searchParams
}: {
  searchParams: { session_id?: string }
}) {
  // 1. Parse session_id from URL
  const sessionId = searchParams.session_id

  if (!sessionId) {
    redirect('/pricing')
  }

  // 2. Retrieve session from Stripe
  const session = await stripe.checkout.sessions.retrieve(sessionId)

  // 3. Verify payment succeeded
  if (session.payment_status !== 'paid') {
    return <PaymentFailedError />
  }

  // 4. Check if dealer already exists
  const dealer = await prisma.dealer.findFirst({
    where: { stripeCustomerId: session.customer as string }
  })

  if (dealer?.status === 'active') {
    // Already fully registered
    redirect('/dashboard')
  }

  // 5. Show welcome message
  return (
    <WelcomeScreen
      customerName={session.customer_details?.name}
      subscriptionTier={getTierFromSession(session)}
    />
  )
}
```

**UI**:
```tsx
<WelcomeScreen>
  <h1>Welcome to AMSOIL Dealer Pages!</h1>
  <p>Your payment was successful. Let's get your site set up.</p>
  <p>Subscription: {subscriptionTier} - ${amount}/month</p>
  <Button href="/registration/onboarding">
    Continue to Setup
  </Button>
</WelcomeScreen>
```

### 4.2 Route: `/registration/onboarding`

**Purpose**: Multi-step form for subdomain selection and profile setup
**File**: `app/registration/onboarding/page.tsx` (NEW)

**Steps**:

#### Step 1: Subdomain Selection (Required First)
```tsx
<SubdomainStep>
  <Input
    placeholder="yourname"
    suffix=".amsoil.com (or .ca)"
    onBlur={checkAvailability}
  />
  {available && <SuccessMessage>Available!</SuccessMessage>}
  {!available && <ErrorMessage>Already taken</ErrorMessage>}
  <Button disabled={!available}>Continue</Button>
</SubdomainStep>
```

**API**: `POST /api/dealers/[id]/subdomain`
```typescript
// Atomically claim subdomain
await prisma.dealer.update({
  where: { id: dealerId },
  data: { subdomain: requestedSubdomain }
})
```

#### Step 2: Business Profile ✅
- Business name
- Contact info (pre-filled from Stripe)
  - Email (read-only)
  - Phone
  - Address
  - City, State, ZIP

#### Step 3: Review & Publish ✅

**Note**: Page content customization (hero text, about section, services, etc.) is OUT OF SCOPE for initial implementation.

```tsx
<ReviewStep>
  {/* Preview functionality OUT OF SCOPE for initial implementation */}

  <BusinessInfoSummary />

  <PublishButton onClick={handlePublish}>
    Publish Site
  </PublishButton>
</ReviewStep>
```

**Publish Handler**:
```typescript
async function handlePublish() {
  const response = await fetch(`/api/dealers/${dealerId}/publish`, {
    method: 'POST'
  })

  if (response.ok) {
    // Update status to 'active'
    redirect('/dashboard?published=true')
  }
}
```

### 4.3 Route: `/dashboard`

**Purpose**: Main dealer dashboard after publishing
**File**: `app/dashboard/page.tsx` (may exist)

**Shows**:
- Site status (Published / Draft)
- Analytics (if tier allows)
- Edit profile button
- Manage subscription
- View live site link

---

## Part 5: API Endpoints

### 5.1 Check Subdomain Availability

**File**: `app/api/check-subdomain/route.ts` (ALREADY EXISTS!)

Located at: `app/api/check-subdomain/route.ts:103`

**Already implements**:
- ✅ Subdomain validation
- ✅ Availability checking
- ✅ Domain assignment (.com vs .ca)
- ✅ Rate limiting

**No changes needed!**

### 5.2 Publish Site

**File**: `app/api/dealers/[id]/publish/route.ts` (NEW)

```typescript
import { NextRequest, NextResponse } from 'next/server'
import { prisma } from '@/lib/prisma'
import { getServerSession } from 'next-auth'

export async function POST(
  request: NextRequest,
  { params }: { params: { id: string } }
) {
  // 1. Verify user is authenticated
  const session = await getServerSession()
  if (!session?.user) {
    return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
  }

  // 2. Find dealer and verify ownership
  const dealer = await prisma.dealer.findUnique({
    where: { id: params.id },
    include: { user: true }
  })

  if (!dealer) {
    return NextResponse.json({ error: 'Dealer not found' }, { status: 404 })
  }

  if (dealer.user.email !== session.user.email) {
    return NextResponse.json({ error: 'Forbidden' }, { status: 403 })
  }

  // 3. Verify subdomain is set
  if (!dealer.subdomain) {
    return NextResponse.json(
      { error: 'Subdomain required before publishing' },
      { status: 400 }
    )
  }

  // 4. Publish site
  await prisma.dealer.update({
    where: { id: dealer.id },
    data: { status: 'active' }
  })

  // TODO: Trigger static site generation
  // TODO: Send "Site Published" email

  return NextResponse.json({
    success: true,
    siteUrl: `https://${dealer.subdomain}.amsoil.${dealer.domain}`
  })
}
```

---

## Part 6: Email Notifications

> **⚠️ STATUS: PENDING IMPLEMENTATION**
> Email notifications are designed but not yet implemented. TODO comments have been added in the codebase at the appropriate trigger points.

### 6.1 Welcome Email (On Payment Success)

**Trigger**: `checkout.session.completed` webhook
**Service**: Resend (already configured)
**Implementation Location**: `app/api/webhooks/stripe/route.ts` (TODO added)

```typescript
import { Resend } from 'resend'

const resend = new Resend(process.env.RESEND_API_KEY)

await resend.emails.send({
  from: process.env.RESEND_FROM_ADDRESS,
  to: customerEmail,
  subject: 'Welcome to AMSOIL Dealer Pages!',
  html: `
    <h1>Welcome!</h1>
    <p>Thank you for subscribing to ${subscriptionTier}.</p>
    <p>Let's get your dealer site set up.</p>
    <a href="${siteUrl}/registration/welcome?session_id=${sessionId}">
      Start Setup
    </a>
  `
})
```

### 6.2 Site Published Email

**Trigger**: User clicks "Publish" button
**Service**: Resend
**Implementation Location**: `app/api/dealers/[id]/publish/route.ts` (TODO added)

```typescript
await resend.emails.send({
  from: process.env.RESEND_FROM_ADDRESS,
  to: dealer.user.email,
  subject: 'Your AMSOIL Dealer Site is Live!',
  html: `
    <h1>Congratulations!</h1>
    <p>Your dealer page is now live at:</p>
    <a href="https://${subdomain}.amsoil.${domain}">
      https://${subdomain}.amsoil.${domain}
    </a>
  `
})
```

---

## Part 7: Testing Strategy

### 7.1 Test Credit Card Flow

**Test Cards** (Stripe):
```
Success: 4242 4242 4242 4242
Decline: 4000 0000 0000 0002
```

**Steps**:
1. Click payment link → Enter test card
2. Verify redirect to `/registration/welcome?session_id=xxx`
3. Verify dealer created with `status: 'registration_pending'`
4. Complete onboarding
5. Click "Publish"
6. Verify `status: 'active'`
7. Verify site accessible at subdomain

### 7.2 Test Subscription Management

**Events to test**:
- `customer.subscription.updated` → Tier change
- `customer.subscription.deleted` → Account cancellation

---

## Part 8: Implementation Phases

### Phase 1: Stripe Configuration ⏳
1. Update payment links with redirect URLs (TODO: needs production deployment)
2. Restrict to credit cards only (TODO: needs production deployment)
3. Test payment flow end-to-end (TODO: needs production deployment)

**Status**: Waiting for production deployment

### Phase 2: Registration Routes ✅
1. ✅ Create `/registration/welcome` page
2. ✅ Create `/registration/onboarding` page
3. ✅ Create multi-step form with subdomain selection
4. ❌ Add preview functionality (OUT OF SCOPE)

**Status**: Complete (preview functionality intentionally excluded)

### Phase 3: Publish API ✅
1. ✅ Create `/api/dealers/[id]/publish` endpoint
2. ✅ Add authentication checks
3. ✅ Update status to `active`
4. ✅ Test publishing flow

**Status**: Complete

### Phase 4: Email Notifications ⏳
1. 🔲 Set up Resend templates
2. 🔲 Add welcome email to webhook handler (TODO added in code)
3. 🔲 Add published email to publish endpoint (TODO added in code)
4. 🔲 Test email delivery

**Status**: Pending - TODO comments added at trigger points

### Phase 5: Testing & Polish ✅
1. ✅ TypeScript/ESLint fixes
2. ✅ Error handling
3. ✅ Loading states
4. ✅ Success messages

**Status**: Complete (end-to-end testing requires production deployment)

---

## Part 9: What We Already Have

### ✅ Already Implemented

1. **Database Schema** - `prisma/schema.prisma`
   - Dealer table with status field
   - User table with NextAuth
   - Webhook deduplication table

2. **Webhook Handler** - `app/api/webhooks/stripe/route.ts`
   - Signature verification
   - Event deduplication
   - Dealer creation
   - Subscription updates

3. **Subdomain API** - `app/api/check-subdomain/route.ts`
   - Validation
   - Availability checking
   - Rate limiting

4. **Helper Functions** - `lib/stripe-webhook-handlers.ts`
   - `createDealerFromCheckout()` - Already creates dealer with correct status
   - `getTierFromPriceId()` - Maps price IDs to tiers
   - Idempotency built-in

5. **Test Environment**
   - Local PostgreSQL database
   - Stripe CLI for webhook testing
   - Test payment links

### 🔲 Need to Build

1. `/registration/welcome` page
2. `/registration/onboarding` page
3. `/api/dealers/[id]/publish` endpoint
4. Email notifications
5. `/dashboard` page (if doesn't exist)

---

## Part 10: Differences from Async Plan

| Feature | Async Payment (ACH) | Credit Card (This Plan) |
|---------|---------------------|--------------------------|
| Payment time | 2-4 business days | Instant |
| Status states | 6 states | 3 states |
| Webhook events | 2+ events | 1 event |
| User experience | Wait + build profile | Instant activation |
| Publish gating | Payment-dependent | Always available after onboarding |
| Complexity | High | Low |
| Code changes | ~15 files | ~5 files |
| Development time | ~8 hours | ~4 hours |

---

## Part 11: Success Criteria

✅ **User Journey**:
1. User pays with card → Payment succeeds immediately
2. User redirected to welcome page
3. User completes onboarding
4. User publishes site → Site goes live instantly

✅ **Technical**:
- Webhook creates dealer with `status: 'registration_pending'`
- Subdomain claimed atomically (no race conditions)
- Publish button updates status to `active`
- Site accessible at chosen subdomain

✅ **No Edge Cases to Handle**:
- ❌ No payment pending states
- ❌ No out-of-order webhooks
- ❌ No async payment failures
- ❌ No waiting period UX

---

## Summary

**Total New Code**:
- 3 new routes (welcome, onboarding, publish API)
- 2 email templates
- Stripe payment link updates

**No Changes Needed**:
- Webhook handler ✅
- Database schema ✅
- Subdomain API ✅
- Helper functions ✅

**Estimated Development Time**: ~6-7 hours total

**Next Step**: Update Stripe payment links and start building `/registration/welcome`
