Claude
Skills
Sign in
Back

web-payments

Included with Lifetime
$97 forever

Stripe Checkout, subscriptions, webhooks, customer portal

Sales & CRM

What this skill does


# Web Payments Skill (Stripe)


For integrating Stripe payments into web applications - one-time payments, subscriptions, and checkout flows.

**Sources:** [Stripe Checkout](https://docs.stripe.com/payments/checkout) | [Payment Element Best Practices](https://docs.stripe.com/payments/payment-element/best-practices) | [Building Solid Stripe Integrations](https://stripe.dev/blog/building-solid-stripe-integrations-developers-guide-success) | [Subscriptions](https://docs.stripe.com/billing/subscriptions/build-subscriptions)

---

## Setup

### 1. Create Stripe Account
1. Go to https://dashboard.stripe.com/register
2. Complete business verification
3. Get API keys from https://dashboard.stripe.com/apikeys

### 2. Environment Variables
```bash
# .env
STRIPE_SECRET_KEY=sk_test_xxx          # Server-side only
STRIPE_PUBLISHABLE_KEY=pk_test_xxx     # Client-side safe
STRIPE_WEBHOOK_SECRET=whsec_xxx        # For webhook verification

# Production
STRIPE_SECRET_KEY=sk_live_xxx
STRIPE_PUBLISHABLE_KEY=pk_live_xxx
```

### 3. Install SDK
```bash
# Node.js
npm install stripe @stripe/stripe-js

# Python
pip install stripe
```

---

## Integration Options

| Method | Best For | Complexity |
|--------|----------|------------|
| **Checkout (Hosted)** | Quick setup, Stripe-hosted page | Low |
| **Checkout (Embedded)** | Custom site, embedded form | Low |
| **Payment Element** | Full customization, complex flows | Medium |
| **Custom Form** | Complete control (rare) | High |

**Recommendation**: Start with Checkout, migrate to Payment Element if needed.

---

## Stripe Checkout (Recommended)

### Server: Create Checkout Session

#### Node.js / Next.js
```typescript
// app/api/checkout/route.ts (Next.js App Router)
import Stripe from "stripe";
import { NextResponse } from "next/server";

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

export async function POST(request: Request) {
  const { priceId, mode = "payment" } = await request.json();

  try {
    const session = await stripe.checkout.sessions.create({
      mode: mode as "payment" | "subscription",
      payment_method_types: ["card"],
      line_items: [
        {
          price: priceId,
          quantity: 1,
        },
      ],
      success_url: `${process.env.NEXT_PUBLIC_URL}/success?session_id={CHECKOUT_SESSION_ID}`,
      cancel_url: `${process.env.NEXT_PUBLIC_URL}/canceled`,
      // Optional: Link to existing customer
      // customer: customerId,
      // Optional: Collect shipping
      // shipping_address_collection: { allowed_countries: ["US", "CA"] },
      // Optional: Add metadata for tracking
      metadata: {
        userId: "user_123",
        source: "pricing_page",
      },
    });

    return NextResponse.json({ sessionId: session.id, url: session.url });
  } catch (error) {
    console.error("Stripe error:", error);
    return NextResponse.json({ error: "Failed to create session" }, { status: 500 });
  }
}
```

#### Python / FastAPI
```python
# app/api/checkout.py
import stripe
from fastapi import APIRouter, HTTPException
from pydantic import BaseModel
import os

stripe.api_key = os.environ["STRIPE_SECRET_KEY"]
router = APIRouter()

class CheckoutRequest(BaseModel):
    price_id: str
    mode: str = "payment"  # or "subscription"

@router.post("/api/checkout")
async def create_checkout_session(request: CheckoutRequest):
    try:
        session = stripe.checkout.Session.create(
            mode=request.mode,
            payment_method_types=["card"],
            line_items=[{
                "price": request.price_id,
                "quantity": 1,
            }],
            success_url=f"{os.environ['APP_URL']}/success?session_id={{CHECKOUT_SESSION_ID}}",
            cancel_url=f"{os.environ['APP_URL']}/canceled",
            metadata={
                "user_id": "user_123",
            },
        )
        return {"session_id": session.id, "url": session.url}
    except stripe.error.StripeError as e:
        raise HTTPException(status_code=400, detail=str(e))
```

### Client: Redirect to Checkout

```typescript
// components/CheckoutButton.tsx
"use client";

import { loadStripe } from "@stripe/stripe-js";

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

export function CheckoutButton({ priceId }: { priceId: string }) {
  const handleCheckout = async () => {
    const response = await fetch("/api/checkout", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ priceId }),
    });

    const { url } = await response.json();

    // Redirect to Stripe Checkout
    window.location.href = url;
  };

  return (
    <button onClick={handleCheckout}>
      Subscribe Now
    </button>
  );
}
```

---

## Embedded Checkout

For keeping users on your site:

```typescript
// components/EmbeddedCheckout.tsx
"use client";

import { useEffect, useState } from "react";
import { loadStripe } from "@stripe/stripe-js";
import {
  EmbeddedCheckoutProvider,
  EmbeddedCheckout,
} from "@stripe/react-stripe-js";

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

export function EmbeddedCheckoutForm({ priceId }: { priceId: string }) {
  const [clientSecret, setClientSecret] = useState("");

  useEffect(() => {
    fetch("/api/checkout/embedded", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ priceId }),
    })
      .then((res) => res.json())
      .then((data) => setClientSecret(data.clientSecret));
  }, [priceId]);

  if (!clientSecret) return <div>Loading...</div>;

  return (
    <EmbeddedCheckoutProvider stripe={stripePromise} options={{ clientSecret }}>
      <EmbeddedCheckout />
    </EmbeddedCheckoutProvider>
  );
}
```

Server endpoint for embedded:
```typescript
// app/api/checkout/embedded/route.ts
export async function POST(request: Request) {
  const { priceId } = await request.json();

  const session = await stripe.checkout.sessions.create({
    mode: "subscription",
    line_items: [{ price: priceId, quantity: 1 }],
    ui_mode: "embedded",
    return_url: `${process.env.NEXT_PUBLIC_URL}/success?session_id={CHECKOUT_SESSION_ID}`,
  });

  return NextResponse.json({ clientSecret: session.client_secret });
}
```

---

## Webhooks (Critical)

**Never trust client-side data**. Always verify payments via webhooks.

### Webhook Endpoint

```typescript
// app/api/webhooks/stripe/route.ts
import Stripe from "stripe";
import { headers } from "next/headers";

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;

  // Verify webhook signature
  try {
    event = stripe.webhooks.constructEvent(body, signature, webhookSecret);
  } catch (err) {
    console.error("Webhook signature verification failed");
    return new Response("Invalid signature", { status: 400 });
  }

  // Handle events
  switch (event.type) {
    case "checkout.session.completed": {
      const session = event.data.object as Stripe.Checkout.Session;
      await handleCheckoutComplete(session);
      break;
    }
    case "customer.subscription.created":
    case "customer.subscription.updated": {
      const subscription = event.data.object as Stripe.Subscription;
      await handleSubscriptionUpdate(subscription);
      break;
    }
    case "customer.subscription.deleted": {
      const subscription = event.data.object as Stripe.Subscription;
      await handleSubscriptionCanceled(subscription);
      break;
    }
    case "invoice.payment_failed": {
      const invoice = event.data.object as Stripe.Invoice;
      await handlePaymentFailed(invoice);
      break;
    }
    default:
      console.log(`Unhandled event type: ${event.type}`);
  }

  // Return 200 quickly - process async if needed
  return new Respo
Files: 1
Size: 17.3 KB
Complexity: 24/100
Category: Sales & CRM

Related in Sales & CRM