Advertisement
ronaldkwandy

payment-template.tsx

Jul 14th, 2022
1,198
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. import PinBottomSheet from 'Components/organism/bottom-sheet/pin-bs';
  2. import { AuthHandler } from 'handlers/auth-handler';
  3. import {
  4.   arrayIntersects,
  5.   isMobile,
  6.   moneyDot,
  7.   getWithdrawAmount,
  8.   getTopUpAmount,
  9.   getDefault,
  10.   titleCase,
  11.   isset,
  12.   stringToBoolean,
  13.   exchangeRateConversionHelper,
  14. } from 'Helpers/common-helper';
  15. import { ROUTER_NAME, Types } from 'Helpers/navigation-helper';
  16. import { useRouter } from 'next/router';
  17. import React, { useEffect, useState, useMemo, useRef } from 'react';
  18. import { useDispatch, useSelector, shallowEqual } from 'react-redux';
  19. import navigationObject from 'Redux/reducers/navigation';
  20. import { CardPaymentRecap, Payments } from 'Components/molecules/card/card-payment-recap';
  21. import { UserHandler } from 'handlers/user-handler';
  22. import { WalletHandler } from 'handlers/wallet-handler';
  23. import { CouponHandler } from 'handlers/coupon-handler';
  24. import { CoinHandler } from 'handlers/coin-handler';
  25. import {
  26.   DEFAULT_PAYMENT,
  27.   dompetkuEngagementDialog,
  28.   headerTitleHelper,
  29.   IAllExtraPrice,
  30.   openSandbox,
  31.   paymentConfirmationRoutes,
  32.   paymentMethodDenomFeeHelper,
  33.   paymentMethodFeeHelper,
  34.   paymentWithOvoRoutes,
  35.   PAYMENT_SOURCE,
  36.   IPaidItem,
  37.   IItemsToBePaid,
  38.   checkItemsToBePaidSource,
  39. } from 'Helpers/payment-helper';
  40. import { ITransactionData } from 'Helpers/transaction-helper';
  41. import { TransactionHandler } from 'handlers/transaction-handler';
  42. import HeaderBack from 'Components/organism/header/header-back';
  43. import PaymentSkeleton from 'Components/atoms/skeleton/payment-skeleton';
  44. import Divider from '@itemku/itemku-arcade/components/atoms/marker/divider';
  45. import CardPaymentMethod from 'Components/molecules/card/card-payment-method';
  46. import Banner from 'Components/organism/banner/banner';
  47. import ButtonOutlined from 'Components/molecules/button/button-outlined';
  48. import UserDetailInput from 'Components/organism/payment/user-detail-input';
  49. import ButtonText from 'Components/molecules/button/button-text';
  50. import Dialog from 'Components/organism/dialog';
  51. import StickyButtonWithText from 'Components/organism/sticky/sticky-button-with-text';
  52. import CardPremiumOption from 'Components/organism/payment/card-premium-option';
  53. import { routes } from 'Helpers/route-helper';
  54. import { TransactionBoundary } from 'Components/templates/transaction-boundary';
  55. import { analyticsEvent } from 'Helpers/analytics-helper';
  56. import analyticsObject from 'Redux/reducers/analytics';
  57. import { IReducers } from 'Redux/reducers';
  58. import paymentObject from 'Redux/reducers/payment';
  59. import { useLogin, putLocalStorage, useLocalStorage } from 'Helpers/hook-helper';
  60. import { withdrawDoneUrlKey } from 'Helpers/withdraw-helper';
  61. import { ICreateVendorVoucherTransactionPayload } from 'Redux/reducers/transaction-voucher';
  62. import Head from 'next/head';
  63. import {
  64.   couponValidationDataKey,
  65.   ICouponValidationData,
  66.   validateEquippedCoupon,
  67.   calculateCouponDiscount,
  68.   paymentMethodCouponValidation,
  69.   couponSessionKey,
  70.   ICouponDetail,
  71.   paymentSourceCouponValidation,
  72. } from 'Helpers/coupon-helper';
  73. import statusObject from 'Redux/reducers/status';
  74. import couponObject from 'Redux/reducers/coupon';
  75. import { ERR_TYPES, COUPON_ERROR_MESSAGE, getErrorMessage } from 'Helpers/error-helper';
  76. import CardCoupon from 'Components/molecules/card/card-coupon';
  77. import CardDompetkuEngagement from 'Components/organism/payment/card-dompetku-engagement';
  78. import { IModal } from '../dialog';
  79. import ItemkuWorldCoin from 'Components/organism/payment/itemku-world-coin';
  80. import coinObject, { IRequestValidCoin } from 'Redux/reducers/coin';
  81. import loadingComponentObject from 'Redux/reducers/loading-component';
  82. import { PREMIUM_BUYER_TIER } from 'Helpers/constants';
  83. import ShoppingRecapSection from 'components-update/templates/payment/sections/shopping-recap-section';
  84. import CardProductSubscription from 'components-update/organism/payment/card-product-subscription';
  85. import productDetailObject from 'Redux/reducers/product-detail';
  86. import { addDays, format } from 'date-fns';
  87. import persistObject from 'Redux/reducers/persist-state';
  88. import { hiddenFeatureConfig, HIDDEN_FEATURE_FOR_GLOBAL, REGION_CODE, useLanguage } from 'Helpers/translation-helper';
  89. import DivHtmlContent from 'Components/atoms/div-html-content';
  90. import { CurrencyHandler } from 'handlers/currency-handler';
  91.  
  92. type DompetkuButton = { label: string; desc: any; onClick: () => void };
  93. type ItemToPay = { itemInfo: string; serverGroup: string; storeName: string; price: number; totalItem: number };
  94. type ItemToPayOS = { name: string; nominal: number; qty: number };
  95. export type UserDetail = {
  96.   isPhoneVerified: boolean;
  97.   isEmailVerified: boolean;
  98.   hasSetupPIN: boolean;
  99.   dompetkuBalance: number;
  100.   totalPayment: number;
  101. };
  102.  
  103. const initialUserData: UserDetail & { id: any; usedCoupon: string[] } = {
  104.   id: 123,
  105.   isEmailVerified: true,
  106.   isPhoneVerified: true,
  107.   hasSetupPIN: true,
  108.   dompetkuBalance: 0,
  109.   totalPayment: 0,
  110.   usedCoupon: [],
  111. };
  112.  
  113. type Input = { name: string; phoneNumber: string };
  114. interface IProps {
  115.   paymentSource: PAYMENT_SOURCE;
  116.   paymentMethodRoute: ROUTER_NAME;
  117. }
  118.  
  119. const PaymentTemplate = ({ paymentSource, paymentMethodRoute }: IProps) => {
  120.   const router = useRouter();
  121.   const dispatch = useDispatch();
  122.   const { userData } = UserHandler.useUserData();
  123.   const { checkPin, requestResetPinOTP } = AuthHandler.useCheckPin(); // need request reset on handler
  124.   const isMobileDevice = React.useRef(isMobile());
  125.   const [isLoading, setIsLoading] = useState(true);
  126.   const [userDetail, setUserDetail] = useState<UserDetail & { id: any; usedCoupon: string[] }>(initialUserData);
  127.   const [itemToPay, setItemToPay] = useState<ItemToPay[] | ItemToPayOS[]>([]);
  128.   const [extraCharge, setExtraCharge] = useState<Payments>([]);
  129.   const [totalPayment, setTotalPayment] = useState<number>(0);
  130.   const [totalDiscountPrice, setTotalDiscountPrice] = useState<number>(0);
  131.   const [totalExtraCharge, setTotalExtraCharge] = useState<number>(0);
  132.   const [errorBanner, setErrorBanner] = useState(false);
  133.   const [errorVoucherBuyerInfo, setErrorVoucherBuyerInfo] = useState({ name: '', phoneNumber: '' });
  134.   const [dialog, setDialog] = useState({
  135.     dialogProps: {},
  136.     dompetku: false,
  137.     failedTransaction: false,
  138.     loadPayment: false,
  139.     closePayment: false,
  140.     cancelPayment: false,
  141.   });
  142.   const [dompetkuButton, setDompetkuButton] = useState<DompetkuButton>();
  143.   const isAlreadySuccessSendRequest = useRef(false);
  144.   const [latestPaymentErrorBanner, setLatestPaymentErrorBanner] = useState('');
  145.   const [pinBottomSheet, setPinBottomSheet] = useState({
  146.     isOpen: false,
  147.     wrongPin: false,
  148.     errorMessage: undefined as undefined | string,
  149.     pinNumber: undefined as undefined | string,
  150.     isSuccess: false,
  151.   });
  152.   const [convertedTotalPayment, setConvertedTotalPayment] = useState<number>(0);
  153.   const [convertedExtraCharge, setConvertedExtraCharge] = useState<number>(0);
  154.  
  155.   // Payment using BCA Virtual Account
  156.   const [paymentBCA, setPaymentBCA] = useState<boolean>(false);
  157.   const [unpayBCAPayment, setUnpayBCAPayment] = useState<boolean>(false); // initial value true for testing
  158.   const bcaPaymentMethodId = useSelector(
  159.     (state: IReducers) => state.configReducers.configList.BCA_VA_PAYMENT_METHOD_ID,
  160.   );
  161.  
  162.   const couponValidationLoading = useSelector(
  163.     (state: IReducers) => state.statusReducers.loadingState[couponObject.names.requestValidateSelectedCoupon],
  164.     shallowEqual,
  165.   );
  166.  
  167.   const region = useSelector((state: IReducers) => state.regionReducers.regionData.country_code);
  168.   const isGlobal: boolean = region !== REGION_CODE.INDONESIA;
  169.  
  170.   const { coinLoading } = CoinHandler.useCoin();
  171.   const handleGoToPaymentMethod = () => {
  172.     document.body.scrollIntoView();
  173.     dispatch(navigationObject.actions.routeNavigateReplaceTo({
  174.       routeName: paymentMethodRoute,
  175.       query: isProductSubs === true
  176.         ? {
  177.           ...router.query,
  178.           is_subscription: isSubscription,
  179.         }
  180.         : router.query,
  181.     }));
  182.   };
  183.   const isAnalyticsSent = useSelector((state: IReducers) => state.paymentReducers.isAnalyticsSent);
  184.   // IWorld Coin
  185.   const isUsingCoin = useSelector((state: IReducers) => state.paymentReducers.isUsingCoin);
  186.  
  187.   const handleToggleUsingCoin = (isUsed: boolean) => {
  188.     dispatch(paymentObject.actions.setIsUsingCoin({
  189.       ...isUsingCoin,
  190.       [itemKey]: isUsed,
  191.     }));
  192.   };
  193.   // Subscription
  194.   const [isSubscription, setSubscription] = useState<boolean>(stringToBoolean(router?.query?.is_subscription as string || '') || false);
  195.  
  196.   const [pinInteractionValue, setPinInteractionValue] = useState('');
  197.   const handleInputPin = (value: string) => {
  198.     setPinInteractionValue(value);
  199.     const pinCallback = (pinStatus: boolean, msg: any) => {
  200.       if (pinStatus) {
  201.         setPinBottomSheet({
  202.           ...pinBottomSheet,
  203.           isOpen: false,
  204.           wrongPin: false,
  205.           errorMessage: undefined,
  206.           pinNumber: value,
  207.           isSuccess: true,
  208.         });
  209.       } else {
  210.         setPinBottomSheet({ ...pinBottomSheet, wrongPin: true, errorMessage: msg });
  211.       }
  212.     };
  213.     if (value.length >= 6) {
  214.       checkPin(pinCallback, value);
  215.     } else {
  216.       setPinBottomSheet({ ...pinBottomSheet, wrongPin: false });
  217.     }
  218.   };
  219.  
  220.   // Handle premium option //
  221.   const [itemKey, setItemKey] = useState('');
  222.   const premiumOption = useSelector((state: IReducers) => state.paymentReducers.isPremiumTicked);
  223.  
  224.   useEffect(() => {
  225.     if (userData?.is_user_member) {
  226.       dispatch(paymentObject.actions.setIsPremiumTicked({
  227.         ...premiumOption,
  228.         [itemKey]: false,
  229.       }));
  230.     }
  231.   }, [userData, itemKey]);
  232.   // End Handle premium option //
  233.  
  234.   // Items to be Paid & Payment Recap //
  235.   const [noItemToBePaid, setNoItemToBePaid] = useState(false);
  236.   const itemsToBePaidLoading = useSelector((state: IReducers) => state.statusReducers.loadingState[paymentObject.names.requestItemsToBePaid], shallowEqual);
  237.   const itemsToBePaid = useSelector((state: IReducers) => state.paymentReducers.itemsToBePaid, shallowEqual);
  238.  
  239.   const getItemsToBePaid = () => {
  240.     const cb = (data: any) => {
  241.       if (!data || !data.items.length) {
  242.         dispatch(
  243.           statusObject.actions.errorSetNew({
  244.             errorCode: ERR_TYPES.NO_CHECKOUT_ITEMS,
  245.             errorMessage: getErrorMessage(ERR_TYPES.NO_CHECKOUT_ITEMS),
  246.           }),
  247.         );
  248.         setNoItemToBePaid(true);
  249.       }
  250.     };
  251.     let key = new URL(location.href).searchParams.get('key') || '';
  252.     setItemKey(key);
  253.     dispatch(paymentObject.actions.requestItemsToBePaid(cb, key));
  254.   };
  255.  
  256.   // itemsToBePaid loading state always true even the data fetched
  257.   const [itemsToBePaidReady, setItemsToBePaidReady] = useState(false);
  258.  
  259.   useEffect(() => {
  260.     getItemsToBePaid();
  261.   }, []);
  262.  
  263.   const [currentItemsToBePaid, setCurrentItemsToBePaid] = useState<IItemsToBePaid>();
  264.   useEffect(() => {
  265.     setCurrentItemsToBePaid(itemsToBePaid[itemKey]);
  266.   }, [itemsToBePaid, itemKey]);
  267.  
  268.   useEffect(() => {
  269.     if (currentItemsToBePaid && !checkItemsToBePaidSource(currentItemsToBePaid, paymentSource)) {
  270.       setNoItemToBePaid(true);
  271.     }
  272.   }, [currentItemsToBePaid, paymentSource]);
  273.  
  274.   useEffect(() => {
  275.     if (currentItemsToBePaid?.items) {
  276.       setItemsToBePaidReady(true);
  277.     }
  278.   }, [currentItemsToBePaid]);
  279.  
  280.   useEffect(() => {
  281.     let totalPaymentTemp: number = 0;
  282.     let totalDiscountPriceTemp: number = 0;
  283.     let convertedTotalPaymentTemp: number = 0;
  284.     currentItemsToBePaid?.items.forEach((item: any) => {
  285.       const priceOriginal = item.totalItem * item.price;
  286.       const discountPrice = item.discountPrice ? item.totalItem * +item.discountPrice : 0;
  287.  
  288.       totalPaymentTemp += priceOriginal - discountPrice;
  289.       totalDiscountPriceTemp += discountPrice;
  290.       convertedTotalPaymentTemp += exchangeRateConversionHelper(foreignExchangeRate, regionData, item.price) * item.totalItem;
  291.     });
  292.     setTotalPayment(totalPaymentTemp);
  293.     setTotalDiscountPrice(totalDiscountPriceTemp);
  294.     setConvertedTotalPayment(convertedTotalPaymentTemp);
  295.     if (
  296.       paymentSource === PAYMENT_SOURCE.OFFICIAL_STORE_TOPUP ||
  297.       paymentSource === PAYMENT_SOURCE.OFFICIAL_STORE_VOUCHER
  298.     ) {
  299.       setItemToPay((currentItemsToBePaid?.items || []) as ItemToPayOS[]);
  300.     } else {
  301.       setItemToPay((currentItemsToBePaid?.items || []) as ItemToPay[]);
  302.     }
  303.     if (currentItemsToBePaid && !isAnalyticsSent) {
  304.       // add event to amplitude
  305.       const productName: string[] = [];
  306.       const productID: number[] = [];
  307.       const gameName: string[] = [];
  308.       currentItemsToBePaid?.items.forEach((item: any) => {
  309.         productName.push(item.itemInfo);
  310.         productID.push(item.productId);
  311.         gameName.push(item.serverGroup);
  312.       });
  313.       const type = titleCase(paymentSource);
  314.       const isQuickbuy =
  315.         paymentSource === PAYMENT_SOURCE.QUICK_BUY
  316.           ? 'YES'
  317.           : paymentSource === PAYMENT_SOURCE.OFFICIAL_STORE_TOPUP ||
  318.             paymentSource === PAYMENT_SOURCE.OFFICIAL_STORE_VOUCHER
  319.             ? 'Official Store'
  320.             : 'NO';
  321.       const sumOfProduct = currentItemsToBePaid.items.length;
  322.       const totalValue = totalPaymentTemp;
  323.       dispatch(
  324.         analyticsObject.actions.logEvent(
  325.           analyticsEvent.addPaymentInfoPage(
  326.             productName,
  327.             productID,
  328.             gameName,
  329.             type,
  330.             isQuickbuy,
  331.             sumOfProduct,
  332.             totalValue,
  333.           ),
  334.         ),
  335.       );
  336.       dispatch(paymentObject.actions.setIsAnalyticsSent(true));
  337.     }
  338.   }, [currentItemsToBePaid]);
  339.   // End Items to be Paid & Payment Recap //
  340.  
  341.   // Used Payment Method //
  342.   const [usedPaymentMethod, setUsedPaymentMethod] = useState<any>();
  343.  
  344.   const { latestPayment, getLatestPayment, latestPaymentLoading } = UserHandler.useLatestPayment();
  345.   const { loadWalletDetail, walletDetail, walletDetailLoading } = WalletHandler.useWalletDetail();
  346.   const premiumBuyerPrice = useSelector(
  347.     (state: IReducers) => state.configReducers.configList[PREMIUM_BUYER_TIER.TIER_1],
  348.     shallowEqual,
  349.   ) || 5000;
  350.  
  351.   const selectedPaymentMethod = useSelector((state: IReducers) => state.paymentReducers.selectedPaymentMethod,);
  352.   const { loadCoupons, coupons, couponLoading } = CouponHandler.useCoupons();
  353.   const [voucherBuyerInfo, setVoucherBuyerInfo] = useState<Input>({
  354.     name: userData.username as string,
  355.     phoneNumber: userData.phone?.substring(2) as string,
  356.   });
  357.  
  358.   useEffect(() => {
  359.     getLatestPayment({
  360.       web_type: isMobileDevice.current ? 2 : 1,
  361.     });
  362.   }, []);
  363.  
  364.   useEffect(() => {
  365.     if (latestPayment?.id === 0) {
  366.       Object.assign(latestPayment, DEFAULT_PAYMENT);
  367.     }
  368.     loadWalletDetail();
  369.   }, [latestPayment]);
  370.  
  371.   useEffect(() => {
  372.     const paymentMethod = selectedPaymentMethod[itemKey] || DEFAULT_PAYMENT;
  373.     let paymentInfoText = paymentMethod?.payment_information_text;
  374.     if (paymentInfoText?.substr(0, 2) === '- ') paymentInfoText = paymentInfoText.substring(2);
  375.     setUsedPaymentMethod({ ...paymentMethod, payment_information_text: paymentInfoText });
  376.   }, [selectedPaymentMethod, latestPayment, itemKey]);
  377.  
  378.   useEffect(() => {
  379.     setUserDetail({
  380.       ...userDetail,
  381.       id: userData.id,
  382.       isEmailVerified: Boolean(userData.is_email_verified),
  383.       isPhoneVerified: Boolean(userData.phone_verification_id),
  384.       hasSetupPIN: Boolean(userData.has_security_pin),
  385.       dompetkuBalance: walletDetail.order_able,
  386.     });
  387.     // if (paymentSource === PAYMENT_SOURCE.OFFICIAL_STORE_VOUCHER) {
  388.     //   setVoucherBuyerInfo({
  389.     //     name: userData.username!,
  390.     //     phoneNumber: userData.phone!
  391.     //   });
  392.     // }
  393.   }, [userData, walletDetail]);
  394.   // End Used Payment Method //
  395.  
  396.   const invalidPhoneMessage = useLanguage('nomor_telepon_tidak_valid');
  397.   const noPhoneMessage = useLanguage('nomor_telepon_tidak_boleh_kosong');
  398.   const noNameMessage = useLanguage('nama_tidak_boleh_kosong');
  399.  
  400.   const voucherBuyerInfoValidation = (buyerInfo: Input) => {
  401.     const errorList = {
  402.       name: '',
  403.       phoneNumber: '',
  404.     };
  405.     if (buyerInfo.phoneNumber) {
  406.       const phoneValidation = buyerInfo.phoneNumber.match(/^8\d{8,11}$/);
  407.       if (!phoneValidation) {
  408.         errorList.phoneNumber = invalidPhoneMessage;
  409.       } else {
  410.         errorList.phoneNumber = '';
  411.       }
  412.     } else {
  413.       errorList.phoneNumber = noPhoneMessage;
  414.     }
  415.     if (!buyerInfo.name) {
  416.       errorList.name = noNameMessage;
  417.     }
  418.     setErrorVoucherBuyerInfo(errorList);
  419.   };
  420.  
  421.   useEffect(() => {
  422.     voucherBuyerInfoValidation(voucherBuyerInfo);
  423.   }, [voucherBuyerInfo]);
  424.  
  425.   // Payment Skeleton //
  426.   useEffect(() => {
  427.     const loadingState = Boolean(
  428.       (itemsToBePaidLoading && !itemsToBePaidReady) ||
  429.         latestPaymentLoading ||
  430.         walletDetailLoading ||
  431.         couponLoading ||
  432.         couponValidationLoading ||
  433.         coinLoading,
  434.     );
  435.     if (loadingState !== isLoading) {
  436.       setIsLoading(loadingState);
  437.     }
  438.   }, [
  439.     itemsToBePaidLoading,
  440.     itemsToBePaidReady,
  441.     latestPaymentLoading,
  442.     walletDetailLoading,
  443.     couponLoading,
  444.     couponValidationLoading,
  445.     coinLoading,
  446.   ]);
  447.   // EndPayment Skeleton //
  448.  
  449.   // Start coupon handling //
  450.   const [, setCouponValiData] = useLocalStorage<ICouponValidationData>(couponValidationDataKey, {});
  451.   const [lastCoupon, setLastCoupon] = useLocalStorage<ICouponDetail | null>(couponSessionKey, null);
  452.   const equippedCoupon = useSelector((state: IReducers) => state.couponReducers.usedCoupon);
  453.   const lastKey = useSelector((state: IReducers) => state.couponReducers.couponLastKey);
  454.   const totalCoupon = useSelector((state: IReducers) => state.couponReducers.couponAllCount)[lastKey];
  455.   const [validCoupon, setValidCoupon] = useState(false);
  456.  
  457.   useEffect(() => {
  458.     if (equippedCoupon) {
  459.       const result = paymentSourceCouponValidation(equippedCoupon, paymentSource);
  460.  
  461.       if (result.success == false) {
  462.         dispatch(couponObject.actions.clearUserCoupoun());
  463.         dispatch(statusObject.actions.errorSetNew({ errorCode: result.code, errorMessage: result.message }));
  464.       }
  465.     }
  466.   }, [equippedCoupon]);
  467.   useEffect(() => {
  468.     loadCoupons();
  469.   }, []);
  470.  
  471.   // check if coupon is valid
  472.  
  473.   const isCouponSame = useMemo(() => {
  474.     if (!isset(equippedCoupon) || lastCoupon?.id !== equippedCoupon?.id) {
  475.       setLastCoupon(equippedCoupon);
  476.       return false;
  477.     } else {
  478.       return true;
  479.     }
  480.   }, [equippedCoupon?.id]);
  481.  
  482.   useEffect(() => {
  483.     // clear used coupon when used coupon not found
  484.     dispatch(couponObject.actions.requestValidateSelectedCoupon());
  485.   }, []);
  486.  
  487.   const isCouponValid = useMemo(() => {
  488.     if (equippedCoupon && currentItemsToBePaid && !isLoading) {
  489.       const newCouponValiData: ICouponValidationData = {
  490.         game_ids: currentItemsToBePaid?.items?.map((item: any) => item.gameId) || [],
  491.         item_type_ids: currentItemsToBePaid?.items?.map((item: any) => item.itemTypeId),
  492.         total_price: totalPayment,
  493.       };
  494.       const isValid = validateEquippedCoupon(equippedCoupon, newCouponValiData, paymentSource);
  495.       setValidCoupon(isValid.success);
  496.       return isValid;
  497.     }
  498.  
  499.     return { success: false, code: '', message: '' };
  500.   }, [currentItemsToBePaid, equippedCoupon, isLoading]);
  501.  
  502.   const couponDiscount = useMemo(() => {
  503.     if (isCouponValid && equippedCoupon) {
  504.       return calculateCouponDiscount(equippedCoupon, totalPayment);
  505.     }
  506.     return 0;
  507.   }, [equippedCoupon, isCouponValid]);
  508.  
  509.   // dispatch coupon error
  510.   useEffect(() => {
  511.     if (isLoading) {
  512.       return;
  513.     }
  514.  
  515.     if (equippedCoupon && !isCouponSame) {
  516.       if (isCouponValid.success === false && isCouponValid.code && isCouponValid.message) {
  517.         dispatch(
  518.           statusObject.actions.errorSetNew({
  519.             errorCode: isCouponValid.code,
  520.             errorMessage: isCouponValid.message,
  521.           }),
  522.         );
  523.         dispatch(couponObject.actions.clearUserCoupoun());
  524.         return;
  525.       }
  526.     }
  527.   }, [equippedCoupon, isCouponSame, isLoading]);
  528.  
  529.   //dispatch coupon success snackbar
  530.   useEffect(() => {
  531.     if (isLoading) {
  532.       return;
  533.     }
  534.  
  535.     if (true === validCoupon) {
  536.       dispatch(
  537.         statusObject.actions.errorSetNew({
  538.           errorCode: ERR_TYPES.COUPON_EQUIP_SUCCESS_PAYMENT,
  539.           errorMessage: COUPON_ERROR_MESSAGE[ERR_TYPES.COUPON_EQUIP_SUCCESS_PAYMENT],
  540.         }),
  541.       );
  542.     }
  543.   }, [validCoupon]);
  544.  
  545.   const handleSeeCoupon = () => {
  546.     if (currentItemsToBePaid) {
  547.       const newCouponValiData: ICouponValidationData = {
  548.         game_ids: currentItemsToBePaid?.items?.map((item: any) => item.gameId) || [],
  549.         item_type_ids: currentItemsToBePaid?.items?.map((item: any) => item.itemTypeId),
  550.       };
  551.       setCouponValiData(newCouponValiData);
  552.       if (equippedCoupon !== null) {
  553.         dispatch(
  554.           navigationObject.actions.routeNavigateTo({
  555.             routeName: ROUTER_NAME.MY_COUPON_ID,
  556.             params: { id: equippedCoupon.id },
  557.             query: { use_coupon: 1 },
  558.           }),
  559.         );
  560.       } else if (coupons?.length !== 0) {
  561.         dispatch(
  562.           navigationObject.actions.routeNavigateTo({ routeName: ROUTER_NAME.MY_COUPON, query: { use_coupon: 1 } }),
  563.         );
  564.       } else {
  565.         dispatch(
  566.           navigationObject.actions.routeNavigateTo({
  567.             routeName: ROUTER_NAME.POIN_EXCHANGE,
  568.             query: { use_coupon: 1 },
  569.           }),
  570.         );
  571.       }
  572.     }
  573.   };
  574.   // End coupon handling //
  575.  
  576.   // BEGIN ITEMKU WORLD COIN
  577.   // Retrieve discount games
  578.   const [hasItemkuWorldProduct, setHasItemkuWorldProduct] = useState<boolean>(false); // as MVP will be applied only to ROX product.
  579.  
  580.   const [discountGameIds, setDiscountGameIds] = useState<number[]>([]);
  581.   const discountGamesData = useSelector((state: IReducers) => state.coinReducers.discountGames);
  582.  
  583.   useEffect(() => {
  584.     dispatch(coinObject.actions.requestDiscountGames());
  585.   }, []);
  586.  
  587.   useEffect(() => {
  588.     if (discountGamesData) {
  589.       if (discountGamesData.length > 0) {
  590.         const arrGameids = discountGamesData.map((item: any) => item.game_id);
  591.         setDiscountGameIds(arrGameids);
  592.       }
  593.     }
  594.   }, [discountGamesData]);
  595.  
  596.   useEffect(() => {
  597.     const rawGameIds: number[] = currentItemsToBePaid?.items.map((item: any) => item.gameId) || [];
  598.  
  599.     if (discountGameIds.length > 0) {
  600.       const isOrderContainDiscountedGames = arrayIntersects(discountGameIds, rawGameIds);
  601.       if (isOrderContainDiscountedGames === true) setHasItemkuWorldProduct(true);
  602.     }
  603.   }, [currentItemsToBePaid, discountGameIds]);
  604.  
  605.   // Retrieve coin data
  606.   const { userValidCoin, loadUserValidCoin, validDiscount, loadValidDiscount } = CoinHandler.useCoin();
  607.   const [gameIdsForCoin, setGameIdsForCoin] = useState<number[]>([]);
  608.  
  609.   useEffect(() => {
  610.     if (hasItemkuWorldProduct) {
  611.       const rawGameIds: number[] = currentItemsToBePaid?.items.map((item: any) => item.gameId) || [];
  612.       const rawTotalPrice: number[] = currentItemsToBePaid?.items.map((item: any) => Number(item.price * item.totalItem)) || [];
  613.  
  614.       if (rawGameIds && rawTotalPrice) {
  615.         if (rawGameIds.length === rawTotalPrice.length && rawGameIds.length > 0 && rawTotalPrice.length > 0) {
  616.           const param = {
  617.             game_ids: rawGameIds,
  618.             amounts: rawTotalPrice,
  619.           };
  620.           loadValidDiscount(param);
  621.         }
  622.       }
  623.  
  624.       const gameIds = currentItemsToBePaid?.items.map((item: any) => {
  625.         if (item.gameId != undefined) return item.gameId;
  626.       });
  627.  
  628.       if (gameIds != undefined) {
  629.         const uniqueGameIds = Array.from(new Set(gameIds)) as number[];
  630.         setGameIdsForCoin(uniqueGameIds);
  631.       }
  632.     }
  633.   }, [currentItemsToBePaid, hasItemkuWorldProduct]);
  634.  
  635.   useEffect(() => {
  636.     if (gameIdsForCoin.length > 0 && hasItemkuWorldProduct) loadUserValidCoin(gameIdsForCoin);
  637.   }, [gameIdsForCoin, hasItemkuWorldProduct]);
  638.  
  639.   const [ownedCoin, setOwnCoin] = useState<number>(0);
  640.   const [maxAmountOfCoin, setMaxAmountOfCoin] = useState<number>(0);
  641.   const [coinData, setCoinData] = useState<IRequestValidCoin>();
  642.   const [usedItemkuWorldCoin, setUsedItemkuWorlCoin] = useState<number>(0);
  643.   const [coinDiscount, setCoinDiscount] = useState<number>(0);
  644.   const allCoinDiscount = useSelector((state: IReducers) => state.paymentReducers.coinDiscount);
  645.  
  646.   useEffect(() => {
  647.     if (userValidCoin) {
  648.       if (userValidCoin.length > 0) {
  649.         setOwnCoin(userValidCoin[0].coin_balance);
  650.         setCoinData(userValidCoin[0]);
  651.       }
  652.     }
  653.   }, [userValidCoin]);
  654.  
  655.   useEffect(() => {
  656.     if (validDiscount) {
  657.       if (validDiscount.length > 0) {
  658.         let totalDiscountAmount: number = 0;
  659.         let maxCoinCanBeUsed: number = 0;
  660.  
  661.         for (let item of validDiscount) {
  662.           totalDiscountAmount += item.discount_amount;
  663.           maxCoinCanBeUsed += item.coin_use;
  664.         }
  665.         setCoinDiscount(totalDiscountAmount);
  666.         setMaxAmountOfCoin(maxCoinCanBeUsed);
  667.         if (itemKey && itemToPay.length) {
  668.           dispatch(paymentObject.actions.setCoinDiscount({
  669.             ...allCoinDiscount,
  670.             [itemKey]: totalDiscountAmount,
  671.           }));
  672.         }
  673.       }
  674.     }
  675.   }, [itemToPay, validDiscount, itemKey]);
  676.  
  677.   useEffect(() => {
  678.     if (ownedCoin <= maxAmountOfCoin) setUsedItemkuWorlCoin(ownedCoin);
  679.     else setUsedItemkuWorlCoin(maxAmountOfCoin);
  680.   }, [totalPayment, ownedCoin, maxAmountOfCoin]);
  681.   // END ITEMKU WORLD COIN
  682.  
  683.   // Extra Charge //
  684.   const setExtraPrice = (data: IAllExtraPrice) => {
  685.     dispatch(paymentObject.actions.setExtraPrice(data));
  686.   };
  687.   const handleCheckPremiumOption = (checked: boolean) => {
  688.     dispatch(paymentObject.actions.setIsPremiumTicked({
  689.       ...premiumOption,
  690.       [itemKey]: checked,
  691.     }));
  692.   };
  693.   useEffect(() => {
  694.     if (
  695.       paymentSource === PAYMENT_SOURCE.OFFICIAL_STORE_VOUCHER ||
  696.       paymentSource === PAYMENT_SOURCE.OFFICIAL_STORE_TOPUP ||
  697.       (isGlobal && hiddenFeatureConfig[HIDDEN_FEATURE_FOR_GLOBAL.HIDE_PREMIUM])
  698.     ) {
  699.       dispatch(paymentObject.actions.setIsPremiumTicked({
  700.         ...premiumOption,
  701.         [itemKey]: false,
  702.       }));
  703.     }
  704.   }, [itemKey]);
  705.   const [cashback, setCashback] = useState(0);
  706.  
  707.   const premiumName = useLanguage('layanan_pembeli_premium') ;
  708.   const feesName = useLanguage('biaya_admin');
  709.  
  710.   const extraPrice = useSelector((state: IReducers) => state.paymentReducers.extraPrice);
  711.   useEffect(() => {
  712.     if (!currentItemsToBePaid) {
  713.       return;
  714.     }
  715.  
  716.     const finalPremiumBuyerPrice =
  717.       paymentSource === PAYMENT_SOURCE.CART && currentItemsToBePaid.items.length >= 4
  718.         ? premiumBuyerPrice * 2
  719.         : premiumBuyerPrice;
  720.  
  721.     const premiumRecap = { name: premiumName || 'Layanan Pembeli Premium', nominal: finalPremiumBuyerPrice };
  722.     let extraChargeTemp: any = [];
  723.     if (premiumOption[itemKey]) {
  724.       extraChargeTemp = [premiumRecap, ...extraChargeTemp];
  725.     }
  726.     setExtraPrice({
  727.       ...extraPrice,
  728.       [itemKey]: extraChargeTemp,
  729.     });
  730.  
  731.     let fees = 0;
  732.     if (usedPaymentMethod?.payment_denom_item && usedPaymentMethod?.payment_denom_item.length) {
  733.       const paymentDenomFee = paymentMethodDenomFeeHelper(usedPaymentMethod.payment_denom_item, totalPayment);
  734.       if (paymentDenomFee) {
  735.         const premiumFee = premiumOption[itemKey] ? finalPremiumBuyerPrice : 0;
  736.         fees = paymentDenomFee.totalPaid - totalPayment - premiumFee;
  737.         setCashback(fees - paymentDenomFee.fee);
  738.       }
  739.     } else {
  740.       const paymentFee = paymentMethodFeeHelper(usedPaymentMethod?.payment_method_fee || [], totalPayment);
  741.  
  742.       const totalPaymentAndPremium = premiumOption[itemKey] ? totalPayment + finalPremiumBuyerPrice : totalPayment;
  743.       const finalAmount = totalPaymentAndPremium - couponDiscount - (isUsingCoin[itemKey] ? coinDiscount : 0);
  744.       fees = paymentFee.fixedRate ? (finalAmount * paymentFee.fixedRate) / 100 : 0;
  745.       fees += paymentFee.fixedFee ? paymentFee.fixedFee : 0;
  746.     }
  747.     if (fees) {
  748.       extraChargeTemp = [
  749.         ...extraChargeTemp.filter((item: any) => item.name !== feesName),
  750.         { name: feesName, nominal: Math.ceil(fees) },
  751.       ];
  752.     }
  753.     setExtraCharge(extraChargeTemp);
  754.   }, [currentItemsToBePaid, premiumOption, premiumBuyerPrice, usedPaymentMethod, totalPayment, itemKey, couponDiscount, isUsingCoin, coinDiscount]);
  755.  
  756.   useEffect(() => {
  757.     const totalExtraChargeTemp = extraCharge.reduce((acc: number, item: any) => acc + item.nominal, 0);
  758.     setTotalExtraCharge(totalExtraChargeTemp);
  759.  
  760.     const convertedExtraChargeTemp = extraCharge.reduce((acc: number, item: any) => exchangeRateConversionHelper(foreignExchangeRate, regionData, (acc + item.nominal)), 0);
  761.     setConvertedExtraCharge(convertedExtraChargeTemp);
  762.   }, [extraCharge]);
  763.   // End Extra Charge //
  764.  
  765.   const topupTitleText = useLanguage('top_up_dompetku');
  766.   const topupDescText = useLanguage('dompetku_tidak_cukup_top_up_sebesar_{{topup_amount}}_kelipatan_rp_1_000_atau_ubah_metode_pembayaranmu', { topup_amount: 'Rp' + moneyDot(getTopUpAmount(userDetail.dompetkuBalance, totalPayment + totalExtraCharge)) });
  767.   const topupCancelText = useLanguage('ubah_pembayaran');
  768.   const topupMainText = useLanguage('lanjut_top_up');
  769.   const topupEngagementCancelText = useLanguage('batalkan');
  770.  
  771.   const withdrawTitleText = useLanguage('cairkan_dana_dompetku');
  772.   const withdrawDescText = useLanguage('transaksi_tidak_dapat_dilanjutkan_karena_dana_dompetku_melebihi_batas_{{topup_limit}}_cairkan_sebagian_dana_dompetku_terlebih_dahulu', { topup_limit: 'Rp' + moneyDot(walletDetail.topup_limit) });
  773.   const withdrawCancelText = useLanguage('ubah_pembayaran');
  774.   const withdrawMainText = useLanguage('cairkan_dana_dompetku');
  775.  
  776.   // Dompetku Error //
  777.   const showDompetkuDialog = (type: 'topup' | 'withdraw' | 'topup-engagement') => {
  778.     let modal: IModal = {
  779.       title: '',
  780.       description: '',
  781.       cancelActionTitle: '',
  782.       mainActionTitle: '',
  783.       cancelAction: () => {
  784.         /* do nothing */
  785.       },
  786.       mainAction: () => {
  787.         /* do nothing */
  788.       },
  789.     };
  790.     if (type === 'topup') {
  791.       modal = {
  792.         title: topupTitleText,
  793.         description: (
  794.           <p>
  795.             <DivHtmlContent data={topupDescText} draggable={false} />
  796.           </p>
  797.         ),
  798.         cancelActionTitle: topupCancelText,
  799.         mainActionTitle: topupMainText,
  800.         cancelAction: handleGoToPaymentMethod,
  801.         mainAction: () => dispatch(navigationObject.actions.routeNavigateTo({ routeName: ROUTER_NAME.DOMPETKU_TOPUP })),
  802.       };
  803.     } else if (type === 'withdraw') {
  804.       modal = {
  805.         title: withdrawTitleText,
  806.         description: withdrawDescText,
  807.         cancelActionTitle: withdrawCancelText,
  808.         mainActionTitle: withdrawMainText,
  809.         cancelAction: handleGoToPaymentMethod,
  810.         mainAction: () => {
  811.           putLocalStorage(withdrawDoneUrlKey, router.pathname);
  812.           dispatch(
  813.             navigationObject.actions.routeNavigateTo({
  814.               routeName: ROUTER_NAME.DOMPETKU_WITHDRAW,
  815.               query: {
  816.                 withdraw_amount: getWithdrawAmount(userDetail.dompetkuBalance, walletDetail.topup_limit),
  817.               },
  818.             }),
  819.           );
  820.         },
  821.       };
  822.     } else if (type === 'topup-engagement') {
  823.       modal = {
  824.         title: topupTitleText,
  825.         description: (
  826.           <p>
  827.             <DivHtmlContent data={topupDescText} draggable={false} />
  828.           </p>
  829.         ),
  830.         cancelActionTitle: topupEngagementCancelText,
  831.         mainActionTitle: topupMainText,
  832.         cancelAction: () => {},
  833.         mainAction: () => dispatch(navigationObject.actions.routeNavigateTo({ routeName: ROUTER_NAME.DOMPETKU_TOPUP })),
  834.       };
  835.     }
  836.     dispatch(
  837.       navigationObject.actions.modalNavigateTo(Types.Dialog, {
  838.         title: modal.title,
  839.         type: 'multiple',
  840.         description: modal.description,
  841.         cancelActionTitle: modal.cancelActionTitle,
  842.         mainActionTitle: modal.mainActionTitle,
  843.         cancelAction: modal.cancelAction,
  844.         mainAction: modal.mainAction,
  845.       }),
  846.     );
  847.   };
  848.  
  849.   let dompetkuWithButton = false;
  850.   let dompetkuButtonLabel = topupTitleText;
  851.   let dompetkuOnClick = () => {
  852.     /* do nothing */
  853.   };
  854.   if (userDetail.hasSetupPIN && userDetail.isEmailVerified && userDetail.isPhoneVerified) {
  855.     if (userDetail.dompetkuBalance < totalPayment + totalExtraCharge) {
  856.       dompetkuWithButton = true;
  857.       dompetkuOnClick = () => showDompetkuDialog('topup');
  858.     }
  859.   }
  860.  
  861.   const emailVerificationLabel = useLanguage('verifikasi_email');
  862.   const emailVerificationDesc = useLanguage('untuk_melakukan_transaksi_dengan_dompetku_kamu_harus_melakukan_verifikasi_email');
  863.  
  864.   const phoneVerificationLabel = useLanguage('verifikasi_no_handphone');
  865.   const phoneVerificationDesc = useLanguage('untuk_melakukan_transaksi_dengan_dompetku_kamu_harus_melakukan_verifikasi_no_handphone');
  866.  
  867.   const pinVerificationLabel = useLanguage('atur_pin_sekarang');
  868.   const pinVerificationDesc = useLanguage('atur_pin_kamu_untuk_transaksi_dengan_dompetku');
  869.  
  870.   const emptyWalletDesc = useLanguage('dana_dompetku_tidak_cukup_top_up_dana_dompetku_kamu_atau_gunakan_metode_pembayaran_lain');
  871.  
  872.   useEffect(() => {
  873.     if (!latestPaymentLoading && !walletDetailLoading) {
  874.       if (!userDetail.isEmailVerified || !userDetail.isPhoneVerified || !userDetail.hasSetupPIN) {
  875.         // user dompetku inactive
  876.         if (Object.keys(latestPayment).length === 0) {
  877.           // user first purchase
  878.           setIsDompetkuEngagementShown(false);
  879.           if (!userDetail.isEmailVerified) {
  880.             setDompetkuButton({
  881.               ...dompetkuButton,
  882.               label: emailVerificationLabel,
  883.               desc: emailVerificationDesc,
  884.               onClick: () => router.push(routes.verifikasi.route + 'email'),
  885.             });
  886.           } else if (!userDetail.isPhoneVerified) {
  887.             setDompetkuButton({
  888.               ...dompetkuButton,
  889.               label: phoneVerificationLabel,
  890.               desc: phoneVerificationDesc,
  891.               onClick: () => router.push(routes.verifikasi.route + 'nomor-handphone'),
  892.             });
  893.           } else if (!userDetail.hasSetupPIN) {
  894.             setDompetkuButton({
  895.               ...dompetkuButton,
  896.               label: pinVerificationLabel,
  897.               desc: (
  898.                 <p>
  899.                   <DivHtmlContent data={pinVerificationDesc} draggable={false} />
  900.                 </p>
  901.               ),
  902.               onClick: () => {
  903.                 if (userDetail.isPhoneVerified) {
  904.                   router.push(routes.pin.route + 'add');
  905.                 } else {
  906.                   router.push(routes.verifikasi.route + 'nomor-handphone');
  907.                 }
  908.               },
  909.             });
  910.           }
  911.         } else {
  912.           // user repurchase
  913.           setIsDompetkuEngagementShown(true);
  914.           setDompetkuButton(undefined);
  915.           const paymentMethod = selectedPaymentMethod[itemKey] || latestPayment;
  916.           let paymentInfoText = paymentMethod?.payment_information_text;
  917.           if (paymentInfoText?.substr(0, 2) === '- ') paymentInfoText = paymentInfoText.substring(2);
  918.           setUsedPaymentMethod({ ...paymentMethod, payment_information_text: paymentInfoText });
  919.         }
  920.       } else {
  921.         // user dompetku active
  922.         if (Object.keys(latestPayment).length === 0) {
  923.           // user first purchase
  924.           if (userDetail.dompetkuBalance >= totalPayment) {
  925.             // sufficient balance
  926.             setIsDompetkuEngagementShown(false);
  927.             setDompetkuButton(undefined);
  928.           } else {
  929.             // insufficient balance
  930.             setIsDompetkuEngagementShown(false);
  931.             setDompetkuButton({
  932.               ...dompetkuButton,
  933.               label: '',
  934.               desc: emptyWalletDesc,
  935.               onClick: () => null,
  936.             });
  937.           }
  938.         } else {
  939.           // user repurchase
  940.           if (latestPayment.id === 0) {
  941.             // user latest payment using dompetku
  942.             if (userDetail.dompetkuBalance >= totalPayment) {
  943.               // sufficient balance
  944.               setIsDompetkuEngagementShown(false);
  945.               setDompetkuButton(undefined);
  946.             } else {
  947.               // insufficient balance
  948.               setIsDompetkuEngagementShown(false);
  949.               setDompetkuButton({
  950.                 ...dompetkuButton,
  951.                 label: '',
  952.                 desc: emptyWalletDesc,
  953.                 onClick: () => null,
  954.               });
  955.             }
  956.           } else {
  957.             // user latest payment using other than dompetku
  958.             if (userDetail.dompetkuBalance >= totalPayment) {
  959.               // sufficient balance
  960.               setIsDompetkuEngagementShown(false);
  961.               setDompetkuButton(undefined);
  962.               const paymentMethod = selectedPaymentMethod[itemKey] || DEFAULT_PAYMENT;
  963.               let paymentInfoText = paymentMethod?.payment_information_text;
  964.               if (paymentInfoText?.substr(0, 2) === '- ') paymentInfoText = paymentInfoText.substring(2);
  965.               setUsedPaymentMethod({ ...paymentMethod, payment_information_text: paymentInfoText });
  966.             } else {
  967.               // insufficient balance
  968.               setIsDompetkuEngagementShown(true);
  969.               setDompetkuButton(undefined);
  970.               const paymentMethod = selectedPaymentMethod[itemKey] || latestPayment;
  971.               let paymentInfoText = paymentMethod?.payment_information_text;
  972.               if (paymentInfoText?.substr(0, 2) === '- ') paymentInfoText = paymentInfoText.substring(2);
  973.               setUsedPaymentMethod({ ...paymentMethod, payment_information_text: paymentInfoText });
  974.             }
  975.           }
  976.         }
  977.       }
  978.     }
  979.   }, [userDetail, walletDetail, totalPayment, latestPayment, latestPaymentLoading, walletDetailLoading, itemKey]);
  980.   // End Dompetku Error //
  981.  
  982.   // Latest Payment Error //
  983.   const [paymentMethodError, setPaymentMethodError] = useState(false);
  984.  
  985.   const notAvailablePaymentError = useLanguage('metode_pembayaran_yang_kamu_pilih_sedang_tidak_tersedia_atau_dalam_perbaikan');
  986.   const lessThanMinPaymentError = useLanguage('produk_yang_kamu_beli_kurang_dari_minimal_pembayaran');
  987.   const moreThanMaxPaymentError = useLanguage('produk_yang_kamu_beli_melebihi_maksimal_pembayaran');
  988.  
  989.   useEffect(() => {
  990.     const gameIds = currentItemsToBePaid?.items.map((item: any) => item.gameId);
  991.     if (usedPaymentMethod) {
  992.       const isHealthy =
  993.         usedPaymentMethod.is_healthy ||
  994.         usedPaymentMethod.isHealthy ||
  995.         usedPaymentMethod.payment_method_health === null ||
  996.         (usedPaymentMethod.payment_method_health && usedPaymentMethod.payment_method_health.health);
  997.       const finalPremiumBuyerPrice =
  998.         paymentSource === PAYMENT_SOURCE.CART && currentItemsToBePaid?.items && currentItemsToBePaid.items.length >= 4
  999.           ? premiumBuyerPrice * 2
  1000.           : premiumBuyerPrice;
  1001.       const total = usedPaymentMethod.payment_denom_item?.length
  1002.         ? totalPayment + finalPremiumBuyerPrice
  1003.         : totalPayment + totalExtraCharge;
  1004.       const notSupported = arrayIntersects(gameIds || [], usedPaymentMethod?.exclude_game_id || []);
  1005.       if (
  1006.         (usedPaymentMethod.id !== DEFAULT_PAYMENT.id && !isHealthy) ||
  1007.         notSupported ||
  1008.         usedPaymentMethod.is_maintenance
  1009.       ) {
  1010.         setLatestPaymentErrorBanner(notAvailablePaymentError);
  1011.         setPaymentMethodError(true);
  1012.       } else if (usedPaymentMethod?.minimum_payment_limit !== 0 && total < usedPaymentMethod?.minimum_payment_limit) {
  1013.         setLatestPaymentErrorBanner(lessThanMinPaymentError);
  1014.         setPaymentMethodError(true);
  1015.       } else if (usedPaymentMethod?.maximum_payment_limit !== 0 && total > usedPaymentMethod?.maximum_payment_limit) {
  1016.         setLatestPaymentErrorBanner(moreThanMaxPaymentError);
  1017.         setPaymentMethodError(true);
  1018.       } else {
  1019.         setLatestPaymentErrorBanner('');
  1020.         setPaymentMethodError(false);
  1021.       }
  1022.     }
  1023.   }, [usedPaymentMethod, totalPayment, totalExtraCharge, currentItemsToBePaid]);
  1024.   // End Latest Payment Error //
  1025.  
  1026.   // Payment with Dompetku - Dompetku Pin //
  1027.   const [attemps, setAttemps] = useState(0);
  1028.   useEffect(() => {
  1029.     setErrorBanner(attemps > 0);
  1030.     if (usedPaymentMethod?.name === DEFAULT_PAYMENT.name && attemps > 1) {
  1031.       if (userDetail.dompetkuBalance < totalPayment) showDompetkuDialog('topup');
  1032.     }
  1033.   }, [attemps]);
  1034.  
  1035.   useEffect(() => {
  1036.     if (pinBottomSheet.wrongPin) {
  1037.       const timer = setTimeout(() => {
  1038.         setPinBottomSheet({ ...pinBottomSheet, wrongPin: false, pinNumber: undefined });
  1039.         setPinInteractionValue('');
  1040.       }, 1000);
  1041.       return () => clearTimeout(timer);
  1042.     }
  1043.   }, [pinBottomSheet]);
  1044.   // End Payment with Dompetku - Dompetku Pin //
  1045.  
  1046.   // Proceed Payment //
  1047.   const [transactionData, setTransactionData] = useState<ITransactionData>();
  1048.   const [subscriptionId, setSubscriptionId] = useState<number>();
  1049.  
  1050.   const { createTransaction } = TransactionHandler.useCreateTransaction();
  1051.   const { saveTransactionToStash } = TransactionHandler.useTransactionStash();
  1052.  
  1053.   useEffect(() => {
  1054.     if (router.query.subscription_id) {
  1055.       const subsId = parseInt(router.query.subscription_id as string);
  1056.       setSubscriptionId(!isNaN(subsId) ? subsId : undefined);
  1057.     }
  1058.   }, [router]);
  1059.  
  1060.   useEffect(() => {
  1061.     // populate transaction payload to be created
  1062.     if (!currentItemsToBePaid || !usedPaymentMethod || !currentItemsToBePaid.items.length) {
  1063.       return;
  1064.     }
  1065.     const cartIds: Set<number> = new Set();
  1066.     currentItemsToBePaid.items.forEach((item: any) => cartIds.add(item.cartId!));
  1067.     const paymentWalletAmount = usedPaymentMethod.name === DEFAULT_PAYMENT.name ? totalPayment + totalExtraCharge : 0;
  1068.     let payload = undefined;
  1069.     if (paymentSource === PAYMENT_SOURCE.OFFICIAL_STORE_TOPUP) {
  1070.       payload = {
  1071.         payment_method_id: usedPaymentMethod.id,
  1072.         payment_wallet_amount: paymentWalletAmount,
  1073.         product_id: currentItemsToBePaid.items[0].productId!,
  1074.         quantity: currentItemsToBePaid.items[0].totalItem!,
  1075.         price: currentItemsToBePaid.items[0].price!,
  1076.         user_information_data: currentItemsToBePaid.items[0].userInfoData,
  1077.         note: currentItemsToBePaid.items[0].notes,
  1078.         buyer_account: undefined,
  1079.         coupon_code: equippedCoupon?.code,
  1080.         pin: pinBottomSheet.pinNumber,
  1081.         server_id: currentItemsToBePaid.items[0].serverId,
  1082.         key: itemKey,
  1083.       };
  1084.     } else if (paymentSource === PAYMENT_SOURCE.OFFICIAL_STORE_VOUCHER) {
  1085.       payload = ({
  1086.         payment_method_id: usedPaymentMethod.id,
  1087.         payment_wallet_amount: paymentWalletAmount,
  1088.         product_ids: currentItemsToBePaid.items.map((item: any) => {
  1089.           return +item.productId;
  1090.         }),
  1091.         quantities: currentItemsToBePaid.items.map((item: any) => {
  1092.           return +item.totalItem;
  1093.         }),
  1094.         buyer_phone: '62' + String(voucherBuyerInfo!.phoneNumber),
  1095.         game_ids: currentItemsToBePaid.items.map((item: any) => {
  1096.           return +item.gameId;
  1097.         }),
  1098.         total_amount: totalPayment,
  1099.         buyer_account: undefined,
  1100.         pin: pinBottomSheet.pinNumber,
  1101.         coupon_code: equippedCoupon?.code,
  1102.         is_used_coin: isUsingCoin[itemKey],
  1103.         key: itemKey,
  1104.       } as ICreateVendorVoucherTransactionPayload);
  1105.     } else {
  1106.       payload = {
  1107.         is_premium: premiumOption[itemKey],
  1108.         payment_method_id: usedPaymentMethod.id,
  1109.         payment_wallet_amount: paymentWalletAmount,
  1110.         product_id: currentItemsToBePaid.items[0].productId!,
  1111.         quantity: currentItemsToBePaid.items[0].totalItem!,
  1112.         price: currentItemsToBePaid.items[0].price!,
  1113.         required_information: paymentSource !== PAYMENT_SOURCE.CART ? currentItemsToBePaid.items[0].requiredInformation : undefined,
  1114.         note: currentItemsToBePaid.items[0].notes,
  1115.         buyer_account: undefined,
  1116.         coupon_code: equippedCoupon?.code,
  1117.         pin: pinBottomSheet.pinNumber,
  1118.         cart_ids: [...cartIds],
  1119.         is_from_quickbuy: currentItemsToBePaid.items[0].fromQuickbuy,
  1120.         is_from_cheapest_product: currentItemsToBePaid.items[0].fromCheapest,
  1121.         is_used_coin: isUsingCoin[itemKey],
  1122.         is_with_subscription: paymentSource === PAYMENT_SOURCE.SUBSCRIPTION_BUY_NOW ? undefined : isSubscription,
  1123.         is_from_langganan_page: paymentSource === PAYMENT_SOURCE.SUBSCRIPTION_BUY_NOW,
  1124.         subscription_id: subscriptionId,
  1125.         key: itemKey,
  1126.       };
  1127.     }
  1128.     const transactionPayload = {
  1129.       source: paymentSource,
  1130.       payload: payload,
  1131.     };
  1132.     setTransactionData(transactionPayload);
  1133.   }, [
  1134.     paymentSource,
  1135.     currentItemsToBePaid,
  1136.     pinBottomSheet,
  1137.     premiumOption,
  1138.     usedPaymentMethod,
  1139.     userDetail,
  1140.     totalPayment,
  1141.     totalExtraCharge,
  1142.     equippedCoupon?.code,
  1143.     voucherBuyerInfo,
  1144.     isUsingCoin,
  1145.     isSubscription,
  1146.     subscriptionId,
  1147.     itemKey,
  1148.   ]);
  1149.  
  1150.   const gotoFinalOrderPage = (trxId: number) => {
  1151.     if (paymentSource == PAYMENT_SOURCE.OFFICIAL_STORE_VOUCHER) {
  1152.       dispatch(
  1153.         navigationObject.actions.routeNavigateTo({
  1154.           routeName: ROUTER_NAME.OFFICIAL_STORE_VOUCHER_PAYMENT_CONFIRMATION,
  1155.           params: { transaction_id: trxId },
  1156.         }),
  1157.       );
  1158.     } else if (paymentSource == PAYMENT_SOURCE.OFFICIAL_STORE_TOPUP) {
  1159.       dispatch(
  1160.         navigationObject.actions.routeNavigateTo({
  1161.           routeName: ROUTER_NAME.OFFICIAL_STORE_TOPUP_PAYMENT_CONFIRMATION,
  1162.           params: { transaction_id: trxId },
  1163.         }),
  1164.       );
  1165.     } else {
  1166.       dispatch(
  1167.         navigationObject.actions.routeNavigateReplaceTo({
  1168.           routeName: paymentConfirmationRoutes[paymentSource],
  1169.           params: { transaction_id: trxId },
  1170.           query: {
  1171.             is_premium: premiumOption[itemKey] == true ? 1 : 0,
  1172.           },
  1173.         }),
  1174.       );
  1175.     }
  1176.   };
  1177.  
  1178.   const handleTransactionCallback = (success: boolean, response: any) => {
  1179.     if (success) {
  1180.       // prevent clear coupon so not send double request
  1181.       if (isAlreadySuccessSendRequest.current !== success) {
  1182.         dispatch(couponObject.actions.clearUserCoupoun());
  1183.       }
  1184.       isAlreadySuccessSendRequest.current = success;
  1185.       const responseTransaction = response.data;
  1186.  
  1187.       if (responseTransaction.subscription) {
  1188.         dispatch(persistObject.actions.setSubscriptionStatusSuccess(responseTransaction.subscription.data.data));
  1189.       }
  1190.  
  1191.       if (responseTransaction.direct_payment) {
  1192.         openSandbox(
  1193.           usedPaymentMethod.name,
  1194.           responseTransaction.direct_payment?.gateway,
  1195.           responseTransaction.direct_payment?.payload,
  1196.         );
  1197.       } else {
  1198.         gotoFinalOrderPage(
  1199.           Array.isArray(responseTransaction.data) ? responseTransaction.data[0].id : responseTransaction.data.id,
  1200.         ); // removed because want to open sandbox in current tab
  1201.       }
  1202.     } else {
  1203.       // clear dompetku status when transaction fail
  1204.       if (transactionData && transactionData.payload.pin) {
  1205.         delete transactionData.payload.pin;
  1206.         setPinBottomSheet({
  1207.           ...pinBottomSheet,
  1208.           isSuccess: false,
  1209.           wrongPin: false,
  1210.         });
  1211.         setTransactionData({
  1212.           ...transactionData,
  1213.           payload: {
  1214.             ...transactionData.payload,
  1215.           },
  1216.         });
  1217.       }
  1218.       isAlreadySuccessSendRequest.current = success;
  1219.       setDialog({ ...dialog, loadPayment: false });
  1220.     }
  1221.   };
  1222.  
  1223.   const handleDompetkuPayment = () => {
  1224.     if (!transactionData) {
  1225.       return;
  1226.     }
  1227.     if (userDetail.dompetkuBalance < totalPayment + totalExtraCharge) {
  1228.       setAttemps(attemps + 1);
  1229.     } else {
  1230.       setTransactionData({
  1231.         ...transactionData,
  1232.         payload: {
  1233.           coupon_code: equippedCoupon?.code,
  1234.           ...transactionData.payload,
  1235.           payment_wallet_amount: totalPayment + totalExtraCharge,
  1236.         },
  1237.       });
  1238.       setPinBottomSheet({ ...pinBottomSheet, isOpen: true });
  1239.     }
  1240.   };
  1241.  
  1242.   useEffect(() => {
  1243.     // create dompetku transaction after pin success
  1244.     if (
  1245.       !isAlreadySuccessSendRequest.current &&
  1246.       pinBottomSheet.isSuccess &&
  1247.       !pinBottomSheet.wrongPin &&
  1248.       transactionData &&
  1249.       transactionData.payload.pin
  1250.     ) {
  1251.       setDialog({ ...dialog, loadPayment: true });
  1252.       createTransaction(transactionData, handleTransactionCallback);
  1253.     }
  1254.   }, [pinBottomSheet, transactionData]);
  1255.  
  1256.   const handleOvoPayment = () => {
  1257.     if (!transactionData) {
  1258.       return;
  1259.     }
  1260.     saveTransactionToStash(transactionData);
  1261.     dispatch(paymentObject.actions.setPaymentEntryUrl(router.asPath));
  1262.     dispatch(navigationObject.actions.routeNavigateTo({ routeName: paymentWithOvoRoutes[paymentSource], query: router.query }));
  1263.   };
  1264.  
  1265.   const handleOthersPayment = () => {
  1266.     if (!transactionData) {
  1267.       return;
  1268.     }
  1269.     if (paymentMethodError) {
  1270.       setAttemps(attemps + 1);
  1271.       return;
  1272.     }
  1273.     setDialog({ ...dialog, loadPayment: true });
  1274.     createTransaction(transactionData, handleTransactionCallback);
  1275.   };
  1276.  
  1277.   const doPayClickPay = () => {
  1278.     if (paymentSource === PAYMENT_SOURCE.OFFICIAL_STORE_VOUCHER) {
  1279.       voucherBuyerInfoValidation(voucherBuyerInfo);
  1280.       if (errorVoucherBuyerInfo.name || errorVoucherBuyerInfo.phoneNumber) {
  1281.         return null;
  1282.       }
  1283.     }
  1284.     if (totalPayment <= 0 || totalPayment == null || isLoading) {
  1285.       return null;
  1286.     } else if (usedPaymentMethod.id === DEFAULT_PAYMENT.id) {
  1287.       handleDompetkuPayment();
  1288.     } else if (usedPaymentMethod.name.toLowerCase() === 'ovo') {
  1289.       handleOvoPayment();
  1290.     } else {
  1291.       handleOthersPayment();
  1292.     }
  1293.   };
  1294.  
  1295.   // BCA VA check
  1296.   const unpaidPaymentBCAVA = useSelector((state: IReducers) => state.paymentReducers.unpaidBCAPayment);
  1297.   const transactionTypePrevBCAPayment = useSelector((state: IReducers) => state.paymentReducers.prevBCAPaymentType);
  1298.  
  1299.   useEffect(() => {
  1300.     if (usedPaymentMethod) {
  1301.       if (usedPaymentMethod.id === bcaPaymentMethodId) {
  1302.         dispatch(paymentObject.actions.checkBCAUnpaidPayment());
  1303.         setPaymentBCA(true);
  1304.       } else {
  1305.         setPaymentBCA(false);
  1306.       }
  1307.     }
  1308.   }, [usedPaymentMethod]);
  1309.  
  1310.   useEffect(() => {
  1311.     if (unpaidPaymentBCAVA) {
  1312.       if (unpaidPaymentBCAVA > 0) {
  1313.         setUnpayBCAPayment(true);
  1314.       } else {
  1315.         setUnpayBCAPayment(false);
  1316.       }
  1317.     }
  1318.   }, [unpaidPaymentBCAVA]);
  1319.  
  1320.   const cancelActionBCA = (transactionType: string) => {
  1321.     switch (transactionType) {
  1322.       case 'LT':
  1323.         dispatch(
  1324.           navigationObject.actions.routeNavigateTo({ routeName: ROUTER_NAME.ITEMKU_PLAY_CREDITS, query: { tab: 1 } }),
  1325.         );
  1326.         break;
  1327.       case 'TO':
  1328.         dispatch(navigationObject.actions.routeNavigateTo({ routeName: ROUTER_NAME.DOMPETKU }));
  1329.         break;
  1330.       case 'VT':
  1331.         dispatch(
  1332.           navigationObject.actions.routeNavigateTo({
  1333.             routeName: ROUTER_NAME.OFFICIAL_STORE_TRANSACTION_HISTORY,
  1334.             query: { tab: 0, chip: 1, page: 1 },
  1335.           }),
  1336.         );
  1337.         break;
  1338.       case 'DT':
  1339.         dispatch(
  1340.           navigationObject.actions.routeNavigateTo({
  1341.             routeName: ROUTER_NAME.OFFICIAL_STORE_TRANSACTION_HISTORY,
  1342.             query: { tab: 0, chip: 0, page: 1 },
  1343.           }),
  1344.         );
  1345.         break;
  1346.       default:
  1347.         dispatch(navigationObject.actions.routeNavigateTo({ routeName: ROUTER_NAME.TRANSACTION_ORDER_HISTORY }));
  1348.     }
  1349.   };
  1350.  
  1351.   const bcaPaymentTitleText = useLanguage('ada_transaksi_yang_belum_dibayar');
  1352.   const bcaPaymentCancelText = useLanguage('bayar_sebelumnya');
  1353.   const bcaPaymentMainText = useLanguage('lanjut_bayar');
  1354.   const bcaPaymentDescText = useLanguage('jika_melanjutkan_pembayaran_untuk_transaksi_ini_maka_transaksimu_yang_sebelumnya_akan_dibatalkan');
  1355.  
  1356.   const bcaVirtualAccountPaymentDialog = () => {
  1357.     dispatch(
  1358.       navigationObject.actions.modalNavigateTo(Types.Dialog, {
  1359.         type: 'multiple',
  1360.         title: bcaPaymentTitleText,
  1361.         mainActionTitle: bcaPaymentMainText,
  1362.         cancelActionTitle: bcaPaymentCancelText,
  1363.         description: (
  1364.           <p>{bcaPaymentDescText}</p>
  1365.         ),
  1366.         mainAction: () => {
  1367.           // cancel prev transaction, create new transaction
  1368.           if (transactionData) {
  1369.             transactionData.payload.cancel_previous_transaction = true;
  1370.             doPayClickPay();
  1371.           }
  1372.         },
  1373.         cancelAction: () => {
  1374.           // navigate to coresponding pending payment
  1375.           cancelActionBCA(transactionTypePrevBCAPayment ? transactionTypePrevBCAPayment : 'TR');
  1376.         },
  1377.       }),
  1378.     );
  1379.   };
  1380.   // BCA VA check end
  1381.  
  1382.   // show dialog when use coupon totalPayment < coupon minimum purcase
  1383.  
  1384.   const couponErrorTitleText = useLanguage('pesanan_kurang');
  1385.   const couponErrorCancelText = useLanguage('tambah_pesanan');
  1386.   const couponErrorMainText = useLanguage('lanjut_belanja');
  1387.   const couponErrorDescText = useLanguage('total_pesanan_kamu_belum_memenuhi_syarat_untuk_pakai_kupon_tambah_pesanan_agar_kupon_tetap_bisa_terpakai');
  1388.  
  1389.   const checkCartUpdates = () => {
  1390.     const cb = (data: any, sameCart?: boolean) => {
  1391.       if (!data.items?.length) {
  1392.         dispatch(
  1393.           statusObject.actions.errorSetNew({
  1394.             errorCode: ERR_TYPES.NO_CHECKOUT_ITEMS,
  1395.             errorMessage: getErrorMessage(ERR_TYPES.NO_CHECKOUT_ITEMS),
  1396.           }),
  1397.         );
  1398.         setNoItemToBePaid(true);
  1399.       } else if (sameCart == false) {
  1400.         dispatch(
  1401.           statusObject.actions.errorSetNew({
  1402.             errorCode: 'cartUpdated',
  1403.             errorMessage: 'produk_yang_akan_kamu_bayar_mengalami_perubahan_silakan_periksa_lagi_produk_pilihan_kamu',
  1404.           }),
  1405.         );
  1406.       } else {
  1407.         handleClickPay();
  1408.       }
  1409.     };
  1410.     dispatch(paymentObject.actions.requestItemsToBePaid(cb, itemKey, true));
  1411.   };
  1412.  
  1413.   const handleClickPay = () => {
  1414.     if (equippedCoupon) {
  1415.       const newCouponValiData: ICouponValidationData = {
  1416.         game_ids: currentItemsToBePaid?.items?.map((item: any) => item.gameId) || [],
  1417.         item_type_ids: currentItemsToBePaid?.items?.map((item: any) => item.itemTypeId),
  1418.         payment_method: usedPaymentMethod?.id,
  1419.         total_price: totalPayment,
  1420.       };
  1421.       const isPaymentValid = paymentMethodCouponValidation(equippedCoupon, newCouponValiData);
  1422.       if (!isPaymentValid.success) {
  1423.         dispatch(
  1424.           statusObject.actions.errorSetNew({
  1425.             errorCode: isPaymentValid.code,
  1426.             errorMessage: isPaymentValid.message,
  1427.           }),
  1428.         );
  1429.       } else if (equippedCoupon.minimum_purchase < totalPayment) {
  1430.         if (paymentBCA && unpayBCAPayment) {
  1431.           bcaVirtualAccountPaymentDialog();
  1432.         } else {
  1433.           doPayClickPay();
  1434.         }
  1435.       } else {
  1436.         dispatch(
  1437.           navigationObject.actions.modalNavigateTo(Types.Dialog, {
  1438.             title: couponErrorTitleText,
  1439.             mainActionTitle: couponErrorMainText,
  1440.             cancelActionTitle: couponErrorCancelText,
  1441.             description: (
  1442.               <p>
  1443.                 {couponErrorDescText}
  1444.               </p>
  1445.             ),
  1446.             mainAction: () => {
  1447.               dispatch(couponObject.actions.clearUserCoupoun());
  1448.               doPayClickPay();
  1449.             },
  1450.             cancelAction: () => {
  1451.               dispatch(navigationObject.actions.routeNavigateTo({ routeName: ROUTER_NAME.HOME }));
  1452.             },
  1453.           }),
  1454.         );
  1455.       }
  1456.     } else if (paymentBCA && unpayBCAPayment) {
  1457.       bcaVirtualAccountPaymentDialog();
  1458.     } else {
  1459.       doPayClickPay();
  1460.     }
  1461.   };
  1462.  
  1463.   // End Proceed Payment //
  1464.  
  1465.   // Continue payment anyway //
  1466.   // This is for when delivery guarantee changes, but user wants to continue anyway //
  1467.   const [continuePayStart, setContinuePayStart] = useState(false);
  1468.   useEffect(() => {
  1469.     if (continuePayStart) {
  1470.       setContinuePayStart(false);
  1471.       handleClickPay();
  1472.     }
  1473.   }, [continuePayStart]);
  1474.  
  1475.   const handleDeliveryGuaranteeChangedContinue = () => {
  1476.     if (transactionData) {
  1477.       setTransactionData({
  1478.         source: transactionData.source,
  1479.         payload: {
  1480.           ...transactionData.payload,
  1481.           is_from_quickbuy: false,
  1482.         },
  1483.       });
  1484.       setContinuePayStart(true);
  1485.     }
  1486.   };
  1487.   // End continue payment anyway //
  1488.  
  1489.   // Handle Back //
  1490.   const handleBack = () => {
  1491.     dispatch(navigationObject.actions.routeGoBack());
  1492.   };
  1493.  
  1494.   useEffect(() => {
  1495.     if (noItemToBePaid) {
  1496.       const prevPage = new URL(location.href).searchParams.get('prev_page');
  1497.       if (prevPage) {
  1498.         router.replace(decodeURIComponent(prevPage));
  1499.       } else {
  1500.         dispatch(navigationObject.actions.routeGoBack());
  1501.       }
  1502.     }
  1503.   }, [noItemToBePaid]);
  1504.   // End Handle Back //
  1505.  
  1506.   const finalAmount = useMemo(() => {
  1507.     return totalPayment + totalExtraCharge - couponDiscount - (isUsingCoin[itemKey] ? coinDiscount : 0);
  1508.   }, [totalPayment, totalExtraCharge, couponDiscount, isUsingCoin, itemKey, coinDiscount]);
  1509.  
  1510.   const convertedFinalAmount = useMemo(() => {
  1511.     return convertedTotalPayment + convertedExtraCharge;
  1512.   }, [convertedTotalPayment, convertedExtraCharge]);
  1513.  
  1514.   const paymentRecapProps = () => {
  1515.     // const officialStoreItems: Payments = itemToPay.slice(0, 2).map((data: any) => {
  1516.     //   return {
  1517.     //     name: data.itemInfo,
  1518.     //     nominal: data.price,
  1519.     //     qty: data.totalItem,
  1520.     //   };
  1521.     // });
  1522.  
  1523.     switch (paymentSource) {
  1524.       // case PAYMENT_SOURCE.OFFICIAL_STORE_TOPUP:
  1525.       case PAYMENT_SOURCE.OFFICIAL_STORE_VOUCHER:
  1526.         return {
  1527.           paymentId: selectedPaymentMethod[itemKey]?.id,
  1528.           paymentName: usedPaymentMethod?.name || '',
  1529.           totalPayment: totalPayment + totalDiscountPrice, // OS voucher base price, before Discount
  1530.           extraCharge: extraCharge,
  1531.           grandTotal: totalPayment + totalExtraCharge - couponDiscount,
  1532.           payments: [],
  1533.           discount: (totalDiscountPrice * 100) / (totalDiscountPrice + totalPayment), // Discount game
  1534.           discountCoupon: getDefault(equippedCoupon?.discount_rate, 0),
  1535.           discountPriceCoupon: couponDiscount,
  1536.           discountPrice: totalDiscountPrice,
  1537.           usedDompetkuBalance: 0,
  1538.           withPaymentInfo: false,
  1539.         };
  1540.       default:
  1541.         return {
  1542.           paymentId: selectedPaymentMethod[itemKey]?.id,
  1543.           paymentName: usedPaymentMethod?.name || '',
  1544.           totalPayment: !isGlobal ? totalPayment : convertedTotalPayment,
  1545.           extraCharge: extraCharge,
  1546.           grandTotal: !isGlobal ? finalAmount : convertedFinalAmount,
  1547.           payments: [],
  1548.           discount: getDefault(equippedCoupon?.discount_rate, 0),
  1549.           discountPrice: couponDiscount,
  1550.           usedDompetkuBalance: 0,
  1551.           withPaymentInfo: false,
  1552.           usedItemkuWorldCoin: isUsingCoin[itemKey] ? coinDiscount : 0,
  1553.           itemkuWorldCoinMaxDisc: coinData ? coinData.max_percent_discount : 0,
  1554.         };
  1555.     }
  1556.   };
  1557.  
  1558.   // NEW DOMPETKU ENGAGEMENT BEGIN
  1559.   const [isDompetkuEngagementShown, setIsDompetkuEngagementShown] = useState<Boolean>(false);
  1560.   const handleDompetkuEngagementButton = (dataUser: UserDetail) => {
  1561.     if (!dataUser.isEmailVerified) {
  1562.       dispatch(
  1563.         navigationObject.actions.modalNavigateTo(Types.Dialog, {
  1564.           ...dompetkuEngagementDialog.email,
  1565.           mainAction: () =>
  1566.             dispatch(navigationObject.actions.routeNavigateTo({ routeName: ROUTER_NAME.EMAIL_VERIFICATION })),
  1567.         }),
  1568.       );
  1569.     } else if (!dataUser.isPhoneVerified) {
  1570.       dispatch(
  1571.         navigationObject.actions.modalNavigateTo(Types.Dialog, {
  1572.           ...dompetkuEngagementDialog.phone,
  1573.           mainAction: () =>
  1574.             dispatch(navigationObject.actions.routeNavigateTo({ routeName: ROUTER_NAME.PHONE_VERIFICATION })),
  1575.         }),
  1576.       );
  1577.     } else if (!dataUser.hasSetupPIN) {
  1578.       dispatch(
  1579.         navigationObject.actions.modalNavigateTo(Types.Dialog, {
  1580.           ...dompetkuEngagementDialog.pin,
  1581.           mainAction: () => dispatch(navigationObject.actions.routeNavigateTo({ routeName: ROUTER_NAME.PIN_ADD })),
  1582.         }),
  1583.       );
  1584.     } else {
  1585.       showDompetkuDialog('topup-engagement');
  1586.     }
  1587.   };
  1588.   // NEW DOMPETKU ENGAGEMENT END
  1589.  
  1590.   // PRODUCT SUBSCRIPTION
  1591.   const [isProductSubs, setProductSubs] = useState<boolean>(false);
  1592.   const productId = currentItemsToBePaid?.items?.[0]?.productId;
  1593.   const subscriptionDetail = useSelector((state: IReducers) => state.productDetailReducers.productSubscriptionDetail);
  1594.   let expireDateSubscription = new Date();
  1595.  
  1596.   if (subscriptionDetail) {
  1597.     expireDateSubscription = addDays(new Date(), subscriptionDetail.subscription_day);
  1598.   }
  1599.  
  1600.   useEffect(() => {
  1601.     dispatch(persistObject.actions.clearSubscriptionStatus());
  1602.   }, []);
  1603.  
  1604.   useEffect(() => {
  1605.     if (productId) {
  1606.       dispatch(productDetailObject.actions.requestSubscriptionDetail(productId));
  1607.     }
  1608.   }, [productId]);
  1609.  
  1610.   useEffect(() => {
  1611.     if (paymentSource !== PAYMENT_SOURCE.QUICK_BUY
  1612.       && paymentSource !== PAYMENT_SOURCE.PRODUCT_DETAIL
  1613.       && paymentSource !== PAYMENT_SOURCE.SUBSCRIPTION_BUY_NOW) {
  1614.       setProductSubs(false);
  1615.       return;
  1616.     }
  1617.  
  1618.     if (subscriptionDetail == null) {
  1619.       setProductSubs(false);
  1620.     } else {
  1621.       setProductSubs(true);
  1622.       if (paymentSource === PAYMENT_SOURCE.SUBSCRIPTION_BUY_NOW) {
  1623.         setSubscription(true);
  1624.       }
  1625.     }
  1626.   }, [subscriptionDetail, paymentSource]);
  1627.   // PRODUCT SUBSCRIPTION END
  1628.  
  1629.   // CURRENCY
  1630.   const regionData = useSelector((state: IReducers) => state.regionReducers.regionData);
  1631.   const currency = useSelector((state: IReducers) => state.regionReducers.regionData.country_currency) || 'IDR';
  1632.   const { foreignExchangeRate, loadForeignExchangeRate } = CurrencyHandler.useForeignExchangeRate();
  1633.  
  1634.   React.useEffect(() => {
  1635.     loadForeignExchangeRate(currency);
  1636.   }, [currency]);
  1637.   // CURRENCY END
  1638.  
  1639.   const paymentError = useLanguage('terjadi_kesalahan_silakan_coba_lagi');
  1640.   const subscriptionTitle = useLanguage('langganan_produk');
  1641.   const subscriptionMessageDompetku = useLanguage('dengan_membayar_menggunakan_dompetku_pada_pembelian_ini_autodebit_dompetku_akan_otomatis_aktif_untuk_bayar_tagihan_langganan_berikutnya');
  1642.   const subscriptionMessage = useLanguage('bayar_tagihan_langganan_berikutnya_dengan_autodebit_dompetku_aktivasi_autodebit_dompetku_dapat_dilakukan_setelah_pembelian_ini_selesai');
  1643.  
  1644.   const paymentMethodLabel = useLanguage('metode_pembayaran');
  1645.   const viewAllLabel = useLanguage('lihat_semua');
  1646.   const lastPaymentMethodLabel = useLanguage('terakhir_dipakai');
  1647.   const suggestionPaymentMethodLabel = useLanguage('dana_yang_akan_digunakan');
  1648.   const choosePaymentMethodLabel = useLanguage('pilih_metode_pembayaran');
  1649.   const cashbackLabel = useLanguage('cashback_dompetku');
  1650.  
  1651.   const PaymentElement: React.ReactElement = (
  1652.     <div className='w-full max-w-600px min-h-screen pb-14'>
  1653.       <Head>
  1654.         <title>{headerTitleHelper(paymentSource)} | itemku</title>
  1655.       </Head>
  1656.       <PinBottomSheet
  1657.         show={pinBottomSheet.isOpen}
  1658.         close={() => setPinBottomSheet({ ...pinBottomSheet, isOpen: false })}
  1659.         error={pinBottomSheet.wrongPin}
  1660.         errorMsg={pinBottomSheet.errorMessage}
  1661.         handleGetPin={handleInputPin}
  1662.         forgotPIN={() =>
  1663.           requestResetPinOTP((isSuccess: boolean) => {
  1664.             if (isSuccess) router.push({ pathname: routes.pin.route + 'reset-pin' });
  1665.             else setPinBottomSheet({ ...pinBottomSheet, errorMessage: paymentError });
  1666.           })
  1667.         }
  1668.         value={pinInteractionValue}
  1669.       />
  1670.       <div className='w-full pb-14'>
  1671.         <div className='w-full sticky top-0 z-30'>
  1672.           <HeaderBack
  1673.             title={useLanguage(headerTitleHelper(paymentSource))}
  1674.             leftOnClick={() => setDialog({ ...dialog, closePayment: true })}
  1675.           />
  1676.         </div>
  1677.         {dialog.cancelPayment ? (
  1678.           <PaymentSkeleton />
  1679.         ) : (
  1680.           <>
  1681.             {paymentSource === PAYMENT_SOURCE.OFFICIAL_STORE_VOUCHER ? (
  1682.               <>
  1683.                 <UserDetailInput
  1684.                   value={voucherBuyerInfo}
  1685.                   valueCallback={(value: any) => setVoucherBuyerInfo(value)}
  1686.                   nameError={!!errorVoucherBuyerInfo.name}
  1687.                   phoneNumberError={!!errorVoucherBuyerInfo.phoneNumber}
  1688.                   nameErrorMsg={errorVoucherBuyerInfo.name}
  1689.                   phoneNumberErrorMsg={errorVoucherBuyerInfo.phoneNumber}
  1690.                 />
  1691.                 <Divider variant='filled' />
  1692.               </>
  1693.             ) : null}
  1694.             <div className='w-full px-4 py-6'>
  1695.               <ShoppingRecapSection
  1696.                 productItem={itemToPay as IPaidItem[]}
  1697.                 paymentSource={paymentSource}
  1698.                 isUsingCoin={isUsingCoin[itemKey]}
  1699.                 validDiscount={validDiscount}
  1700.                 coinData={coinData}
  1701.                 coinDiscount={coinDiscount}
  1702.               />
  1703.             </div>
  1704.             {/* langganan produk */}
  1705.             {isGlobal && hiddenFeatureConfig[HIDDEN_FEATURE_FOR_GLOBAL.HIDE_SUBSCRIPTION] ? null : isProductSubs && paymentSource !== PAYMENT_SOURCE.SUBSCRIPTION_BUY_NOW &&
  1706.             <div>
  1707.               <Divider variant='filled' />
  1708.               <div className='p-4 space-y-4'>
  1709.                 <h3 className='text-xl text-nero font-bold'>{subscriptionTitle}</h3>
  1710.                 <CardProductSubscription
  1711.                   checkedState={isSubscription}
  1712.                   handleCheck={() => setSubscription((prev) => !prev)}
  1713.                   message={usedPaymentMethod?.name === 'Dompetku'
  1714.                     ? subscriptionMessageDompetku
  1715.                     : subscriptionMessage}
  1716.                   expiredDate={format(expireDateSubscription, 'yyyy-MM-dd hh:mm:ss')}
  1717.                   subscriptionPeriod={subscriptionDetail?.subscription_day}
  1718.                 />
  1719.               </div>
  1720.             </div>
  1721.             }
  1722.             <Divider variant='filled' />
  1723.             <div className='w-full px-4 py-6'>
  1724.               {latestPayment && (
  1725.                 <div className='w-full flex flex-row justify-between mb-2'>
  1726.                   <h3 className='text-xl text-nero font-bold'>{paymentMethodLabel}</h3>
  1727.                   {Object.keys(latestPayment).length > 0 ||
  1728.                   (usedPaymentMethod?.name === 'dompetku' &&
  1729.                     userDetail.isEmailVerified &&
  1730.                     userDetail.isPhoneVerified &&
  1731.                     userDetail.hasSetupPIN) ? (
  1732.                       <ButtonText label={viewAllLabel} onClick={handleGoToPaymentMethod} />
  1733.                     ) : null}
  1734.                 </div>
  1735.               )}
  1736.               {Object.keys(latestPayment).length > 0 && latestPayment.name === usedPaymentMethod?.name && (
  1737.                 <h3 className='text-lg text-zambesi mb-3'>{lastPaymentMethodLabel}</h3>
  1738.               )}
  1739.               <CardPaymentMethod
  1740.                 paymentName={usedPaymentMethod?.name || ''}
  1741.                 extraChargeDesc={''} // updated reason: no longer need payment method fee text
  1742.                 itemImageURL={usedPaymentMethod?.media_url}
  1743.                 differentiator={usedPaymentMethod?.name === 'Dompetku' ? 'static-dompetku' : 'static-payment-method'}
  1744.                 totalPayment={totalPayment + totalExtraCharge}
  1745.                 walletBalance={userDetail.dompetkuBalance}
  1746.                 onClick={dompetkuOnClick}
  1747.                 withButton={dompetkuWithButton}
  1748.                 dompetkuButtonLabel={dompetkuButtonLabel}
  1749.                 paymentDisabled={false}
  1750.                 paymentInformationText={usedPaymentMethod?.payment_information_text}
  1751.                 withSuggestion
  1752.                 suggestionDesc={suggestionPaymentMethodLabel + ' Rp' + moneyDot(finalAmount)}
  1753.                 isFromPaymentPage
  1754.                 isCreditCard={Boolean(usedPaymentMethod?.is_credit_card)}
  1755.                 icon={usedPaymentMethod?.is_credit_card ? 'credit-card-color' : usedPaymentMethod?.icon}
  1756.               />
  1757.               {isDompetkuEngagementShown && (
  1758.                 <div className='w-full mt-4'>
  1759.                   <CardDompetkuEngagement
  1760.                     isDompetkuActivated={
  1761.                       userDetail.isEmailVerified && userDetail.isPhoneVerified && userDetail.hasSetupPIN
  1762.                     }
  1763.                     onButtonClick={() => handleDompetkuEngagementButton(userDetail)}
  1764.                   />
  1765.                 </div>
  1766.               )}
  1767.               {usedPaymentMethod?.name === 'Dompetku' &&
  1768.                 dompetkuButton && ( // hide it anyway if user has sufficient balance to pay
  1769.                 <div className='mt-4'>
  1770.                   <Banner
  1771.                     differentiator={errorBanner ? 'error' : 'alert'}
  1772.                     message={dompetkuButton.desc}
  1773.                     action={dompetkuButton.label}
  1774.                     button={dompetkuButton.label !== ''}
  1775.                     onClick={dompetkuButton.onClick}
  1776.                     icon={false}
  1777.                     border
  1778.                   />
  1779.                 </div>
  1780.               )}
  1781.               {usedPaymentMethod?.name !== 'Dompetku' && latestPaymentErrorBanner !== '' && (
  1782.                 <div className='mt-4'>
  1783.                   <Banner
  1784.                     differentiator={errorBanner ? 'error' : 'alert'}
  1785.                     message={latestPaymentErrorBanner}
  1786.                     button={false}
  1787.                     icon={false}
  1788.                     border
  1789.                   />
  1790.                 </div>
  1791.               )}
  1792.               {Object.keys(latestPayment).length === 0 ||
  1793.               (usedPaymentMethod?.name === 'dompetku' &&
  1794.                 !userDetail.isEmailVerified &&
  1795.                 !userDetail.isPhoneVerified &&
  1796.                 !userDetail.hasSetupPIN) ? (
  1797.                   <div className='w-full mt-6'>
  1798.                     <ButtonOutlined label={choosePaymentMethodLabel} onClick={handleGoToPaymentMethod} big full />
  1799.                   </div>
  1800.                 ) : null}
  1801.             </div>
  1802.             {isGlobal && hiddenFeatureConfig[HIDDEN_FEATURE_FOR_GLOBAL.HIDE_COUPON] ? null : (<>
  1803.               <Divider variant='filled' />
  1804.               <div className='w-full px-4 py-6 space-y-3'>
  1805.                 <CardCoupon
  1806.                   totalCoupon={totalCoupon}
  1807.                   isCouponUsed={equippedCoupon !== null}
  1808.                   couponName={equippedCoupon?.name}
  1809.                   onClick={handleSeeCoupon}
  1810.                   hasCoupon={coupons && totalCoupon !== 0}
  1811.                 />
  1812.                 {/* {usedPaymentMethod?.name !== 'Dompetku' && userDetail.usedCoupon.length > 0 && (
  1813.                       <Banner
  1814.                         differentiator={errorBanner ? 'error' : 'neutral'}
  1815.                         message="Penggunaan kupon hanya dapat digunakan melalui metode pembayaran Dompetku"
  1816.                         button={false}
  1817.                         icon
  1818.                         border
  1819.                       />
  1820.                     )} */}
  1821.               </div>
  1822.             </>)}
  1823.             {isGlobal && hiddenFeatureConfig[HIDDEN_FEATURE_FOR_GLOBAL.HIDE_COMMUNITY] ? null : hasItemkuWorldProduct && (
  1824.               <>
  1825.                 <Divider variant='filled' />
  1826.                 <ItemkuWorldCoin
  1827.                   ownedCoin={ownedCoin}
  1828.                   usedItemkuWorldCoin={usedItemkuWorldCoin}
  1829.                   usingCoinCallback={(isUsed: boolean) => handleToggleUsingCoin(isUsed)}
  1830.                   isUsingCoin={isUsingCoin[itemKey]}
  1831.                   // isDisabled={ownedCoin <= 0}
  1832.                 />
  1833.               </>
  1834.             )}
  1835.             <Divider variant='filled' />
  1836.             {(isGlobal && hiddenFeatureConfig[HIDDEN_FEATURE_FOR_GLOBAL.HIDE_PREMIUM]) || paymentSource === PAYMENT_SOURCE.OFFICIAL_STORE_VOUCHER ||
  1837.             paymentSource === PAYMENT_SOURCE.OFFICIAL_STORE_TOPUP ? null : (
  1838.                 <>
  1839.                   <div className='w-full px-4 py-6'>
  1840.                     <CardPremiumOption
  1841.                       disable={userData?.is_user_member}
  1842.                       premiumPrice={premiumBuyerPrice}
  1843.                       isChecked={premiumOption[itemKey]}
  1844.                       getCheckedStatus={handleCheckPremiumOption}
  1845.                     />
  1846.                   </div>
  1847.                   <Divider variant='filled' />
  1848.                 </>
  1849.               )}
  1850.             <div className='w-full px-4 py-6 space-y-4'>
  1851.               <CardPaymentRecap {...paymentRecapProps()} />
  1852.               {/* show if any cashback */}
  1853.               {cashback ? (
  1854.                 <Banner
  1855.                   message={cashbackLabel + ' Rp' + moneyDot(Math.abs(cashback))}
  1856.                   differentiator='dompetku-cashback'
  1857.                   border
  1858.                 />
  1859.               ) : null}
  1860.             </div>
  1861.           </>
  1862.         )}
  1863.       </div>
  1864.       <Dialog
  1865.         title={useLanguage('transaksi_gagal')}
  1866.         description={useLanguage('produk_yang_ingin_kamu_beli_sudah_tidak_tersedia_atau_toko_sedang_tutup')}
  1867.         isActive={dialog.failedTransaction}
  1868.         mainActionTitle={useLanguage('cari_produk_lain')}
  1869.         cancelActionTitle=''
  1870.         mainAction={() => dispatch(navigationObject.actions.routeNavigateTo({ routeName: ROUTER_NAME.PRODUCT }))}
  1871.         cancelAction={() => setDialog({ ...dialog, failedTransaction: false })}
  1872.         getChildrenActiveState={(status: boolean) => setDialog({ ...dialog, failedTransaction: status })}
  1873.         {...dialog.dialogProps}
  1874.       />
  1875.       <Dialog
  1876.         title={useLanguage('keluar_dari_halaman_pembayaran')}
  1877.         description={useLanguage('kamu_akan_keluar_dari_halaman_pembayaran_dan_membatalkan_pembelian')}
  1878.         isActive={dialog.closePayment}
  1879.         mainActionTitle={useLanguage('lanjut_bayar')}
  1880.         cancelActionTitle={useLanguage('keluar')}
  1881.         getChildrenActiveState={(isActive: boolean) => setDialog({ ...dialog, closePayment: isActive })}
  1882.         mainAction={() => setDialog({ ...dialog, closePayment: false })}
  1883.         cancelAction={handleBack}
  1884.       />
  1885.       <div className='w-full fixed bottom-0 overflow-hidden max-w-600px pt-1'>
  1886.         <StickyButtonWithText
  1887.           label={useLanguage('bayar')}
  1888.           contentTitle={useLanguage('total_pembayaran')}
  1889.           contentPrice={!isGlobal ? finalAmount : convertedFinalAmount}
  1890.           disabled={totalPayment <= 0 || totalPayment == null || isLoading}
  1891.           onClick={paymentSource == PAYMENT_SOURCE.CART ? checkCartUpdates : handleClickPay}
  1892.         />
  1893.       </div>
  1894.     </div>
  1895.   );
  1896.  
  1897.   const waitPaymentText = useLanguage('segera_selesaikan_pembayaranmu_menggunakan_metode_pembayaran_yang_kamu_pilih');
  1898.   // begin loading
  1899.   useEffect(() => {
  1900.     if (dialog.loadPayment) {
  1901.       dispatch(
  1902.         loadingComponentObject.actions.triggerLoading({
  1903.           type: 'payment-as-page',
  1904.           paymentWording: waitPaymentText,
  1905.         }),
  1906.       );
  1907.     } else {
  1908.       dispatch(loadingComponentObject.actions.resetLoading());
  1909.     }
  1910.   }, [dialog]);
  1911.   // end loading
  1912.  
  1913.   /**
  1914.    * Note:
  1915.    * Not wrapping with CheckLogin because it will cause infinite loading.
  1916.    */
  1917.   const isLogin = useLogin();
  1918.   const headerTitle = useLanguage('pembayaran');
  1919.  
  1920.   return !isLogin ? null : (
  1921.     <TransactionBoundary
  1922.       transactionData={transactionData}
  1923.       handleTransactionCallback={handleTransactionCallback}
  1924.       onDeliveryGuaranteeChangedContinue={handleDeliveryGuaranteeChangedContinue}>
  1925.       {isLoading ? (
  1926.         <>
  1927.           <div className='w-full sticky top-0 z-30'>
  1928.             <HeaderBack title={headerTitle} />
  1929.           </div>
  1930.           <PaymentSkeleton />
  1931.         </>
  1932.       ) : (
  1933.         PaymentElement
  1934.       )}
  1935.     </TransactionBoundary>
  1936.   );
  1937. };
  1938.  
  1939. export default PaymentTemplate;
  1940.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement