> **ARCHIVED:** 2025-12-22 | OAuth implementation completed and working. Historical reference for the OAuth-before-payment architecture.

# OAuth Integration (Google & Apple) with Stripe Checkout - Implementation Guide

## Overview

This implementation adds OAuth authentication (Google or Apple) **before** Stripe checkout, ensuring users have a valid NextAuth session throughout the entire registration process. This solves the "Unauthorized" error that occurred when users tried to publish their sites without proper authentication.

## Architecture

### Flow Diagram

**New OAuth-Before-Payment Flow:**

```
Landing Page → Sign In (Google/Apple) → Stripe Checkout → Welcome Page → Onboarding → Publish
     ↓                ↓                      ↓                ↓              ↓           ↓
  /landing/     /auth/signin      /api/checkout/create-session   Auto-reload   Setup    ✅ Success
                  (OAuth)               (Authenticated)           (Webhook)    Domain
```

**Legacy Payment Link Flow (Still Supported):**

```
Direct Stripe Link → Stripe Checkout → Welcome Page → Onboarding → Publish (fails)
         ↓                  ↓               ↓              ↓            ↓
   Payment Link        No session      Webhook creates  Setup      ❌ Unauthorized
                                         bare User       Domain      (expected)
```

## Files Modified

### 1. Created Files

#### `app/api/checkout/create-session/route.ts` (NEW)

- **Purpose**: Creates Stripe Checkout Sessions for authenticated users
- **Key Features**:
  - Verifies user authentication via `getServerSession()`
  - Accepts `plan` query parameter (starter, growth, enhanced, professional)
  - Maps plan names to Stripe Price IDs from environment variables
  - Sets `client_reference_id` to authenticated user's ID
  - Redirects unauthenticated users to sign-in with callback URL
- **Endpoint**: `GET /api/checkout/create-session?plan=<plan_name>`

#### `app/auth/signin/page.tsx` (NEW)

- **Purpose**: Branded custom sign-in page for OAuth authentication
- **Key Features**:
  - Displays AMSOIL and AIMCLEAR logos
  - Shows selected plan name in header
  - "Sign in with Google" and "Sign in with Apple" buttons
  - Handles `callbackUrl` parameter for post-auth redirect
  - Matches site design aesthetic with gradient background
- **Route**: `/auth/signin`

### 2. Modified Files

#### `lib/stripe-webhook-handlers.ts`

- **Function Modified**: `createDealerFromCheckout()`
- **Changes**:
  - Added `client_reference_id` lookup priority
  - If `session.client_reference_id` exists, look up User by ID first
  - Falls back to email lookup for backward compatibility
  - Added comprehensive logging for debugging
  - Prevents duplicate User creation for OAuth users

#### `lib/auth.ts`

- **Section Modified**: NextAuth configuration
- **Changes**:
  1. Updated `pages.signIn` from `'/'` to `'/auth/signin'`
  2. **Critical Fix**: Updated `signIn` callback to create new users via OAuth
     - Previous behavior: Rejected sign-in if no existing User found
     - New behavior: Creates User + Account records for new OAuth sign-ins
     - Maintains backward compatibility for Stripe-created users
     - Supports both OAuth-before-payment and legacy flows

#### `public/landing/index.html`

- **Section Modified**: Subscribe button URLs
- **Location**: Marketing landing page (accessible at `/landing/`)
- **Changes**:
  - Changed all 4 Subscribe buttons from direct Stripe Payment Links to:
    - Starter: `/api/checkout/create-session?plan=starter`
    - Growth: `/api/checkout/create-session?plan=growth`
    - Enhanced: `/api/checkout/create-session?plan=enhanced`
    - Professional: `/api/checkout/create-session?plan=professional`
  - Enabled Professional plan (previously disabled)

#### `.env.example`

- **Section Modified**: Stripe configuration
- **Changes**: Added Stripe Price ID environment variables:
  ```bash
  STRIPE_PRICE_STARTER="price_xxx_starter_test"
  STRIPE_PRICE_GROWTH="price_xxx_growth_test"
  STRIPE_PRICE_ENHANCED="price_xxx_enhanced_test"
  STRIPE_PRICE_PROFESSIONAL="price_xxx_professional_test"
  ```

## Setup Instructions

### Step 1: Get Stripe Price IDs

You need to obtain the actual Stripe Price IDs for each plan. There are two methods:

#### Method A: From Stripe Dashboard (Recommended)

1. Go to https://dashboard.stripe.com/test/products
2. Click on each product (Starter, Growth, Enhanced, Professional)
3. Copy the Price ID (starts with `price_`)
4. Record each Price ID

#### Method B: Extract from Existing Payment Links

1. Go to https://dashboard.stripe.com/test/payment-links
2. Find your existing payment links:
   - `https://buy.stripe.com/test_bJe9ATdaW0aQ3lr7tEgUM04` (Starter)
   - `https://buy.stripe.com/test_aFa5kD5Iu9Lq4pvg0agUM05` (Growth)
   - `https://buy.stripe.com/test_7sYeVdb2ObTyf493dogUM06` (Enhanced)
3. Click on each link → View Details → Find the associated Price ID

### Step 2: Configure Environment Variables

Create or update `.env.local` in the project root:

```bash
# Copy from .env.example if needed
cp .env.example .env.local

# Then edit .env.local and replace the placeholder values:
STRIPE_PRICE_STARTER="price_1ABC123xyz..."
STRIPE_PRICE_GROWTH="price_1DEF456xyz..."
STRIPE_PRICE_ENHANCED="price_1GHI789xyz..."
STRIPE_PRICE_PROFESSIONAL="price_1JKL012xyz..."
```

### Step 3: Verify Existing Environment Variables

Ensure these are already set in `.env.local`:

```bash
# Database
DATABASE_URL="postgresql://..."

# NextAuth
NEXTAUTH_SECRET="your-secret-key"
NEXTAUTH_URL="http://localhost:3000"

# Google OAuth
GOOGLE_CLIENT_ID="your-google-client-id"
GOOGLE_CLIENT_SECRET="your-google-client-secret"

# Stripe
STRIPE_SECRET_KEY="sk_test_..."
STRIPE_WEBHOOK_SECRET="whsec_..."
```

### Step 4: Generate Prisma Client

```bash
npx prisma generate
```

### Step 5: Start Development Server

```bash
npm run dev
```

## Testing Guide

### Test Case 1: New User Registration (OAuth-Before-Payment)

**Objective**: Verify complete registration flow with OAuth authentication

**Steps**:

1. Clear all cookies and localStorage
2. Visit `http://localhost:3000/landing/`
3. Click "Subscribe" on any plan (e.g., Starter)
4. **Expected**: Redirected to `/auth/signin?callbackUrl=...`
5. Click "Sign in with Google"
6. Complete Google OAuth in popup
7. **Expected**: Redirected to Stripe checkout page
8. **Verify**: Email is pre-filled from Google account
9. Enter test card: `4242 4242 4242 4242`
10. Enter any future expiry date and CVV
11. Fill in address information
12. Click "Subscribe"
13. **Expected**: Redirected to `/registration/welcome?session_id=cs_test_xxx`
14. **Verify**: Welcome message displays with correct plan
15. Click "Continue to Setup"
16. Complete subdomain selection
17. Complete onboarding form
18. Click "Publish Site"
19. **Expected**: ✅ Site publishes successfully (NO "Unauthorized" error)

**Database Verification**:

```sql
-- Should return 1 User with OAuth data
SELECT id, email, name, image FROM "User" WHERE email = 'your-test@gmail.com';

-- Should return 1 Dealer linked to the User
SELECT id, "userId", "stripeCustomerId", status, "subscriptionTier"
FROM "Dealer" WHERE "userId" = '<user-id-from-above>';

-- Should return 1 Google Account linked to User
SELECT * FROM "Account" WHERE "userId" = '<user-id>' AND provider = 'google';
```

### Test Case 2: Returning User (Prevent Duplicates)

**Objective**: Verify no duplicate User records are created

**Steps**:

1. Sign out from Test Case 1
2. Visit landing page again
3. Click Subscribe on **different plan** (e.g., Growth)
4. Sign in with **same Google account**
5. Complete Stripe checkout
6. **Expected**: No errors, subscription updated

**Database Verification**:

```sql
-- Should still return only 1 User (no duplicates)
SELECT COUNT(*) FROM "User" WHERE email = 'your-test@gmail.com';
-- Expected: 1

-- Dealer record should be updated or new one created depending on business logic
SELECT * FROM "Dealer" WHERE "userId" = '<user-id>';
```

### Test Case 3: Backward Compatibility (Legacy Payment Links)

**Objective**: Verify old Payment Links still work

**Steps**:

1. Visit direct Stripe Payment Link (old URL):
   - `https://buy.stripe.com/test_bJe9ATdaW0aQ3lr7tEgUM04`
2. Complete payment without any prior authentication
3. **Expected**: Redirected to welcome page
4. **Expected**: User + Dealer created via webhook
5. **Note**: Publish will fail with "Unauthorized" (this is expected for legacy flow)

### Test Case 4: Edge Cases

#### 4a. User Closes Browser During OAuth

- **Expected**: NextAuth handles this automatically
- User can restart flow from landing page

#### 4b. User Closes Browser After OAuth, Before Stripe

- **Expected**: User record exists with OAuth account
- Can restart from Subscribe button
- Will be already authenticated, proceeds directly to Stripe

#### 4c. Webhook Arrives Before User Returns

- **Expected**: Welcome page has auto-reload component
- Page polls every 3 seconds until Dealer record exists

#### 4d. Invalid or Expired Session ID

- **Expected**: Welcome page shows error message
- Link to return home

## Debugging

### Enable Detailed Logging

The implementation includes comprehensive console logging:

**In Browser Console** (Next.js Dev):

- Check for NextAuth session data
- Verify redirect flows

**In Server Console** (Terminal running `npm run dev`):

```bash
# Look for these log messages:

# Checkout session creation:
"Using authenticated user: <user-id> (<email>)"

# Webhook processing:
"Using authenticated user: <user-id> (<email>)"
"Email mismatch: session email <email1> != user email <email2>. Using authenticated user."
"Creating new user: <email>"
"Using existing user found by email: <user-id> (<email>)"

# NextAuth signIn callback:
"Creating new user via OAuth: <email>"
"New user created via OAuth: <user-id>"
"Linked OAuth account: google to user: <user-id>"
```

### Common Issues

#### Issue 1: "Missing price ID for plan"

**Symptom**: 500 error when clicking Subscribe
**Solution**: Verify `.env.local` has all `STRIPE_PRICE_*` variables set

#### Issue 2: "Unauthorized" on Publish

**Symptom**: User can't publish site after completing registration
**Causes**:

- User went through old Payment Link (not OAuth flow)
- Session expired
- NextAuth signIn callback failed
  **Solution**: Check server logs for signIn callback errors

#### Issue 3: Infinite Redirect Loop

**Symptom**: User redirected between sign-in and checkout repeatedly
**Cause**: NextAuth configuration issue
**Solution**: Verify `NEXTAUTH_SECRET` and `NEXTAUTH_URL` are set correctly

#### Issue 4: Stripe Checkout Not Pre-filling Email

**Symptom**: Email field is empty in Stripe checkout
**Cause**: Session doesn't have email
**Solution**: Check session in `/api/checkout/create-session` logs

#### Issue 5: Duplicate User Records

**Symptom**: Multiple User records for same email
**Cause**: Race condition between webhook and OAuth
**Solution**: Verify webhook is looking up by `client_reference_id` first

## Architecture Decisions

### Why OAuth Before Payment?

**Problem**: Users created via Stripe webhook don't have OAuth accounts, causing "Unauthorized" errors when accessing authenticated endpoints like `/api/dealers/[id]/publish`.

**Solution**: Require OAuth authentication before Stripe checkout, ensuring:

1. User record has linked Google OAuth account
2. NextAuth session exists throughout registration
3. All authenticated endpoints work correctly
4. Better UX: Email pre-filled in Stripe checkout

### Why Keep Backward Compatibility?

**Reason**: Existing Payment Links may already be shared via email, printed materials, or external websites. Breaking these links would harm user experience and potentially lose customers.

**Implementation**: Webhook checks for `client_reference_id` first, falls back to email-based lookup.

### Why Custom Sign-In Page?

**Benefits**:

1. Branded experience matching AMSOIL/AIMCLEAR aesthetic
2. Shows selected plan name for context
3. Explains what happens next
4. More professional than default NextAuth page

## Security Considerations

### Session Security

- NextAuth uses JWT strategy with secure cookies
- Session tokens encrypted with `NEXTAUTH_SECRET`
- OAuth tokens stored securely in database

### Stripe Security

- `client_reference_id` links checkout to authenticated user
- Webhook validates signatures with `STRIPE_WEBHOOK_SECRET`
- No sensitive data exposed in URLs

### Email Verification

- Google OAuth provides verified email addresses
- Additional email verification not required
- Webhook logs warning if email mismatch detected

## Rollback Plan

If issues occur in production:

1. **Immediate**: Revert `public/landing/index.html` Subscribe button URLs back to direct Stripe Payment Links
2. **Short-term**: Users can still register via legacy flow
3. **Long-term**: Debug and fix OAuth flow issues

## Future Enhancements

1. **Apple Sign In**: Already configured in `.env.example`, just needs implementation
2. **Email Magic Links**: Alternative to OAuth for users without Google accounts
3. **Plan Change Flow**: Allow existing dealers to upgrade/downgrade plans
4. **Admin Dashboard**: Manage user accounts and troubleshoot OAuth issues
5. **Analytics**: Track OAuth conversion rates and drop-off points

## Support

For questions or issues:

1. Check server console logs for detailed error messages
2. Verify all environment variables are set correctly
3. Test with Stripe's test card numbers
4. Review Stripe webhook logs in dashboard

## Success Metrics

After implementing, you should see:

- ✅ Zero "Unauthorized" errors on publish
- ✅ 100% of OAuth users have linked Account records
- ✅ No duplicate User records
- ✅ Stripe checkout email pre-fill rate: 100%
- ✅ Registration completion rate increases

---

**Implementation Date**: 2025-11-04
**Author**: Claude (Anthropic)
**Version**: 1.0.0
