删除咸鱼旧链路
This commit is contained in:
@@ -0,0 +1,248 @@
|
||||
import { findClaimTokenByToken, updateClaimToken } from '../../repositories/claim-token-repo.js'
|
||||
import { releaseReservedInventoryItem } from '../../repositories/inventory-repo.js'
|
||||
import { getOrderItemById } from '../../repositories/order-item-repo.js'
|
||||
import { getOrderById } from '../../repositories/order-repo.js'
|
||||
import { findTaskByClaimTokenId, updateTask } from '../../repositories/task-repo.js'
|
||||
import { createHttpError } from '../../utils/http.js'
|
||||
import { formatFenToAmount, normalizeFen } from '../../utils/money.js'
|
||||
import { parseTaskContext as parseTaskContextValue } from '../../utils/task-json.js'
|
||||
import { nowIso } from '../../utils/time.js'
|
||||
import { buildClaimUrl } from './claim-service.js'
|
||||
|
||||
export const CLAIM_TERMINAL_STATUSES = new Set(['expired', 'closed'])
|
||||
export const KUAISHOU_CLOUD_CLAIM_GUIDE_BASE_PATH = '/kuaishou-cloud-guide'
|
||||
|
||||
export async function getClaimContext(token: unknown) {
|
||||
const normalized = String(token || '').trim()
|
||||
|
||||
if (!normalized) {
|
||||
throw createHttpError('缺少领取 token', {
|
||||
statusCode: 400,
|
||||
errorCode: 'missing_claim_token',
|
||||
})
|
||||
}
|
||||
|
||||
const claimToken = await findClaimTokenByToken(normalized)
|
||||
|
||||
if (!claimToken) {
|
||||
throw createHttpError('领取链接无效或不存在', {
|
||||
statusCode: 404,
|
||||
errorCode: 'claim_token_not_found',
|
||||
})
|
||||
}
|
||||
|
||||
const task = await findTaskByClaimTokenId(claimToken.id)
|
||||
|
||||
if (!task) {
|
||||
throw createHttpError('领取任务不存在', {
|
||||
statusCode: 404,
|
||||
errorCode: 'claim_task_not_found',
|
||||
})
|
||||
}
|
||||
|
||||
if (claimToken.status !== 'active') {
|
||||
throw createHttpError('领取链接当前不可用', {
|
||||
statusCode: 410,
|
||||
errorCode: 'claim_token_inactive',
|
||||
})
|
||||
}
|
||||
|
||||
if (claimToken.expired_at && new Date(claimToken.expired_at).getTime() <= Date.now()) {
|
||||
const expiredContext = await expireClaimContext(claimToken, task)
|
||||
|
||||
throw createHttpError('领取链接已过期', {
|
||||
statusCode: 410,
|
||||
errorCode: 'claim_token_expired',
|
||||
context: expiredContext,
|
||||
})
|
||||
}
|
||||
|
||||
const [order, orderItem] = await Promise.all([
|
||||
getOrderById(task.order_id),
|
||||
getOrderItemById(task.order_item_id),
|
||||
])
|
||||
|
||||
if (!order || !orderItem) {
|
||||
throw createHttpError('领取任务关联订单不完整', {
|
||||
statusCode: 500,
|
||||
errorCode: 'claim_order_incomplete',
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
claimToken,
|
||||
task,
|
||||
order,
|
||||
orderItem,
|
||||
}
|
||||
}
|
||||
|
||||
export function buildClaimDetailPayload({ claimToken, task, order, orderItem }) {
|
||||
const kuaishouCloudFulfillment = mapClaimKuaishouCloudFulfillment(task, order)
|
||||
|
||||
return {
|
||||
tokenStatus: claimToken.status,
|
||||
claimUrl: buildClaimUrl(claimToken.token),
|
||||
flowType: 'kuaishou_cloud',
|
||||
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,
|
||||
browserSessionId: task.browser_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: orderItem.sku_name,
|
||||
quantity: orderItem.quantity,
|
||||
},
|
||||
session: null,
|
||||
kuaishouCloudFulfillment,
|
||||
result: task.redeemed_at
|
||||
? {
|
||||
resultCode: String(task.result_code || ''),
|
||||
resultMessage: String(task.result_message || ''),
|
||||
screenshotReady: false,
|
||||
screenshotUrl: '',
|
||||
}
|
||||
: null,
|
||||
}
|
||||
}
|
||||
|
||||
export function mapClaimKuaishouCloudFulfillment(task, order) {
|
||||
if (String(task?.executor_key || '').trim() !== 'kuaishou_ct_assisted') {
|
||||
return null
|
||||
}
|
||||
|
||||
const source = parseTaskContext(task).kuaishouCloudFulfillment
|
||||
if (!source || typeof source !== 'object') {
|
||||
return null
|
||||
}
|
||||
|
||||
const binding = source.binding && typeof source.binding === 'object' ? source.binding : {}
|
||||
const role = source.role && typeof source.role === 'object' ? source.role : {}
|
||||
const purchase = source.purchase && typeof source.purchase === 'object' ? source.purchase : {}
|
||||
const ticket = source.ticket && typeof source.ticket === 'object' ? source.ticket : {}
|
||||
const dispatch = source.dispatch && typeof source.dispatch === 'object' ? source.dispatch : {}
|
||||
const returnNumber = source.returnNumber && typeof source.returnNumber === 'object' ? source.returnNumber : {}
|
||||
const consume = source.consume && typeof source.consume === 'object' ? source.consume : {}
|
||||
|
||||
return {
|
||||
flowType: 'kuaishou_cloud',
|
||||
shopId: String(order?.shop_id || '').trim(),
|
||||
shopName: String(order?.shop_name || '').trim(),
|
||||
guideImages: [
|
||||
`${KUAISHOU_CLOUD_CLAIM_GUIDE_BASE_PATH}/1.png`,
|
||||
`${KUAISHOU_CLOUD_CLAIM_GUIDE_BASE_PATH}/2.png`,
|
||||
`${KUAISHOU_CLOUD_CLAIM_GUIDE_BASE_PATH}/3.png`,
|
||||
],
|
||||
ticket: {
|
||||
code: String(ticket.code || '').trim(),
|
||||
status: String(ticket.status || 'pending').trim() || 'pending',
|
||||
capturedAt: ticket.capturedAt || null,
|
||||
verifiedAt: ticket.verifiedAt || null,
|
||||
goodsTitle: String(ticket.goodsTitle || '').trim(),
|
||||
leftCount: Number(ticket.leftCount || 0) || 0,
|
||||
},
|
||||
binding: {
|
||||
prepareStatus: String(binding.prepareStatus || 'pending').trim() || 'pending',
|
||||
cloudSourceKey: String(binding.cloudSourceKey || '').trim(),
|
||||
skuId: Number(binding.skuId || 0) || 0,
|
||||
skuName: String(binding.skuName || '').trim(),
|
||||
vnKey: String(binding.vnKey || '').trim(),
|
||||
vnId: Number(binding.vnId || 0) || 0,
|
||||
bindUrl: String(binding.bindUrl || '').trim(),
|
||||
bindPreparedAt: binding.bindPreparedAt || null,
|
||||
bindExpiresAt: binding.bindExpiresAt || null,
|
||||
bindProbeAt: binding.bindProbeAt || null,
|
||||
bindProbeStatus: String(binding.bindProbeStatus || '').trim(),
|
||||
bindProbeMessage: String(binding.bindProbeMessage || '').trim(),
|
||||
vnPhone: String(binding.vnPhone || '').trim(),
|
||||
roleName: String(binding.roleName || '').trim(),
|
||||
roleId: String(binding.roleId || '').trim(),
|
||||
},
|
||||
role: {
|
||||
status: String(role.status || 'pending').trim() || 'pending',
|
||||
name: String(role.name || binding.roleName || '').trim(),
|
||||
rid: String(role.rid || binding.roleId || '').trim(),
|
||||
refreshedAt: role.refreshedAt || null,
|
||||
errorMessage: String(role.errorMessage || '').trim(),
|
||||
},
|
||||
purchase: {
|
||||
autoBuyEnabled: purchase.autoBuyEnabled !== false,
|
||||
minAssetReserve: Number(purchase.minAssetReserve || 0) || 0,
|
||||
usedKnapsack: purchase.usedKnapsack === true,
|
||||
purchaseTriggered: purchase.purchaseTriggered === true,
|
||||
assetBefore: Number(purchase.assetBefore || 0) || 0,
|
||||
assetAfter: Number(purchase.assetAfter || 0) || 0,
|
||||
purchaseAt: purchase.purchaseAt || null,
|
||||
},
|
||||
dispatch: {
|
||||
status: String(dispatch.status || 'pending').trim() || 'pending',
|
||||
dispatchAt: dispatch.dispatchAt || null,
|
||||
note: String(dispatch.note || '').trim(),
|
||||
},
|
||||
returnNumber: {
|
||||
status: String(returnNumber.status || 'pending').trim() || 'pending',
|
||||
returnedAt: returnNumber.returnedAt || null,
|
||||
},
|
||||
consume: {
|
||||
status: String(consume.status || 'pending').trim() || 'pending',
|
||||
shopId: String(consume.shopId || '').trim(),
|
||||
shopName: String(consume.shopName || '').trim(),
|
||||
consumedAt: consume.consumedAt || null,
|
||||
errorMessage: String(consume.errorMessage || '').trim(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
function parseTaskContext(task) {
|
||||
return parseTaskContextValue(task)
|
||||
}
|
||||
|
||||
async function expireClaimContext(claimToken, task) {
|
||||
const now = nowIso()
|
||||
const nextClaimToken = await updateClaimToken(claimToken.id, {
|
||||
status: 'expired',
|
||||
updated_at: now,
|
||||
})
|
||||
|
||||
let nextTask = task
|
||||
|
||||
if (!CLAIM_TERMINAL_STATUSES.has(String(task.task_status || '')) && task.task_status !== 'redeemed') {
|
||||
if (task.primary_inventory_item_id) {
|
||||
await releaseReservedInventoryItem(task.primary_inventory_item_id, now)
|
||||
}
|
||||
|
||||
nextTask = await updateTask(task.id, {
|
||||
task_status: 'expired',
|
||||
inventory_status: 'pending',
|
||||
user_action_status: 'expired',
|
||||
last_error: '领取链接已过期,预占库存项已释放',
|
||||
updated_at: now,
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
claimToken: nextClaimToken,
|
||||
task: nextTask,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user