开通条件。
商户注册超过90天且,连续30天有交易,可以每天支付1元来刷,目前测试可行。随后在微信商户平台 - 产品管理自动开启,然后需要申请。
注意:要有公众号appid才行,但是听说移动应用的appid也可以。
小程序appid与对应的openid也可以,如果非同主体,可以尝试mch_id与appid绑定;
移动应用appid与对应的openid也可以,如果非同主体,可以尝试mch_id与appid绑定;
Attention:如果主体里面有括号,注意英文括号和中文括号区别;
2.此类接口使用场景。
无论是微信还是支付宝,现金红包,单笔转账,企业转账等资金支出类接口都可以用作,活动营销、招新、邀请好友发送奖励、企业内部报销、合伙人团长提现等场景。
注意:
1.微信商户账号需要有余额;
2.根据实际业务,比如发红包,需要做并发和同步锁处理,同时建议在商户平台开启防盗刷处理;
3.调用三方接口,看好官方文档,参数获取的对,传递的对,工具类找的好,基本就ok了;
4.不支持给非实名用户付款,如果报错,请处理,并且展示给前端可理解字
企业付款到微信:
import com.alipay.api.AlipayApiException;
import com.alipay.api.AlipayClient;
import com.alipay.api.DefaultAlipayClient;
import com.alipay.api.domain.AlipayFundTransToaccountTransferModel;
import com.alipay.api.request.AlipayFundTransToaccountTransferRequest;
import com.alipay.api.response.AlipayFundTransToaccountTransferResponse;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.github.wxpay.sdk.WXPay;
import com.github.wxpay.sdk.WXPayConstants;
import com.sun.org.apache.xerces.internal.impl.dv.util.Base64;
import com.yx.api.ms.mk.alipay.AlipayConfig;
import com.yx.api.ms.mk.wxpay.MyWXPayConfig;
import com.yx.api.ms.mk.wxpay.WXPayUtil;
import com.yx.api.ms.mk.wxpay.util.*;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.ResourceUtils;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.security.PublicKey;
import java.sql.Timestamp;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
/**
* @Description: 提现
**/
public class WxWithdrawBank {
private Logger logger = LoggerFactory.getLogger(TransactionServiceImpl.class);
@Resource
private MyWXPayConfig config;
@Resource
private WXPay wxPay;
/**
* 微信提现到钱包
* @param request request
* @return Result
* @throws Exception Exception
*/
public Result transferPay(HttpServletRequest request) throws Exception {
Map<String, String> map = new HashMap<>();
map.put("mch_appid",config.getAppID());
map.put("mchid",config.getMchID());
//随机字符串
map.put("nonce_str", UUID.randomUUID().toString().replaceAll("-","").toUpperCase());
//本地订单编号
map.put("partner_trade_no","订单号");
//openid
// map.put("openid","oYeUZ6E_9Xafm7NLOdIlRn2uL3x4");
map.put("openid","微信openid");
//需要收款人真实姓名
map.put("check_name","FORCE_CHECK");
// map.put("check_name","NO_CHECK");
map.put("re_user_name","收款人姓名");
//金额
BigDecimal totalAmount = new BigDecimal("100");
int amount = new BigDecimal("10").multiply(totalAmount).intValue();
map.put("amount",amount+"");
map.put("desc","提现!");
//IP
map.put("spbill_create_ip",getClientIpAddress(request));
map.put("sign", WXPayUtil.generateSignature(map, config.getKey(), WXPayConstants.SignType.MD5));
//生成交易记录,这一步才调用微信提现接口,上面的是封装参数
String restxml = HttpUtils.posts(config.getTransfersUrl(), XmlUtil.xmlFormat(map, false));
Map<String, String> transferInfo = XmlUtil.xmlParse(restxml);
logger.error(transferInfo.get("err_code_des"));
if (CollectionUtil.isNotEmpty(transferInfo) && "SUCCESS".equals(transferInfo.get("result_code"))) {
// 提现成功,处理业务逻辑
return Result.success(CommonExceptionStatus.ACCEPTED, "提现成功", transactionDTO.getRecId());
}
if ( CollectionUtil.isNotEmpty(transferInfo) && "NOTENOUGH".equals(transferInfo.get("err_code"))){
return Result.failure(CommonExceptionStatus.NOT_SUFFICIENT_FUNDS, "not_sufficient_funds", request.getRequestURI(),request.getMethod());
}
logger.error("微信提现钱包失败,请稍后再试");
return Result.failure(CommonExceptionStatus.UPDATE_FAILURE, "remit_failure", request.getRequestURI(),request.getMethod());
}
public static void main(String[] args) throws Exception {
Map<String, String> map = new HashMap<>();
//微信支付分配的商户号
map.put("mch_id","1513745611");
//随机字符串
map.put("nonce_str", UUID.randomUUID().toString().replaceAll("-","").toUpperCase());
//签名
map.put("sign", WXPayUtil.generateSignature(map, "38844f2e2a142b0a8e727512d2911ad9", WXPayConstants.SignType.MD5).toUpperCase());
//生成交易记录,这一步才调用微信提现接口,上面的是封装参数
// 将当前的map结合转化成xml格式
String restxml = HttpUtils.posts("https://fraud.mch.weixin.qq.com/risk/getpublickey", XmlUtil.xmlFormat(map, false));
Map<String, String> transferInfo = XmlUtil.xmlParse(restxml);
System.err.println(transferInfo.get("return_code"));
System.err.println(transferInfo.get("return_msg"));
if (CollectionUtil.isNotEmpty(transferInfo) && "SUCCESS".equals(transferInfo.get("result_code"))) {
System.err.println(transferInfo);
System.err.println(transferInfo.get("return_code"));
System.err.println(transferInfo.get("return_msg"));
}
System.err.println(transferInfo.get("err_code"));
System.err.println(transferInfo.get("err_code_des"));
}
/**
* 微信提现到银行卡
* @param request request
* @param transactionDTO 交易对象
* @return Result
* @throws Exception Exception
*/
public Result transferPayBank(HttpServletRequest request) throws Exception {
//需加密的银行账号
String encBankAcctNo = "银行账号";
//需加密的银行账户名
String encBankAcctName = "银行账户名";
//读取PKCS8密钥文件
File file = ResourceUtils.getFile("classpath:wxPay/public_key.pem");
PublicKey pub= RSAUtils.getPubKey(file.toString(),"RSA");
// rsa是微信付款到银行卡要求我们填充的字符串(Java)
String rsa = "RSA/ECB/OAEPWITHSHA-1ANDMGF1PADDING";
//对银行账号进行加密
byte[] estr = RSAUtils.encrypt(encBankAcctNo.getBytes("UTF-8"),pub,2048, 11,rsa);
//并转为base64格式---- 调用付款需要传的 银行卡号
encBankAcctNo = Base64.encode(estr);
//对银行账号用户名进行加密
byte[] name = RSAUtils.encrypt(encBankAcctName.getBytes("UTF-8"),pub,2048, 11,rsa);
//并转为base64格式--调用付款需要传的 用户名
encBankAcctName =Base64.encode(name);
Map<String, String> map = new HashMap<>();
//金额
BigDecimal totalAmount = new BigDecimal("100");
int amount = new BigDecimal("10").multiply(totalAmount).intValue();
map.put("amount",amount+"");
//收款方开户行
map.put("bank_code", "开户行号");
map.put("desc","提现!");
//收款方银行卡号
map.put("enc_bank_no", "银行卡号");
//收款方用户名
map.put("enc_true_name","用户名");
//微信支付分配的商户号
map.put("mch_id",config.getMchID());
//随机字符串
map.put("nonce_str", UUID.randomUUID().toString().replaceAll("-","").toUpperCase());
//订单编号
map.put("partner_trade_no","订单号");
//签名
map.put("sign", WXPayUtil.generateSignature(map, config.getKey(), WXPayConstants.SignType.MD5).toUpperCase());
//生成交易记录,这一步才调用微信提现接口,上面的是封装参数
// 将当前的map结合转化成xml格式
String restxml = HttpUtils.posts(config.getTransfersPayBank(), XmlUtil.xmlFormat(map, false));
Map<String, String> transferInfo = XmlUtil.xmlParse(restxml);
if (CollectionUtil.isNotEmpty(transferInfo) && "SUCCESS".equals(transferInfo.get("result_code"))) {
// 提现成功,进行业务逻辑处理
return Result.success(CommonExceptionStatus.ACCEPTED, "提现成功");
}
//错误代码
logger.error(transferInfo.get("err_code"));
if ( CollectionUtil.isNotEmpty(transferInfo) && "NOTENOUGH".equals(transferInfo.get("err_code"))){
//错误代码描述
logger.error(transferInfo.get("err_code_des"));
return Result.failure(CommonExceptionStatus.NOT_SUFFICIENT_FUNDS, "not_sufficient_funds", request.getRequestURI(),request.getMethod());
}
//错误代码描述
logger.error(transferInfo.get("err_code_des"));
logger.error("微信提现到银行卡失败,请稍后再试");
return Result.failure(CommonExceptionStatus.UPDATE_FAILURE, "remit_failure", request.getRequestURI(),request.getMethod());
}
/**
* 查询企业付款到零钱
*/
@GetMapping("/wxPay/transferIQuery")
public Result getTransferInfo(HttpServletRequest request, HttpServletResponse response,
String callback,String order_code) throws Exception {
Map<String, String> restmap = null;
Map<String, String> parm = new HashMap<String, String>();
parm.put("appid", config.getAppID());
//商户id
parm.put("mch_id", config.getMchID());
parm.put("partner_trade_no", order_code);
parm.put("nonce_str", UUID.randomUUID().toString().replaceAll("-","").toUpperCase());
parm.put("sign",WXPayUtil.generateSignature(parm, config.getKey(), WXPayConstants.SignType.MD5));
String restxml = HttpUtils.posts(config.getTransfersPayQuery(), XmlUtil.xmlFormat(parm, true));
restmap = XmlUtil.xmlParse(restxml);
if (CollectionUtil.isNotEmpty(restmap) && "SUCCESS".equals(restmap.get("result_code"))) {
// 订单查询成功 处理业务逻辑
//logger.info("订单查询:订单" + restmap.get("partner_trade_no") + "支付成功");
Map<String, String> transferMap = new HashMap<>();
//商户转账订单号
transferMap.put("partnerTradeNo", restmap.get("partner_trade_no"));
//收款微信号
transferMap.put("openid", restmap.get("openid"));
//转账金额
transferMap.put("paymentAmount", restmap.get("payment_amount"));
//转账时间
transferMap.put("transferTime", restmap.get("transfer_time"));
//转账描述
transferMap.put("desc", restmap.get("desc"));
return Result.success().setData(transferMap);
}else {
if (CollectionUtil.isNotEmpty(restmap)) {
//logger.info("订单转账失败:" + restmap.get("err_code") + ":" + restmap.get("err_code_des"));
}
return Result.success().setData(restmap);
}
}
}
企业付款http请求工具:HttpUtils
package com.yx.api.ms.mk.wxpay.util;
import org.apache.http.NameValuePair;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.ssl.SSLContexts;
import org.apache.http.util.EntityUtils;
import org.springframework.util.ResourceUtils;
import javax.net.ssl.SSLContext;
import java.io.*;
import java.net.URLEncoder;
import java.security.KeyStore;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* @Description: 企业付款http请求工具
**/
public class HttpUtils {
private static final String DEFAULT_CHARSET = "UTF-8";
//链接超时时间3秒
private static final int CONNECT_TIME_OUT = 5000;
private static final RequestConfig REQUEST_CONFIG = RequestConfig.custom().setConnectTimeout(CONNECT_TIME_OUT).build();
//微信支付ssl证书
private static SSLContext wx_ssl_context = null;
/**
* 证书密码默认是商户号
*/
private static final String MCH_ID = "1513745611";
static{
InputStream inputStream = null;
//证书
//Resource resource = new ClassPathResource("classpath:wxPay/apiclient_cert.p12");
try {
File file = ResourceUtils.getFile("classpath:wxPay/apiclient_cert.p12");
String fileUrl = file.toString();
inputStream = new FileInputStream(file);
KeyStore keystore = KeyStore.getInstance("PKCS12");
//证书密码
char[] keyPassword = MCH_ID.toCharArray();
keystore.load(inputStream, keyPassword);
wx_ssl_context = SSLContexts.custom().loadKeyMaterial(keystore, keyPassword).build();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* @description 功能描述: get 请求
* @param url 请求地址
* @param params 参数
* @param headers headers参数
* @return 请求失败返回null
*/
public static String get(String url, Map<String, String> params, Map<String, String> headers) {
CloseableHttpClient httpClient = null;
if (params != null && !params.isEmpty()) {
StringBuffer param = new StringBuffer();
boolean flag = true; // 是否开始
for (Map.Entry<String, String> entry : params.entrySet()) {
if (flag) {
param.append("?");
flag = false;
} else {
param.append("&");
}
param.append(entry.getKey()).append("=");
try {
param.append(URLEncoder.encode(entry.getValue(), DEFAULT_CHARSET));
} catch (UnsupportedEncodingException e) {
//编码失败
}
}
url += param.toString();
}
String body = null;
CloseableHttpResponse response = null;
try {
httpClient = HttpClients.custom().setDefaultRequestConfig(REQUEST_CONFIG).build();
HttpGet httpGet = new HttpGet(url);
response = httpClient.execute(httpGet);
body = EntityUtils.toString(response.getEntity(), DEFAULT_CHARSET);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (response != null) {
try {
response.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (httpClient != null) {
try {
httpClient.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return body;
}
/**
* @description 功能描述: get 请求
* @param url 请求地址
* @return 请求失败返回null
*/
public static String get(String url) {
return get(url, null);
}
/**
* @description 功能描述: get 请求
* @param url 请求地址
* @param params 参数
* @return 请求失败返回null
*/
public static String get(String url, Map<String, String> params) {
return get(url, params, null);
}
/**
* @description 功能描述: post 请求
* @param url 请求地址
* @param params 参数
* @return 请求失败返回null
*/
public static String post(String url, Map<String, String> params) {
CloseableHttpClient httpClient = null;
HttpPost httpPost = new HttpPost(url);
List<NameValuePair> nameValuePairs = new ArrayList<>();
if (params != null && !params.isEmpty()) {
for (Map.Entry<String, String> entry : params.entrySet()) {
nameValuePairs.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
}
}
String body = null;
CloseableHttpResponse response = null;
try {
httpClient = HttpClients.custom().setDefaultRequestConfig(REQUEST_CONFIG).build();
httpPost.setEntity(new UrlEncodedFormEntity(nameValuePairs, DEFAULT_CHARSET));
response = httpClient.execute(httpPost);
body = EntityUtils.toString(response.getEntity(), DEFAULT_CHARSET);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (response != null) {
try {
response.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (httpClient != null) {
try {
httpClient.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return body;
}
/**
* @description 功能描述: post 请求
* @param url 请求地址
* @param s 参数xml
* @return 请求失败返回null
*/
public static String post(String url, String s) {
CloseableHttpClient httpClient = null;
HttpPost httpPost = new HttpPost(url);
String body = null;
CloseableHttpResponse response = null;
try {
httpClient = HttpClients.custom().setDefaultRequestConfig(REQUEST_CONFIG).build();
httpPost.setEntity(new StringEntity(s, DEFAULT_CHARSET));
response = httpClient.execute(httpPost);
body = EntityUtils.toString(response.getEntity(), DEFAULT_CHARSET);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (response != null) {
try {
response.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (httpClient != null) {
try {
httpClient.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return body;
}
/**
* @description 功能描述: post https请求,服务器双向证书验证
* @param url 请求地址
* @param params 参数
* @return 请求失败返回null
*/
public static String posts(String url, Map<String, String> params) {
CloseableHttpClient httpClient = null;
HttpPost httpPost = new HttpPost(url);
List<NameValuePair> nameValuePairs = new ArrayList<>();
if (params != null && !params.isEmpty()) {
for (Map.Entry<String, String> entry : params.entrySet()) {
nameValuePairs.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
}
}
String body = null;
CloseableHttpResponse response = null;
try {
httpClient = HttpClients.custom().setDefaultRequestConfig(REQUEST_CONFIG).setSSLSocketFactory(getSSLConnectionSocket()).build();
httpPost.setEntity(new UrlEncodedFormEntity(nameValuePairs, DEFAULT_CHARSET));
response = httpClient.execute(httpPost);
body = EntityUtils.toString(response.getEntity(), DEFAULT_CHARSET);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (response != null) {
try {
response.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (httpClient != null) {
try {
httpClient.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return body;
}
/**
* @description 功能描述: post https请求,服务器双向证书验证
* @param url 请求地址
* @param s 参数xml
* @return 请求失败返回null
*/
public static String posts(String url, String s) {
CloseableHttpClient httpClient = null;
HttpPost httpPost = new HttpPost(url);
String body = null;
CloseableHttpResponse response = null;
try {
httpClient = HttpClients.custom().setDefaultRequestConfig(REQUEST_CONFIG).setSSLSocketFactory(getSSLConnectionSocket()).build();
httpPost.setEntity(new StringEntity(s, DEFAULT_CHARSET));
response = httpClient.execute(httpPost);
body = EntityUtils.toString(response.getEntity(), DEFAULT_CHARSET);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (response != null) {
try {
response.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (httpClient != null) {
try {
httpClient.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return body;
}
//获取ssl connection链接
private static SSLConnectionSocketFactory getSSLConnectionSocket() {
return new SSLConnectionSocketFactory(wx_ssl_context, new String[] {"TLSv1", "TLSv1.1", "TLSv1.2"}, null, SSLConnectionSocketFactory.getDefaultHostnameVerifier());
}
}
银行卡、用户名称加密辅助类:RSAUtils
package com.yx.api.ms.mk.wxpay.util;
import com.sun.org.apache.xml.internal.security.utils.Base64;
import org.springframework.util.ResourceUtils;
import javax.crypto.Cipher;
import java.io.*;
import java.lang.reflect.Method;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
/**
* @Description: 银行卡、用户名称加密辅助类
**/
public class RSAUtils {
public static void main(String[] args) throws Exception {
//加密的银行账号
String encBankAcctNo = "银行卡号";
//加密的银行账户名
String encBankAcctName = "账户名";