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

@rytass/invoice-adapter-bank-pro

v0.2.14

Published

Rytass Invoice Gateway - BankPro

Downloads

753

Readme

Rytass Utils - Invoice Adapter Bank Pro

A comprehensive Bank Pro electronic invoice integration adapter for Taiwan's electronic invoice system. This adapter provides seamless integration with Bank Pro's B2B2C Web API, supporting complete invoice lifecycle management with robust tax compliance and carrier support.

Features

  • [x] Complete Bank Pro B2B2C Web API integration
  • [x] B2B and B2C invoice issuance
  • [x] Invoice query functionality (by order ID or invoice number)
  • [x] Invoice voiding operations
  • [x] Invoice allowance (partial refund) management
  • [x] Allowance invalidation support
  • [x] Multiple carrier type support
  • [x] Tax calculation and validation
  • [x] Detailed product information support
  • [x] Email notification integration
  • [x] Production and development environment support
  • [x] TypeScript type safety
  • [x] Comprehensive error handling

Installation

npm install @rytass/invoice-adapter-bank-pro
# or
yarn add @rytass/invoice-adapter-bank-pro

Peer Dependencies:

npm install @rytass/invoice

Configuration

Environment Setup

import { BankProInvoiceGateway, BankProBaseUrls } from '@rytass/invoice-adapter-bank-pro';

// Development environment
const developmentGateway = new BankProInvoiceGateway({
  user: 'YOUR_DEVELOPMENT_USER',
  password: 'YOUR_DEVELOPMENT_PASSWORD',
  systemOID: 12345, // Your development system OID
  sellerBAN: '12345678', // Your company VAT number
  baseUrl: BankProBaseUrls.DEVELOPMENT, // http://webtest.bpscm.com.tw/webapi/api/B2B2CWebApi
});

// Production environment
const productionGateway = new BankProInvoiceGateway({
  user: 'YOUR_PRODUCTION_USER',
  password: 'YOUR_PRODUCTION_PASSWORD',
  systemOID: 54321, // Your production system OID
  sellerBAN: '12345678', // Your company VAT number
  baseUrl: BankProBaseUrls.PRODUCTION, // http://www.bpscm.com.tw/webapi/api/B2B2CWebApi
});

Configuration Options

interface BankProInvoiceGatewayOptions {
  user: string; // Bank Pro API user ID
  password: string; // Bank Pro API password
  systemOID: number; // Bank Pro system identifier
  sellerBAN: string; // Seller's VAT number (統編)
  baseUrl?: BankProBaseUrls; // API base URL (development/production)
}

Basic Usage

B2C Invoice Issuance

import { BankProInvoiceGateway, InvoiceCarriers, TaxType } from '@rytass/invoice-adapter-bank-pro';

const gateway = new BankProInvoiceGateway({
  user: process.env.BANKPRO_USER!,
  password: process.env.BANKPRO_PASSWORD!,
  systemOID: parseInt(process.env.BANKPRO_SYSTEM_OID!),
  sellerBAN: process.env.SELLER_VAT_NUMBER!,
  baseUrl: BankProBaseUrls.PRODUCTION,
});

// Issue B2C invoice
const invoice = await gateway.issue({
  orderId: 'ORD-2024-001',
  buyerEmail: '[email protected]',
  buyerName: 'Customer Name',
  buyerAddress: '台北市信義區信義路五段7號',
  buyerZipCode: '110',
  buyerMobile: '0912345678',
  carrier: InvoiceCarriers.PRINT,
  items: [
    {
      name: 'Premium Product',
      unitPrice: 2000,
      quantity: 1,
      unit: '個',
      taxType: TaxType.TAXED,
      id: 'PROD-001',
      barcode: '1234567890123',
      spec: '高級款',
      remark: '限量商品',
    },
    {
      name: 'Standard Service',
      unitPrice: 1500,
      quantity: 2,
      unit: '次',
      taxType: TaxType.TAXED,
      id: 'SRV-001',
    },
  ],
});

console.log('Invoice Number:', invoice.number);
console.log('Random Code:', invoice.randomCode);
console.log('Total Amount:', invoice.amount);

B2B Invoice Issuance

// B2B invoice with company information
const b2bInvoice = await gateway.issue({
  orderId: 'B2B-2024-001',
  vatNumber: '12345678', // Buyer's VAT number
  companyName: '採購公司股份有限公司',
  buyerEmail: '[email protected]',
  buyerName: '採購部經理',
  buyerAddress: '新北市板橋區文化路二段242號',
  buyerZipCode: '220',
  carrier: InvoiceCarriers.PRINT,
  sellerCode: 'SELLER-001', // Optional seller code
  remark: 'B2B訂單 - 月結30天',
  items: [
    {
      name: '企業方案服務',
      unitPrice: 50000,
      quantity: 1,
      unit: '年',
      taxType: TaxType.TAXED,
      id: 'ENT-PLAN-001',
      spec: '專業版',
      remark: '包含技術支援',
    },
    {
      name: '客製化開發',
      unitPrice: 100000,
      quantity: 1,
      unit: '式',
      taxType: TaxType.TAXED,
      id: 'CUSTOM-DEV-001',
    },
  ],
});

Invoice with Mobile Carrier

// Issue invoice with mobile barcode carrier
const mobileInvoice = await gateway.issue({
  orderId: 'MOBILE-2024-001',
  buyerEmail: '[email protected]',
  buyerName: 'Mobile User',
  carrier: InvoiceCarriers.MOBILE('/ABC12345'),
  items: [
    {
      name: 'Digital Product',
      unitPrice: 1200,
      quantity: 1,
      unit: '個',
      taxType: TaxType.TAXED,
    },
  ],
});

Invoice with Love Code (Donation)

// Issue invoice with donation to charity
const donationInvoice = await gateway.issue({
  orderId: 'DONATION-2024-001',
  buyerEmail: '[email protected]',
  buyerName: 'Charitable Donor',
  carrier: InvoiceCarriers.LOVE_CODE('001'),
  items: [
    {
      name: 'Charity Purchase',
      unitPrice: 1000,
      quantity: 3,
      unit: '份',
      taxType: TaxType.TAXED,
    },
  ],
});

Advanced Usage

Mixed Tax Type Items

const mixedTaxInvoice = await gateway.issue({
  orderId: 'MIXED-2024-001',
  buyerEmail: '[email protected]',
  buyerName: 'Export Customer',
  buyerAddress: 'Export Address',
  customsMark: CustomsMark.YES,
  carrier: InvoiceCarriers.PRINT,
  items: [
    {
      name: 'Domestic Product',
      unitPrice: 1000,
      quantity: 2,
      taxType: TaxType.TAXED, // 5% tax
      unit: '個',
    },
    {
      name: 'Tax-Free Product',
      unitPrice: 2000,
      quantity: 1,
      taxType: TaxType.TAX_FREE, // No tax
      unit: '件',
    },
    {
      name: 'Export Product',
      unitPrice: 3000,
      quantity: 1,
      taxType: TaxType.ZERO_TAX, // Export (0% tax)
      unit: '組',
    },
  ],
});

Detailed Product Information

const detailedInvoice = await gateway.issue({
  orderId: 'DETAILED-2024-001',
  buyerEmail: '[email protected]',
  buyerName: 'Detail Customer',
  carrier: InvoiceCarriers.PRINT,
  items: [
    {
      name: 'Smart Phone',
      unitPrice: 25000,
      quantity: 1,
      unit: '支',
      taxType: TaxType.TAXED,
      id: 'PHONE-001',
      barcode: '4710543211234',
      spec: '128GB 藍色',
      remark: '保固一年',
    },
    {
      name: 'Phone Case',
      unitPrice: 500,
      quantity: 2,
      unit: '個',
      taxType: TaxType.TAXED,
      id: 'CASE-001',
      barcode: '4710543211241',
      spec: '透明矽膠',
      remark: '防摔保護',
    },
  ],
});

Query Operations

Query by Order ID

// Query invoice using order ID
const queriedByOrder = await gateway.query({
  orderId: 'ORD-2024-001',
});

console.log('Invoice Number:', queriedByOrder.number);
console.log('Invoice State:', queriedByOrder.state);
console.log('Invoice Amount:', queriedByOrder.amount);
console.log('Tax Amount:', queriedByOrder.taxAmount);

Query by Invoice Number

// Query invoice using invoice number
const queriedByNumber = await gateway.query({
  invoiceNumber: 'AA12345678',
});

console.log('Order ID:', queriedByNumber.orderId);
console.log('Random Code:', queriedByNumber.randomCode);
console.log('Issued On:', queriedByNumber.issuedOn);
console.log('Void Status:', queriedByNumber.state);

Invoice Management

Invoice Voiding

// Query and void an invoice
const invoiceToVoid = await gateway.query({
  orderId: 'ORD-2024-001',
});

const voidedInvoice = await gateway.void(invoiceToVoid, {
  reason: 'Customer requested cancellation due to shipping delay',
});

console.log('Void Status:', voidedInvoice.state); // InvoiceState.VOID
console.log('Voided On:', voidedInvoice.voidOn);

Invoice Allowance (Partial Refund)

// Create allowance for partial refund
const originalInvoice = await gateway.query({
  orderId: 'ORD-2024-001',
});

const allowanceInvoice = await gateway.allowance(originalInvoice, [
  {
    name: 'Returned Product',
    unitPrice: 2000,
    quantity: 1, // Return 1 item
    unit: '個',
    taxType: TaxType.TAXED,
    id: 'RETURN-001',
  },
]);

console.log('Allowance Number:', allowanceInvoice.allowances[0].number);
console.log('Allowance Amount:', allowanceInvoice.allowances[0].amount);
console.log('Remaining Amount:', allowanceInvoice.allowances[0].remainingAmount);

Invalidate Allowance

// Invalidate a previously issued allowance
const allowanceToInvalidate = allowanceInvoice.allowances[0];
const updatedInvoice = await gateway.invalidAllowance(allowanceToInvalidate);

console.log('Allowance invalidated:', updatedInvoice.allowances[0].invalidatedOn);

Error Handling

Bank Pro Specific Errors

try {
  const invoice = await gateway.issue({
    orderId: 'ERROR-TEST',
    buyerEmail: '[email protected]',
    carrier: InvoiceCarriers.PRINT,
    items: [
      /* invalid items */
    ],
  });
} catch (error) {
  if (error.message.includes('authentication failed')) {
    console.error('Bank Pro authentication failed - check credentials');
  } else if (error.message.includes('system unavailable')) {
    console.error('Bank Pro system temporarily unavailable');
  } else if (error.message.includes('duplicate order')) {
    console.error('Order ID already exists - use different order ID');
  } else if (error.message.includes('invalid VAT number')) {
    console.error('Buyer VAT number validation failed');
  } else {
    console.error('Invoice issuance failed:', error.message);
  }
}

Network and System Errors

async function safeInvoiceOperation<T>(operation: () => Promise<T>, retries: number = 3): Promise<T> {
  for (let attempt = 1; attempt <= retries; attempt++) {
    try {
      return await operation();
    } catch (error) {
      if (attempt === retries) {
        throw error;
      }

      if (error.code === 'ECONNRESET' || error.code === 'ENOTFOUND') {
        console.warn(`Network error on attempt ${attempt}, retrying...`);
        await new Promise(resolve => setTimeout(resolve, 1000 * attempt));
      } else {
        throw error; // Don't retry non-network errors
      }
    }
  }

  throw new Error('All retry attempts failed');
}

// Usage with retry logic
const invoice = await safeInvoiceOperation(() =>
  gateway.issue({
    orderId: 'RETRY-001',
    buyerEmail: '[email protected]',
    carrier: InvoiceCarriers.PRINT,
    items: [
      /* items */
    ],
  }),
);

Integration Examples

Express.js API Integration

import express from 'express';
import { BankProInvoiceGateway, BankProBaseUrls, InvoiceCarriers, TaxType } from '@rytass/invoice-adapter-bank-pro';

const app = express();
const gateway = new BankProInvoiceGateway({
  user: process.env.BANKPRO_USER!,
  password: process.env.BANKPRO_PASSWORD!,
  systemOID: parseInt(process.env.BANKPRO_SYSTEM_OID!),
  sellerBAN: process.env.SELLER_VAT_NUMBER!,
  baseUrl: process.env.NODE_ENV === 'production' ? BankProBaseUrls.PRODUCTION : BankProBaseUrls.DEVELOPMENT,
});

// Issue invoice endpoint
app.post('/api/invoices', async (req, res) => {
  try {
    const { orderId, customer, items, isB2B } = req.body;

    const invoiceData = {
      orderId,
      buyerEmail: customer.email,
      buyerName: customer.name,
      buyerAddress: customer.address,
      buyerZipCode: customer.zipCode,
      buyerMobile: customer.mobile,
      carrier: customer.mobileBarcode ? InvoiceCarriers.MOBILE(customer.mobileBarcode) : InvoiceCarriers.PRINT,
      items: items.map(item => ({
        name: item.name,
        unitPrice: item.price,
        quantity: item.quantity,
        unit: item.unit || '個',
        taxType: TaxType.TAXED,
        id: item.productId,
        barcode: item.barcode,
        spec: item.specification,
        remark: item.notes,
      })),
    };

    // Add B2B specific fields
    if (isB2B && customer.vatNumber) {
      Object.assign(invoiceData, {
        vatNumber: customer.vatNumber,
        companyName: customer.companyName,
      });
    }

    const invoice = await gateway.issue(invoiceData as any);

    res.json({
      success: true,
      invoice: {
        number: invoice.number,
        randomCode: invoice.randomCode,
        amount: invoice.amount,
        taxAmount: invoice.taxAmount,
        issuedOn: invoice.issuedOn,
      },
    });
  } catch (error) {
    res.status(400).json({
      success: false,
      error: error.message,
    });
  }
});

// Query invoice endpoint
app.get('/api/invoices/order/:orderId', async (req, res) => {
  try {
    const { orderId } = req.params;

    const invoice = await gateway.query({ orderId });

    res.json({
      success: true,
      invoice: {
        number: invoice.number,
        orderId: invoice.orderId,
        state: invoice.state,
        amount: invoice.amount,
        issuedOn: invoice.issuedOn,
      },
    });
  } catch (error) {
    res.status(404).json({
      success: false,
      error: 'Invoice not found',
    });
  }
});

// Void invoice endpoint
app.post('/api/invoices/:orderId/void', async (req, res) => {
  try {
    const { orderId } = req.params;
    const { reason } = req.body;

    const invoice = await gateway.query({ orderId });
    const voidedInvoice = await gateway.void(invoice, { reason });

    res.json({
      success: true,
      invoice: {
        number: voidedInvoice.number,
        state: voidedInvoice.state,
        voidedOn: voidedInvoice.voidOn,
      },
    });
  } catch (error) {
    res.status(400).json({
      success: false,
      error: error.message,
    });
  }
});

NestJS Service Integration

import { Injectable, BadRequestException, NotFoundException } from '@nestjs/common';
import { BankProInvoiceGateway, BankProBaseUrls, InvoiceCarriers, TaxType } from '@rytass/invoice-adapter-bank-pro';

@Injectable()
export class BankProInvoiceService {
  private readonly gateway: BankProInvoiceGateway;

  constructor() {
    this.gateway = new BankProInvoiceGateway({
      user: process.env.BANKPRO_USER!,
      password: process.env.BANKPRO_PASSWORD!,
      systemOID: parseInt(process.env.BANKPRO_SYSTEM_OID!),
      sellerBAN: process.env.SELLER_VAT_NUMBER!,
      baseUrl: process.env.NODE_ENV === 'production' ? BankProBaseUrls.PRODUCTION : BankProBaseUrls.DEVELOPMENT,
    });
  }

  async createInvoiceForOrder(order: {
    id: string;
    customer: {
      email: string;
      name: string;
      address?: string;
      zipCode?: string;
      mobile?: string;
      vatNumber?: string;
      companyName?: string;
      mobileBarcode?: string;
    };
    items: Array<{
      name: string;
      price: number;
      quantity: number;
      unit?: string;
      productId?: string;
      barcode?: string;
      specification?: string;
      notes?: string;
    }>;
    notes?: string;
  }) {
    // Determine invoice type
    const isB2B = !!order.customer.vatNumber;

    // Determine carrier
    let carrier = InvoiceCarriers.PRINT;
    if (!isB2B && order.customer.mobileBarcode) {
      // Validate mobile barcode if needed
      const isValid = await this.gateway.isMobileBarcodeValid(order.customer.mobileBarcode);
      if (isValid) {
        carrier = InvoiceCarriers.MOBILE(order.customer.mobileBarcode);
      }
    }

    // Prepare invoice data
    const invoiceData = {
      orderId: order.id,
      buyerEmail: order.customer.email,
      buyerName: order.customer.name,
      buyerAddress: order.customer.address,
      buyerZipCode: order.customer.zipCode,
      buyerMobile: order.customer.mobile,
      carrier,
      remark: order.notes,
      items: order.items.map(item => ({
        name: item.name,
        unitPrice: item.price,
        quantity: item.quantity,
        unit: item.unit || '個',
        taxType: TaxType.TAXED,
        id: item.productId,
        barcode: item.barcode,
        spec: item.specification,
        remark: item.notes,
      })),
    };

    // Add B2B specific fields
    if (isB2B) {
      Object.assign(invoiceData, {
        vatNumber: order.customer.vatNumber,
        companyName: order.customer.companyName,
      });
    }

    const invoice = await this.gateway.issue(invoiceData as any);

    return {
      invoiceNumber: invoice.number,
      randomCode: invoice.randomCode,
      amount: invoice.amount,
      taxAmount: invoice.taxAmount,
      issuedOn: invoice.issuedOn,
    };
  }

  async queryInvoiceByOrderId(orderId: string) {
    try {
      const invoice = await this.gateway.query({ orderId });
      return {
        invoiceNumber: invoice.number,
        orderId: invoice.orderId,
        randomCode: invoice.randomCode,
        state: invoice.state,
        amount: invoice.amount,
        taxAmount: invoice.taxAmount,
        issuedOn: invoice.issuedOn,
        voidOn: invoice.voidOn,
      };
    } catch (error) {
      throw new NotFoundException('Invoice not found');
    }
  }

  async voidInvoice(orderId: string, reason: string) {
    const invoice = await this.gateway.query({ orderId });

    if (invoice.state === InvoiceState.VOID) {
      throw new BadRequestException('Invoice is already voided');
    }

    const voidedInvoice = await this.gateway.void(invoice, { reason });

    return {
      invoiceNumber: voidedInvoice.number,
      state: voidedInvoice.state,
      voidedOn: voidedInvoice.voidOn,
    };
  }

  async createAllowance(
    orderId: string,
    allowanceItems: Array<{
      name: string;
      price: number;
      quantity: number;
      unit?: string;
      productId?: string;
    }>,
  ) {
    const originalInvoice = await this.gateway.query({ orderId });

    const allowanceInvoice = await this.gateway.allowance(
      originalInvoice,
      allowanceItems.map(item => ({
        name: item.name,
        unitPrice: item.price,
        quantity: item.quantity,
        unit: item.unit || '個',
        taxType: TaxType.TAXED,
        id: item.productId,
      })),
    );

    return {
      allowanceNumber: allowanceInvoice.allowances[0].number,
      allowanceAmount: allowanceInvoice.allowances[0].amount,
      remainingAmount: allowanceInvoice.allowances[0].remainingAmount,
      allowancedOn: allowanceInvoice.allowances[0].issuedOn,
    };
  }
}

E-commerce Platform Integration

import { BankProInvoiceGateway, InvoiceCarriers, TaxType, CustomsMark } from '@rytass/invoice-adapter-bank-pro';

class EcommerceBankProInvoiceProcessor {
  constructor(private gateway: BankProInvoiceGateway) {}

  async processOrderInvoice(order: {
    id: string;
    type: 'b2c' | 'b2b';
    customer: {
      email: string;
      name: string;
      address?: string;
      zipCode?: string;
      mobile?: string;
      vatNumber?: string;
      companyName?: string;
      preferences: {
        carrier?: 'print' | 'mobile' | 'love_code';
        carrierCode?: string;
      };
    };
    items: Array<{
      name: string;
      price: number;
      quantity: number;
      unit?: string;
      category: string;
      productId: string;
      barcode?: string;
      specification?: string;
      notes?: string;
      isExport?: boolean;
    }>;
    shipping: {
      isExport?: boolean;
      notes?: string;
    };
    seller?: {
      code?: string; // Seller code for multi-vendor platforms
    };
  }) {
    // Determine carrier based on customer preferences
    let carrier = InvoiceCarriers.PRINT;

    if (order.type === 'b2c') {
      switch (order.customer.preferences.carrier) {
        case 'mobile':
          if (order.customer.preferences.carrierCode) {
            // Validate mobile barcode if needed
            const isValidBarcode = await this.gateway.isMobileBarcodeValid(order.customer.preferences.carrierCode);
            if (isValidBarcode) {
              carrier = InvoiceCarriers.MOBILE(order.customer.preferences.carrierCode);
            }
          }
          break;
        case 'love_code':
          if (order.customer.preferences.carrierCode) {
            const isValidLoveCode = await this.gateway.isLoveCodeValid(order.customer.preferences.carrierCode);
            if (isValidLoveCode) {
              carrier = InvoiceCarriers.LOVE_CODE(order.customer.preferences.carrierCode);
            }
          }
          break;
      }
    }

    // Map items with appropriate tax types
    const invoiceItems = order.items.map((item, index) => ({
      name: item.name,
      unitPrice: item.price,
      quantity: item.quantity,
      unit: item.unit || '個',
      taxType: item.isExport ? TaxType.ZERO_TAX : TaxType.TAXED,
      id: item.productId,
      barcode: item.barcode,
      spec: item.specification,
      remark: item.notes,
    }));

    // Prepare invoice options
    const invoiceOptions = {
      orderId: order.id,
      buyerEmail: order.customer.email,
      buyerName: order.customer.name,
      buyerAddress: order.customer.address,
      buyerZipCode: order.customer.zipCode,
      buyerMobile: order.customer.mobile,
      carrier,
      sellerCode: order.seller?.code,
      remark: order.shipping.notes,
      items: invoiceItems,
      customsMark: order.shipping.isExport ? CustomsMark.YES : CustomsMark.NO,
    };

    // Add B2B specific fields
    if (order.type === 'b2b') {
      Object.assign(invoiceOptions, {
        vatNumber: order.customer.vatNumber,
        companyName: order.customer.companyName,
      });
    }

    const invoice = await this.gateway.issue(invoiceOptions as any);

    // Store invoice reference
    await this.storeInvoiceReference(order.id, invoice.number, invoice.randomCode);

    return {
      invoiceNumber: invoice.number,
      randomCode: invoice.randomCode,
      amount: invoice.amount,
      taxAmount: invoice.taxAmount,
      issuedOn: invoice.issuedOn,
    };
  }

  async processOrderRefund(
    orderId: string,
    refundItems: Array<{
      productId: string;
      name: string;
      price: number;
      quantity: number;
      unit?: string;
    }>,
  ) {
    // Query original invoice
    const originalInvoice = await this.gateway.query({ orderId });

    // Create allowance for refunded items
    const allowanceInvoice = await this.gateway.allowance(
      originalInvoice,
      refundItems.map(item => ({
        name: item.name,
        unitPrice: item.price,
        quantity: item.quantity,
        unit: item.unit || '個',
        taxType: TaxType.TAXED,
        id: item.productId,
      })),
    );

    return {
      allowanceNumber: allowanceInvoice.allowances[0].number,
      allowanceAmount: allowanceInvoice.allowances[0].amount,
      remainingAmount: allowanceInvoice.allowances[0].remainingAmount,
    };
  }

  private async storeInvoiceReference(orderId: string, invoiceNumber: string, randomCode: string) {
    // Implementation to store invoice-order relationship in database
  }
}

Best Practices

Configuration Management

  • Store sensitive credentials in environment variables
  • Use different configurations for development and production environments
  • Regularly rotate API credentials and passwords
  • Implement proper error handling for authentication failures

Invoice Processing

  • Always validate carrier information before invoice issuance
  • Implement proper retry logic for network failures
  • Store order IDs and invoice mappings for future reference
  • Handle duplicate order IDs appropriately

Tax Compliance

  • Use correct tax types for different product categories
  • Handle export/import scenarios with appropriate customs marks
  • Validate VAT numbers for B2B transactions
  • Maintain detailed product information for audit purposes

Performance Optimization

  • Implement connection pooling for high-volume processing
  • Use appropriate timeout settings for API calls
  • Cache frequently accessed invoice data
  • Monitor API response times and implement alerting

Security

  • Validate all input parameters before processing
  • Sanitize customer data and product information
  • Log all invoice operations for audit trails
  • Implement proper access controls for invoice management functions

Testing

import { BankProInvoiceGateway, BankProBaseUrls, InvoiceCarriers, TaxType } from '@rytass/invoice-adapter-bank-pro';

describe('Bank Pro Invoice Integration', () => {
  let gateway: BankProInvoiceGateway;

  beforeEach(() => {
    gateway = new BankProInvoiceGateway({
      user: 'test_user',
      password: 'test_password',
      systemOID: 12345,
      sellerBAN: '12345678',
      baseUrl: BankProBaseUrls.DEVELOPMENT,
    });
  });

  it('should issue B2C invoice successfully', async () => {
    const invoice = await gateway.issue({
      orderId: 'TEST-001',
      buyerEmail: '[email protected]',
      buyerName: 'Test Customer',
      carrier: InvoiceCarriers.PRINT,
      items: [
        {
          name: 'Test Product',
          unitPrice: 1000,
          quantity: 1,
          unit: '個',
          taxType: TaxType.TAXED,
        },
      ],
    });

    expect(invoice.number).toBeDefined();
    expect(invoice.amount).toBe(1050); // Including 5% tax
    expect(invoice.state).toBe(InvoiceState.ISSUED);
  });

  it('should query invoice by order ID', async () => {
    const queriedInvoice = await gateway.query({
      orderId: 'TEST-001',
    });

    expect(queriedInvoice.orderId).toBe('TEST-001');
    expect(queriedInvoice.number).toBeDefined();
  });

  it('should create allowance successfully', async () => {
    const originalInvoice = await gateway.query({
      orderId: 'TEST-001',
    });

    const allowanceInvoice = await gateway.allowance(originalInvoice, [
      {
        name: 'Returned Product',
        unitPrice: 1000,
        quantity: 1,
        unit: '個',
        taxType: TaxType.TAXED,
      },
    ]);

    expect(allowanceInvoice.allowances).toHaveLength(1);
    expect(allowanceInvoice.allowances[0].amount).toBe(1050);
  });
});

License

MIT