PayServiceClient.java 18.1 KB
package com.infoloop.tianting.service.client;

import cn.hutool.core.convert.Convert;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonParser.Feature;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import com.infoloop.tianting.clientcustomerorderservice.ClientCustomerOrderModification;
import com.infoloop.tianting.clientcustomerorderservice.OnlinePayEnum;
import com.infoloop.tianting.clientcustomerorderservice.OrderStatusEnum;
import com.infoloop.tianting.clientcustomerorderservice.PayStatusEnum;
import com.infoloop.tianting.clientresourcetagservice.SingleResourceLabelEntityRefsResponse;
import com.infoloop.tianting.config.BusinessConfig;
import com.infoloop.tianting.context.LoginContextHolder;
import com.infoloop.tianting.exception.ClientEndExceptions;
import com.infoloop.tianting.exception.ErrorCodeEnum;
import com.infoloop.tianting.model.dto.PayDTO.PayInformRequestDto;
import com.infoloop.tianting.model.dto.PayDTO.PayRefundRequestDto;
import com.infoloop.tianting.model.dto.PayDTO.PayResponseDto;
import com.infoloop.tianting.model.dto.PayDTO.ThirdPartyPayInformRequestDto;
import com.infoloop.tianting.model.dto.PaymentCallbackDTO;
import com.infoloop.tianting.model.dto.PaymentCallbackResult;
import com.infoloop.tianting.server.UserAuthData;
import com.infoloop.tianting.server.WebSocketServer;
import com.infoloop.tianting.server.message.MessageTypeEnum;
import com.infoloop.tianting.server.message.NoticeTypeEnum;
import com.infoloop.tianting.server.message.SocketMessage;
import com.infoloop.tianting.server.message.data.OrderCreateSuccessData;
import com.infoloop.tianting.server.message.data.OrderPaymentSuccessData;
import com.infoloop.tianting.server.session.UserSessionKey;
import com.infoloop.tianting.server.session.UserTypeEnum;
import com.infoloop.tianting.utils.DateUtil;
import com.infoloop.tianting.utils.EncryptionUtil;
import com.infoloop.tianting.utils.JsonUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.digest.DigestUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import java.io.IOException;
import java.math.BigDecimal;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.text.MessageFormat;
import java.text.ParseException;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

@Slf4j
@Service
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class PayServiceClient {

  private final RestTemplate restTemplate;

  private final BusinessConfig businessConfig;

  private final OrderServiceRpcClient orderServiceRpcClient;

  private final ClientResourceTagServiceRpcClient clientResourceTagServiceRpcClient;

  private final EnterpriseResourceServiceRpcClient enterpriseResourceServiceRpcClient;

  private final XmlMapper xmlMapper = new XmlMapper();

  private final OkHttpClient client = new OkHttpClient();

  public PayResponseDto payInform(PayInformRequestDto payRequestDto) {
    final var orderById =
        orderServiceRpcClient.getOrderById(Integer.valueOf(payRequestDto.getOrderId()));
    if (orderById == null
        || orderById.getPayStatus() == PayStatusEnum.PAY_CANCELED
        || orderById.getStatus() == OrderStatusEnum.ORDER_CANCELED) {
      log.info("Order {} not found or canceled, skipping.", payRequestDto.getOrderId());
      throw ClientEndExceptions.BusinessException.build(ErrorCodeEnum.ORDER_CANCEL);
    }
    String url = MessageFormat.format(businessConfig.getPayUrl(), businessConfig.getPayClientId());
    String requestDate = DateUtil.formatDate(LocalDateTime.now(), DateUtil.YMDHMS);
    ThirdPartyPayInformRequestDto jsonData = buildRequestData(payRequestDto, requestDate);
    String jsonDataString;
    try {
      ObjectMapper objectMapper = new ObjectMapper();
      objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
      objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
      objectMapper.setPropertyNamingStrategy(PropertyNamingStrategy.UPPER_CAMEL_CASE);
      jsonDataString = objectMapper.writeValueAsString(jsonData);
    } catch (JsonProcessingException e) {
      log.error("Failed to serialize request data", e);
      throw new IllegalArgumentException("支付信息获取失败: JSON 解析错误");
    }
    HttpEntity<String> requestEntity = new HttpEntity<>(jsonDataString, createHeaders());
    log.info("payInform request: {}", jsonDataString);
    ResponseEntity<String> responseEntity =
        restTemplate.exchange(url, HttpMethod.POST, requestEntity, String.class);
    return processResponse(responseEntity);
  }

  private ThirdPartyPayInformRequestDto buildRequestData(
      PayInformRequestDto payRequestDto, String requestDate) {
    ThirdPartyPayInformRequestDto jsonData = new ThirdPartyPayInformRequestDto();
    jsonData.setClientId(businessConfig.getPayClientId());
    jsonData.setTradeId(payRequestDto.getOrderId());
    jsonData.setRequestDate(requestDate);
    jsonData.setRequestSource("OFWXApplet");
    jsonData.setPayerOpenId(LoginContextHolder.getOpenId());
    jsonData.setHospitalCode(payRequestDto.getHospitalCode());
    jsonData.setPID(LoginContextHolder.getOpenId());
    jsonData.setRemark("");
    jsonData.setTransAmount(payRequestDto.getPrice());
    jsonData.setTradeDesc("");
    jsonData.setSign(
        EncryptionUtil.sha1Encryption(
            businessConfig.getPayClientId()
                + payRequestDto.getOrderId()
                + requestDate
                + businessConfig.getPayClientId()));
    return jsonData;
  }

  private HttpHeaders createHeaders() {
    HttpHeaders headers = new HttpHeaders();
    headers.setContentType(MediaType.APPLICATION_JSON);
    return headers;
  }

  private PayResponseDto processResponse(ResponseEntity<String> responseEntity) {
    if (!responseEntity.getStatusCode().is2xxSuccessful() || responseEntity.getBody() == null) {
      log.error("payInform failed with response: {}", responseEntity.getBody());
      throw new IllegalArgumentException("支付信息获取失败");
    }
    try {
      log.info("processResponse response: {}", responseEntity.getBody());
      return xmlMapper.readValue(responseEntity.getBody(), PayResponseDto.class);
    } catch (Exception e) {
      log.error("Failed to parse XML response", e);
      throw new RuntimeException("支付信息结果解析失败", e);
    }
  }

  public boolean callback(PaymentCallbackDTO paymentCallbackDTO) {
    log.info("callback request received: {}", paymentCallbackDTO);
    try {
      final var paymentCallbackResult =
          xmlMapper.readValue(paymentCallbackDTO.getPayResult(), PaymentCallbackResult.class);
      log.info("Parsed paymentCallbackResult: {}", paymentCallbackResult);
      final var orderId = Integer.parseInt(paymentCallbackResult.getTradeId());
      final var orderById = orderServiceRpcClient.getOrderById(orderId);
      if (orderById == null) {
        log.error("Order not found, orderId: {}", orderId);
        return false;
      }

      final var transType = paymentCallbackResult.getTransType();
      if (transType.equals("00")) {
        if (orderById.getPayStatus() != PayStatusEnum.TO_PAY
            && orderById.getPayStatus() != PayStatusEnum.IN_PROGRESS) {
          log.error("Order status is not TO_PAY or IN_PROGRESS, orderId: {}", orderId);
          return true;
        }
        long payTime;
        try {
          payTime =
              DateUtil.parseDate(paymentCallbackResult.getTransDate(), "yyyyMMddHHmmss").getTime();
        } catch (ParseException e) {
          log.error("Failed to parse TransDate: {}", paymentCallbackResult.getTransDate(), e);
          throw new IllegalArgumentException("支付时间格式错误: " + paymentCallbackResult.getTransDate(), e);
        }
        BigDecimal realAmount;
        try {
          realAmount = Convert.toBigDecimal(paymentCallbackResult.getRealAmount());
          if (realAmount == null) {
            throw new NumberFormatException("RealAmount is null or empty");
          }
        } catch (NumberFormatException e) {
          log.error("Invalid RealAmount value: {}", paymentCallbackResult.getRealAmount(), e);
          throw new IllegalArgumentException("支付金额错误: " + paymentCallbackResult.getRealAmount(), e);
        }
        final var build =
            ClientCustomerOrderModification.newBuilder()
                .setId(orderId)
                .setShouldUpdatePayTime(true)
                .setPayTime(payTime)
                .setShouldUpdatePayPrice(true)
                .setPayPrice(realAmount.multiply(new BigDecimal("100")).intValue())
                .setShouldUpdatePayStatus(true)
                .setPayStatus(PayStatusEnum.SUCCEED)
                .setShouldUpdateStatus(true)
                .setStatus(OrderStatusEnum.PREPARING)
                .setShouldUpdateTransactionId(true)
                .setTransactionId(paymentCallbackResult.getTransSN())
                .build();
        final var updateResponse =
            orderServiceRpcClient.updateClientCustomerOrderPaymentResult(orderById, build);
        log.info("callback updateClientCustomerOrder result: {}", updateResponse.getIsUpdated());
        if (updateResponse.getIsUpdated()) {
          final var orderPaymentSuccessData =
              OrderPaymentSuccessData.builder()
                  .orderId(orderById.getId())
                  .orderCode(orderById.getOrderCode())
                  .createTime(DateUtil.formatDate(orderById.getCreatedAt(), DateUtil.Y_M_D_H_M_S))
                  .payTime(DateUtil.formatDate(orderById.getPayTime(), DateUtil.Y_M_D_H_M_S))
                  .build();
          final var data =
              SocketMessage.<OrderPaymentSuccessData>builder()
                  .type(MessageTypeEnum.ORDER)
                  .noticeType(NoticeTypeEnum.ORDER_PAYMENT_SUCCESS)
                  .subject(NoticeTypeEnum.ORDER_PAYMENT_SUCCESS.getDescription())
                  .message("您的订单:" + orderById.getOrderCode() + " 支付成功啦,快去查看吧~")
                  .data(orderPaymentSuccessData)
                  .build();
          WebSocketServer.sendMessageToUser(
              UserSessionKey.builder()
                  .userId(orderById.getOpenId())
                  .userType(UserTypeEnum.MICRO)
                  .build(),
              data);
          if (LocalDate.now().equals(DateUtil.toLocalDate(orderById.getMealTime()))) {
            final var orderCreateSuccessData =
                OrderCreateSuccessData.builder().orderId(orderById.getId()).build();
            final var createOrderData =
                SocketMessage.<OrderCreateSuccessData>builder()
                    .type(MessageTypeEnum.ORDER)
                    .noticeType(NoticeTypeEnum.ORDER_CREATED)
                    .subject(NoticeTypeEnum.ORDER_CREATED.getDescription())
                    .message("有新的订单下单成功啦,快去查看吧~")
                    .data(orderCreateSuccessData)
                    .build();
            final var clientOperatorIds =
                clientResourceTagServiceRpcClient
                    .getOperatorIdsByStallIds(List.of(orderById.getStallId())).stream()
                    .map(SingleResourceLabelEntityRefsResponse::getEntityId)
                    .distinct()
                    .collect(Collectors.toList());
            final var enterpriseOperatorIds =
                enterpriseResourceServiceRpcClient.getOperatorIdsByStallId(
                    orderById.getEnterpriseId(), orderById.getStallId());
            final var userAuthData = new ArrayList<UserAuthData>();
            userAuthData.addAll(
                clientOperatorIds.stream()
                    .map(
                        e ->
                            UserAuthData.builder()
                                .operatorId(e)
                                .userType(UserTypeEnum.CLIENT)
                                .build())
                    .collect(Collectors.toList()));
            userAuthData.addAll(
                enterpriseOperatorIds.stream()
                    .map(e -> UserAuthData.builder().operatorId(e).userType(UserTypeEnum.KDS).build())
                    .collect(Collectors.toList()));
            log.info("received userAuthData:{}", JsonUtil.writeAsJson(userAuthData));
            try {
              WebSocketServer.sendMessageByUserTypes(
                  List.of(UserTypeEnum.CLIENT, UserTypeEnum.KDS), userAuthData, createOrderData);
            } catch (IOException e) {
              log.error("Failed to send message to user", e);
            }
          }
        }
        return updateResponse.getIsUpdated();
      } else {
        final var build =
            ClientCustomerOrderModification.newBuilder()
                .setId(orderId)
                .setShouldUpdatePayStatus(true)
                .setPayStatus(PayStatusEnum.REFUND)
                .build();
        final var updateResponse =
            orderServiceRpcClient.updateClientCustomerOrderPaymentResult(orderById, build);
        log.info("callback updateClientCustomerOrder result: {}", updateResponse.getIsUpdated());
        return updateResponse.getIsUpdated();
      }
    } catch (JsonProcessingException e) {
      log.error("Failed to parse XML response, raw XML: {}", paymentCallbackDTO.getPayResult(), e);
      throw new RuntimeException("支付/退款通知结果解析失败", e);
    } catch (Exception e) {
      log.error("Unexpected error in callback, request: {}", paymentCallbackDTO, e);
      throw new RuntimeException("支付/退款通知处理失败", e);
    }
  }

  public boolean waiting(Integer orderId) {
    final var orderById = orderServiceRpcClient.getOrderById(orderId);
    if (orderById == null) {
      log.error("waiting Order not found, orderId: {}", orderId);
      return false;
    }
    if (orderById.getPayStatus() != PayStatusEnum.TO_PAY) {
      log.info("waiting Order {} already processed, skipping.", orderId);
      return false;
    }
    final var build =
        ClientCustomerOrderModification.newBuilder()
            .setId(orderId)
            .setShouldUpdatePayStatus(true)
            .setPayStatus(PayStatusEnum.IN_PROGRESS)
            .build();
    final var updateResponse =
        orderServiceRpcClient.updateClientCustomerOrderPaymentResult(orderById, build);
    log.info("waiting updateClientCustomerOrder result: {}", updateResponse.getIsUpdated());
    return updateResponse.getIsUpdated();
  }

  public String orderPayRefund(PayRefundRequestDto payRefundRequestDto) {
    Integer orderId = payRefundRequestDto.getOrderId();
    final var orderById = orderServiceRpcClient.getOrderById(orderId);
    if (orderById == null) {
      log.error("Order not found, orderId: {}", orderId);
      throw ClientEndExceptions.BusinessException.build(ErrorCodeEnum.ORDER_NOT_FOUND);
    }
    if (orderById.getOnlinePay() != OnlinePayEnum.ONLINE) {
      log.info("Order {} is not online pay.", orderId);
      throw ClientEndExceptions.BusinessException.build(ErrorCodeEnum.ORDER_NOT_ONLINE_PAY);
    }
    if (orderById.getPayStatus() != PayStatusEnum.SUCCEED) {
      log.info("Order {} pay is not succeed.", orderId);
      throw ClientEndExceptions.BusinessException.build(ErrorCodeEnum.ORDER_NOT_SUCCESS);
    }
    LocalDate today = LocalDate.now();
    LocalDate tomorrow = today.plusDays(1);
    LocalDateTime tomorrowZero = tomorrow.atStartOfDay();
    LocalDateTime orderPayTime = DateUtil.toLocalDateTime(orderById.getPayTime());
    if (!orderPayTime.isBefore(tomorrowZero)) {
      log.info("Order {} pay is beyond cancel time.", orderId);
      throw ClientEndExceptions.BusinessException.build(ErrorCodeEnum.ORDER_PAY_REFUND_FAILED);
    }

    String appId = businessConfig.getPayClientId();

    // 构建paycontent
    String payTime = DateUtil.formatDate(orderById.getPayTime(), DateUtil.YMDHMS);

    String payContentStr =
        orderById.getId()
            + "^"
            + "01"
            + "^"
            + ""
            + "^"
            + orderById.getOpenId()
            + "^"
            + orderById.getOrderCode()
            + "^"
            + orderById.getPayPrice()
            + "^"
            + payTime
            + "^"
            + orderById.getTransactionId()
            + "^"
            + payRefundRequestDto.getHospitalCode()
            + "^"
            + ""
            + "^"
            + LoginContextHolder.getId()
            + "^"
            + LoginContextHolder.getName();
    String payContent =
        URLEncoder.encode(
            Base64.encodeBase64String(payContentStr.getBytes()), StandardCharsets.UTF_8);

    // 构建signinfo
    String signInfoStr = appId + orderById.getId() + payTime + businessConfig.getPayClientId();
    String signInfo = DigestUtils.sha1Hex(signInfoStr.getBytes());

    // 构建请求
    try {
      String url = businessConfig.getCancelPayUrl() + "?" + "appid=" + appId + "&paycontent=" + payContent + "&signinfo=" + signInfo;
      log.info("cancel order pay url:" + url);
//      Request request =
//          new Request.Builder().url(url).get().build();
//      Response payResponse = client.newCall(request).execute();
//      if (payResponse.code() != 200) {
//        throw ClientEndExceptions.BusinessException.build(ErrorCodeEnum.ORDER_PAY_REFUND_FAILED);
//      }
//      String htmlContent = payResponse.body().string();
      return url;
    } catch (Exception e) {
      log.error(e.getMessage());
      throw ClientEndExceptions.BusinessException.build(ErrorCodeEnum.ORDER_PAY_REFUND_FAILED);
    }
  }
}