Portix.One

example · applications

Receipt Generator Example

By Portix.One Published
An immutable receipt model rendered through separate HTML, PDF, and ESC/POS renderers

Quick answer

Calculate totals in the transaction service and pass a complete immutable receipt model to a renderer. Use separate renderers for HTML, PDF, and ESC/POS; none should recalculate tax or mutate the sale.

Quick answer

Calculate totals in the transaction service and pass a complete immutable receipt model to a renderer. Use separate renderers for HTML, PDF, and ESC/POS; none should recalculate tax or mutate the sale.

type ReceiptModel = {
  id: string;
  issuedAt: string;
  merchant: { name: string; taxId?: string };
  lines: { description: string; quantity: string; amount: string }[];
  subtotal: string;
  taxLines: { label: string; amount: string }[];
  total: string;
  currency: string;
  paymentSummary: string;
};

interface ReceiptRenderer<T> {
  render(model: Readonly<ReceiptModel>): Promise<T>;
}
async function generate<T>(model: ReceiptModel, renderer: ReceiptRenderer<T>) {
  validateRequiredFields(model);
  return renderer.render(Object.freeze(model));
}

Use decimal or minor-unit arithmetic before building the model. Store the renderer/template version and artifact checksum. Escape untrusted text in HTML and constrain control characters in raw formats.

Verify

  • Line totals reconcile to subtotal and taxes.
  • Currency and rounding are explicit.
  • Long names and zero/negative lines render intentionally.
  • Repeated rendering produces equivalent output.
  • Legally required fields are reviewed per market.

Related content