springboot+uniapp +微信支付V3

需求

最近一直在忙的项目主要是app开发 其中涉及到支付开通会员选项

之前一直是使用的支付宝 但是最近需求新增了微信支付的功能

其实对于我个人而言 支付宝的支付比较好对接  微信的接口有点抽象 而且还有v2和v3两个版本

v2的主要是通过xml格式传输信息 v3是使用json格式传输信息  

v2是不需要加密  只要在微信支付后台有v2Key就行  v3的话需要加密 还要有证书 有v3key

我本意是使用v2版本  但是我没找到示例代码  看一些教程又比较笼统 实在是磨不出来

没办法 只能去使用v3版本 看了微信的v3示例代码  又看了微信的api接口  终于app把微信支付弄好了 本篇 主要讲解微信的v3接口对接的流程

依赖

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

微信接口分析

微信v3版本 因为涉及加密  所以需要在微信支付平台里面申请证书  v3密钥  申请证书的同时还有证书序列号也需要记录  微信证书申请下面保存到本地的话 解压后内容如下

其中key.pem为我们需要的密钥 其他都可忽略

然后我们需要的资料在代码中都体现一下  方便调用   (示例代码 一切从简)

其中商户私钥的话就是key.pem文件 打开全复制 黏贴到字符串中就行 

public interface WXPayConstants {
    String NOTIFY_URL ="";//支付成功回调地址
    String MCH_ID ="";// 商户号
    String MCH_SERIAL_NO="";//商户证书序列号
    String API_V3KEY ="";// api3密钥
    String App_ID="";//appid
    String PACKAGE ="sign=WXPay";//签名固定字符串(微信要求的)
    //你的商户私钥
    String PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\n" +
           
            "-----END PRIVATE KEY-----";
}

微信V3接口流程

微信和支付宝的支付有区分的  微信的话 是先去创建预支付下单  然后微信返回给你一个预支付订单号 然后再去跳转到微信 去根据你的预支付订单号查询相关信息(金额,商户等等)然后去完成支付 那就先去和微信交互 创建预支付订单  微信在每次交互的时候还会检查证书的有效期 一般证书的有效期为2个小时  意思就是两次交互的时间间隔为2小时 证书失效 要先更新证书 微信v3支付的新版本 解决了这个问题 我这里不想再去翻资料了  就每次请求更新一下证书  按照道理来说 连接的client还要有个线程池 一切从简了 我们哪app也没多少日活  没必要

预支付后端

 public  R createOrder(Double money,Integer userId) throws IOException {

        PrivateKey merchantPrivateKey =
PemUtil.loadPrivateKey(WXPayConstants.PRIVATE_KEY);
        //使用自动更新的签名验证器,不需要传入证书
        AutoUpdateCertificatesVerifier  verifier = new AutoUpdateCertificatesVerifier(
                new WechatPay2Credentials(WXPayConstants.MCH_ID, new PrivateKeySigner(WXPayConstants.MCH_SERIAL_NO, merchantPrivateKey)),
                WXPayConstants.API_V3KEY.getBytes(StandardCharsets.UTF_8));
        CloseableHttpClient httpClient = WechatPayHttpClientBuilder.create()
                .withMerchant(WXPayConstants.MCH_ID, WXPayConstants.MCH_SERIAL_NO, merchantPrivateKey)
                .withValidator(new WechatPay2Validator(verifier))
                .build();
        HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/pay/transactions/app");  //app下单地址
        httpPost.addHeader("Accept", "application/json");
        httpPost.addHeader("Content-type","application/json; charset=utf-8");

        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectMapper objectMapper = new ObjectMapper();
        //我们系统自己的订单号 时间戳 + 三位随机数字
        String order_id = System.currentTimeMillis() + RandomUtil.randomInteger(3);
       
        ObjectNode rootNode = objectMapper.createObjectNode();
        //发起预支付订单需要的信息  mchid  appid  描述  回调地址 自己的订单号 
        //amount里面是支付的钱数(以分为单位 传入整数) 还可以写支付币种等等  
        //其他参数就需要看微信支付api接口 参数必填有就行了  其他想要也可以填
        //这里支付1分
        rootNode.put("mchid",WXPayConstants.MCH_ID)
                .put("appid", WXPayConstants.App_ID)
                .put("description", "会员充值")
                .put("notify_url", WXPayConstants.NOTIFY_URL)
                .put("out_trade_no", order_id);
        rootNode.putObject("amount")
                .put("total", 1);
            objectMapper.writeValue(bos, rootNode);
       
         
        httpPost.setEntity(new StringEntity(bos.toString("UTF-8"), "UTF-8"));
       //交互
        CloseableHttpResponse  response = httpClient.execute(httpPost);
      
        //取得返回值
        String  bodyAsString = EntityUtils.toString(response.getEntity());
      
        JsonNode  jsonNode = objectMapper.readTree(bodyAsString);
        // 这个地方特别抽象  他取出来的值 就是用双引号包裹好的  所以需要去掉双引号
        // 因为加密的数据不能携带引号
        //微信加密需要 使用应用id 时间戳 随机字符串 微信订单id 且用换行符隔开  且最后一个也需要换行符
        String prepayId = String.valueOf(jsonNode.get("prepay_id"));
        String substring = prepayId.substring(1, prepayId.length() - 1);

        String time_mill = (System.currentTimeMillis() /1000)+"";
        //随机32字符串
        String str = RandomUtil.random(32);
        StringBuffer sb = new StringBuffer();
        //应用id
        sb.append( WXPayConstants.App_ID).append("\n");
        //时间戳
        sb.append(time_mill).append("\n");
        //随机字符串
        sb.append(str).append("\n");
        //微信订单id
        sb.append(substring).append("\n");
        System.out.println(sb);
        
       //加密  使用密钥加密
        String  sign = sign(sb.toString().getBytes(),merchantPrivateKey);
       
       //数据库实例化(略) 生成订单信息     
       
        
        // 返回前端的内容
        Map<String,String> map=new HashMap<>();
        map.put("appid",WXPayConstants.App_ID);
        map.put("partnerid",WXPayConstants.MCH_ID);
        map.put("prepayid", substring);
        map.put("package","Sign=WXPay");
        map.put("noncestr",str);
        map.put("timestamp",time_mill);
        map.put("sign",sign);
        
        // 记得关闭 
        httpClient.close();
        return R.ok(map);
    }

    static String sign(byte[] message,  PrivateKey merchantPrivateKey) throws SignatureException, NoSuchAlgorithmException, InvalidKeyException {
        Signature sign = Signature.getInstance("SHA256withRSA");
        sign.initSign(merchantPrivateKey);
        sign.update(message);
        return Base64.getEncoder().encodeToString(sign.sign());
    }

异常

java.security.InvalidKeyException: Illegal key size 、

这个加密解密的地方部分jdk 可能会报这种错误 去百度百度 看看就可以解决了

前端处理

//订单对象,从服务器获取
var orderInfo = {
  "appid": "wx499********7c70e",  // 应用ID(AppID)
  "partnerid": "148*****52",      // 商户号(PartnerID)
  "prepayid": "wx202254********************fbe90000", // 预支付交易会话ID
  "package": "Sign=WXPay",        // 固定值
  "noncestr": "c5sEwbaNPiXAF3iv", // 随机字符串
  "timestamp": 1597935292,        // 时间戳(单位:秒)
  "sign": "aaaaaaaaaaaa"          // 签名,v2这里用的 MD5 签名  v3是SHA256withRSA 转base64
};
//获取支付渠道
var wxpaySev = null;
plus.payment.getChannels(function(channels){
    for (var i in channels) {
        var channel = channels[i];
        if (channel.id === 'wxpay') {
            wxpaySev = channel;
        }
    }
    //发起支付
    plus.payment.request(wxpaySev, orderInfo, function(result) {
        var rawdata = JSON.parse(result.rawdata);
        console.log("支付成功");
    }, function(e) {
        console.log("支付失败:" + JSON.stringify(e));
    });
  }, function(e){
      console.log("获取支付渠道失败:" + JSON.stringify(e));
});

校验

这个地方的校验是指 预支付下单的时候 你也可以使用微信的工具进行校验 看看你原始数据和微信加密的数据对不对  如果校验通过的话 那就和后端没关系了

微信校验工具

https://pay.wechatpay.cn/wiki/doc/apiv3/wechatpay/download/Product_5.zip

使用第三个 选择你的密钥(key.pem)上面输入这些信息 每个信息后面有一个换行符 最后一个也有 别忘了 下面输入你sign的加密内容 校验就可以了  

需要注意的是不同支付方式的需要的加密信息也不同 具体需要以微信官方的api示例

具体需要查询官方地址 

产品介绍 - JSAPI支付 | 微信支付商户文档中心 (qq.com)

支付结果的查询

最后呢 当然是支付结果的查询  所有支付里面都是 回调的  不是立即给你返回支付成功的信息

目前有两种解决方案  我们在配置微信支付的相关信息里面 会有回调地址  这个地址就是支付成功是微信给你发送信息的地方 但是好像微信会对信息进行加密 然后还需要解密  而且微信的第一次通知为15s 这个老板不是很满意的 所以我选择了第二种  第二种简单 但是吃性能 适合日活少的app 写法简单 粗暴  就是主动去微信里面查询 我的订单的支付状态

     @Scheduled(cron = "0/10 * * * * *")
    @Transactional(rollbackFor = Exception.class)
    public void order() throws URISyntaxException, IOException {
       
         PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(WXPayConstants.PRIVATE_KEY);
        //使用自动更新的签名验证器,不需要传入证书
        AutoUpdateCertificatesVerifier verifier = new AutoUpdateCertificatesVerifier(
                new WechatPay2Credentials(WXPayConstants.MCH_ID, new PrivateKeySigner(WXPayConstants.MCH_SERIAL_NO, merchantPrivateKey)),
                WXPayConstants.API_V3KEY.getBytes(StandardCharsets.UTF_8));
        CloseableHttpClient httpClient = WechatPayHttpClientBuilder.create()
                .withMerchant(WXPayConstants.MCH_ID, WXPayConstants.MCH_SERIAL_NO, merchantPrivateKey)
                .withValidator(new WechatPay2Validator(verifier))
                .build();
        // 去查询所有没有支付状态的订单 我们系统设计里面 预下单为1 即订单初始化
        //2 为订单结束(没有支付) 3为支付成功   
        List<WxPay> wxPays = wxPayMapper.selectByStateIsOne();
        for (WxPay  wxpay:wxPays) {
               URIBuilder uriBuilder = new URIBuilder("https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/"+wxpay.getTitle()+"?mchid=商户号(定值)");
               HttpGet httpGet = new HttpGet(uriBuilder.build());
               httpGet.addHeader("Accept", "application/json");
               CloseableHttpResponse response = httpClient.execute(httpGet);
               String bodyAsString = EntityUtils.toString(response.getEntity());
               System.out.println(bodyAsString);
               JSONObject jsonObject = JSONObject.parseObject(bodyAsString);
                //这是支付结果  根据自己系统去处理就行了
               String result = jsonObject.get("trade_state").toString();
                 //* SUCCESS:支付成功
                //* REFUND:转入退款
                //* NOTPAY:未支付
                //* CLOSED:已关闭
            }
}

当然 也需要写一个超时的订单转状态的定时任务  这样的话 主动询问微信的订单也不会变得很多

 @Scheduled(cron = "0 0 0/2 * * ?")
    public void wad() {
        log.warn("进入订单未支付的修改异常状态 定时任务");
        //当前时间
        Calendar calendar = Calendar.getInstance();
        //减去2小时
        calendar.add(Calendar.HOUR_OF_DAY, -2);
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String format = dateFormat.format(calendar.getTime());
        //更新两小时之前订单未支付的
        wxPayMapper.updateStateFromCreateTime(format);

    }

嘿嘿 最后 我们的app里面没有退款的功能 所有不需要写那么多  但是我看了一下 基本接口都是万变不离其宗 主要就是看看微信的接口 注意一些参数 

希望诸君顺利

最后 我还是想吐槽一下微信 

为什么v2的demo 我都找不到 

为什么微信支付的技术客服我排队一上午还都在排队 全靠自己摸索

而且v2和v3之间的割裂感太强了 

  • 51
    点赞
  • 40
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值