尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

14个模板+易支付对接:构建无需公众号回调的本地生活支付系统

14个模板+易支付对接:构建无需公众号回调的本地生活支付系统 做这类本地生活服务工具的开发踩过的坑比想象中多得多。这个项目最核心的一点不是那14个模板本身而是“模板支付回调”这一整套流程怎么被串联起来。直接说结论把模板抽象好、把易支付对接调稳、把订单状态查准这套系统就能在不需要公众号回调的前提下把支付成功率拉到能看的水平同时把维护成本压到最低。先把这个项目的背景说清楚。这里的业务场景是用户在私域场景比如小程序、H5商城、微信群里的商品页里下单需要发起一笔“美团代付”流程让付款方以美团的方式完成付款。标题里的“14个模板”指的是针对不同商品形态做的前端展示和支付引导页面从外卖到团购到跑腿覆盖了常见本地生活类目。“支持易支付”指的是收银台和异步通知走的是易支付这类个人/聚合支付网关。“无需公众号回调”则直接砍掉了微信公众号支付回调这一环用查单轮询代替省掉一堆配置成本。这篇文章适合正在做同类私域交易工具、代理业务、本地生活服务SaaS的人参考。全文不涉及任何违规操作的内容重心全放在模板系统设计、支付对接、订单不丢失的技术工程上。1. 为什么要做14个模板先拆业务场景1.1 模板不是花架子是收银台的统一内核一开始我也以为“14个模板”是个营销噱头真正动手做的时候才发现不同商品类目对应完全不同的用户心智和展示路径。比如外卖类订单用户关心的是“送到哪、多久到”模板上要重点展示地址、配送时长、商家信息而团购类订单用户关心的是“买了几份、核销码在哪”模板的重心就应该放在数量、总价、有效期和取码入口上。这两类需求如果共用一套页面要么信息冗余要么关键信息被折叠在角落转化率一定会受影响。所以14个模板的本质是“一类商品一套收银台”。但它们不能是14个独立开发的页面那样维护成本会直接爆炸。正确做法是抽一套统一的内核再按业务场景做配置化差异。内核需要包含的东西其实不多订单信息区块商品名、数量、金额、订单号用户操作区块代付按钮、分享按钮、客服入口状态提示区块待支付、支付中、已成功、已取消安全与合规区块平台说明、服务协议、支付渠道说明14个模板只是在这四个区块上做了不同权重和样式组合。外卖场景弱化协议区团购场景强化订单明细区跑腿场景强化地址和联系方式区。这样设计的好处是后续哪怕新增第15个模板成本也只是“调样式参数”而不是“重新开发一套页面”。1.2 14个模板的场景划分与复用边界我在项目里是按“商品交付形态”来做模板划分的这里可以直接给出一份参考划分表模板编号场景类型核心信息展示支付后动作T01-T03外卖代付商家、送餐地址、预计时间跳转订单详情/配送进度T04-T05跑腿帮买需求描述、收货联系人展示取货码/配送状态T06-T08团购代购套餐内容、有效期、张数展示核销码T09-T10超市便利商品明细、小票金额展示提货信息T11-T12话费/充值充值号码、面额、到账方式展示充值进度T13-T14通用/定制自定义图文、链接、金额自定义跳转这套划分的复用边界很清晰凡是“交付物是虚拟凭证”的场景模板都只需要关注支付前后状态凡是“交付物涉及线下履约”的场景模板就必须额外承载地址、联系人、时间窗这类信息。实际迭代中容易被忽略的一个点是模板切换不能影响订单数据流。我的做法是每个模板只绑定一个scene_code支付请求里的attach字段携带这个场景码回调通知里原样返回。这样模板系统哪怕调整布局订单对账也不会被波及。2. 模板系统落地选型、渲染与海报版本2.1 模板引擎选型模板字符串已经够用先说结论这个项目的页面模板我最后没有上重型框架直接用ES6模板字符串加一个几十行的渲染器搞定。理由很简单页面都是后端下发的H5页面没有复杂交互数据流也是单向的杀鸡不用牛刀。所谓模板字符串就是JavaScript里用反引号包起来、可以通过${}直接插值的字符串写法。对比一下传统字符串拼接的痛苦模板字符串的可读性和维护性都高出太多const tpl div classorder-card p classshop-name${orderData.shopName}/p p classorder-amount¥ ${formatAmount(orderData.amount)}/p p classorder-status${statusText[orderData.status]}/p /div ;配合一个简单的renderPage(templateName, data)函数把模板和数据组装成完整HTML返回给用户端整条链路跑起来非常清爽。如果单个模板变复杂了也可以把公共区块拆出来用函数组合而不是模板继承来复用function renderHeader(data) { return header${data.title}/header; } function renderFooter(protocolText) { return footer${protocolText}/footer; } function renderOrderPage(data) { return ${renderHeader(data)}${renderOrderDetail(data)}${renderFooter(data.protocol)}; }这样做的另一个好处是方便做权限控制——模板系统本身不接触数据库所有数据都通过接口拉取后在渲染前做字段白名单过滤避免用户可控内容直接打上页面造成注入问题。2.2 海报版本是怎么做出来的标题里提到的“带海报版本”说白了就是每个模板除了H5页面还要配套一张可保存、可转发的付款海报。这个需求来自真实业务场景用户不一定立刻付或者需要发给朋友/家人帮忙付这时候一张二维码海报就是最好的传播载体。海报版本的实现不能靠手工截图。项目里用的是Canvas动态合成方案拉取模板背景图作为底图在底图上绘制商品名称、金额、截止时间等文本在指定位置嵌入订单二维码用canvas.toDataURL()输出图片并允许长按保存这里有几个坑值得记录。第一个是中文文本溢出金额最多七八个字符没问题但商品名长了之后直接跑到画布外面。解决思路是在绘制函数里做截断超过一定像素宽度就在字符处切分并加省略号。第二个坑是Canvas的跨域问题背景图必须配置crossOriginanonymous否则合成后导出图片会变成空白。第三个坑是二维码容错率微信扫一扫对复杂背景的识别率会下降建议二维码周围至少留出10px以上的静区。2.3 移动端适配的四个细节模板系统主战场是移动端浏览器适配不做好前面都白费。这里说四个实际踩过坑的细节。第一个是微信内置浏览器的标题栏页面加载时不能用document.title直接改部分版本会不生效必须配合WeixinJSBridgeReady事件触发后修改。第二个是安全区域iPhone X之后的机型底部有Home Indicator如果不给底部按钮留出env(safe-area-inset-bottom)支付按钮就会被遮挡。项目里统一在底部固定操作栏上加了这样一个值.pay-bar { padding-bottom: env(safe-area-inset-bottom); }第三个是键盘弹出。下单页里如果带了备注输入框iOS上键盘弹出会遮住提交按钮最稳妥的做法是滚动容器用position: fixed语义下的实际页面高度动态计算不要在键盘弹起事件里做复杂位移越简单越稳。第四个是页面缓存。用户在支付成功后点返回容易看到旧的“待支付”页面需要在页面visibilitychange时主动刷新订单状态接口。3. 易支付对接从下单到回调验签3.1 对接前必须搞清楚的三个概念易支付这类网关和微信支付官方渠道的区别是它给你一个聚合收银台由它去完成收款你只需要向它发起下单请求、接收异步通知。对接前必须搞清楚三个概念商户号、应用ID和密钥。商户号是你在这个支付渠道的身份标识应用ID是你的某个业务应用对应的ID比如14个模板可以共用同一个应用ID也可以按业务拆成多个密钥则是一切签名计算的基础材料。从安全角度要给所有要对接这套系统的人提个醒请优先选择持有合法支付业务资质、有明确商户准入流程的持牌机构或持牌机构的合规服务商。这里的代码只是技术对接示例入网资质、商户审核、交易合规这些事情比代码本身重要得多。3.2 下单与签名算法对接的第一步是发起下单。标准的流程是后端收到前端支付请求后组装下单参数计算签名提交到支付网关的pay接口拿到返回的支付链接再把它302给前端用户跳转。下单参数里最关键的字段包括pid商户IDtype支付方式比如微信H5out_trade_no商户订单号notify_url异步回调地址return_url同步跳转地址name商品名称money金额元sign签名值sign_type签名类型一般是MD5签名算法是所有对接中最容易出错的环节它的逻辑不复杂但规矩很死去除sign、sign_type和空值参数把剩余参数按照参数名字典序升序排列拼接成参数名参数值参数名参数值的字符串最后在末尾拼接上商户密钥对整个字符串做MD5得到32位小写签名。function makeSign($params, $secret) { ksort($params); $str ; foreach ($params as $key $value) { if ($value || $value null) { continue; } $str . $key . . $value . ; } $str rtrim($str, ); $str . $secret; return md5($str); }这个ksort加rtrim的过程看着简单实际项目里十次对接有八次出问题都在这里要么漏了空值过滤要么拼完字符串末尾还残留一个要么MD5输出成了大写导致网关验签失败。3.3 回调验签与“无需公众号回调”的真相支付完成后网关会向notify_url发送异步通知里面包含订单号、交易流水号、实际支付金额、状态等字段同样带一个sign。你的后端收到通知之后必须先验签确认这笔通知确实是网关发出的再修改订单状态。验签算法和下单签名完全一样把收到的参数拿出来去掉sign和空值排序拼接加密钥算MD5跟通知里的sign比对。一致才继续不一致直接丢弃。这里必须强调一个容易被忽视的实践验签通过还不够还要做“验金”也就是比对通知里的实际支付金额跟订单在数据库里存的应付金额是否一致。否则哪怕签名被正确解析也可能出现支付金额与订单金额不符的异常状态对账就乱了。现在可以解释“无需公众号回调”的真相了。很多人在对接微信支付时模板会有固定思维必须先注册公众号、开通微信支付、配置回调域名否则没法收款。但用易支付这类聚合网关它自己在网关侧已经完成了与微信的交互把支付结果整合成一次统一回调给你。你的服务器不需要直接跟微信服务器打交道也就不需要在公众号后台配置那套回调地址。这就是“无需公众号回调”这句话的技术基础。当然回调不是100%必达的网络抖动、服务器重启、接口超时都可能导致通知丢失。所以跟回调并行的还必须要有一套查单机制也就是主动向网关查询订单支付状态用来兜底掉单情况。4. 订单不丢不漏轮询、状态机与异常排查4.1 掉单的常见原因做支付系统“掉单”是最让人头疼的事用户付了钱系统显示未支付。我复盘过这个项目里所有掉单场景归纳起来就是三类原因。第一类是异步通知丢失这个刚才提过网关通知没送达或者送达了但你的服务没处理成功。第二类是验签失败导致通知被丢弃常见于下单和回调用两套不同的密钥生成逻辑或者字符串编码不一致。第三类是回调处理逻辑的时序问题比如并发消费同一个订单的重复通知时把已成功的订单状态又覆盖回了待支付。针对第三类问题解决方案是在订单表上做状态机加幂等约束。订单状态只允许按固定路径流转待支付 - 支付中 - 支付成功待支付 - 已取消支付成功 - 已退款回调处理里必须加一层检查只有当订单处于待支付或支付中状态才能更新为成功如果已经是成功状态直接忽略重复通知但照常返回success给网关。这样既不会重复入账也不会因为返回值异常导致网关无限重试。4.2 查单轮询怎么设计设计和实现查单轮询核心问题是什么时候查、查多频繁、查到什么结果算结束。前端层面可以做页面轮询但轮询频率不宜过高。我看过有人写1秒请求一次订单状态的页面属实没有必要既占带宽又增加服务端压力。合理方案是页面加载后立即查一次之后每3到5秒查一次最多持续120秒。如果超过两分钟还没结果页面主动提示用户“支付结果确认中请勿关闭页面”同时开启后台兜底查单。后端兜底查单是另一条链路用计划任务或者延迟队列做定时任务定时去查询那些已经发起支付但长时间处于支付中的订单。// 查询逻辑示例 $result $paymentGateway-queryOrder($outTradeNo); if ($result[status] paid) { $orderService-markAsPaid($outTradeNo, $result[tradeNo], $result[amount]); } elseif ($result[status] closed) { $orderService-markAsClosed($outTradeNo); }这里有个参数细节查单接口的金额字段要注意单位。有的渠道金额单位是元有的是分不统一转换直接入库会造成金额误差而且这种误差在对账时非常难查。4.3 排查问题清单把项目上线后遇到最多的问题整理成一张速查表方便后面维护的人照着排查异常现象优先排查项处理建议用户支付成功但订单未更新检查异步通知是否送达先看网关后台的推送记录再看服务端访问日志里有没有对应请求回调地址收到请求但验签失败检查参数排序和密钥将收到的原始参数打印出来逐项跟网关文档比对订单金额不一致检查单位转换确认下单、回调、查单三处的金额单位是否统一跳转收银台后白屏检查参数编码商品名称等中文参数做URL编码避免网关解析异常海报二维码扫不出来检查二维码静区保证二维码周边有足够留白不要紧贴文字前端状态与后端不一致检查页面缓存在页面visibilitychange时主动请求最新订单状态排查日志是这里最重要的依赖。我建议在项目中单开一张payment_callback_log表把每次收到的回调原始报文、验签结果、订单处理结果都记下来。它可能看起来不起眼但真正遇到线上问题的时候能帮你把排查时间从几小时压缩到几分钟。5. 最后再分享几个实战中的小体会模板系统的维护难度跟模板数量不成正比跟模板之间的差异度成正比。14个模板听着很多但只要你把公共区块抽得足够干净每次业务方提“新模板”的需求真正要做的只是填充差异配置而不是从零搭页面。易支付这类网关在测试阶段最常见的问题就是回调环境不通。本地开发环境收不到网关通知是正常的网关服务器访问不到你的内网地址这种情况就别纠结了直接把回调地址指向公网测试服务器或者用内网穿透的方式把回调暴露出去验证即可。“无需公众号回调”这个特性在部署阶段省了很多事但它也意味着支付环节对你来说是黑盒。用户支付成功到收到回调之间有一个时间窗口这个窗口里用户可能会反复刷新页面、重复点击支付按钮。我的建议是前端统一做按钮防抖后端在创建支付订单时做订单号唯一约束从源头杜绝重复支付的产生。根据我个人的经验这类系统上线后最重要的不是新增功能而是保证每一笔订单都能被对得上账。回调日志、订单状态流转记录、网关侧流水记录三份数据能互相印证这个项目的质量才算真正过关。
返回列表