Use Cases

Claude Code के साथ Integrate Payment Systems (Stripe) कैसे करें

Claude Code का उपयोग करके integrate payment systems (stripe) सीखें। Practical code examples और step-by-step guidance शामिल है।

決済システムのimplementationにClaude Code use करनाメリット

決済システムはsecurityと正確性が求められるimportantなfeatures है।Claude CodeはStripeのベストプラクティスに沿ったimplementation generateし、Webhookprocessingやerror handlingの見落としを防ぎ है।

Stripe Checkoutによる決済

> Stripe Checkoutを使ったsubscription決済をimplement करो。
> フリー・プロ・エンタープライズの3プランで。
> 成功・キャンセル時のredirectもsettingsして。

checkアウトセッションのcreate

// src/services/payment-service.ts
import Stripe from 'stripe';

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
  apiVersion: '2024-12-18.acacia',
});

const PLANS = {
  pro: {
    priceId: process.env.STRIPE_PRO_PRICE_ID!,
    name: 'Pro',
  },
  enterprise: {
    priceId: process.env.STRIPE_ENTERPRISE_PRICE_ID!,
    name: 'Enterprise',
  },
} as const;

type PlanKey = keyof typeof PLANS;

export class PaymentService {
  async createCheckoutSession(userId: string, plan: PlanKey) {
    // 既存のStripe顧客 search、なければcreate
    let customer = await this.findOrCreateCustomer(userId);

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

    return { url: session.url };
  }

  async createPortalSession(userId: string) {
    const customer = await this.getCustomerByUserId(userId);

    const session = await stripe.billingPortal.sessions.create({
      customer: customer.id,
      return_url: `${process.env.APP_URL}/settings/billing`,
    });

    return { url: session.url };
  }

  private async findOrCreateCustomer(userId: string) {
    const user = await prisma.user.findUnique({ where: { id: userId } });
    if (!user) throw new Error('User not found');

    if (user.stripeCustomerId) {
      return stripe.customers.retrieve(user.stripeCustomerId) as Promise<Stripe.Customer>;
    }

    const customer = await stripe.customers.create({
      email: user.email,
      metadata: { userId },
    });

    await prisma.user.update({
      where: { id: userId },
      data: { stripeCustomerId: customer.id },
    });

    return customer;
  }

  private async getCustomerByUserId(userId: string) {
    const user = await prisma.user.findUnique({ where: { id: userId } });
    if (!user?.stripeCustomerId) throw new Error('No Stripe customer found');
    return stripe.customers.retrieve(user.stripeCustomerId) as Promise<Stripe.Customer>;
  }
}

APIrouteのsettings

// src/app/api/checkout/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { PaymentService } from '@/services/payment-service';
import { getSession } from '@/lib/auth';

const paymentService = new PaymentService();

export async function POST(req: NextRequest) {
  const session = await getSession();
  if (!session) {
    return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
  }

  const { plan } = await req.json();
  if (!['pro', 'enterprise'].includes(plan)) {
    return NextResponse.json({ error: 'Invalid plan' }, { status: 400 });
  }

  const checkout = await paymentService.createCheckoutSession(session.userId, plan);
  return NextResponse.json(checkout);
}

Webhookprocessing

> Stripeのwebhookhandlerをimplement करो。
> subscriptionのcreate・update・キャンセルをprocessingして。
> 署名検証もimplement करो。
// src/app/api/webhooks/stripe/route.ts
import { NextRequest, NextResponse } from 'next/server';
import Stripe from 'stripe';
import { prisma } from '@/lib/db';

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) {
    console.error('Webhook signature verification failed');
    return NextResponse.json({ error: 'Invalid signature' }, { status: 400 });
  }

  switch (event.type) {
    case 'checkout.session.completed': {
      const session = event.data.object as Stripe.Checkout.Session;
      await handleCheckoutCompleted(session);
      break;
    }

    case 'customer.subscription.updated': {
      const subscription = event.data.object as Stripe.Subscription;
      await handleSubscriptionUpdated(subscription);
      break;
    }

    case 'customer.subscription.deleted': {
      const subscription = event.data.object as Stripe.Subscription;
      await handleSubscriptionCanceled(subscription);
      break;
    }

    case 'invoice.payment_failed': {
      const invoice = event.data.object as Stripe.Invoice;
      await handlePaymentFailed(invoice);
      break;
    }
  }

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

async function handleCheckoutCompleted(session: Stripe.Checkout.Session) {
  const userId = session.metadata?.userId;
  if (!userId) return;

  await prisma.user.update({
    where: { id: userId },
    data: {
      plan: session.metadata?.plan || 'pro',
      stripeSubscriptionId: session.subscription as string,
      subscriptionStatus: 'active',
    },
  });
}

async function handleSubscriptionUpdated(subscription: Stripe.Subscription) {
  const user = await prisma.user.findFirst({
    where: { stripeSubscriptionId: subscription.id },
  });
  if (!user) return;

  await prisma.user.update({
    where: { id: user.id },
    data: { subscriptionStatus: subscription.status },
  });
}

async function handleSubscriptionCanceled(subscription: Stripe.Subscription) {
  const user = await prisma.user.findFirst({
    where: { stripeSubscriptionId: subscription.id },
  });
  if (!user) return;

  await prisma.user.update({
    where: { id: user.id },
    data: {
      plan: 'free',
      subscriptionStatus: 'canceled',
      stripeSubscriptionId: null,
    },
  });
}

async function handlePaymentFailed(invoice: Stripe.Invoice) {
  const customerId = invoice.customer as string;
  const user = await prisma.user.findFirst({
    where: { stripeCustomerId: customerId },
  });
  if (!user) return;

  // 支払い失敗の通知メールを送信
  await sendPaymentFailedEmail(user.email);
}

料金表component

function PricingTable() {
  const handleCheckout = async (plan: string) => {
    const res = await fetch('/api/checkout', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ plan }),
    });
    const { url } = await res.json();
    window.location.href = url;
  };

  return (
    <div className="grid gap-6 md:grid-cols-3">
      <PricingCard
        name="Free"
        price="$0"
        features={['基本features', '月5Project']}
        buttonText="現在のプラン"
        disabled
      />
      <PricingCard
        name="Pro"
        price="$1,980/月"
        features={['全features', '無制限Project', '優先サポート']}
        buttonText="Proにupgrade"
        onSelect={() => handleCheckout('pro')}
        highlighted
      />
      <PricingCard
        name="Enterprise"
        price="$9,800/月"
        features={['全features', 'SSO', '専任サポート', 'SLA保証']}
        buttonText="お問い合わせ"
        onSelect={() => handleCheckout('enterprise')}
      />
    </div>
  );
}

Summary

Claude Code का उपयोग करके、Stripe決済のintegrationをcheckアウト सेWebhookprocessing तकsafeかつefficientlyimplementationでき है।決済はsecurityに直結するため、code reviewは必ず行いましょう。Projectの決済方針はCLAUDE.mdに記述しておく बातをお勧めし है।code品質の維持にはrefactoringのautomationもutilizationして करें।

Claude Codeके details के लिएAnthropicofficial documentationदेखें。StripeのimplementationガイドはStripeofficial documentationもदेखें。

#Claude Code #Stripe #payments #subscription #TypeScript