feat: add payment gateway and billing plans

This commit is contained in:
Entropy.Xu
2026-05-13 01:18:38 +08:00
parent 0fa97595bf
commit 10285c5eb9
97 changed files with 14797 additions and 404 deletions
@@ -1,6 +1,6 @@
use super::super::shared::{
admin_wallet_id_from_suffix_path, admin_wallet_operator_id,
build_admin_wallet_not_found_response, build_admin_wallet_summary_payload,
build_admin_wallet_not_found_response, build_admin_wallet_summary_payload_with_package,
build_admin_wallet_transaction_payload, build_admin_wallets_bad_request_response,
build_admin_wallets_data_unavailable_response, normalize_admin_wallet_balance_type,
normalize_admin_wallet_description, normalize_admin_wallet_non_zero_amount,
@@ -79,7 +79,8 @@ pub(in super::super) async fn build_admin_wallet_adjust_response(
};
};
let owner = resolve_admin_wallet_owner_summary(state, &wallet).await?;
let wallet_payload = build_admin_wallet_summary_payload(&wallet, &owner);
let wallet_payload =
build_admin_wallet_summary_payload_with_package(state, &wallet, &owner).await?;
let transaction_payload = build_admin_wallet_transaction_payload(
&wallet,
&owner,
@@ -1,7 +1,7 @@
use super::super::shared::{
admin_wallet_operator_id, admin_wallet_refund_ids_from_suffix_path,
build_admin_wallet_not_found_response, build_admin_wallet_refund_not_found_response,
build_admin_wallet_refund_payload, build_admin_wallet_summary_payload,
build_admin_wallet_refund_payload, build_admin_wallet_summary_payload_with_package,
build_admin_wallet_transaction_payload, build_admin_wallets_bad_request_response,
build_admin_wallets_data_unavailable_response, normalize_admin_wallet_required_text,
resolve_admin_wallet_owner_summary, AdminWalletRefundFailRequest,
@@ -62,8 +62,10 @@ pub(in super::super) async fn build_admin_wallet_fail_refund_response(
{
crate::AdminWalletMutationOutcome::Applied((wallet, refund, transaction)) => {
let owner = resolve_admin_wallet_owner_summary(state, &wallet).await?;
let wallet_payload =
build_admin_wallet_summary_payload_with_package(state, &wallet, &owner).await?;
let response = Json(json!({
"wallet": build_admin_wallet_summary_payload(&wallet, &owner),
"wallet": wallet_payload,
"refund": build_admin_wallet_refund_payload(&wallet, &owner, &refund),
"transaction": transaction
.map(|transaction| {
@@ -1,7 +1,7 @@
use super::super::shared::{
admin_wallet_operator_id, admin_wallet_refund_ids_from_suffix_path,
build_admin_wallet_not_found_response, build_admin_wallet_refund_not_found_response,
build_admin_wallet_refund_payload, build_admin_wallet_summary_payload,
build_admin_wallet_refund_payload, build_admin_wallet_summary_payload_with_package,
build_admin_wallet_transaction_payload, build_admin_wallets_bad_request_response,
build_admin_wallets_data_unavailable_response, resolve_admin_wallet_owner_summary,
ADMIN_WALLETS_API_KEY_REFUND_DETAIL,
@@ -49,8 +49,10 @@ pub(in super::super) async fn build_admin_wallet_process_refund_response(
{
crate::AdminWalletMutationOutcome::Applied((wallet, refund, transaction)) => {
let owner = resolve_admin_wallet_owner_summary(state, &wallet).await?;
let wallet_payload =
build_admin_wallet_summary_payload_with_package(state, &wallet, &owner).await?;
let response = Json(json!({
"wallet": build_admin_wallet_summary_payload(&wallet, &owner),
"wallet": wallet_payload,
"refund": build_admin_wallet_refund_payload(&wallet, &owner, &refund),
"transaction": build_admin_wallet_transaction_payload(
&wallet,
@@ -1,7 +1,7 @@
use super::super::shared::{
admin_wallet_id_from_suffix_path, admin_wallet_operator_id,
build_admin_wallet_not_found_response, build_admin_wallet_payment_order_payload,
build_admin_wallet_summary_payload, build_admin_wallets_bad_request_response,
build_admin_wallet_summary_payload_with_package, build_admin_wallets_bad_request_response,
build_admin_wallets_data_unavailable_response, normalize_admin_wallet_description,
normalize_admin_wallet_payment_method, normalize_admin_wallet_positive_amount,
resolve_admin_wallet_owner_summary, AdminWalletRechargeRequest,
@@ -79,8 +79,10 @@ pub(in super::super) async fn build_admin_wallet_recharge_response(
};
};
let owner = resolve_admin_wallet_owner_summary(state, &wallet).await?;
let wallet_payload =
build_admin_wallet_summary_payload_with_package(state, &wallet, &owner).await?;
let response = Json(json!({
"wallet": build_admin_wallet_summary_payload(&wallet, &owner),
"wallet": wallet_payload,
"payment_order": build_admin_wallet_payment_order_payload(
payment_order.id,
payment_order.order_no,
@@ -1,6 +1,6 @@
use super::super::shared::{
admin_wallet_id_from_detail_path, build_admin_wallet_not_found_response,
build_admin_wallet_summary_payload, build_admin_wallets_bad_request_response,
build_admin_wallet_summary_payload_with_package, build_admin_wallets_bad_request_response,
resolve_admin_wallet_owner_summary,
};
use crate::handlers::admin::request::{AdminAppState, AdminRequestContext};
@@ -29,7 +29,8 @@ pub(in super::super) async fn build_admin_wallet_detail_response(
};
let owner = resolve_admin_wallet_owner_summary(state, &wallet).await?;
let mut payload = build_admin_wallet_summary_payload(&wallet, &owner);
let mut payload =
build_admin_wallet_summary_payload_with_package(state, &wallet, &owner).await?;
if let Some(object) = payload.as_object_mut() {
object.insert("pending_refund_count".to_string(), serde_json::Value::Null);
}
@@ -1,6 +1,6 @@
use super::super::shared::{
build_admin_wallets_bad_request_response, parse_admin_wallets_limit,
parse_admin_wallets_offset, parse_admin_wallets_owner_type_filter,
build_admin_wallets_bad_request_response, enrich_admin_wallet_package_summary,
parse_admin_wallets_limit, parse_admin_wallets_offset, parse_admin_wallets_owner_type_filter,
wallet_owner_summary_from_fields,
};
use crate::handlers::admin::request::{AdminAppState, AdminRequestContext};
@@ -32,38 +32,48 @@ pub(in super::super) async fn build_admin_wallet_list_response(
let (wallets, total) = state
.list_admin_wallets(status.as_deref(), owner_type.as_deref(), limit, offset)
.await?;
let items = wallets
.into_iter()
.map(|wallet| {
let owner = wallet_owner_summary_from_fields(
wallet.user_id.as_deref(),
wallet.user_name.clone(),
wallet.api_key_id.as_deref(),
wallet.api_key_name.clone(),
);
json!({
"id": wallet.id,
"user_id": wallet.user_id,
"api_key_id": wallet.api_key_id,
"owner_type": owner.owner_type,
"owner_name": owner.owner_name,
"balance": wallet.balance + wallet.gift_balance,
"recharge_balance": wallet.balance,
"gift_balance": wallet.gift_balance,
"refundable_balance": wallet.balance,
"currency": wallet.currency,
"status": wallet.status,
"limit_mode": wallet.limit_mode.clone(),
"unlimited": wallet.limit_mode.eq_ignore_ascii_case("unlimited"),
"total_recharged": wallet.total_recharged,
"total_consumed": wallet.total_consumed,
"total_refunded": wallet.total_refunded,
"total_adjusted": wallet.total_adjusted,
"created_at": wallet.created_at_unix_ms.and_then(unix_secs_to_rfc3339),
"updated_at": wallet.updated_at_unix_secs.and_then(unix_secs_to_rfc3339),
})
})
.collect::<Vec<_>>();
let mut items = Vec::with_capacity(wallets.len());
for wallet in wallets {
let owner = wallet_owner_summary_from_fields(
wallet.user_id.as_deref(),
wallet.user_name.clone(),
wallet.api_key_id.as_deref(),
wallet.api_key_name.clone(),
);
let user_id = wallet.user_id.clone();
let wallet_balance = wallet.balance + wallet.gift_balance;
let unlimited = wallet.limit_mode.eq_ignore_ascii_case("unlimited");
let mut payload = json!({
"id": wallet.id,
"user_id": wallet.user_id,
"api_key_id": wallet.api_key_id,
"owner_type": owner.owner_type,
"owner_name": owner.owner_name,
"balance": wallet_balance,
"recharge_balance": wallet.balance,
"gift_balance": wallet.gift_balance,
"refundable_balance": wallet.balance,
"currency": wallet.currency,
"status": wallet.status,
"limit_mode": wallet.limit_mode.clone(),
"unlimited": unlimited,
"total_recharged": wallet.total_recharged,
"total_consumed": wallet.total_consumed,
"total_refunded": wallet.total_refunded,
"total_adjusted": wallet.total_adjusted,
"created_at": wallet.created_at_unix_ms.and_then(unix_secs_to_rfc3339),
"updated_at": wallet.updated_at_unix_secs.and_then(unix_secs_to_rfc3339),
});
enrich_admin_wallet_package_summary(
state,
&mut payload,
user_id.as_deref(),
wallet_balance,
unlimited,
)
.await?;
items.push(payload);
}
Ok(Json(json!({
"items": items,
@@ -1,6 +1,6 @@
use super::super::shared::{
admin_wallet_id_from_suffix_path, build_admin_wallet_not_found_response,
build_admin_wallet_refund_payload, build_admin_wallet_summary_payload,
build_admin_wallet_refund_payload, build_admin_wallet_summary_payload_with_package,
build_admin_wallets_bad_request_response, parse_admin_wallets_limit,
parse_admin_wallets_offset, resolve_admin_wallet_owner_summary,
ADMIN_WALLETS_API_KEY_REFUND_DETAIL,
@@ -46,7 +46,8 @@ pub(in super::super) async fn build_admin_wallet_refunds_response(
}
let owner = resolve_admin_wallet_owner_summary(state, &wallet).await?;
let wallet_payload = build_admin_wallet_summary_payload(&wallet, &owner);
let wallet_payload =
build_admin_wallet_summary_payload_with_package(state, &wallet, &owner).await?;
let (refunds, total) = state
.list_admin_wallet_refunds(&wallet.id, limit, offset)
.await?;
@@ -1,6 +1,6 @@
use super::super::shared::{
admin_wallet_id_from_suffix_path, build_admin_wallet_not_found_response,
build_admin_wallet_summary_payload, build_admin_wallets_bad_request_response,
build_admin_wallet_summary_payload_with_package, build_admin_wallets_bad_request_response,
parse_admin_wallets_limit, parse_admin_wallets_offset, resolve_admin_wallet_owner_summary,
};
use crate::handlers::admin::request::{AdminAppState, AdminRequestContext};
@@ -39,7 +39,8 @@ pub(in super::super) async fn build_admin_wallet_transactions_response(
return Ok(build_admin_wallet_not_found_response());
};
let owner = resolve_admin_wallet_owner_summary(state, &wallet).await?;
let wallet_payload = build_admin_wallet_summary_payload(&wallet, &owner);
let wallet_payload =
build_admin_wallet_summary_payload_with_package(state, &wallet, &owner).await?;
let (transactions, total) = state
.list_admin_wallet_transactions(&wallet.id, limit, offset)
@@ -1,5 +1,6 @@
use crate::handlers::admin::request::AdminAppState;
use crate::handlers::admin::shared::unix_secs_to_rfc3339;
use crate::handlers::shared::round_to;
use crate::GatewayError;
use serde_json::json;
@@ -160,6 +161,79 @@ pub(in super::super) fn build_admin_wallet_summary_payload(
})
}
pub(in super::super) async fn build_admin_wallet_summary_payload_with_package(
state: &AdminAppState<'_>,
wallet: &aether_data::repository::wallet::StoredWalletSnapshot,
owner: &AdminWalletOwnerSummary,
) -> Result<serde_json::Value, GatewayError> {
let mut payload = build_admin_wallet_summary_payload(wallet, owner);
enrich_admin_wallet_package_summary(
state,
&mut payload,
wallet.user_id.as_deref(),
wallet.balance + wallet.gift_balance,
wallet.limit_mode.eq_ignore_ascii_case("unlimited"),
)
.await?;
Ok(payload)
}
pub(in super::super) async fn enrich_admin_wallet_package_summary(
state: &AdminAppState<'_>,
payload: &mut serde_json::Value,
user_id: Option<&str>,
wallet_balance: f64,
unlimited: bool,
) -> Result<(), GatewayError> {
let daily_quota = match user_id {
Some(user_id) if !user_id.trim().is_empty() => {
state
.app()
.find_user_daily_quota_availability(user_id)
.await?
}
_ => None,
};
let (has_active_daily_quota, total_quota_usd, used_usd, remaining_usd, allow_wallet_overage) =
daily_quota
.map(|quota| {
(
quota.has_active_daily_quota,
quota.total_quota_usd,
quota.used_usd,
quota.remaining_usd,
quota.allow_wallet_overage,
)
})
.unwrap_or((false, 0.0, 0.0, 0.0, false));
let package_balance = if has_active_daily_quota {
remaining_usd.max(0.0)
} else {
0.0
};
payload["daily_quota"] = json!({
"has_active": has_active_daily_quota,
"total_usd": round_to(total_quota_usd.max(0.0), 6),
"used_usd": round_to(used_usd.max(0.0), 6),
"remaining_usd": round_to(package_balance, 6),
"allow_wallet_overage": allow_wallet_overage,
});
payload["package_balance"] = json!(round_to(package_balance, 6));
payload["wallet_balance"] = json!(round_to(wallet_balance.max(0.0), 6));
payload["total_available_balance"] = if unlimited {
serde_json::Value::Null
} else {
json!(round_to((wallet_balance + package_balance).max(0.0), 6))
};
payload["deduction_order"] = json!([
"package_daily_quota",
"wallet_recharge_balance",
"wallet_gift_balance"
]);
Ok(())
}
pub(in super::super) fn build_admin_wallet_refund_payload(
wallet: &aether_data::repository::wallet::StoredWalletSnapshot,
owner: &AdminWalletOwnerSummary,