diff --git a/migrations/003_set_all_users_paid.sql b/migrations/003_set_all_users_paid.sql index a90cfe6..2c1b062 100644 --- a/migrations/003_set_all_users_paid.sql +++ b/migrations/003_set_all_users_paid.sql @@ -1 +1,6 @@ -UPDATE users SET is_paid = true WHERE is_paid = false; +-- ATTENTION: This migration was originally `UPDATE users SET is_paid = true WHERE is_paid = false` +-- which incorrectly made ALL existing users permanent paid members (paid_expires_at = NULL → permanent). +-- The accumulation logic now handles membership correctly in confirm_payment_order. +-- DO NOT re-enable without understanding the full consequences. +-- This file is kept empty as a placeholder to prevent migration numbering gaps. +SELECT 1 WHERE 1 = 1; -- no-op diff --git a/src/db.rs b/src/db.rs index cc52d29..7138507 100644 --- a/src/db.rs +++ b/src/db.rs @@ -346,20 +346,24 @@ pub async fn create_payment_order( /// 确认订单支付(模拟):更新订单状态 + 激活用户付费 /// /// 返回该订单的 expires_at(永久会员为 None) +/// 确认订单支付(模拟):更新订单状态 + 激活用户付费(累加有效期) +/// +/// 返回计算后的到期时间(永久会员返回 2099-12-31) pub async fn confirm_payment_order( pool: &PgPool, order_no: &str, user_id: i32, ) -> Result>, AppError> { - let row = sqlx::query_as::<_, (i32, String, Option>)>( - r#"SELECT user_id, status, expires_at FROM payment_orders WHERE order_no = $1"#, + // 检查订单有效 + let row = sqlx::query_as::<_, (i32, String)>( + r#"SELECT user_id, status FROM payment_orders WHERE order_no = $1"#, ) .bind(order_no) .fetch_optional(pool) .await .map_err(|e| AppError::Database(format!("查询订单失败: {}", e)))?; - let (order_user_id, status, expires_at) = match row { + let (order_user_id, status) = match row { Some(r) => r, None => return Err(AppError::NotFound("订单不存在".to_string())), }; @@ -372,24 +376,36 @@ pub async fn confirm_payment_order( return Err(AppError::BadRequest("订单状态异常,无法确认支付".to_string())); } - sqlx::query( - r#"UPDATE payment_orders SET status = 'paid', paid_at = NOW() WHERE order_no = $1"#, + // 一次性完成:更新订单状态 + 累加计算新的到期时间(纯 SQL) + let new_expires: Option> = sqlx::query_scalar( + r#" + WITH updated_order AS ( + UPDATE payment_orders SET status = 'paid', paid_at = NOW() WHERE order_no = $1 RETURNING package_type + ) + UPDATE users SET + is_paid = true, + paid_expires_at = + CASE + WHEN uo.package_type = 'permanent' THEN '2099-12-31 23:59:59+00'::timestamptz + ELSE GREATEST(COALESCE(users.paid_expires_at, NOW()), NOW()) + + CASE + WHEN uo.package_type = 'monthly' THEN INTERVAL '30 days' + WHEN uo.package_type = 'yearly' THEN INTERVAL '365 days' + ELSE INTERVAL '0 days' + END + END + FROM updated_order uo + WHERE users.id = $2 + RETURNING users.paid_expires_at + "#, ) .bind(order_no) - .execute(pool) - .await - .map_err(|e| AppError::Database(format!("更新订单状态失败: {}", e)))?; - - sqlx::query( - r#"UPDATE users SET is_paid = true, paid_expires_at = $1 WHERE id = $2"#, - ) - .bind(expires_at) .bind(user_id) - .execute(pool) + .fetch_one(pool) .await - .map_err(|e| AppError::Database(format!("更新用户付费状态失败: {}", e)))?; + .map_err(|e| AppError::Database(format!("支付确认失败: {}", e)))?; - Ok(expires_at) + Ok(new_expires) } /// 确认订单支付(支付宝异步回调用,通过 order_no 查找,不校验 user_id) @@ -397,40 +413,50 @@ pub async fn confirm_payment_order_by_orderno( pool: &PgPool, order_no: &str, ) -> Result<(), AppError> { - let row = sqlx::query_as::<_, (i32, String, Option>)>( - r#"SELECT user_id, status, expires_at FROM payment_orders WHERE order_no = $1"#, + // 检查订单有效 + let status: Option = sqlx::query_scalar( + r#"SELECT status FROM payment_orders WHERE order_no = $1"#, ) .bind(order_no) .fetch_optional(pool) .await .map_err(|e| AppError::Database(format!("查询订单失败: {}", e)))?; - let (user_id, status, expires_at) = match row { - Some(r) => r, + let status = match status { + Some(s) => s, None => return Err(AppError::NotFound("订单不存在".to_string())), }; if status != "pending" { - // 已支付直接返回成功(幂等) return Ok(()); } - let mut tx = pool.begin().await.map_err(|e| AppError::Database(e.to_string()))?; - - sqlx::query(r#"UPDATE payment_orders SET status = 'paid', paid_at = NOW() WHERE order_no = $1"#) - .bind(order_no) - .execute(&mut *tx) - .await - .map_err(|e| AppError::Database(format!("更新订单状态失败: {}", e)))?; - - sqlx::query(r#"UPDATE users SET is_paid = true, paid_expires_at = $1 WHERE id = $2"#) - .bind(expires_at) - .bind(user_id) - .execute(&mut *tx) - .await - .map_err(|e| AppError::Database(format!("更新用户付费状态失败: {}", e)))?; - - tx.commit().await.map_err(|e| AppError::Database(e.to_string()))?; + // 一次性完成:更新订单状态 + 累加计算新的到期时间 + sqlx::query( + r#" + WITH updated_order AS ( + UPDATE payment_orders SET status = 'paid', paid_at = NOW() WHERE order_no = $1 RETURNING package_type, user_id + ) + UPDATE users SET + is_paid = true, + paid_expires_at = + CASE + WHEN uo.package_type = 'permanent' THEN '2099-12-31 23:59:59+00'::timestamptz + ELSE GREATEST(COALESCE(users.paid_expires_at, NOW()), NOW()) + + CASE + WHEN uo.package_type = 'monthly' THEN INTERVAL '30 days' + WHEN uo.package_type = 'yearly' THEN INTERVAL '365 days' + ELSE INTERVAL '0 days' + END + END + FROM updated_order uo + WHERE users.id = uo.user_id + "#, + ) + .bind(order_no) + .execute(pool) + .await + .map_err(|e| AppError::Database(format!("支付确认失败: {}", e)))?; Ok(()) }