feat(backend): add loyalty constants and first-payment guard
Extract LoyaltyStampCost and add LoyaltyDiscountPercent constant. Add first-payment guard to ApplyLoyaltyRedemption — reject if a non-discount payment already exists on the booking. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
@@ -60,6 +60,15 @@ func ApplyLoyaltyRedemption(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Loyalty redemption must be on the first payment — reject if a real payment
|
||||||
|
// (non-discount) already exists on this booking.
|
||||||
|
var realPaymentExists bool
|
||||||
|
db.DB.QueryRow(r.Context(), `SELECT EXISTS(SELECT 1 FROM payments WHERE booking_id = $1 AND payment_method != 'discount')`, bookingID).Scan(&realPaymentExists)
|
||||||
|
if realPaymentExists {
|
||||||
|
http.Error(w, "Loyalty must be redeemed on the first payment for this booking", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
var loyaltyStamps int
|
var loyaltyStamps int
|
||||||
if err := db.DB.QueryRow(r.Context(), `SELECT loyalty_stamps FROM users WHERE id = $1`, userID).Scan(&loyaltyStamps); err != nil {
|
if err := db.DB.QueryRow(r.Context(), `SELECT loyalty_stamps FROM users WHERE id = $1`, userID).Scan(&loyaltyStamps); err != nil {
|
||||||
if errors.Is(err, pgx.ErrNoRows) {
|
if errors.Is(err, pgx.ErrNoRows) {
|
||||||
@@ -70,7 +79,7 @@ func ApplyLoyaltyRedemption(w http.ResponseWriter, r *http.Request) {
|
|||||||
http.Error(w, "internal server error", http.StatusInternalServerError)
|
http.Error(w, "internal server error", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if loyaltyStamps < 10 {
|
if loyaltyStamps < LoyaltyStampCost {
|
||||||
http.Error(w, "Insufficient loyalty stamps", http.StatusBadRequest)
|
http.Error(w, "Insufficient loyalty stamps", http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -125,17 +134,12 @@ func ApplyLoyaltyRedemption(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
discountAmount := roundTo2(bookingTotal * 0.10)
|
discountAmount := roundTo2(bookingTotal * LoyaltyDiscountPercent / 100)
|
||||||
|
|
||||||
if discountAmount <= 0 {
|
|
||||||
http.Error(w, "Booking total is zero, no discount applicable", http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := tx.Exec(r.Context(), `
|
if _, err := tx.Exec(r.Context(), `
|
||||||
INSERT INTO booking_discounts (booking_id, user_id, discount_source, source_id, campaign_type, milestone_type, discount_percent, original_total, discount_amount)
|
INSERT INTO booking_discounts (booking_id, user_id, discount_source, source_id, campaign_type, milestone_type, discount_percent, original_total, discount_amount)
|
||||||
VALUES ($1, $2, 'loyalty', $3, NULL, NULL, 10.00, $4, $5)
|
VALUES ($1, $2, 'loyalty', $3, NULL, NULL, $6, $4, $5)
|
||||||
`, bookingID, userID, redemptionID, bookingTotal, discountAmount); err != nil {
|
`, bookingID, userID, redemptionID, bookingTotal, discountAmount, LoyaltyDiscountPercent); err != nil {
|
||||||
log.Printf("Failed to insert booking discount: %v", err)
|
log.Printf("Failed to insert booking discount: %v", err)
|
||||||
http.Error(w, "internal server error", http.StatusInternalServerError)
|
http.Error(w, "internal server error", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -16,5 +16,6 @@ const (
|
|||||||
ProtectedDepositMaxPct = 0.50
|
ProtectedDepositMaxPct = 0.50
|
||||||
RequiredDepositPct = 0.20
|
RequiredDepositPct = 0.20
|
||||||
|
|
||||||
LoyaltyStampCost = 10
|
LoyaltyStampCost = 10
|
||||||
|
LoyaltyDiscountPercent = 10.0
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -255,7 +255,7 @@ func ProcessCancellationRefund(
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Failed to refund loyalty stamps for booking %s: %v", bookingID, err)
|
log.Printf("Failed to refund loyalty stamps for booking %s: %v", bookingID, err)
|
||||||
} else {
|
} else {
|
||||||
log.Printf("Refunded 10 loyalty stamps to user %s after cancellation of booking %s", bookingUserID, bookingID)
|
log.Printf("Refunded %d loyalty stamps to user %s after cancellation of booking %s", LoyaltyStampCost, bookingUserID, bookingID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user