feat: 支持充值商品配置和档位首充
This commit is contained in:
@@ -8,8 +8,9 @@ use crate::{
|
||||
},
|
||||
runtime_profile::{
|
||||
admin_disable_profile_redeem_code, admin_disable_profile_task_config,
|
||||
admin_list_profile_invite_codes, admin_list_profile_redeem_codes,
|
||||
admin_list_profile_task_configs, admin_upsert_profile_invite_code,
|
||||
admin_list_profile_invite_codes, admin_list_profile_recharge_products,
|
||||
admin_list_profile_redeem_codes, admin_list_profile_task_configs,
|
||||
admin_upsert_profile_invite_code, admin_upsert_profile_recharge_product,
|
||||
admin_upsert_profile_redeem_code, admin_upsert_profile_task_config,
|
||||
},
|
||||
state::AppState,
|
||||
@@ -104,7 +105,14 @@ pub fn router(state: AppState) -> Router<AppState> {
|
||||
)
|
||||
.route(
|
||||
"/admin/api/profile/tasks/disable",
|
||||
axum::routing::post(admin_disable_profile_task_config)
|
||||
axum::routing::post(admin_disable_profile_task_config).route_layer(
|
||||
middleware::from_fn_with_state(state.clone(), require_admin_auth),
|
||||
),
|
||||
)
|
||||
.route(
|
||||
"/admin/api/profile/recharge-products",
|
||||
get(admin_list_profile_recharge_products)
|
||||
.post(admin_upsert_profile_recharge_product)
|
||||
.route_layer(middleware::from_fn_with_state(state, require_admin_auth)),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -9,13 +9,15 @@ use module_runtime::{
|
||||
PROFILE_RECHARGE_PAYMENT_CHANNEL_WECHAT_MINI_PROGRAM, RuntimeProfileFeedbackEvidenceRecord,
|
||||
RuntimeProfileFeedbackEvidenceSnapshot, RuntimeProfileFeedbackSubmissionRecord,
|
||||
RuntimeProfileInviteCodeRecord, RuntimeProfileMembershipBenefitRecord,
|
||||
RuntimeProfileRechargeCenterRecord, RuntimeProfileRechargeOrderRecord,
|
||||
RuntimeProfileRechargeOrderStatus, RuntimeProfileRechargeProductRecord,
|
||||
RuntimeProfileRedeemCodeMode, RuntimeProfileRedeemCodeRecord,
|
||||
RuntimeProfileRewardCodeRedeemRecord, RuntimeProfileTaskCenterRecord,
|
||||
RuntimeProfileTaskClaimRecord, RuntimeProfileTaskConfigRecord, RuntimeProfileTaskCycle,
|
||||
RuntimeProfileTaskItemRecord, RuntimeProfileTaskStatus, RuntimeProfileWalletLedgerSourceType,
|
||||
RuntimeReferralInviteCenterRecord, RuntimeTrackingScopeKind,
|
||||
RuntimeProfileMembershipTier, RuntimeProfileRechargeCenterRecord,
|
||||
RuntimeProfileRechargeOrderRecord, RuntimeProfileRechargeOrderStatus,
|
||||
RuntimeProfileRechargeProductConfigRecord, RuntimeProfileRechargeProductKind,
|
||||
RuntimeProfileRechargeProductRecord, RuntimeProfileRedeemCodeMode,
|
||||
RuntimeProfileRedeemCodeRecord, RuntimeProfileRewardCodeRedeemRecord,
|
||||
RuntimeProfileTaskCenterRecord, RuntimeProfileTaskClaimRecord, RuntimeProfileTaskConfigRecord,
|
||||
RuntimeProfileTaskCycle, RuntimeProfileTaskItemRecord, RuntimeProfileTaskStatus,
|
||||
RuntimeProfileWalletLedgerSourceType, RuntimeReferralInviteCenterRecord,
|
||||
RuntimeTrackingScopeKind,
|
||||
};
|
||||
use serde::Deserialize;
|
||||
use serde_json::{Value, json};
|
||||
@@ -23,12 +25,16 @@ use shared_contracts::runtime::{
|
||||
ANALYTICS_GRANULARITY_DAY, ANALYTICS_GRANULARITY_MONTH, ANALYTICS_GRANULARITY_QUARTER,
|
||||
ANALYTICS_GRANULARITY_WEEK, ANALYTICS_GRANULARITY_YEAR, AdminDisableProfileRedeemCodeRequest,
|
||||
AdminDisableProfileTaskConfigRequest, AdminUpsertProfileInviteCodeRequest,
|
||||
AdminUpsertProfileRedeemCodeRequest, AdminUpsertProfileTaskConfigRequest,
|
||||
AnalyticsBucketMetricResponse, AnalyticsMetricQueryResponse, ClaimProfileTaskRewardResponse,
|
||||
AdminUpsertProfileRechargeProductRequest, AdminUpsertProfileRedeemCodeRequest,
|
||||
AdminUpsertProfileTaskConfigRequest, AnalyticsBucketMetricResponse,
|
||||
AnalyticsMetricQueryResponse, ClaimProfileTaskRewardResponse,
|
||||
ConfirmWechatProfileRechargeOrderResponse, CreateProfileRechargeOrderRequest,
|
||||
CreateProfileRechargeOrderResponse, PROFILE_FEEDBACK_STATUS_OPEN, PROFILE_TASK_CYCLE_DAILY,
|
||||
PROFILE_TASK_STATUS_CLAIMABLE, PROFILE_TASK_STATUS_CLAIMED, PROFILE_TASK_STATUS_DISABLED,
|
||||
PROFILE_TASK_STATUS_INCOMPLETE, PROFILE_WALLET_LEDGER_SOURCE_TYPE_ASSET_OPERATION_CONSUME,
|
||||
CreateProfileRechargeOrderResponse, PROFILE_FEEDBACK_STATUS_OPEN,
|
||||
PROFILE_MEMBERSHIP_TIER_MONTH, PROFILE_MEMBERSHIP_TIER_NORMAL, PROFILE_MEMBERSHIP_TIER_SEASON,
|
||||
PROFILE_MEMBERSHIP_TIER_YEAR, PROFILE_RECHARGE_PRODUCT_KIND_MEMBERSHIP,
|
||||
PROFILE_RECHARGE_PRODUCT_KIND_POINTS, PROFILE_TASK_CYCLE_DAILY, PROFILE_TASK_STATUS_CLAIMABLE,
|
||||
PROFILE_TASK_STATUS_CLAIMED, PROFILE_TASK_STATUS_DISABLED, PROFILE_TASK_STATUS_INCOMPLETE,
|
||||
PROFILE_WALLET_LEDGER_SOURCE_TYPE_ASSET_OPERATION_CONSUME,
|
||||
PROFILE_WALLET_LEDGER_SOURCE_TYPE_ASSET_OPERATION_REFUND,
|
||||
PROFILE_WALLET_LEDGER_SOURCE_TYPE_DAILY_TASK_REWARD,
|
||||
PROFILE_WALLET_LEDGER_SOURCE_TYPE_INVITE_INVITEE_REWARD,
|
||||
@@ -42,6 +48,7 @@ use shared_contracts::runtime::{
|
||||
ProfileInviteCodeAdminListResponse, ProfileInviteCodeAdminResponse,
|
||||
ProfileMembershipBenefitResponse, ProfileMembershipResponse, ProfilePlayStatsResponse,
|
||||
ProfilePlayedWorkSummaryResponse, ProfileRechargeCenterResponse, ProfileRechargeOrderResponse,
|
||||
ProfileRechargeProductConfigAdminListResponse, ProfileRechargeProductConfigAdminResponse,
|
||||
ProfileRechargeProductResponse, ProfileRedeemCodeAdminListResponse,
|
||||
ProfileRedeemCodeAdminResponse, ProfileReferralInviteCenterResponse,
|
||||
ProfileReferralInvitedUserResponse, ProfileTaskCenterResponse,
|
||||
@@ -669,6 +676,84 @@ pub async fn admin_disable_profile_task_config(
|
||||
))
|
||||
}
|
||||
|
||||
pub async fn admin_list_profile_recharge_products(
|
||||
State(state): State<AppState>,
|
||||
Extension(request_context): Extension<RequestContext>,
|
||||
Extension(admin): Extension<AuthenticatedAdmin>,
|
||||
) -> Result<Json<Value>, Response> {
|
||||
let entries = state
|
||||
.spacetime_client()
|
||||
.admin_list_profile_recharge_products(admin.session().subject.clone())
|
||||
.await
|
||||
.map_err(|error| {
|
||||
runtime_profile_error_response(
|
||||
&request_context,
|
||||
map_runtime_profile_client_error(error),
|
||||
)
|
||||
})?;
|
||||
|
||||
Ok(json_success_body(
|
||||
Some(&request_context),
|
||||
ProfileRechargeProductConfigAdminListResponse {
|
||||
entries: entries
|
||||
.into_iter()
|
||||
.map(build_profile_recharge_product_config_admin_response)
|
||||
.collect(),
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
pub async fn admin_upsert_profile_recharge_product(
|
||||
State(state): State<AppState>,
|
||||
Extension(request_context): Extension<RequestContext>,
|
||||
Extension(admin): Extension<AuthenticatedAdmin>,
|
||||
Json(payload): Json<AdminUpsertProfileRechargeProductRequest>,
|
||||
) -> Result<Json<Value>, Response> {
|
||||
let kind = parse_profile_recharge_product_kind(&payload.kind).map_err(|error| {
|
||||
runtime_profile_error_response(
|
||||
&request_context,
|
||||
AppError::from_status(StatusCode::BAD_REQUEST).with_message(error),
|
||||
)
|
||||
})?;
|
||||
let tier = parse_profile_membership_tier(&payload.tier).map_err(|error| {
|
||||
runtime_profile_error_response(
|
||||
&request_context,
|
||||
AppError::from_status(StatusCode::BAD_REQUEST).with_message(error),
|
||||
)
|
||||
})?;
|
||||
let updated_at_micros = OffsetDateTime::now_utc().unix_timestamp_nanos() / 1_000;
|
||||
let record = state
|
||||
.spacetime_client()
|
||||
.admin_upsert_profile_recharge_product(
|
||||
admin.session().subject.clone(),
|
||||
payload.product_id,
|
||||
payload.title,
|
||||
payload.price_cents,
|
||||
kind,
|
||||
payload.points_amount,
|
||||
payload.bonus_points,
|
||||
payload.duration_days,
|
||||
payload.badge_label.unwrap_or_default(),
|
||||
payload.description.unwrap_or_default(),
|
||||
tier,
|
||||
payload.enabled,
|
||||
payload.sort_order.unwrap_or(10),
|
||||
updated_at_micros as i64,
|
||||
)
|
||||
.await
|
||||
.map_err(|error| {
|
||||
runtime_profile_error_response(
|
||||
&request_context,
|
||||
map_runtime_profile_client_error(error),
|
||||
)
|
||||
})?;
|
||||
|
||||
Ok(json_success_body(
|
||||
Some(&request_context),
|
||||
build_profile_recharge_product_config_admin_response(record),
|
||||
))
|
||||
}
|
||||
|
||||
pub async fn admin_list_profile_redeem_codes(
|
||||
State(state): State<AppState>,
|
||||
Extension(request_context): Extension<RequestContext>,
|
||||
@@ -1172,6 +1257,29 @@ fn build_profile_task_config_admin_response(
|
||||
}
|
||||
}
|
||||
|
||||
fn build_profile_recharge_product_config_admin_response(
|
||||
record: RuntimeProfileRechargeProductConfigRecord,
|
||||
) -> ProfileRechargeProductConfigAdminResponse {
|
||||
ProfileRechargeProductConfigAdminResponse {
|
||||
product_id: record.product_id,
|
||||
title: record.title,
|
||||
price_cents: record.price_cents,
|
||||
kind: format_profile_recharge_product_kind(record.kind).to_string(),
|
||||
points_amount: record.points_amount,
|
||||
bonus_points: record.bonus_points,
|
||||
duration_days: record.duration_days,
|
||||
badge_label: record.badge_label,
|
||||
description: record.description,
|
||||
tier: format_profile_membership_tier(record.tier).to_string(),
|
||||
enabled: record.enabled,
|
||||
sort_order: record.sort_order,
|
||||
created_by: record.created_by,
|
||||
created_at: record.created_at,
|
||||
updated_by: record.updated_by,
|
||||
updated_at: record.updated_at,
|
||||
}
|
||||
}
|
||||
|
||||
fn normalize_admin_invite_code_metadata(metadata: Option<Value>) -> Result<String, AppError> {
|
||||
let metadata = match metadata {
|
||||
Some(Value::Null) | None => json!({}),
|
||||
@@ -1233,6 +1341,28 @@ fn parse_profile_task_cycle(raw: &str) -> Result<RuntimeProfileTaskCycle, String
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_profile_recharge_product_kind(
|
||||
raw: &str,
|
||||
) -> Result<RuntimeProfileRechargeProductKind, String> {
|
||||
match raw.trim().to_ascii_lowercase().as_str() {
|
||||
PROFILE_RECHARGE_PRODUCT_KIND_POINTS => Ok(RuntimeProfileRechargeProductKind::Points),
|
||||
PROFILE_RECHARGE_PRODUCT_KIND_MEMBERSHIP => {
|
||||
Ok(RuntimeProfileRechargeProductKind::Membership)
|
||||
}
|
||||
_ => Err("充值商品类型无效".to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_profile_membership_tier(raw: &str) -> Result<RuntimeProfileMembershipTier, String> {
|
||||
match raw.trim().to_ascii_lowercase().as_str() {
|
||||
PROFILE_MEMBERSHIP_TIER_NORMAL => Ok(RuntimeProfileMembershipTier::Normal),
|
||||
PROFILE_MEMBERSHIP_TIER_MONTH => Ok(RuntimeProfileMembershipTier::Month),
|
||||
PROFILE_MEMBERSHIP_TIER_SEASON => Ok(RuntimeProfileMembershipTier::Season),
|
||||
PROFILE_MEMBERSHIP_TIER_YEAR => Ok(RuntimeProfileMembershipTier::Year),
|
||||
_ => Err("会员档位无效".to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_tracking_scope_kind(raw: &str) -> Result<RuntimeTrackingScopeKind, String> {
|
||||
match raw.trim().to_ascii_lowercase().as_str() {
|
||||
TRACKING_SCOPE_KIND_SITE => Ok(RuntimeTrackingScopeKind::Site),
|
||||
@@ -1269,6 +1399,22 @@ fn format_profile_task_status(status: RuntimeProfileTaskStatus) -> &'static str
|
||||
}
|
||||
}
|
||||
|
||||
fn format_profile_recharge_product_kind(kind: RuntimeProfileRechargeProductKind) -> &'static str {
|
||||
match kind {
|
||||
RuntimeProfileRechargeProductKind::Points => PROFILE_RECHARGE_PRODUCT_KIND_POINTS,
|
||||
RuntimeProfileRechargeProductKind::Membership => PROFILE_RECHARGE_PRODUCT_KIND_MEMBERSHIP,
|
||||
}
|
||||
}
|
||||
|
||||
fn format_profile_membership_tier(tier: RuntimeProfileMembershipTier) -> &'static str {
|
||||
match tier {
|
||||
RuntimeProfileMembershipTier::Normal => PROFILE_MEMBERSHIP_TIER_NORMAL,
|
||||
RuntimeProfileMembershipTier::Month => PROFILE_MEMBERSHIP_TIER_MONTH,
|
||||
RuntimeProfileMembershipTier::Season => PROFILE_MEMBERSHIP_TIER_SEASON,
|
||||
RuntimeProfileMembershipTier::Year => PROFILE_MEMBERSHIP_TIER_YEAR,
|
||||
}
|
||||
}
|
||||
|
||||
fn format_tracking_scope_kind(scope_kind: RuntimeTrackingScopeKind) -> &'static str {
|
||||
match scope_kind {
|
||||
RuntimeTrackingScopeKind::Site => TRACKING_SCOPE_KIND_SITE,
|
||||
@@ -1702,6 +1848,7 @@ mod tests {
|
||||
for uri in [
|
||||
"/admin/api/profile/redeem-codes",
|
||||
"/admin/api/profile/invite-codes",
|
||||
"/admin/api/profile/recharge-products",
|
||||
] {
|
||||
let response = app
|
||||
.clone()
|
||||
|
||||
Reference in New Issue
Block a user