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 "返回通信失败的错误页面";
    }
}
原文地址:https://www.cnblogs.com/pxblog/p/13372134.html