"use client"; 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, CreateCheckoutAddressVariables, 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 {normalizePhoneForForm} from "@/utils/phoneNumberTools"; import { LoadingSpinner } from "@components/common/LoadingSpinner"; import AddressResultDisplay from "./AddressResultDisplay"; import ShippingAddressCheckout from "./ShippingAddressCheckout"; import BillingAddressCheckout from "./BillingAddressCheckout"; import {ShippingMethodCheckout,RefShippingMethodsHandle} from "./ShippingMethodCheckout"; import {PaymentMethodCheckout,RefPaymentMethodsHandle} from "./PaymentMethodCheckout"; import LoadingCheckoutPlaceOrder from "./LoadingCheckoutPlaceOrder"; import CheckoutPlaceOrder from "./CheckoutPlaceOrder"; import CommonModal from "@/components/theme/ui/CommonModal"; import PaypalApplepayButton from "./PaymentButton/PaypalApplepayButton"; import CheckoutGiftCardGate from "./GiftCard/CheckoutGiftCardGate"; import PromotionsDetails from "./PromotionsDetails"; import CheckoutVipPlus from "./VipPlus/CheckoutVipPlus"; /*** * 不用useForShipping字段了 * 以shipping address 为准,根据shippingaddress 设置billing address * 保存完地址之后要重新获取运输方式和支付方式 * 变更运输方式后重新获取支付方式 * @todo 购物车为空 跳转到空购物车页面 */ /** * 地址表单默认值 */ function getBillingAddressFormData(billingAddress:CartAddress) { const billingCountry = billingAddress.country || 'US'; const billingPhoneNumber = normalizePhoneForForm(billingAddress.phone, billingCountry) || ""; const res = { "billingAddressId": billingAddress.id, "billingEmail": billingAddress.email, "billingFirstName": billingAddress.firstName, "billingLastName": billingAddress.lastName, "billingCompanyName": "", "billingAddress": billingAddress.address, "billingCountry": billingCountry, "billingState": billingAddress.state, "billingCity": billingAddress.city, "billingPostcode": billingAddress.postcode, "billingPhoneNumber": billingPhoneNumber, }; return res; } function getShippingAddressFormData(shippingAddress:CartAddress) { const shippingCountry = shippingAddress.country || 'US'; const shippingPhoneNumber = normalizePhoneForForm(shippingAddress.phone, shippingCountry) || ""; const res = { "shippingAddressId": shippingAddress.id, "shippingEmail": shippingAddress.email, "shippingFirstName": shippingAddress.firstName, "shippingLastName": shippingAddress.lastName, "shippingCompanyName": "", "shippingAddress": shippingAddress.address, "shippingCountry": shippingCountry, "shippingState": shippingAddress.state, "shippingCity": shippingAddress.city, "shippingPostcode": shippingAddress.postcode, "shippingPhoneNumber": shippingPhoneNumber, }; return res; } function getAddressFormDataFromCart(cartDetail:CartDetail, loginEmail?: string) { const shippingAddress = cartDetail.shippingAddress; const billingAddress = cartDetail.billingAddress; const defaultValues = { "shippingAddressId": "", "shippingEmail": loginEmail ?? '', "shippingFirstName": "", "shippingLastName": "", "shippingCompanyName": "", "shippingAddress": "", "shippingCountry": "US", "shippingState": "", "shippingCity": "", "shippingPostcode": "", "shippingPhoneNumber": "", "billingAddressId": "", "billingEmail": loginEmail ?? '', "billingFirstName": "", "billingLastName": "", "billingCompanyName": "", "billingAddress": "", "billingCountry": "US", "billingState": "", "billingCity": "", "billingPostcode": "", "billingPhoneNumber": "", "billingSameAsShipping": true } if(billingAddress) { const bs = getBillingAddressFormData(billingAddress); defaultValues.billingAddressId = bs.billingAddressId; defaultValues.billingEmail = bs.billingEmail; defaultValues.billingFirstName = bs.billingFirstName; defaultValues.billingLastName = bs.billingLastName; defaultValues.billingCompanyName = bs.billingCompanyName; defaultValues.billingAddress = bs.billingAddress; defaultValues.billingCountry = bs.billingCountry; defaultValues.billingState = bs.billingState; defaultValues.billingCity = bs.billingCity; defaultValues.billingPostcode = bs.billingPostcode; defaultValues.billingPhoneNumber = bs.billingPhoneNumber; // defaultValues.billingSameAsShipping = true; } if(shippingAddress) { const ss = getShippingAddressFormData(shippingAddress); defaultValues.shippingAddressId = ss.shippingAddressId; defaultValues.shippingEmail = ss.shippingEmail; defaultValues.shippingFirstName = ss.shippingFirstName; defaultValues.shippingLastName = ss.shippingLastName; defaultValues.shippingCompanyName = ss.shippingCompanyName; defaultValues.shippingAddress = ss.shippingAddress; defaultValues.shippingCountry = ss.shippingCountry; defaultValues.shippingState = ss.shippingState; defaultValues.shippingCity = ss.shippingCity; defaultValues.shippingPostcode = ss.shippingPostcode; defaultValues.shippingPhoneNumber = ss.shippingPhoneNumber; } if( defaultValues.billingEmail === defaultValues.shippingEmail && defaultValues.billingFirstName === defaultValues.shippingFirstName && defaultValues.billingLastName === defaultValues.shippingLastName && defaultValues.billingCompanyName === defaultValues.shippingCompanyName && defaultValues.billingAddress === defaultValues.shippingAddress && defaultValues.billingCountry === defaultValues.shippingCountry && defaultValues.billingState === defaultValues.shippingState && defaultValues.billingCity === defaultValues.shippingCity && defaultValues.billingPostcode === defaultValues.shippingPostcode && defaultValues.billingPhoneNumber === defaultValues.shippingPhoneNumber ) { defaultValues.billingSameAsShipping = true; } return defaultValues; } /** * 生成createCheckoutAddress接口的参数 */ function generateSaveCheckoutAddressParam(formData: FullAddressFormData):CreateCheckoutAddressVariables { const formDataShippingAddress:ShipAddressFormData = { shippingAddressId: formData.shippingAddressId, shippingEmail: formData.shippingEmail, shippingFirstName: formData.shippingFirstName, shippingLastName: formData.shippingLastName, shippingCompanyName: formData.shippingCompanyName, shippingAddress: formData.shippingAddress, shippingCountry: formData.shippingCountry, shippingState: formData.shippingState, shippingCity: formData.shippingCity, shippingPostcode: formData.shippingPostcode, shippingPhoneNumber: formData.shippingPhoneNumber, }; let formDataBillingAddress = { billingAddressId: formData.billingAddressId, billingEmail: formData.billingEmail, billingFirstName: formData.billingFirstName, billingLastName: formData.billingLastName, billingCompanyName : formData.billingCompanyName, billingAddress: formData.billingAddress, billingCountry: formData.billingCountry, billingState: formData.billingState, billingCity: formData.billingCity, billingPostcode: formData.billingPostcode, billingPhoneNumber: formData.billingPhoneNumber, useForShipping: false, }; if(formData.billingSameAsShipping) { formDataBillingAddress = { billingAddressId: formData.billingAddressId, billingEmail: formDataShippingAddress.shippingEmail, billingFirstName: formDataShippingAddress.shippingFirstName, billingLastName: formDataShippingAddress.shippingLastName, billingCompanyName : formDataShippingAddress.shippingCompanyName, billingAddress: formDataShippingAddress.shippingAddress, billingCountry: formDataShippingAddress.shippingCountry, billingState: formDataShippingAddress.shippingState, billingCity: formDataShippingAddress.shippingCity, billingPostcode: formDataShippingAddress.shippingPostcode, billingPhoneNumber: formDataShippingAddress.shippingPhoneNumber, useForShipping: true, } } return { ...formDataShippingAddress, ...formDataBillingAddress }; } export default function CheckoutWrapper({ loginEmail, cartDetailData, }: { loginEmail: string; cartDetailData: CartDetail; }) { const erudaCount = useRef(0); const {loadSdk} = usePaymentSDKContext(); const { createOrder } = usePlaceOrder(); const addressFormDefaultValues = getAddressFormDataFromCart(cartDetailData,loginEmail); const dispatch = useAppDispatch(); const [cartData, setCartData] = useState(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 { saveCheckoutAddress } = useCheckoutAddress(loginEmail); const methodShiippingRef = useRef(null); const methodPaymentRef = useRef(null); // useForm() 只会在组件初始化时读取一次 defaultValues const addressForm = useForm({ mode: "onChange", // 或 "onChange" reValidateMode: "onChange", defaultValues: addressFormDefaultValues }); const [addressModalOpen, setAddressModalOpen] = useState(false); const [addressSaving, setAddressSaving] = useState(false); // 保存地址时的loading状态 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) => { if(action === "clear") { dispatch(clearCart()); } else if(action === "update" && payload) { dispatch(updateCart(payload)); } },[dispatch]); const openAddressModal = () => { setAddressModalOpen(true); if(cartData.billingAddress && cartData.shippingAddress) { addressForm.reset({ ...getAddressFormDataFromCart(cartData) }); } }; const closeAddressModal = (e:boolean) => { setAddressModalOpen(e); // 重置表单 addressForm.reset(); }; const addressFormOnSubmit = async (formData: FullAddressFormData) => { // console.log('addressFormOnSubmit ---- ',formData); return; if(addressSaving) { return; } const saveAddressParam = generateSaveCheckoutAddressParam(formData); setAddressSaving(true); try { 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) { // 错误处理 console.error("save address error", err); showToast('Save address failed. Please try again.', 'danger'); } finally { setAddressSaving(false); } }; const handlePaymentMethodChange = async (method: string) => { changePaymentMethodToloadSdk(method); // let shippingMethod = '-1'; // if(methodShiippingRef.current) { // shippingMethod = methodShiippingRef.current.getSelectShipMethod(); // } 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 () => { const addressValid = await addressForm.trigger(); if(!addressValid) { showToast('Please fill in the address information correctly.', '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; },[addressForm,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 (params?: CreatePaymentInitiateVariables): Promise> => { 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; 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) => { dispatchCartStore('update',paylod); setCartData({...paylod}); // @todo 还要请求支付方式和运输方式 // await getShippingMethod(); // await getPaymentMethod(); await Promise.all([getShippingMethod(),getPaymentMethod()]); }; // function tt() { // const arr = [ // ['US','3803800217'], // ['CH','+4186043315'], // ['US','334-208-6177'], // ['AU','+61 404103617'], // ['US','1-9166705105'], // ['CA','819 384 3221'], // ['US','(409) 239-9482'], // ['US','1-(651) 421-2762'], // ['CH', '1-792930242'], // ['US', '+1 4570438892'], // ['US', '1-+1 (404) 276-1068'], // ['CH', '+49 015164340021'], // 国家与phonecode不一致情况 // ]; // arr.forEach((item) => { // normalizePhoneForForm(item[1], item[0]); // }); // } const testEruda = () => { erudaCount.current++ if(window.eruda && erudaCount.current > 10) { window.eruda.init(); } }; return (
{/* */} {/* {loadingAddress ? : } */} Shipping Address
} body={

Select a billing address from your address book or enter a new address.

} footer={
} />

Shipping Method

{!cartData.isGuest &&
}

Promotion Details

Payment Method

By providing your information, you agree to Wiggins'sPrivacy Policyand Terms of Use.

{ paymentMethodLoading && }
); };