From cc8e1024ed0e4cdb78f6262efab8b14050126ed9 Mon Sep 17 00:00:00 2001 From: tuannguyenh1 Date: Fri, 8 Nov 2024 13:49:51 +0700 Subject: [PATCH] #1180 & #1237 intergration with front end - create payment and order in paypal --- .../kafka/consumer/OrderStatusConsumer.java | 4 +- .../kafka/consumer/PaymentUpdateConsumer.java | 14 +- .../order/viewmodel/checkout/CheckoutVm.java | 13 ++ .../controller/CheckoutControllerTest.java | 8 + .../consumer/OrderStatusConsumerTest.java | 2 +- .../com/yas/payment/config/JsonConfig.java | 22 +++ .../payment/controller/PaymentController.java | 6 +- .../kafka/consumer/PaymentCreateConsumer.java | 10 ++ .../java/com/yas/payment/model/Payment.java | 7 + .../java/com/yas/payment/utils/Constants.java | 2 +- .../java/com/yas/payment/utils/JsonUtils.java | 33 ++++ .../com/yas/payment/viewmodel/PaymentVm.java | 8 +- .../db/changelog/ddl/changelog-0005.sql | 2 + .../consumer/PaymentCreateConsumerTest.java | 4 +- .../modules/common/models/EPaymentMethod.ts | 5 + .../order/components/CheckOutDetail.tsx | 14 ++ storefront/modules/order/models/Checkout.ts | 10 +- .../modules/order/models/ECheckoutProgress.ts | 9 ++ .../modules/order/models/ECheckoutState.ts | 10 ++ .../modules/order/services/OrderService.ts | 8 +- .../modules/payment/models/EPaymentStatus.ts | 7 + storefront/modules/payment/models/Payment.ts | 16 ++ .../payment/services/PaymentService.ts | 9 ++ storefront/pages/cart/index.tsx | 2 +- storefront/pages/checkout/[id].tsx | 148 ++++++++++++++++-- 25 files changed, 337 insertions(+), 36 deletions(-) create mode 100644 payment/src/main/java/com/yas/payment/config/JsonConfig.java create mode 100644 payment/src/main/java/com/yas/payment/utils/JsonUtils.java create mode 100644 payment/src/main/resources/db/changelog/ddl/changelog-0005.sql create mode 100644 storefront/modules/common/models/EPaymentMethod.ts create mode 100644 storefront/modules/order/models/ECheckoutProgress.ts create mode 100644 storefront/modules/order/models/ECheckoutState.ts create mode 100644 storefront/modules/payment/models/EPaymentStatus.ts create mode 100644 storefront/modules/payment/models/Payment.ts create mode 100644 storefront/modules/payment/services/PaymentService.ts diff --git a/order/src/main/java/com/yas/order/kafka/consumer/OrderStatusConsumer.java b/order/src/main/java/com/yas/order/kafka/consumer/OrderStatusConsumer.java index e575a1867c..0c78375085 100644 --- a/order/src/main/java/com/yas/order/kafka/consumer/OrderStatusConsumer.java +++ b/order/src/main/java/com/yas/order/kafka/consumer/OrderStatusConsumer.java @@ -58,7 +58,9 @@ public void listen(ConsumerRecord consumerRecord) { private void processCheckoutEvent(JsonObject valueObject) { Optional.ofNullable(valueObject) - .filter(value -> value.has("after")) + .filter( + value -> value.has("op") && "u".equals(value.get("op").getAsString()) + ) .map(value -> value.getAsJsonObject("after")) .ifPresent(this::handleAfterJson); } diff --git a/order/src/main/java/com/yas/order/kafka/consumer/PaymentUpdateConsumer.java b/order/src/main/java/com/yas/order/kafka/consumer/PaymentUpdateConsumer.java index e3be435c6a..0f78037556 100644 --- a/order/src/main/java/com/yas/order/kafka/consumer/PaymentUpdateConsumer.java +++ b/order/src/main/java/com/yas/order/kafka/consumer/PaymentUpdateConsumer.java @@ -89,20 +89,12 @@ private void updateCheckOut(String checkoutId, String paymentProviderCheckoutId) 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); + ObjectNode attributesNode = getAttributesNode(objectMapper, checkout.getAttributes()); attributesNode.put(Constants.Column.CHECKOUT_ATTRIBUTES_PAYMENT_PROVIDER_CHECKOUT_ID_FIELD, paymentProviderCheckoutId); + checkout.setAttributes(convertObjectToString(objectMapper, attributesNode)); - return attributesNode; + checkoutService.updateCheckout(checkout); } } \ No newline at end of file diff --git a/order/src/main/java/com/yas/order/viewmodel/checkout/CheckoutVm.java b/order/src/main/java/com/yas/order/viewmodel/checkout/CheckoutVm.java index 8a99b697d2..33035a421f 100644 --- a/order/src/main/java/com/yas/order/viewmodel/checkout/CheckoutVm.java +++ b/order/src/main/java/com/yas/order/viewmodel/checkout/CheckoutVm.java @@ -1,6 +1,9 @@ package com.yas.order.viewmodel.checkout; import com.yas.order.model.Checkout; +import com.yas.order.model.enumeration.CheckoutProgress; +import com.yas.order.model.enumeration.CheckoutState; +import com.yas.order.model.enumeration.PaymentMethod; import java.math.BigDecimal; import java.util.Set; import lombok.Builder; @@ -11,6 +14,11 @@ public record CheckoutVm( String email, String note, String couponCode, + CheckoutState checkoutState, + CheckoutProgress progress, + PaymentMethod paymentMethodId, + String attributes, + String lastError, BigDecimal totalAmount, BigDecimal totalDiscountAmount, Set checkoutItemVms @@ -22,6 +30,11 @@ public static CheckoutVm fromModel(Checkout checkout, Set checko .email(checkout.getEmail()) .note(checkout.getNote()) .couponCode(checkout.getCouponCode()) + .checkoutState(checkout.getCheckoutState()) + .progress(checkout.getProgress()) + .paymentMethodId(checkout.getPaymentMethodId()) + .attributes(checkout.getAttributes()) + .lastError(checkout.getLastError()) .checkoutItemVms(checkoutItemVms) .build(); } diff --git a/order/src/test/java/com/yas/order/controller/CheckoutControllerTest.java b/order/src/test/java/com/yas/order/controller/CheckoutControllerTest.java index 19fc7ce794..a95dd214d9 100644 --- a/order/src/test/java/com/yas/order/controller/CheckoutControllerTest.java +++ b/order/src/test/java/com/yas/order/controller/CheckoutControllerTest.java @@ -12,6 +12,9 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectWriter; import com.yas.order.OrderApplication; +import com.yas.order.model.enumeration.CheckoutProgress; +import com.yas.order.model.enumeration.CheckoutState; +import com.yas.order.model.enumeration.PaymentMethod; import com.yas.order.service.CheckoutService; import com.yas.order.viewmodel.checkout.CheckoutItemPostVm; import com.yas.order.viewmodel.checkout.CheckoutItemVm; @@ -201,6 +204,11 @@ private CheckoutVm getCheckoutVm() { "user@example.com", "Please deliver after 5 PM", "DISCOUNT20", + CheckoutState.PAYMENT_PROCESSING, + CheckoutProgress.PROMOTION_CODE_APPLIED, + PaymentMethod.PAYPAL, + null, + null, BigDecimal.valueOf(900), BigDecimal.valueOf(9), checkoutItemVms diff --git a/order/src/test/java/com/yas/order/kafka/consumer/OrderStatusConsumerTest.java b/order/src/test/java/com/yas/order/kafka/consumer/OrderStatusConsumerTest.java index d6bc855f4e..1e95152491 100644 --- a/order/src/test/java/com/yas/order/kafka/consumer/OrderStatusConsumerTest.java +++ b/order/src/test/java/com/yas/order/kafka/consumer/OrderStatusConsumerTest.java @@ -34,7 +34,7 @@ class OrderStatusConsumerTest { private OrderStatusConsumer orderStatusConsumer; - private final String jsonRecord = "{\"after\": {" + private final String jsonRecord = "{\"op\": \"u\", \"after\": {" + " \"status\": \"PAYMENT_PROCESSING\"," + " \"progress\": \"STOCK_LOCKED\"," + " \"id\": 12345," diff --git a/payment/src/main/java/com/yas/payment/config/JsonConfig.java b/payment/src/main/java/com/yas/payment/config/JsonConfig.java new file mode 100644 index 0000000000..fa2c9f7997 --- /dev/null +++ b/payment/src/main/java/com/yas/payment/config/JsonConfig.java @@ -0,0 +1,22 @@ +package com.yas.payment.config; + + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.gson.Gson; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + + +@Configuration +public class JsonConfig { + + @Bean + public ObjectMapper objectMapper() { + return new ObjectMapper(); + } + + @Bean + public Gson gson() { + return new Gson(); + } +} \ No newline at end of file diff --git a/payment/src/main/java/com/yas/payment/controller/PaymentController.java b/payment/src/main/java/com/yas/payment/controller/PaymentController.java index b4a7ca4743..e289e4d3de 100644 --- a/payment/src/main/java/com/yas/payment/controller/PaymentController.java +++ b/payment/src/main/java/com/yas/payment/controller/PaymentController.java @@ -30,8 +30,10 @@ public InitPaymentResponseVm initPayment(@Valid @RequestBody InitPaymentRequestV } @PostMapping(value = "/capture") - public CapturePaymentResponseVm capturePayment(@Valid @RequestBody CapturePaymentRequestVm capturePaymentRequestVM) { - return paymentService.capturePayment(capturePaymentRequestVM); + public CapturePaymentResponseVm capturePayment( + @Valid @RequestBody CapturePaymentRequestVm capturePaymentRequestVm + ) { + return paymentService.capturePayment(capturePaymentRequestVm); } @GetMapping(value = "/cancel") diff --git a/payment/src/main/java/com/yas/payment/kafka/consumer/PaymentCreateConsumer.java b/payment/src/main/java/com/yas/payment/kafka/consumer/PaymentCreateConsumer.java index 03ca84bd10..81931a607f 100644 --- a/payment/src/main/java/com/yas/payment/kafka/consumer/PaymentCreateConsumer.java +++ b/payment/src/main/java/com/yas/payment/kafka/consumer/PaymentCreateConsumer.java @@ -1,5 +1,10 @@ package com.yas.payment.kafka.consumer; +import static com.yas.payment.utils.JsonUtils.convertObjectToString; +import static com.yas.payment.utils.JsonUtils.getAttributesNode; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; import com.google.gson.Gson; import com.google.gson.JsonElement; import com.google.gson.JsonObject; @@ -27,6 +32,7 @@ public class PaymentCreateConsumer { private static final Logger LOGGER = LoggerFactory.getLogger(PaymentCreateConsumer.class); private final PaymentService paymentService; + private final ObjectMapper objectMapper; private final Gson gson; @KafkaListener( @@ -92,6 +98,10 @@ private void createOrderOnPaypal(Payment payment) { payment.setPaymentStatus(PaymentStatus.PROCESSING); payment.setPaymentProviderCheckoutId(initPaymentResponseVm.paymentId()); + ObjectNode attributesNode = getAttributesNode(objectMapper, payment.getAttributes()); + attributesNode.put(Constants.Column.REDIRECT_URL_ID_COLUMN, initPaymentResponseVm.redirectUrl()); + payment.setAttributes(convertObjectToString(objectMapper, attributesNode)); + paymentService.updatePayment(payment); } else { LOGGER.warn(Constants.ErrorCode.ORDER_CREATION_FAILED, payment.getId()); diff --git a/payment/src/main/java/com/yas/payment/model/Payment.java b/payment/src/main/java/com/yas/payment/model/Payment.java index 0a9ca2d80e..7a2a455644 100644 --- a/payment/src/main/java/com/yas/payment/model/Payment.java +++ b/payment/src/main/java/com/yas/payment/model/Payment.java @@ -2,6 +2,7 @@ import com.yas.payment.model.enumeration.PaymentMethod; import com.yas.payment.model.enumeration.PaymentStatus; +import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.EnumType; import jakarta.persistence.Enumerated; @@ -15,6 +16,8 @@ import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; +import org.hibernate.annotations.JdbcTypeCode; +import org.hibernate.type.SqlTypes; @Entity @Table(name = "payment") @@ -49,4 +52,8 @@ public class Payment extends AbstractAuditEntity { private String paymentProviderCheckoutId; + @JdbcTypeCode(SqlTypes.JSON) + @Column(name = "attributes", columnDefinition = "jsonb") + private String attributes; + } diff --git a/payment/src/main/java/com/yas/payment/utils/Constants.java b/payment/src/main/java/com/yas/payment/utils/Constants.java index 1b3be05f90..d98f69308c 100644 --- a/payment/src/main/java/com/yas/payment/utils/Constants.java +++ b/payment/src/main/java/com/yas/payment/utils/Constants.java @@ -22,7 +22,7 @@ private Column() { public static final String ID_COLUMN = "id"; // Column name of Checkout table - public static final String REDIRECT_URL_ID_COLUMN = "redirect-url"; + public static final String REDIRECT_URL_ID_COLUMN = "redirect_url"; } public final class Message { diff --git a/payment/src/main/java/com/yas/payment/utils/JsonUtils.java b/payment/src/main/java/com/yas/payment/utils/JsonUtils.java new file mode 100644 index 0000000000..4e99823cbd --- /dev/null +++ b/payment/src/main/java/com/yas/payment/utils/JsonUtils.java @@ -0,0 +1,33 @@ +package com.yas.payment.utils; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.yas.commonlibrary.exception.BadRequestException; + +public class JsonUtils { + + private JsonUtils() { + throw new UnsupportedOperationException(); + } + + public static String convertObjectToString(ObjectMapper objectMapper, Object value) { + try { + return objectMapper.writeValueAsString(value); + } catch (JsonProcessingException e) { + throw new BadRequestException(Constants.ErrorCode.CANNOT_CONVERT_TO_STRING, value); + } + } + + 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 BadRequestException("Invalid Json: {}", attributes); + } + } +} \ No newline at end of file diff --git a/payment/src/main/java/com/yas/payment/viewmodel/PaymentVm.java b/payment/src/main/java/com/yas/payment/viewmodel/PaymentVm.java index 5f3c3eb9a0..b655c36247 100644 --- a/payment/src/main/java/com/yas/payment/viewmodel/PaymentVm.java +++ b/payment/src/main/java/com/yas/payment/viewmodel/PaymentVm.java @@ -15,7 +15,9 @@ public record PaymentVm( PaymentMethod paymentMethod, PaymentStatus paymentStatus, String gatewayTransactionId, - String failureMessage + String failureMessage, + String paymentProviderCheckoutId, + String attributes ) { public static PaymentVm fromModel(Payment payment) { @@ -28,7 +30,9 @@ public static PaymentVm fromModel(Payment payment) { payment.getPaymentMethod(), payment.getPaymentStatus(), payment.getGatewayTransactionId(), - payment.getFailureMessage() + payment.getFailureMessage(), + payment.getPaymentProviderCheckoutId(), + payment.getAttributes() ); } } \ No newline at end of file diff --git a/payment/src/main/resources/db/changelog/ddl/changelog-0005.sql b/payment/src/main/resources/db/changelog/ddl/changelog-0005.sql new file mode 100644 index 0000000000..a824753ab3 --- /dev/null +++ b/payment/src/main/resources/db/changelog/ddl/changelog-0005.sql @@ -0,0 +1,2 @@ +ALTER TABLE IF EXISTS "payment" +ADD COLUMN attributes JSONB; \ No newline at end of file diff --git a/payment/src/test/java/com/yas/payment/kafka/consumer/PaymentCreateConsumerTest.java b/payment/src/test/java/com/yas/payment/kafka/consumer/PaymentCreateConsumerTest.java index 911880ac8e..7bf3c6a9d2 100644 --- a/payment/src/test/java/com/yas/payment/kafka/consumer/PaymentCreateConsumerTest.java +++ b/payment/src/test/java/com/yas/payment/kafka/consumer/PaymentCreateConsumerTest.java @@ -8,6 +8,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import com.fasterxml.jackson.databind.ObjectMapper; import com.google.gson.Gson; import com.yas.commonlibrary.exception.BadRequestException; import com.yas.payment.model.Payment; @@ -31,8 +32,9 @@ class PaymentCreateConsumerTest { @BeforeEach void setUp() { paymentService = mock(PaymentService.class); + ObjectMapper objectMapper = new ObjectMapper(); Gson gson = new Gson(); - paymentCreateConsumer = new PaymentCreateConsumer(paymentService, gson); + paymentCreateConsumer = new PaymentCreateConsumer(paymentService, objectMapper, gson); } @Test diff --git a/storefront/modules/common/models/EPaymentMethod.ts b/storefront/modules/common/models/EPaymentMethod.ts new file mode 100644 index 0000000000..e399862508 --- /dev/null +++ b/storefront/modules/common/models/EPaymentMethod.ts @@ -0,0 +1,5 @@ +export enum EPaymentMethod { + COD = 'COD', + BANKING = 'BANKING', + PAYPAL = 'PAYPAL', +} diff --git a/storefront/modules/order/components/CheckOutDetail.tsx b/storefront/modules/order/components/CheckOutDetail.tsx index e1eae4066e..74e29628b4 100644 --- a/storefront/modules/order/components/CheckOutDetail.tsx +++ b/storefront/modules/order/components/CheckOutDetail.tsx @@ -137,6 +137,7 @@ const CheckOutDetail = ({ orderItems, disablePaymentProcess, setPaymentMethod }:

+ ); diff --git a/storefront/modules/order/models/Checkout.ts b/storefront/modules/order/models/Checkout.ts index 12f1e1497d..de5f2f6825 100644 --- a/storefront/modules/order/models/Checkout.ts +++ b/storefront/modules/order/models/Checkout.ts @@ -1,11 +1,19 @@ +import { EPaymentMethod } from '@/modules/common/models/EPaymentMethod'; import { CheckoutItem } from './CheckoutItem'; +import { ECheckoutState } from './ECheckoutState'; +import { ECheckoutProgress } from './ECheckoutProgress'; export type Checkout = { id?: string; email: string; note?: string; couponCode?: string; + checkoutState?: ECheckoutState; + progress?: ECheckoutProgress; + paymentMethodId?: EPaymentMethod; + attributes?: string; + lastError?: string; totalAmount: number; totalDiscountAmount: number; - checkoutItemPostVms: CheckoutItem[]; + checkoutItemVms: CheckoutItem[]; }; diff --git a/storefront/modules/order/models/ECheckoutProgress.ts b/storefront/modules/order/models/ECheckoutProgress.ts new file mode 100644 index 0000000000..9a42e573d4 --- /dev/null +++ b/storefront/modules/order/models/ECheckoutProgress.ts @@ -0,0 +1,9 @@ +export enum ECheckoutProgress { + INIT = 'INIT', + PROMOTION_CODE_APPLIED = 'PROMOTION_CODE_APPLIED', + PROMOTION_CODE_APPLIED_FAILED = 'PROMOTION_CODE_APPLIED_FAILED', + STOCK_LOCKED = 'STOCK_LOCKED', + STOCK_LOCKED_FAILED = 'STOCK_LOCKED_FAILED', + PAYMENT_CREATED = 'PAYMENT_CREATED', + PAYMENT_CREATED_FAILED = 'PAYMENT_CREATED_FAILED', +} diff --git a/storefront/modules/order/models/ECheckoutState.ts b/storefront/modules/order/models/ECheckoutState.ts new file mode 100644 index 0000000000..afc72210e8 --- /dev/null +++ b/storefront/modules/order/models/ECheckoutState.ts @@ -0,0 +1,10 @@ +export enum ECheckoutState { + COMPLETED = 'COMPLETED', + PENDING = 'PENDING', + LOCK = 'LOCK', + CHECKED_OUT = 'CHECKED_OUT', + PAYMENT_PROCESSING = 'PAYMENT_PROCESSING', + PAYMENT_FAILED = 'PAYMENT_FAILED', + PAYMENT_CONFIRMED = 'PAYMENT_CONFIRMED', + FULFILLED = 'FULFILLED', +} diff --git a/storefront/modules/order/services/OrderService.ts b/storefront/modules/order/services/OrderService.ts index be46b296b7..dd2a11e651 100644 --- a/storefront/modules/order/services/OrderService.ts +++ b/storefront/modules/order/services/OrderService.ts @@ -33,8 +33,14 @@ export async function createCheckout(checkout: Checkout): Promise { const response = await apiClientService.get(`${baseUrl}/checkouts/${id}`); if (response.status >= 200 && response.status < 300) return response.json(); throw new Error(response.statusText); } + +export async function processPayment(id: string): Promise { + const response = await apiClientService.post(`${baseUrl}/checkouts/${id}/process-payment`, {}); + if (response.status >= 200 && response.status < 300) return; + throw new Error(response.statusText); +} diff --git a/storefront/modules/payment/models/EPaymentStatus.ts b/storefront/modules/payment/models/EPaymentStatus.ts new file mode 100644 index 0000000000..dd6ac2602e --- /dev/null +++ b/storefront/modules/payment/models/EPaymentStatus.ts @@ -0,0 +1,7 @@ +export enum EPaymentStatus { + NEW = 'NEW', + PROCESSING = 'PROCESSING', + PENDING = 'PENDING', + COMPLETED = 'COMPLETED', + CANCELLED = 'CANCELLED', +} diff --git a/storefront/modules/payment/models/Payment.ts b/storefront/modules/payment/models/Payment.ts new file mode 100644 index 0000000000..41570e93ce --- /dev/null +++ b/storefront/modules/payment/models/Payment.ts @@ -0,0 +1,16 @@ +import { EPaymentMethod } from '../../common/models/EPaymentMethod'; +import { EPaymentStatus } from './EPaymentStatus'; + +export type Payment = { + id?: number; + orderId?: number; + checkoutId: string; + amount: number; + paymentFee?: number; + paymentMethod?: EPaymentMethod; + paymentStatus?: EPaymentStatus; + gatewayTransactionId?: string; + failureMessage?: string; + paymentProviderCheckoutId?: string; + attributes?: string; +}; diff --git a/storefront/modules/payment/services/PaymentService.ts b/storefront/modules/payment/services/PaymentService.ts new file mode 100644 index 0000000000..ed25f1c362 --- /dev/null +++ b/storefront/modules/payment/services/PaymentService.ts @@ -0,0 +1,9 @@ +import apiClientService from '@/common/services/ApiClientService'; + +const baseUrl = '/api/payment'; + +export async function getPaymentById(id: string) { + const response = await apiClientService.get(`${baseUrl}/storefront/payments/${id}`); + if (response.status >= 200 && response.status < 300) return response.json(); + throw new Error(response.statusText); +} diff --git a/storefront/pages/cart/index.tsx b/storefront/pages/cart/index.tsx index 53479d2612..f8fdbd61c3 100644 --- a/storefront/pages/cart/index.tsx +++ b/storefront/pages/cart/index.tsx @@ -205,7 +205,7 @@ const Cart = () => { couponCode: couponCode, totalAmount: totalPrice, totalDiscountAmount: discountMoney, - checkoutItemPostVms: checkoutItems, + checkoutItemVms: checkoutItems, }; createCheckout(checkout) .then((res) => { diff --git a/storefront/pages/checkout/[id].tsx b/storefront/pages/checkout/[id].tsx index 27980ec441..710e88a297 100644 --- a/storefront/pages/checkout/[id].tsx +++ b/storefront/pages/checkout/[id].tsx @@ -4,12 +4,16 @@ import { SubmitHandler, useForm } from 'react-hook-form'; import { Order } from '@/modules/order/models/Order'; import CheckOutDetail from 'modules/order/components/CheckOutDetail'; import { OrderItem } from '@/modules/order/models/OrderItem'; -import { useEffect, useState } from 'react'; +import { useEffect, useRef, useState } from 'react'; import { useRouter } from 'next/router'; import { Input } from 'common/items/Input'; import { Address } from '@/modules/address/models/AddressModel'; import AddressForm from '@/modules/address/components/AddressForm'; -import { createOrder, getCheckoutById } from '@/modules/order/services/OrderService'; +import { + createOrder, + getCheckoutById, + processPayment, +} from '@/modules/order/services/OrderService'; import * as yup from 'yup'; import { createUserAddress, @@ -17,12 +21,17 @@ import { } from '@/modules/customer/services/CustomerService'; import ModalAddressList from '@/modules/order/components/ModalAddressList'; import CheckOutAddress from '@/modules/order/components/CheckOutAddress'; -import { Checkout } from '@/modules/order/models/Checkout'; +import type { Checkout } from '@/modules/order/models/Checkout'; import { toastError } from '@/modules/catalog/services/ToastService'; import { CheckoutItem } from '@/modules/order/models/CheckoutItem'; import { initPaymentPaypal } from '@/modules/paymentPaypal/services/PaymentPaypalService'; import { InitPaymentPaypalRequest } from '@/modules/paymentPaypal/models/InitPaymentPaypalRequest'; import SpinnerComponent from '@/common/components/SpinnerComponent'; +import { getPaymentById } from '@/modules/payment/services/PaymentService'; +import { Payment } from '@/modules/payment/models/Payment'; +import { ECheckoutState } from '@/modules/order/models/ECheckoutState'; +import { ECheckoutProgress } from '@/modules/order/models/ECheckoutProgress'; +import { EPaymentStatus } from '@/modules/payment/models/EPaymentStatus'; const phoneRegExp = /^((\+[1-9]{1,4}[ -]*)|(\([0-9]{2,3}\)[ -]*)|[0-9]{2,4}[ -]*)?[0-9]{3,4}?[ -]*[0-9]{3,4}?$/; @@ -40,6 +49,11 @@ const Checkout = () => { const router = useRouter(); const { id } = router.query; const [checkout, setCheckout] = useState(); + + const CREATE_PAYMENT_TIMEOUT = 10; + const elapsedTimeRef = useRef(0); + let intervalId: NodeJS.Timeout | undefined; + const { handleSubmit, register, @@ -142,7 +156,12 @@ const Checkout = () => { }); }; - const onSubmitForm: SubmitHandler = async (data) => { + const onSubmitForm: SubmitHandler = async (data, event) => { + const nativeEvent = event?.nativeEvent as SubmitEvent; + + const clickedButton = nativeEvent?.submitter as HTMLButtonElement | undefined; + const clickedButtonId = clickedButton?.id; + let isValidate = true; if (addShippingAddress) { @@ -202,18 +221,114 @@ const Checkout = () => { order.orderItemPostVms = orderItems; setIsShowSpinner(true); setDisableProcessPayment(true); - createOrder(order) - .then(() => { - handleCheckOutProcess(order); - }) - .catch(() => { - setIsShowSpinner(false); - setDisableProcessPayment(false); - toast.error('Place order failed'); - }); + + if (clickedButtonId === 'newProcessToPaymentButton') { + processPayment(id as string); + handlePaymentProcess(order); + } else { + createOrder(order) + .then(() => { + handleCheckOutProcess(order); + }) + .catch(() => { + setIsShowSpinner(false); + setDisableProcessPayment(false); + toast.error('Place order failed'); + }); + } + } + }; + + const fetchOrderCheckout = async (id: string): Promise => { + try { + const res = await getCheckoutById(id); + return res; + } catch (err) { + console.log('Fetch Order Checkout Failed: ', err); + return null; + } + }; + + const fetchOrderPayment = async (id: string): Promise => { + try { + const res = await getPaymentById(id); + return res; + } catch (err) { + console.log('Fetch Order Payment Failed: ', err); + return null; + } + }; + + const handlePaymentProcessForPaypal = () => { + intervalId = setInterval(async () => { + try { + elapsedTimeRef.current += 2; + + const orderCheckout = await fetchOrderCheckout(id as string); + + if (isCreatedPayment(orderCheckout)) { + const attributes = orderCheckout?.attributes + ? JSON.parse(orderCheckout.attributes) + : null; + const orderPayment = await fetchOrderPayment(attributes?.payment_id); + + if (isPaymentProcessingAndExistedOrderId(orderPayment)) { + redirectToPayment(orderPayment); + clearInterval(intervalId); + } + } else if (elapsedTimeRef.current >= CREATE_PAYMENT_TIMEOUT) { + handleErrorCreatingOrder(); + } + } catch (err) { + console.error('Handler check status Checkout and Payment Failed: ', err); + handleErrorCreatingOrder(); + } + }, 2000); + }; + + const isCreatedPayment = (orderCheckout: Checkout | null): boolean => { + return ( + orderCheckout?.checkoutState === ECheckoutState.PAYMENT_PROCESSING && + orderCheckout?.progress === ECheckoutProgress.PAYMENT_CREATED + ); + }; + + const isPaymentProcessingAndExistedOrderId = (orderPayment: Payment | null): boolean => { + return ( + orderPayment?.paymentStatus === EPaymentStatus.PROCESSING && + orderPayment.paymentProviderCheckoutId !== null + ); + }; + + const redirectToPayment = (orderPayment: Payment | null) => { + const attributes = orderPayment?.attributes ? JSON.parse(orderPayment.attributes) : null; + window.location.replace(attributes.redirect_url); + }; + + const handlePaymentProcess = (order: Order) => { + const paymentMethod = order?.paymentMethod ?? ''; + switch (paymentMethod.toUpperCase()) { + case 'COD': + processCodPayment(order); + break; + case 'PAYPAL': + handlePaymentProcessForPaypal(); + break; + default: + setIsShowSpinner(false); + setDisableProcessPayment(false); + toast.error('Place order failed'); } }; + const handleErrorCreatingOrder = () => { + setIsShowSpinner(false); + clearInterval(intervalId); + elapsedTimeRef.current = 0; + setDisableProcessPayment(false); + toast.error('Payment processing failed, please try again later.'); + }; + const handleCheckOutProcess = async (order: Order) => { const paymentMethod = order.paymentMethod ?? ''; switch (paymentMethod.toUpperCase()) { @@ -254,7 +369,12 @@ const Checkout = () => {
-
void handleSubmit(onSubmitForm)(event)}> + { + event.preventDefault(); + handleSubmit((data) => onSubmitForm(data, event))(event); + }} + >

Shipping Address