Skip to content

Latest commit

 

History

History
271 lines (201 loc) · 7.08 KB

File metadata and controls

271 lines (201 loc) · 7.08 KB

🔑 Step 1: Set Up Your Stripe Account

1.1 Create an Account

  1. Go to https://stripe.com and create an account
  2. Complete onboarding (you can skip to test mode)

1.2 Get API Keys

  1. In the Stripe Dashboard, go to DevelopersAPI keys
  2. You will see two keys:
    • Publishable key (starts with pk_test_... or pk_live_...)
    • Secret key (starts with sk_test_... or sk_live_...)
    • Important: Use test keys during development!

1.3 Set Up Webhooks (Optional but Recommended)

  1. In the Dashboard, go to DevelopersWebhooks
  2. Click Add endpoint
  3. URL: https://your-domain.com/api/webhooks/stripe (use ngrok for local testing)
  4. Events to listen for:
    • payment_intent.succeeded
    • payment_intent.payment_failed
    • payment_intent.canceled

📦 Step 2: Install Dependencies

npm install stripe @stripe/stripe-js

🔐 Step 3: Configure Environment Variables

Add to your .env.local file:

# Stripe Keys
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=your_publishable_key_here
STRIPE_SECRET_KEY=your_secret_key_here

# Webhook Secret (get this after setting up the webhook in Stripe)
STRIPE_WEBHOOK_SECRET=your_webhook_secret_here

⚠️ IMPORTANT:

  • Never commit secret keys to Git!
  • Use test keys during development
  • Switch to live keys only in production

🏗️ Step 4: File Structure Example

src/
├── lib/
│   └── stripe/
│       ├── client.ts          # Stripe client for frontend
}

export const stripePromise = loadStripe(stripePublishableKey)

5.2 Create Stripe Client (Backend)

src/lib/stripe/server.ts

import Stripe from 'stripe'

const stripeSecretKey = process.env.STRIPE_SECRET_KEY!

if (!stripeSecretKey) {
  throw new Error('Missing STRIPE_SECRET_KEY')
}

export const stripe = new Stripe(stripeSecretKey, {
  apiVersion: '2024-11-20.acacia',
})

5.3 API Route: Create Payment Intent

src/app/api/payment-intent/route.ts

import { NextRequest, NextResponse } from 'next/server'
import { stripe } from '@/lib/stripe/server'

export async function POST(request: NextRequest) {
  try {
    const { amount, currency = 'usd', customerEmail, metadata } = await request.json()

    // Create Payment Intent
    const paymentIntent = await stripe.paymentIntents.create({
      amount: amount, // in cents
      currency: currency.toLowerCase(),
      customer_email: customerEmail,
      metadata: metadata || {},
      automatic_payment_methods: {
        enabled: true,
      },
    })

    return NextResponse.json({
      clientSecret: paymentIntent.client_secret,
      paymentIntentId: paymentIntent.id,
    })
  } catch (error: any) {
    return NextResponse.json(
      { error: error.message },
      { status: 500 }
    )
  }
}

5.4 Stripe Webhook

src/app/api/webhooks/stripe/route.ts

import { NextRequest, NextResponse } from 'next/server'
import { stripe } from '@/lib/stripe/server'
import { createClient } from '@/lib/supabase/server'

export async function POST(request: NextRequest) {
  const body = await request.text()
  const signature = request.headers.get('stripe-signature')!

  const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET!

  let event

  try {
    event = stripe.webhooks.constructEvent(body, signature, webhookSecret)
  } catch (err: any) {
    return NextResponse.json(
      { error: `Webhook signature verification failed: ${err.message}` },
      { status: 400 }
    )
  }

  // Handle events
  switch (event.type) {
    case 'payment_intent.succeeded':
      const paymentIntent = event.data.object
      // Update booking in the database as paid
      const supabase = await createClient()
      await supabase
        .from('bookings')
        .update({ status: 'confirmed' })
        .eq('payment_intent_id', paymentIntent.id)
      
      console.log('✅ Payment succeeded:', paymentIntent.id)
      break

    case 'payment_intent.payment_failed':
      console.log('❌ Payment failed:', event.data.object.id)
      break

    default:
      console.log(`Unhandled event type: ${event.type}`)
  }
  return NextResponse.json({ received: true })
}

🎨 Step 6: Integrate with the Form

6.1 Install Stripe Elements

In the BookingForm.tsx component, you will need to:

  1. Create Payment Intent before showing the payment form
  2. Use @stripe/react-stripe-js for form elements
  3. Confirm payment when the user submits

Additional dependency:

npm install @stripe/react-stripe-js

6.2 Update BookingForm

Main changes:

  • Create Payment Intent when moving to the payment step
  • Use Stripe <Elements>
  • Use <PaymentElement> for the card form
  • Confirm payment with stripe.confirmPayment()
  • Update booking with payment_intent_id in the database

📊 Step 7: Full Flow

1. User fills in details → Clicks "Continue to Payment"
2. Frontend calls `/api/payment-intent` → Receives `clientSecret`
3. Shows Stripe form (PaymentElement)
4. User enters card → Clicks "Confirm Payment"
5. Frontend confirms with Stripe → `stripe.confirmPayment()`
6. Stripe processes payment
7. Webhook receives event → Updates booking in the database
8. Frontend redirects to confirmation page

🧪 Step 8: Test with Stripe Test Cards

Stripe provides test cards:

Card Number Description
4242 4242 4242 4242 Success
4000 0000 0000 9995 Declined (insufficient)
4000 0025 0000 3155 Requires 3D Secure

Test Codes:

  • CVV: Any 3 digits
  • Expiry: Any future date
  • ZIP: Any

🔒 Step 9: Security

  1. Never expose the Secret Key on the frontend
  2. Always validate webhooks with signature
  3. Use HTTPS in production
  4. Validate data on the backend before creating Payment Intent
  5. Store only payment_intent_id in the database (never card data)

🎯 Next Steps After Integration

  1. Test thoroughly in test mode
  2. Set up webhooks in production
  3. Implement retry logic for failures
  4. Add payment logs
  5. Create a payment dashboard (optional)

📚 Resources


❓ Common Questions

Q: Can I test locally?
A: Yes! Use ngrok or stripe CLI to test webhooks locally.

Q: How do I switch from test to production?
A: Just change the API keys in .env.local (replace pk_test_ with pk_live_).

Q: Do I need SSL for webhooks?
A: Yes, Stripe requires HTTPS. Use ngrok during development.


Ready to implement? Say "yes" and I will create all the files! 🚀