s-m-r-t
Open the s-m-r-t source on GitHub Switch to dark color scheme
← All packages
Business & operations v0.57.40

@happyvertical/smrt-commerce

Customers, vendors, contracts, invoices, credit notes, payments, payment rails, tax codes, and fulfillment, with four recipes.

Source ↗

What it gives you

Customers, vendors, contracts, invoices, credit notes, payments, payment rails, tax codes, and fulfillment, with four recipes.

  1. 01Four recipes: customers, vendors, sales, and purchases
  2. 02Tenant tax codes with exclusive, inclusive, or no-tax line pricing over exact integer minor units
  3. 03Issued invoices keep their net amounts and stored tax; credit notes and the tax summary read those facts
  4. 04One issuing provider and any number of payment rails

Recipes cover the headers first

commerce.customers, commerce.vendors, commerce.sales, and commerce.purchases are declared in the package and emitted into the manifest recipes. Sales requires customers and purchases requires vendors. They cover Customer, Vendor, Order, and PurchaseOrder headers only; line items, fulfillment, invoices, and payments are later recipes. Each ships user-facing help, and the fields it shows carry plain-language descriptions.

One contract hierarchy supports commercial lifecycle

channelId distinguishes Estimate, Order, Lease, Agreement, PurchaseOrder, WholesaleOrder, ProductionOrder, Cart, and LicenseSale in one table. LicenseSale snapshots rights immutably and revoke() is its explicit transition. Money is stored as integer minor units. InvoiceLineItem.quantity is decimal with six fractional places; rounding happens once, where quantity times price meets money. There are no tolerances on any money path. The cost-billing subpath is an independent entry for signed cost billing with explicit tax rounding.

Sales and purchase contracts have separate parties

The base Contract holds no party. Abstract SalesContract owns customerId and covers Order, Estimate, Agreement, Lease, LicenseSale, WholesaleOrder, and Cart. Abstract PurchaseContract owns vendorId and covers PurchaseOrder and ProductionOrder. A sales leaf refuses vendorId and a purchase leaf refuses customerId. The contractId on Invoice, Payment, and Fulfillment points at SalesContract. Run preflightCommercePartyLinks on existing data before an upgrade.

Tax codes and components

A TaxCode lists one or more TaxComponent rows. Each has an exact percent and an optional liability account; a GST plus PST code has two. A used component never changes its percent: archive it and add a replacement. Every tenant starts with exempt, zero-rated, and out-of-scope codes. TaxSettings holds the tenant defaults, registrations, line amount type, and rounding scope. A line resolves its code from its own override, then a customer exemption, the product, the customer, and the tenant default. applyTaxCodeExamplePack imports example codes for Canada, the UK, Australia, and New Zealand; they are examples to review, not jurisdiction rules.

Exclusive, inclusive, or no-tax pricing

calculateMoneyDocument prices each line as quantity times unit price less one discount. An exclusive line amount is the net before tax. An inclusive amount is the gross the customer sees, and the net is derived by removing tax. A none line carries no tax and needs an out-of-scope code. Rounding is per line or per document, and the chosen scope is stored with the invoice. previewInvoiceMoney shows a proposed draft without writing it.

Issued invoices keep net amounts and stored tax

Saving a draft line recalculates the whole invoice once and writes the header totals, each line, and its DocumentTax rows together. InvoiceLineItem.amount is the net line amount and taxAmount is stored beside it. DocumentTax is read-only and records the code, component, percent, liability account, taxable base, and tax for each document. When an invoice leaves draft its money is frozen, and later edits to amounts or lines are refused. getInvoiceMoneyFacts reads the stored facts without recalculating them, and InvoiceRecordScreen shows them on a record page.

Every invoice and contract write needs a tenant

After creating a tenant, call ensureCommerceTenant(db, tenantId, { cookbook }) once. It seeds the tax settings, the standard tax codes, and the cookbook defaults, returns { tenantId }, and is safe to repeat. Money data written by an earlier release is refused with COMMERCE_DATA_NEEDS_UPGRADE, and the error names the one-time command to run: smrt db:upgrade-commerce-money.

Credit notes

A credit note is an Invoice with kind credit_note that names the invoice it credits. It credits a quantity or an amount of a source line, never more than the line, or adds a goodwill line with its own tax code. Tax comes from the source invoice as issued, not from current settings. An issued credit note reduces the source balance. A credit refund is a Payment of kind refund, allocated only to a credit note. Refunding an unapplied customer advance is a separate command, refundCustomerFunds, and that refund can never be allocated to an invoice credit.

Posting by account and the tax summary

Revenue recognition posts each line net to the revenue account stored on the line. Each tax component posts to its own liability account. A credit note posts the reverse. cancelWithRevenueReversal cancels a posted invoice by posting an exact reversing journal. TaxSummaryReport is a materialized report of taxable amounts, tax, and recoverable tax by period, currency, kind of tax code, and component. Expense and ledger figures sit beside them, and each figure drills down to its documents. createTaxSummaryMaterializedReportHost registers it with an application, and MaterializedReportTable from smrt-reports renders it.

Invoices, payments, and payouts retain evidence

recognizeRevenue posts accounts-receivable journal evidence. PaymentAllocation and Invoice.updatePaymentStatus connect settlement to the invoice. PaymentIntent locks a multi-rail price behind an idempotency key, while PaymentInstrument identifies the selected rail. Vendor payouts move from pending to sent, confirmed, or failed.

Payment rails behind one issuing provider

A runtime has one issuing provider and any number of paymentProviders, each with a unique name and its own inbox namespace. Settlement is decided by the pure decideAttempt from gateway state and the payment policy; the gateway is authoritative, and nothing here counts confirmations. Crypto rails implement the port over the payments package and never call a gateway from here. Rails never save cards or charge tax. CustomerSelect and VendorSelect are the registered selectors; QuoteEditor and PurchaseOrderEditor keep draft strings apart from minor-unit revisions.