- A commerce platform can prove that an order totals correctly and still produce something unusable for accounting.
- Checkout knows products, quantities, discounts, shipping and tax.
- That was the client problem in a property-procurement marketplace built on Medusa.
- We built an accounting review and export capability around validated purchase orders.
The client problem
A commerce platform can prove that an order totals correctly and still produce something unusable for accounting.
Checkout knows products, quantities, discounts, shipping and tax. Finance needs GL accounts, property locations, supplier codes, invoice dates, document references and rows that reconcile to the payable amount at the cent.
That was the client problem in a property-procurement marketplace built on Medusa. One purchase could contain goods allocated across several accounts, fulfilments with different shipping charges, platform tax and supplier-entered tax. The final export still had to remain readable and balanced.
We built an accounting review and export capability around validated purchase orders. Merchandise starts from the order's GL allocations. Shipping follows fulfilment evidence where available. Tax follows merchandise value. Discounts and rounding differences are reconciled against the authoritative invoice total. The export is numbered, linked to its source invoice and recorded through a stateful transition that can be reversed only while the accounting journey has not advanced.
The result is not a CSV dump of order fields. It is a controlled translation from commerce truth into accounting rows.
The client problem: checkout totals and accounting rows serve different jobs
At checkout, one final total is ideal. It tells the buyer what the order costs.
An accounting system needs to understand why that total exists.
A purchase of three units may be divided between two GL accounts. Shipping may relate to separate fulfilments. Tax may come partly from Medusa and partly from a supplier invoice review. A discount changes the amount payable without changing the physical quantity received.
Flattening everything into one row loses the business dimensions finance needs. Exporting every raw commerce field creates the opposite problem: a technical record that the accounting import cannot interpret.
We treated export as a transformation with invariants. Every row must have a location, supplier identity, document reference, GL code, currency and amount. Together, those rows must reproduce the authoritative invoice total.
Admit only reviewed purchase orders
The accounting queue begins after the purchasing journey has established enough truth.
The buyer invoice must be validated. The accounting state then moves through explicit queues such as reviewed, awaiting and exported. An export request resolves eligible order identifiers inside the authenticated organisation rather than accepting arbitrary orders from the browser.
The invoice date is mandatory before an order can be exported. That date drives the created date and due-date calculation expected by the downstream accounting process.
The platform also requires a supplier accounting code, a property location identifier, an invoice number and complete GL allocations. Missing business data becomes a blocking reason tied to the affected order.
This keeps bad rows out of the file. Finance does not discover after import that a supplier has no accounting identity or a line has nowhere to post.
Start merchandise from the allocation the buyer approved
Each commerce line can contain one or more allocation portions. A portion carries a quantity and its GL account, with property-unit context available elsewhere in the purchasing lifecycle.
The export resolves the GL code for every portion and multiplies the line's unit price by the allocated quantity. A three-unit product split one-and-two across two accounts therefore becomes two merchandise bases.
This preserves the buyer's accounting intent. The exporter does not guess the account from the product category or apply one order-wide default after the fact.
If an item has no allocation or an allocation has no usable GL code, the order is rejected from the export. The file is not allowed to look complete while silently dropping part of the purchase.
The product description, quantity, SKU and historical manufacturer snapshot accompany the row so finance can trace the posting back to the purchased line.
Separate shipping and tax without losing their GL destination
Shipping and tax are order-level amounts in many commerce representations. The accounting import still needs them attached to coded rows.
The exporter creates additional shipping and tax rows beside each merchandise allocation when the allocated amount is non-zero at cent precision. Those rows inherit the GL code of the merchandise portion they accompany.
Tax is distributed according to positive merchandise value. If one allocation represents one third of the merchandise value and another represents two thirds, the tax follows the same ratio.
Shipping uses more context when it is available. The platform connects fulfilments to line-item quantities and shipping options, then uses the corresponding shipping-method amounts to distribute delivery cost across the merchandise represented by each fulfilment.
If that mapping cannot be established, shipping falls back to merchandise weights. The total is still preserved instead of disappearing because a fulfilment link is incomplete.
Treat supplier-entered tax as part of the invoice
The supplier invoice may contain tax that is not represented in Medusa's ordinary order tax total.
After authorised invoice review, the platform can retain a supplier-entered tax amount in order metadata. The accounting transformation combines that amount with Medusa's tax total before allocation.
The payable invoice total is adjusted consistently as well. Otherwise, the export would create tax rows that no longer reconcile with the header amount.
This is an important boundary between commerce and accounts payable. The storefront's calculated tax remains visible, but the reviewed supplier document can contribute the final invoice tax required by finance.
The export does not choose a tax value from guesswork. It consumes the value already established by the invoice-validation journey.
Reconcile discounts against the authoritative total
The raw merchandise bases come from unit price multiplied by allocated quantity. Their sum may differ from the final merchandise amount because of discounts, reviewed price edits or rounding.
Rather than exporting a plausible set of rows whose total is wrong, the platform calculates the difference between those bases and the authoritative invoice amount after shipping and tax.
That difference is distributed across positive merchandise rows by value. Any remaining cent is assigned deterministically by row order.
This means a discount is reflected in the merchandise postings it affected, while shipping and tax remain separately identifiable. The final sum still matches the invoice.
The exporter refuses reconciliation when there is a positive order amount but no positive merchandise row capable of receiving the adjustment.
The design goal is not to expose every internal promotion object to accounting. It is to produce explainable coded rows that reproduce the payable truth exactly.
Preserve cents deterministically
Proportional allocation often produces fractions of a cent.
If each row is rounded independently, the exported rows can be one or more cents away from the order total. Small discrepancies create noisy accounting reviews and can cause downstream imports to reject an otherwise correct invoice.
The allocation helper converts the amount to cents, calculates each row's exact share, floors the initial result and then distributes the remaining cents according to the largest fractional remainder. Row order resolves a final tie.
Tests cover tax and shipping totals as small as one cent across several allocations. The sum remains exact, and the chosen recipient is predictable.
This is a modest algorithm with a direct operational consequence: finance does not have to invent a balancing line because the commerce exporter lost a cent.
Number rows as an accounting document
The downstream file contains one sequence across merchandise, shipping and tax.
For each allocation, the merchandise row appears first. A non-zero shipping row follows, then a non-zero tax row. Line numbers increment across the complete invoice rather than restarting when two products share a GL account.
Amounts that round to zero cents are omitted. This keeps the file readable without changing its financial value.
Each row carries the invoice number, supplier code, posting date, invoice date, due date, description, purchase-order number, GL account, property location, amount and currency.
The export also includes a controlled link to the source invoice file. An accountant can move from the posting row back to the document that supports it instead of searching for an attachment in another system.
Create a numbered, stateful export batch
An export is a business event, not only downloaded bytes.
The platform allocates the next export number under an organisation-level database lock. Concurrent users therefore cannot receive the same number for two batches.
It uploads the generated file, creates the export record, links every order and records line counts and downstream document numbers. The order moves from reviewed to awaiting with the export identity and an audit entry.
Before that transition commits, the platform rechecks that the order is still reviewed and that its invoice date has not changed since the CSV was built. If the state moved, the transaction fails and the uploaded file is removed.
This closes a subtle race: finance should not export one date while the live order already contains another.
Allow reversal only before the accounting state advances
Operators can make mistakes when assembling a batch. A completed export can be reverted while its linked orders remain in the validated and awaiting state.
Reversal returns those orders to reviewed, removes the active export association and marks the batch as reverted.
Once an invoice has advanced in the downstream accounting journey or the buyer payment state has progressed, the same reversal is rejected. At that point, pretending the export never happened would create a conflict between systems.
This makes the export reversible without making it casually mutable. The permitted correction depends on business state, not merely on whether somebody still has access to a button.
Keep the review trail legible
The accounting interface can separate validated, reviewed, awaiting and exported work. Accountants can filter by properties and suppliers, inspect the order detail and see which batch contains an order.
Numbered batches expose their file, row count, order count and status. Invoice links connect the export to its evidence. Audit metadata records who reviewed and exported the order.
These relationships let finance answer practical questions:
- Which invoice produced this posting?
- Which property and supplier does it belong to?
- Why was shipping charged to this GL account?
- Which export batch contained the order?
- Has the downstream process advanced?
- Can this batch still be safely reverted?
The value is not more metadata. It is a coherent chain from purchase to posting.
Why Medusa was the right foundation
Medusa supplied the order, line, price, shipping, tax, currency and fulfilment truth. The client needed those commerce objects translated into a property-accounting contract.
We extended the platform with GL allocations, invoice validation, supplier accounting codes, property locations, reconciliation rules and stateful export batches.
Because the engine exposes its workflows and data relationships, accounting did not have to become a detached nightly spreadsheet process. It could remain connected to the purchase decisions that produced the amounts.
That is the architectural advantage: use Medusa for commerce truth, then add the exact accounting capability required by the client's operating model.
A practical accounting-export checklist
Before exporting marketplace orders to accounting, define:
- Has the buyer validated the supplier invoice?
- Has accounting reviewed the order?
- Is the invoice date present and stable?
- Does every supplier have an accounting code?
- Does every property have a downstream location identifier?
- Does every purchased quantity resolve to a GL code?
- How are merchandise amounts derived from allocation quantities?
- How does tax follow merchandise value?
- How does shipping follow fulfilments and shipping options?
- What is the fallback when fulfilment mapping is incomplete?
- How are discounts and price edits reconciled?
- How are fractional cents distributed deterministically?
- Do export rows sum to the authoritative invoice total?
- Is the source invoice linked to the posting?
- Are batch numbers protected from concurrent allocation?
- Under which business states may an export be reverted?
These questions turn an order export into a controlled accounting hand-off.
The broader lesson
The client did not need every Medusa field in a CSV.
They needed finance to receive a balanced document whose rows still explained the purchase.
We started from reviewed orders and buyer-approved GL allocations. Merchandise remains traceable to products and quantities. Shipping and tax become separate coded rows. Discounts and rounding differences reconcile against the authoritative invoice total. Numbered batches link back to invoice evidence and can be reversed only while the accounting state allows it.
For buyers, accounting intent survives checkout. For finance, every row has a reason. For the platform, commerce and accounting stop maintaining competing versions of the same purchase.
