新增 affiliate_dash 统一领取页流程(阶段 4)
- 后端:claim 详情分流 affiliate_dash 分支 + 详情轮询刷新 bind-result - 输 UID 触发 bind(拿 bindUuid/二维码, task→waiting_binding) - 新增 POST /claim/:token/affiliate-dash/submit(submit→redeeming + 事件) - 前端:ClaimAffiliateDashSteps(二维码/绑定状态/mismatch警告/提交发货) - claim-snapshot/claim-poll 按 flowType 分流(step2 绑定中/step3 已绑定待提交/step4 结果) - 前后端 typecheck + 后端 212 测试通过
This commit is contained in:
@@ -6,6 +6,7 @@ import {
|
||||
getKuaishouCloudClaimDetail,
|
||||
rebindKuaishouCloudClaimRole,
|
||||
redeemKuaishouCloudClaim,
|
||||
submitAffiliateDashClaim,
|
||||
submitClaimUid,
|
||||
} from '../services/claim/kuaishou-cloud-claim-service.js'
|
||||
import { buildNotFoundPayload, createRouteHandler } from '../utils/http.js'
|
||||
@@ -43,6 +44,16 @@ router.post(
|
||||
}),
|
||||
)
|
||||
|
||||
router.post(
|
||||
'/:token/affiliate-dash/submit',
|
||||
claimWriteRateLimit,
|
||||
createRouteHandler((req) => submitAffiliateDashClaim(req.params.token, req.body || {}), {
|
||||
successMessage: '发货已提交',
|
||||
errorMessage: '提交发货失败',
|
||||
scope: '[claims/:token/affiliate-dash/submit]',
|
||||
}),
|
||||
)
|
||||
|
||||
router.post(
|
||||
'/:token/kuaishou-cloud/confirm-role',
|
||||
claimWriteRateLimit,
|
||||
|
||||
@@ -10,6 +10,7 @@ import { TASK_STATUS, isTaskFinalStatus } from '../../domain/task-status.js'
|
||||
import { buildClaimUrl } from './claim-service.js'
|
||||
import { buildClaimIdentityPayload, getClaimIdentityFromContext } from './claim-identity.js'
|
||||
import { resolveKuaishouFeifeiH5UrlWithUid } from '../fulfillment/kuaishou-feifei/index.js'
|
||||
import { normalizeAffiliateDashFlow } from '../fulfillment/affiliate-dash/index.js'
|
||||
import type { ClaimTokenRow, OrderItemRow, OrderRow, TaskRow } from '../../types/repository/rows.js'
|
||||
import { asJsonObject, type JsonObject } from '../../types/json.js'
|
||||
|
||||
@@ -91,6 +92,10 @@ export function buildClaimDetailPayload({ claimToken, task, order, orderItem }:
|
||||
return buildKuaishouFeifeiClaimDetailPayload({ claimToken, task, order, orderItem })
|
||||
}
|
||||
|
||||
if (String(task.executor_key || '').trim() === 'affiliate_dash') {
|
||||
return buildAffiliateDashClaimDetailPayload({ claimToken, task, order, orderItem })
|
||||
}
|
||||
|
||||
const taskContext = parseTaskContext(task)
|
||||
const claimIdentity = buildClaimIdentityPayload(taskContext.claimIdentity)
|
||||
const kuaishouCloudSource = resolveClaimKuaishouCloudSource(task)
|
||||
@@ -152,6 +157,76 @@ export function buildClaimDetailPayload({ claimToken, task, order, orderItem }:
|
||||
}
|
||||
}
|
||||
|
||||
function buildAffiliateDashClaimDetailPayload({ claimToken, task, order, orderItem }: ClaimContext) {
|
||||
const context = parseTaskContext(task)
|
||||
const claimIdentity = buildClaimIdentityPayload(context.claimIdentity)
|
||||
const flow = normalizeAffiliateDashFlow(context.affiliateDash)
|
||||
const productTitle = String(
|
||||
flow.productName || orderItem.sku_name || orderItem.sku_code || '',
|
||||
).trim()
|
||||
const product = {
|
||||
title: productTitle,
|
||||
skuCode: String(orderItem.sku_code || '').trim(),
|
||||
quantity: Math.max(1, Number(orderItem.quantity || 1) || 1),
|
||||
isBundle: false,
|
||||
items: [{
|
||||
cloudSkuId: 0,
|
||||
name: productTitle,
|
||||
quantity: Math.max(1, Number(orderItem.quantity || 1) || 1),
|
||||
}],
|
||||
}
|
||||
|
||||
return {
|
||||
tokenStatus: claimToken.status,
|
||||
claimUrl: buildClaimUrl(claimToken.token),
|
||||
flowType: 'affiliate_dash',
|
||||
claimIdentity,
|
||||
task: {
|
||||
taskId: task.id,
|
||||
taskNo: task.task_no,
|
||||
status: task.task_status,
|
||||
executorKey: task.executor_key || '',
|
||||
requiresSupportReview: false,
|
||||
expiresAt: claimToken.expired_at,
|
||||
claimedAt: task.claimed_at,
|
||||
roleConfirmedAt: task.role_confirmed_at,
|
||||
redeemedAt: task.redeemed_at,
|
||||
loginType: task.login_type,
|
||||
lastError: task.last_error,
|
||||
runtimeSessionId: task.runtime_session_id,
|
||||
},
|
||||
order: {
|
||||
orderId: order.id,
|
||||
platform: order.platform,
|
||||
platformOrderId: order.platform_order_id,
|
||||
payStatus: order.pay_status,
|
||||
orderStatus: order.order_status,
|
||||
totalAmount: formatFenToAmount(order.total_amount),
|
||||
totalAmountFen: normalizeFen(order.total_amount),
|
||||
currency: order.currency,
|
||||
},
|
||||
orderItem: {
|
||||
orderItemId: orderItem.id,
|
||||
skuCode: orderItem.sku_code,
|
||||
skuName: productTitle,
|
||||
quantity: orderItem.quantity,
|
||||
},
|
||||
product,
|
||||
session: null as null,
|
||||
kuaishouCloudFulfillment: null as null,
|
||||
kuaishouFeifei: null as null,
|
||||
affiliateDash: flow,
|
||||
result: task.redeemed_at
|
||||
? {
|
||||
resultCode: String(task.result_code || ''),
|
||||
resultMessage: String(task.result_message || ''),
|
||||
screenshotReady: false,
|
||||
screenshotUrl: '',
|
||||
}
|
||||
: null,
|
||||
}
|
||||
}
|
||||
|
||||
function buildKuaishouFeifeiClaimDetailPayload({ claimToken, task, order, orderItem }: ClaimContext) {
|
||||
const context = parseTaskContext(task)
|
||||
const claimIdentity = buildClaimIdentityPayload(context.claimIdentity)
|
||||
|
||||
@@ -16,6 +16,15 @@ import {
|
||||
normalizeKuaishouCloudFlow,
|
||||
} from '../fulfillment/kuaishou-cloud/index.js'
|
||||
import { syncKuaishouFeifeiTaskStatus } from '../fulfillment/kuaishou-feifei/index.js'
|
||||
import {
|
||||
normalizeAffiliateDashFlow,
|
||||
syncAffiliateDashTaskStatus,
|
||||
} from '../fulfillment/affiliate-dash/index.js'
|
||||
import {
|
||||
bindAffiliateDashDelivery,
|
||||
getAffiliateDashBindResult,
|
||||
submitAffiliateDashDelivery,
|
||||
} from '../platforms/affiliate-dash/order-service.js'
|
||||
import {
|
||||
assertValidClaimUid,
|
||||
canUpdateClaimUid,
|
||||
@@ -221,6 +230,11 @@ export async function getKuaishouCloudClaimDetail(token: unknown): Promise<Claim
|
||||
task = (await syncKuaishouFeifeiTaskStatus(task)) || task
|
||||
}
|
||||
|
||||
if (executorKey === 'affiliate_dash') {
|
||||
task = (await syncAffiliateDashTaskStatus(task)) || task
|
||||
task = (await refreshAffiliateDashBindState(task)) || task
|
||||
}
|
||||
|
||||
if (executorKey === 'kuaishou-industry') {
|
||||
const flow = normalizeKuaishouCloudFlow(parseTaskContext(task).kuaishouCloudFulfillment)
|
||||
// 已核销也要走 verify:内部会补 prepare 绑定资源
|
||||
@@ -357,9 +371,173 @@ export async function submitClaimUid(
|
||||
}
|
||||
}
|
||||
|
||||
if (executorKey === 'affiliate_dash') {
|
||||
updatedTask = (await bindAffiliateDashClaimForTask(updatedTask, expectedUid)) || updatedTask
|
||||
}
|
||||
|
||||
return getKuaishouCloudClaimDetail(token)
|
||||
}
|
||||
|
||||
/**
|
||||
* affiliate_dash 领取 Step2:提交发货(绑定完成后)。
|
||||
*/
|
||||
export async function submitAffiliateDashClaim(
|
||||
token: unknown,
|
||||
payload: { gameAccount?: unknown; bindUuid?: unknown } = {},
|
||||
): Promise<ClaimDetailPayload> {
|
||||
const context = await getClaimContext(token)
|
||||
const task = context.task
|
||||
const executorKey = String(task.executor_key || '').trim()
|
||||
if (executorKey !== 'affiliate_dash') {
|
||||
throw createHttpError('当前领取链接不是 affiliate-dash 领取流程', {
|
||||
statusCode: 409,
|
||||
errorCode: 'claim_not_affiliate_dash',
|
||||
})
|
||||
}
|
||||
|
||||
const taskContext = parseTaskContextValue(task)
|
||||
const flow = normalizeAffiliateDashFlow(taskContext.affiliateDash)
|
||||
if (!flow.orderNo) {
|
||||
throw createHttpError('affiliate-dash 订单尚未创建', {
|
||||
statusCode: 409,
|
||||
errorCode: 'affiliate_dash_order_missing',
|
||||
})
|
||||
}
|
||||
|
||||
const gameAccount = String(payload.gameAccount || flow.gameAccount || '').trim()
|
||||
const bindUuid = String(payload.bindUuid || flow.bindUuid || '').trim()
|
||||
if (!gameAccount || !bindUuid) {
|
||||
throw createHttpError('缺少绑定账号或 bindUuid', {
|
||||
statusCode: 400,
|
||||
errorCode: 'affiliate_dash_submit_missing',
|
||||
})
|
||||
}
|
||||
|
||||
const now = nowIso()
|
||||
const result = await submitAffiliateDashDelivery({
|
||||
orderNo: flow.orderNo,
|
||||
gameAccount,
|
||||
bindUuid,
|
||||
})
|
||||
|
||||
const nextFlow = {
|
||||
...flow,
|
||||
gameAccount,
|
||||
bindUuid,
|
||||
submitStatus: result.status,
|
||||
orderStatus: result.status || flow.orderStatus,
|
||||
}
|
||||
|
||||
await updateTask(task.id, {
|
||||
task_status: TASK_STATUS.REDEEMING,
|
||||
context_json: JSON.stringify({
|
||||
...taskContext,
|
||||
affiliateDash: nextFlow,
|
||||
}),
|
||||
result_code: 'affiliate_dash_submitted',
|
||||
result_message: result.message || 'affiliate-dash 已提交发货',
|
||||
updated_at: now,
|
||||
})
|
||||
await createTaskEvent(
|
||||
task.id,
|
||||
'affiliate_dash_submitted',
|
||||
{
|
||||
orderNo: flow.orderNo,
|
||||
submitStatus: result.status,
|
||||
providerOrderNo: result.providerOrderNo,
|
||||
},
|
||||
now,
|
||||
)
|
||||
|
||||
return getKuaishouCloudClaimDetail(token)
|
||||
}
|
||||
|
||||
/**
|
||||
* 提交 UID 后触发 affiliate-dash 绑定(bind),把 bind_uuid / 二维码写回上下文,
|
||||
* task → waiting_binding(link_generated → waiting_binding 合法)。
|
||||
*/
|
||||
async function bindAffiliateDashClaimForTask(task: TaskRow, gameAccount: string) {
|
||||
const taskContext = parseTaskContextValue(task)
|
||||
const flow = normalizeAffiliateDashFlow(taskContext.affiliateDash)
|
||||
if (!flow.orderNo || !gameAccount) {
|
||||
return task
|
||||
}
|
||||
|
||||
const now = nowIso()
|
||||
const bindResult = await bindAffiliateDashDelivery({
|
||||
orderNo: flow.orderNo,
|
||||
gameAccount,
|
||||
})
|
||||
const nextFlow = {
|
||||
...flow,
|
||||
bindUuid: bindResult.bindUuid,
|
||||
bindUrl: bindResult.bindUrl,
|
||||
qrUrl: bindResult.qrUrl,
|
||||
gameAccount,
|
||||
}
|
||||
|
||||
const updatedTask = await updateTask(task.id, {
|
||||
task_status: TASK_STATUS.WAITING_BINDING,
|
||||
context_json: JSON.stringify({
|
||||
...taskContext,
|
||||
affiliateDash: nextFlow,
|
||||
}),
|
||||
updated_at: now,
|
||||
})
|
||||
await createTaskEvent(
|
||||
task.id,
|
||||
'affiliate_dash_bind_created',
|
||||
{
|
||||
orderNo: flow.orderNo,
|
||||
bindUuid: bindResult.bindUuid,
|
||||
gameAccount,
|
||||
},
|
||||
now,
|
||||
)
|
||||
|
||||
return updatedTask || task
|
||||
}
|
||||
|
||||
/**
|
||||
* 轮询绑定状态:详情查询时若有 bind_uuid,拉取 affiliate-dash bind-result 实时刷新
|
||||
* bound / 绑定账号 / mismatch 到上下文(可重入,拉取失败不阻塞详情)。
|
||||
*/
|
||||
async function refreshAffiliateDashBindState(task: TaskRow): Promise<TaskRow | null> {
|
||||
const taskContext = parseTaskContextValue(task)
|
||||
const flow = normalizeAffiliateDashFlow(taskContext.affiliateDash)
|
||||
if (!flow.orderNo || !flow.bindUuid) {
|
||||
return task
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await getAffiliateDashBindResult({
|
||||
orderNo: flow.orderNo,
|
||||
bindUuid: flow.bindUuid,
|
||||
})
|
||||
const nextFlow = {
|
||||
...flow,
|
||||
bound: result.bound,
|
||||
boundAccount: result.gameAccount || flow.boundAccount,
|
||||
gameChannel: result.gameChannel || flow.gameChannel,
|
||||
bindMismatch: result.mismatch,
|
||||
}
|
||||
const now = nowIso()
|
||||
|
||||
return (
|
||||
(await updateTask(task.id, {
|
||||
context_json: JSON.stringify({
|
||||
...taskContext,
|
||||
affiliateDash: nextFlow,
|
||||
}),
|
||||
updated_at: now,
|
||||
})) || task
|
||||
)
|
||||
} catch {
|
||||
// 绑定状态拉取失败:保持现状,前端可继续轮询
|
||||
return task
|
||||
}
|
||||
}
|
||||
|
||||
export async function rebindKuaishouCloudClaimRole(token: unknown) {
|
||||
const context = await getClaimContext(token)
|
||||
assertLewanClaimTask(context.task)
|
||||
|
||||
@@ -240,6 +240,10 @@ export type AffiliateDashFlow = {
|
||||
gameAccount: string
|
||||
expectedGameAccount: string
|
||||
bindMismatch: boolean
|
||||
/** 是否已在 affiliate-dash 侧完成绑定(bind-result.bound) */
|
||||
bound: boolean
|
||||
boundAccount: string
|
||||
gameChannel: string
|
||||
submitStatus: string
|
||||
consumeStatus: string
|
||||
lastSyncedAt: unknown
|
||||
@@ -270,6 +274,9 @@ export function normalizeAffiliateDashFlow(value: unknown): AffiliateDashFlow {
|
||||
gameAccount: String(source.gameAccount || '').trim(),
|
||||
expectedGameAccount: String(source.expectedGameAccount || '').trim(),
|
||||
bindMismatch: Boolean(source.bindMismatch),
|
||||
bound: Boolean(source.bound),
|
||||
boundAccount: String(source.boundAccount || '').trim(),
|
||||
gameChannel: String(source.gameChannel || '').trim(),
|
||||
submitStatus: String(source.submitStatus || '').trim(),
|
||||
consumeStatus: String(source.consumeStatus || 'pending').trim(),
|
||||
lastSyncedAt: source.lastSyncedAt || null,
|
||||
|
||||
@@ -0,0 +1,161 @@
|
||||
import { CheckCircleOutlined, LinkOutlined } from '@ant-design/icons'
|
||||
import { Alert, Button, Card, Typography } from 'antd'
|
||||
import type {
|
||||
ClaimAffiliateDashFlowInfo,
|
||||
ClaimOrderInfo,
|
||||
ClaimProductInfo,
|
||||
} from '@/types/claim'
|
||||
import type { ClaimSnapshot } from './claim-snapshot'
|
||||
import { InfoTile, UidEditRow } from './claim-shared'
|
||||
|
||||
export function AffiliateDashClaimPanel({
|
||||
affiliateDash,
|
||||
expectedUid,
|
||||
order,
|
||||
product,
|
||||
taskLastError,
|
||||
qrCodeDataUrl,
|
||||
submittingUid,
|
||||
submittingSubmit,
|
||||
uidInput,
|
||||
onUidChange,
|
||||
onResubmitUid,
|
||||
onSubmit,
|
||||
onOpenBindUrl,
|
||||
}: {
|
||||
affiliateDash: ClaimAffiliateDashFlowInfo
|
||||
expectedUid: string
|
||||
order: ClaimOrderInfo | null
|
||||
product: ClaimProductInfo | null
|
||||
taskLastError?: string
|
||||
qrCodeDataUrl: string
|
||||
submittingUid: boolean
|
||||
submittingSubmit: boolean
|
||||
uidInput: string
|
||||
onUidChange: (value: string) => void
|
||||
onResubmitUid: () => void
|
||||
onSubmit: () => void
|
||||
onOpenBindUrl: () => void
|
||||
}) {
|
||||
const bindReady = Boolean(affiliateDash.bindUrl || affiliateDash.qrUrl)
|
||||
const bound = Boolean(affiliateDash.bound)
|
||||
const bindMismatch = Boolean(affiliateDash.bindMismatch)
|
||||
|
||||
return (
|
||||
<Card className="claim-content-card">
|
||||
<div className="claim-feifei-main">
|
||||
<span className="claim-feifei-label">affiliate-dash</span>
|
||||
<Typography.Title level={2}>
|
||||
{bound ? '第 3 步:确认并提交发货' : '第 2 步:绑定领取账号'}
|
||||
</Typography.Title>
|
||||
<p>{bound ? '账号绑定成功,确认无误后提交发货' : '打开绑定链接完成绑定,系统将自动确认'}</p>
|
||||
</div>
|
||||
|
||||
<UidEditRow
|
||||
uidInput={uidInput}
|
||||
submitting={submittingUid}
|
||||
onChange={onUidChange}
|
||||
onSubmit={onResubmitUid}
|
||||
/>
|
||||
|
||||
<div className="claim-info-grid">
|
||||
<InfoTile label="填写 UID" value={expectedUid || '-'} />
|
||||
<InfoTile label="订单号" value={order?.platformOrderId || '-'} />
|
||||
<InfoTile label="affiliate-dash 单号" value={affiliateDash.orderNo || '-'} />
|
||||
<InfoTile label="领取商品" value={product?.title || affiliateDash.productName || '-'} />
|
||||
<InfoTile label="金额" value={`${affiliateDash.amount} ${affiliateDash.currency}`.trim() || '-'} />
|
||||
</div>
|
||||
|
||||
{taskLastError ? (
|
||||
<Alert
|
||||
type="warning"
|
||||
showIcon
|
||||
message="上次操作遇到问题"
|
||||
description={taskLastError}
|
||||
style={{ marginBottom: 16 }}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{bindMismatch ? (
|
||||
<Alert
|
||||
type="warning"
|
||||
showIcon
|
||||
message="绑定账号与填写 UID 不一致"
|
||||
description={`已绑定 ${affiliateDash.boundAccount || '-'},请确认绑定的正是你填写的 UID(${expectedUid || '-'})`}
|
||||
style={{ marginBottom: 16 }}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{bound ? (
|
||||
<Alert
|
||||
type="success"
|
||||
showIcon
|
||||
icon={<CheckCircleOutlined />}
|
||||
message={`已绑定账号:${affiliateDash.boundAccount || affiliateDash.gameAccount || '-'}`}
|
||||
description={affiliateDash.gameChannel ? `渠道:${affiliateDash.gameChannel}` : undefined}
|
||||
style={{ marginBottom: 16 }}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{!bound ? (
|
||||
<div className="claim-bind-area" style={{ marginBottom: 16 }}>
|
||||
{qrCodeDataUrl ? (
|
||||
<img
|
||||
src={qrCodeDataUrl}
|
||||
alt="绑定二维码"
|
||||
style={{ width: 180, height: 180, borderRadius: 8 }}
|
||||
/>
|
||||
) : null}
|
||||
{bindReady ? (
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<LinkOutlined />}
|
||||
onClick={onOpenBindUrl}
|
||||
style={{ marginLeft: 16 }}
|
||||
>
|
||||
打开绑定链接
|
||||
</Button>
|
||||
) : (
|
||||
<Typography.Text type="secondary">绑定链接生成中,请稍候自动刷新…</Typography.Text>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<Button
|
||||
type="primary"
|
||||
size="large"
|
||||
loading={submittingSubmit}
|
||||
disabled={submittingSubmit}
|
||||
onClick={onSubmit}
|
||||
style={{ marginBottom: 16 }}
|
||||
>
|
||||
提交发货
|
||||
</Button>
|
||||
)}
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
export function AffiliateDashResultStep({
|
||||
snapshot,
|
||||
}: {
|
||||
snapshot: ClaimSnapshot
|
||||
}) {
|
||||
const affiliateDash = snapshot.affiliateDash
|
||||
|
||||
return (
|
||||
<Card className="claim-content-card claim-result-card">
|
||||
<div className="claim-result-title">{snapshot.resultTitle}</div>
|
||||
<div className="claim-result-description">{snapshot.resultDescription}</div>
|
||||
|
||||
{affiliateDash ? (
|
||||
<div className="claim-info-grid" style={{ marginTop: 24 }}>
|
||||
<InfoTile label="领取商品" value={affiliateDash.productName || '-'} />
|
||||
<InfoTile label="affiliate-dash 单号" value={affiliateDash.orderNo || '-'} />
|
||||
<InfoTile label="订单状态" value={affiliateDash.orderStatus || '-'} />
|
||||
<InfoTile label="发货状态" value={affiliateDash.submitStatus || '-'} />
|
||||
<InfoTile label="金额" value={`${affiliateDash.amount} ${affiliateDash.currency}`.trim() || '-'} />
|
||||
</div>
|
||||
) : null}
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
@@ -10,10 +10,12 @@ import {
|
||||
fetchClaimDetail,
|
||||
rebindKuaishouCloudClaimRole,
|
||||
redeemKuaishouCloudClaim,
|
||||
submitAffiliateDashClaim,
|
||||
submitClaimUid,
|
||||
} from '@/services/claim'
|
||||
import type { ClaimDetailData } from '@/types/claim'
|
||||
|
||||
import { AffiliateDashClaimPanel, AffiliateDashResultStep } from './ClaimAffiliateDashSteps'
|
||||
import { FeifeiClaimPanel, FeifeiResultStep } from './ClaimFeifeiSteps'
|
||||
import { ClaimHeaderCard } from './ClaimHeaderCard'
|
||||
import { ClaimResultStep, KuaishouCloudClaimSteps } from './ClaimLewanSteps'
|
||||
@@ -32,6 +34,7 @@ export default function ClaimPage() {
|
||||
const [confirmingRole, setConfirmingRole] = useState(false)
|
||||
const [rebindingRole, setRebindingRole] = useState(false)
|
||||
const [redeeming, setRedeeming] = useState(false)
|
||||
const [submittingSubmit, setSubmittingSubmit] = useState(false)
|
||||
const [errorMessage, setErrorMessage] = useState('')
|
||||
const [detail, setDetail] = useState<ClaimDetailData | null>(null)
|
||||
const [qrCodeDataUrl, setQrCodeDataUrl] = useState('')
|
||||
@@ -81,7 +84,11 @@ export default function ClaimPage() {
|
||||
setUidInput(expectedUid)
|
||||
}
|
||||
await generateQRCode(
|
||||
String(nextDetail.kuaishouCloudFulfillment?.binding.bindUrl || '').trim(),
|
||||
String(
|
||||
nextDetail.kuaishouCloudFulfillment?.binding.bindUrl ||
|
||||
nextDetail.affiliateDash?.bindUrl ||
|
||||
'',
|
||||
).trim(),
|
||||
)
|
||||
},
|
||||
[generateQRCode],
|
||||
@@ -172,6 +179,28 @@ export default function ClaimPage() {
|
||||
}
|
||||
}
|
||||
|
||||
async function handleSubmitAffiliateDash() {
|
||||
const affiliateDash = snapshot.affiliateDash
|
||||
const uid = uidInput.trim() || affiliateDash?.gameAccount || ''
|
||||
const bindUuid = affiliateDash?.bindUuid || ''
|
||||
|
||||
if (!uid || !bindUuid) {
|
||||
showError('绑定尚未完成,请先绑定账号')
|
||||
return
|
||||
}
|
||||
|
||||
setSubmittingSubmit(true)
|
||||
try {
|
||||
const response = await submitAffiliateDashClaim(token, { gameAccount: uid, bindUuid })
|
||||
await applyDetail(response.data)
|
||||
showSuccess('发货已提交')
|
||||
} catch (error) {
|
||||
showError(error instanceof Error ? error.message : '提交发货失败')
|
||||
} finally {
|
||||
setSubmittingSubmit(false)
|
||||
}
|
||||
}
|
||||
|
||||
async function refreshRole() {
|
||||
setRefreshingRole(true)
|
||||
try {
|
||||
@@ -263,7 +292,9 @@ export default function ClaimPage() {
|
||||
}
|
||||
|
||||
function openBindUrl(useCurrentPage = false) {
|
||||
const bindUrl = String(snapshot.flow?.binding.bindUrl || '').trim()
|
||||
const bindUrl = String(
|
||||
snapshot.flow?.binding.bindUrl || snapshot.affiliateDash?.bindUrl || '',
|
||||
).trim()
|
||||
if (!bindUrl) {
|
||||
showError('绑定链接还没准备好,请稍后刷新')
|
||||
return
|
||||
@@ -337,9 +368,27 @@ export default function ClaimPage() {
|
||||
) : snapshot.hasRedeemResult ? (
|
||||
snapshot.isFeifeiFlow ? (
|
||||
<FeifeiResultStep snapshot={snapshot} />
|
||||
) : snapshot.isAffiliateDashFlow ? (
|
||||
<AffiliateDashResultStep snapshot={snapshot} />
|
||||
) : (
|
||||
<ClaimResultStep snapshot={snapshot} />
|
||||
)
|
||||
) : snapshot.isAffiliateDashFlow && snapshot.affiliateDash ? (
|
||||
<AffiliateDashClaimPanel
|
||||
affiliateDash={snapshot.affiliateDash}
|
||||
expectedUid={snapshot.expectedUid}
|
||||
order={snapshot.order}
|
||||
product={snapshot.product}
|
||||
taskLastError={snapshot.task?.lastError}
|
||||
qrCodeDataUrl={qrCodeDataUrl}
|
||||
submittingUid={submittingUid}
|
||||
submittingSubmit={submittingSubmit}
|
||||
uidInput={uidInput}
|
||||
onUidChange={setUidInput}
|
||||
onResubmitUid={() => void handleSubmitUid()}
|
||||
onSubmit={() => void handleSubmitAffiliateDash()}
|
||||
onOpenBindUrl={openBindUrl}
|
||||
/>
|
||||
) : snapshot.isFeifeiFlow && snapshot.feifei ? (
|
||||
<FeifeiClaimPanel
|
||||
feifei={snapshot.feifei}
|
||||
|
||||
@@ -65,6 +65,13 @@ export function resolveNextPollDelayMs(
|
||||
return FEIFEI_POLL_MS
|
||||
}
|
||||
|
||||
if (nextSnapshot.isAffiliateDashFlow) {
|
||||
if (!nextSnapshot.affiliateDash?.bound) {
|
||||
return BINDING_PREPARE_POLL_MS
|
||||
}
|
||||
return FEIFEI_POLL_MS
|
||||
}
|
||||
|
||||
if (nextSnapshot.currentStep === 2) {
|
||||
if (!nextSnapshot.isBindingPrepared) {
|
||||
return BINDING_PREPARE_POLL_MS
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
normalizeTaskStatus,
|
||||
} from '@/domain/task-status'
|
||||
import type {
|
||||
ClaimAffiliateDashFlowInfo,
|
||||
ClaimDetailData,
|
||||
ClaimKuaishouCloudFlowInfo,
|
||||
ClaimKuaishouFeifeiFlowInfo,
|
||||
@@ -20,6 +21,7 @@ export function createClaimSnapshot(
|
||||
) {
|
||||
const flow = detail?.kuaishouCloudFulfillment || null
|
||||
const feifei = detail?.kuaishouFeifei || null
|
||||
const affiliateDash = detail?.affiliateDash || null
|
||||
const order = detail?.order || null
|
||||
const orderItem = detail?.orderItem || null
|
||||
const product = detail?.product || null
|
||||
@@ -31,6 +33,7 @@ export function createClaimSnapshot(
|
||||
const roleName = flow?.role.name || flow?.binding.roleName || ''
|
||||
const roleId = flow?.role.rid || flow?.binding.roleId || ''
|
||||
const isFeifeiFlow = detail?.flowType === 'kuaishou_feifei'
|
||||
const isAffiliateDashFlow = detail?.flowType === 'affiliate_dash'
|
||||
const isBindUrlExpired = isDateExpired(flow?.binding.bindExpiresAt || '')
|
||||
const isBindingPrepared =
|
||||
flow?.binding.prepareStatus === 'ready' &&
|
||||
@@ -54,25 +57,33 @@ export function createClaimSnapshot(
|
||||
const isRedeemFailed =
|
||||
normalizedStatus === TASK_STATUS.MANUAL_REVIEW ||
|
||||
normalizedStatus === TASK_STATUS.FAILED ||
|
||||
normalizedStatus === TASK_STATUS.CLOSED ||
|
||||
String(flow?.dispatch.status || '').trim() === 'failed' ||
|
||||
(isFeifeiFlow && [40, 50].includes(Number(feifei?.rechargeStatus || 0)))
|
||||
(isFeifeiFlow && [40, 50].includes(Number(feifei?.rechargeStatus || 0))) ||
|
||||
(isAffiliateDashFlow &&
|
||||
['ship_failed', 'cancelled'].includes(String(affiliateDash?.orderStatus || '').trim()))
|
||||
const isCompleted =
|
||||
isKuaishouCloudCompletedStatus(task?.status) ||
|
||||
(isFeifeiFlow && Number(feifei?.rechargeStatus || 0) === 30)
|
||||
const hasRedeemResult =
|
||||
isDispatched ||
|
||||
hasKuaishouCloudRedeemResultStatus(task?.status) ||
|
||||
(isFeifeiFlow && [30, 40, 50, 60].includes(Number(feifei?.rechargeStatus || 0)))
|
||||
(isFeifeiFlow && [30, 40, 50, 60].includes(Number(feifei?.rechargeStatus || 0))) ||
|
||||
(isAffiliateDashFlow &&
|
||||
['delivered', 'ship_failed', 'cancelled'].includes(String(affiliateDash?.orderStatus || '').trim()))
|
||||
const currentStep = resolveCurrentStep({
|
||||
hasExpectedUid,
|
||||
hasRedeemResult,
|
||||
isRoleConfirmed,
|
||||
isFeifeiFlow,
|
||||
isUidMatched,
|
||||
isAffiliateDashFlow,
|
||||
affiliateDash,
|
||||
})
|
||||
const progressText = resolveProgressText({
|
||||
feifei,
|
||||
isFeifeiFlow,
|
||||
isAffiliateDashFlow,
|
||||
affiliateDash,
|
||||
hasExpectedUid,
|
||||
expectedUid,
|
||||
hasRedeemResult,
|
||||
@@ -80,11 +91,18 @@ export function createClaimSnapshot(
|
||||
isBindingPrepared,
|
||||
isUidMatched,
|
||||
})
|
||||
const resultTitle = resolveResultTitle({ isRedeemFailed, isCompleted, isDispatched })
|
||||
const resultTitle = resolveResultTitle({
|
||||
isRedeemFailed,
|
||||
isCompleted,
|
||||
isDispatched,
|
||||
isAffiliateDashFlow,
|
||||
affiliateDash,
|
||||
})
|
||||
const resultDescription = resolveResultDescription({
|
||||
detail,
|
||||
flow,
|
||||
feifei,
|
||||
affiliateDash,
|
||||
task,
|
||||
isRedeemFailed,
|
||||
isCompleted,
|
||||
@@ -98,6 +116,7 @@ export function createClaimSnapshot(
|
||||
return {
|
||||
flow,
|
||||
feifei,
|
||||
affiliateDash,
|
||||
order,
|
||||
orderItem,
|
||||
product,
|
||||
@@ -108,6 +127,7 @@ export function createClaimSnapshot(
|
||||
roleName,
|
||||
roleId,
|
||||
isFeifeiFlow,
|
||||
isAffiliateDashFlow,
|
||||
isBindUrlExpired,
|
||||
isBindingPrepared,
|
||||
isBindingPreparing,
|
||||
@@ -161,6 +181,8 @@ function resolveCurrentStep(options: {
|
||||
hasRedeemResult: boolean
|
||||
isRoleConfirmed: boolean
|
||||
isFeifeiFlow: boolean
|
||||
isAffiliateDashFlow: boolean
|
||||
affiliateDash: ClaimAffiliateDashFlowInfo | null
|
||||
isUidMatched?: boolean
|
||||
}) {
|
||||
if (!options.hasExpectedUid) {
|
||||
@@ -169,6 +191,12 @@ function resolveCurrentStep(options: {
|
||||
if (options.hasRedeemResult) {
|
||||
return 4
|
||||
}
|
||||
if (options.isAffiliateDashFlow) {
|
||||
if (!options.affiliateDash?.bound) {
|
||||
return 2
|
||||
}
|
||||
return 3
|
||||
}
|
||||
if (!options.isFeifeiFlow && options.isRoleConfirmed) {
|
||||
return 3
|
||||
}
|
||||
@@ -177,7 +205,9 @@ function resolveCurrentStep(options: {
|
||||
|
||||
function resolveProgressText(options: {
|
||||
feifei: ClaimKuaishouFeifeiFlowInfo | null
|
||||
affiliateDash: ClaimAffiliateDashFlowInfo | null
|
||||
isFeifeiFlow: boolean
|
||||
isAffiliateDashFlow: boolean
|
||||
hasExpectedUid: boolean
|
||||
expectedUid: string
|
||||
hasRedeemResult: boolean
|
||||
@@ -188,6 +218,17 @@ function resolveProgressText(options: {
|
||||
if (!options.hasExpectedUid) {
|
||||
return '请填写游戏编号'
|
||||
}
|
||||
if (options.isAffiliateDashFlow) {
|
||||
if (options.hasRedeemResult) {
|
||||
return '发货结果已生成'
|
||||
}
|
||||
if (!options.affiliateDash?.bound) {
|
||||
return options.affiliateDash?.bindUrl || options.affiliateDash?.qrUrl
|
||||
? '请扫码绑定账号'
|
||||
: '绑定链接生成中,请稍候'
|
||||
}
|
||||
return '已绑定,请提交发货'
|
||||
}
|
||||
if (options.isFeifeiFlow) {
|
||||
return options.feifei?.rechargeStatusLabel || `UID ${options.expectedUid},请打开领取链接`
|
||||
}
|
||||
@@ -210,9 +251,11 @@ function resolveResultTitle(options: {
|
||||
isRedeemFailed: boolean
|
||||
isCompleted: boolean
|
||||
isDispatched: boolean
|
||||
isAffiliateDashFlow: boolean
|
||||
affiliateDash: ClaimAffiliateDashFlowInfo | null
|
||||
}) {
|
||||
if (options.isRedeemFailed) {
|
||||
return '兑换遇到问题'
|
||||
return '发货遇到问题'
|
||||
}
|
||||
if (options.isCompleted) {
|
||||
return '兑换成功'
|
||||
@@ -220,6 +263,9 @@ function resolveResultTitle(options: {
|
||||
if (options.isDispatched) {
|
||||
return '兑换请求已提交'
|
||||
}
|
||||
if (options.isAffiliateDashFlow && options.affiliateDash?.orderStatus === 'delivered') {
|
||||
return '发货已完成'
|
||||
}
|
||||
return '结果已记录'
|
||||
}
|
||||
|
||||
@@ -227,6 +273,7 @@ function resolveResultDescription(options: {
|
||||
detail: ClaimDetailData | null
|
||||
flow: ClaimKuaishouCloudFlowInfo | null
|
||||
feifei: ClaimKuaishouFeifeiFlowInfo | null
|
||||
affiliateDash: ClaimAffiliateDashFlowInfo | null
|
||||
task: ClaimDetailData['task'] | null
|
||||
isRedeemFailed: boolean
|
||||
isCompleted: boolean
|
||||
@@ -236,16 +283,21 @@ function resolveResultDescription(options: {
|
||||
options.task?.lastError ||
|
||||
options.flow?.dispatch.errorMessage ||
|
||||
options.feifei?.rechargeResultMessage ||
|
||||
options.affiliateDash?.failureReason ||
|
||||
options.detail?.result?.resultMessage ||
|
||||
'',
|
||||
).trim()
|
||||
return message || '当前兑换流程需要客服处理,后续结果请以后台任务界面为准。'
|
||||
return message || '当前流程需要客服处理,后续结果请以后台任务界面为准。'
|
||||
}
|
||||
|
||||
if (options.isCompleted) {
|
||||
return '当前兑换流程已经完成。'
|
||||
}
|
||||
|
||||
if (options.affiliateDash) {
|
||||
return '你的发货请求已经提交。后续结果会由系统保存在后台任务界面。'
|
||||
}
|
||||
|
||||
return '你的兑换请求已经提交。后续结果会由系统保存在后台任务界面。'
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,13 @@ export function submitClaimUid(token: string, uid: string) {
|
||||
return apiPost<ClaimDetailData>(`/api/v1/claim/${token}/uid`, { uid })
|
||||
}
|
||||
|
||||
export function submitAffiliateDashClaim(
|
||||
token: string,
|
||||
payload: { gameAccount: string; bindUuid: string },
|
||||
) {
|
||||
return apiPost<ClaimDetailData>(`/api/v1/claim/${token}/affiliate-dash/submit`, payload)
|
||||
}
|
||||
|
||||
export function confirmKuaishouCloudClaimRole(token: string) {
|
||||
return apiPost<ClaimDetailData>(`/api/v1/claim/${token}/kuaishou-cloud/confirm-role`, {})
|
||||
}
|
||||
|
||||
@@ -162,10 +162,37 @@ export interface ClaimKuaishouFeifeiFlowInfo {
|
||||
lastSyncedAt: string | null
|
||||
}
|
||||
|
||||
export interface ClaimAffiliateDashFlowInfo {
|
||||
flowType: 'affiliate_dash'
|
||||
sku: string
|
||||
productName: string
|
||||
orderNo: string
|
||||
clientOrderNo: string
|
||||
orderStatus: string
|
||||
canShip: boolean
|
||||
cannotShipReason: string
|
||||
providerOrderNo: string
|
||||
failureReason: string
|
||||
amount: number
|
||||
currency: string
|
||||
bindUuid: string
|
||||
bindUrl: string
|
||||
qrUrl: string
|
||||
gameAccount: string
|
||||
expectedGameAccount: string
|
||||
bindMismatch: boolean
|
||||
bound: boolean
|
||||
boundAccount: string
|
||||
gameChannel: string
|
||||
submitStatus: string
|
||||
consumeStatus: string
|
||||
lastSyncedAt: string | null
|
||||
}
|
||||
|
||||
export interface ClaimDetailData {
|
||||
tokenStatus: ClaimTokenStatus
|
||||
claimUrl: string
|
||||
flowType: 'kuaishou_cloud' | 'kuaishou_ct_assisted' | 'kuaishou_feifei' | (string & {})
|
||||
flowType: 'kuaishou_cloud' | 'kuaishou_ct_assisted' | 'kuaishou_feifei' | 'affiliate_dash' | (string & {})
|
||||
claimIdentity: ClaimIdentityInfo | null
|
||||
task: ClaimTaskInfo
|
||||
order: ClaimOrderInfo
|
||||
@@ -174,5 +201,6 @@ export interface ClaimDetailData {
|
||||
session: unknown | null
|
||||
kuaishouCloudFulfillment: ClaimKuaishouCloudFlowInfo | null
|
||||
kuaishouFeifei: ClaimKuaishouFeifeiFlowInfo | null
|
||||
affiliateDash: ClaimAffiliateDashFlowInfo | null
|
||||
result: ClaimResultInfo | null
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user