Skip to content

Commit 2f36638

Browse files
nashtech-tuannguyenhuu1tuannguyenh1
authored and
tuannguyenh1
committed
#1180-return-payment-id
1 parent af2c942 commit 2f36638

29 files changed

+776
-18
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"connector.class": "io.debezium.connector.postgresql.PostgresConnector",
3+
"topic.prefix": "dbcheckout-status",
4+
"database.user": "admin",
5+
"database.dbname": "order",
6+
"database.hostname": "postgres",
7+
"database.password": "admin",
8+
"database.port": "5432",
9+
"key.converter.schemas.enable": "false",
10+
"value.converter.schemas.enable": "false",
11+
"value.converter": "org.apache.kafka.connect.json.JsonConverter",
12+
"key.converter": "org.apache.kafka.connect.json.JsonConverter",
13+
"schema.include.list": "public",
14+
"table.include.list": "public.checkout",
15+
"slot.name": "checkout_status_slot"
16+
}

kafka/connects/debezium-payment.json

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"connector.class": "io.debezium.connector.postgresql.PostgresConnector",
3+
"topic.prefix": "dbpayment",
4+
"database.user": "admin",
5+
"database.dbname": "payment",
6+
"database.hostname": "postgres",
7+
"database.password": "admin",
8+
"database.port": "5432",
9+
"key.converter.schemas.enable": "false",
10+
"value.converter.schemas.enable": "false",
11+
"value.converter": "org.apache.kafka.connect.json.JsonConverter",
12+
"key.converter": "org.apache.kafka.connect.json.JsonConverter",
13+
"schema.include.list": "public",
14+
"table.include.list": "public.payment",
15+
"slot.name": "payment_slot"
16+
}

order/pom.xml

+13
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,14 @@
2929
<groupId>org.springframework.boot</groupId>
3030
<artifactId>spring-boot-starter-validation</artifactId>
3131
</dependency>
32+
<dependency>
33+
<groupId>org.springframework.kafka</groupId>
34+
<artifactId>spring-kafka</artifactId>
35+
</dependency>
36+
<dependency>
37+
<groupId>com.google.code.gson</groupId>
38+
<artifactId>gson</artifactId>
39+
</dependency>
3240
<dependency>
3341
<groupId>org.springframework.boot</groupId>
3442
<artifactId>spring-boot-starter-data-jpa</artifactId>
@@ -51,6 +59,11 @@
5159
<groupId>org.liquibase</groupId>
5260
<artifactId>liquibase-core</artifactId>
5361
</dependency>
62+
<dependency>
63+
<groupId>org.testcontainers</groupId>
64+
<artifactId>kafka</artifactId>
65+
<scope>test</scope>
66+
</dependency>
5467
<dependency>
5568
<groupId>com.yas</groupId>
5669
<artifactId>common-library</artifactId>

order/src/it/resources/application.properties

+4-1
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,7 @@ spring.security.oauth2.resourceserver.jwt.issuer-uri=test
1212
springdoc.oauthflow.authorization-url=test
1313
springdoc.oauthflow.token-url=test
1414
spring.jpa.open-in-view=true
15-
cors.allowed-origins=*
15+
cors.allowed-origins=*
16+
17+
cdc.event.checkout.status.topic-name=dbcheckout-status.public.checkout
18+
cdc.event.checkout.status.group-id=checkout-status
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package com.yas.order.config;
2+
3+
4+
import com.fasterxml.jackson.databind.ObjectMapper;
5+
import com.google.gson.Gson;
6+
import org.springframework.context.annotation.Bean;
7+
import org.springframework.context.annotation.Configuration;
8+
9+
10+
@Configuration
11+
public class JsonConfig {
12+
13+
@Bean
14+
public ObjectMapper objectMapper() {
15+
return new ObjectMapper();
16+
}
17+
18+
@Bean
19+
public Gson gson() {
20+
return new Gson();
21+
}
22+
}

order/src/main/java/com/yas/order/config/ServiceUrlConfig.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,5 @@
44

55
@ConfigurationProperties(prefix = "yas.services")
66
public record ServiceUrlConfig(
7-
String cart, String customer, String product, String tax, String promotion) {
7+
String cart, String customer, String product, String tax, String promotion, String payment) {
88
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
package com.yas.order.consumer;
2+
3+
import static com.yas.order.utils.JsonUtils.convertObjectToString;
4+
import static com.yas.order.utils.JsonUtils.createJsonErrorObject;
5+
import static com.yas.order.utils.JsonUtils.getAttributesNode;
6+
import static com.yas.order.utils.JsonUtils.getJsonValueOrThrow;
7+
8+
import com.fasterxml.jackson.databind.ObjectMapper;
9+
import com.fasterxml.jackson.databind.node.ObjectNode;
10+
import com.google.gson.Gson;
11+
import com.google.gson.JsonObject;
12+
import com.yas.commonlibrary.exception.BadRequestException;
13+
import com.yas.commonlibrary.exception.NotFoundException;
14+
import com.yas.order.model.Checkout;
15+
import com.yas.order.model.enumeration.CheckoutProgress;
16+
import com.yas.order.model.enumeration.CheckoutState;
17+
import com.yas.order.repository.CheckoutRepository;
18+
import com.yas.order.service.PaymentService;
19+
import com.yas.order.utils.Constants;
20+
import com.yas.order.viewmodel.payment.CheckoutPaymentVm;
21+
import java.io.IOException;
22+
import java.util.Objects;
23+
import java.util.Optional;
24+
import lombok.RequiredArgsConstructor;
25+
import org.apache.kafka.clients.consumer.ConsumerRecord;
26+
import org.slf4j.Logger;
27+
import org.slf4j.LoggerFactory;
28+
import org.springframework.kafka.annotation.KafkaListener;
29+
import org.springframework.kafka.annotation.RetryableTopic;
30+
import org.springframework.stereotype.Service;
31+
32+
@Service
33+
@RequiredArgsConstructor
34+
public class OrderStatusConsumer {
35+
36+
private static final Logger LOGGER = LoggerFactory.getLogger(OrderStatusConsumer.class);
37+
private final PaymentService paymentService;
38+
private final CheckoutRepository checkoutRepository;
39+
private final ObjectMapper objectMapper;
40+
private final Gson gson;
41+
42+
@KafkaListener(
43+
topics = "${cdc.event.checkout.status.topic-name}",
44+
groupId = "${cdc.event.checkout.status.group-id}"
45+
)
46+
@RetryableTopic(
47+
attempts = "1"
48+
)
49+
public void listen(ConsumerRecord<?, ?> consumerRecord) {
50+
51+
if (Objects.isNull(consumerRecord)) {
52+
LOGGER.info("ConsumerRecord is null");
53+
return;
54+
}
55+
JsonObject valueObject = gson.fromJson((String) consumerRecord.value(), JsonObject.class);
56+
processCheckoutEvent(valueObject);
57+
58+
}
59+
60+
private void processCheckoutEvent(JsonObject valueObject) {
61+
Optional.ofNullable(valueObject)
62+
.filter(value -> value.has("after"))
63+
.map(value -> value.getAsJsonObject("after"))
64+
.ifPresent(this::handleAfterJson);
65+
}
66+
67+
private void handleAfterJson(JsonObject after) {
68+
69+
String id = getJsonValueOrThrow(after, Constants.Column.ID_COLUMN,
70+
Constants.ErrorCode.ID_NOT_EXISTED);
71+
String status = getJsonValueOrThrow(after, Constants.Column.STATUS_COLUMN,
72+
Constants.ErrorCode.STATUS_NOT_EXISTED, id);
73+
String progress = getJsonValueOrThrow(after, Constants.Column.CHECKOUT_PROGRESS_COLUMN,
74+
Constants.ErrorCode.PROGRESS_NOT_EXISTED, id);
75+
76+
if (!isPaymentProcessing(status, progress)) {
77+
LOGGER.info("Checkout record with ID {} lacks the status 'PAYMENT_PROCESSING' and progress 'STOCK_LOCKED'",
78+
id);
79+
return;
80+
}
81+
82+
LOGGER.info("Checkout record with ID {} has the status 'PAYMENT_PROCESSING' and the process 'STOCK_LOCKED'",
83+
id);
84+
85+
Checkout checkout = checkoutRepository
86+
.findById(id)
87+
.orElseThrow(() -> new NotFoundException(Constants.ErrorCode.CHECKOUT_NOT_FOUND, id));
88+
89+
processPaymentAndUpdateCheckout(checkout);
90+
}
91+
92+
private boolean isPaymentProcessing(String status, String process) {
93+
return CheckoutState.PAYMENT_PROCESSING.name().equalsIgnoreCase(status)
94+
&& CheckoutProgress.STOCK_LOCKED.name().equalsIgnoreCase(process);
95+
}
96+
97+
private void processPaymentAndUpdateCheckout(Checkout checkout) {
98+
99+
try {
100+
Long paymentId = processPayment(checkout);
101+
checkout.setProgress(CheckoutProgress.PAYMENT_CREATED);
102+
checkout.setLastError(null);
103+
104+
ObjectNode updatedAttributes = updateAttributesWithPayment(checkout.getAttributes(), paymentId);
105+
checkout.setAttributes(convertObjectToString(objectMapper, updatedAttributes));
106+
107+
} catch (Exception e) {
108+
109+
checkout.setProgress(CheckoutProgress.PAYMENT_CREATED_FAILED);
110+
111+
ObjectNode error = createJsonErrorObject(objectMapper, CheckoutProgress.PAYMENT_CREATED_FAILED.name(),
112+
e.getMessage());
113+
checkout.setLastError(convertObjectToString(objectMapper, error));
114+
115+
LOGGER.error(e.getMessage());
116+
throw new BadRequestException(Constants.ErrorCode.PROCESS_CHECKOUT_FAILED, checkout.getId());
117+
118+
} finally {
119+
checkoutRepository.save(checkout);
120+
}
121+
}
122+
123+
private Long processPayment(Checkout checkout) {
124+
125+
CheckoutPaymentVm requestDto = new CheckoutPaymentVm(
126+
checkout.getId(),
127+
checkout.getPaymentMethodId(),
128+
checkout.getTotalAmount()
129+
);
130+
131+
Long paymentId = paymentService.createPaymentFromEvent(requestDto);
132+
LOGGER.info("Payment created successfully with ID: {}", paymentId);
133+
134+
return paymentId;
135+
}
136+
137+
private ObjectNode updateAttributesWithPayment(String attributes, Long paymentId) throws IOException {
138+
139+
ObjectNode attributesNode = getAttributesNode(objectMapper, attributes);
140+
attributesNode.put(Constants.Column.CHECKOUT_ATTRIBUTES_PAYMENT_ID_FIELD, paymentId);
141+
142+
return attributesNode;
143+
}
144+
145+
}

order/src/main/java/com/yas/order/mapper/CheckoutMapper.java

+12-1
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,22 @@
1616
public interface CheckoutMapper {
1717
@Mapping(target = "id", ignore = true)
1818
@Mapping(target = "checkout", ignore = true)
19+
@Mapping(target = "checkoutId", ignore = true)
1920
CheckoutItem toModel(CheckoutItemPostVm checkoutItemPostVm);
2021

2122
@Mapping(target = "id", ignore = true)
2223
@Mapping(target = "checkoutState", ignore = true)
23-
@Mapping(target = "totalAmount", source = "totalAmount") // Ánh xạ tường minh cho totalAmount
24+
@Mapping(target = "progress", ignore = true)
25+
@Mapping(target = "customerId", ignore = true)
26+
@Mapping(target = "shipmentMethodId", ignore = true)
27+
@Mapping(target = "paymentMethodId", ignore = true)
28+
@Mapping(target = "shippingAddressId", ignore = true)
29+
@Mapping(target = "lastError", ignore = true)
30+
@Mapping(target = "attributes", ignore = true)
31+
@Mapping(target = "totalShipmentFee", ignore = true)
32+
@Mapping(target = "totalShipmentTax", ignore = true)
33+
@Mapping(target = "totalTax", ignore = true)
34+
@Mapping(target = "totalAmount", source = "totalAmount")
2435
@Mapping(target = "totalDiscountAmount", source = "totalDiscountAmount")
2536
Checkout toModel(CheckoutPostVm checkoutPostVm);
2637

order/src/main/java/com/yas/order/model/Checkout.java

+6-5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package com.yas.order.model;
22

3+
import com.yas.order.model.enumeration.CheckoutProgress;
34
import com.yas.order.model.enumeration.CheckoutState;
5+
import com.yas.order.model.enumeration.PaymentMethod;
46
import jakarta.persistence.Column;
57
import jakarta.persistence.Entity;
68
import jakarta.persistence.EnumType;
@@ -39,22 +41,21 @@ public class Checkout extends AbstractAuditEntity {
3941
@Enumerated(EnumType.STRING)
4042
private CheckoutState checkoutState;
4143

42-
@SuppressWarnings("unused")
43-
private String progress;
44+
@Enumerated(EnumType.STRING)
45+
private CheckoutProgress progress;
4446

4547
@SuppressWarnings("unused")
4648
private Long customerId;
4749

4850
@SuppressWarnings("unused")
4951
private String shipmentMethodId;
5052

51-
@Column(name = "payment_method_id")
52-
private String paymentMethodId;
53+
@Enumerated(EnumType.STRING)
54+
private PaymentMethod paymentMethodId;
5355

5456
@SuppressWarnings("unused")
5557
private Long shippingAddressId;
5658

57-
@SuppressWarnings("unused")
5859
@JdbcTypeCode(SqlTypes.JSON)
5960
@Column(name = "last_error", columnDefinition = "jsonb")
6061
private String lastError;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package com.yas.order.model.enumeration;
2+
3+
public enum CheckoutProgress {
4+
INIT("Init"),
5+
PROMOTION_CODE_APPLIED("Promotion code applied"),
6+
PROMOTION_CODE_APPLIED_FAILED("Promotion Code applied failed"),
7+
STOCK_LOCKED("Stock locked"),
8+
STOCK_LOCKED_FAILED("Stock locked failed"),
9+
PAYMENT_CREATED("Payment created"),
10+
PAYMENT_CREATED_FAILED("Payment created failed");
11+
private final String name;
12+
13+
CheckoutProgress(String name) {
14+
this.name = name;
15+
}
16+
17+
public String getName() {
18+
return name;
19+
}
20+
}

order/src/main/java/com/yas/order/model/enumeration/CheckoutState.java

+9-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,15 @@
11
package com.yas.order.model.enumeration;
22

33
public enum CheckoutState {
4-
COMPLETED("Completed"), PENDING("Pending"), LOCK("LOCK");
4+
COMPLETED("Completed"),
5+
PENDING("Pending"),
6+
LOCK("LOCK"),
7+
CHECKED_OUT("Checked Out"),
8+
PAYMENT_PROCESSING("Payment Processing"),
9+
PAYMENT_FAILED("Payment Failed"),
10+
PAYMENT_CONFIRMED("Payment Confirmed"),
11+
FULFILLED("Fulfilled")
12+
;
513
private final String name;
614

715
CheckoutState(String name) {
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,14 @@
11
package com.yas.order.model.enumeration;
22

33
public enum PaymentMethod {
4-
COD, BANKING, PAYPAL
4+
COD, BANKING, PAYPAL;
5+
6+
public static PaymentMethod fromValue(String value) {
7+
try {
8+
return PaymentMethod.valueOf(value.toUpperCase());
9+
} catch (IllegalArgumentException e) {
10+
throw new IllegalArgumentException("Invalid payment method: " + value);
11+
}
12+
}
513
}
14+

order/src/main/java/com/yas/order/service/CheckoutService.java

+8-1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import com.yas.order.model.CheckoutItem;
1010
import com.yas.order.model.Order;
1111
import com.yas.order.model.enumeration.CheckoutState;
12+
import com.yas.order.model.enumeration.PaymentMethod;
1213
import com.yas.order.repository.CheckoutItemRepository;
1314
import com.yas.order.repository.CheckoutRepository;
1415
import com.yas.order.utils.AuthenticationUtils;
@@ -19,6 +20,7 @@
1920
import com.yas.order.viewmodel.checkout.CheckoutStatusPutVm;
2021
import com.yas.order.viewmodel.checkout.CheckoutVm;
2122
import java.util.List;
23+
import java.util.Objects;
2224
import java.util.Set;
2325
import java.util.UUID;
2426
import java.util.stream.Collectors;
@@ -104,9 +106,14 @@ public Long updateCheckoutStatus(CheckoutStatusPutVm checkoutStatusPutVm) {
104106
}
105107

106108
public void updateCheckoutPaymentMethod(String id, CheckoutPaymentMethodPutVm checkoutPaymentMethodPutVm) {
109+
110+
if (Objects.isNull(checkoutPaymentMethodPutVm.paymentMethodId())) {
111+
return;
112+
}
113+
107114
Checkout checkout = checkoutRepository.findById(id)
108115
.orElseThrow(() -> new NotFoundException(CHECKOUT_NOT_FOUND, id));
109-
checkout.setPaymentMethodId(checkoutPaymentMethodPutVm.paymentMethodId());
116+
checkout.setPaymentMethodId(PaymentMethod.fromValue(checkoutPaymentMethodPutVm.paymentMethodId()));
110117
checkoutRepository.save(checkout);
111118
}
112119
}

0 commit comments

Comments
 (0)