微信支付开发(5)--JSAPI支付开发详解

1. 场景

JSAPI是微信网页发起支付,应用场景包括:

  • 用户进入商家公众号,打开某个主页面,完成支付
  • 用户点击朋友圈、聊天窗口等商家页面链接打开商家页面,完成支付
  • 用户扫描二维码后在微信浏览器中打开页面后完成支付

2. 普通商户支付

2.1 构建项目

构建springboot项目wx-server,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>

2.2 编写配置类

支付配置类编写,注意支持两种模式:普通商户模式、服务商模式,通过isServiceMode切换。

/**
 * 微信支付配置
 */
@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;
	}
}

由于JSAPI支付还需要网页授权,所以编写公众号配置类:

/**
 * 微信公众平台配置
 */
@Configuration
public class WxMpConfig {
    
    
	@Bean
	public WxMpDefaultConfigImpl wxMpDefaultConfigImpl() {
    
    
		WxMpDefaultConfigImpl config = new WxMpDefaultConfigImpl();
		config.setAppId(""); // 设置微信公众号的appid
		config.setSecret(""); // 设置微信公众号的app corpSecret
		config.setToken(""); // 设置微信公众号的token
		config.setAesKey(""); // 设置微信公众号的EncodingAESKey
		return config;
	}
	@Bean
	public WxMpService wxMpService() {
    
    
		WxMpService wxMpService = new WxMpServiceImpl();// 实际项目中请注意要保持单例,不要在每次请求时构造实例,具体可以参考demo项目
		wxMpService.setWxMpConfigStorage(wxMpDefaultConfigImpl());
		return wxMpService;
	}
}

2.3 网页授权获取openid

由于JSAPI支付需要用于的openid,所以我们编写控制器,实现引导用户授权并获取openid。

/**
 * 支付控制器
 */
@Controller
@Api(tags = "支付API")
public class PayController {
    
    
	@Autowired
	private WxMpService wxMpService;
	@Autowired
	private WxPayService wxPayService;

	/**
	 * 第一步,引导用户授权
	 */
	@GetMapping("/wxPayAuth")
	public String wxPayAuth(HttpServletRequest request) throws Exception {
    
    
		String url = "http://easypanda.oicp.io/wx-server/wxPayUserInfo";
		String redirectUrl = wxMpService.getOAuth2Service().buildAuthorizationUrl(url, WxConsts.OAuth2Scope.SNSAPI_BASE, null);
		return "redirect:" + redirectUrl;
	}

	/**
	 * 第二步,用户授权后,回调该方法,拿到code换取access_token
	 */
	@GetMapping("/wxPayUserInfo")
	public String wxPayUserInfo(@RequestParam("code") String code, @RequestParam("state") String state) throws Exception {
    
    
		WxOAuth2AccessToken wxOAuth2AccessToken = wxMpService.getOAuth2Service().getAccessToken(code);
		String openId = wxOAuth2AccessToken.getOpenId();
		// 返回我们开发的网页
		return "redirect:" + "http://easypanda.oicp.io/wx-server/wxpay.html?openid=" + openId;
	}
}

2.4 编写网页发起支付

网页授权后,会进入页面wxpay.html,此时我们编写wxpay.html发起支付。

<html>
<head>
<meta charset="utf-8">
<style>
* {
      
      
	font-size: 1.5em;
}
</style>
</head>
<body>
	<input type="button" value="发起支付" onclick="btnPay()"> |
	<script src="https://cdn.staticfile.org/jquery/1.9.1/jquery.min.js"></script>
	<script src="http://res.wx.qq.com/open/js/jweixin-1.6.0.js"></script>
	<script>
		function getQueryVariable(variable) {
      
      
			var query = window.location.search.substring(1);
			var vars = query.split("&");
			for (var i = 0; i < vars.length; i++) {
      
      
				var pair = vars[i].split("=");
				if (pair[0] == variable) {
      
      
					return pair[1];
				}
			}
			return '';
		}
		function btnPay() {
      
      
			var param = {
      
      
				body : '电费',
				outTradeNo : '10000001',
				openid : getQueryVariable('openid'),
				spbillCreateIp : '123.135.154.64',
				feeType : 'CNY',
				totalFee : 1,//单位分
				tradeType : 'JSAPI',
				notifyUrl : 'http://easypanda.oicp.io/wx-server/wxNotify',
			};
			$.ajax({
      
      
				type : "POST",
				url : "/wx-server/wxPay",
				data : JSON.stringify(param),
				contentType : "application/json",
				dataType : "json",
				success : function(re) {
      
      
					console.log(re);
					WeixinJSBridge.invoke(
						"getBrandWCPayRequest",
						{
      
      
						      appId: re.appId, //公众号名称,由商户传入
						      timeStamp: re.timeStamp, //时间戳,自1970年以来的秒数
						      nonceStr: re.nonceStr, //随机串
						      package: re.packageValue, //
						      signType: re.signType, //微信签名方式:
						      paySign: re.paySign //微信签名
						},
						function (res) {
      
      
						      if (res.err_msg == "get_brand_wcpay_request:ok") {
      
      
						        alert('支付成功!')
						      } else if (res.err_msg == "get_brand_wcpay_request:cancel") {
      
      
						        alert('支付取消!')
						      } else if (res.err_msg == "get_brand_wcpay_request:fail") {
      
      
						        alert('支付失败,请重试!')
						      } // 使用以上方式判断前端返回,微信团队郑重提示:res.err_msg将在用户支付成功后返回ok,但并不保证它绝对可靠。
						}
					); //invoke
				}
			});
		}
	</script>
</body>
</html>

2.5 后端生成订单方法

后端接收前端请求后,生成订单,注意outTradeNo是唯一订单号,用于区分订单。

	/**
	 * 发起支付
	 */
	@PostMapping("/wxPay")
	@ResponseBody
	public Object wxPay(@RequestBody WxPayUnifiedOrderRequest request) throws Exception {
    
    
		return wxPayService.createOrder(request);
	}

2.6 支付结果通知

支付成功后,微信官方会向notifyUrl指定的地址推送支付结果,应根据该结果处理订单状态。

	/**
	 * 支付结果通知
	 */
	@ResponseBody
	@PostMapping("/wxNotify")
	public String wxNotify(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());
		}
	}

2.7 测试

使用手机微信打开网页,点击【发起支付】,效果如下:
在这里插入图片描述
确认支付后,后台wxNotify收到通知数据如下,可以发现result_code、return_code为SUCCESS,表示支付成功。

扫描二维码关注公众号,回复: 13726489 查看本文章
<xml><appid><![CDATA[wxa0168d6e3b5547]]></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[16147815]]></mch_id>
<nonce_str><![CDATA[1644396002773]]></nonce_str>
<openid><![CDATA[oINiq6UqTiKqfXN3H6RmeKvvRn]]></openid>
<out_trade_no><![CDATA[10000001]]></out_trade_no>
<result_code><![CDATA[SUCCESS]]></result_code>
<return_code><![CDATA[SUCCESS]]></return_code>
<sign><![CDATA[100B0932A6D10D31126F7F18EBE021]]></sign>
<time_end><![CDATA[20220209164017]]></time_end>
<total_fee>1</total_fee>
<trade_type><![CDATA[JSAPI]]></trade_type>
<transaction_id><![CDATA[42000014062022020952146956]]></transaction_id>
</xml>

3. 服务商模式支付

修改配置类,启用服务商模式,并配置服务商模式需要的参数。

/**
 * 微信支付配置
 */
@Configuration
public class PayConfig {
    
    

	public static boolean isServiceMode = true;

	@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;
	}

}

再次通过微信客户端发起支付,此时会发现商户名称发生了变化,此时是服务商模式下的特约商户的名字。

4. 小结

JSAPI支付通过微信网页发起,支付成功后支付结果发送给指定的地址,我们通过后端接收支付结果来修改订单状态。

服务商模式下,只需要修改配置即可。

猜你喜欢

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