| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429 |
- "use client";
- import {useState, useRef, useEffect, useCallback } from "react";
- import clsx from "clsx";
- import { useCustomToast } from "@/utils/hooks/useToast";
- import {useCheckoutPaymentMethod} from "@/utils/hooks/useCheckoutPaymentMethod";
- import {useCheckoutShippingMethod} from "@/utils/hooks/useCheckoutShippingMethod";
- import { useAppDispatch } from "@/store/hooks";
- import { updateCart, clearCart } from "@/store/slices/cart-slice";
- import {useSaveCheckoutCart} from "@/utils/hooks/useSaveCheckoutCart";
- import { usePlaceOrder } from "@/utils/hooks/usePlaceOrder";
- import {usePaymentSDKContext} from "@/providers/PaymentSDKProvider";
- import {
- PlaceOrderResult,
- PlaceOrderFunction,
- CreatePaymentInitiateVariables
- } from "@/types/checkout/type";
- import { CartDetail,CartAddress } from "@/types/cart/type";
- import { formatCartDetail } from "@/utils/cartDetailTools";
- import { overlayLoading } from "@/components/theme/ui/kernel/loading/api";
- import { confirmDialog } from "@/components/theme/ui/kernel/confirm/api";
- import {ShippingMethodCheckout,RefShippingMethodsHandle} from "./ShippingMethodCheckout";
- import {PaymentMethodCheckout,RefPaymentMethodsHandle} from "./PaymentMethodCheckout";
- import {CheckoutAddress, RefCheckoutAddressHandle} from "./CheckoutAddress/CheckoutAssress";
- import LoadingCheckoutPlaceOrder from "./LoadingCheckoutPlaceOrder";
- import CheckoutPlaceOrder from "./CheckoutPlaceOrder";
- import PaypalApplepayButton from "./PaymentButton/PaypalApplepayButton";
- import CheckoutGiftCardGate from "./GiftCard/CheckoutGiftCardGate";
- import PromotionsDetails from "./PromotionsDetails";
- import CheckoutVipPlus from "./VipPlus/CheckoutVipPlus";
- import CheckoutProducts from "./CheckoutProducts/CheckoutProducts";
- import CheckoutCoupon from "./CheckoutCoupon/CheckoutCoupon";
- /***
- * 保存完地址之后要重新获取运输方式和支付方式
- * 变更运输方式后重新获取支付方式
- * @todo 购物车为空 跳转到空购物车页面
- */
- export default function CheckoutWrapper({
- loginEmail,
- cartDetailData,
- }: {
- loginEmail: string;
- cartDetailData: CartDetail;
- }) {
- const erudaCount = useRef(0);
- const {loadSdk} = usePaymentSDKContext();
- const { createOrder } = usePlaceOrder();
-
- const dispatch = useAppDispatch();
-
- const [cartData, setCartData] = useState<CartDetail>(cartDetailData);
- const {saveCheckoutCart} = useSaveCheckoutCart();
-
- const {
- data: shippingMethodDatas,
- loading: shippingMethodLoading,
- error: shippingMethodError,
- getShippingMethod,
- } = useCheckoutShippingMethod();
- const selectedPaymentMethod = cartData.paymentMethod || 'paypal_smart_button';
- const selectedShippingRate = cartData.selectedShippingRate;
-
- const {
- data: paymentMethodDatas,
- loading: paymentMethodLoading,
- error: paymentMethodError,
- getPaymentMethod,
- } = useCheckoutPaymentMethod();
- const { showToast } = useCustomToast();
- const checkoutAddressRef = useRef<RefCheckoutAddressHandle>(null);
- const methodShiippingRef = useRef<RefShippingMethodsHandle>(null);
- const methodPaymentRef = useRef<RefPaymentMethodsHandle>(null);
-
- const changePaymentMethodToloadSdk = (method:string) => {
- if(method === 'klarna' ||
- method === 'afterpay' ||
- method === 'clearpay' ||
- method === 'applepay'
- ) {
- loadSdk(method);
- }
- };
- useEffect(() => {
- changePaymentMethodToloadSdk(selectedPaymentMethod);
- }, [selectedPaymentMethod]);
- const dispatchCartStore = useCallback((action: "clear" | "update", payload?:Partial<CartDetail>) => {
- if(action === "clear") {
- dispatch(clearCart());
- } else if(action === "update" && payload) {
- dispatch(updateCart(payload));
- }
- },[dispatch]);
- const saveAddressCallback = async (resData: {billingAddress: CartAddress; shippingAddress: CartAddress;}) => {
- await Promise.all([getShippingMethod(),getPaymentMethod()]);
- dispatchCartStore('update',{
- billingAddress: resData.billingAddress,
- shippingAddress: resData.shippingAddress
- });
- setCartData(Object.assign({...cartData},{
- billingAddress: resData.billingAddress,
- shippingAddress: resData.shippingAddress
- }));
- };
- const handlePaymentMethodChange = async (method: string) => {
- changePaymentMethodToloadSdk(method);
-
- overlayLoading.start();
- const saveRes = await saveCheckoutCart({
- shippingMethod: '-1',
- paymentMethod: method,
- couponCode: '-1',
- giftCardNumber: '-1',
- memberDiscount: '-1'
- });
- overlayLoading.stop();
- if(!saveRes.error) {
- if(saveRes.data) {
- const newCartDetail = formatCartDetail(saveRes.data);
- dispatchCartStore('update',newCartDetail);
- setCartData({...newCartDetail});
- }
- return true;
- } else {
- showToast(saveRes.msg, 'danger');
- return false;
- }
- };
- const handleShippingMethodChange = useCallback(async (method: string) => {
- overlayLoading.start();
- const saveRes = await saveCheckoutCart({
- shippingMethod: method,
- paymentMethod: '-1',
- couponCode: '-1',
- giftCardNumber: '-1',
- memberDiscount: '-1'
- });
- overlayLoading.stop();
- if(!saveRes.error) {
- if(saveRes.data) {
- const newCartDetail = formatCartDetail(saveRes.data);
- dispatchCartStore('update',newCartDetail);
- setCartData({...newCartDetail});
- }
- // 保存成功之后需要重新获取支付方式
- getPaymentMethod();
- } else {
- showToast(saveRes.msg, 'danger');
- }
- },[saveCheckoutCart,showToast,getPaymentMethod,dispatchCartStore]);
- // 校验地址 运输方式,支付方式;
- const validateCheckout = useCallback(async () => {
-
- if(checkoutAddressRef.current) {
- const addressValid = await checkoutAddressRef.current.validateAddressForm();
- if(!addressValid) {
- showToast('Please check your address.', 'danger');
- return false;
- }
- } else {
- showToast('Please wait for address form loading.', 'danger');
- return false;
- }
- if(methodShiippingRef.current) {
- const shippingMethodValid = await methodShiippingRef.current.validateShipMethod();
- if(!shippingMethodValid) {
- showToast('Please select a shipping method.', 'danger');
- return false;
- }
- } else {
- showToast('Please wait for shipping method loading.', 'danger');
- return false;
- }
- if(methodPaymentRef.current) {
- const paymentMethodValid = await methodPaymentRef.current.validatePaymentMethod();
- const paymentMethod = methodPaymentRef.current.getSelectPaymentMethod();
- if(!paymentMethodValid) {
- showToast('Please select a payment method.', 'danger');
- return false;
- }
- if(paymentMethod === 'airwallex') {
- const paymentMethodValid = methodPaymentRef.current.validateAirwallexCard();
- return paymentMethodValid;
- }
- } else {
- showToast('Please wait for payment method loading.', 'danger');
- return false;
- }
- if(!cartData) {
- showToast('Please add product to your cart.', 'danger');
- return false;
- }
-
- //如果购物车详情没有保存过支付方式,则需要保存支付方式
- if(cartData.paymentMethod === null) {
- const pmethod = methodPaymentRef.current.getSelectPaymentMethod();
- const saveRes = await saveCheckoutCart({
- shippingMethod: '-1',
- paymentMethod: pmethod,
- couponCode: '-1',
- giftCardNumber: '-1',
- memberDiscount: '-1'
- });
- if(!saveRes.error) {
- if(saveRes.data) {
- const newCartDetail = formatCartDetail(saveRes.data);
- dispatchCartStore('update',newCartDetail);
- setCartData({...newCartDetail});
- }
- } else {
- showToast(saveRes.msg, 'danger');
- }
- }
- return true;
- },[showToast,saveCheckoutCart,dispatchCartStore]);
- // 苹果支付下单
- const applepayPlaceOrder = useCallback(async () => {
- const res = await createOrder();
- if(!res.error) {
- //创建订单成功后清空redux 购物车
- dispatchCartStore('clear');
- return {
- orderId: res.data?.gatewayOrderId ?? '',
- webOrderId: res.data?.orderId ?? ''
- };
- } else{
- // 抛出错误让第三方支付能够捕获
- throw new Error("CREATE_ORDER_ERROR: " + res.msg);
- }
- },[createOrder,dispatchCartStore]);
- // 下单(下单和校验分开)
- const handlePlaceOrder: PlaceOrderFunction = async <T,>(params?: CreatePaymentInitiateVariables): Promise<PlaceOrderResult<T>> => {
- const res = await createOrder(params);
- if(res.error) {
- overlayLoading.stop();
- confirmDialog({
- title: "Warning",
- content: res.msg + ". Create order failed. Please try again.",
- noCancel: true,
- }).then(() => {});
- } else {
- //创建订单成功后清空redux 购物车
- dispatchCartStore('clear');
- }
- const pmethod = methodPaymentRef.current?.getSelectPaymentMethod();
- let resultData: PlaceOrderResult<T>;
- if(pmethod === 'airwallex' && methodPaymentRef.current) {
- const airwallexCardElements = methodPaymentRef.current.getAirwallexCardElements();
- const airCardNumber = airwallexCardElements.cardNumber as T;
- resultData = {
- data: res.data,
- otherData: airCardNumber
- };
- } else if(pmethod === 'klarna') {
- const klaData = {
- billingAddress: cartData.billingAddress,
- shippingAddress: cartData.shippingAddress
- } as T;
- resultData = {
- data: res.data,
- otherData: klaData
- };
- } else {
- resultData = {
- data: res.data,
- otherData: null
- };
- }
- return resultData;
- }
- const onCartDataChange = async (paylod: CartDetail, dispatch: boolean = true) => {
- if(dispatch) {
- dispatchCartStore('update',paylod);
- }
- setCartData({...paylod});
- // 还要请求支付方式和运输方式
- await Promise.all([getShippingMethod(),getPaymentMethod()]);
- };
-
- const testEruda = () => {
- erudaCount.current++
- if(window.eruda && erudaCount.current > 10) {
- window.eruda.init();
- }
- };
- useEffect(() => {
- console.log('checkoutWrapper mout==========================');
- return () => {
- console.log('checkoutWrapper unmout==========================');
- }
- },[]);
- return (<>
- <section className="w-full">
- <div>
- <CheckoutAddress
- ref={checkoutAddressRef}
- loginEmail={loginEmail}
- cartData={cartData}
- onSaveAddress={saveAddressCallback}
- />
-
-
- </div>
- <div className="mt-6 box-border px-4">
- <CheckoutProducts
- cartData={cartData}
- onCartChange={onCartDataChange}
- />
- </div>
- <div className="mt-6 box-border px-4">
- <h3 className="text-ly-24 font-medium" onClick={testEruda}>Shipping Method</h3>
-
- <ShippingMethodCheckout
- ref={methodShiippingRef}
- shipMethods={shippingMethodDatas}
- errorMsg={shippingMethodError}
- selectedShippingRate={selectedShippingRate}
- shippingMethodLoading={shippingMethodLoading}
- onShipMethodChange={handleShippingMethodChange}
- />
- </div>
- <div className="mt-6 box-border px-4">
- <CheckoutCoupon
- couponCode={cartData.couponCode || ''}
- discountAmount={cartData.formattedDiscountAmount}
- onCouponChange={onCartDataChange}
- />
- </div>
-
- {!cartData.isGuest &&
- <div className="mt-6 box-border px-4">
- <CheckoutGiftCardGate
- giftcardNumber={cartData.giftcardNumber || ''}
- formattedGiftcardAmount={cartData.formattedGiftcardAmount}
- onChangeGiftcard={onCartDataChange}
- />
- </div>
- }
- <div className="mt-6 box-border px-4">
- <CheckoutVipPlus
- cartData={cartData}
- onSwitchVip={onCartDataChange}
- />
- </div>
-
- <div className="mt-6 box-border px-4">
- <h3 className="text-ly-24 font-medium">Promotion Details</h3>
- <div className="w-full box-border mt-3">
- <PromotionsDetails cartData={cartData} />
- </div>
- </div>
- <div className="mt-6 box-border px-4">
- <h3 className="text-ly-24 font-medium">Payment Method</h3>
- <PaymentMethodCheckout
- ref={methodPaymentRef}
- paymentMethods={paymentMethodDatas}
- selectedPaymentMethod={selectedPaymentMethod}
- paymentMethodLoading={paymentMethodLoading}
- loadError={paymentMethodError}
- onPaymentMethodChange={handlePaymentMethodChange}
- />
-
-
- </div>
- <div className="mt-3 box-border px-4 pb-8">
- <p className="text-ly-12 text-[#666666] leading-ly-20">By providing your information, you agree to Wiggins'sPrivacy Policyand Terms of Use.</p>
- <div className="mt-6 w-full">
- { paymentMethodLoading && <LoadingCheckoutPlaceOrder />}
-
- <div className={clsx("w-full",{
- "hidden": selectedPaymentMethod !== 'applepay' || paymentMethodLoading
- })}>
- <PaypalApplepayButton
- grandTotal={cartData.grandTotal}
- billingContact={cartData.billingAddress}
- shippingContact={cartData.shippingAddress}
- validateCheckout={validateCheckout}
- createOrder={applepayPlaceOrder}
- />
- </div>
- <div className={clsx("w-full",{
- "hidden": selectedPaymentMethod === 'applepay' || paymentMethodLoading
- })}>
- <CheckoutPlaceOrder
- paymentMethod={selectedPaymentMethod}
- validateCheckout={validateCheckout}
- clickPlaceOrder={handlePlaceOrder}
- />
- </div>
-
- </div>
- </div>
- </section>
- </>);
- };
|