第八章:印鈔機啟動!綠界科技 (ECPay) 金流串接與 Webhook 訂單回呼處理
在商業世界裡,有一個殘酷的現實: 如果一個網站不能收錢,不管它的動畫多漂亮、架構多完美,它充其量只能算是個「作品集」。 一旦它成功串接了金流,可以 24 小時不間斷地收錢入帳,它就正式升級為一台會下金蛋的「數位資產 (Digital Asset)」。
在台灣,要建立線上信用卡收付,最穩定、市佔率最高的老字號系統就是綠界科技 (ECPay)。
然而,綠界的官方文件對於新手工程師來說,往往像是一本晦澀難懂的天書。特別是它要求使用極度繁瑣的 SHA256 壓碼與排序加密機制。
許多工程師在這個關卡卡了幾個禮拜最後放棄。但在 Vibe Tutor 這套企業級原始碼中,我們已經幫你把所有的雷都踩過了,並將複雜的加密邏輯封裝成最乾淨俐落的 Next.js API Routes!
🎯 本章目標
- 理解綠界金流的運作原理與安全機制 (為什麼需要 HashKey?)。
- 解析前台結帳 API (
/api/ecpay/checkout) 的加密與自動跳轉流程。 - 掌握非同步的 Webhook 回呼機制 (
/api/ecpay/return),確保訂單不漏接。
🛒 步驟一:前台表單加密與送出 (Checkout Route)
當使用者在你的定價頁面上,興沖沖地按下「💳 立即購買 VIP 通行證」的瞬間,發生了什麼事?
前端的表單資料 (包含 courseId, price, tier) 會透過 HTTP POST 送到我們後端寫的 API Route:src/app/api/ecpay/checkout/route.ts。
在這個關鍵的 API 中,我們必須在伺服器端完成三件大事:
- 驗證使用者是否登入:如果沒登入,就不准付款。因為付完錢後,系統會不知道要把這堂尊貴的 VIP 課程歸屬給哪一個帳號。
- 建立唯一訂單編號 (MerchantTradeNo):產生一組絕對不重複的字串(例如:
VIBE+ 時間戳記1710234567),這是未來對帳的唯一憑證。 - 打包綠界需要的參數並加密:這包含商品名稱、金額、ReturnURL (回呼網址),以及最重要的防偽檢查碼。
🛡️ 核心大魔王:計算 CheckMacValue (防偽檢查碼)
為什麼不能直接把金額丟給綠界?因為如果傳輸過程沒有加密,懂點技術的駭客可以攔截封包,把「9,999 元」竄改成「1 元」,然後綠界就會傻傻地刷 1 塊錢,然後告訴你付款成功。
為了解決這個問題,綠界要求我們必須把所有的參數,加上只有你和綠界知道的專屬密碼 HashKey 與 HashIV,利用極其嚴格的規則排序並進行 SHA256 加密。
在我們的原始碼中,這段頭痛的加解密已經被完美封裝:
import crypto from 'crypto';
// 1. 將所有要送給綠界的參數,依照參數名稱的英文字母 A-Z 順序排列
const sortedKeys = Object.keys(params).sort();
// 2. 將密碼 (HashKey) 放在最前面,然後把參數串接起來
let checkValue = `HashKey=${process.env.ECPAY_HASH_KEY}`;
for (const key of sortedKeys) {
checkValue += `&${key}=${params[key]}`;
}
// 把密碼 (HashIV) 放在最後面
checkValue += `&HashIV=${process.env.ECPAY_HASH_IV}`;
// 3. 進行 URLEncode 並轉換大小寫 (這是綠界最常讓人踩坑的特殊規定)
checkValue = encodeURIComponent(checkValue).toLowerCase();
checkValue = checkValue.replace(/%2d/g, '-').replace(/%5f/g, '_').replace(/%2e/g, '.').replace(/%21/g, '!');
// 4. 進行強大的 SHA256 雜湊加密,轉成大寫
const CheckMacValue = crypto.createHash('sha256').update(checkValue).digest('hex').toUpperCase();
// 最後,把這個算出來的 CheckMacValue 也塞進參數裡
params.CheckMacValue = CheckMacValue;
這段程式碼是在伺服器端 (Node.js) 執行的,所以你的 HashKey 絕對不會洩漏給前端的使用者。
算出檢查碼後,我們的 API 會回傳一個包含所有欄位的隱藏 HTML <form> 表單,並透過一段小腳本讓瀏覽器「自動提交 (Auto-Submit)」跳轉到綠界的刷卡網頁。
📡 步驟二:綠界背景回呼機制 (Webhook / Return URL)
這是一般新手工程師最容易搞混、也是引發客訴最頻繁的地方。 當使用者在綠界輸入完信用卡號,按下確認付款並成功扣款後,綠界會有「兩個獨立的動作」:
- ClientRedirectURL (前端畫面跳轉):使用者的瀏覽器畫面會跳轉回你設定的「付款成功感謝頁面」。 【致命警告】:絕對不能在這個頁面的邏輯中寫入資料庫!因為使用者可能在刷卡成功的瞬間,網路斷線或是手滑關閉了瀏覽器,導致他永遠沒進到感謝頁面,這樣他的錢被扣了,權限卻沒開通,引發嚴重的客訴。
- ReturnURL (伺服器背景 Webhook):這才是最穩定的機制!綠界的伺服器會在背景,默默地發送一個 HTTP POST 請求給你的伺服器,通知你「這筆訂單真的付錢了」。
這支負責接旨的 API 位於 src/app/api/ecpay/return/route.ts:
export async function POST(request: Request) {
// 解析綠界送來的表單資料
const text = await request.text();
const params = new URLSearchParams(text);
// 1. 檢查綠界的狀態碼,'1' 代表信用卡付款成功
const RtnCode = params.get('RtnCode');
if (RtnCode === '1') {
// 2. 我們在第一步結帳時,利用綠界提供的 CustomField 來偷藏使用者的 UID 與課程 ID
const userId = params.get('CustomField1');
const itemId = params.get('CustomField2');
// 3. ⚠️ 資安防護:必須用相同的邏輯,驗證綠界傳來的 CheckMacValue,確保這個請求不是駭客偽造的!
// (此處省略驗證邏輯,請參考實際原始碼)
// 4. 以最高權限 (Service Role) 寫入 Supabase 資料庫
const supabase = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.SUPABASE_SERVICE_ROLE_KEY! // 無視上一章提到的 RLS 防護牆的超級鑰匙
);
// 寫入購買紀錄,解鎖權限!
await supabase.from('vt_purchases').insert({
user_id: userId,
item_id: itemId,
item_type: 'course',
price: parseInt(params.get('TradeAmt') || '0') // 紀錄實際收到的金額
});
}
// 5. 【極度重要】不管處理成功與否,一定要回傳純文字 "1|OK" 給綠界
// 如果你不回傳這個,綠界的系統會以為你的伺服器掛了,它會瘋狂地一直重試打你的 API!
return new NextResponse('1|OK', {
headers: { 'Content-Type': 'text/plain' },
});
}
🧪 步驟三:使用綠界測試信用卡與環境變數設定坑
在開發與測試階段,綠界提供了一組特殊的「測試用信用卡」,讓你可以在不扣真實金錢的情況下跑完完整的刷卡與 Webhook 流程。
💳 官方測試信用卡資訊
結帳時,請選擇信用卡付款並輸入:
- 信用卡號:
4311-9522-2222-2222 - 有效年月:任何未來的日期(例如
12/30) - 安全碼 (CVV):
222 - 手機驗證碼 (OTP):若跳出簡訊驗證畫面,請隨便輸入
123456即可通過。
🚨 新手必踩大坑:ReturnURL 與 Localhost
在我們設定 Webhook 時,系統會自動將你的網址作為 ReturnURL 傳給綠界。
這裡有一個致命的雷區:當你把網站部署到 Vercel (正式上線) 時,千萬不能在環境變數把網址設為 http://localhost:3000!
綠界的伺服器在外網,它絕對找不到你的「本地端電腦(localhost)」。這會導致:
- 綠界結帳畫面顯示「刷卡成功」。
- 但綠界發送 Webhook 到 localhost 失敗。
- 你的資料庫永遠不會收到付款通知,使用者的課程也不會解鎖,引發嚴重客訴!
解決方案:
在 Vercel 的環境變數 (Environment Variables) 設定中,務必確保程式碼能抓到 Vercel 的正式網址(例如 https://vibe-tutor-web.vercel.app),這樣綠界的 Webhook 訊號才能準確射進你的資料庫!
✅ 本章小結
這就是最經典、最穩定、最堅不可摧的非同步訂單處理流程。 這套強大的 Webhook 機制確保了:即使使用者的手機沒電關機、網路突然斷線,只要銀行的信用卡扣款成功,綠界的伺服器依然會不屈不撓地把訂單成功的資訊,安全地送到我們的資料庫中,瞬間幫使用者解鎖他夢寐以求的課程!
恭喜你!你的印鈔機已經正式組裝完畢並插上電源了。 在下一章,我們將探討如何透過 UI/UX 的「遊戲化 (Gamification)」設計心理學,讓使用者買完一堂課後,還想繼續買下一堂課!