feat: 接入微信小程序支付

This commit is contained in:
2026-05-14 00:16:17 +08:00
parent bf4423e53b
commit ae58a443a3
42 changed files with 2265 additions and 191 deletions

View File

@@ -6,15 +6,16 @@ use axum::{
};
use module_runtime::{
AnalyticsGranularity, PROFILE_RECHARGE_PAYMENT_CHANNEL_MOCK,
RuntimeProfileFeedbackEvidenceRecord, RuntimeProfileFeedbackEvidenceSnapshot,
RuntimeProfileFeedbackSubmissionRecord, RuntimeProfileInviteCodeRecord,
RuntimeProfileMembershipBenefitRecord, RuntimeProfileRechargeCenterRecord,
RuntimeProfileRechargeOrderRecord, RuntimeProfileRechargeProductRecord,
RuntimeProfileRedeemCodeMode, RuntimeProfileRedeemCodeRecord,
RuntimeProfileRewardCodeRedeemRecord, RuntimeProfileTaskCenterRecord,
RuntimeProfileTaskClaimRecord, RuntimeProfileTaskConfigRecord, RuntimeProfileTaskCycle,
RuntimeProfileTaskItemRecord, RuntimeProfileTaskStatus, RuntimeProfileWalletLedgerSourceType,
RuntimeReferralInviteCenterRecord, RuntimeTrackingScopeKind,
PROFILE_RECHARGE_PAYMENT_CHANNEL_WECHAT_MINI_PROGRAM, RuntimeProfileFeedbackEvidenceRecord,
RuntimeProfileFeedbackEvidenceSnapshot, RuntimeProfileFeedbackSubmissionRecord,
RuntimeProfileInviteCodeRecord, RuntimeProfileMembershipBenefitRecord,
RuntimeProfileRechargeCenterRecord, RuntimeProfileRechargeOrderRecord,
RuntimeProfileRechargeProductRecord, RuntimeProfileRedeemCodeMode,
RuntimeProfileRedeemCodeRecord, RuntimeProfileRewardCodeRedeemRecord,
RuntimeProfileTaskCenterRecord, RuntimeProfileTaskClaimRecord, RuntimeProfileTaskConfigRecord,
RuntimeProfileTaskCycle, RuntimeProfileTaskItemRecord, RuntimeProfileTaskStatus,
RuntimeProfileWalletLedgerSourceType, RuntimeReferralInviteCenterRecord,
RuntimeTrackingScopeKind,
};
use serde::Deserialize;
use serde_json::{Value, json};
@@ -56,8 +57,13 @@ use spacetime_client::SpacetimeClientError;
use time::OffsetDateTime;
use crate::{
admin::AuthenticatedAdmin, api_response::json_success_body, auth::AuthenticatedAccessToken,
http_error::AppError, request_context::RequestContext, state::AppState,
admin::AuthenticatedAdmin,
api_response::json_success_body,
auth::AuthenticatedAccessToken,
http_error::AppError,
request_context::RequestContext,
state::AppState,
wechat_pay::{build_wechat_payment_request, current_unix_micros, map_wechat_pay_error},
};
pub async fn get_profile_dashboard(
@@ -186,14 +192,15 @@ pub async fn create_profile_recharge_order(
let payment_channel = payload
.payment_channel
.unwrap_or_else(|| PROFILE_RECHARGE_PAYMENT_CHANNEL_MOCK.to_string());
let created_at_micros = OffsetDateTime::now_utc().unix_timestamp_nanos() / 1_000;
let payment_channel = payment_channel.trim().to_string();
let created_at_micros = current_unix_micros();
let (center, order) = state
.spacetime_client()
.create_profile_recharge_order(
user_id,
payload.product_id,
payment_channel,
created_at_micros as i64,
payment_channel.clone(),
created_at_micros,
)
.await
.map_err(|error| {
@@ -203,11 +210,36 @@ pub async fn create_profile_recharge_order(
)
})?;
let wechat_mini_program_pay_params = if payment_channel
== PROFILE_RECHARGE_PAYMENT_CHANNEL_WECHAT_MINI_PROGRAM
{
let identity = resolve_wechat_identity_for_payment(&state, &order.user_id)
.await
.map_err(|error| runtime_profile_error_response(&request_context, error))?;
Some(
state
.wechat_pay_client()
.create_mini_program_order(build_wechat_payment_request(
order.order_id.clone(),
order.product_title.clone(),
order.amount_cents,
identity,
))
.await
.map_err(|error| {
runtime_profile_error_response(&request_context, map_wechat_pay_error(error))
})?,
)
} else {
None
};
Ok(json_success_body(
Some(&request_context),
CreateProfileRechargeOrderResponse {
order: build_profile_recharge_order_response(order),
center: build_profile_recharge_center_response(center),
wechat_mini_program_pay_params,
},
))
}
@@ -750,6 +782,25 @@ fn runtime_profile_error_response(request_context: &RequestContext, error: AppEr
error.into_response_with_context(Some(request_context))
}
async fn resolve_wechat_identity_for_payment(
state: &AppState,
user_id: &str,
) -> Result<String, AppError> {
if let Some(identity) = state
.wechat_auth_service()
.get_identity_by_user_id(user_id)
.map_err(|error| {
AppError::from_status(StatusCode::INTERNAL_SERVER_ERROR)
.with_message(format!("读取微信身份失败:{error}"))
})?
{
return Ok(identity.provider_uid);
}
Err(AppError::from_status(StatusCode::BAD_REQUEST)
.with_message("当前账号缺少微信小程序身份,请在小程序内重新登录后再支付"))
}
fn build_profile_recharge_center_response(
record: RuntimeProfileRechargeCenterRecord,
) -> ProfileRechargeCenterResponse {
@@ -825,6 +876,7 @@ fn build_profile_recharge_order_response(
status: record.status.as_str().to_string(),
payment_channel: record.payment_channel,
paid_at: record.paid_at,
provider_transaction_id: record.provider_transaction_id,
created_at: record.created_at,
points_delta: record.points_delta,
membership_expires_at: record.membership_expires_at,