1. 程式人生 > 其它 >java實現微信退款

java實現微信退款

技術標籤:程式碼

微信退款

@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