Ran into this while poking around the checkout service's telemetry attributes.
PlaceOrder and prepareOrderItemsAndShippingQuoteFromCart in src/checkout/main.go both convert a Money proto (units + nanos) into a float64 for the demo.shipping.amount / demo.order.amount span attributes and log fields, using this pattern:
shippingCostFloat, _ := strconv.ParseFloat(fmt.Sprintf("%d.%02d", shippingPrice.GetUnits(), shippingPrice.GetNanos()/1000000000), 64)
Three call sites do this: src/checkout/main.go:383, :384 (same line, but for the order total) and :455.
The divisor is wrong. Per pb/demo.proto, Money.nanos is documented as being in the range -999,999,999 to 999,999,999 - it can never reach 1,000,000,000. So nanos / 1000000000 is integer division of a value that's always less than 1e9 by 1e9, which is always 0 in Go. The %02d after the decimal point then always prints 00, no matter what the actual cents are.
Concrete example: a shipping cost of $5.99 comes through as Money{units: 5, nanos: 990000000}. 990000000 / 1000000000 is 0, so the code formats it as "5.00" and reports demo.shipping.amount = 5.0 instead of 5.99. Same thing happens to demo.order.amount.
This isn't just a hunch either, the telemetry schema documents both attributes with non-integer examples: telemetry-schema/attributes/shipping.yaml lists [5.99, 15.00] for demo.shipping.amount, and telemetry-schema/attributes/order.yaml lists [99.99, 150.50] for demo.order.amount. So the schema's own examples don't match what actually gets emitted.
To reproduce:
- Place an order where the shipping cost or order total has a non-zero cents component (i.e. basically any real order).
- Look at the
demo.shipping.amount / demo.order.amount attributes on the checkout span, or the same fields in the "order placed" log line.
- Compare against the actual amount shown in the frontend/cart - the telemetry value is always rounded down to a whole dollar.
The fix is just the divisor. nanos is billionths of a unit, so to get a 2-decimal-place value you need nanos / 10_000_000 (1 cent = 10,000,000 nanos), not nanos / 1_000_000_000. src/shipping/src/shipping_service.rs:18 already gets this right (const NANOS_MULTIPLE: u32 = 10000000u32;), so this is really just bringing main.go in line with what the Rust side already does correctly.
Component(s)
checkout, telemetry
Ran into this while poking around the checkout service's telemetry attributes.
PlaceOrderandprepareOrderItemsAndShippingQuoteFromCartinsrc/checkout/main.goboth convert aMoneyproto (units + nanos) into a float64 for thedemo.shipping.amount/demo.order.amountspan attributes and log fields, using this pattern:Three call sites do this:
src/checkout/main.go:383,:384(same line, but for the order total) and:455.The divisor is wrong. Per
pb/demo.proto,Money.nanosis documented as being in the range-999,999,999to999,999,999- it can never reach1,000,000,000. Sonanos / 1000000000is integer division of a value that's always less than 1e9 by 1e9, which is always0in Go. The%02dafter the decimal point then always prints00, no matter what the actual cents are.Concrete example: a shipping cost of
$5.99comes through asMoney{units: 5, nanos: 990000000}.990000000 / 1000000000is0, so the code formats it as"5.00"and reportsdemo.shipping.amount = 5.0instead of5.99. Same thing happens todemo.order.amount.This isn't just a hunch either, the telemetry schema documents both attributes with non-integer examples:
telemetry-schema/attributes/shipping.yamllists[5.99, 15.00]fordemo.shipping.amount, andtelemetry-schema/attributes/order.yamllists[99.99, 150.50]fordemo.order.amount. So the schema's own examples don't match what actually gets emitted.To reproduce:
demo.shipping.amount/demo.order.amountattributes on the checkout span, or the same fields in the "order placed" log line.The fix is just the divisor.
nanosis billionths of a unit, so to get a 2-decimal-place value you neednanos / 10_000_000(1 cent = 10,000,000 nanos), notnanos / 1_000_000_000.src/shipping/src/shipping_service.rs:18already gets this right (const NANOS_MULTIPLE: u32 = 10000000u32;), so this is really just bringingmain.goin line with what the Rust side already does correctly.Component(s)
checkout, telemetry