package com.lunhan.water.host.controller.notify; import com.google.gson.Gson; import com.lunhan.water.common.ConstantFactory; import com.lunhan.water.common.ExecutedResult; import com.lunhan.water.common.config.SysConfig; import com.lunhan.water.common.enums.ELogger; import com.lunhan.water.common.util.LocalDateTimeUtil; import com.lunhan.water.common.util.LoggerUtil; import com.lunhan.water.common.util.SerializeUtil; import com.lunhan.water.common.util.StringUtil; import com.lunhan.water.common.wechat.WechatPayV3Util; import com.lunhan.water.entity.dto.pay.WeiXinPayNotifyDto; import com.lunhan.water.entity.dto.pay.WeiXinRefundNotifyDto; import com.lunhan.water.entity.enums.EPaymentChannel; import com.lunhan.water.entity.response.pay.ResWeiXinPayNotify; import com.lunhan.water.host.api.NonLogin; import com.lunhan.water.service.PaymentServices; import com.lunhan.water.service.ThirdNotifyService; import org.slf4j.Logger; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import javax.annotation.Resource; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.math.BigDecimal; import java.nio.charset.StandardCharsets; import java.time.LocalDateTime; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Objects; /** * 10010.异步通知相关接口 * * @order 10010 */ @NonLogin @RestController @RequestMapping("notify") public class NotifyController { private Logger logger = LoggerUtil.get(ELogger.PAY_SERVICE); @Autowired private PaymentServices paymentService; @Autowired private ThirdNotifyService thirdNotifyService; /** * 微信支付结果通知 * * @param reqBody 请求参数列表 */ @PostMapping(value = "pay/{channel}/{payWay}/{businessType}") public Object pay(@RequestBody Map reqBody, @PathVariable Integer channel, @PathVariable Integer payWay, @PathVariable Integer businessType) { String action = "pay"; if (Objects.isNull(reqBody) || reqBody.isEmpty()) { logger.error(action + ", body cant't be null."); return "body cant't be null."; } String body = SerializeUtil.toJson(reqBody); logger.info(action + "-notify, channel: " + channel + "; payWay: " + payWay + "; body: " + body); EPaymentChannel findChannel = EPaymentChannel.getByValue(channel); if (Objects.isNull(findChannel)) { return "支付渠道不支持." + channel; } Object result; switch (findChannel) { case WE_CHAT: result = this.doWeiXinPayNotify(reqBody, body, businessType); break; case ALI_PAY: // TODO result = "支付宝支付待实现"; break; default: result = "支付渠道不支持." + channel; break; } return result; } private ResWeiXinPayNotify doWeiXinPayNotify(Map reqBody, String body, Integer businessType) { String action = "doWeiXinPayNotify"; ResWeiXinPayNotify result = new ResWeiXinPayNotify("FAILED", "unknown error."); ExecutedResult saveWinXinNotify = thirdNotifyService.saveWinXinPayNotify(EPaymentChannel.WE_CHAT, "PAY", body, businessType); if (saveWinXinNotify.isFailed()) { if (Objects.equals(saveWinXinNotify.getMsgCode(), ConstantFactory.MCODE_TOKENERROR)) { result.setCode("SUCCESS"); result.setMessage("SUCCESS"); } else { result.setMessage(saveWinXinNotify.getMsg()); } return result; } Long notifyId = saveWinXinNotify.getData(); // 处理微信支付结果通知 try { if (reqBody.containsKey("event_type") && "TRANSACTION.SUCCESS".equals(reqBody.get("event_type").toString())) { this.doWeiXinPayResult(SerializeUtil.toJson(reqBody.get("resource")), notifyId); } } catch (Exception e) { logger.error(action, e); result.setMessage(e.getMessage()); return result; } result.setCode("SUCCESS"); result.setMessage("SUCCESS"); return result; } /** * 更新微信支付结果 * * @param body 通知报文 * @param notifyId 通知报文记录id */ private void doWeiXinPayResult(String body, Long notifyId) throws Exception { String actionStr = "weiXinPay, "; Map data = new Gson().fromJson(body, Map.class); if (data.containsKey("algorithm") && "AEAD_AES_256_GCM".equals(data.get("algorithm").toString())) { String signParams = data.get("ciphertext").toString(); String paramsStr = ""; byte[] bufferKey = SysConfig.weiXinPay.getMerchantKey().getBytes(StandardCharsets.UTF_8); byte[] bufferAssociatedData = data.get("associated_data").toString().getBytes(StandardCharsets.UTF_8); byte[] bufferNonce = data.get("nonce").toString().getBytes(StandardCharsets.UTF_8); paramsStr = WechatPayV3Util.decryptToString(bufferAssociatedData, bufferNonce, signParams, bufferKey); logger.info(actionStr + "支付结果解密: " + paramsStr); WeiXinPayNotifyDto payInfo = SerializeUtil.toObject(paramsStr, WeiXinPayNotifyDto.class); // 更新通知报文业务编号 thirdNotifyService.updateBusinessCode(notifyId, payInfo.getOut_trade_no()); if ("SUCCESS".equalsIgnoreCase(payInfo.getTrade_state())) { LocalDateTime payTime = null; if (StringUtil.isNotNullOrEmpty(payInfo.getSuccess_time())) { payTime = LocalDateTimeUtil.getDateTime(payInfo.getSuccess_time(), "yyyy-MM-d'T'HH:mm:ssXXX"); } Integer paidAmount = payInfo.getAmount().getPayer_total(); ExecutedResult doPaidSuccess = paymentService.doPaidSuccess( payInfo.getOut_trade_no(), payInfo.getTransaction_id(), new BigDecimal(paidAmount.toString()).divide(new BigDecimal(ConstantFactory.NUM100.toString())), // 微信支付金额乘了100 payTime ); logger.info(actionStr + "支付成功更新支付结果: " + SerializeUtil.toJson(doPaidSuccess)); } else if ("PAYERROR".equalsIgnoreCase(payInfo.getTrade_state())) { ExecutedResult doPaidFailed = paymentService.doPaidFailed( payInfo.getOut_trade_no(), payInfo.getTransaction_id(), payInfo.getTrade_state_desc() ); logger.info(actionStr + "支付失败,更新支付结果: " + SerializeUtil.toJson(doPaidFailed)); } } else { logger.error(actionStr + "报文加密方式未能识别!" + body); } } /** * 微信退款结果通知 * * @param reqBody 请求参数列表 */ @PostMapping(value = "refund/{channel}/{payWay}/{businessType}") public Object refund(@RequestBody Map reqBody, @PathVariable Integer channel, @PathVariable Integer payWay, @PathVariable Integer businessType) { String action = "refund"; if (Objects.isNull(reqBody)) { logger.error(action + ", body cant't be null."); return "请求正文不能为空."; } String body = SerializeUtil.toJson(reqBody); logger.info(action + "-notify, channel: " + channel + "; payWay: " + payWay + "; body: " + body); EPaymentChannel findChannel = EPaymentChannel.getByValue(channel); if (Objects.isNull(findChannel)) { return "支付渠道不支持." + channel; } Object result; switch (findChannel) { case WE_CHAT: result = this.doWeiXinRefundNotify(reqBody, body, businessType); break; case ALI_PAY: // TODO result = "支付宝支付待实现"; break; default: result = "支付渠道不支持"; break; } return result; } private Object doWeiXinRefundNotify(Map reqBody, String body, Integer businessType) { String action = "doWeiXinRefundNotify"; ResWeiXinPayNotify result = new ResWeiXinPayNotify("FAILED", "unknown error."); ExecutedResult saveWinXinNotify = thirdNotifyService.saveWinXinPayNotify(EPaymentChannel.WE_CHAT, "REFUND", body, businessType); if (saveWinXinNotify.isFailed()) { if (Objects.equals(saveWinXinNotify.getMsgCode(), ConstantFactory.MCODE_TOKENERROR)) { result.setCode("SUCCESS"); result.setMessage("SUCCESS"); } else { result.setMessage(saveWinXinNotify.getMsg()); } return result; } Long notifyId = saveWinXinNotify.getData(); // 处理微信支付结果通知 try { if (reqBody.containsKey("event_type") && "REFUND.SUCCESS".equals(reqBody.get("event_type").toString())) { this.doWeiXinRefundResult(SerializeUtil.toJson(reqBody.get("resource")), notifyId); } } catch (Exception e) { logger.error(action, e); result.setMessage(e.getMessage()); return result; } result.setCode("SUCCESS"); result.setMessage("SUCCESS"); return result; } /** * 更新微信退款结果 * * @param body 通知报文 * @param notifyId 通知报文记录id */ private void doWeiXinRefundResult(String body, Long notifyId) throws Exception { String actionStr = "weiXinRefund, "; Map data = new Gson().fromJson(body, Map.class); if (data.containsKey("algorithm") && "AEAD_AES_256_GCM".equals(data.get("algorithm").toString())) { String signParams = data.get("ciphertext").toString(); String paramsStr = ""; byte[] bufferKey = SysConfig.weiXinPay.getMerchantKey().getBytes(StandardCharsets.UTF_8); byte[] bufferAssociatedData = data.get("associated_data").toString().getBytes(StandardCharsets.UTF_8); byte[] bufferNonce = data.get("nonce").toString().getBytes(StandardCharsets.UTF_8); paramsStr = WechatPayV3Util.decryptToString(bufferAssociatedData, bufferNonce, signParams, bufferKey); logger.info(actionStr + "退款结果解密: " + paramsStr); WeiXinRefundNotifyDto refundInfo = SerializeUtil.toObject(paramsStr, WeiXinRefundNotifyDto.class); // 更新通知报文业务编号 thirdNotifyService.updateBusinessCode(notifyId, refundInfo.getOut_trade_no()); if ("SUCCESS".equalsIgnoreCase(refundInfo.getRefund_status())) { LocalDateTime refundTime = null; if (StringUtil.isNotNullOrEmpty(refundInfo.getSuccess_time())) { refundTime = LocalDateTimeUtil.getDateTime(refundInfo.getSuccess_time(), "yyyy-MM-d'T'HH:mm:ssXXX"); } Integer paidAmount = refundInfo.getAmount().getPayer_refund(); ExecutedResult doRefundSuccess = paymentService.doRefundSuccess( refundInfo.getOut_trade_no(), refundInfo.getTransaction_id(), new BigDecimal(paidAmount.toString()).divide(new BigDecimal(ConstantFactory.NUM100.toString())), // 微信支付金额乘了100 refundTime ); logger.info(actionStr + "退款成功更新支付结果: " + SerializeUtil.toJson(doRefundSuccess)); } else { String errorMsg = "refund_status: ABNORMAL. 退款异常,退款到银行发现用户的卡作废或者冻结了,导致原路退款银行卡失败,可前往【商户平台—>交易中心】,手动处理此笔退款"; if ("CLOSED".equalsIgnoreCase(refundInfo.getRefund_status())) { errorMsg = "refund_status: CLOSED. 退款关闭"; } ExecutedResult doRefundFailed = paymentService.doRefundFailed( refundInfo.getOut_trade_no(), refundInfo.getTransaction_id(), errorMsg ); logger.info(actionStr + "退款失败,更新退款结果: " + SerializeUtil.toJson(doRefundFailed)); } } else { logger.error(actionStr + "报文加密方式未能识别!" + body); } } }