feat(douyin-mashida): 抖音码速达最小接入后端(消息建单/券码同步/兑换核销/退款作废)
- 签名:出网 md5(secret+Σ(名+值)+secret) 与推送 md5(secret+data+pub_time+topic+secret) 两套独立实现,含文档 golden 向量单测(与 91 大写 / affiliate_dash HMAC 都不通用) - 进单:trade_TradePaid 推送 → 验签 → webhook_events 幂等 → order.orderDetail → upsertOrderFromSource(provider=douyin_mashida / platform=doudian) - 精度:19 位订单号超出 JS 安全整数,JSON.parse 会把 p_id 抹成 ...000; 故解析后从原始 JSON 文本用正则抓回精确数字(单测同时证明"不修会错") - 券码:迁移 094 redeem_certificates((provider,order_id) 唯一做发券幂等) + DYC 券码生成 + coupons.syncV2 同步(子单号优先、父单兜底) - 兑换:POST /api/v1/redeem/lookup|confirm,按 orderNo+IP 限流; 核销成功后 order.message 通知买家(best-effort,失败只记错误日志) - 退款/取消:作废未核销券;券已核销时打 WARN 资损告警(已消费不可回滚) - 后台:GET/POST /api/v1/admin/platform-config/douyin-mashida, secret 出参置空 + 掩码,掩码回写不覆盖原值 - 消息路由挂在全局 body parser 之前(app.ts),验签必须用 body 原始字符串
This commit is contained in:
@@ -0,0 +1,57 @@
|
||||
-- 094_redeem_certificates.sql —— 兑换券表(抖音码速达最小接入,多平台预留 provider 列)。
|
||||
--
|
||||
-- 背景:抖音码速达把抖店订单推给我们履约,我们**自己生成券码**(DYC 前缀)通过
|
||||
-- `coupons.syncV2` 交给平台展示给买家;买家打开固定兑换链接 `https://<域名>/redeem`
|
||||
-- 输入订单号兑换,我们再调 `coupons.verifyV3` 上报核销。
|
||||
--
|
||||
-- 为什么独立建表而**不复用** kuaishou_industry_vouchers:
|
||||
-- 那张表是「快手电子凭证」(字段 oid/token/seller_id,核销走快手 OpenAPI),
|
||||
-- 与本表的「我们自发的兑换券」语义不同,混用会污染既有状态机与发码回调重试逻辑。
|
||||
--
|
||||
-- 量级预期:与订单量同阶(一单一券),需要按 order / cert_no 精确检索。
|
||||
|
||||
CREATE TABLE IF NOT EXISTS redeem_certificates (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
-- 平台标识:本期只会有 douyin_mashida;列保留是为了以后接第二个平台时不用改表
|
||||
provider TEXT NOT NULL,
|
||||
-- DYC + Crockford Base32(全局唯一)
|
||||
cert_no TEXT NOT NULL,
|
||||
-- 回执给平台用的订单号(syncV2 / order.message 的 order_id,子订单号优先、父单兜底)
|
||||
order_id TEXT NOT NULL,
|
||||
-- 平台父订单号,便于排查
|
||||
parent_order_id TEXT NOT NULL DEFAULT '',
|
||||
-- 本地关联(兑换页按订单号查到本地订单后反查券)
|
||||
local_order_id BIGINT,
|
||||
order_item_id BIGINT,
|
||||
-- 发给平台的固定兑换链接(配置项 certLink,不硬编码)
|
||||
cert_link TEXT NOT NULL DEFAULT '',
|
||||
-- issued 已生成 / synced 已同步给平台 / verified 已核销 / invalid 已作废(退款、取消)
|
||||
status TEXT NOT NULL DEFAULT 'issued'
|
||||
CHECK (status IN ('issued', 'synced', 'verified', 'invalid')),
|
||||
sync_status TEXT NOT NULL DEFAULT 'pending'
|
||||
CHECK (sync_status IN ('pending', 'success', 'failed')),
|
||||
sync_error TEXT NOT NULL DEFAULT '',
|
||||
grant_time TIMESTAMPTZ,
|
||||
synced_at TIMESTAMPTZ,
|
||||
verified_at TIMESTAMPTZ,
|
||||
raw_payload_json JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- 券码全局唯一:兑换页/核销都按券码反查
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_redeem_certificates_cert_no
|
||||
ON redeem_certificates(cert_no);
|
||||
|
||||
-- 同一平台同一订单号只允许一张券:发券幂等(平台重复推消息不会重复发券)
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_redeem_certificates_order
|
||||
ON redeem_certificates(provider, order_id);
|
||||
|
||||
-- 兑换页按本地订单反查券(订单号查询命中后取券状态)
|
||||
CREATE INDEX IF NOT EXISTS idx_redeem_certificates_local_order
|
||||
ON redeem_certificates(local_order_id);
|
||||
|
||||
COMMENT ON TABLE redeem_certificates IS '兑换券(我方生成、经第三方平台展示给买家的券码;抖音码速达最小接入)';
|
||||
COMMENT ON COLUMN redeem_certificates.order_id IS 'syncV2/order.message 用的订单号(子订单号优先、父单兜底)';
|
||||
COMMENT ON COLUMN redeem_certificates.status IS 'issued 已生成 / synced 已同步 / verified 已核销 / invalid 已作废';
|
||||
COMMENT ON COLUMN redeem_certificates.cert_link IS '固定兑换链接(来自配置 certLink,发出去就收不回来,换域名只影响新券)';
|
||||
Reference in New Issue
Block a user