A valid signature still needs a trust decision
An Oprindo sample had a matching asset hash and a valid signature, alongside an untrusted credential result. The useful story is in those separate answers.
Three small discounted items produce 15p when each line is rounded and 14p when the total is rounded. Follow the difference into a provider payload and a partial refund.
The basket contains three five-pence items, each discounted by ten percent. Its total can be 15p or 14p without either implementation making an arithmetic mistake. Each exact discounted line is £0.045: round those lines half-up to £0.05 before adding and you get £0.15. Add the exact values first and £0.135 rounds to £0.14. The extra penny appears at a decision about rounding, before a payment provider has touched the amount.
I like this small basket as a way to discuss an integration because every penny remains visible. The two services could each be faithfully applying their own rule and still disagree about what to charge. That leaves the accounts team investigating a discrepancy neither service considers its error. Agreeing the calculation before exchanging the payload is easier than trying to deduce the rule later from two successful payment requests.
The figures here are an original teaching basket with an explicit round-half-up policy. They do not specify how a particular business should calculate tax, discounts or refunds. The engineering lesson is narrower: a commercial rule needs a precise representation and a reproducible calculation, and its consequences extend beyond the first successful payment request.
Write the original amount and discount as decimal values, rather than first passing them through a binary floating-point approximation. Python’s Decimal documentation explains construction from strings, exact decimal representation and explicit rounding modes. [1] In the fixture, Decimal('0.05') multiplied by Decimal('0.90') produces the intermediate value 0.045. The rounding point remains a visible choice instead of a side effect of display formatting.
For the line policy, quantize each discounted value to 0.01 using ROUND_HALF_UP, then sum the results. For the total policy, sum the exact values and quantize once at the end. Those steps deserve to be visible in the calculation code. A helper named format_money sounds as though it changes how an amount looks; making it decide what the customer owes would give that modest function a surprisingly large responsibility.
Also distinguish accepting input from calculating with it. A decimal library can represent values outside the amounts your workflow permits, including special values or an unintended precision. Validate the agreed range, scale and currency separately. Exact arithmetic does not decide whether a negative figure is a permitted credit, whether a discount is authorised or whether the original price is correct.
Store the rule’s identity with the relevant transaction or calculation record when reproducibility matters. A later configuration change should not cause an old invoice to acquire a different interpretation when somebody opens it. The source amount alone cannot tell a maintainer whether lines were rounded before summing, which mode was used or how a final penny was allocated. Changing the rounding mode is another independent decision: at the exact halfway value 0.045, half-up gives 0.05 while half-even gives 0.04. A boundary agreement without a mode still leaves the result under-specified, so record both with the posted calculation.
Suppose the total-rounding policy is selected. The payable amount is £0.14. For a provider contract expecting GBP minor units, that is 14 alongside the currency identifier, not 0.14 and not 14.0 pounds. Stripe’s currency documentation describes amount formatting and currency-specific minor-unit rules. [2] The request must follow the actual provider’s contract, including its exceptions, rather than assume every currency has the same scale.
A value such as 1250 is therefore incomplete outside its contract. It can represent £12.50 in pence or £1,250 in major units. A JSON parser can accept the number in either case. Successful parsing does not establish agreement about meaning. Keep currency and unit interpretation attached to the amount in internal interfaces and make conversions explicit at the boundary.
RFC 8259 discusses numeric precision and interoperability in JSON. [3] Integer minor units can be a useful representation when bounded appropriately, but they do not provide unlimited range or make every financial calculation an integer operation. The discounted line still had a fractional penny before the policy resolved it. Decimal strings are another possible internal choice; they need validation and deliberate conversion rather than casual concatenation or parsing.
Test the outgoing payload against the selected calculation. If the local basket records 14p while a line-summing downstream service creates 15p, do not solve the difference by choosing whichever total lets the API call succeed. Find the rule mismatch. A one-penny test case is inexpensive evidence of a boundary that can matter across much larger volumes.
Choosing 14p for the basket raises another question: what amounts belong to A, B and C? Three independently rounded 5p lines add to 15p, so displaying them unchanged beneath a 14p total leaves a discrepancy for the customer or the accounts team to explain. The calculation needs an allocation policy if line-level amounts must reconcile to the posted total.
The fixture demonstrates one possible rule. Express each exact discounted line in pence: 4.5, 4.5 and 4.5. Start with the integer floor of each, giving 4p per line and 12p overall. The target total is 14p, leaving two pennies to distribute. Assign them to the largest fractional remainders, using a stable item identifier to break equal ties. The resulting recorded amounts are A 5p, B 5p and C 4p.
This allocation is one commercial choice, not the only defensible one. Its useful property is that the same item identities receive the same pennies when the calculation runs again. Without an agreed tie-break, merely changing input order could move a penny from C to A while leaving the total untouched. That becomes more than a presentation detail when one line is refunded. Decide how stable the line amounts need to be, then retain the identity or ordering rule that delivers it.
The figure shows both calculation paths without treating either as universally correct. Their contracts differ in rounding boundary, mode and allocation. Agreeing those choices gives the integrating systems a common set of expected values. A change from binary floating point to decimal arithmetic can improve representation, but it cannot decide which commercial rule should govern the basket or how the posted amount belongs to its lines.
Three illustrative £0.05 items receive a 10% discount. The specified teaching rule uses decimal arithmetic and round-half-up; different rounding boundaries produce different totals.
Scroll sideways to see all columns.
| Item or step | Exact discounted value | Round each line | Round total then allocate |
|---|---|---|---|
| A | £0.045 | £0.05 | £0.05 recorded |
| B | £0.045 | £0.05 | £0.05 recorded |
| C | £0.045 | £0.05 | £0.04 recorded |
| Sum before final rounding | £0.135 | £0.15 already rounded | £0.135 |
| Payable total | Chosen policy determines it | £0.15 · 15 minor units | £0.14 · 14 minor units |
| Refund item C | Use the posted allocation | £0.05 if line policy used | £0.04 if allocated-total policy used |
Decimal fixture. The allocation uses floor amounts then distributes remaining pennies by largest remainder, with stable A/B/C tie-breaking; it is an illustrative commercial rule, not a tax prescription.Now refund only item C. Under the allocated-total policy, its recorded amount is 4p. If the refund code independently recalculates 0.045 and rounds half-up, it refunds 5p. The original basket can have been processed correctly while a later operation creates the penny discrepancy again. Reusing the original inputs is not enough when the later code applies a different boundary.
Use the posted allocation as the starting point for a line-level refund under this example’s policy. Keep the relationship between the refund and the original line, including any later adjustments. If the commercial rule intentionally calculates refunds differently, state that rule rather than disguising it as the reversal of the original arithmetic. The important thing is that the resulting record remains explainable.
Old orders also need protection from improvements to the pricing code. A new discount, tie-break or rounding mode may be right for tomorrow’s orders and wrong for explaining yesterday’s. Retaining the posted result and its rule version lets the refund follow the transaction the customer actually paid. If that transaction needs correcting, a linked adjustment can explain the change without quietly rewriting the old calculation until the current code happens to reproduce it.
The total-rounding example posts the 14p basket as A 5p, B 5p and C 4p.
Scroll sideways to see all columns.
| Refund calculation | Amount for C | Relationship to the original transaction |
|---|---|---|
| Use posted allocation | 4p | Reverses the recorded C amount under this policy |
| Recalculate 0.045 and round half-up | 5p | Uses a different boundary from the allocated basket |
| Apply a deliberately different refund policy | Must be explicitly specified | Cannot be described as the same original arithmetic |
The downloadable decimal calculation keeps the 15p line-rounded basket, the 14p total-rounded basket and the 5/5/4 allocation together. Change the tie-break and check the item identities as well as the sum. The total can remain correct while the penny moves to another line, changing the amount available to a later refund. That is why the contract needs both a basket result and posted line amounts: the payment boundary and the refund boundary depend on different parts of the same calculation.
Add similarly small cases to the real integration contract: zero amounts, the relevant currencies, negative adjustments where permitted and a partial reversal. Explain expected values in terms the person responsible for the records can check. The test should fail when two services apply different rules, rather than merely confirm that each service can parse its preferred number format.
When the refund differs, there are now specific places to look: the unit conversion, rounding point, penny allocation and any later recalculation. C’s posted 4p explains why independently rounding its discount to 5p would not reverse this basket under the selected policy. The original request could have been accepted perfectly well while leaving that refund problem waiting.
Working on a workflow like this? Hamsa helps finance and operations teams connect their tools and handle the exceptions, starting with one workflow.
See the Payment Operations PilotPrimary sources behind this article. Living documentation may change.

Practical engineering. A human perspective.
More about Hamsa Software and the person behind it. Editorial notes.