mirror of
https://github.com/fawney19/Aether.git
synced 2026-10-08 18:37:46 +08:00
feat(referrals): 添加邀请返利和注册确认功能
This commit is contained in:
@@ -0,0 +1,241 @@
|
||||
use crate::data::state::{
|
||||
ReferralRelationshipListQuery, ReferralRelationshipRecord, ReferralRewardConfig,
|
||||
ReferralRewardListQuery, ReferralRewardRecord, ReferralUserDashboard,
|
||||
};
|
||||
use crate::{AppState, GatewayError};
|
||||
use axum::http::StatusCode;
|
||||
|
||||
fn referral_data_error(err: aether_data::DataLayerError) -> GatewayError {
|
||||
match err {
|
||||
aether_data::DataLayerError::InvalidInput(detail) => GatewayError::Client {
|
||||
status: StatusCode::BAD_REQUEST,
|
||||
message: detail,
|
||||
},
|
||||
other => GatewayError::Internal(other.to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
fn config_bool(value: Option<&serde_json::Value>, default: bool) -> bool {
|
||||
match value {
|
||||
Some(serde_json::Value::Bool(value)) => *value,
|
||||
Some(serde_json::Value::String(value)) => {
|
||||
match value.trim().to_ascii_lowercase().as_str() {
|
||||
"true" | "1" | "yes" | "on" => true,
|
||||
"false" | "0" | "no" | "off" => false,
|
||||
_ => default,
|
||||
}
|
||||
}
|
||||
Some(serde_json::Value::Number(value)) => {
|
||||
value.as_i64().map(|value| value != 0).unwrap_or(default)
|
||||
}
|
||||
_ => default,
|
||||
}
|
||||
}
|
||||
|
||||
fn config_string(value: Option<&serde_json::Value>) -> Option<String> {
|
||||
match value {
|
||||
Some(serde_json::Value::String(value)) => {
|
||||
let value = value.trim();
|
||||
(!value.is_empty()).then_some(value.to_string())
|
||||
}
|
||||
Some(value) => Some(value.to_string()),
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn config_f64(value: Option<&serde_json::Value>, default: f64) -> f64 {
|
||||
match value {
|
||||
Some(serde_json::Value::Number(value)) => value.as_f64().unwrap_or(default),
|
||||
Some(serde_json::Value::String(value)) => value.trim().parse::<f64>().unwrap_or(default),
|
||||
_ => default,
|
||||
}
|
||||
}
|
||||
|
||||
impl AppState {
|
||||
pub(crate) fn has_referral_data_backend(&self) -> bool {
|
||||
self.data.has_referral_data_backend()
|
||||
}
|
||||
|
||||
pub(crate) async fn record_user_privacy_policy_acceptance(
|
||||
&self,
|
||||
user_id: &str,
|
||||
version: &str,
|
||||
) -> Result<bool, GatewayError> {
|
||||
self.data
|
||||
.record_user_privacy_policy_acceptance(user_id, version)
|
||||
.await
|
||||
.map_err(|err| GatewayError::Internal(err.to_string()))
|
||||
}
|
||||
|
||||
pub(crate) async fn referral_reward_config(
|
||||
&self,
|
||||
) -> Result<Option<ReferralRewardConfig>, GatewayError> {
|
||||
let enabled = self
|
||||
.read_system_config_json_value("referral_enabled")
|
||||
.await?;
|
||||
if !config_bool(enabled.as_ref(), false) {
|
||||
return Ok(None);
|
||||
}
|
||||
let mode = self
|
||||
.read_system_config_json_value("referral_reward_mode")
|
||||
.await?;
|
||||
let mode = config_string(mode.as_ref()).unwrap_or_else(|| "percent".to_string());
|
||||
let percent = self
|
||||
.read_system_config_json_value("referral_recharge_percent")
|
||||
.await?;
|
||||
let headcount_amount = self
|
||||
.read_system_config_json_value("referral_headcount_amount_usd")
|
||||
.await?;
|
||||
let headcount_trigger = self
|
||||
.read_system_config_json_value("referral_headcount_trigger")
|
||||
.await?;
|
||||
let headcount_trigger =
|
||||
config_string(headcount_trigger.as_ref()).unwrap_or_else(|| "registration".to_string());
|
||||
Ok(Some(ReferralRewardConfig {
|
||||
percent_enabled: matches!(mode.as_str(), "percent" | "both"),
|
||||
percent_rate: config_f64(percent.as_ref(), 0.0),
|
||||
headcount_enabled: matches!(mode.as_str(), "headcount" | "both"),
|
||||
headcount_amount_usd: config_f64(headcount_amount.as_ref(), 0.0),
|
||||
headcount_trigger,
|
||||
}))
|
||||
}
|
||||
|
||||
pub(crate) async fn bind_referral_invite_after_registration(
|
||||
&self,
|
||||
user_id: &str,
|
||||
email_verified: bool,
|
||||
invite_code: Option<&str>,
|
||||
source: Option<serde_json::Value>,
|
||||
) -> Result<(), GatewayError> {
|
||||
let Some(config) = self.referral_reward_config().await? else {
|
||||
return Ok(());
|
||||
};
|
||||
let relationship = self
|
||||
.data
|
||||
.bind_referral_invite_code(user_id, invite_code, source)
|
||||
.await
|
||||
.map_err(referral_data_error)?;
|
||||
let trigger_matches = config.headcount_trigger == "registration"
|
||||
|| (config.headcount_trigger == "email_verified" && email_verified);
|
||||
if relationship.is_some()
|
||||
&& config.headcount_enabled
|
||||
&& trigger_matches
|
||||
&& config.headcount_amount_usd > 0.0
|
||||
{
|
||||
self.data
|
||||
.apply_registration_referral_reward(
|
||||
user_id,
|
||||
config.headcount_amount_usd,
|
||||
&config.headcount_trigger,
|
||||
)
|
||||
.await
|
||||
.map_err(|err| GatewayError::Internal(err.to_string()))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn referral_dashboard(
|
||||
&self,
|
||||
user_id: &str,
|
||||
) -> Result<Option<ReferralUserDashboard>, GatewayError> {
|
||||
self.data
|
||||
.referral_dashboard(user_id)
|
||||
.await
|
||||
.map_err(|err| GatewayError::Internal(err.to_string()))
|
||||
}
|
||||
|
||||
pub(crate) async fn list_admin_referral_relationships(
|
||||
&self,
|
||||
query: ReferralRelationshipListQuery,
|
||||
) -> Result<
|
||||
Option<(
|
||||
Vec<ReferralRelationshipRecord>,
|
||||
u64,
|
||||
crate::data::state::ReferralAdminStats,
|
||||
)>,
|
||||
GatewayError,
|
||||
> {
|
||||
self.data
|
||||
.list_admin_referral_relationships(query)
|
||||
.await
|
||||
.map_err(|err| GatewayError::Internal(err.to_string()))
|
||||
}
|
||||
|
||||
pub(crate) async fn list_admin_referral_rewards(
|
||||
&self,
|
||||
query: ReferralRewardListQuery,
|
||||
) -> Result<
|
||||
Option<(
|
||||
Vec<ReferralRewardRecord>,
|
||||
u64,
|
||||
crate::data::state::ReferralAdminStats,
|
||||
)>,
|
||||
GatewayError,
|
||||
> {
|
||||
self.data
|
||||
.list_admin_referral_rewards(query)
|
||||
.await
|
||||
.map_err(|err| GatewayError::Internal(err.to_string()))
|
||||
}
|
||||
|
||||
pub(crate) async fn retry_referral_reward(
|
||||
&self,
|
||||
reward_id: &str,
|
||||
operator_id: Option<&str>,
|
||||
note: Option<&str>,
|
||||
) -> Result<Option<ReferralRewardRecord>, GatewayError> {
|
||||
self.data
|
||||
.retry_referral_reward(reward_id, operator_id, note)
|
||||
.await
|
||||
.map_err(referral_data_error)
|
||||
}
|
||||
|
||||
pub(crate) async fn void_referral_reward(
|
||||
&self,
|
||||
reward_id: &str,
|
||||
operator_id: Option<&str>,
|
||||
note: Option<&str>,
|
||||
) -> Result<Option<ReferralRewardRecord>, GatewayError> {
|
||||
self.data
|
||||
.void_referral_reward(reward_id, operator_id, note)
|
||||
.await
|
||||
.map_err(referral_data_error)
|
||||
}
|
||||
|
||||
pub(crate) async fn apply_referral_rewards_for_paid_order(
|
||||
&self,
|
||||
order: &aether_data::repository::wallet::StoredAdminPaymentOrder,
|
||||
) -> Result<Vec<ReferralRewardRecord>, GatewayError> {
|
||||
let Some(config) = self.referral_reward_config().await? else {
|
||||
return Ok(Vec::new());
|
||||
};
|
||||
self.data
|
||||
.apply_paid_order_referral_rewards(&order.id, config)
|
||||
.await
|
||||
.map_err(|err| GatewayError::Internal(err.to_string()))
|
||||
}
|
||||
|
||||
pub(crate) async fn apply_referral_rewards_for_payment_order_id(
|
||||
&self,
|
||||
order_id: &str,
|
||||
) -> Result<Vec<ReferralRewardRecord>, GatewayError> {
|
||||
let Some(config) = self.referral_reward_config().await? else {
|
||||
return Ok(Vec::new());
|
||||
};
|
||||
self.data
|
||||
.apply_paid_order_referral_rewards(order_id, config)
|
||||
.await
|
||||
.map_err(|err| GatewayError::Internal(err.to_string()))
|
||||
}
|
||||
|
||||
pub(crate) async fn reverse_referral_rewards_for_order(
|
||||
&self,
|
||||
order_id: &str,
|
||||
amount_usd: f64,
|
||||
) -> Result<Vec<ReferralRewardRecord>, GatewayError> {
|
||||
self.data
|
||||
.reverse_referral_rewards_for_order(order_id, amount_usd)
|
||||
.await
|
||||
.map_err(|err| GatewayError::Internal(err.to_string()))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user