diff --git a/apps/backend/src/services/claim/kuaishou-cloud-claim-service.ts b/apps/backend/src/services/claim/kuaishou-cloud-claim-service.ts index 23123d21..b508fdf1 100644 --- a/apps/backend/src/services/claim/kuaishou-cloud-claim-service.ts +++ b/apps/backend/src/services/claim/kuaishou-cloud-claim-service.ts @@ -404,35 +404,86 @@ export async function submitAffiliateDashClaim( }) } - const gameAccount = String(payload.gameAccount || flow.gameAccount || '').trim() - const bindUuid = String(payload.bindUuid || flow.bindUuid || '').trim() - if (!gameAccount || !bindUuid) { + const expectedGameAccount = String( + getClaimIdentityFromTask(task).expectedUid || flow.expectedGameAccount || flow.gameAccount || '', + ).trim() + const requestedGameAccount = String(payload.gameAccount || '').trim() + const requestedBindUuid = String(payload.bindUuid || '').trim() + const bindUuid = String(flow.bindUuid || '').trim() + if (!expectedGameAccount || !bindUuid) { throw createHttpError('缺少绑定账号或 bindUuid', { statusCode: 400, errorCode: 'affiliate_dash_submit_missing', }) } + if (requestedGameAccount && normalizeUid(requestedGameAccount) !== normalizeUid(expectedGameAccount)) { + throw createHttpError('提交 UID 与第一步填写 UID 不一致,请先更新 UID 后重新绑定', { + statusCode: 409, + errorCode: 'affiliate_dash_submit_uid_changed', + }) + } + if (requestedBindUuid && requestedBindUuid !== bindUuid) { + throw createHttpError('绑定二维码已更新,请刷新页面后重试', { + statusCode: 409, + errorCode: 'affiliate_dash_bind_uuid_stale', + }) + } const now = nowIso() const isMock = Boolean(flow.mock?.enabled) + const gameAccount = expectedGameAccount + let latestFlow = flow // 强制校验:绑定返回的游戏账号与第一步填写 UID 不一致时禁止提交(防止 uid 输错), - // 不依赖平台 mismatch 字段。拉取绑定结果失败时不阻塞(平台提交时仍会校验)。 + // 不依赖请求体 UID,也不依赖平台 mismatch 字段,避免错绑后被前端或手动请求绕过。 if (!isMock && flow.bindUuid) { - let mismatch = false try { const bindResult = await getAffiliateDashBindResult({ orderNo: flow.orderNo, bindUuid, }) const boundAccount = bindResult.gameAccount - mismatch = + const bindMismatch = Boolean(bindResult.mismatch) || - Boolean(boundAccount && normalizeUid(boundAccount) !== normalizeUid(gameAccount)) + Boolean(boundAccount && normalizeUid(boundAccount) !== normalizeUid(expectedGameAccount)) + latestFlow = { + ...flow, + bound: bindResult.bound, + boundAccount: boundAccount || flow.boundAccount, + gameChannel: bindResult.gameChannel || flow.gameChannel, + bindMismatch, + } + await updateTask(task.id, { + context_json: JSON.stringify({ + ...taskContext, + affiliateDash: latestFlow, + }), + updated_at: now, + }) } catch { - // 拉取失败:保持放行 + // 拉取失败:只能使用本地最近一次详情刷新结果,不能无校验放行。 } - if (mismatch) { + const latestBoundAccount = latestFlow.boundAccount + const latestMismatch = + Boolean(latestFlow.bindMismatch) || + Boolean( + latestFlow.bound && + latestBoundAccount && + normalizeUid(latestBoundAccount) !== normalizeUid(expectedGameAccount), + ) + if (!latestFlow.bound) { + throw createHttpError('绑定尚未完成,请先扫码绑定账号', { + statusCode: 409, + errorCode: 'affiliate_dash_bind_pending', + }) + } + if (!latestBoundAccount) { + throw createHttpError('暂时无法确认绑定 UID,请稍后自动刷新后再提交', { + statusCode: 409, + errorCode: 'affiliate_dash_bound_account_missing', + }) + } + if (latestMismatch) { throw createHttpError('绑定账号与填写 UID 不一致,请确认后重新绑定', { statusCode: 409, errorCode: 'affiliate_dash_bind_mismatch', @@ -453,7 +504,7 @@ export async function submitAffiliateDashClaim( }) const nextFlow = { - ...flow, + ...latestFlow, gameAccount, bindUuid, submitStatus: result.status, @@ -519,6 +570,7 @@ async function bindAffiliateDashClaimForTask(task: TaskRow, gameAccount: string) // 重新绑定:清空旧绑定状态,等待用户用新账号扫码完成新绑定 bound: false, boundAccount: '', + gameChannel: '', bindMismatch: false, } diff --git a/apps/frontend/src/pages/claim/ClaimAffiliateDashSteps.tsx b/apps/frontend/src/pages/claim/ClaimAffiliateDashSteps.tsx index 061e26f9..84c5d0e3 100644 --- a/apps/frontend/src/pages/claim/ClaimAffiliateDashSteps.tsx +++ b/apps/frontend/src/pages/claim/ClaimAffiliateDashSteps.tsx @@ -2,6 +2,7 @@ import { CheckCircleOutlined, ExclamationCircleOutlined } from '@ant-design/icon import { Alert, Button, Card, Typography } from 'antd' import { formatAdminDateTime } from '@/utils/admin-time' import type { ClaimAffiliateDashFlowInfo } from '@/types/claim' +import { normalizeUid } from './claim-snapshot' import type { ClaimSnapshot } from './claim-snapshot' import { ClaimProductItems, InfoTile, UidEditRow } from './claim-shared' @@ -29,20 +30,37 @@ export function AffiliateDashClaimPanel({ onSubmit: () => void }) { const bound = Boolean(affiliateDash.bound) - const bindMismatch = Boolean(affiliateDash.bindMismatch) + const expectedAccount = String( + expectedUid || affiliateDash.expectedGameAccount || affiliateDash.gameAccount || '', + ).trim() + const boundAccount = String(affiliateDash.boundAccount || '').trim() + const boundAccountMatched = Boolean( + bound && + expectedAccount && + boundAccount && + normalizeUid(boundAccount) === normalizeUid(expectedAccount), + ) + const bindMismatch = Boolean( + affiliateDash.bindMismatch || + (bound && boundAccount && expectedAccount && !boundAccountMatched), + ) + const canSubmit = Boolean(bound && boundAccountMatched && !bindMismatch) + const displayBoundAccount = boundAccount || '-' return (
- {bound && !bindMismatch ? '第 3 步:确认并提交发货' : '第 2 步:绑定领取账号'} + {canSubmit ? '第 3 步:确认并提交发货' : '第 2 步:绑定领取账号'}

- {bound && !bindMismatch + {canSubmit ? '账号绑定成功,确认无误后提交发货' : bindMismatch ? '绑定账号与填写 UID 不一致,请核对 UID 后用正确账号重新绑定' - : '打开绑定链接完成绑定,系统将自动确认'} + : bound + ? '已扫码,系统正在确认绑定账号' + : '打开绑定链接完成绑定,系统将自动确认'}

@@ -68,23 +86,33 @@ export function AffiliateDashClaimPanel({ type="warning" showIcon message="绑定账号与填写 UID 不一致" - description={`已绑定 ${affiliateDash.boundAccount || '-'},请确认绑定的正是你填写的 UID(${expectedUid || '-'})`} + description={`当前识别到 ${displayBoundAccount},请使用你填写的 UID(${expectedAccount || '-'})对应账号重新扫码绑定。`} style={{ marginBottom: 16 }} /> ) : null} - {bound ? ( + {bound && !bindMismatch && !boundAccountMatched ? ( + + ) : null} + + {canSubmit ? ( } - message={`已绑定账号:${affiliateDash.boundAccount || affiliateDash.gameAccount || '-'}`} + message={`已绑定账号:${displayBoundAccount}`} description={affiliateDash.gameChannel ? `渠道:${affiliateDash.gameChannel}` : undefined} style={{ marginBottom: 16 }} /> ) : null} - {!bound ? ( + {!canSubmit ? (
- ) : bindMismatch ? ( - ) : (