1. Email với Resend
npm install resend @react-email/components
// emails/welcome.tsx — React Email template
import { Html, Head, Body, Container, Heading, Text, Button } from '@react-email/components';
export function WelcomeEmail({ name, url }: { name: string; url: string }) {
return (
<Html>
<Head />
<Body style={{ fontFamily: 'sans-serif' }}>
<Container>
<Heading>Chào mừng, {name}!</Heading>
<Text>Cảm ơn bạn đã đăng ký tài khoản.</Text>
<Button href={url} style={{ background: '#0070f3', color: '#fff', padding: '12px 24px' }}>
Xác nhận email
</Button>
</Container>
</Body>
</Html>
);
}
// lib/email.ts
import { Resend } from 'resend';
import { WelcomeEmail } from '@/emails/welcome';
const resend = new Resend(process.env.RESEND_API_KEY);
export async function sendWelcomeEmail(email: string, name: string) {
await resend.emails.send({
from: '[email protected]',
to: email,
subject: `Chào mừng ${name} đến với xDev!`,
react: WelcomeEmail({ name, url: `https://blog.xdev.asia/verify?email=${email}` }),
});
}
2. Stripe Payment Setup
npm install stripe @stripe/stripe-js @stripe/react-stripe-js
// lib/stripe.ts
import Stripe from 'stripe';
export const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
apiVersion: '2024-12-18.acacia',
});
3. Checkout Flow
// app/api/checkout/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { stripe } from '@/lib/stripe';
import { auth } from '@/auth';
export async function POST(request: NextRequest) {
const session = await auth();
if (!session) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
const { priceId } = await request.json();
const checkoutSession = await stripe.checkout.sessions.create({
customer_email: session.user?.email!,
mode: 'payment', // or 'subscription'
payment_method_types: ['card'],
line_items: [
{ price: priceId, quantity: 1 },
],
success_url: `${process.env.NEXT_PUBLIC_URL}/payment/success?session_id={CHECKOUT_SESSION_ID}`,
cancel_url: `${process.env.NEXT_PUBLIC_URL}/payment/cancel`,
metadata: {
userId: session.user?.id!,
},
});
return NextResponse.json({ url: checkoutSession.url });
}
// Client — Redirect to Stripe Checkout
'use client';
export function CheckoutButton({ priceId }: { priceId: string }) {
const handleCheckout = async () => {
const res = await fetch('/api/checkout', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ priceId }),
});
const { url } = await res.json();
window.location.href = url;
};
return (
<button onClick={handleCheckout}>
Mua ngay
</button>
);
}
4. Stripe Webhook
// app/api/webhooks/stripe/route.ts
import { stripe } from '@/lib/stripe';
import { headers } from 'next/headers';
import { db } from '@/lib/db';
export async function POST(request: Request) {
const body = await request.text();
const headersList = await headers();
const signature = headersList.get('stripe-signature')!;
let event;
try {
event = stripe.webhooks.constructEvent(
body,
signature,
process.env.STRIPE_WEBHOOK_SECRET!
);
} catch {
return new Response('Invalid signature', { status: 400 });
}
switch (event.type) {
case 'checkout.session.completed': {
const session = event.data.object;
await db.order.create({
data: {
userId: session.metadata?.userId!,
stripeSessionId: session.id,
amount: session.amount_total! / 100,
status: 'completed',
},
});
break;
}
case 'invoice.payment_failed': {
const invoice = event.data.object;
// Handle failed subscription payment
break;
}
}
return new Response('OK');
}
5. Subscription Management
// Server Action — manage subscription
'use server';
import { stripe } from '@/lib/stripe';
import { auth } from '@/auth';
import { db } from '@/lib/db';
export async function createCustomerPortal() {
const session = await auth();
if (!session) throw new Error('Unauthorized');
const user = await db.user.findUnique({
where: { id: session.user?.id },
});
const portalSession = await stripe.billingPortal.sessions.create({
customer: user!.stripeCustomerId!,
return_url: `${process.env.NEXT_PUBLIC_URL}/settings/billing`,
});
return { url: portalSession.url };
}
Bài tiếp theo: Testing React & Next.js — unit test, integration test, E2E.