前言

最近为一个公众号h5商城接入了微信领取性能,查找材料过程中踩了很多坑,以此文章记录一下和大家分享

后期筹备

公众号认证

微信领取性能须要开明企业号并进行资质认证,费用一年300,且需企业营业执照等信息,对公账户打款验证

登录微信公众平台https://mp.weixin.qq.com/,创立服务号

如果已有服务号扫码登录后点击公众号头像抉择认证详情菜单

商户开明

点击公众号左侧微信领取菜单,抉择右侧关联商户按钮,如果没有商户按指引申请

参数获取

公众号参数

点击左侧根本配置菜单,记录右侧的利用ID(appid

商户参数

点击公众号左侧微信领取菜单,滑动到已关联商户号,点击查看按钮

进入商户后,抉择产品核心,左侧开发配置,记录商户号(mchId

进入商户后,抉择账户核心,左侧API平安,依照指引获取APIV3密钥(apiV3Key),API证书的序列号(merchantSerialNumber)和私钥文件apiclient_key.pem

参数配置

外网映射

在微信领取本地调试时须要用到外网映射工具,这里举荐NATAPP:https://natapp.cn/(非广)

一个月带备案域名的映射隧道12元,咱们须要两个,一个映射公众号菜单页面,一个映射后端接口

公众号参数

进入公众点击左侧自定义菜单,右侧点击增加菜单,输出外网映射后的菜单地址

如果你是老手,须要进行网页受权认证获取用户openid,那你还须要进行网页受权域名的设置

点左侧接口权限菜单,批改右侧的网页受权用户信息获取

进入后设置JS接口平安域名,会须要将一个txt认证文件搁置到你的动态页面目录,参照指引即可

商户参数

进入商户后,抉择产品核心,左侧我的产品,进入JSAPI领取

点击产品设置,在领取配置模块,增加领取受权目录(后端接口和前端网页都增加)

领取对接

参数申明

wechartpay:  # 公众号id  appId: xxx  # 公众号中微信领取绑定的商户的商户号  mchId: xxxx  # 商户apiV3Keyz密钥  apiV3Key: xxxx  #商户证书序列号  merchantSerialNumber: xxxx  # 领取回调地址  v3PayNotifyUrl: http://xxxxxx/wechatpay/pay_notify  # 退款回调地址  v3BackNotifyUrl: http://xxxxx/wechatpay/back_notify
    @Value("${wechartpay.appId}")    private String appId;    @Value("${wechartpay.mchId}")    private String mchId;    @Value("${wechartpay.apiV3Key}")    private String apiV3Key;    @Value("${wechartpay.merchantSerialNumber}")    private String merchantSerialNumber;    @Value("${wechartpay.v3PayNotifyUrl}")    private String v3PayNotifyUrl;    @Value("${wechartpay.v3BackNotifyUrl}")    private String v3BackNotifyUrl;    public static RSAAutoCertificateConfig config = null;    public static JsapiServiceExtension service = null;    public static RefundService backService = null;    private void initPayConfig() {        initConfig();        // 构建service        if (service == null) {            service = new JsapiServiceExtension.Builder().config(config).build();        }    }    private void initBackConfig() {        initConfig();        // 构建service        if (backService == null) {            backService = new RefundService.Builder().config(config).build();        }    }    private void initConfig() {        String filePath = getFilePath("apiclient_key.pem");        if (config == null) {            config = new RSAAutoCertificateConfig.Builder()                    .merchantId(mchId)                    .privateKeyFromPath(filePath)                    .merchantSerialNumber(merchantSerialNumber)                    .apiV3Key(apiV3Key)                    .build();        }    }    public RSAAutoCertificateConfig getConfig() {        initConfig();        return config;    }    public static String getFilePath(String classFilePath) {        String filePath = "";        try {            String templateFilePath = "tempfiles/classpathfile/";            File tempDir = new File(templateFilePath);            if (!tempDir.exists()) {                tempDir.mkdirs();            }            String[] filePathList = classFilePath.split("/");            String checkFilePath = "tempfiles/classpathfile";            for (String item : filePathList) {                checkFilePath += "/" + item;            }            File tempFile = new File(checkFilePath);            if (tempFile.exists()) {                filePath = checkFilePath;            } else {                //解析                ClassPathResource classPathResource = new ClassPathResource(classFilePath);                InputStream inputStream = classPathResource.getInputStream();                checkFilePath = "tempfiles/classpathfile";                for (int i = 0; i < filePathList.length; i++) {                    checkFilePath += "/" + filePathList[i];                    if (i == filePathList.length - 1) {                        //文件                        File file = new File(checkFilePath);                        if (!file.exists()) {                            FileUtils.copyInputStreamToFile(inputStream, file);                        }                    } else {                        //目录                        tempDir = new File(checkFilePath);                        if (!tempDir.exists()) {                            tempDir.mkdirs();                        }                    }                }                inputStream.close();                filePath = checkFilePath;            }        } catch (Exception e) {            e.printStackTrace();        }        return filePath;    }

将apiclient_key.pem私钥文件拷贝到resources文件夹根目录

Maven援用

        <dependency>            <groupId>com.github.wechatpay-apiv3</groupId>            <artifactId>wechatpay-java</artifactId>            <version>0.2.11</version>        </dependency>        <dependency>            <groupId>commons-io</groupId>            <artifactId>commons-io</artifactId>            <version>2.8.0</version>        </dependency>

用户受权

为了测试流程的完整性,这里简略形容下如何通过网页受权获取用户的openid,几个参数定义如下

var appid = "xxxx";var appsecret = "xxxx";redirect_uri = encodeURIComponent("http://xxxx/xxx.html");response_type = "code";scope = "snsapi_userinfo";
  • 发动受权申请

    function getCodeUrl() {    var url = "https://open.weixin.qq.com/connect/oauth2/authorize?appid=" + appid + "&redirect_uri=" + redirect_uri + "&response_type=" + response_type + "&scope=" + scope + "#wechat_redirect";    return url}

    跳转到开始受权页面,会跳转到redirect_uri这个页面,url参数携带受权code

  • 用户批准受权

    function getAuthUrl(code) {    var url = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=" + appid + "&secret=" + appsecret + "&code=" + code + "&grant_type=authorization_code";    return url}

    依据code生成正式受权url,须要用户手动点击批准,应用get形式申请该url胜利后会返回openid

    var url = getAuthUrl(code);$.get(url, function (data, status) {   var result = JSON.parse(data)   if (!result.errcode) {       var openid = result.openid;    }});

不应用用户受权流程也能简略的获取到用户openid进行测试,如果该用户关注了公众号,抉择公众号左侧的用户治理菜单,点击用户跳转到与该用户的聊天界面,url参数中的tofakeid就是用户的openid

领取筹备

依据用户的openid,订单号,订单金额,订单阐明四个参数进行领取前的参数筹备,会返回如下参数

公众号ID(appId)

工夫戳(timeStamp)

随机串(nonceStr)

打包值(packageVal)

微信签名形式(signType)

微信签名(paySign)

这里的orderID指业务中生成的订单号,最大32位,由数字和字母组成,领取金额最终要转转换成已分为单位

    @PostMapping("/prepay")    public Object prepay(@RequestBody Map<String, Object> params) throws Exception {        String openId = "xxxx";        String orderID = String.valueOf(params.get("orderID"));        BigDecimal payAmount = new BigDecimal(String.valueOf(params.get("payAmount")));        String payDes = "领取测试";        return paySDK.getPreparePayInfo(openId,orderID,payAmount,payDes);    }
    //领取前的筹备参数,供前端调用    public PrepayWithRequestPaymentResponse getPreparePayInfo(String openid, String orderID, BigDecimal payAmount, String payDes) {        initPayConfig();        //元转换为分        Integer amountInteger = (payAmount.multiply(new BigDecimal(100))).intValue();        //组装预约领取的实体        // request.setXxx(val)设置所需参数,具体参数可见Request定义        PrepayRequest request = new PrepayRequest();        //计算金额        Amount amount = new Amount();        amount.setTotal(amountInteger);        amount.setCurrency("CNY");        request.setAmount(amount);        //公众号appId        request.setAppid(appId);        //商户号        request.setMchid(mchId);        //领取者信息        Payer payer = new Payer();        payer.setOpenid(openid);        request.setPayer(payer);        //形容        request.setDescription(payDes);        //微信回调地址,须要是https://结尾的,必须外网能够失常拜访        //本地测试能够应用内网穿透工具,网上很多的        request.setNotifyUrl(v3PayNotifyUrl);        //订单号        request.setOutTradeNo(orderID);        // 加密        PrepayWithRequestPaymentResponse payment = service.prepayWithRequestPayment(request);        //默认加密类型为RSA        payment.setSignType("MD5");        payment.setAppId(appId);        return payment;    }

领取拉起

在微信环境调用领取筹备接口获取参数后,应用WeixinJSBridge.invoke办法发动微信领取

function submitWeChatPay(orderID,payAmount,callback) {    if (typeof WeixinJSBridge != "undefined") {        var param = {            orderID:orderID,            payAmount:payAmount        }        httpPost(JSON.stringify(param),"http://xxxx/wechatpay/prepay",function (data, status) {            var param = {                "appId": data.appId,                    "timeStamp": data.timeStamp,                  "nonceStr": data.nonceStr,                    "package": data.packageVal,                "signType": data.signType,                    "paySign": data.paySign            }            WeixinJSBridge.invoke(                'getBrandWCPayRequest', param,callback);        })    } else {        alert("非微信环境")    }}

领取回调

领取回调地址是在领取筹备阶段传递的,在用户付款实现后会主动调用该接口,传递领取订单的相干信息

    @PostMapping("/pay_notify")    public void pay_notify(HttpServletRequest request, HttpServletResponse response) throws Exception {        //获取报文        String body = getRequestBody(request);        //随机串        String nonceStr = request.getHeader("Wechatpay-Nonce");        //微信传递过去的签名        String signature = request.getHeader("Wechatpay-Signature");        //证书序列号(微信平台)        String serialNo = request.getHeader("Wechatpay-Serial");        //工夫戳        String timestamp = request.getHeader("Wechatpay-Timestamp");        InputStream is = null;        try {            is = request.getInputStream();            // 结构 RequestParam            com.wechat.pay.java.core.notification.RequestParam requestParam = new com.wechat.pay.java.core.notification.RequestParam.Builder()                    .serialNumber(serialNo)                    .nonce(nonceStr)                    .signature(signature)                    .timestamp(timestamp)                    .body(body)                    .build();            // 如果曾经初始化了 RSAAutoCertificateConfig,能够间接应用  config            // 初始化 NotificationParser            NotificationParser parser = new NotificationParser(paySDK.getConfig());            // 验签、解密并转换成 Transaction            Transaction transaction = parser.parse(requestParam, Transaction.class);            //记录日志信息            Transaction.TradeStateEnum state = transaction.getTradeState();            String orderNo = transaction.getOutTradeNo();            System.out.println("订单号:" + orderNo);            if (state == Transaction.TradeStateEnum.SUCCESS) {                System.out.println("领取胜利");                //TODO------                //依据本人的需要解决相应的业务逻辑,异步                //告诉微信回调胜利                response.getWriter().write("<xml><return_code><![CDATA[SUCCESS]]></return_code></xml>");            } else {                System.out.println("微信回调失败,JsapiPayController.payNotify.transaction:" + transaction.toString());                //告诉微信回调失败                response.getWriter().write("<xml><return_code><![CDATA[FAIL]]></return_code></xml>");            }        } catch (Exception e) {            e.printStackTrace();        } finally {            is.close();        }    }

订单查问

除了领取回调的异步告诉,咱们还须要通过定时工作被动去查问领取信息来保障业务订单领取状态的正确

    @PostMapping("/pay_check")    public Object pay_check(@RequestBody Map<String, Object> params) throws Exception {        String orderID = String.valueOf(params.get("orderID"));        com.wechat.pay.java.service.payments.model.Transaction transaction = paySDK.getPayOrderInfo(orderID);        com.wechat.pay.java.service.payments.model.Transaction.TradeStateEnum state = transaction.getTradeState();        if (state == com.wechat.pay.java.service.payments.model.Transaction.TradeStateEnum.SUCCESS) {            return Result.okResult().add("obj", transaction);        } else {            return Result.errorResult().add("obj", transaction);        }    }
    //获取订单领取后果信息    public com.wechat.pay.java.service.payments.model.Transaction getPayOrderInfo(String orderID) {        initPayConfig();        QueryOrderByOutTradeNoRequest request = new QueryOrderByOutTradeNoRequest();        request.setMchid(mchId);        request.setOutTradeNo(orderID);        com.wechat.pay.java.service.payments.model.Transaction transaction = service.queryOrderByOutTradeNo(request);        return transaction;    }

退款申请

退款申请须要业务订单号和微信领取号,所以咱们这里先通过查问订单信息失去transactionId,你也能够冗余记录在表中

退款反对全款退款和局部退款,局部退款对应的场景就是同一个订单买了多个商品,只退款了其中一个

这里只发动退款申请,具体的退款解决进度告诉由退款回调实现

    @PostMapping("/back")    public Object back(@RequestBody Map<String, Object> params) throws Exception {        String orderID = String.valueOf(params.get("orderID"));        String backID = String.valueOf(params.get("backID"));        BigDecimal backAmount = new BigDecimal(String.valueOf(params.get("backAmount")));        paySDK.applyRefund(orderID,backID,backAmount);        return Result.okResult();    }
    //申请退款    public void applyRefund(String orderID, String backID,BigDecimal backAmount) {        initPayConfig();        initBackConfig();        QueryOrderByOutTradeNoRequest payRequest = new QueryOrderByOutTradeNoRequest();        payRequest.setMchid(mchId);        payRequest.setOutTradeNo(orderID);        com.wechat.pay.java.service.payments.model.Transaction transaction = service.queryOrderByOutTradeNo(payRequest);        CreateRequest request = new CreateRequest();        request.setTransactionId(transaction.getTransactionId());        request.setNotifyUrl(v3BackNotifyUrl);        request.setOutTradeNo(transaction.getOutTradeNo());        request.setOutRefundNo(backID);        request.setReason("测试退款");        AmountReq amountReq = new AmountReq();        amountReq.setCurrency(transaction.getAmount().getCurrency());        amountReq.setTotal(Long.parseLong((transaction.getAmount().getTotal().toString())));        amountReq.setRefund( (backAmount.multiply(new BigDecimal(100))).longValue());        request.setAmount(amountReq);        backService.create(request);    }

退款回调

退款回调在申请退款后主动调用该接口,因为退款须要肯定的解决工夫,所以回调告诉个别显示的状态为解决中(PROCESSING)能够在此回调更新订单退款的解决状态

    @PostMapping("/back_notify")    public void back_notify(HttpServletRequest request, HttpServletResponse response) throws Exception {        //获取报文        String body = getRequestBody(request);        //随机串        String nonceStr = request.getHeader("Wechatpay-Nonce");        //微信传递过去的签名        String signature = request.getHeader("Wechatpay-Signature");        //证书序列号(微信平台)        String serialNo = request.getHeader("Wechatpay-Serial");        //工夫戳        String timestamp = request.getHeader("Wechatpay-Timestamp");        InputStream is = null;        try {            is = request.getInputStream();            // 结构 RequestParam            com.wechat.pay.java.core.notification.RequestParam requestParam = new com.wechat.pay.java.core.notification.RequestParam.Builder()                    .serialNumber(serialNo)                    .nonce(nonceStr)                    .signature(signature)                    .timestamp(timestamp)                    .body(body)                    .build();            // 如果曾经初始化了 RSAAutoCertificateConfig,能够间接应用  config            // 初始化 NotificationParser            NotificationParser parser = new NotificationParser(paySDK.getConfig());            // 验签、解密并转换成 Transaction            Refund refund = parser.parse(requestParam, Refund.class);            //记录日志信息            Status state = refund.getStatus();            String orderID = refund.getOutTradeNo();            String backID = refund.getOutRefundNo();            System.out.println("订单ID:" + orderID);            System.out.println("退款ID:" + backID);            if (state == Status.PROCESSING) {                //TODO------                //依据本人的需要解决相应的业务逻辑,异步                //告诉微信回调胜利                response.getWriter().write("<xml><return_code><![CDATA[SUCCESS]]></return_code></xml>");                System.out.println("退款解决中");            } else if (state == Status.SUCCESS) {                //TODO------                //依据本人的需要解决相应的业务逻辑,异步                //告诉微信回调胜利                response.getWriter().write("<xml><return_code><![CDATA[SUCCESS]]></return_code></xml>");                System.out.println("退款实现");            } else {                System.out.println("微信回调失败,JsapiPayController.Refund:" + state.toString());                //告诉微信回调失败                response.getWriter().write("<xml><return_code><![CDATA[FAIL]]></return_code></xml>");            }        } catch (Exception e) {            e.printStackTrace();        } finally {            is.close();        }    }

退款查问

除了退款回调的异步告诉,咱们还须要通过定时工作被动去查问退款信息来保障业务订单退款状态的正确

 @PostMapping("/back_check")    public Object back_check(@RequestBody Map<String, Object> params) throws Exception {        String backID = String.valueOf(params.get("backID"));        Refund refund = paySDK.getRefundOrderInfo(backID);        if (refund.getStatus() == Status.SUCCESS) {            return Result.okResult().add("obj", refund);        }if (refund.getStatus() == Status.PROCESSING) {            return Result.okResult().setCode(2).setMsg("退款解决中").add("obj", refund);        } else {            return Result.errorResult().add("obj", refund);        }    }
    //获取订单退款后果信息    public Refund getRefundOrderInfo(String backID){        initBackConfig();        QueryByOutRefundNoRequest request = new QueryByOutRefundNoRequest();        request.setOutRefundNo(backID);        return backService.queryByOutRefundNo(request);    }