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

import cn.hutool.core.convert.Convert;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
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.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.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 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.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();

    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(payRequestDto.getPhone());
        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;
            }
            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();
        } 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();
    }
}