最近在网上总结了spring cloud 微信扫码支付的流程,
本人是刚入行的小白,有不对的地方请大家指出
也欢迎大家来多多交流
我的商户APPID和秘钥的一些配置信息,是公司的 这些需要微信的商户认证
一.微信支付类型
微信native扫码支付
链接: https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=1_1.
二. native扫码支付
2.1. 业务流程
(1)商户后台系统根据用户选购的商品生成订单。
(2)用户确认支付后调用微信支付【统一下单API】生成预支付交易;
(3)微信支付系统收到请求后生成预支付交易单,并返回交易会话的二维码链接code_url。
(4)商户后台系统根据返回的code_url生成二维码。
(5)用户打开微信“扫一扫”扫描二维码,微信客户端将扫码内容发送到微信支付系统。
(6)微信支付系统收到客户端请求,验证链接有效性后发起用户支付,要求用户授权。
(7)用户在微信客户端输入密码,确认支付后,微信客户端提交授权。
(8)微信支付系统根据用户授权完成支付交易。
(9)微信支付系统完成支付交易后给微信客户端返回交易结果,并将交易结果通过短信、微信消息提示用户。微信客户端展示支付交易结果页面。
(10)微信支付系统通过发送异步消息通知商户后台系统支付结果。商户后台系统需回复接收情况,通知微信后台系统不再发送该单的支付通知。
(11)未收到支付通知的情况,商户后台系统调用【查询订单API】。
(12)商户确认订单已支付后给用户发货。
2.2. 后端代码编写
2.2.1 引入依赖
<!--微信支付-->
<dependency>
<groupId>com.github.wxpay</groupId>
<artifactId>wxpay-sdk</artifactId>
<version>RELEASE</version>
</dependency>
编写application.properties
#微信
#APPID
wx.AppID=
#商户ID
wx.MchID=
#API秘钥
wx.Key=
#异步接收微信支付结果通知的回调地址(此处的值随意填写)
wx.NotifyUrl=
2.2.2 编写WxPayConfig 配置类
package com.hcloud.wxconfig;
import java.io.InputStream;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import com.github.wxpay.sdk.WXPayConfig;
@Component
public class WxPayConfig implements WXPayConfig{
@Value("${wx.AppID}")
private String AppID;
@Value("${wx.MchID}")
private String MchID;
@Value("${wx.Key}")
private String Key;
@Value("${wx.NotifyUrl}")
private String NotifyUrl;
/**
* 获取 App ID
*
* @return App ID
*/
@Override
public String getAppID() {
return AppID;
}
/**
* 获取 Mch ID
*
* @return Mch ID
*/
@Override
public String getMchID() {
return MchID;
}
/**
* 获取 API 密钥
*
* @return API密钥
*/
@Override
public String getKey() {
return Key;
}
/**
* 异步接收微信支付结果通知的回调地址
*
* @return
*/
public String getNotifyUrl() {
return NotifyUrl;
}
/**
* 获取商户证书内容
*
* @return 商户证书内容
*/
@Override
public InputStream getCertStream() {
return null;
}
/**
* HTTP(S) 连接超时时间,单位毫秒
*
* @return
*/
@Override
public int getHttpConnectTimeoutMs() {
return 8000;
}
/**
* HTTP(S) 读数据超时时间,单位毫秒
*
* @return
*/
@Override
public int getHttpReadTimeoutMs() {
return 10000;
}
}
2.2.3 编写WxNativeService 扫码支付(这里调用微信的[统一下单]API)
package com.hcloud.payservice;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.github.wxpay.sdk.WXPay;
import com.hcloud.wxconfig.WxPayConfig;
@Service
public class WxNativeService {
@Autowired
WxPayConfig payConfig;
/**
* 统一下单接口(用于返回二维码的url)
*
* @param money 金额
* @param outTradeNo 商户订单号
* @param ip 终端IP
* @return
*/
public Map<String, String> unifiedOrder(String money, String outTradeNo, String ip) {
Map<String, String> paramMap = new LinkedHashMap<>(8);
WXPay wxPay = new WXPay(payConfig);
//入参 根据微信开放文档的接口入参规则填写即可
paramMap.put("device_info", "WEB");//自定义参数,可以为终端设备号(门店号或收银设备ID),PC网页或公众号内支付可以传"WEB"
paramMap.put("body", "华远科技");//商品名称
paramMap.put("out_trade_no", outTradeNo);//微信支付订单号
paramMap.put("total_fee", money);//支付金额
paramMap.put("spbill_create_ip", ip);//支持IPV4和IPV6两种格式的IP地址。用户的客户端IP
paramMap.put("notify_url", payConfig.getNotifyUrl());//支付成功回调路径
paramMap.put("trade_type", "NATIVE");//支付方式 NATIVE扫码支付
paramMap.put("product_id", "000000000");//商品ID
HashMap<String, String> result = new HashMap<>(2);
try {
Map<String, String> map = wxPay.unifiedOrder(paramMap);
//return_code是通信标识,非交易标识,交易是否成功需要查看result_code来判断
if ("SUCCESS".equals(map.get("return_code"))) {
if ("SUCCESS".equals(map.get("result_code"))) {
result.put("status", "success");
result.put("msg", "请求成功");
result.put("code_url", map.get("code_url"));
result.put("prepay_id", map.get("prepay_id"));
result.put("trade_type", map.get("trade_type"));
result.put("money", money);
result.put("out_trade_no", outTradeNo);
return result;
} else {
result.put("status", "fail");
result.put("msg", map.get("err_code_des"));
return result;
}
} else {
result.put("status", "fail");
result.put("msg", map.get("return_msg"));
return result;
}
} catch (Exception e) {
e.printStackTrace();
}
result.put("status", "fail");
result.put("msg", "未知异常");
return result;
}
/**
* 查询订单
*
* @param outTradeNo 商户订单号
* @return
*/
public Map<String, String> orderQuery(String outTradeNo) {
Map<String, String> paramMap = new LinkedHashMap<>(1);
WXPay wxPay = new WXPay(payConfig);
paramMap.put("out_trade_no", outTradeNo);
Map<String, String> result = new HashMap<>();
try {
Map<String, String> map = wxPay.orderQuery(paramMap);
if ("SUCCESS".equals(map.get("return_code"))) {
if ("SUCCESS".equals(map.get("result_code"))) {
//result.put("status", "success");
result.put("out_trade_no", map.get("out_trade_no")); //商户订单号
switch (map.get("trade_state")) {
case "SUCCESS":
result.put("trade_type", map.get("trade_type")); //支付类型
result.put("trade_state", map.get("trade_state")); //支付状态
result.put("transaction_id", map.get("transaction_id")); //微信支付订单号
result.put("total_fee", map.get("total_fee")); //支付金额
result.put("time_end", map.get("time_end")); //支付完成时间
result.put("msg", "支付成功");
break;
case "REFUND":
result.put("msg", "转入退款");
break;
case "NOTPAY":
map.put("msg", "未支付");
break;
case "CLOSED":
map.put("msg", "已关闭");
break;
case "REVOKED":
map.put("msg", "已撤销");
break;
case "USERPAYING":
map.put("msg", "用户支付中");
break;
case "PAYERROR":
map.put("msg", "支付失败");
break;
default:
map.put("msg", "支付失败");
break;
}
return result;
} else {
result.put("status", "fail");
result.put("out_trade_no", map.get("out_trade_no")); //商户订单号
result.put("msg", map.get("err_code_des"));
return result;
}
} else {
result.put("status", "fail");
result.put("msg", map.get("return_msg"));
return result;
}
} catch (Exception e) {
e.printStackTrace();
}
result.put("status", "fail");
result.put("msg", "未知异常");
return result;
}
}
2.2.4 编写WxIpUtil 生成终端IP工具类
package com.hcloud.payutil;
import java.net.InetAddress;
import java.net.UnknownHostException;
import javax.servlet.http.HttpServletRequest;
/*
* @className: IpUtil
* @description 获取终端IP
* @since JDK1.8
* @author ljh
* @createdAt 2020/8/30 0030
* @version 1.0.0
**/
public class WxIpUtil {
public static String getIpAddr(HttpServletRequest request) {
String ipAddress = null;
try {
ipAddress = request.getHeader("x-forwarded-for");
if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
ipAddress = request.getHeader("Proxy-Client-IP");
}
if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
ipAddress = request.getHeader("WL-Proxy-Client-IP");
}
if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
ipAddress = request.getRemoteAddr();
if (ipAddress.equals("127.0.0.1")) {
// 根据网卡取本机配置的IP
InetAddress inet = null;
try {
inet = InetAddress.getLocalHost();
} catch (UnknownHostException e) {
e.printStackTrace();
}
ipAddress = inet.getHostAddress();
}
}
// 对于通过多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割
if (ipAddress != null && ipAddress.length() > 15) { // "***.***.***.***".length()
// = 15
if (ipAddress.indexOf(",") > 0) {
ipAddress = ipAddress.substring(0, ipAddress.indexOf(","));
}
}
} catch (Exception e) {
ipAddress="";
}
// ipAddress = this.getRequest().getRemoteAddr();
return "0:0:0:0:0:0:0:1".equals(ipAddress) ? "127.0.0.1" : ipAddress;
}
}
2.2.5 编写PayController
package com.hcloud.paycontroller;
import java.math.BigDecimal;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.client.RestTemplate;
import com.github.wxpay.sdk.WXPayUtil;
import com.hcloud.payservice.WxNativeService;
import com.hcloud.payutil.WxIpUtil;
@Controller
@EnableEurekaClient
public class AliPayController {
@Autowired
private WxNativeService nativeService;
/**
* 微信支付页面
* @return
*/
public String WxPay(String total, Map<String, Object> paramMap) {
paramMap.put("total", total);
return "/Pay/WxPay";
}
/**
* 调用微信接口生成二维码
* @param request
* @param money
* @return
*/
@ResponseBody
@RequestMapping("/Wxpay/createNative")
public Map<String, String> createNative(HttpServletRequest request, String money) {
return nativeService.unifiedOrder(money, WXPayUtil.generateNonceStr(), WxIpUtil.getIpAddr(request));
}
/**
* 定时查询微信是否支付成功
* @param outTradeNo
* @return
*/
@ResponseBody
@RequestMapping("/Wxpay/orderQuery")
public Map<String, String> orderQuery(String outTradeNo) {
int i=0;
while (true) {
Map<String, String> map = nativeService.orderQuery(outTradeNo);
i++;
if ("SUCCESS".equals(map.get("trade_state"))) {
return map;
}
try {
//因为这里是循环调用查询订单,所以如果前台超过5分钟没有支付 就会让刷新页面重新生成二维码
Thread.sleep(3000);
if (i>=100){
System.out.println(i);
map.put("status","fail");
map.put("msg","二维码已超时");
return map;
}
} catch (InterruptedException e) {
e.printStackTrace();
map.put("msg", "支付失败");
return map;
}
}
}
/**
* 微信支付成功的回调
* */
@RequestMapping("/WxPay/fallback")
public String WxFallback (HttpServletRequest request) {
Map<String, String[]> parameterMap = request.getParameterMap();
System.err.println(parameterMap);
System.out.println("进入了微信回调");
//写更改数据库的方法 逻辑
return "/Pay/WxPayReturn";
}
}
2.3 前端代码编写
微信扫码页面
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=9; IE=8; IE=7; IE=EDGE">
<meta http-equiv="X-UA-Compatible" content="IE=EmulateIE7"/>
<title>微信支付页</title>
<link rel="stylesheet" type="text/css" href="css/webbase.css"/>
<link rel="stylesheet" type="text/css" href="css/pages-weixinpay.css"/>
<!--引入jQuery和qrious相关类库-->
<script src="js/jquery.min.js"></script>
<script src="js/qrious.js"></script>
</head>
<body>
<!--head-->
<div>
<!--主内容-->
<div class="pay">
<div class="checkout-tit">
<h4 class="fl tit-txt">
<span class="success-icon"></span><span class="success-info">订单提交成功,请您及时付款!订单号:<span id="out_trade_no"></span></span>
</h4>
<span class="fr"><em class="sui-lead">应付金额:</em><em class="orange money">¥</em><span id="money"></span>元</span>
<div class="clearfix"></div>
</div>
<div class="checkout-steps">
<div class="fl weixin">微信支付</div>
<div class="fl sao">
<p class="red"></p>
<div class="fl code">
<img id="qrious">
<div class="saosao">
<p>请使用微信扫一扫</p>
<p>扫描二维码支付</p>
</div>
</div>
<div class="fl phone">
</div>
</div>
</div>
</div>
</div>
<script type="text/javascript">
//页面一加载就调用
window.onload = function() {
createNative()
}
/*统一下单接口调用*/
function createNative() {
$.ajax({
type: 'get',
url: 'http://localhost:8080/createNative',
success: function(data) {
console.log(data);
$("#money").html((data.money / 100).toFixed(2))
$("#out_trade_no").html(data.out_trade_no)
/*调用后端统一下单接口根据返回的code_url 用qrious生成二维码*/
var qr = new QRious({
element: document.getElementById('qrious'),
size: 250,
level: 'H', //二维码容错级别 从小到大 : L M Q H
value: data.code_url
});
orderQuery(data.out_trade_no)
}
})
}
/*查询订单接口调用*/
function orderQuery(out_trade_no) {
$.ajax({
type: 'get',
url: 'http://localhost:8080/orderQuery?outTradeNo=' + out_trade_no,
success: function(data) {
console.log(data);
//支付成功后跳转成功页面
if (data.trade_state == "SUCCESS") {
location.href = 'paysuccess.html?money=' + ((data.total_fee) / 100).toFixed(2)
} else {
if (data.msg == "二维码已超时") {
console.log(data.msg);
//二维码超时后重新生成二维码
createNative();
} else {
//跳转失败页面
location.href = 'payfail.html'
}
}
}
})
}
</script>
</body>
</html>
成功页面paysuccess.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=9; IE=8; IE=7; IE=EDGE">
<meta http-equiv="X-UA-Compatible" content="IE=EmulateIE7"/>
<title>支付页-成功</title>
<link rel="stylesheet" type="text/css" href="css/webbase.css"/>
<link rel="stylesheet" type="text/css" href="css/pages-paysuccess.css"/>
<script src="js/jquery.min.js"></script>
</head>
<body>
<!--head-->
<div class="cart">
<!--logoArea-->
<div class="logoArea">
<div class="fl logo"><span class="title">支付页</span></div>
</div>
<!--主内容-->
<div class="paysuccess">
<div class="success">
<h3><img src="img/right.png" width="48" height="48"> 恭喜您,支付成功啦!</h3>
<div class="paydetail">
<p>支付方式:微信支付</p>
<p>支付金额:¥<span id="money"></span>元</p>
<p class="button">
<a href="myOrder.html" class="sui-btn btn-xlarge btn-danger">查看订单</a> <a href="index.html" class="sui-btn btn-xlarge ">继续购物</a>
</p>
</div>
</div>
</div>
</div>
</body>
<script>
window.onload = function() {
var money = getUrlParam("money");
$("#money").html(money)
}
//解析跳转过来时url携带的参数
function getUrlParam(name) {
var reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)"); //构造一个含有目标参数的正则表达式对象
var r = window.location.search.substr(1).match(reg); //匹配目标参数
if (r != null) return unescape(r[2]);
return null; //返回参数值
}
</script>
</html>
支付失败页面payfail.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=9; IE=8; IE=7; IE=EDGE">
<meta http-equiv="X-UA-Compatible" content="IE=EmulateIE7" />
<title>支付页-失败</title>
<link rel="stylesheet" type="text/css" href="css/webbase.css" />
<link rel="stylesheet" type="text/css" href="css/pages-payfail.css" />
</head>
<body>
<!--head-->
<div class="cart">
<!--logoArea-->
<div class="logoArea">
<div class="fl logo"><span class="title">支付页</span></div>
</div>
<!--主内容-->
<div class="payfail">
<div class="fail">
<h3><img src="img/fail.png" width="48" height="48"> 支付失败,请稍后再试</h3>
<div class="fail-text">
<p>失败原因:不能使用金币购买!</p>
<p class="button"><a href="" class="sui-btn btn-xlarge btn-danger">重新支付</a></p>
</div>
</div>
</div>
</div>
<!--页面底部END-->
</body>
</html>
成功啦