微信支付(java版本)

废话不多说,直接进入主体

        最近在做支付相关的项目,大致需求是用户在公众号发起订单之后调起微信支付。我也是第一次做微信支付,在这里把方法记录下来,也为了给初次做微信支付的同行们提供点经验。

一:接入准备工作

        1、要接入入微信支付首先要有载体,也就是是平台,这里我用的是公众号,然后就有一个APPID;

        2、然后申请商户,我这里直接注册的微信商户,申请成功之后有个商户ID,也就是mchId;

        3、在微信公众号后台绑定商户,一个公众号可以绑定多个商户号;

        4、登录商户后台进行配置秘钥、支付授权目录、网页授权域名等。

        前期的准备工作在官方网站都有说明。

二:接入API

        ps:微信支付有多个产品,我这里介绍JSAPI支付,JSAPI支付是指商户通过调用微信支付提供的JSAPI接口,在支付场景中调起微信支付模块完成收款,如下图:

        1、JSAPI下单

                这个也就是拿着参数去微信平台请求下单,成功之后会返回一个参数prepay_id(预支付交易会话标识),前端用这个参数才能唤醒微信支付控件;示例代码如下:

Map<String, String> param = new HashMap<>();
//部分参数省略
param.put("openid", openId);
param.put("body", body);

//签名+转xml格式
String xmlStr = WxPayUtils.generateSignedXml(param, key);
//请求
String resultStr = HttpClientUtil.post("https://api.mch.weixin.qq.com/pay/unifiedorder", xmlStr);
//将结果转成Map格式
Map<String, String> map = WxPayUtils.xmlToMap(resultStr);

贴上工具类代码:

public class WxPayUtils {


    private static final String PREFIX_XML = "<xml>";

    private static final String SUFFIX_XML = "</xml>";

    private static final String PREFIX_CDATA = "<![CDATA[";

    private static final String SUFFIX_CDATA = "]]>";

    private static final String ALGORITHM_MODE_PADDING = "AES/ECB/PKCS7Padding";

    private static final String ALGORITHM = "AES";

    /**
     * 随机生成16位字符串
     *
     * @return
     */
    public static String getRandomStr() {
        String base = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
        Random random = new Random();
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < 32; i++) {
            int number = random.nextInt(base.length());
            sb.append(base.charAt(number));
        }
        return sb.toString();
    }

    /**
     * MD5签名
     *
     * @param param 参数
     * @param key   微信商户平台(pay.weixin.qq.com)-->账户设置-->API安全-->密钥设置
     * @return
     */
    public static String getSignStr(Map<String, String> param, String key) {
        StringBuffer sb = new StringBuffer();
        param.entrySet().stream().filter(n -> n.getValue() != null).sorted(Map.Entry.comparingByKey()).forEachOrdered(e -> sb.append(e.getKey() + "=" + e.getValue() + "&"));
        sb.append("key=" + key);
        return MD5Helper.MD5Encode(sb.toString()).toUpperCase();
    }

    /**
     * 解析小程序传过来的wxtoke字符串,包括session_key,openid等
     *
     * @param token
     * @return
     */
    public static Map<String, String> getWxTokenMap(String token) {
        try {
            String tokenStr = AES3.getInstance().decrypt(token);
            return JsonHelper.jsonToBean(Map.class, tokenStr);
        } catch (Exception e) {
            return null;
        }
    }

    /**
     * map转xml
     *
     * @param parm
     * @param isAddCDATA
     * @return
     */
    public static String mapToXml(Map<String, String> parm, boolean isAddCDATA) {
        StringBuffer strbuff = new StringBuffer(PREFIX_XML);
        if (parm != null) {
            for (Map.Entry<String, String> entry : parm.entrySet()) {
                strbuff.append("<").append(entry.getKey()).append(">");
                if ("attach".equalsIgnoreCase(entry.getKey()) || "body".equalsIgnoreCase(entry.getKey())) {
                    strbuff.append(PREFIX_CDATA);
                    if (StrTools.isNotNullOrEmpty(entry.getValue())) {
                        strbuff.append(entry.getValue());
                    }
                    strbuff.append(SUFFIX_CDATA);
                } else {
                    if (StrTools.isNotNullOrEmpty(entry.getValue())) {
                        strbuff.append(entry.getValue());
                    }
                }
                strbuff.append("</").append(entry.getKey()).append(">");
            }
        }
        return strbuff.append(SUFFIX_XML).toString();
    }

    /**
     * xml转map
     *
     * @param strxml
     * @return
     * @throws Exception
     */
    public static Map<String, String> xmlToMap(String strxml) throws Exception {
        strxml = strxml.replaceFirst("encoding=\".*\"", "encoding=\"UTF-8\"");
        if (null == strxml || "".equals(strxml)) {
            return null;
        }
        Map<String, String> m = new HashMap<String, String>();
        InputStream in = new ByteArrayInputStream(strxml.getBytes("UTF-8"));
        SAXBuilder builder = new SAXBuilder();
        Document doc = builder.build(in);
        Element root = doc.getRootElement();
        List list = root.getChildren();
        Iterator it = list.iterator();
        while (it.hasNext()) {
            Element e = (Element) it.next();
            String k = e.getName();
            String v = "";
            List children = e.getChildren();
            if (children.isEmpty()) {
                v = e.getTextNormalize();
            } else {
                v = getChildrenText(children);
            }
            m.put(k, v);
        }
        in.close();
        return m;
    }

    public static String getChildrenText(List children) {
        StringBuffer sb = new StringBuffer();
        if (!children.isEmpty()) {
            Iterator it = children.iterator();
            while (it.hasNext()) {
                Element e = (Element) it.next();
                String name = e.getName();
                String value = e.getTextNormalize();
                List list = e.getChildren();
                sb.append("<" + name + ">");
                if (!list.isEmpty()) {
                    sb.append(getChildrenText(list));
                }
                sb.append(value);
                sb.append("</" + name + ">");
            }
        }
        return sb.toString();
    }

   

    /**
     * 生成带有 sign 的 XML 格式字符串
     *
     * @param data Map类型数据
     * @param key  API密钥
     * @return 含有sign字段的XML
     */
    public static String generateSignedXml(final Map<String, String> data, String key) throws Exception {
        //签名
        String signStr = getSignStr(data, key);
        data.put("sign", signStr);
        //转成xml
        return WxPayUtils.mapToXml(data, false);
    }

    /**
     * 接收参数签名校验
     *
     * @param data
     * @param key
     * @return
     */
    public static boolean isSignatureValid(Map<String, String> data, String key) {
        if (!data.containsKey("sign")) {
            return false;
        }
        String oldSign = data.get("sign");
        data.remove("sign");
        return oldSign.equals(getSignStr(data, key));
    }

    @SneakyThrows
    public static Map<String, String> decryptRefundStrToMap(String reqInfo, String key) {
        //初始化
        Security.addProvider(new BouncyCastleProvider());
        Cipher cipher = Cipher.getInstance(ALGORITHM_MODE_PADDING, "BC");
        SecretKeySpec spec = new SecretKeySpec(MD5Helper.MD5Encode(key).toLowerCase().getBytes(), ALGORITHM);
        cipher.init(Cipher.DECRYPT_MODE, spec);
        byte[] decode = Base64.getDecoder().decode(reqInfo);
        return xmlToMap(new String(cipher.doFinal(decode)));
    }

    /**
     * xml解析-支持多层xml
     *
     * @param xmlStr
     * @return
     */
    public static Map<String, Object> packXmlToMap(String xmlStr) {
        List<Map<String, String>> resultList = iterateWholeXML(xmlStr);
        Map<String, Object> retMap = new HashMap<String, Object>();
        for (int i = 0; i < resultList.size(); i++) {
            Map map = (Map) resultList.get(i);
            for (Iterator iterator = map.keySet().iterator(); iterator.hasNext(); ) {
                String key = (String) iterator.next();
                retMap.put(key, (String) map.get(key));
            }
        }
        return retMap;
    }

    /**
     * 递归解析任意的xml 遍历每个节点和属性
     *
     * @param xmlStr
     */
    private static List<Map<String, String>> iterateWholeXML(String xmlStr) {
        List<Map<String, String>> list = new ArrayList<Map<String, String>>();
        try {
            org.dom4j.Document document = DocumentHelper.parseText(xmlStr);
            org.dom4j.Element root = document.getRootElement();
            recursiveNode(root, list);
            return list;
        } catch (DocumentException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 递归遍历所有的节点获得对应的值
     *
     * @param
     */
    private static void recursiveNode(org.dom4j.Element root, List<Map<String, String>> list) {
        // 遍历根结点的所有孩子节点
        HashMap<String, String> map = new HashMap<String, String>();
        for (Iterator iter = root.elementIterator(); iter.hasNext(); ) {
            org.dom4j.Element element = (org.dom4j.Element) iter.next();
            if (element == null)
                continue;
            // 获取属性和它的值
            for (Iterator attrs = element.attributeIterator(); attrs.hasNext(); ) {
                Attribute attr = (Attribute) attrs.next();
                System.out.println(attr);
                if (attr == null)
                    continue;
                String attrName = attr.getName();
                System.out.println("attrname" + attrName);
                String attrValue = attr.getValue();
                map.put(attrName, attrValue);
            }
            // 如果有PCDATA,则直接提出
            if (element.isTextOnly()) {
                String innerName = element.getName();
                String innerValue = element.getText();
                map.put(innerName, innerValue);
                list.add(map);
            } else {
                // 递归调用
                recursiveNode(element, list);
            }
        }
    }

    /**
     * 加载独立node
     * @param root
     * @param map
     */
    public static void loadXmlNode(org.dom4j.Element root,Map<String,String> map){
        for (Iterator<org.dom4j.Element> elementIterator = root.elementIterator(); elementIterator.hasNext(); ) {
            org.dom4j.Element next = elementIterator.next();
            if(next.isTextOnly()){
                String innerName = next.getName();
                String innerValue = next.getText();
                map.put(innerName, innerValue);
            }else if(!StringUtils.equals("LIST",next.getName())){
                loadXmlNode(next,map);
            }

        }
    }

    /**
     * 加载nodeList
     * @param root
     * @return
     */
    public static List<Map<String, String>> loadXmlNodeList(org.dom4j.Element root){
        List<Map<String, String>> list = new ArrayList<>();
        Iterator<org.dom4j.Element> listNode = root.elementIterator("TX_INFO").next().elementIterator("LIST");
        while (listNode.hasNext()){
            Map<String, String> map = new HashMap<>();
            Iterator<org.dom4j.Element> elementIterator = listNode.next().elementIterator();
            while (elementIterator.hasNext()){
                org.dom4j.Element next = elementIterator.next();
                map.put(next.getName(), next.getText());
            }
            list.add(map);
        }
        return list;
    }

   
    


}

2、将下单接口返回的prepay_id扔给前端,前端通过微信公众的js调起控件进行支付就行了,这都是前端的事情,和后台没关系。


3、微信支付结果回调

        支付结果通知的url在支付请求的时候已经传过了,是字段notify_url,支付结果微信是通过post方法访问我们设置的回调url,接收并解析代码如下:
 

    public String wxNotify(HttpServletRequest request, HttpServletResponse response) {
            String resXml = resFailXml;
            InputStream inStream;
            try {
                inStream = request.getInputStream();
                ByteArrayOutputStream outSteam = new ByteArrayOutputStream();
                byte[] buffer = new byte[1024];
                int len = 0;
                while ((len = inStream.read(buffer)) != -1) {
                    outSteam.write(buffer, 0, len);
                }
                log.info("wxnotify:微信支付----start----");
                // 获取微信调用我们notify_url的返回信息
                String result = new String(outSteam.toByteArray(), "utf-8");
                System.out.println("接收的微信的回调信息字符串:" + result);
                log.info("接收微信支付回调信息str:{}", result);
                // 关闭流
                outSteam.close();
                inStream.close();
                // xml转换为map
                Map<String, String> resultMap = WxPayUtils.xmlToMap(result);
                if ("SUCCESS".equalsIgnoreCase(resultMap.get("return_code"))) {
                    log.info("微信支付回调返回成功");
                    String key = "key";//商户秘钥
                    //校验签名
                    if (WxPayUtils.isSignatureValid(resultMap, key)) {
                        log.info("接收微信回调信息,签名通过");
                        //业务处理

                    } else {
                        log.info("接收微信回调信息,签名失败");
                    }
                } else {
                    log.info("接收微信支付回调信息失败,原因:{}", resultMap.get("return_msg"));
                    resXml = String.format(resXml, resultMap.get("return_msg"));
                }

            } catch (Exception e) {
                log.error("支付回调发生异常:{}", e.getMessage());
            } finally {
                try {
                    // 处理业务完毕
                    BufferedOutputStream out = new BufferedOutputStream(response.getOutputStream());
                    out.write(resXml.getBytes());
                    out.flush();
                    out.close();
                } catch (IOException e) {
                    log.error("支付回调发生异常end:{}", e.getMessage());
                }
            }
            System.out.println("最终返回的回调信息" + resXml);
            return resXml;
    }

 注意,在接收到微信的回调之后需要给予应答,让微信方知道我们收到了支付通知,否则微信会通过一定的策略定期重新发起通知,尽可能提高通知的成功率,但微信不保证通知最终能成功。(通知频率为15s/15s/30s/3m/10m/20m/30m/30m/30m/60m/3h/3h/3h/6h/6h - 总计 24h4m)。
 

大概就这么多,微信支付还有其他的一些API可以自己去网站查询,这里只是记录最基础的用法。

        

        

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值