JAVA微信支付——企业付款(企业向微信用户个人付款、转账)
本地开发环境支付回调调试方法可以参考:https://www.cnblogs.com/pxblog/p/11623053.html
需要自行引入相关依赖
官方文档地址: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 "返回通信失败的错误页面"; } }
-----------------------有任何问题可以在评论区评论,也可以私信我,我看到的话会进行回复,欢迎大家指教------------------------
(蓝奏云官网有些地址失效了,需要把请求地址lanzous改成lanzoux才可以)