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

@voyant-travel/plugin-smartbill

v0.127.0

Published

SmartBill e-invoicing sync adapter bundle for Voyant.

Downloads

1,516

Readme

@voyant-travel/plugin-smartbill

SmartBill e-invoicing sync adapter bundle for Voyant.

Architecturally, this package is primarily:

  • a SmartBill e-invoicing adapter
  • a subscriber bundle for finance invoice events
  • an optional packaged bundle when an app wants one installable entrypoint

It subscribes to invoice events and creates, cancels, or syncs invoices via the SmartBill REST API for Romanian tax compliance.

Install

pnpm add @voyant-travel/plugin-smartbill

Usage

import { smartbillPlugin } from "@voyant-travel/plugin-smartbill"
import { createApp } from "@voyant-travel/hono"

const smartbillSync = smartbillPlugin({
  username: env.SMARTBILL_USERNAME,
  apiToken: env.SMARTBILL_API_TOKEN,
  companyVatCode: "RO12345678",
  seriesName: (event) => (event.channel === "online" ? "WEB" : "A"),
  mentions: async (event) => `Booking ${event.bookingCode ?? event.id}`,
  observations: "Generated by Voyant",
  art311SpecialRegimeText: "Custom Art. 311 disclosure",
  // optional: language, art311SpecialRegime, events, mapEvent, logger, onError
  artifacts: {
    db: appDb,
    documentStorage,
  },
})

const app = createApp({
  plugins: [smartbillSync],
})

smartbillPlugin(...) is the packaged distribution helper. At runtime, the package behaves primarily as a subscriber-driven SmartBill sync adapter. By default it wires up 4 subscribers (invoice.issued, invoice.proforma.issued, invoice.voided, invoice.external.sync.requested) that create, cancel, and check payment status on SmartBill. All error handling is fire-and-forget per the EventBus contract.

When artifacts.db is configured, successful invoice/proforma creation also registers the SmartBill external reference through @voyant-travel/finance. When artifacts.documentStorage is configured, the plugin downloads the generated SmartBill PDF, uploads it to document storage, and records both a ready invoice_renditions row and a smartbill_pdf invoice_attachments row. The upload path defaults to invoices/<invoiceId>/smartbill/...; pass artifacts.documentStorageKeyPrefix to customize it. Existing smartbill_pdf attachments are reused so repeat event delivery does not upload the same PDF again.

seriesName, mentions, and observations may be static strings or event-specific callbacks. The packaged plugin awaits these callbacks in its default mapper, so each invoice event can choose a SmartBill series or compliance text based on document type, sales channel, or booking metadata.

When artifacts.db is configured, duplicate invoice.issued and invoice.proforma.issued deliveries are idempotent by default: the plugin checks for an existing non-error SmartBill external reference before creating a new SmartBill document. Disable this with idempotency: { skipExistingExternalRef: false } only when the caller owns deduplication elsewhere. Create failures are recorded as SmartBill external refs with status: "error" and syncError; onError(event, error) can be supplied for application-specific reporting.

Use syncSmartbillInvoice({ db, invoiceId, pluginOptions }) to run the same create-or-retry flow from an admin action. It loads the finance invoice, booking, line items, and tax metadata, maps them through the configured SmartBill options, reuses an existing non-error SmartBill ref when present, and persists external refs/PDF artifacts through the configured artifact runtime.

Apps that use @voyant-travel/hono can mount the packaged admin module:

import { createSmartbillAdminModule } from "@voyant-travel/plugin-smartbill/hono"

const app = createApp({
  plugins: [smartbillSync],
  modules: [
    createSmartbillAdminModule({
      pluginOptions: {
        username: env.SMARTBILL_USERNAME,
        apiToken: env.SMARTBILL_API_TOKEN,
        companyVatCode: "RO12345678",
        seriesName: "A",
        artifacts: { documentStorage },
      },
    }),
  ],
})

The admin module mounts POST /v1/admin/smartbill/invoices/:id/sync. The route uses the request database for external-ref and artifact persistence unless pluginOptions.artifacts.db supplies a custom database resolver.

Use retrySmartbillInvoiceArtifact({ runtime, client, externalRef, documentType }) to re-download and re-attach a SmartBill PDF from an existing external ref without issuing a new document.

Invoice UI

The optional ./invoice-ui entry ships React helpers for invoice detail pages. It reads SmartBill refs from /v1/finance/invoices/:id/external-refs using the @voyant-travel/finance-react provider context and can be mounted in InvoiceDetailPage's integration slot.

import { InvoiceDetailPage } from "@voyant-travel/finance-react/ui"
import { SmartbillInvoicePanel } from "@voyant-travel/plugin-smartbill/invoice-ui"

export function InvoicePage({ invoiceId }: { invoiceId: string }) {
  return (
    <InvoiceDetailPage
      id={invoiceId}
      slots={{
        integrationsContent: ({ invoice }) => (
          <SmartbillInvoicePanel invoiceId={invoice.id} />
        ),
      }}
    />
  )
}

SmartbillInvoicePanel displays the SmartBill series, number, document type, sync status, sync errors, and document/PDF links when the external ref contains them. By default, send and retry actions call POST /v1/admin/smartbill/invoices/:id/sync, and proforma conversion calls the finance POST /v1/finance/invoices/:id/convert-to-invoice endpoint. Pass sendAction, retryAction, or convertProformaAction to override those defaults. For custom layouts, use useSmartbillInvoiceRef(invoiceId) together with resolveSmartbillInvoiceReferenceParts(ref) and getSmartbillInvoiceDocumentLinks(ref).

Workflow Factories

The package also ships scheduler-agnostic workflow factories for recurring SmartBill maintenance. They return async functions that can be called from @voyant-travel/workflows, a Cloudflare cron handler, Trigger.dev, Hatchet, or any other job runner.

import {
  createSmartbillDriftReconciler,
  createSmartbillProformaConversionPoller,
} from "@voyant-travel/plugin-smartbill"

const pollProformas = createSmartbillProformaConversionPoller({
  db,
  client: smartbillClient,
  source: "invoices",
  requestSpacingMs: 350,
  onConverted: async (proformaRef, conversion) => {
    // Record a Voyant payment or emit a domain event in the host app.
    // The plugin reports the SmartBill invoice series/number and source
    // proforma ref, but leaves payment semantics to the consumer.
  },
})

const reconcileSmartbill = createSmartbillDriftReconciler({
  db,
  client: smartbillClient,
  source: "invoices",
  discoverRemote: true,
  requestSpacingMs: 350,
  onFinding: async (finding) => {
    // Log, alert, or open an operator ticket.
  },
  onMissingLocal: async (finding) => {
    const pdf = await finding.remote.accessors?.viewPdf()
    // Create a local invoice or attach SmartBill evidence in the host app.
  },
})

createSmartbillProformaConversionPoller scans SmartBill proforma external refs and calls client.listEstimateInvoices(...) to detect SmartBill-created final invoices. createSmartbillDriftReconciler verifies known SmartBill refs by default. Pass discoverRemote: true to walk SmartBill invoice/proforma series with client.listSeries() and report documents that exist remotely without a local external ref as missing_local; those findings include lazy PDF and payment-status/conversion lookup accessors on finding.remote.accessors. Both workflow factories use invoice_external_refs by default. Pass source: "invoices" to derive candidates from finance invoice rows instead, or pass listCandidateInvoices for a custom invoice-table/source query. Candidate refs are materialized as SmartBill external refs when db is available; custom sources can override that writeback with recordCandidateExternalRef. Consumers can still pass listRemoteDocuments to provide their own remote inventory. The reconciler only reports drift; it does not delete, void, or create finance records. Pass requestSpacingMs to either factory to enforce a minimum interval between SmartBill requests made by the workflow, including remote discovery and lazy remote-document accessors returned by discovery.

Exports

| Entry | Description | | --- | --- | | . | Barrel re-exports | | ./plugin | smartbillPlugin(options) — packaged adapter/subscriber bundle | | ./client | createSmartbillClientcreateInvoice, cancelInvoice, viewPdf, getPaymentStatus, etc. | | ./hono | createSmartbillAdminModule(options) and admin sync routes | | ./sync | syncSmartbillInvoice(...) and event-level sync helpers | | ./invoice-ui | Optional React hooks, display helpers, and SmartbillInvoicePanel for invoice detail integrations | | ./mock | createSmartbillMockServer — stateful local SmartBill-compatible mock for tests | | ./workflows | Proforma conversion polling and drift reconciliation factories | | ./types | SmartBill adapter and bundle types |

Rate limits

SmartBill can block an account after bursty traffic. createSmartbillClient throws SmartbillRateLimitError when a response carries SmartBill's rate-limit shape, with retryAfterMs, retryAfterAt, and blockedAt when the response text contains enough timing data.

For cron or batch pollers, enable the process-local circuit breaker so repeated calls do not keep hitting SmartBill while the account is blocked:

import {
  createSmartbillClient,
  SmartbillRateLimitCircuitOpenError,
  SmartbillRateLimitError,
} from "@voyant-travel/plugin-smartbill/client"

const client = createSmartbillClient({
  username,
  apiToken,
  rateLimit: {
    circuitBreaker: true,
  },
})

try {
  await client.listEstimateInvoices(companyVatCode, seriesName, number)
} catch (err) {
  if (
    err instanceof SmartbillRateLimitError ||
    err instanceof SmartbillRateLimitCircuitOpenError
  ) {
    // Stop the batch and retry after `err.retryAfterMs`.
  }
}

Workflow factories also stop the current run after SmartbillRateLimitError or SmartbillRateLimitCircuitOpenError, returning results processed before the limit was reached and recording the rate-limit failure through onError.

Local SmartBill Mock

SmartBill does not provide a practical sandbox, and invoice/proforma calls can create real accounting documents. Use the packaged mock for local workflows and end-to-end tests instead of pointing development credentials at the live API.

For in-process tests, pass the mock fetch implementation and any local apiUrl:

import { createSmartbillClient } from "@voyant-travel/plugin-smartbill/client"
import { createSmartbillMockServer } from "@voyant-travel/plugin-smartbill/mock"

const smartbill = createSmartbillMockServer()
const client = createSmartbillClient({
  username: "local",
  apiToken: "local",
  apiUrl: "http://smartbill.local/SBORO/api",
  fetch: smartbill.fetch,
})

For full app tests, start the local HTTP listener and wire the plugin/client to the returned base URL:

const smartbill = createSmartbillMockServer()
const server = await smartbill.listen({ port: 4555 })

const plugin = smartbillPlugin({
  username: "local",
  apiToken: "local",
  apiUrl: server.apiUrl,
  companyVatCode: "RO12345678",
  seriesName: "SB-TEST",
})

await server.close()

The mock supports the SmartBill endpoints used by the plugin and common local billing flows:

  • GET /tax
  • GET /series
  • POST /invoice
  • GET /invoice/pdf
  • GET /invoice/paymentstatus
  • PUT /invoice/cancel
  • PUT /invoice/reverse
  • PUT /invoice/restore
  • DELETE /invoice
  • POST /estimate
  • GET /estimate/pdf
  • GET /estimate/invoices

Documents are stateful and deterministic per series. Generated PDF URLs use the smartbill-mock://test-document/... scheme, and stored document mentions are marked with TEST DOCUMENT - SmartBill local mock.

License

Apache-2.0