スキル一覧に戻る
nathanielcrowell12-spec

stripe

by nathanielcrowell12-spec

A collection point for various photography website

0🍴 0📅 2026年1月21日
GitHubで見るManusで実行

SKILL.md


name: stripe description: > Implement Stripe Connect payments for PhotoVault marketplace using destination charges. Use when working with checkout sessions, webhook handlers, platform subscriptions ($22/month), commission splits (50/50), Connect onboarding, failed payment handling (dunning), or debugging payment issues. Includes idempotency patterns and PhotoVault product IDs.

⚠️ MANDATORY WORKFLOW - DO NOT SKIP

When this skill activates, you MUST follow the expert workflow before writing any code:

  1. Spawn Domain Expert using the Task tool with this prompt:

    Read the expert prompt at: C:\Users\natha\Stone-Fence-Brain\VENTURES\PhotoVault\claude\experts\stripe-expert.md
    
    Then research the codebase and write an implementation plan to: docs/claude/plans/stripe-[task-name]-plan.md
    
    Task: [describe the user's request]
    
  2. Spawn QA Critic after expert returns, using Task tool:

    Read the QA critic prompt at: C:\Users\natha\Stone-Fence-Brain\VENTURES\PhotoVault\claude\experts\qa-critic-expert.md
    
    Review the plan at: docs/claude/plans/stripe-[task-name]-plan.md
    Write critique to: docs/claude/plans/stripe-[task-name]-critique.md
    
  3. Present BOTH plan and critique to user - wait for approval before implementing

DO NOT read files and start coding. DO NOT rationalize that "this is simple." Follow the workflow.


Stripe Integration

Core Principles

Idempotency is Non-Negotiable

Webhooks can fire multiple times. API calls can timeout and retry. Your code must handle duplicates gracefully.

async function handlePaymentSucceeded(paymentIntent: Stripe.PaymentIntent) {
  const existing = await db.payments.findOne({
    stripe_payment_intent_id: paymentIntent.id
  })

  if (existing) {
    console.log(`Payment ${paymentIntent.id} already processed, skipping`)
    return
  }

  await db.payments.create({
    stripe_payment_intent_id: paymentIntent.id,
    amount: paymentIntent.amount,
    status: 'completed'
  })
}

Use Destination Charges for Marketplaces

PhotoVault uses Stripe Connect with Express accounts. Destination charges:

  • Route money directly to photographer
  • Deduct platform fee automatically
  • Create charge + transfer atomically
const session = await stripe.checkout.sessions.create({
  mode: 'payment',
  line_items: [{ price: priceId, quantity: 1 }],
  payment_intent_data: {
    application_fee_amount: platformFeeCents, // 50% to PhotoVault
    transfer_data: {
      destination: photographer.stripe_connect_account_id,
    },
  },
})

Always Verify Webhook Signatures

const event = stripe.webhooks.constructEvent(
  rawBody,
  signature,
  webhookSecret
)

Anti-Patterns

Webhook Mistakes

Not verifying webhook signatures

// WRONG
const event = JSON.parse(req.body)

// RIGHT
const event = stripe.webhooks.constructEvent(body, sig, secret)

Not handling duplicate events

// WRONG
case 'checkout.session.completed':
  await createCommission(session)  // Duplicates on retry!

// RIGHT
case 'checkout.session.completed':
  const exists = await db.commissions.findOne({
    stripe_session_id: session.id
  })
  if (!exists) {
    await createCommission(session)
  }

Processing webhooks synchronously

// WRONG: Slow response, timeout risk
app.post('/webhook', async (req, res) => {
  await sendEmails()
  await updateDatabase()
  res.json({ received: true })
})

// RIGHT: Acknowledge fast
app.post('/webhook', async (req, res) => {
  await queueForProcessing(event)
  res.json({ received: true })
})

Connect Mistakes

Using transfers when you should use destination charges

// WRONG: Two API calls, race condition risk
const charge = await stripe.paymentIntents.create({ amount: 10000 })
const transfer = await stripe.transfers.create({ amount: 5000, destination: accountId })

// RIGHT: Atomic destination charge
const session = await stripe.checkout.sessions.create({
  payment_intent_data: {
    application_fee_amount: 5000,
    transfer_data: { destination: accountId }
  }
})

Webhook Handler Pattern

import Stripe from 'stripe'
import { NextRequest, NextResponse } from 'next/server'

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

export async function POST(req: NextRequest) {
  const body = await req.text()
  const signature = req.headers.get('stripe-signature')!

  let event: Stripe.Event
  try {
    event = stripe.webhooks.constructEvent(
      body,
      signature,
      process.env.STRIPE_WEBHOOK_SECRET!
    )
  } catch (err) {
    return NextResponse.json({ error: 'Invalid signature' }, { status: 400 })
  }

  try {
    switch (event.type) {
      case 'checkout.session.completed':
        await handleCheckoutComplete(event.data.object as Stripe.Checkout.Session)
        break
      case 'invoice.paid':
        await handleInvoicePaid(event.data.object as Stripe.Invoice)
        break
      case 'invoice.payment_failed':
        await handlePaymentFailed(event.data.object as Stripe.Invoice)
        break
    }
  } catch (err) {
    console.error(`Error processing ${event.type}:`, err)
  }

  return NextResponse.json({ received: true })
}

PhotoVault Configuration

Stripe Products (Test Mode)

ProductIDPrice
Year Packageprod_TV5f6EOT5K3wKt$100 + $8/mo
6-Month Packageprod_TV5f1eAehZIlA2$50 + $8/mo
6-Month Trialprod_TV5fYvY8l0WaaV$20 one-time
Client Monthlyprod_TV5gXyg5nNn635$8/month
Direct Monthlyprod_TV6BkuQUCil1ZD$8/month (0% commission)
Platform Feeprod_TV5evkNAa2Ezo5$22/month

Key Files

src/
├── lib/stripe.ts                    # Stripe config & helpers
├── app/api/
│   ├── stripe/
│   │   ├── create-checkout/         # Checkout session creation
│   │   ├── connect/                 # Connect onboarding
│   │   └── platform-subscription/
│   └── webhooks/stripe/             # Webhook handler

Environment Variables

STRIPE_SECRET_KEY=sk_test_...
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_...
STRIPE_WEBHOOK_SECRET=whsec_...
STRIPE_PRICE_PLATFORM_MONTHLY=price_...

Commission Rate

  • Platform takes 50% (PHOTOGRAPHER_COMMISSION_RATE = 0.50)
  • API Version: 2025-09-30.clover

Testing with Stripe CLI

# Forward webhooks to local server
stripe listen --forward-to localhost:3002/api/webhooks/stripe

# Trigger test events
stripe trigger checkout.session.completed
stripe trigger invoice.payment_failed

Test Cards

CardNumberResult
Success4242 4242 4242 4242Payment succeeds
Decline4000 0000 0000 0002Card declined
3D Secure4000 0025 0000 3155Requires auth
Insufficient4000 0000 0000 9995Insufficient funds

Debugging Checklist

  1. Check Stripe Dashboard → Payments → Find the payment
  2. Check webhook logs → Developers → Webhooks → Recent events
  3. Verify Connect account → Connect → Accounts → Is it enabled?
  4. Check your logs → Console for errors
  5. Verify webhook secret → Is it correct for this endpoint?
  6. Check idempotency → Is the commission already created?

スコア

総合スコア

50/100

リポジトリの品質指標に基づく評価

SKILL.md

SKILL.mdファイルが含まれている

+20
LICENSE

ライセンスが設定されている

0/10
説明文

100文字以上の説明がある

0/10
人気

GitHub Stars 100以上

0/15
最近の活動

3ヶ月以内に更新がある

0/10
フォーク

10回以上フォークされている

0/5
Issue管理

オープンIssueが50未満

+5
言語

プログラミング言語が設定されている

+5
タグ

1つ以上のタグが設定されている

0/5

レビュー

💬

レビュー機能は近日公開予定です