zoukankan      html  css  js  c++  java
  • JAVA微信支付——企业付款(企业向微信用户个人付款、转账)

    需要自行引入相关依赖

    官方文档地址:https://pay.weixin.qq.com/wiki/doc/api/tools/mch_pay.php?chapter=14_2

    用于企业向微信用户个人付款,目前支持向指定微信用户的openid付款。

    官方提示

    ClientCustomSSL.java

    package com.weixinpay;
    
    import org.apache.commons.lang.StringUtils;
    import org.apache.http.HttpEntity;
    import org.apache.http.HttpResponse;
    import org.apache.http.client.methods.HttpPost;
    import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
    import org.apache.http.conn.ssl.SSLContexts;
    import org.apache.http.entity.StringEntity;
    import org.apache.http.impl.client.CloseableHttpClient;
    import org.apache.http.impl.client.HttpClients;
    import org.apache.http.util.EntityUtils;
    
    import javax.net.ssl.SSLContext;
    import java.io.File;
    import java.io.FileInputStream;
    import java.security.KeyStore;
    
    
    /**
     * This example demonstrates how to create secure connections with a custom SSL
     * context.
     */
    public class ClientCustomSSL {
    
        public static String getInSsl(String url,File pkcFile,String storeId, 
                String params,String contentType) 
                throws Exception {
            String text = "";
            // 指定读取证书格式为PKCS12
            KeyStore keyStore = KeyStore.getInstance("PKCS12");
            // 读取本机存放的PKCS12证书文件
            FileInputStream instream = new FileInputStream(pkcFile);
            try {
                // 指定PKCS12的密码(商户ID)
                keyStore.load(instream, storeId.toCharArray());
            } finally {
                instream.close();
            }
    
            // Trust own CA and all self-signed certs
            SSLContext sslcontext = SSLContexts.custom().loadKeyMaterial(keyStore, storeId.toCharArray()).build();
            // Allow TLSv1 protocol only
            // 指定TLS版本 
            SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslcontext, new String[] { "TLSv1" }, null,
                    SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);
            // 设置httpclient的SSLSocketFactory
            CloseableHttpClient httpclient = HttpClients.custom().setSSLSocketFactory(sslsf).build();
            try {
                HttpPost post = new HttpPost(url);
                StringEntity s = new StringEntity(params,"utf-8");
                if(StringUtils.isBlank(contentType)){
                   s.setContentType("application/xml");
                }
                s.setContentType(contentType);
                post.setEntity(s);
                HttpResponse res = httpclient.execute(post);
                HttpEntity entity = res.getEntity();
                text= EntityUtils.toString(entity, "utf-8");
            } finally {
                httpclient.close();
            }
            return text;
        }
    
    }

    Num62.java

    package com.weixinpay;
    
    /**
     * 62进制数字
     */
    public class Num62 {
        /**
         * 62个字母和数字,含大小写
         */
        public static final char[] N62_CHARS = {'0', '1', '2', '3', '4', '5', '6',
                '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J',
                'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
                'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j',
                'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
                'x', 'y', 'z'};
    
    
    }

    PaymentConfig.java

    package com.weixinpay;
    
    public class PaymentConfig {
        /*******微信支付参数*********/
    
        //公众账号ID
        public static final String appid="wxd3e";
    
        //密钥 key设置路径:微信商户平台(pay.weixin.qq.com)-->账户设置-->API安全-->密钥设置
        public static final String appKey="abcde";
    
        //商户号
        public static final String mch_id="15849";
    
        //转账请求接口地址
        public static final String pay_url="https://api.mch.weixin.qq.com/mmpaymkttransfers/promotion/transfers";
    
    
    }

    PayUtil.java

    package com.weixinpay;
    
    import org.apache.commons.lang.StringUtils;
    import org.jdom.Document;
    import org.jdom.Element;
    import org.jdom.JDOMException;
    import org.jdom.input.SAXBuilder;
    
    import java.io.ByteArrayInputStream;
    import java.io.IOException;
    import java.io.InputStream;
    import java.security.MessageDigest;
    import java.util.*;
    
    
    public class PayUtil {
    
        /**
         * UTF-8编码
         */
        public static final String UTF8 = "UTF-8";
    
    
        /**
        * 将需要传递给微信的参数转成xml格式
        * @param parameters
        * @return
        */
        public static String assembParamToXml(Map<String,String> parameters){
            StringBuffer sb = new StringBuffer();
            sb.append("<xml>");
            Set<String> es = parameters.keySet();
            List<Object> list=new ArrayList<Object>(es);
            Object[] ary =list.toArray();
            Arrays.sort(ary);
            list=Arrays.asList(ary);
            Iterator<Object> it = list.iterator();
            while(it.hasNext()) {
                String key =  (String) it.next();
                String val=(String) parameters.get(key);
                if ("attach".equalsIgnoreCase(key)||"body".equalsIgnoreCase(key)||"sign".equalsIgnoreCase(key)) {
                    sb.append("<"+key+">"+"<![CDATA["+val+"]]></"+key+">");
                }else {
                    sb.append("<"+key+">"+val+"</"+key+">");
                }
            }
            sb.append("</xml>");
            return sb.toString();
        }
    
        /**
         * 解析xml,返回第一级元素键值对。如果第一级元素有子节点,则此节点的值是子节点的xml数据。
         * @param strxml
         * @return
         * @throws JDOMException
         * @throws IOException
         */
        public static Map parseXMLToMap(String strxml) throws JDOMException, IOException {
            strxml = strxml.replaceFirst("encoding=".*"", "encoding=""+UTF8+""");
            if(null == strxml || "".equals(strxml)) {
                return null;
            }
            Map m = new HashMap();
            InputStream in = new ByteArrayInputStream(strxml.getBytes(UTF8));
            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 =PayUtil.getChildrenText(children);
                }
                m.put(k, v);
            }
            //关闭流
            in.close();
            return m;
        }
    
        /**
         * 获取子结点的xml
         * @param children
         * @return String
         */
        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
        * @param param
        * @param key
        * @return
        */
        public static String createSign(Map<String, String> param,String key){
            //签名步骤一:按字典排序参数
            List list=new ArrayList(param.keySet());
            Object[] ary =list.toArray();
            Arrays.sort(ary);
            list=Arrays.asList(ary);
            String str="";
            for(int i=0;i<list.size();i++){
                str+=list.get(i)+"="+param.get(list.get(i)+"")+"&";
            }
            //签名步骤二:加上key
            str+="key="+key;
            //步骤三:加密并大写
            str=PayUtil.MD5Encode(str,"utf-8").toUpperCase();
            return str;
        }
    
        public static String MD5Encode(String origin,String charsetName){
            String resultString=null;
            try{
                resultString=new String(origin);
                MessageDigest md=MessageDigest.getInstance("MD5");
                if(StringUtils.isBlank(charsetName)){
                    resultString=byteArrayToHexString(md.digest(resultString.getBytes()));
                }else{
                    resultString=byteArrayToHexString(md.digest(resultString.getBytes(charsetName)));
                }
            }catch(Exception e){
            
            }
            return resultString;
        }
    
        public static String byteArrayToHexString(byte b[]){
            StringBuffer resultSb=new StringBuffer();
            for(int i=0;i<b.length;i++){
                resultSb.append(PayUtil.byteToHexString(b[i]));
            }
            return resultSb.toString();
        }
    
        public static String byteToHexString(byte b){
            int n=b;
            if(n<0){
                n+=256;
            }
            int d1=n/16;
            int d2=n%16;
            return PayUtil.hexDigits[d1]+PayUtil.hexDigits[d2];
        }
    
        public static final String hexDigits[]={ "0", "1", "2", "3", "4", "5",  
        "6", "7", "8", "9", "a", "b", "c", "d", "e", "f" };
    
    
        /**
         * 元转换为分
         * @param amount
         */
        public static String changeY2F(Double amount){    
                String currency =  amount.toString();  
                int index = currency.indexOf(".");    
                int length = currency.length();    
                Long amLong = 0l;    
                if(index == -1){    
                    amLong = Long.valueOf(currency+"00");    
                }else if(length - index >= 3){    
                    amLong = Long.valueOf((currency.substring(0, index+3)).replace(".", ""));    
                }else if(length - index == 2){    
                    amLong = Long.valueOf((currency.substring(0, index+2)).replace(".", "")+0);    
                }else{    
                    amLong = Long.valueOf((currency.substring(0, index+1)).replace(".", "")+"00");    
                }    
                return amLong.toString();    
        }
    
    }

    WeixinPay.java

    package com.weixinpay;
    
    import org.apache.commons.lang.RandomStringUtils;
    
    import java.io.File;
    import java.util.HashMap;
    import java.util.Map;
    
    
    public class WeixinPay {
    
    
        /**
         * 企业付款接口  用于企业向微信用户个人付款
         * 目前支持向指定微信用户的openid付款。
         * @param pkcFile  商户证书文件
         * @param orderNo  订单编号
         * @param weixinOpenId  要付款的用户openid
         * @param realname  收款用户姓名
         * @param payAmount 转账金额
         * @param desc  企业付款备注
         * @param ip  ip地址
         * @return
         */
        public static Object[] payToUser(File pkcFile, String orderNo, String weixinOpenId, String realname
                , Double payAmount, String desc, String ip) {
            Map<String, String> paramMap = new HashMap<String, String>();
            // 公众账号appid[必填]
            paramMap.put("mch_appid", PaymentConfig.appid);
            // 微信支付分配的商户号 [必填]
            paramMap.put("mchid", PaymentConfig.mch_id);
            // 终端设备号(门店号或收银设备ID),注意:PC网页或公众号内支付请传"WEB" [非必填]
            paramMap.put("device_info", "WEB");
            // 随机字符串,不长于32位。 [必填]
            paramMap.put("nonce_str", RandomStringUtils.random(16, Num62.N62_CHARS));
    
            // 商户订单号,需保持唯一性[必填]
            paramMap.put("partner_trade_no", orderNo);
    
            // 商户appid下,某用户的openid[必填]
            paramMap.put("openid", weixinOpenId);
    
            //校验用户姓名选项   NO_CHECK:不校验真实姓名 FORCE_CHECK:强校验真实姓名
            paramMap.put("check_name", "OPTION_CHECK");
    
            //收款用户姓名,如果check_name设置为FORCE_CHECK,则必填用户真实姓名
            paramMap.put("re_user_name", realname);
            // 企业付款金额,金额必须为整数 单位为分 [必填]
            paramMap.put("amount", PayUtil.changeY2F(payAmount));
            // 企业付款描述信息 [必填]
            paramMap.put("desc", desc);
            // 调用接口的机器Ip地址[必填]
            paramMap.put("spbill_create_ip", ip);
            // 根据微信签名规则,生成签名
            paramMap.put("sign",
                    PayUtil.createSign(paramMap, PaymentConfig.appKey));
            // 把参数转换成XML数据格式
            String xmlWeChat = PayUtil.assembParamToXml(paramMap);
            String resXml = "";
            boolean postError = false;
            try {
                resXml = ClientCustomSSL.getInSsl(PaymentConfig.pay_url, pkcFile, PaymentConfig.mch_id
                        , xmlWeChat, "application/xml");
            } catch (Exception e1) {
                postError = true;
                e1.printStackTrace();
            }
            Object[] result = new Object[2];
            result[0] = postError;
            result[1] = resXml;
            return result;
        }
    
    
    
    }

    商户证书说明:https://pay.weixin.qq.com/wiki/doc/api/tools/mch_pay.php?chapter=4_3

    控制器调用类

    PayContoller.java

    package com.weixinpay;
    
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestMapping;
    
    import java.io.File;
    import java.util.HashMap;
    import java.util.Map;
    
    @Controller
    public class PayContoller {
    
        /**
         * 企业付款到用户个人微信
         * 简单demo 需要自行完善逻辑
         * @return
         */
        @RequestMapping(value = "/transfers")
        public String transfers() {
             
            File pkcFile = new File("商户证书路径");
    
            String orderNo = "订单编号 可以参考:https://www.cnblogs.com/pxblog/p/12818067.html";
    
            String weixinOpenId = "用户的openid 可以参考:https://www.cnblogs.com/pxblog/p/10542698.html";
    
            String realname = "用户的真实姓名";
    
            //转账的金额  金额格式转换可以参考 https://www.cnblogs.com/pxblog/p/13186037.html
            Double payAmount = 0.01;
    
            String desc = "企业付款备注";
    
            String ip = "ip地址 可以参考:https://www.cnblogs.com/pxblog/p/13360768.html";
    
            Object result[] = WeixinPay.payToUser(pkcFile, orderNo, weixinOpenId, realname, payAmount, desc, ip);
            String resXml = (String) result[1];
            boolean postError = (Boolean) result[0];
            if (!postError) {
                Map<String, String> map = new HashMap<String, String>();
                try {
                    map = PayUtil.parseXMLToMap(resXml);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                String returnCode = map.get("return_code");
                if (returnCode.equalsIgnoreCase("FAIL")) {
                    //支付失败
                    return map.get("return_msg");
                } else if (returnCode.equalsIgnoreCase("SUCCESS")) {
                    if (map.get("err_code") != null) {
                        //支付失败
                        return map.get("err_code_des");
                    } else if (map.get("result_code").equalsIgnoreCase(
                            "SUCCESS")) {
                        //支付成功  paymentNo:微信付款单号  payment_time:付款成功时间
                        String paymentNo = map.get("payment_no");
                        String payment_time = map.get("payment_time");
                        try {
    
                            //如果是体现操作,在这里处理体现订单的状态,把状态转为提现成功
    
    
    
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                        return "返回到成功的页面";
                    }
                }
            }
    
            return "返回通信失败的错误页面";
        }
    }
  • 相关阅读:
    网站测试自动化系统—在测试代码中硬编码测试数据
    在WPF里面显示DIB图片格式的图片
    网站测试自动化系统—系统应该有的功能
    Windbg 教程调试非托管程序的基本命令下
    Windbg教程调试非托管程序的基本命令上
    网站测试自动化系统—基于Selenium和VSTT
    WPF中一个通用的BoolToXXX转换类
    使用MPLex实现语法高亮显示的功能代码解释
    网站测试自动化系统—数据驱动测试
    如何控制float类型小数点后的位数
  • 原文地址:https://www.cnblogs.com/pxblog/p/13372134.html
Copyright © 2011-2022 走看看