Skip to content

Commit b05370c

Browse files
author
tuannguyenh1
committed
#1237: [Order Refactor][Step 2] Create Order in PayPal and get order id
1 parent a5dfd9f commit b05370c

32 files changed

+564
-41
lines changed

order/src/main/java/com/yas/order/consumer/OrderStatusConsumer.java

+2-3
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
import com.yas.order.service.PaymentService;
1919
import com.yas.order.utils.Constants;
2020
import com.yas.order.viewmodel.payment.CheckoutPaymentVm;
21-
import java.io.IOException;
2221
import java.util.Objects;
2322
import java.util.Optional;
2423
import lombok.RequiredArgsConstructor;
@@ -66,7 +65,7 @@ private void processCheckoutEvent(JsonObject valueObject) {
6665

6766
private void handleAfterJson(JsonObject after) {
6867

69-
String id = getJsonValueOrThrow(after, Constants.Column.CHECKOUT_ID_COLUMN,
68+
String id = getJsonValueOrThrow(after, Constants.Column.ID_COLUMN,
7069
Constants.ErrorCode.ID_NOT_EXISTED);
7170
String status = getJsonValueOrThrow(after, Constants.Column.CHECKOUT_STATUS_COLUMN,
7271
Constants.ErrorCode.STATUS_NOT_EXISTED, id);
@@ -134,7 +133,7 @@ private Long processPayment(Checkout checkout) {
134133
return paymentId;
135134
}
136135

137-
private ObjectNode updateAttributesWithPayment(String attributes, Long paymentId) throws IOException {
136+
private ObjectNode updateAttributesWithPayment(String attributes, Long paymentId) {
138137

139138
ObjectNode attributesNode = getAttributesNode(objectMapper, attributes);
140139
attributesNode.put(Constants.Column.CHECKOUT_ATTRIBUTES_PAYMENT_ID_FIELD, paymentId);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
package com.yas.order.consumer;
2+
3+
import static com.yas.order.utils.JsonUtils.convertObjectToString;
4+
import static com.yas.order.utils.JsonUtils.getAttributesNode;
5+
import static com.yas.order.utils.JsonUtils.getJsonValueOrNull;
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.order.model.Checkout;
13+
import com.yas.order.model.enumeration.CheckoutProgress;
14+
import com.yas.order.model.enumeration.CheckoutState;
15+
import com.yas.order.service.CheckoutService;
16+
import com.yas.order.utils.Constants;
17+
import java.util.Objects;
18+
import java.util.Optional;
19+
import lombok.RequiredArgsConstructor;
20+
import org.apache.kafka.clients.consumer.ConsumerRecord;
21+
import org.slf4j.Logger;
22+
import org.slf4j.LoggerFactory;
23+
import org.springframework.kafka.annotation.KafkaListener;
24+
import org.springframework.kafka.annotation.RetryableTopic;
25+
import org.springframework.stereotype.Service;
26+
27+
@Service
28+
@RequiredArgsConstructor
29+
public class PaymentConsumer {
30+
31+
private static final Logger LOGGER = LoggerFactory.getLogger(PaymentConsumer.class);
32+
private final CheckoutService checkoutService;
33+
private final ObjectMapper objectMapper;
34+
private final Gson gson;
35+
36+
@KafkaListener(
37+
topics = "${cdc.event.payment.topic-name}",
38+
groupId = "${cdc.event.payment.update.group-id}"
39+
)
40+
@RetryableTopic
41+
public void listen(ConsumerRecord<?, ?> consumerRecord) {
42+
43+
if (Objects.isNull(consumerRecord)) {
44+
LOGGER.info("Consumer Record is null");
45+
return;
46+
}
47+
JsonObject valueObject = gson.fromJson((String) consumerRecord.value(), JsonObject.class);
48+
processPaymentEvent(valueObject);
49+
50+
}
51+
52+
private void processPaymentEvent(JsonObject valueObject) {
53+
Optional.ofNullable(valueObject)
54+
.filter(
55+
value -> value.has("op") && "u".equals(value.get("op").getAsString())
56+
)
57+
.filter(value -> value.has("before") && value.has("after"))
58+
.ifPresent(this::handleJsonForUpdateCheckout);
59+
}
60+
61+
private void handleJsonForUpdateCheckout(JsonObject valueObject) {
62+
63+
JsonObject before = valueObject.getAsJsonObject("before");
64+
JsonObject after = valueObject.getAsJsonObject("after");
65+
66+
String id = getJsonValueOrThrow(after, Constants.Column.ID_COLUMN,
67+
Constants.ErrorCode.ID_NOT_EXISTED);
68+
69+
String beforePaypalOrderId = getJsonValueOrNull(before,
70+
71+
Constants.Column.CHECKOUT_ATTRIBUTES_PAYMENT_PROVIDER_CHECKOUT_ID_FIELD);
72+
String afterPaypalOrderId = getJsonValueOrNull(after, Constants.Column.CHECKOUT_ATTRIBUTES_PAYMENT_PROVIDER_CHECKOUT_ID_FIELD);
73+
74+
if (!Objects.isNull(afterPaypalOrderId) && !afterPaypalOrderId.equals(beforePaypalOrderId)) {
75+
76+
LOGGER.info("Handle json for update Checkout with Payment {}", id);
77+
78+
String checkoutId = getJsonValueOrThrow(after, Constants.Column.CHECKOUT_ID_COLUMN,
79+
Constants.ErrorCode.CHECKOUT_ID_NOT_EXISTED);
80+
updateCheckOut(checkoutId, afterPaypalOrderId);
81+
} else {
82+
LOGGER.info("It's not an event to create an Order on PayPal with Payment ID {}", id);
83+
}
84+
}
85+
86+
private void updateCheckOut(String checkoutId, String paymentProviderCheckoutId) {
87+
88+
Checkout checkout = checkoutService.findCheckoutById(checkoutId);
89+
checkout.setCheckoutState(CheckoutState.PAYMENT_PROCESSING);
90+
checkout.setProgress(CheckoutProgress.PAYMENT_CREATED);
91+
92+
ObjectNode updatedAttributes = updateAttributesWithCheckout(checkout.getAttributes(),
93+
paymentProviderCheckoutId);
94+
checkout.setAttributes(convertObjectToString(objectMapper, updatedAttributes));
95+
96+
checkoutService.updateCheckout(checkout);
97+
}
98+
99+
private ObjectNode updateAttributesWithCheckout(String attributes, String paymentProviderCheckoutId) {
100+
101+
ObjectNode attributesNode = getAttributesNode(objectMapper, attributes);
102+
attributesNode.put(Constants.Column.CHECKOUT_ATTRIBUTES_PAYMENT_PROVIDER_CHECKOUT_ID_FIELD,
103+
paymentProviderCheckoutId);
104+
105+
return attributesNode;
106+
}
107+
108+
}

order/src/main/java/com/yas/order/controller/CheckoutController.java

+7-2
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,13 @@ public ResponseEntity<Long> updateCheckoutStatus(@Valid @RequestBody CheckoutSta
3030
return ResponseEntity.ok(checkoutService.updateCheckoutStatus(checkoutStatusPutVm));
3131
}
3232

33-
@GetMapping("/storefront/checkouts/{id}")
34-
public ResponseEntity<CheckoutVm> getOrderWithItemsById(@PathVariable String id) {
33+
@GetMapping("/storefront/checkouts/pending/{id}")
34+
public ResponseEntity<CheckoutVm> getPendingCheckoutDetailsById(@PathVariable String id) {
3535
return ResponseEntity.ok(checkoutService.getCheckoutPendingStateWithItemsById(id));
3636
}
37+
38+
@GetMapping("/storefront/checkouts/{id}")
39+
public ResponseEntity<CheckoutVm> getCheckoutById(@PathVariable String id) {
40+
return ResponseEntity.ok(checkoutService.findCheckoutWithItemsById(id));
41+
}
3742
}

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

+33
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import static com.yas.order.utils.Constants.ErrorCode.CHECKOUT_NOT_FOUND;
44

5+
import com.yas.commonlibrary.exception.BadRequestException;
56
import com.yas.commonlibrary.exception.Forbidden;
67
import com.yas.commonlibrary.exception.NotFoundException;
78
import com.yas.order.mapper.CheckoutMapper;
@@ -17,7 +18,10 @@
1718
import com.yas.order.viewmodel.checkout.CheckoutPostVm;
1819
import com.yas.order.viewmodel.checkout.CheckoutStatusPutVm;
1920
import com.yas.order.viewmodel.checkout.CheckoutVm;
21+
import java.util.Collections;
2022
import java.util.List;
23+
import java.util.Objects;
24+
import java.util.Optional;
2125
import java.util.Set;
2226
import java.util.UUID;
2327
import java.util.stream.Collectors;
@@ -101,4 +105,33 @@ public Long updateCheckoutStatus(CheckoutStatusPutVm checkoutStatusPutVm) {
101105
Order order = orderService.findOrderByCheckoutId(checkoutStatusPutVm.checkoutId());
102106
return order.getId();
103107
}
108+
109+
public Checkout findCheckoutById(String id) {
110+
111+
return this.checkoutRepository.findById(id)
112+
.orElseThrow(() -> new NotFoundException(CHECKOUT_NOT_FOUND, id));
113+
}
114+
115+
public CheckoutVm findCheckoutWithItemsById(String id) {
116+
117+
Checkout checkout = findCheckoutById(id);
118+
119+
List<CheckoutItem> checkoutItems = checkoutItemRepository.findAllByCheckoutId(checkout.getId());
120+
121+
Set<CheckoutItemVm> checkoutItemVms = Optional.ofNullable(checkoutItems)
122+
.orElse(Collections.emptyList())
123+
.stream()
124+
.map(checkoutMapper::toVm)
125+
.collect(Collectors.toSet());
126+
127+
return CheckoutVm.fromModel(checkout, checkoutItemVms);
128+
}
129+
130+
public void updateCheckout(Checkout checkout) {
131+
132+
if (Objects.isNull(checkout.getId())) {
133+
throw new BadRequestException(Constants.ErrorCode.ID_NOT_EXISTED);
134+
}
135+
checkoutRepository.save(checkout);
136+
}
104137
}

order/src/main/java/com/yas/order/utils/Constants.java

+8-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package com.yas.order.utils;
22

33
public final class Constants {
4-
public final class ErrorCode {
4+
public static final class ErrorCode {
55

66
private ErrorCode() {
77
throw new UnsupportedOperationException();
@@ -16,18 +16,23 @@ private ErrorCode() {
1616
public static final String ID_NOT_EXISTED = "ID_NOT_EXISTED";
1717
public static final String STATUS_NOT_EXISTED = "STATUS_NOT_EXISTED";
1818
public static final String PROGRESS_NOT_EXISTED = "PROGRESS_NOT_EXISTED";
19+
public static final String CHECKOUT_ID_NOT_EXISTED = "CHECKOUT_ID_NOT_EXISTED";
1920
}
2021

21-
public final class Column {
22+
public static final class Column {
2223

2324
private Column() {
2425
throw new UnsupportedOperationException();
2526
}
2627

2728
// Column name of Checkout table
28-
public static final String CHECKOUT_ID_COLUMN = "id";
29+
public static final String ID_COLUMN = "id";
30+
public static final String CHECKOUT_ID_COLUMN = "checkout_id";
2931
public static final String CHECKOUT_STATUS_COLUMN = "status";
3032
public static final String CHECKOUT_PROGRESS_COLUMN = "progress";
3133
public static final String CHECKOUT_ATTRIBUTES_PAYMENT_ID_FIELD = "payment_id";
34+
public static final String CHECKOUT_ATTRIBUTES_PAYMENT_PROVIDER_CHECKOUT_ID_FIELD
35+
= "payment_provider_checkout_id";
36+
3237
}
3338
}

order/src/main/java/com/yas/order/utils/JsonUtils.java

+20-6
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
import com.google.gson.JsonElement;
77
import com.google.gson.JsonObject;
88
import com.yas.commonlibrary.exception.BadRequestException;
9-
import java.io.IOException;
109
import java.util.Optional;
1110

1211
public class JsonUtils {
@@ -23,11 +22,15 @@ public static String convertObjectToString(ObjectMapper objectMapper, Object val
2322
}
2423
}
2524

26-
public static ObjectNode getAttributesNode(ObjectMapper objectMapper, String attributes) throws IOException {
27-
if (attributes == null || attributes.isBlank()) {
28-
return objectMapper.createObjectNode();
29-
} else {
30-
return (ObjectNode) objectMapper.readTree(attributes);
25+
public static ObjectNode getAttributesNode(ObjectMapper objectMapper, String attributes) {
26+
try {
27+
if (attributes == null || attributes.isBlank()) {
28+
return objectMapper.createObjectNode();
29+
} else {
30+
return (ObjectNode) objectMapper.readTree(attributes);
31+
}
32+
} catch (JsonProcessingException e) {
33+
throw new RuntimeException(e);
3134
}
3235
}
3336

@@ -49,4 +52,15 @@ public static String getJsonValueOrThrow(
4952
.map(JsonElement::getAsString)
5053
.orElseThrow(() -> new BadRequestException(errorCode, errorParams));
5154
}
55+
56+
public static String getJsonValueOrNull(
57+
JsonObject jsonObject,
58+
String columnName
59+
) {
60+
JsonElement jsonElement = jsonObject.get(columnName);
61+
if (jsonElement != null && !jsonElement.isJsonNull()) {
62+
return jsonElement.getAsString();
63+
}
64+
return null;
65+
}
5266
}

order/src/main/java/com/yas/order/viewmodel/checkout/CheckoutVm.java

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

3+
import com.yas.order.model.Checkout;
34
import java.util.Set;
45
import lombok.Builder;
56

@@ -11,4 +12,13 @@ public record CheckoutVm(
1112
String couponCode,
1213
Set<CheckoutItemVm> checkoutItemVms
1314
) {
15+
public static CheckoutVm fromModel(Checkout checkout, Set<CheckoutItemVm> checkoutItemVms) {
16+
return CheckoutVm.builder()
17+
.id(checkout.getId())
18+
.email(checkout.getEmail())
19+
.note(checkout.getNote())
20+
.couponCode(checkout.getCouponCode())
21+
.checkoutItemVms(checkoutItemVms)
22+
.build();
23+
}
1424
}

order/src/main/java/com/yas/order/viewmodel/orderaddress/OrderAddressPostVm.java

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
@Builder
88
public record OrderAddressPostVm(
9+
Long id,
910
@NotBlank String contactName,
1011
@NotBlank String phone,
1112
@NotBlank String addressLine1,

order/src/main/resources/application.properties

+4-1
Original file line numberDiff line numberDiff line change
@@ -52,4 +52,7 @@ resilience4j.circuitbreaker.instances.rest-circuit-breaker.permitted-number-of-c
5252
cors.allowed-origins=*
5353

5454
cdc.event.checkout.status.topic-name=dbcheckout-status.public.checkout
55-
cdc.event.checkout.status.group-id=checkout-status
55+
cdc.event.checkout.status.group-id=checkout-status
56+
57+
cdc.event.payment.topic-name=dbpayment.public.payment
58+
cdc.event.payment.update.group-id=payment-update

order/src/main/resources/messages/messages.properties

+2-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ FORBIDDEN=You don't have permission to access this page
66
PAYMENT_METHOD_NOT_EXISTED=Payment method id is not existed in Checkout {}
77
CANNOT_CONVERT_TO_STRING=Can not convert object to String : {}
88
PROCESS_CHECKOUT_FAILED=Failed to process checkout event for ID {}
9-
ID_NOT_EXISTED=ID is missing
9+
ID_NOT_EXISTED=ID is not existed
10+
CHECKOUT_ID_NOT_EXISTED=Checkout ID is not existed
1011
STATUS_NOT_EXISTED=Status is missing for Checkout ID {}
1112
PROGRESS_NOT_EXISTED=Progress is missing for Checkout ID {}

order/src/test/java/com/yas/order/controller/CheckoutControllerTest.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -90,13 +90,13 @@ void testUpdateCheckoutStatus_whenRequestIsValid_thenReturnLong() throws Excepti
9090
}
9191

9292
@Test
93-
void testGetOrderWithItemsById_whenRequestIsValid_thenReturnCheckoutVm() throws Exception {
93+
void testGetPendingCheckoutDetailsById_whenRequestIsValid_thenReturnCheckoutVm() throws Exception {
9494

9595
String id = "123";
9696
CheckoutVm response = getCheckoutVm();
9797
when(checkoutService.getCheckoutPendingStateWithItemsById(id)).thenReturn(response);
9898

99-
mockMvc.perform(get("/storefront/checkouts/{id}", id)
99+
mockMvc.perform(get("/storefront/checkouts/pending/{id}", id)
100100
.accept(MediaType.APPLICATION_JSON))
101101
.andExpect(status().isOk())
102102
.andExpect(MockMvcResultMatchers.content().json(objectWriter.writeValueAsString(response)));

order/src/test/java/com/yas/order/controller/OrderControllerTest.java

+2
Original file line numberDiff line numberDiff line change
@@ -337,6 +337,7 @@ private Set<OrderItemVm> getOrderItemVms() {
337337
private OrderPostVm getOrderPostVm() {
338338

339339
OrderAddressPostVm shippingAddress = new OrderAddressPostVm(
340+
1L,
340341
"John Doe",
341342
"+123456789",
342343
"123 Main St",
@@ -352,6 +353,7 @@ private OrderPostVm getOrderPostVm() {
352353
);
353354

354355
OrderAddressPostVm billingAddress = new OrderAddressPostVm(
356+
1L,
355357
"Jane Smith",
356358
"+1987654321",
357359
"789 Elm Street",

payment-paypal/pom.xml

+4
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@
3030
<groupId>org.springframework.boot</groupId>
3131
<artifactId>spring-boot-starter-validation</artifactId>
3232
</dependency>
33+
<dependency>
34+
<groupId>org.springframework.kafka</groupId>
35+
<artifactId>spring-kafka</artifactId>
36+
</dependency>
3337
<dependency>
3438
<groupId>org.springframework.boot</groupId>
3539
<artifactId>spring-boot-starter-data-jpa</artifactId>

payment-paypal/src/main/java/com/yas/paymentpaypal/config/SecurityConfig.java

+3-1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import org.springframework.core.convert.converter.Converter;
1010
import org.springframework.security.config.Customizer;
1111
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
12+
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
1213
import org.springframework.security.core.GrantedAuthority;
1314
import org.springframework.security.core.authority.SimpleGrantedAuthority;
1415
import org.springframework.security.oauth2.jwt.Jwt;
@@ -21,10 +22,11 @@ public class SecurityConfig {
2122
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
2223

2324
return http
25+
.csrf(AbstractHttpConfigurer::disable)
2426
.authorizeHttpRequests(auth -> auth
2527
.requestMatchers("/actuator/prometheus", "/actuator/health/**",
2628
"/swagger-ui", "/swagger-ui/**", "/error", "/v3/api-docs/**").permitAll()
27-
.requestMatchers("/capture", "/cancel").permitAll()
29+
.requestMatchers("/capture", "/cancel", "/events/checkout/orders").permitAll()
2830
.requestMatchers("/storefront/**").permitAll()
2931
.requestMatchers("/actuator/**").permitAll()
3032
.requestMatchers("/backoffice/**").hasRole("ADMIN")

0 commit comments

Comments
 (0)