目录
- Native 下单
- 1、内网穿透 ngrok
- 1-1:注册下载
- 2-2:使用方式
- 3-3:测试
- 2、支付通知--接收支付通知和返回应答
- 完整需求介绍:
- 2-1、需求1:应答测试
- 2-2、应答的代码:
- 2-3、结果:
- 测试:应答不符合规范
- 测试:应答出错
- 测试:应答超时
- 2-4、需求2:验签
- 代码:
- WxPayController
- WechatPay2ValidatorForRequest
- 2-5、测试:
- 2-6、总结支付通知的流程:
- 2-7、完整代码:
- WxPayController
- WechatPay2ValidatorForRequest
Native 下单
1、内网穿透 ngrok
1-1:注册下载
下载
2-2:使用方式
直接在该目录cmd打开
第一次时候这个ngrok时,需要为计算机做授权
授权命令:
ngrok config add-authtoken 2XmL8EfYQe6uVAjM9Iami0pWogd_5ztKmSxHs6UeAQn9RQBFt
后面关闭ngrok再打开,就不需要再授权了,直接 ngrok http 8090 创建个隧道就可以了
这个免费的隧道,是有时效性的,当重启ngrok的时候,或长时间使用后,该隧道会改变的,需要记得修改
3-3:测试
之前写的一个方法,url是 http://localhost:8090/api/test
成功
2、支付通知–接收支付通知和返回应答
完整需求介绍:
就是当用户扫描二维码并支付之后,微信支付那边就会返回一个【支付通知】,来告诉商户他的支付结果。商户收到这个支付通知后,会进行一些签名的验签和订单的处理操作之类的,然后再响应回给微信系统,向微信支付系统应答我们对这个支付通知的处理情况。
(应答包括:接收支付通知成功(响应码:200或204)和接收失败(响应码:4xx或5xx))
正常就是告诉微信它发来的支付通知,我们这边已经收到了,并且通过支付通知的数据,处理完自己的核心业务了,并给微信支付平台一个成功的应答。
现在就是做图片中的这步:
微信平台异步通知商户支付结果,商户端告知支付通知的接收情况。
2-1、需求1:应答测试
这里是接收微信支付系统发来的商户支付后的支付通知,这边先不做验签和订单处理,先简单的写几个应答情况给微信支付平台看应答效果
应答情况分为:
接收支付通知成功则返回响应码200或204
接受支付通知失败则返回响应码5xx或4xx
支付通知:
2-2、应答的代码:
这里是接收微信支付系统发来的商户支付后的支付通知,这边先不做验签和订单处理,先简单的测试应答情况给微信支付平台
WxPayController 的这个 nativeNotify() 方法是让微信支付平台来调用的,不是我们去调用的
路径的来源如图:之前调用微信平台的下单接口时,发送过去的我们定义的一个回调支付通知地址。
当我们支付成功后哦,微信平台就会通过这个地址,把支付情况发送给我们。
2-3、结果:
如图,发来的数据是加密的,后续需要对微信发来的结果进行验签,验签成功再用对称加密的密钥对数据进行解密。
测试:应答不符合规范
支付通知:
当响应给微信支付系统的响应码是错误的201时,属于应答不符合规范。
微信认为通知失败,因为我们要么没收到通知,要么处理通知失败,所以微信会通过一定的策略定期重新发起通知
测试:应答出错
测试:应答超时
回调处理逻辑注意事项
让线程睡眠5秒,然后微信支付那边会因为超时没有收到商户的应答,而认为商户这边没有收到通知或通知处理失败,就会继续按规则重复发送通知
2-4、需求2:验签
APIv3证书与密钥使用说明:
自定义一个针对微信平台发来的request类型的支付通知的签名,进行验签操作的工具类。
因为微信支付端并没有给我们提供默认的集成在SDK内部的不用我们自己编写的签名验证,所以我们要自己编写一个【针对微信发来的请求的一个签名验证】。
就是做图片中的这一步。
先看一个jar包里面的针对响应的一个签名验证工具类,参考SDK源码中的 WechatPay2Validator 创建通知验签工具类 WechatPay2ValidatorForRequest
我们要弄一个针对微信支付系统发来的请求来进行验证的工具类,就可以模仿这个工具类
构造验签名串:
代码:
WxPayController
WechatPay2ValidatorForRequest
自定义验签工具类
验签工具类–验的是微信支付平台发给商户端的支付通知,
属于request请求
2-5、测试:
2-6、总结支付通知的流程:
流程:
1、商户端向微信平台发起支付请求。
2、微信平台则对该请求进行响应,响应的内容是一个订单编号和支付二维码的 URL 。
3、商户端这边接收到支付二维码的URL,通过这个URL获取支付二维码图片,并进行支付。
4、商户端进行支付之后,微信平台会自动发起一个请求,请求的内容是商户端支付的结果,就是支付通知。
5、商户端这边对该支付通知进行验签,得到该请求的数据并进行一些业务操作,然后给微信支付平台应答,告诉其自己已经成功收到该通知了。
应答的内容也包括:接收通知成功和接收通知失败两种。
6、如果给微信支付平台的应答是接受通知失败的状态码。那么微信支付平台就会根据规则,在一段事件内重复发送支付通知给客户端。当然,这个重复发送通知的作用,只是为了提高商户端那边能成功接收到通知的概率(比如商户端接收通知失败的原因可以有网络原因),但是不保证一定成功。
2-7、完整代码:
WxPayController
@CrossOrigin //跨域
@RestController
@RequestMapping("/api/wx-pay")
@Api(tags = "网站微信支付API") //swagger 注解
@Slf4j
public class WxPayController
{
@Resource
private WxPayService wxPayService;
//获取签名验证器需要的类
@Resource
private Verifier verifier;
//调用统一下单API,生成支付二维码的链接和订单号
//swagger注解
@ApiOperation("调用统一下单API,生成支付二维码")
@PostMapping("/native/{productId}")
public R nativePay(@PathVariable Long productId) throws Exception
{
log.info("发起支付请求");
//返回支付二维码的链接和订单号
Map<String, Object> map = wxPayService.nativePay(productId);
return R.ok().setData(map);
}
/*
* 接收并处理完微信发来的通知后,需要给微信支付系统应答我们的处理情况
* 告诉微信它发来的支付成功的通知,我们这边已经收到了,并且处理完自己的核心业务了,现在可以给微信一个成功的应答了。
*/
//当我们支付后,微信支付系统会通过我们之前给的notify_url路径,把支付的结果响应回来
//通知接口(回调通知)---接收微信服务器给我们发来的请求
//request: 接收微信支付端发来的支付通知的请求的参数数据 , response: 响应回给微信支付端的应答
@PostMapping("/native/notify")
public String nativeNotify(HttpServletRequest request, HttpServletResponse response)
{
Gson gson = new Gson();
//创建一个应答对象
HashMap<String, String> map = new HashMap<>();
try
{
//处理微信支付系统响应回来的通知参数
String body = HttpUtils.readData(request);
//gson.fromJson()是Gson库提供的一个方法,用于将 JSON 字符串转换为 Java 对象
Map<String, Object> bodyMap = gson.fromJson(body, HashMap.class);
//微信支付端发来的支付通知的请求的id
String requestId = (String) bodyMap.get("id");
log.info("支付通知的id ====> {}", bodyMap.get("id"));
//log.info("支付通知的完整数据 ====> {}", body);
//todo:签名的验证
WechatPay2ValidatorForRequest wechatPay2ValidatorForRequest =
new WechatPay2ValidatorForRequest(verifier, requestId,body);
//判断验签是否成功
if (!wechatPay2ValidatorForRequest.validate(request))
{
log.error("通知验签失败");
//如果验签不通过,就给个失败的应答
response.setStatus(500);
map.put("code", "ERROR");
map.put("message", "微信支付系统发给商户的通知,验签失败");
return gson.toJson(map);
}
log.info("签名验证成功");
//todo :处理订单
/*
* 通知应答
* 接收成功: HTTP应答状态码需返回200或204,无需返回应答报文。
* 接收失败: HTTP应答状态码需返回5XX或4XX,同时需返回应答报文,格式如下
*/
//Thread.sleep(10000); //单位:毫秒, 1000毫秒=1秒,这里是睡眠10秒钟
//TimeUnit.SECONDS.sleep(6); //让线程睡眠超过5秒
response.setStatus(200);
map.put("code", "SUCCESS");
map.put("message", "成功");
return gson.toJson(map);
} catch (Exception e)
{
e.printStackTrace();
response.setStatus(500);
map.put("code", "ERROR");
map.put("message", "失败");
return gson.toJson(map);
}
}
}
WechatPay2ValidatorForRequest
验签工具类–验的是微信支付平台发给商户端的支付通知,
属于request请求
package cn.ljh.paymentdemo.util;
import com.wechat.pay.contrib.apache.httpclient.auth.Verifier;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.time.DateTimeException;
import java.time.Duration;
import java.time.Instant;
import static com.wechat.pay.contrib.apache.httpclient.constant.WechatPayHttpHeaders.*;
//这个类用来验证微信支付系统发给商户的支付通知的签名,就是验签
public class WechatPay2ValidatorForRequest
{
protected static final Logger log = LoggerFactory.getLogger(WechatPay2ValidatorForRequest.class);
/**
* 应答超时时间,单位为分钟
*/
protected static final long RESPONSE_EXPIRED_MINUTES = 5;
protected final Verifier verifier;
protected final String requestId;
protected final String body;
//参数1:获取签名验证器 参数2:微信支付系统发来的那个请求通知的id 参数3:微信支付系统响应回来的通知参数
public WechatPay2ValidatorForRequest(Verifier verifier, String requestId, String body)
{
this.verifier = verifier;
this.requestId = requestId;
this.body = body;
}
protected static IllegalArgumentException parameterError(String message, Object... args)
{
message = String.format(message, args);
return new IllegalArgumentException("parameter error: " + message);
}
protected static IllegalArgumentException verifyFail(String message, Object... args)
{
message = String.format(message, args);
return new IllegalArgumentException("signature verify fail: " + message);
}
//验签方法
public final boolean validate(HttpServletRequest request) throws IOException
{
try
{
//处理请求参数
validateParameters(request);
//调用构造验签名串方法
String message = buildMessage(request);
//从请求头当中拿到平台证书序列号
String serial = request.getHeader(WECHAT_PAY_SERIAL);
//从请求头当中拿到请求当中携带的签名
String signature = request.getHeader(WECHAT_PAY_SIGNATURE);
//利用verifier这个对象,进行验签的具体操作
if (!verifier.verify(serial, message.getBytes(StandardCharsets.UTF_8), signature))
{
//如果验签失败,则抛异常
throw verifyFail("serial=[%s] message=[%s] sign=[%s], request-id=[%s]",
serial, message, signature, requestId);
}
} catch (IllegalArgumentException e)
{
log.warn(e.getMessage());
return false;
}
//验签成功
return true;
}
//处理请求参数的方法-- 对参数进行判断的过程
protected final void validateParameters(HttpServletRequest request)
{
// NOTE: ensure HEADER_WECHAT_PAY_TIMESTAMP at last
String[] headers = {WECHAT_PAY_SERIAL, WECHAT_PAY_SIGNATURE, WECHAT_PAY_NONCE, WECHAT_PAY_TIMESTAMP};
String header = null;
//判空
for (String headerName : headers)
{
header = request.getHeader(headerName);
if (header == null)
{
throw parameterError("empty [%s], request-id=[%s]", headerName, requestId);
}
}
//header 就是这个---> WECHAT_PAY_TIMESTAMP 时间戳,用于判断请求是否过期
String timestampStr = header;
try
{
//通过时间戳创建一个基于时间戳那个时间的时间对象
Instant responseTime = Instant.ofEpochSecond(Long.parseLong(timestampStr));
// 如果时间戳过期了,就拒绝过期请求, 创建一个基于此时此刻的时间对象--> Instant.now()
//比较这两个时间对象 --> responseTimeh 和 Instant.now() ,如果比较的时间大于5分钟,就会认为这是一个过期的请求,那么就拒绝
if (Duration.between(responseTime, Instant.now()).abs().toMinutes() >= RESPONSE_EXPIRED_MINUTES)
{
throw parameterError("timestamp=[%s] expires, request-id=[%s]", timestampStr, requestId);
}
} catch (DateTimeException | NumberFormatException e)
{
throw parameterError("invalid timestamp=[%s], request-id=[%s]", timestampStr, requestId);
}
}
//构造验签名串方法
protected final String buildMessage(HttpServletRequest request) throws IOException
{
//获取时间戳
String timestamp = request.getHeader(WECHAT_PAY_TIMESTAMP);
//随机数的字符串形式
String nonce = request.getHeader(WECHAT_PAY_NONCE);
return timestamp + "\n"
+ nonce + "\n"
+ body + "\n";
}
}