feat(affiliate): 完善邀请返利系统

- 修复返利不到账的根因:tryClaimAffiliateRebateAudit 中 PostgreSQL 参数类型推断冲突
  - 补全 OAuth 注册路径(LinuxDo/OIDC/WeChat/Pending Flow)的邀请码绑定
  - 前端 OAuth 注册页面传递 aff_code 参数
  - 新增返利冻结期机制:可配置冻结时间,到期后自动解冻(懒解冻)
  - 新增返利有效期:绑定后 N 天内有效,过期不再产生返利
  - 新增单人返利上限:超出上限部分精确截断
  - 增强返利流程 slog 结构化日志,便于排查问题
  - 已邀请用户列表增加返利明细列
This commit is contained in:
shaw
2026-04-26 12:31:52 +08:00
parent 496469ac4e
commit 9b6dcc57bd
42 changed files with 852 additions and 104 deletions

View File

@@ -340,6 +340,11 @@ import {
type OAuthTokenResponse,
type PendingOAuthExchangeResponse
} from '@/api/auth'
import {
clearAllAffiliateReferralCodes,
loadOAuthAffiliateCode,
oauthAffiliatePayload
} from '@/utils/oauthAffiliate'
const route = useRoute()
const router = useRouter()
@@ -802,6 +807,7 @@ async function finalizeCompletion(completion: PendingOAuthExchangeResponse, redi
if (getOAuthCompletionKind(completion) === 'bind') {
const bindRedirect = sanitizeRedirectPath(completion.redirect || '/profile')
clearPendingAuthSession()
clearAllAffiliateReferralCodes()
appStore.showSuccess(bindSuccessMessage)
await router.replace(bindRedirect)
return
@@ -813,6 +819,7 @@ async function finalizeCompletion(completion: PendingOAuthExchangeResponse, redi
persistOAuthTokenContext(completion)
await authStore.setToken(completion.access_token)
clearAllAffiliateReferralCodes()
appStore.showSuccess(t('auth.loginSuccess'))
await router.replace(redirect)
}
@@ -861,18 +868,20 @@ async function handleSubmitInvitation() {
isSubmitting.value = true
try {
const affCode = loadOAuthAffiliateCode()
const decision = currentAdoptionDecision()
const completion: PendingWeChatCompletion = legacyPendingOAuthToken.value
? (
await apiClient.post<PendingWeChatCompletion>('/auth/oauth/wechat/complete-registration', {
pending_oauth_token: legacyPendingOAuthToken.value,
invitation_code: invitationCode.value.trim(),
...serializeAdoptionDecision(currentAdoptionDecision())
...oauthAffiliatePayload(affCode),
...serializeAdoptionDecision(decision)
})
).data
: await completeWeChatOAuthRegistration(
invitationCode.value.trim(),
currentAdoptionDecision()
)
: affCode
? await completeWeChatOAuthRegistration(invitationCode.value.trim(), decision, affCode)
: await completeWeChatOAuthRegistration(invitationCode.value.trim(), decision)
await finalizePendingAccountResponse(completion)
} catch (e: unknown) {
const err = e as { message?: string; response?: { data?: { message?: string } } }
@@ -907,6 +916,7 @@ async function handleCreateAccount(payload: PendingOAuthCreateAccountPayload) {
password: payload.password,
verify_code: payload.verifyCode || undefined,
invitation_code: payload.invitationCode || undefined,
...oauthAffiliatePayload(loadOAuthAffiliateCode()),
...serializeAdoptionDecision(currentAdoptionDecision())
})
await finalizePendingAccountResponse(data)
@@ -955,6 +965,7 @@ async function handleSubmitTotpChallenge() {
})
persistOAuthTokenContext(completion)
await authStore.setToken(completion.access_token)
clearAllAffiliateReferralCodes()
appStore.showSuccess(t('auth.loginSuccess'))
await router.replace(redirectTo.value)
} catch (e: unknown) {
@@ -1015,6 +1026,7 @@ onMounted(async () => {
if (legacyLogin) {
persistOAuthTokenContext(legacyLogin)
await authStore.setToken(legacyLogin.access_token)
clearAllAffiliateReferralCodes()
appStore.showSuccess(t('auth.loginSuccess'))
await router.replace(redirect)
return