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

v0.5.0

Published

Rytass Invoice Gateway

Downloads

1,644

Readme

Rytass Utils - Invoice

A comprehensive TypeScript invoice management framework designed for Taiwan's electronic invoice system. Provides a unified interface for issuing, managing, and processing invoices across different service providers while maintaining compliance with Taiwan's tax regulations.

Features

  • [x] Unified invoice interface across multiple providers
  • [x] Taiwan electronic invoice system compliance
  • [x] Support for B2B and B2C invoice scenarios
  • [x] Multiple carrier types (Mobile, MOICA, Love Code, etc.)
  • [x] VAT number validation and formatting
  • [x] Invoice allowance (partial refund) management
  • [x] Tax type categorization and validation
  • [x] Invoice state management and tracking
  • [x] Customs mark support for export/import
  • [x] TypeScript type safety throughout
  • [x] Extensible adapter pattern architecture

Available Adapters

This package provides the core interfaces and types. Use with specific adapter implementations:

Installation

npm install @rytass/invoice
# Install a specific adapter
npm install @rytass/invoice-adapter-ecpay
# or
yarn add @rytass/invoice @rytass/invoice-adapter-ecpay

Basic Usage

Invoice Interface Implementation

import { InvoiceGateway, InvoiceIssueOptions, InvoiceCarriers, TaxType, CustomsMark } from '@rytass/invoice';
import { ECPayInvoiceGateway } from '@rytass/invoice-adapter-ecpay';

// Initialize invoice gateway
const invoiceGateway: InvoiceGateway = new ECPayInvoiceGateway({
  hashKey: 'YOUR_HASH_KEY',
  hashIV: 'YOUR_HASH_IV',
  merchantId: 'YOUR_MERCHANT_ID',
  isProduction: false,
});

// Issue a B2C invoice
const invoice = await invoiceGateway.issue({
  items: [
    {
      name: 'Product A',
      unitPrice: 1000,
      quantity: 2,
      taxType: TaxType.TAXED,
    },
    {
      name: 'Product B',
      unitPrice: 500,
      quantity: 1,
      taxType: TaxType.TAXED,
    },
  ],
  carrier: InvoiceCarriers.MOBILE('AB12345678'), // Mobile barcode
  customsMark: CustomsMark.NO,
});

console.log('Invoice Number:', invoice.number);
console.log('Invoice State:', invoice.state);

B2B Invoice with VAT Number

const b2bInvoice = await invoiceGateway.issue({
  items: [
    {
      name: 'Professional Service',
      unitPrice: 50000,
      quantity: 1,
      taxType: TaxType.TAXED,
    },
  ],
  vatNumber: '12345678', // Company VAT number
  customsMark: CustomsMark.NO,
});

Multiple Tax Types

const mixedTaxInvoice = await invoiceGateway.issue({
  items: [
    {
      name: 'Taxed Item',
      unitPrice: 1000,
      quantity: 1,
      taxType: TaxType.TAXED, // 5% tax
    },
    {
      name: 'Tax-Free Item',
      unitPrice: 2000,
      quantity: 1,
      taxType: TaxType.TAX_FREE, // No tax
    },
    {
      name: 'Zero Tax Export',
      unitPrice: 3000,
      quantity: 1,
      taxType: TaxType.ZERO_TAX, // Export goods
    },
  ],
  carrier: InvoiceCarriers.PRINT,
  customsMark: CustomsMark.YES, // For export items
});

Core Concepts

Invoice Carriers

Taiwan's electronic invoice system supports various carrier types for different delivery methods:

import { InvoiceCarriers, InvoiceCarrierType } from '@rytass/invoice';

// Print invoice (traditional paper receipt)
const printCarrier = InvoiceCarriers.PRINT;

// Mobile barcode carrier
const mobileCarrier = InvoiceCarriers.MOBILE('/ABC1234'); // Barcode from mobile app

// MOICA (Government digital certificate)
const moicaCarrier = InvoiceCarriers.MOICA('AB12345678901234');

// Love Code (donation to charity)
const loveCodeCarrier = InvoiceCarriers.LOVE_CODE('123');

// Member carrier (platform-specific)
const memberCarrier = { type: InvoiceCarrierType.MEMBER, code: 'member123' };

// Platform carrier (e-commerce platforms)
const platformCarrier = { type: InvoiceCarrierType.PLATFORM, code: 'platform456' };

Tax Types and Categories

import { TaxType, SpecialTaxCode } from '@rytass/invoice';

// Standard tax types
const taxedItem = {
  name: 'Regular Product',
  unitPrice: 1000,
  quantity: 1,
  taxType: TaxType.TAXED, // 5% business tax
};

const taxFreeItem = {
  name: 'Exempt Product',
  unitPrice: 1000,
  quantity: 1,
  taxType: TaxType.TAX_FREE, // Tax exempt goods
};

const zeroTaxItem = {
  name: 'Export Product',
  unitPrice: 1000,
  quantity: 1,
  taxType: TaxType.ZERO_TAX, // Export goods (0% tax)
};

// Special tax scenarios
const specialTaxItem = {
  name: 'Banking Service',
  unitPrice: 1000,
  quantity: 1,
  taxType: TaxType.SPECIAL,
  specialTaxCode: SpecialTaxCode.BANK_COMMON,
};

Invoice States

import { InvoiceState } from '@rytass/invoice';

// Invoice lifecycle states
InvoiceState.INITED; // Invoice created but not issued
InvoiceState.ISSUED; // Invoice successfully issued
InvoiceState.VOID; // Invoice voided/cancelled
InvoiceState.ALLOWANCED; // Invoice has allowances (partial refunds)

Advanced Usage

Invoice Allowances (Partial Refunds)

// Issue original invoice
const originalInvoice = await invoiceGateway.issue({
  items: [
    {
      name: 'Product A',
      unitPrice: 1000,
      quantity: 3, // Total: 3000
      taxType: TaxType.TAXED,
    },
  ],
  carrier: InvoiceCarriers.MOBILE('/ABC1234'),
});

// Create allowance for partial refund (return 1 item)
const allowanceInvoice = await invoiceGateway.allowance(
  originalInvoice,
  [
    {
      name: 'Product A',
      unitPrice: 1000,
      quantity: 1, // Refund 1 item
      taxType: TaxType.TAXED,
    },
  ],
  {
    taxType: TaxType.TAXED, // Allowance tax type
  },
);

console.log('Allowance created:', allowanceInvoice.allowances.length);

Invoice Voiding

// Void an entire invoice
const voidedInvoice = await invoiceGateway.void(invoice, {
  reason: 'Customer requested cancellation',
});

console.log('Invoice state:', voidedInvoice.state); // InvoiceState.VOID

Carrier Validation

// Validate mobile barcode
const isMobileValid = await invoiceGateway.isMobileBarcodeValid('/ABC1234');
if (!isMobileValid) {
  throw new Error('Invalid mobile barcode');
}

// Validate love code (charity donation)
const isLoveCodeValid = await invoiceGateway.isLoveCodeValid('123');
if (!isLoveCodeValid) {
  throw new Error('Invalid love code');
}

VAT Number Validation

import { isValidVATNumber } from '@rytass/invoice';

// Validate Taiwan VAT number format
const vatNumber = '12345678';
const isValid = isValidVATNumber(vatNumber);

if (!isValid) {
  throw new Error('Invalid VAT number format');
}

Tax Type Detection

import { getTaxTypeFromItems } from '@rytass/invoice';

const items = [
  { name: 'Item 1', unitPrice: 1000, quantity: 1, taxType: TaxType.TAXED },
  { name: 'Item 2', unitPrice: 2000, quantity: 1, taxType: TaxType.TAXED },
];

// Automatically determine overall tax type for invoice
const overallTaxType = getTaxTypeFromItems(items);
console.log('Invoice tax type:', overallTaxType); // TaxType.TAXED

TypeScript Integration

Custom Payment Items

import { InvoicePaymentItem, PaymentItem } from '@rytass/invoice';

// Extend base PaymentItem with custom properties
interface CustomPaymentItem extends PaymentItem {
  productId: string;
  category: string;
  discount?: number;
}

// Use with invoice system
interface CustomInvoiceItem extends InvoicePaymentItem<CustomPaymentItem> {
  productId: string;
  category: string;
  discount?: number;
}

const customInvoice = await invoiceGateway.issue({
  items: [
    {
      name: 'Premium Product',
      unitPrice: 1000,
      quantity: 1,
      taxType: TaxType.TAXED,
      productId: 'PROD-001',
      category: 'Electronics',
      discount: 100,
    },
  ] as CustomInvoiceItem[],
  carrier: InvoiceCarriers.PRINT,
});

Generic Invoice Gateway

import { InvoiceGateway, Invoice } from '@rytass/invoice';

// Type-safe invoice gateway
class CustomInvoiceService<T extends PaymentItem> {
  constructor(private gateway: InvoiceGateway<T>) {}

  async issueInvoice(items: InvoicePaymentItem<T>[]): Promise<Invoice<T>> {
    return this.gateway.issue({
      items,
      carrier: InvoiceCarriers.PRINT,
    });
  }

  async processRefund(invoice: Invoice<T>, refundItems: InvoicePaymentItem<T>[]): Promise<Invoice<T>> {
    return this.gateway.allowance(invoice, refundItems);
  }
}

Error Handling

import { InvoiceState } from '@rytass/invoice';

try {
  const invoice = await invoiceGateway.issue({
    items: [
      {
        name: 'Product',
        unitPrice: 1000,
        quantity: 1,
        taxType: TaxType.TAXED,
      },
    ],
    vatNumber: '12345678',
    carrier: InvoiceCarriers.MOBILE('/INVALID'),
  });
} catch (error) {
  if (error.message.includes('invalid mobile barcode')) {
    console.error('Mobile barcode validation failed');
  } else if (error.message.includes('VAT number')) {
    console.error('VAT number validation failed');
  } else if (error.message.includes('tax calculation')) {
    console.error('Tax calculation error');
  } else {
    console.error('Invoice issuance failed:', error.message);
  }
}

Integration Examples

Express.js API Endpoint

import express from 'express';
import { ECPayInvoiceGateway } from '@rytass/invoice-adapter-ecpay';
import { InvoiceCarriers, TaxType } from '@rytass/invoice';

const app = express();
const invoiceGateway = new ECPayInvoiceGateway({
  hashKey: process.env.ECPAY_HASH_KEY!,
  hashIV: process.env.ECPAY_HASH_IV!,
  merchantId: process.env.ECPAY_MERCHANT_ID!,
  isProduction: process.env.NODE_ENV === 'production',
});

app.post('/api/invoices', async (req, res) => {
  try {
    const { items, vatNumber, carrierType, carrierCode } = req.body;

    // Determine carrier
    let carrier;
    if (carrierType === 'mobile') {
      carrier = InvoiceCarriers.MOBILE(carrierCode);
    } else if (carrierType === 'love_code') {
      carrier = InvoiceCarriers.LOVE_CODE(carrierCode);
    } else {
      carrier = InvoiceCarriers.PRINT;
    }

    // Issue invoice
    const invoice = await invoiceGateway.issue({
      items: items.map(item => ({
        ...item,
        taxType: TaxType.TAXED,
      })),
      vatNumber,
      carrier,
    });

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

NestJS Service Integration

import { Injectable } from '@nestjs/common';
import { InvoiceGateway, InvoiceCarriers, TaxType } from '@rytass/invoice';
import { ECPayInvoiceGateway } from '@rytass/invoice-adapter-ecpay';

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

  constructor() {
    this.invoiceGateway = new ECPayInvoiceGateway({
      hashKey: process.env.ECPAY_HASH_KEY!,
      hashIV: process.env.ECPAY_HASH_IV!,
      merchantId: process.env.ECPAY_MERCHANT_ID!,
      isProduction: process.env.NODE_ENV === 'production',
    });
  }

  async createInvoiceForOrder(order: {
    items: Array<{
      name: string;
      price: number;
      quantity: number;
    }>;
    customerVat?: string;
    mobileBarcode?: string;
  }) {
    // Validate mobile barcode if provided
    if (order.mobileBarcode) {
      const isValid = await this.invoiceGateway.isMobileBarcodeValid(order.mobileBarcode);
      if (!isValid) {
        throw new Error('Invalid mobile barcode');
      }
    }

    const invoice = await this.invoiceGateway.issue({
      items: order.items.map(item => ({
        name: item.name,
        unitPrice: item.price,
        quantity: item.quantity,
        taxType: TaxType.TAXED,
      })),
      vatNumber: order.customerVat,
      carrier: order.mobileBarcode ? InvoiceCarriers.MOBILE(order.mobileBarcode) : InvoiceCarriers.PRINT,
    });

    return {
      invoiceNumber: invoice.number,
      amount: invoice.amount,
      tax: invoice.taxAmount,
      state: invoice.state,
    };
  }

  async processRefund(
    invoiceNumber: string,
    refundItems: Array<{
      name: string;
      price: number;
      quantity: number;
    }>,
  ) {
    // Query existing invoice
    const invoice = await this.invoiceGateway.query({ number: invoiceNumber });

    // Create allowance
    const updatedInvoice = await this.invoiceGateway.allowance(
      invoice,
      refundItems.map(item => ({
        name: item.name,
        unitPrice: item.price,
        quantity: item.quantity,
        taxType: TaxType.TAXED,
      })),
    );

    return {
      allowances: updatedInvoice.allowances,
      state: updatedInvoice.state,
    };
  }
}

E-commerce Platform Integration

import { InvoiceGateway, InvoiceCarriers, TaxType, CustomsMark } from '@rytass/invoice';

class EcommerceInvoiceManager {
  constructor(private invoiceGateway: InvoiceGateway) {}

  async processOrderInvoice(order: {
    id: string;
    items: Array<{
      name: string;
      price: number;
      quantity: number;
      isExport?: boolean;
    }>;
    customer: {
      vatNumber?: string;
      mobileBarcode?: string;
      email: string;
    };
  }) {
    // Determine if order contains export items
    const hasExportItems = order.items.some(item => item.isExport);

    // Map items with appropriate tax types
    const invoiceItems = order.items.map(item => ({
      name: item.name,
      unitPrice: item.price,
      quantity: item.quantity,
      taxType: item.isExport ? TaxType.ZERO_TAX : TaxType.TAXED,
    }));

    // Determine carrier based on customer preferences
    let carrier;
    if (order.customer.mobileBarcode) {
      const isValid = await this.invoiceGateway.isMobileBarcodeValid(order.customer.mobileBarcode);
      if (isValid) {
        carrier = InvoiceCarriers.MOBILE(order.customer.mobileBarcode);
      }
    }

    if (!carrier) {
      carrier = InvoiceCarriers.PRINT;
    }

    const invoice = await this.invoiceGateway.issue({
      items: invoiceItems,
      vatNumber: order.customer.vatNumber,
      carrier,
      customsMark: hasExportItems ? CustomsMark.YES : CustomsMark.NO,
    });

    // Store invoice reference with order
    await this.saveInvoiceReference(order.id, invoice.number);

    return invoice;
  }

  private async saveInvoiceReference(orderId: string, invoiceNumber: string) {
    // Implementation to store invoice-order relationship
  }
}

Best Practices

Configuration Management

  • Store API credentials securely using environment variables
  • Use different configurations for staging and production environments
  • Implement proper error handling for credential validation

Tax Compliance

  • Always validate VAT numbers before issuing B2B invoices
  • Use appropriate tax types for different product categories
  • Handle export/import scenarios with proper customs marks

State Management

  • Track invoice states throughout their lifecycle
  • Implement proper allowance workflows for partial refunds
  • Handle void operations with proper audit trails

Performance

  • Cache carrier validation results to reduce API calls
  • Batch invoice operations when processing multiple orders
  • Implement retry logic for network failures

Security

  • Validate all input parameters before processing
  • Sanitize carrier codes and VAT numbers
  • Log invoice operations for audit purposes

Testing

import { InvoiceGateway, InvoiceCarriers, TaxType } from '@rytass/invoice';

describe('Invoice Management', () => {
  let invoiceGateway: InvoiceGateway;

  beforeEach(() => {
    // Initialize test gateway
    invoiceGateway = new TestInvoiceGateway();
  });

  it('should issue B2C invoice successfully', async () => {
    const invoice = await invoiceGateway.issue({
      items: [
        {
          name: 'Test Product',
          unitPrice: 1000,
          quantity: 1,
          taxType: TaxType.TAXED,
        },
      ],
      carrier: InvoiceCarriers.PRINT,
    });

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

  it('should validate mobile barcode', async () => {
    const isValid = await invoiceGateway.isMobileBarcodeValid('/ABC1234');
    expect(isValid).toBe(true);
  });
});

License

MIT