一、总述
用户登录微信公众号,把自己的系统余额提现到微信账户里。微信提现接口参考:https://pay.weixin.qq.com/wiki/doc/api/tools/mch_pay.php?chapter=14_2
这里的交互实体有用户、微信、平台。第一步用户需要在微信取得授权;第二步用户登录平台;第三步平台调用微信提现接口。
二、时序图
1. 用户在微信进行登录授权
用户在微信进行登录授权.png
2.用户登录平台
用户登录平台.png
3. 平台调用微信提现接口
平台调用微信提现接口.png
三、微信提现的关键代码
1、调用微信提现接口:
/**
* 请求报文示例:
*
* wx91f15555be5d81f8
* 1487727532
* ce758408f6ef98d7c7a7b786eca7b3a8
* 8D21F9E7A76FBE7DBCB39AFD0552E900
* 1511343699128386
* o6WWEwA9AlhoFWMxdnWkiiixBSHw
* NO_CHECK
* 2000000
* 收益发放
* 192.168.80.103
*
* 返回报文示例:
*
*
*
*
*
*
*
*
*
*
*
* 或者
*
*
*
*
*
*
*
*
*
*/
@Service
public class PayServiceImpl implements PayService {
@Value("${wechat.appId}")
private String wxAppId;
@Value("${wechat.appSecret}")
private String wxAppSecret;
@Value("${webchat.merchantId}")
private String wxMerchantId;
@Value("${webchat.merchantKey}")
private String wxMerchantKey;
@Autowired
private WechatMessageLogService wechatMessageLogService;
/**
* 微信转账url
*/
private static final String WECHAT_TRANSFER_URL = "https://api.mch.weixin.qq.com/mmpaymkttransfers/promotion/transfers";
/**
* 微信转账处理
*
* @param uid
* @param recordId
* @param openId
* @param amount
* @return
*/
@Override
public WechatMessageLog handleWechatTransfer(Long uid, Long recordId, String openId, BigDecimal amount) {
//组装退款数据
WeixinTransferSendData transferSendData = buildWeixinTransferSendData(recordId, openId, amount);
XmlUtil xmlUtil = new XmlUtil<>();
String inTransferSendXml = xmlUtil.parseToXml(transferSendData, WeixinTransferSendData.class);
GwsLogger.info("微信转账处理开始,用户ID:{},记录ID:{},请求入参是{}", uid, recordId, inTransferSendXml);
//https转账
WechatMessageLog wechatMessageLog = new WechatMessageLog();
wechatMessageLog.setUid(uid);
wechatMessageLog.setRecordId(recordId);
wechatMessageLog.setAmount(amount);
wechatMessageLog.setInParam(inTransferSendXml);
wechatMessageLog.setTradeStatus(WechatWithdrawStatus.FAILURE.getCode());
try {
String resultXML = transfer(WECHAT_TRANSFER_URL, inTransferSendXml, wxMerchantId);
wechatMessageLog.setOutParam(resultXML);
GwsLogger.info("微信转账返回报文是{},用户ID:{},记录ID:{}", resultXML, uid, recordId);
Map resultMap = XMLUtil.doXMLParse(resultXML);
if (CollectionUtils.isEmpty(resultMap)) {
return wechatMessageLog;
}
if ("SUCCESS".equals(resultMap.get("return_code"))) {
// 结果响应正常
if ("SUCCESS".equals(resultMap.get("result_code"))) {
String outTradeNo = resultMap.get("payment_no");
wechatMessageLog.setOutTradeNo(outTradeNo);
wechatMessageLog.setTradeStatus(WechatWithdrawStatus.SUCCESS.getCode());
GwsLogger.info("微信转账成功, 转账金额: {}, 用户ID:{},记录ID:{}", amount, uid, recordId);
return wechatMessageLog;
} else {
String errCode = resultMap.get("err_code");
String errCodeDes = resultMap.get("err_code_des");
wechatMessageLog.setErrorCode(errCode);
wechatMessageLog.setErrorMsg(errCodeDes);
GwsLogger.info("微信转账失败,错误码是{}, 错误信息是{}, 用户ID:{},记录ID:{}",
errCode, errCodeDes, uid, recordId);
return wechatMessageLog;
}
}
} catch (Exception e) {
GwsLogger.info("微信转账处理系统异常,用户ID:{},记录ID:{},异常明细:{}", uid, recordId, e);
wechatMessageLog.setErrorCode("500001");
wechatMessageLog.setErrorMsg("微信转账失败");
return wechatMessageLog;
} finally {
wechatMessageLogService.saveWechatMessageLog(wechatMessageLog);
}
return wechatMessageLog;
}
private String transfer(String url, String data, String mchId) throws Exception {
/**
* PKCS12证书 是从微信商户平台-》账户设置-》 API安全 中下载的
*/
KeyStore keyStore = KeyStore.getInstance("PKCS12");
Resource resource = new ClassPathResource("apiclient_cert.p12");
InputStream instream = resource.getInputStream();
try {
/**密码: MCHID*/
keyStore.load(instream, mchId.toCharArray());
} finally {
instream.close();
}
SSLContext sslcontext = SSLContexts.custom().loadKeyMaterial(keyStore, mchId.toCharArray()).build();
// Allow TLSv1 protocol only
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslcontext,
new String[]{"TLSv1"}, null,
SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);
CloseableHttpClient httpclient = HttpClients.custom().setSSLSocketFactory(sslsf).build();
try {
// 设置响应头信息
HttpPost httpost = new HttpPost(url);
httpost.addHeader("Connection", "keep-alive");
httpost.addHeader("Accept", "*/*");
httpost.addHeader("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
httpost.addHeader("Host", "api.mch.weixin.qq.com");
httpost.addHeader("X-Requested-With", "XMLHttpRequest");
httpost.addHeader("Cache-Control", "max-age=0");
httpost.addHeader("User-Agent", "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0) ");
httpost.setEntity(new StringEntity(data, "UTF-8"));
CloseableHttpResponse response = httpclient.execute(httpost);
try {
HttpEntity entity = response.getEntity();
String jsonStr = EntityUtils.toString(response.getEntity(), "UTF-8");
EntityUtils.consume(entity);
return jsonStr;
} finally {
response.close();
}
} finally {
httpclient.close();
}
}
/**
* 组装退款数据
*
* @param recordId
* @param openId
* @param amount
* @return
*/
private WeixinTransferSendData buildWeixinTransferSendData(Long recordId, String openId, BigDecimal amount) {
WeixinTransferSendData transferSendData = new WeixinTransferSendData();
transferSendData.setAmount(amount.multiply(BigDecimal.TEN).multiply(BigDecimal.TEN).intValue());
transferSendData.setCheckName("NO_CHECK");
transferSendData.setDesc("收益发放");
transferSendData.setMchAppId(wxAppId);
transferSendData.setMchid(wxMerchantId);
transferSendData.setNonceStr(WeixinSignUtil.getNonceStr());
transferSendData.setOpenid(openId);
transferSendData.setPartnerTradeNo(recordId.toString());
transferSendData.setSpbillCreateIp(GwsUtil.getLocalIP());
// 签名
String sign = WeixinSignUtil.createSign(transferSendData.getSortedMap(), wxMerchantKey);
transferSendData.setSign(sign);
return transferSendData;
}
}
2、用户在平台发起提现申请
/**
* 微信转账.
* 1.创建微信提现记录
* 2.调用trade服务, 扣减账户余额
* 3.开始发起微信转账
* 4.判断转账成功还是失败.如果失败,则调用trade服务的返还用户的余额
* 如果成功,则更新提现状态, 累计提现成功的次数.
*
* @param uid
* @param amount
* @param openId
* @param account
* @return
*/
@Override
public OperationResult wechatWithdraw(Long uid, BigDecimal amount, String openId, String account) {
/**记录微信提现记录*/
WechatWithdrawRecord withdrawRecord = createWechatWithdrawRecord(uid, WechatWithdrawStatus.WAIT_PROCESS, amount);
Long recordId = withdrawRecord.getRecordId();
GwsLogger.info("uid=[{}]创建微信提现记录,记录ID:{}", uid, recordId);
/**调用trade服务, 扣减账户余额*/
CommonResponse response = accountRemoteService.callHandleWechatAcct(uid, recordId, WechatCategory.WITHDRAW, amount);
if (!SystemCode.SUCCESS.getCode().equals(response.getCode())) {
GwsLogger.error("调用trade服务扣减账户余额失败,用户ID:{},记录ID:{},返回结果:{}",
uid, recordId, JSON.toJSONString(response));
withdrawRecordService.updateWechatWithdrawRecords(recordId, WechatWithdrawStatus.FAILURE);
return new OperationResult(response);
}
/**发起微信转账*/
StopWatch stopWatch = new StopWatch();
stopWatch.start("转账处理任务, 用户ID: " + uid);
WechatMessageLog wechatMessageLog = payService.handleWechatTransfer(uid, recordId, openId, amount);
stopWatch.stop();
GwsLogger.info(stopWatch.getLastTaskName() + ",耗时:" + stopWatch.getTotalTimeMillis());
/**返回转账结果信息*/
if (null == wechatMessageLog) {
return new OperationResult(BizErrorCode.WITHDRAW_ERROR);
}
this.handleWechatMessageLog(wechatMessageLog);
if (WechatWithdrawStatus.SUCCESS.getCode().equals(wechatMessageLog.getTradeStatus())) {
return new OperationResult(true);
}
return new OperationResult(wechatMessageLog.getErrorCode(), wechatMessageLog.getErrorMsg());
}
@Async
public void handleWechatMessageLog(WechatMessageLog wechatMessageLog) {
Long uid = wechatMessageLog.getUid();
Long recordId = wechatMessageLog.getRecordId();
BigDecimal amount = wechatMessageLog.getAmount();
if (WechatWithdrawStatus.FAILURE.getCode().equals(wechatMessageLog.getTradeStatus())) {
// 转账失败. 调用trade服务的返还账户余额接口
GwsLogger.info("微信转账失败, 开始返还账户余额接口. 用户ID:{},记录ID:{}", uid, recordId);
CommonResponse result = accountRemoteService.callHandleWechatAcct(uid,
recordId, WechatCategory.WITHDRAW_BACK, amount);
if (SystemCode.SUCCESS.getCode().equals(result.getCode())) {
withdrawRecordService.updateWechatWithdrawRecords(recordId, WechatWithdrawStatus.FAILURE);
GwsLogger.info("微信提现失败, 账户余额已返还至账户! 用户ID:{},记录ID:{}", uid, recordId);
} else {
// warn 发送邮件警告. 自动返还提现失败的金额到账户余额
GwsLogger.error("微信提现失败, 自动返还提现失败的金额到账户余额出现错误! 用户ID:{},记录ID:{}", uid, recordId);
emailService.sendEmail("微信提现, 自动返还提现失败的金额到账户余额出现错误",
"用户ID是:" + uid + ", 提现记录ID是:" + recordId);
}
} else if (WechatWithdrawStatus.SUCCESS.getCode().equals(wechatMessageLog.getTradeStatus())) {
// 转账成功.
GwsLogger.info("微信转账成功. 用户ID:{},领取金额:{},记录ID:{}", uid, amount, recordId);
String nowYayStr = DateUtil.getFormatDate(new Date(), DateUtil.DATA_PATTON_YYYYMMDD);
// 累计用户提现成功的次数
redis.increment(CachePrefix.WECHAT_SUCCESS_TIMES_PREFIX, nowYayStr + "_" + uid, 1L);
withdrawRecordService.updateWechatWithdrawRecords(recordId, WechatWithdrawStatus.SUCCESS);
}
}