feat: implement payment order system (create-order, mock-confirm, get-quota)

This commit is contained in:
2026-04-17 12:45:53 +08:00
parent 454139f70d
commit 562528a1f0
7 changed files with 296 additions and 3 deletions

View File

@@ -2,6 +2,7 @@
pub mod admin;
pub mod auth;
pub mod health;
pub mod payment;
pub mod static_files;
pub mod user;
pub mod weather;
@@ -24,3 +25,7 @@ pub use weather::generate_temp_token_handler;
pub use weather::get_weather_brief;
pub use weather::get_weather_details;
pub use weather::post_weather_data;
pub use payment::create_order;
pub use payment::get_user_quota;
pub use payment::mock_confirm;

160
src/handlers/payment.rs Normal file
View File

@@ -0,0 +1,160 @@
// handlers/payment.rs — 支付相关处理器
use actix_web::{get, post, web, HttpResponse, Responder};
use chrono::Utc;
use sqlx::postgres::PgPool;
use tracing::error;
use uuid::Uuid;
use crate::db;
use crate::models::{Claims, CreateOrderRequest, MockConfirmRequest};
struct PackageInfo {
amount: i32,
display_amount: &'static str,
display_name: &'static str,
days: Option<i64>,
}
fn get_package_info(package_type: &str) -> Option<PackageInfo> {
match package_type {
"monthly" => Some(PackageInfo {
amount: 990,
display_amount: "¥9.9",
display_name: "包月会员",
days: Some(30),
}),
"yearly" => Some(PackageInfo {
amount: 5900,
display_amount: "¥59",
display_name: "包年会员",
days: Some(365),
}),
"permanent" => Some(PackageInfo {
amount: 19900,
display_amount: "¥199",
display_name: "永久会员",
days: None,
}),
_ => None,
}
}
/// POST /api/payment/create-order
#[post("/api/payment/create-order")]
pub async fn create_order(
pool: web::Data<PgPool>,
claims: web::ReqData<Claims>,
body: web::Json<CreateOrderRequest>,
) -> impl Responder {
let user_id = claims.user_id;
let pkg = match get_package_info(&body.package_type) {
Some(p) => p,
None => {
return HttpResponse::Ok().json(serde_json::json!({
"success": false,
"errcode": 400,
"errmsg": "无效的套餐类型"
}));
}
};
let order_no = Uuid::new_v4().to_string();
let expires_at = pkg.days.map(|d| Utc::now() + chrono::Duration::days(d));
match db::create_payment_order(
pool.get_ref(),
user_id,
&order_no,
&body.package_type,
pkg.amount,
expires_at,
)
.await
{
Ok(_) => HttpResponse::Ok().json(serde_json::json!({
"success": true,
"data": {
"order_id": order_no,
"package_type": body.package_type,
"amount": pkg.amount,
"display_amount": pkg.display_amount,
"display_name": pkg.display_name,
"expires_at": expires_at,
}
})),
Err(e) => {
error!("创建订单失败: {}", e);
HttpResponse::Ok().json(serde_json::json!({
"success": false,
"errcode": 500,
"errmsg": "创建订单失败,请重试"
}))
}
}
}
/// POST /api/payment/mock-confirm
#[post("/api/payment/mock-confirm")]
pub async fn mock_confirm(
pool: web::Data<PgPool>,
claims: web::ReqData<Claims>,
body: web::Json<MockConfirmRequest>,
) -> impl Responder {
let user_id = claims.user_id;
match db::confirm_payment_order(pool.get_ref(), &body.order_id, user_id).await {
Ok(expires_at) => HttpResponse::Ok().json(serde_json::json!({
"success": true,
"data": {
"is_paid_active": true,
"paid_expires_at": expires_at,
}
})),
Err(e) => {
error!("确认支付失败: {}", e);
HttpResponse::Ok().json(serde_json::json!({
"success": false,
"errcode": 400,
"errmsg": "支付确认失败,请重试"
}))
}
}
}
/// GET /api/user/quota
#[get("/api/user/quota")]
pub async fn get_user_quota(
pool: web::Data<PgPool>,
claims: web::ReqData<Claims>,
) -> impl Responder {
let user_id = claims.user_id;
match db::get_user_quota(pool.get_ref(), user_id).await {
Ok((used, is_paid_active, paid_expires_at)) => {
let limit: i64 = std::env::var("FREE_USER_DATA_LIMIT")
.ok()
.and_then(|v| v.parse().ok())
.unwrap_or(20);
HttpResponse::Ok().json(serde_json::json!({
"success": true,
"data": {
"used": used,
"limit": limit,
"unlimited": is_paid_active,
"is_paid_active": is_paid_active,
"paid_expires_at": paid_expires_at,
}
}))
}
Err(e) => {
error!("获取配额信息失败: {}", e);
HttpResponse::Ok().json(serde_json::json!({
"success": false,
"errcode": 500,
"errmsg": "获取配额信息失败"
}))
}
}
}