Skip to content

Commit

Permalink
#1237: [Order Refactor][Step 2] Create Order in PayPal and get order id
Browse files Browse the repository at this point in the history
  • Loading branch information
tuannguyenh1 committed Oct 29, 2024
1 parent a5dfd9f commit ea686af
Show file tree
Hide file tree
Showing 28 changed files with 557 additions and 43 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
import com.yas.order.service.PaymentService;
import com.yas.order.utils.Constants;
import com.yas.order.viewmodel.payment.CheckoutPaymentVm;
import java.io.IOException;
import java.util.Objects;
import java.util.Optional;
import lombok.RequiredArgsConstructor;
Expand Down Expand Up @@ -134,7 +133,7 @@ private Long processPayment(Checkout checkout) {
return paymentId;
}

private ObjectNode updateAttributesWithPayment(String attributes, Long paymentId) throws IOException {
private ObjectNode updateAttributesWithPayment(String attributes, Long paymentId) {

ObjectNode attributesNode = getAttributesNode(objectMapper, attributes);
attributesNode.put(Constants.Column.CHECKOUT_ATTRIBUTES_PAYMENT_ID_FIELD, paymentId);
Expand Down
108 changes: 108 additions & 0 deletions order/src/main/java/com/yas/order/consumer/PaymentConsumer.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package com.yas.order.consumer;

import static com.yas.order.utils.JsonUtils.convertObjectToString;
import static com.yas.order.utils.JsonUtils.getAttributesNode;
import static com.yas.order.utils.JsonUtils.getJsonValueOrNull;
import static com.yas.order.utils.JsonUtils.getJsonValueOrThrow;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import com.yas.order.model.Checkout;
import com.yas.order.model.enumeration.CheckoutProgress;
import com.yas.order.model.enumeration.CheckoutState;
import com.yas.order.service.CheckoutService;
import com.yas.order.utils.Constants;
import java.util.Objects;
import java.util.Optional;
import lombok.RequiredArgsConstructor;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.kafka.annotation.KafkaListener;
import org.springframework.kafka.annotation.RetryableTopic;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
public class PaymentConsumer {

private static final Logger LOGGER = LoggerFactory.getLogger(PaymentConsumer.class);
private final CheckoutService checkoutService;
private final ObjectMapper objectMapper;
private final Gson gson;

@KafkaListener(
topics = "${cdc.event.payment.topic-name}",
groupId = "${cdc.event.payment.group-id}"
)
@RetryableTopic
public void listen(ConsumerRecord<?, ?> consumerRecord) {

if (Objects.isNull(consumerRecord)) {
LOGGER.info("Consumer Record is null");
return;
}
JsonObject valueObject = gson.fromJson((String) consumerRecord.value(), JsonObject.class);
processPaymentEvent(valueObject);

}

private void processPaymentEvent(JsonObject valueObject) {
Optional.ofNullable(valueObject)
.filter(
value -> value.has("op") && "u".equals(value.get("op").getAsString())
)
.filter(value -> value.has("before") && value.has("after"))
.ifPresent(this::handleJsonForUpdateCheckout);
}

private void handleJsonForUpdateCheckout(JsonObject valueObject) {

JsonObject before = valueObject.getAsJsonObject("before");
JsonObject after = valueObject.getAsJsonObject("after");

String id = getJsonValueOrThrow(after, Constants.Column.ID_COLUMN,
Constants.ErrorCode.ID_NOT_EXISTED);

String beforePaypalOrderId = getJsonValueOrNull(before,

Constants.Column.CHECKOUT_ATTRIBUTES_PAYMENT_PROVIDER_CHECKOUT_ID_FIELD);
String afterPaypalOrderId = getJsonValueOrNull(after, Constants.Column.CHECKOUT_ATTRIBUTES_PAYMENT_PROVIDER_CHECKOUT_ID_FIELD);

if (!Objects.isNull(afterPaypalOrderId) && !afterPaypalOrderId.equals(beforePaypalOrderId)) {

LOGGER.info("Handle json for update Checkout with Payment {}", id);

String checkoutId = getJsonValueOrThrow(after, Constants.Column.CHECKOUT_ID_COLUMN,
Constants.ErrorCode.CHECKOUT_ID_NOT_EXISTED);
updateCheckOut(checkoutId, afterPaypalOrderId);
} else {
LOGGER.info("It's not an event to create an Order on PayPal with Payment ID {}", id);
}
}

private void updateCheckOut(String checkoutId, String paymentProviderCheckoutId) {

Checkout checkout = checkoutService.findCheckoutById(checkoutId);
checkout.setCheckoutState(CheckoutState.PAYMENT_PROCESSING);
checkout.setProgress(CheckoutProgress.PAYMENT_CREATED);

ObjectNode updatedAttributes = updateAttributesWithCheckout(checkout.getAttributes(),
paymentProviderCheckoutId);
checkout.setAttributes(convertObjectToString(objectMapper, updatedAttributes));

checkoutService.updateCheckout(checkout);
}

private ObjectNode updateAttributesWithCheckout(String attributes, String paymentProviderCheckoutId) {

ObjectNode attributesNode = getAttributesNode(objectMapper, attributes);
attributesNode.put(Constants.Column.CHECKOUT_ATTRIBUTES_PAYMENT_PROVIDER_CHECKOUT_ID_FIELD,
paymentProviderCheckoutId);

return attributesNode;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,13 @@ public ResponseEntity<Long> updateCheckoutStatus(@Valid @RequestBody CheckoutSta
return ResponseEntity.ok(checkoutService.updateCheckoutStatus(checkoutStatusPutVm));
}

@GetMapping("/storefront/checkouts/{id}")
public ResponseEntity<CheckoutVm> getOrderWithItemsById(@PathVariable String id) {
@GetMapping("/storefront/checkouts/pending/{id}")
public ResponseEntity<CheckoutVm> getPendingCheckoutDetailsById(@PathVariable String id) {
return ResponseEntity.ok(checkoutService.getCheckoutPendingStateWithItemsById(id));
}

@GetMapping("/storefront/checkouts/{id}")
public ResponseEntity<CheckoutVm> getCheckoutById(@PathVariable String id) {
return ResponseEntity.ok(checkoutService.findCheckoutWithItemsById(id));
}
}
33 changes: 33 additions & 0 deletions order/src/main/java/com/yas/order/service/CheckoutService.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

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

import com.yas.commonlibrary.exception.BadRequestException;
import com.yas.commonlibrary.exception.Forbidden;
import com.yas.commonlibrary.exception.NotFoundException;
import com.yas.order.mapper.CheckoutMapper;
Expand All @@ -17,7 +18,10 @@
import com.yas.order.viewmodel.checkout.CheckoutPostVm;
import com.yas.order.viewmodel.checkout.CheckoutStatusPutVm;
import com.yas.order.viewmodel.checkout.CheckoutVm;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
Expand Down Expand Up @@ -101,4 +105,33 @@ public Long updateCheckoutStatus(CheckoutStatusPutVm checkoutStatusPutVm) {
Order order = orderService.findOrderByCheckoutId(checkoutStatusPutVm.checkoutId());
return order.getId();
}

public Checkout findCheckoutById(String id) {

return this.checkoutRepository.findById(id)
.orElseThrow(() -> new NotFoundException(CHECKOUT_NOT_FOUND, id));
}

public CheckoutVm findCheckoutWithItemsById(String id) {

Checkout checkout = findCheckoutById(id);

List<CheckoutItem> checkoutItems = checkoutItemRepository.findAllByCheckoutId(checkout.getId());

Set<CheckoutItemVm> checkoutItemVms = Optional.ofNullable(checkoutItems)
.orElse(Collections.emptyList())
.stream()
.map(checkoutMapper::toVm)
.collect(Collectors.toSet());

return CheckoutVm.fromModel(checkout, checkoutItemVms);
}

public void updateCheckout(Checkout checkout) {

if (Objects.isNull(checkout.getId())) {
throw new BadRequestException(Constants.ErrorCode.ID_NOT_EXISTED);
}
checkoutRepository.save(checkout);
}
}
7 changes: 6 additions & 1 deletion order/src/main/java/com/yas/order/utils/Constants.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ private ErrorCode() {
public static final String ID_NOT_EXISTED = "ID_NOT_EXISTED";
public static final String STATUS_NOT_EXISTED = "STATUS_NOT_EXISTED";
public static final String PROGRESS_NOT_EXISTED = "PROGRESS_NOT_EXISTED";
public static final String CHECKOUT_ID_NOT_EXISTED = "CHECKOUT_ID_NOT_EXISTED";
}

public final class Column {
Expand All @@ -25,9 +26,13 @@ private Column() {
}

// Column name of Checkout table
public static final String CHECKOUT_ID_COLUMN = "id";
public static final String ID_COLUMN = "id";
public static final String CHECKOUT_ID_COLUMN = "checkout_id";
public static final String CHECKOUT_STATUS_COLUMN = "status";
public static final String CHECKOUT_PROGRESS_COLUMN = "progress";
public static final String CHECKOUT_ATTRIBUTES_PAYMENT_ID_FIELD = "payment_id";
public static final String CHECKOUT_ATTRIBUTES_PAYMENT_PROVIDER_CHECKOUT_ID_FIELD
= "payment_provider_checkout_id";

}
}
26 changes: 20 additions & 6 deletions order/src/main/java/com/yas/order/utils/JsonUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.yas.commonlibrary.exception.BadRequestException;
import java.io.IOException;
import java.util.Optional;

public class JsonUtils {
Expand All @@ -23,11 +22,15 @@ public static String convertObjectToString(ObjectMapper objectMapper, Object val
}
}

public static ObjectNode getAttributesNode(ObjectMapper objectMapper, String attributes) throws IOException {
if (attributes == null || attributes.isBlank()) {
return objectMapper.createObjectNode();
} else {
return (ObjectNode) objectMapper.readTree(attributes);
public static ObjectNode getAttributesNode(ObjectMapper objectMapper, String attributes) {
try {
if (attributes == null || attributes.isBlank()) {
return objectMapper.createObjectNode();
} else {
return (ObjectNode) objectMapper.readTree(attributes);
}
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
}

Expand All @@ -49,4 +52,15 @@ public static String getJsonValueOrThrow(
.map(JsonElement::getAsString)
.orElseThrow(() -> new BadRequestException(errorCode, errorParams));
}

public static String getJsonValueOrNull(
JsonObject jsonObject,
String columnName
) {
JsonElement jsonElement = jsonObject.get(columnName);
if (jsonElement != null && !jsonElement.isJsonNull()) {
return jsonElement.getAsString();
}
return null;
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.yas.order.viewmodel.checkout;

import com.yas.order.model.Checkout;
import java.util.Set;
import lombok.Builder;

Expand All @@ -11,4 +12,13 @@ public record CheckoutVm(
String couponCode,
Set<CheckoutItemVm> checkoutItemVms
) {
public static CheckoutVm fromModel(Checkout checkout, Set<CheckoutItemVm> checkoutItemVms) {
return CheckoutVm.builder()
.id(checkout.getId())
.email(checkout.getEmail())
.note(checkout.getNote())
.couponCode(checkout.getCouponCode())
.checkoutItemVms(checkoutItemVms)
.build();
}
}
5 changes: 4 additions & 1 deletion order/src/main/resources/application.properties
Original file line number Diff line number Diff line change
Expand Up @@ -52,4 +52,7 @@ resilience4j.circuitbreaker.instances.rest-circuit-breaker.permitted-number-of-c
cors.allowed-origins=*

cdc.event.checkout.status.topic-name=dbcheckout-status.public.checkout
cdc.event.checkout.status.group-id=checkout-status
cdc.event.checkout.status.group-id=checkout-status

cdc.event.payment.topic-name=dbpayment.public.payment
cdc.event.payment.group-id=payment
3 changes: 2 additions & 1 deletion order/src/main/resources/messages/messages.properties
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ FORBIDDEN=You don't have permission to access this page
PAYMENT_METHOD_NOT_EXISTED=Payment method id is not existed in Checkout {}
CANNOT_CONVERT_TO_STRING=Can not convert object to String : {}
PROCESS_CHECKOUT_FAILED=Failed to process checkout event for ID {}
ID_NOT_EXISTED=ID is missing
ID_NOT_EXISTED=ID is not existed
CHECKOUT_ID_NOT_EXISTED=Checkout ID is not existed
STATUS_NOT_EXISTED=Status is missing for Checkout ID {}
PROGRESS_NOT_EXISTED=Progress is missing for Checkout ID {}
Original file line number Diff line number Diff line change
Expand Up @@ -90,13 +90,13 @@ void testUpdateCheckoutStatus_whenRequestIsValid_thenReturnLong() throws Excepti
}

@Test
void testGetOrderWithItemsById_whenRequestIsValid_thenReturnCheckoutVm() throws Exception {
void testGetPendingCheckoutDetailsById_whenRequestIsValid_thenReturnCheckoutVm() throws Exception {

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

mockMvc.perform(get("/storefront/checkouts/{id}", id)
mockMvc.perform(get("/storefront/checkouts/pending/{id}", id)
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(MockMvcResultMatchers.content().json(objectWriter.writeValueAsString(response)));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
.requestMatchers("/capture", "/cancel").permitAll()
.requestMatchers("/storefront/**").permitAll()
.requestMatchers("/actuator/**").permitAll()
.requestMatchers("/events/checkout/orders").permitAll()
.requestMatchers("/backoffice/**").hasRole("ADMIN")
.anyRequest().authenticated())
.oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults()))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import com.yas.paymentpaypal.service.PaypalService;
import com.yas.paymentpaypal.viewmodel.CapturedPaymentVm;
import com.yas.paymentpaypal.viewmodel.PaymentPaypalRequest;
import com.yas.paymentpaypal.viewmodel.PaymentPaypalResponse;
import com.yas.paymentpaypal.viewmodel.PaypalRequestPayment;
import com.yas.paymentpaypal.viewmodel.RequestPayment;
import jakarta.validation.Valid;
Expand Down Expand Up @@ -32,4 +34,9 @@ public CapturedPaymentVm capturePayment(@RequestParam("token") String token) {
public ResponseEntity<String> cancelPayment() {
return ResponseEntity.ok("Payment cancelled");
}

@GetMapping(value = "/events/checkout/orders")
public PaymentPaypalResponse createOrderOnPaypal(@Valid @RequestBody PaymentPaypalRequest paymentPaypalRequest) {
return paypalService.createOrderOnPaypal(paymentPaypalRequest);
}
}
Loading

0 comments on commit ea686af

Please sign in to comment.