第二章:Next.js 整合 Stripe Checkout
在這章中,我們將學習如何使用 Stripe 的大殺器:Stripe Checkout。
如果你曾經串接過本土金流(如綠界),你會發現你需要自己刻一個結帳頁面,然後把資料打包、加密、轉址。而 Stripe Checkout 完全顛覆了這個流程。 Stripe Checkout 是一個由 Stripe 託管 (Hosted) 的結帳頁面,它經過了數十億次交易的 A/B 測試,擁有極高的轉換率。它不僅支援多國語系自動切換、貨幣在地化,更內建了 Apple Pay 與 Google Pay 的一鍵付款功能!
我們現在要做的,就是透過 Next.js 的後端 API,向 Stripe「申請」一個專屬的 Checkout URL,然後把使用者引導過去。
💻 實作:建立 Checkout Session API
在 App Router 架構下,我們建立一支 API 路由:src/app/api/stripe/checkout/route.ts。
import { NextResponse } from 'next/server';
import Stripe from 'stripe';
import { createClient } from '@/utils/supabase/server'; // 假設你用 Supabase
// 初始化 Stripe 實例
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
apiVersion: '2023-10-16', // 建議鎖定 API 版本
});
export async function POST(request: Request) {
try {
// 1. 驗證使用者身分 (確保只有登入會員可以購買)
const supabase = await createClient();
const { data: { user } } = await supabase.auth.getUser();
if (!user) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
// 2. 從前端取得要購買的 Price ID
const body = await request.json();
const { priceId } = body;
// 3. 建立 Stripe Checkout Session
// 這是最核心的 API 呼叫!
const session = await stripe.checkout.sessions.create({
// 告訴 Stripe 這次交易包含什麼商品
line_items: [
{
price: priceId, // 傳入上一章我們在 Dashboard 建立的 Price ID
quantity: 1, // 數量為 1
},
],
// mode 可以是 'payment' (單次付款) 或是 'subscription' (訂閱制)
mode: 'subscription',
// 成功與失敗的跳轉網址 (ReturnURL 的前端版本)
// 注意:這裡必須使用環境變數來動態決定網址,避免部署後跳回 localhost!
success_url: `${process.env.NEXT_PUBLIC_SITE_URL}/payment-success?session_id={CHECKOUT_SESSION_ID}`,
cancel_url: `${process.env.NEXT_PUBLIC_SITE_URL}/pricing`,
// 將我們資料庫的 User ID 偷藏在中繼資料 (metadata) 裡面
// 這樣當付款成功時,Stripe Webhook 會把這個 ID 傳回來給我們
metadata: {
userId: user.id,
},
// 預先帶入使用者的 Email,省去他們填寫的時間,提升轉換率!
customer_email: user.email,
});
// 4. 回傳建立好的 Checkout URL 給前端
return NextResponse.json({ url: session.url });
} catch (error: any) {
console.error('Stripe Checkout Error:', error);
return NextResponse.json({ error: error.message }, { status: 500 });
}
}
💡 程式碼亮點解析
mode: 'subscription':只要這一行字,Stripe 就會自動幫你處理每個月定期扣款的邏輯,你完全不需要自己寫 Cron Job (排程) 去定期刷卡!如果你賣的是單次買斷的課程,只要改成mode: 'payment'即可。metadata: { userId: user.id }:這是一個極度重要的技巧!這就像是我們在綠界裡用的CustomField。當使用者在 Stripe 頁面刷完卡,Stripe 會發一個 Webhook 給我們,我們必須靠這個metadata.userId才知道到底要幫資料庫裡的哪一個帳號開通權限。success_url:這就是使用者刷卡成功後會看到的頁面。Stripe 允許你在 URL 裡面加上{CHECKOUT_SESSION_ID}變數,Stripe 會自動把它替換成真實的 Session ID,讓你的前端頁面可以拿這個 ID 去要資料。
🖥️ 前端串接:發起結帳請求
後端 API 寫好後,前端的按鈕就非常簡單了。在你的 Pricing 頁面上:
'use client';
import { useState } from 'react';
export default function PricingButton({ priceId }: { priceId: string }) {
const [loading, setLoading] = useState(false);
const handleCheckout = async () => {
setLoading(true);
try {
const response = await fetch('/api/stripe/checkout', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ priceId }),
});
const data = await response.json();
if (data.url) {
// 直接將瀏覽器導向 Stripe 的高轉換率結帳頁面
window.location.href = data.url;
}
} catch (error) {
console.error('Error:', error);
} finally {
setLoading(false);
}
};
return (
<button
onClick={handleCheckout}
disabled={loading}
className="bg-indigo-600 text-white font-bold py-3 px-6 rounded-xl hover:bg-indigo-700 transition"
>
{loading ? '準備結帳中...' : '使用 Apple Pay / 信用卡付款'}
</button>
);
}
✅ 本章小結
現在,只要使用者點擊按鈕,他們就會被絲滑地引導到 Stripe 的官方結帳頁面,並且能看見 Apple Pay 或 Google Pay 的選項!
但等等,使用者付款成功被跳轉回 success_url 之後,我們該怎麼把權限給他?
在下一章,我們將探討金流系統的靈魂:Webhook 伺服器對伺服器通訊。這將確保即使使用者的瀏覽器在付款當下崩潰,你的資料庫依然能準確紀錄這筆美金入帳!