| | |
| | | package com.ruoyi.order.controller.miniapp; |
| | | |
| | | import com.alibaba.fastjson.JSONObject; |
| | | import com.github.binarywang.wxpay.bean.ecommerce.*; |
| | | import com.github.binarywang.wxpay.bean.notify.WxPayNotifyV3Response; |
| | | import com.github.binarywang.wxpay.bean.profitsharingV3.ProfitSharingNotifyData; |
| | | import com.github.binarywang.wxpay.bean.profitsharingV3.ProfitSharingNotifyResult; |
| | | import com.github.binarywang.wxpay.exception.WxPayException; |
| | | import com.github.binarywang.wxpay.service.ProfitSharingV3Service; |
| | | import com.github.binarywang.wxpay.service.WxPayService; |
| | | import com.github.binarywang.wxpay.v3.auth.Verifier; |
| | | import com.github.binarywang.wxpay.v3.util.AesUtils; |
| | | import com.google.gson.Gson; |
| | | import com.google.gson.GsonBuilder; |
| | | import com.ruoyi.common.core.domain.R; |
| | | import com.ruoyi.common.core.web.controller.BaseController; |
| | | import com.ruoyi.order.domain.dto.AppSureOrderDto; |
| | | import com.ruoyi.order.domain.dto.WXPayNotifyDto; |
| | | import com.ruoyi.order.domain.vo.AppSureOrderVo; |
| | | import com.ruoyi.common.redis.service.RedisService; |
| | | import com.ruoyi.order.domain.dto.WechatPaymentRefundDto; |
| | | import com.ruoyi.order.domain.dto.WeixinPaymentNotifyDto; |
| | | import com.ruoyi.order.domain.pojo.account.OrderRefund; |
| | | import com.ruoyi.order.domain.vo.ProfitSharingNotifyNewResult; |
| | | import com.ruoyi.order.domain.vo.WeixinPaymentNotifyVo; |
| | | import com.ruoyi.order.domain.vo.WeixinPaymentRefundVo; |
| | | import com.ruoyi.order.enums.RefundStatusEnum; |
| | | import com.ruoyi.order.enums.WxPayNotifyEventTypeEnum; |
| | | import com.ruoyi.order.enums.dict.IDict; |
| | | import com.ruoyi.order.service.account.BackMessageService; |
| | | import com.ruoyi.order.service.account.OrderRefundService; |
| | | import com.ruoyi.order.service.order.OrderService; |
| | | import com.wechat.pay.contrib.apache.httpclient.notification.Notification; |
| | | import com.wechat.pay.contrib.apache.httpclient.notification.NotificationHandler; |
| | | import com.wechat.pay.contrib.apache.httpclient.notification.NotificationRequest; |
| | | import com.wechat.pay.java.core.Config; |
| | | import com.wechat.pay.java.core.RSAAutoCertificateConfig; |
| | | import com.wechat.pay.java.core.RSAConfig; |
| | | import com.wechat.pay.java.core.cipher.PrivacyEncryptor; |
| | | import com.wechat.pay.java.core.notification.NotificationConfig; |
| | | import com.wechat.pay.java.core.notification.NotificationParser; |
| | | import com.wechat.pay.java.core.notification.RequestParam; |
| | | import com.wechat.pay.java.service.partnerpayments.jsapi.model.Transaction; |
| | | import com.ruoyi.order.util.HuiFuTianXiaUtil; |
| | | import io.swagger.annotations.Api; |
| | | import io.swagger.annotations.ApiOperation; |
| | | import net.bytebuddy.asm.Advice; |
| | | import org.springframework.web.bind.annotation.RequestBody; |
| | | import org.springframework.web.bind.annotation.RequestMapping; |
| | | import org.springframework.web.bind.annotation.RequestMethod; |
| | | import org.springframework.web.bind.annotation.RestController; |
| | | import lombok.extern.log4j.Log4j2; |
| | | 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.io.BufferedReader; |
| | | import java.io.IOException; |
| | | import java.io.PrintWriter; |
| | | import java.math.BigDecimal; |
| | | import java.nio.charset.StandardCharsets; |
| | | import java.util.HashMap; |
| | | import java.security.GeneralSecurityException; |
| | | import java.util.Collection; |
| | | import java.util.Enumeration; |
| | | import java.util.Map; |
| | | import java.util.Objects; |
| | | import java.util.concurrent.TimeUnit; |
| | | |
| | | /** |
| | | * @program: ruoyi |
| | |
| | | @Api(value = "微信通知控制", tags = "微信通知控制", description = "微信通知控制") |
| | | @RestController |
| | | @RequestMapping("/app/notify") |
| | | @Log4j2 |
| | | public class NotifyController extends BaseController { |
| | | /** 商户号 */ |
| | | public static String merchantId = ""; |
| | | /** 商户API私钥路径 */ |
| | | public static String privateKeyPath = ""; |
| | | /** 商户证书序列号 */ |
| | | public static String merchantSerialNumber = ""; |
| | | /** 微信支付平台证书路径 */ |
| | | public static String wechatPayCertificatePath = ""; |
| | | /** 商户APIV3密钥 */ |
| | | public static String apiV3key = "..."; |
| | | |
| | | public static final String WECHAT_PAY_SERIAL = "Wechatpay-Serial"; |
| | | public static final String WECHAT_PAY_SIGNATURE = "Wechatpay-Signature"; |
| | | public static final String WECHAT_PAY_TIMESTAMP = "Wechatpay-Timestamp"; |
| | | public static final String WECHAT_PAY_NONCE = "Wechatpay-Nonce"; |
| | | |
| | | private static final Gson GSON = new GsonBuilder().create(); |
| | | |
| | | @Resource |
| | | private OrderService orderService; |
| | | |
| | | @RequestMapping(value = "/payNotify", method = RequestMethod.POST) |
| | | @ApiOperation(value = "微信支付通知") |
| | | public Map payNotify(HttpServletRequest request, HttpServletResponse response) { |
| | | Config config = |
| | | new RSAConfig.Builder() |
| | | .merchantId(merchantId) |
| | | .privateKeyFromPath(privateKeyPath) |
| | | .merchantSerialNumber(merchantSerialNumber) |
| | | .wechatPayCertificatesFromPath(wechatPayCertificatePath) |
| | | .build(); |
| | | @Resource |
| | | private WxPayService wxService; |
| | | @Resource |
| | | private BackMessageService backMessageService; |
| | | |
| | | PrivacyEncryptor encryptor = config.createEncryptor(); |
| | | String wechatPayCertificateSerialNumber = encryptor.getWechatpaySerial(); |
| | | @Resource |
| | | private OrderRefundService orderRefundService; |
| | | |
| | | @Resource |
| | | private RedisService redisService; |
| | | |
| | | String nonoc=request.getHeader("Wechatpay-Nonce"); |
| | | String signature=request.getHeader("Wechatpay-Signature"); |
| | | String timestamp=request.getHeader("Wechatpay-Timestamp"); |
| | | String serial=request.getHeader("Wechatpay-Serial"); |
| | | String signatureType=request.getHeader("Wechatpay-Signature-Type"); |
| | | @PostMapping(value = "/payNotify") |
| | | @ApiOperation(value = "微信支付/退款通知") |
| | | public String payNotify(@RequestBody String notifyData, HttpServletRequest request, HttpServletResponse response) throws WxPayException { |
| | | |
| | | String requestBody=""; |
| | | // 获取请求头 |
| | | SignatureHeader signatureHeader = getSignatureHeader(request); |
| | | |
| | | log.info("微信支付/退款通知: {}", notifyData); |
| | | |
| | | NotifyResponse responseData = GSON.fromJson(notifyData, NotifyResponse.class); |
| | | String eventType = responseData.getEventType(); |
| | | |
| | | int resultType = 1; |
| | | String resultMessage = notifyData; |
| | | |
| | | try { |
| | | BufferedReader reader = request.getReader(); |
| | | String line = ""; |
| | | StringBuffer inputString = new StringBuffer(); |
| | | while ( (line = reader.readLine()) != null ) { |
| | | inputString.append(line); |
| | | if (WxPayNotifyEventTypeEnum.TRANSACTION_SUCCESS.getCode().equals(eventType)) { |
| | | PartnerTransactionsNotifyResult notifyResult = wxService.getEcommerceService().parsePartnerNotifyResult(notifyData, signatureHeader); |
| | | PartnerTransactionsResult result = notifyResult.getResult(); |
| | | // 支付通知回调 |
| | | if ("SUCCESS".equals(result.getTradeState())) { |
| | | orderService.payBack(result, BigDecimal.ONE); |
| | | } |
| | | resultMessage = GSON.toJson(notifyResult); |
| | | } else if (WxPayNotifyEventTypeEnum.REFUND_SUCCESS.getCode().equals(eventType) |
| | | || WxPayNotifyEventTypeEnum.REFUND_ABNORMAL.getCode().equals(eventType) |
| | | || WxPayNotifyEventTypeEnum.REFUND_CLOSED.getCode().equals(eventType)) { |
| | | |
| | | RefundNotifyResult result = this.parseRefundNotifyResult(notifyData, signatureHeader); |
| | | orderService.orderRefundBack(result); |
| | | resultType = 2; |
| | | resultMessage = GSON.toJson(result); |
| | | } |
| | | if(inputString!=null && !"".equals(inputString)) { |
| | | requestBody = inputString.toString(); |
| | | } |
| | | reader.close(); |
| | | } catch (IOException e) { |
| | | |
| | | }catch (Exception e){ |
| | | e.printStackTrace(); |
| | | log.info("微信支付/退款通知异常: {}", e.getMessage()); |
| | | } |
| | | // 保存支付/退款回调信息 |
| | | backMessageService.saveBackMessage(resultType, resultMessage); |
| | | return WxPayNotifyV3Response.success("成功"); |
| | | } |
| | | |
| | | RequestParam requestParam = new RequestParam.Builder() |
| | | .serialNumber(wechatPayCertificateSerialNumber) |
| | | .nonce(nonoc) |
| | | .signature(signature) |
| | | .timestamp(timestamp) |
| | | .body(requestBody) |
| | | .build(); |
| | | @PostMapping(value = "/profitSharingNotify") |
| | | @ApiOperation(value = "微信分账通知") |
| | | public String profitSharingNotify(@RequestBody String notifyData, HttpServletRequest request, HttpServletResponse response) throws WxPayException { |
| | | ProfitSharingV3Service sharingV3Service = wxService.getProfitSharingV3Service(); |
| | | // ProfitSharingNotifyResult |
| | | log.info("微信分账通知: {}", notifyData); |
| | | |
| | | NotificationConfig notifiConfig = new RSAAutoCertificateConfig.Builder() |
| | | .merchantId(merchantId) |
| | | .privateKeyFromPath(privateKeyPath) |
| | | .merchantSerialNumber(merchantSerialNumber) |
| | | .apiV3Key(apiV3key) |
| | | .build(); |
| | | // 获取请求头 |
| | | SignatureHeader signatureHeader = getSignatureHeader(request); |
| | | |
| | | NotificationParser parser = new NotificationParser(notifiConfig); |
| | | ProfitSharingNotifyNewResult notifyResult = getProfitSharingNotifyData(notifyData, signatureHeader); |
| | | ProfitSharingNotifyResult result = notifyResult.getResult(); |
| | | String eventType = "-1"; |
| | | String resultMessage = notifyData; |
| | | try { |
| | | eventType = notifyResult.getRawData().getEventType(); |
| | | if (WxPayNotifyEventTypeEnum.PROFIT_SHARING_SUCCESS.getCode().equals(eventType)) { |
| | | // 分账通知回调 |
| | | orderService.profitSharingBack(result); |
| | | } |
| | | |
| | | Transaction transaction = parser.parse(requestParam, Transaction.class); |
| | | |
| | | |
| | | String tradeStateEnum=transaction.getTradeState().toString(); |
| | | if(tradeStateEnum.equals("success")){ |
| | | orderService.payBack(transaction); |
| | | resultMessage = GSON.toJson(notifyResult); |
| | | }catch (Exception e){ |
| | | log.info("微信分账通知通知异常: {}", e.getMessage()); |
| | | } |
| | | // 保存记录分账回调信息 |
| | | backMessageService.saveBackMessage(3, resultMessage); |
| | | |
| | | Map map=new HashMap(); |
| | | map.put("code","SUCCESS"); |
| | | map.put("message","成功"); |
| | | |
| | | return map; |
| | | return WxPayNotifyV3Response.success("成功"); |
| | | } |
| | | |
| | | @RequestMapping(value = "/shareNotify", method = RequestMethod.POST) |
| | |
| | | return null; |
| | | } |
| | | |
| | | public ProfitSharingNotifyNewResult getProfitSharingNotifyData(String notifyData, SignatureHeader header) throws WxPayException { |
| | | if (Objects.nonNull(header) && !this.verifyNotifySign(header, notifyData)) { |
| | | throw new WxPayException("非法请求,头部信息验证失败"); |
| | | } |
| | | ProfitSharingNotifyData response = GSON.fromJson(notifyData, ProfitSharingNotifyData.class); |
| | | |
| | | ProfitSharingNotifyData.Resource resource = response.getResource(); |
| | | String cipherText = resource.getCipherText(); |
| | | String associatedData = resource.getAssociatedData(); |
| | | String nonce = resource.getNonce(); |
| | | String apiV3Key = this.wxService.getConfig().getApiV3Key(); |
| | | try { |
| | | String result = AesUtils.decryptToString(associatedData, nonce, cipherText, apiV3Key); |
| | | ProfitSharingNotifyResult profitSharingResult = GSON.fromJson(result, ProfitSharingNotifyResult.class); |
| | | |
| | | ProfitSharingNotifyNewResult notifyResult = new ProfitSharingNotifyNewResult(); |
| | | notifyResult.setRawData(response); |
| | | notifyResult.setResult(profitSharingResult); |
| | | return notifyResult; |
| | | } catch (GeneralSecurityException | IOException e) { |
| | | throw new WxPayException("解析报文异常!", e); |
| | | } |
| | | } |
| | | |
| | | private RefundNotifyResult parseRefundNotifyResult(String notifyData, SignatureHeader header) throws WxPayException { |
| | | |
| | | NotifyResponse response = GSON.fromJson(notifyData, NotifyResponse.class); |
| | | NotifyResponse.Resource resource = response.getResource(); |
| | | String cipherText = resource.getCiphertext(); |
| | | String associatedData = resource.getAssociatedData(); |
| | | String nonce = resource.getNonce(); |
| | | String apiV3Key = this.wxService.getConfig().getApiV3Key(); |
| | | try { |
| | | String result = AesUtils.decryptToString(associatedData, nonce, cipherText, apiV3Key); |
| | | RefundNotifyResult notifyResult = GSON.fromJson(result, RefundNotifyResult.class); |
| | | notifyResult.setRawData(response); |
| | | return notifyResult; |
| | | } catch (GeneralSecurityException | IOException e) { |
| | | throw new WxPayException("解析报文异常!", e); |
| | | } |
| | | } |
| | | |
| | | private boolean verifyNotifySign(SignatureHeader header, String data) throws WxPayException { |
| | | String beforeSign = String.format("%s\n%s\n%s\n", |
| | | header.getTimeStamp(), |
| | | header.getNonce(), |
| | | data); |
| | | Verifier verifier = this.wxService.getConfig().getVerifier(); |
| | | if (verifier == null) { |
| | | throw new WxPayException("证书检验对象为空"); |
| | | } |
| | | return verifier.verify(header.getSerialNo(), |
| | | beforeSign.getBytes(StandardCharsets.UTF_8), header.getSigned()); |
| | | } |
| | | |
| | | private SignatureHeader getSignatureHeader(HttpServletRequest request){ |
| | | SignatureHeader signatureHeader = new SignatureHeader(); |
| | | signatureHeader.setSerialNo(request.getHeader(WECHAT_PAY_SERIAL)); |
| | | signatureHeader.setSigned(request.getHeader(WECHAT_PAY_SIGNATURE)); |
| | | signatureHeader.setNonce(request.getHeader(WECHAT_PAY_NONCE)); |
| | | signatureHeader.setTimeStamp(request.getHeader(WECHAT_PAY_TIMESTAMP)); |
| | | |
| | | log.info("timeStamp:{} nonce : {} signed:{} serialNo:{}", |
| | | signatureHeader.getTimeStamp(), |
| | | signatureHeader.getNonce(), |
| | | signatureHeader.getSigned(), |
| | | signatureHeader.getSerialNo()); |
| | | |
| | | return signatureHeader; |
| | | } |
| | | |
| | | |
| | | |
| | | @RequestMapping(value = "/wechatPaymentCallback", method = RequestMethod.POST) |
| | | @ApiOperation(value = "微信支付通知(汇付天下)") |
| | | public void wechatPaymentCallback(WeixinPaymentNotifyDto dto, HttpServletResponse response){ |
| | | R<WeixinPaymentNotifyVo> r = HuiFuTianXiaUtil.weixinPaymentNotify(dto); |
| | | if(r.getCode() == 200){ |
| | | WeixinPaymentNotifyVo data = r.getData(); |
| | | String transStat = data.getTransStat(); |
| | | if("S".equals(transStat)){ |
| | | PartnerTransactionsResult transaction = new PartnerTransactionsResult(); |
| | | transaction.setOutTradeNo(data.getReqSeqId()); |
| | | |
| | | //防止回调频繁导致时间处理异常 |
| | | Object cacheObject = redisService.getCacheObject(data.getReqSeqId()); |
| | | if(null == cacheObject){ |
| | | redisService.setCacheObject(data.getReqSeqId(), "", 10L, TimeUnit.SECONDS); |
| | | }else{ |
| | | return; |
| | | } |
| | | //开始处理业务数据 |
| | | orderService.payBack(transaction, data.getFeeFlag() == 1 ? BigDecimal.ZERO : new BigDecimal(data.getFeeAmount())); |
| | | |
| | | response.setStatus(200); |
| | | PrintWriter out = null; |
| | | try { |
| | | out = response.getWriter(); |
| | | out.print("RECV_ORD_ID_" + data.getReqSeqId()); |
| | | out.flush(); |
| | | } catch (IOException e) { |
| | | e.printStackTrace(); |
| | | }finally { |
| | | out.close(); |
| | | } |
| | | |
| | | } |
| | | } |
| | | } |
| | | |
| | | |
| | | |
| | | @RequestMapping(value = "/wechatPaymentRefundCallback", method = RequestMethod.POST) |
| | | @ApiOperation(value = "微信支付退款通知(汇付天下)") |
| | | public void wechatPaymentRefundCallback(WechatPaymentRefundDto dto, HttpServletResponse response){ |
| | | R<WeixinPaymentRefundVo> r = HuiFuTianXiaUtil.weixinPaymentRefundNotify(dto); |
| | | if(r.getCode() == 200){ |
| | | WeixinPaymentRefundVo data = r.getData(); |
| | | String transStat = data.getTransStat(); |
| | | if("S".equals(transStat)){ |
| | | //开始处理业务数据 |
| | | String reqSeqId = data.getReqSeqId(); |
| | | OrderRefund orderRefund = orderRefundService.getById(reqSeqId); |
| | | if(null != orderRefund && orderRefund.getRefundStatus() != 2){ |
| | | orderRefund.setWxRefundId(data.getPartyOrderId()); |
| | | orderRefund.setBackTime(data.getTransDate() + data.getTransTime()); |
| | | orderRefund.setRefundStatus(2); |
| | | orderRefundService.saveOrUpdate(orderRefund); |
| | | } |
| | | |
| | | response.setStatus(200); |
| | | PrintWriter out = null; |
| | | try { |
| | | out = response.getWriter(); |
| | | out.print("RECV_ORD_ID_" + data.getReqSeqId()); |
| | | out.flush(); |
| | | } catch (IOException e) { |
| | | e.printStackTrace(); |
| | | }finally { |
| | | out.close(); |
| | | } |
| | | |
| | | } |
| | | } |
| | | } |
| | | } |