网络知识 娱乐 微信小程序微信支付《JSAPI支付》APIV3详细教程

微信小程序微信支付《JSAPI支付》APIV3详细教程

文章目录

  • 前提
    • 整体介绍
    • 我的maven依赖
    • 1、整体流程
    • 2、openid 的获取
    • 3、统一下单Controller(预支付订单)
    • 4、配置类和配置文件
    • 5、工具类
    • 6、前端接收到必要的参数,进行调起支付页面
    • 7、微信支付通知,notify_url的回调Controller
    • 8、前端小程序端,定时器调用查询订单状态
    • 9、后端提供给小程序查询订单状态的接口
    • 10、用户取消订单
    • 11、商户端迟迟未收到异步通知结果
    • 12、申请退款
    • 13、退款回调通知
    • 欢迎+Q群讨论:821596752

前提

在进行JSAPI微信支付之前,需要准备好一下配置

  • 申请小程序的appid:wxaxxxxxxxxxxbxx8a (类似这样的)

  • 申请商户号:1xxxxxxxxx6

  • 小程序开通微信支付,绑定已经申请好的商户号。登录小程序后台(mp.weixin.qq.com)。点击左侧导航栏的微信支付,在页面中进行开通。(

    注意:以上信息的申请都需要使用企业账户,个人账户不行

    ​ 商户号官网地址:pay.weixin.qq.com

    ​ 小程序官网地址: mp.weixin.qq.com

    • 需要在商户端(pay.weixin.qq.com),api安全配置好apiv3的密钥

整体介绍

博主这篇博客,主要是小程序对接微信支付(JSAPI)

后端:spring boot

前端:微信小程序,uinapp

适用人群:已经申请好所有的资料,小程序平台,微信商户平台等等,本文不提供任何资料。并且需要有自己的业务场景,部分代码无法直接运行,需要加入自己的订单结构

我的maven依赖

<dependencies>
        <dependency>
            <groupId>org.jdom</groupId>
            <artifactId>jdom2</artifactId>
            <version>2.0.6.1</version>
        </dependency>

        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
        </dependency>

        <dependency>
            <groupId>com.github.wechatpay-apiv3</groupId>
            <artifactId>wechatpay-apache-httpclient</artifactId>
            <version>0.4.7</version>
        </dependency>

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

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.25</version>
        </dependency>

        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.3.1</version>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

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

        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>2.7.0</version>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>2.7.0</version>
        </dependency>

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

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

1、整体流程

支付的流程图:
在这里插入图片描述

1-1、如上图,第2点请求下单,访问我们自己的后端接口所需的参数

字段名变量名类型必填示例值描述
应用IDappidstring[1,32]wxd678efh567hg6787由微信生成的应用ID,全局唯一。请求基础下单接口时请注意APPID的应用属性,例如公众号场景下,需使用应用属性为公众号的服务号APPID
直连商户号mchidstring[1,32]1230000109直连商户的商户号,由微信支付生成并下发。
商品描述descriptionstring[1,127]Image形象店-深圳腾大-QQ公仔商品描述
商户订单号out_trade_nostring[6,32]1217752501201407033233368018商户系统内部订单号,只能是数字、大小写字母_-*且在同一个商户号下唯一
通知地址notify_urlstring[1,256]可以先随便写一个不存的地址都行,不影响正常支付,但是获取不到支付结果信息,无法进行修改订单状态异步接收微信支付结果通知的回调地址,通知url必须为外网可访问的url,不能携带参数。 公网域名必须为https,如果是走专线接入,使用专线NAT IP或者私有回调域名可使用http
订单金额amountobjectHashMap amountMap = new HashMap(); amountMap.put(“total”,fee);//金额 amountMap.put(“currency”,“CNY”);//货币类型订单金额信息,他需要一个map,需要进行一层嵌套,可以去参考官网https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_5_1.shtml
支付者payerobject//支付者 HashMap playerMap = new HashMap(); playerMap.put(“openid”,openid);支付者信息,用户在直连商户appid下的唯一标识。 下单前需获取到用户的Openid

以上参数需要注意,openid的获取

2、openid 的获取

需要在小程序端调用wx.login获取临时登陆凭证在获取openid

wx.login({
  success (res) {
    if (res.code) {
      //发起网络请求
      wx.request({
        url: 'https://example.com/onLogin',
        data: {
          code: res.code
        }
      })
    } else {
      console.log('登录失败!' + res.errMsg)
    }
  }
})


通过上面这个方法获取到res.code然后我们自己在编写一个后端接口,去获取openid

下面这个是我自己写的controller

		
		@Resource
    private WxPayConfig wxPayConfig;//这个是一个wx的配置类
	
    @Resource
    private CloseableHttpClient wxPayClient;//配置类中的一个bean

@GetMapping("/onLogin")
    public string onLogin(HttpServletRequest request){
        String js_code = request.getParameter("code");//前端发起请求携带上面获取到的code,后端接收

      //app Secret是小程序密钥(在mp.weixin.qq.com中的开发管理-》开发设置-》AppSecret(小程序密钥)中设置)
        String baseUrl="https://api.weixin.qq.com/sns/jscode2session?appid="+wxPayConfig.getAppid()+"&secret="+wxPayConfig.getAppSecret()
          +"&js_code="+js_code+"&grant_type=authorization_code";
        String res=null;
        try {
          //这里发起请求获取到session-key,和openid
            res = requestByGetMethod(baseUrl).split("/n")[0];
            System.out.println(res);
        } catch (Exception e) {
            e.printStackTrace();
        }
        log.info("res:"+res);
        return res;//返回给前端
    }


//这个方法就是用于发起get请求的
/**
     * 模拟发送url Get 请求
     * @param url
     * @return
     */
    public String requestByGetMethod(String url) {
        log.info("发起get请求");
        CloseableHttpClient httpClient = HttpClients.createDefault();
        StringBuilder entityStringBuilder = null;
        try {
            HttpGet get = new HttpGet(url);
            CloseableHttpResponse httpResponse = null;
            httpResponse = httpClient.execute(get);
            try {
                HttpEntity entity = httpResponse.getEntity();
                entityStringBuilder = new StringBuilder();
                if (null != entity) {
                    BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(httpResponse.getEntity().getContent(), "UTF-8"), 8 * 1024);
                    String line = null;
                    while ((line = bufferedReader.readLine()) != null) {
                        entityStringBuilder.append(line + "/n");
                    }
                }
            } finally {
                httpResponse.close();
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (httpClient != null) {
                    httpClient.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return entityStringBuilder.toString();
    }

3、统一下单Controller(预支付订单)

调用下单接口,返回prepay_id等信息提供给前端,供前端调起支付页面,这里也对应官方图的第二点下单请求

注意:在请求中你需要携带一下参数,具体需要的参数可以看1-1的表格

在这里插入图片描述

		
		@Resource
    private WxPayConfig wxPayConfig;

    @Resource
    private CloseableHttpClient wxPayClient;

    @Resource
    private Verifier verifier;

		@ResponseBody
    @RequestMapping("returnparam")
    public HashMap<String, String> doOrder(HttpServletRequest request, HttpServletResponse response) throws Exception{
        request.setCharacterEncoding("UTF-8");
        response.setCharacterEncoding("UTF-8");
        //得到openid(微信用户唯一的openid)
        String openid = request.getParameter("openid");
        //得到价钱(自定义)
        int fee = 0;//单位是分
        if (null != request.getParameter("price")) {
            fee = Integer.parseInt(request.getParameter("price").toString());
        }
        //得到商品的ID(自定义)
        String goodsid=request.getParameter("goodsid");
        //订单标题(自定义)
        String title = request.getParameter("title");
        //时间戳,
        String times = System.currentTimeMillis() + "";

        //订单编号(自定义 这里以时间戳+随机数)
        Random random = new Random();
        String did = times+random.nextInt(1000);

        log.info("生成订单");
        //调用统一下单API
        HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi");
        // 请求body参数
        Gson gson = new Gson();
        HashMap<Object, Object> paramsMap = new HashMap<>();
        paramsMap.put("appid",wxPayConfig.getAppid());//appid
        paramsMap.put("mchid",wxPayConfig.getMchId());//商户号
        paramsMap.put("description",title);//商品描述
        paramsMap.put("out_trade_no",did);//商户订单号
        paramsMap.put("notify_url","http://d4a93w.natappfree.cc/wxBuy");//通知地址,可随便写,如果不需要通知的话,不影响支付,但是影响后续修改订单状态

        //订单金额
        HashMap<Object, Object> amountMap = new HashMap<>();
        amountMap.put("total",fee);//金额
        amountMap.put("currency","CNY");//货币类型
        paramsMap.put("amount",amountMap);

        //支付者
        HashMap<Object, Object> playerMap = new HashMap<>();
        playerMap.put("openid",openid);

        paramsMap.put("payer",playerMap);

        //将参数转化未json字符串
        String jsonParamsMap = gson.toJson(paramsMap);
        log.info("请求参数:"+jsonParamsMap);

        StringEntity entity = new StringEntity(jsonParamsMap,"utf-8");
        entity.setContentType("application/json");
        httpPost.setEntity(entity);
        httpPost.setHeader("Accept", "application/json");

        //完成签名并执行请求
        CloseableHttpResponse resp = wxPayClient.execute(httpPost);

        try {
            int statusCode = resp.getStatusLine().getStatusCode();
            String bodyAsString = EntityUtils.toString(resp.getEntity());
            if (statusCode == 200) { //处理成功
                log.info("成功,返回结果 = " + bodyAsString);
            } else if (statusCode == 204) { //处理成功,无返回Body
                log.info("成功");
            } else {
                System.out.println("小程序下单失败,响应码 = " + statusCode + ",返回结果 = " + bodyAsString);
                throw new IOException("request failed");
            }
            //相应结果
            HashMap<String,String> resultMap = gson.fromJson(bodyAsString, HashMap.class);


          	//获取prepay—id
            String prepayId = resultMap.get("prepay_id");


            //获取到perpayid之后需要对数据进行二次封装,前端调起支付必须存在的参数
            HashMap<String, String> payMap = new HashMap<>();
            payMap.put("appid",wxPayConfig.getAppid());//appid
            long currentTimestamp = System.currentTimeMillis();//时间戳,别管那么多,他就是需要
            payMap.put("timeStamp",currentTimestamp+"");
            String nonceStr = UUID.randomUUID().toString()
                .replaceAll("-", "")
                .substring(0, 32);;//随机字符串,别管那么多他就是需要,要咱就给
            payMap.put("nonceStr",nonceStr);
						//apiv3只支持这种加密方式
            payMap.put("signType","RSA");
						
            payMap.put("package","prepay_id="+prepayId);

            //通过appid,timeStamp,nonceStr,signType,package以及商户密钥进行key=value形式进行拼接加密
         	 	//加密方法我会放在这个代码段段下面
            String aPackage = buildMessageTwo("传入你的appid", currentTimestamp, nonceStr, payMap.get("package"));
          
            //获取对应的签名
          	//加密方法我会放在这个代码段段下面
            String paySign = sign(wxPayConfig.getPrivateKeyPath(),aPackage.getBytes("utf-8"));

            payMap.put("paySign",paySign);
          
         /**
          *	在这里你可以加入自己的数据库操作,存储一条订单信息,状态为未支付就行了
          *	在这里你可以加入自己的数据库操作,存储一条订单信息,状态为未支付就行了
          *	在这里你可以加入自己的数据库操作,存储一条订单信息,状态为未支付就行了
          */

            log.info("给前端的玩意:"+payMap);//前端会根据这些参数调起支付页面
					//到这里,就已经完成了官网图中的第8步了
            return payMap;

        }finally {
            resp.close();
        }
    }

4、配置类和配置文件

注意:下面这个配置文件需要读取resources中的wxpay.properties配置文件,等下我也会把我的wxpay.properties贴到下方,还有一个证书文件,需要放置在与src同级的目录中《apiclient_key.pem》,这个文件的获取在https://pay.weixin.qq.com/index.php/core/cert/api_cert#/中申请API证书,也就是申请APIV3的那个页面

一定要记得把证书放入到项目目录中!!!
aoiclient_key.pem就是证书