Почему Stripe
Stripe — ведущая платформа для приёма платежей. Поддерживает карты, Apple Pay, Google Pay, подписки и многое другое. Отличная документация и SDK для всех языков.
Установка и настройка
npm install stripe @stripe/stripe-js
// lib/stripe.ts
import Stripe from 'stripe';
export const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
apiVersion: '2023-10-16',
typescript: true
});
// Клиентская часть
// lib/stripe-client.ts
import { loadStripe } from '@stripe/stripe-js';
export const stripePromise = loadStripe(
process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY!
);
Stripe Checkout
Самый простой способ принять платёж — Stripe Checkout:
// app/api/checkout/route.ts
import { stripe } from '@/lib/stripe';
import { NextResponse } from 'next/server';
export async function POST(request: Request) {
const { items, userId } = await request.json();
const session = await stripe.checkout.sessions.create({
payment_method_types: ['card'],
line_items: items.map((item: CartItem) => ({
price_data: {
currency: 'rub',
product_data: {
name: item.name,
images: [item.image],
description: item.description
},
unit_amount: item.price * 100 // В копейках
},
quantity: item.quantity
})),
mode: 'payment',
success_url: `${process.env.NEXT_PUBLIC_URL}/success?session_id={CHECKOUT_SESSION_ID}`,
cancel_url: `${process.env.NEXT_PUBLIC_URL}/cart`,
metadata: {
userId
}
});
return NextResponse.json({ sessionId: session.id });
}
// Клиентский компонент
'use client';
import { loadStripe } from '@stripe/stripe-js';
const stripePromise = loadStripe(process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY!);
function CheckoutButton({ items }: { items: CartItem[] }) {
const handleCheckout = async () => {
const response = await fetch('/api/checkout', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ items, userId: 'user-123' })
});
const { sessionId } = await response.json();
const stripe = await stripePromise;
await stripe?.redirectToCheckout({ sessionId });
};
return (
<button onClick={handleCheckout}>
Оплатить
</button>
);
}
Подписки
// Создание подписки
export async function POST(request: Request) {
const { priceId, customerId } = await request.json();
// Создаём или получаем customer
let customer = customerId;
if (!customer) {
const newCustomer = await stripe.customers.create({
email: 'user@example.com',
metadata: { userId: 'user-123' }
});
customer = newCustomer.id;
}
const session = await stripe.checkout.sessions.create({
customer,
payment_method_types: ['card'],
line_items: [{
price: priceId, // ID цены из Stripe Dashboard
quantity: 1
}],
mode: 'subscription',
success_url: `${process.env.NEXT_PUBLIC_URL}/subscription/success`,
cancel_url: `${process.env.NEXT_PUBLIC_URL}/pricing`
});
return NextResponse.json({ sessionId: session.id });
}
// Отмена подписки
export async function cancelSubscription(subscriptionId: string) {
const subscription = await stripe.subscriptions.update(subscriptionId, {
cancel_at_period_end: true
});
return subscription;
}
// Изменение плана
export async function updateSubscription(
subscriptionId: string,
newPriceId: string
) {
const subscription = await stripe.subscriptions.retrieve(subscriptionId);
const updated = await stripe.subscriptions.update(subscriptionId, {
items: [{
id: subscription.items.data[0].id,
price: newPriceId
}],
proration_behavior: 'create_prorations'
});
return updated;
}
Webhooks
// app/api/webhooks/stripe/route.ts
import { stripe } from '@/lib/stripe';
import { headers } from 'next/headers';
export async function POST(request: Request) {
const body = await request.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 });
}
switch (event.type) {
case 'checkout.session.completed': {
const session = event.data.object as Stripe.Checkout.Session;
await handleSuccessfulPayment(session);
break;
}
case 'invoice.paid': {
const invoice = event.data.object as Stripe.Invoice;
await handleInvoicePaid(invoice);
break;
}
case 'invoice.payment_failed': {
const invoice = event.data.object as Stripe.Invoice;
await handlePaymentFailed(invoice);
break;
}
case 'customer.subscription.deleted': {
const subscription = event.data.object as Stripe.Subscription;
await handleSubscriptionCanceled(subscription);
break;
}
}
return new Response('OK', { status: 200 });
}
async function handleSuccessfulPayment(session: Stripe.Checkout.Session) {
const userId = session.metadata?.userId;
await prisma.order.create({
data: {
userId,
stripeSessionId: session.id,
amount: session.amount_total! / 100,
status: 'paid'
}
});
// Отправляем email
await sendOrderConfirmation(session.customer_email!);
}
Обработка ошибок
try {
const paymentIntent = await stripe.paymentIntents.create({
amount: 1000,
currency: 'rub'
});
} catch (error) {
if (error instanceof Stripe.errors.StripeCardError) {
// Ошибка карты
console.log('Card declined:', error.message);
} else if (error instanceof Stripe.errors.StripeRateLimitError) {
// Превышен лимит запросов
console.log('Rate limit exceeded');
} else if (error instanceof Stripe.errors.StripeInvalidRequestError) {
// Неверные параметры
console.log('Invalid request:', error.message);
} else {
// Другая ошибка
console.log('Stripe error:', error);
}
}
Заключение
Stripe упрощает приём платежей и управление подписками. Checkout — быстрый старт, webhooks — для синхронизации данных. Всегда проверяйте подписи webhooks и обрабатывайте ошибки.
Тестируйте в Stripe Test Mode перед production. Используйте тестовые карты: 4242 4242 4242 4242.
