【Java 实现微信支付、Native 支付流程】,从编写代码到支付成功,一步到位!
文章目录1. 项目环境介绍2. 微信支付文档2.1 业务流程说明3. 准备信息3.1 微信公众账号如何获取?3.2 商户号如何获取?3.3 API密钥如何获取?3.4 准备工具类4. 进入开发阶段4.1 后端编写`生成微信支付二维码`的接口4.2 前端实现4.3 后端编写 `查询订单支付状态` 接口4.4 前端调用 `查询订单支付状态` 接口总结1. 项目环境介绍jdk 1.8mysql 5.7m
文章目录
1. 项目环境介绍
jdk 1.8
mysql 5.7
maven 3.6
项目前后端分离:后端 SpringBoot 项目、前端 Vue 项目
2. 微信支付文档
官方文档:https://pay.weixin.qq.com/wiki/doc/api/index.html
Native 场景介绍:用户扫描商户展示在各种场景的二维码进行支付。
这里我们支付流程选择方式二:
官方流程图:
2.1 业务流程说明
用我自己完成一次的过程来说。
- 在前端页面,用户肯定要点击某个按钮或者东西来触发事件,调用我们自己编写的接口,然后返回给前端一个二维码。
- 我们通过调用微信支付 【统一下单API】接口得到一个 code_url。
- 然后在前端通过某项技术跟据返回的 code_url 生成二维码。
- 用户打开微信 “扫一扫”,然后扫描这个二维码。
- 进入支付页面,支付指定金额后完成支付交易。
- 然后我们在调用微信支付【查询订单 API】查询用户支付状态
- 通过此支付状态可以判断用户支付成功还是支付失败。
- 支付成功就执行我们自己的业务逻辑,一般像修改定单状态改成已支付。
3. 准备信息
pom 文件引入微信支付 SDK 依赖
<!-- 微信支付 SDK -->
<dependency>
<groupId>com.github.wxpay</groupId>
<artifactId>wxpay-sdk</artifactId>
<version>0.0.3</version>
</dependency>
由于我们后面要调用微信支付的 API,所以我们要提前准备好一些配置信息.
/**
* 微信支付参数配置信息
*
* @author: 南独酌酒 <211425401@126.com>
* @date: 2020/10/9 16:14
*/
public class ConfigUtil {
/**
* 微信公众账号
*/
public final static String APPID = "填写自己的";
/**
* 商户号
*/
public final static String MCH_ID = "填写自己的";
/**
* API密钥
*/
public final static String API_KEY = "填写自己的";
}
3.1 微信公众账号如何获取?
公众账号ID是什么:微信支付分配的公众账号ID(企业号corpid即为此appId)
申请地址:PC网站接入支付
3.2 商户号如何获取?
商户号是什么:微信支付分配的商户号
申请地址:https://pay.weixin.qq.com/index.php/apply/applyment_home/guide_normal
3.3 API密钥如何获取?
文档地址:https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=4_3
API 密钥是自己的商户平台设置的:微信商户平台(pay.weixin.qq.com)-->账户设置-->API安全-->密钥设置
这三种信息的获取,大家要好好看文档,微信的生态圈还是挺庞大的,还是要多花些时间好好研究一下。
3.4 准备工具类
这是用来发送 Http 请求的工具类
import javax.net.ssl.HttpsURLConnection;
import java.io.OutputStream;
import java.net.URL;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.lang.StringUtils;
/**
* 自定义微信支付工具类
*
* @author: 南独酌酒 <211425401@126.com>
* @date: 2020/10/9 16:34
*/
public class CommonUtil {
/**
* 发送 http 请求
*
* @param requestUrl 请求路径
* @param requestMethod 请求方式(GET/POST/PUT/DELETE/...)
* @param outputStr 请求参数体
* @return 结果信息
*/
public static String httpsRequest(String requestUrl, String requestMethod, String outputStr) {
try {
URL url = new URL(requestUrl);
HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
conn.setDoOutput(true);
conn.setDoInput(true);
conn.setUseCaches(false);
// 设置请求方式(GET/POST)
conn.setRequestMethod(requestMethod);
conn.setRequestProperty("content-type", "application/x-www-form-urlencoded");
// 当outputStr不为null时向输出流写数据
if (null != outputStr) {
OutputStream outputStream = conn.getOutputStream();
// 注意编码格式
outputStream.write(outputStr.getBytes("UTF-8"));
outputStream.close();
}
// 从输入流读取返回内容
InputStream inputStream = conn.getInputStream();
InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "utf-8");
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
String str = null;
StringBuffer buffer = new StringBuffer();
while ((str = bufferedReader.readLine()) != null) {
buffer.append(str);
}
// 释放资源
bufferedReader.close();
inputStreamReader.close();
inputStream.close();
inputStream = null;
conn.disconnect();
return buffer.toString();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 获取ip
*
* @param request 请求
* @return ip 地址
*/
public static String getIp(HttpServletRequest request) {
if (request == null) {
return "";
}
String ip = request.getHeader("X-Requested-For");
if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("X-Forwarded-For");
}
if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_CLIENT_IP");
}
if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_X_FORWARDED_FOR");
}
if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
return ip;
}
}
4. 进入开发阶段
4.1 后端编写生成微信支付二维码
的接口
编写第一个接口:主要的作用是调用微信支付【统一下单 API】,这里接口返回结果我们封装成一个 Map ,主要返回给前端 code_url
二维码、 total_fee
总金额、 out_trade_no
订单号
创建一个 WeiXinPayController 控制器
import com.github.wxpay.sdk.WXPayConstants;
import com.github.wxpay.sdk.WXPayUtil;
import com.ruoyi.common.utils.pay.CommonUtil;
import com.ruoyi.common.utils.pay.ConfigUtil;
import com.ruoyi.framework.web.domain.AjaxResult;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.text.DecimalFormat;
import java.util.*;
import java.util.concurrent.TimeUnit;
/**
* 微信支付控制层
*
* @author: 南独酌酒 <211425401@126.com>
* @date: 2020/10/9 17:07
*/
@RestController
@RequestMapping(value = "/pay")
public class WeiXinPayController {
private static final DecimalFormat df = new DecimalFormat("#");
/**
* 生成微信支付二维码
*
* @param outTradeNo 订单号
* @param totalFee 金额(分)
*/
@RequestMapping(value = "/createNative")
public Map<String, String> createNative(@RequestParam("outTradeNo") String outTradeNo,
@RequestParam("totalFee") String totalFee,
HttpServletRequest request, HttpServletResponse response) {
try {
//todo 创建请求参数
SortedMap<String, String> req = new TreeMap<String, String>();
req.put("appid", ConfigUtil.APPID); //公众号
req.put("mch_id", ConfigUtil.MCH_ID); // 商户号
req.put("nonce_str", WXPayUtil.generateNonceStr()); // 32位随机字符串
req.put("body", "去氘水"); // 商品描述
req.put("out_trade_no", outTradeNo); // 商户订单号
req.put("total_fee", df.format(Double.parseDouble(totalFee) * 100)); // 标价金额(分)
req.put("spbill_create_ip", CommonUtil.getIp(request)); // 终端IP
req.put("notify_url", "http://www.baidu.com"); // 回调地址
req.put("trade_type", "NATIVE"); // 交易类型
req.put("sign", WXPayUtil.generateSignature(req, ConfigUtil.API_KEY, WXPayConstants.SignType.MD5)); // 签名
//todo 生成要发送的 xml
String xmlBody = WXPayUtil.generateSignedXml(req, ConfigUtil.API_KEY);
System.err.println(String.format("微信支付预下单请求 xml 格式:\n%s", xmlBody));
//todo 发送 POST 请求 统一下单 API 并携带 xmlBody 内容,然后获得返回接口结果
String result = CommonUtil.httpsRequest("https://api.mch.weixin.qq.com/pay/unifiedorder", "POST", xmlBody);
System.err.println(String.format("%s", result));
//todo 将返回结果从 xml 格式转换为 map 格式
Map<String, String> resultMap = WXPayUtil.xmlToMap(result);
Map<String, String> map = new HashMap<>();
map.put("code_url", resultMap.get("code_url")); // 支付地址
map.put("total_fee", totalFee); // 总金额
map.put("out_trade_no", outTradeNo); // 订单号
return map;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
4.2 前端实现
- 前端通过点击结算按钮,调用此接口,获取到返回的
code_url
属性,然后通过Vue-qr
来实现二维码的生成操作。
- 首先实现 showalert 函数,通过一系列表单选项都填写后,调用生成二维码的函数
// 立即支付按钮事件
showalert() {
let that = this;
if (that.pro === '') {
that.$message.warning("请选择收货省份");
} else if (that.city === '') {
that.$message.warning("请选择收货城市");
} else if (that.value3 === '') {
that.$message.warning("请填写联系人");
} else if (that.value4 === '') {
that.$message.warning("请填写联系电话");
} else {
if (that.fukuan === true) {
that.createNative();
} else {
that.$message.warning("请选择支付方式");
}
}
}
}
- 二维码实现的函数调用
// 生成微信支付二维码
createNative() {
let that = this;
let outTradeNo = moment(new Date()).format('YYYYMMDDHHmmssSSS'); // 订单号
let totalFee = 1;
$.ajax({
type: 'GET',
url: 'http://localhost:8080/pay/createNative?outTradeNo=' + outTradeNo + '&totalFee=' + totalFee,
success: function (res) {
that.codeUrl = res.code_url;
}
});
}
- moment 是一个日期格式化工具包【Moment.js 文档】
Vue 使用 moment :
// 通过 cnpm 安装 moment
cnpm install moment --save
// 在组件中引入后即可使用 moment
import moment from 'moment'
codeUrl
是用来在页面上展示二维码的变量
- 使用 Vue-qr 来在页面上展示二维码
// 安装 vue-qr
cnpm install vue-qr --save
// 使用还是先引入 vueqr
import VueQr from 'vue-qr'
// 然后通过 components 注册 VueQr
components: {
VueQr
}
将二维码展示到页面上,codeUrl 就是用的上面提前声明好的 codeUrl
<vue-qr class="wximg"
:size="191"
:margin="0"
:auto-color="true"
:dot-scale="1"
:text="codeUrl"/>
常用属性介绍
4.3 后端编写 查询订单支付状态
接口
前面通过后台返回 code_url
,前端通过调用接口,然后利用 vue-qr 技术正确的将支付二维码展示到页面上去了。现在来通过调用微信支付【查询订单 API】,查看用户支付成功还是支付失败。
继续在 WeiXinPayController
控制器中添加接口,通过订单号查询此次支付的状态。
/**
* 查询订单支付状态
*
* @param outTradeNo 订单号
* @return 支付状态
*/
@RequestMapping(value = "/queryOrder")
public AjaxResult queryOrder(@RequestParam("outTradeNo") String outTradeNo) {
int x = 0;
while (true) {
// 调用查询微信支付订单状态方法
Map<String, String> map = this.queryPayStatus(outTradeNo);
if (map.isEmpty()) {
return AjaxResult.error("支付出错!");
}
if (map.get("trade_state").equals("SUCCESS")) {
return AjaxResult.success("支付成功!");
}
try {
// 间隔3秒
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 为了不让程序无休止的运行下去,定义一个 x 变量,当超过 100 次的话我们就退出此程序
x++;
if (x >= 100) {
return AjaxResult.error("二维码超时!");
}
}
}
/**
* 查询微信支付订单状态
*
* @param outTradeNo 订单号
* @return 支付状态
*/
private Map<String, String> queryPayStatus(String outTradeNo) {
try {
//todo 创建请求参数
SortedMap<String, String> req = new TreeMap<String, String>();
req.put("appid", ConfigUtil.APPID); // 公众号ID
req.put("mch_id", ConfigUtil.MCH_ID); // 商户号
req.put("out_trade_no", outTradeNo); // 订单号
req.put("nonce_str", WXPayUtil.generateNonceStr()); // 随机字符串
req.put("sign", WXPayUtil.generateSignature(req, ConfigUtil.API_KEY, WXPayConstants.SignType.MD5));
//todo 生成要发送的 xml
String xmlBody = WXPayUtil.generateSignedXml(req, ConfigUtil.API_KEY);
System.err.println(String.format("查询订单支付状态 xml 格式:\n%s", xmlBody));
//todo 调用查询订单支付状态 API
String result = CommonUtil.httpsRequest("https://api.mch.weixin.qq.com/pay/orderquery", "POST", xmlBody);
//todo 返回解析后的 map 数据
Map<String, String> map = WXPayUtil.xmlToMap(result);
System.out.println(String.format("%s", map));
return map;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
4.4 前端调用 查询订单支付状态
接口
- 在原本实现过的
createNative
函数中调用此接口
// 生成微信支付二维码
createNative() {
let that = this;
let outTradeNo = moment(new Date()).format('YYYYMMDDHHmmssSSS'); // 订单号
let totalFee = 1;
$.ajax({
type: 'GET',
url: 'http://localhost:8080/pay/createNative?outTradeNo=' + outTradeNo + '&totalFee=' + totalFee,
success: function (res) {
that.codeUrl = res.code_url;
// 在这里调用查询支付状态的函数 --------------------------begin
/*
调用查询订单,查看微信支付状态
订单号就是生成支付二维码接口返回的参数,直接传给 queryPayStatus 即可
*/
that.queryPayStatus(res.out_trade_no);
// 在这里调用查询支付状态的函数 --------------------------end
}
});
}
- queryPayStatus 函数实现
// 查询订单支付状态
queryPayStatus(outTradeNo) {
let that = this;
$.ajax({
type: 'GET',
url: 'http://localhost:8080/pay/queryOrder?outTradeNo=' + outTradeNo,
success: function (res) {
if (res.code === 200) {
// 支付成功
that.$alert(res.msg, '支付窗口', {
confirmButtonText: '确定'
//todo 支付成功跳转至自己的订单页面
});
// 剩下的就是调用自己的业务逻辑等等...
/*
比如自己写个函数,这里来调用
然后主要逻辑就是修改定单的状态为已支付
然后清空购物车
跳转至订单页面等等...
*/
} else {
// 支付失败
that.$alert(res.msg, '支付窗口', {
confirmButtonText: '重新支付',
callback: action => {
that.createNative(); // 重新生成二维码
}
});
}
}
});
}
总结
到这里基本的支付流程就算完成了,通过调用微信开发文档提供的【统一下单】【查询订单】就可以实现一个简单的支付功能,还有后续一些申请退款等功能未实现。
如果你觉得这篇文章对你有帮助,那就给作者点个赞,来个评论增加点人气,让我也开心开心😊。
即使再小的帆也能远航
更多推荐
所有评论(0)