Floating-point deposit checks and credit notes broke our Shopify B2B Net-30 fulfillment holds
When a wholesale buyer places a €2,500.00 order on Shopify Plus B2B with Net-30 payment terms and a mandatory 30% upfront deposit (€750.00), warehouse fulfillment must remain locked in PENDING_DEPOSIT until the full deposit clears.
In production, two subtle bugs routinely break custom Shopify Plus B2B webhook handlers: converting decimal currency strings with IEEE-754 floating-point multiplication (19.99 * 100 === 1998.9999999999998) combined with naive orders/partially_paid triggers, and ignoring post-invoice credit notes when computing remaining wholesale balances.
1. Why IEEE-754 float math and partial SEPA transfers release holds prematurely
Many B2B middleware scripts listen to Shopify's orders/partially_paid webhook and release fulfillmentOrders holds as soon as any non-zero payment arrives, or compare floats like paidAmount >= orderTotal * (depositPercent / 100).
When an enterprise buyer sends a partial SEPA bank wire of €500.00 (50000 cents) against a €2,500.00 (250000 cents) order requiring a 30% deposit (75000 cents), a handler that keys off financial_status === "partially_paid" without verifying integer-cent deposit thresholds releases the warehouse hold €250.00 early. Even worse, IEEE-754 binary floating-point arithmetic introduces 1-cent rounding drift on routine wholesale line items (Math.floor(19.99 * 100) === 1998 instead of 1999), causing deterministic ledger reconciliations to fail audit checks.
2. Post-invoice credit notes, returns, and cancelled wholesale orders
In B2B wholesale, invoices rarely stay static between deposit collection and the Net-15, Net-30, or Net-60 maturity date. Damaged pallets, SKU substitutions, and volume rebates generate credit notes (creditNoteCents) after the initial deposit is paid:
- Credit-note reduction before deposit & balance gates: On a
€2,500.00(250000cents) order where the buyer has paid€2,000.00(200000cents) and receives an€800.00(80000cents) credit note, the effective invoice total iseffectiveTotalCents = Math.max(0, totalOrderCents - creditNoteCents) = 170000cents (€1,700.00). Naive scripts that ignore credit notes demand another€500.00from the buyer instead of marking the orderFULLY_RECONCILEDand surfacingcreditDueCents: 30000(€300.00overpayment credit due). - Cancelled orders with partial deposit refunds: If a wholesale order is cancelled (
isCancelled: true) after a€750.00(75000cents) deposit was collected and only€250.00(25000cents) was refunded, the state machine must lockfulfillmentHold: trueunderORDER_CANCELLEDand surfacerefundDueCents: 50000so finance never orphans unrefunded buyer funds. - Delinquent Net-Terms expiration: Once
nowSec > balanceDueAtSecwithremainingBalanceCents > 0, the account transitions deterministically toBALANCE_OVERDUE(b2b_net_terms:overdue_balance) with exactoverdueSecondstelemetry.
3. The 5-state integer-cent wholesale reconciliation state machine
Every wholesale order evaluation must operate strictly on safe integer cents (Number.isSafeInteger) across 5 primary wholesale ledger states (plus REFUNDED_OR_CREDITED when effectiveTotalCents === 0):
| Wholesale Scenario | Order & Ledger Inputs (EUR Cents) | Naive Float / Webhook Bug | Deterministic Integer-Cent Verdict |
|---|---|---|---|
| 1. Partial Deposit Shortfall | totalOrderCents: 250000, requiredDepositCents: 75000 (30%), paidAmountCents: 50000 |
Releases hold on first partial SEPA transfer | PENDING_DEPOSIT (fulfillmentHold: true, depositShortfallCents: 25000) |
| 2. Deposit Cleared (Net-30 Active) | totalOrderCents: 250000, creditNoteCents: 10000, paidAmountCents: 80000 |
Float drift on 30% deposit check & adjusted balance | DEPOSIT_CLEARED_ACTIVE (fulfillmentHold: false, remainingBalanceCents: 160000) |
| 3. Credit-Note Overpayment | totalOrderCents: 250000, creditNoteCents: 80000, paidAmountCents: 200000 |
Ignores credit note; demands €500.00 balance |
FULLY_RECONCILED (creditDueCents: 30000, overpaidWarning: true) |
| 4. Past-Due Net-30 Invoice | totalOrderCents: 250000, paidAmountCents: 75000, nowSec > balanceDueAtSec |
Leaves buyer account active past Net-30 maturity | BALANCE_OVERDUE (orderTag: "b2b_net_terms:overdue_balance", remainingBalanceCents: 175000) |
| 5. Cancelled Order Partial Refund | isCancelled: true, paidAmountCents: 75000, refundedAmountCents: 25000 |
Orphans unrefunded €500.00 deposit on cancel |
ORDER_CANCELLED (fulfillmentHold: true, refundDueCents: 50000) |
You can invoke the exact integer-cent reconciler over JSON-RPC 2.0 via our Model Context Protocol (MCP) server (POST https://api.revclamp.com/api/mcp) or REST simulator endpoint:
{
"jsonrpc": "2.0",
"id": "net-terms-check-1",
"method": "tools/call",
"params": {
"name": "revclamp_reconcile_b2b_net_terms",
"arguments": {
"orderId": "gid://shopify/Order/908812",
"totalOrderCents": 250000,
"paidAmountCents": 80000,
"creditNoteCents": 10000,
"requiredDepositPercent": 30,
"paymentTermsDays": 30
}
}
}