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 业务流程说明

用我自己完成一次的过程来说。

  1. 在前端页面,用户肯定要点击某个按钮或者东西来触发事件,调用我们自己编写的接口,然后返回给前端一个二维码。
  2. 我们通过调用微信支付 【统一下单API】接口得到一个 code_url。
  3. 然后在前端通过某项技术跟据返回的 code_url 生成二维码。
  4. 用户打开微信 “扫一扫”,然后扫描这个二维码。
  5. 进入支付页面,支付指定金额后完成支付交易。
  6. 然后我们在调用微信支付【查询订单 API】查询用户支付状态
  7. 通过此支付状态可以判断用户支付成功还是支付失败。
  8. 支付成功就执行我们自己的业务逻辑,一般像修改定单状态改成已支付。
    在这里插入图片描述

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 前端实现

  1. 前端通过点击结算按钮,调用此接口,获取到返回的 code_url 属性,然后通过 Vue-qr 来实现二维码的生成操作。
    在这里插入图片描述
  2. 首先实现 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("请选择支付方式");
          }
        }
      }
    }
  1. 二维码实现的函数调用
	// 生成微信支付二维码
    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 是用来在页面上展示二维码的变量
  1. 使用 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 前端调用 查询订单支付状态 接口

  1. 在原本实现过的 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
        }
      });
    }
  1. 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();  // 重新生成二维码
              }
            });
          }
        }
      });
    }

总结

到这里基本的支付流程就算完成了,通过调用微信开发文档提供的【统一下单】【查询订单】就可以实现一个简单的支付功能,还有后续一些申请退款等功能未实现。
如果你觉得这篇文章对你有帮助,那就给作者点个赞,来个评论增加点人气,让我也开心开心😊。

即使再小的帆也能远航

Logo

前往低代码交流专区

更多推荐