fix(payments): compare global coupon redemptions against max_redemptions not max_redemptions_per_user
All checks were successful
build-and-release / build (employees) (push) Successful in 11s
build-and-release / build (cron) (push) Successful in 14s
build-and-release / build (customers) (push) Successful in 16s
build-and-release / build (catering-services) (push) Successful in 19s
build-and-release / build (companies) (push) Successful in 21s
build-and-release / build (developers) (push) Successful in 20s
build-and-release / build (fitness-trainers) (push) Successful in 7s
build-and-release / build (gateway) (push) Successful in 9s
build-and-release / build (graphic-designers) (push) Successful in 8s
build-and-release / build (job-seekers) (push) Successful in 8s
build-and-release / build (jobs) (push) Successful in 10s
build-and-release / build (social-media-managers) (push) Successful in 8s
build-and-release / build (makeup-artists) (push) Successful in 12s
build-and-release / build (photographers) (push) Successful in 8s
build-and-release / build (tutors) (push) Successful in 7s
build-and-release / build (users) (push) Successful in 6s
backend-integration-tests / ai-credits (push) Successful in 9s
build-and-release / build (ugc-content-creators) (push) Successful in 11s
build-and-release / build (video-editors) (push) Successful in 11s
build-and-release / build (payments) (push) Successful in 1m0s
All checks were successful
build-and-release / build (employees) (push) Successful in 11s
build-and-release / build (cron) (push) Successful in 14s
build-and-release / build (customers) (push) Successful in 16s
build-and-release / build (catering-services) (push) Successful in 19s
build-and-release / build (companies) (push) Successful in 21s
build-and-release / build (developers) (push) Successful in 20s
build-and-release / build (fitness-trainers) (push) Successful in 7s
build-and-release / build (gateway) (push) Successful in 9s
build-and-release / build (graphic-designers) (push) Successful in 8s
build-and-release / build (job-seekers) (push) Successful in 8s
build-and-release / build (jobs) (push) Successful in 10s
build-and-release / build (social-media-managers) (push) Successful in 8s
build-and-release / build (makeup-artists) (push) Successful in 12s
build-and-release / build (photographers) (push) Successful in 8s
build-and-release / build (tutors) (push) Successful in 7s
build-and-release / build (users) (push) Successful in 6s
backend-integration-tests / ai-credits (push) Successful in 9s
build-and-release / build (ugc-content-creators) (push) Successful in 11s
build-and-release / build (video-editors) (push) Successful in 11s
build-and-release / build (payments) (push) Successful in 1m0s
validate_coupon was checking: total_redemptions (= redemptions_used, global count) >= max_per_user (per-user limit) which means a coupon with max_redemptions=1000 and max_redemptions_per_user=1 would be marked exhausted after the first person ever used it. Fix: also SELECT max_redemptions and compare global count against it. The per-user check on line 192 (user_redemptions >= max_per_user) was already correct and is unchanged. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
86e1fd47d2
commit
4e0e6af3e1
1 changed files with 11 additions and 10 deletions
|
|
@ -151,17 +151,18 @@ async fn validate_coupon(
|
||||||
original_price: i32,
|
original_price: i32,
|
||||||
) -> Result<Option<(i32, String, i32)>, String> {
|
) -> Result<Option<(i32, String, i32)>, String> {
|
||||||
// Get coupon details
|
// Get coupon details
|
||||||
let coupon: Option<(Uuid, String, i32, Option<i32>, i32, i32, Vec<Uuid>)> = sqlx::query_as(
|
let coupon: Option<(Uuid, String, i32, Option<i32>, i32, i32, Vec<Uuid>, i32)> = sqlx::query_as(
|
||||||
r#"
|
r#"
|
||||||
SELECT
|
SELECT
|
||||||
id, discount_type,
|
id, discount_type,
|
||||||
CAST(discount_value * 100 AS INTEGER) as discount_value,
|
CAST(discount_value * 100 AS INTEGER) as discount_value,
|
||||||
CAST(max_discount_amount * 100 AS INTEGER) as max_discount_amount,
|
CAST(max_discount_amount * 100 AS INTEGER) as max_discount_amount,
|
||||||
max_redemptions_per_user, redemptions_used, applicable_package_ids
|
max_redemptions_per_user, redemptions_used, applicable_package_ids,
|
||||||
|
max_redemptions
|
||||||
FROM ai_coupons
|
FROM ai_coupons
|
||||||
WHERE code = $1
|
WHERE code = $1
|
||||||
AND is_active = TRUE
|
AND is_active = TRUE
|
||||||
AND valid_from <= NOW()
|
AND valid_from <= NOW()
|
||||||
AND (valid_until IS NULL OR valid_until > NOW())
|
AND (valid_until IS NULL OR valid_until > NOW())
|
||||||
"#
|
"#
|
||||||
)
|
)
|
||||||
|
|
@ -170,12 +171,12 @@ async fn validate_coupon(
|
||||||
.await
|
.await
|
||||||
.map_err(|e| format!("DB error: {e}"))?;
|
.map_err(|e| format!("DB error: {e}"))?;
|
||||||
|
|
||||||
let Some((coupon_id, discount_type, discount_value, max_discount_amount, max_per_user, total_redemptions, applicable_packages)) = coupon else {
|
let Some((coupon_id, discount_type, discount_value, max_discount_amount, max_per_user, total_redemptions, applicable_packages, global_limit)) = coupon else {
|
||||||
return Ok(None); // Coupon not found or invalid
|
return Ok(None); // Coupon not found or invalid
|
||||||
};
|
};
|
||||||
|
|
||||||
// Check if coupon has remaining redemptions
|
// Check if coupon has remaining global redemptions
|
||||||
if total_redemptions >= max_per_user {
|
if total_redemptions >= global_limit {
|
||||||
return Err("Coupon redemption limit reached".to_string());
|
return Err("Coupon redemption limit reached".to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue