> ## Documentation Index
> Fetch the complete documentation index at: https://shipfast.so/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Payments

> Accept subscriptions and one-time payments with Stripe.

## Steps

<Steps>
  <Step title="Create Stripe Account">
    Get your Stripe account ready:

    1. Go to [stripe.com](https://stripe.com) and sign up
    2. Complete account setup
    3. Switch to **Test Mode** (toggle top-right)

    <Tip>
      Test mode lets you test payments without real money 💳
    </Tip>
  </Step>

  <Step title="Get API Keys">
    Grab your credentials:

    1. Click **Developers** (top right)
    2. Click **API keys**
    3. You'll see two keys:
       * **Publishable key** (starts with `pk_test_`)
       * **Secret key** (starts with `sk_test_` - click "Reveal")

    Add to `.env.local`:

    ```bash .env.local theme={null}
    NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_xxxxx
    STRIPE_SECRET_KEY=sk_test_xxxxx
    ```

    <Warning>
      Never commit `STRIPE_SECRET_KEY` to git. Keep it in `.env.local` only.
    </Warning>
  </Step>

  <Step title="Create Products">
    Set up your pricing plans in Stripe:

    1. Go to **Product catalog** → **Add product**
    2. Fill in:
       * **Name:** Basic Plan
       * **Description:** What customers get
       * **Pricing:** Choose "Recurring" for subscriptions, "One-off" for one-time payments
       * **Price:** \$29 (or your price)
    3. Click **Save product**
    4. **Copy the Price ID** (starts with `price_`)
       * On the product page, under **Pricing**, click the **⋯** menu next to the price and select **Copy Price ID**.

    Repeat for each plan (Starter, Pro, etc.)

    <Info>
      Price IDs connect your buttons to Stripe products. Don't lose them!
    </Info>
  </Step>

  <Step title="Add Price IDs to Config">
    Open `/config.ts`:

    ```typescript /config.ts theme={null}
    export const config = {
        // ... other settings
        stripe: {
            ...
            mode: "subscription", // or "payment" for one-time
            plans: [
            {
                isFeatured: true,
                name: "Individual Plan",
                description: "",
                price: 19, // Display price
                priceId: 'price_xxxxx', // 👈 Your Price ID here    
                features: [
                { text: "Feature 1" },
                ...
                { text: "Lifetime updates", isAvailable: false },
                ],
            },
        },
    }
    ```

    Your pricing page will now show these plans automatically.
  </Step>

  <Step title="Set Up Webhooks">
    Webhooks tell your app when payments happen:

    1. In Stripe, search for **Webhooks** and select it
    2. Click **Add Destination**
    3. Click **Select events** and choose:
       * `checkout.session.completed`
       * `checkout.session.expired`
       * `customer.subscription.updated`
       * `customer.subscription.deleted`
       * `invoice.paid`
       * `invoice.payment_failed`
    4. Click **Continue**
    5. Destination type > **Webhook endpoint**
    6. **Endpoint URL:** `https://yourapp.com/api/stripe/webhook`
    7. Copy the **Signing secret** (starts with `whsec_`)

    Add to production environment variables (Vercel, etc.):

    ```bash theme={null}
    STRIPE_WEBHOOK_SECRET=whsec_xxxxx
    ```

    <Note>
      Webhook logic lives in `/app/api/stripe/webhook/route.ts`. Customize it for your needs.
    </Note>
  </Step>

  <Step title="Test Local Webhooks">
    To test webhooks locally, install Stripe CLI:

    ```bash Terminal theme={null}
    # Install Stripe CLI (macOS)
    brew install stripe/stripe-cli/stripe

    # Login
    stripe login

    # Forward webhooks to your local server
    stripe listen --forward-to localhost:3000/api/stripe/webhook
    ```

    Copy the webhook signing secret and add to `.env.local`:

    ```bash .env.local theme={null}
    STRIPE_WEBHOOK_SECRET=whsec_xxxxx
    ```

    Keep this terminal running while testing.

    \
    Trigger events:

    ```bash Terminal theme={null}
    # In a separate terminal tab, trigger events e.g.
    stripe trigger checkout.session.completed
    ```
  </Step>

  <Step title="Test a Payment">
    Time to see it work:

    1. Start dev server: `npm run dev`
    2. Start webhook forwarding (from Step 6)
    3. Go to `http://localhost:3000/#pricing`
    4. Click a "Get Started" button
    5. Use Stripe test card:
       * **Card:** `4242 4242 4242 4242`
       * **Expiry:** Any future date (12/34)
       * **CVC:** Any 3 digits (123)
    6. Complete checkout

    <Check>
      Success! Check:

      * Stripe dashboard for the payment
      * Supabase `profiles` table for `has_access = true`
    </Check>
  </Step>

  <Step title="Customer Portal">
    Users can manage subscriptions themselves:

    * Cancel subscription
    * Update payment method
    * View invoices
    * Download receipts

    Already set up at `/dashboard/account` 🎉
  </Step>
</Steps>

## Going Live

Ready to accept real payments?

<Steps>
  <Step title="Complete Verification">
    Stripe needs to verify your business:

    * Business details
    * Bank account
    * Identity verification
  </Step>

  <Step title="Switch to Live Mode">
    1. Toggle to **Live Mode** in Stripe dashboard
    2. Get your live API keys (`pk_live_` and `sk_live_`)
    3. Create live products (same as test)
    4. Copy live Price IDs
  </Step>

  <Step title="Update Production">
    In Vercel/your host:

    ```bash theme={null}
    NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_live_xxxxx
    STRIPE_SECRET_KEY=sk_live_xxxxx
    STRIPE_WEBHOOK_SECRET=whsec_xxxxx (from live webhook)
    ```

    Update `/config.ts` with live Price IDs.
  </Step>

  <Step title="Create Live Webhook">
    Same as Step 5, but in live mode:

    * Endpoint: `https://yourapp.com/api/stripe/webhook`
    * Same events
    * Get new signing secret
  </Step>
</Steps>

## Troubleshooting

<AccordionGroup>
  <Accordion title="Payment works but user doesn't get access?">
    Check these:

    * Webhook events in Stripe dashboard (any errors?)
    * `.env.local` has correct keys
    * Webhook secret is correct
    * Supabase connection works
    * Check logs - `/app/api/stripe/webhook/route.ts`
  </Accordion>

  <Accordion title="Webhook not receiving events?">
    * Check webhook URL is correct
    * Verify endpoint is reachable (try `curl`)
    * Look at webhook logs in Stripe dashboard
    * For local testing, ensure `stripe listen` is running
  </Accordion>

  <Accordion title="Customer portal not loading?">
    * Check `STRIPE_SECRET_KEY` is set
    * User must have a `customer_id` in database
    * Try logging in again to refresh session
  </Accordion>
</AccordionGroup>

***
