Repository navigation
Closed
Description
错误描述
微信支付成功,异步通知解密数据异常
运行环境
java:JDK17
依赖微信sdk:wechatpay-java;0.2.4版本
示例代码
// 使用自动更新平台证书的RSA配置
// 一个商户号只能初始化一个配置,否则会因为重复的下载任务报错
RSAAutoCertificateConfig config = new RSAAutoCertificateConfig.Builder()
.merchantId(WeiXinPayConstant.MCH_ID)
.privateKey(WeiXinPayConstant.MCH_PRIVATE_KEY)
.merchantSerialNumber(WeiXinPayConstant.MCH_SERIAL_NO)
.apiV3Key(WeiXinPayConstant.API_V3_KEY)
.build();
异常信息:
com.wechat.pay.java.core.exception.ValidationException: Processing WechatPay notification,signature verification failed,signType[WECHATPAY2-SHA256-RSA2048] serial[72A58B94F5350B13A124585D15BF88C1E55AB350] message[1672906824
WYDi5b6dyyi36Cry6Vd95cYW5EOrwwNo
{"summary":"支付成功","event_type":"TRANSACTION.SUCCESS","create_time":"2023-01-05T16:16:20+08:00","resource":{"original_type":"transaction","algorithm":"AEAD_AES_256_GCM","ciphertext":"dFZoL6Qa18H3/R0fpBvviskQ1xVaXQDkRSJETChAtBcYfmNvBOqmipI8u1yTTl5ddgjarOdAuK5npSjMjfc1/OEPaNx91R2kG82e3NFaXP3csvtSLCNRN6h1MFBlMPqHVGk8SoOKqgGQAxAmzeLUzUlRI9gQm8GDRvJYzsIkTdWs//2yZZAdEOLXe29Uu+Zj41Te+/iHR0JkmHup9XXMSOZ+cjwmoTlycYd9b0u7j1O4f54fG78jsP76jt6YhniJPhi1nN9Mb2v4wYzH6LHA3wWN4oPLQmXMORL8F+gqm88yyrf79XlcZgovTXM1cUIklDb0pGyennQXSfrzz7Z8dxPmmiLo8zp8XHMUtsb81W8lV6oC4c/nykd7j6NPoI8F6VUYDptCH6aZu/nvsMyCRRvalSFHvLYwIIRtNc1/VB49KKF1H4EncY/lx3gOdI4FSrt8Q/wYt+QPqZ/1QEs0QlFYW98TlXPtCC9pgveR4gZRUHdrvYwxwJT2Gn5rQn18vhZDfxrFaBZLxLYruNyyGRXkBMPqsa9i0Exu6e/gcEcvylo9yQUoJ1GDliOIBJ8eF9rELEos","associated_data":"transaction","nonce":"Zs7DNh4Njdvf"},"resource_type":"encrypt-resource","id":"a179bbb2-257f-5c07-826a-287d39b995f6"}
] sign[hpq6R1V4LO25TYN97VnhkPrRheXyn7WrtmYDLkeI4eF7O248YXYJC6TLCp/T57lNZBAyPtHz+4K0Rm7SqXAQzmGh/OvlAi3FTvXk/Wm8FOp59VqOerxw7HYD3UFItAS0rpozoIC5ha5b2FHqmGCp5l7OGIXLf9PnGW2s551im2BkBLb+1WW2f6udJywoGL/YGVZ4Jl7bwMFYf64GZAc0AIGQqRWBrL7zIiL8Nw4ajBZTiwDOlZiSJg9vzVaHJyj4nSKe8wPy5ZcPq2Hd4JZ0VUvakhiqyxjsz4KJE8nB8HQicMgBSZpuJPRPzxaWd/DB9x73XmCY2A26DRFfs0mZNA==]
at com.wechat.pay.java.core.notification.NotificationParser.validateRequest(NotificationParser.java:93)
at com.wechat.pay.java.core.notification.NotificationParser.parse(NotificationParser.java:49)
at com.pyrange.app.main.web.service.payment.weixin.WeixinPaymentServiceImpl.paymentSuccessNotify(WeixinPaymentServiceImpl.java:367)
at com.pyrange.app.main.web.controller.WeixinPaymentController.paymentSuccessNotify(WeixinPaymentController.java:37)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:568)
at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:343)
at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:196)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:752)
at org.springframework.aop.framework.adapter.AfterReturningAdviceInterceptor.invoke(AfterReturningAdviceInterceptor.java:57)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:173)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:752)
at org.springframework.aop.framework.adapter.MethodBeforeAdviceInterceptor.invoke(MethodBeforeAdviceInterceptor.java:58)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:173)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:752)
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:97)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:752)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:703)
at com.pyrange.app.main.web.controller.WeixinPaymentController$$SpringCGLIB$$0.paymentSuccessNotify(<generated>)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:568)
at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:207)
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:152)
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:117)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:884)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:797)
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1080)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:973)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1003)
at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:906)
at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:731)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:880)
at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:814)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:223)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:158)
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:185)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:158)
at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:185)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:158)
at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:185)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:158)
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:185)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:158)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:197)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:542)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:119)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:357)
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:400)
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:861)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1739)
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52)
at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191)
at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.base/java.lang.Thread.run(Thread.java:833)
重现bug的步骤
- 微信小程序支付
- 收到微信通知
预期行为
正常解析密文
导致错误的代码片段
Transaction parse = parser.parse(requestParam, Transaction.class);操作系统
win10
Java 版本
java 17
wechatpay-java 版本
0.2.4
其他信息
无
Metadata
Metadata
Assignees
Labels
No labels

Activity
xy-peng commentedon Jan 5, 2023
body要使用原始的报文,而不是
toJSONString()的字符串。经过序列化和反序列化之后,跟原文不一致。402626224 commentedon Jan 9, 2023
这样子也是验签不通过
Roc1991x commentedon Nov 5, 2023
能给一个获取原始body的例子么?
liguangling-lgl commentedon Jan 13, 2024
有的极少订单回调会报这个解签错误,大部分订单可以成功,这个是body读取的代码示例,不清楚为什么会这样,微信sdk版本是0.2.7
AndromedaX7 commentedon May 27, 2024
感谢大佬们先踩坑了,让咱吃了现成的了
helloWorld233333333 commentedon Jul 1, 2024
我猜你要找的是‘签名探测流量’,https://pay.weixin.qq.com/docs/merchant/development/interface-rules/signature-verification.html
songfayuan commentedon Dec 16, 2024
`package com.github.modules.miniapp.service.impl;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.github.common.utils.HttpServletUtils;
import com.github.common.utils.Response;
import com.github.config.wx.WxPayConfig;
import com.github.modules.miniapp.entity.CreateOrderReq;
import com.github.modules.miniapp.entity.PayOrderInfo;
import com.github.modules.miniapp.entity.QueryOrderReq;
import com.github.modules.miniapp.entity.RefundOrderReq;
import com.github.modules.miniapp.service.WxMiniappPayService;
import com.wechat.pay.java.core.Config;
import com.wechat.pay.java.core.exception.HttpException;
import com.wechat.pay.java.core.exception.MalformedMessageException;
import com.wechat.pay.java.core.exception.ServiceException;
import com.wechat.pay.java.core.exception.ValidationException;
import com.wechat.pay.java.core.notification.NotificationConfig;
import com.wechat.pay.java.core.notification.NotificationParser;
import com.wechat.pay.java.core.notification.RequestParam;
import com.wechat.pay.java.service.payments.jsapi.JsapiServiceExtension;
import com.wechat.pay.java.service.payments.jsapi.model.Amount;
import com.wechat.pay.java.service.payments.jsapi.model.;
import com.wechat.pay.java.service.payments.model.Transaction;
import com.wechat.pay.java.service.refund.RefundService;
import com.wechat.pay.java.service.refund.model.;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.math.BigDecimal;
import java.util.HashMap;
import java.util.Map;
/**
微信小程序支付服务实现
@author songfayuan
@Date 2024/9/14 14:23
*/
@slf4j
@service
public class WxMiniappPayServiceImpl implements WxMiniappPayService {
@Autowired
private WxPayConfig wxPayConfig;
@Autowired
@qualifier("rsaAutoCertificateConfig")
private Config config;
@Autowired
@qualifier("rsaAutoCertificateConfig")
private Config notificationConfig;
/**
预支付订单/统一下单
@PARAM req
@return
*/
@OverRide
public Response createOrder(CreateOrderReq req) {
// 创建本地订单
// 这里做本地业务相关的处理,包括生成一个订单号传递给微信,等于通过这个值来形成两边的数据对应。后续微信那边会返回他们的订单编号,也建议存在自己这边的数据库里。
PayOrderInfo order = new PayOrderInfo();
order.setOutTradeNo("test_out_trade_no_" + System.currentTimeMillis());
order.setDescription("测试订单");
order.setAmount(new BigDecimal("0.01"));
// 请求微信支付相关配置
JsapiServiceExtension service = new JsapiServiceExtension.Builder()
.config(config)
.signType("RSA")
.build();
PrepayWithRequestPaymentResponse response = new PrepayWithRequestPaymentResponse();
try {
PrepayRequest request = new PrepayRequest();
request.setAppid(wxPayConfig.getAppid());
request.setMchid(wxPayConfig.getMerchantId());
request.setDescription(order.getDescription());
request.setOutTradeNo(order.getOutTradeNo());
request.setNotifyUrl(wxPayConfig.getPayNotifyUrl());
Amount amount = new Amount();
// 微信支付的单位是分,这里都需要乘以100
amount.setTotal(order.getAmount().multiply(new BigDecimal("100")).intValue());
request.setAmount(amount);
Payer payer = new Payer();
payer.setOpenid(req.getWxOpenId());
request.setPayer(payer);
log.info("请求预支付下单,请求参数:{}", JSONObject.toJSONString(request));
// 调用预下单接口
response = service.prepayWithRequestPayment(request);
log.info("订单【{}】发起预支付成功,返回信息:{}", order.getOutTradeNo(), JSONObject.toJSONString(response));
} catch (HttpException e) {
// 发送HTTP请求失败
log.error("微信下单发送HTTP请求失败,错误信息:", e);
return Response.error("下单失败");
} catch (ServiceException e) {
// 服务返回状态小于200或大于等于300,例如500
log.error("微信下单服务状态错误,错误信息:", e);
return Response.error("下单失败");
} catch (MalformedMessageException e) {
// 服务返回成功,返回体类型不合法,或者解析返回体失败
log.error("服务返回成功,返回体类型不合法,或者解析返回体失败,错误信息:", e);
return Response.error("下单失败");
} catch (ValidationException e) {
// 验证签名失败
log.error("微信下单验证签名失败,错误信息:", e);
return Response.error("下单失败");
} catch (Exception e) {
log.error("微信下单失败,错误信息:", e);
return Response.error("下单失败");
}
// TODO 更新订单状态
// 这里就可以更新订单状态为待支付之类的
return Response.success(response);
}
/**
支付回调
注意:
对后台通知交互时,如果微信收到应答不是成功或超时,微信认为通知失败,微信会通过一定的策略定期重新发起通知,尽可能提高通知的成功率,但微信不保证通知最终能成功
同样的通知可能会多次发送给商户系统。商户系统必须能够正确处理重复的通知。 推荐的做法是,当商户系统收到通知进行处理时,先检查对应业务数据的状态,并判断该通知是否已经处理。如果未处理,则再进行处理;如果已处理,则直接返回结果成功。在对业务数据进行状态检查和处理之前,要采用数据锁进行并发控制,以避免函数重入造成的数据混乱。
如果在所有通知频率后没有收到微信侧回调。商户应调用查询订单接口确认订单状态。
@PARAM request
@return
@throws IOException
*/
@OverRide
public String payNotify(HttpServletRequest request) throws IOException {
// 请求头Wechatpay-Signature
String signature = request.getHeader("Wechatpay-Signature");
// 请求头Wechatpay-nonce
String nonce = request.getHeader("Wechatpay-Nonce");
// 请求头Wechatpay-Timestamp
String timestamp = request.getHeader("Wechatpay-Timestamp");
// 微信支付证书序列号
String serial = request.getHeader("Wechatpay-Serial");
// 签名方式
String signType = request.getHeader("Wechatpay-Signature-Type");
// 构造 RequestParam
RequestParam requestParam = new RequestParam.Builder()
.serialNumber(serial)
.nonce(nonce)
.signature(signature)
.timestamp(timestamp)
.signType(signType)
.body(HttpServletUtils.getRequestBody(request))
.build();
log.info("微信支付回调body信息:{}", requestParam.getBody());
// 初始化 NotificationParser
NotificationParser parser = new NotificationParser((NotificationConfig) notificationConfig);
// 以支付通知回调为例,验签、解密并转换成 Transaction
log.info("验签参数:{}", JSONObject.toJSONString(requestParam));
Map<String, String> returnMap = new HashMap<>(2);
returnMap.put("code", "FAIL");
returnMap.put("message", "失败");
Transaction transaction = null;
try {
transaction = parser.parse(requestParam, Transaction.class);
} catch (MalformedMessageException e) {
log.error("验签失败,解析微信支付应答或回调报文异常,返回信息:", e);
return JSONObject.toJSONString(returnMap);
} catch (ValidationException e) {
log.error("验签失败,验证签名失败,返回信息:", e);
return JSONObject.toJSONString(returnMap);
} catch (Exception e) {
log.error("验签失败,返回信息:", e);
return JSONObject.toJSONString(returnMap);
}
log.info("验签成功!-支付回调结果:{}", transaction.toString());
// 判断订单状态
if (Transaction.TradeStateEnum.SUCCESS != transaction.getTradeState()) {
log.info("内部订单号【{}】,微信支付订单号【{}】支付未成功", transaction.getOutTradeNo(), transaction.getTransactionId());
return JSONObject.toJSONString(returnMap);
}
// TODO 修改订单信息
returnMap.put("code", "SUCCESS");
returnMap.put("message", "成功");
return JSONObject.toJSONString(returnMap);
}
/**
根据支付订单号查询订单
需要调用查询接口的情况:
当商户后台、网络、服务器等出现异常,商户系统最终未接收到支付通知。
调用支付接口后,返回系统错误或未知交易状态情况。
调用付款码支付API,返回USERPAYING的状态。
调用关单或撤销接口API之前,需确认支付状态。
@PARAM req
@return
*/
@OverRide
public Response queryOrder(QueryOrderReq req) {
QueryOrderByIdRequest queryRequest = new QueryOrderByIdRequest();
queryRequest.setMchid(wxPayConfig.getMerchantId());
queryRequest.setTransactionId(req.getTransactionId());
try {
JsapiServiceExtension service =
new JsapiServiceExtension.Builder()
.config(config)
.signType("RSA")
.build();
Transaction result = service.queryOrderById(queryRequest);
if (Transaction.TradeStateEnum.SUCCESS != result.getTradeState()) {
log.info("内部订单号【{}】,微信支付订单号【{}】支付未成功", result.getOutTradeNo(), result.getTransactionId());
return Response.error(result.getTradeStateDesc());
}
log.info("根据支付订单号查询订单:内部订单号【{}】,微信支付订单号【{}】支付成功", result.getOutTradeNo(), result.getTransactionId());
log.info("根据支付订单号查询订单:订单数据data = {}", JSONObject.toJSONString(result));
} catch (ServiceException e) {
log.error("根据支付订单号查询订单:订单查询失败,发送HTTP请求成功,返回异常,返回码:{},返回信息:", e.getErrorCode(), e);
return Response.error("订单查询失败");
} catch (MalformedMessageException e) {
log.error("根据支付订单号查询订单:订单查询失败,解析微信支付应答或回调报文异常,返回信息:", e);
return Response.error("订单查询失败");
} catch (ValidationException e) {
log.error("根据支付订单号查询订单:订单查询失败,验证签名失败,返回信息:", e);
return Response.error("验证签名失败");
} catch (HttpException e) {
log.error("根据支付订单号查询订单:订单查询失败,发送HTTP请求失败:", e);
return Response.error("订单查询失败");
} catch (Exception e) {
log.error("根据支付订单号查询订单:订单查询失败,异常:", e);
return Response.error("订单查询失败");
}
}
/**
根据商户订单号查询订单
需要调用查询接口的情况:
当商户后台、网络、服务器等出现异常,商户系统最终未接收到支付通知。
调用支付接口后,返回系统错误或未知交易状态情况。
调用付款码支付API,返回USERPAYING的状态。
调用关单或撤销接口API之前,需确认支付状态。
@PARAM req
@return
/
@OverRide
public Response queryOrderByOutTradeNo(QueryOrderReq req) {
QueryOrderByOutTradeNoRequest queryRequest = new QueryOrderByOutTradeNoRequest();
queryRequest.setMchid(wxPayConfig.getMerchantId());
queryRequest.setOutTradeNo(req.getOutTradeNo());
try {
JsapiServiceExtension service =
new JsapiServiceExtension.Builder()
.config(config)
.signType("RSA")
.build();
Transaction result = service.queryOrderByOutTradeNo(queryRequest);
// trade_state【交易状态】交易状态,枚举值:SUCCESS:支付成功REFUND:转入退款NOTPAY:未支付CLOSED:已关闭REVOKED:已撤销(仅付款码支付会返回)*USERPAYING:用户支付中(仅付款码支付会返回)*PAYERROR:支付失败(仅付款码支付会返回)
if (Transaction.TradeStateEnum.SUCCESS != result.getTradeState()) {
log.info("内部订单号【{}】,微信支付订单号【{}】支付未成功", result.getOutTradeNo(), result.getTransactionId());
return Response.error(result.getTradeStateDesc());
}
log.info("根据商户订单号查询订单:内部订单号【{}】,微信支付订单号【{}】支付成功", result.getOutTradeNo(), result.getTransactionId());
log.info("根据商户订单号查询订单:订单数据data = {}", JSONObject.toJSONString(result));
} catch (ServiceException e) {
log.error("根据商户订单号查询订单:订单查询失败,发送HTTP请求成功,返回异常,返回码:{},返回信息:", e.getErrorCode(), e);
return Response.error("订单查询失败");
} catch (MalformedMessageException e) {
log.error("根据商户订单号查询订单:订单查询失败,解析微信支付应答或回调报文异常,返回信息:", e);
return Response.error("订单查询失败");
} catch (ValidationException e) {
log.error("根据商户订单号查询订单:订单查询失败,验证签名失败,返回信息:", e);
return Response.error("验证签名失败");
} catch (HttpException e) {
log.error("根据商户订单号查询订单:订单查询失败,发送HTTP请求失败:", e);
return Response.error("订单查询失败");
} catch (Exception e) {
log.error("根据商户订单号查询订单:订单查询失败,异常:", e);
return Response.error("订单查询失败");
}
}
/**
*/
@OverRide
public Response closeOrder(QueryOrderReq req) {
// 初始化服务
JsapiServiceExtension service =
new JsapiServiceExtension.Builder()
.config(config)
.signType("RSA")
.build();
CloseOrderRequest request = new CloseOrderRequest();
// 调用request.setXxx(val)设置所需参数,具体参数可见Request定义
request.setMchid(wxPayConfig.getMerchantId());
request.setOutTradeNo(req.getOutTradeNo());
// 调用接口
try {
service.closeOrder(request);
} catch (HttpException e) {
log.error("关闭订单申请失败,发送HTTP请求失败:", e);
return Response.error("关闭订单申请失败");
} catch (MalformedMessageException e) {
log.error("关闭订单申请失败,解析微信支付应答或回调报文异常,返回信息:", e);
return Response.error("关闭订单申请失败");
} catch (ValidationException e) {
log.error("关闭订单申请失败,验证签名失败,返回信息:", e);
return Response.error("验证签名失败");
} catch (ServiceException e) {
log.error("关闭订单申请失败,发送HTTP请求成功,返回异常,返回码:{},返回信息:", e.getErrorCode(), e);
return Response.error("关闭订单失败:" + e.getErrorMessage());
} catch (Exception e) {
log.error("关闭订单申请失败,异常:", e);
return Response.error("关闭订单申请失败");
}
return Response.success("关闭订单申请成功");
}
/**
退款
交易时间超过一年的订单无法提交退款(按支付成功时间+365天计算)
微信支付退款支持单笔交易分多次退款,多次退款需要提交原支付订单的商户订单号和设置不同的退款单号。申请退款总金额不能超过订单金额。 一笔退款失败后重新提交,请不要更换退款单号,请使用原商户退款单号
请求频率限制:150qps,即每秒钟正常的申请退款请求次数不超过150次
每个支付订单的部分退款次数不能超过50次
如果同一个用户有多笔退款,建议分不同批次进行退款,避免并发退款导致退款失败
申请退款接口的返回仅代表业务的受理情况,具体退款是否成功,需要通过退款查询接口获取结果
错误或无效请求频率限制:6qps,即每秒钟异常或错误的退款申请请求不超过6次
一个月之前的订单申请退款频率限制为:5000/min
同一笔订单多次退款的请求需相隔1分钟
@PARAM req
@return
*/
@OverRide
public Response refund(RefundOrderReq req) {
// 初始化服务
RefundService service = new RefundService.Builder().config(config).build();
CreateRequest request = new CreateRequest();
// 调用request.setXxx(val)设置所需参数,具体参数可见Request定义
request.setOutTradeNo(req.getOutTradeNo());
request.setOutRefundNo("REFUND_" + req.getOutTradeNo());
AmountReq amount = new AmountReq();
// 订单总金额,单位为分,只能为整数,详见支付金额
amount.setTotal(decimalToLong(req.getTotalAmount()));
// 退款金额,单位为分,只能为整数,不能超过支付总额
amount.setRefund(decimalToLong(req.getRefundAmount()));
amount.setCurrency("CNY");
request.setAmount(amount);
request.setNotifyUrl(wxPayConfig.getRefundNotifyUrl());
// 调用接口
Refund refund = null;
try {
refund = service.create(request);
} catch (HttpException e) {
log.error("退款申请失败,发送HTTP请求失败:", e);
return Response.error("退款失败");
} catch (MalformedMessageException e) {
log.error("退款申请失败,解析微信支付应答或回调报文异常,返回信息:", e);
return Response.error("退款失败");
} catch (ValidationException e) {
log.error("退款申请失败,验证签名失败,返回信息:", e);
return Response.error("验证签名失败");
} catch (ServiceException e) {
log.error("退款申请失败,发送HTTP请求成功,返回异常,返回码:{},返回信息:", e.getErrorCode(), e);
return Response.error("退款失败:" + e.getErrorMessage());
} catch (Exception e) {
log.error("退款申请失败,异常:", e);
return Response.error("退款失败");
}
if (Status.SUCCESS.equals(refund.getStatus())) {
log.info("退款成功!-订单号:{}", req.getOutTradeNo());
return Response.success("退款成功");
} else if (Status.CLOSED.equals(refund.getStatus())) {
log.info("退款关闭!-订单号:{}", req.getOutTradeNo());
return Response.error("退款关闭");
} else if (Status.PROCESSING.equals(refund.getStatus())) {
log.info("退款处理中!-订单号:{}", req.getOutTradeNo());
return Response.error("退款处理中");
} else if (Status.ABNORMAL.equals(refund.getStatus())) {
log.info("退款异常!-订单号:{}", req.getOutTradeNo());
return Response.error("退款异常");
}
return Response.error("退款失败");
}
/**
查询单笔退款(通过商户退款单号)
提交退款申请后,通过调用该接口查询退款状态。退款有一定延时,建议查询退款状态在提交退款申请后1分钟发起,一般来说零钱支付的退款5分钟内到账,银行卡支付的退款1-3个工作日到账。
@PARAM outRefundNo 商户退款单号
@return
*/
@OverRide
public Response queryByOutRefundNo(String outRefundNo) {
// 初始化服务
RefundService service = new RefundService.Builder().config(config).build();
QueryByOutRefundNoRequest request = new QueryByOutRefundNoRequest();
// 调用request.setXxx(val)设置所需参数,具体参数可见Request定义
request.setOutRefundNo(outRefundNo);
// 调用接口
Refund refund = null;
try {
refund = service.queryByOutRefundNo(request);
log.info("退款查询结果:{}", JSONObject.toJSONString(refund));
//【退款状态】退款到银行发现用户的卡作废或者冻结了,导致原路退款银行卡失败,可前往商户平台(pay.weixin.qq.com)-交易中心,手动处理此笔退款。可选取值:SUCCESS:退款成功CLOSED:退款关闭PROCESSING:退款处理中ABNORMAL:退款异常【退款状态】退款到银行发现用户的卡作废或者冻结了,导致原路退款银行卡失败,可前往商户平台(pay.weixin.qq.com)-交易中心,手动处理此笔退款。可选取值:SUCCESS:退款成功CLOSED:退款关闭PROCESSING:退款处理中ABNORMAL:退款异常
if (Status.SUCCESS.equals(refund.getStatus())) {
log.info("退款成功!-订单号:{}", outRefundNo);
return Response.success("退款成功");
} else if (Status.CLOSED.equals(refund.getStatus())) {
log.info("退款关闭!-订单号:{}", outRefundNo);
return Response.error("退款关闭");
} else if (Status.PROCESSING.equals(refund.getStatus())) {
log.info("退款处理中!-订单号:{}", outRefundNo);
return Response.success("退款处理中");
} else if (Status.ABNORMAL.equals(refund.getStatus())) {
log.info("退款异常!-订单号:{}", outRefundNo);
return Response.error("退款异常");
}
} catch (HttpException e) {
log.error("退款查询失败,发送HTTP请求失败:", e);
return Response.error("退款查询失败");
} catch (MalformedMessageException e) {
log.error("退款查询失败,解析微信支付应答或回调报文异常,返回信息:", e);
return Response.error("退款查询失败");
} catch (ValidationException e) {
log.error("退款查询失败,验证签名失败,返回信息:", e);
return Response.error("退款查询失败");
} catch (ServiceException e) {
log.error("退款查询失败,发送HTTP请求成功,返回异常,返回码:{},返回信息:", e.getErrorCode(), e);
return Response.error("退款查询失败");
} catch (Exception e) {
log.error("退款查询失败,异常:", e);
return Response.error("退款查询失败");
}
return Response.success(refund);
}
/**
微信小程序退款回调
注意:
对后台通知交互时,如果微信收到应答不是成功或超时,微信认为通知失败,微信会通过一定的策略定期重新发起通知,尽可能提高通知的成功率,但微信不保证通知最终能成功
同样的通知可能会多次发送给商户系统。商户系统必须能够正确处理重复的通知。 推荐的做法是,当商户系统收到通知进行处理时,先检查对应业务数据的状态,并判断该通知是否已经处理。如果未处理,则再进行处理;如果已处理,则直接返回结果成功。在对业务数据进行状态检查和处理之前,要采用数据锁进行并发控制,以避免函数重入造成的数据混乱。
如果在所有通知频率后没有收到微信侧回调。商户应调用查询订单接口确认订单状态。
@PARAM request
@return
*/
@OverRide
public String refundNotify(HttpServletRequest request) {
Map<String, String> returnMap = new HashMap<>(2);
returnMap.put("code", "FAIL");
returnMap.put("message", "失败");
try {
// 请求头Wechatpay-Signature
String signature = request.getHeader("Wechatpay-Signature");
// 请求头Wechatpay-nonce
String nonce = request.getHeader("Wechatpay-Nonce");
// 请求头Wechatpay-Timestamp
String timestamp = request.getHeader("Wechatpay-Timestamp");
// 微信支付证书序列号
String serial = request.getHeader("Wechatpay-Serial");
// 签名方式
String signType = request.getHeader("Wechatpay-Signature-Type");
// 构造解析器和请求参数
NotificationParser parser = new NotificationParser((NotificationConfig) notificationConfig);
RequestParam requestParam = new RequestParam.Builder()
.serialNumber(serial)
.nonce(nonce)
.signature(signature)
.timestamp(timestamp)
.signType(signType)
.body(HttpServletUtils.getRequestBody(request))
.build();
log.info("微信小程序支付退款回调验签参数: {}", JSON.toJSONString(requestParam));
// 解析通知
RefundNotification notification = null;
try {
notification = parser.parse(requestParam, RefundNotification.class);
} catch (MalformedMessageException e) {
log.error("微信小程序支付退款回调:回调通知参数不正确、解析通知数据失败:", e);
returnMap.put("message", "回调通知参数不正确");
return JSONObject.toJSONString(returnMap);
} catch (ValidationException e) {
log.error("微信小程序支付退款回调:签名验证失败 ", e);
returnMap.put("message", "签名验证失败");
return JSONObject.toJSONString(returnMap);
} catch (Exception e) {
log.error("微信小程序支付退款回调:未知异常 ", e);
returnMap.put("message", "未知异常");
return JSONObject.toJSONString(returnMap);
}
log.info("微信小程序支付退款回调解析成功: {}", JSON.toJSONString(notification));
// 根据退款状态处理
Status refundStatus = notification.getRefundStatus();
switch (refundStatus) {
case SUCCESS:
// TODO 退款成功逻辑
} catch (Exception e) {
log.error("退款回调处理异常", e);
returnMap.put("message", "退款处理异常");
return JSONObject.toJSONString(returnMap);
}
}
/**
*/
private static long decimalToLong(BigDecimal money) {
return money.multiply(BigDecimal.valueOf(100)).longValue();
}
}
`
`package com.github.config.wx;
import com.wechat.pay.java.core.Config;
import com.wechat.pay.java.core.RSAAutoCertificateConfig;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.annotation.Resource;
/**
微信支付证书自动更新配置
一个商户号只能初始化一个配置,否则会因为重复的下载任务报错
@author songfayuan
@Date 2024/9/30 15:57
*/
@slf4j
@configuration
public class WxPayAutoCertificateConfig {
@resource
private WxPayConfig wxPayConfig;
/**
*/
@bean
public Config rsaAutoCertificateConfig() {
// 这里把Config作为配置Bean是为了避免多次创建资源,一般项目运行的时候这些东西都确定了
// 具体的参数改为申请的数据,可以通过读配置文件的形式获取
Config config = new RSAAutoCertificateConfig.Builder()
.merchantId(wxPayConfig.getMerchantId())
.privateKeyFromPath(wxPayConfig.getPrivateKeyPath())
.merchantSerialNumber(wxPayConfig.getMerchantSerialNumber())
.apiV3Key(wxPayConfig.getApiV3Key())
.build();
// Config config = new RSAAutoCertificateConfig.Builder()
// .merchantId(wxPayConfig.getMerchantId())
// .privateKeyFromPath(wxPayConfig.getPrivateKeyPath())
// .merchantSerialNumber(wxPayConfig.getMerchantSerialNumber())
// .apiV3Key(wxPayConfig.getApiV3Key())
// .build();
log.info("初始化微信支付商户配置完成...");
return config;
}
}`
`package com.github.common.utils;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
/**
HttpServletRequest 获取请求体
@author songfayuan
@Date 2024/9/30 19:11
*/
public class HttpServletUtils {
/**
*/
public static String getRequestBody(HttpServletRequest request) throws IOException {
ServletInputStream stream = null;
BufferedReader reader = null;
StringBuffer sb = new StringBuffer();
try {
stream = request.getInputStream();
// 获取响应
reader = new BufferedReader(new InputStreamReader(stream, StandardCharsets.UTF_8));
String line;
while ((line = reader.readLine()) != null) {
sb.append(line);
}
} catch (IOException e) {
throw new IOException("读取返回支付接口数据流出现异常!");
} finally {
reader.close();
}
return sb.toString();
}
}
`
已解决
songfayuan commentedon Dec 16, 2024
亲测完整教程:微信支付|SpringBoot集成微信小程序创建订单&支付&退款(apiV3+SDK保姆级教程)
https://blog.csdn.net/u011019141/article/details/144447844
snowwalk commentedon Dec 25, 2024
我甚至先怀疑了一下jdk自带的base64有问题,都没怀疑微信回调的有问题。建议把签名探测 放在回调文档那一页,并突出展示。
Gawainde commentedon Jun 13, 2025
woc.....感谢!
carlos5438 commentedon Jul 15, 2025
RequestParam requestParam = new RequestParam.Builder()
.serialNumber(req.getHeader("Wechatpay-Serial"))
.nonce(req.getHeader("Wechatpay-Nonce"))
.signature(req.getHeader("Wechatpay-Signature"))
.timestamp(req.getHeader("Wechatpay-Timestamp"))
.signType(req.getHeader("Wechatpay-SignType"))
.body(readRequestBody(req))
.build();用这个方法获取body的解密失败有可能是因为
private String readRequestBody(HttpServletRequest req) throws IOException {
req.setCharacterEncoding("UTF-8");
StringBuilder buffer = new StringBuilder();
String line;
while ((line = req.getReader().readLine()) != null) {
buffer.append(line);
}
return buffer.toString();
}方法中官方并没有加 req.setCharacterEncoding("UTF-8"); 默认字符