Financial Precision

May 18, 2021

Marketplace tax correction

Diagnosing why NetSuite order totals didn't match marketplace reports — and the surgical fix across two systems.

Finance noticed first.

The order totals NetSuite was recording didn't match what Yuju had reported from the marketplaces, and every order was affected. The integration was already in production, orders were flowing, fulfillment was working — what wasn't working was the money. The issue was first routed to the in-house consulting team handling the Yuju implementation. They identified the relevant pieces (the rates coming in from Yuju already included tax, NetSuite was layering its own tax calculation on top, and discounts and coupons were involved somewhere), but they weren't certain about the right order of operations, and after a few cycles without a clean resolution the work was escalated to me.

Context

The setup was a Yuju marketplace integration feeding orders into Valoreo's NetSuite — orders come from Yuju via RESTlet, NetSuite creates the sales order, fulfillment proceeds. The problem was that marketplace prices are tax-inclusive (the consumer sees one all-in price), but NetSuite was treating those prices as base and adding its own tax calculation on top. Tax was getting applied twice. That was the leak.

I was leading the technical work on both sides of the integration at that point — the NetSuite layer and the middleware layer that sat between Yuju and NetSuite. The middleware was where Yuju payloads were transformed into the structure NetSuite needed, including splitting Yuju's single-line representation of price, discounts, and coupons into the multi-line structure NetSuite expected. So fixing the tax problem cleanly meant solving it across both systems rather than only patching the symptom on one side.

Approach

The fix had to satisfy three constraints. First, it had to extract the real base price from the tax-inclusive amount using the actual configured tax for each item, since the product mix included different tax treatments. Second, it had to handle discount coupons correctly, since coupons interact with the surrounding lines in non-trivial ways and required the middleware to expose them as their own line items first so the NetSuite-side script could reason about them. Third, it had to leave manual orders and orders from other channels completely untouched, since the only orders with the tax-inclusive problem were the ones coming through the Yuju RESTlet.

The last constraint shaped the execution. Rather than build a generalized tax-correction script that ran on every order, I built a beforeSubmit user event that fires only when four conditions are met simultaneously: the order is being created (not edited), it's coming through a RESTlet (not the UI or CSV import), tax override isn't already set, and the order has a Yuju order ID stamped on it. Any one of those checks failing and the script exits without touching the record. In a multi-channel ERP environment, the most expensive bugs are the ones where a script meant to fix one flow silently changes the behavior of another, so the scope had to be surgical.

Validation and rollout

Validation was done by case enumeration: every plausible combination of items, coupons, discounts, and tax configurations was routed through both sandbox and production paths in parallel, using middleware capability that could send the same incoming order to both environments simultaneously. Comparing sandbox output against production output for the same input is what makes financial logic changes safe to deploy. Through that comparison, a second issue surfaced that hadn't been visible before the base-price problem was fixed: NetSuite's rounding on tax recalculation was introducing one-cent drift on certain order configurations, which became correctable once the larger arithmetic was right. After the matrix passed cleanly, the script went into production with a day or two of close monitoring, which was sufficient because the logic had been validated against real data rather than synthetic test cases, and because Valoreo's accounting at that stage wasn't running directly off NetSuite yet — the operation was still in startup mode where the philosophy was get the orders in, fix the details later. This was fixing the details.

What this kind of work actually requires

The script that came out of this is about 80 lines of code, and it isn't difficult on its own. What made it hard is that the fix had to be financially correct, surgically scoped, and demonstrable before deploy, which requires reading both the code and the accounting flow it's modifying — and in this case, coordinating that fix across two systems rather than one. Most NetSuite developers can write user event scripts. Fewer can read a marketplace payload, walk through what the financial consequence of each line will be in NetSuite, and prove out the correction mathematically before any code runs in production. That's where most integration work tends to get stuck.

Technical details

Optional reading for implementation specifics.

For each line on the sales order, the script reads the tax rate that NetSuite has already loaded for that line via its taxdetailsreference, which means the rate isn't hardcoded — it's whatever combination of IVA, IEPS, or zero-rated treatment the item is configured for. With that rate in hand, the base price is recovered by dividing the incoming tax-inclusive amount by (1 + tax_rate), then rounded to two decimals using the standard JavaScript pattern for currency math (Math.round(value * 100) / 100), which prevents floating-point artifacts from compounding into the same kind of one-cent drift the original problem was producing.

Coupons follow a slightly different path. Because coupons arrive from the middleware as separate line items with their own tax-inclusive amount, they require absorbing the tax adjustment from the previous line rather than carrying their own correction. And on edge cases where a coupon fully neutralizes the previous line (for example "third item free" promotions), the previous line is removed entirely rather than left as a zero-value entry, since zero-value lines can produce their own rounding artifacts in NetSuite.