zhuyifan

支付回调对接

...@@ -12,7 +12,6 @@ import lombok.RequiredArgsConstructor; ...@@ -12,7 +12,6 @@ import lombok.RequiredArgsConstructor;
12 import lombok.extern.slf4j.Slf4j; 12 import lombok.extern.slf4j.Slf4j;
13 import org.springframework.beans.factory.annotation.Autowired; 13 import org.springframework.beans.factory.annotation.Autowired;
14 import org.springframework.http.HttpStatus; 14 import org.springframework.http.HttpStatus;
15 -import org.springframework.http.MediaType;
16 import org.springframework.validation.annotation.Validated; 15 import org.springframework.validation.annotation.Validated;
17 import org.springframework.web.bind.annotation.PostMapping; 16 import org.springframework.web.bind.annotation.PostMapping;
18 import org.springframework.web.bind.annotation.RequestBody; 17 import org.springframework.web.bind.annotation.RequestBody;
...@@ -41,10 +40,10 @@ public class PayController { ...@@ -41,10 +40,10 @@ public class PayController {
41 40
42 @SaIgnore 41 @SaIgnore
43 @ApiOperation(value = "支付通知") 42 @ApiOperation(value = "支付通知")
44 - @PostMapping(value = "/pay/callback", consumes = MediaType.APPLICATION_XML_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) 43 + @PostMapping(value = "/pay/callback")
45 @ResponseStatus(HttpStatus.OK) 44 @ResponseStatus(HttpStatus.OK)
46 - public void callback(@RequestBody PaymentCallbackDTO payInformRequestDto) { 45 + public void callback(@Valid @RequestBody PaymentCallbackDTO paymentCallbackDTO) {
47 - payServiceClient.callback(payInformRequestDto); 46 + payServiceClient.callback(paymentCallbackDTO);
48 } 47 }
49 48
50 } 49 }
...\ No newline at end of file ...\ No newline at end of file
......
1 package com.infoloop.tianting.model.dto; 1 package com.infoloop.tianting.model.dto;
2 2
3 -import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; 3 +import com.fasterxml.jackson.annotation.JsonProperty;
4 -import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
5 import lombok.AllArgsConstructor; 4 import lombok.AllArgsConstructor;
6 import lombok.Data; 5 import lombok.Data;
7 import lombok.NoArgsConstructor; 6 import lombok.NoArgsConstructor;
8 7
8 +import javax.validation.constraints.NotEmpty;
9 +
9 @Data 10 @Data
10 @NoArgsConstructor 11 @NoArgsConstructor
11 @AllArgsConstructor 12 @AllArgsConstructor
12 -@JacksonXmlRootElement(localName = "Request")
13 public class PaymentCallbackDTO { 13 public class PaymentCallbackDTO {
14 14
15 - @JacksonXmlProperty(localName = "TradeId") 15 + @NotEmpty(message = "AppId不能为空")
16 - private String tradeId; 16 + @JsonProperty("AppId")
17 - 17 + private String appId;
18 - @JacksonXmlProperty(localName = "TransSN")
19 - private String transSN;
20 -
21 - @JacksonXmlProperty(localName = "TransType")
22 - private String transType;
23 -
24 - @JacksonXmlProperty(localName = "TransAmount")
25 - private String transAmount;
26 -
27 - @JacksonXmlProperty(localName = "RealAmount")
28 - private String realAmount;
29 -
30 - @JacksonXmlProperty(localName = "PayMode")
31 - private String payMode;
32 -
33 - @JacksonXmlProperty(localName = "PayModeRemark")
34 - private String payModeRemark;
35 -
36 - @JacksonXmlProperty(localName = "TransDate")
37 - private String transDate;
38 -
39 - @JacksonXmlProperty(localName = "OrginalTransSN")
40 - private String orginalTransSN;
41 -
42 - @JacksonXmlProperty(localName = "Remark")
43 - private String remark;
44 -
45 - @JacksonXmlProperty(localName = "PayModeNote")
46 - private String payModeNote;
47 18
48 - @JacksonXmlProperty(localName = "PayIdentify") 19 + @NotEmpty(message = "PayResult不能为空")
49 - private String payIdentify; 20 + @JsonProperty("PayResult")
21 + private String payResult;
50 22
51 - @JacksonXmlProperty(localName = "PayIdentifyNote") 23 + @NotEmpty(message = "SignInfo不能为空")
52 - private String payIdentifyNote; 24 + @JsonProperty("SignInfo")
25 + private String signInfo;
53 } 26 }
......
1 +package com.infoloop.tianting.model.dto;
2 +
3 +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
4 +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
5 +import lombok.AllArgsConstructor;
6 +import lombok.Data;
7 +import lombok.NoArgsConstructor;
8 +
9 +@Data
10 +@NoArgsConstructor
11 +@AllArgsConstructor
12 +@JacksonXmlRootElement(localName = "Request")
13 +public class PaymentCallbackResult {
14 +
15 + @JacksonXmlProperty(localName = "TradeId")
16 + private String tradeId;
17 +
18 + @JacksonXmlProperty(localName = "TransSN")
19 + private String transSN;
20 +
21 + @JacksonXmlProperty(localName = "TransType")
22 + private String transType;
23 +
24 + @JacksonXmlProperty(localName = "TransAmount")
25 + private String transAmount;
26 +
27 + @JacksonXmlProperty(localName = "RealAmount")
28 + private String realAmount;
29 +
30 + @JacksonXmlProperty(localName = "PayMode")
31 + private String payMode;
32 +
33 + @JacksonXmlProperty(localName = "PayModeRemark")
34 + private String payModeRemark;
35 +
36 + @JacksonXmlProperty(localName = "TransDate")
37 + private String transDate;
38 +
39 + @JacksonXmlProperty(localName = "OrginalTransSN")
40 + private String orginalTransSN;
41 +
42 + @JacksonXmlProperty(localName = "Remark")
43 + private String remark;
44 +
45 + @JacksonXmlProperty(localName = "PayModeNote")
46 + private String payModeNote;
47 +
48 + @JacksonXmlProperty(localName = "PayIdentify")
49 + private String payIdentify;
50 +
51 + @JacksonXmlProperty(localName = "PayIdentifyNote")
52 + private String payIdentifyNote;
53 +}
...@@ -15,6 +15,7 @@ import com.infoloop.tianting.clientcustomerorderservice.CloseTimeType; ...@@ -15,6 +15,7 @@ import com.infoloop.tianting.clientcustomerorderservice.CloseTimeType;
15 import com.infoloop.tianting.clientcustomerorderservice.CreateClientCustomerOrderRpcRequest; 15 import com.infoloop.tianting.clientcustomerorderservice.CreateClientCustomerOrderRpcRequest;
16 import com.infoloop.tianting.clientcustomerorderservice.CreateClientCustomerOrderRpcResponse; 16 import com.infoloop.tianting.clientcustomerorderservice.CreateClientCustomerOrderRpcResponse;
17 import com.infoloop.tianting.clientcustomerorderservice.CustomerNotice; 17 import com.infoloop.tianting.clientcustomerorderservice.CustomerNotice;
18 +import com.infoloop.tianting.clientcustomerorderservice.GetClientCustomerOrderByIdRpcRequest;
18 import com.infoloop.tianting.clientcustomerorderservice.GetClientCustomerOrderDetailsByIdsRpcRequest; 19 import com.infoloop.tianting.clientcustomerorderservice.GetClientCustomerOrderDetailsByIdsRpcRequest;
19 import com.infoloop.tianting.clientcustomerorderservice.GetClientCustomerOrderDetailsByOrderIdsRpcRequest; 20 import com.infoloop.tianting.clientcustomerorderservice.GetClientCustomerOrderDetailsByOrderIdsRpcRequest;
20 import com.infoloop.tianting.clientcustomerorderservice.GetClientCustomerOrdersByCustomerIdRpcRequest; 21 import com.infoloop.tianting.clientcustomerorderservice.GetClientCustomerOrdersByCustomerIdRpcRequest;
...@@ -54,6 +55,7 @@ import org.redisson.api.RedissonClient; ...@@ -54,6 +55,7 @@ import org.redisson.api.RedissonClient;
54 import org.springframework.beans.factory.annotation.Autowired; 55 import org.springframework.beans.factory.annotation.Autowired;
55 import org.springframework.stereotype.Service; 56 import org.springframework.stereotype.Service;
56 57
58 +import javax.annotation.Nullable;
57 import java.time.LocalDate; 59 import java.time.LocalDate;
58 import java.time.ZoneOffset; 60 import java.time.ZoneOffset;
59 import java.util.ArrayList; 61 import java.util.ArrayList;
...@@ -71,6 +73,15 @@ public class OrderServiceRpcClient { ...@@ -71,6 +73,15 @@ public class OrderServiceRpcClient {
71 private final OperatorServiceRpcClient operatorServiceRpcClient; 73 private final OperatorServiceRpcClient operatorServiceRpcClient;
72 private final RedissonClient redissonClient; 74 private final RedissonClient redissonClient;
73 75
76 + @Nullable
77 + public SingleClientCustomerOrderRpcResponse getOrderById(Integer orderId) {
78 + final var clientCustomerOrderById = orderServiceRpcBlockingStub.getClientCustomerOrderById(GetClientCustomerOrderByIdRpcRequest.newBuilder().setId(orderId).build());
79 + if (!clientCustomerOrderById.hasResponse()) {
80 + return null;
81 + }
82 + return clientCustomerOrderById.getResponse();
83 + }
84 +
74 public List<SingleClientCustomerOrderRpcResponse> getOrdersByCustomerId(Integer customerId) { 85 public List<SingleClientCustomerOrderRpcResponse> getOrdersByCustomerId(Integer customerId) {
75 return orderServiceRpcBlockingStub.getClientCustomerOrdersByCustomerId( 86 return orderServiceRpcBlockingStub.getClientCustomerOrdersByCustomerId(
76 GetClientCustomerOrdersByCustomerIdRpcRequest.newBuilder().setCustomerId(customerId).build()).getResponsesList(); 87 GetClientCustomerOrdersByCustomerIdRpcRequest.newBuilder().setCustomerId(customerId).build()).getResponsesList();
...@@ -138,6 +149,16 @@ public class OrderServiceRpcClient { ...@@ -138,6 +149,16 @@ public class OrderServiceRpcClient {
138 return orderServiceRpcBlockingStub.updateClientCustomerOrder(request); 149 return orderServiceRpcBlockingStub.updateClientCustomerOrder(request);
139 } 150 }
140 151
152 + public UpdateClientCustomerOrderRpcResponse updateClientCustomerOrderPaymentResult(SingleClientCustomerOrderRpcResponse order, ClientCustomerOrderModification modification) {
153 + final var request = UpdateClientCustomerOrderRpcRequest.newBuilder()
154 + .setEnterpriseId(order.getEnterpriseId())
155 + .setUpdatedBy(order.getCreatedBy())
156 + .setUpdateSource(order.getCreationSource())
157 + .setModification(modification)
158 + .build();
159 + return orderServiceRpcBlockingStub.updateClientCustomerOrder(request);
160 + }
161 +
141 162
142 public BatchUpdateClientCustomerOrderDetailsRpcResponse batchUpdateClientCustomerOrderDetails( 163 public BatchUpdateClientCustomerOrderDetailsRpcResponse batchUpdateClientCustomerOrderDetails(
143 OrderDetailBatchUpdateDto orderDetailBatchUpdateDto) { 164 OrderDetailBatchUpdateDto orderDetailBatchUpdateDto) {
......
1 package com.infoloop.tianting.service.client; 1 package com.infoloop.tianting.service.client;
2 2
3 +import cn.hutool.core.convert.Convert;
3 import com.fasterxml.jackson.annotation.JsonInclude; 4 import com.fasterxml.jackson.annotation.JsonInclude;
4 import com.fasterxml.jackson.core.JsonProcessingException; 5 import com.fasterxml.jackson.core.JsonProcessingException;
5 import com.fasterxml.jackson.databind.DeserializationFeature; 6 import com.fasterxml.jackson.databind.DeserializationFeature;
6 import com.fasterxml.jackson.databind.ObjectMapper; 7 import com.fasterxml.jackson.databind.ObjectMapper;
7 import com.fasterxml.jackson.databind.PropertyNamingStrategy; 8 import com.fasterxml.jackson.databind.PropertyNamingStrategy;
8 import com.fasterxml.jackson.dataformat.xml.XmlMapper; 9 import com.fasterxml.jackson.dataformat.xml.XmlMapper;
10 +import com.infoloop.tianting.clientcustomerorderservice.ClientCustomerOrderModification;
11 +import com.infoloop.tianting.clientcustomerorderservice.PayStatusEnum;
9 import com.infoloop.tianting.config.BusinessConfig; 12 import com.infoloop.tianting.config.BusinessConfig;
10 import com.infoloop.tianting.model.dto.PayDTO.PayInformRequestDto; 13 import com.infoloop.tianting.model.dto.PayDTO.PayInformRequestDto;
11 import com.infoloop.tianting.model.dto.PayDTO.PayResponseDto; 14 import com.infoloop.tianting.model.dto.PayDTO.PayResponseDto;
12 import com.infoloop.tianting.model.dto.PayDTO.ThirdPartyPayInformRequestDto; 15 import com.infoloop.tianting.model.dto.PayDTO.ThirdPartyPayInformRequestDto;
13 import com.infoloop.tianting.model.dto.PaymentCallbackDTO; 16 import com.infoloop.tianting.model.dto.PaymentCallbackDTO;
17 +import com.infoloop.tianting.model.dto.PaymentCallbackResult;
14 import com.infoloop.tianting.utils.DateUtil; 18 import com.infoloop.tianting.utils.DateUtil;
15 import com.infoloop.tianting.utils.EncryptionUtil; 19 import com.infoloop.tianting.utils.EncryptionUtil;
16 import lombok.RequiredArgsConstructor; 20 import lombok.RequiredArgsConstructor;
...@@ -25,6 +29,8 @@ import org.springframework.http.ResponseEntity; ...@@ -25,6 +29,8 @@ import org.springframework.http.ResponseEntity;
25 import org.springframework.stereotype.Service; 29 import org.springframework.stereotype.Service;
26 import org.springframework.web.client.RestTemplate; 30 import org.springframework.web.client.RestTemplate;
27 31
32 +import java.math.BigDecimal;
33 +import java.text.ParseException;
28 import java.time.LocalDateTime; 34 import java.time.LocalDateTime;
29 35
30 36
...@@ -37,11 +43,11 @@ public class PayServiceClient { ...@@ -37,11 +43,11 @@ public class PayServiceClient {
37 43
38 private final BusinessConfig businessConfig; 44 private final BusinessConfig businessConfig;
39 45
46 + private final OrderServiceRpcClient orderServiceRpcClient;
47 + private final XmlMapper xmlMapper = new XmlMapper();
40 @Value("${payment.client-id:Order0001}") 48 @Value("${payment.client-id:Order0001}")
41 private String clientId; 49 private String clientId;
42 50
43 - private final XmlMapper xmlMapper = new XmlMapper();
44 -
45 public PayResponseDto payInform(PayInformRequestDto payRequestDto) { 51 public PayResponseDto payInform(PayInformRequestDto payRequestDto) {
46 String url = businessConfig.getPayUrl(); 52 String url = businessConfig.getPayUrl();
47 String requestDate = DateUtil.formatDate(LocalDateTime.now(), DateUtil.YMDHMS); 53 String requestDate = DateUtil.formatDate(LocalDateTime.now(), DateUtil.YMDHMS);
...@@ -55,7 +61,7 @@ public class PayServiceClient { ...@@ -55,7 +61,7 @@ public class PayServiceClient {
55 jsonDataString = objectMapper.writeValueAsString(jsonData); 61 jsonDataString = objectMapper.writeValueAsString(jsonData);
56 } catch (JsonProcessingException e) { 62 } catch (JsonProcessingException e) {
57 log.error("Failed to serialize request data", e); 63 log.error("Failed to serialize request data", e);
58 - throw new IllegalArgumentException("支付通知失败: JSON 解析错误"); 64 + throw new IllegalArgumentException("支付信息获取失败: JSON 解析错误");
59 } 65 }
60 HttpEntity<String> requestEntity = new HttpEntity<>(jsonDataString, createHeaders()); 66 HttpEntity<String> requestEntity = new HttpEntity<>(jsonDataString, createHeaders());
61 log.info("payInform request: {}", jsonDataString); 67 log.info("payInform request: {}", jsonDataString);
...@@ -88,19 +94,64 @@ public class PayServiceClient { ...@@ -88,19 +94,64 @@ public class PayServiceClient {
88 private PayResponseDto processResponse(ResponseEntity<String> responseEntity) { 94 private PayResponseDto processResponse(ResponseEntity<String> responseEntity) {
89 if (!responseEntity.getStatusCode().is2xxSuccessful() || responseEntity.getBody() == null) { 95 if (!responseEntity.getStatusCode().is2xxSuccessful() || responseEntity.getBody() == null) {
90 log.error("payInform failed with response: {}", responseEntity.getBody()); 96 log.error("payInform failed with response: {}", responseEntity.getBody());
91 - throw new IllegalArgumentException("支付通知失败"); 97 + throw new IllegalArgumentException("支付信息获取失败");
92 } 98 }
93 try { 99 try {
94 log.info("processResponse response: {}", responseEntity.getBody()); 100 log.info("processResponse response: {}", responseEntity.getBody());
95 return xmlMapper.readValue(responseEntity.getBody(), PayResponseDto.class); 101 return xmlMapper.readValue(responseEntity.getBody(), PayResponseDto.class);
96 } catch (Exception e) { 102 } catch (Exception e) {
97 log.error("Failed to parse XML response", e); 103 log.error("Failed to parse XML response", e);
98 - throw new RuntimeException("支付通知结果解析失败", e); 104 + throw new RuntimeException("支付信息结果解析失败", e);
99 } 105 }
100 } 106 }
101 107
102 - public void callback(PaymentCallbackDTO payInformRequestDto) { 108 + public void callback(PaymentCallbackDTO paymentCallbackDTO) {
103 - log.info("callback: {}", payInformRequestDto); 109 + log.info("callback request received: {}", paymentCallbackDTO);
104 - 110 + try {
111 + final var paymentCallbackResult = xmlMapper.readValue(paymentCallbackDTO.getPayResult(), PaymentCallbackResult.class);
112 + log.info("Parsed paymentCallbackResult: {}", paymentCallbackResult);
113 + final var orderId = Integer.parseInt(paymentCallbackResult.getTradeId());
114 + final var orderById = orderServiceRpcClient.getOrderById(orderId);
115 + if (orderById == null) {
116 + log.error("Order not found, orderId: {}", orderId);
117 + return;
118 + }
119 + if (orderById.getPayStatus() == PayStatusEnum.SUCCEED || orderById.getPayStatus() == PayStatusEnum.FAILED) {
120 + log.info("Order {} already processed, skipping.", orderId);
121 + return;
122 + }
123 + long payTime;
124 + try {
125 + payTime = DateUtil.parseDate(paymentCallbackResult.getTransDate(), "yyyyMMddHHmmss").getTime();
126 + } catch (ParseException e) {
127 + log.error("Failed to parse TransDate: {}", paymentCallbackResult.getTransDate(), e);
128 + throw new IllegalArgumentException("支付时间格式错误: " + paymentCallbackResult.getTransDate(), e);
129 + }
130 + BigDecimal realAmount;
131 + try {
132 + realAmount = Convert.toBigDecimal(paymentCallbackResult.getRealAmount());
133 + if (realAmount == null) {
134 + throw new NumberFormatException("RealAmount is null or empty");
135 + }
136 + } catch (NumberFormatException e) {
137 + log.error("Invalid RealAmount value: {}", paymentCallbackResult.getRealAmount(), e);
138 + throw new IllegalArgumentException("支付金额错误: " + paymentCallbackResult.getRealAmount(), e);
139 + }
140 + final var build = ClientCustomerOrderModification.newBuilder()
141 + .setId(orderId)
142 + .setShouldUpdatePayTime(true).setPayTime(payTime)
143 + .setShouldUpdatePayPrice(true).setPayPrice(realAmount.multiply(new BigDecimal("100")).intValue())
144 + .setShouldUpdatePayStatus(true).setPayStatus(PayStatusEnum.SUCCEED)
145 + .setShouldUpdateTransactionId(true).setTransactionId(paymentCallbackResult.getTransSN())
146 + .build();
147 + final var updateResponse = orderServiceRpcClient.updateClientCustomerOrderPaymentResult(orderById, build);
148 + log.info("callback updateClientCustomerOrder result: {}", updateResponse.getIsUpdated());
149 + } catch (JsonProcessingException e) {
150 + log.error("Failed to parse XML response, raw XML: {}", paymentCallbackDTO.getPayResult(), e);
151 + throw new RuntimeException("支付通知结果解析失败", e);
152 + } catch (Exception e) {
153 + log.error("Unexpected error in callback, request: {}", paymentCallbackDTO, e);
154 + throw new RuntimeException("支付通知处理失败", e);
155 + }
105 } 156 }
106 } 157 }
......