PC网站微信扫码支付,Native支付,“当前商户号暂不支持关联该类型的appid“,“签名错误,请检查后再试““springBoot 微信支付“


最近项目需求集成支付功能,支付宝支付就不用多说了,官方文档Demo都很详细,仔细搞一下就可以。今天这篇文章主要讲集成PC网站集成Native支付,中间遇到了几个坑,这里给大家讲解下解决方法,相信一定会帮到你。
下面的支付Demo是老版本使用xml传输的格式,新版本使用的是json
api文档: https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=6_1

一、采坑大合集

1.当前商户号暂不支持关联该类型的appid

微信支付需要的三个重要参数 :1.mch_id 2.mch_api_key 3.app_id

  1. mch_id:商户id,这个去微信商户平台注册认证即可
  2. mch_api_key:注册成功后,进入商户平台->账户中心->API安全->设置API密钥
    在这里插入图片描述
  3. app_id:appId,才是接下来的重点,当你根据官方文档说明,去微信公众平台注册账号,并交完认证费300元后,获得了appId,然后再进行appId与mch_id绑定时,提示"当前商户号暂不支持关联该类型的appid"。去社区客服回答:“开放平台网站应用和第三方应用均不支持自助绑定,请悉知”
    解决办法: 在微信公众平台注册时,有四种选择:订阅号,服务号,小程序,企业微信。默认创建的应该是订阅号,但是Native支付只能绑定公众平台类型为服务号的appId,时间:2021/09/03,以后是否支持不一定。所以小伙伴们麻溜的去申请一个服务号类型的appId就可以了

2.签名错误,请检查后再试

  1. 签名错误问题,首先需要检查的是对照官方的api,看看哪些字段是必须的,是否有漏掉
  2. 字段无误后,发现还是错误,然后网上搜了一圈,说是什么必须按照某个顺序罗列字段才行,然后你就试了各种顺序还是错误。其实并不是你的字段错误,而真的是签名错误。
  3. 签名校验原理:将所有参数用api_key进行md5加密后生成sign,然后将sign随参数一起发送微信,微信端根据参数也用api_key进行md5加密后生成的sign与你参数的sign进行比较,相同的话就认证成功,不同就认证失败
    在这里插入图片描述
    在这里插入图片描述
    看上面两段代码有何不同,在生成sign的时候一定要将全部参数都加进去,不能在生成了sign后,还有别的参数添加,这样就会导致签名错误。所以只要保证sign在最后一个就好了

二、springboot集成微信支付Demo(老版本XML)

1.官方SDK下载:https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=11_1

在这里插入图片描述
好多工具类从sdk中copy即可,下面是我的集成demo,

统一下单以及回调接口
WXpay.java

private static String wxpayString="<form name=\"punchout_form\" method=\"post\" action=\"%s\">\n" +
            "<input type=\"hidden\" name=\"wxpayUrl\" value=\"%s\">\n" +
            "<input type=\"hidden\" name=\"id\" value=\"%s\">\n" +
            "<input type=\"submit\"  style=\"display:none\" >\n" +
            "</form>\n" +
            "<script>document.forms[0].submit();</script>\n";
    private static String  qrCodeString="/wxpay/qrCode";
 /**
     * 腾讯支付
     * @param product
     * @param order must 1.order_code 2.money 3.spbill_create_ip
     * @return
     * @throws Exception
     */
    public static String goWXpay(BillingPackageVo product, BillingPackageOrderVo order) {
        String result="";
        try {
            //1.配置请求参数
            HashMap<String,String> reqData=new HashMap<>();
            reqData.put("appid", app_id);
            reqData.put("mch_id", mch_id);
            reqData.put("device_info", WEB);
            reqData.put("nonce_str", WXpayUtil.generateNonceStr());
            reqData.put("sign_type", SignType.MD5+"");
            //商品描述
            reqData.put("body", product.getBillingPackageName()+",服务期限"+
                    product.getBillingPeriod()+"天,支持设备"+product.getMaxAccessClientNumber()+"个");
            //商品详情
//        reqData.put("detail", "微信支付测试");
            //订单号
            reqData.put("out_trade_no",order.getBillingPackageOrderCode());
            //订单总金额,单位为分
            reqData.put("total_fee",(long)(order.getOrderAmount()*100)+"");
            //终端IP,客户端IP
            reqData.put("spbill_create_ip",order.getSpbillCreateIp());
            //通知地址
            reqData.put("notify_url",notify_url);
            //交易类型
            reqData.put("trade_type", TradeType.NATIVE+"");
            //商品Id,trade_type=NATIVE时,此参数必传。
            reqData.put("product_id",order.getBillingPackageOrderCode());
            // 附加数据,在查询API和支付通知中原样返回,可作为自定义参数使用
            reqData.put("attach",String.valueOf(null==order.getAccessClientId()?0:order.getAccessClientId()));
            reqData.put("sign", WXpayUtil.generateSignature(reqData, api_key, SignType.MD5));
            // 2.map转换成xml
            String reqBody = WXpayUtil.mapToXml(reqData);
            log.info("订单号:"+order.getBillingPackageOrderCode()+",微信支付请求xml:"+reqBody);
            //3.发送支付请求
            String resXml= HttpClient.sendPostDataByXml(gatewayUrl, reqBody);
            log.info("订单号:"+order.getBillingPackageOrderCode()+",微信支付返回xml:"+resXml);
            //4.xml转换成map
            Map<String, String> resMap = WXpayUtil.xmlToMap(resXml);
            String codeUrl=(SUCCESS.equals(resMap.get("return_code")) && SUCCESS.equals(resMap.get("result_code")))?resMap.get("code_url"):"";

            //5.组装返回值
            result=String.format(wxpayString,qrCodeString,codeUrl,order.getId());
            log.info("微信支付跳转信息:"+result);
            return result;
        }catch (Exception e){
            e.printStackTrace();
            log.info("微信支付请求失败,订单号:"+order.getBillingPackageOrderCode());
            return  result;
        }
    }
     /**
     * 微信支付回调
     * @param request
     * @return
     */
    @ResponseBody
    @RequestMapping("/wechatNotify")
    public String weChatNotify(HttpServletRequest request) throws Exception {
        log.info("微信支付成功, 进入异步通知接口...");
        Map<String,String> returnMap=new HashMap<>();
        returnMap.put("return_code",SUCCESS);
        returnMap.put("return_msg","");

        Map<String, String> notifyMap = WXpayUtil.getNotifyParameter(request);  // 转换成map

        if (!this.isPayResultNotifySignatureValid(notifyMap)) {
            // 签名错误,如果数据里没有sign字段,也认为是签名错误
            log.info("签名错误!!!!");
            returnMap.put("return_msg","sign not valid");
            return WXpayUtil.mapToXml(returnMap);
        }
        //订单号
        String orderCode = notifyMap.get("out_trade_no");
        String resultCode = notifyMap.get("result_code");
        //格式为yyyyMMddHHmmss,如2009年12月25日9点10分10秒表示为20091225091010。
        Long paymentTime =DataUtil.stringIntegerSpecialDate(notifyMap.get("time_end"));
        String paymentCode= notifyMap.get("transaction_id");
        // 附加数据,在查询API和支付通知中原样返回,可作为自定义参数使用
        String attach= notifyMap.get("attach");
        //这里做其他业务操作
        returnMap.put("return_msg","");
        return WXpayUtil.mapToXml(returnMap);
    }
     /**
     * 判断支付结果通知中的sign是否有效
     *
     * @param reqData 向wxpay post的请求数据
     * @return 签名是否有效
     * @throws Exception
     */
    private boolean isPayResultNotifySignatureValid(Map<String, String> reqData) throws Exception {
        String signTypeInData = reqData.get(WXPayConstants.FIELD_SIGN_TYPE);
        SignType signType;
        if (signTypeInData == null) {
            signType = SignType.MD5;
        } else {
            signTypeInData = signTypeInData.trim();
            if (signTypeInData.length() == 0) {
                signType = SignType.MD5;
            }
            else if (WXPayConstants.MD5.equals(signTypeInData)) {
                signType = SignType.MD5;
            }
            else if (WXPayConstants.HMACSHA256.equals(signTypeInData)) {
                signType = SignType.HMACSHA256;
            }
            else {
                throw new Exception(String.format("Unsupported sign_type: %s", signTypeInData));
            }
        }
        return this.isSignatureValid(reqData,api_key, signType);
    }

    /**
     * 判断签名是否正确,必须包含sign字段,否则返回false。
     *
     * @param data Map类型数据
     * @param key API密钥
     * @param signType 签名方式
     * @return 签名是否正确
     * @throws Exception
     */
    public static boolean isSignatureValid(Map<String, String> data, String key, SignType signType) throws Exception {
        if (!data.containsKey(WXPayConstants.FIELD_SIGN) ) {
            return false;
        }
        String sign = data.get(WXPayConstants.FIELD_SIGN);
        return WXpayUtil.generateSignature(data, key, signType).equals(sign);
    }

WXpayUtil.java

private static final String SYMBOLS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";

    private static final Random RANDOM = new SecureRandom();

    /**
     * XML格式字符串转换为Map
     *
     * @param strXML XML字符串
     * @return XML数据转换后的Map
     * @throws Exception
     */
    public static Map<String, String> xmlToMap(String strXML) throws Exception {
        try {
            Map<String, String> data = new HashMap<String, String>();
            DocumentBuilder documentBuilder = WXpayUtil.newDocumentBuilder();
            InputStream stream = new ByteArrayInputStream(strXML.getBytes("UTF-8"));
            org.w3c.dom.Document doc = documentBuilder.parse(stream);
            doc.getDocumentElement().normalize();
            NodeList nodeList = doc.getDocumentElement().getChildNodes();
            for (int idx = 0; idx < nodeList.getLength(); ++idx) {
                Node node = nodeList.item(idx);
                if (node.getNodeType() == Node.ELEMENT_NODE) {
                    org.w3c.dom.Element element = (org.w3c.dom.Element) node;
                    data.put(element.getNodeName(), element.getTextContent());
                }
            }
            try {
                stream.close();
            } catch (Exception ex) {
                ex.printStackTrace();
            }
            return data;
        } catch (Exception ex) {
            throw ex;
        }

    }

    /**
     * 将Map转换为XML格式的字符串
     *
     * @param data Map类型数据
     * @return XML格式的字符串
     * @throws Exception
     */
    public static String mapToXml(Map<String, String> data) throws Exception {
        org.w3c.dom.Document document = WXpayUtil.newDocument();
        org.w3c.dom.Element root = document.createElement("xml");
        document.appendChild(root);
        for (String key: data.keySet()) {
            String value = data.get(key);
            if (value == null) {
                value = "";
            }
            value = value.trim();
            org.w3c.dom.Element filed = document.createElement(key);
            filed.appendChild(document.createTextNode(value));
            root.appendChild(filed);
        }
        TransformerFactory tf = TransformerFactory.newInstance();
        Transformer transformer = tf.newTransformer();
        DOMSource source = new DOMSource(document);
        transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
        transformer.setOutputProperty(OutputKeys.INDENT, "yes");
        StringWriter writer = new StringWriter();
        StreamResult result = new StreamResult(writer);
        transformer.transform(source, result);
        String output = writer.getBuffer().toString(); //.replaceAll("\n|\r", "");
        try {
            writer.close();
        }
        catch (Exception ex) {
            ex.printStackTrace();
        }
        return output;
    }

    /**
     * 获取随机字符串 Nonce Str
     *
     * @return String 随机字符串
     */
    public static String generateNonceStr() {
        char[] nonceChars = new char[32];
        for (int index = 0; index < nonceChars.length; ++index) {
            nonceChars[index] = SYMBOLS.charAt(RANDOM.nextInt(SYMBOLS.length()));
        }
        return new String(nonceChars);
    }
    /**
     * 生成签名. 注意,若含有sign_type字段,必须和signType参数保持一致。
     *
     * @param data 待签名数据
     * @param key API密钥
     * @param signType 签名方式
     * @return 签名
     */
    public static String generateSignature(final Map<String, String> data, String key, SignType signType) throws Exception {
        Set<String> keySet = data.keySet();
        String[] keyArray = keySet.toArray(new String[keySet.size()]);
        Arrays.sort(keyArray);
        StringBuilder sb = new StringBuilder();
        for (String k : keyArray) {
            if (k.equals(WXPayConstants.FIELD_SIGN)) {
                continue;
            }
            if (data.get(k).trim().length() > 0) // 参数值为空,则不参与签名
                sb.append(k).append("=").append(data.get(k).trim()).append("&");
        }
        sb.append("key=").append(key);
        if (SignType.MD5.equals(signType)) {
            return MD5(sb.toString()).toUpperCase();
        }
        else if (SignType.HMACSHA256.equals(signType)) {
            return HMACSHA256(sb.toString(), key);
        }
        else {
            throw new Exception(String.format("Invalid sign_type: %s", signType));
        }
    }
    /**
     * 生成 MD5
     *
     * @param data 待处理数据
     * @return MD5结果
     */
    public static String MD5(String data) throws Exception {
        java.security.MessageDigest md = MessageDigest.getInstance("MD5");
        byte[] array = md.digest(data.getBytes("UTF-8"));
        StringBuilder sb = new StringBuilder();
        for (byte item : array) {
            sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3));
        }
        return sb.toString().toUpperCase();
    }

    /**
     * 生成 HMACSHA256
     * @param data 待处理数据
     * @param key 密钥
     * @return 加密结果
     * @throws Exception
     */
    public static String HMACSHA256(String data, String key) throws Exception {
        Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
        SecretKeySpec secret_key = new SecretKeySpec(key.getBytes("UTF-8"), "HmacSHA256");
        sha256_HMAC.init(secret_key);
        byte[] array = sha256_HMAC.doFinal(data.getBytes("UTF-8"));
        StringBuilder sb = new StringBuilder();
        for (byte item : array) {
            sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3));
        }
        return sb.toString().toUpperCase();
    }

    /**
     * 从request的inputStream中获取参数
     * @param request
     * @return
     * @throws Exception
     */
    public  static Map<String, String> getNotifyParameter(HttpServletRequest request) throws Exception {
        InputStream inputStream = request.getInputStream();
        ByteArrayOutputStream outSteam = new ByteArrayOutputStream();
        byte[] buffer = new byte[1024];
        int length = 0;
        while ((length = inputStream.read(buffer)) != -1) {
            outSteam.write(buffer, 0, length);
        }
        outSteam.close();
        inputStream.close();

        // 获取微信调用我们notify_url的返回信息
        String resultXml = new String(outSteam.toByteArray(), "utf-8");
        log.info("微信异步通知返回xml:"+resultXml);
        Map<String, String> notifyMap = xmlToMap(resultXml);
        log.info("********************** 微信支付返回参数**********************");
        notifyMap.forEach((key, value) -> {
            log.info((key+":"+value));
        });
        log.info("********************************************");
        return notifyMap;
    }

    /**
     * 组装xml
     * @return
     * @throws ParserConfigurationException
     */
    public static DocumentBuilder newDocumentBuilder() throws ParserConfigurationException {
        DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
        documentBuilderFactory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
        documentBuilderFactory.setFeature("http://xml.org/sax/features/external-general-entities", false);
        documentBuilderFactory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
        documentBuilderFactory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
        documentBuilderFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
        documentBuilderFactory.setXIncludeAware(false);
        documentBuilderFactory.setExpandEntityReferences(false);

        return documentBuilderFactory.newDocumentBuilder();
    }
    public static Document newDocument() throws ParserConfigurationException {
        return newDocumentBuilder().newDocument();
    }

WXPayConfig.java

@Configuration
public class WXPayConfig implements InitializingBean {

    // 公众账号ID
    public static String app_id;

    // 商户号
    public static String mch_id;

    // 商户的key【API密匙】
    public static String api_key;

    // 微信网关
    public static String gatewayUrl;

    // 服务器异步通知页面路径
    public static String notify_url;

    @Value("${wxpay.app_id}")
    private String getApp_id;

    @Value("${wxpay.mch_id}")
    private String getMch_id;

    @Value("${wxpay.api_key}")
    private String getApi_key;

    @Value("${wxpay.gatewayUrl}")
    private String getGatewayUrl;

    @Value("${project_address}")
    private String getProject_address;

    private String getNotify_url="/wxpay/wechatNotify";

    @Override
    public void afterPropertiesSet() throws Exception {
        WXPayConfig.app_id=this.getApp_id;
        WXPayConfig.mch_id=this.getMch_id;
        WXPayConfig.api_key=this.getApi_key;
        WXPayConfig.gatewayUrl=this.getGatewayUrl;
        WXPayConfig.notify_url=this.getProject_address+this.getNotify_url;
    }
}

httpClient.java

 /**
     * post请求传输xml数据
     * @param url
     * @param xml
     * @return
     * @throws ClientProtocolException
     * @throws IOException
     */
    public static String sendPostDataByXml(String url, String xml){
        log.info("url:"+url+",body:"+xml);
        String result = "";

        // 创建httpclient对象
        CloseableHttpClient httpClient = HttpClients.createDefault();

        // 创建post方式请求对象
        HttpPost httpPost = new HttpPost(url);

        // 设置参数到请求对象中
        StringEntity stringEntity = new StringEntity(xml, "UTF-8");
        httpPost.addHeader("Content-Type", "text/xml");
        httpPost.setEntity(stringEntity);

        // 执行请求操作,并拿到结果(同步阻塞)
        CloseableHttpResponse response = null;
        try {
            response = httpClient.execute(httpPost);

            // 获取结果实体
            // 判断网络连接状态码是否正常(0--200都数正常)
            if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
                result = EntityUtils.toString(response.getEntity(), "UTF-8");
                log.info("xml请求返回结果:"+result);
            }
        } catch (Exception e) {
            e.printStackTrace();
            log.info("请求失败:"+url);
        }finally {
            // 释放链接
            try {
                response.close();
                httpClient.close();
            } catch (IOException e) {
                e.printStackTrace();
            }

        }

        return result;
    }

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值