抖音订单并非纯自动发货:内部 SKU 命中 AFFILIATE_DASH_SKU_MAPPING 才走自动通道, 未命中显式回落人工,且核销依赖买家自己到 /redeem 输订单号。此前核销侧在后台完全 不可见:自动核销是 void .catch(warn) 只落日志,cancelVerify 出网方法已就绪但没有入口。 照快手电子凭证的骨架做一套抖音版(两张表两套状态机,不复用): - 券码状态、平台侧状态、同步错误全部可视化,支持只看同步失败 - 核销/冲正/重新同步/平台对账/重发券码/作废,每个写操作都带审计 - 券码列显示「核销实际会用的券码」(platform_cert_no 优先),平台改过名时额外标注 - 冲正先打平台 cancelVerify 再回退本地,避免本地与平台状态漂移
189 lines
6.1 KiB
TypeScript
189 lines
6.1 KiB
TypeScript
import { Router } from 'express'
|
||
|
||
import {
|
||
cancelAdminDouyinMashidaCertificateVerify,
|
||
checkAdminDouyinMashidaCertificate,
|
||
fetchAdminDouyinMashidaOrderDetail,
|
||
invalidateAdminDouyinMashidaCertificate,
|
||
listAdminDouyinMashidaCertificates,
|
||
resendAdminDouyinMashidaCertificate,
|
||
syncAdminDouyinMashidaCertificate,
|
||
verifyAdminDouyinMashidaCertificate,
|
||
} from '../../services/admin/douyin-mashida-certificate-service.js'
|
||
import { createJsonHandler, requireAdminRoles } from './session.js'
|
||
import type {
|
||
AdminDouyinMashidaCertificateActionRouteBody,
|
||
AdminDouyinMashidaCertificateListRouteQuery,
|
||
} from '../../types/admin/route-inputs.js'
|
||
|
||
/**
|
||
* 后台「抖音券核销」:与 `admin/kuaishou-industry.ts` 同型的运营工具路由,
|
||
* 操作对象换成我们自发的兑换券(`redeem_certificates`,provider=douyin_mashida)。
|
||
*
|
||
* 这里每个写操作都带审计:**<|hy_place▁holder▁no▁813|>码/核销/冲正会直接改变平台侧资金相关状态**,
|
||
* 没有留痕等于出事没法追溯。
|
||
*/
|
||
const router = Router()
|
||
|
||
const MANAGE_ROLES = ['admin', 'operator', 'support']
|
||
|
||
router.get(
|
||
'/douyin-mashida/certificates',
|
||
createJsonHandler(
|
||
(req) =>
|
||
listAdminDouyinMashidaCertificates(req.query as AdminDouyinMashidaCertificateListRouteQuery),
|
||
{
|
||
successMessage: 'ok',
|
||
errorMessage: '查询抖音兑换券失败',
|
||
scope: '[admin/douyin-mashida/certificates]',
|
||
},
|
||
),
|
||
)
|
||
|
||
router.post(
|
||
'/douyin-mashida/certificates/verify',
|
||
requireAdminRoles(MANAGE_ROLES),
|
||
createJsonHandler(
|
||
(req) =>
|
||
verifyAdminDouyinMashidaCertificate(req.body as AdminDouyinMashidaCertificateActionRouteBody),
|
||
{
|
||
successMessage: '券码已核销',
|
||
errorMessage: '手动核销券码失败',
|
||
scope: '[admin/douyin-mashida/certificates/verify]',
|
||
audit: (req, data) =>
|
||
buildCertificateAudit('douyin_mashida_certificate_verify', req.body, data),
|
||
},
|
||
),
|
||
)
|
||
|
||
router.post(
|
||
'/douyin-mashida/certificates/cancel-verify',
|
||
requireAdminRoles(MANAGE_ROLES),
|
||
createJsonHandler(
|
||
(req) =>
|
||
cancelAdminDouyinMashidaCertificateVerify(
|
||
req.body as AdminDouyinMashidaCertificateActionRouteBody,
|
||
),
|
||
{
|
||
successMessage: '券码已冲正',
|
||
errorMessage: '券码冲正失败',
|
||
scope: '[admin/douyin-mashida/certificates/cancel-verify]',
|
||
audit: (req, data) =>
|
||
buildCertificateAudit('douyin_mashida_certificate_cancel_verify', req.body, data),
|
||
},
|
||
),
|
||
)
|
||
|
||
router.post(
|
||
'/douyin-mashida/certificates/sync',
|
||
requireAdminRoles(MANAGE_ROLES),
|
||
createJsonHandler(
|
||
(req) =>
|
||
syncAdminDouyinMashidaCertificate(req.body as AdminDouyinMashidaCertificateActionRouteBody),
|
||
{
|
||
successMessage: '券码已重新同步',
|
||
errorMessage: '重新同步券码失败',
|
||
scope: '[admin/douyin-mashida/certificates/sync]',
|
||
audit: (req, data) =>
|
||
buildCertificateAudit('douyin_mashida_certificate_sync', req.body, data),
|
||
},
|
||
),
|
||
)
|
||
|
||
router.post(
|
||
'/douyin-mashida/certificates/check',
|
||
requireAdminRoles(MANAGE_ROLES),
|
||
createJsonHandler(
|
||
(req) =>
|
||
checkAdminDouyinMashidaCertificate(req.body as AdminDouyinMashidaCertificateActionRouteBody),
|
||
{
|
||
successMessage: '平台券码已查询',
|
||
errorMessage: '查询平台券码失败',
|
||
scope: '[admin/douyin-mashida/certificates/check]',
|
||
audit: (req, data) =>
|
||
buildCertificateAudit('douyin_mashida_certificate_check', req.body, data),
|
||
},
|
||
),
|
||
)
|
||
|
||
router.post(
|
||
'/douyin-mashida/certificates/resend',
|
||
requireAdminRoles(MANAGE_ROLES),
|
||
createJsonHandler(
|
||
(req) =>
|
||
resendAdminDouyinMashidaCertificate(req.body as AdminDouyinMashidaCertificateActionRouteBody),
|
||
{
|
||
successMessage: '券码已重发',
|
||
errorMessage: '重发券码失败',
|
||
scope: '[admin/douyin-mashida/certificates/resend]',
|
||
audit: (req, data) =>
|
||
buildCertificateAudit('douyin_mashida_certificate_resend', req.body, data),
|
||
},
|
||
),
|
||
)
|
||
|
||
router.post(
|
||
'/douyin-mashida/certificates/invalidate',
|
||
requireAdminRoles(['admin', 'operator']),
|
||
createJsonHandler(
|
||
(req) =>
|
||
invalidateAdminDouyinMashidaCertificate(
|
||
req.body as AdminDouyinMashidaCertificateActionRouteBody,
|
||
),
|
||
{
|
||
successMessage: '券码已作废',
|
||
errorMessage: '作废券码失败',
|
||
scope: '[admin/douyin-mashida/certificates/invalidate]',
|
||
audit: (req, data) =>
|
||
buildCertificateAudit('douyin_mashida_certificate_invalidate', req.body, data),
|
||
},
|
||
),
|
||
)
|
||
|
||
router.post(
|
||
'/douyin-mashida/order-detail',
|
||
requireAdminRoles(MANAGE_ROLES),
|
||
createJsonHandler(
|
||
(req) =>
|
||
fetchAdminDouyinMashidaOrderDetail(req.body as AdminDouyinMashidaCertificateActionRouteBody),
|
||
{
|
||
successMessage: '平台订单详情已查询',
|
||
errorMessage: '查询平台订单详情失败',
|
||
scope: '[admin/douyin-mashida/order-detail]',
|
||
audit: (req, data) => buildCertificateAudit('douyin_mashida_order_detail', req.body, data),
|
||
},
|
||
),
|
||
)
|
||
|
||
/** 审计只记「操作了哪张券 + 结果摘要」,不整包落 rawPayload(里面有商品快照)。 */
|
||
function buildCertificateAudit(action: string, body: unknown, data: unknown) {
|
||
const payload = body && typeof body === 'object' ? (body as Record<string, unknown>) : {}
|
||
const result = data && typeof data === 'object' ? (data as Record<string, unknown>) : {}
|
||
const certificate = result.certificate as Record<string, unknown> | undefined
|
||
|
||
return {
|
||
action,
|
||
targetType: 'douyin_mashida_certificate',
|
||
targetId: resolveAuditTargetId(payload, certificate),
|
||
data: {
|
||
certNo: String(certificate?.certNo || payload.certNo || '').trim(),
|
||
orderId: String(
|
||
certificate?.orderId || payload.orderId || payload.platformOrderId || '',
|
||
).trim(),
|
||
reason: String(payload.reason || '').trim(),
|
||
success: result.success !== false,
|
||
},
|
||
}
|
||
}
|
||
|
||
function resolveAuditTargetId(
|
||
payload: Record<string, unknown>,
|
||
certificate: Record<string, unknown> | undefined,
|
||
): string {
|
||
return String(
|
||
certificate?.certNo || payload.certNo || payload.orderId || payload.platformOrderId || '-',
|
||
)
|
||
}
|
||
|
||
export default router
|