{"_id":"@carllee1983/ecpay-fulllogistics","_rev":"2-59290fdf0527e633f8bbe05b4526da4b","name":"@carllee1983/ecpay-fulllogistics","dist-tags":{"latest":"1.0.0"},"versions":{"1.0.0":{"name":"@carllee1983/ecpay-fulllogistics","version":"1.0.0","keywords":["ecpay","logistics","fulllogistics","taiwan","shipping","bun","typescript","sdk","綠界","大物流"],"author":{"name":"Carl"},"license":"MIT","_id":"@carllee1983/ecpay-fulllogistics@1.0.0","maintainers":[{"name":"carllee1983","email":"carllee0520@gmail.com"}],"homepage":"https://github.com/CarlLee1983/ecpay-fulllogistics-node#readme","bugs":{"url":"https://github.com/CarlLee1983/ecpay-fulllogistics-node/issues"},"dist":{"shasum":"2dd0cf12a5febd6590a3cfe99f6a95ddbd73dbb8","tarball":"https://registry.npmjs.org/@carllee1983/ecpay-fulllogistics/-/ecpay-fulllogistics-1.0.0.tgz","fileCount":20,"integrity":"sha512-XYuFK2WL/VayhaOiqct7Y2hxI+k4RAPr/OyZNAM10X6jsZANM069HjZsFMXHGBMAFkujt0B1wlkZLMjdwbyZOg==","signatures":[{"sig":"MEQCIEfiwvuhkWujgyY1cGkKwE+aIgFlJrvSRgVzyizut8oTAiA8RQkdTf1G1le/QWAAPiRgVvT/daXcZTWSaCDvp0KI7A==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@carllee1983%2fecpay-fulllogistics@1.0.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":82251},"main":"./dist/index.cjs","type":"module","types":"./dist/types/index.d.ts","module":"./dist/index.mjs","engines":{"bun":">=1.0","node":">=18"},"exports":{".":{"import":{"types":"./dist/types/index.d.ts","default":"./dist/index.mjs"},"require":{"types":"./dist/types/index.d.ts","default":"./dist/index.cjs"}}},"gitHead":"313870b5027d85103061e7692a5481d879c31ca8","scripts":{"lint":"eslint .","test":"bun test","build":"bun run build.ts","clean":"rm -rf dist coverage","format":"prettier --write .","test:ci":"bun test --coverage --coverage-reporter=lcov 2>&1 | tee coverage-output.txt && bun run scripts/check-coverage.ts","lint:fix":"eslint . --fix","typecheck":"tsc --noEmit","format:check":"prettier --check .","test:coverage":"bun test --coverage","prepublishOnly":"bun run lint && bun run typecheck && bun run test && bun run build"},"_npmUser":{"name":"carllee1983","email":"carllee0520@gmail.com"},"repository":{"url":"git+https://github.com/CarlLee1983/ecpay-fulllogistics-node.git","type":"git"},"_npmVersion":"10.8.2","description":"Unofficial ECPay Full Logistics (綠界全方位物流) SDK for Node.js. Type-safe & Bun-compatible.","directories":{},"_nodeVersion":"20.19.6","_hasShrinkwrap":false,"devDependencies":{"eslint":"^9.39.1","globals":"^16.5.0","prettier":"^3.7.4","bun-types":"^1.3.4","@eslint/js":"^9.39.1","@types/bun":"latest","typescript":"^5.7.3","typescript-eslint":"^8.48.1","eslint-config-prettier":"^10.1.8","@typescript-eslint/parser":"^8.48.1","@typescript-eslint/eslint-plugin":"^8.48.1"},"peerDependencies":{"typescript":"^5"},"_npmOperationalInternal":{"tmp":"tmp/ecpay-fulllogistics_1.0.0_1765248943348_0.3354694688710167","host":"s3://npm-registry-packages-npm-production"}}},"time":{"created":"2025-12-09T02:55:43.220Z","modified":"2026-09-15T02:54:44.569Z","1.0.0":"2025-12-09T02:55:43.498Z"},"bugs":{"url":"https://github.com/CarlLee1983/ecpay-fulllogistics-node/issues"},"author":{"name":"Carl"},"license":"MIT","homepage":"https://github.com/CarlLee1983/ecpay-fulllogistics-node#readme","keywords":["ecpay","logistics","fulllogistics","taiwan","shipping","bun","typescript","sdk","綠界","大物流"],"repository":{"url":"git+https://github.com/CarlLee1983/ecpay-fulllogistics-node.git","type":"git"},"description":"Unofficial ECPay Full Logistics (綠界全方位物流) SDK for Node.js. Type-safe & Bun-compatible.","maintainers":[{"email":"yashino538@gmail.com","name":"praxisbound"}],"readme":"# @carllee1983/ecpay-fulllogistics\n\n[English](README.md) | [繁體中文](README_TW.md)\n\n[![npm version](https://img.shields.io/npm/v/@carllee1983/ecpay-fulllogistics.svg)](https://www.npmjs.com/package/@carllee1983/ecpay-fulllogistics)\n[![CI](https://github.com/CarlLee1983/ecpay-fulllogistics-node/actions/workflows/ci.yml/badge.svg)](https://github.com/CarlLee1983/ecpay-fulllogistics-node/actions/workflows/ci.yml)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n[![TypeScript](https://img.shields.io/badge/TypeScript-5.x-blue.svg)](https://www.typescriptlang.org/)\n[![Bun](https://img.shields.io/badge/Bun-1.x-orange.svg)](https://bun.sh/)\n\n> Unofficial ECPay Full Logistics (綠界全方位物流) SDK for Node.js. Type-safe & Bun-compatible.\n\n## ✨ Features\n\n- 🚀 **Modern TypeScript** - Full type safety with strict mode\n- 📦 **Dual Module Support** - ESM (primary) and CJS builds\n- 🧪 **95%+ Test Coverage** - Production-ready with enforced coverage thresholds\n- ⚡ **Bun Optimized** - Built and tested with Bun for maximum performance\n- 🔒 **AES-128-CBC Encryption** - Built-in encryption/decryption compatible with ECPay API\n- 📋 **JSON-based API** - Uses ECPay's new JSON + AES encryption format\n\n## 📦 Installation\n\n```bash\n# npm\nnpm install @carllee1983/ecpay-fulllogistics\n\n# yarn\nyarn add @carllee1983/ecpay-fulllogistics\n\n# pnpm\npnpm add @carllee1983/ecpay-fulllogistics\n\n# bun\nbun add @carllee1983/ecpay-fulllogistics\n```\n\n## 🚀 Quick Start\n\n### Basic Configuration\n\n```typescript\nimport {\n  ApiMode,\n  getApiUrl,\n  validateConfig,\n  type EcPayConfig,\n} from '@carllee1983/ecpay-fulllogistics'\n\n// Use test credentials for staging environment\nconst config: EcPayConfig = {\n  merchantId: '2000132',\n  hashKey: '5294y06JbISpM5x9',\n  hashIv: 'v77hoKGq4kWxNNIS',\n  mode: ApiMode.Staging,\n}\n\n// Validate before use\nif (validateConfig(config)) {\n  console.log('✅ Configuration valid')\n  console.log('📍 API URL:', getApiUrl(config.mode))\n  // Output: https://logistics-stage.ecpay.com.tw\n}\n```\n\n## 📖 Usage Examples\n\n### 1. AES Encryption/Decryption\n\nThe `CipherService` handles AES-128-CBC encryption compatible with ECPay's specification:\n\n```typescript\nimport { CipherService } from '@carllee1983/ecpay-fulllogistics'\n\n// Initialize with 16-character keys\nconst cipher = new CipherService('5294y06JbISpM5x9', 'v77hoKGq4kWxNNIS')\n\n// Encrypt data (automatically URL-encodes before encryption)\nconst plaintext = '{\"MerchantTradeNo\":\"ORDER123\",\"GoodsAmount\":1000}'\nconst encrypted = cipher.encrypt(plaintext)\nconsole.log('Encrypted:', encrypted)\n// Output: Base64 encoded string\n\n// Decrypt data (automatically URL-decodes after decryption)\nconst decrypted = cipher.decrypt(encrypted)\nconsole.log('Decrypted:', decrypted)\n// Output: {\"MerchantTradeNo\":\"ORDER123\",\"GoodsAmount\":1000}\n```\n\n### 2. Building API Request Payloads\n\nThe `PayloadEncoder` creates properly formatted request payloads:\n\n```typescript\nimport { PayloadEncoder } from '@carllee1983/ecpay-fulllogistics'\n\nconst encoder = new PayloadEncoder('5294y06JbISpM5x9', 'v77hoKGq4kWxNNIS')\n\n// Build encrypted payload for API request\nconst payload = encoder.encode('2000132', {\n  MerchantTradeNo: 'ORDER_' + Date.now(),\n  LogisticsType: 'CVS',\n  LogisticsSubType: 'UNIMART',\n  GoodsAmount: 1000,\n  GoodsName: 'Test Product',\n  SenderName: 'Sender',\n  SenderCellPhone: '0912345678',\n  ReceiverName: 'Receiver',\n  ReceiverCellPhone: '0987654321',\n  ReceiverStoreID: '991182',\n  ServerReplyURL: 'https://your-domain.com/callback',\n})\n\nconsole.log('Request Payload:', JSON.stringify(payload, null, 2))\n// Output:\n// {\n//   \"MerchantID\": \"2000132\",\n//   \"RqHeader\": {\n//     \"Timestamp\": 1733749200,\n//     \"Revision\": \"1.0.0\"\n//   },\n//   \"Data\": \"encrypted_base64_string...\"\n// }\n\n// Send to ECPay API\nconst response = await fetch('https://logistics-stage.ecpay.com.tw/Express/v2/CreateOrder', {\n  method: 'POST',\n  headers: { 'Content-Type': 'application/json' },\n  body: JSON.stringify(payload),\n})\n```\n\n### 3. Parsing API Responses\n\nThe `Response` class provides convenient methods for handling ECPay responses:\n\n```typescript\nimport { PayloadEncoder, Response } from '@carllee1983/ecpay-fulllogistics'\n\nconst encoder = new PayloadEncoder('5294y06JbISpM5x9', 'v77hoKGq4kWxNNIS')\n\n// Simulated API response from ECPay\nconst apiResponse = {\n  TransCode: 1,\n  TransMsg: 'Success',\n  Data: 'encrypted_response_string...',\n}\n\n// Create Response wrapper with automatic decryption\nconst response = new Response(apiResponse, encoder)\n\n// Check success status\nif (response.isSuccess()) {\n  // Use convenience getters\n  console.log('Logistics ID:', response.getAllPayLogisticsID())\n  console.log('Merchant Trade No:', response.getMerchantTradeNo())\n  console.log('Logistics Status:', response.getLogisticsStatus())\n  console.log('Shipment No:', response.getShipmentNo())\n  console.log('CVS Validation No:', response.getCVSValidationNo())\n  console.log('Print URL:', response.getPrintUrl())\n  console.log('Receiver Store ID:', response.getReceiverStoreID())\n  console.log('Receiver Store Name:', response.getReceiverStoreName())\n\n  // Or access specific fields\n  const goodsAmount = response.get('GoodsAmount')\n  console.log('Goods Amount:', goodsAmount)\n\n  // Or get the full data object\n  const fullData = response.getData()\n  console.log('Full Response:', fullData)\n} else {\n  console.error('API Error:', response.getRtnCode(), response.getRtnMsg())\n}\n```\n\n### 4. Handling Responses Without Encryption\n\nSome API responses may not be encrypted:\n\n```typescript\nimport { Response } from '@carllee1983/ecpay-fulllogistics'\n\n// Response with unencrypted Data object\nconst errorResponse = {\n  TransCode: 0,\n  TransMsg: 'Parameter Error',\n  Data: {\n    RtnCode: 10100001,\n    RtnMsg: 'MerchantTradeNo is required',\n  },\n}\n\n// Create Response without encoder\nconst response = new Response(errorResponse)\n\nif (!response.isSuccess()) {\n  console.error('Error Code:', response.getRtnCode())\n  console.error('Error Message:', response.getRtnMsg())\n}\n```\n\n### 5. Error Handling with LogisticsException\n\nUse `LogisticsException` for consistent error handling:\n\n```typescript\nimport { LogisticsException } from '@carllee1983/ecpay-fulllogistics'\n\nfunction validateOrder(data: {\n  merchantTradeNo?: string\n  goodsAmount?: number\n  senderName?: string\n}) {\n  // Required field validation\n  if (!data.merchantTradeNo) {\n    throw LogisticsException.required('MerchantTradeNo')\n    // Error: \"MerchantTradeNo 為必填欄位。\"\n  }\n\n  // Length validation\n  if (data.merchantTradeNo.length > 20) {\n    throw LogisticsException.tooLong('MerchantTradeNo', 20)\n    // Error: \"MerchantTradeNo 不可超過 20 個字元。\"\n  }\n\n  // Format validation\n  if (!/^[A-Za-z0-9]+$/.test(data.merchantTradeNo)) {\n    throw LogisticsException.invalid('MerchantTradeNo', '只能包含英數字')\n    // Error: \"MerchantTradeNo 格式無效：只能包含英數字\"\n  }\n\n  // Range validation\n  const validAmounts = [60, 90, 120]\n  if (data.goodsAmount && !validAmounts.includes(data.goodsAmount)) {\n    throw LogisticsException.notInRange('GoodsAmount', validAmounts)\n    // Error: \"GoodsAmount 必須為下列值之一：60, 90, 120\"\n  }\n}\n\n// Usage\ntry {\n  validateOrder({ merchantTradeNo: '', goodsAmount: 100 })\n} catch (error) {\n  if (error instanceof LogisticsException) {\n    console.error('Validation Error:', error.message)\n  }\n}\n```\n\n### 6. Complete API Call Example\n\n```typescript\nimport {\n  ApiMode,\n  getApiUrl,\n  PayloadEncoder,\n  Response,\n  LogisticsException,\n  type EcPayConfig,\n} from '@carllee1983/ecpay-fulllogistics'\n\nasync function createLogisticsOrder(orderData: {\n  merchantTradeNo: string\n  goodsAmount: number\n  goodsName: string\n  receiverName: string\n  receiverPhone: string\n  receiverStoreId: string\n}) {\n  // Configuration\n  const config: EcPayConfig = {\n    merchantId: '2000132',\n    hashKey: '5294y06JbISpM5x9',\n    hashIv: 'v77hoKGq4kWxNNIS',\n    mode: ApiMode.Staging,\n  }\n\n  const encoder = new PayloadEncoder(config.hashKey, config.hashIv)\n\n  // Build request payload\n  const payload = encoder.encode(config.merchantId, {\n    MerchantTradeNo: orderData.merchantTradeNo,\n    LogisticsType: 'CVS',\n    LogisticsSubType: 'UNIMART',\n    GoodsAmount: orderData.goodsAmount,\n    GoodsName: orderData.goodsName,\n    SenderName: 'Shop Name',\n    SenderCellPhone: '0912345678',\n    ReceiverName: orderData.receiverName,\n    ReceiverCellPhone: orderData.receiverPhone,\n    ReceiverStoreID: orderData.receiverStoreId,\n    ServerReplyURL: 'https://your-domain.com/logistics/callback',\n  })\n\n  try {\n    // Send API request\n    const apiUrl = getApiUrl(config.mode) + '/Express/v2/CreateOrder'\n    const result = await fetch(apiUrl, {\n      method: 'POST',\n      headers: { 'Content-Type': 'application/json' },\n      body: JSON.stringify(payload),\n    })\n\n    const apiResponse = await result.json()\n    const response = new Response(apiResponse, encoder)\n\n    if (response.isSuccess()) {\n      return {\n        success: true,\n        logisticsId: response.getAllPayLogisticsID(),\n        validationNo: response.getCVSValidationNo(),\n        data: response.getData(),\n      }\n    } else {\n      throw LogisticsException.apiError(response.getRtnCode(), response.getRtnMsg())\n    }\n  } catch (error) {\n    if (error instanceof LogisticsException) {\n      throw error\n    }\n    throw LogisticsException.httpError(String(error))\n  }\n}\n\n// Usage\ncreateLogisticsOrder({\n  merchantTradeNo: 'ORDER_' + Date.now(),\n  goodsAmount: 500,\n  goodsName: 'Test Product',\n  receiverName: 'John Doe',\n  receiverPhone: '0987654321',\n  receiverStoreId: '991182',\n})\n  .then((result) => console.log('Success:', result))\n  .catch((error) => console.error('Failed:', error.message))\n```\n\n## 📖 API Reference\n\n### `ApiMode`\n\nEnum for API environments:\n\n| Value        | URL                                    |\n| ------------ | -------------------------------------- |\n| `Production` | `https://logistics.ecpay.com.tw`       |\n| `Staging`    | `https://logistics-stage.ecpay.com.tw` |\n\n### `EcPayConfig`\n\n```typescript\ninterface EcPayConfig {\n  merchantId: string // ECPay Merchant ID\n  hashKey: string // 16-character Hash Key\n  hashIv: string // 16-character Hash IV\n  mode?: ApiMode // API mode (defaults to Staging)\n}\n```\n\n### `CipherService`\n\n| Method            | Description                          |\n| ----------------- | ------------------------------------ |\n| `encrypt(text)`   | Encrypts text, returns Base64 string |\n| `decrypt(cipher)` | Decrypts Base64 string, returns text |\n\n### `PayloadEncoder`\n\n| Method                     | Description                           |\n| -------------------------- | ------------------------------------- |\n| `encode(merchantId, data)` | Creates encrypted API request payload |\n| `decode<T>(encryptedData)` | Decrypts API response data string     |\n\n### `Response<T>`\n\n| Method                   | Return Type | Description             |\n| ------------------------ | ----------- | ----------------------- |\n| `isSuccess()`            | `boolean`   | Check if RtnCode === 1  |\n| `getRtnCode()`           | `number`    | Get return code         |\n| `getRtnMsg()`            | `string`    | Get return message      |\n| `getData()`              | `T`         | Get full data object    |\n| `get(key)`               | `unknown`   | Get specific field      |\n| `getAllPayLogisticsID()` | `string?`   | Get ECPay Logistics ID  |\n| `getLogisticsStatus()`   | `string?`   | Get logistics status    |\n| `getMerchantTradeNo()`   | `string?`   | Get merchant trade no   |\n| `getShipmentNo()`        | `string?`   | Get shipment number     |\n| `getCVSValidationNo()`   | `string?`   | Get CVS validation no   |\n| `getPrintUrl()`          | `string?`   | Get print URL           |\n| `getReceiverStoreID()`   | `string?`   | Get receiver store ID   |\n| `getReceiverStoreName()` | `string?`   | Get receiver store name |\n\n### `LogisticsException`\n\n| Factory Method              | Error Message Example                       |\n| --------------------------- | ------------------------------------------- |\n| `required(field)`           | `MerchantTradeNo 為必填欄位。`              |\n| `invalid(field, reason?)`   | `MerchantTradeNo 格式無效：只能包含英數字`  |\n| `tooLong(field, maxLength)` | `MerchantTradeNo 不可超過 20 個字元。`      |\n| `httpError(message)`        | `HTTP 請求錯誤：Connection timeout`         |\n| `apiError(code, message)`   | `API 錯誤 [10100001]：參數錯誤`             |\n| `notInRange(field, values)` | `LogisticsType 必須為下列值之一：CVS, HOME` |\n\n## 🔐 Security Notice\n\n> ⚠️ **Never expose HashKey/HashIV in frontend code** (JavaScript, HTML, CSS). Always use environment variables or secure configuration.\n\n```typescript\n// ✅ Good: Use environment variables\nconst config: EcPayConfig = {\n  merchantId: process.env.ECPAY_MERCHANT_ID!,\n  hashKey: process.env.ECPAY_HASH_KEY!,\n  hashIv: process.env.ECPAY_HASH_IV!,\n  mode: process.env.NODE_ENV === 'production' ? ApiMode.Production : ApiMode.Staging,\n}\n\n// ❌ Bad: Hardcoded credentials in client code\n```\n\n## 🧪 Test Environment\n\n| Type | Merchant ID | HashKey          | HashIV           |\n| ---- | ----------- | ---------------- | ---------------- |\n| C2C  | 2000132     | 5294y06JbISpM5x9 | v77hoKGq4kWxNNIS |\n| B2C  | 2000933     | XBERn1YOvpM9nfZc | h1ONHk4P4yqbl5LK |\n\n- **Staging URL**: `https://logistics-stage.ecpay.com.tw`\n- **Production URL**: `https://logistics.ecpay.com.tw`\n\n## 🛠 Development\n\n### Prerequisites\n\n- [Bun](https://bun.sh/) >= 1.0\n- Node.js >= 18 (for compatibility)\n\n### Setup\n\n```bash\ngit clone https://github.com/CarlLee1983/ecpay-fulllogistics-node.git\ncd ecpay-fulllogistics-node\nbun install\n```\n\n### Scripts\n\n| Command                 | Description                           |\n| ----------------------- | ------------------------------------- |\n| `bun run build`         | Build ESM, CJS, and type declarations |\n| `bun test`              | Run tests                             |\n| `bun run test:coverage` | Run tests with coverage report        |\n| `bun run typecheck`     | TypeScript type checking              |\n| `bun run lint`          | Run ESLint                            |\n| `bun run format`        | Format code with Prettier             |\n\n## 📚 Resources\n\n- [ECPay Full Logistics API Documentation](https://developers.ecpay.com.tw/?p=10075)\n- [ECPay Vendor Portal (Staging)](https://vendor-stage.ecpay.com.tw/)\n\n## 📝 License\n\n[MIT](LICENSE) © Carl\n\n## 🤝 Contributing\n\nContributions are welcome! Please read the [Contributing Guide](CONTRIBUTING.md) for details.\n\n## 🔒 Security\n\nFor security concerns, please see our [Security Policy](SECURITY.md).\n","readmeFilename":"README.md"}