Java微信支付-退款成功异步回调验签

接上一篇Java微信支付-申请退款API,本篇在上篇文章的基础上讲述调用申请退款API后退款成功之后微信异步回调通知
下文中所需配置、类都在以请查看以上链接内容。

在调用微信支付-申请退款API时,会传递notify_url这个参数给微信,这个参数是退款成功之后微信端会向此地址进行通知,我们应该在接受到微信发来的通知时进行验签确保安全性。
注意notify_url必须为外网可访问的url,不能携带参数。测试时可以使用内网穿透进行测试,这个东西在此我就不赘述了,需要的朋友自行Google。

/**
 * 微信支付Controller
 * 
 * @create: 2019-10-10 15:40
 * @author: Sun
 */
@RequestMapping(value = "/wxpay")
@RestController
@Slf4j
public class WxPayController {

    @Autowired
    private WxPayService wxPayService;

    /**
     * 微信退款异步回调接口
     *
     * @return
     */
    @RequestMapping(value = "/refund/async/notify")
    public void refundAsyncNotify(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws Exception {
        log.info("[refundAsyncNotify]");
        
        String resultXml = wxPayService.refundAsyncNotify(httpServletRequest);

        BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(httpServletResponse.getOutputStream());
        bufferedOutputStream.write(resultXml.getBytes());
        bufferedOutputStream.flush();
        bufferedOutputStream.close();
    }
}
/**
 * 微信支付接口
 * 
 * @create: 2019-10-10 15:40
 * @author: Sun
 */
public interface WxPayService {

    /**
     * 微信退款异步通知验证签名
     * @param httpServletRequest
     * @return
     */
    String refundAsyncNotify(HttpServletRequest httpServletRequest);
}
/**
 * 微信支付实现
 * 
 * @author: Sun
 * @create: 2019-10-10 17:54
 * @version: v1.0
 */
@Service
@Slf4j
public class WxPayServiceImpl implements WxPayService {

    @Autowired
    private MyWxPayConfig myWxPayConfig;
    @Autowired
    private WxDecodeUtil wxDecodeUtil;
    
    /**
     * 微信响应字段
     */
    private static final String RETURN_CODE = "return_code";
    

    @Override
    public String refundAsyncNotify(HttpServletRequest httpServletRequest) {

        String returnXmlMessage = null;
        String notifyXmlData = null;

        try {
            notifyXmlData = readXmlFromStream(httpServletRequest);
            Map<String, String> notifyMapData = WXPayUtil.xmlToMap(notifyXmlData);
            log.info("[refundAsyncNotify] [xml转换为map数据成功] [notifyMapData:{}]", notifyMapData);

            if (WXPayConstants.SUCCESS.equals(notifyMapData.get(RETURN_CODE))) {
                // 获得加密信息
                String reqInfo = notifyMapData.get("req_info");
                // 进行AES解密 获取req_info中包含的相关信息(解密失败会抛出异常)
                String refundDecryptedData = wxDecodeUtil.decryptData(reqInfo);
                Map<String, String> reqInfoMap = WXPayUtil.xmlToMap(refundDecryptedData);
                log.info("[refundAsyncNotify] [reqInfo解密成功] [reqInfoMap:{}]", reqInfoMap);
                // TODO 订单退款成功后相关业务逻辑...
                
				// 组装返回给微信的xml数据
                returnXmlMessage = setReturnXml(WXPayConstants.SUCCESS, "OK");
                log.info("[refundAsyncNotify]  [out_trade_no:{}] [out_refund_no:{}] [退款异步消息处理成功:{}]",
                        reqInfoMap.get("out_trade_no"), reqInfoMap.get("out_refund_no"), returnXmlMessage);
            } else {
                returnXmlMessage = setReturnXml(WXPayConstants.FAIL, "return_code不为success");
            }
        } catch (IOException e) {
            log.error("[refundAsyncNotify] [读取微信服务器返回流中xml数据时发生异常:{}] ", ExceptionUtils.getStackTrace(e));
            returnXmlMessage = setReturnXml(WXPayConstants.FAIL, "An exception occurred while reading the WeChat server returning xml data in the stream.");
        } catch (Exception e) {
            log.error("[refundAsyncNotify [处理异常]] [xml数据:{}] [异常:{}] ", notifyXmlData, ExceptionUtils.getStackTrace(e));

            returnXmlMessage = setReturnXml(WXPayConstants.FAIL, "Refund successful, exception occurred during asynchronous notification processing.");
            log.warn("[refundAsyncNotify] [退款异步消息处理失败:{}]", returnXmlMessage);
        }

        return returnXmlMessage;
    }

    /**
     * 设置返回给微信服务器的xml信息
     *
     * @param returnCode
     * @param returnMsg
     * @return
     */
    private String setReturnXml(String returnCode, String returnMsg) {
        return "<xml><return_code><![CDATA[" + returnCode + "]]></return_code><return_msg><![CDATA[" + returnMsg + "]]></return_msg></xml>";
    }

    /**
     * 从流中读取微信返回的xml数据
     *
     * @param httpServletRequest
     * @return
     * @throws IOException
     */
    private String readXmlFromStream(HttpServletRequest httpServletRequest) throws IOException {
        InputStream inputStream = httpServletRequest.getInputStream();
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
        final StringBuffer sb = new StringBuffer();
        String line = null;
        try {
            while ((line = bufferedReader.readLine()) != null) {
                sb.append(line);
            }
        } finally {
            bufferedReader.close();
            inputStream.close();
        }

        return sb.toString();
    }
}
/**
 * 微信解密工具类(AES-256-ECB解密 PKCS7Padding)
 * 解密方式官方文档:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_16&index=10#menu1
 * 解密步骤如下:
 * (1)对加密串A做base64解码,得到加密串B
 * (2)对商户key做md5,得到32位小写key* ( key设置路径:微信商户平台(pay.weixin.qq.com)-->账户设置-->API安全-->密钥设置 )
 * (3)用key*对加密串B做AES-256-ECB解密(PKCS7Padding)
 */
@Component
public class WxDecodeUtil {

    private String algorithm = "AES";

    private String algorithmModePadding = "AES/ECB/PKCS7Padding";

    private String key;

    SecretKeySpec secretKey;
	
	private boolean initialized = false;

    /**
     * AES解密
     * @param base64Data
     * @return
     * @throws Exception
     */
    public String decryptData(String base64Data) throws Exception {
		initialize();
 
        // 获取解码器实例,"BC"指定Java使用BouncyCastle库里的加/解密算法。
        Cipher cipher = Cipher.getInstance(algorithmModePadding, "BC");
        // 使用秘钥并指定为解密模式初始化解码器
        cipher.init(Cipher.DECRYPT_MODE, secretKey);
        // cipher.doFinal(byte[] b)在单部分操作中加密或解密数据,或完成多部分操作。 根据此秘钥的初始化方式,对数据进行加密或解密。
        return new String(cipher.doFinal(Base64.decode(base64Data)));
    }
    
    /**
     * 安全提供者列表中注册解密算法提供者,这个加载过程还挺慢的,有时候要好几秒,只需要加载一次就能一直使用。
     */
    private void initialize() {
        if (initialized) {
            return;
        }

        Security.addProvider(new BouncyCastleProvider());
        initialized = true;
    }


    /**
     * 构造方法(容器初始化时从配置文件中获取key,在全局中维护一个唯一的SecretKeySpec)
     * @param key
     */
    public WxDecodeUtil(@Value("${wxconfig.key}") String key) {
        this.key = key;
        // 转化成JAVA的密钥格式
        secretKey = new SecretKeySpec(WxMd5Util.MD5Encode(key, "UTF-8").toLowerCase().getBytes(), algorithm);
    }

}

发布了40 篇原创文章 · 获赞 10 · 访问量 4045

猜你喜欢

转载自blog.csdn.net/qq_41693150/article/details/102543016
今日推荐