Claude
Skills
Sign in
Back

stripe-connect

Included with Lifetime
$97 forever

Build marketplace and platform payment flows with Stripe Connect. Use when: building two-sided marketplaces, splitting payments between buyers and sellers, onboarding sellers/providers to accept payments, handling platform fees and payouts, or managing connected accounts for a platform.

Sales & CRM

What this skill does


# Stripe Connect

## Overview

Stripe Connect enables platforms to route payments between buyers, sellers, and your platform. It handles regulatory compliance (KYC), payouts, and tax reporting for connected accounts.

**Account types:**
| Type | Onboarding | Branding | Best for |
|------|------------|----------|----------|
| **Express** | Stripe-hosted | Stripe UI | Marketplaces (recommended) |
| **Standard** | OAuth redirect | Seller's branding | Platforms where sellers have existing Stripe accounts |
| **Custom** | Fully custom | Your UI | Large platforms needing full control |

**Charge types:**
| Type | Who pays Stripe fees | Use when |
|------|---------------------|----------|
| Direct | Connected account | Seller wants full control |
| Destination | Platform | Platform manages UX |
| Separate charges + transfers | Platform | Complex routing |

## Setup

```bash
npm install stripe
```

```ts
// lib/stripe.ts
import Stripe from "stripe";

export const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
  apiVersion: "2024-06-20",
});

// Platform account key (your Stripe account)
// Connected accounts are identified by their account ID
```

---

## Express Accounts (Recommended)

### 1. Create a connected account

```ts
// POST /api/sellers/onboard
import { stripe } from "@/lib/stripe";

export async function createExpressAccount(email: string) {
  const account = await stripe.accounts.create({
    type: "express",
    email,
    capabilities: {
      card_payments: { requested: true },
      transfers: { requested: true },
    },
    business_type: "individual",
  });

  return account.id; // Store this as seller.stripeAccountId in your DB
}
```

### 2. Generate onboarding link

```ts
export async function createOnboardingLink(accountId: string, userId: string) {
  const accountLink = await stripe.accountLinks.create({
    account: accountId,
    refresh_url: `${process.env.BASE_URL}/sellers/onboard/refresh?userId=${userId}`,
    return_url: `${process.env.BASE_URL}/sellers/onboard/complete?userId=${userId}`,
    type: "account_onboarding",
  });

  return accountLink.url; // Redirect seller here
}

// Full flow:
app.post("/api/sellers/onboard", async (req, res) => {
  const { email, userId } = req.body;
  const accountId = await createExpressAccount(email);

  // Save accountId to your DB
  await db.sellers.update(userId, { stripeAccountId: accountId });

  const url = await createOnboardingLink(accountId, userId);
  res.json({ url });
});
```

### 3. Check onboarding status

```ts
export async function isSellerOnboarded(accountId: string): Promise<boolean> {
  const account = await stripe.accounts.retrieve(accountId);
  return account.details_submitted && !account.requirements?.currently_due?.length;
}

app.get("/sellers/onboard/complete", async (req, res) => {
  const { userId } = req.query;
  const seller = await db.sellers.findById(userId);
  const onboarded = await isSellerOnboarded(seller.stripeAccountId);

  if (onboarded) {
    await db.sellers.update(userId, { status: "active" });
    res.redirect("/dashboard?onboarded=true");
  } else {
    // Seller didn't finish — show completion prompt
    res.redirect("/sellers/onboard/pending");
  }
});
```

---

## Charging Buyers

### Destination Charges (Platform collects, sends to seller)

```ts
// POST /api/payments/charge
export async function chargeWithDestination({
  amount,          // in cents
  currency = "usd",
  paymentMethodId,
  customerId,
  sellerAccountId,
  platformFeePercent = 15,
}: {
  amount: number;
  currency?: string;
  paymentMethodId: string;
  customerId: string;
  sellerAccountId: string;
  platformFeePercent?: number;
}) {
  const platformFee = Math.round(amount * (platformFeePercent / 100));

  const paymentIntent = await stripe.paymentIntents.create({
    amount,
    currency,
    customer: customerId,
    payment_method: paymentMethodId,
    confirm: true,
    transfer_data: {
      destination: sellerAccountId, // Route net to seller
    },
    application_fee_amount: platformFee, // Platform keeps this
    automatic_payment_methods: { enabled: true, allow_redirects: "never" },
  });

  return paymentIntent;
}
```

### Direct Charges (Seller's Stripe account)

```ts
// Charge appears on seller's Stripe dashboard; platform gets fee
export async function directCharge({
  amount,
  paymentMethodId,
  sellerAccountId,
  platformFeePercent = 10,
}: {
  amount: number;
  paymentMethodId: string;
  sellerAccountId: string;
  platformFeePercent?: number;
}) {
  const platformFee = Math.round(amount * (platformFeePercent / 100));

  const paymentIntent = await stripe.paymentIntents.create(
    {
      amount,
      currency: "usd",
      payment_method: paymentMethodId,
      confirm: true,
      application_fee_amount: platformFee,
    },
    {
      stripeAccount: sellerAccountId, // Create on behalf of seller
    }
  );

  return paymentIntent;
}
```

### Separate Charges + Transfers (most flexible)

```ts
// 1. Charge buyer on platform account
const paymentIntent = await stripe.paymentIntents.create({
  amount: 10000, // $100
  currency: "usd",
  payment_method: paymentMethodId,
  confirm: true,
});

// 2. Later: transfer to seller (e.g., after service delivered)
export async function payoutToSeller(
  paymentIntentId: string,
  sellerAccountId: string,
  amount: number // amount to send seller (after platform fee)
) {
  const transfer = await stripe.transfers.create({
    amount,
    currency: "usd",
    destination: sellerAccountId,
    source_transaction: paymentIntentId, // Links transfer to original charge
  });
  return transfer;
}
```

---

## Payouts

### Automatic payouts (default)

By default, Stripe automatically pays out to sellers based on their payout schedule. No extra code needed.

```ts
// Check payout schedule for a connected account
const account = await stripe.accounts.retrieve(sellerAccountId);
console.log(account.settings?.payouts?.schedule);
// { interval: "daily" | "weekly" | "monthly", ... }
```

### Manual / triggered payouts

```ts
// Trigger an instant payout (seller must have instant payouts enabled)
export async function triggerPayout(sellerAccountId: string, amount: number) {
  const payout = await stripe.payouts.create(
    {
      amount,
      currency: "usd",
      method: "instant", // or "standard"
    },
    {
      stripeAccount: sellerAccountId,
    }
  );
  return payout;
}
```

### Check seller balance

```ts
export async function getSellerBalance(sellerAccountId: string) {
  const balance = await stripe.balance.retrieve({
    stripeAccount: sellerAccountId,
  });

  return {
    available: balance.available[0]?.amount ?? 0,
    pending: balance.pending[0]?.amount ?? 0,
  };
}
```

---

## Webhooks for Connect

Connect webhooks can fire for your platform account or for events on connected accounts.

```ts
// POST /webhooks/stripe
import { stripe } from "@/lib/stripe";

app.post("/webhooks/stripe", express.raw({ type: "application/json" }), async (req, res) => {
  const sig = req.headers["stripe-signature"] as string;

  let event: Stripe.Event;
  try {
    event = stripe.webhooks.constructEvent(
      req.body,
      sig,
      process.env.STRIPE_WEBHOOK_SECRET!
    );
  } catch (err) {
    return res.status(400).send(`Webhook Error: ${(err as Error).message}`);
  }

  // For Connect events, check event.account
  const connectedAccountId = (event as any).account as string | undefined;

  switch (event.type) {
    // Connected account completed onboarding
    case "account.updated": {
      const account = event.data.object as Stripe.Account;
      if (account.details_submitted) {
        await db.sellers.update(
          { stripeAccountId: account.id },
          { status: "active" }
        );
      }
      break;
    }

    // Payment succeeded
    case "payment_intent.succeeded": {
      const pi = event.data.object as Stripe.PaymentIntent;
      await db.orders.update(
        { stripePaymentIntentId: pi.id },
     
Files: 2
Size: 13.8 KB
Complexity: 25/100
Category: Sales & CRM

Related in Sales & CRM