java微服务调起微信小程序支付记录

原创
05/12 10:47
阅读数 352

微服务调起微信小程序支付记录

  • 申请小程序

  • 申请商户号

  • 查看小程序支付文档

    文档一定要仔细的完整的阅读一遍

  • 设置获取小程序/商户号参数

    • 小程序参数
      • appid 小程序ID
      • openId 微信用户唯一标识

        注意这里小程序使用的支付类型为JSAPI,所以支付的参数里面,openId为必须参数

      查看小程序这些参数的路径为 登陆进入左侧页面,点击开发,打开开发设置,查看小程序参数

      小程序认证,需要先完善小程序的基础资料,比如名称,介绍,头像等,然后资料填写完毕,才会出现去认证。

    • 商户号参数
      • mch_id 商户id
      • 商户密钥 登陆商户-账户中心-API安全-API密钥
  • 内网穿透

    • 继续验用之前介绍的工具-natapp
  • 进入开发

    • 微信统一下单接口

         	private static final String UNIFIEDORDER_URL = "https://api.mch.weixin.qq.com/pay/unifiedorder";
      	public static String getWXUnifiedorderPrepayId(String appId,String mchId,String body,String outTradeNo,
                                                          String totalFee,String notifyUrl,String  tradeType,
                                                          String openId,String mchPublicKey){
                  Map<String,String> payParameter = new HashMap<>(16);
                  //小程序ID
                  payParameter.put("appid",appId);
                  //商户号
                  payParameter.put("mch_id",mchId);
                  //商品描述
                  payParameter.put("body",body);
                  //随机字符串
                  payParameter.put("nonce_str", RandomUtil.randomString(32));
                  //商户订单号
                  payParameter.put("out_trade_no",outTradeNo);
                  //标价金额
                  payParameter.put("total_fee",totalFee);
                  //终端IP
                  payParameter.put("spbill_create_ip", NetUtil.getLocalhostStr());
                  //通知地址-回调地址
                  payParameter.put("notify_url",notifyUrl);
                  //交易类型
                  payParameter.put("trade_type",tradeType);
                  //openid  -- 小程序的JSAPI 需要此参数
                  if (WXPayEnum.TradeType.JSAPI.getKey().equals(tradeType)){
                      payParameter.put("openid",openId);
                  }
                  //签名
                  payParameter.put("sign", WXPayUtils.getSign(payParameter,mchPublicKey));
                  RestTemplate restTemplate = new RestTemplate();
      				//设置字符集,防止返回结果乱码
                  List<HttpMessageConverter<?>> converterList = restTemplate.getMessageConverters();
                  converterList.add(1, new StringHttpMessageConverter(StandardCharsets.UTF_8));
                  restTemplate.setMessageConverters(converterList);
                  String requestBody = XmlUtil.mapToXmlStr(payParameter,"xml");
                  URI uri = URI.create(UNIFIEDORDER_URL);
                  String responseStr = restTemplate.postForObject(uri,requestBody,String.class);
                  Map<String,Object> responseMap = XmlUtil.xmlToMap(responseStr);
                  //判断通信标识是否请求微信成功
                  if (responseMap.get("return_code").toString().equals(WXPayEnum.ReturnCode.FAIL.getKey())){
                      throw new CheckedException(responseMap.get("return_msg").toString());
                  }
                  //查看result_code来判断交易是否成功
                  if (responseMap.get("result_code").toString().equals(WXPayEnum.ResultCode.FAIL.getKey())){
                      throw new CheckedException(WXPayEnum.ErrCode.getErrValue(responseMap.get("result_code").toString()));
                  }
                  return responseMap.get("prepay_id").toString();
              }
      

      这里就是调用微信的统一下单,获取微信的预支付id 注意,预支付id要保存到数据库里,如果用户支付取消,下次再次支付,直接用预支付id 重新拉起,如果重新生成微信回报错,提醒改订单号已经被使用。

    • 签名

          public static String getSign(Map<String,String> signMap, String mchPublicKey) {
          //排除集合内的非空参数
          Predicate<Map.Entry<String, String>> predicateStr = x -> StringUtils.isNotBlank(x.getValue());
          //参数名ASCII码从小到大排序转化成url 参数拼接的模式
          List<Object> list = signMap.entrySet().stream().filter(predicateStr).sorted(Comparator.comparing(x -> x.getKey())).map(et -> et.getKey() + "=" + et.getValue()).collect(Collectors.toList());
          String stringA = StringUtils.join(list,"&");
          System.out.println(stringA);
          //拼接商户平台的密匙key
          String stringSignTemp = stringA+"&key="+ mchPublicKey;
          System.out.println(stringSignTemp);
          //MD5 加密,并且转化大写
          return SecureUtil.md5(stringSignTemp).toUpperCase();
          }
      
    • 支付回调

          	public String wxNotify(HttpServletRequest httpRequest){
              return qnwlOrderService.wxNotify(httpRequest);
          }
      
      
          //取出参数
          String requestInp = IoUtil.read(httpServletRequest.getInputStream(), CharsetUtil.UTF_8);
          log.info("微信支付回调返回xml格式如下:" + requestInp);
          Map<String, Object> requestMap = XmlUtil.xmlToMap(requestInp);
      
      
          //这里使用的IoUtil 和 XmlUtil 都是hutool 的工具
      

      微信的回调返回参数,是给返回的请求里面,塞入了数据流,所以我这边,从HttpServletRequest里面取出了数据流xml 格式为字符串,然后转了map,

    • 验证签名

      	public static boolean checkSign(Map<String,Object> signMap, String mchPublicKey) {
          //取出sign
          String sign = Optional.ofNullable(signMap.get("sign")).orElseThrow(() -> new CheckedException("签名为空,无法验签")).toString();
          //删除map中的签名
          signMap.remove("sign");
          //排除集合内的非空参数
          Predicate<Map.Entry<String, Object>> predicateStr = x -> StringUtils.isNotBlank(x.getValue() + "");
          //参数名ASCII码从小到大排序转化成url 参数拼接的模式
          List<Object> list = signMap.entrySet().stream().filter(predicateStr).sorted(Comparator.comparing(x -> x.getKey())).map(et -> et.getKey() + "=" + et.getValue()).collect(Collectors.toList());
          String stringA = StringUtils.join(list,"&");
          System.out.println(stringA);
          //拼接商户平台的密匙key
          String stringSignTemp = stringA+"&key="+ mchPublicKey;
          System.out.println(stringSignTemp);
          //MD5 加密,并且转化大写
          if (sign.equals(SecureUtil.md5(stringSignTemp).toUpperCase())){
              return true;
          }
          return false;
       }
      
    • 业务代码,返回小程序支付参数

          @GetMapping("/xxxxxxx/{orderNum}/{openId}")
          public R getWxAppletsPay(@PathVariable String orderNum,@PathVariable String openId){
              Order order =  xxxxxxx.getOne(Wrappers.query(new Order()).eq("order_num",orderNum));
              Optional.ofNullable(order).orElseThrow(() -> new CheckedException("下单失败,订单信息为空"));
              if (!QnwlOrderEnums.OrderStatus.ORDER_ACCEPT.key.equals(order.getStatus())){
                  throw new CheckedException("订单状态为已接单才能进行支付");
              }
              String body = "xxxxxxx";
              String prepayId;
              String totalFee = Optional.ofNullable(order.getRealFreight()).orElseThrow(() -> new CheckedException("支付订单费用为空")).multiply(new BigDecimal(100)).stripTrailingZeros().toPlainString();
              if (!Optional.ofNullable(order.getWxPayId()).filter(StringUtils::isNotBlank).isPresent()){
                  prepayId = WXPayUtils.getWXUnifiedorderPrepayId(XCX_APPID,MCH_ID,body,order.getOrderNum(),totalFee,NOTIFY_URL,WXPayEnum.TradeType.JSAPI.getKey(),openId,MCH_PUBLIC_KEY);
                  //将微信的预支付id进行存储,防止下次重新拉取支付。
                  order.setWxPayId(prepayId);
                  xxxxxx.updateById(order);
              } else {
                  prepayId = order.getWxPayId();
              }
              Map<String,String> mapPay = new HashMap<>(5);
              mapPay.put("appId",XCX_APPID);
              mapPay.put("timeStamp", String.valueOf(LocalDateTime.now().toInstant(ZoneOffset.of("+8")).toEpochMilli()));
              mapPay.put("nonceStr", RandomUtil.randomString(32));
              mapPay.put("package","prepay_id=" + prepayId);
              mapPay.put("signType","MD5");
              String paySign = WXPayUtils.getSign(mapPay,MCH_PUBLIC_KEY);
              mapPay.put("paySign",paySign);
              return R.ok(mapPay);
          }
      

      这里返回给小程序支付的参数以后,由小程序前端调用支付的函数,调起支付即可。 这里需要注意,我这里给支付价格的时候,使用了stripTrailingZeros().toPlainString(); 的原因是,我用的类型是decimal 类型,保留了两位小数,这里是去掉xx.00的小数点后面为0 的两位的。

  • 开发中遇到的相关问题

    • 微信回调以后,如何取出参数

      微信回调的参数在请求中,放入了数据流,需要用HttpServletRequest 接收请求,然后用httpServletRequest.getInputStream(),取出流,转字符串,进行查看

    • 如何验签

      验签这里需要注意,微信返回的xml 里面,又好多字段,你需要取出sign,然后对剩余的字段进行排序加密,然后和微信返回的sign进行对比,一致放行。这里回调返回的sign 和你发起下单请求的sign 并不是同一个。

    • 关于一直回调问题

      在测试回调的时候,我给本地打了一个断点,查看回调是否进来,在这里遇到了一个问题。由于有了断点,我未及时的释放请求,进行返回,导致微信,在后续的几秒钟,又进行了调用,所以,我这边后面断点走完了,返回了,微信还是调用我,那会有点迷惑,后面我取消了断点,请求进来,直接给微信返回没又延迟,后续没在出现一直调用我回调接口的问题。

  • 说明

    • 关于微服务如何进行支付回调

      微服务在本地测试的时候,进行回调,我开始想的是,把网关得端口进行映射出去,让微信进行回调,但是回对原有项目由所影响,所以本地测试回调的时候,我只是把我单一的那个门户服务端口进行了映射,让微信直接回调,没由经过网关,回调的接口,对外进行了暴露

展开阅读全文
打赏
0
0 收藏
分享
加载中
更多评论
打赏
0 评论
0 收藏
0
分享
OSCHINA
登录后可查看更多优质内容
返回顶部
顶部