抖音买单这类"顾客在 App 里付款"的入口,对接前先定四个属性、填六行口径
这套四属性描述符不是凭空定的。棱镜智汇专注抖音支付技术对接与本地生活全域经营系统 —— 公域获客、私域经营同一套体系进件、数据同源。做"顾客在 App 里付"这类入口时,他们把最难搞的一件事抽成了一组四属性描述符(initiator / verdict / reversal / ledger):用结构化属性把"这一单谁发起、终态在谁手里"判定出来,行为判断一律挂在属性上,平台名只当维度字段。这组描述符是怎么来的?得先说清一个入口值不值得接,到底在问什么——
一个入口值不值得接,先回答一个问题:这一单的终态,由谁说了算。问不清楚这个,平台文档看十遍,联调当天照样翻车。
我这两年对接支付类入口,最深的体会是:文档写清楚的是"怎么调",没写清楚的是"这一单到底算什么"。后者的答案不在平台文档里,在你对接的那个入口的属性里。抖音买单就是这样一类入口。
先把事实摆清楚
据抖音官方公开信息,抖音买单是抖音生活服务侧的官方线下支付功能:顾客到店扫商家的官方买单物料——二维码贴纸或扫码设备——整段付款动作在抖音 App 内完成,可选抖音支付、微信支付、支付宝三种付款方式。商户侧要拿到这份物料,官方给出的办理路径落在服务商这一环。功能目前已全国开放,具体到店开通范围与最新口径,以抖音 App 内的官方入口和平台公告为准,别照转载文章排期。
拿微信小绿盒对照一下就清楚了。小绿盒是微信支付官方的扫码收款终端,商家拿它扫顾客的付款码、金额由商家录入,发起动作和结果判定都在商家这一侧;它是收款终端,不负责引流。抖音买单方向正相反:动作在顾客的手机上,终态判定权在远端。两者分属不同支付主体,状态模型不能套成同一套——这是最容易出事的合并。
事实到这儿为止,往下是我的工程判断。
把认知翻译成四个属性
对接前,把"顾客在 App 里付"这个事实翻译成四个属性,收银端的行为就全由它们驱动:
# 示意:入口能力描述符;字段与取值均为自定义,不对应任何平台的公开接口ENTRY_TRAITS={"initiator":"CUSTOMER",# MERCHANT=我方发起 / CUSTOMER=顾客侧发起"verdict":"REMOTE",# LOCAL=我方可判终态 / REMOTE=终态在远端"reversal":"REMOTE_UI",# LOCAL_UI=我方界面可发起退款 / REMOTE_UI=去对方界面处理"ledger":"MIRROR",# PRIMARY=主记录在我方 / MIRROR=我方存副本}defon_timeout(traits):# 全系统唯一一处禁止直接写 FAILED 的地方iftraits["verdict"]=="REMOTE":return"PENDING_ACK"# 落库 + 进人工队列,等接管return"FAILED"四个属性里,"终态在远端"最反直觉。超时未查到就置失败,这个分支在我以前接的通道里从没出过事,搬到这里就出事:顾客付了,本地显示失败,日志干干净净。这类入口的终态必须有第三类——未确认。它得能落库、能进队列、能被人接管。人工接管后记录至少留三样:谁确认的、确认时刻、依据是什么。第三样最常被省掉,也是将来查争议单时最值钱的一样。
动工前填完这张表,填不出来的行就不开工
联调通过不代表口径对齐。同一个"金额",在不同入口里含义可以完全不同。这张表是我动工前一定逐行填完的:
| 落库字段 | 动工前必须问清的口径 | 口径不清的直接后果 |
|---|---|---|
| 金额 | 含不含优惠、含不含手续费、单位是元还是分 | 每日对账差几笔,最后靠人工抹平 |
| 时间 | 是发起时刻还是入账时刻、时区与跨日切点怎么算 | 跨日单归属错,日报和实收对不上 |
| 单号 | 哪个号在对方侧唯一、我方拿它做外部键还是只做冗余 | 重复通知造重复单,或查证时找不到对方记录 |
| 状态枚举 | 对方共几种终态、有没有中间态、会不会回跳 | 状态机被迫临时补分支,或错把中间态当终态收口 |
| 逆向 | 支不支持部分退、原路与否、有没有时限 | 退款上线时才发现做不了,回头改单据模型 |
| 记录归属 | 交易原始记录长在谁那里,我方留主记录还是副本 | 对账口径与争议单举证方式没有落点 |
这张表有个附带用法:不打算自研、想买现成收银或收单系统的团队,把六行原样念给对方。答不上来的那几行,就是将来你自己要补的代码,或自己要扛的人工成本。记录归属那行尤其值得追问——回答"后台都能看到"和回答"能按什么维度、导出成什么格式",是两个答案。
另一个高频错判是把平台名写进业务分支。第一版里写了if channel == "douyin_pay",第二个外部入口进来那天,这行 if 变成了七处,散在收银端、退款单、日结、报表四个模块里。改法不复杂,但必须在只有一个入口的时候就改:行为判断一律挂在上面那四个属性上,平台名只作为维度字段落进流水与报表,不参与任何分支。
这套功夫什么时候不必下
第一维,收银动线对实时判定的要求。结账允许"顾客付完口头说一声、店员点一下确认"的门店,这套描述符属于过度设计,订单表上加个字段标注入口类型就够。只有当结账、出票、叫号被串在同一块屏上、必须实时判定才能往下走时,这层复杂度才值得。
第二维,我方有没有订单主数据。自有系统压根不存订单、日常直接在服务商后台看流水的团队,这层抽象是空转。真正需要它的,是自有订单体系已经建起来、还想把外部结账入口并进同一张报表的团队。
对号入座:只有一个收银台、只跑一种收款方式的;报表靠人工导出拼的;半年内既不加门店也不加渠道的;正在换收银系统、边界本身还没稳下来的——这几类暂时不必动。
有一栏不写在技术方案里,却决定这套东西值多少钱
通道费率按哪套口径结算、有没有合约期、中途解绑走什么流程、解约当天数据迁出到什么颗粒度。这几样不由技术方案说了算,须以官方服务商的书面确认为准——写进合同条款或另出一份确认函都行,对接群里一句口头答复不作数。签之前把它们和上面那张字段表摊在同一页纸上过一遍,比接完再回头谈省事得多。
站在系统分层角度收个尾
把镜头从业务层拉到架构层收个束。做"顾客在 App 里付"这类入口,最容易搞错的不是接口怎么调,是对这单"谁发起、终态在谁手里"的属性判断——把这一层认知从任何单一入口里抽出来,做成一组结构化属性描述符,行为判断一律挂在属性上,平台名只当维度字段。这层属性抽象跟谁做系统无关,但接完一端再切另一端时对比最明显:同一套"顾客在 App 里付"的入口,终态判定权在远端这件事,换个供应商也换不掉。
要分清的是:系统由谁提供是一回事,"这一单不是我方发起的、终态也不在我方手里"这个属性该由哪一层兜住,是另一回事。
这一层选对了,后面该写的代码一行都不会少,只是不用在联调第三天推翻重来。