微信支付开发(7)-Native支付(主扫)开发详解

点此查看 微信公众号/微信网页/微信支付/企业微信/小程序开发合集及源代码下载

1. 场景

Native支付是指商户系统生成支付二维码,用户再用微信“扫一扫”完成支付的模式。也就是用户主动扫码,简称主扫。

相对于付款码支付(被扫),二维码直接在屏幕生成,减少了一个扫码器/扫码枪的配置,降低了成本。

另外平时大家出门买个早点、买个菜也习惯了主动扫码支付,所以这个模式还是很受欢迎的。

2. 开发说明

说一下主要流程:

  1. 用户选择商品,确定金额后发起支付。
  2. 支付请求来到后端,后端调用Native支付接口,生成支付链接。注意该链接实际上是一个字符串,用户使用微信点击该链接即可调起微信支付。如果将该链接字符串转换为二维码,用户扫码就相当于点击该链接,同样会发起支付。
  3. 前台轮询,查询支付结果,如果查询到支付成功则通知用户。这个过程中如果用户一直未完成支付,则提示支付超时。
  4. 支付成功后,微信会向开发者指定的地址(必须是公网地址)推送消息,此时可以加入自己的订单处理逻辑。

此处需要注意的是第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,当后台收到支付通知时,消息直接通知到网页,也是一种很好的技术方法。

猜你喜欢

转载自blog.csdn.net/woshisangsang/article/details/125948992