npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@paid-tw/einvoice-ecpay

v0.5.2

Published

ECPay 綠界 (ecpay.com.tw) adapter for @paid-tw/einvoice — implements InvoiceProvider over the ECPay B2C e-invoice 2.0 API.

Readme

@paid-tw/einvoice-ecpay

npm version npm downloads types: TypeScript license: MIT

English繁體中文

@paid-tw/einvoiceECPay 綠界 轉接器,基於 ECPay B2C 電子發票 2.0 API(AES JSON API,而非舊版的 CheckMacValue API)實作了 InvoiceProvider 介面。

pnpm add @paid-tw/einvoice @paid-tw/einvoice-ecpay
import { createEcpayProvider } from "@paid-tw/einvoice-ecpay";

const invoices = createEcpayProvider({
  merchantId: process.env.ECPAY_MERCHANT_ID!,
  hashKey: process.env.ECPAY_HASH_KEY!, // 16 chars
  hashIV: process.env.ECPAY_HASH_IV!, // 16 chars
  mode: "TEST", // stage host; "PRODUCTION" → live host
});

await invoices.issue({ /* IssueInvoiceInput */ });

不需帳號即可試用

ECPay 提供共用的 sandbox 憑證。直接使用匯出的 ECPAY_SANDBOX 即可對測試特店開立發票:

import { createEcpayProvider, ECPAY_SANDBOX } from "@paid-tw/einvoice-ecpay";

const invoices = createEcpayProvider({ ...ECPAY_SANDBOX, mode: "TEST" }); // 特店 2000132 — never use in production

公開 sandbox 特店(2000132)務必帶入 ProductServiceID 綠界 2024-06-18 為開立 API 新增「產品服務別」(B2C 系統多組字軌)。公開 sandbox 的可用字軌全部綁定產品服務別,因此 不帶此參數時所有開立都回 5070350 查無可使用字軌或發票號碼——很容易被誤判為「沙盒號碼用罄」。 透過具名的 providerOptions.productServiceId 傳入即可:

await invoices.issue({ /* … */, providerOptions: { productServiceId: "A00001" } });

運作方式(已於測試環境實機驗證)

| 項目 | 說明 | | --- | --- | | 認證 | Data 欄位 = JSON → PHP urlencode → AES-128-CBC (PKCS7) → Base64(解碼時反向操作)。PHP url(en/de)code 語意:空白為 +,而非 %20。 | | 封包 | { MerchantID, RqHeader: { Timestamp }, Data };回應外層為 { TransCode, TransMsg, Data }TransCode === 1 代表傳輸成功。 | | 結果 | 解密 Data{ RtnCode, RtnMsg, … }RtnCode === 1 代表成功;否則為錯誤(這些代碼分布的範圍並不一致,因此對應關係改以 RtnMsg 為依據)。 | | 商品明細 | 為 { ItemSeq, ItemName, ItemCount, ItemWord, ItemPrice, ItemTaxType, ItemAmount } 的 JSON 陣列 — 並非以管線符號串接,也沒有 CheckMacValue。 | | 載具 | CarrierType:空=紙本 / 1=綠界 / 2=自然人憑證 / 3=手機條碼。含載具或捐贈的發票不得列印。 |

延遲開立(延遲 / 預約 / 觸發開立)

// TRIGGER (待觸發, default): issues only when you trigger it.
const { relateNumber } = await invoices.issuePending({ /* IssueInvoiceInput */ });
const res = await invoices.triggerIssue({ relateNumber });
// res.issued: true (DelayDay=0 → 4000004, res.invoiceNumber set) |
//             false (DelayDay>0 → 4000003, auto-issues later — query by relateNumber after)

// SCHEDULE (預約): auto-issues after N days (1–15), no trigger needed.
await invoices.issuePending({ /* … */ }, { mode: "SCHEDULE", delayDay: 3 });

// Edit a still-pending delayed invoice (keyed by its Tsr = orderId).
await invoices.editDelayIssue({ /* updated IssueInvoiceInput */ });

// Cancel a still-pending delayed invoice (before it issues/triggers).
await invoices.cancelDelayIssue(relateNumber);

載具驗證(手機條碼 / 愛心碼)

await invoices.validateMobileBarcode("/ABC1234"); // → boolean (CheckBarcode)
await invoices.validateLoveCode("168001"); // → boolean (CheckLoveCode)
await invoices.lookupLoveCodeOrganName("168001"); // → "財團法人…" | undefined (the charity name)

宣告為 CARRIER_VALIDATION 能力。

統一編號驗證

await invoices.lookupCompanyName("97025978"); // → "綠界科技股份有限公司" | undefined
await invoices.validateBan("97025978"); // → boolean

⚠️ 沒有公開資料的統編(政府/醫療/福委會 等)會回傳 undefined/false — 這 並不 代表它無效,因此請繼續開立。 只有當檢查碼或格式錯誤時才會拋出 VALIDATION(這種情況才應停止)。

設定

| 選項 | 必填 | 說明 | | --- | --- | --- | | merchantId | ✅ | 特店編號 | | hashKey | ✅ | 16 字元的 AES HashKey(僅限伺服器端) | | hashIV | ✅ | 16 字元的 AES HashIV(僅限伺服器端) | | mode | | "TEST"(預設,測試環境)或 "PRODUCTION" | | validatePayload | | 在本地端驗證開立的 payload(預設 true) | | debug | | 選用的請求追蹤 logger(metadata:method/url/status/耗時/error,不含請求內容)(預設 undefined) |

此外也接受由 @paid-tw/einvoiceBaseProviderConfig 繼承而來的共用欄位:baseUrl / timeoutMs / fetch / debug(適用於 sandbox / 自訂 agent / edge runtime 等情境)。

輸入會先經共用 schema 驗證,失敗丟出 InvoiceError(code VALIDATION);可用 validatePayload: false 關閉本地驗證。

字軌 / 編號

// 查詢財政部配號結果 — the invoice-number ranges allocated for a 民國年.
const ranges = await invoices.getGovInvoiceWordSetting("115");
// → [{ term, invType, header, start, end, count }, …]; throws NOT_FOUND if unallocated.

// 查詢字軌 — this merchant's own 字軌 (TrackID, range, used number, status).
const tracks = await invoices.getInvoiceWordSetting({ invoiceYear: "115", useStatus: "IN_USE" });
// → [{ trackId, year, term, invType, header, start, end, currentNumber, status }, …]

// 設定字軌號碼狀態 — a newly added 字軌 is inactive; enable it before issuing.
await invoices.setInvoiceWordStatus(trackId, "ENABLE"); // or "PAUSE" / "DISABLE"

新增字軌(AddInvoiceWordSetting)尚未包裝,目前僅能透過 invoices.raw() 呼叫。實測限制: InvoiceCategory 只能是 4(離線發票);MachineID 必須是後台已註冊的機器編號(無對應 API, 亂填回 1900003 查無此機器編號);號段限制為起號尾數 00/50、迄號尾數 49/99

發票列印

// Get a print URL (valid for 1 hour). Defaults to single-sided, today's date.
const url = await invoices.getPrintUrl({
  invoiceNumber: "JU11084038",
  invoiceDate: "2026-06-17", // optional; defaults to today (Asia/Taipei)
  style: "DOUBLE",   // SINGLE | DOUBLE | THERMAL | B2B_A4 | B2B_A5
  showDetail: true,  // B2B / 統編 invoices always show detail
  reprint: true,     // stamp as 補印 (ignored for B2B styles)
});

只有可列印紙本的發票才有效:含載具或捐贈的發票(Print=0)或 未知的號碼會回傳 查無資料 → NOT_FOUNDB2B_A4 / B2B_A5 樣式 需要帶有統編的發票。

發送發票通知

// Email / SMS an invoice, void, allowance or award notification to the buyer
// and/or merchant. ECPay's stage env validates the request but does not deliver.
await invoices.sendNotification({
  invoiceNumber: "JU11084029",
  tag: "ISSUE",        // ISSUE | VOID | ALLOWANCE | ALLOWANCE_VOID | AWARD | ONLINE_ALLOWANCE
  method: "EMAIL",     // EMAIL | SMS | BOTH
  recipient: "CUSTOMER", // CUSTOMER | MERCHANT | BOTH
  email: "[email protected]", // and/or phone — at least one is required
});

折讓相關的 tag(ALLOWANCE / ALLOWANCE_VOID / ONLINE_ALLOWANCE)需要帶 allowanceNumberONLINE_ALLOWANCE 必須使用 EMAIL + CUSTOMER。對 未中獎的發票以 tag: "AWARD" 發送通知會拋出 NOT_FOUND(查無發票中獎資料)。

註銷重開

// Atomically void an invoice and reissue it. ECPay keeps the original
// 發票號碼 / 自訂編號 / 開立時間 — only the random code changes — so the reissue
// must carry the original orderId and issue time. Do it before the 13th of the
// month after the invoice's period.
const res = await invoices.voidWithReissue({
  invoiceNumber: orig.invoiceNumber,
  voidReason: "客戶要求重開",      // ≤ 20 chars
  invoiceDate: orig.invoiceDate,  // the original issue time (Date or yyyy-MM-dd HH:mm:ss)
  reissue: { ...issueInput, orderId: orig.orderId }, // same shape as issue()
});
res.invoiceNumber === orig.invoiceNumber; // true — reuses the original number

尚在待處理的發票(尚未上傳至財政部)還無法再次作廢; 未知的號碼會回傳 查無發票資料 → NOT_FOUND

補充說明

  • 錯誤分流InvoiceError 上的正規化 reason 欄位(duplicate_orderalready_voidedvoid_blocked_by_allowance…)優先以 RtnCode 對應 (ecpayErrorReason(rtnMsg, rtnCode));少數穩定碼(如 5070357 自訂編號重覆、 5070453 已作廢過、5070450 已折讓過無法作廢)因實機 RtnMsg 含多個狀態詞 (會誤導純關鍵字比對)而直接建表,其餘才回退至 RtnMsg 關鍵字,無法判定時為 undefined。這三碼皆對應為 CONFLICT(實機驗證,2026-08-01)。

  • 傳輸層錯誤:金鑰錯誤不會走到業務層 —— HashKey/HashIV 錯誤時 Data 解不開, 外層直接回 TransCode 110(HTTP 500,實機驗證 2026-08-15)。這類封包層錯誤 以 TransCode 建表對應:110 解密失敗 → AUTH/credentials_invalid115 功能尚未開通(特店不存在或未啟用)→ AUTH/not_enrolled104 Timestamp 偏移超過 10 分鐘 → AUTH/stale_timestamp, 其餘 TransCode ≠ 1PROVIDER。另外 RqHeader.Revision 文件標必填, 實測不帶也會成功,本 SDK 不送。

  • 產品服務別:透過 providerOptions: { productServiceId: "A00001" } 傳入 (對應綠界 ProductServiceID)。公開 sandbox 特店需要它,否則開立回 5070350(見上方)。

  • 零稅率發票(taxType: "ZERO_RATED" 或混合)需要通關方式註記: 傳入 providerOptions: { clearanceMark: "1" | "2" }(1=非經海關,2=經海關)。 這些驗證規則是依據實機 API 行為驗證,而非僅參照文件(例如 ECPay 的 ZeroTaxRateReason/SpecialTaxType「必填」其實並未被 API 強制,且 載具+捐贈 / B2B+載具 也會被接受)。

  • voidallowance 需要發票的日期 — 透過 providerOptions: { invoiceDate: "YYYY-MM-DD" } 傳入(開立結果中已帶有此值)。 省略時預設為今日(Asia/Taipei)。

  • allowance 使用 一般開立折讓(/B2CInvoice/Allowance,紙本):會立即回傳真實的 折讓單號,並可立即作廢(綠界 隔日才上傳至財政部)。預設不通知買受人;傳入 providerOptions: { allowanceNotify: "E"|"S"|"A", notifyMail, notifyPhone, reason } 即可發送通知。

  • allowanceOnline(input, { notifyMail, returnUrl?, … }) 為 線上折讓 (AllowanceByCollegiate):ECPay 會以 email 寄給買受人一個確認連結(72 小時 expiresAt);唯有買受人點選後才會開立折讓。可用 cancelAllowanceOnline({ invoiceNumber, allowanceNumber }) 取消尚在待處理者; 已確認或紙本者則用 voidAllowance 作廢。

  • 實機測試以 ECPAY_LIVE=1 執行(預設使用 ECPAY_SANDBOX)。

授權條款

MIT