🛑 處理退款與客訴 (Refunds & Disputes) 實戰
當你的產品越賣越好,難免會遇到需要「退款」的狀況。 可能是客戶買錯了、不滿意,或是更麻煩的:客戶忘記自己買過,直接打電話給發卡銀行宣告「這筆是盜刷」。
在電商與 SaaS 領域中,如果你不具備處理退款與爭議 (Disputes / Chargebacks) 的自動化能力,你會被大量的客服信件與銀行罰單給淹沒。本章將帶你實戰這兩大危機處理機制!
1. 為什麼不能只在後台手動退款?
你可能會問:「如果客戶要退款,我登入 Stripe 後台按一個按鈕不就好了?」
確實可以。但如果你的產品是「自動派發系統」(例如:買了立刻發送軟體序號、買了立刻開通課程權限),當你在 Stripe 手動退款後,你的資料庫並不知道這件事! 這會導致:錢退給客戶了,但他依然擁有課程觀看權限、依然能使用你的軟體。
這就是為什麼我們必須學會:
- 主動發起退款 API:讓我們的後台系統能一鍵退款,並同步拔除權限。
- 被動監聽退款 Webhook:如果老闆自己在 Stripe 後台按了退款,我們的系統也要能抓到通知並同步處理。
2. 主動發起退款 (Refund API)
假設你在自己的管理後台 (Admin Dashboard) 做了一個「一鍵退款」按鈕,當按下時,它會呼叫我們接下來要寫的這支 API。
Stripe 提供了一個非常簡單的 stripe.refunds.create 方法。
撰寫退款 API
建立檔案:src/app/api/stripe/refund/route.ts
import { NextResponse } from 'next/server';
import Stripe from 'stripe';
import { revokeUserAccess } from '@/lib/db'; // 你的資料庫邏輯
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
apiVersion: '2023-10-16',
});
export async function POST(request: Request) {
try {
const { paymentIntentId, reason, amount } = await request.json();
if (!paymentIntentId) {
return NextResponse.json({ error: 'Missing paymentIntentId' }, { status: 400 });
}
// 1. 發起退款請求
const refundConfig: Stripe.RefundCreateParams = {
payment_intent: paymentIntentId,
// 可選:退款原因 (duplicate, fraudulent, requested_by_customer)
reason: reason || 'requested_by_customer',
};
// 如果有傳入 amount,代表是「部分退款」(Partial Refund)
// 注意:Stripe 的金額單位是最小幣值 (例如 100 代表 1 塊美金或 100 塊台幣)
if (amount) {
refundConfig.amount = amount;
}
const refund = await stripe.refunds.create(refundConfig);
// 2. 退款成功!同步更新我們的資料庫,拔除使用者的權限
// (如果是部分退款,可能不需要拔除權限,請依照商業邏輯判斷)
if (!amount) {
// 假設這是全額退款
await revokeUserAccess(paymentIntentId);
console.log(`Access revoked for payment: ${paymentIntentId}`);
}
return NextResponse.json({
success: true,
refundId: refund.id,
status: refund.status
});
} catch (error: any) {
console.error('Refund Error:', error);
// 錯誤處理:例如餘額不足、已經退款過等
return NextResponse.json({ error: error.message }, { status: 500 });
}
}
💡 實戰地雷:退款需要「帳戶餘額」
在 Stripe 中,退款是從你的「Stripe 帳戶餘額」中扣除的。如果你的餘額已經全部提現到銀行帳戶了,退款 API 就會報錯 (Insufficient Funds)。這時候你可能需要從銀行帳戶轉錢回 Stripe,或是等下一筆訂單進來。
3. 被動監聽退款與爭議 (Webhooks)
如果你不想寫「主動發起退款」的 API,而是選擇用 Stripe App 或網頁後台手動處理,那你就「必須」寫 Webhook 來同步資料庫狀態。
更重要的是,信用卡爭議 (Disputes / Chargebacks) 只能透過 Webhook 來捕捉。
什麼是信用卡爭議 (Chargebacks)?
當客戶打電話給銀行說「這筆交易我沒刷過」或「我沒收到貨」,銀行會直接強制把錢從你的 Stripe 帳戶扣走(通常還會附帶高達 $15 美金的罰款!)。這在業界稱為 Chargeback。 此時,你的商品必須立刻停止服務,以免損失擴大。
實作進階 Webhook 監聽
在我們之前寫的 src/app/api/stripe/webhook/route.ts 中,加入以下兩個事件的監聽:
// ... 前方 Webhook 驗證程式碼省略 ...
switch (event.type) {
// 🟢 監聽:退款成功
case 'charge.refunded': {
const charge = event.data.object as Stripe.Charge;
const paymentIntentId = charge.payment_intent as string;
console.log(`Charge refunded for PaymentIntent: ${paymentIntentId}`);
// 判斷是全額還是部分退款
if (charge.amount_refunded === charge.amount) {
console.log('這是全額退款,立刻拔除權限!');
// TODO: 去資料庫把這個訂單狀態改為 'refunded',並拔除客戶權限
} else {
console.log(`這是部分退款,退了 ${charge.amount_refunded}`);
}
break;
}
// 🔴 監聽:可怕的信用卡爭議 (Chargeback) 被發起
case 'charge.dispute.created': {
const dispute = event.data.object as Stripe.Dispute;
const paymentIntentId = dispute.payment_intent as string;
console.log(`⚠️ 警告!收到信用卡爭議!PaymentIntent: ${paymentIntentId}`);
console.log(`爭議原因: ${dispute.reason}`);
// reason 可能是 fraudulent(盜刷)、product_not_received 等
// 💣 最佳實踐:遇到爭議,立刻鎖定該帳號,停止提供服務!
// TODO: 去資料庫把這個訂單狀態改為 'disputed',並立刻凍結客戶帳號
// 你也可以在這裡串接 Line Notify 或寄信通知老闆立刻上線處理!
break;
}
// 🔵 監聽:爭議處理結果出爐 (你贏了或是你輸了)
case 'charge.dispute.closed': {
const dispute = event.data.object as Stripe.Dispute;
if (dispute.status === 'won') {
console.log('🎉 恭喜!銀行判決我們勝訴,錢拿回來了!');
// TODO: 恢復客戶帳號與權限
} else if (dispute.status === 'lost') {
console.log('😭 悲劇,銀行判決我們敗訴,錢被扣走了。');
// 摸摸鼻子認賠,帳號繼續凍結
}
break;
}
default:
console.log(`Unhandled event type ${event.type}`);
}
4. 如何防止爭議發生?(商業智慧)
身為工程師,除了用程式碼處理爭議,你更該懂得如何「預防」它:
- 帳單描述 (Statement Descriptor) 要清楚:
在 Stripe 設定中,把帳單描述設定為你的品牌名稱 (例如
VIBETUTOR*COURSE)。很多爭議只是因為客戶看到帳單上寫著奇怪的英文字,忘記自己買過什麼就當成盜刷。 - 良好的客服管道: 在網頁最顯眼的地方提供退款政策與聯絡信箱。如果客戶能輕易找到你並獲得退款,他們就不會走銀行爭議流程(走銀行流程你會多賠 $15 美金的處理費)。
- 開啟 Stripe Radar: 這是 Stripe 內建的 AI 防偽系統,能自動阻擋高風險的盜刷卡片,每筆交易抽微薄的手續費,但能幫你省下大筆的 Dispute 罰款。
掌握了退款與爭議的程式化處理,你的平台才算是真正擁有「抗風險能力」的成熟商業級產品!盾牌架好後,就準備安心迎接龐大的金流吧!