分享给好友,领取红包,红包满100可以提取到微信零钱,有一段时间天天被“PDD”好友助力轰炸,但是提现这个玩意到底怎么玩的呢,一直也没时间也没场景去搞一下。这几天刚好项目需要提现的需求,于是乎在这里整理一下。
微信提现在微信开发文档中成为企业付款,更多详细的介绍及参数配置,可参考微信开发文档
https://pay.weixin.qq.com/wiki/doc/api/tools/mch_pay.php?chapter=14_1
首先创建一个提现日志表,用来追踪提现记录
@Data
public class WithdrawalLog{
private Long id;
/**
* 商户订单号
*/
private String cashSn;
/**
* 用户id
*/
private String userId;
/**
* openId
*/
private String openId;
/**
* 提现金额
*/
private BigDecimal amount;
/**
* 提现结果
*/
private Boolean status;
/**
* 失败原因
*/
private String message;
private Date createTime;
}
准备好微信提现需要的必备参数,比如什么appid,商户号,用户openId等等。准备好之后废话不多说直接提吧。
public BaseResponse<WithdrawalLog> withdrawalCash(String openId,String userId, BigDecimal amount) {
try {
//项目保存的插件对象,不用在意,只要有mchId即可
PluginVo pluginConfig = wxpayPaymentPlugin.getPluginConfig();
String mch_id = pluginConfig.getAttribute("mchID");
//用来生成32位随机字符
SnowflakeIdWorker idWorker = SnowflakeIdWorker.getFlowIdWorkerInstance();
String nonceStr = RandomStringUtils.randomAlphanumeric(32);
String sn = String.valueOf(idWorker.nextId());
Map<String, Object> parameterMap = new HashMap<>();
parameterMap.put("mch_appid", pluginConfig.getAttribute("appID"));//商户账号appid
parameterMap.put("mchid", mch_id);//商户号
parameterMap.put("nonce_str", nonceStr);//随机字符串
parameterMap.put("partner_trade_no", sn);//商户订单号
parameterMap.put("openid", openId);//用户openid
parameterMap.put("check_name", "NO_CHECK");//校验用户姓名选项
parameterMap.put("amount", amount.multiply(new BigDecimal("100")).intValue());//金额
parameterMap.put("desc", "佣金");//企业付款备注
parameterMap.put("sign", generateSign(parameterMap));//签名
//map转为xml工具类
String postDataXML = XMLUtils.toXML(parameterMap);
log.info("提现参数准备XML:{}", postDataXML);
//调用微信提现请求
String result = sendPost(postDataXML, mch_id);
//微信返回xml类型的结果转为map
Map responseMap = XMLUtils.fromXML(result);
//保存提现日志
WithdrawalLog withdrawalLog = new WithdrawalLog();
withdrawalLog.setAmount(amount);
withdrawalLog.setCashSn(sn);
withdrawalLog.setOpenId(openId);
withdrawalLog.setUserId(userId);
if (FAIL.equals(responseMap.get(RETURN_CODE))) {
withdrawalLog.setStatus(false);
withdrawalLog.setMessage(responseMap.get(RETURN_MSG).toString());
saveWithdrawalLog(withdrawalLog);
return BaseResponse.successResponse("提现失败," + responseMap.get(RETURN_MSG));
} else {
if (SUCCESS.equals(responseMap.get(RESULT_CODE))) {
withdrawalLog.setStatus(true);
saveWithdrawalLog(withdrawalLog);
return BaseResponse.successResponse("微信退款完成");
} else {
//出现业务错误
String errorCodeDes = String.valueOf(responseMap.get(ERR_CODE_DES));
withdrawalLog.setStatus(false);
withdrawalLog.setMessage(errorCodeDes);
saveWithdrawalLog(withdrawalLog);
log.error("err_code_des:" + errorCodeDes);
return BaseResponse.exceptionResponse("退款失败" + errorCodeDes);
}
}
} catch (Exception e) {
return BaseResponse.exceptionResponse("提现失败");
}
}
//签名的处理,下边会提供笔者生成签名的工具类,可作为参考
private String generateSign(Map<String, ?> parameterMap) {
return SignatureUtils.getSign(parameterMap, "key", "sign");
}
//请求微信提现URL
private String sendPost(String postDataXML, String mch_id) throws Exception {
CloseableHttpClient httpClient = null;
CloseableHttpResponse httpResponse = null;
try {
//获取微信退款证书
KeyStore keyStore = getCertificate(mch_id);
SSLContext sslContext = SSLContexts.custom().loadKeyMaterial(keyStore, mch_id.toCharArray()).build();
SSLConnectionSocketFactory sslf = new SSLConnectionSocketFactory(sslContext);
httpClient = HttpClients.custom().setSSLSocketFactory(sslf).build();
HttpPost httpPost = new HttpPost("\"https://api.mch.weixin.qq.com/mmpaymkttransfers/promotion/transfers\"");//提现接口
StringEntity reqEntity = new StringEntity(postDataXML);
// 设置类型
reqEntity.setContentType("application/x-www-form-urlencoded");
httpPost.setEntity(reqEntity);
String result = null;
httpResponse = httpClient.execute(httpPost);
HttpEntity httpEntity = httpResponse.getEntity();
if (Validator.isNotNullOrEmpty(httpEntity)) {
result = EntityUtils.toString(httpEntity, "UTF-8");
EntityUtils.consume(httpEntity);
}
return result;
} finally {//关流
httpClient.close();
httpResponse.close();
}
}
private KeyStore getCertificate(String mch_id) {
//try-with-resources 关流
try (FileInputStream inputStream = new FileInputStream(new File("D:\\wx\\1248201701_20190812_cert\\apiclient_cert.p12"))) {
KeyStore keyStore = KeyStore.getInstance(PKCS12);
keyStore.load(inputStream, mch_id.toCharArray());
return keyStore;
} catch (Exception e) {
throw new RuntimeException(e.getMessage(), e);
}
}
简单提醒:代码中pluginConfig
为项目中对微信插件的配置,还有生成的32位随机字符串也是项目中的工具类,微信提现金额单位是分
关于签名的问题,按照微信文档提出的要求,拼接加密即可
微信签名,下边提供一下我用到的签名工具类
关于证书的问题:
去微信指定的地址下载证书,测试的时候放到本地即可,微信的提现和退款关于证书的操作是一样的,获取证书可能会碰到一写问题,可参考一下我之前写的ERROR: api.mch.weixin.qq.com:443 failed to respond [微信退款]
签名
public class SignatureUtils
{
private static final Logger logger = LoggerFactory.getLogger(SignatureUtils.class);
public static String getSign(Map<String, ?> map, String signKey, String... ignoreKeys)
{
StringBuilder signContent = new StringBuilder();
if (null != map)
{
List<String> keys = new ArrayList(map.keySet());
Collections.sort(keys, String.CASE_INSENSITIVE_ORDER);
for (String key : keys) {
if ((map.get(key) != null) && (!ArrayUtils.contains(ignoreKeys, key)))
{
String value;
if ((map.get(key) instanceof String[]))
{
StringBuilder sb = new StringBuilder();
for (String v : (String[])map.get(key))
{
if (sb.length() > 0) {
sb.append(",");
}
sb.append(v);
}
value = sb.toString();
}
else
{
value = map.get(key).toString();
}
if (value.length() > 0)
{
signContent.append(key);
signContent.append("=");
signContent.append(value);
signContent.append("&");
}
}
}
}
signContent.append("key=");
signContent.append(signKey);
logger.debug("Sign Before MD5:" + signContent.toString());
String result = DigestUtils.md5Hex(signContent.toString()).toUpperCase();
return result;
}
}
最后,微信提现需要登录微信支付商户平台-产品中心,开通企业付款,要不然代码写的再华丽,终是一场空。