微信支付集成实战:工具包设计与核心模块实现
发布时间:2026/10/24 4:55:44 锦皓数字建站

1. 从一个真实需求说起为什么我要啃微信支付集成这块硬骨头做移动端和Web端项目的朋友迟早都会撞上“支付”这堵墙。尤其是国内的项目微信支付几乎是绕不开的选项。我最早接触微信支付是在一个电商类的小程序项目里当时想得特别简单——不就是调个API、传个订单号、等回调吗结果真正上手才发现从统一下单到签名验证从证书管理到回调幂等每一步都有坑而且坑坑不一样。这篇文章我想聊的不是那种“复制粘贴官方文档”的教程而是从一个实际开发者的角度把微信支付集成这件事拆开揉碎。我会重点讲清楚工具包的设计思路、核心模块的实现细节、以及我在真实项目中踩过的那些坑。不管你是刚接触支付集成的新手还是已经做过几套支付系统的老手应该都能从中找到一些有用的东西。先明确一下范围这里讨论的“工具包”指的是我们自己封装的一套支付集成中间层不是微信官方的SDK本身。官方SDK提供了基础的签名、验签、请求发送能力但真正在业务系统里用起来还需要做大量的适配和封装工作。这套工具包要解决的问题包括统一的下单接口、回调的幂等处理、证书的自动管理、多商户配置的隔离、以及异常情况的兜底策略。适合谁看如果你正在做或者准备做微信支付集成不管是小程序、公众号、H5还是Native支付这篇文章里的思路和代码结构都可以直接参考。如果你已经集成完了但总觉得不够稳也可以看看我在“常见问题与排查”那部分总结的经验说不定能帮你省下几个通宵。2. 工具包整体设计与核心思路拆解2.1 为什么不直接用官方SDK官方SDK能用吗能用。但直接用的后果就是你的业务代码里会散落着大量的签名逻辑、HTTP请求、XML解析、证书加载代码。我见过一个项目光是处理支付回调的代码就写了八百多行里面混杂着业务逻辑、验签、日志、异常处理维护起来简直是噩梦。封装工具包的核心目的就三个字隔离性。把支付相关的技术细节全部收拢到一个独立的模块里业务层只需要调用payService.createOrder()和payService.handleCallback()两个方法剩下的签名、请求、验签、解密、幂等全部由工具包内部消化。这样做的好处是当微信支付API升级或者需要切换支付渠道时只需要改工具包这一层业务代码几乎不用动。另一个考虑是可测试性。支付这东西没法在本地环境真实调用必须有一套完整的Mock机制。工具包里我们把微信支付的HTTP客户端抽象成了一个接口测试环境下注入Mock实现可以模拟各种成功、失败、超时的场景不用真的去调微信的接口就能跑完整个支付流程的单元测试。2.2 分层架构设计工具包整体分为四层从下往上依次是基础层负责HTTP通信、签名算法、XML/JSON序列化、证书加载。这一层不包含任何业务逻辑纯粹是技术工具。协议层封装微信支付的各个API接口比如统一下单、订单查询、退款、关闭订单。每个接口对应一个Request和Response对象参数校验和签名在这一层完成。服务层提供面向业务的门面接口比如createOrder、queryOrder、refund。这一层负责编排多个协议层调用处理重试和降级。回调层专门处理微信服务器的异步通知包括验签、解密、幂等检查、业务分发。这样分层的好处是职责清晰。比如要新增一个“分账”功能只需要在协议层加一个接口服务层加一个方法回调层加一个通知类型不会影响到已有的代码。2.3 核心配置项的设计配置管理是支付工具包里最容易被忽视但最重要的部分。我见过太多项目把商户号、API密钥、证书路径硬编码在代码里换一个环境就要改代码重新打包。正确的做法是把所有配置项抽出来支持多环境、多商户的动态加载。工具包里我们定义了这样一个配置结构wxpay: merchants: - merchantId: 1000001 appId: wx1234567890 apiV3Key: your-api-v3-key privateKeyPath: classpath:cert/apiclient_key.pem certSerialNo: your-cert-serial notifyUrl: https://your-domain.com/pay/callback - merchantId: 1000002 appId: wx0987654321 apiV3Key: another-api-v3-key privateKeyPath: classpath:cert/apiclient_key_2.pem certSerialNo: another-cert-serial notifyUrl: https://your-domain.com/pay/callback关键点在于merchants是一个列表支持配置多个商户。每个商户有独立的密钥和证书互不干扰。业务层调用时通过merchantId来指定使用哪个商户的配置。这个设计在服务商模式下特别有用一个系统可以同时为多个子商户处理支付。注意APIv3密钥和证书私钥绝对不能提交到代码仓库。我们用的是配置中心加密存储本地开发环境通过环境变量注入。这一点后面在“避坑经验”里还会详细说。3. 核心模块深入解析与实操要点3.1 签名与验签支付安全的第一道门微信支付的签名机制是整个集成里最核心也最容易出错的部分。V3版本用的是SHA256withRSA算法签名串的构造规则是HTTP方法 URL路径 时间戳 随机串 请求体。这五个部分用换行符拼接然后用商户私钥签名最后Base64编码放到Authorization头里。我见过最常见的错误是URL路径拼错了。比如请求的完整URL是https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi签名时用的路径必须是/v3/pay/transactions/jsapi不能带域名也不能带查询参数。这个细节官方文档里写了但很多人第一次做的时候还是会搞错。工具包里我们把签名逻辑封装成了一个独立的Signer类public class WxPaySigner { private final PrivateKey privateKey; private final String certSerialNo; public String sign(String method, String urlPath, String timestamp, String nonceStr, String body) { String message method \n urlPath \n timestamp \n nonceStr \n body \n; Signature sign Signature.getInstance(SHA256withRSA); sign.initSign(privateKey); sign.update(message.getBytes(StandardCharsets.UTF_8)); byte[] signature sign.sign(); return Base64.getEncoder().encodeToString(signature); } public String buildAuthorizationHeader(String method, String urlPath, String body) { String timestamp String.valueOf(System.currentTimeMillis() / 1000); String nonceStr UUID.randomUUID().toString().replace(-, ); String signature sign(method, urlPath, timestamp, nonceStr, body); return WECHATPAY2-SHA256-RSA2048 mchid\ merchantId \, nonce_str\ nonceStr \, timestamp\ timestamp \, serial_no\ certSerialNo \, signature\ signature \; } }验签则是反过来用微信平台的公钥对回调通知里的签名进行验证。这里有个关键点微信平台证书是会定期更换的所以不能把证书写死在代码里必须实现自动下载和更新机制。工具包里我们用一个定时任务每天检查一次平台证书发现序列号变化就重新下载。3.2 统一下单从参数校验到请求发送统一下单是支付流程的起点也是最复杂的接口之一。以JSAPI支付为例需要传入的参数包括appid、mchid、description、out_trade_no、notify_url、amount、payer等。每个参数都有格式要求比如out_trade_no只能是数字、字母、下划线、横线长度6到32位。工具包里我们在协议层做了严格的参数校验用注解的方式声明每个字段的约束public class JsapiOrderRequest { NotBlank Size(min 6, max 32) Pattern(regexp ^[0-9A-Za-z_-]$) private String outTradeNo; NotNull Min(1) private Integer totalAmount; NotBlank Size(max 127) private String description; NotBlank private String notifyUrl; NotBlank private String openid; // ... 省略getter/setter }校验通过后序列化成JSON调用签名器生成Authorization头然后通过HTTP客户端发送POST请求。这里有个细节微信支付的APIv3接口要求请求头里必须带Accept: application/json和Content-Type: application/json少一个都可能返回400错误。请求发送后拿到响应要判断HTTP状态码。200表示成功其他状态码要根据响应体里的code字段来判断具体错误。比如PARAM_ERROR表示参数错误SIGN_ERROR表示签名错误FREQUENCY_LIMITED表示频率限制。工具包里我们把常见的错误码映射成了枚举方便业务层做针对性处理。3.3 回调处理幂等、验签、解密三件套支付回调是整个流程里最考验系统稳定性的环节。微信服务器会在支付成功后多次推送通知直到收到成功响应为止。所以回调处理必须做到幂等——同一笔订单的回调处理多次结果要一致。工具包里的回调处理流程是这样的验签从请求头里取出Wechatpay-Signature、Wechatpay-Timestamp、Wechatpay-Nonce、Wechatpay-Serial用平台公钥验证签名。验签失败直接返回失败不处理业务。解密回调通知的resource字段是加密的用APIv3密钥进行AES-256-GCM解密得到明文的通知内容。幂等检查根据解密后的out_trade_no查询本地订单状态如果已经是“已支付”直接返回成功不重复处理。业务处理更新订单状态、记录支付流水、触发后续业务逻辑比如发货、通知用户。返回响应处理成功返回{code: SUCCESS}失败返回{code: FAIL, message: ...}。幂等检查这里有个坑并发情况下两个回调同时到达都查到订单是“待支付”然后都去更新订单状态就会导致重复处理。解决办法是用数据库的行锁或者分布式锁。工具包里我们用的是SELECT ... FOR UPDATE在事务里锁定订单行确保只有一个线程能处理。Transactional public void handleCallback(String outTradeNo, String transactionId) { Order order orderMapper.selectForUpdate(outTradeNo); if (order null) { throw new BizException(订单不存在); } if (OrderStatus.PAID.equals(order.getStatus())) { return; // 幂等直接返回 } order.setStatus(OrderStatus.PAID); order.setTransactionId(transactionId); orderMapper.updateById(order); // 触发后续业务 eventPublisher.publishEvent(new OrderPaidEvent(order)); }提示回调接口的URL必须是HTTPS的而且不能带查询参数。微信服务器会校验域名是否在商户平台配置过没配置的域名会直接拒绝。3.4 证书管理与自动更新微信支付的证书体系分两类商户私钥和平台证书。商户私钥是自己生成的用于签名平台证书是微信提供的用于验签。商户私钥相对简单生成一次就行但平台证书会定期更换必须实现自动更新。工具包里我们用一个CertManager来管理证书Component public class CertManager { private volatile MapString, X509Certificate platformCerts new ConcurrentHashMap(); Scheduled(cron 0 0 3 * * ?) // 每天凌晨3点更新 public void refreshPlatformCerts() { ListCertificateInfo certs wxPayClient.downloadPlatformCerts(); MapString, X509Certificate newCerts new HashMap(); for (CertificateInfo cert : certs) { newCerts.put(cert.getSerialNo(), parseCert(cert.getCertPem())); } this.platformCerts newCerts; } public X509Certificate getCert(String serialNo) { X509Certificate cert platformCerts.get(serialNo); if (cert null) { refreshPlatformCerts(); // 没找到就立即刷新一次 cert platformCerts.get(serialNo); } return cert; } }这里的关键是volatile和ConcurrentHashMap保证多线程环境下证书切换的可见性和线程安全。另外当验签时发现序列号在本地缓存里找不到要立即触发一次刷新而不是直接报错。因为微信可能在你还没到定时任务时间就换了证书。4. 完整实操流程与核心环节实现4.1 环境准备与依赖引入先说一下技术栈。工具包是基于Java 8和Spring Boot 2.x开发的HTTP客户端用的是OkHttpJSON处理用的是JacksonXML处理用的是Jackson的XML扩展。如果你用的是其他语言思路是一样的只是具体实现不同。Maven依赖大概是这样dependency groupIdcom.squareup.okhttp3/groupId artifactIdokhttp/artifactId version4.9.3/version /dependency dependency groupIdcom.fasterxml.jackson.dataformat/groupId artifactIdjackson-dataformat-xml/artifactId version2.13.0/version /dependency dependency groupIdorg.bouncycastle/groupId artifactIdbcprov-jdk15on/artifactId version1.70/version /dependencyBouncyCastle是用来处理证书和加密的Java自带的加密库在某些场景下不够用比如加载PKCS#8格式的私钥。这个依赖加上之后记得在代码里注册Providerstatic { Security.addProvider(new BouncyCastleProvider()); }4.2 商户私钥的生成与配置商户私钥的生成有两种方式用微信提供的工具生成或者用OpenSSL自己生成。我推荐用OpenSSL因为更灵活而且生成的私钥格式更标准。# 生成私钥 openssl genrsa -out apiclient_key.pem 2048 # 从私钥导出公钥 openssl rsa -in apiclient_key.pem -pubout -out apiclient_pub.pem # 查看私钥的序列号用于配置certSerialNo openssl x509 -in apiclient_cert.pem -noout -serial生成的私钥文件apiclient_key.pem要放到项目的资源目录下但不要提交到Git。我们用的是.gitignore排除然后在部署时通过配置中心或者环境变量注入。加载私钥的代码public PrivateKey loadPrivateKey(String keyPath) throws Exception { String content new String(Files.readAllBytes(Paths.get(keyPath))); content content.replace(-----BEGIN PRIVATE KEY-----, ) .replace(-----END PRIVATE KEY-----, ) .replaceAll(\\s, ); byte[] keyBytes Base64.getDecoder().decode(content); PKCS8EncodedKeySpec spec new PKCS8EncodedKeySpec(keyBytes); KeyFactory factory KeyFactory.getInstance(RSA); return factory.generatePrivate(spec); }4.3 统一下单的完整调用链以JSAPI支付为例从业务层发起支付到拿到前端调起支付所需的参数完整流程如下第一步业务层创建订单PostMapping(/order/create) public ResultPayParams createOrder(RequestBody CreateOrderRequest request) { // 1. 创建本地订单 Order order orderService.createOrder(request); // 2. 调用支付工具包统一下单 JsapiOrderRequest payRequest new JsapiOrderRequest(); payRequest.setOutTradeNo(order.getOrderNo()); payRequest.setTotalAmount(order.getAmount()); payRequest.setDescription(order.getSubject()); payRequest.setNotifyUrl(wxPayConfig.getNotifyUrl()); payRequest.setOpenid(request.getOpenid()); JsapiOrderResponse payResponse wxPayService.createJsapiOrder(payRequest); // 3. 组装前端调起支付所需的参数 PayParams params new PayParams(); params.setAppId(wxPayConfig.getAppId()); params.setTimeStamp(String.valueOf(System.currentTimeMillis() / 1000)); params.setNonceStr(UUID.randomUUID().toString().replace(-, )); params.setPackageValue(prepay_id payResponse.getPrepayId()); params.setSignType(RSA); params.setPaySign(wxPaySigner.sign(params)); return Result.success(params); }第二步工具包内部处理工具包收到JsapiOrderRequest后先做参数校验然后构造请求体{ appid: wx1234567890, mchid: 1000001, description: 测试商品, out_trade_no: ORDER202401010001, notify_url: https://your-domain.com/pay/callback, amount: { total: 100, currency: CNY }, payer: { openid: oUpF8uMuAJO_M2pxb1Q9zNjWeS6o } }然后生成签名发送POST请求到https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi。微信返回的响应里包含prepay_id这个就是前端调起支付的关键参数。第三步前端调起支付前端拿到PayParams后调用wx.chooseWXPaywx.chooseWXPay({ appId: params.appId, timeStamp: params.timeStamp, nonceStr: params.nonceStr, package: params.packageValue, signType: params.signType, paySign: params.paySign, success: function(res) { // 支付成功等待回调 }, fail: function(res) { // 支付失败 } });4.4 回调接口的实现与部署回调接口必须是一个公网可访问的HTTPS接口。我们用的是Spring Boot的RestControllerPostMapping(/pay/callback) public String handleCallback(RequestBody String body, RequestHeader(Wechatpay-Signature) String signature, RequestHeader(Wechatpay-Timestamp) String timestamp, RequestHeader(Wechatpay-Nonce) String nonce, RequestHeader(Wechatpay-Serial) String serial) { try { // 1. 验签 boolean verified wxPaySigner.verifyCallback(signature, timestamp, nonce, body, serial); if (!verified) { log.error(回调验签失败); return {\code\:\FAIL\,\message\:\验签失败\}; } // 2. 解密 CallbackResource resource wxPayDecryptor.decrypt(body); // 3. 幂等处理 orderService.handlePayCallback(resource.getOutTradeNo(), resource.getTransactionId()); // 4. 返回成功 return {\code\:\SUCCESS\}; } catch (Exception e) { log.error(回调处理异常, e); return {\code\:\FAIL\,\message\:\ e.getMessage() \}; } }部署的时候有几个注意点回调接口不能有鉴权拦截器否则微信服务器访问不了接口的响应时间要控制在5秒以内超时微信会重试接口必须支持POST方法而且请求体是原始JSON字符串不能被框架提前解析。4.5 订单查询与退款实现订单查询和退款是支付集成的配套功能。订单查询比较简单调用GET /v3/pay/transactions/out-trade-no/{out_trade_no}?mchid{mchid}就行。退款稍微复杂一点需要传入退款单号、原订单号、退款金额等参数。退款接口的调用示例public RefundResponse refund(String outTradeNo, String outRefundNo, Integer refundAmount, Integer totalAmount) { RefundRequest request new RefundRequest(); request.setOutTradeNo(outTradeNo); request.setOutRefundNo(outRefundNo); request.setAmount(new RefundAmount(totalAmount, refundAmount, CNY)); String urlPath /v3/refund/domestic/refunds; String body objectMapper.writeValueAsString(request); String authorization wxPaySigner.buildAuthorizationHeader(POST, urlPath, body); Request httpRequest new Request.Builder() .url(https://api.mch.weixin.qq.com urlPath) .post(RequestBody.create(body, MediaType.parse(application/json))) .header(Authorization, authorization) .header(Accept, application/json) .header(Content-Type, application/json) .build(); try (Response response httpClient.newCall(httpRequest).execute()) { String responseBody response.body().string(); if (response.isSuccessful()) { return objectMapper.readValue(responseBody, RefundResponse.class); } else { throw new WxPayException(退款失败: responseBody); } } }退款是异步的调用成功后微信会返回一个退款单号实际的退款结果通过退款回调通知。退款回调的处理逻辑和支付回调类似也需要验签、解密、幂等处理。5. 常见问题与排查技巧实录5.1 签名错误排查指南签名错误是集成过程中最高频的问题没有之一。微信返回的错误信息通常是SIGN_ERROR但不会告诉你具体哪里错了。我总结了一个排查清单排查项常见错误解决方法URL路径带了域名或查询参数只保留路径部分如/v3/pay/transactions/jsapi请求体序列化后的JSON和签名时用的不一致签名和发送用同一个字符串不要重新序列化时间戳单位搞错用了毫秒必须是秒级时间戳10位数字私钥格式不对或者加载错了确认是PKCS#8格式且和证书序列号匹配换行符拼接时少了或多了换行每个部分后面都要有\n包括最后一行我遇到过一次特别隐蔽的签名错误请求体里的中文被URL编码了但签名时用的是原始中文导致签名不一致。后来统一用UTF-8编码问题解决。5.2 回调收不到或重复回调回调收不到的原因通常有三个回调URL配置错误、服务器防火墙拦截、接口返回了非200状态码。排查的时候先在微信商户平台查看回调记录确认微信是否发送了通知。如果发送了但你没收到检查服务器的访问日志看看有没有请求进来。重复回调是正常现象微信在收到成功响应之前会一直重试。但如果你的接口处理时间过长微信可能会认为超时然后重试。解决办法是把业务处理异步化回调接口只做验签、解密、落库然后立即返回成功后续业务逻辑通过消息队列异步处理。PostMapping(/pay/callback) public String handleCallback(RequestBody String body, ...) { // 验签、解密 CallbackResource resource wxPayDecryptor.decrypt(body); // 快速落库 callbackLogMapper.insert(new CallbackLog(resource)); // 发送消息队列异步处理 messageQueue.send(new PayCallbackMessage(resource.getOutTradeNo())); // 立即返回成功 return {\code\:\SUCCESS\}; }5.3 证书过期与自动更新失败平台证书过期会导致验签失败所有回调都处理不了。工具包里虽然做了自动更新但更新本身也可能失败比如网络问题、权限问题。所以需要加一个告警机制当证书距离过期还有7天时发送告警通知当验签失败率超过阈值时也发送告警。另外平台证书下载接口本身也需要签名用的是商户私钥。如果商户私钥也过期了那就彻底没法自动更新了。所以商户私钥的过期时间也要监控提前一个月告警。5.4 金额精度与货币单位微信支付的金额单位是分不是元。这个看起来简单但实际开发中很容易搞错。比如商品价格是9.9元传给微信的必须是990。我见过一个项目前端传的是元后端没转换直接传给微信结果用户付了9.9分也就是不到一毛钱。工具包里我们在参数校验阶段就做了检查如果金额小于1或者大于100000000100万元直接抛出异常。同时所有金额相关的字段都用Integer类型避免浮点数精度问题。5.5 多商户配置隔离问题服务商模式下一个系统要处理多个商户的支付。如果配置隔离没做好可能会出现A商户的订单用了B商户的证书签名导致签名错误。工具包里的做法是每个商户有独立的WxPayService实例通过merchantId来路由。业务层调用时必须显式传入merchantId不允许使用默认值。Service public class WxPayServiceFactory { private MapString, WxPayService services new ConcurrentHashMap(); public WxPayService getService(String merchantId) { WxPayService service services.get(merchantId); if (service null) { throw new BizException(未配置的商户: merchantId); } return service; } }5.6 网络超时与重试策略微信支付的API调用偶尔会超时尤其是网络波动的时候。工具包里配置了超时时间和重试策略连接超时5秒读取超时10秒重试3次每次间隔1秒。但要注意统一下单接口不能盲目重试因为重试可能导致重复下单。正确的做法是超时后先调用订单查询接口确认订单状态如果订单已存在就直接返回不存在再重新下单。public JsapiOrderResponse createOrderWithRetry(JsapiOrderRequest request) { try { return doCreateOrder(request); } catch (TimeoutException e) { // 超时后查询订单 QueryOrderResponse query queryOrder(request.getOutTradeNo()); if (query ! null SUCCESS.equals(query.getTradeState())) { return convertToOrderResponse(query); } // 订单不存在重新下单 return doCreateOrder(request); } }6. 一些实战中攒下来的避坑经验6.1 日志脱敏与安全审计支付相关的日志绝对不能包含完整的请求体和响应体因为里面可能有用户的openid、订单金额等敏感信息。工具包里我们做了日志脱敏只记录订单号、金额、状态码不记录完整的JSON。如果需要排查问题可以通过订单号去数据库里查详细的请求记录但数据库里的敏感字段也是加密存储的。另外所有支付相关的操作都要记录审计日志包括谁在什么时间发起了退款、修改了配置等。这个在合规审计的时候特别重要。6.2 测试环境的Mock方案微信支付没有沙箱环境所有测试都必须用真实的小额支付。这在开发阶段很不方便。我们的做法是在工具包里内置一个Mock模式通过配置开关切换。Mock模式下统一下单直接返回一个假的prepay_id回调接口模拟微信发送通知这样可以完整地跑通整个支付流程不用花一分钱。Profile(dev) Configuration public class MockWxPayConfig { Bean public WxPayClient mockWxPayClient() { return new MockWxPayClient(); } }Mock客户端的行为可以配置比如模拟支付成功、模拟支付失败、模拟回调延迟等。这样测试用例可以覆盖各种异常场景。6.3 对账与异常订单处理每天凌晨要跑一次对账任务下载微信的账单和本地的订单流水做比对。不一致的订单要标记出来人工处理。常见的不一致情况包括本地订单已支付但微信账单没有、微信账单有但本地订单状态不对、金额不一致等。对账文件的下载接口是GET /v3/bill/tradebill?bill_date2024-01-01bill_typeALL返回的是一个CSV文件的下载链接。下载后解析CSV逐行比对。这个任务比较耗时建议用异步任务处理不要阻塞主线程。6.4 版本升级与兼容性微信支付的API版本从V2升级到V3之后签名算法、数据格式、证书体系都变了。工具包里我们同时支持V2和V3通过配置来切换。但新项目建议直接用V3V2迟早会下线。升级的时候要注意V3的证书序列号是必须的V2用的是MD5密钥V3的请求体是JSONV2是XMLV3的回调是加密的V2是明文的。这些差异在工具包里都做了适配业务层不需要关心。6.5 性能优化与并发处理支付接口的QPS通常不会太高但回调接口可能会在短时间内收到大量通知比如秒杀活动结束后。工具包里对回调接口做了限流和队列化处理用信号量控制并发处理数超过阈值的请求放入队列等待避免把数据库打挂。private final Semaphore callbackSemaphore new Semaphore(50); PostMapping(/pay/callback) public String handleCallback(RequestBody String body, ...) { if (!callbackSemaphore.tryAcquire(3, TimeUnit.SECONDS)) { return {\code\:\FAIL\,\message\:\系统繁忙\}; } try { // 处理回调 } finally { callbackSemaphore.release(); } }这个信号量的值要根据数据库的连接池大小和机器的处理能力来调整。太小会导致回调处理慢微信重试太大会把数据库打挂。我们一般设置为数据库连接池最大连接数的80%。6.6 一个小技巧用订单号反查支付状态有时候业务系统出了问题订单状态不对但又不确定微信那边到底扣没扣钱。这时候可以用订单号去微信查一下支付状态。工具包里提供了一个queryOrder方法传入商户订单号就能查到微信侧的支付状态。如果微信显示已支付但本地是待支付说明回调丢了可以手动触发一次补单。public void repairOrder(String outTradeNo) { QueryOrderResponse response wxPayService.queryOrder(outTradeNo); if (SUCCESS.equals(response.getTradeState())) { orderService.handlePayCallback(outTradeNo, response.getTransactionId()); } }这个补单接口在运维的时候特别有用建议做成一个后台功能方便客服人员处理用户投诉。7. 最后聊几句个人体会这套工具包从第一版到现在前后迭代了两年多踩过的坑不计其数。最大的体会是支付集成这件事技术难度其实不算特别高但细节特别多而且每个细节都关系到真金白银。一个签名错误可能导致所有支付失败一个幂等没做好可能导致重复发货一个证书过期可能导致回调全部丢失。所以我的建议是不要急着写业务代码先把工具包的基础设施搭好。签名、验签、证书管理、幂等处理、日志脱敏、对账这些看似“外围”的功能其实才是支付系统稳定的关键。工具包做好之后业务层的代码会变得非常薄维护成本也会低很多。另外一定要重视测试。支付流程的测试用例要覆盖各种异常场景签名失败、网络超时、回调重复、证书过期、金额非法等等。这些场景在真实环境中可能几个月才遇到一次但一旦遇到就是生产事故。提前用Mock把这些场景跑通比出了问题再救火要划算得多。最后再分享一个小技巧把微信支付的官方文档加入书签经常翻一翻。微信的文档更新比较频繁有时候接口参数变了或者新增了功能文档里会有说明。我习惯每个月花半小时过一遍更新日志看看有没有影响现有集成的变更。这个习惯帮我避免了好几次潜在的故障。
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。