java實現微信退款
阿新 • • 發佈:2021-01-14
技術標籤:程式碼
微信退款
@Component
@Slf4j
public class WechatServiceImpl implements WechatService {
@Value("${app.wechat.login-url}")
private String wechatLoginUrl;
@Value("${app.wechat.app-id}")
private String wechatAppId;
@Value("${app.wechat.secret}")
private String wechatSecret;
@Value("${app.wechat.pay.mch_id}")
private String wechatPayMchId;
@Value("${app.wechat.pay.key}")
private String wechatPayKey;
@Value("${app.wechat.pay.url}")
private String wechatPayUrl;
@Value("${order.pay.overtime}")
private Long orderPayOvertime;
@Value ("${app.wechat.pay.refund.url}")
private String wechatPayRefundUrl;
@Value("${server.domain.name}")
private String domainName;
@Override
public Boolean payRefund(ReturnPolicy returnPolicy, Pay pay, Order order) throws XMLParseException, WechatPayException {
//保證順序 簽名驗證必須按著key自然排序
Map<String, String> map = new TreeMap<>();
map.put("appid", wechatAppId);
map.put("mch_id", wechatPayMchId);
map.put("nonce_str", Utils.generateString(32));
map.put("sign_type", "MD5");
map.put("transaction_id", pay.getTransactionId());
map.put("out_refund_no", returnPolicy.getNo());
map.put("total_fee", order.getPayAmount() + "");
map.put("refund_fee", returnPolicy.getRealityAmount() + "");
map.put("refund_fee_type", "CNY");
map.put("refund_desc", returnPolicy.getReason());
map.put("notify_url", domainName + "/public/pay/refund/wechat/notify");
map.put("sign", Utils.sign(map, wechatPayKey));
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.TEXT_XML);
headers.set(HttpHeaders.ACCEPT_CHARSET, StandardCharsets.UTF_8.toString());
HttpEntity<String> request = new HttpEntity<>(Utils.mapToXml(map), headers);
String xml = restTemplateWithCert.postForObject(wechatPayRefundUrl, request, String.class);
Map<String, String> resultMap = Utils.xmlToMap(xml);
if (!resultMap.containsKey("result_code")) {
log.error("微信退款呼叫失敗:{}", xml);
log.error("微信退款呼叫失敗:{}", resultMap);
throw new WechatPayException();
}
return true;
}
}
微信退款回撥
/**
* 微信退款回撥介面
*/
@ApiMethod("微信退款回撥介面")
@RequestMapping(value = "/public/pay/refund/wechat/notify", produces = MediaType.TEXT_XML_VALUE + ";charset=UTF-8", consumes = MediaType.TEXT_XML_VALUE + ";charset=UTF-8")
public String wechatRefundNotify(@RequestBody String xml) {
Map<String, String> map;
try {
map = Utils.xmlToMap(xml);
} catch (XMLParseException e) {
log.error("微信退款通知XML轉換失敗,微信支付非同步通知返回引數map:{}", xml);
return "<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[簽名驗證失敗]]></return_msg></xml>";
}
log.info("微信退款非同步通知返回引數xml:{}", xml);
log.info("微信退款非同步通知返回引數map:{}", map);
String reqInfo = map.get("req_info");
String parseReqInfo;
try {
parseReqInfo = ParseReqInfo.parseReqInfo(wechatPayKey, reqInfo);
} catch (Exception e) {
log.error("微信退款通知解密req_info失敗,微信支付非同步通知返回引數map:{}", map);
return "<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[簽名驗證失敗]]></return_msg></xml>";
}
map.clear();
try {
map = Utils.xmlToMap(parseReqInfo);
} catch (XMLParseException e) {
log.error("微信退款通知req_info結果XML轉換失敗,微信支付非同步通知返回引數parseReqInfo:{}", parseReqInfo);
return "<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[簽名驗證失敗]]></return_msg></xml>";
}
if (!"SUCCESS".equals(map.get("refund_status"))) {
log.error("微信退款通知refund_status狀態異常,微信支付非同步通知返回引數parseReqInfo-->map:{}", map);
return "<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[簽名驗證失敗]]></return_msg></xml>";
}
ReturnPolicy returnPolicy = returnPolicyService.getOne(Wrappers.<ReturnPolicy>lambdaQuery().eq(ReturnPolicy::getNo, map.get("out_refund_no")), false);
returnPolicy.setAmountStatus(300);
returnPolicyService.updateById(returnPolicy);
//返回給微信成功的訊息
log.info("微信退款通知簽名驗證成功,返回結果:<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>");
return "<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>";
}
工具類
import org.dom4j.Document;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
public class Utils {
/**
* 將Map轉換為XML格式的字串
*
* @param data Map型別資料
* @return XML格式的字串
*/
public static String mapToXml(Map<String, String> data) {
StringBuilder builder = new StringBuilder("<xml>");
for (Map.Entry<String, String> entry : data.entrySet()) {
builder.append("<").append(entry.getKey()).append(">").append(entry.getValue()).append("</").append(entry.getKey()).append(">");
}
return builder.append("</xml>").toString();
}
/**
* xml轉map
*
* @param xml XML格式的字串
* @return Map型別資料
*/
public static Map<String, String> xmlToMap(String xml) throws XMLParseException {
try {
Map<String, String> map = new TreeMap<>();
Document document = DocumentHelper.parseText(xml);
Element nodeElement = document.getRootElement();
List<?> node = nodeElement.elements();
for (Object o : node) {
org.dom4j.Element elm = (org.dom4j.Element) o;
map.put(elm.getName(), elm.getText());
}
return map;
} catch (Exception e) {
throw new XMLParseException("XML解析異常");
}
}
}
微信退req_info款解密
package com.citrsw.shangshangpin.common;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import javax.crypto.Cipher;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.SecretKeySpec;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.Security;
import java.util.Base64;
/**
* 微信退req_info款解密
*
* @author Zhenfeng Li
* @version 1.0
* @date 2020-12-21 12:16
*/
public class ParseReqInfo {
/**
* 解碼器
*/
private static Cipher cipher = null;
/**
* 解密
*/
public static String parseReqInfo(String mchKey, String reqInfo) throws Exception {
if (cipher == null) {
init(mchKey);
}
Base64.Decoder decoder = Base64.getDecoder();
byte[] base64ByteArr = decoder.decode(reqInfo);
return new String(cipher.doFinal(base64ByteArr));
}
public static void init(String mchKey) {
//對key進行解密
//商戶祕鑰
String key = getMd5(mchKey);
SecretKeySpec secretKeySpec = new SecretKeySpec(key.getBytes(), "AES");
Security.addProvider(new BouncyCastleProvider());
try {
cipher = Cipher.getInstance("AES/ECB/PKCS7Padding");
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec);
} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException e) {
e.printStackTrace();
}
}
public static String getMd5(String str) {
try {
MessageDigest md = MessageDigest.getInstance("MD5");
return md5(str, md);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
return "";
}
}
public static String md5(String strSrc, MessageDigest md) {
byte[] bt = strSrc.getBytes();
md.update(bt);
return bytes2Hex(md.digest());
}
public static String bytes2Hex(byte[] bts) {
StringBuilder des = new StringBuilder();
String tmp;
for (byte bt : bts) {
tmp = (Integer.toHexString(bt & 0xFF));
if (tmp.length() == 1) {
des.append("0");
}
des.append(tmp);
}
return des.toString();
}
}
解密req_info 必須替換jre中的這兩個檔案
這兩個檔案的連線放這裡了
連結:https://pan.baidu.com/s/1_aUbvYEhw0GtCosbxO7Ksg
提取碼:yfkb