微信支付V3版本回调+验签流程
回调验签流程介绍官方文档 https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_4_5.shtmlhttps://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay4_1.shtml注意:同样的通知可能会多次发送给商户系统,商户系统必须能够正确处理重复的通知确保回调URL是外部可正常访问的
本文主要是接前面2篇微信V3支付参数准备和微信V3支付整合进项目中的后续之微信支付后的回调。
一、回调验签流程介绍
本文主要是接前面2篇微信V3支付参数准备和微信V3支付整合进项目中的后续之微信支付后的回调。
一文带你学会微信V3版本下单支付、退款、关单流程代码实操_8年开发工作经验的老王,积极分享工作中遇到的问题~-CSDN博客_wechatpay-apache-httpclient本文介绍微信支付中的Native支付方式,版本是APIv3,其中Native和JSAPI的区别如下Native支付:商家在系统中按微信支付协议生成支付二维码,用户扫码拉起微信收银台,确认并完成付款JSAPI支付:商家张贴收款码物料,用户打开扫一扫,扫码后输入金额,完成付款以下博文没有掺杂过多业务逻辑,对象数据都是固定写的,方便将注意力集中在微信支付的接口使用上。正式对接到项目里面,只需要改动支付的部分业务参数即可。微信支付的文档还是很详细的,建议多看几遍,以下博文也都是根据官方文档的描述来操https://blog.csdn.net/wnn654321/article/details/122933324微信V3版本支付下单、查询支付订单状态、订单退款接入正式项目中并引入策略模式实操_8年开发工作经验的老王,积极分享工作中遇到的问题~-CSDN博客本文介绍微信支付中的Native支付方式,版本是APIv3,其中Native和JSAPI的区别如下Native支付:商家在系统中按微信支付协议生成支付二维码,用户扫码拉起微信收银台,确认并完成付款JSAPI支付:商家张贴收款码物料,用户打开扫一扫,扫码后输入金额,完成付款以下内容增加了支付接口调和策略模式的使用,支付的VO类,大家可以参照自己公司的VO字段,需要改动支付的部分业务参数即可。注意:微信支付个人无法对接操作,需要有公司商户账号,一般开发过程中是产品经理或者相关负责人提供。https://blog.csdn.net/wnn654321/article/details/123192971
一、回调验签流程介绍
官方文档 https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_4_5.shtml
https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay4_1.shtml
注意:
同样的通知可能会多次发送给商户系统,商户系统必须能够正确处理重复的通知
确保回调URL是外部可正常访问的,且不能携带后缀参数
微信回调通知重复问题(不一定准确按照时间间隔推送,需要保证幂等性处理)
重复通知的时候,微信的请求id是一样的,用这个做请求幂等性处理
响应给微信的内容不规范 或者 超过5秒没响应
测试的问题:如果有多个未响应的,则测试的请求id,可能有之前的请求继续回调过来
二、核心流程操作
第一步 获取报文
第二步 验证签名(确保是微信传输过来的)
第三步 解密(AES对称解密出原始数据)
第四步 处理业务逻辑
第五步 响应请求
#支付成功,回调通知 pay.wechat.callback-url= http://mbnny9.natappfree.cc/api/callback/order/v1/wechat
注意:这个http://mbnny9.natappfree.cc需要是外网可以访问的,比如说 启动的微信支付服务是在自己电脑本地的服务,但是微信支付是在外网环境的,这时候 微信回调自己本地电脑的服务就是不通的,也就没法回调。但是可以使用小米球或者NATAPP这些内网穿透工具。本文就是使用的NATAPP。
pom.xml引入:
<!--微信支付扩展包-->
<dependency>
<groupId>com.github.wechatpay-apiv3</groupId>
<artifactId>wechatpay-apache-httpclient</artifactId>
<version>0.3.0</version>
</dependency>
package net.wnn.controller;
import com.alibaba.fastjson.JSONObject;
import com.wechat.pay.contrib.apache.httpclient.auth.ScheduledUpdateCertificatesVerifier;
import com.wechat.pay.contrib.apache.httpclient.util.AesUtil;
import lombok.extern.slf4j.Slf4j;
import net.wnn.config.WechatPayConfig;
import net.wnn.service.ProductOrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.security.GeneralSecurityException;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@Controller
@RequestMapping("/api/callback/order/v1/")
@Slf4j
public class PayCallbackController {
@Autowired
private WechatPayConfig wechatPayConfig;
@Autowired
private ProductOrderService productOrderService;
@Autowired
private ScheduledUpdateCertificatesVerifier verifier;
/**
* * 获取报文
* <p>
* * 验证签名(确保是微信传输过来的)
* <p>
* * 解密(AES对称解密出原始数据)
* <p>
* * 处理业务逻辑
* <p>
* * 响应请求
*
* @param request
* @param response
* @return
*/
@RequestMapping("wechat")
@ResponseBody
public Map<String, String> wehcatPayCallback(HttpServletRequest request, HttpServletResponse response) {
//获取报文
String body = getRequestBody(request);
//随机串
String nonceStr = request.getHeader("Wechatpay-Nonce");
//微信传递过来的签名
String signature = request.getHeader("Wechatpay-Signature");
//证书序列号(微信平台)
String serialNo = request.getHeader("Wechatpay-Serial");
//时间戳
String timestamp = request.getHeader("Wechatpay-Timestamp");
//构造签名串
//应答时间戳\n
//应答随机串\n
//应答报文主体\n
String signStr = Stream.of(timestamp, nonceStr, body).collect(Collectors.joining("\n", "", "\n"));
Map<String, String> map = new HashMap<>(2);
try {
//验证签名是否通过
boolean result = verifiedSign(serialNo, signStr, signature);
if(result){
//解密数据
String plainBody = decryptBody(body);
log.info("解密后的明文:{}",plainBody);
Map<String, String> paramsMap = convertWechatPayMsgToMap(plainBody);
//处理业务逻辑 TODO
//响应微信
map.put("code", "SUCCESS");
map.put("message", "成功");
}
} catch (Exception e) {
log.error("微信支付回调异常:{}", e);
}
return map;
}
/**
* 转换body为map
* @param plainBody
* @return
*/
private Map<String,String> convertWechatPayMsgToMap(String plainBody){
Map<String,String> paramsMap = new HashMap<>(2);
JSONObject jsonObject = JSONObject.parseObject(plainBody);
//商户订单号
paramsMap.put("out_trade_no",jsonObject.getString("out_trade_no"));
//交易状态
paramsMap.put("trade_state",jsonObject.getString("trade_state"));
//附加数据
paramsMap.put("account_no",jsonObject.getJSONObject("attach").getString("accountNo"));
return paramsMap;
}
/**
* 解密body的密文
*
* "resource": {
* "original_type": "transaction",
* "algorithm": "AEAD_AES_256_GCM",
* "ciphertext": "",
* "associated_data": "",
* "nonce": ""
* }
*
* @param body
* @return
*/
private String decryptBody(String body) throws UnsupportedEncodingException, GeneralSecurityException {
AesUtil aesUtil = new AesUtil(wechatPayConfig.getApiV3Key().getBytes("utf-8"));
JSONObject object = JSONObject.parseObject(body);
JSONObject resource = object.getJSONObject("resource");
String ciphertext = resource.getString("ciphertext");
String associatedData = resource.getString("associated_data");
String nonce = resource.getString("nonce");
return aesUtil.decryptToString(associatedData.getBytes("utf-8"),nonce.getBytes("utf-8"),ciphertext);
}
/**
* 验证签名
*
* @param serialNo 微信平台-证书序列号
* @param signStr 自己组装的签名串
* @param signature 微信返回的签名
* @return
* @throws UnsupportedEncodingException
*/
private boolean verifiedSign(String serialNo, String signStr, String signature) throws UnsupportedEncodingException {
return verifier.verify(serialNo, signStr.getBytes("utf-8"), signature);
}
/**
* 读取请求数据流
*
* @param request
* @return
*/
private String getRequestBody(HttpServletRequest request) {
StringBuffer sb = new StringBuffer();
try (ServletInputStream inputStream = request.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
) {
String line;
while ((line = reader.readLine()) != null) {
sb.append(line);
}
} catch (IOException e) {
log.error("读取数据流异常:{}", e);
}
return sb.toString();
}
}
下单后再用postman支付调用下,很快就可以在回调接口中收到断点啦
这个是我本地电脑用来做内网穿透的NATAPP工具
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)