@zentring/zinvoice 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -193,8 +193,7 @@ if (client.supports(Capability.CARRIER)) {
193
193
  }
194
194
 
195
195
  if (client.supports(Capability.EXCHANGE)) {
196
- // 支援換開發票
197
- await client.invoices.exchange(...);
196
+ // 支援 B2B 發票交換
198
197
  }
199
198
 
200
199
  // 取得所有支援的能力
@@ -213,7 +212,7 @@ console.log(capabilities);
213
212
  | `DONATION` | 捐贈 | ✓ |
214
213
  | `ALLOWANCE` | 折讓 | ✓ |
215
214
  | `VOID` | 作廢 | ✓ |
216
- | `EXCHANGE` | 換開 | ✓ |
215
+ | `EXCHANGE` | B2B 交換 | ✓ |
217
216
  | `PRINT` | 列印格式 | ✓ |
218
217
 
219
218
  ## 錯誤處理
@@ -246,11 +245,51 @@ try {
246
245
  }
247
246
  ```
248
247
 
248
+ ## 自訂系統商
249
+
250
+ 如果您使用的加值中心尚未內建支援,可以自行實作:
251
+
252
+ ```typescript
253
+ import { Zinvoice, Capability } from '@zentring/zinvoice';
254
+
255
+ const client = Zinvoice.custom({
256
+ name: '自訂系統商',
257
+ capabilities: [Capability.B2C, Capability.QUERY, Capability.LIST],
258
+ invoices: {
259
+ issue: async (invoice) => {
260
+ // 實作開立發票邏輯
261
+ return {
262
+ invoiceNumber: InvoiceNumber.create('AA12345678'),
263
+ invoiceTime: new Date(),
264
+ randomNumber: '1234',
265
+ barcode: '...',
266
+ qrcodeLeft: '...',
267
+ qrcodeRight: '...',
268
+ };
269
+ },
270
+ findByNumber: async (number) => { /* ... */ },
271
+ findByOrderId: async (orderId) => { /* ... */ },
272
+ getStatus: async (numbers) => { /* ... */ },
273
+ list: async (query) => { /* ... */ },
274
+ },
275
+ });
276
+ ```
277
+
278
+ **重要:** 註冊的 Capability 必須實作對應的方法,否則會拋出 `ValidationError`:
279
+
280
+ | Capability | 必須實作 |
281
+ |------------|----------|
282
+ | `B2C` / `B2B` | `invoices.issue` |
283
+ | `VOID` | `invoices.void` |
284
+ | `QUERY` | `invoices.findByNumber`, `findByOrderId`, `getStatus` |
285
+ | `LIST` | `invoices.list` |
286
+ | `ALLOWANCE` | `allowances.issue`, `void`, `findByNumber`, `findByInvoiceNumber`, `getStatus`, `list` |
287
+
249
288
  ## 支援的加值中心
250
289
 
251
290
  | Provider | 狀態 | 說明 |
252
291
  |----------|:----:|------|
253
- | `Provider.AMEGO` | ✓ | 光貿資訊 |
292
+ | `Provider.AMEGO` | ✓ | 光貿科技 |
254
293
 
255
294
  ## 測試帳號
256
295
 
package/dist/index.cjs CHANGED
@@ -35,7 +35,7 @@ var PROVIDER_CAPABILITIES = {
35
35
  ])
36
36
  };
37
37
  var PROVIDER_NAMES = {
38
- ["amego" /* AMEGO */]: "\u5149\u8CBF\u8CC7\u8A0A"
38
+ ["amego" /* AMEGO */]: "\u5149\u8CBF\u79D1\u6280"
39
39
  };
40
40
 
41
41
  // src/errors/index.ts
@@ -2654,17 +2654,100 @@ var AmegoAllowanceService = class {
2654
2654
  }
2655
2655
  };
2656
2656
 
2657
+ // src/CustomProvider.ts
2658
+ var CAPABILITY_REQUIREMENTS = {
2659
+ ["b2c" /* B2C */]: { invoices: ["issue"] },
2660
+ ["b2b" /* B2B */]: { invoices: ["issue"] },
2661
+ ["carrier" /* CARRIER */]: {},
2662
+ // Part of issue, no separate handler
2663
+ ["donation" /* DONATION */]: {},
2664
+ // Part of issue, no separate handler
2665
+ ["void" /* VOID */]: { invoices: ["void"] },
2666
+ ["query" /* QUERY */]: { invoices: ["findByNumber", "findByOrderId", "getStatus"] },
2667
+ ["list" /* LIST */]: { invoices: ["list"] },
2668
+ ["allowance" /* ALLOWANCE */]: { allowances: ["issue", "void", "findByNumber", "findByInvoiceNumber", "getStatus", "list"] },
2669
+ ["exchange" /* EXCHANGE */]: {},
2670
+ // TODO: Add exchange handlers when implemented
2671
+ ["print" /* PRINT */]: {}
2672
+ // Part of issue result, no separate handler
2673
+ };
2674
+ function validateCustomProviderConfig(config) {
2675
+ const missingHandlers = [];
2676
+ for (const capability of config.capabilities) {
2677
+ const requirements = CAPABILITY_REQUIREMENTS[capability];
2678
+ if (requirements.invoices) {
2679
+ for (const handler of requirements.invoices) {
2680
+ if (!config.invoices?.[handler]) {
2681
+ missingHandlers.push(`invoices.${handler} (required for ${capability})`);
2682
+ }
2683
+ }
2684
+ }
2685
+ if (requirements.allowances) {
2686
+ for (const handler of requirements.allowances) {
2687
+ if (!config.allowances?.[handler]) {
2688
+ missingHandlers.push(`allowances.${handler} (required for ${capability})`);
2689
+ }
2690
+ }
2691
+ }
2692
+ }
2693
+ if (missingHandlers.length > 0) {
2694
+ throw new ValidationError(
2695
+ "customProvider",
2696
+ `Missing required handlers for registered capabilities:
2697
+ - ${missingHandlers.join("\n - ")}`
2698
+ );
2699
+ }
2700
+ }
2701
+ function createCustomInvoiceService(providerName, capabilities, handlers = {}) {
2702
+ const notImplemented = (method, capability) => {
2703
+ return async () => {
2704
+ if (!capabilities.has(capability)) {
2705
+ throw new UnsupportedCapabilityError(capability, providerName);
2706
+ }
2707
+ throw new Error(`${providerName}: ${method} is not implemented`);
2708
+ };
2709
+ };
2710
+ return {
2711
+ issue: handlers.issue ?? notImplemented("issue", "b2c" /* B2C */),
2712
+ void: handlers.void ?? notImplemented("void", "void" /* VOID */),
2713
+ findByNumber: handlers.findByNumber ?? notImplemented("findByNumber", "query" /* QUERY */),
2714
+ findByOrderId: handlers.findByOrderId ?? notImplemented("findByOrderId", "query" /* QUERY */),
2715
+ getStatus: handlers.getStatus ?? notImplemented("getStatus", "query" /* QUERY */),
2716
+ list: handlers.list ?? notImplemented("list", "list" /* LIST */)
2717
+ };
2718
+ }
2719
+ function createCustomAllowanceService(providerName, capabilities, handlers = {}) {
2720
+ const notImplemented = (method) => {
2721
+ return async () => {
2722
+ if (!capabilities.has("allowance" /* ALLOWANCE */)) {
2723
+ throw new UnsupportedCapabilityError("allowance" /* ALLOWANCE */, providerName);
2724
+ }
2725
+ throw new Error(`${providerName}: ${method} is not implemented`);
2726
+ };
2727
+ };
2728
+ return {
2729
+ issue: handlers.issue ?? notImplemented("issue"),
2730
+ void: handlers.void ?? notImplemented("void"),
2731
+ findByNumber: handlers.findByNumber ?? notImplemented("findByNumber"),
2732
+ findByInvoiceNumber: handlers.findByInvoiceNumber ?? notImplemented("findByInvoiceNumber"),
2733
+ getStatus: handlers.getStatus ?? notImplemented("getStatus"),
2734
+ list: handlers.list ?? notImplemented("list")
2735
+ };
2736
+ }
2737
+
2657
2738
  // src/Zinvoice.ts
2658
2739
  var Zinvoice = class _Zinvoice {
2659
2740
  _provider;
2741
+ _providerName;
2660
2742
  _invoiceService;
2661
2743
  _allowanceService;
2662
2744
  _capabilities;
2663
- constructor(provider, invoiceService, allowanceService) {
2745
+ constructor(provider, providerName, capabilities, invoiceService, allowanceService) {
2664
2746
  this._provider = provider;
2747
+ this._providerName = providerName;
2748
+ this._capabilities = capabilities;
2665
2749
  this._invoiceService = invoiceService;
2666
2750
  this._allowanceService = allowanceService;
2667
- this._capabilities = PROVIDER_CAPABILITIES[provider];
2668
2751
  }
2669
2752
  /**
2670
2753
  * Create a Zinvoice client
@@ -2689,7 +2772,60 @@ var Zinvoice = class _Zinvoice {
2689
2772
  };
2690
2773
  const invoiceService = new AmegoInvoiceService(amegoConfig);
2691
2774
  const allowanceService = new AmegoAllowanceService(amegoConfig);
2692
- return new _Zinvoice("amego" /* AMEGO */, invoiceService, allowanceService);
2775
+ return new _Zinvoice(
2776
+ "amego" /* AMEGO */,
2777
+ PROVIDER_NAMES["amego" /* AMEGO */],
2778
+ PROVIDER_CAPABILITIES["amego" /* AMEGO */],
2779
+ invoiceService,
2780
+ allowanceService
2781
+ );
2782
+ }
2783
+ /**
2784
+ * Create a custom Zinvoice client with user-provided handlers
2785
+ *
2786
+ * @example
2787
+ * ```typescript
2788
+ * const client = Zinvoice.custom({
2789
+ * name: '自訂系統商',
2790
+ * capabilities: [Capability.B2C, Capability.QUERY, Capability.LIST],
2791
+ * invoices: {
2792
+ * issue: async (invoice) => {
2793
+ * // 實作開立發票邏輯
2794
+ * return { invoiceNumber, invoiceTime, randomNumber };
2795
+ * },
2796
+ * findByNumber: async (number) => {
2797
+ * // 實作查詢邏輯
2798
+ * return invoice;
2799
+ * },
2800
+ * findByOrderId: async (orderId) => { ... },
2801
+ * getStatus: async (numbers) => { ... },
2802
+ * list: async (query) => { ... },
2803
+ * },
2804
+ * });
2805
+ * ```
2806
+ *
2807
+ * @throws {ValidationError} If required handlers are not implemented for registered capabilities
2808
+ */
2809
+ static custom(config) {
2810
+ validateCustomProviderConfig(config);
2811
+ const capabilities = new Set(config.capabilities);
2812
+ const invoiceService = createCustomInvoiceService(
2813
+ config.name,
2814
+ capabilities,
2815
+ config.invoices
2816
+ );
2817
+ const allowanceService = createCustomAllowanceService(
2818
+ config.name,
2819
+ capabilities,
2820
+ config.allowances
2821
+ );
2822
+ return new _Zinvoice(
2823
+ "custom",
2824
+ config.name,
2825
+ capabilities,
2826
+ invoiceService,
2827
+ allowanceService
2828
+ );
2693
2829
  }
2694
2830
  /**
2695
2831
  * Get the invoice service
@@ -2705,6 +2841,7 @@ var Zinvoice = class _Zinvoice {
2705
2841
  }
2706
2842
  /**
2707
2843
  * Get the current provider
2844
+ * Returns 'custom' for custom providers
2708
2845
  */
2709
2846
  get provider() {
2710
2847
  return this._provider;
@@ -2713,7 +2850,7 @@ var Zinvoice = class _Zinvoice {
2713
2850
  * Get the provider display name
2714
2851
  */
2715
2852
  get providerName() {
2716
- return PROVIDER_NAMES[this._provider];
2853
+ return this._providerName;
2717
2854
  }
2718
2855
  /**
2719
2856
  * Check if a capability is supported
@@ -2732,9 +2869,15 @@ var Zinvoice = class _Zinvoice {
2732
2869
  */
2733
2870
  requireCapability(capability) {
2734
2871
  if (!this.supports(capability)) {
2735
- throw new UnsupportedCapabilityError(capability, this._provider);
2872
+ throw new UnsupportedCapabilityError(capability, this._providerName);
2736
2873
  }
2737
2874
  }
2875
+ /**
2876
+ * Check if this is a custom provider
2877
+ */
2878
+ get isCustom() {
2879
+ return this._provider === "custom";
2880
+ }
2738
2881
  };
2739
2882
 
2740
2883
  // src/domain/shared/CarrierCode.ts