Branch8

香港 Shopify Plus 轉數快 FPS 支付整合教學 2026

Matt Li
September 17, 2026
13 mins read
香港 Shopify Plus 轉數快 FPS 支付整合教學 2026

Key Takeaways

  • FPS 整合走 Shopify Payments App Extension 的 offsite 流程最可控
  • 以 payment session id 作 idempotency key,避免重複開單
  • QR 必須用動態碼(Payload 01=12),金額不可被客人修改
  • Webhook 不可靠,務必加輪詢對帳 job 補償漏單
  • EMVCo 結構共通,香港做法可延伸至 PayNow、PromptPay、QRIS

在 Shopify Plus 上整合轉數快(FPS),實務做法是透過支援 FPS 的香港收單機構,配合 Shopify Payments App Extension 建立 offsite 付款流程:商戶開立 FPS 收款帳戶、部署 payment session 伺服器、生成 EMVCo QR,再以 webhook 回寫訂單狀態。

為什麼 2026 年要把 FPS 放進 Shopify Plus 結帳頁?

轉數快(Faster Payment System,FPS)由香港金融管理局與香港銀行同業結算有限公司(HKICL)於 2018 年推出,支援 7x24 即時港元及人民幣轉帳。根據香港金融管理局公布的零售支付統計,FPS 的登記用戶數已突破一千五百萬,日均交易量以百萬宗計,並持續按年增長——對一個人口約 750 萬的市場而言,這代表滲透率已接近飽和。

對電商營運團隊,FPS 的吸引力在三點:

  1. 手續費結構不同於信用卡。信用卡收單費率通常按交易金額百分比計算;FPS 在多數香港收單機構屬於帳戶對帳戶轉帳,費率結構偏低或採固定費用(實際條款須向收單機構確認)。對客單價高的品類(珠寶、家電、B2B 補貨單)差異明顯。
  2. 無 chargeback 風險。FPS 為推送式付款(push payment),消費者主動發起,不存在信用卡的拒付爭議機制。代價是退款必須由商戶主動處理,你的後台要能發起退款。
  3. 跨境延伸性。FPS 的 EMVCo QR 規格與新加坡 PayNow、泰國 PromptPay、馬來西亞 DuitNow、印尼 QRIS 屬同一族系。香港金管局與泰國央行已推出 FPS–PromptPay 跨境二維碼互通。一套為 FPS 寫好的 QR 生成與對帳邏輯,遷移到其他東南亞市場的改動成本遠低於重寫。

這也是為什麼不少歐美品牌把亞太支付整合的工程基地放在香港或新加坡:一個團隊同時理解 HKMA、MAS 的監管節奏,與 EMVCo 規格的共通點。

開始前需要準備什麼?

帳戶與資格

  • Shopify Plus 方案。Payments App Extension 可在一般方案使用,但要搭配 Payment Customization Function(按條件顯示/隱藏付款方式)與 Checkout UI Extension 的完整能力,需要 Plus。
  • 香港商業登記與銀行商戶帳戶。FPS 收款需要企業帳戶,個人 FPS ID 不適用於規模化電商對帳。
  • 支援 FPS 的收單機構或支付服務商(PSP)。香港市場常見選項包括 Airwallex、Yedpay、Global Payments Asia-Pacific、PayMe for Business(匯豐),以及部分銀行自家的商戶收款 API。務必在合約階段確認三件事:是否提供 動態 QR(每筆訂單獨立金額與 reference)、是否提供 即時 webhook、是否支援 API 發起退款。只提供靜態收款碼的方案無法自動對帳,不要選。
  • Shopify Partner 帳戶,用於建立 custom app 並部署 payments extension。

本機開發環境

1# Node.js 20 LTS 以上
2node -v
3# v20.11.0
4
5npm install -g @shopify/cli@latest
6shopify version
7# 3.x

技術前提

你需要一台可公開存取的 HTTPS 伺服器(Payment Session URL 必須是公網 HTTPS,不接受自簽憑證)。開發階段可用 Cloudflare Tunnel 或 Shopify CLI 內建的 tunnel。

Ready to Transform Your Ecommerce Operations?

Branch8 specializes in ecommerce platform implementation and AI-powered automation solutions. Contact us today to discuss your ecommerce automation strategy.

三種整合路線,先選對再動工

路線 A:PSP 現成 Shopify App(最快)

若你選的 PSP 已在 Shopify App Store 上架官方 payments app,安裝後在 Settings → Payments → Add payment methods 直接啟用即可,工程量接近零。限制是結帳頁文案、QR 呈現方式、對帳欄位都由 PSP 決定,你無法自訂 reference 格式。

適合:單一香港店、訂單量中等、不需要把 FPS reference 寫進 ERP 的品牌。

路線 B:自建 Payments App Extension(最可控)

你以 Shopify 的 Payments Apps API 自建一個 offsite payment app,中介到 PSP 的 API。你完全控制 payment session 的建立、QR 頁面、reference 編碼、逾時策略與退款流程。

適合:多店(HK/SG/TW)、需要與 NetSuite、SAP 或自建 OMS 對帳、有 B2B 大額訂單的品牌。本文其餘部分以此路線為主。

路線 C:手動轉帳 + 離線付款(不建議做為長期方案)

用 Shopify 的 Manual Payment Method 顯示靜態 FPS ID,客人自行轉帳後上傳截圖。實作成本最低,但每張訂單都要人手核對,錯漏率高,且無法處理部分付款。只建議作為 PSP 上線前的兩週過渡。

步驟一:建立 Payments App 專案骨架

1npm init @shopify/app@latest -- --template=payments_app
2cd fps-payments-app
3shopify app generate extension --template=payments_offsite --name="fps-hk"

產生的 extensions/fps-hk/shopify.extension.toml 大致如下(欄位名稱請以你安裝的 CLI 版本與 shopify.dev Payments Apps 文件為準):

1api_version = "2026-01"
2
3[[extensions]]
4name = "FPS (轉數快)"
5type = "payments_extension"
6handle = "fps-hk"
7
8 [extensions.payment_session]
9 url = "https://pay.yourdomain.com/fps/sessions"
10
11 [extensions.refund_session]
12 url = "https://pay.yourdomain.com/fps/refunds"
13
14 [extensions.void_session]
15 url = "https://pay.yourdomain.com/fps/voids"
16
17 [extensions.targeting]
18 merchant_label = "轉數快 FPS"
19 supported_countries = ["HK"]
20 supported_payment_methods = ["offsite"]
21 supports_deferred_payments = false
22 supports_installments = false
23 test_mode_available = true
24
25 [[extensions.targeting.merchant_label_translations]]
26 locale = "zh-TW"
27 label = "轉數快 FPS"
28
29 [[extensions.targeting.merchant_label_translations]]
30 locale = "en"
31 label = "FPS (Faster Payment System)"

部署:

1shopify app deploy
2# ✔ Deployed to Shopify
3# Extension: fps-hk (payments_extension) — version 1

部署後,到目標商店的 Settings → Payments → Add payment methods,你的 app 會出現在清單中。

Ready to Transform Your Ecommerce Operations?

Branch8 specializes in ecommerce platform implementation and AI-powered automation solutions. Contact us today to discuss your ecommerce automation strategy.

步驟二:實作 Payment Session 端點

Shopify 會在客人於結帳頁選擇 FPS 並按下付款時,POST 一個 payment session 到你的 payment_session.url。你的任務是:驗證簽章、向 PSP 建單、回傳一個 redirect URL。

驗證 HMAC

1// verify.js
2import crypto from 'node:crypto';
3
4export function verifyShopifyHmac(rawBody, hmacHeader, secret) {
5 const digest = crypto
6 .createHmac('sha256', secret)
7 .update(rawBody, 'utf8')
8 .digest('base64');
9
10 const a = Buffer.from(digest);
11 const b = Buffer.from(hmacHeader || '');
12 return a.length === b.length && crypto.timingSafeEqual(a, b);
13}

注意:必須使用 原始 request body,不要先經過 JSON parser,否則空白與鍵序差異會導致驗證永遠失敗。這是最常見的第一天卡關點。

處理 session 請求

1// server.js (Express 5)
2import express from 'express';
3import { verifyShopifyHmac } from './verify.js';
4import { createFpsOrder } from './psp.js';
5
6const app = express();
7app.use('/fps', express.raw({ type: 'application/json' }));
8
9app.post('/fps/sessions', async (req, res) => {
10 const raw = req.body.toString('utf8');
11 if (!verifyShopifyHmac(raw, req.get('X-Shopify-Hmac-Sha256'), process.env.APP_SECRET)) {
12 return res.status(401).send('Invalid HMAC');
13 }
14
15 const s = JSON.parse(raw);
16 // s.id, s.gid, s.amount, s.currency, s.test, s.group,
17 // s.payment_method, s.customer, s.proposed_at
18
19 if (s.currency !== 'HKD') {
20 return res.status(200).json({ error: 'UNSUPPORTED_CURRENCY' });
21 }
22
23 // reference 建議編碼店碼 + 訂單號,長度 <= 25,只用英數
24 const reference = `HK${s.group.replace(/[^A-Za-z0-9]/g, '').slice(-14)}`;
25
26 const pspOrder = await createFpsOrder({
27 amount: s.amount, // 字串,如 "1280.00"
28 currency: s.currency,
29 reference,
30 idempotencyKey: s.id, // 極重要:Shopify 會重試
31 expiresInSeconds: 900,
32 testMode: Boolean(s.test),
33 });
34
35 await db.sessions.put({
36 shopifySessionId: s.id,
37 shopifyGid: s.gid,
38 pspOrderId: pspOrder.id,
39 reference,
40 amount: s.amount,
41 status: 'PENDING',
42 });
43
44 return res.status(201).json({
45 redirect_url: `https://pay.yourdomain.com/fps/qr/${pspOrder.id}`,
46 });
47});

預期輸出:Shopify 收到 redirect_url 後,把客人導向你的 QR 頁面。若你回傳 error,結帳頁會顯示失敗並讓客人改選其他付款方式。

冪等性不是可選項

Shopify 在網路逾時後會以相同 id 重送 payment session。若你每次都向 PSP 開新單,同一張訂單會產生多個 QR,客人付其中一個,其餘掛在對帳系統變成孤兒。以 s.id 作為 PSP 的 idempotency key,並在自家 DB 加上 unique index。

步驟三:生成合規的 FPS EMVCo QR

多數 PSP 會直接回傳 QR payload 或圖片;但若你需要自行渲染(例如要把 QR 嵌進自訂的付款頁、或加上品牌框),需要理解 EMVCo Merchant-Presented Mode 的 TLV 結構。

1// emv.js — 僅示範結構與 CRC;實際 Merchant ID / FPS ID 由收單機構提供
2function tlv(id, value) {
3 return id + String(value.length).padStart(2, '0') + value;
4}
5
6function crc16(payload) {
7 let crc = 0xFFFF;
8 for (const byte of Buffer.from(payload, 'utf8')) {
9 crc ^= byte << 8;
10 for (let i = 0; i < 8; i++) {
11 crc = (crc & 0x8000) ? ((crc << 1) ^ 0x1021) & 0xFFFF : (crc << 1) & 0xFFFF;
12 }
13 }
14 return crc.toString(16).toUpperCase().padStart(4, '0');
15}
16
17export function buildFpsQr({ fpsId, merchantName, city, amount, reference }) {
18 let p = '';
19 p += tlv('00', '01'); // Payload Format Indicator
20 p += tlv('01', '12'); // 12 = dynamic QR(每筆唯一)
21 p += tlv('26', tlv('00', 'hk.com.hkicl') + tlv('02', fpsId));
22 p += tlv('52', '0000'); // MCC,依收單機構指示
23 p += tlv('53', '344'); // HKD = 344 (ISO 4217)
24 p += tlv('54', amount); // 動態金額
25 p += tlv('58', 'HK');
26 p += tlv('59', merchantName.slice(0, 25));
27 p += tlv('60', city.slice(0, 15));
28 p += tlv('62', tlv('05', reference)); // Bill Number / Reference
29 p += '6304';
30 return p + crc16(p);
31}

預期輸出(截短示意):

1000201010212262400121hk.com.hkicl0210123456789520400005303344540
271280.005802HK5910BRAND LTD6009Hong Kong62110507HK000123 6304A1B2

把這串丟進任何 QR 產生器渲染即可。驗證方式:用支援 FPS 的銀行 app 掃描,金額與 reference 應自動帶入且不可編輯(01 = 12 動態碼的關鍵)。若金額欄位可被客人修改,代表你送出了靜態碼,對帳會失控。

Ready to Transform Your Ecommerce Operations?

Branch8 specializes in ecommerce platform implementation and AI-powered automation solutions. Contact us today to discuss your ecommerce automation strategy.

步驟四:以 Webhook 回寫 Shopify 訂單狀態

客人完成轉帳後,PSP 推送 webhook 到你的伺服器。你要做的是呼叫 Shopify 的 Payments Apps API,把 session resolve 掉。

1app.post('/fps/psp-webhook', async (req, res) => {
2 const evt = JSON.parse(req.body.toString('utf8'));
3 if (!verifyPspSignature(req)) return res.sendStatus(401);
4
5 const session = await db.sessions.getByPspOrderId(evt.orderId);
6 if (!session) return res.sendStatus(404);
7 if (session.status === 'RESOLVED') return res.sendStatus(200); // 重送保護
8
9 if (evt.status === 'PAID' && evt.amount === session.amount) {
10 await shopifyGraphQL(session.shop, `
11 mutation Resolve($id: ID!) {
12 paymentSessionResolve(id: $id) {
13 paymentSession { id status { code } }
14 userErrors { field message }
15 }
16 }`, { id: session.shopifyGid });
17
18 await db.sessions.update(session.shopifySessionId, { status: 'RESOLVED' });
19 }
20
21 if (evt.status === 'EXPIRED' || evt.status === 'FAILED') {
22 await shopifyGraphQL(session.shop, `
23 mutation Reject($id: ID!, $reason: PaymentSessionRejectionReasonInput!) {
24 paymentSessionReject(id: $id, reason: $reason) {
25 paymentSession { id status { code } }
26 userErrors { field message }
27 }
28 }`, {
29 id: session.shopifyGid,
30 reason: { code: 'PROCESSING_ERROR', merchantMessage: evt.status },
31 });
32 }
33
34 res.sendStatus(200);
35});

金額比對是防詐核心。FPS 是推送式付款,理論上客人可以掃了 QR 之後手動改金額(若 QR 被誤設為靜態)。永遠比對 evt.amount === session.amount,不相等時 reject 並轉人工處理,不要自動放行。

退款

1app.post('/fps/refunds', async (req, res) => {
2 const r = JSON.parse(req.body.toString('utf8'));
3 const payout = await psp.createFpsPayout({
4 originalOrderId: r.payment_id,
5 amount: r.amount,
6 idempotencyKey: r.id,
7 });
8 // 非同步:PSP 完成出金後再 resolve
9 await db.refunds.put({ id: r.id, gid: r.gid, payoutId: payout.id });
10 return res.sendStatus(201);
11});

FPS 退款通常是一筆反向轉帳,需要收款人的 FPS ID 或銀行帳號。多數 PSP 要求商戶提供對方帳戶資料,或只允許退回原付款帳戶。設計 UI 時,客服後台要能擷取原始付款人識別碼。

步驟五:用 Payment Customization 控制顯示邏輯

Shopify Plus 的 Payment Customization Function 讓你以規則決定 FPS 是否出現。典型場景:只在 HKD 貨幣、且訂單金額低於單筆轉帳上限時顯示。

1// extensions/payment-rules/src/run.js
2export function run(input) {
3 const fps = input.paymentMethods.find(m => m.name.includes('轉數快'));
4 if (!fps) return { operations: [] };
5
6 const total = parseFloat(input.cart.cost.totalAmount.amount);
7 const currency = input.cart.cost.totalAmount.currencyCode;
8
9 if (currency !== 'HKD' || total > 50000) {
10 return { operations: [{ hide: { paymentMethodId: fps.id } }] };
11 }
12 return { operations: [] };
13}
1shopify app function build
2shopify app deploy

單筆上限視客人所屬銀行的 FPS 設定而定,並非全市場統一;把門檻做成 metafield 而非硬編碼,方便營運團隊調整。

Ready to Transform Your Ecommerce Operations?

Branch8 specializes in ecommerce platform implementation and AI-powered automation solutions. Contact us today to discuss your ecommerce automation strategy.

測試清單:上線前必跑的九項

  1. 沙盒模式下完成一次成功付款,確認 Shopify 訂單狀態由 PendingPaid
  2. 掃碼後不付款,等待 QR 逾時(建議 15 分鐘),確認 session 被 reject 且庫存釋放。
  3. 重送同一個 payment session id,確認 PSP 不會開出第二張單。
  4. PSP webhook 重送三次,確認 Shopify 訂單不會重複標記。
  5. 付款金額少於訂單金額(部分付款),確認系統攔截並轉人工。
  6. 發起全額退款與部分退款各一次。
  7. 在 iOS Safari 與 Android Chrome 各測一次 redirect 流程——FPS 付款常發生在手機上,客人會跳去銀行 app 再跳回來,回跳路徑最容易斷。
  8. 關閉手機網路後恢復,確認 QR 頁面能以輪詢或 SSE 更新為「已付款」。
  9. zh-Hantzh-Hansen 三種 locale 檢查結帳頁與 QR 頁文案。

常見錯誤與排解

Invalid HMAC 持續出現

九成是 body 已被 middleware 解析。確認 express.raw() 掛在 payments 路由之前,且沒有全域 express.json() 搶先處理。

結帳頁看不到 FPS 選項

依序檢查:extension 是否已 shopify app deploy;商店是否已在 Settings → Payments 啟用該 app;supported_countries 是否含 HK;Payment Customization Function 是否誤把它隱藏;商店貨幣是否為 HKD。

客人說已付款但訂單仍是 Pending

先查你的 DB 有無收到 PSP webhook。若沒有,多半是 webhook URL 未白名單或簽章驗證失敗(檢查 4xx 日誌)。務必實作一個 對帳 job:每 5 分鐘輪詢 PSP 查詢 30 分鐘內仍 PENDING 的訂單,以輪詢結果補償 webhook 遺失。只靠 webhook 的系統在生產環境一定會漏單。

QR 掃描後銀行 app 顯示「無效二維碼」

先驗 CRC。CRC16-CCITT 必須涵蓋包含 6304 在內的整串 payload,但不含 CRC 值本身。其次確認 TLV 長度欄位是兩位數補零,且中文商戶名稱未寫入 59 欄位(該欄位規格上為 ASCII)。

Reference 在對帳檔中被截斷

不同銀行對 62 欄位的長度處理不一致。把 reference 控制在 15 個英數字元以內最安全,並在自家 DB 保留完整映射,不要依賴銀行原樣回傳。

Ready to Transform Your Ecommerce Operations?

Branch8 specializes in ecommerce platform implementation and AI-powered automation solutions. Contact us today to discuss your ecommerce automation strategy.

把香港整合延伸到東南亞

如果你的路線圖包含新加坡、馬來西亞、泰國或印尼,現在的架構決定了明年的工作量。EMVCo QR 在各市場的差異主要落在三處:

  • Merchant Account Information 的 template ID 與 GUID:FPS 用 hk.com.hkicl;PayNow、DuitNow、PromptPay、QRIS 各有自己的標識與欄位規則。把 buildFpsQr 重構成 buildQr(scheme, params),以 scheme 設定檔驅動。
  • 貨幣與金額格式:ISO 4217 數字碼(HKD 344、SGD 702、THB 764、MYR 458、IDR 360),小數位數也不同(IDR 通常為 0)。
  • 監管與 KYC:每個市場的收單機構要求不同的公司實體證明。Shopify Markets 可以讓你以一個 store 覆蓋多市場,但收款主體往往需要當地實體或當地 PSP 合作。

Branch8 曾為一家在香港與台灣同時營運的多品牌零售集團處理 Shopify Plus 的多市場付款層,做法是把 QR scheme 抽象為設定檔、把對帳邏輯集中在單一 reconciliation service,而不是每個市場複製一份 app。這個取捨的代價是初期工程較重;回報是新增市場時只需新增 scheme 設定與 PSP adapter,不必重測整條結帳流程。

用 LLM 加速對帳與客服,而非取代控制邏輯

2026 年值得做、也容易做過頭的一塊是自動化。務實的切法:

  • 可以交給 LLM:把銀行對帳單 CSV/PDF 的欄位映射成標準格式、將客人上傳的轉帳截圖做 OCR 後抽取金額與時間、根據異常型態生成客服回覆草稿、把重複的對帳例外歸類成待處理批次。
  • 不要交給 LLM:金額比對、付款狀態判定、退款觸發。這些必須是決定論的程式邏輯,有明確的審計軌跡。

一個合理的分工是:LLM 負責把非結構化輸入轉成結構化資料,規則引擎負責決策。跨時區團隊尤其受益——香港與新加坡團隊收工後,澳洲團隊接手的對帳例外清單已經被分類與摘要好,交接成本大幅下降。

Ready to Transform Your Ecommerce Operations?

Branch8 specializes in ecommerce platform implementation and AI-powered automation solutions. Contact us today to discuss your ecommerce automation strategy.

上線後的營運檢查點

  • 每日:孤兒 session(PENDING 超過 1 小時)數量、webhook 失敗率。
  • 每週:FPS 佔總交易筆數與金額的比例、平均掃碼到完成付款的時間、逾時放棄率。
  • 每月:FPS 與信用卡的實際成本對比(含退款人力)、各銀行 app 的回跳成功率。

逾時放棄率若持續偏高,通常不是技術問題,而是 QR 頁面沒有告訴客人「請切換到銀行 app 掃描,完成後回到此頁」。加一段三步驟圖文說明,往往比調整 timeout 有效。

如果你正在規劃香港或亞太多市場的 Shopify Plus 支付架構,Branch8 的香港、新加坡與台灣團隊可以協助評估 PSP 選型、建置 Payments App Extension,並設計跨市場的對帳流程——歡迎聯絡我們討論你的整合藍圖。

Sources

FAQ

截至目前,Shopify Payments 在香港主要提供信用卡與部分錢包收單,並未原生提供 FPS。要在結帳頁加入 FPS,需透過支援 FPS 的香港收單機構或 PSP,搭配 Shopify 的 Payments App Extension 或第三方 payments app 實作。

About the Author

Matt Li

Co-Founder & CEO, Branch8 & Second Talent

Matt Li is Co-Founder and CEO of Branch8, a Y Combinator-backed (S15) Adobe Solution Partner and e-commerce consultancy headquartered in Hong Kong, and Co-Founder of Second Talent, a global tech hiring platform ranked #1 in Global Hiring on G2. With 12 years of experience in e-commerce strategy, platform implementation, and digital operations, he has led delivery of Adobe Commerce Cloud projects for enterprise clients including Chow Sang Sang, HomePlus (HKBN), Maxim's, Hong Kong International Airport, Hotai/Toyota, and Evisu. Prior to founding Branch8, Matt served as Vice President of Mid-Market Enterprises at HSBC. He serves as Vice Chairman of the Hong Kong E-Commerce Business Association (HKEBA). A self-taught software engineer, Matt graduated from the University of Toronto with a Bachelor of Commerce in Finance and Economics.