微信支付之Native扫码支付功能
一、微信支付-扫码支付基本介绍
1.1 扫码支付
需要注意的是,扫码支付分两种形式:
线下的扫码支付:
这种方式非常简单,商户在微信申请付款二维码贴纸,贴到收银台附近位置,客户购买商品直接使用手机扫码二维码,输入付款金额即可支付,这种方式不需要编程人员,大大减少商户成本,比如常见的便利店,商场线下店等等。
线上的扫码支付:
这是本文选择实现的方式,也叫做Native支付,这种方式适合PC端的网站,比如大众点评、携程、优酷等,如果使用的是电脑来访问网页,并需要支付相关费用,比如商品付款,充值VIP这些都是比较常见的场景,很多网站都会选用这种方式,总之最后付款的那一刻,就在网页上展示付款码,让用户去扫并付款即可,因为这种方式不是面对面付款,所以必须要保证客户付款的金额是准确的,所以这个二维码不是固定的,是根据账单金额生成的,用户扫码之后就可以马上看到需要付款的金额,确认无误再进行付款。
1.2 商家接入
准备工作
一.注册商户号
到微信支付商户平台https://pay.weixin.qq.com
提交商家相关资料,注册一个商户账号,并开通Native支付功能。
二.绑定公众号或小程序
目的是要得到一个授权的APPID。
三.设置API密钥,登录商户平台——>账户中心——>API安全——>API密钥
该密钥在后面的代码中计算支付签名的时候需要使用到。
我们设置微信支付密匙,关心的是账户中心页面。我们点击下图所示 API安全
我们通常第一次设置时,需要安装一个证书,证书也是傻瓜式的安装,安装提示一步步来就可以了
我们找到API密匙,在这里设置密匙。
然后我们开始设置密匙
设置密匙时一定要按照下图箭头所示的规定设置。32个字符,包含英文和数字,英文要同时有大写和小写。如:bianchengxiaoshitou2501902696AAA
设置完点确定,然后会弹出下面弹窗,
输入操作密码(如果没有请设置)
验证手机,然后点确定。
Native支付实现模式
官方提供了两种模式实现,第一种模式比较复杂,需要自己处理二维码地址相关信息,该模式的预付单有效期为2小时,过期后无法支付;第二种比较简单,微信可以直接把生成好的二维码地址返回给我们,所以我们使用第一种实现。
具体区别可参考:https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=6_3
实现的大致流程可参考官方提供的时序图:
但是流程有很多,不一一演示,我们选取核心的部分来实现即可。
二、微信支付开发步骤
调用微信支付接口有两种方式:一、通过restful风格发送http请求直接调用微信服务接口 二、通过SDK调用微信服务接口,采用这种方式需要我们下载对应的SDK。下面我们将采用第二种方式来进行开发。
2.1 下载SDK
微信最新SDK版本为3.0.9由于该版本并未发布到MAVEN的中央库中,因此需要我们手动下载并手动安装到本地库中。
微信SDK下载页面:https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=11_1
下载完后解压WxPayAPI_JAVA.zip文件并进入对应目录执行mvn install将其安装到当前电脑的本地库中。
2.2 搭建SSM工程
由于本案例的重点是如何调用微信支付接口实现扫码支付功能,所以关于本地数据库的操作将省略替代为模拟数据。
2.2.1 项目整体结构
2.2.1 导入相关座标
<?xml version="1.0" encoding="UTF-8"?>
<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.itheima</groupId>
<artifactId>wxpay</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<dependencies>
<!--spring-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.9.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.1.9.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.1.9.RELEASE</version>
</dependency>
<!--servlet-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<!--jackson-->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.10</version>
</dependency>
<!--测试-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.1.9.RELEASE</version>
</dependency>
<dependency>
<groupId>com.github.wxpay</groupId>
<artifactId>wxpay-sdk</artifactId>
<version>3.0.9</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.62</version>
</dependency>
</dependencies>
<build>
<!--配置tomcat7插件-->
<plugins>
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.1</version>
<configuration>
<port>80</port>
<path>/</path>
<uriEncoding>utf-8</uriEncoding>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.2</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
</plugins>
</build>
</project>
2.2.2 web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<!--字符过滤器-->
<filter>
<filter-name>encodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>utf-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!--核心控制器DispatcherServlet-->
<servlet>
<servlet-name>sprinmvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath*:spring-mvc.xml</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>sprinmvc</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
2.2.3 spring-mvc.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc
https://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
">
<!--扫描控制器-->
<context:component-scan base-package="com.itheima.controller"></context:component-scan>
<mvc:default-servlet-handler/>
<!--mvc注解驱动-->
<mvc:annotation-driven></mvc:annotation-driven>
</beans>
2.3 生成二维码
2.3.1 创建订单页面
订单页面提供一个文本框让用户输入订单号即可。具体情况可以根据实际业务进行调整。
order.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
请输入订单号:<input type="text" name="orderId" id="orderId"/><input type="button" id="btnStart" value="开始支付"/>
<script>
document.getElementById("btnStart").onclick=function(){
window.location.href="pay.html?orderId="+document.getElementById("orderId").value;
}
</script>
</body>
</html>
用户输入完订单号后单击"开始支付"按钮跳转到pay.html页面,该页面用于显示二维码。
2.3.2 创建支付页面
pay.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="js/jquery.min.js"></script>
<script src="js/qrcode.min.js"></script> <!--二维码生成工具-->
<script src="js/axios-0.18.0.js"></script>
<script src="js/healthmobile.js"></script>
<style>
#qrcode {
width:160px;
height:160px;
margin-top:15px;
}
</style>
</head>
<body>
<div id="qrcode">
</div>
<script>
var id = getUrlParam("orderId");
</script>
<script>
var qrcode = new QRCode("qrcode");
axios.post("/wx/order?orderId="+id).then((res)=>{ //发送ajax请求获取支付链接
qrcode.makeCode(res.data.data); //根据支付链接生成二维码
})
</script>
在该页面中接收订单页面传过来的订单id,并发送ajax请求将订单id传到控制器。控制器需要调用微信支付接口,微信接口会返回一个支付链接,前端页面获取该链接后调用二维码生成工具生成支付二维码。二维码生成工具可以到https://www.runoob.com/w3cnote/javascript-qrcodejs-library.html网站下载,它本质上是一个js文件。
2.3.3 开发控制器
PayController
package com.itheima.controller;
import com.alibaba.fastjson.JSON;
import com.github.wxpay.sdk.WXPay;
import com.github.wxpay.sdk.WXPayConfig;
import com.github.wxpay.sdk.WXPayUtil;
import com.itheima.entity.Result;
import com.itheima.utils.ConvertUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
@RestController
@RequestMapping("/wx")
public class PayController {
@Autowired
private WXPay wxPay;
@PostMapping("/order")
public Result order(String orderId){
try {
Map map = new HashMap<String, Object>();
//指定商家定单号
map.put("out_trade_no", orderId);
//支付成功之后的回调地址。
map.put("notify_url", "http://mpvtm3.natappfree.cc/wx/notify");
//设置交易类型
map.put("trade_type","NATIVE");
//设置支付金额
map.put("total_fee","1");
//设置货币单位
map.put("fee_type","CNY");
//商品描述
map.put("body","职场无忧体检套餐");
//调用微信SDK进行统一下单
Map<String, String> stringStringMap = wxPay.unifiedOrder(map);
//获取支付链接
String code_url = stringStringMap.get("code_url");
//将支付链接响应到前台用于生成支付二维码
return new Result(true, "订单生成成功", code_url);
}catch (Exception ex){
ex.printStackTrace();
return new Result(false,"订单生成失败");
}
}
}
此处需要调用WXPay的对象,因此首先要在spring容器中配置该对象,具体做法如下:
1、创建配置类MyConfig
package com.github.wxpay.sdk;
import java.io.InputStream;
public class MyConfig extends WXPayConfig {
public MyConfig() {
super();
}
@Override
String getAppID() {
return "wx8397f8696b538317";
}
@Override
String getMchID() {
return "1473426802";
}
@Override
String getKey() {
return "T6m9iK73b0kn9g5v426MKfHQH7X8rKwb";
}
@Override
public int getHttpConnectTimeoutMs() {
return super.getHttpConnectTimeoutMs();
}
@Override
public int getHttpReadTimeoutMs() {
return super.getHttpReadTimeoutMs();
}
@Override
public boolean shouldAutoReport() {
return super.shouldAutoReport();
}
@Override
public int getReportWorkerNum() {
return super.getReportWorkerNum();
}
@Override
public int getReportQueueMaxSize() {
return super.getReportQueueMaxSize();
}
@Override
public int getReportBatchSize() {
return super.getReportBatchSize();
}
@Override
InputStream getCertStream() {
return null;
}
@Override
IWXPayDomain getWXPayDomain() {
// 这个方法需要这样实现, 否则无法正常初始化WXPay
IWXPayDomain iwxPayDomain = new IWXPayDomain() {
public void report(String domain, long elapsedTimeMillis, Exception ex) {
}
public DomainInfo getDomain(WXPayConfig config) {
return new IWXPayDomain.DomainInfo(WXPayConstants.DOMAIN_API, true);
}
};
return iwxPayDomain;
}
}
其中 Appid、MchID、Key在商家接入时应已生成。
2、在spring-mvc.xml文件中添加如下配置
以下配置用于在spring容器中创建wxpay对象
<!--微信支付配置对象-->
<bean id="myConfig" class="com.github.wxpay.sdk.MyConfig"></bean>
<!--微信支付核心对象-->
<bean id="wxPay" class="com.github.wxpay.sdk.WXPay">
<constructor-arg name="config" ref="myConfig"/> <!--注入配置对象-->
<constructor-arg name="autoReport" value="true"/> <1--自动生成报告-->
<constructor-arg name="useSandbox" value="false"/><!--不使用沙箱模式-->
</bean>
3、WXPay API
WXPay对应API参考https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=9_1
2.4 支付回调
生成二维码,用户扫码成功后。微信会根据前期我们设置的回调地址通知我们支付结果,因此我们在服务器的控制器中还需在添加一个新的方法用于接收微信发送给我们的信息,相关API参考:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_7&index=8
PayController
//如果用户支付成功,微信会回调该接口。下面的代码需要接收微信回调时传过来的信息(回调回来的结果是一个xml文件)并响应
//结果(响应回去 的数据也必须是一个xml文件)API参考https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_7&index=8。否则微信会一直进行回调
@RequestMapping("/notify")
public void notify1(HttpServletRequest request, HttpServletResponse response){
try {
//使用自定义工具类将字节流转换为xml文件
String xml = ConvertUtils.convertToString(request.getInputStream());
//使用微信提供的帮助类将xml文件转换成一个map文件
Map<String,String> map = WXPayUtil.xmlToMap(xml);
//判断是否支付成功
if(!map.get("result_code").equals("SUCCESS")){
//输出错误原因
System.out.println(map.get("err_code_des"));
return ;
}
//修改本地数据库的订单状态略
//返回一个成功结果给微信
//给微信一个结果通知
response.setContentType("text/xml");
String data = "<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>";
response.getWriter().write(data);
} catch (Exception e) {
e.printStackTrace();
}
}
由于我们的开发环境是在内网中,微信是无法直接访问我们控制器中的方法的。所以此处需要用于内网穿透技术,关于内网穿透可参考网址:https://natapp.cn/。我们需要在本地电脑中启动natapp软件如下图所示:
此处的http://8t96k7.natappfree.cc就是我们能够对外通讯的域名,我们只需将前面的回调地址改成http://8t96k7.natappfree.cc/wx/notify就可以了。
另外由于微信回调回来的数据是XML格式的数据,因此我们还需要做请求中解析出该XML文件,下面提供一个工具类用于专门将字节流转换成字符串
package com.itheima.utils;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
public class ConvertUtils {
/**
* 输入流转换为xml字符串
* @param inputStream
* @return
*/
public static String convertToString(InputStream inputStream) throws IOException {
ByteArrayOutputStream outSteam = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len = 0;
while ((len = inputStream.read(buffer)) != -1) {
outSteam.write(buffer, 0, len);
}
outSteam.close();
inputStream.close();
String result = new String(outSteam.toByteArray(), "utf-8");
return result;
}
}
2.5 跳转支付成功页面
完成二维码页面生成和支付回调功能后,我们前端的页面还停留在pay.html页面,此时在pay.html页面要轮询到后台去查询是否支付成功,如果支付成功则跳转到paysuccess.html页面。
2.5.1 修改pay.html页面添加如下javascript代码
setInterval("checkPayStatus()",2000); //每隔2秒到后台查询订单支付成功
function checkPayStatus(){
axios.post("/wx/orderquery?orderId="+id).then((res)=>{
if(res.data.flag){
window.location.href="paysuccess.html";
}
})
}
2.5.2 修改控制器添加orderquery方法
@RequestMapping("/orderquery")
public Result orderQuery(String orderId){
try {
Map map = new HashMap();
map.put("out_trade_no", orderId);
Map result = wxPay.orderQuery(map);
if (!"SUCCESS".equals(result.get("result_code")) || !"SUCCESS".equals(result.get("return_code")) || !"SUCCESS".equals(result.get("trade_state"))) {
//输出错误原因
return new Result(false,(String)map.get("err_code_des"));
}
return new Result(true,"支付成功");
}catch(Exception ex){
ex.printStackTrace();
return new Result(false,"订单支付失败");
}
}
此处需要调用微信API将订单号传过去查询该订单的支付状态。具体API参考
https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=9_2
2.5.3 添加支付成功页面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>支付成功</h1>
</body>
</html>
评论前必须登录!
注册