Skip to content

Repository files navigation

payment-tw

CI npm npm ecpay types: TypeScript license: MIT Node.js

統一的台灣金流 SDK。一套與供應商無關的 PaymentProvider 介面,搭配多家閘道轉接器 —— 在 PAYUNi、藍新、綠界等之間切換,不需重寫商業邏輯。

抽象是照類別切的:付款閘道共用 PaymentProvider,核貸型的無卡分期(BNPL)則有自己的一組介面 —— 見 BNPL:另一種形狀

對齊 @paid-tw/einvoice 的 monorepo 形狀:core + per-provider packages。

套件

套件 角色
@paid-tw/payment core:型別、PaymentProvider、capabilities、PaymentErrorMockProvider
@paid-tw/payment-ecpay ECPay 綠界 — 四條產品線、四個 factory(見下)
@paid-tw/payment-payuni PAYUNi 統一金流 — 目前只有 trade query;create / refund 會丟 UNSUPPORTED
@paid-tw/payment-newebpay NewebPay 藍新 — MPG 幕前支付 + 信用卡定期定額,兩個 factory(見下)
@paid-tw/payment-zingala 中租零卡分期 — 無卡分期(BNPL):核貸流程,另一組介面(見下)

只需安裝你會用到的供應商。core 永不依賴 adapters;由 CLI / app compose。

pnpm add @paid-tw/payment @paid-tw/payment-ecpay

使用方式

import { createEcpayProvider } from "@paid-tw/payment-ecpay";

const payments = createEcpayProvider({
  merchantId: process.env.ECPAY_MERCHANT_ID!,
  hashKey: process.env.ECPAY_HASH_KEY!,
  hashIv: process.env.ECPAY_HASH_IV!,
  sandbox: true,
});

// create 回傳導轉表單(非已付款)
const form = await payments.createPayment({
  amount: 1000,
  currency: "TWD",
  method: "card",
  orderId: "ORDER123",
  notifyUrl: "https://example.com/notify",
});

const data = await payments.getPayment({ merTradeNo: "ORDER123" });

綠界:四條產品線,一個套件

綠界不是一套 API,而是四套;同一個 npm 套件、四個 factory、四個 name

Factory 產品線 create 結果
createEcpayProvider AIO 全方位金流(導轉) redirect form
createEcpayEcpgProvider 站內付 2.0 (ECPG) token
createEcpayPayCodeProvider 非信用卡幕後取號 虛擬帳號/繳費代碼/條碼
createEcpayBackAuthProvider 信用卡幕後授權 ⚠️ 收 raw PAN 3dsauthorized(含定期定額)

⚠️ createEcpayBackAuthProvider@paid-tw/payment-ecpay/backauth 這個 subpath,不在套件根目錄。它是唯一會收到完整卡號的 adapter,獨立 subpath 讓應用可以用 import graph 機械地證明自己沒有把 raw-PAN 介面打包進去。

藍新:兩條產品線,一個套件

藍新照綠界的前例拆 factory:MPG 幕前支付與信用卡定期定額共用商店金鑰,但端點、加密信封(MPG 有 TradeSha,定期定額沒有——解密成功就是唯一的完整性檢查)、回應形狀、錯誤碼表都不同:

Factory 產品線 create 結果
createNewebpayProvider MPG 幕前支付(導轉) redirect form(僅限瀏覽器 form post)
createNewebpayPeriodProvider 信用卡定期定額 redirect form(藍新代管刷卡頁)

定期定額不經手卡號,所以兩個 factory 都在套件根目錄(不像綠界 backauth 需要 subpath)。每期扣款的退款走 MPG factory 的 refundPayment,用 N050 通知裡的該期 TradeNo

BNPL:另一種形狀

@paid-tw/payment-zingala(中租零卡分期)不實作 PaymentProvider,因為它的流程不一樣:刷卡是當下授權,無卡分期是核貸——送出申請後有審核、可能婉拒,核准後還要等撥款。status 只有 paid / unpaid 的話,「審核中」和「已核准未撥款」沒地方放。

所以它的方法名照核貸流程走(applyInstallment 開的是一份信用申請),而且多了其他 adapter 都沒有的一件事:中租會反過來呼叫你(通知審核結果、詢問訂單是否仍有效)。用法見 packages/payment-zingala/README.md

跨供應商的共用 BNPL 介面(中租/AFTEE/oppay)是目標但還沒定 —— 目前只有一家有實錄資料,只憑一家推出來的抽象就只是這一家換個名字。

開發

pnpm install
pnpm build
pnpm test          # 離線(MSW),CI 預設
pnpm typecheck     # 含測試檔
pnpm lint
pnpm format

Live 測試

每個 adapter 都有一組 env-gated 的 live 測試,打真實 sandbox;平常的 pnpm test 不會跑到。

pnpm test:live:ecpay           # AIO
pnpm test:live:ecpay:paycode   # 幕後取號
pnpm test:live:ecpay:backauth  # 幕後授權
pnpm test:live:ecpay:credit    # 信用卡查詢
pnpm test:live:ecpay:period    # 定期定額 ⚠️ 會真的扣款,見套件 README
pnpm test:live:zingala         # 中租零卡分期 UAT
pnpm test:live:newebpay        # 藍新 MPG ⚠️ 每次消耗一次查無交易額度(TRA10071 四小時鎖)
pnpm test:live:newebpay:period # 藍新定期定額

憑證命名見 .env.example⚠️ 這個 repo 沒有 dotenv,.env 放了不會自動生效 —— 用 set -a; source .env; set +a

綠界公布了明碼的測試特店(例如模擬 3D 的 3002607、3D 關閉的 2000132),所以 ECPay 的 fixtures 可以在 repo 裡用同一把金鑰重新簽章;中租的 UAT 金鑰是你自己的,因此 zingala 的錄音不含任何憑證,需要真金鑰才能驗證的測試都是 env-gated。

Fixtures 的原則

adapter 的 fixtures 一律是實際錄下來的回應,逐欄與 sandbox 一致,不是照文件編的。這條規則抓到的東西比 review 多 —— 綠界文件沒寫的 ExecLog / ExecStatusExecTimes 最小值是 2 而非 1、中租任何版本手冊都沒有的 801,全都是這樣挖出來的。

sandbox 到不了的分支就明確標成 doc-derived,不會默默用文件填補(目前唯一一處:payment-zingala 的 notify,因為 UAT 觸發不了)。

邊界

放這裡 不放這裡
create / get / refund、簽章、endpoint、normalized errors 特店申請、bind merchant、mermcc、KYC(→ paid.tw)
adapter 專有 extension CLI flags / table 輸出(→ @paid-tw/cli
核貸流程(BNPL) 行銷/個資分享 API(例如中租的電商推薦會員資料)

發版

不在本機 npm publish。走 git tag → publish.yml → npm OIDC trusted publishing。 完整步驟:docs/release.md

License

MIT

About

台灣金流 SDK:一致的 PaymentProvider 介面,方便串接。支援 PAYUNi、藍新 NewebPay、綠界 ECPay(AIO + 站內付 2.0)。

Topics

Resources

Stars

80 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages