手把手教你实现Java微信JSAPI支付

需求
想在微信浏览器里面实现微信支付. 查看微信支付文档, 发现要用微信JSAPI公众号支付, 微信H5支付是不能实现的.
自己在实现的时候踩了许多坑, 花费了很多时间. 特此记录, 希望能帮助到看到该文章的开发者.

开发环境
后端SpringBoot, 前端VUE

准备工作
1.首先我们要在微信公众号平台微信商户平台 注册账号

2.准备JSAPI支付, 和调用微信统一下单的时候, 所需要的信息.

appid: 这个appid是公众号的appId.
微信公众号截图
重要的一步. 这里appid如果想使用, 必需要在微信商户号那里关联并授权. 可根据 查看指引 这一步如何操作
微信商户平台截图
appsecret: 开发者密码. 可在微信公众号平台, 自己设置. 切记,设置后保存一下, 因为没有地方可以回显.
微信公众号开发者密码
mer_id: 微信商户号id
微信商户号id
key: 微信商户号API秘钥. 配置完之后也需要保存, 因为没有地方可以回显
在这里插入图片描述
body: 订单备注
nonce_str: 即用来标识一笔单, 是个随机数, 可自行实现.
openid: 这个重中之重, 需要前后端一起配合, 后面讲解如何获取.
out_trade_no: 订单编号
spbill_create_ip: ip地址,可以获取当前请求的ip地址, 实现方式有很多, 可自行百度实现.
total_fee: 支付金额, 需要注意这个金额的单位是分, 所以在使用的时候, 要自己订单金额 *100, 可能有时候失败, 也是因为这个原因.
sign_type: 加签方式, 也就是生成 sign 的方式, sign后面会提到, 这里默认是MD5, 建议也写上MD5, 方便调起JSAPI支付的时候, 做统一.
trade_type: 调用的支付方式, 因为这里是JSAPI支付, 所以值为 JSAPI
notify_url: 微信支付的回调, 用于支付成功后处理的业务逻辑. 这个地址必须是外网可以访问的地址, 如果支付成功没有进入回调, 可能就是这里的原因.
sign: 签名, 这个需要程序自己生成, 是根据上面的信息生成, 具体方式可看下面的代码.

其中openid还没有获取到, 最费劲的也就是它了. 下面给出获取openid的步骤, 需要前后端一起配合. 具体实现流程, 可根据自身情况.

获取openid

  1. 用户同意授权, 获取code, 这里获取code的方式, 是我们给前端实现了.
// appid: 需要把我们的appid给前端
// redirect_uri: 回调地址, 前端自己填写, 用来获取code的
https://open.weixin.qq.com/connect/oauth2/authorize?appid=appid&redirect_uri=redirect_uri&response_type=code&scope=SCOPE&state=STATE#wechat_redirect
  1. 根据code获取openid
    有一点需要注意. 这里的code有效时间是5分钟, 且只能用一次, 如果获取openid失败了, 看一下这个code是不是失效了,或者重复使用了, 我们之前前端获取openid失败, 就是因为没有刷新, code重复使用了.
    @ApiOperation("根据code获取openId")
    @GetMapping("openid")
    public Result getOpenIdByCode(@RequestParam String code){
        log.info("根据code:{}获取openId", code);
        String getopenid_url = "https://api.weixin.qq.com/sns/oauth2/access_token";
        String param= "appid="+appid+"&secret="+secret+"&code="+code+"&grant_type=authorization_code";
        String openIdStr = HttpUtils.sendGet(getopenid_url, param);
        JSONObject json = JSONObject.parseObject(openIdStr);// 转成Json格式
        log.info("查询结果: "+json.toString());
        String openId = json.getString("openid");// 获取openId
        return Result.success(ResultEnum.SELECT_SUCCESS, openId);
    }

目前为止, 我们已经获取到了微信JSAPI支付所需要的全部数据, 如果您能跟着实现到了这里, 就已经成功了百分之八十.


下面开始代码实现

  1. 后端接口准备. 编写支付接口中的JSAPI. 这里主要是为了给前端调起JSAPI支付所需要的数据. 也就是后面返回的这几个值
    appId: 也就是这里一直用的appid
    timeStamp: 注意,这里是时间戳, 我们之前调起失败, 也是因为这里没用时间错
    nonceStr: 随机字符
    signType: 签名方式, 和我们调用统一下单生成的签名方式一样. 也要设置为MD5
    package: 包名. 注意: 不能写 package, 因为是关键字
    paySign: 签名
	// 获取当前请求的ip地址
	String requestIp = CommonUtils.getIpAddr(request);
    // 拼接统一下单地址参数
    SortedMap<String,String> params = new TreeMap<>();
    params.put("appid", appid);
    params.put("body", remark);
    params.put("mch_id", merId);
    params.put("nonce_str", CommonUtils.generateUUID());
    params.put("openid", openId);
    params.put("out_trade_no", out_trade_no);//订单号
    params.put("spbill_create_ip", requestIp);
    params.put("total_fee","100");
    params.put("sign_type", "MD5");
    params.put("trade_type", "JSAPI");
    params.put("notify_url", weChatConfig.notify_url);
  String sign = WXPayUtil.createSign(params, weChatConfig.key);
  // 生成签名之后, 注意一定要存进去.
  params.put("sign", sign);
  String payXml;
  try {
      // 微信支付要求这里必须是一个xml
      payXml = WXPayUtil.mapToXml(params);
      log.info("payXml: "+payXml);
      // 调用微信的统一下单接口
      String orderStr = HttpUtils.doPost("https://api.mch.weixin.qq.com/pay/unifiedorder",payXml,4000);
      log.info("统一下单返回结果: "+orderStr);
      if(StringUtils.isEmpty(orderStr)){
          log.error("微信支付失败.原因: 调用微信统一下单接口失败");
          throw new TPlusException(ErrorEnum.WECHAT_PAY_ERROR);
      }
      // 预支付id
      String prepay_id = "";
      if (orderStr.indexOf("SUCCESS") != -1) {
         // 这个工具类可以用微信官方案例中的
          Map<String, String> map = WXPayUtil.xmlToMap(orderStr);
          prepay_id = map.get("prepay_id");
      }
      /** 返回给前端所需要的数据 */
      SortedMap<String,String> payMap = new TreeMap<>();
      payMap.put("appId", appid);
      payMap.put("timeStamp", String.valueOf(new Date().getTime()/1000));
      payMap.put("nonceStr", CommonUtils.generateUUID());
      payMap.put("signType", "MD5");
      payMap.put("package", "prepay_id="+prepay_id);
      String paySign = WXPayUtil.createSign(payMap, key);
      payMap.put("paySign", paySign);
      return payMap;
  } catch (Exception e) {
      log.error("微信支付失败.原因: map参数转xml失败({})", e.getMessage());
      throw new TPlusException(ErrorEnum.WECHAT_PAY_ERROR);
  }
  1. 得到需要的数据之后, 前端来说就很简单了, 直接可以根据返回的数据调起微信支付.
    也可参考: JSAPI调起支付文档
function onBridgeReady(){
   WeixinJSBridge.invoke(
      'getBrandWCPayRequest', {
         "appId":appId,     //公众号ID,由商户传入     
         "timeStamp":timeStamp,         //时间戳,自1970年以来的秒数     
         "nonceStr":nonceStr, //随机串     
         "package":package,     
         "signType":signType,         //微信签名方式:     
         "paySign":paySign //微信签名 
      },
      function(res){
      if(res.err_msg == "get_brand_wcpay_request:ok" ){
      // 使用以上方式判断前端返回,微信团队郑重提示:
            //res.err_msg将在用户支付成功后返回ok,但并不保证它绝对可靠。
      } 
   }); 
}
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();
}

最后一个需要注意的问题是, 前端在调起微信JSAPI支付的时候, 通过本地是无法调用成功的, 必须发布到线上, 而且要在微信商户平台, 配置线上地址的授权域名.
在这里插入图片描述
特别注意: 该域名必须和前端访问的域名一致, 不可以配置主域名. 我们一直无法调起支付, 其中有一个坑也是这个原因.

上面Java调用统一下单时所需要的工具类

  1. CommonUtils
public class CommonUtils {
    /**
     * 生成uuid,即用来标识一笔单,也用做 微信支付的nonce_str
     * @return
     */
    public static String generateUUID(){
        String uuid = UUID.randomUUID().toString().
                replaceAll("-","").substring(0,32);
        return uuid;
    }

    /**
     * 获取用户请求ip
     * @param request
     * @return
     */
    public static String getIpAddr(HttpServletRequest request) {
        String ipAddress = request.getHeader("x-forwarded-for");
        if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
            ipAddress = request.getHeader("Proxy-Client-IP");
        }
        if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
            ipAddress = request.getHeader("WL-Proxy-Client-IP");
        }
        if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
            ipAddress = request.getRemoteAddr();
            if (ipAddress.equals("127.0.0.1") || ipAddress.equals("0:0:0:0:0:0:0:1")) {
                //根据网卡取本机配置的IP
                InetAddress inet = null;
                try {
                    inet = InetAddress.getLocalHost();
                } catch (UnknownHostException e) {
                    e.printStackTrace();
                }
                ipAddress = inet.getHostAddress();
            }
        }
        //对于通过多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割
        if (ipAddress != null && ipAddress.length() > 15) { //"***.***.***.***".length() = 15
            if (ipAddress.indexOf(",") > 0) {
                ipAddress = ipAddress.substring(0, ipAddress.indexOf(","));
            }
        }
        return ipAddress;
    }


到此为止, 就已经完成了微信JSAPI支付, 以上纯手写, 如有错误的地方, 欢迎指教. 谢谢!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值