Webflow Ecommerce 整合台灣金流的可能性
以下是我們為堅持使用TapPay(台灣市場常用的第三方收單)而不是 Stripe/PayPal 的 Webflow 客戶提供的實用「真正有效」操作指南。
0 . Reality check — what Webflow will (and won’t) let you do
-
Webflow Ecommerce 的結帳畫面只能直接掛 Stripe 與 PayPal;官方目前沒有「自訂金流」入口點.
-
因此你有兩條路:
- 完全替換 Webflow 內建結帳(自己做前端+Serverless 後端呼叫 TapPay API)。
- 把購物車外包給可支援自訂金流的嵌入式平台(Foxy.io、Shopify Buy-Button 等)。本文聚焦第一條路;第二條路在附錄簡述。
1 . 準備工作
事項 | 說明 |
---|---|
TapPay 帳號 | 申請商家後拿到 APP ID / APP Key / Partner Key / Merchant ID |
環境切換 | Sandbox 測試→Production 上線(兩組金鑰要分開存放) |
Webflow | Site Plan 已開啟 Ecommerce,但後面我們不會用官方 Checkout 元件 |
2 . 在 Webflow 建立「自製 Checkout」頁
- 複製一份 Checkout Page → 移除 Webflow 的
Payment Info
區塊,保留購物車摘要、客戶地址欄位。 - 在 Page Settings → Custom Code → Footer 加入 TapPay JS SDK:
<script src="https://js.tappaysdk.com/tpdirect/v4"></script>
<script>
TPDirect.setupSDK(APP_ID, 'APP_KEY', 'sandbox'); // 上線改 'production'
</script>
``` :contentReference[oaicite:2]{index=2}
3. 在畫面放三個空 div 容器(可用 Embed 元件):
```html
<div class="tpfield" id="card-number"></div>
<div class="tpfield" id="card-expiration-date"></div>
<div class="tpfield" id="card-ccv"></div>
- 用 TapPay Fields 產生信用卡欄位並監聽狀態:
const fields = {
number: {element:'#card-number', placeholder:'**** **** **** ****'},
expirationDate:{element:'#card-expiration-date',placeholder:'MM / YY'},
ccv: {element:'#card-ccv', placeholder:'後三碼'}
};
TPDirect.card.setup({ fields, styles:{'input':{'font-size':'16px'}} });
TPDirect.card.onUpdate(update=>{
submitBtn.disabled = !update.canGetPrime;
});
``` :contentReference[oaicite:3]{index=3}
---
## 3 . 取得 Prime 並呼叫 Serverless 後端
```js
checkoutForm.addEventListener('submit', async e=>{
e.preventDefault();
const { status, card } = await TPDirect.card.getPrime();
if (status !== 0) { alert(card.msg); return; }
const res = await fetch('/api/pay', {
method:'POST',
headers:{'Content-Type':'application/json'},
body:JSON.stringify({
prime: card.prime,
amount: window.cartTotal,
details: 'Webflow Order #' + window.orderNo,
buyer: { name, email, phone }
})
});
const result = await res.json();
if(result.status===0) window.location='/thank-you';
else alert('付款失敗:'+result.msg);
});
4 . 範例 Node(Vercel / Netlify Function)
import TapPay from 'tappay-nodejs'; // yarn add tappay-nodejs
TapPay.initialize({ partner_key: process.env.PARTNER_KEY, env:'sandbox' });
export default async (req,res)=>{
const { prime, amount, details, buyer } = req.body;
try{
const result = await TapPay.payByPrime({
prime,
merchant_id: process.env.MERCHANT_ID,
amount,
currency:'TWD',
details,
cardholder:{
phone_number: buyer.phone,
name: buyer.name,
email: buyer.email
}
}); // ← 若要記卡號可加 remember:true
res.status(200).json(result);
}catch(err){
res.status(500).json({status:-1, msg:err.message});
}
};
``` :contentReference[oaicite:4]{index=4}
---
## 5 . 後續:訂單紀錄與發貨
| 需求 | 作法 |
|------|------|
| **把訂單寫回 Webflow** | Webflow API 目前無法「建立」Order,只能用 *ecomm_new_order* Webhook 接收。一般做法是:<br>① 先用 **Free/Manual Order** 流 (價格設 0) 讓 Webflow 產生訂單<br>② 在自製結帳完成後透過 **PATCH Order** API 更新 `financialStatus=paid`。 |
| **自動寄信/開發票** | 在 Serverless 成功付款後:<br>• 用 SendGrid / MailerSend API 寄信<br>• 呼叫 ERP 或 Invoicing API 建立發票 |
| **失敗 & 3-D Secure 回傳** | TapPay 會以 JSON 回應;也可透過其 Webhook 確認補款或退款狀態 |
> Webflow 的 Webhook Payload 範例可參考官方文件:contentReference[oaicite:5]{index=5}.
---
## 6 . 測試清單
1. Sandbox 測試卡號 4000-2211-************ 等。
2. 測試 retry & 失敗流程。
3. 嚴格比對金額單位(TapPay 預設 **TWD**,Webflow 商品可以是 USD)。
4. 核心環境變數放在 Dashboard → *Environment Variables*;切 Production 記得替換 key。
---
## 7 . 附錄:用外部購物車 (Foxy.io) 把 TapPay「包」進 Webflow
* **Foxy.io** 嵌入任何 HTML,並宣稱可連接 100+ Gateways:contentReference[oaicite:6]{index=6};Webflow 社群常用來填補 iDEAL/Klarna 等缺失:contentReference[oaicite:7]{index=7}。
* 若 Foxy 端尚未內建 TapPay,可走 **Hosted Payment Gateway** 流程或讓 Foxy 技術團隊幫你加入 Cherri Tech API。
* 這種方案等於「整組結帳換掉」,優點是少寫程式;缺點是 Webflow 原生庫存、折扣碼、稅金設定要搬家到 Foxy 管理。
---
### 最後一哩提醒
* **PCI-DSS 合規**:TapPay Fields 保證你不落地卡號,但頁面必須 HTTPS、不要在 DevTools console 打印 prime。
* **前後端同時驗金額**,避免 client 被竄改。
* 一次做 MVP:先上 *Sandbox → 0 元訂單 → 手動收款*;跑順後再把收款流程全自動。
照上述步驟,你就能在 Webflow Ecommerce 上跑出「看似原生」的 TapPay 結帳體驗,並保持 Webflow 的設計自由度與 TapPay 在地收款優勢。祝開發順利!
::contentReference[oaicite:8]{index=8}