AgentSkillsCN

stripe-integration

实现 Stripe 支付处理功能,打造稳健且符合 PCI 标准的支付流程,涵盖结账、订阅、Webhook 以及积分系统等环节。当您需要集成 Stripe 支付、构建订阅系统、实现安全的结账流程,或管理基于积分的定价方案时,可选用此技能。

SKILL.md
--- frontmatter
name: stripe-integration
description: Implement Stripe payment processing for robust, PCI-compliant payment flows including checkout, subscriptions, webhooks, and credits systems. Use when integrating Stripe payments, building subscription systems, implementing secure checkout flows, or managing credits-based pricing.

Stripe Integration Expert

Master Stripe payment processing integration for robust, PCI-compliant payment flows including checkout, subscriptions, webhooks, refunds, and credits-based systems.

When to Use This Skill

  • Implementing payment processing in web/mobile applications
  • Setting up subscription billing systems
  • Handling one-time payments and recurring charges
  • Building credits/top-up payment systems
  • Processing refunds and disputes
  • Managing customer payment methods
  • Implementing SCA (Strong Customer Authentication) for European payments

Core Concepts

1. Payment Flows

Checkout Session (Hosted)

  • Stripe-hosted payment page
  • Minimal PCI compliance burden
  • Fastest implementation
  • Supports one-time and recurring payments

Payment Intents (Custom UI)

  • Full control over payment UI
  • Requires Stripe.js for PCI compliance
  • More complex implementation
  • Better customization options

Payment Links (No-Code)

  • Shareable payment URLs
  • No code required
  • Good for simple products

2. Webhooks

Critical Events:

  • checkout.session.completed: Checkout completed
  • payment_intent.succeeded: Payment completed
  • payment_intent.payment_failed: Payment failed
  • customer.subscription.updated: Subscription changed
  • customer.subscription.deleted: Subscription canceled
  • invoice.paid: Subscription invoice paid
  • charge.refunded: Refund processed

3. Products & Prices

Products represent what you're selling. Prices define how much and how often to charge.

javascript
// One-time price (for credits)
const price = await stripe.prices.create({
  product: productId,
  unit_amount: 500, // $5.00 in cents
  currency: 'usd',
});

// Recurring price (for subscriptions)
const recurringPrice = await stripe.prices.create({
  product: productId,
  unit_amount: 1999, // $19.99/month
  currency: 'usd',
  recurring: { interval: 'month' }
});

Quick Start (Next.js + TypeScript)

Checkout Session

typescript
// app/api/checkout/route.ts
import Stripe from 'stripe';

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

export async function POST(req: Request) {
  const { priceId, userId, credits } = await req.json();

  const session = await stripe.checkout.sessions.create({
    line_items: [{ price: priceId, quantity: 1 }],
    mode: 'payment',
    success_url: `${process.env.NEXT_PUBLIC_URL}/success?session_id={CHECKOUT_SESSION_ID}`,
    cancel_url: `${process.env.NEXT_PUBLIC_URL}/pricing`,
    metadata: {
      user_id: userId,
      credits: credits.toString()
    }
  });

  return Response.json({ url: session.url });
}

Payment Link

typescript
const paymentLink = await stripe.paymentLinks.create({
  line_items: [{ price: priceId, quantity: 1 }],
  metadata: { product_type: 'credits' }
});
// Share paymentLink.url

Webhook Handling (Next.js)

typescript
// app/api/webhooks/stripe/route.ts
import Stripe from 'stripe';
import { headers } from 'next/headers';

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

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

  let event: Stripe.Event;

  try {
    event = stripe.webhooks.constructEvent(
      body,
      signature,
      process.env.STRIPE_WEBHOOK_SECRET!
    );
  } catch (err) {
    console.error('Webhook signature verification failed');
    return new Response('Invalid signature', { status: 400 });
  }

  // Idempotency check
  const eventId = event.id;
  if (await isEventProcessed(eventId)) {
    return new Response('Already processed', { status: 200 });
  }

  try {
    switch (event.type) {
      case 'checkout.session.completed': {
        const session = event.data.object as Stripe.Checkout.Session;
        await handleCheckoutComplete(session);
        break;
      }
      case 'invoice.paid': {
        const invoice = event.data.object as Stripe.Invoice;
        await handleInvoicePaid(invoice);
        break;
      }
      case 'customer.subscription.deleted': {
        const subscription = event.data.object as Stripe.Subscription;
        await handleSubscriptionCanceled(subscription);
        break;
      }
    }

    await markEventProcessed(eventId);
  } catch (err) {
    console.error('Webhook handler error:', err);
    return new Response('Handler error', { status: 500 });
  }

  return new Response('OK', { status: 200 });
}

async function handleCheckoutComplete(session: Stripe.Checkout.Session) {
  const userId = session.metadata?.user_id;
  const credits = parseInt(session.metadata?.credits || '0');

  if (session.mode === 'payment' && credits > 0) {
    // Add credits to user
    await addCreditsToUser(userId, credits, session.payment_intent as string);
  }
}

Credits System (Supabase Integration)

Database Schema

sql
-- User credits balance
create table user_credits (
  id uuid primary key default gen_random_uuid(),
  user_id uuid references auth.users(id) not null unique,
  balance integer not null default 0,
  stripe_customer_id text,
  created_at timestamptz default now(),
  updated_at timestamptz default now()
);

-- Credit transactions audit trail
create table credit_transactions (
  id uuid primary key default gen_random_uuid(),
  user_id uuid references auth.users(id) not null,
  amount integer not null, -- positive = add, negative = use
  type text not null, -- 'purchase', 'usage', 'refund', 'subscription'
  description text,
  stripe_payment_id text,
  created_at timestamptz default now()
);

-- Enable RLS
alter table user_credits enable row level security;
alter table credit_transactions enable row level security;

-- Policies
create policy "Users can view own credits"
  on user_credits for select to authenticated
  using (auth.uid() = user_id);

create policy "Users can view own transactions"
  on credit_transactions for select to authenticated
  using (auth.uid() = user_id);

PostgreSQL Functions

sql
-- Add credits (after purchase)
create or replace function add_credits(
  p_user_id uuid,
  p_amount integer,
  p_type text,
  p_stripe_payment_id text default null
)
returns void
language plpgsql security definer set search_path = ''
as $$
begin
  insert into public.user_credits (user_id, balance)
  values (p_user_id, p_amount)
  on conflict (user_id) do update set
    balance = public.user_credits.balance + p_amount,
    updated_at = now();

  insert into public.credit_transactions (user_id, amount, type, stripe_payment_id)
  values (p_user_id, p_amount, p_type, p_stripe_payment_id);
end;
$$;

-- Use credits (with balance check)
create or replace function use_credits(
  p_user_id uuid,
  p_amount integer,
  p_description text
)
returns boolean
language plpgsql security definer set search_path = ''
as $$
declare
  v_balance integer;
begin
  select balance into v_balance
  from public.user_credits where user_id = p_user_id for update;

  if v_balance is null or v_balance < p_amount then
    return false;
  end if;

  update public.user_credits
  set balance = balance - p_amount, updated_at = now()
  where user_id = p_user_id;

  insert into public.credit_transactions (user_id, amount, type, description)
  values (p_user_id, -p_amount, 'usage', p_description);

  return true;
end;
$$;

Customer Management

typescript
// Create customer and link to Supabase user
async function createStripeCustomer(user: User) {
  const customer = await stripe.customers.create({
    email: user.email,
    name: user.name,
    metadata: { supabase_user_id: user.id }
  });

  // Store in Supabase
  await supabase
    .from('user_credits')
    .upsert({ user_id: user.id, stripe_customer_id: customer.id });

  return customer;
}

// Customer Portal (manage subscriptions)
async function createPortalSession(customerId: string) {
  const session = await stripe.billingPortal.sessions.create({
    customer: customerId,
    return_url: `${process.env.NEXT_PUBLIC_URL}/account`,
  });
  return session.url;
}

Refund Handling

typescript
async function createRefund(
  paymentIntentId: string,
  amount?: number, // partial refund in cents
  reason?: 'duplicate' | 'fraudulent' | 'requested_by_customer'
) {
  const refund = await stripe.refunds.create({
    payment_intent: paymentIntentId,
    amount, // omit for full refund
    reason
  });

  // Deduct credits if applicable
  if (refund.status === 'succeeded') {
    // Handle credit reversal in your database
  }

  return refund;
}

Testing

Test Card Numbers

CardNumberUse Case
Success4242424242424242Successful payment
Declined4000000000000002Generic decline
3D Secure4000002500003155Requires authentication
Insufficient Funds4000000000009995Insufficient funds

Local Webhook Testing

bash
# Install Stripe CLI
stripe listen --forward-to localhost:3000/api/webhooks/stripe

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

MCP Tools Available

The Stripe MCP provides these tools:

ToolDescription
create_productCreate a new product
list_productsList all products
create_priceCreate a price for a product
list_pricesList all prices
create_customerCreate a customer
list_customersList customers
create_payment_linkCreate a payment link
list_payment_intentsList payment intents
create_invoiceCreate an invoice
list_invoicesList invoices
create_refundCreate a refund
create_couponCreate a coupon
list_couponsList coupons
list_subscriptionsList subscriptions
cancel_subscriptionCancel a subscription
update_subscriptionUpdate a subscription
retrieve_balanceGet account balance
list_disputesList disputes
update_disputeUpdate dispute with evidence

Best Practices

Security

  1. Always verify webhook signatures - Never trust unverified events
  2. Use metadata to link Stripe objects to your database
  3. Never expose secret keys to the client
  4. Handle PCI compliance - Use Stripe.js, never raw card data

Reliability

  1. Implement idempotency - Handle webhook events exactly once
  2. Don't rely on client-side confirmation - Always use webhooks
  3. Handle all error cases gracefully
  4. Log all payment events for debugging

User Experience

  1. Use Checkout Sessions for fastest implementation
  2. Provide Customer Portal for subscription management
  3. Send email receipts (Stripe handles this)
  4. Show clear error messages on payment failures

Testing

  1. Use test mode keys during development
  2. Test with Stripe CLI for webhooks
  3. Test all card scenarios (success, decline, 3DS)
  4. Test subscription lifecycle (create, update, cancel)

Pricing Models for cv-optimizer

Per-Page Credits ($1/page)

PackCreditsPricePer Credit
Starter5$5$1.00
Basic12$10$0.83
Pro30$20$0.67
Enterprise100$50$0.50

Monthly Subscription (Phase 2)

PlanCredits/monthPriceExtras
Basic20$15/moHistory
Pro50$30/moHistory + Priority
Team200$99/moMulti-user