npm package: @rytass/invoice-adapter-ecpay
Description: Rytass Invoice Gateway - ECPay
Author: Chia Yu Pai
Latest version: 0.5.12

# Rytass Utils - ECPay Invoice Adapter

Comprehensive invoice management adapter for ECPay's Taiwan electronic invoice system. Provides unified invoice issuance, querying, and management functionality with support for various carrier types, VAT numbers, and Taiwan's electronic invoice regulations.

## Features

- [x] Electronic invoice issuance
- [x] Mobile barcode carrier support
- [x] Digital certificate (Moica) carrier support
- [x] Print invoice support
- [x] VAT number validation and business invoices
- [x] Love code (donation) validation
- [x] Invoice query by order ID or invoice number
- [x] Invoice void and cancellation
- [x] Allowance (credit note) creation
- [x] Multi-tax type support (taxed, tax-free, zero-rate)
- [x] Automatic customs mark calculation
- [x] B2B and B2C invoice handling

## Installation

```bash
npm install @rytass/invoice-adapter-ecpay
# or
yarn add @rytass/invoice-adapter-ecpay
```

## Basic Usage

### Gateway Setup

```typescript
import { ECPayInvoiceGateway } from '@rytass/invoice-adapter-ecpay';

const invoiceGateway = new ECPayInvoiceGateway({
  merchantId: 'YOUR_ECPAY_MERCHANT_ID',
  aesKey: 'YOUR_ECPAY_AES_KEY',
  aesIv: 'YOUR_ECPAY_AES_IV',
  baseUrl: 'https://einvoice.ecpay.com.tw', // Production
  // baseUrl: 'https://einvoice-stage.ecpay.com.tw', // Staging
});
```

### Basic Invoice Issuance

```typescript
// Issue a simple B2C invoice
const invoice = await invoiceGateway.issue({
  orderId: '2024081400001',
  customer: {
    email: 'customer@example.com',
  },
  items: [
    {
      name: 'Premium Software License',
      quantity: 1,
      unitPrice: 1500, // Tax-included price
    },
    {
      name: 'Technical Support',
      quantity: 12,
      unitPrice: 200,
    },
  ],
});

console.log('Invoice Number:', invoice.invoiceNumber);
console.log('Issue Date:', invoice.issuedOn);
console.log('QR Code:', invoice.qrCode);
```

## Invoice Carrier Types

### Mobile Barcode Carrier

```typescript
import { InvoiceCarriers } from '@rytass/invoice-adapter-ecpay';

// Issue invoice with mobile barcode carrier
const invoice = await invoiceGateway.issue({
  orderId: '2024081400002',
  customer: {
    email: 'customer@example.com',
  },
  carrier: InvoiceCarriers.MOBILE('/-F-K0PR'), // Mobile barcode
  items: [
    {
      name: 'Coffee Beans',
      quantity: 2,
      unitPrice: 350,
    },
  ],
});
```

### Digital Certificate (Moica) Carrier

```typescript
// Issue invoice with digital certificate carrier
const invoice = await invoiceGateway.issue({
  orderId: '2024081400003',
  customer: {
    email: 'customer@example.com',
  },
  carrier: InvoiceCarriers.MOICA('AB12345678901234'), // Digital certificate
  items: [
    {
      name: 'Online Course',
      quantity: 1,
      unitPrice: 2999,
    },
  ],
});
```

### Print Invoice

```typescript
// Issue print invoice (traditional paper invoice)
const invoice = await invoiceGateway.issue({
  orderId: '2024081400004',
  customer: {
    name: 'John Doe',
    address: 'No. 123, Section 1, Roosevelt Rd., Taipei City',
    email: 'customer@example.com',
  },
  carrier: InvoiceCarriers.PRINT,
  items: [
    {
      name: 'Hardware Component',
      quantity: 1,
      unitPrice: 5000,
    },
  ],
});
```

## Business-to-Business (B2B) Invoices

### VAT Number Invoice

```typescript
// Issue B2B invoice with VAT number
const businessInvoice = await invoiceGateway.issue({
  orderId: '2024081400005',
  vatNumber: '12345678', // Company VAT number
  carrier: InvoiceCarriers.PRINT, // B2B invoices must be print
  customer: {
    name: 'ABC Technology Co., Ltd.',
    address: 'No. 456, Section 2, Zhongshan N. Rd., Taipei City 104',
    email: 'accounting@abctech.com.tw',
  },
  items: [
    {
      name: 'Enterprise Software License',
      quantity: 10,
      unitPrice: 15000,
    },
    {
      name: 'Implementation Service',
      quantity: 1,
      unitPrice: 50000,
    },
  ],
});
```

### VAT Number Validation

```typescript
// Validate VAT number before invoice issuance
const [isValid, companyName] = await invoiceGateway.isValidGUI('12345678');

if (isValid) {
  console.log('Valid VAT number for:', companyName);
  // Proceed with B2B invoice
} else {
  console.log('Invalid VAT number');
}
```

## Carrier Validation

### Mobile Barcode Validation

```typescript
// Validate mobile barcode before use
const isValidMobile = await invoiceGateway.isMobileBarcodeValid('/-F-K0PR');

if (isValidMobile) {
  // Proceed with mobile carrier invoice
} else {
  console.log('Invalid mobile barcode');
}
```

### Love Code Validation

```typescript
// Validate donation code (love code)
const isValidLoveCode = await invoiceGateway.isLoveCodeValid('001');

if (isValidLoveCode) {
  // Issue donation invoice
  const donationInvoice = await invoiceGateway.issue({
    orderId: '2024081400006',
    customer: { email: 'donor@example.com' },
    loveCode: '001', // NPO donation code
    items: [
      {
        name: 'Charity Donation',
        quantity: 1,
        unitPrice: 1000,
      },
    ],
  });
} else {
  console.log('Invalid love code');
}
```

## Multi-Tax Type Support

### Mixed Tax Types

```typescript
import { TaxType } from '@rytass/invoice-adapter-ecpay';

// Invoice with mixed tax types
const mixedTaxInvoice = await invoiceGateway.issue({
  orderId: '2024081400007',
  customer: { email: 'customer@example.com' },
  items: [
    {
      name: 'Taxable Product',
      quantity: 1,
      unitPrice: 100,
      taxType: TaxType.TAXED,
    },
    {
      name: 'Tax-free Service',
      quantity: 1,
      unitPrice: 200,
      taxType: TaxType.TAX_FREE,
    },
    {
      name: 'Zero-rate Export',
      quantity: 1,
      unitPrice: 300,
      taxType: TaxType.ZERO_TAX,
    },
  ],
});
```

## Invoice Management

### Query Invoice

```typescript
// Query by invoice number and issue date
const invoiceByNumber = await invoiceGateway.query({
  invoiceNumber: 'ZZ12345678',
  issuedOn: new Date('2024-08-14'),
});

// Query by order ID
const invoiceByOrderId = await invoiceGateway.query({
  orderId: '2024081400001',
});

console.log('Invoice Status:', invoiceByOrderId.state);
console.log('Total Amount:', invoiceByOrderId.totalAmount);
```

### Void Invoice

```typescript
// Void (cancel) an issued invoice
const voidResult = await invoiceGateway.void({
  invoiceNumber: 'ZZ12345678',
  reason: 'Customer cancellation',
});

if (voidResult.success) {
  console.log('Invoice voided successfully');
} else {
  console.log('Void failed:', voidResult.message);
}
```

## Allowances (Credit Notes)

### Issue Allowance

```typescript
// First, query the invoice
const invoice = await invoiceGateway.query({
  invoiceNumber: 'ZZ12345678',
  issuedOn: new Date('2024-08-14'),
});

// Issue allowance for partial refund
const updatedInvoice = await invoiceGateway.allowance(
  invoice,
  [
    {
      name: 'Partial Refund - Product Return',
      quantity: 1,
      unitPrice: 500, // Allowance amount
    },
  ],
  {
    buyerName: 'Customer Name',
    notifyEmail: 'customer@example.com',
  },
);

console.log('Allowances:', updatedInvoice.allowances);
```

### Void Allowance

```typescript
// Void (invalidate) an issued allowance
// First get the allowance from the invoice
const allowance = invoice.allowances[0];
const updatedInvoice = await invoiceGateway.invalidAllowance(
  allowance,
  'Incorrect allowance amount',
);
```

## Configuration Options

### ECPayInvoiceGatewayOptions

| Property                      | Type      | Required | Default     | Description                    |
| ----------------------------- | --------- | -------- | ----------- | ------------------------------ |
| `merchantId`                  | `string`  | Yes      | -           | ECPay merchant ID              |
| `aesKey`                      | `string`  | Yes      | -           | AES encryption key             |
| `aesIv`                       | `string`  | Yes      | -           | AES initialization vector      |
| `baseUrl`                     | `string`  | No       | Development | API base URL                   |
| `skipMobileBarcodeValidation` | `boolean` | No       | `false`     | Skip mobile barcode validation |
| `skipLoveCodeValidation`      | `boolean` | No       | `false`     | Skip love code validation      |

## Environment Configuration

```bash
# .env
ECPAY_MERCHANT_ID=your_merchant_id
ECPAY_AES_KEY=your_aes_key
ECPAY_AES_IV=your_aes_iv
ECPAY_ENVIRONMENT=production # or development
```

```typescript
const invoiceGateway = new ECPayInvoiceGateway({
  merchantId: process.env.ECPAY_MERCHANT_ID!,
  aesKey: process.env.ECPAY_AES_KEY!,
  aesIv: process.env.ECPAY_AES_IV!,
  baseUrl:
    process.env.ECPAY_ENVIRONMENT === 'production'
      ? 'https://einvoice.ecpay.com.tw'
      : 'https://einvoice-stage.ecpay.com.tw',
});
```

## Advanced Features

### Custom Remark and Notes

```typescript
const invoice = await invoiceGateway.issue({
  orderId: '2024081400008',
  customer: { email: 'customer@example.com' },
  items: [{ name: 'Product', quantity: 1, unitPrice: 100 }],
  remark: 'Special order - urgent delivery',
  customerNote: 'Thank you for your business!',
});
```

### Customs Declaration

```typescript
import { CustomsMark } from '@rytass/invoice-adapter-ecpay';

// For export invoices requiring customs declaration
const exportInvoice = await invoiceGateway.issue({
  orderId: '2024081400009',
  customer: { email: 'international@example.com' },
  customsMark: CustomsMark.CUSTOMS_REQUIRED,
  items: [
    {
      name: 'Export Product',
      quantity: 5,
      unitPrice: 1000,
      taxType: TaxType.ZERO_TAX,
    },
  ],
});
```

### Batch Operations

```typescript
// Issue multiple invoices in sequence
const orderIds = ['ORDER_001', 'ORDER_002', 'ORDER_003'];
const invoices = [];

for (const orderId of orderIds) {
  const invoice = await invoiceGateway.issue({
    orderId,
    customer: { email: 'batch@example.com' },
    items: [{ name: 'Batch Item', quantity: 1, unitPrice: 100 }],
  });
  invoices.push(invoice);
}

console.log(`Successfully issued ${invoices.length} invoices`);
```

## Error Handling

```typescript
import { ECPayInvoiceError } from '@rytass/invoice-adapter-ecpay';

try {
  const invoice = await invoiceGateway.issue({
    orderId: 'INVALID_ORDER',
    customer: { email: 'invalid-email' },
    items: [],
  });
} catch (error) {
  if (error instanceof ECPayInvoiceError) {
    console.error('ECPay Error Code:', error.code);
    console.error('Error Message:', error.message);

    switch (error.code) {
      case '10000999':
        console.log('System error - please try again later');
        break;
      case '10002003':
        console.log('Invalid merchant ID');
        break;
      case '10002004':
        console.log('Duplicate order ID');
        break;
      default:
        console.log('Unknown error occurred');
    }
  }
}
```

## Testing

### Development Environment

```typescript
// Use ECPay's test environment
const testGateway = new ECPayInvoiceGateway({
  merchantId: '2000132', // Test merchant ID
  aesKey: 'ejCk326UnaZWKisg', // Test AES key
  aesIv: 'q9jcZX8Ib9LM8wYk', // Test AES IV
  baseUrl: 'https://einvoice-stage.ecpay.com.tw',
});

// Test invoice issuance
const testInvoice = await testGateway.issue({
  orderId: 'TEST_' + Date.now(),
  customer: { email: 'test@example.com' },
  items: [{ name: 'Test Product', quantity: 1, unitPrice: 100 }],
});
```

### Test Data

Use these test values for development:

- **Test Mobile Barcode**: `/-F-K0PR`
- **Test VAT Number**: `54366906`
- **Test Love Code**: `001`

## Integration Examples

### E-commerce Integration

```typescript
class OrderService {
  constructor(
    private invoiceGateway: ECPayInvoiceGateway,
    private orderRepository: OrderRepository,
  ) {}

  async completeOrder(orderId: string) {
    const order = await this.orderRepository.findById(orderId);

    if (!order) throw new Error('Order not found');

    try {
      // Issue invoice after payment confirmation
      const invoice = await this.invoiceGateway.issue({
        orderId: order.id,
        customer: {
          email: order.customerEmail,
          name: order.customerName,
        },
        items: order.items.map(item => ({
          name: item.productName,
          quantity: item.quantity,
          unitPrice: item.unitPrice,
        })),
        carrier: order.invoiceCarrier,
      });

      // Save invoice information
      await this.orderRepository.updateInvoice(orderId, {
        invoiceNumber: invoice.invoiceNumber,
        issuedOn: invoice.issuedOn,
      });

      return { success: true, invoice };
    } catch (error) {
      console.error('Invoice issuance failed:', error);
      return { success: false, error: error.message };
    }
  }
}
```

### NestJS Integration

```typescript
import { Injectable } from '@nestjs/common';
import { ECPayInvoiceGateway } from '@rytass/invoice-adapter-ecpay';

@Injectable()
export class InvoiceService {
  private readonly invoiceGateway: ECPayInvoiceGateway;

  constructor() {
    this.invoiceGateway = new ECPayInvoiceGateway({
      merchantId: process.env.ECPAY_MERCHANT_ID!,
      aesKey: process.env.ECPAY_AES_KEY!,
      aesIv: process.env.ECPAY_AES_IV!,
      baseUrl: process.env.ECPAY_INVOICE_BASE_URL!,
    });
  }

  async issueInvoice(orderData: any) {
    return await this.invoiceGateway.issue(orderData);
  }

  async queryInvoice(params: any) {
    return await this.invoiceGateway.query(params);
  }
}
```

## Best Practices

### Security

- Store sensitive credentials in environment variables
- Use production URLs only in production environment
- Implement proper error handling and logging
- Validate input data before API calls

### Performance

- Cache validation results for frequently used codes
- Implement retry mechanisms for network failures
- Use batch processing for multiple operations
- Monitor API rate limits

### Compliance

- Follow Taiwan electronic invoice regulations
- Ensure proper VAT calculations
- Maintain audit trails for all invoice operations
- Regular reconciliation with ECPay reports

## License

MIT