Skillspayment-adapters
P

payment-adapters

Taiwan payment integration (台灣支付整合). Use when working with ECPay (綠界), NewebPay (藍新), HwaNan Bank (華南銀行), CTBC (中信), iCash Pay, or Happy Card payment services. Covers credit card (信用卡), virtual account (虛擬帳號), ATM, CVS payment (超商代碼/條碼), card binding (卡片綁定), installments (分期付款), recurring payments (訂閱付款), and NestJS integration.

Rytass
7 stars
1.2k downloads
Updated 2w ago

Readme

payment-adapters follows the SKILL.md standard. Use the install command to add it to your agent stack.

---
name: payment-adapters
description: Taiwan payment integration (台灣支付整合). Use when working with ECPay (綠界), NewebPay (藍新), HwaNan Bank (華南銀行), CTBC (中信), iCash Pay, or Happy Card payment services. Covers credit card (信用卡), virtual account (虛擬帳號), ATM, CVS payment (超商代碼/條碼), card binding (卡片綁定), installments (分期付款), recurring payments (訂閱付款), and NestJS integration.
---

# Taiwan Payment Adapters

This skill provides comprehensive guidance for using `@rytass/payments-adapter-*` packages to integrate Taiwan payment service providers.

## Overview

All adapters implement the `PaymentGateway` interface from `@rytass/payments`, providing a unified API across different providers:

| Package | Provider | Description |
|---------|----------|-------------|
| `@rytass/payments-adapter-ecpay` | ECPay (綠界科技) | Most popular Taiwan payment gateway |
| `@rytass/payments-adapter-newebpay` | NewebPay (藍新金流) | Also known as EZPay, supports multiple payment methods |
| `@rytass/payments-adapter-hwanan` | HwaNan Bank (華南銀行) | Bank-integrated payment service |
| `@rytass/payments-adapter-ctbc-micro-fast-pay` | CTBC (中國信託) | CTBC Micro Fast Pay integration |
| `@rytass/payments-adapter-icash-pay` | iCash Pay (愛金卡) | Unified group payment service |
| `@rytass/payments-adapter-happy-card` | Happy Card (統一禮物卡) | Unified group gift card payment |

### Base Interface (@rytass/payments)

All adapters share these core concepts:

**PaymentGateway** - Main interface for payment operations
```typescript
interface PaymentGateway<OCM extends OrderCommitMessage> {
  emitter: EventEmitter;
  prepare<N extends OCM>(input: InputFromOrderCommitMessage<N>): Promise<Order<N>>;
  query<OO extends Order>(id: string, options?: unknown): Promise<OO>;
}
```

**Order Lifecycle** - All orders follow this state machine:
```
INITED → PRE_COMMIT → [ASYNC_INFO_RETRIEVED] → COMMITTED/FAILED → REFUNDED
```

**Payment Channels** - Supported payment methods:
- `CREDIT_CARD` - Credit card payments (信用卡)
- `VIRTUAL_ACCOUNT` - Virtual account transfer (虛擬帳號)
- `WEB_ATM` - Web ATM transfer (網路 ATM)
- `CVS_KIOSK` - Convenience store code payment (超商代碼)
- `CVS_BARCODE` - Convenience store barcode payment (超商條碼)
- `APPLE_PAY` - Apple Pay integration
- `LINE_PAY` - LINE Pay integration

## Installation

```bash
# Install base package
npm install @rytass/payments

# Choose the adapter for your provider
npm install @rytass/payments-adapter-ecpay
npm install @rytass/payments-adapter-newebpay
npm install @rytass/payments-adapter-hwanan
npm install @rytass/payments-adapter-ctbc-micro-fast-pay
npm install @rytass/payments-adapter-icash-pay
npm install @rytass/payments-adapter-happy-card

# For NestJS integration
npm install @rytass/payments-nestjs-module
```

## Quick Start

### ECPay (綠界)

```typescript
import { ECPayPayment, Channel, PaymentEvents } from '@rytass/payments-adapter-ecpay';

// Initialize gateway
const gateway = new ECPayPayment({
  merchantId: process.env.ECPAY_MERCHANT_ID!,
  hashKey: process.env.ECPAY_HASH_KEY!,
  hashIv: process.env.ECPAY_HASH_IV!,
  serverHost: 'https://your-domain.com', // Your server URL for callbacks
  withServer: true, // Enable built-in callback server
});

// Listen to payment events
gateway.emitter.on(PaymentEvents.ORDER_COMMITTED, (order) => {
  console.log('Payment successful:', order.id, order.totalPrice);
});

gateway.emitter.on(PaymentEvents.ORDER_FAILED, (failure) => {
  console.error('Payment failed:', failure.code, failure.message);
});

// Prepare payment order
const order = await gateway.prepare({
  channel: Channel.CREDIT_CARD,
  items: [
    { name: 'Product A', unitPrice: 1000, quantity: 2 },
    { name: 'Product B', unitPrice: 500, quantity: 1 },
  ],
  clientBackUrl: 'https://your-site.com/payment/return', // User return URL
});

// Get checkout URL (3 ways)
const checkoutUrl = order.checkoutURL; // Built-in server URL (recommended)
const autoSubmitHtml = order.formHTML; // HTML with auto-submit form
const formData = order.form; // Raw form data for custom implementation
```

### NewebPay (藍新)

```typescript
import { NewebPayPayment, NewebPaymentChannel } from '@rytass/payments-adapter-newebpay';

const gateway = new NewebPayPayment({
  merchantId: process.env.NEWEBPAY_MERCHANT_ID!,
  aesKey: process.env.NEWEBPAY_AES_KEY!,
  aesIv: process.env.NEWEBPAY_AES_IV!,
  serverHost: 'https://your-domain.com',
  withServer: true,
});

const order = await gateway.prepare({
  channel: NewebPaymentChannel.CREDIT,  // 注意:使用 NewebPaymentChannel 而非 Channel
  items: [{ name: 'Service Fee', unitPrice: 3000, quantity: 1 }],
});

console.log('Checkout URL:', order.checkoutURL);
```

### HwaNan Bank (華南銀行)

```typescript
import { HwaNanPayment, HwaNanPaymentChannel } from '@rytass/payments-adapter-hwanan';

const gateway = new HwaNanPayment({
  merchantId: process.env.HWANAN_MERCHANT_ID!,
  terminalId: process.env.HWANAN_TERMINAL_ID!,
  merID: process.env.HWANAN_MER_ID!,
  identifier: process.env.HWANAN_IDENTIFIER!,
  merchantName: 'My Store Name',
  withServer: true,
});

const order = await gateway.prepare({
  channel: HwaNanPaymentChannel.CREDIT_CARD,  // 注意:使用 HwaNanPaymentChannel
  items: [{ name: 'Purchase', unitPrice: 5000, quantity: 1 }],
});
```

### CTBC (中信)

```typescript
import { CTBCPayment, Channel } from '@rytass/payments-adapter-ctbc-micro-fast-pay';

const gateway = new CTBCPayment({
  merchantId: process.env.CTBC_MERCHANT_ID!,
  merId: process.env.CTBC_MER_ID!,
  txnKey: process.env.CTBC_TXN_KEY!,
  terminalId: process.env.CTBC_TERMINAL_ID!,
  withServer: true,
});

const order = await gateway.prepare({
  channel: Channel.CREDIT_CARD,
  items: [{ name: 'Item', unitPrice: 2000, quantity: 1 }],
});
```

### iCash Pay (愛金卡)

```typescript
import { ICashPayPayment, ICashPayBaseUrls } from '@rytass/payments-adapter-icash-pay';

const gateway = new ICashPayPayment({
  baseUrl: ICashPayBaseUrls.DEVELOPMENT, // 必填:環境設定
  merchantId: process.env.ICASH_PAY_MERCHANT_ID!,
  clientPrivateKey: process.env.ICASH_PAY_CLIENT_PRIVATE_KEY!,
  serverPublicKey: process.env.ICASH_PAY_SERVER_PUBLIC_KEY!,
  aesKey: {
    id: process.env.ICASH_PAY_AES_KEY_ID!,
    key: process.env.ICASH_PAY_AES_KEY!,
    iv: process.env.ICASH_PAY_AES_IV!,
  },
});

// iCash Pay uses barcode scanning
const order = await gateway.prepare({
  id: 'ORDER-001',
  storeName: 'My Coffee Shop',
  barcode: '280012345678901234', // 18-digit barcode from customer
  amount: 150,
  items: [{ name: 'Americano', unitPrice: 120, quantity: 1 }],
  collectedAmount: 150,
});

const result = await order.commit();
```

### Happy Card (統一禮物卡)

```typescript
import { HappyCardPayment, HappyCardRecordType } from '@rytass/payments-adapter-happy-card';

const gateway = new HappyCardPayment({
  cSource: process.env.HAPPY_CARD_C_SOURCE!,
  key: process.env.HAPPY_CARD_KEY!,
});

// Query card balance first (get detailed records)
const [records, productType] = await gateway.getCardBalance('HC1234567890123456', true);
console.log('Product type:', productType);
records.forEach(record => {
  console.log(`Record ${record.id}: Type=${record.type}, Amount=${record.amount}`);
});

// Make payment using card
const order = await gateway.prepare({
  id: 'HAPPY-ORDER-001',
  cardSerial: 'HC1234567890123456',
  items: [{ name: 'Coffee', unitPrice: 150, quantity: 2 }],
  useRecords: [
    { id: 12345, type: HappyCardRecordType.BONUS, amount: 300 },
  ],
});

const result = await order.commit(); // Immediate deduction
```

## Common Patterns

### Event-Driven Architecture

All payment adapters use `EventEmitter` for asynchronous notifications:

```typescript
import { PaymentEvents } from '@rytass/payments';

// Payment success
gateway.emitter.on(PaymentEvents.ORDER_COMMITTED, (order) => {
  console.log('Order ID:', order.id);
  console.log('Total Price:', order.totalPrice);
  console.log('Committed At:', order.committedAt);
  console.log('Additional Info:', order.additionalInfo); // Card info, bank code, etc.

  // Update database, send notifications, etc.
  updateOrderStatus(order.id, 'PAID');
  sendReceiptEmail(order);
});

// Payment failure
gateway.emitter.on(PaymentEvents.ORDER_FAILED, (failure) => {
  console.error('Failed Order ID:', failure.id);
  console.error('Error Code:', failure.code);
  console.error('Error Message:', failure.message);

  // Handle failure
  updateOrderStatus(failure.id, 'FAILED');
  notifyCustomer(failure);
});

// Async payment info retrieved (Virtual Account, CVS codes)
gateway.emitter.on(PaymentEvents.ORDER_INFO_RETRIEVED, (order) => {
  if (order.asyncInfo?.channel === Channel.VIRTUAL_ACCOUNT) {
    console.log('Bank Code:', order.asyncInfo.bankCode);
    console.log('Account Number:', order.asyncInfo.account);
    console.log('Expires At:', order.asyncInfo.expiredAt);

    // Send virtual account info to customer
    sendVirtualAccountEmail({
      bankCode: order.asyncInfo.bankCode,
      account: order.asyncInfo.account,
      amount: order.totalPrice,
      expiredAt: order.asyncInfo.expiredAt,
    });
  }

  if (order.asyncInfo?.channel === Channel.CVS_KIOSK) {
    console.log('Payment Code:', order.asyncInfo.paymentCode);
    console.log('Expires At:', order.asyncInfo.expiredAt);

    // Send CVS code to customer
    sendCVSCodeSMS(order.asyncInfo.paymentCode);
  }
});

// Card binding success
gateway.emitter.on(PaymentEvents.CARD_BOUND, (bindRequest) => {
  console.log('Member ID:', bindRequest.memberId);
  console.log('Card ID:', bindRequest.cardId);
  console.log('Card Suffix:', bindRequest.cardNumberSuffix);

  // Save card info to database
  saveCardToDatabase({
    memberId: bindRequest.memberId,
    cardId: bindRequest.cardId,
    lastFourDigits: bindRequest.cardNumberSuffix,
  });
});

// Card binding failure
gateway.emitter.on(PaymentEvents.CARD_BINDING_FAILED, (bindRequest) => {
  console.error('Binding failed for member:', bindRequest.memberId);
  console.error('Error:', bindRequest.failedMessage);
});
```

### Order Lifecycle

Understanding the order state machine:

```typescript
import { OrderState } from '@rytass/payments';

// 1. INITED - Order object created but not sent to gateway
const order = await gateway.prepare({ ... });
console.log(order.state); // OrderState.INITED

// Order becomes PRE_COMMIT after prepare() returns
console.log(order.state); // OrderState.PRE_COMMIT

// 2. For sync payments (Credit Card):
// User completes payment → Gateway sends callback → commit() called → COMMITTED

// 3. For async payments (Virtual Account, CVS):
// prepare() → ASYNC_INFO_RETRIEVED → User pays → COMMITTED

// 4. Payment can fail at any time → FAILED
// Check for failure:
if (order.state === OrderState.FAILED) {
  console.error('Reason:', order.failedMessage?.message);
}

// 5. After committed, can refund → REFUNDED (if supported)
if (order.state === OrderState.COMMITTED) {
  await order.refund(); // Full refund
  await order.refund(500); // Partial refund (if supported)
}
```

### Credit Card Payment Flow

Complete flow from preparation to completion:

```typescript
// 1. Prepare order
const order = await gateway.prepare({
  channel: Channel.CREDIT_CARD,
  items: [{ name: 'Product', unitPrice: 1000, quantity: 1 }],
  clientBackUrl: 'https://your-site.com/payment/return',
});

// 2. Three ways to get checkout:
// Option A: Built-in server URL (recommended if withServer: true)
res.redirect(order.checkoutURL);

// Option B: Auto-submit HTML
res.send(order.formHTML);

// Option C: Custom form implementation
res.render('checkout', { formData: order.form });

// 3. User completes payment on payment gateway page

// 4. Gateway sends callback to your server
// If withServer: true, this is handled automatically
// The ORDER_COMMITTED event will fire:

gateway.emitter.on(PaymentEvents.ORDER_COMMITTED, (order) => {
  // Order is now paid!
  console.log('Payment completed:', order.id);
  console.log('Card info:', {
    lastFour: order.additionalInfo.card4Number,
    authCode: order.additionalInfo.authCode,
    eci: order.additionalInfo.eci,
  });
});

// 5. User is redirected to clientBackUrl
// You can query the order status:
const updatedOrder = await gateway.query(order.id);
console.log('Final state:', updatedOrder.state);
console.log('Is paid:', updatedOrder.state === OrderState.COMMITTED);
```

### Virtual Account Payment Flow

Async payment with bank transfer:

```typescript
// 1. Prepare virtual account order
const order = await gateway.prepare({
  channel: Channel.VIRTUAL_ACCOUNT,
  items: [{ name: 'Bulk Purchase', unitPrice: 50000, quantity: 1 }],
  virtualAccountExpireDays: 7, // Account expires in 7 days
});

// 2. Listen for virtual account info
gateway.emitter.on(PaymentEvents.ORDER_INFO_RETRIEVED, (order) => {
  if (order.asyncInfo?.channel === Channel.VIRTUAL_ACCOUNT) {
    const { bankCode, account, expiredAt } = order.asyncInfo;

    console.log('=== Virtual Account Info ===');
    console.log('Bank Code:', bankCode); // e.g., '004' (Taiwan Bank)
    console.log('Account:', account); // e.g., '88801234567890'
    console.log('Amount:', order.totalPrice);
    console.log('Expires:', expiredAt);

    // Send to customer via email/SMS
    sendVirtualAccountNotification({
      orderId: order.id,
      bankCode,
      account,
      amount: order.totalPrice,
      expiredAt,
    });
  }
});

// 3. User makes bank transfer

// 4. When payment is received, ORDER_COMMITTED event fires
gateway.emitter.on(PaymentEvents.ORDER_COMMITTED, (order) => {
  console.log('Virtual account payment received!');
  console.log('Paid from bank:', order.additionalInfo.buyerBankCode);
  console.log('Account:', order.additionalInfo.buyerAccountNumber);

  // Fulfill order
  processOrder(order.id);
});
```

### CVS Payment Flow

Convenience store code/barcode payment:

```typescript
// CVS Kiosk Code Payment (超商代碼)
const cvsOrder = await gateway.prepare({
  channel: Channel.CVS_KIOSK,
  items: [{ name: 'Online Course', unitPrice: 1200, quantity: 1 }],
});

gateway.emitter.on(PaymentEvents.ORDER_INFO_RETRIEVED, (order) => {
  if (order.asyncInfo?.channel === Channel.CVS_KIOSK) {
    console.log('Payment Code:', order.asyncInfo.paymentCode); // e.g., '1234567890'
    console.log('Expires:', order.asyncInfo.expiredAt);

    // Customer can pay at 7-Eleven, FamilyMart, OK Mart, Hi-Life
    sendCVSCodeSMS(order.asyncInfo.paymentCode);
  }
});

// CVS Barcode Payment (超商條碼)
const barcodeOrder = await gateway.prepare({
  channel: Channel.CVS_BARCODE,
  items: [{ name: 'Magazine', unitPrice: 280, quantity: 1 }],
});

gateway.emitter.on(PaymentEvents.ORDER_INFO_RETRIEVED, (order) => {
  if (order.asyncInfo?.channel === Channel.CVS_BARCODE) {
    const [barcode1, barcode2, barcode3] = order.asyncInfo.barcodes;
    console.log('Barcode 1:', barcode1);
    console.log('Barcode 2:', barcode2);
    console.log('Barcode 3:', barcode3);

    // Send barcodes to customer (display on page or send via email)
    sendBarcodesEmail([barcode1, barcode2, barcode3]);
  }
});
```

### Query Order Status

Check payment status at any time:

```typescript
// Query by order ID
const order = await gateway.query('ORDER-2024-001');

console.log('Order State:', order.state);
console.log('Created At:', order.createdAt);
console.log('Committed At:', order.committedAt);
console.log('Total Price:', order.totalPrice);

// Check state
if (order.state === OrderState.COMMITTED) {
  console.log('Payment completed!');
  console.log('Additional Info:', order.additionalInfo);
}

if (order.state === OrderState.FAILED) {
  console.error('Payment failed!');
  console.error('Error Code:', order.failedMessage?.code);
  console.error('Error Message:', order.failedMessage?.message);
}

if (order.state === OrderState.PRE_COMMIT) {
  console.log('Waiting for payment...');
}

if (order.state === OrderState.ASYNC_INFO_RETRIEVED) {
  console.log('Virtual account/CVS code generated, waiting for customer payment');
  console.log('Payment Info:', order.asyncInfo);
}
```

## Advanced Features

### Card Binding (卡片綁定)

Bind credit cards for future one-click payments:

```typescript
import { PaymentEvents } from '@rytass/payments';

// Step 1: Prepare card binding request
const bindRequest = await gateway.prepareBindCard('MEMBER_123');

console.log('Binding URL:', bindRequest.checkoutURL);

// Redirect user to binding URL
res.redirect(bindRequest.checkoutURL);

// Step 2: Listen for binding result
gateway.emitter.on(PaymentEvents.CARD_BOUND, (bindRequest) => {
  console.log('Card bound successfully!');
  console.log('Member ID:', bindRequest.memberId);
  console.log('Card ID:', bindRequest.cardId); // Save this for future use
  console.log('Last 4 digits:', bindRequest.cardNumberSuffix);

  // Save to database
  await db.cards.create({
    memberId: bindRequest.memberId,
    cardId: bindRequest.cardId,
    lastFourDigits: bindRequest.cardNumberSuffix,
    createdAt: new Date(),
  });
});

// Step 3: Use bound card for payment
const boundOrder = await gateway.checkoutWithBoundCard({
  memberId: 'MEMBER_123',
  cardId: 'CARD_ID_FROM_BINDING',
  items: [{ name: 'Subscription', unitPrice: 999, quantity: 1 }],
  orderId: 'SUBSCRIPTION-001', // Optional
});

console.log('Payment result:', boundOrder);

// Alternative: Bind card with transaction (ECPay only)
gateway.emitter.on(PaymentEvents.ORDER_COMMITTED, async (order) => {
  // After successful payment, bind the card used
  const bindRequest = await gateway.bindCardWithTransaction(
    'MEMBER_123',
    order.platformTradeNumber,
    order.id
  );

  console.log('Card bound with transaction:', bindRequest.cardId);
});
```

### Memory Card vs Card Binding

Understanding the difference:

```typescript
// Memory Card (記憶卡) - Gateway remembers card for single user session
const memoryPayment = new ECPayPayment({
  merchantId: 'YOUR_MERCHANT_ID',
  hashKey: 'YOUR_KEY',
  hashIv: 'YOUR_IV',
  memory: true, // Enable memory card
});

// User only needs to enter card once per session
// Subsequent payments in same session can reuse card
// Card is NOT saved permanently
// Cannot use bindCardWithTransaction when memory: true

// Card Binding (卡片綁定) - Permanently save card for future use
const bindingPayment = new ECPayPayment({
  merchantId: 'YOUR_MERCHANT_ID',
  hashKey: 'YOUR_KEY',
  hashIv: 'YOUR_IV',
  memory: false, // Must be false for binding
});

// Card is saved permanently
// Can use across sessions and devices
// Requires prepareBindCard() or bindCardWithTransaction()
// User must consent to saving card
```

### Installment Payments (分期付款)

Credit card installment options:

```typescript
// ECPay installments
const installmentPayment = new ECPayPayment({
  merchantId: 'YOUR_MERCHANT_ID',
  hashKey: 'YOUR_KEY',
  hashIv: 'YOUR_IV',
  installments: '3,6,12,18,24', // Available installment periods
});

const order = await installmentPayment.prepare({
  channel: Channel.CREDIT_CARD,
  items: [{ name: 'Laptop', unitPrice: 30000, quantity: 1 }],
  // User can choose 3, 6, 12, 18, or 24 installments on payment page
});

// CTBC installments
const ctbcPayment = new CTBCPayment({
  merchantId: 'YOUR_MERCHANT_ID',
  merId: 'YOUR_MER_ID',
  txnKey: 'YOUR_KEY',
  terminalId: 'YOUR_TERMINAL',
  installmentCount: 12, // Fixed 12 installments
});

// HwaNan installments
const hwananPayment = new HwaNanPayment({
  merchantId: 'YOUR_MERCHANT_ID',
  terminalId: 'YOUR_TERMINAL',
  merID: 'YOUR_MER_ID',
  identifier: 'YOUR_IDENTIFIER',
  installmentAmount: 5000, // Minimum amount for installments
});
```

### Recurring Payments (訂閱付款)

Set up periodic payments:

```typescript
import { PaymentPeriodType } from '@rytass/payments';

// ECPay recurring payment
const recurringPayment = new ECPayPayment({
  merchantId: 'YOUR_MERCHANT_ID',
  hashKey: 'YOUR_KEY',
  hashIv: 'YOUR_IV',
  period: {
    amountPerPeriod: 999, // Amount per charge
    type: PaymentPeriodType.MONTH, // DAY, MONTH, or YEAR
    frequency: 1, // Charge every 1 month
    times: 12, // Total 12 charges
  },
});

const order = await recurringPayment.prepare({
  channel: Channel.CREDIT_CARD,
  items: [{ name: 'Monthly Subscription', unitPrice: 999, quantity: 1 }],
});

// Gateway will automatically charge 999 every month for 12 months

// Examples:
// Daily payment for 30 days
period: {
  amountPerPeriod: 50,
  type: PaymentPeriodType.DAY,
  frequency: 1,
  times: 30,
}

// Weekly payment (every 7 days) for 8 weeks
period: {
  amountPerPeriod: 200,
  type: PaymentPeriodType.DAY,
  frequency: 7,
  times: 8,
}

// Quarterly payment for 1 year (4 times)
period: {
  amountPerPeriod: 3000,
  type: PaymentPeriodType.MONTH,
  frequency: 3,
  times: 4,
}
```

### Built-in Server & Ngrok

Handle callbacks with built-in server:

```typescript
// Option 1: Built-in server with public domain
const gateway = new ECPayPayment({
  merchantId: 'YOUR_MERCHANT_ID',
  hashKey: 'YOUR_KEY',
  hashIv: 'YOUR_IV',
  serverHost: 'https://your-domain.com',
  withServer: true, // Enable built-in server
});

// Server automatically handles callbacks at:
// https://your-domain.com/payments/callbacks

// Option 2: Built-in server with Ngrok (for development)
const gateway = new ECPayPayment({
  merchantId: 'YOUR_MERCHANT_ID',
  hashKey: 'YOUR_KEY',
  hashIv: 'YOUR_IV',
  withServer: 'ngrok', // Auto-start ngrok tunnel
});

// Listen for server ready
gateway.emitter.on(PaymentEvents.SERVER_LISTENED, (info) => {
  console.log('Server listening on:', info.url);
  // e.g., "https://abc123.ngrok.io"
});

// Option 3: Custom server (no built-in server)
const gateway = new ECPayPayment({
  merchantId: 'YOUR_MERCHANT_ID',
  hashKey: 'YOUR_KEY',
  hashIv: 'YOUR_IV',
  withServer: false,
});

// Implement your own callback endpoint
app.post('/payment/callback', (req, res) => {
  gateway.defaultServerListener(req, res);
});
```

## NestJS Integration

Complete integration with NestJS dependency injection:

### Basic Setup

```typescript
// app.module.ts
import { Module } from '@nestjs/common';
import { PaymentsModule } from '@rytass/payments-nestjs-module';
import { ECPayPayment } from '@rytass/payments-adapter-ecpay';

@Module({
  imports: [
    PaymentsModule.forRoot({
      paymentGateway: new ECPayPayment({
        merchantId: process.env.ECPAY_MERCHANT_ID!,
        hashKey: process.env.ECPAY_HASH_KEY!,
        hashIv: process.env.ECPAY_HASH_IV!,
        serverHost: process.env.SERVER_HOST!,
        withServer: true,
      }),
    }),
  ],
})
export class AppModule {}
```

### Async Configuration

```typescript
// app.module.ts
import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { PaymentsModule } from '@rytass/payments-nestjs-module';
import { ECPayPayment } from '@rytass/payments-adapter-ecpay';

@Module({
  imports: [
    ConfigModule.forRoot(),
    PaymentsModule.forRootAsync({
      imports: [ConfigModule],
      inject: [ConfigService],
      useFactory: (config: ConfigService) => ({
        paymentGateway: new ECPayPayment({
          merchantId: config.get('ECPAY_MERCHANT_ID')!,
          hashKey: config.get('ECPAY_HASH_KEY')!,
          hashIv: config.get('ECPAY_HASH_IV')!,
          serverHost: config.get('SERVER_HOST')!,
          withServer: true,
        }),
      }),
    }),
  ],
})
export class AppModule {}
```

### Using in Services

```typescript
// payment.service.ts
import { Injectable, Inject, Logger } from '@nestjs/common';
import { PaymentGateway, Channel, PaymentEvents, OrderState } from '@rytass/payments';
import { PAYMENTS_GATEWAY } from '@rytass/payments-nestjs-module';

@Injectable()
export class PaymentService {
  private readonly logger = new Logger(PaymentService.name);

  constructor(
    @Inject(PAYMENTS_GATEWAY)
    private readonly paymentGateway: PaymentGateway,
  ) {
    this.setupEventListeners();
  }

  private setupEventListeners() {
    this.paymentGateway.emitter?.on(PaymentEvents.ORDER_COMMITTED, (message) => {
      this.logger.log(`Payment committed: ${message.id}`);
      this.handlePaymentSuccess(message);
    });

    this.paymentGateway.emitter?.on(PaymentEvents.ORDER_FAILED, (failure) => {
      this.logger.error(`Payment failed: ${failure.code} - ${failure.message}`);
      this.handlePaymentFailure(failure);
    });
  }

  async createPayment(data: {
    itemName: string;
    amount: number;
    quantity?: number;
  }) {
    const order = await this.paymentGateway.prepare({
      channel: Channel.CREDIT_CARD,
      items: [{
        name: data.itemName,
        unitPrice: data.amount,
        quantity: data.quantity || 1,
      }],
    });

    return {
      orderId: order.id,
      checkoutUrl: order.checkoutURL,
      totalAmount: order.totalPrice,
    };
  }

  async queryPayment(orderId: string) {
    const order = await this.paymentGateway.query(orderId);

    return {
      orderId: order.id,
      state: order.state,
      isPaid: order.state === OrderState.COMMITTED,
      totalAmount: order.totalPrice,
      committedAt: order.committedAt,
    };
  }

  private async handlePaymentSuccess(message: any) {
    // Update database, send notifications, etc.
  }

  private async handlePaymentFailure(failure: any) {
    // Handle failure logic
  }
}
```

### Built-in Endpoints

PaymentsModule automatically provides these endpoints:

| Method | Endpoint | Description |
|--------|----------|-------------|
| `GET` | `/payments/checkout/:orderNo` | Payment checkout page |
| `POST` | `/payments/callbacks` | Payment gateway callbacks |

These endpoints are automatically marked as public (no authentication required).

### Multiple Gateways

Use multiple payment gateways in one application:

```typescript
// payments.config.ts
import { Module } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { ECPayPayment } from '@rytass/payments-adapter-ecpay';
import { NewebPayPayment } from '@rytass/payments-adapter-newebpay';

@Module({
  providers: [
    {
      provide: 'ECPAY_GATEWAY',
      useFactory: (config: ConfigService) => new ECPayPayment({
        merchantId: config.get('ECPAY_MERCHANT_ID'),
        hashKey: config.get('ECPAY_HASH_KEY'),
        hashIv: config.get('ECPAY_HASH_IV'),
        withServer: true,
      }),
      inject: [ConfigService],
    },
    {
      provide: 'NEWEBPAY_GATEWAY',
      useFactory: (config: ConfigService) => new NewebPayPayment({
        merchantId: config.get('NEWEBPAY_MERCHANT_ID'),
        aesKey: config.get('NEWEBPAY_AES_KEY'),
        aesIv: config.get('NEWEBPAY_AES_IV'),
        withServer: true,
      }),
      inject: [ConfigService],
    },
  ],
  exports: ['ECPAY_GATEWAY', 'NEWEBPAY_GATEWAY'],
})
export class PaymentsConfigModule {}

// Use in service
@Injectable()
export class MultiGatewayService {
  constructor(
    @Inject('ECPAY_GATEWAY') private ecpay: any,
    @Inject('NEWEBPAY_GATEWAY') private newebpay: any,
  ) {}

  async createPayment(provider: 'ecpay' | 'newebpay', data: any) {
    const gateway = provider === 'ecpay' ? this.ecpay : this.newebpay;
    return await gateway.prepare(data);
  }
}
```

## Feature Comparison

| Feature | ECPay | NewebPay | HwaNan | CTBC | iCash Pay | Happy Card |
|---------|:-----:|:--------:|:------:|:----:|:---------:|:----------:|
| **Credit Card** | Yes | Yes | Yes | Yes | Yes | No |
| **Installments** | Yes | Yes | Yes | Yes | No | No |
| **Virtual Account** | Yes | Yes | No | Yes | No | No |
| **Web ATM** | Yes | Yes | No | No | No | No |
| **CVS Code** | Yes | Yes | No | Yes | No | No |
| **CVS Barcode** | Yes | No | No | Yes | No | No |
| **Apple Pay** | Yes | No | No | Yes | No | No |
| **Card Binding** | Yes | Yes | No | Yes | No | No |
| **Memory Card** | Yes | Yes | No | No | No | No |
| **Recurring Payment** | Yes | No | No | No | No | No |
| **Built-in Server** | Yes | Yes | Yes | Yes | No | No |
| **Ngrok Support** | Yes | Yes | Yes | Yes | No | No |
| **Barcode Scan** | No | No | No | No | Yes | No |
| **Gift Card Balance** | No | No | No | No | No | Yes |
| **Bonus Points** | No | No | No | No | No | Yes |
| **Multi-language** | No | Yes | Yes | No | No | No |
| **Refund** | Partial | Partial | No | No | Yes | Yes |

### Signature Methods

| Provider | Method | Algorithm |
|----------|--------|-----------|
| ECPay | SHA256 Hash | SHA256 with URL encoding |
| NewebPay | AES Encryption | AES-256-CBC |
| HwaNan | MAC Signature | MD5/SHA256 |
| CTBC | MAC/TXN | Proprietary algorithm |
| iCash Pay | RSA + AES | RSA-SHA256 + AES-256-CBC |
| Happy Card | MD5 Hash | MD5 signature |

## Environment Configuration

### Development vs Production

All adapters support environment switching:

```typescript
// ECPay
import { ECPayPayment } from '@rytass/payments-adapter-ecpay';

// Development (default)
const devGateway = new ECPayPayment(); // Uses test credentials

// Production
const prodGateway = new ECPayPayment({
  baseUrl: 'https://payment.ecpay.com.tw',
  merchantId: 'YOUR_PROD_MERCHANT_ID',
  hashKey: 'YOUR_PROD_HASH_KEY',
  hashIv: 'YOUR_PROD_HASH_IV',
});

// NewebPay
import { NewebPayPayment } from '@rytass/payments-adapter-newebpay';

// 預設為測試環境: https://ccore.newebpay.com
const prodGateway = new NewebPayPayment({
  baseUrl: 'https://core.newebpay.com',  // 正式環境
  merchantId: 'YOUR_PROD_MERCHANT_ID',
  aesKey: 'YOUR_PROD_AES_KEY',
  aesIv: 'YOUR_PROD_AES_IV',
});

// Happy Card
import { HappyCardPayment, HappyCardBaseUrls } from '@rytass/payments-adapter-happy-card';

const prodGateway = new HappyCardPayment({
  baseUrl: HappyCardBaseUrls.PRODUCTION,
  cSource: 'YOUR_PROD_C_SOURCE',
  key: 'YOUR_PROD_KEY',
});
```

### Environment Variables

```bash
# .env
NODE_ENV=production

# ECPay
ECPAY_MERCHANT_ID=your_merchant_id
ECPAY_HASH_KEY=your_hash_key
ECPAY_HASH_IV=your_hash_iv

# NewebPay
NEWEBPAY_MERCHANT_ID=your_merchant_id
NEWEBPAY_AES_KEY=your_aes_key
NEWEBPAY_AES_IV=your_aes_iv

# HwaNan
HWANAN_MERCHANT_ID=your_merchant_id
HWANAN_TERMINAL_ID=your_terminal_id
HWANAN_MER_ID=your_mer_id
HWANAN_IDENTIFIER=your_identifier

# CTBC
CTBC_MERCHANT_ID=your_merchant_id
CTBC_MER_ID=your_mer_id
CTBC_TXN_KEY=your_txn_key
CTBC_TERMINAL_ID=your_terminal_id

# iCash Pay
ICASH_PAY_MERCHANT_ID=your_merchant_id
ICASH_PAY_CLIENT_PRIVATE_KEY=your_private_key
ICASH_PAY_SERVER_PUBLIC_KEY=server_public_key
ICASH_PAY_AES_KEY_ID=your_aes_key_id
ICASH_PAY_AES_KEY=your_32_char_aes_key
ICASH_PAY_AES_IV=your_16_char_aes_iv

# Happy Card
HAPPY_CARD_C_SOURCE=your_c_source
HAPPY_CARD_KEY=your_key

# Server
SERVER_HOST=https://your-domain.com
```

## Troubleshooting

### Common Issues

#### 1. Signature Verification Failed

**Symptoms:**
- Payment fails immediately
- Error message contains "signature", "checksum", or "verification"
- Callback returns error

**Solutions:**
```typescript
// Check credentials
console.log('Merchant ID:', process.env.ECPAY_MERCHANT_ID);
console.log('Hash Key length:', process.env.ECPAY_HASH_KEY?.length);
console.log('Hash IV length:', process.env.ECPAY_HASH_IV?.length);

// Ensure no extra spaces in .env file
ECPAY_HASH_KEY=your_key_here  # ❌ Trailing space
ECPAY_HASH_KEY=your_key_here   # ✅ No trailing space

// Check baseUrl matches environment
const gateway = new ECPayPayment({
  baseUrl: 'https://payment-stage.ecpay.com.tw', // Test environment
  // Use production URL for production
});
```

#### 2. Callback Not Received

**Symptoms:**
- Payment completes but ORDER_COMMITTED never fires
- Order stuck in PRE_COMMIT state

**Solutions:**
```typescript
// 1. Check withServer setting
const gateway = new ECPayPayment({
  withServer: true, // Must be true for built-in server
  serverHost: 'https://your-domain.com', // Must be accessible from internet
});

// 2. Verify server is listening
gateway.emitter.on(PaymentEvents.SERVER_LISTENED, (info) => {
  console.log('Server listening:', info.url);
  // Ensure this URL is accessible from payment gateway
});

// 3. Test callback endpoint manually
curl -X POST https://your-domain.com/payments/callbacks \
  -H "Content-Type: application/json" \
  -d '{"test": "data"}'

// 4. Check firewall/nginx/load balancer
// Ensure POST requests to /payments/callbacks are allowed
```

#### 3. Ngrok Tunnel Issues

**Symptoms:**
- Ngrok tunnel fails to start
- Ngrok URL changes frequently

**Solutions:**
```typescript
// 1. Use environment variable for ngrok auth
process.env.NGROK_AUTHTOKEN = 'your_ngrok_auth_token';

const gateway = new ECPayPayment({
  withServer: 'ngrok',
});

// 2. Use static ngrok domain (paid feature)
const gateway = new ECPayPayment({
  withServer: 'ngrok',
  serverHost: 'your-static-subdomain.ngrok.io',
});

// 3. Check ngrok installation
// npm install @ngrok/ngrok
```

#### 4. Order ID Conflicts

**Symptoms:**
- Error: "Order ID already exists"
- Cannot create new orders

**Solutions:**
```typescript
// 1. Use unique order IDs
const order = await gateway.prepare({
  id: `ORDER-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
  items: [...],
});

// 2. Or let gateway auto-generate
const order = await gateway.prepare({
  // No id field - gateway generates unique ID
  items: [...],
});

// 3. Check for duplicate submissions
// Implement idempotency in your application
```

#### 5. Amount Mismatch

**Symptoms:**
- Payment amount different from expected
- Calculation errors

**Solutions:**
```typescript
// Check item calculation
const items = [
  { name: 'A', unitPrice: 100, quantity: 2 }, // 200
  { name: 'B', unitPrice: 50, quantity: 3 },  // 150
];
// Total: 350

const order = await gateway.prepare({ items });
console.log('Total Price:', order.totalPrice); // Should be 350

// Verify additionalInfo after payment
gateway.emitter.on(PaymentEvents.ORDER_COMMITTED, (order) => {
  console.log('Charged amount:', order.additionalInfo.amount);
  console.log('Expected amount:', order.totalPrice);

  if (order.additionalInfo.amount !== order.totalPrice) {
    console.error('Amount mismatch!');
  }
});
```

#### 6. Event Listeners Not Firing

**Symptoms:**
- PaymentEvents.ORDER_COMMITTED not triggered
- No event handlers executing

**Solutions:**
```typescript
// 1. Register listeners BEFORE calling prepare()
gateway.emitter.on(PaymentEvents.ORDER_COMMITTED, handler);
gateway.emitter.on(PaymentEvents.ORDER_FAILED, handler);

// Then create order
const order = await gateway.prepare({...});

// 2. Check emitter exists
if (!gateway.emitter) {
  console.error('Gateway has no emitter!');
}

// 3. Use once() for one-time events
gateway.emitter.once(PaymentEvents.ORDER_COMMITTED, handler);

// 4. Check for errors in handler
gateway.emitter.on(PaymentEvents.ORDER_COMMITTED, (order) => {
  try {
    // Your logic here
  } catch (error) {
    console.error('Handler error:', error);
  }
});
```

#### 7. Card Binding Failures

**Symptoms:**
- CARD_BINDING_FAILED event fires
- Cannot bind card

**Solutions:**
```typescript
// 1. Check memory setting
const gateway = new ECPayPayment({
  memory: false, // Must be false for binding
  // ...
});

// 2. Handle already-bound cards
gateway.emitter.on(PaymentEvents.CARD_BINDING_FAILED, (req) => {
  if (req.failedMessage?.code === '10100112') {
    console.log('Card already bound - this is OK');
    // Retrieve existing binding
  }
});

// 3. Use bindCardWithTransaction instead
gateway.emitter.on(PaymentEvents.ORDER_COMMITTED, async (order) => {
  const bindRequest = await gateway.bindCardWithTransaction(
    memberId,
    order.platformTradeNumber,
    order.id
  );
});
```

## CTBC Advanced Features (CTBC 進階功能)

### POS API Utility Functions

CTBC adapter exports utility functions for direct POS API operations:

```typescript
import {
  posApiQuery,
  posApiRefund,
  posApiCancelRefund,
  posApiReversal,
  posApiCapRev,
  posApiSmartCancelOrRefund,
  getPosNextActionFromInquiry,
  CTBCPosApiConfig,
} from '@rytass/payments-adapter-ctbc-micro-fast-pay';

// Configure POS API
const posConfig: CTBCPosApiConfig = {
  URL: 'https://ccapi.ctbcbank.com',
  MacKey: 'YOUR_MAC_KEY', // 8 or 24 characters
};

// Query transaction status
const queryResult = await posApiQuery(posConfig, {
  MERID: 'YOUR_MERID',
  'LID-M': 'ORDER-001',
  Tx_ATTRIBUTE: 'TX_AUTH', // TX_AUTH | TX_SETTLE | TX_VOID | TX_REFUND
});

// Smart cancel or refund (auto-determine action based on transaction state)
const { action, response, inquiry } = await posApiSmartCancelOrRefund(posConfig, {
  MERID: 'YOUR_MERID',
  'LID-M': 'ORDER-001',
  XID: 'TRANSACTION_XID',
  AuthCode: 'AUTH_CODE',
  OrgAmt: '1000',
  PurchAmt: '1000',
  currency: 'TWD',
  exponent: '0',
});

console.log('Action taken:', action); // 'Reversal' | 'CapRev' | 'Refund' | 'None' | 'Pending' | 'Failed'
```

### AMEX SOAP API Utility Functions

For American Express card processing:

```typescript
import {
  amexInquiry,
  amexRefund,
  amexCancelRefund,
  amexAuthRev,
  amexCapRev,
  amexSmartCancelOrRefund,
  getAmexNextActionFromInquiry,
  CTBCAmexConfig,
} from '@rytass/payments-adapter-ctbc-micro-fast-pay';

// Configure AMEX API
const amexConfig: CTBCAmexConfig = {
  wsdlUrl: 'https://amex.ctbcbank.com/wsdl',
  timeout: 30000,
  sslOptions: {
    rejectUnauthorized: true,
  },
};

// Query AMEX transaction
const amexResult = await amexInquiry(amexConfig, {
  merId: 'YOUR_MERID',
  lidm: 'ORDER-001',
  xid: 'TRANSACTION_XID',
  IN_MAC_KEY: 'YOUR_MAC_KEY',
});

// Smart cancel or refund for AMEX
const { action, response } = await amexSmartCancelOrRefund(amexConfig, {
  merId: 'YOUR_MERID',
  xid: 'TRANSACTION_XID',
  lidm: 'ORDER-001',
  purchAmt: 1000,
  orgAmt: 1000,
  IN_MAC_KEY: 'YOUR_MAC_KEY',
});
```

### CTBC Configuration Options

Complete configuration for CTBC gateway:

```typescript
interface CTBCPaymentOptions {
  merchantId: string;           // CTBC merchant ID
  merchantName?: string;        // Merchant display name
  merId: string;                // MER ID from CTBC
  txnKey: string;               // MAC/TXN key for encryption
  terminalId: string;           // Terminal ID
  baseUrl?: string;             // API base URL (default: https://ccapi.ctbcbank.com)
  isAmex?: boolean;             // Enable AMEX card support (uses SOAP API)

  // Server options
  withServer?: boolean | 'ngrok';
  serverHost?: string;
  callbackPath?: string;
  checkoutPath?: string;
  bindCardPath?: string;
  boundCardPath?: string;
  boundCardCheckoutResultPath?: string;

  // Cache options
  orderCache?: OrderCache;
  orderCacheTTL?: number;
  bindCardRequestsCache?: BindCardRequestCache;
  bindCardRequestsCacheTTL?: number;

  // Callbacks
  serverListener?: (req, res) => void;
  onServerListen?: () => void;
  onCommit?: (order) => void;
}
```

## Happy Card Advanced Features (統一禮物卡進階功能)

### Get Card Balance

Query card balance before making payment:

```typescript
import { HappyCardPayment, HappyCardRecordType } from '@rytass/payments-adapter-happy-card';

const gateway = new HappyCardPayment({
  cSource: process.env.HAPPY_CARD_C_SOURCE!,
  key: process.env.HAPPY_CARD_KEY!,
});

// Get total balance (sum of all records)
const [balance, productType] = await gateway.getCardBalance('HC1234567890123456', false);
console.log('Total balance:', balance);
console.log('Product type:', productType);

// Get detailed records
const [records, productType2] = await gateway.getCardBalance('HC1234567890123456', true);
records.forEach(record => {
  console.log(`Record ${record.id}: ${record.type === HappyCardRecordType.AMOUNT ? 'Amount' : 'Bonus'} = ${record.amount}`);
});
```

### Happy Card Enums

```typescript
// Record types
enum HappyCardRecordType {
  AMOUNT = 1,  // Cash value (現金價值)
  BONUS = 2,   // Bonus points (紅利點數)
}

// Product types
enum HappyCardProductType {
  INVOICE_FIRST_HAPPY_CARD_GF = '1',     // 發票先開禮物卡 GF
  INVOICE_LATER_HAPPY_CARD_GS = '2',     // 發票後開禮物卡 GS
  INVOICE_FIRST_DIGITAL_GIFT_GF = '3',   // 發票先開數位禮物 GF
  INVOICE_LATER_DIGITAL_GIFT_GS = '4',   // 發票後開數位禮物 GS
  INVOICE_FIRST_PHYSICAL_GIFT_GF = '5',  // 發票先開實體禮物 GF
  INVOICE_LATER_PHYSICAL_GIFT_GS = '6',  // 發票後開實體禮物 GS
}

// Result codes
enum HappyCardResultCode {
  FAILED = '0',
  SUCCESS = '1',
}

// Base URLs
enum HappyCardBaseUrls {
  PRODUCTION = 'https://prd-jp-posapi.azurewebsites.net/api/Pos',
  DEVELOPMENT = 'https://uat-pos-api.azurewebsites.net/api/Pos',
}
```

### Complete Happy Card Payment Flow

```typescript
import {
  HappyCardPayment,
  HappyCardRecordType,
  HappyCardBaseUrls,
} from '@rytass/payments-adapter-happy-card';

const gateway = new HappyCardPayment({
  baseUrl: HappyCardBaseUrls.PRODUCTION,
  cSource: process.env.HAPPY_CARD_C_SOURCE!,
  key: process.env.HAPPY_CARD_KEY!,
});

// Step 1: Check card balance and get records
const [records, productType] = await gateway.getCardBalance('HC1234567890123456', true);
console.log('Available records:', records);

// Step 2: Prepare payment with specific records
const order = await gateway.prepare({
  id: 'ORDER-001',
  cardSerial: 'HC1234567890123456',
  items: [
    { name: 'Coffee', unitPrice: 120, quantity: 1 },
    { name: 'Cake', unitPrice: 180, quantity: 1 },
  ],
  useRecords: [
    { id: records[0].id, type: HappyCardRecordType.AMOUNT, amount: 200 },
    { id: records[1].id, type: HappyCardRecordType.BONUS, amount: 100 },
  ],
  posTradeNo: 'POS001', // Optional, max 6 chars
  uniMemberGID: 'MEMBER_GID', // Optional unified member ID
  isIsland: false, // true for offshore islands (離島)
});

// Step 3: Commit the payment
await order.commit();
console.log('Payment successful');
```

## HwaNan Bank Advanced Features (華南銀行進階功能)

### Installment Payments

HwaNan supports installment payments (分期付款):

```typescript
import {
  HwaNanPayment,
  HwaNanTransactionType,
  HwaNanAutoCapMode,
  HwaNanCustomizePageType,
} from '@rytass/payments-adapter-hwanan';

const gateway = new HwaNanPayment({
  merchantId: process.env.HWANAN_MERCHANT_ID!,
  terminalId: process.env.HWANAN_TERMINAL_ID!,
  merID: process.env.HWANAN_MER_ID!,
  identifier: process.env.HWANAN_IDENTIFIER!,
  merchantName: 'My Store',
  withServer: true,
});

// The order payload includes installment configuration
// txType: HwaNanTransactionType.INSTALLMENTS (1)
// NumberOfPay: Number of installments (minimum 3)
```

### HwaNan Enums

```typescript
// Transaction types
enum HwaNanTransactionType {
  ONE_TIME = 0,      // 一次付清
  INSTALLMENTS = 1,  // 分期付款
}

// Auto capture mode
enum HwaNanAutoCapMode {
  MANUALLY = 0,  // 手動請款
  AUTO = 1,      // 自動請款
}

// Customize page language
enum HwaNanCustomizePageType {
  ZH_TW = 1,  // 繁體中文
  ZH_CN = 2,  // 簡體中文
  EN_US = 3,  // English
  JA_JP = 4,  // 日本語
  OTHER = 5,  // 其他
}

// Payment channel (currently only credit card)
enum HwaNanPaymentChannel {
  CREDIT = 1,
}
```

### HwaNan Configuration Options

```typescript
interface HwaNanPaymentInitOptions {
  merchantId: string;           // 商店代碼
  terminalId: string;           // 端末機代碼
  merchantName: string;         // 商店名稱
  merID: string;                // MER ID
  identifier: string;           // 識別碼 (用於產生 checkValue)
  baseUrl?: string;             // API base URL
  customizePageType?: HwaNanCustomizePageType;  // 頁面語言
  customizePageVersion?: string;                // 頁面版本

  // Server options
  serverHost?: string;
  callbackPath?: string;
  checkoutPath?: string;
  withServer?: boolean | 'ngrok';
  ttl?: number;                 // Order TTL in ms

  // Callbacks
  serverListener?: (req, res) => void;
  onCommit?: (order) => void;
  onServerListen?: () => void;

  // Cache
  ordersCache?: OrdersCache;
}
```

## Detailed Documentation

For complete API reference and advanced usage:

- [ECPay Adapter Reference](ECPAY.md)
- [NewebPay Adapter Reference](NEWEBPAY.md)
- [HwaNan Bank Adapter Reference](HWANAN.md)
- [CTBC Adapter Reference](CTBC.md)
- [iCash Pay Adapter Reference](ICASH-PAY.md)
- [Happy Card Adapter Reference](HAPPY-CARD.md)

Install

Requires askill CLI v1.0+

Metadata

LicenseUnknown
Version-
Updated2w ago
PublisherRytass

Tags

apidatabasesecuritytesting