소스 검색

结账页地址保存优化;支付方式和运输方式板块优化;下单按钮显示隐藏使用display;运输方式不自动给用户选,让用户自己选。

fogwind 4 일 전
부모
커밋
5df1cf30cf

+ 10 - 4
src/app/(checkout)/checkout/_components/CheckoutPlaceOrder.tsx

@@ -1,5 +1,6 @@
 "use client";
 
+import clsx from 'clsx';
 import { useRouter } from 'next/navigation'
 import {AirwallexCartNumberElementType} from "@/lib/Airwallex/airwallexInit";
 import PaypalButton from "./PaymentButton/PaypalButton";
@@ -297,11 +298,14 @@ export default function CheckoutPlaceOrder({
     return (
         <>
             <div className="relative w-full">
-                {paymentMethod === 'paypal_smart_button' && 
+        
+                <div className={clsx("w-full",{
+                    "hidden": paymentMethod !== 'paypal_smart_button'
+                })}>
                     <PaypalButton
                         createOrder={paypalCreateOrder}
                     />
-                }
+                </div>
                 {(paymentMethod === 'awxklarna' || paymentMethod === 'awxafterpay') &&
                     <button className="flex items-center justify-center w-full h-12 bg-ly-green text-white rounded-3xl text-ly-16 font-bold"
                         onClick={airwallexPlaceOrder}
@@ -323,9 +327,11 @@ export default function CheckoutPlaceOrder({
                         Place Order
                     </button>
                 }
-                {paymentMethod === 'klarna' &&
+                <div className={clsx("w-full",{
+                    "hidden": paymentMethod !== 'klarna'
+                })}>
                     <KlarnaButton clickHandler={klarnaPlaceOrder} />
-                }
+                </div>
                 
 
             </div>

+ 169 - 106
src/app/(checkout)/checkout/_components/CheckoutWrapper.tsx

@@ -1,9 +1,17 @@
 "use client";
 
-import {useState, useRef, useEffect } from "react";
-import { LoadingSpinner } from "@components/common/LoadingSpinner";
-import CheckoutShippingMethodLoading from "./CheckoutShippingMethodLoading";
+import {useState, useRef, useEffect, useCallback } from "react";
+import clsx from "clsx";
 import { useForm, FormProvider } from "react-hook-form";
+import { useCustomToast } from "@/utils/hooks/useToast";
+import {useCheckoutAddress} from "@/utils/hooks/useCheckoutAddress"
+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 { 
     ShipAddressFormData,
     FullAddressFormData,
@@ -12,31 +20,26 @@ import {
     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 {normalizePhoneForForm} from "@/utils/phoneNumberTools";
+import { LoadingSpinner } from "@components/common/LoadingSpinner";
+import CheckoutShippingMethodLoading from "./CheckoutShippingMethodLoading";
 import AddressResultDisplay from "./AddressResultDisplay";
 import ShippingAddressCheckout from "./ShippingAddressCheckout";
 import BillingAddressCheckout from "./BillingAddressCheckout";
 import {ShippingMethodCheckout,RefShippingMethodsHandle} from "./ShippingMethodCheckout";
-import { useCustomToast } from "@/utils/hooks/useToast";
-import {useCheckoutAddress} from "@/utils/hooks/useCheckoutAddress"
-import {useCheckoutPaymentMethod} from  "@/utils/hooks/useCheckoutPaymentMethod";
-import {useCheckoutShippingMethod} from  "@/utils/hooks/useCheckoutShippingMethod";
-import { useAppDispatch } from "@/store/hooks";
-import { updateCart } from "@/store/slices/cart-slice";
 import {PaymentMethodCheckout,RefPaymentMethodsHandle} from "./PaymentMethodCheckout";
 import LoadingPaymentMethod from "./LoadingPaymentMethod";
 import LoadingCheckoutPlaceOrder from "./LoadingCheckoutPlaceOrder";
 import CheckoutPlaceOrder from "./CheckoutPlaceOrder";
 import CommonModal from "@/components/theme/ui/CommonModal";
-import {useSaveCheckoutCart} from "@/utils/hooks/useSaveCheckoutCart";
-import { formatCartDetail, shippingAddressToCartAddress, billingAddressToCartAddress } from "@/utils/cartDetailTools";
-import { overlayLoading } from "@/components/theme/ui/kernel/loading/api";
-import { confirmDialog } from "@/components/theme/ui/kernel/confirm/api";
-import { CartDetail,CartAddress } from "@/types/cart/type";
-import {normalizePhoneForForm} from "@/utils/phoneNumberTools";
-import { usePlaceOrder } from "@/utils/hooks/usePlaceOrder";
-import { clearCart } from "@/store/slices/cart-slice";
 import PaypalApplepayButton from "./PaymentButton/PaypalApplepayButton";
-import {usePaymentSDKContext} from "@/providers/PaymentSDKProvider";
+import CheckoutGiftCardGate from "./GiftCard/CheckoutGiftCardGate";
+import PromotionsDetails from "./PromotionsDetails";
+
 /***
  * 不用useForShipping字段了
  * 以shipping address 为准,根据shippingaddress 设置billing address
@@ -250,7 +253,7 @@ export default function CheckoutWrapper({
     } = useCheckoutPaymentMethod();
 
     const { showToast } = useCustomToast();
-    const { getCheckoutAddress, saveCheckoutAddress } = useCheckoutAddress(loginEmail);
+    const { saveCheckoutAddress } = useCheckoutAddress(loginEmail);
 
     const methodShiippingRef = useRef<RefShippingMethodsHandle>(null);
     const methodPaymentRef = useRef<RefPaymentMethodsHandle>(null);
@@ -283,6 +286,15 @@ export default function CheckoutWrapper({
         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 openAddressModal = () => {
         setAddressModalOpen(true);
 
@@ -308,35 +320,65 @@ export default function CheckoutWrapper({
         setAddressSaving(true);
         try {
 
-            const res = await saveCheckoutAddress(saveAddressParam);
-            console.log('CREATE_CHECKOUT_ADDRESS res ====== ',res);
-            // 地址保存成功后重新获取运输方式和支付方式 
-            await getShippingMethod();
-            await getPaymentMethod();
-            
-            // 保存完地址之后还要重新请求地址,把保存后的地址id同步到表单里;
-            const queryAddressRes = await getCheckoutAddress();
-            if(queryAddressRes !== null) {
-                addressForm.resetField('shippingAddressId',{
-                    defaultValue: queryAddressRes.shippingAddress.shippingAddressId
-                });
-                addressForm.resetField('billingAddressId',{
-                    defaultValue: queryAddressRes.billingAddress.billingAddressId
-                });
-
-      
-                // 同步地址到购物车详情
-                const newBillingAddress = billingAddressToCartAddress(queryAddressRes.billingAddress);
-                const newShippingAddress = shippingAddressToCartAddress(queryAddressRes.shippingAddress);
-                dispatch(updateCart({
-                    billingAddress: newBillingAddress,
-                    shippingAddress: newShippingAddress
-                }));
-                setCartData(Object.assign({...cartData},{
-                    billingAddress: {...newBillingAddress},
-                    shippingAddress: {...newShippingAddress}
-                }));
+            const saveRes = await saveCheckoutAddress(saveAddressParam);
+            console.log('CREATE_CHECKOUT_ADDRESS res ====== ',saveRes);
+            if(!saveRes.error) {
+                // 地址保存成功后重新获取运输方式和支付方式 
+                // await getShippingMethod();
+                // await getPaymentMethod();
+                await Promise.all([getShippingMethod(),getPaymentMethod()]);
+                
+                // 保存完地址之后,把保存后的地址id同步到表单里;
+                const createAddressData = saveRes.data;
+                if(createAddressData !== null) {
+                    addressForm.resetField('shippingAddressId',{
+                        defaultValue: createAddressData.shippingAddressId
+                    });
+                    addressForm.resetField('billingAddressId',{
+                        defaultValue: createAddressData.billingAddressId
+                    });
 
+        
+                    // 同步地址到购物车详情
+                    const newBillingAddress =  {
+                        id: String(createAddressData.billingAddressId),
+                        firstName: createAddressData.billingFirstName,
+                        lastName: createAddressData.billingLastName,
+                        email: createAddressData.billingEmail,
+                        address: createAddressData.billingAddress,
+                        city: createAddressData.billingCity,
+                        state: createAddressData.billingState,
+                        country: createAddressData.billingCountry,
+                        postcode: createAddressData.billingPostcode,
+                        phone: createAddressData.billingPhoneNumber
+                    };
+                    const newShippingAddress = {
+                        id: String(createAddressData.shippingAddressId),
+                        firstName: createAddressData.shippingFirstName,
+                        lastName: createAddressData.shippingLastName,
+                        email: createAddressData.shippingEmail,
+                        address: createAddressData.shippingAddress,
+                        city: createAddressData.shippingCity,
+                        state: createAddressData.shippingState,
+                        country: createAddressData.shippingCountry,
+                        postcode: createAddressData.shippingPostcode,
+                        phone: createAddressData.shippingPhoneNumber,
+                    };
+                    dispatchCartStore('update',{
+                        billingAddress: newBillingAddress,
+                        shippingAddress: newShippingAddress
+                    });
+                    setCartData(Object.assign({...cartData},{
+                        billingAddress: {...newBillingAddress},
+                        shippingAddress: {...newShippingAddress}
+                    }));
+
+                } else {
+                    // 提示用户出错了,刷新页面
+                    showToast('Something wrong. Please refresh the page.', 'danger');
+                }
+            } else {
+                showToast(saveRes.msg, 'danger');
             }
             setAddressModalOpen(false);
         } catch(err) {
@@ -353,21 +395,23 @@ export default function CheckoutWrapper({
 
     const handlePaymentMethodChange =  async (method: string) => { 
         changePaymentMethodToloadSdk(method);
-        let shippingMethod = '-1';
-        if(methodShiippingRef.current) {
-            shippingMethod = methodShiippingRef.current.getSelectShipMethod();
-        }
+        // let shippingMethod = '-1';
+        // if(methodShiippingRef.current) {
+        //     shippingMethod = methodShiippingRef.current.getSelectShipMethod();
+        // }
         overlayLoading.start();
         const saveRes = await saveCheckoutCart({ 
-            shippingMethod: shippingMethod,
+            shippingMethod: '-1',
             paymentMethod: method,
-            couponCode: '-1'
+            couponCode: '-1',
+            giftCardNumber: '-1',
+            memberDiscount: '-1'
         });
         overlayLoading.stop();
         if(!saveRes.error) {
             if(saveRes.data) {
                 const newCartDetail = formatCartDetail(saveRes.data);
-                dispatch(updateCart(newCartDetail));
+                dispatchCartStore('update',newCartDetail);
                 setCartData({...newCartDetail});
             }
             return true;
@@ -377,18 +421,20 @@ export default function CheckoutWrapper({
         }
 
     };
-    const handleShippingMethodChange =  async (method: string) => { 
+    const handleShippingMethodChange =  useCallback(async (method: string) => { 
         overlayLoading.start();
         const saveRes = await saveCheckoutCart({ 
             shippingMethod: method,
             paymentMethod: '-1',
-            couponCode: '-1'
+            couponCode: '-1',
+            giftCardNumber: '-1',
+            memberDiscount: '-1'
         });
         overlayLoading.stop();
         if(!saveRes.error) {
             if(saveRes.data) {
                 const newCartDetail = formatCartDetail(saveRes.data);
-                dispatch(updateCart(newCartDetail));
+                dispatchCartStore('update',newCartDetail);
                 setCartData({...newCartDetail});
             }
             // 保存成功之后需要重新获取支付方式
@@ -397,11 +443,11 @@ export default function CheckoutWrapper({
             showToast(saveRes.msg, 'danger');
         }
 
-    };
+    },[saveCheckoutCart,showToast,getPaymentMethod,dispatchCartStore]);
 
 
     // 校验地址 运输方式,支付方式;
-    const validateCheckout = async () => { 
+    const validateCheckout = useCallback(async () => { 
         const addressValid = await addressForm.trigger();
 
         if(!addressValid) {
@@ -446,26 +492,30 @@ export default function CheckoutWrapper({
             const saveRes = await saveCheckoutCart({ 
                 shippingMethod: '-1',
                 paymentMethod: pmethod,
-                couponCode: '-1'
+                couponCode: '-1',
+                giftCardNumber: '-1',
+                memberDiscount: '-1'
             });
             if(!saveRes.error) {
                 if(saveRes.data) {
                     const newCartDetail = formatCartDetail(saveRes.data);
-                    dispatch(updateCart(newCartDetail));
+                    dispatchCartStore('update',newCartDetail);
                     setCartData({...newCartDetail});
                 }
+            } else {
+                showToast(saveRes.msg, 'danger');
             }
         }
 
         return true;
 
-    };
+    },[addressForm,showToast,saveCheckoutCart,dispatchCartStore]);
     // 苹果支付下单
-    const applepayPlaceOrder = async () => { 
+    const applepayPlaceOrder = useCallback(async () => { 
         const res = await createOrder();
         if(!res.error) {
             //创建订单成功后清空redux 购物车
-            dispatch(clearCart());  
+            dispatchCartStore('clear'); 
             return {
                 orderId: res.data?.gatewayOrderId ?? '',
                 webOrderId: res.data?.orderId ?? ''
@@ -474,24 +524,20 @@ export default function CheckoutWrapper({
             // 抛出错误让第三方支付能够捕获
             throw new Error("CREATE_ORDER_ERROR: " + res.msg);
         } 
-    };
-    // 下单
+    },[createOrder,dispatchCartStore]);
+    // 下单(下单和校验分开)
     const handlePlaceOrder: PlaceOrderFunction = async <T,>(params?: CreatePaymentInitiateVariables): Promise<PlaceOrderResult<T>>  => { 
-        // 校验地址
-        // overlayLoading.start();
-        // const validRes = await validateCheckout();
-        // if(validRes) {
             const res = await createOrder(params);
             if(res.error) {
                 overlayLoading.stop();
                 confirmDialog({
                     title: "Warning",
-                    content: res.msg + " Create order failed. Please try again.",
+                    content: res.msg + ". Create order failed. Please try again.",
                     noCancel: true,
                 }).then(() => {});
             } else {
                 //创建订单成功后清空redux 购物车
-                dispatch(clearCart());  
+                dispatchCartStore('clear');
             }
             const pmethod = methodPaymentRef.current?.getSelectPaymentMethod();
             let resultData: PlaceOrderResult<T>;
@@ -518,12 +564,16 @@ export default function CheckoutWrapper({
                 };
             }
             return resultData;
-        // } else {
-        //     overlayLoading.stop();
-        //     return {valid: false, data: null, otherData: null};
-        // }
     }
 
+    const onChangeGiftcard = async (paylod: CartDetail) => {
+        dispatchCartStore('update',paylod);
+        setCartData({...paylod});
+        // @todo 还要请求支付方式和运输方式
+        // await getShippingMethod();
+        //     await getPaymentMethod();
+        await Promise.all([getShippingMethod(),getPaymentMethod()]);
+    };
 
     // function tt() {
     //     const arr = [
@@ -603,42 +653,52 @@ export default function CheckoutWrapper({
             </div>
             <div className="mt-6 box-border px-4">
                 <h3 className="text-ly-24 font-medium" onClick={testEruda}>Shipping Method</h3>
-                {shippingMethodLoading ?
-                
-                    <CheckoutShippingMethodLoading /> 
-                :
-                    <ShippingMethodCheckout 
-                        ref={methodShiippingRef}  
-                        shipMethods={shippingMethodDatas}
-                        errorMsg={shippingMethodError}  
-                        selectedShippingRate={selectedShippingRate}
-                        onShipMethodChange={handleShippingMethodChange}  
-                    />
-                }
+           
+                <ShippingMethodCheckout 
+                    ref={methodShiippingRef}  
+                    shipMethods={shippingMethodDatas}
+                    errorMsg={shippingMethodError}  
+                    selectedShippingRate={selectedShippingRate}
+                    shippingMethodLoading={shippingMethodLoading}
+                    onShipMethodChange={handleShippingMethodChange}  
+                />
+            </div>
 
+            <div className="mt-6 box-border px-4">
+                <CheckoutGiftCardGate 
+                    giftcardNumber={cartData.giftcardNumber || ''}
+                    formattedGiftcardAmount={cartData.formattedGiftcardAmount} 
+                    onChangeGiftcard={onChangeGiftcard}
+                />
+            </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>
-                { paymentMethodLoading && <LoadingPaymentMethod /> }
-                { (!paymentMethodLoading && paymentMethodError) && <p className="mt-3 text-red-500 text-ly-12">{paymentMethodError}</p>}
-                { (!paymentMethodLoading && !paymentMethodError) && 
-                    <PaymentMethodCheckout 
-                        ref={methodPaymentRef}
-                        paymentMethods={paymentMethodDatas} 
-                        selectedPaymentMethod={selectedPaymentMethod}
-                        onPaymentMethodChange={handlePaymentMethodChange}
-                    />
-                }
+                <PaymentMethodCheckout 
+                    ref={methodPaymentRef}
+                    paymentMethods={paymentMethodDatas} 
+                    selectedPaymentMethod={selectedPaymentMethod}
+                    paymentMethodLoading={paymentMethodLoading}
+                    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 />
-                    :
-                        (selectedPaymentMethod === 'applepay' ?
+                    { paymentMethodLoading && <LoadingCheckoutPlaceOrder />}
+                    
+                    <div className={clsx("w-full",{
+                        "hidden": selectedPaymentMethod !== 'applepay' || paymentMethodLoading
+                    })}>
                         <PaypalApplepayButton 
                             grandTotal={cartData.grandTotal}
                             billingContact={cartData.billingAddress}
@@ -646,14 +706,17 @@ export default function CheckoutWrapper({
                             validateCheckout={validateCheckout}
                             createOrder={applepayPlaceOrder}
                         />
-                        :
+                    </div>
+                    <div className={clsx("w-full",{
+                        "hidden": selectedPaymentMethod === 'applepay' || paymentMethodLoading
+                    })}>
                         <CheckoutPlaceOrder 
                             paymentMethod={selectedPaymentMethod}
                             validateCheckout={validateCheckout}
                             clickPlaceOrder={handlePlaceOrder}
-                        />)
-                        
-                    }
+                        />
+                    </div>  
+                    
                 </div>
             </div>
 

+ 16 - 6
src/app/(checkout)/checkout/_components/ContinueToPay/PaymentMethodContinueTpPay.tsx

@@ -1,5 +1,6 @@
 "use client";
 
+import clsx from "clsx";
 import Image from "next/image";
 import { use,useState,useCallback } from "react";
 import { useForm, get, useWatch } from "react-hook-form";
@@ -505,12 +506,15 @@ export function PaymentMethodContinueTpPay({
                 </p>
 
                 <div className="mt-6 w-full">
-                    {selectedPaymentMethod === 'paypal_smart_button' && 
+                    <div className={clsx("w-full",{
+                        "hidden": selectedPaymentMethod !== 'paypal_smart_button'
+                    })}>
                         <PaypalButton
                             createOrder={paypalRepayOrder}
                             isRePay={true}
                         />
-                    }
+                    </div>
+                    
                     {(selectedPaymentMethod === 'awxklarna' || selectedPaymentMethod === 'awxafterpay') &&
                         <button className="flex items-center justify-center w-full h-12 bg-ly-green text-white rounded-3xl text-ly-16 font-bold"
                             onClick={airwallexRepay}
@@ -534,7 +538,10 @@ export function PaymentMethodContinueTpPay({
                             Place Order
                         </button>
                     }
-                    {selectedPaymentMethod === 'applepay' &&
+
+                    <div className={clsx("w-full",{
+                        "hidden": selectedPaymentMethod !== 'applepay'
+                    })}>
                         <PaypalApplepayButton 
                             grandTotal={orderDetail.grandTotal}
                             billingContact={billingContact}
@@ -543,12 +550,15 @@ export function PaymentMethodContinueTpPay({
                             createOrder={applepayPlaceOrder}
                             isRePay={true}
                         />
-                    }
-                    {selectedPaymentMethod === 'klarna' &&
+                    </div>
+                   
+                    <div className={clsx("w-full",{
+                        "hidden": selectedPaymentMethod !== 'klarna'
+                    })}>
                         <KlarnaButton 
                             clickHandler={klarnaPlaceOrder}
                         />
-                    }
+                    </div>
 
                 </div>
             </div>

+ 35 - 23
src/app/(checkout)/checkout/_components/PaymentMethodCheckout.tsx

@@ -1,13 +1,15 @@
 "use client";
 
+import clsx from "clsx";
 import Image from "next/image";
-import { Ref, useImperativeHandle } from "react";
+import { Ref, useEffect, useImperativeHandle } from "react";
 import { useForm, get } from "react-hook-form";
 import { CheckoutPaymentMethod } from "@/types/checkout/type";
 import { AirwallexCardElements } from "@/lib/Airwallex/airwallexInit";
 import { useCustomToast } from "@/utils/hooks/useToast";
 import AirwallexCardInput from "./PaymentMethodAdditional/AirwallexCardInput";
 import {useAirwallexCard} from "@/lib/Airwallex/useAirwallexCard";
+import LoadingPaymentMethod from "./LoadingPaymentMethod";
 
 /**
  * 关于如何确定默认选中哪个支付方式:
@@ -24,49 +26,37 @@ export function PaymentMethodCheckout({
     ref,
     paymentMethods,
     selectedPaymentMethod,
+    paymentMethodLoading,
     onPaymentMethodChange,
 }: {
     ref: Ref<RefPaymentMethodsHandle>;
     paymentMethods: CheckoutPaymentMethod[];
     selectedPaymentMethod: string;
+    paymentMethodLoading: boolean;
     onPaymentMethodChange: (value: string) => Promise<boolean>;
 }) {
 
     const { showToast } = useCustomToast();
 
 
-    const {airwallexCardState,getCardElements} = useAirwallexCard(true,{
+    const {airwallexCardState,getCardElements} = useAirwallexCard(!paymentMethodLoading,{
         cardNumber: 'airwallex_cardNumber', 
         cvc: 'airwallex_cvc', 
         expiry: 'airwallex_expiry'
     });
   
-    let defaultValue = '';
- 
-    const findItem = paymentMethods.find((item) => item.method === selectedPaymentMethod);
-    if(findItem !== undefined) {
-        defaultValue = selectedPaymentMethod;
-
-    } else {
-        paymentMethods.forEach((item) => {
-            if(item.method === 'paypal_smart_button') {
-                defaultValue = item.method;
-            }
-        });
-    }
-
-  
     const {
         register,
         getValues,
         trigger,
+        reset,
         formState: { errors }
     } = useForm({
         mode: "onChange",
         reValidateMode: "onChange", 
-        defaultValues: {
-            paymentMethod: defaultValue,
-        }
+        // defaultValues: {
+        //     paymentMethod: defaultValue,
+        // }
     });
 
     // 参考 https://github.com/react-hook-form/error-message/blob/master/src/ErrorMessage.tsx
@@ -77,6 +67,23 @@ export function PaymentMethodCheckout({
         onPaymentMethodChange(event.target.value);                                
     };
 
+    useEffect(() => {
+        let defaultValue = '';
+ 
+        const findItem = paymentMethods.find((item) => item.method === selectedPaymentMethod);
+        if(findItem !== undefined) {
+            defaultValue = selectedPaymentMethod;
+
+        } else {
+            paymentMethods.forEach((item) => {
+                if(item.method === 'paypal_smart_button') {
+                    defaultValue = item.method;
+                }
+            });
+        }
+        reset({ paymentMethod: defaultValue });
+    },[paymentMethods,selectedPaymentMethod]);
+
     useImperativeHandle(ref, () => {
         return {
             // 获取用户选择的支付方式
@@ -132,8 +139,13 @@ export function PaymentMethodCheckout({
     const airwallexOtherInfoShow = selectedPaymentMethod === 'airwallex' && airwallexCardState.cardNumberReady && airwallexCardState.expiryReady && airwallexCardState.cvcReady;
 
     
-    return (
-        <form name="shipping method" className="w-full">
+    return (<>
+
+        { paymentMethodLoading && <LoadingPaymentMethod /> }
+        { (!paymentMethodLoading && paymentMethodError) && <p className="mt-3 text-red-500 text-ly-12">{paymentMethodError}</p>}
+        <form name="shipping method" className={clsx("w-full", {
+            "hidden": (paymentMethodLoading || paymentMethodError)
+        })}>
         
             <div className="mt-3 w-full">
                 <ul className="w-full">
@@ -186,7 +198,7 @@ export function PaymentMethodCheckout({
             </div>
             
         </form>
-    );
+    </>);
   
 }
 

+ 18 - 5
src/app/(checkout)/checkout/_components/ShippingMethodCheckout.tsx

@@ -1,10 +1,11 @@
 "use client";
 
-
+import clsx from "clsx";
 import { useEffect, useImperativeHandle, Ref  } from "react";
 import { useForm, get } from "react-hook-form";
 
 import {CheckoutShippingRate} from "@/types/checkout/type";
+import CheckoutShippingMethodLoading from "./CheckoutShippingMethodLoading";
 
 
 
@@ -24,12 +25,14 @@ export function ShippingMethodCheckout({
     errorMsg,
     shipMethods,
     selectedShippingRate,
+    shippingMethodLoading,
     onShipMethodChange
 }: {
     ref: Ref<RefShippingMethodsHandle>;
     errorMsg: string | null;
     shipMethods: CheckoutShippingRate[];
     selectedShippingRate: string | null;
+    shippingMethodLoading: boolean;
     onShipMethodChange: (value: string) => void;
 }) {
 
@@ -61,7 +64,7 @@ export function ShippingMethodCheckout({
         }
         
     },[]);
-    // 根据cartDetail 的loading判断,是true 返回,是false时才往下执行
+    
     useEffect(() => { 
         if(shipMethods.length > 0 ) {
             let defaultValue = '';
@@ -72,6 +75,10 @@ export function ShippingMethodCheckout({
                     defaultValue = selectedShippingRate;
                 }
             }
+            if(defaultValue) {
+                reset({ shippingMethod: defaultValue });
+            }
+            /*
             if(!defaultValue) {
                 findItem = shipMethods.find((item) => item.price === 0) || shipMethods[0];
                 defaultValue = findItem.method;
@@ -83,12 +90,18 @@ export function ShippingMethodCheckout({
             if(defaultValue !== selectedShippingRate) {
                 onShipMethodChange(defaultValue);
             }
+            */
         }
         
     }, [shipMethods,selectedShippingRate]);
     
-    return (
-        <form name="shipping method" className="w-full mt-3">
+    return (<>
+       
+        {shippingMethodLoading && <CheckoutShippingMethodLoading />}
+    
+        <form name="shipping method" className={clsx("w-full mt-3",{
+            "hidden": shippingMethodLoading
+        })}>
             {errorMsg && <p className="text-red-500 text-ly-12">{errorMsg}</p>}
             {shipMethods.length === 0 && <p className="text-red-500 text-ly-12">Please complete address data to get shipping methods list.</p>}
             <div className="w-full">
@@ -129,7 +142,7 @@ export function ShippingMethodCheckout({
             </div>
             
         </form>
-    );
+    </>);
   
 }
 

+ 24 - 12
src/graphql/checkout/mutations/CreateCheckoutAddress.ts

@@ -1,16 +1,5 @@
 import { gql, TypedDocumentNode } from "@apollo/client";
-
-type CreateCheckoutAddressResponse = {
-  createCheckoutAddress:{
-      checkoutAddress: {
-          success: boolean;
-          message: string;
-          id: string;
-          cartToken: string;
-      };
-  }
-  
-};
+import  {CreateCheckoutAddressResponse} from "@/types/checkout/type"
 
 export const CREATE_CHECKOUT_ADDRESS: TypedDocumentNode<CreateCheckoutAddressResponse> = gql`
  mutation createCheckoutAddress(
@@ -66,6 +55,29 @@ export const CREATE_CHECKOUT_ADDRESS: TypedDocumentNode<CreateCheckoutAddressRes
       message
       id
       cartToken
+      billingFirstName
+      billingLastName
+      billingEmail
+      billingCompanyName
+      billingAddress
+      billingCountry
+      billingState
+      billingCity
+      billingPostcode
+      billingPhoneNumber
+      billingAddressId
+
+      shippingFirstName
+      shippingLastName
+      shippingEmail
+      shippingCompanyName
+      shippingAddress
+      shippingCountry
+      shippingState
+      shippingCity
+      shippingPostcode
+      shippingPhoneNumber
+      shippingAddressId
     }
   }
 }

+ 3 - 0
src/lib/Airwallex/airwallexInit.ts

@@ -31,6 +31,9 @@ export function airwallexInit() {
           : 'prod',
 
       enabledElements: ['payments'],
+    }).catch((err) => {
+      initPromise = null;
+      throw err;
     });
 
   }

+ 2 - 1
src/lib/Airwallex/useAirwallexCard.ts

@@ -153,7 +153,7 @@ export function useAirwallexCard(
 
       const els = await airwallexManager.createElements();
       elementRef.current = els;
-
+console.log('useAirwallexCard +++++++++++++++++++++++++++++',destroyed);
       if (destroyed) return;
 
       airwallexManager.mount({
@@ -176,6 +176,7 @@ export function useAirwallexCard(
     init();
 
     return () => {
+        console.log('useAirwallexCard ___________________________________',mountedRef.current)
       destroyed = true;
       if (mountedRef.current) {
         airwallexManager.unmount();// 解除挂载

+ 37 - 0
src/types/checkout/type.ts

@@ -58,7 +58,42 @@ export interface BillAddressFormData {
 
 export type FullAddressFormData = ShipAddressFormData & BillAddressFormData;
 
+export interface CreateCheckoutAddressData {
+    billingFirstName: string;
+    billingLastName: string;
+    billingEmail: string;
+    billingCompanyName: string;
+    billingAddress: string;
+    billingCountry: string;
+    billingState: string;
+    billingCity: string;
+    billingPostcode: string;
+    billingPhoneNumber: string;
+    billingAddressId: number;
 
+    shippingFirstName: string;
+    shippingLastName: string;
+    shippingEmail: string;
+    shippingCompanyName: string;
+    shippingAddress: string;
+    shippingCountry: string;
+    shippingState: string;
+    shippingCity: string;
+    shippingPostcode: string;
+    shippingPhoneNumber: string;
+    shippingAddressId: number;
+}
+export type CreateCheckoutAddressResponse = {
+  createCheckoutAddress:{
+      checkoutAddress: {
+          success: boolean;
+          message: string;
+          id: string;
+          cartToken: string;
+      } & CreateCheckoutAddressData
+  }
+  
+};
 
 export interface MappedCheckoutAddress {
   firstName: string;
@@ -197,6 +232,8 @@ export interface SaveCheckoutCartVariables {
   shippingMethod: string;
   paymentMethod: string;
   couponCode: string;
+  giftCardNumber: string;
+  memberDiscount: string;
 }
 // Checkout Place Order
 

+ 16 - 6
src/utils/hooks/useCheckoutAddress.ts

@@ -38,10 +38,7 @@ export function useCheckoutAddress(loginEmail: string) {
     const apolloClient = useApolloClient();
 
 
-    const getCheckoutAddress = (
-        resolveCallback: (shippingAddress:ShipAddressFormData, billingAddress: BillAddressFormData) => void = () => {}, 
-        rejectCallback: () => void = ()=> {}
-    ) => {
+    const getCheckoutAddress = () => {
         return apolloClient.query({
             query: GET_CHECKOUT_ADDRESSES,
             fetchPolicy: "no-cache",
@@ -92,7 +89,6 @@ export function useCheckoutAddress(loginEmail: string) {
             const billSameAsShip = addressIsSame(defaultBillAddress, defaultShipAddress);
             defaultBillAddress.billingSameAsShipping = billSameAsShip;
             console.log('GET_CHECKOUT_ADDRESSES res ---- ',defaultShipAddress,defaultBillAddress);
-            resolveCallback(defaultShipAddress,defaultBillAddress);
             return {
                 shippingAddress: defaultShipAddress,
                 billingAddress: defaultBillAddress
@@ -100,7 +96,6 @@ export function useCheckoutAddress(loginEmail: string) {
         
         }).catch((err) => {
             console.error('GET_CHECKOUT_ADDRESSES error ---- ',err);
-            rejectCallback();
             return null;
         });
     };
@@ -109,6 +104,21 @@ export function useCheckoutAddress(loginEmail: string) {
         return apolloClient.mutate({
             mutation: CREATE_CHECKOUT_ADDRESS,
             variables:saveAddressParam,
+        }).then((res) => {
+            const resData = res.data?.createCheckoutAddress.checkoutAddress ?? null; 
+            const result = {
+                error: false,
+                msg: '',
+                data: resData
+            };
+            return result;
+        }).catch((err) => {
+            const result = {
+                error: true,
+                msg: err.message,
+                data: null,
+            };
+            return result;
         });
     }
 

+ 4 - 4
src/utils/hooks/useCheckoutPaymentMethod.ts

@@ -1,6 +1,6 @@
 "use client";
 
-import { useEffect, useState, useRef } from "react";
+import { useEffect, useState, useRef, useCallback } from "react";
 import { GET_CHECKOUT_PAYMENT_METHODS, CREATE_CHECKOUT_PAYMENT_METHODS } from "@/graphql";
 import {useApolloClient} from "@apollo/client/react";
 import {CheckoutPaymentMethod, CreateCheckoutPaymentMethodVariables} from "@/types/checkout/type";
@@ -17,7 +17,7 @@ export function useCheckoutPaymentMethod() {
     const [error, setError] = useState<string|null>(null);
     const isIntRef = useRef(true);
 
-    const getPaymentMethod = () => {
+    const getPaymentMethod = useCallback(() => {
         if(!isIntRef.current) {
             setLoading(true);
         }
@@ -63,7 +63,7 @@ export function useCheckoutPaymentMethod() {
             
             return resData;
         });
-    };
+    },[apolloClient]);
 
     const savePaymentMethod = (params: CreateCheckoutPaymentMethodVariables) => {
         return apolloClient.mutate({
@@ -99,7 +99,7 @@ export function useCheckoutPaymentMethod() {
         //     setError(rejectData.msg);
         //     setLoading(false);
         // });
-    }, []);
+    }, [getPaymentMethod]);
 
     return {
         data,

+ 4 - 4
src/utils/hooks/useCheckoutShippingMethod.ts

@@ -1,6 +1,6 @@
 "use client";
 
-import { useState, useEffect, useRef } from "react";
+import { useState, useEffect, useRef, useCallback } from "react";
 import { GET_CHECKOUT_SHIPPING_RATES, CREATE_CHECKOUT_SHIPPING_METHODS } from "@/graphql";
 import {useApolloClient} from "@apollo/client/react";
 import {CheckoutShippingRate, CreateCheckoutShippingMethodVariables} from "@/types/checkout/type";
@@ -17,7 +17,7 @@ export function useCheckoutShippingMethod() {
     const [error, setError] = useState<string|null>(null);
     const isIntRef = useRef(true);
 
-    const getShippingMethod = () => {
+    const getShippingMethod = useCallback(() => {
         if(!isIntRef.current) {
             setLoading(true);
         }
@@ -61,7 +61,7 @@ export function useCheckoutShippingMethod() {
             // rejectCallback(resData);
             return resData;
         });
-    };
+    },[apolloClient]);
 
     const saveShippingMethod = (params: CreateCheckoutShippingMethodVariables) => {
         return apolloClient.mutate({
@@ -97,7 +97,7 @@ export function useCheckoutShippingMethod() {
         //     setError(rejectData.msg);
         //     setLoading(false);
         // });
-    }, []);
+    }, [getShippingMethod]);
 
 
     return {

+ 3 - 2
src/utils/hooks/usePlaceOrder.ts

@@ -1,3 +1,4 @@
+import { useCallback } from "react";
 import { useApolloClient } from "@apollo/client/react";
 import { 
     CREATE_PAYMENT_INITIATE,
@@ -39,7 +40,7 @@ interface PaymentReplayResult {
 export const usePlaceOrder = () => {
     const client = useApolloClient();
 
-    const createOrder = (params: CreatePaymentInitiateVariables = {}) => {
+    const createOrder = useCallback((params: CreatePaymentInitiateVariables = {}) => {
         return client.mutate({
             mutation: CREATE_PAYMENT_INITIATE,
             variables: params
@@ -60,7 +61,7 @@ export const usePlaceOrder = () => {
             return result;
         });
 
-    };
+    },[client]);
 
     // 支付回调
     const createPaymentCallback = (params: CreatePaymentCallbackVariables) => { 

+ 3 - 2
src/utils/hooks/useSaveCheckoutCart.ts

@@ -1,3 +1,4 @@
+import {useCallback} from "react";
 import { useApolloClient } from "@apollo/client/react";
 import { 
     CREATE_SAVE_CHECKOUT_CART
@@ -13,7 +14,7 @@ interface SaveCheckoutCartResult {
 
 export const useSaveCheckoutCart = () => {
     const client = useApolloClient();
-    const saveCheckoutCart = (params: SaveCheckoutCartVariables) => {
+    const saveCheckoutCart = useCallback((params: SaveCheckoutCartVariables) => {
         return client.mutate({
             mutation: CREATE_SAVE_CHECKOUT_CART,
             variables: params
@@ -34,7 +35,7 @@ export const useSaveCheckoutCart = () => {
             return result;
         });
 
-    };
+    },[client]);
 
 
 

+ 3 - 2
src/utils/hooks/useToast.ts

@@ -1,15 +1,16 @@
+import {useCallback} from "react";
 import { useToast } from "@/providers";
 
 export const useCustomToast = () => {
   const { addToast } = useToast();
 
-  const showToast = (
+  const showToast = useCallback((
     message: string,
     type: "success" | "danger" | "warning" | "primary" = "primary",
     duration = 5000,
   ) => {
     addToast({ message, type, duration });
-  };
+  },[addToast]);
 
   return { showToast };
 };