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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值