Shopify Plus 香港八達通支付整合教學:完整實作步驟

Key Takeaways
- Shopify Plus 無原生八達通,須經收單機構 App 或自建 Payments App
- 收單機構 App 最快上線,但無法客製支付流程與會員綁定
- 自建須用 Payments Apps API offsite 模式,並通過 Shopify 審核
- 以 session.id 做冪等鍵,可避免 notify 重送造成重複訂單
- 同一套架構可延伸至 PayNow、LINE Pay、FPX 等亞太支付
Shopify Plus 沒有原生八達通支付。整合方法有兩條:接入已上架的香港收單機構 App(如 AsiaPay、QFPay、Yedpay),或用 Shopify Payments Apps API 自建 offsite 支付 App 串接八達通線上支付。本文逐步示範兩條路線的設定、程式碼與測試流程。
為什麼 Shopify Plus 沒有原生八達通支付?
Shopify Payments 在香港只處理信用卡與部分錢包(Apple Pay、Google Pay)。八達通屬香港金管局監管的儲值支付工具(SVF),收單流程由本地持牌機構處理,而不是 Shopify 的全球收單網絡。根據香港金融管理局公布的儲值支付工具監管資料,SVF 發行人須持牌並受獨立清算規則約束,這令它無法像一般卡組織一樣直接掛進 Shopify Payments。
但市場需求很實在。根據八達通卡有限公司官方網站資料,八達通卡及流動支付帳戶的流通量超過 3,000 萬,日均交易宗數以千萬計——對任何做香港本地零售、餐飲、票務或會員儲值的品牌來說,結帳頁少了八達通,等於主動放棄一批習慣「嘟一嘟」的消費者。
對跨境品牌而言,這是結構性問題而非單一市場問題:你在台灣要接 LINE Pay、在新加坡要接 PayNow、在馬來西亞要接 FPX、在澳洲要接 PayID。香港的八達通只是這套「本地支付矩陣」的其中一塊。本文的做法可以重用到其他市場。
開始前的前置條件
在動手之前,先確認以下項目都到位,否則第三步一定卡住。
- Shopify Plus 方案。Payments App Extension 可在一般方案安裝,但自建 payments app 的測試、checkout 擴充與多店舖部署,實務上要 Plus 才有足夠彈性(Shopify Partner 開發商店亦可先行測試)。
- Shopify Partner 帳號 + Development Store,地區設定為 Hong Kong,預設貨幣 HKD。
- 八達通線上支付商戶合約。可經八達通公司直接申請,或經收單機構(AsiaPay PayDollar、QFPay、Yedpay、Oceanpayment、Global Payments 等)轉介。你需要拿到:Merchant ID、API Key / Secret、Sandbox 與 Production 的 endpoint。
- Shopify CLI 3.x(
npm install -g @shopify/cli@latest)與 Node.js 18 以上。 - 可公開存取的 HTTPS endpoint。開發階段用
shopify app dev的 tunnel,正式環境建議用 Cloudflare Workers、AWS Lambda + API Gateway,或部署在新加坡 / 香港區域的容器,降低 webhook 來回延遲。 - Payments App 須經 Shopify 審批。這點最常被低估:Shopify 要求 payments app 通過獨立審核流程才能在正式商店啟用,審核涵蓋 PCI 範圍、退款與爭議處理、錯誤狀態回報。把審核時間排進專案計劃。
確認版本:
1shopify version2# @shopify/cli 3.x.x3node -v4# v18.x 或以上
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.
路線一:直接安裝收單機構的 Shopify App
如果你的需求是「結帳頁要有八達通」,而不是「我要自己控制支付流程」,先走這條。時間成本通常是幾天而非幾週。
實作步驟
- 與收單機構簽約,確認合約內已開通八達通(O! ePay / 八達通錢包)作為支付方式。不同機構的八達通開通狀態不同,簽約前要白紙黑字寫明。
- 在 Shopify Admin → Settings → Payments → Additional payment methods 搜尋並安裝該機構的 App。
- 填入 Merchant ID 與 API credentials,先切到 Test mode。
- 在 Payment methods 清單啟用八達通,並確認只對
HKD顯示。 - 用 Shopify 的 Bogus Gateway 以外的方式測一筆真實小額交易(多數 HK 收單機構 sandbox 不支援八達通全流程,最終驗證要在 production 做 HKD 1 的測試單後退款)。
這條路線的取捨
好處:免開發、免 Shopify payments app 審核、對帳與爭議由收單機構處理。
代價:支付方式名稱、排序與結帳文案受 App 限制;多店舖(多個 Shopify store 對應同一法人)時,每個 store 要獨立設定;若你要把八達通餘額查詢、會員儲值綁定做進 checkout,這條路做不到。
路線二:用 Payments Apps API 自建 offsite 支付 App
當你需要控制跳轉體驗、把八達通與自家會員系統綁定,或要同一套服務同時供應香港、台灣、新加坡多個 Shopify store 時,自建才划算。
Shopify 的 payments app 有四種類型,八達通屬於 offsite payment:買家在結帳頁選八達通 → 被導向你的服務 → 你再導向八達通 App 或顯示 QR → 完成後回到 Shopify 的 cancel_url 或你 resolve 後的 next_action URL。
步驟 1:建立 app 骨架
1shopify app init --name octopus-payments2cd octopus-payments3shopify app generate extension --type payments_extension
CLI 會問你 extension 子類型,選 Offsite payments。
預期輸出:
1✔ Your extension was created in extensions/octopus-offsite.
步驟 2:設定 payments extension
extensions/octopus-offsite/shopify.extension.toml:
1api_version = "2025-01"23[[extensions]]4name = "Octopus"5handle = "octopus-offsite"6type = "payments_extension"78 [extensions.targeting]9 target = "payments.offsite.render"1011 [extensions.payment_session]12 url = "https://pay.example-hk.com/shopify/payment_sessions"1314 [extensions.refund_session]15 url = "https://pay.example-hk.com/shopify/refund_sessions"1617 [extensions.settings]18 merchant_label = "八達通 Octopus"19 supported_countries = ["HK"]20 supported_payment_methods = ["octopus"]21 test_mode_available = true2223 [[extensions.settings.fields]]24 key = "merchant_id"25 type = "string"26 name = "Octopus Merchant ID"2728 [[extensions.settings.fields]]29 key = "api_secret"30 type = "string"31 name = "API Secret"
欄位名稱會隨 API 版本微調,部署前請對照 shopify.dev 上對應 api_version 的 payments extension 設定文件。
步驟 3:用 paymentsAppConfigure 啟用設定
商戶在 Admin 輸入 Merchant ID 後,你的 app 要呼叫 Payments Apps API 把設定標記為已完成,支付方式才會在結帳頁出現:
1mutation PaymentsAppConfigure($externalHandle: String!, $ready: Boolean!) {2 paymentsAppConfigure(externalHandle: $externalHandle, ready: $ready) {3 paymentsAppConfiguration {4 externalHandle5 ready6 }7 userErrors {8 field9 message10 }11 }12}
Variables:
1{ "externalHandle": "OCTOPUS-MID-88123", "ready": true }
預期回應:
1{2 "data": {3 "paymentsAppConfigure": {4 "paymentsAppConfiguration": { "externalHandle": "OCTOPUS-MID-88123", "ready": true },5 "userErrors": []6 }7 }8}
如果 ready 一直是 false,結帳頁不會顯示八達通——這是最常見的「我明明裝好了但看不到」原因。
步驟 4:接收 payment session 並驗證簽章
Shopify 會 POST 一個 payment session 到你設定的 URL。核心欄位包括 id、gid、group、amount、currency、test、payment_method、customer、cancel_url。
Express 範例(先驗 HMAC,再開八達通交易):
1import express from 'express';2import crypto from 'crypto';34const app = express();5app.use('/shopify', express.raw({ type: 'application/json' }));67function verifyHmac(req) {8 const received = req.get('Shopify-Hmac-Sha256') || '';9 const digest = crypto10 .createHmac('sha256', process.env.SHOPIFY_API_SECRET)11 .update(req.body)12 .digest('base64');13 const a = Buffer.from(received);14 const b = Buffer.from(digest);15 return a.length === b.length && crypto.timingSafeEqual(a, b);16}1718app.post('/shopify/payment_sessions', async (req, res) => {19 if (!verifyHmac(req)) return res.status(401).send('Invalid HMAC');2021 const session = JSON.parse(req.body.toString('utf8'));2223 if (session.currency !== 'HKD') {24 return res.status(422).json({ error: 'OCTOPUS_HKD_ONLY' });25 }2627 // amount 為字串,單位為主貨幣單位;八達通 API 多以「仙」為整數單位28 const amountCents = Math.round(parseFloat(session.amount) * 100);2930 const octopus = await createOctopusOrder({31 merchantId: process.env.OCTOPUS_MID,32 merchantRef: session.id, // 用於對帳與冪等33 amount: amountCents,34 currency: 'HKD',35 returnUrl: `${process.env.BASE_URL}/octopus/return?sid=${session.id}`,36 notifyUrl: `${process.env.BASE_URL}/octopus/notify`,37 isTest: session.test === true,38 });3940 await db.sessions.put({41 shopifySessionId: session.id,42 gid: session.gid,43 group: session.group,44 cancelUrl: session.cancel_url,45 octopusOrderId: octopus.orderId,46 status: 'PENDING',47 });4849 // 回 201 並帶 redirect_url,Shopify 會把買家導過去50 return res.status(201).json({ redirect_url: octopus.paymentUrl });51});
重點:
- 一定要用
session.id作為你向八達通送出的 merchant reference。Shopify 在網絡重試時會用同一個id重送,沒有冪等就會重複開單。 session.test === true時必須走 sandbox,絕不可碰真實資金。- 回應必須在 Shopify 的 timeout 內送出;重 I/O 工作丟去 queue。
步驟 5:在八達通回呼後 resolve 或 reject
八達通的 server-to-server notify 到達後,驗證其簽章,再呼叫 Shopify:
1app.post('/octopus/notify', express.urlencoded({ extended: false }), async (req, res) => {2 if (!verifyOctopusSignature(req.body)) return res.status(400).end();34 const record = await db.sessions.getByOctopusOrder(req.body.orderId);5 if (!record || record.status !== 'PENDING') return res.status(200).end(); // 冪等67 if (req.body.status === 'SUCCESS') {8 await shopifyGraphQL(RESOLVE_MUTATION, { id: record.gid });9 await db.sessions.setStatus(record.shopifySessionId, 'RESOLVED');10 } else {11 await shopifyGraphQL(REJECT_MUTATION, {12 id: record.gid,13 reason: { code: 'PROCESSING_ERROR', merchantMessage: req.body.errorMessage },14 });15 await db.sessions.setStatus(record.shopifySessionId, 'REJECTED');16 }17 res.status(200).end();18});
Resolve mutation:
1mutation PaymentSessionResolve($id: ID!) {2 paymentSessionResolve(id: $id) {3 paymentSession {4 id5 status { code }6 nextAction {7 action8 context { ... on PaymentSessionActionsRedirect { redirectUrl } }9 }10 }11 userErrors { field message }12 }13}
預期回應會回傳 status.code: "RESOLVED" 與一個 redirectUrl——把買家導向這個 URL,他們就會落在 Shopify 的 thank-you 頁。不要自己拼湊 thank-you URL,一定要用回傳的 redirectUrl。
Reject mutation:
1mutation PaymentSessionReject($id: ID!, $reason: PaymentSessionRejectionReasonInput!) {2 paymentSessionReject(id: $id, reason: $reason) {3 paymentSession { id status { code } }4 userErrors { field message }5 }6}
步驟 6:處理退款
商戶在 Shopify Admin 按退款時,Shopify POST 到你的 refund_session.url,payload 含 id、gid、payment_id、amount、currency。流程與 payment session 對稱:
1mutation RefundSessionResolve($id: ID!) {2 refundSessionResolve(id: $id) {3 refundSession { id status { code } }4 userErrors { field message }5 }6}
八達通的退款規則與信用卡不同——部分商戶合約下退款須退回原八達通帳戶,且有時效與金額限制。把這些限制寫進你的 refundSessionReject 理由碼,不要讓客服在 Admin 面對一個沒有說明的失敗。
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.
八達通端的買家流程長什麼樣?
桌面與手機的體驗不同,UI 要分開處理:
- 手機瀏覽器:用 deep link 直接喚起八達通 App,付款後由 App 導回你的
returnUrl。要處理「用戶沒裝 App」的 fallback。 - 桌面瀏覽器:顯示 QR code,同頁以 polling 或 SSE 查交易狀態。建議每 2–3 秒 poll 一次,上限 5 分鐘後顯示「交易逾時,請返回重試」並導回
cancel_url。 - 實體卡:線上流程處理的是八達通流動支付帳戶,不是實體卡拍卡。實體卡要走 Shopify POS 搭配支援八達通的收款終端(如 QFPay、Soepay 的裝置),屬另一條整合線。
寫 polling 時記得:前端狀態只用於 UI,真正的 resolve 永遠以 server-to-server notify 為準。曾有一個香港多品牌餐飲集團的 Shopify Plus 專案,因為前端 polling 搶在 notify 之前就跳 thank-you 頁,導致訂單短暫顯示未付款,客服每天要手動核對。把前端定位成「只負責畫面」,這類問題就消失了。
測試清單與預期輸出
開發 tunnel 啟動:
1shopify app dev2# Preview URL: https://xxxx.trycloudflare.com
逐項跑完以下情境,每項都要同時檢查 Shopify 訂單狀態與你資料庫的 session 狀態:
- 成功付款 → 訂單
financial_status=paid,session =RESOLVED。 - 買家中途取消 → 呼叫
paymentSessionReject,買家回到cancel_url,Shopify 購物車保留。 - 八達通餘額不足 → reject 理由碼
PROCESSING_ERROR,結帳頁顯示可讀錯誤訊息(中英雙語)。 - notify 重送三次 → 只建立一筆訂單,驗證冪等。
- 部分退款 HKD 50 / 總額 HKD 200 → Shopify 顯示 partially refunded。
- 全額退款 →
refundSessionResolve回RESOLVED。 - Test mode 開啟時 → 絕無真實扣款紀錄。
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.
常見錯誤排查
結帳頁看不到八達通選項
依序檢查:商店地區是否 Hong Kong;購物車貨幣是否 HKD(Shopify Markets 多貨幣下,買家若以 USD 結帳,supported_countries = ["HK"] 的支付方式不會出現);paymentsAppConfigure 的 ready 是否為 true;payment method 在 Admin 是否已 Activate。
401 Invalid HMAC
九成是因為在驗證前就把 body 解析成 JSON 了。必須用 raw body 計算 HMAC。另一個原因是用錯 secret——payments app 用的是 app 的 API secret,不是 webhook signing key。
付款成功但訂單仍顯示 pending
代表你沒有呼叫 paymentSessionResolve,或 mutation 回了 userErrors。把每次 mutation 的完整回應記入 log;userErrors 不會讓 HTTP 狀態碼變成非 200,靜默失敗很容易被忽略。
重複訂單
沒有以 session.id 做冪等鍵。補上唯一索引,重送時直接回傳上一次的 redirect_url。
金額對不上
Shopify 的 amount 是字串、以元為單位(如 "199.00"),八達通多數 API 以仙為整數單位。用 Math.round(parseFloat(amount) * 100),不要用浮點數直接相乘後 truncate。
Checkout Extensibility 下的客製失效
若你同時在用 checkout UI extension 調整支付區塊文案,注意 checkout.liquid 的客製在 Checkout Extensibility 架構下不再生效。文案改動應透過 Checkout Editor 或 UI extension 處理。
把這套做法延伸到其他亞太市場
一旦你建好了 offsite payments app 的骨架,加新市場的邊際成本就低很多。同一個 service 可以用不同 extension handle 對應:
- 台灣:LINE Pay、街口支付、ATM 虛擬帳號(需處理 T+N 入帳的 pending 狀態)
- 新加坡:PayNow QR(UEN 收款),流程與八達通 QR 幾乎一致
- 馬來西亞:FPX 網銀轉帳,須處理銀行端維護時段
- 澳洲 / 紐西蘭:PayID、BPAY,多以 offsite redirect 模式
對美國、英國或歐洲總部的品牌來說,這正是把香港或新加坡設為亞太數碼營運樞紐的實際價值:本地支付知識、監管理解、與收單機構的直接關係,都不是遠端靠文件就能補足的。再加上時區覆蓋——香港與台灣團隊在歐洲下班後仍能處理結帳線上的 incident——營運連續性比單純外包便宜更有意義。
至於 AI 的角色,務實的用法是:用 LLM 產生 payment session 的測試 fixture、把多語系錯誤訊息一次生成繁中/簡中/英文三版、以及從 log 中自動歸類 reject 理由碼的分佈。真正的資金流判斷,仍然要由確定性的程式碼處理。
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.
上線前檢查表
- Production credentials 與 sandbox 分離,存於 secret manager 而非環境變數檔。
- 所有 mutation 回應(含
userErrors)皆入 log,並保留至少 90 天以供對帳。 - 設定 session 逾時清理 job:超過 15 分鐘仍 PENDING 的,主動查單後 resolve 或 reject。
- 退款政策、八達通退款限制已寫入商店條款與客服 SOP。
- 已完成 Shopify payments app 審核,並在一個真實 HKD 小額訂單上驗證全流程後退款。
- 監控:notify endpoint 的 5xx 率、resolve 平均延遲、pending session 數量,三項設告警。
做到這裡,八達通就不再是結帳頁上的缺口,而是一條可監控、可對帳、可複製到其他市場的支付通道。
如果你正在規劃 Shopify Plus 的香港或亞太支付整合,Branch8 在香港、新加坡、台灣與澳洲都有交付團隊,熟悉本地收單機構合約與 Payments Apps API 的審核流程——歡迎與我們談談你的整合藍圖。
Sources
FAQ
不可以。Shopify Payments 在香港只支援信用卡與 Apple Pay、Google Pay 等錢包,八達通屬香港金管局監管的儲值支付工具,須經本地持牌收單機構處理。你必須安裝第三方收單機構的 Shopify App,或自建 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.