Astro กับ Stripe: รับชำระเงินออนไลน์
#astro13 เม.ย. 2569
Astro กับ Stripe: รับชำระเงินออนไลน์
Stripe เป็น payment platform ที่ได้รับความนิยมสูงสุดในโลก ด้วย API ที่ครบครันและ documentation ที่ยอดเยี่ยม การผสาน Stripe เข้ากับ Astro ทำให้คุณสามารถรับชำระเงินออนไลน์ได้อย่างปลอดภัยและมีประสิทธิภาพ
การติดตั้ง
npm install stripe @stripe/stripe-js
ตั้งค่า environment variables:
# .env
STRIPE_SECRET_KEY=sk_test_...
STRIPE_PUBLISHABLE_KEY=pk_test_...
STRIPE_WEBHOOK_SECRET=whsec_...
สร้าง Stripe Client
// src/lib/stripe.ts
import Stripe from 'stripe';
export const stripe = new Stripe(import.meta.env.STRIPE_SECRET_KEY, {
apiVersion: '2024-06-20',
typescript: true,
});
export const STRIPE_PUBLISHABLE_KEY = import.meta.env.STRIPE_PUBLISHABLE_KEY;
สร้าง Checkout Session
// src/pages/api/checkout.ts
import type { APIRoute } from 'astro';
import { stripe } from '../../lib/stripe';
interface CartItem {
id: string;
name: string;
price: number;
quantity: number;
image?: string;
}
export const POST: APIRoute = async ({ request }) => {
const { items, successUrl, cancelUrl } = await request.json() as {
items: CartItem[];
successUrl: string;
cancelUrl: string;
};
const session = await stripe.checkout.sessions.create({
payment_method_types: ['card', 'promptpay'],
line_items: items.map((item) => ({
price_data: {
currency: 'thb',
product_data: {
name: item.name,
images: item.image ? [item.image] : [],
},
unit_amount: item.price * 100, // Stripe ใช้ satang
},
quantity: item.quantity,
})),
mode: 'payment',
success_url: successUrl,
cancel_url: cancelUrl,
locale: 'th',
metadata: {
orderId: crypto.randomUUID(),
},
});
return new Response(
JSON.stringify({ sessionId: session.id, url: session.url }),
{ headers: { 'Content-Type': 'application/json' } }
);
};
Checkout Page
---
// src/pages/checkout.astro
import { STRIPE_PUBLISHABLE_KEY } from '../lib/stripe';
---
<html lang="th">
<head>
<title>ชำระเงิน</title>
<script src="https://js.stripe.com/v3/" is:inline></script>
</head>
<body>
<div id="checkout-container">
<h1>สรุปคำสั่งซื้อ</h1>
<div id="cart-items"></div>
<button id="checkout-btn">ชำระเงิน</button>
</div>
<script define:vars={{ publishableKey: STRIPE_PUBLISHABLE_KEY }}>
const stripe = Stripe(publishableKey);
// ดึงข้อมูล cart จาก localStorage
const cart = JSON.parse(localStorage.getItem('cart') || '[]');
document.getElementById('checkout-btn')?.addEventListener('click', async () => {
const response = await fetch('/api/checkout', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
items: cart,
successUrl: `${window.location.origin}/success?session_id={CHECKOUT_SESSION_ID}`,
cancelUrl: `${window.location.origin}/cart`,
}),
});
const { url } = await response.json();
window.location.href = url;
});
</script>
</body>
</html>
Webhook Handler
// src/pages/api/webhooks/stripe.ts
import type { APIRoute } from 'astro';
import { stripe } from '../../../lib/stripe';
import Stripe from 'stripe';
export const POST: APIRoute = async ({ request }) => {
const body = await request.text();
const signature = request.headers.get('stripe-signature')!;
let event: Stripe.Event;
try {
event = stripe.webhooks.constructEvent(
body,
signature,
import.meta.env.STRIPE_WEBHOOK_SECRET
);
} catch (err) {
return new Response(`Webhook Error: ${err}`, { status: 400 });
}
switch (event.type) {
case 'checkout.session.completed': {
const session = event.data.object as Stripe.Checkout.Session;
await handleSuccessfulPayment(session);
break;
}
case 'payment_intent.payment_failed': {
const paymentIntent = event.data.object as Stripe.PaymentIntent;
await handleFailedPayment(paymentIntent);
break;
}
case 'customer.subscription.created':
case 'customer.subscription.updated': {
const subscription = event.data.object as Stripe.Subscription;
await handleSubscriptionChange(subscription);
break;
}
}
return new Response(JSON.stringify({ received: true }), {
headers: { 'Content-Type': 'application/json' },
});
};
async function handleSuccessfulPayment(session: Stripe.Checkout.Session) {
console.log('Payment successful:', session.id);
// บันทึก order ลง database
// ส่ง confirmation email
}
async function handleFailedPayment(paymentIntent: Stripe.PaymentIntent) {
console.log('Payment failed:', paymentIntent.id);
// แจ้งเตือน customer
}
async function handleSubscriptionChange(subscription: Stripe.Subscription) {
console.log('Subscription changed:', subscription.id);
// อัปเดต user subscription status
}
Subscription Plans
// src/pages/api/subscriptions/create.ts
import type { APIRoute } from 'astro';
import { stripe } from '../../../lib/stripe';
export const POST: APIRoute = async ({ request }) => {
const { priceId, customerId } = await request.json();
const subscription = await stripe.subscriptions.create({
customer: customerId,
items: [{ price: priceId }],
payment_behavior: 'default_incomplete',
payment_settings: {
save_default_payment_method: 'on_subscription',
},
expand: ['latest_invoice.payment_intent'],
});
const invoice = subscription.latest_invoice as any;
const paymentIntent = invoice?.payment_intent;
return new Response(
JSON.stringify({
subscriptionId: subscription.id,
clientSecret: paymentIntent?.client_secret,
}),
{ headers: { 'Content-Type': 'application/json' } }
);
};
Success Page
---
// src/pages/success.astro
import { stripe } from '../lib/stripe';
const sessionId = Astro.url.searchParams.get('session_id');
if (!sessionId) {
return Astro.redirect('/');
}
const session = await stripe.checkout.sessions.retrieve(sessionId, {
expand: ['line_items', 'customer'],
});
---
<html lang="th">
<body>
<h1>ชำระเงินสำเร็จ!</h1>
<p>ขอบคุณสำหรับการสั่งซื้อ</p>
<p>หมายเลขคำสั่งซื้อ: {session.metadata?.orderId}</p>
<p>ยอดชำระ: {(session.amount_total ?? 0) / 100} บาท</p>
</body>
</html>
สรุป
การผสาน Stripe กับ Astro ทำให้สามารถสร้างระบบรับชำระเงินที่ปลอดภัยและครบครันได้อย่างรวดเร็ว ตั้งแต่ one-time payments ไปจนถึง subscription plans ด้วย webhook handling ที่ถูกต้อง ทำให้ระบบ payment มีความน่าเชื่อถือสูง