DeesseJS

Stripe Integration

Complete guide to Stripe integration with DeesseJS

Stripe Integration

Complete guide to integrating Stripe payments with DeesseJS.

Setup

1. Create Stripe Account

  1. Sign up at stripe.com
  2. Get your API keys from the Dashboard
  3. Copy publishable and secret keys

2. Configure Environment Variables

# .env.local
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_...
STRIPE_SECRET_KEY=sk_test_...
STRIPE_WEBHOOK_SECRET=whsec_...

3. Install Dependencies

npm install stripe @stripe/stripe-js

Configuration

// deesse.config.ts
import { defineConfig } from '@deessejs/core'

export const config = defineConfig({
  payments: {
    provider: 'stripe',

    credentials: {
      publishableKey: process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY,
      secretKey: process.env.STRIPE_SECRET_KEY,
      webhookSecret: process.env.STRIPE_WEBHOOK_SECRET,
    },

    currency: 'usd',
    locale: 'en',

    features: {
      checkout: true,           // Enable Stripe Checkout
      links: true,              // Enable Payment Links
      billingPortal: true,      // Enable Customer Portal
    },
  },
})

One-Time Payments

Server-Side

// app/api/create-payment-intent/route.ts
import { NextResponse } from 'next/server'
import Stripe from 'stripe'

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!)

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

  // Create payment intent
  const paymentIntent = await stripe.paymentIntents.create({
    amount: Math.round(amount * 100), // Convert to cents
    currency,
    automatic_payment_methods: {
      enabled: true,
    },
    metadata: {
      orderId: metadata.orderId || '',
      userId: metadata.userId || '',
    },
  })

  // Save to database
  await db.payments.create({
    data: {
      stripePaymentIntentId: paymentIntent.id,
      amount,
      currency,
      status: 'pending',
    },
  })

  return NextResponse.json({
    clientSecret: paymentIntent.client_secret,
    paymentIntentId: paymentIntent.id,
  })
}

Client-Side with Elements

// app/checkout/page.tsx
'use client'

import { loadStripe } from '@stripe/stripe-js'
import { Elements, CardElement, useStripe, useElements } from '@stripe/react-stripe-js'
import { FormEvent, useState } from 'react'

const stripePromise = loadStripe(process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY!)

function CheckoutForm() {
  const stripe = useStripe()
  const elements = useElements()
  const [error, setError] = useState<string>()
  const [processing, setProcessing] = useState(false)

  const handleSubmit = async (e: FormEvent) => {
    e.preventDefault()
    setProcessing(true)

    if (!stripe || !elements) return

    // Create payment intent
    const response = await fetch('/api/create-payment-intent', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ amount: 99.99 }),
    })

    const { clientSecret } = await response.json()

    // Confirm payment
    const { error: stripeError } = await stripe.confirmCardPayment(clientSecret, {
      payment_method: {
        card: elements.getElement(CardElement)!,
        billing_details: {
          name: 'Customer Name',
        },
      },
    })

    if (stripeError) {
      setError(stripeError.message!)
    } else {
      // Payment successful
      window.location.href = '/success'
    }

    setProcessing(false)
  }

  return (
    <form onSubmit={handleSubmit}>
      <CardElement />
      {error && <div className="error">{error}</div>}
      <button type="submit" disabled={processing || !stripe}>
        {processing ? 'Processing...' : 'Pay $99.99'}
      </button>
    </form>
  )
}

export default function CheckoutPage() {
  return (
    <Elements stripe={stripePromise}>
      <CheckoutForm />
    </Elements>
  )
}

Stripe Checkout

Stripe Checkout is a pre-built payment page hosted by Stripe.

Create Checkout Session

// app/api/create-checkout-session/route.ts
import { NextResponse } from 'next/server'
import Stripe from 'stripe'

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!)

export async function POST(request: Request) {
  const { productId } = await request.json()

  const product = await db.products.findUnique({
    where: { id: productId },
  })

  const session = await stripe.checkout.sessions.create({
    payment_method_types: ['card'],
    line_items: [
      {
        price_data: {
          currency: 'usd',
          product_data: {
            name: product.name,
            images: [product.image],
          },
          unit_amount: Math.round(product.price * 100),
        },
        quantity: 1,
      },
    ],
    mode: 'payment',
    success_url: `${process.env.NEXT_PUBLIC_URL}/success?session_id={CHECKOUT_SESSION_ID}`,
    cancel_url: `${process.env.NEXT_PUBLIC_URL}/checkout`,
    metadata: {
      productId,
    },
  })

  return NextResponse.json({ url: session.url })
}

Redirect to Checkout

// app/products/[id]/page.tsx
'use client'

export function BuyButton({ productId }: { productId: string }) {
  const handlePurchase = async () => {
    const response = await fetch('/api/create-checkout-session', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ productId }),
    })

    const { url } = await response.json()
    window.location.href = url
  }

  return <button onClick={handlePurchase}>Buy Now</button>
}

Subscriptions

Create Product and Price in Stripe

// Use Stripe Dashboard or API

// Via API
import Stripe from 'stripe'
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!)

// Create product
const product = await stripe.products.create({
  name: 'Pro Plan',
  description: 'Monthly subscription',
})

// Create price
const price = await stripe.prices.create({
  product: product.id,
  unit_amount: 2900, // $29.00
  currency: 'usd',
  recurring: {
    interval: 'month',
  },
})

console.log('Price ID:', price.id)

Create Subscription

// app/api/create-subscription/route.ts
import { NextResponse } from 'next/server'
import Stripe from 'stripe'

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!)

export async function POST(request: Request) {
  const { priceId, customerId, email } = await request.json()

  // Create customer if not exists
  let customer = customerId
  if (!customer) {
    const customerData = await stripe.customers.create({
      email,
      metadata: {
        userId: 'user_123',
      },
    })
    customer = customerData.id
  }

  // Create subscription
  const subscription = await stripe.subscriptions.create({
    customer,
    items: [{ price: priceId }],
    payment_behavior: 'default_incomplete',
    payment_settings: {
      save_default_payment_method: 'on_subscription',
    },
    expand: ['latest_invoice.payment_intent'],
  })

  return NextResponse.json({
    subscriptionId: subscription.id,
    clientSecret: subscription.latest_invoice.payment_intent.client_secret,
    customerId: customer,
  })
}

Subscription Management

// Cancel subscription
export async function cancelSubscription(subscriptionId: string) {
  await stripe.subscriptions.update(subscriptionId, {
    cancel_at_period_end: true,
  })
}

// Update subscription
export async function updateSubscription(
  subscriptionId: string,
  newPriceId: string
) {
  await stripe.subscriptions.update(subscriptionId, {
    items: [{ price: newPriceId }],
  })
}

// Pause subscription
export async function pauseSubscription(subscriptionId: string) {
  await stripe.subscriptions.update(subscriptionId, {
    pause_collection: {
      behavior: 'keep_as_draft',
    },
  })
}

Webhooks

Handle Webhook Events

// app/api/webhooks/stripe/route.ts
import { headers } from 'next/headers'
import { NextResponse } from 'next/server'
import Stripe from 'stripe'

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!)
const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET!

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

  let event: Stripe.Event

  try {
    event = stripe.webhooks.constructEvent(body, signature, webhookSecret)
  } catch (err) {
    return NextResponse.json({ error: 'Invalid signature' }, { status: 400 })
  }

  switch (event.type) {
    case 'payment_intent.succeeded':
      await handlePaymentSucceeded(event.data.object)
      break

    case 'payment_intent.payment_failed':
      await handlePaymentFailed(event.data.object)
      break

    case 'checkout.session.completed':
      await handleCheckoutCompleted(event.data.object)
      break

    case 'customer.subscription.created':
      await handleSubscriptionCreated(event.data.object)
      break

    case 'customer.subscription.deleted':
      await handleSubscriptionDeleted(event.data.object)
      break

    case 'invoice.paid':
      await handleInvoicePaid(event.data.object)
      break

    case 'invoice.payment_failed':
      await handleInvoicePaymentFailed(event.data.object)
      break
  }

  return NextResponse.json({ received: true })
}

async function handlePaymentSucceeded(paymentIntent: Stripe.PaymentIntent) {
  // Update order status
  await db.payments.update({
    where: { stripePaymentIntentId: paymentIntent.id },
    data: { status: 'completed' },
  })

  // Send confirmation email
  await sendConfirmationEmail(paymentIntent.metadata.orderId)
}

Customer Portal

Stripe Customer Portal allows customers to manage their subscriptions.

Create Portal Session

// app/api/create-portal-session/route.ts
import { NextResponse } from 'next/server'
import Stripe from 'stripe'

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!)

export async function POST(request: Request) {
  const { customerId } = await request.json()

  const session = await stripe.billingPortal.sessions.create({
    customer: customerId,
    return_url: `${process.env.NEXT_PUBLIC_URL}/account`,
  })

  return NextResponse.json({ url: session.url })
}
'use client'

export function ManageSubscriptionButton({ customerId }: { customerId: string }) {
  const handleClick = async () => {
    const response = await fetch('/api/create-portal-session', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ customerId }),
    })

    const { url } = await response.json()
    window.location.href = url
  }

  return <button onClick={handleClick}>Manage Subscription</button>
}

Testing

Test Cards

Success: 4242 4242 4242 4242
Requires authentication: 4000 0025 0000 3155
Decline: 4000 0000 0000 0002
Insufficient funds: 4000 0000 0000 9995
Expired card: 4000 0000 0000 0069

Test Locally

# Install Stripe CLI
brew install stripe/stripe-cli/stripe

# Login
stripe login

# Forward webhooks
stripe listen --forward-to localhost:3000/api/webhooks/stripe

# Test webhook
stripe trigger payment_intent.succeeded

Next Steps

On this page