本文目录
1. 场景
Native支付是指商户系统生成支付二维码,用户再用微信“扫一扫”完成支付的模式。也就是用户主动扫码
,简称主扫。
相对于付款码支付(被扫),二维码直接在屏幕生成,减少了一个扫码器/扫码枪的配置,降低了成本。
另外平时大家出门买个早点、买个菜也习惯了主动扫码支付,所以这个模式还是很受欢迎的。
2. 开发说明
说一下主要流程:
- 用户选择商品,确定金额后发起支付。
- 支付请求来到后端,后端调用Native支付接口,生成支付链接。注意该链接实际上是一个字符串,用户使用微信点击该链接即可调起微信支付。如果将该链接字符串转换为二维码,用户扫码就相当于点击该链接,同样会发起支付。
- 前台轮询,查询支付结果,如果查询到支付成功则通知用户。这个过程中如果用户一直未完成支付,则提示支付超时。
- 支付成功后,微信会向开发者指定的地址(必须是公网地址)推送消息,此时可以加入自己的订单处理逻辑。
此处需要注意的是第3步,前台轮询,可以直接调用微信提供的查询订单接口来判断订单状态,也可以查询数据库中订单的状态(数据库中的订单状态可以由推送消息来触发变化)。这两种方式,前者更快一些,后者业务逻辑更完整一些,各位优秀开发者根据自己业务系统情况灵活选择即可。
3. 普通商户Native支付
3.1 项目构建
同样构建springboot项目,并配置pom.xml引入微信公众号、微信支付相关的依赖。
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.5.RELEASE</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<groupId>cn.pandabrother</groupId>
<artifactId>wx-server</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>war</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<maven-jar-plugin.version>3.0.0</maven-jar-plugin.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
<!-- 添加swagger2相关功能 -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
<!-- 添加swagger-ui相关功能 -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>
<!-- 微信公众号 -->
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>weixin-java-mp</artifactId>
<version>4.1.0</version>
</dependency>
<!-- 微信支付 -->
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>weixin-java-pay</artifactId>
<version>4.1.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
3.2 编写配置类
编写微信支付配置类:
/**
* 微信支付配置
*/
@Configuration
public class PayConfig {
public static boolean isServiceMode = false;
@Bean
public WxPayService wxService() {
WxPayConfig payConfig = new WxPayConfig();
// ----------------------------------------------------------------------------------普通商户
if (isServiceMode == false) {
payConfig.setAppId("");// 微信公众号或者小程序等的appid
payConfig.setMchId("");// 微信支付商户号
payConfig.setMchKey("");// 微信支付商户密钥
payConfig.setKeyPath("classpath:xxx.p12");// p12证书的位置,可以指定绝对路径,也可以指定类路径(以classpath:开头)
}
// ----------------------------------------------------------------------------------服务商+特约商户
else if (isServiceMode == true) {
payConfig.setAppId("");// 微信公众号或者小程序等的appid
payConfig.setMchId("");// 服务商商户号
payConfig.setMchKey("");// 服务商商户密钥
payConfig.setSubAppId("");// 服务商模式下的子商户公众账号ID,注意此处如果子商户使用了服务商的appid,则无需填写
payConfig.setSubMchId("");// 服务商模式下的子商户号
payConfig.setKeyPath("classpath:xxx.p12");// p12证书的位置,可以指定绝对路径,也可以指定类路径(以classpath:开头)
}
// 可以指定是否使用沙箱环境
payConfig.setUseSandboxEnv(false);
WxPayService wxPayService = new WxPayServiceImpl();
wxPayService.setConfig(payConfig);
return wxPayService;
}
}
3.3 编写网页
通过定时器完成倒计时,点击确定后发起Native支付。支付结果通过定时器轮询。
另外特别注意:引入了jquery.qrcode将返回的支付链接转换为二维码。
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<input id="btn-pay" type="button" value="开始Native支付" onclick="btnPay()">
<br>
<div id="info-box">信息提示:</div>
<br>
<!-- 显示二维码 -->
<div id="qrcode" style="width: 200px; height: 200px; border: 1px solid black;"></div>
<script src="https://cdn.staticfile.org/jquery/1.9.1/jquery.min.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery.qrcode/1.0/jquery.qrcode.min.js"></script>
<script>
// 支付倒计时
var curOrderId = ""; // 订单编号
var timer = null; //定时器
var seconds = 0; //倒计时剩余时间
function btnPay() {
if (timer != null) {
alert("请等待上次支付完成!");
}
curOrderId = '30000001'; // 设置订单号
var param = {
body : '电费',
outTradeNo : curOrderId,
spbillCreateIp : '123.135.154.64',
feeType : 'CNY',
totalFee : 1, //单位分
tradeType : 'NATIVE',//此处必须指定为Native支付
notifyUrl : 'https://233sd254xx.oicp.vip/wx-server/nativePayNotify',//支付成功后的回调地址,此处使用了内网穿透,如果有服务器公网IP可以直接指定
productId : '001',//产品编号
};
$.ajax({
type : "POST",
url : "/wx-server/nativePay",
data : JSON.stringify(param),
contentType : "application/json",
dataType : "json",
success : function(re) {
// 生成订单后,根据re.codeUrl展示二维码
$('#qrcode').qrcode({
text : re.codeUrl
});
// 开始轮询支付结果
if (timer == null) {
$("#btn-pay").blur();
seconds = 90;//倒计时90秒
timer = setInterval("clock()", 1000); // 开始计时
}
}
});
}
function clock() {
var str = "请使用微信扫码完成支付,剩余时间:" + seconds + "秒";
$("#info-box").text(str);
if (seconds <= 0) {
stopPay(); // 停止支付
alert("支付超时!");
} else if (curOrderId != "") {
//如果存在当前订单,且未结束支付,需要主动向后台查询订单状态
var param = {
outTradeNo : curOrderId,
};
$.ajax({
type : "POST",
url : "/wx-server/nativePayQuery",
data : JSON.stringify(param),
contentType : "application/json",
dataType : "json",
success : function(re) {
console.log("query re:", re);
if (re.returnCode == "SUCCESS" && re.resultCode == "SUCCESS" && re.tradeState == "SUCCESS") {
stopPay();
alert("查询支付成功!");
}
}
});
}
seconds--;
}
function stopPay() {
clearInterval(timer);
timer = null;
curOrderId = "";
}
</script>
</body>
</html>
3.4 编写后端方法
后端有3个方法,一个是生成订单,一个是查询订单,最后是回调通知。
/**
* Native支付控制器
*/
@Controller
@Api(tags = "Native支付API")
public class NativePayController {
@Autowired
private WxPayService wxPayService;
/**
* Native支付
*/
@PostMapping("/nativePay")
@ResponseBody
public WxPayNativeOrderResult nativePay(@RequestBody WxPayUnifiedOrderRequest request) throws WxPayException {
// 此处使用的统一下单的接口,注意request.tradeType务必指定为NATIVE,代表付款码支付
WxPayNativeOrderResult result = wxPayService.createOrder(request);
return result;
}
/**
* 查询订单,其实是统一的查询接口
*/
@PostMapping("/nativePayQuery")
@ResponseBody
public WxPayOrderQueryResult nativePayQuery(@RequestBody WxPayOrderQueryRequest wxPayOrderQueryRequest) throws WxPayException {
return wxPayService.queryOrder(wxPayOrderQueryRequest);
}
/**
* 支付结果通知
*/
@ResponseBody
@PostMapping("/nativePayNotify")
public String nativePayNotify(HttpServletRequest request, HttpServletResponse response) {
try {
String xmlResult = IOUtils.toString(request.getInputStream(), request.getCharacterEncoding());
WxPayOrderNotifyResult result = wxPayService.parseOrderNotifyResult(xmlResult);
// 加入自己处理订单的业务逻辑,需要判断订单是否已经支付过,否则可能会重复调用
String orderId = result.getOutTradeNo();
String tradeNo = result.getTransactionId();
String totalFee = BaseWxPayResult.fenToYuan(result.getTotalFee());
return WxPayNotifyResponse.success("处理成功!");
} catch (Exception e) {
return WxPayNotifyResponse.fail(e.getMessage());
}
}
}
3.5 测试
3.5.1 发起支付
点击开始支付,成功生成二维码:
3.5.2 扫码完成支付
微信扫码支付后,提示支付成功。
3.5.3 支付结果通知
支付成功后,后端nativePayNotify方法收到支付成功通知,报文如下:
<xml><appid><![CDATA[xxx]]></appid>
<bank_type><![CDATA[ICBC_CREDIT]]></bank_type>
<cash_fee><![CDATA[1]]></cash_fee>
<fee_type><![CDATA[CNY]]></fee_type>
<is_subscribe><![CDATA[Y]]></is_subscribe>
<mch_id><![CDATA[xx]]></mch_id>
<nonce_str><![CDATA[1658566534286]]></nonce_str>
<openid><![CDATA[xx]]></openid>
<out_trade_no><![CDATA[30000002]]></out_trade_no>
<result_code><![CDATA[SUCCESS]]></result_code>
<return_code><![CDATA[SUCCESS]]></return_code>
<sign><![CDATA[520CD215A8C1493A03C6B0B01D5A8400]]></sign>
<time_end><![CDATA[20220723165617]]></time_end>
<total_fee>1</total_fee>
<trade_type><![CDATA[NATIVE]]></trade_type>
<transaction_id><![CDATA[4200001458202207235326708195]]></transaction_id>
</xml>
可以根据支付结果处理业务系统逻辑。
4.服务商模式付款码支付
4.1 修改配置类
修改PayConfig的isServiceMode值为true,同时配置服务商模式下的各个参数:
else if (isServiceMode == true) {
payConfig.setAppId("");// 微信公众号或者小程序等的appid
payConfig.setMchId("");// 服务商商户号
payConfig.setMchKey("");// 服务商商户密钥
payConfig.setSubAppId("");// 服务商模式下的子商户公众账号ID,注意此处如果子商户使用了服务商的appid,则无需填写
payConfig.setSubMchId("");// 服务商模式下的子商户号
payConfig.setKeyPath("classpath:xxx.p12");// p12证书的位置,可以指定绝对路径,也可以指定类路径(以classpath:开头)
}
4.2 产品授权
此时发起支付,会提示【特约子商户商户号未授权服务商的产品权限】,此时需要先登录服务商平台发起授权邀请,然后登录商户平台授权。
授权邀请依次点击【服务商平台】-【产品中心】-【特约商户授权产品】-【服务商Native支付】-【发起邀请】。
特约商户授权依次点击【商户平台】-【产品中心】-【我授权的产品】-【授权】。
4.3 支付测试
此时再次发起支付,会发现支付商户变为了特约商户,测试成功。
5. 小结
Native支付(主扫)相对比较简单一些,生成订单后将codeUrl转换为二维码展示给用户即可。
本篇使用了轮询来查询支付结果,其实也可以通过websocket,当后台收到支付通知时,消息直接通知到网页,也是一种很好的技术方法。