| name | invoice-development |
| description | Development guide for @rytass/invoice base package (發票基底套件開發指南). Use when creating new invoice adapters (新增發票 adapter), understanding base interfaces, or extending invoice functionality. Covers Invoice, InvoiceGateway, InvoiceAllowance interfaces and implementation patterns. |
Invoice Development Guide
This skill provides guidance for developers working with the @rytass/invoice base package, including creating new invoice adapters.
Overview
The @rytass/invoice package defines the core interfaces and types that all invoice adapters must implement. It follows the adapter pattern to provide a unified API across different Taiwan e-invoice providers.
Architecture
@rytass/invoice (Base Package)
│
├── Invoice<Item> # Invoice entity interface
├── InvoiceGateway<...> # Gateway interface
├── InvoiceAllowance<Item> # Allowance interface
├── Enums & Types # Shared types
└── Utility Functions # Helpers
@rytass/invoice-adapter-* # Provider implementations
│
├── [Provider]InvoiceGateway # Implements InvoiceGateway
├── [Provider]Invoice # Implements Invoice
└── [Provider]Allowance # Implements InvoiceAllowance
Installation
npm install @rytass/invoice
Core Interfaces
InvoiceGateway
The main interface that all adapters must implement:
interface InvoiceGateway<
Item extends PaymentItem = PaymentItem,
I extends Invoice<Item> = Invoice<Item>,
QueryOptions = unknown,
> {
issue(options: InvoiceIssueOptions<Item>): Promise<I>;
void(invoice: Invoice<PaymentItem>, options: InvoiceVoidOptions): Promise<Invoice<PaymentItem>>;
allowance(invoice: Invoice<PaymentItem>, allowanceItems: InvoicePaymentItem[], options?: InvoiceAllowanceOptions): Promise<Invoice<PaymentItem>>;
invalidAllowance(allowance: InvoiceAllowance<PaymentItem>): Promise<Invoice<PaymentItem>>;
query(options: QueryOptions): Promise<I>;
isMobileBarcodeValid(code: string): Promise<boolean>;
isLoveCodeValid(code: string): Promise<boolean>;
}
Invoice
The invoice entity interface:
interface Invoice<Item extends PaymentItem> {
readonly invoiceNumber: string;
readonly issuedOn: Date;
readonly allowances: InvoiceAllowance<PaymentItem>[];
readonly issuedAmount: number;
readonly randomCode: string;
readonly items: Item[];
state: InvoiceState;
nowAmount: number;
voidOn: Date | null;
setVoid: () => void;
awardType?: InvoiceAwardType;
}
InvoiceAllowance
The allowance entity interface:
interface InvoiceAllowance<Item extends PaymentItem> {
readonly allowanceNumber: string;
readonly allowancePrice: number;
readonly allowancedOn: Date;
readonly remainingAmount: number;
readonly items: Item[];
readonly parentInvoice: Invoice<Item>;
status: InvoiceAllowanceState;
invalidOn: Date | null;
invalid: () => void;
}
Quick Reference
Supporting Types
interface InvoiceIssueOptions<Item extends PaymentItem = PaymentItem> {
items: InvoicePaymentItem<Item>[];
vatNumber?: string;
carrier?: InvoiceCarrier;
customsMark?: CustomsMark;
}
type InvoicePaymentItem<Item extends PaymentItem = PaymentItem> = Item & {
taxType?: Omit<TaxType, TaxType.MIXED>;
};
interface InvoiceVoidOptions {
reason: string;
}
interface InvoiceAllowanceOptions {
taxType?: Omit<TaxType, TaxType.MIXED | TaxType.SPECIAL>;
}
interface BaseInvoiceQueryOptions {
orderId?: string;
invoiceNumber?: string;
[key: string]: unknown;
}
type InvoiceQueryOptions<T = BaseInvoiceQueryOptions> = T;
type InvoiceCarrier =
| InvoicePrintCarrier
| InvoiceMobileCarrier
| InvoiceMoicaCarrier
| InvoiceLoveCodeCarrier
| InvoiceMemberCarrier
| InvoicePlatformCarrier;
type InvoiceTax = CommonTax | SpecialTax;
interface CommonTax {
type: Exclude<TaxType, TaxType.SPECIAL>;
}
interface SpecialTax {
type: TaxType.SPECIAL;
taxCode: SpecialTaxCode;
}
Enums
| Enum | Values |
|---|
TaxType | TAXED, TAX_FREE, ZERO_TAX, SPECIAL, MIXED |
InvoiceState | INITED, ISSUED, VOID, ALLOWANCED |
InvoiceAllowanceState | INITED, ISSUED, INVALID |
InvoiceCarrierType | PRINT, MOBILE, MOICA, LOVE_CODE, MEMBER, PLATFORM |
CustomsMark | YES, NO |
SpecialTaxCode | TEA, CLUB, BANK_SELF, INSURANCE, BANK_COMMON, BANK_SELF_SALES_BEFORE_103, BANK_SELF_SALES_AFTER_103, FREE |
InvoiceAwardType | TWO_HUNDRED, FIVE_HUNDRED, EIGHT_HUNDRED, ONE_THOUSAND, FOUR_THOUSAND, TEN_THOUSAND, FORTY_THOUSAND, TWO_HUNDRED_THOUSAND, ONE_MILLION, TWO_MILLION, TEN_MILLION, CLOUD_TWO_THOUSAND |
InvoiceAwardType (發票中獎類型)
import { InvoiceAwardType } from '@rytass/invoice';
enum InvoiceAwardType {
TWO_HUNDRED = 6,
FIVE_HUNDRED = 11,
EIGHT_HUNDRED = 12,
ONE_THOUSAND = 5,
FOUR_THOUSAND = 4,
TEN_THOUSAND = 3,
FORTY_THOUSAND = 2,
TWO_HUNDRED_THOUSAND = 1,
ONE_MILLION = 10,
TWO_MILLION = 7,
TEN_MILLION = 8,
CLOUD_TWO_THOUSAND = 9,
}
SpecialTaxCode (特殊稅種代碼)
當 TaxType.SPECIAL 時需指定特殊稅種代碼:
import { TaxType, SpecialTaxCode } from '@rytass/invoice';
enum SpecialTaxCode {
TEA = 1,
CLUB = 2,
BANK_SELF = 3,
INSURANCE = 4,
BANK_COMMON = 5,
BANK_SELF_SALES_BEFORE_103 = 6,
BANK_SELF_SALES_AFTER_103 = 7,
FREE = 8,
}
const invoice = await gateway.issue({
orderId: 'ORDER-001',
items: [...],
tax: {
type: TaxType.SPECIAL,
taxCode: SpecialTaxCode.BANK_COMMON,
},
});
Utility Functions
| Function | Description |
|---|
isValidVATNumber(input) | Validate Taiwan VAT number |
getTaxTypeFromItems(items) | Determine TaxType from items |
Carrier Helpers
import { InvoiceCarriers } from '@rytass/invoice';
InvoiceCarriers.PRINT;
InvoiceCarriers.MEMBER;
InvoiceCarriers.PLATFORM;
InvoiceCarriers.MOBILE('/ABC+123');
InvoiceCarriers.MOICA('...');
InvoiceCarriers.LOVE_CODE('168001');
Deprecated Functions
import { verifyVatNumber, isValidVATNumber } from '@rytass/invoice';
verifyVatNumber('12345678');
isValidVATNumber('12345678');
Detailed Documentation
For complete interface specifications and implementation guide: