Use Cases

Stripe dengan Claude Code

Pelajari tentang stripe menggunakan Claude Code. Dilengkapi tips praktis dan contoh kode.

Stripesubscriptionimplementasi dengan Claude Code: efisiensi

subscription課金 SaaSビジネス 根幹.プラン変更、トライアル、請求manajemen、Webhookpemrosesan dll.考慮すべき点 多い す 、Claude Code 使えばStripe best practices 沿ったimplementasi efisien 進められ.

プランdefinisiとCheckoutsession

> Stripesubscription implementasikan.
> Free / Pro / Enterprise 3プラン dengan 、
> トライアル期間14日間、プラン変更・キャンセルdukungan、
> Webhook dengan 状態同期 juga 含めて。
// src/lib/subscription.ts
import Stripe from 'stripe';

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

export const PLANS = {
  free: { name: 'Free', priceId: null, features: ['dasar機能', '3proyek'] },
  pro: { name: 'Pro', priceId: process.env.STRIPE_PRO_PRICE_ID!, features: ['全機能', '無制限proyek', '優先サポート'] },
  enterprise: { name: 'Enterprise', priceId: process.env.STRIPE_ENT_PRICE_ID!, features: ['全機能', 'SSO', '専任担当', 'SLA'] },
} as const;

export type PlanKey = keyof typeof PLANS;

export async function createSubscription(userId: string, plan: PlanKey) {
  if (plan === 'free') throw new Error('Freeプラン subscription 不要 す');

  const customer = await findOrCreateCustomer(userId);
  const priceId = PLANS[plan].priceId;

  const session = await stripe.checkout.sessions.create({
    customer: customer.id,
    mode: 'subscription',
    line_items: [{ price: priceId!, quantity: 1 }],
    subscription_data: { trial_period_days: 14 },
    success_url: `${process.env.APP_URL}/dashboard?upgraded=true`,
    cancel_url: `${process.env.APP_URL}/pricing`,
    metadata: { userId, plan },
  });

  return session.url;
}

Penanganan Webhook

// src/app/api/webhooks/stripe/route.ts
import { NextRequest, NextResponse } from 'next/server';
import Stripe from 'stripe';
import { prisma } from '@/lib/prisma';

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

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

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

  switch (event.type) {
    case 'customer.subscription.created':
    case 'customer.subscription.updated': {
      const subscription = event.data.object as Stripe.Subscription;
      await prisma.subscription.upsert({
        where: { stripeSubscriptionId: subscription.id },
        create: {
          stripeSubscriptionId: subscription.id,
          stripeCustomerId: subscription.customer as string,
          status: subscription.status,
          plan: subscription.metadata.plan || 'pro',
          currentPeriodEnd: new Date(subscription.current_period_end * 1000),
        },
        update: {
          status: subscription.status,
          currentPeriodEnd: new Date(subscription.current_period_end * 1000),
        },
      });
      break;
    }

    case 'customer.subscription.deleted': {
      const subscription = event.data.object as Stripe.Subscription;
      await prisma.subscription.update({
        where: { stripeSubscriptionId: subscription.id },
        data: { status: 'canceled' },
      });
      break;
    }

    case 'invoice.payment_failed': {
      const invoice = event.data.object as Stripe.Invoice;
      // 支払いgagal notifikasiメール pengiriman
      await sendPaymentFailedEmail(invoice.customer as string);
      break;
    }
  }

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

プラン変更pemrosesan

// src/lib/subscription.ts(続き)
export async function changePlan(userId: string, newPlan: PlanKey) {
  const sub = await prisma.subscription.findFirst({
    where: { userId, status: 'active' },
  });

  if (!sub) throw new Error('アクティブなsubscription ありません');

  const stripeSubscription = await stripe.subscriptions.retrieve(
    sub.stripeSubscriptionId
  );

  await stripe.subscriptions.update(sub.stripeSubscriptionId, {
    items: [
      {
        id: stripeSubscription.items.data[0].id,
        price: PLANS[newPlan].priceId!,
      },
    ],
    proration_behavior: 'create_prorations', // 日割り計算
    metadata: { plan: newPlan },
  });
}

export async function cancelSubscription(userId: string) {
  const sub = await prisma.subscription.findFirst({
    where: { userId, status: 'active' },
  });

  if (!sub) throw new Error('アクティブなsubscription ありません');

  // 期間selesai時 キャンセル(即時 tidak)
  await stripe.subscriptions.update(sub.stripeSubscriptionId, {
    cancel_at_period_end: true,
  });
}

顧客ポータル

Stripe 顧客ポータル 使えば、プラン変更・支払いmetode pembaruan・請求書 閲覧 Stripe側 UI 任せられ.

export async function createPortalSession(userId: string) {
  const customer = await getCustomerByUserId(userId);
  const session = await stripe.billingPortal.sessions.create({
    customer: customer.id,
    return_url: `${process.env.APP_URL}/settings/billing`,
  });
  return session.url;
}

Artikel Terkait

決済全般 mengenai Stripe決済 integrasipanduan、認証周り 認証機能 implementasi silakan lihat.

Stripe 公式panduan(stripe.com/docs/billing) juga 必ずkonfirmasiし.

#Claude Code #Stripe #subscription #課金 #TypeScript