solidus_numeral gem plugs into Solidus’s pluggable tax calculator API to calculate tax in real time at checkout through Numeral’s tax engine. Completed orders are automatically committed to Numeral’s transaction ledger for filing — the tax charged to the customer is exactly what gets filed. RMA reimbursements, order cancellations, and short-ships are reported to Numeral as refunds automatically, and customer exemptions are managed per-user from the Solidus admin.
System requirements
Prerequisites
Before installing, make sure you have:- A Numeral account at dashboard.numeralhq.com
- Deploy access to your Solidus application (Gemfile, initializers, migrations)
- A production-grade ActiveJob backend (Sidekiq, GoodJob, SolidQueue, …) — transaction commits, refunds, and syncs run through ActiveJob, and with the default inline/async adapters a Numeral outage could impact request handling and failed jobs won’t be retried
- Outbound HTTPS from your Rails servers to
api.numeralhq.com
Part 1: Connect Solidus in the Numeral dashboard
- In the Numeral dashboard, go to Connections.
- Click Add Connection and select Solidus.
- The connection provides your pair of secret API keys:
Which mode the gem runs in is determined entirely by which key you configure — there is no separate test-mode flag. Start with the
sk_test_ key and switch to the sk_live_ key when you go to production.Part 2: Install the gem
Thesolidus_numeral gem is provided by Numeral — reach out to your Numeral implementation contact for access if you don’t already have it.
Add it to your Gemfile:
config/initializers/solidus_numeral.rb, copies the gem’s migrations into your app, and offers to run them.
Prefer to wire things up manually?
Prefer to wire things up manually?
Create the initializer yourself:Then copy and run the migrations:
Part 3: Configure the initializer
Set your API key from Part 1 (thesk_test_ key first) in the environment variable the generated initializer reads:
Configuration reference
Part 4: Map tax categories to Numeral product categories
Numeral decides taxability per line from its product taxonomy. The gem sends each line item’sSpree::TaxCategory#tax_code (a core Solidus column) as the Numeral product_category:
- Find the valid codes in the Product Categories reference.
- In the Solidus admin, set Tax Code on each of your tax categories to the matching Numeral code (e.g.
CLOTHING,FOOD_BEVERAGE_GROCERIES, …).
Lines with no mapped code fall back to
GENERAL_MERCHANDISE — calculations never fail on an unmapped SKU.Product sync (optional, recommended)
Calculations auto-create products from SKUs, so no pre-sync is required. To make your catalog visible in the Numeral dashboard (and eligible for AI categorization and ops review), push it once:SolidusNumeral::SyncProductsJob and writes a summary to the solidus_numeral_sync_logs table (action=product_sync).
Part 5: Mark customers tax-exempt (optional)
Visit/admin/users/:id/numeral_information for any user to manage their Numeral tax settings. The screen is not injected into the admin user tabs — link it wherever fits your admin. From there you can:
- Mark the user tax exempt. The gem syncs a customer record to Numeral (
is_tax_exempt: true) and subsequent calculations for that user reference it and return zero tax. Unchecking syncs the revocation. - Store a VAT identification number. Calculations then send the user as a
BUSINESScustomer with the VAT id (typed per the order’s destination country, e.g.eu_vat/gb_vat), enabling EU B2B reverse charge. If the destination country has no supported tax id type, the id is omitted and a warning is logged.
Testing it out
With yoursk_test_ key configured, everything is recorded in Numeral’s test mode and never filed:
- Add a product to the cart on your staging storefront and proceed to checkout with a complete US shipping address.
- At checkout, verify a tax adjustment appears (labeled e.g. Sales Tax (8.875%)).
- Complete the order — the gem commits it to Numeral in the background.
- In the Numeral dashboard (in test mode), confirm the calculation and the committed transaction appear.
- Create and reimburse an RMA for the order, then confirm a partial refund is recorded against the transaction in Numeral.
Tax appears once you enable collection for a jurisdiction in Numeral — until then calculations return 0% “no nexus” results. That’s by design, not an error.
Where to look when something’s wrong
solidus_numeral_sync_logstable — every ledger attempt (commit, refund, void, customer sync, product sync) is recorded with its action, status, Numeral id, and error message, so silent background-job failures are visible.- Rails logs — checkout calculation failures are routed through the configured
exception_handler, which by default logs[solidus_numeral] tax calculation failed - .... Checkout is never blocked: any failure yields zero tax instead of raising. - Misconfiguration warning — if the extension is disabled or has no API key, the gem logs
[solidus_numeral] tax calculation is OFF ... - all orders are getting zero tax(once per process) so a misconfigured store doesn’t silently under-collect.
How it works
- Calculation caching. Solidus recalculates taxes on every cart mutation and checkout transition (5–15 times per checkout). Responses are cached in
Rails.cachekeyed on the serialized calculation request, so identical order states cost nothing and any change to lines, address, or discounts busts the cache naturally. Default TTL is 3 hours (Numeral calculations stay valid for 24). - Checkout never breaks. Missing/incomplete addresses, empty carts, the kill switch, and API errors all yield zero tax instead of raising.
- Order lifecycle. Orders are committed to Numeral when finalized. Cancellation triggers a full refund (or the remaining headroom, if partial refunds exist), RMA reimbursement triggers a partial refund grouped per original line item, and short-shipping triggers a delta partial refund. Refund amounts are capped against the amounts actually committed to Numeral, and the gem reconciles its local refund ledger against Numeral’s records before every refund, so job retries never double-report.
- Shipping. Each shipment is sent as a
SHIPPINGline item, so shipping taxability follows each state’s rules. The delivery-step shipping rate tax display is intentionally a no-op (it would cost one API call per candidate rate for a display-only number); shipping tax is calculated and charged as soon as the order recalculates after rate selection. - Tax-inclusive (VAT) prices. Whether amounts are treated as tax-inclusive is decided per order: only when an
included_in_priceSpree::TaxRate’s zone covers the order’s tax address (so US-destination orders on a mixed US+VAT store stay exclusive). Override globally withconfig.tax_included_in_priceif that heuristic doesn’t fit your store. - Discounts. Line-level promotions are already reflected in the taxable amounts sent to Numeral; order-level promotions are prorated across line items (by pre-discount line total) before encoding.
- Placeholder tax rate. Solidus requires each tax adjustment to reference a
Spree::TaxRate; the gem maintains a single zero-amount rate named"Numeral Tax"for this. Don’t delete it or attach zones/categories to it.
Troubleshooting
Tax not appearing at checkout
Tax not appearing at checkout
- Confirm
config.api_keyis set andconfig.enabledistrue— if not, the log contains[solidus_numeral] tax calculation is OFF(logged once per process). - Confirm the shipping address is complete — calculations are skipped for missing or incomplete addresses (US/CA addresses also require a state).
- Confirm you’ve enabled tax collection for the destination jurisdiction in Numeral — until then, calculations legitimately return 0% “no nexus” results.
- Check the Rails log for
[solidus_numeral] tax calculation failedentries from the exception handler.
Orders complete but no transactions appear in Numeral
Orders complete but no transactions appear in Numeral
Commits run through ActiveJob. Confirm your job backend is running and processing the gem’s jobs, then check the
solidus_numeral_sync_logs table for action=commit rows and their error messages. With the test key configured, also confirm you’re viewing the Numeral dashboard in test mode.Tax shows in the wrong mode (test vs. live)
Tax shows in the wrong mode (test vs. live)
There is no test-mode flag — the mode is determined entirely by which key is configured. Verify whether
NUMERAL_API_KEY holds an sk_test_ or sk_live_ key in that environment.A tax rate named "Numeral Tax" appeared in my admin
A tax rate named "Numeral Tax" appeared in my admin
That’s the gem’s placeholder
Spree::TaxRate — Solidus requires every tax adjustment to reference a persisted rate, and the actual amounts come from Numeral. Don’t delete it or attach zones/categories to it.VAT id isn't applied to an order
VAT id isn't applied to an order
The VAT id is typed per the order’s destination country (e.g.
eu_vat / gb_vat). If the destination country has no supported tax id type, the id is omitted from the calculation and a warning is logged.