微信支付之Native支付和JSAPI支付详细开发步骤!

微信支付之扫码Native支付与JSAPI支付

在电商网站开发中,我们必不可少的功能环节就是“支付”了,我们可以根据公司或者自己的开发需求,选择不一样的支付方式,比如微信支付,支付宝支付,银联支付,以及跨境支付如PayPal等等的众多方式供你选择,今天,我将和大家聊聊微信支付之二微信扫码Native支付与微信JSAPI支付。

进入主题

根据微信官方提供的文档,我们来进行梳理。首先我们访问:微信支付-开发文档
进入如下界面:

在这里插入图片描述

一、Native支付
1. 使用场景

我们首先会调用微信提供的API得到二维码链接,这个链接可以通过前端qrcode.js将链接转换为二维码,也可以在后台通过谷歌的ZXing工具生成二维码。详细介绍如下:

在这里插入图片描述

2. 开发步骤

首先,你需要在微信支付的商户平台配置扫码支付的回调域名,具体位置为:商户平台–>产品中心–>开发配置。

在这里插入图片描述

当然,你也可以不用配置扫码支付的回调地址,而是在调用微信支付的统一下单API接口中传递参数notify_url。我选的是这种方式,可以将回调配置在yml文件中,自由定义,不需要关心商户平台的回调地址。

在这里插入图片描述

最后,还需要选择一种模式,我采用的是模式二,你也可以根据自己的需求,选择你认为合适的开发模式。如下图模式二说明了接下来我们编码的流程:

在这里插入图片描述

在这里插入图片描述

3. 开始开发

(1)商户后台系统根据用户选购的商品生成订单。

用户在商城平台提交订单,我们会生成初始订单。

在这里插入图片描述

商城平台生成订单之后,接着调用微信支付。进行第二,第三步骤,如下:

(2)用户确认支付后调用微信支付【统一下单API】生成预支付交易;

这一步骤,有一个关键的地方是生成商户单号。如下是我生成商户单号的方式。

在这里插入图片描述

生成商户单号

@Override
    @Transactional(rollbackFor = Exception.class)
    public String generateCode(KeSequence.Tag tag,  String userMobile) {
        Long sequence = this.findSequence(Long.parseLong(tag.getCode()+""));

        this.updateSequence(Long.parseLong(tag.getCode()+""),sequence);

        return DateWithRandomUtils.numsToRandom()+ sequence+ ((userMobile.length()>4)?(userMobile.substring(userMobile.length()-4)):userMobile);
    }

调用工具方法

/**
     * 将生成的时间+num位随机数,打乱
     * @return
     */
    public static String numsToRandom(){
        String randomNO = randomNO(4);
        char[] chars = randomNO.toCharArray();
        List<String> list = new ArrayList<>();
        for (char aChar : chars) {
            list.add(aChar+"");
        }
        Collections.shuffle(list);

        StringBuffer sb = new StringBuffer();
        list.forEach(ch->{
            sb.append(ch);
        });
        return sb.toString();
    }

工具方法细节:

/**
     * 唯一编号 = 点前时间年月日时分秒+num位随机数
     * @return
     */
    public static String randomNO(int num){
        String newsNo = timeNoYMDHMS();
        long random = getRandom(num);
        return newsNo+random;
    }

    /**
     * 获取当前时间 yyyyMMddHHmmss
     * @return
     */
    public static  String timeNoYMDHMS(){
        //设置日期格式
        SimpleDateFormat df = new SimpleDateFormat("yyyyMMddHHmmss");
        // new Date()为获取当前系统时间,也可使用当前时间戳
        String newsNo = df.format(new Date());
        return newsNo;
    }
    /**
     * 生成固定长度随机码
     * @param n    长度
     */
    public static long getRandom(long n) {
        long min = 1,max = 9;
        for (int i = 1; i < n; i++) {
            min *= 10;
            max *= 10;
        }
        long rangeLong = (((long) (new Random().nextDouble() * (max - min)))) + min ;
        return rangeLong;
    }

(3)微信支付系统收到请求后生成预支付交易单,并返回交易会话的二维码链接code_url。

生成商户单号,接着可以通过微信提供的SDK,调用统一下单的方法了,我的做法如下:

核心代码:

/**
     * 微信支付
     * @param keOrder
     * @return
     */
    private ResultMsg WxUnifiedorder(KeOrder keOrder,String bizPayNo, Integer tradeType, String openid) throws Exception {
        //设置需要的参数
        WxParamQuery wxParamQuery = new WxParamQuery();
        //是
        wxParamQuery.setOut_trade_no(bizPayNo);
        //以分为单位
        wxParamQuery.setTotal_fee(keOrder.getActualTotal().multiply(BigDecimal.valueOf(100)).longValue()+"");
        String body = "";
        if(!StringUtils.isEmpty(keOrder.getProdName())){
            body = keOrder.getProdName().length()>15 ? keOrder.getProdName().substring(0,15)+"...":keOrder.getProdName();
        }
        wxParamQuery.setBody(body);
        wxParamQuery.setReceipt("N");
        // 注意:此处配置交易方式, JSPAI, NATIVE...
        wxParamQuery.setTrade_type(WxParamQuery.getTradeTypeMap(tradeType));
        //否
        wxParamQuery.setAttach(keOrder.getOrderNo());
        wxParamQuery.setProduct_id("");
        wxParamQuery.setOpenid(openid);
        wxParamQuery.setDetail("");
        wxParamQuery.setDevice_info("WEB");
        wxParamQuery.setUserId(keOrder.getUserId());


        return wxPayService.unifiedorder(wxParamQuery);
    }

调用统一下单API:

/**
     * 微信支付-统一下单
     * @param wxParamQuery
     * @return
     */
    @Override
    @Transactional
    public ResultMsg unifiedorder(WxParamQuery wxParamQuery) throws Exception {
        Map<String,String> data = new ConcurrentHashMap();
        data.put("device_info",wxParamQuery.getDevice_info());
        data.put("body",wxParamQuery.getBody());
        data.put("detail",wxParamQuery.getDetail());
        data.put("attach",wxParamQuery.getAttach());
        data.put("out_trade_no",wxParamQuery.getOut_trade_no());
        data.put("total_fee",wxParamQuery.getTotal_fee());
        data.put("trade_type",wxParamQuery.getTrade_type());
        data.put("product_id",wxParamQuery.getProduct_id());
        data.put("openid",wxParamQuery.getOpenid());
        data.put("receipt",wxParamQuery.getReceipt());
        data.put("spbill_create_ip", spbill_create_ip);

        if("JSAPI".equalsIgnoreCase(wxParamQuery.getTrade_type())){
            if(StringUtils.isEmpty(wxParamQuery.getOpenid())){
                return ResultMsg.fail("缺少参数openid",null);
            }
        }

        try {
            WXPayConfig payConfig =  new PayConfig(appID,mchID,key,domain,primaryDomain, true);
            WXPay wxPay = new WXPay(payConfig,notify_url,true,false);
            data = wxPay.fillRequestData(data);

            //微信提供的统一下单方法一:
            Map<String,String> resMap = wxPay.unifiedOrder(data);

            //主动调微信统一下单方法二
            /*
            String xml = WXPayUtil.mapToXml(data);
            String res = HttpHelper.post(pay_url, xml, "text/plain;charset=UTF-8");
            Map<String, String> resMap = WXPayUtil.xmlToMap(res);
            */

            if("SUCCESS".equalsIgnoreCase(resMap.get("return_code"))){
                if("SUCCESS".equalsIgnoreCase(resMap.get("result_code"))){
                    // 获取二维码链接
                    String code_url = resMap.get("code_url");
                    String prepay_id = resMap.get("prepay_id");
                    Map<String, String> payParam = wxUtils.getJsapiPayParam(prepay_id);
                    if(wxParamQuery.getTrade_type().equals(WxParamQuery.TradeType.NATIVE.name())){
                        return ResultMsg.suc(code_url);
                    }else{
                        return ResultMsg.suc(payParam);
                    }
                }
            }
            String return_msg = resMap.get("return_msg");
            return ResultMsg.fail(return_msg,return_msg);

        } catch (Exception e) {
            e.printStackTrace();
            return ResultMsg.fail(e.getMessage(),e.getMessage());
        }
    }

调用成功之后,微信会返回code_url, 二维码链接,至此,我们可以将二维码链接转换为二维码图片,即可。

当用户扫码支付完成之后,会进入回调,我们可以在回调中修改订单的状态或者其他必要的操作。如下,是我的回调代码。

核心代码:

@RequestMapping(value = "/notifyUrl")
    public void notifyUrl(HttpServletRequest request, HttpServletResponse response) throws Exception {
        String resXml = "";
        try {
            log.info("微信notifyUrl回调方法,开始进入。。。");

            //读取参数
            StringBuffer sb = new StringBuffer();
            InputStream inputStream = request.getInputStream();
            BufferedReader in = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"));
            String str = null ;
            while ((str = in.readLine()) != null){
                sb.append(str);
            }
            in.close();
            inputStream.close();


            //解析xml成map
            Map<String, String> m = WXPayUtil.xmlToMap(sb.toString());


            //过滤空 设置 TreeMap
            SortedMap<String,String> packageParams = new TreeMap<>();
            Iterator<String> it = m.keySet().iterator();
            while (it.hasNext()) {
                String parameter = it.next();
                String parameterValue = m.get(parameter);

                String v = "";
                if(null != parameterValue) {
                    v = parameterValue.trim();
                }
                packageParams.put(parameter, v);
            }
            log.info("微信支付返回回来的参数:"+packageParams);

            try {
                // 支付日志入库
                iKePayLogService.saveWxPayLog(packageParams);
            }catch (Exception e){
                e.printStackTrace();
            }


            //判断签名是否正确
            if(WXPayUtil.isSignatureValid(m, key, WXPayConstants.SignType.HMACSHA256)) {
                //------------------------------
                //处理业务开始
                //------------------------------

                if("SUCCESS".equalsIgnoreCase( packageParams.get("return_code")) &&
                        "SUCCESS".equals(packageParams.get("result_code"))){
                    // 这里是支付成功
                    //执行自己的业务逻辑开始
                    String app_id = packageParams.get("appid");
                    String mch_id = packageParams.get("mch_id");
                    String openid = packageParams.get("openid");
                    //是否关注公众号
                    String is_subscribe = packageParams.get("is_subscribe");

                    //附加参数【订单号】
                    String attach = packageParams.get("attach");
                    //商户订单号
                    String out_trade_no = packageParams.get("out_trade_no");

                    //付款金额【以分为单位】
                    String total_fee = packageParams.get("total_fee");

                    //微信生成的交易订单号
                    String transaction_id = packageParams.get("transaction_id");

                    //支付完成时间
                    String time_end= packageParams.get("time_end");

                    log.info("app_id:"+app_id);
                    log.info("mch_id:"+mch_id);
                    log.info("openid:"+openid);
                    log.info("is_subscribe:"+is_subscribe);
                    log.info("out_trade_no:"+out_trade_no);
                    log.info("total_fee:"+total_fee);
                    log.info("额外参数_attach:"+attach);
                    log.info("time_end:"+time_end);


                    //执行自己的业务逻辑开始
                    log.info("支付成功。。。执行自己的业务逻辑开始");
                    OrderPayDTO orderPayDTO = new OrderPayDTO();
                    orderPayDTO.setTransaction_id(transaction_id);
                    orderPayDTO.setOut_trade_no(out_trade_no);
                    orderPayDTO.setMch_id(mch_id);
                    orderPayDTO.setApp_id(app_id);
                    orderPayDTO.setAttach(attach);
                    orderPayDTO.setTime_end(LocalDateTime.now());
                    orderPayDTO.setOpenid(openid);
                    orderPayDTO.setIs_subscribe(is_subscribe);
                    orderPayDTO.setTotal_fee(BigDecimal.valueOf(Double.parseDouble(total_fee)));
                    orderPayDTO.setPayType(KeOrder.orderPayType.WECHAT_PAY.getCode());

                    //调用业务处理方法
                    iKeOrderService.orderPaySuc(orderPayDTO);
                    //执行自己的业务逻辑结束

                    //通知微信.异步确认成功.必写.不然会一直通知后台.八次之后就认为交易失败了.
                    resXml = notifyToWx("SUCCESS","OK");

                } else {
                    log.info("支付失败,错误信息:" + packageParams.get("err_code"));
                    resXml = notifyToWx("FAIL",packageParams.get("err_code"));
                }

            } else{
                resXml = notifyToWx("FAIL","签名验证失败");
            }
        }catch (Exception e){
            e.printStackTrace();
            resXml = notifyToWx("FAIL","程序异常");
        }

        //------------------------------
        //处理业务完毕
        //------------------------------
        BufferedOutputStream out = new BufferedOutputStream(
                response.getOutputStream());
        out.write(resXml.getBytes());
        out.flush();
        out.close();
    }



    public String notifyToWx(String return_code, String return_msg){
        return "<xml>" + "<return_code><![CDATA["+return_code+"]]></return_code>"
                + "<return_msg><![CDATA["+return_msg+"]]></return_msg>" + "</xml> ";
    }
二、JSAPI支付

JSAPI支付的后端实现逻辑和Native之后一样,只需要多传入一个必传参数openid,上面已经列举,我就不必再次重复说明了。 接下来,我将说明和Native不同的实现逻辑。

1. 使用场景

我们开发公众号时,需要调用微信支付,这时候就是需要JSPAPI支付了,JSAPI需要通过网页授权,获取用户的openid,也就是上述步骤中调用微信的统一下单需要的步骤,然后再改变trade_type为JSAPI,如下
在这里插入图片描述
其余步骤与Native一致。

在这里插入图片描述

2. 开发步骤

JSAPI支付必须要在商户平台配置支付目录,所谓支付目录就是指:前端调起微信支付的当前URL地址。 设置如下:

在这里插入图片描述

3. 开始开发

调起支付,需要配置jsconfig,我用的是vue, 以下我将介绍vue的方式发起支付。
首先前端配置如下:

mounted() {
    this.axios
      .get(
        "/kuais/essleyWeb/essley/wx/getWxConfigParams",
        {
          params: {
            webUrl: this.webUrl
          }
        }
      )
      .then(res => {
        var data = res.data.data;
          console.log(data)
        wx.config({
          debug: false, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。
          appId: data.appId, // 必填,公众号的唯一标识
          timestamp: data.timestamp, // 必填,生成签名的时间戳
          nonceStr: data.nonceStr, // 必填,生成签名的随机串
          signature: data.signature, // 必填,签名,见附录1
          jsApiList: ["chooseWXPay"]
        });
        wx.ready(function() {
          wx.checkJsApi({
            jsApiList: ["chooseWXPay"],
            success: function(res) {
              console.log("seccess");
              console.log(res);
            },
            fail: function(res) {
              console.log("fail");
              console.log(res);
            }
          });
        });
      });
  },

我们需要在后端,获取token,然后获取jsapi_ticket。 得到初始化参数之后,返回给前端,配置jsconfig。 可以通过微信开发者工具中查看是否配置成功,(有时候你看JS-SDK中提示签名错误,其实也是可以调起微信支付的)。 代码如下:

/**
     * 签名算法
     签名生成规则如下:参与签名的字段包括noncestr(随机字符串),
     有效的jsapi_ticket, timestamp(时间戳),
     url(当前网页的URL,不包含#及其后面部分) 。
     对所有待签名参数按照字段名的ASCII 码从小到大排序(字典序)后,
     使用URL键值对的格式(即key1=value1&key2=value2…)拼接成字符串string1。
     这里需要注意的是所有参数名均为小写字符。对string1作sha1加密,字段名和字段值都采用原始值,不进行URL 转义。
     */
    public Map<String,String> wxConfigParams(String webUrl){
        String jsapi_ticket = jsapiTicketFromRedis();
        String noncestr = WXPayUtil.generateNonceStr();
        String timestamp = WXPayUtil.getCurrentTimestamp()+"";
        String url = webUrl;

        String[] arr = new String[]{jsapi_ticket,noncestr, timestamp,url};
        String string1 = "jsapi_ticket="+jsapi_ticket+"&noncestr="+noncestr+"&timestamp="+timestamp+"&url="+url;

        log.info("拼接好的string1: "+string1);
        String sha1Hex = DigestUtils.sha1Hex(string1.getBytes());

        log.info("jsapi_ticket签名:  "+sha1Hex);

        Map<String,String> resMap = new HashMap<>();
        resMap.put("appId", WebAppID);
        resMap.put("timestamp", timestamp);
        resMap.put("nonceStr", noncestr);
        resMap.put("signature", sha1Hex);

        return resMap;
    }

jsconfig配置成功之后,接下来就可以调用微信统一下单API了, 与Native扫码支付不同的是,它需要返回如下参数:

appId: data.appId,
timestamp: data.timeStamp, // 支付签名时间戳,注意微信jssdk中的所有使用timestamp字段均为小写。但最新版的支付后台生成签名使用的timeStamp字段名需大写其中的S字符
nonceStr: data.nonceStr, // 支付签名随机串,不长于 32
package: data.package, // 统一支付接口返回的prepay_id参数值,提交格式如:prepay_id=\*\*\*)
signType: data.signType, // 签名方式,默认为'SHA1',使用新版支付需传入'MD5'
paySign: data.paySign, // 支付签名

如果后端返回的参数没有问题,这时候,就可以发起微信支付了。输入密码,就可以支付完成。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值