feat: Rock 兑换支持上游异步 pending 并记录发货全链路报文
问题:上游 0666.pro 兑换返回 HTTP 200 + status=pending(官方处理中), 旧逻辑仅凭「没有 error 字段」就判定成功并写 redeemed 终态,导致本地显示 已发货而上游仍是 bound,且 redeemed 为硬终态无法自愈。 后端: - RedeemSkin 改为三态判定(success / pending / failed),不再无 error 即成功 - 新增 redeeming「发货处理中」状态;pending 不写 redeemed_at/fulfill_id - 成功落库前二次 refresh 确认上游 redeemed 或 redeemed_at 才置终态 - 新增确认 worker:20s 轮询、30s→5m 指数退避、110001 全局冷却 60s、 15 分钟未确认仅告警不回退,确认后同步订单为 delivered 并触发回调 - pending 期间禁止重复兑换,订单保持 delivering,不下发发货成功回调 - 新增 rock_delivery_logs 表与记录器:入站参数、内部状态、上游请求/响应 原文全部留档,保留 180 天 - 分配/绑定校验/刷新/导入/删除/回收站/统计/客服查询全部适配 redeeming - 迁移 026:redeeming 跟踪字段、部分索引与报文日志表 前端: - Token 列表新增「发货处理中」筛选项、标签与超时提示 - 删除/回收站纳入 redeeming 危险状态 - 历史抽屉拆为「状态历史 / 发货报文」双页签,按 trace 展示报文详情 - 买家发货页在 pending 时提示官方处理中,无需重复提交 测试:新增 pending 不落终态、异常响应不误判、重复提交不重复兑换、 确认后同步订单、保持 pending 并退避 5 个用例;go build/vet/test 与 前端构建均通过。
This commit is contained in:
@@ -597,6 +597,7 @@ export interface RockStats {
|
||||
available_stock: number;
|
||||
using_stock: number;
|
||||
bound_stock: number;
|
||||
redeeming_stock?: number;
|
||||
redeemed_stock: number;
|
||||
mismatch_stock: number;
|
||||
abnormal_stock: number;
|
||||
@@ -604,6 +605,7 @@ export interface RockStats {
|
||||
using_overdue_stock?: number;
|
||||
using_danger_stock?: number;
|
||||
bound_overdue_stock?: number;
|
||||
redeeming_timeout_stock?: number;
|
||||
auto_refresh_mismatch_enabled?: boolean;
|
||||
upstream_points: number;
|
||||
auto_delivery_enabled: boolean;
|
||||
@@ -672,6 +674,14 @@ export interface RockTokenItem {
|
||||
using_at?: string;
|
||||
bound_at?: string;
|
||||
redeemed_at?: string;
|
||||
// 上游异步兑奖(pending)跟踪字段
|
||||
redeem_requested_at?: string;
|
||||
redeem_checked_at?: string;
|
||||
redeem_attempts?: number;
|
||||
redeem_next_retry_at?: string;
|
||||
redeem_last_message?: string;
|
||||
redeem_last_error?: string;
|
||||
redeem_timeout_warned_at?: string;
|
||||
created_at: string;
|
||||
updated_at?: string;
|
||||
deleted_at?: string | null;
|
||||
@@ -816,6 +826,32 @@ export interface RockTokenStatusLog {
|
||||
created_at: string
|
||||
}
|
||||
|
||||
// 发货全链路报文日志:一次发货动作的每一步(入站 / 上游请求 / 上游响应 / 内部状态)。
|
||||
export interface RockDeliveryLog {
|
||||
id: number
|
||||
trace_id: string
|
||||
token_id: number
|
||||
upstream_token_id: string
|
||||
order_no: string
|
||||
step: string
|
||||
direction: 'inbound' | 'outbound' | 'internal' | string
|
||||
status: string
|
||||
http_method: string
|
||||
http_url: string
|
||||
http_status: number
|
||||
request_headers: string
|
||||
request_body: string
|
||||
response_headers: string
|
||||
response_body: string
|
||||
duration_ms: number
|
||||
error_message: string
|
||||
actor_user_id?: number | null
|
||||
actor_ip: string
|
||||
user_agent: string
|
||||
created_at: string
|
||||
finished_at?: string
|
||||
}
|
||||
|
||||
export interface RockStaleRefreshResult {
|
||||
scanned: number;
|
||||
fixed: number;
|
||||
@@ -983,6 +1019,10 @@ export const rockApi = {
|
||||
request
|
||||
.get(`/platform/rock/tokens/${id}/status-logs`)
|
||||
.then((r) => r.data.data as { list: RockTokenStatusLog[] }),
|
||||
listDeliveryLogs: (id: number) =>
|
||||
request
|
||||
.get(`/platform/rock/tokens/${id}/delivery-logs`)
|
||||
.then((r) => r.data.data as { list: RockDeliveryLog[] }),
|
||||
refreshTokenBinding: (id: number) =>
|
||||
request
|
||||
.post(`/platform/rock/tokens/${id}/refresh-binding`)
|
||||
|
||||
Reference in New Issue
Block a user