SpringBoot 搭建微信小程序支付(JSAPI) 纯后端

一、支付流程

PS:做这个之前  ,先去下载官方的SDK吧  https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=11_1

1.首先要拿到appid,key,AppSecretmch_id (商户号) 。  这几个参数由你或者是你公司去申请

2.前端需要给后端的(统一下单接口)传递code(登录凭证),标题body(自定义,比如游戏充值)和支付金额 。

3.后端需要有统一下单的接口和一个支付成功后的回调接口

4.统一下单接口在收到这三个参数后,先根据code去获取openid(JSAPI必传openid),

    官网放出的获取openid的请求地址  需要appid secret,  code,   grant_type的值写成固定的->authorization_code

GET https://api.weixin.qq.com/sns/jscode2session?appid=APPID&secret=SECRET&js_code=JSCODE&grant_type=authorization_code

5.然后把各种参数封装为一个map 再根据secret和map进行MD5加密返回一个签名 ,再把这个签名封装到map中         

  最后 发送请求并返回结果  

6.获取到第一次签名请求的结果后,再对返回的数据进行第二次签名,最后返回给前端一系列的参数

7.前端在收到返回的参数后,如果为success ,就调起微信支付页面,付款成功后微信会自动调用我们定义的回调函数,在回调函数中需要响应结果给微信系统,表示我们已经支付完成 ,不然微信会调用我们的回调函数8次。

8.回调函数会返回结果,如果为success就表示此次支付成功完成

官网放出的获取openid的请求地址  需要appid , secret,code,grant_type写成固定的->authorization_code

具体代码:

统一下单,回调函数,查询订单--具体代码:

注意: 这里面的地址我都不是用的localhost  ,需要弄成外网的样子  所以这里我做了一个内网穿透  不然回调函数无法执行

            内网穿透我用的是ngrok,官网下载后启动执行ngrok http 端口号 。然后就会生成一行地址,你把你接口的地址加在生成的地址后面就可以访问了

1.

package com.github.wxpay.sdk.wxpaydemo.controller;


import com.github.wxpay.sdk.wxpaydemo.config.WXPay;
import com.github.wxpay.sdk.wxpaydemo.config.WXPayConstants;
import com.github.wxpay.sdk.wxpaydemo.config.WXPayUtil;
import com.github.wxpay.sdk.wxpaydemo.domain.OrderQueryReturnDomain;
import com.github.wxpay.sdk.wxpaydemo.domain.OrderReturnDomain;
import com.github.wxpay.sdk.wxpaydemo.domain.PayDomain;
import com.github.wxpay.sdk.wxpaydemo.util.PayUtil;
import com.github.wxpay.sdk.wxpaydemo.util.RandomStringGenerator;
import com.github.wxpay.sdk.wxpaydemo.util.WxPayGobal;
import com.thoughtworks.xstream.annotations.XStreamAlias;
import com.thoughtworks.xstream.XStream;
import org.springframework.web.bind.annotation.*;

import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.InetAddress;
import java.util.HashMap;
import java.util.Map;

/**
 * 微信小程序支付接口
 */

@RestController
@RequestMapping("/pay")
public class wxpay {

    //JSAPI和APP 统一下单请求地址 (固定的)
    private static  final String JSAPI_URL="https://api.mch.weixin.qq.com/pay/unifiedorder";



    //支付回调地址(你自己的请求地址,可以自己随意配置啦,写在这方便你理解)
    private static final String  NOTIFY_URL = "http://04a3af668030.ngrok.io/pay/back/wxNotify";

    //支付订单查询地址 (固定的)
    private static  final String ORDER_URL="https://api.mch.weixin.qq.com/pay/orderquery";

    //交易类型(这里是公众号支付)
    private static final String TRADE_TYPE = "JSAPI";


    /**
     * JSAPI 统一下单接口
     *
     * @param code  登录凭证
     * @param body  标题
     * @param money 支付金额
     * @return 返回交易类型和预支付会话标识
     * @throws Exception
     */
    @PostMapping("/wxJSApiPay")
    public Map<String, Object> wxJSApiPay(String code, String body, String money) throws Exception {
        String orderNumber = WxPayGobal.orderNumber();

        Map<String, String> data = new HashMap<String, String>();
        Map<String, Object> map = new HashMap<String, Object>();
        data.put("appid", PayDomain.getAppid());   //appid
        data.put("mch_id", PayDomain.getMch_id()); //商户id
        data.put("body", body);                            //标题
        data.put("out_trade_no", orderNumber);//订单号
        data.put("total_fee", money);                      //金额  单位为分
        data.put("spbill_create_ip", InetAddress.getLocalHost().getHostAddress());//终端IP
        data.put("notify_url", NOTIFY_URL);                                       //回调地址
        data.put("trade_type", TRADE_TYPE);                                       // 交易类型
        data.put("openid", WxPayGobal.getOpenId(code));                           //用户标识
        data.put("nonce_str", RandomStringGenerator.getRandomStringByLength(32)); //随机字符串

        System.out.println(data);
        String result = WxPayGobal.wxPay(data,JSAPI_URL);                                   //传递参数发送请求并返回结果


        XStream xStream = new XStream();
        //出于安全考虑,这里必须限制类型,不然会报错
        xStream.allowTypes(new Class[]{OrderReturnDomain.class});
        xStream.alias("xml", OrderReturnDomain.class);
        OrderReturnDomain s = (OrderReturnDomain) xStream.fromXML(result);        //返回结果转换为实体


        //当请求返回结果为success时  进行二次签名
        if ("SUCCESS".equals(s.getReturn_code()) && s.getReturn_code().equals(s.getResult_code())) {
            //生成签名(官方给出来的签名方法)
            Map<String, String> map2 = new HashMap<String, String>();
            long time = System.currentTimeMillis() / 1000;
            map2.put("appId", PayDomain.getAppid());
            map2.put("timeStamp", String.valueOf(time));
            //这边的随机字符串必须是第一次生成sign时,微信返回的随机字符串,不然小程序支付时会报签名错误
            map2.put("nonceStr", s.getNonce_str());
            map2.put("package", "prepay_id=" + s.getPrepay_id());
            map2.put("signType", "MD5");

            String sign2 = WXPayUtil.generateSignature(map2, PayDomain.getKey(), WXPayConstants.SignType.MD5);
            System.out.println("二次签名的sign2----->" + sign2);


            Map<String, Object> payInfo = new HashMap<String, Object>();
            payInfo.put("timeStamp", String.valueOf(time));
            payInfo.put("nonceStr", s.getNonce_str());
            payInfo.put("package", "prepay_id="+s.getPrepay_id());
            payInfo.put("signType", "MD5");
            payInfo.put("paySign", sign2);
            payInfo.put("orderNumber",orderNumber);
            map.put("status", 200);
            map.put("msg", "success");
            map.put("data", payInfo);
            //此处写逻辑代码实现
            /**
             * --
             * --
             */

        } else {
            return null;
        }
        return map;
    }

  

    /**
     *
     * @param request
     * @param response
     * @param orderNumber  下单成功后的订单号
     * @param sign  下单成功后的签名
     * @return
     * @throws Exception
     */

    @PostMapping(value = "/orderQuery")
    public static OrderQueryReturnDomain orderQuery(HttpServletRequest request, HttpServletResponse response,String orderNumber,String sign) throws Exception {
        Map<String, String> data = new HashMap<String, String>();
        data.put("appid", PayDomain.getAppid());
        data.put("mch_id", PayDomain.getMch_id());
        data.put("out_trade_no", orderNumber);//订单号
        data.put("nonce_str", RandomStringGenerator.getRandomStringByLength(32)); //随机字符串
        data.put("sign",sign);                                                    //签名
        System.out.println(data);
        String s = WxPayGobal.wxPay(data,ORDER_URL);                                   //传递参数发送请求并返回结果
        XStream xStream = new XStream();
        OrderQueryReturnDomain result = (OrderQueryReturnDomain) xStream.fromXML(s);
        return result;
    }



    /**
     * 微信小程序支付成功回调函数
     *
     * @param request
     * @param response
     * @throws Exception
     */
    @RequestMapping(value = "/callback/wxNotify")
    public static void wxNotify(HttpServletRequest request, HttpServletResponse response) throws Exception {
        BufferedReader br = new BufferedReader(new InputStreamReader((ServletInputStream) request.getInputStream()));
        String line = null;
        StringBuilder sb = new StringBuilder();
        while ((line = br.readLine()) != null) {
            sb.append(line);
        }
        br.close();
        //sb为微信返回的xml
        String notityXml = sb.toString();
        String resXml = "";
        System.out.println("接收到的报文:" + notityXml);
        @SuppressWarnings("unchecked")
        Map<String, String> map = PayUtil.doXMLParse(notityXml);

        String returnCode = (String) map.get("return_code");
        if ("SUCCESS".equals(returnCode)) {
            //验证签名是否正确
            Map<String, String> validParams = PayUtil.paraFilter(map);  //回调验签时需要去除sign和空值参数
            String validStr = PayUtil.createLinkString(validParams);//把数组所有元素,按照“参数=参数值”的模式用“&”字符拼接成字符串
            String sign = PayUtil.sign(validStr, PayDomain.getKey(), "utf-8").toUpperCase();//拼装生成服务器端验证的签名
            // 因为微信回调会有八次之多,所以当第一次回调成功了,那么我们就不再执行逻辑了

            //根据微信官网的介绍,此处不仅对回调的参数进行验签,还需要对返回的金额与系统订单的金额进行比对等
            if (sign.equals(map.get("sign"))) {
                /**此处添加自己的业务逻辑代码start**/
                // bla bla bla....
                /**此处添加自己的业务逻辑代码end**/
                //通知微信服务器已经支付成功
                resXml = "<xml>" + "<return_code><![CDATA[SUCCESS]]></return_code>"
                        + "<return_msg><![CDATA[OK]]></return_msg>" + "</xml> ";
            } else {
                System.out.println("微信支付回调失败!签名不一致");
            }
        } else {
            resXml = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>"
                    + "<return_msg><![CDATA[报文为空]]></return_msg>" + "</xml> ";
        }
        System.out.println(resXml);
        System.out.println("微信支付回调数据结束");
        BufferedOutputStream out = new BufferedOutputStream(
                response.getOutputStream());
        out.write(resXml.getBytes());
        out.flush();
        out.close();
    }


}

2.工具类   用于发送请求  获取openid  生成订单号(具体的订单号生成规则需要修改,我这里只是演示,不能用于上线)

package com.github.wxpay.sdk.wxpaydemo.util;


import com.github.wxpay.sdk.wxpaydemo.config.WXPayUtil;
import com.github.wxpay.sdk.wxpaydemo.domain.PayDomain;
import org.apache.http.HttpEntity;
import org.apache.http.HttpRequest;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import java.io.IOException;

import java.time.LocalDateTime;
import java.util.Map;

import com.alibaba.fastjson.JSONObject;

/**
 * 小程序支付工具类
 */

@Component
public class WxPayGobal {

    /**
     * 请求地址  (用于获取openid)
     */
    private final static String url = "https://api.weixin.qq.com/sns/jscode2session";

    private static Logger logger = LoggerFactory.getLogger(WxPayGobal.class);

    public static String wxPay(Map<String, String> data,String url) {

        //生成签名
        String sign = null;
        try {
            sign = WXPayUtil.generateSignature(data, PayDomain.getKey());
        } catch (Exception e) {
            e.printStackTrace();
        }
        data.put("sign", sign);
        String s = null;
        try {
            s = WXPayUtil.mapToXml(data);
        } catch (Exception e) {
            e.printStackTrace();
        }
        //发生请求并返回请求结果
        String s1 = doPostXml(url, s);
        System.out.println(s1);
        return s1;
    }

    /**
     * 发送请求
     *
     * @param url            请求地址
     * @param requestDataXml 请求的xml类型的字符串
     * @return
     */
    public static String doPostXml(String url, String requestDataXml) {
        CloseableHttpClient httpClient = null;
        CloseableHttpResponse httpResponse = null;
        httpClient = HttpClients.createDefault();
        HttpPost httpPost = new HttpPost(url);
        RequestConfig requestConfig = RequestConfig.custom().setConnectTimeout(15000)
                .setConnectionRequestTimeout(60000)
                .setSocketTimeout(60000)
                .build();
        httpPost.setConfig(requestConfig);
        httpPost.setEntity(new StringEntity(requestDataXml, "UTF-8"));
        httpPost.addHeader("Content-Type", "text/xml");

        String result = "";
        try {
            httpResponse = httpClient.execute(httpPost);
            HttpEntity entity = httpResponse.getEntity();
            result = EntityUtils.toString(entity, "UTF-8");
        } catch (Exception e) {
            e.printStackTrace();
        }

        return result;

    }

    /**
     * 获取openId
     * @param code 登录凭证
     * @return
     */
    public static String getOpenId(String code) {

        //请求参数
        String params = url + "?appid=" + PayDomain.getAppid() + "&secret=" + PayDomain.getSecret() + "&js_code=" + code + "&grant_type=" + "authorization_code";
        logger.info("请求地址:"+params);
        //发送请求
        HttpGet httpGet = new HttpGet(params);
        CloseableHttpClient httpClients = HttpClients.createDefault();
        String result = null;
        try {
            CloseableHttpResponse execute = httpClients.execute(httpGet);
            HttpEntity entity = execute.getEntity();
            result = EntityUtils.toString(entity);
            System.out.println(result+"...");
        } catch (IOException e) {
            e.printStackTrace();
        }
        JSONObject jsonObject = JSONObject.parseObject(result);
        logger.info("获取openid" + jsonObject.getString("openid"));
        if (null!=jsonObject.getString("openid")) {
            String openid = jsonObject.getString("openid");
            return openid;
        }
        return null;
    }

    /**
     * 订单号生成    当前日期+随机数字+商户ID
     */
    public static String orderNumber(){
        LocalDateTime now = LocalDateTime.now();
        String str=now.getYear()+""+now.getMonthValue()+now.getDayOfMonth()+"";//当前年月日

        String randomNumber=""; //随机数字
        for (int i = 0; i < 6; i++) {
            int max=9,min=0;
            int ran2 = (int) (Math.random()*(max-min)+min);

            randomNumber=ran2+randomNumber;
        }
        System.out.println(str+randomNumber);
        return str+randomNumber;
    }


}

 实体类:

package com.github.wxpay.sdk.wxpaydemo.domain;

import lombok.Data;

/**
 * 接收统一下单调用微信接口返回的参数
 * @author Administrator
 *   */
@Data
public class OrderReturnDomain {

    /**
     * 返回状态码 (SUCCESS或 fail)
     */
    private String return_code;

    /**
     * 返回信息
     */
    private String return_msg;

    /**
     * 返回的结果状态码
     */
    private String result_code;

    /**
     * 返回的appid
     */
    private String appid;

    /**
     * 返回的商户号
     */
    private String mch_id;
    /**
     * 返回的随机字符串
     */
    private String nonce_str;

    /**
     * 返回的签名
     */
    private String sign;
    /**
     *微信生成的预支付会话标识,用于后续接口调用中使用,该值有效期为2小时
     */
    private String prepay_id;

    /**
     * 返回的交易类型 (JSAPI,APP,NATIVE)
     */
    private String trade_type;

    /**
     * 返回的错误码
     */
    private String err_code;

    /**
     * 返回的错误信息描述
     */
    private String err_code_des;
}

package com.github.wxpay.sdk.wxpaydemo.domain;

import lombok.Data;

/**
 * 封装订单支付成功后的返回结果
 */

@Data
public class OrderQueryReturnDomain {
    private String return_code;     //返回返回状态码
    private String return_msg;      //返回信息
    private String appid;           //公众账号ID
    private String mch_id;          //商户号
    private String nonce_str;       //随机字符串
    private String sign;            //签名
    private String result_code;     //业务结果
    private String openid;          //用户标识
    private String is_subscribe;    //是否关注公众账号
    private String trade_type;      //交易类型
    private String bank_type;       //付款银行
    private String total_fee;       //标价金额
    private String fee_type;        //标价币种
    private String transaction_id;  //微信支付订单号
    private String out_trade_no;    //商户订单号
    private String time_end;        //支付完成时间
    private String trade_state;     //交易状态
    private String cash_fee;        //现金支付金额
    private String trade_state_desc;//交易状态描述
    private String cash_fee_type;   //现金支付币种

}
package com.github.wxpay.sdk.wxpaydemo.domain;

import com.github.wxpay.sdk.wxpaydemo.config.WXPayConfig;
import lombok.Data;
import lombok.Getter;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;


@Component
public class PayDomain  {


    /**
     * 密钥key
     */
    private static  String key;



    /**
     * 应用ID
     */
    private static  String appid;


    /**
     * 商户号
     */
    private static  String mch_id;


    /**
     * 密钥
     * @param key
     */
    private static  String secret;

    @Value("${com.wx.wxpay.key}")
    public void setKey(String key) {
        this.key = key;
    }
    @Value("${com.wx.wxpay.appid}")
    public void setAppid(String appid) {
        this.appid = appid;
    }
    @Value("${com.wx.wxpay.mch_id}")
    public void setMch_id(String mch_id) {
        this.mch_id = mch_id;
    }

    @Value("${com.wx.wxpay.secret}")
    public  void setSecret(String secret) {
        PayDomain.secret = secret;
    }

    public static String getSecret() {
        return secret;
    }

    public static  String getKey() {
        return key;
    }

    public static  String getAppid() {
        return appid;
    }

    public static  String getMch_id() {
        return mch_id;
    }

}

pom:


 

    <dependencies>
<!--        map和xml之间的转换器-->
        <dependency>
            <groupId>com.thoughtworks.xstream</groupId>
            <artifactId>xstream</artifactId>
            <version>1.4.9</version>
        </dependency>
<!--        操作xml标签-->
        <dependency>
            <groupId>org.jdom</groupId>
            <artifactId>jdom</artifactId>
            <version>2.0.2</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.4</version>
        </dependency>
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
            <version>4.5.3</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.16.18</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

 


记得一定要下载官方SDK !!! 然后把我这些代码加上去就OK   。

由于需要得朋友太多了,我就直接上传文件吧。

完整demo文件下载地址:不需要积分

https://download.csdn.net/download/aSmart_Q/36142998

觉得好用,给我点个赞吧!!感谢

  • 5
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 36
    评论
评论 36
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

xixililicm

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值