fix: payment validation — tip cap ValidateAmount, RefundRequest gt=0 tag, float64 pence conversion docs
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
@@ -150,7 +150,7 @@ type CreateBookingPaymentRequest struct {
|
||||
}
|
||||
|
||||
type RefundRequest struct {
|
||||
Amount int64 `json:"amount"`
|
||||
Amount int64 `json:"amount" validate:"required,gt=0"`
|
||||
Reason string `json:"reason"`
|
||||
// Optional client-generated idempotency key. Two DISTINCT refunds of the
|
||||
// same amount against the same payment must not collide on the default
|
||||
@@ -4900,6 +4900,12 @@ func CreateTipPayment(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
if err := ValidateAmount(req.Amount); err != nil {
|
||||
log.Printf("Failed to process request: %v", err)
|
||||
http.Error(w, "Invalid request", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// Product rule (security): only verified accounts may save cards. An
|
||||
// unverified/guest/affiliate user may still tip, but save_card=true is
|
||||
// rejected here — before any charge source resolution or payment record.
|
||||
@@ -4935,12 +4941,6 @@ func CreateTipPayment(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
if err := ValidateAmount(req.Amount); err != nil {
|
||||
log.Printf("Failed to process request: %v", err)
|
||||
http.Error(w, "Invalid request", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// maxOnlineTipPence bound: ValidateAmount's generic £10,000 cap is the
|
||||
// money-minting ceiling for booking charges, but a tip is gratuity on a
|
||||
// percentage of the service — a single online tip over £250 is not a
|
||||
|
||||
@@ -762,6 +762,8 @@ func scanStaleRow(table string, rows pgx.Rows) (staleRow, error) {
|
||||
r.IsCreate = isCreate != nil && *isCreate
|
||||
r.HasGiftCard = hasGiftCard
|
||||
r.AmountPence = int64(math.Round(r.TotalAmount * 100))
|
||||
// NOTE: float64→int64 pence conversion can drift by ±1p between charge and replay.
|
||||
// A full fix would store pence as int64 throughout (out of scope for this change).
|
||||
return r, nil
|
||||
}
|
||||
var amount float64
|
||||
@@ -771,6 +773,8 @@ func scanStaleRow(table string, rows pgx.Rows) (staleRow, error) {
|
||||
}
|
||||
r.SquareRequestSnapshot = []byte(snapshot.String)
|
||||
r.AmountPence = int64(math.Round(amount * 100))
|
||||
// NOTE: float64→int64 pence conversion can drift by ±1p between charge and replay.
|
||||
// A full fix would store pence as int64 throughout (out of scope for this change).
|
||||
if bookingID.Valid && bookingID.String != "" {
|
||||
b := bookingID.String
|
||||
r.BookingID = &b
|
||||
|
||||
@@ -197,6 +197,8 @@ func deriveTillIdempotencyKey(req TillSaleRequest, adminID string) string {
|
||||
sb.WriteString(adminID)
|
||||
sb.WriteString(":")
|
||||
sb.WriteString(strconv.FormatInt(int64(math.Round(req.Amount*100)), 10))
|
||||
// NOTE: float64→int64 pence conversion can drift by ±1p between charge and replay.
|
||||
// A full fix would store pence as int64 throughout (out of scope for this change).
|
||||
if req.GiftCardID != nil && *req.GiftCardID != "" {
|
||||
sb.WriteString(":gc:")
|
||||
sb.WriteString(validators.NormalizeGiftCardCode(*req.GiftCardID))
|
||||
@@ -610,6 +612,8 @@ func CreateTillSale(w http.ResponseWriter, r *http.Request) {
|
||||
// the effective pence figure exactly as the Square charge amount below is
|
||||
// derived (math.Round(req.Amount * 100)).
|
||||
amountPence := int64(math.Round(req.Amount * 100))
|
||||
// NOTE: float64→int64 pence conversion can drift by ±1p between charge and replay.
|
||||
// A full fix would store pence as int64 throughout (out of scope for this change).
|
||||
// Gift-card creates/topups are additionally capped at £250 per transaction
|
||||
// (owner decision — the shared maxAdminGiftCardTransactionPence from
|
||||
// giftcard_limits.go, the single source of the £250 cap). Both the create
|
||||
|
||||
Reference in New Issue
Block a user