微信支付企业付款到零钱
- 在微信支付企业付款到零钱的时候网上找了一下微信支付到商户的很多,但是微信支付企业付款到用户(提现~应该可以叫提现吧)却很少,于是想写写。
- 1、首先微信支付企业付款文档地址
- 2、这第一步就是获取微信用户的openid,这个就不赘述了,只要配置对了,经过那几步请求基本上就能获取到openid。
- 3、获取证书:微信支付接口中,涉及资金回滚的接口会使用到商户证书,包括退款、撤销接口。商家在申请微信支付成功后,收到的相应邮件后,可以按照指引下载API证书,也可以按照以下路径下载:微信商户平台(pay.weixin.qq.com)–>账户设置–>API安全–>证书下载 。证书文件有四个,详情可点击此处安全规范到最下面的商户证书查看。
4、使用https://api.mch.weixin.qq.com/mmpaymkttransfers/promotion/transfers接口地址付款,就是加上一系列的请求参数,把一些参数以字典序排序加上商户key签名得到sign字段,最后再请求接口这样。
5、比较麻烦的地方就是这个证书问题就是这个要使用证书去发起https请求的问题。我请求的使用就一直报 证书问题:Certificate for <api.mch.weixin.qq.com> doesn't match any of the subject alternative names
,后面我在微信支付那边例子代码找到了加上证书发起请求的例子就成功了。地址 : 微信支付例子地址
6、下面我就贴完整微信支付企业付款到零钱代码
WeChatWithdrawUtils.java 微信支付企业付款到零钱主文件
import com.rw.common.Constants;
import com.rw.common.utils.MD5Utils;
import com.rw.common.utils.OtherUtils;
import com.rw.common.utils.http.HttpClient;
import com.rw.common.utils.http.HttpUtils;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.BasicHttpClientConnectionManager;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sun.net.www.protocol.https.DefaultHostnameVerifier;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.security.KeyStore;
import java.security.SecureRandom;
import java.util.*;
/**
*
* 所需lib
* <dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.3</version>
</dependency>
* **/
/**
* @author Zhou Zhong Qing
* @Title: ${file_name}
* @Package ${package_name}
* @Description: 微信提现
* @date 2018/6/25 16:57
*/
public class WeChatWithdrawUtils {
private static final Logger log = LoggerFactory.getLogger(WeChatWithdrawUtils.class);
private byte[] certData;
public InputStream getCertStream() {
ByteArrayInputStream certBis;
certBis = new ByteArrayInputStream(this.certData);
return certBis;
}
public WeChatWithdrawUtils() throws Exception{
String certPath = Constants.CERT_PATH;
File file = new File(certPath);
InputStream certStream = new FileInputStream(file);
this.certData = new byte[(int) file.length()];
certStream.read(this.certData);
certStream.close();
}
public static void main(String[] args) {
try {
String result = withdrawRequestOnce(new HashMap<>(),3000,3000,true);
/*<xml>
<return_code><![CDATA[SUCCESS]]></return_code>
<return_msg><![CDATA[支付失败]]></return_msg>
<mch_appid><![CDATA[wx317e16f8818682cf]]></mch_appid>
<mchid><![CDATA[1507675211]]></mchid>
<result_code><![CDATA[FAIL]]></result_code>
<err_code><![CDATA[NOTENOUGH]]></err_code>
<err_code_des><![CDATA[请到商户平台充值后再重试.]]></err_code_des>
</xml>*/
Map<String,String> resultMap = OtherUtils.readStringXmlOut(result);
log.info(resultMap.toString());
if(resultMap.containsKey("result_code") && "SUCCESS".equals(resultMap.getOrDefault("result_code",""))){
//TODO 成功
}else{
//TODO 失败
}
}catch (Exception e){
e.printStackTrace();
}
}
/**
*
* 提现
* 请求,只请求一次,不做重试
* @param connectTimeoutMs
* @param readTimeoutMs
* @return
* @throws Exception
*/
public static String withdrawRequestOnce( Map<String,String> params, int connectTimeoutMs, int readTimeoutMs,boolean useCert) throws Exception {
Map<String, String> paraMap = new HashMap<>();
paraMap.put("mch_appid", Constants.OPEN_ANDROID_APP_ID);
paraMap.put("mchid",Constants.MCH_ID);
paraMap.put("nonce_str", OtherUtils.getNonceStr());
paraMap.put("partner_trade_no","qianchen"+System.currentTimeMillis());
paraMap.put("openid",params.getOrDefault("openId",""));//"o5mZ40yBjIqco2NzKc19k9oIBI9o");
// 校验用户姓名选项 NO_CHECK:不校验真实姓名 FORCE_CHECK:强校验真实姓名
paraMap.put("check_name","NO_CHECK");
paraMap.put("amount",params.getOrDefault("money",""));//"100");
//企业付款操作说明信息。必填。
paraMap.put("desc",params.getOrDefault("clientId","")+"用户提现");
paraMap.put("spbill_create_ip",params.getOrDefault("spbillCreateIp",""));
String url = OtherUtils.formatUrlMap(paraMap, false, false);
url = url + "&key=" + Constants.MCH_SECRET;
String sign = MD5Utils.MD5Encoding(url).toUpperCase();
StringBuffer xml = new StringBuffer();
xml.append("<xml>");
for (Map.Entry<String, String> entry : paraMap.entrySet()) {
xml.append("<" + entry.getKey() + ">");
xml.append(entry.getValue());
xml.append("</" + entry.getKey() + ">" + "\n");
}
xml.append("<sign>");
xml.append(sign);
xml.append("</sign>");
xml.append("</xml>");
log.info("xml {} ", xml.toString());
BasicHttpClientConnectionManager connManager;
if (useCert) {
// 证书
char[] password = Constants.MCH_ID.toCharArray();
InputStream certStream = new WeChatWithdrawUtils().getCertStream();
KeyStore ks = KeyStore.getInstance("PKCS12");
ks.load(certStream, password);
// 实例化密钥库 & 初始化密钥工厂
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(ks, password);
// 创建 SSLContext
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(kmf.getKeyManagers(), null, new SecureRandom());
SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(
sslContext,
new String[]{"TLSv1"},
null,
new org.apache.http.conn.ssl.DefaultHostnameVerifier());
connManager = new BasicHttpClientConnectionManager(
RegistryBuilder.<ConnectionSocketFactory>create()
.register("http", PlainConnectionSocketFactory.getSocketFactory())
.register("https", sslConnectionSocketFactory)
.build(),
null,
null,
null
);
}
else {
connManager = new BasicHttpClientConnectionManager(
RegistryBuilder.<ConnectionSocketFactory>create()
.register("http", PlainConnectionSocketFactory.getSocketFactory())
.register("https", SSLConnectionSocketFactory.getSocketFactory())
.build(),
null,
null,
null
);
}
org.apache.http.client.HttpClient httpClient = HttpClientBuilder.create()
.setConnectionManager(connManager)
.build();
HttpPost httpPost = new HttpPost(Constants.WITHDRAW_URL);
RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(readTimeoutMs).setConnectTimeout(connectTimeoutMs).build();
httpPost.setConfig(requestConfig);
StringEntity postEntity = new StringEntity(xml.toString(), "UTF-8");
httpPost.addHeader("Content-Type", "text/xml");
httpPost.addHeader("User-Agent", "wxpay sdk java v1.0 " + Constants.MCH_ID); // TODO: 很重要,用来检测 sdk 的使用情况,要不要加上商户信息?
httpPost.setEntity(postEntity);
HttpResponse httpResponse = httpClient.execute(httpPost);
HttpEntity httpEntity = httpResponse.getEntity();
return EntityUtils.toString(httpEntity, "UTF-8");
}
}
后面的都是工具类了:OtherUtils.java
import org.apache.commons.lang3.RandomUtils;
import org.apache.commons.lang3.StringUtils;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/*
* 生成32位随机字符串
* */
public static String getNonceStr() {
UUID uuid = UUID.randomUUID();
return uuid.toString().replace("-", "");
}
/**
* 方法用途: 对所有传入参数按照字段名的Unicode码从小到大排序(字典序),并且生成url参数串<br>
* 实现步骤: <br>
*
* @param paraMap 要排序的Map对象
* @param urlEncode 是否需要URLENCODE
* @param keyToLower 是否需要将Key转换为全小写
* true:key转化成小写,false:不转化
* @return
*/
public static String formatUrlMap(Map<String, String> paraMap, boolean urlEncode, boolean keyToLower) {
String buff = "";
Map<String, String> tmpMap = paraMap;
try {
List<Map.Entry<String, String>> infoIds = new ArrayList<Map.Entry<String, String>>(tmpMap.entrySet());
// 对所有传入参数按照字段名的 ASCII 码从小到大排序(字典序)
Collections.sort(infoIds, new Comparator<Map.Entry<String, String>>() {
@Override
public int compare(Map.Entry<String, String> o1, Map.Entry<String, String> o2) {
return (o1.getKey()).toString().compareTo(o2.getKey());
}
});
// 构造URL 键值对的格式
StringBuilder buf = new StringBuilder();
for (Map.Entry<String, String> item : infoIds) {
if (StringUtils.isNotBlank(item.getKey())) {
String key = item.getKey();
String val = item.getValue();
if (urlEncode) {
val = URLEncoder.encode(val, "utf-8");
}
if (keyToLower) {
buf.append(key.toLowerCase() + "=" + val);
} else {
buf.append(key + "=" + val);
}
buf.append("&");
}
}
buff = buf.toString();
if (buff.isEmpty() == false) {
buff = buff.substring(0, buff.length() - 1);
}
} catch (Exception e) {
return null;
}
return buff;
}
/**
* 功能描述: 判断值是否为double
* @auther: zdg
* @date: 2018/6/15 19:00
*/
public static boolean isToDouble(Object o){
try {
Double.valueOf(o.toString());
return true;
}catch (Exception e){
return false;
}
}
/**
* @param xml
* @return Map
* @description 将xml字符串转换成map
*/
public static Map<String, String> readStringXmlOut(String xml) {
Map<String, String> map = new HashMap<String, String>();
Document doc = null;
try {
doc = DocumentHelper.parseText(xml); // 将字符串转为XML
Element rootElt = doc.getRootElement(); // 获取根节点
@SuppressWarnings("unchecked")
List<Element> list = rootElt.elements();// 获取根节点下所有节点
for (Element element : list) { // 遍历节点
map.put(element.getName(), element.getText()); // 节点的name为map的key,text为map的value
}
} catch (DocumentException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
return map;
}
MD5Utils.java
import java.security.MessageDigest;
public class MD5Utils {
private static final char hexDigits[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
/*
* 先转为utf-8
* */
public static String MD5Encoding(String s) {
byte[] btInput = null;
try {
btInput = s.getBytes("UTF-8");
}catch (Exception e){
}
return MD5(btInput, 32);
}
public static String MD5(String s) {
byte[] btInput = s.getBytes();
return MD5(btInput, 32);
}
public static String MD5_16(String str) {
byte[] btInput = str.getBytes();
return MD5(btInput, 16);
}
private static String MD5(byte[] btInput, int length) {
try {
// 获得MD5摘要算法的 MessageDigest 对象
MessageDigest mdInst = MessageDigest.getInstance("MD5");
// MessageDigest mdInst = MessageDigest.getInstance("SHA-1");
// 使用指定的字节更新摘要
mdInst.update(btInput);
// 获得密文
byte[] md = mdInst.digest();
// 把密文转换成十六进制的字符串形式
int j = md.length;
char str[] = new char[j * 2];
int k = 0;
for (byte byte0 : md) {
str[k++] = hexDigits[byte0 >>> 4 & 0xf];
str[k++] = hexDigits[byte0 & 0xf];
}
String result = new String(str);
return length == 16 ? result.substring(8, 24) : result;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
这样基本上可以了,后面的付款到银行卡应该也是差不多的,希望文章能对大家做付款到用户这款有所帮助,同时如果我写的有误的地方,还请指正,谢谢。