Stripe Connect skill for Codex

Marketplace payouts with a traceable money trail. Codex asks who the merchant of record is and who absorbs losses before writing any Connect code, then picks direct, destination or separate-charge flows to match. Sellers stay hidden until account.updated reports charges_enabled, every order traces to its fee and transfer, refunds reverse the seller share deliberately, and the funds-flow design is flagged for legal and finance review.

  • Integrations
  • $stripe-connect
  • 196 lines
  • 2 files
  • ~2,100 tokens
  • MIT
  • Updated

Install

curl -fsSL codexskills.dev/install.sh \  | sh -s -- stripe-connect

Installs to ~/.agents/skills/stripe-connect/ for every project on this machine.

104-line POSIX script. No sudo, no telemetry. Read install.sh

Use it in Codex

Start a prompt with $stripe-connect, or pick it from /skills. You can also just describe the task: Codex reads the description below and loads the playbook when your request matches.

What Codex reads before loading it

Designs and builds Stripe Connect marketplaces and platforms - connected account configuration, hosted or embedded onboarding, direct, destination and separate-charges-and-transfers flows, application fees, payouts, refunds with transfer reversal, and Connect webhooks. Use when a platform must pay out sellers, creators or service providers, when choosing a Connect charge type, onboarding connected accounts, handling account.updated requirements, or splitting a payment across several recipients.

Use it when

  • A platform collects payments on behalf of sellers, creators or providers
  • You must choose between direct, destination and separate-charge flows
  • Connected accounts are stuck with outstanding onboarding requirements
  • One order needs to be split between several recipients

Use something else when

How to check the result

Trace a test order from PaymentIntent to application fee to transfer and seller balance, and confirm a refund with reverse_transfer returns the seller's share.

The mistake it prevents

Calling the API for a seller without the stripeAccount option, so the charge or refund silently happens on the platform account instead.

~/.agents/skills/stripe-connect/SKILL.md

--- name: stripe-connect description: Designs and builds Stripe Connect marketplaces and platforms - connected account configuration, hosted or embedded onboarding, direct, destination… license: MIT ---

Done well means: the charge type matches who carries liability and who the customer thinks they are paying, connected accounts cannot transact until charges_enabled is true, every money movement is traceable from one order to its charge, fee and transfers, and refunds reverse the seller’s share as intended.

Platforms that move money for others carry regulatory, tax-reporting and liability obligations. Say plainly that the user’s legal and finance advisers must sign off on the funds-flow design before launch; this skill covers the engineering.

Before writing code

  1. Read AGENTS.md; find the existing Stripe client, webhook routes and the model for sellers.
  2. Ask the user (do not assume) these three things, because they fix the design:
    • Who is the merchant of record the buyer sees: the platform or the seller?
    • Who pays Stripe fees and absorbs losses from disputes and negative balances?
    • Does one payment ever go to more than one seller?
  3. Check the pinned API version and whether the account already uses Connect with legacy type: 'standard' | 'express' | 'custom' accounts. Keep consistency with existing accounts.
  4. Creating live connected accounts, sending transfers or payouts, and reversing transfers need explicit user confirmation. Work in test mode.

Choose the charge type

SituationCharge typeKey parameters
Seller is the merchant; platform takes a cut (SaaS for businesses)Direct chargestripeAccount request option, application_fee_amount
Platform is the merchant; one seller per payment (typical marketplace)Destination chargetransfer_data.destination, application_fee_amount, optionally on_behalf_of
One payment split across several sellers, or paid out laterSeparate charges and transferstransfer_group on the charge, then stripe.transfers.create per seller

Default for a marketplace with one seller per order: destination charge. Use separate charges and transfers only when splitting or delaying is a real requirement; it puts all reconciliation on you.

Connected account configuration

Prefer configuring accounts by controller properties over legacy types:

const account = await stripe.accounts.create({
  country: "US",
  email: seller.email,
  controller: {
    fees: { payer: "application" },              // platform pays Stripe fees
    losses: { payments: "application" },        // platform covers negative balances
    stripe_dashboard: { type: "express" },       // seller gets the Express dashboard
    requirement_collection: "stripe",            // Stripe collects KYC
  },
  capabilities: {
    card_payments: { requested: true },
    transfers: { requested: true },
  },
  metadata: { sellerId: seller.id },
}, { idempotencyKey: `acct-${seller.id}` });

Store account.id on the seller row immediately. Stripe has also introduced a newer Accounts v2 API; if the project already uses it, follow its shapes rather than mixing versions.

Onboarding

Hosted (fastest):

const link = await stripe.accountLinks.create({
  account: seller.stripeAccountId,
  type: "account_onboarding",
  refresh_url: `${APP_URL}/seller/onboarding/refresh`,  // link expired: create a new one
  return_url: `${APP_URL}/seller/onboarding/return`,     // not proof of completion
});
// redirect to link.url

Embedded: create an Account Session server-side (stripe.accountSessions.create({ account, components: { account_onboarding: { enabled: true } } })) and render the onboarding component with Connect.js on the client.

Account Links are single-use and short-lived. Never email them; generate on demand.

Gate on account state, not on the return URL

Subscribe a Connect webhook endpoint (it receives events from connected accounts, with event.account set) and handle account.updated:

case "account.updated": {
  const a = event.data.object as Stripe.Account;
  await db.sellers.update({
    where: { stripeAccountId: a.id },
    data: {
      chargesEnabled: a.charges_enabled,
      payoutsEnabled: a.payouts_enabled,
      requirementsDue: a.requirements?.currently_due ?? [],
      disabledReason: a.requirements?.disabled_reason ?? null,
    },
  });
}

Hide a seller’s listings while charges_enabled is false, and surface currently_due with a fresh onboarding link. Requirements can reappear later (volume thresholds, document expiry).

Creating charges

// Destination charge via Checkout
await stripe.checkout.sessions.create({
  mode: "payment",
  line_items: [{ price_data: { currency: "usd", unit_amount: 5000, product_data: { name: listing.title } }, quantity: 1 }],
  payment_intent_data: {
    application_fee_amount: 750,                       // platform keeps $7.50
    transfer_data: { destination: seller.stripeAccountId },
    on_behalf_of: seller.stripeAccountId,              // seller's descriptor and country
    metadata: { orderId },
  },
  success_url, cancel_url,
}, { idempotencyKey: `order-${orderId}` });

// Direct charge: same call, made on the connected account
await stripe.paymentIntents.create(
  { amount: 5000, currency: "usd", application_fee_amount: 750, automatic_payment_methods: { enabled: true } },
  { stripeAccount: seller.stripeAccountId, idempotencyKey: `order-${orderId}` },
);

// Separate charges and transfers: after the charge succeeds
await stripe.transfers.create(
  { amount: 4250, currency: "usd", destination: sellerA, transfer_group: `order-${orderId}`, source_transaction: chargeId },
  { idempotencyKey: `xfer-${orderId}-${sellerA}` },
);

source_transaction ties the transfer to the charge so it waits for the funds instead of failing on an empty platform balance.

Refunds and disputes

Charge typeRefund callSeller share
Destinationrefunds.create({ payment_intent, reverse_transfer: true, refund_application_fee: true })Pulled back from seller
DirectRefund on the connected account (stripeAccount option)Seller’s balance pays
Separate transfersRefund the charge, then transfers.createReversal per sellerEach reversal is explicit

Decide with the user whether the platform keeps its fee on refunds. Disputes on destination charges debit the platform; plan how to recover from the seller.

Payouts

Default to automatic payouts on the connected account’s schedule. Manual payouts (settings.payouts.schedule.interval: 'manual') make the platform responsible for triggering them and holding funds; only use when the business requires escrow-like timing and the user has confirmed it is allowed.

Local testing

stripe listen --forward-to localhost:3000/webhooks/stripe \
  --forward-connect-to localhost:3000/webhooks/stripe-connect

Use test-mode onboarding values from Stripe’s docs (test SSN, test bank account) only in test mode. Connect endpoints have their own signing secret.

Gotchas

  • Mixing charge types per seller without recording which was used: refunds then go wrong.
  • Forgetting on_behalf_of with destination charges across countries: settlement and statement descriptors follow the platform, which may be wrong for the buyer.
  • Using the platform webhook endpoint for connected-account events: they never arrive there.
  • Calling APIs for a connected account without stripeAccount: you act on the platform.
  • Fees in the wrong currency: application_fee_amount is in the charge currency, minor units.
  • Deleting or rejecting connected accounts is destructive; confirm with the user first.

Verification

  • Trace one test order: PaymentIntent, its application_fee, the transfer to the seller, and the seller balance (stripe balance retrieve --stripe-account acct_...).
  • A seller with charges_enabled: false cannot be purchased from in the UI.
  • A refund with reverse_transfer returns the seller’s share and the platform fee as designed.
  • account.updated events update the seller row when onboarding completes.

Report template

**Funds flow:** destination charges, platform fee 15%, on_behalf_of seller; platform covers losses
**Accounts:** controller-configured, Express dashboard, Stripe-collected requirements
**Onboarding:** hosted Account Links; gating on charges_enabled via account.updated
**Refund policy:** reverse_transfer + refund_application_fee (confirmed with user)
**Needs legal/finance review:** liability for disputes, payout timing, tax reporting (1099-K etc.)

What the zip contains

SKILL.md plus the interface file below. Nothing runs on install.

agents/openai.yamlgenerated from this page

Gives the Codex app a display name, a one-line description and a starter prompt for this skill.

interface:
  display_name: "Stripe Connect"
  short_description: "Marketplace payouts with a traceable money trail"
  default_prompt: "$stripe-connect Build seller onboarding for our marketplace with Stripe Connect Express"

About this skill

Written and maintained by the Codex Skills editors to the same editorial standard as every skill here: a selective description, a real workflow, known pitfalls and a way to verify the work. First published , last updated . MIT licensed. Something wrong or out of date? Tell us.

esc
Just launched

Codex Skills Pro is live

79 premium skills for Codex, each with scripts, references and an evidence report that proves the result.

Bought one by one$882.21
All 79 skills$49
94% off

That's $0.62 a skill, paid once.

Get all 79 for $49Or pick single skills from $4.99 →

Secure checkout by Creem