@voyantjs/finance
v0.86.0
Published
Finance module for Voyant. Invoices, payments, credit notes, supplier payments, and finance notes.
Readme
@voyantjs/finance
Finance module for Voyant. Invoices, payments, credit notes, supplier payments, and finance notes.
Install
pnpm add @voyantjs/financeUsage
import { financeModule } from "@voyantjs/finance"
import { createApp } from "@voyantjs/hono"
const app = createApp({
modules: [financeModule],
// ...
})Entities
- Invoices + Invoice lines (
inv,inli) - Payments (
pay) - Credit notes + Credit note lines (
crn,cnli) - Supplier payments (
spay) - Finance notes (
fnot) - Invoice number series (
invs) - Invoice templates (
invt) - Invoice renditions (
invr) - Tax regimes (
txrg) - Invoice external refs (
iner)
Invoice Rendition Events
Use financeService.bindInvoiceRendition(db, invoiceId, artifact, { eventBus })
when a rendered invoice artifact has already been stored and should be bound to
the invoice as the ready rendition. The helper creates the invoice_renditions
row with status: "ready" inside a transaction, optionally marks previous
renditions of the same format as stale when replaceExisting is true, and
then emits invoice.rendered after the transaction commits.
invoice.rendered is an internal service event. Subscribers receive metadata
only:
invoiceIdinvoiceStatusinvoiceTyperenditionIdformatstorageKeycontentTypebyteSizecontentHash
The event does not include rendered document bodies or signed download URLs. Subscriber failures do not roll back the rendition write; use a durable job or workflow when a downstream reaction needs retries.
Customer-Safe Document Lookup
Public finance routes include booking-scoped document lookup for customer portal and checkout surfaces:
GET /v1/public/finance/bookings/:bookingId/documentsGET /v1/public/finance/bookings/:bookingId/documents/by-reference?reference=...GET /v1/public/finance/documents/by-reference?reference=...
Booking-scoped routes require a checkout capability for the requested booking. The by-reference variant resolves invoice numbers and payment reference numbers only inside that booking, so a valid capability for one booking cannot retrieve documents from another booking.
Booking Tax Preview
Booking creation UIs can show the same tax line that booking finalization will persist by mounting the booking-tax extension:
import { createBookingTaxHonoExtension } from "@voyantjs/finance/booking-tax"
createApp({
extensions: [
createBookingTaxHonoExtension({
resolveBookingTaxSettings: async (db) => {
const settings = await getTaxSettings(db)
return {
taxPriceMode: settings?.taxPriceMode ?? "inclusive",
taxPolicyProfileId: settings?.taxPolicyProfileId ?? null,
}
},
}),
],
})The settings callback is the storage seam. A template can read a singleton
settings table, KV, environment configuration, or any other deployment-owned
store, while @voyantjs/finance owns the tax policy rule walker, tax-regime
lookup, product tax-class fallback, and inclusive/exclusive math.
Templates that already mount custom routes can call mountBookingTaxRoutes(...)
from the same entrypoint instead of using the Hono extension.
Mounting this route registers:
GET /v1/admin/bookings/tax-settingsPATCH /v1/admin/bookings/tax-settingswhenupdateBookingTaxSettingsis suppliedPOST /v1/admin/bookings/tax-preview
The preview endpoint is consumed by @voyantjs/bookings-react tax-preview
hooks. Consumers that use the booking-create dialog without mounting the route
will silently lose tax rows in the dialog summary because the client treats a
missing preview as "no tax to show".
Server-side booking-finalize code can use the same helpers directly:
import {
computeBookingItemTaxLine,
loadProductTaxFacts,
resolveBookingSellTaxRate,
} from "@voyantjs/finance/booking-tax"Invoice FX Settings
Invoice issuing can enrich invoice.issued events with operator accounting
currency, FX rate, FX commission, and effective provider rate. Configure the
finance module with invoiceFxSettings or resolveInvoiceFxSettings, plus an
exchange-rate resolver:
import {
createFinanceHonoModule,
createVoyantDataFxExchangeRateResolver,
} from "@voyantjs/finance"
createFinanceHonoModule({
invoiceFxSettings: {
baseCurrency: "RON",
fxCommissionBps: 200,
fxCommissionInvoiceMention: "2% comision curs risc valutar",
},
resolveInvoiceExchangeRate: createVoyantDataFxExchangeRateResolver({
apiKey: process.env.VOYANT_DATA_API_KEY!,
}),
})The default data resolver uses @voyantjs/data-sdk to call the Voyant Data FX
pair route /data/fx/v1/fx/pair/{invoiceCurrency}/{baseCurrency}. SDK
responses can add provenance metadata such as source, quotedAt, and
validUntil; invoice-issued events expose those as fxRateSource,
fxRateQuotedAt, and fxRateValidUntil. If the invoice currency matches the
operator base currency, no FX fields are emitted.
The same resolver also backs GET /v1/finance/invoice-fx-rate, which lets
operator UI surfaces auto-fill cross-currency payment rates without exposing
the Voyant Cloud API key to the browser.
Exports
| Entry | Description |
| --- | --- |
| . | Module export |
| ./invoice-fx | Invoice FX settings, route helpers, and data FX resolver |
| ./schema | Drizzle tables |
| ./validation | Zod schemas |
| ./booking-tax | Booking sell-side tax policy helpers and route mounting |
| ./routes | Hono routes |
License
Apache-2.0
