第一次做微信的退款处理,特此标记一下
准备
appId 微信分配的公众账号ID(企业号corpid即为此appId)
mch_id 微信支付分配的商户号
pay_secret 商户平台设置的秘钥:微信商户平台(pay.)-->账户设置-->API安全-->密钥设置
官方文档明确指出,退款还需使用到证书,至于证书怎么用后面说明
证书分为PHP版的和其他语言版的,这边以Java为例
下载证书放置服务器的文件目录,windows可直接导入
开发
请求实体对象
package com.macro.mall.portal.domain;
import lombok.Data;
@Data
public class WechatReturnRequestParam {
private String appId;//公众账号ID
private String mchId;//商户号
private String nonceStr;//随机字符串
private String sign;//签名
private String signType = "MD5";//签名类型
private String transactionId;//微信支付返回的订单号
private String outTradeNo;//商户订单号
private String outRefundNo;//退款单号
private Integer totalFee;//订单金额
private Integer refundFee;//退款金额
private String refundFeeType = "CNY";//货币类型
private String refundDesc;//退款原因
private String notifyUrl;//通知地址
private String mchSecret;
}
private static SortedMap buildParamter(WechatReturnRequestParam requstParam) {
SortedMap parameters = new TreeMap();
parameters.put("appid", requstParam.getAppId());
parameters.put("mch_id", requstParam.getMchId());
parameters.put("nonce_str", getNonceStr());
parameters.put("transaction_id", requstParam.getTransactionId());
parameters.put("out_refund_no", requstParam.getOutRefundNo());
parameters.put("total_fee", requstParam.getTotalFee() + "");
parameters.put("refund_fee", requstParam.getRefundFee() + "");
if (!StringUtils.isEmpty(requstParam.getRefundDesc())){
parameters.put("refund_desc", requstParam.getRefundDesc());
}
String sign = createSign("UTF-8", parameters, requstParam.getMchSecret());
parameters.put("sign", sign);
return parameters;
}
拼接参数并返回XML字符串
//1
String requestXML = WechatPayUtil.buildReturnXML(requstParam);
//2
public static String buildRequestXML(WechatPayRequestParam param) {
SortedMap parameters = buildParamter(param);
String requestXML = getRequestXml(parameters);
return requestXML;
}
//3
public static SortedMap buildParamter(WechatPayRequestParam param) {
if (param.getTotalFee() == null) {
param.setTotalFee(1);
}
SortedMap parameters = new TreeMap();
parameters.put("appid", param.getAppId());
parameters.put("mch_id", param.getMchId());
parameters.put("nonce_str", getNonceStr());
parameters.put("body", param.getBody());
parameters.put("out_trade_no", param.getOutTradeNo());
parameters.put("total_fee", param.getTotalFee() + "");
parameters.put("spbill_create_ip", param.getSpbillCreateIp());
parameters.put("notify_url", param.getNotifyUrl());
parameters.put("trade_type", param.getTradeType());
parameters.put("scene_info", param.getSceneInfo());
String sign = createSign("UTF-8", parameters, param.getMchSecret());
parameters.put("sign", sign);
return parameters;
}
//4.
public static String createSign(String characterEncoding, SortedMap parameters, String mchSecret) {
StringBuffer sb = new StringBuffer();
Set es = parameters.entrySet();
Iterator it = es.iterator();
while (it.hasNext()) {
Map.Entry entry = (Map.Entry) it.next();
String k = (String) entry.getKey();
Object v = entry.getValue();
if (null != v && !"".equals(v) && !"sign".equals(k) && !"key".equals(k)) {
sb.append(k + "=" + v + "&");
}
}
sb.append("key=" + mchSecret);
String sign = md5Encode(sb.toString(), characterEncoding).toUpperCase();
return sign;
}
//5
public static String getRequestXml(SortedMap parameters) {
StringBuffer sb = new StringBuffer();
sb.append("");
Set es = parameters.entrySet();
Iterator it = es.iterator();
while (true) {
while (it.hasNext()) {
Map.Entry entry = (Map.Entry) it.next();
String k = (String) entry.getKey();
String v = (String) entry.getValue();
if (!"attach".equalsIgnoreCase(k) && !"body".equalsIgnoreCase(k) && !"sign".equalsIgnoreCase(k)) {
sb.append("" + v + "" + k + ">");
} else {
sb.append("" + k + ">");
}
}
sb.append("");
return sb.toString();
}
}
创建请求
String responseString = WechatPayUtil.preReturnRequest(requestXML, config);
public static String preReturnRequest(String requestXML, WechatConfig config) throws Exception {
KeyStore keyStore = KeyStore.getInstance("PKCS12");
//config.getCertUrl() 是你存放证书的路径
FileInputStream instream = new FileInputStream(new File(config.getCertUrl()));
try {
//config.getMchId() 商户mch_id
keyStore.load(instream, config.getMchId().toCharArray());
} finally {
instream.close();
}
SSLContext sslcontext = SSLContexts.custom().loadKeyMaterial(keyStore, config.getMchId().toCharArray()).build();
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslcontext, new String[]{"TLSv1"}, null, SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);
String responseString = "";
CloseableHttpClient httpClient = HttpClients.custom().setSSLSocketFactory(sslsf).build();
CloseableHttpResponse httpResponse;
HttpPost httpPost = new HttpPost("https://api.mch./secapi/pay/refund");
httpPost.setEntity(new StringEntity(requestXML, "text/xml", "UTF-8"));
httpResponse = httpClient.execute(httpPost);
HttpEntity httpEntity = httpResponse.getEntity();
if (httpEntity != null) {
InputStream is = httpEntity.getContent();
BufferedReader br = new BufferedReader(new InputStreamReader(is, Consts.UTF_8));
for (String line; (line = br.readLine()) != null; responseString = responseString + line) ;
br.close();
is.close();
}
return responseString;
}
获取请求结果
WechatReturnRequestParam requstParam = getWechatReturnRequestParam(order, config);
String requestXML = WechatPayUtil.buildReturnXML(requstParam);
try {
log.info("发起微信退款的请求参数:{}", requestXML);
String responseString = WechatPayUtil.preReturnRequest(requestXML, config);
log.info("微信退款返回的参数:{}", responseString);
JSONObject result = WechatPayUtil.doXMLParse(responseString);
if (result == null) {
throw new RuntimeException("微信退款返回xml解析失败");
}
if (!"SUCCESS".equals(result.getString("return_code"))) {
throw new RuntimeException(result.getString("return_msg"));
}
if (!"SUCCESS".equals(result.getString("result_code"))) {
throw new RuntimeException(result.getString("err_code_des"));
}
log.info("订单{}已从微信退款,", order.getOrderSn());
} catch (Exception e) {
log.error("发起微信退款失败:错误异常:{}", e);
}
后记:我在请求参数的时候并没有传入notify_url通知地址这个参数,需要的童鞋请自行添加。