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

# Stripe Integration

> Set up Stripe for app billing

# Stripe Billing Integration

LaunchMyStore uses Stripe to process app payments. This guide covers setting up Stripe, handling subscriptions, and managing the billing lifecycle.

## Prerequisites

1. A [Stripe account](https://dashboard.stripe.com/register)
2. Your app registered in the LaunchMyStore developer portal
3. Stripe API keys (test and live)

## Setup

### 1. Connect Stripe Account

In the developer portal, connect your Stripe account:

1. Go to **Apps > Your App > Billing**
2. Click **Connect Stripe Account**
3. Complete the Stripe Connect onboarding
4. Copy your Stripe webhook signing secret

### 2. Configure Webhook Endpoint

Set up a webhook endpoint to receive Stripe events:

```javascript theme={null}
// pages/api/webhooks/stripe.js
import Stripe from 'stripe';
import { buffer } from 'micro';

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

export const config = {
  api: {
    bodyParser: false
  }
};

export default async function handler(req, res) {
  if (req.method !== 'POST') {
    return res.status(405).end();
  }
  
  const buf = await buffer(req);
  const sig = req.headers['stripe-signature'];
  
  let event;
  
  try {
    event = stripe.webhooks.constructEvent(buf, sig, webhookSecret);
  } catch (err) {
    console.error('Webhook signature verification failed:', err.message);
    return res.status(400).send(`Webhook Error: ${err.message}`);
  }
  
  // Handle the event
  switch (event.type) {
    case 'checkout.session.completed':
      await handleCheckoutComplete(event.data.object);
      break;
    case 'customer.subscription.created':
      await handleSubscriptionCreated(event.data.object);
      break;
    case 'customer.subscription.updated':
      await handleSubscriptionUpdated(event.data.object);
      break;
    case 'customer.subscription.deleted':
      await handleSubscriptionCanceled(event.data.object);
      break;
    case 'invoice.paid':
      await handleInvoicePaid(event.data.object);
      break;
    case 'invoice.payment_failed':
      await handlePaymentFailed(event.data.object);
      break;
    default:
      console.log(`Unhandled event type: ${event.type}`);
  }
  
  res.json({ received: true });
}
```

### 3. Create Products in Stripe

Create products and prices matching your pricing model:

```javascript theme={null}
import Stripe from 'stripe';

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

async function createProducts() {
  // Create the product
  const product = await stripe.products.create({
    name: 'My App Pro',
    description: 'Pro subscription for My App'
  });
  
  // Create monthly price
  const monthlyPrice = await stripe.prices.create({
    product: product.id,
    unit_amount: 2999,  // $29.99
    currency: 'usd',
    recurring: {
      interval: 'month'
    },
    metadata: {
      plan_id: 'pro_monthly'
    }
  });
  
  // Create annual price
  const annualPrice = await stripe.prices.create({
    product: product.id,
    unit_amount: 29990,  // $299.90 (2 months free)
    currency: 'usd',
    recurring: {
      interval: 'year'
    },
    metadata: {
      plan_id: 'pro_annual'
    }
  });
  
  return { product, monthlyPrice, annualPrice };
}
```

## Checkout Flow

### Create Checkout Session

When a merchant selects a plan, create a checkout session:

```javascript theme={null}
// pages/api/billing/create-checkout.js
import Stripe from 'stripe';

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

export default async function handler(req, res) {
  const { shopId, planId, shopDomain } = req.body;
  
  // Get or create Stripe customer
  let customer = await getStripeCustomer(shopId);
  
  if (!customer) {
    customer = await stripe.customers.create({
      metadata: {
        shop_id: shopId,
        shop_domain: shopDomain
      }
    });
    await saveStripeCustomerId(shopId, customer.id);
  }
  
  // Get price ID for the selected plan
  const priceId = getPriceIdForPlan(planId);
  
  // Create checkout session
  const session = await stripe.checkout.sessions.create({
    customer: customer.id,
    mode: 'subscription',
    payment_method_types: ['card'],
    line_items: [
      {
        price: priceId,
        quantity: 1
      }
    ],
    subscription_data: {
      trial_period_days: 14,  // Optional trial
      metadata: {
        shop_id: shopId,
        plan_id: planId
      }
    },
    success_url: `https://your-app.com/billing/success?session_id={CHECKOUT_SESSION_ID}`,
    cancel_url: `https://your-app.com/billing/canceled`,
    metadata: {
      shop_id: shopId,
      plan_id: planId
    }
  });
  
  res.json({ checkoutUrl: session.url });
}
```

### Handle Checkout Completion

```javascript theme={null}
async function handleCheckoutComplete(session) {
  const { shop_id, plan_id } = session.metadata;
  const subscriptionId = session.subscription;
  
  // Fetch subscription details
  const subscription = await stripe.subscriptions.retrieve(subscriptionId);
  
  // Update your database
  await updateShopSubscription(shop_id, {
    status: 'active',
    planId: plan_id,
    stripeSubscriptionId: subscriptionId,
    stripeCustomerId: session.customer,
    currentPeriodEnd: new Date(subscription.current_period_end * 1000),
    trialEnd: subscription.trial_end 
      ? new Date(subscription.trial_end * 1000) 
      : null
  });
  
  // Notify LaunchMyStore
  await notifyLaunchMyStore('subscription.created', {
    shopId: shop_id,
    planId: plan_id,
    subscriptionId
  });
}
```

## Managing Subscriptions

### Get Subscription Status

```javascript theme={null}
// pages/api/billing/status.js
export default async function handler(req, res) {
  const { shopId } = req.query;
  
  const subscription = await getShopSubscription(shopId);
  
  if (!subscription) {
    return res.json({
      status: 'none',
      plan: null
    });
  }
  
  // Check with Stripe for latest status
  if (subscription.stripeSubscriptionId) {
    const stripeSubscription = await stripe.subscriptions.retrieve(
      subscription.stripeSubscriptionId
    );
    
    return res.json({
      status: stripeSubscription.status,
      plan: subscription.planId,
      currentPeriodEnd: stripeSubscription.current_period_end,
      cancelAtPeriodEnd: stripeSubscription.cancel_at_period_end,
      trialEnd: stripeSubscription.trial_end
    });
  }
  
  res.json({
    status: subscription.status,
    plan: subscription.planId
  });
}
```

### Upgrade/Downgrade Plan

```javascript theme={null}
// pages/api/billing/change-plan.js
export default async function handler(req, res) {
  const { shopId, newPlanId } = req.body;
  
  const subscription = await getShopSubscription(shopId);
  
  if (!subscription?.stripeSubscriptionId) {
    return res.status(400).json({ error: 'No active subscription' });
  }
  
  const newPriceId = getPriceIdForPlan(newPlanId);
  
  // Get current subscription
  const stripeSubscription = await stripe.subscriptions.retrieve(
    subscription.stripeSubscriptionId
  );
  
  // Update subscription
  const updatedSubscription = await stripe.subscriptions.update(
    subscription.stripeSubscriptionId,
    {
      items: [
        {
          id: stripeSubscription.items.data[0].id,
          price: newPriceId
        }
      ],
      proration_behavior: 'create_prorations',  // Prorate the change
      metadata: {
        plan_id: newPlanId
      }
    }
  );
  
  // Update database
  await updateShopSubscription(shopId, {
    planId: newPlanId
  });
  
  res.json({ 
    success: true, 
    subscription: updatedSubscription 
  });
}
```

### Cancel Subscription

```javascript theme={null}
// pages/api/billing/cancel.js
export default async function handler(req, res) {
  const { shopId, immediate = false } = req.body;
  
  const subscription = await getShopSubscription(shopId);
  
  if (!subscription?.stripeSubscriptionId) {
    return res.status(400).json({ error: 'No active subscription' });
  }
  
  if (immediate) {
    // Cancel immediately
    await stripe.subscriptions.cancel(subscription.stripeSubscriptionId);
  } else {
    // Cancel at period end
    await stripe.subscriptions.update(subscription.stripeSubscriptionId, {
      cancel_at_period_end: true
    });
  }
  
  res.json({ success: true });
}
```

### Reactivate Canceled Subscription

```javascript theme={null}
// pages/api/billing/reactivate.js
export default async function handler(req, res) {
  const { shopId } = req.body;
  
  const subscription = await getShopSubscription(shopId);
  
  if (!subscription?.stripeSubscriptionId) {
    return res.status(400).json({ error: 'No subscription found' });
  }
  
  // Remove cancellation
  await stripe.subscriptions.update(subscription.stripeSubscriptionId, {
    cancel_at_period_end: false
  });
  
  res.json({ success: true });
}
```

## Customer Portal

Let merchants manage their billing through Stripe's Customer Portal:

```javascript theme={null}
// pages/api/billing/portal.js
export default async function handler(req, res) {
  const { shopId, returnUrl } = req.body;
  
  const subscription = await getShopSubscription(shopId);
  
  if (!subscription?.stripeCustomerId) {
    return res.status(400).json({ error: 'No billing account' });
  }
  
  const portalSession = await stripe.billingPortal.sessions.create({
    customer: subscription.stripeCustomerId,
    return_url: returnUrl || 'https://your-app.com/settings/billing'
  });
  
  res.json({ url: portalSession.url });
}
```

## Handling Webhook Events

### Subscription Updates

```javascript theme={null}
async function handleSubscriptionUpdated(subscription) {
  const shopId = subscription.metadata.shop_id;
  
  await updateShopSubscription(shopId, {
    status: subscription.status,
    currentPeriodEnd: new Date(subscription.current_period_end * 1000),
    cancelAtPeriodEnd: subscription.cancel_at_period_end
  });
  
  // Handle status changes
  switch (subscription.status) {
    case 'active':
      await enableAppFeatures(shopId);
      break;
    case 'past_due':
      await sendPaymentReminderEmail(shopId);
      break;
    case 'canceled':
      await disableAppFeatures(shopId);
      break;
    case 'unpaid':
      await disableAppFeatures(shopId);
      await sendFinalWarningEmail(shopId);
      break;
  }
}
```

### Payment Failures

```javascript theme={null}
async function handlePaymentFailed(invoice) {
  const customerId = invoice.customer;
  const shopId = await getShopIdByStripeCustomer(customerId);
  
  // Check attempt count
  const attemptCount = invoice.attempt_count;
  
  if (attemptCount === 1) {
    await sendEmail(shopId, 'payment_failed_first', {
      amount: invoice.amount_due / 100,
      nextAttemptDate: new Date(invoice.next_payment_attempt * 1000)
    });
  } else if (attemptCount >= 3) {
    await sendEmail(shopId, 'payment_failed_final', {
      amount: invoice.amount_due / 100
    });
    
    // Consider downgrading or disabling features
    await updateShopSubscription(shopId, {
      status: 'payment_failed',
      paymentFailedAt: new Date()
    });
  }
}
```

## Testing

### Test Mode

Use Stripe test mode during development:

```javascript theme={null}
const stripe = new Stripe(
  process.env.NODE_ENV === 'production'
    ? process.env.STRIPE_SECRET_KEY
    : process.env.STRIPE_TEST_SECRET_KEY
);
```

### Test Card Numbers

| Card Number        | Scenario                  |
| ------------------ | ------------------------- |
| `4242424242424242` | Successful payment        |
| `4000000000000002` | Declined                  |
| `4000000000009995` | Insufficient funds        |
| `4000000000000341` | Attaches but fails charge |

### Test Webhook Events

Use Stripe CLI to forward webhooks locally:

```bash theme={null}
stripe listen --forward-to localhost:3000/api/webhooks/stripe
```

## Security

<Warning>
  Never expose your Stripe secret key in client-side code. All Stripe API calls should be made from your backend.
</Warning>

### Webhook Verification

Always verify webhook signatures:

```javascript theme={null}
try {
  event = stripe.webhooks.constructEvent(
    rawBody,
    req.headers['stripe-signature'],
    webhookSecret
  );
} catch (err) {
  // Invalid signature - reject the request
  return res.status(400).send('Invalid signature');
}
```

### Idempotency

Use idempotency keys for critical operations:

```javascript theme={null}
const subscription = await stripe.subscriptions.create(
  {
    customer: customerId,
    items: [{ price: priceId }]
  },
  {
    idempotencyKey: `sub_create_${shopId}_${Date.now()}`
  }
);
```

## Best Practices

<AccordionGroup>
  <Accordion title="Handle all subscription states">
    Handle `active`, `past_due`, `canceled`, `unpaid`, `trialing`, and `incomplete` states appropriately.
  </Accordion>

  <Accordion title="Send proactive emails">
    Email customers before trial ends, when payment fails, and before cancellation takes effect.
  </Accordion>

  <Accordion title="Offer dunning management">
    Use Stripe's Smart Retries and send custom reminder emails for failed payments.
  </Accordion>

  <Accordion title="Provide self-service">
    Use Stripe's Customer Portal for payment method updates and invoice history.
  </Accordion>

  <Accordion title="Log everything">
    Log all webhook events and subscription changes for debugging and auditing.
  </Accordion>

  <Accordion title="Test thoroughly">
    Test subscription creation, upgrades, downgrades, cancellations, and payment failures.
  </Accordion>
</AccordionGroup>

## See Also

* [Pricing Models](/billing/pricing-models) - Choose your pricing strategy
* [Usage-Based Billing](/billing/usage-billing) - Implement metered billing
