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

资讯详情

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

SpringBoot实战:支付宝微信银联三渠道支付系统开发全解析

SpringBoot实战:支付宝微信银联三渠道支付系统开发全解析 简介这是一套面向Java后端开发者与支付系统学习者的实战型源码资源聚焦互联网支付核心能力构建完整覆盖支付宝、微信、银联三大主流渠道的接入实践适用于电商、在线教育、SaaS服务等需快速集成合规支付能力的业务场景。资源包共250个文件含57个Java核心业务类涵盖Controller/Service/Config模块、24个JS与15个HTML/CSS前端交互脚本、14个SCSS/LESS样式文件以及证书.cer/.pfx、配置.yml/.properties和文档.md等关键支撑文件整体仅1.53MB轻量易导入。已有772人下载学习可直接运行调试深入理解支付请求组装、异步回调验签、订单状态同步、退款流程闭环及HTTPS安全通信等关键实现预览中可见银联测试证书、LayUI前端组件与Font Awesome图标资源表明项目已集成生产级UI与金融级安全凭证具备工程落地参考价值。 做支付系统开发这个方向我前后折腾了不少项目从最早的单渠道对接到后来几个渠道同时接入踩过的坑属实不少。最近把手头这套基于SpringBoot的互联网支付系统源码重新整理了一遍把支付宝、微信、银联三家的详细代码案例都补全了想着分享出来给准备入局支付开发的朋友一个能直接参考的完整项目。这套源码面向的核心场景很明确一个电商平台或者SaaS服务需要接入多家支付渠道用户从收银台发起支付系统根据用户选择的支付方式路由到对应渠道然后统一处理回调、查询、退款和对账。它能解决的核心问题有几个——支付渠道对接文档分散且风格迥异的问题、回调验签与订单状态同步的准确性保障问题以及多渠道并存时的代码复用问题。适合正在做支付相关需求的后端工程师、想系统学习微信支付宝银联三渠道接入细节的SpringBoot学习者还有需要快速落地支付能力的初创团队技术负责人。如果你看完了市面上零零散散的单渠道demo但仍然不知道怎么把三套支付整合到一个项目里那这篇内容应该能帮上大忙。我尽量把每个环节的为什么和怎么做都讲透尤其是那些文档里不会细说的坑。1. 项目整体设计思路为什么选择SpringBoot做底座1.1 SpringBoot在支付系统里扮演的角色很多人问支付系统为什么普遍选SpringBoot而不是其他框架我的理解是——支付系统的本质是大量的外部接口对接、参数签名、消息回调处理这些场景对开发效率的要求远高于对代码花活的要求。SpringBoot的自动装配、starter机制、内嵌容器和丰富的生态恰好能让开发者把精力集中在支付业务本身而不是去折腾配置和部署。拿这套项目里的一个实际例子来说支付宝和微信的HTTP通知回调都需要先验签再处理业务这两段逻辑本质上是一模一样的流程——接收请求、提取参数、执行验签、解析业务数据、更新订单状态、返回应答。用SpringBoot的Controller层来做统一接收再用策略模式分发到不同渠道的handler整个结构就非常清爽。如果用其他框架光是处理这种多渠道动态分发就要多写不少样板代码。另外一个重要的原因是SpringBoot的配置体系对支付系统特别友好。支付渠道的关键参数——应用ID、商户号、证书路径、回调地址——这些都是环境相关的在dev、test、prod之间切换很频繁。SpringBoot的多环境profile配置加上ConfigurableEnvironment的动态刷新能力让我不需要改代码就能切换沙箱和正式环境这在联调阶段帮了大忙。1.2 系统模块划分与源码目录结构我在设计这套源码的时候没有把它做成一个大而全的单体工程而是按照支付系统的通用边界拆成了几个独立的package每个package负责一条清晰的职责链。这样做的最大好处是后续如果要把支付模块抽离成独立微服务代码迁移成本极低。核心的目录结构大概是这样的pay-system/ ├── common/ // 通用工具、常量、异常体系 │ ├── constant/ // 各渠道常量定义 │ ├── enums/ // 订单状态、支付渠道、交易类型枚举 │ ├── exception/ // 业务异常和全局异常处理 │ └── util/ // 签名工具、金额转换、日期处理 ├── controller/ // 收银台接口、支付接口、回调接收接口 ├── service/ │ ├── pay/ // 支付统一接口与策略分发 │ ├── alipay/ // 支付宝对接实现 │ ├── wechat/ // 微信支付对接实现 │ ├── unionpay/ // 银联对接实现 │ ├── order/ // 订单管理与状态流转 │ └── refund/ // 退款处理 ├── config/ // 各渠道配置类、RestTemplate配置、线程池配置 ├── entity/ // 数据库实体 ├── mapper/ // MyBatis-Plus的Mapper层这里每层之间的依赖关系是单向的controller依赖serviceservice依赖mapper和entitycommon层是所有模块共享的底座。特别要说明的是util包里的签名工具它是整个支付系统里最容易被忽视但最要命的部分——一旦格式或编码有问题所有渠道的验签都会失败。1.3 多渠道接入的统一抽象策略做过多渠道支付的人一定深有体会支付宝和微信表面上是发一个支付请求、收一个回调通知但实际接口风格差异巨大。支付宝用开放平台的SDK参数名是下划线风格微信V3用RESTAPI加JSON金额单位还是分银联更传统报文走的是表单提交加证书签名。如果每个渠道各写各的controller层和service层会迅速膨胀成一团乱麻。所以这套源码里我设计了一个统一的PaymentService接口里面有四个核心方法createPayment创建支付、queryPayment查询支付结果、refund退款、parseNotify解析并验签回调。每个渠道都有自己的实现类但对外暴露的方法签名完全一致。这样上层业务不需要关心当前用户在用什么渠道支付只需要拿到一个统一的请求对象就能得到统一的响应结果。在具体实现上我用了策略模式加工厂模式结合的方式。Service层维护一个MapPayChannelEnum, PaymentService通过Spring的ApplicationContext在启动时自动收集所有PaymentService实现类注册到Map里。这样新增一个支付渠道只需要写一个实现类并加上PayChannel注解完全不需要改动现有业务代码。这个模式也是我在实际项目中验证过多次的方案对扩展性要求高的场景非常适用。2. 支付宝接入核心细节从沙箱联调到回调验签2.1 开放平台配置与沙箱环境准备支付宝的对接是三个渠道里对开发者最友好的很大原因在于支付宝提供了功能完整的沙箱环境。你在支付宝开放平台创建应用后可以申请一套沙箱密钥用沙箱账号和沙箱版支付宝App完成整个支付链路的联调不需要真实资金。配置的关键点有几个。首先是密钥生成支付宝推荐使用RSA2加密方式即SHA256withRSA你需要生成一对应用私钥和应用公钥把应用公钥上传到开放平台然后平台会返回一个支付宝公钥。这里有个经常搞反的地方——签名用应用私钥验签用支付宝公钥。这套源码的AlipayConfig里我把这两种密钥分开配置并加了注释说明用途避免后续维护的人混淆。沙箱环境的信息可以在开放平台控制台的开发设置里找到包括沙箱的AppID、支付宝网关地址https://openapi.alipaydev.com/gateway.do、沙箱账号等等。值得一提是沙箱的支付宝公钥和正式环境的支付宝公钥是不同的切换环境时不仅要改AppID和网关公钥也要一起替换否则验签的时候会报公钥不匹配的错。2.2 下单支付流程与收银台二维码生成支付宝的下单接口有好几个现在主流的是alipay.trade.precreate当面付预下单和alipay.trade.page.pay电脑网站支付。这套源码里我两个都写了但核心推荐用precreate因为它的响应里会直接返回一个二维码字符串后端把二维码转成Base64丢给前端展示即可非常适合PC收银台场景。precreate的调用参数大致是这样AlipayTradePrecreateRequest request new AlipayTradePrecreateRequest(); request.setNotifyUrl(alipayProperties.getNotifyUrl()); request.setBizContent({ \out_trade_no\:\ orderNo \, \total_amount\:\ totalAmount \, \subject\:\ subject \ }); AlipayTradePrecreateResponse response alipayClient.execute(request);这里有个细节值得注意total_amount是字符串类型而且单位是元我建议用BigDecimal的toPlainString()而不是toString()来转换后者在大数值时会输出科学计数法支付金额瞬间就错了。另外out_trade_no是商户订单号要保证唯一性这里可以用订单表的主键ID加业务前缀生成。生成二维码那一步我习惯用Hutool的QrCodeUtil工具类写成一个工具方法响应给前端的是Base64编码的图片字符串前端直接用img标签的src加上data:image/png;base64前缀就能渲染。源码里的QrCodeService封装了这段逻辑直接调用即可。2.3 异步通知的验签与订单状态更新支付宝的异步通知是全流程里最核心也最容易出问题的一环。预下单接口返回后用户扫码支付支付宝会向notifyUrl发起异步通知。这个通知是一个POST请求携带一组表单参数比如out_trade_no、trade_no、trade_status、total_amount等等。验签的逻辑在AlipayNotifyHandler里核心代码是boolean signVerified AlipaySignature.rsaCheckV1(paramsMap, alipayProperties.getAlipayPublicKey(), AlipayConstants.CHARSET_UTF8, AlipayConstants.SIGN_TYPE_RSA2);注意几个坑。第一验签用的参数Map必须是支付宝原始POST过来的所有参数除去sign和sign_type不要自己重新构造Map哪怕你只有一个参数有变动验证都会失败。第二验签通过后要查看trade_status只有当状态是TRADE_SUCCESS或TRADE_FINISHED时才能更新订单为已支付其他状态比如WAIT_BUYER_PAY说明用户还没付完不能动订单状态。第三业务处理成功后要返回纯文本success给支付宝如果处理失败返回其他内容支付宝会按照一定频率重复通知直到拿到success。还有一个很多新手不知道的点支付宝的同步跳转return_url只是用户在浏览器端跳转回来它不能作为订单是否支付成功的判断依据因为页面可以被伪造。真正可信的只有异步通知和服务端主动查询。这套源码里我在return_url对应的controller里只做了收银台页面跳转状态的最终确认完全依赖notify回调这是安全设计上必须守住的原则。3. 微信支付接入核心细节V3版API的正确打开方式3.1 为什么微信支付优先选择V3版本微信支付官方目前主推的是APIv3版本和老的V2版本相比最核心的变化是接口风格统一成了RESTful加JSON而且签名方式从MD5升级成了基于商户私钥的SHA256withRSA平台侧验证也换成了微信支付平台证书。客观来说V3的学习曲线比V2略陡峭但接口规范性、安全性远超V2而且新功能基本只在新版上迭代所以这套源码我直接基于V3实现。V3的签名过程需要理解三个关键对象商户API证书apiclient_key.pem和apiclient_cert.pem、APIv3密钥一个32位的随机字符串、微信支付平台证书。商户API证书用于你发请求时给请求签名APIv3密钥用于解密回调里的敏感信息微信支付平台证书用于验证微信响应的签名。三者各司其职少一个都跑不通。证书文件我放在resources/cert目录下配置项在application-wechatpay.yml里包含了mchId、appId、apiV3Key、privateKeyPath、platformCertPath等字段。微信平台证书有有效期需要定时更新源码里我加了一个定时任务每周自动从微信下载最新证书并替换本地缓存避免证书过期后回调验签突然失败。3.2 Native下单流程与金额单位转换Native支付是PC端扫码支付最常用的模式接口地址是POST /v3/pay/transactions/native。请求体里需要带上appid、mchid、description商品描述、out_trade_no、notify_url和amount对象成功后会返回一个code_url这个就是生成二维码的内容。用Java的HttpClient实现签名请求时最关键的是Authorization请求头的构造。它的格式是WECHATPAY2-SHA256-RSA2048 mchid商户号,nonce_str随机串,signature签名,timestamp时间戳,serial_no证书序列号。签名串的内容是HTTP方法\nURL路径\n时间戳\n随机串\n请求体\n拿到后用商户私钥做SHA256withRSA签名。这套源码里我在WechatPayHttpClient里封装了完整的签名逻辑包括自动从证书文件读取商户私钥、自动生成nonce_str、自动拼接待签名串业务代码只需调用一个postJson方法。说实话这部分代码是所有渠道里最需要耐心调试的签名格式任何一个细节不对都会返回403或者验签失败。金额转换也是个容易踩雷的地方。微信的所有金额单位都是分而且用int类型。订单数据在MySQL里存的是BigDecimal类型的元转换逻辑统一写在AmountUtil里public static long yuanToFen(BigDecimal amount) { return amount.multiply(BigDecimal.valueOf(100)).longValue(); }这里强调一下千万不要用double直接乘100再转long浮点误差会导致金额少几分钱用户支付的时候会提示金额不一致直接失败。3.3 回调验签与订单数据AES解密微信支付的回调通知与支付宝完全不同微信的Notification请求头里带了Wechatpay-Signature、Wechatpay-Timestamp、Wechatpay-Nonce、Wechatpay-Serial等参数请求体是一个JSON对象真实的交易数据躺在resource字段里被AES-256-GCM加密过。所以完整的处理流程是先验签再解密再处理业务。验签的逻辑相对固定。把微信的timestamp和nonce以及原始请求体注意是原始字节流不能用框架解析后的对象重新序列化拼接成待验签串timestamp \n nonce \n body \n。然后用微信支付平台证书里的公钥去验证Wechatpay-Signature这个字段的签名。验签用我们自己的SDK代码实现起来不复杂但要注意一定不能用商户私钥去验签这是不少新手搞混的地方。验签通过后需要对resource对象做AES-256-GCM解密。使用APIv3密钥作为AES密钥resource里的nonce作为AES GCM的nonceassociated_data是resource.associated_data。解密后得到的JSON字符串里才有真正的订单信息比如out_trade_no、transaction_id、trade_state、amount等。之后更新订单状态、返回响应响应体是{code:SUCCESS}并且要保证响应的Content-Type是application/json。如果处理失败或者响应非200微信会按策略重试通知间隔逐步拉长最长可以持续几天所以回调处理的幂等性一定要做好。4. 银联支付接入核心细节传统网关支付的证书机制4.1 银联网关支付与证书体系说明银联的接入方式和支付宝微信差异明显它走的是更传统的网关模式。用户在商户的收银台点击银联支付后系统把请求参数通过HTML表单POST到银联的网关页面用户在该页面完成卡号密码输入银联随后把结果同步跳转回frontUrl、异步通知到backUrl。这个流程里最核心的是证书体系。银联要求商户使用pfx格式的商户私钥证书对请求报文做签名同时用银联提供的验签公钥证书对响应做验签。测试阶段的证书可以直接从银联开放平台下载正式环境的证书需要商户资质审核后获取。证书配置在UnionpayConfig里包括signCertPath签名证书、signCertPwd证书密码、verifyCertPath验签证书路径和中英文证书路径差异。银联的报文格式是KV形式的表单参数但签名值包含在签名串里。具体签名的时候要把所有请求参数按照key的字典序ASCII码升序拼接成keyvaluekeyvalue的格式然后去掉最后的再用商户私钥做SHA256withRSA签名得到的签名串Base64之后放在signature字段里。说起来简单但实际容易出问题的点是某些字段值里有特殊字符需要做URL编码编码规则不对会导致签名验证失败。4.2 报文组装、发送与异步通知处理组装银联请求参数时有几个必填项要特别注意version固定5.1.0、encodingUTF-8、txnType01表示消费、txnSubType01表示消费、bizType000201表示B2C网关支付、channelType07表示互联网、accessType0表示商户接入、merId商户号、orderId商户订单号、txnTime交易时间格式yyyyMMddHHmmss、txnAmt单位是分和微信一样。这些字段名和支付宝下划线风格类似但取值含义不一样对接的时候要对照官方文档逐一确认。异步通知接收的逻辑核心在UnionpayNotifyHandler里。银联的异步通知同样是表单参数POST到backUrl验签用的密钥和请求签名不一样——请求签名用的是商户证书私钥验签用的是银联官方公钥证书。验签通过后需要判断respCode字段的值00表示成功其他都是失败或处理中状态。只有respCode为00时才能把订单更新为已支付。比起前两家银联的文档确实显得陈旧而且联调环境测试网关地址响应速度偶尔不稳所以代码里我加了超时重试机制对查询类请求做最多3次重试每次间隔2秒。这套源码里银联的部分我也在注释里写了很多这里为什么这样传参的说明方便大家查阅。4.3 三家渠道接入对比速查做项目复盘的时候我把三家渠道的接入要点整理成了一个对照表方便同一套代码里不同渠道的特殊性一目了然维度支付宝微信V3银联接口风格SDK 参数MapRESTful JSON表单KV 跳转金额单位元字符串分int分int请求签名应用私钥SHA256withRSA商户私钥SHA256withRSA商户证书私钥SHA256withRSA回调验签支付宝公钥验签平台证书验签银联公钥验签回调敏感字段明文GET/POSTAES-256-GCM加密明文POST沙箱支持完善需真实商户号和授权测试网关较简陋这张表基本能回答三家渠道怎么选型的问题——如果是新项目且目标用户主要在移动端微信支付是刚需PC或Web端多渠道并存场景支付宝加微信打底如果业务涉及对公转账或者特定行业银联网关支付也绕不开。这套源码的价值就在于它把三个风格完全不同的渠道统一到了同一个抽象层下面。5. 支付模块的代码设计要点可扩展性才是核心5.1 策略模式封装多渠道支付的具体实现统一抽象和策略分发的代码是整套源码里我认为最值得反复看的部分。在service/pay目录下PayChannelEnum定义了三种渠道类型public enum PayChannelEnum { ALIPAY(alipay, 支付宝), WECHAT(wechat, 微信支付), UNIONPAY(unionpay, 银联支付); private final String code; private final String desc; }每个渠道的PaymentService实现类上标一个自定义注解PayChannel(PayChannelEnum.ALIPAY)然后在PayChannelFactory里通过Spring的ApplicationContext扫描所有带该注解的Bean注册进Map。这样如果用到了新的渠道比如抖音支付或者云闪付只需新增一个实现类并打上注解上层的支付Controller和订单服务一行都不用改。这个设计的价值在项目维护期体现得最明显。我有一个实际项目的经历最初只接了支付宝和微信后来客户要加银联我把银联的对接实现写好注册一下策略Map整个上线过程只花了半天。对比另一个没有做抽象、在service里用if-else硬写渠道分支的项目后者新增渠道几乎要把整个调用链都翻一遍。5.2 订单状态机与幂等处理支付系统的订单状态流转直接用数据库字段加代码if判断很容易埋坑尤其是回调重复通知和多渠道并发到达的情况下。我在entity和service层设计了OrderStatusEnum定义了六种状态WAIT_PAY待支付、PAYING支付中、PAID已支付、REFUNDING退款中、REFUNDED已退款、CLOSED已关闭。状态机的核心约束是只有WAIT_PAY和PAYING状态才能流转到PAID不能被覆盖更新。这个逻辑在OrderService.updateToPaid方法里用SQL条件update实现UPDATE pay_order SET status PAID, channel_order_no ?, pay_time NOW() WHERE order_no ? AND status IN (WAIT_PAY, PAYING)利用数据库的乐观锁只有受影响行数为1时才算更新成功。这样即使支付宝和微信的回调同时到达理论上不太可能但并发场景需要防护也只有一个请求能成功把订单更新为已支付另一个会拿到影响行数为0的结果然后直接返回成功应答不做重复处理。幂等处理这块除了SQL层面我还用Redis做了一层防护。回调入口先通过SETNX一个notify:{orderNo}的锁设置5分钟过期。这样可以在极短时间内拦截完全相同的回调请求减少无谓的数据库操作。对于分布式部署的场景Redis锁是必要的单机部署的话SQL乐观锁其实就够用了。5.3 退款、对账与掉单补偿机制支付系统的完整性不止下单和回调退款和对账更是日常运营离不开的能力。退款接口的设计思路和支付类似统一找PaymentService.refund方法传入商户订单号和退款金额渠道层各自实现。支付宝的退款是即时返回结果微信V3的退款也是同步返回退款单号银联的退款则需要轮询或等待回调通知确认结果。退款一个常见的坑是部分退款的金额校验。如果微信的订单是100元第一次退60元成功第二次想退50元系统应该拒绝。这个校验不能只依赖支付渠道业务侧也要做——退款前查询累计已退金额加上本次退款金额不能超过支付金额。这套源码的RefundService里实现了这个校验逻辑并且把每次退款记录都存到refund_order表中方便对账时追溯。掉单补偿是很多人开发支付系统时忽略的环节。回调通知是不可靠的无论支付宝微信还是银联都可能出现通知丢失的情况。如果用户付了钱但回调没到订单就会一直停留在待支付状态。解决方案是主动查询启动一个定时任务每隔几分钟扫描WAIT_PAY状态且创建时间超过一定阈值的订单调用PaymentService.queryPayment去渠道侧查询真实状态如果已支付则本地补更新订单。这套源码里的OrderCompensateTask就是干这个的它是我在线上被用户说付了钱但订单没变搞到怀疑人生之后加上的加完后再没出过这类问题。6. 常见问题与排查实录6.1 回调验签失败的经典原因回调验签失败是支付对接中最常见的问题我在项目过程中总结出了几个高发原因。支付宝最常见的原因是用错了公钥。很多人把应用公钥当作验签公钥来用或者直接用了官方文档里的示例公钥导致验签永远不过。记住验签必须使用开放平台上开发者中心里显示的支付宝公钥不是应用公钥也不是应用私钥。另外支付宝验签时参数Map里不能包含sign和sign_type这两个字段直接用request的getParameterMap中转成Map即可但要注意Tomcat的getParameterMap返回的值是String[]需要做一层数组到字符串的转换。微信验签失败的原因通常有两个。一个是用了商户API证书的公钥来验签正确做法是用微信支付平台证书的公钥。另一个是验签用的body不是原始请求体很多Web框架在Controller层拿到的是被反序列化过的对象再重新序列化成字符串时字段顺序会变化导致验签串对不上。解决办法是在Controller里接收RequestBody String rawBody把原始字符串直接传给验签方法。银联验签失败则通常和字符集有关容易在中文参数字段上栽跟头。如果请求参数里有中文的txnInfo或者订单描述务必保证签名用的字符串编码和实际传输的编码一致统一用UTF-8。6.2 金额单位与精度转换问题金额精度问题在所有支付对接里都值得单独拿出来说。支付宝接口要求的是元且是字符串微信和银联要求的是分且是整数。如果底层存储和转换逻辑没有一个统一的规范早晚会出金额少一分或者多一分的事故。我在项目里定了一条铁律数据库里金额字段一律用BigDecimal类型单位是元所有代码里的金额变量也用BigDecimal只有在调用微信或银联接口的边界处才通过AmountUtil转换成对应的分。这条规范写进了团队的开发手册里代码Review时凡是看到double或float处理金额的一律打回。另一个细节是支付宝的total_amount字段传给SDK时虽然是字符串但SDK内部会解析成BigDecimal做金额校验如果订单金额是0.1元传0.1是正确的但传0.10或者0.100在某些版本SDK下可能会被判为格式不合法。统一用BigDecimal的stripTrailingZeros().toPlainString()可以规避这个问题。6.3 回调重复通知与并发场景应对回调重复通知是设计回调处理逻辑时必须考虑的场景不是偶发故障而是渠道的固定行为。支付宝在未收到success时会在当天按一定频率重发3次左右微信的重试机制最长能持续几天银联的情况也类似。如果回调处理逻辑没有做幂等处理重复通知轻则产生重复日志重则导致订单状态错乱和重复发货。应对方案在前面5.2小节已经说了核心就两条数据库状态条件更新加Redis锁拦截。再补充一个实操小技巧回调处理的关键业务操作比如更新订单状态、写支付流水、发消息通知业务方建议放在同一个事务里并且把事务控制在最短时间。这样即使并发回调到达第一个事务提交后第二个事务进来也能立刻判断出订单已经处理过直接返回成功。6.4 开发联调阶段的沙箱和正式环境切换沙箱环境做联调确实很方便但切换环境的过程里翻车的案例也不少。最典型的是换环境时只改了AppID和商户号忘了换公钥或证书结果一上线验签全挂。我的建议是所有渠道的环境相关配置都放在配置文件的独立profile里不要和业务配置混在一起。源码里已经有application-dev.yml、application-test.yml、application-prod.yml三个环境文件每个文件里都有对应渠道的完整配置项包括支付宝公钥、微信平台证书路径、银联证书路径。切换环境只需要修改spring.profiles.active这一个配置项即可。另外切记沙箱和正式环境的回调验签公钥不同微信的沙箱环境和正式环境的证书目录也不同所以切换环境时要确认conf文件里指向的证书路径确实存在。我经历过一次线上故障原因是某次发版后application-prod.yml里的微信平台证书路径仍指向沙箱目录到证书过期时间的临时文件无法写入结果回调验签直接抛异常这个问题排查了整整一个下午。后来我在程序启动时加了一个环境自检如果当前profile是prod但证书文件路径包含test字样启动就告警。这个自检逻辑也写在了源码里。6.5 遇到支付超时和订单状态不一致怎么办最后处理一个实战中经常被问到的问题用户明明付了钱但订单状态还是待支付或者是支付完成了但退款却一直处理中。这种情况绝大多数都不是支付渠道的问题而是本地订单状态和渠道侧状态不一致导致的。排查套路我一般分三步。第一步查支付渠道商户平台的订单列表确认用户那笔交易在渠道侧的真实状态。第二步查本地pay_order表和pay_notify_log表确认回调有没有到达、到达后有没有处理成功、处理失败的原因是什么。第三步手动触发一次主动查询调用PaymentService.queryPayment把渠道侧状态同步回本地。这套源码里提供了一个管理员接口可以直接输入商户订单号触发查询上线运营后维护人员用得非常频繁。还有一个加分项是补偿任务的监控。OrderCompensateTask每次跑完把扫描到的异常订单数和处理结果写入日志我建议你接上告警一旦出现掉单补单量突增说明回调链路可能出了故障需要及时介入。写在最后的经验之谈这套源码从写第一行代码到整理成完整项目差不多花了两周的时间中间反复翻了三家厂商的官方文档和SDK源码才把各种边界情况打磨到位。我个人做支付系统最大的感受是千万不要把支付当成不过是调几个接口的简单活——回调的可靠到达、状态的一致性保障、金额的精度控制每一环都是真金白银的教训换来的。如果你正准备上手支付相关的开发我的建议是先通读一遍这套源码里common层的工具类和config层的配置类再逐个渠道过实现类。动手改代码之前先在沙箱环境把下单、回调、查询、退款这个完整闭环跑通一遍比什么都强。支付系统的核心不在于代码多花哨而在于边界情况的处理是否周全这一点在项目上线后你会体会得越来越深。本文还有配套的精品资源点击获取
返回列表