微信公众号开发 之 微信支付(Java+SpringBoot)

目录

一、准备工作

二、开始写代码

1、获取用户openID

(1)获取code

(2)用code换取openid

2、支付主体

(1)业务流程概述

(2)开始写支付部分代码

3、接收支付系统交易结果通知


一、准备工作

    经过跌跌撞撞地探索,终于完成了微信支付功能,现将微信支付开发重要步骤及代码记录下来,以备不时之需。相信很多童鞋要开始开发微信支付功能的时候,都是一脸懵逼、无从下手,不妨回顾一下,使用者角度,一个完整的微信支付流程是怎样的呢:

    (1)首先要有一个“我要充值”的按钮,点击该按钮

    (2)跳转到一个支付页面

    (3)选择好金额之后,点击立即充值”,弹出密码键盘

    (4)密码输入无误,充值完成之后,商品发货,钱包瘪了

1、所以在开发时要事先准备好这些东西

    (1)跳转到充值(下单)界面的一个按钮

    (2)设计好的一个充值页面

2、开发时,要配置好这些参数

    (1)登录公众号平台,设置【授权回调页面域名】,就是你充值页面的url,哪个页面需要获取openID,就写哪个页面的url。

    (2)准备好参数:

appID:公众号appid

appSecret:公众号密钥

mch_id:商户id

key:API密钥

(由于微信开发完成一段时间之后才补的博客,所以有一些细节有点忘记了,这些参数如何配置可以参看这篇博客:https://blog.csdn.net/javaYouCome/article/details/79473743

 

二、开始写代码

在编程时,我将微信支付分成三大部分,一是获取openID,二是支付,三是接收支付结果。

1、获取用户openID

获取openid的时机其实没有明确要求,只要在下单之前将这个参数准备好就行了。我所做的项目中,用户通过账户密码登录之后,便马上获取openid。这里我们假设用户在按下“我要充值”按键时,获取用户openID。

获取openID步骤:先获取code,再用code换取openid。

(1)获取code

用户按下【我要充值】按键时,不直接进入充值页面,而是先进入一个“包含了我们appId和充值页面链接的一个url”,作用就是先获取code,然后再携带获取的code,自动跳转到我们真正的目的地——充值页面。用户感觉不到这其中的跳转过程~

步骤 ①,将【我要充值】按键和获取code的url关联

如果【我要充值】位于公众号底部的快捷菜单键

则登录微信公众平台,在自定义菜单中,在【我要交费】的url改为:

https://open.weixin.qq.com/connect/oauth2/authorize?appid=你的appID&redirect_uri=等会儿要跳转的url&response_type=code&scope=snsapi_base&state=123#wechat_redirect

等会儿要跳转的url,一般情况下,就是充值页面的url啦

步骤 ②:解析url得到code

经过步骤1,来到充值页面时,此时充值页面的url中携带了code的值,例如:http://chongzhiyemian.htm?code=XXXXXXXX&DisplayTile=1

在前端,通过正则匹配将code提取出来,写在函数GetQueryString()中

function GetQueryString(name) {
  var reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)");//构造一个含有目标参数的正则表达式对象
  var r = window.location.search.substr(1).match(reg);//匹配目标参数
  if (r != null) return unescape(r[2]); return null; //返回参数值
}

 

(2)用code换取openid

由于code的有效期只有五分钟,所以要尽快换取openid

==前端:

window.onload = function () {
    var code = GetQueryString("code"); //获取code
    $.post("User/getOpenid", {code: code}, function (msg) {  //将code传到后台用于获取openid
        if (msg.type == 1) {
            var openid = msg.data;
            alert("您的openID是" + openid);
        }
        else if (msg.type == 0) 
        {
            alert("获取openid失败");
            return;
        }
        else {
            alert("异常!")
        }
    })
}

==后台

@RequestMapping("/getOpenid")
@ResponseBody
public String getOpenid(HttpServletRequest request)
{
    String code = request.getParameter("code");
    String appId = "你的appID";
    String appSecret = "你的公众号密钥";
    String result;

    try {
        String URL = "https://api.weixin.qq.com/sns/oauth2/access_token?grant_type=authorization_code";
        String getDataStr = "&appid=" + appId + "&secret=" + appSecret+"&code="+code;
        String str = templateMsgService.HttpGet(URL, getDataStr);
        net.sf.json.JSONObject json = net.sf.json.JSONObject.fromObject(str);  
        String openid = (String)json.get("openid");

        if(openid!=null){
            result=openid;
        }else{
           //获取失败的处理
        }
    } catch (Exception e) {
        // 异常的处理
    }
    return result;
}

==涉及到的工具方法:templateMsgService.HttpGet()

//HttpGet方法
    public String HttpGet(String URL, String GetDataStr){

        String getUrl = URL+GetDataStr;
        StringBuffer sb = new StringBuffer();
        InputStreamReader isr = null;
        BufferedReader br = null;
        try
        {
            java.net.URL url = new URL(getUrl);
            URLConnection urlConnection = url.openConnection();
            urlConnection.setAllowUserInteraction(false);
            isr = new InputStreamReader(url.openStream());
            br = new BufferedReader(isr);
            String line;
            while ((line = br.readLine()) != null)
            {
                sb.append(line);
            }
        }
        catch (IOException e)
        {
            e.printStackTrace();
        }
        finally
        {
            try {
                br.close();
                isr.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return sb.toString();
    }

 

2、支付主体

(1)业务流程概述

现在正式开始支付,步骤很多,坑很多,先来看一下这整个流程是怎样的,我们需要做的有哪些~

微信支付官方给的业务时序图如下,乍一看很头大,过程太多了!但需要我们做的只有其中标红的那部分https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_4

把我们要做的和支付这个过程串起来,就是:

【1. 生成图文消息链接】:其实就是 充值页面。

② 用户选择充值金额,点击<确认充值>按键之后,我们【4. 生成商户订单】(没有固定格式要求,主要是给开发者和管理者看的,根据业务需要,例如用户账号、金额、下单时间),然后【5. 调用“统一下单API”】,生成微信能看得懂的订单。

③ 【6. 生成支付参数及签名】完成后,在支付页面根据支付配置及微信统一订单的prepay_id,发起微信支付。

④ 用户输入密码,确认密码之后,微信会通过异步的方式,通知我们的系统,【10.通知支付的结果】

⑤ 程序中,处理支付的结果,处理好之后,【11.告知微信通知处理结果】

 

(2)开始写支付部分代码

写代码时,分这么几步走:

第一步:凑齐一堆参数,封装成xml,发给微信支付系统的统一下单接口地址,然后返回一个prepay_id
第二步:凑齐一堆包括prepay_id在内的参数,发到前端,由前端调用支付API
(微信API和用户交互:调起密码键盘、扣钱啥的就不关我们的事啦)
第三步:等待支付系统发回的支付结果,然后进行业务处理
第四步:后台发货,前端提醒充值成功

① 第一步:生成商户订单、调用统一下单API、获取prepay_id

     准备必填参数(具体规定见:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_1

     将这些参数转化为xml,再发给统一下单接口,接收prepay_id。

参数

是啥

有啥规定

咱们填啥

appid

APPID

 

XXXX

mch_id

商户ID

 

XXXXX

nonce_str

随机字符串

可以通过微信提供的随机数生成算法生成

WXPayUtil.generateNonceStr()

sign

签名

可以通过微信提供的签名算法生成

WXPayUtil.generateSignature(paraMap, paternerKey)

body

支付的名称

格式:商家名称-销售商品类目

"XXShop-Apple"

out_trade_no

订单号

建议:当前时间+随机序列

当前时间+随机序列,自己写了个函数 generateOrderId()

total_fee

订单金额

单位:分,int类型

后台来处理一下

spbill_create_ip 

用户的终端IP地址

 

后台处理

notify_url

回调地址

接收微信反馈结果。

微信支付系统到时候带着一堆参数请求这个地址,异步通知我们支付成功没,然后我们再在这个地址做一些修改订单状态,赠送积分等事务

 

例如“www.XXXX.com/callback”

 

trade_type

支付类型

 

JSAPI

openid

当前用户的openid

 

之前获取过的

② 第二步:后台准备第二堆参数,存到map中,发给前端,前端用这些数据去调支付API

参数

是啥

有啥规定

咱们填啥

appId

APPID

 

XXXX

timeStamp

时间戳

标准北京时间,时区为东八区,自1970年1月1日 0点0分0秒以来的秒数。注意:部分系统取到的值为毫秒级,需要转换成秒(10位数字)。

微信有提供这个方法

WXPayUtil.getCurrentTimestamp()

nonceStr

随机字符串

同上,通过微信提供的随机数生成算法生成

WXPayUtil.generateNonceStr()

signType

签名方式

默认为MD5。需和统一下单的类型一致

MD5

package

订单详情扩展字符串

统一下单接口返回的prepay_id参数值,

提交格式:prepay_id=***

"prepay_id=" + prepay_id

paySign

签名

同上,采用签名生成算法

XPayUtil.generateSignature(payMap, paternerKey)

①②部分的后台程序,都在一个路径中完成

// 第二步,用户点击“确认充值”按钮后,生成用户订单,存入数据库,然后生成一堆信息,向微信支付系统请求prepay_id
// 得到prepay_id后,将一堆信息打包发送到前端,由前端调起支付界面
@RequestMapping(path = {"/pay/order"}, method = {RequestMethod.POST,RequestMethod.GET})
@ResponseBody
public ResultInfo order(HttpServletRequest request)
{
    String orderAccount= request.getParameter("accountID"); //充值账户
    String orderFee1= request.getParameter("orderFee"); //充值金额(单位:分)
    String paternerKey="你的API密钥";
    String appId = "你的appId";
    String openId= "当前用户的openid";

    // 将充值金额的单位由元转换为分
    int index = orderFee1.indexOf(".");
    int length = orderFee1.length();
    Long amLong = 0l;
    if(index == -1){
        amLong = Long.valueOf(orderFee1+"00");
    }else if(length - index >= 3){
        amLong = Long.valueOf((orderFee1.substring(0, index+3)).replace(".", ""));
    }else if(length - index == 2){
        amLong = Long.valueOf((orderFee1.substring(0, index+2)).replace(".", "")+0);
    }else{
        amLong = Long.valueOf((orderFee1.substring(0, index+1)).replace(".", "")+"00");
    }
    String orderFee= amLong.toString();


    try {
        // ---------------生成用户订单-----------------
        String orderId=generateOrderId();//生成订单编号
        // 最好设置一个订单状态标志位,在第三步“接收交易状态”时再修改标志位
        // 将用户订单存入数据库等等操作
        // --------------------------------------------

        // ---------------获取用户的IP------------------
        String ip = request.getHeader("x-forwarded-for");
        if(ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)){
            ip = request.getHeader("Proxy-Client-IP");
        }
        if(ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)){
            ip = request.getHeader("WL-Proxy-Client-IP");
        }
        if(ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)){
            ip = request.getRemoteAddr();
        }
        if(ip.indexOf(",")!=-1){
            String[] ips = ip.split(",");
            ip = ips[0].trim();
        }
        // -------------------------------------------


        // ----- 统一下单参数------
        // 注意,参数的顺序不能错!!!!否则无法成功下单
        Map<String, String> paraMap = new HashMap<String, String>();
        paraMap.put("appid", appId);
        paraMap.put("body", "XXShop-Apple");
        paraMap.put("mch_id", "你的商户ID");
        paraMap.put("nonce_str", WXPayUtil.generateNonceStr());
        paraMap.put("openid", openId);
        paraMap.put("out_trade_no",orderId );//订单号
        paraMap.put("spbill_create_ip", ip);
        paraMap.put("total_fee",orderFee);
        paraMap.put("trade_type", "JSAPI");
        paraMap.put("notify_url","www.XXXXXXX.com/callback");// 此路径是微信服务器调用支付结果通知路径
        String sign = WXPayUtil.generateSignature(paraMap, paternerKey);
        System.out.println("签名是"+sign);
        paraMap.put("sign", sign);
        String xml = WXPayUtil.mapToXml(paraMap);//将所有参数(map)转xml格式

        // 统一下单接口
        String unifiedorder_url = "https://api.mch.weixin.qq.com/pay/unifiedorder";

        //发送post请求"统一下单接口"返回预支付id:prepay_id
        String xmlStr = HttpRequest.sendPost(unifiedorder_url, xml);
        //System.out.println("xml是"+xmlStr);

        //以下内容是返回前端页面的json数据
        String prepay_id = "";//预支付id
        if (xmlStr.indexOf("SUCCESS") != -1) {
            System.out.println("支付系统返回了prepay_id");
            Map<String, String> map = WXPayUtil.xmlToMap(xmlStr);
            prepay_id =map.get("prepay_id");
        }else {
            System.out.println("prepay_id获取失败");
        }

        // =============至此已成功获取到prepay_id================
        //System.out.println("您的prepay_id的值是:"+prepay_id);

        // 将“微信内H5调起支付”需要的参数打包成JSON,发给前端
        Map<String, String> payMap = new HashMap<String, String>();
        payMap.put("appId", appId);
        payMap.put("timeStamp", WXPayUtil.getCurrentTimestamp()+"");
        payMap.put("nonceStr", WXPayUtil.generateNonceStr());
        payMap.put("signType", "MD5");
        payMap.put("package", "prepay_id=" + prepay_id);
        String paySign = WXPayUtil.generateSignature(payMap, paternerKey);
        payMap.put("paySign", paySign);

        if(payMap!=null){
            resultInfo.setType(1);
            resultInfo.setData(payMap);
            resultInfo.setMessage("成功获得prepay_id,且将数据发送到前端" );
        }else{
            resultInfo.setType(0);
            resultInfo.setMessage("获取prepayId失败" );
            resultInfo.setData("0");
        }

    } catch (Exception e) {
        resultInfo.setType(2);
        resultInfo.setMessage("异常:" + e.toString());
        resultInfo.setData("000");
    }
    System.out.println(resultInfo.getType()+"...."+resultInfo.getMessage()+"...."+resultInfo.getData());

    return resultInfo;
}

==涉及到的工具:

说明:微信官方给了一个工具库,不妨下载之后导入,下载地址:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=11_1

a. 生成用户订单号:利用微信官方给的WXPayUtil工具类,新建一个函数generateOrderId()

public static String generateOrderId() {
    char[] nonceChars = new char[6];  //6个随机字符
    for (int index = 0; index < nonceChars.length; ++index) {
        nonceChars[index] = SYMBOLS.charAt(RANDOM.nextInt(SYMBOLS.length()));
    }
    SimpleDateFormat df = new SimpleDateFormat("yyyyMMddHHmmss");//设置日期格式
    String orderId1=df.format(new Date());

    String orderI2=new String(nonceChars);
    String orderId =orderId1+orderI2;
    return orderId;
}

b. 生成nonce_str:WXPayUtil.generateNonceStr()

c. 生成sign签名:WXPayUtil.generateSignature(paraMap, paternerKey)

d. 发送post请求,访问统一下单接口:sendPost( url, xml)

    public static String sendPost(String url, String param) {
        PrintWriter out = null;
        BufferedReader in = null;
        String result = "";
        try {
            URL realUrl = new URL(url);
            // 打开和URL之间的连接
            URLConnection conn = realUrl.openConnection();
            // 设置通用的请求属性
            conn.setRequestProperty("accept", "*/*");
            conn.setRequestProperty("connection", "Keep-Alive");
            conn.setRequestProperty("user-agent",
                    "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
            // 发送POST请求必须设置如下两行
            conn.setDoOutput(true);
            conn.setDoInput(true);
            // 获取URLConnection对象对应的输出流
            out = new PrintWriter(conn.getOutputStream());
            // 发送请求参数
            out.print(param);
            // flush输出流的缓冲
            out.flush();
            // 定义BufferedReader输入流来读取URL的响应
            in = new BufferedReader(
                    new InputStreamReader(conn.getInputStream()));
            String line;
            while ((line = in.readLine()) != null) {
                result += line;
            }
        } catch (Exception e) {
            System.out.println("发送 POST 请求出现异常!"+e);
            e.printStackTrace();
        }
        //使用finally块来关闭输出流、输入流
        finally{
            try{
                if(out!=null){
                    out.close();
                }
                if(in!=null){
                    in.close();
                }
            }
            catch(IOException ex){
                ex.printStackTrace();
            }
        }
        return result;
    }
}

③ 前端收到后台传来的第二堆参数,然后唤起支付页面

这里特别气,因为官方参考文档中,没说该怎么调用,第一次做微信支付的菜鸟眼泪掉下来。。。经过参考网上优秀大佬的博客,以及数次探索和实验。。。终于知道该咋用了:

function pay(){
    var accountID=document.getElementById("accountID").value;
    var orderFee =  document.getElementById("orderFee").value;
    // 将用户的金额
    $.post("pay/order", { orderFee: orderFee ,accountID:accountID}, function (result) {
        // 以下是后台发来的“第二堆参数”,
        var appId=result.appId;
        var timeStamp=result.timeStamp;
        var nonceStr=result.nonceStr;
        var packageStr=result.package;
        var signType=result.signType;
        var paySign=result.paySign;
        if (typeof WeixinJSBridge == "undefined") {
            if (document.addEventListener) {
                document.addEventListener('WeixinJSBridgeReady',
                    onBridgeReady, false);                
            } else if (document.attachEvent) {
                document.attachEvent('WeixinJSBridgeReady',
                    onBridgeReady);
                document.attachEvent('onWeixinJSBridgeReady',
                    onBridgeReady);
            }
        } else {
            // 唤起支付页面
            onBridgeReady(appId,timeStamp,nonceStr,packageStr,signType,paySign);
        }
    });


    //微信JSDK唤起支付
    function onBridgeReady (appId,timeStamp,nonceStr,packageStr,signType,paySign) {
        //放到pay里了,这些数据啥时候用?zx
        WeixinJSBridge.invoke('getBrandWCPayRequest', {
                "appId": appId,  
                "timeStamp": timeStamp,  
                "nonceStr": nonceStr,
                "package": packageStr,
                "signType": signType, 
                "paySign": paySign 
            },
            function (msg) {
                if (msg.message == "get_brand_wcpay_request:ok") {
                    console.log('支付成功');
                    //支付成功后跳转的页面
                    window.location.href = "/rechargeSuccess.html";
                } else if (msg.message == "get_brand_wcpay_request:cancel") {
                    console.log('支付取消');
                } else if (msg.message == "get_brand_wcpay_request:fail") {
                    console.log('支付失败');
                    WeixinJSBridge.call('closeWindow');
                } //使用以上方式判断前端返回,微信团队郑重提示:res.err_msg将在用户支付成功后返回ok,但并不保证它绝对可靠。
            }
        );
    }
}

3、接收支付系统交易结果通知

怎么知道用户有没有支付成功,进而决定要不要发货呢~当然就是写个接口用来接收微信支付系统的消息啦

这个接口在上述步骤中其实已经提到过了,就是统一下单参数中的notify_url,要求这个回调地址外网能够访问,在调试时,要放在公网上才行。这个接口有两大作用:一是接收微信支付系统的消息;二是进行一些业务处理,例如修改订单状态、安排发货!

菜鸟表示,刚看到这个回调地址的说明时,很费解,那先看程序吧

(1)后台程序:主要包括以下逻辑:

① 从输入流中获得微信支付系统发来的消息,是xml格式的,各参数的含义参见:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_7,但最重要的参数主要是return_code和return_msg

② 解析消息,可以得到某订单号是否成功,然后进行业务处理

③ 给微信支付系统回复“收到”确认信息,否则微信支付系统会不停地发同样的通知给回调接口。。。。

@RequestMapping("/callback")
@ResponseBody
public ResultInfo payCallBack(HttpServletRequest request, HttpServletResponse response){
    ResultInfo resultInfo =new ResultInfo();
    InputStream inputStream = null;
    try {
        inputStream = request.getInputStream();
        String xml = WXPayUtil.inputStream2String(inputStream, "UTF-8");
        Map<String, String> notifyMap = WXPayUtil.xmlToMap(xml);//将微信发的xml转map
        //System.out.println("支付系统返回支付结果"+xml);

        if(notifyMap.get("return_code").equals("SUCCESS")){
            System.out.println("return_code是:"+notifyMap.get("return_code"));
                // 交易成功
                if(notifyMap.get("result_code").equals("SUCCESS")){
                    
                    // 接下来进行一些业务处理
                   
                } 
        }else{
                // 交易失败的处理
            }
        response.getWriter().write("<xml><return_code><![CDATA[SUCCESS]]></return_code></xml>"); //告知微信支付系统已收到消息
        inputStream.close();
    } catch (Exception e) {
       // 异常的处理
    }
    return resultInfo;
}

至此,微信支付的主体程序和流程就结束了。

在微信支付的开发过程中,参看了以下优秀大佬的博客,贴出来特此感谢:

https://blog.csdn.net/javaYouCome/article/details/79473743

https://www.cnblogs.com/imeng/p/4792043.html

https://blog.csdn.net/jrainbow/article/details/49904065?utm_source=blogxgwz3

 

 

  • 5
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 22
    评论
Spring Boot 是一种用于开发 Java 应用程序的框架,它简化了传统 Java 开发的繁琐过程,使开发人员可以更快速地构建高效的应用程序。UniApp 是一个跨平台的开发框架,它可以同时开发 Android、iOS 和 Web 应用程序,极大地提高了开发效率和项目的可维护性。 微信公众号开发是指基于微信平台的应用程序开发,通过微信公众号,我们可以实现与用户的互动交流、推送消息、提供各种服务等。 在使用 Spring Boot 和 UniApp 进行微信公众号开发时,可以采用前后端分离的开发模式。前端使用 UniApp 进行界面设计和用户交互的开发,后端使用 Spring Boot 进行业务逻辑的处理和数据的存储。 首先,我们需要在微信公众平台注册一个开发者账号,获取到相应的公众号信息和接口权限。 接下来,前端开发人员可以使用 UniApp 进行公众号的界面设计和交互逻辑的编写。UniApp 提供了丰富的组件和模板,可以方便地实现各种界面效果,并且可以使用 Vue.js 进行数据的绑定与处理。 后端开发人员使用 Spring Boot 进行接口的开发和业务逻辑的处理。可以使用 Spring Boot 提供的丰富的功能和插件来简化开发,比如使用 Spring Data JPA 来操作数据库,使用 Spring Security 来实现用户认证与权限控制等。 最后,前后端通过接口进行数据的传输和交互,前端将用户的操作发送到后端进行处理,并将后端返回的数据展示给用户。 通过采用 Spring Boot 和 UniApp 进行微信公众号开发,可以充分发挥两者的优势,快速构建高效的应用程序,实现与用户的互动和服务。同时,由于使用的是跨平台的开发框架,可以方便地同时开发多个平台的应用程序,提高开发效率和项目的可维护性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 22
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值