fix: close price-tamper, premature rider-free, and IST/txn gaps in merged cx/handover work
Reviewed the 10 merged customer-app/base-handover commits and fixed the
defects found:
- HIGH (money): CreateCxBooking let the request body's `estimate` set the
billed price with no server-side check; it flows into Estimatedprice →
ridercharges (miler pay + tenant bill) with no weight re-price, so
{min:1,max:1} settled a delivery at ₹1. Now the client estimate is honoured
only when it matches the server quote within 15%, else the server quote
stands.
- MED: base handover freed the rider and closed the booking-level assignment
after the FIRST parcel of a multi-destination pickup, dropping the remaining
stops and crediting one leg. Now finalized only when no consignment of the
booking is still in the rider's hands.
- MED: inwardedat/completedat were written with time.Now() (UTC) instead of
DBNow() (IST), skewing them ~5h30 vs createdat and the earnings/reconcile
windows. Fixed in the handover, inbound-scan, reconcile and pickup-complete
paths.
- MED: B2C customers got two "miler assigned" pushes on auto-assign (two token
stores) and none on manual assign. Reconciled to one cxstage.Notify on both
paths.
- LOW: ReconcileHubInbound now runs in a transaction and checks its audit
inserts (was returning 200 with a silently-missing history row); CxLogout no
longer reports signedOut when the token revoke fails; a rider-named handover
base far from their reported position is rejected instead of silently
rerouting the parcel to another city.
go build, go vet and go test ./... all pass.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WRaFH5hMRqmUQvVPQsyjZD
This commit is contained in:
@@ -91,6 +91,32 @@ type cxCreateBookingRequest struct {
|
||||
Remarks string `json:"remarks"`
|
||||
}
|
||||
|
||||
// cxEstimateMatchesQuote reports whether a customer-supplied price band is close
|
||||
// enough to the server's own quote to be trusted as the agreed price. It guards
|
||||
// the stored Estimatedprice — which becomes ridercharges at completion — against
|
||||
// a tampered request body while still honouring an honest estimate that came
|
||||
// from our estimate endpoint. Rejects negatives, inverted bands, and — when the
|
||||
// server could not price the pickup (zero quote) — any client number at all,
|
||||
// since with no server figure to check against the client's would be unbounded.
|
||||
func cxEstimateMatchesQuote(clientMin, clientMax, quoteMin, quoteMax int) bool {
|
||||
if clientMin < 0 || clientMax < clientMin {
|
||||
return false
|
||||
}
|
||||
serverMid := float64(quoteMin+quoteMax) / 2
|
||||
if serverMid <= 0 {
|
||||
return false
|
||||
}
|
||||
clientMid := float64(clientMin+clientMax) / 2
|
||||
diff := clientMid - serverMid
|
||||
if diff < 0 {
|
||||
diff = -diff
|
||||
}
|
||||
// 15% of the server midpoint absorbs rounding and minor pricing drift between
|
||||
// the estimate call and confirm, without letting a materially different
|
||||
// number through.
|
||||
return diff <= 0.15*serverMid
|
||||
}
|
||||
|
||||
// CreateCxBooking creates the pickup.
|
||||
func CreateCxBooking(c *fiber.Ctx) error {
|
||||
customerID := c.Locals("userid").(int)
|
||||
@@ -228,9 +254,22 @@ func CreateCxBooking(c *fiber.Ctx) error {
|
||||
quote := quoteCxPickup(req.Pickup.Lat, req.Pickup.Lng, estimateDestinations)
|
||||
estimateMin, estimateMax := quote.Min, quote.Max
|
||||
if req.Estimate != nil && req.Estimate.Max > 0 {
|
||||
// The customer's number wins. They agreed to what was on their screen,
|
||||
// and re-pricing at confirm time would quietly change the deal.
|
||||
estimateMin, estimateMax = req.Estimate.Min, req.Estimate.Max
|
||||
if cxEstimateMatchesQuote(req.Estimate.Min, req.Estimate.Max, quote.Min, quote.Max) {
|
||||
// The customer's number wins — but only when it agrees with what the
|
||||
// server independently prices for this pickup. An honest app took its
|
||||
// estimate from our own estimate endpoint, so it matches; re-pricing at
|
||||
// confirm time would quietly change the deal for that customer. A
|
||||
// tampered body (e.g. {min:1,max:1}) does NOT match and must never
|
||||
// stand, because this midpoint becomes Estimatedprice, which the pickup
|
||||
// and every handover leg copy verbatim into bookingassignments.ridercharges
|
||||
// — the miler's pay and the tenant's bill — with no weight re-price.
|
||||
estimateMin, estimateMax = req.Estimate.Min, req.Estimate.Max
|
||||
} else {
|
||||
utils.Warn("CreateCxBooking: client estimate rejected, pricing from server quote",
|
||||
"customer_id", customerID,
|
||||
"client_min", req.Estimate.Min, "client_max", req.Estimate.Max,
|
||||
"quote_min", quote.Min, "quote_max", quote.Max)
|
||||
}
|
||||
}
|
||||
|
||||
now := utils.DBNow()
|
||||
|
||||
Reference in New Issue
Block a user