CheckoutWrapper.tsx 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735
  1. "use client";
  2. import {useState, useRef, useEffect, useCallback } from "react";
  3. import clsx from "clsx";
  4. import { useForm, FormProvider } from "react-hook-form";
  5. import { useCustomToast } from "@/utils/hooks/useToast";
  6. import {useCheckoutAddress} from "@/utils/hooks/useCheckoutAddress"
  7. import {useCheckoutPaymentMethod} from "@/utils/hooks/useCheckoutPaymentMethod";
  8. import {useCheckoutShippingMethod} from "@/utils/hooks/useCheckoutShippingMethod";
  9. import { useAppDispatch } from "@/store/hooks";
  10. import { updateCart, clearCart } from "@/store/slices/cart-slice";
  11. import {useSaveCheckoutCart} from "@/utils/hooks/useSaveCheckoutCart";
  12. import { usePlaceOrder } from "@/utils/hooks/usePlaceOrder";
  13. import {usePaymentSDKContext} from "@/providers/PaymentSDKProvider";
  14. import {
  15. ShipAddressFormData,
  16. FullAddressFormData,
  17. CreateCheckoutAddressVariables,
  18. PlaceOrderResult,
  19. PlaceOrderFunction,
  20. CreatePaymentInitiateVariables
  21. } from "@/types/checkout/type";
  22. import { CartDetail,CartAddress } from "@/types/cart/type";
  23. import { formatCartDetail } from "@/utils/cartDetailTools";
  24. import { overlayLoading } from "@/components/theme/ui/kernel/loading/api";
  25. import { confirmDialog } from "@/components/theme/ui/kernel/confirm/api";
  26. import {normalizePhoneForForm} from "@/utils/phoneNumberTools";
  27. import { LoadingSpinner } from "@components/common/LoadingSpinner";
  28. import AddressResultDisplay from "./AddressResultDisplay";
  29. import ShippingAddressCheckout from "./ShippingAddressCheckout";
  30. import BillingAddressCheckout from "./BillingAddressCheckout";
  31. import {ShippingMethodCheckout,RefShippingMethodsHandle} from "./ShippingMethodCheckout";
  32. import {PaymentMethodCheckout,RefPaymentMethodsHandle} from "./PaymentMethodCheckout";
  33. import LoadingCheckoutPlaceOrder from "./LoadingCheckoutPlaceOrder";
  34. import CheckoutPlaceOrder from "./CheckoutPlaceOrder";
  35. import CommonModal from "@/components/theme/ui/CommonModal";
  36. import PaypalApplepayButton from "./PaymentButton/PaypalApplepayButton";
  37. import CheckoutGiftCardGate from "./GiftCard/CheckoutGiftCardGate";
  38. import PromotionsDetails from "./PromotionsDetails";
  39. import CheckoutVipPlus from "./VipPlus/CheckoutVipPlus";
  40. /***
  41. * 不用useForShipping字段了
  42. * 以shipping address 为准,根据shippingaddress 设置billing address
  43. * 保存完地址之后要重新获取运输方式和支付方式
  44. * 变更运输方式后重新获取支付方式
  45. * @todo 购物车为空 跳转到空购物车页面
  46. */
  47. /**
  48. * 地址表单默认值
  49. */
  50. function getBillingAddressFormData(billingAddress:CartAddress) {
  51. const billingCountry = billingAddress.country || 'US';
  52. const billingPhoneNumber = normalizePhoneForForm(billingAddress.phone, billingCountry) || "";
  53. const res = {
  54. "billingAddressId": billingAddress.id,
  55. "billingEmail": billingAddress.email,
  56. "billingFirstName": billingAddress.firstName,
  57. "billingLastName": billingAddress.lastName,
  58. "billingCompanyName": "",
  59. "billingAddress": billingAddress.address,
  60. "billingCountry": billingCountry,
  61. "billingState": billingAddress.state,
  62. "billingCity": billingAddress.city,
  63. "billingPostcode": billingAddress.postcode,
  64. "billingPhoneNumber": billingPhoneNumber,
  65. };
  66. return res;
  67. }
  68. function getShippingAddressFormData(shippingAddress:CartAddress) {
  69. const shippingCountry = shippingAddress.country || 'US';
  70. const shippingPhoneNumber = normalizePhoneForForm(shippingAddress.phone, shippingCountry) || "";
  71. const res = {
  72. "shippingAddressId": shippingAddress.id,
  73. "shippingEmail": shippingAddress.email,
  74. "shippingFirstName": shippingAddress.firstName,
  75. "shippingLastName": shippingAddress.lastName,
  76. "shippingCompanyName": "",
  77. "shippingAddress": shippingAddress.address,
  78. "shippingCountry": shippingCountry,
  79. "shippingState": shippingAddress.state,
  80. "shippingCity": shippingAddress.city,
  81. "shippingPostcode": shippingAddress.postcode,
  82. "shippingPhoneNumber": shippingPhoneNumber,
  83. };
  84. return res;
  85. }
  86. function getAddressFormDataFromCart(cartDetail:CartDetail, loginEmail?: string) {
  87. const shippingAddress = cartDetail.shippingAddress;
  88. const billingAddress = cartDetail.billingAddress;
  89. const defaultValues = {
  90. "shippingAddressId": "",
  91. "shippingEmail": loginEmail ?? '',
  92. "shippingFirstName": "",
  93. "shippingLastName": "",
  94. "shippingCompanyName": "",
  95. "shippingAddress": "",
  96. "shippingCountry": "US",
  97. "shippingState": "",
  98. "shippingCity": "",
  99. "shippingPostcode": "",
  100. "shippingPhoneNumber": "",
  101. "billingAddressId": "",
  102. "billingEmail": loginEmail ?? '',
  103. "billingFirstName": "",
  104. "billingLastName": "",
  105. "billingCompanyName": "",
  106. "billingAddress": "",
  107. "billingCountry": "US",
  108. "billingState": "",
  109. "billingCity": "",
  110. "billingPostcode": "",
  111. "billingPhoneNumber": "",
  112. "billingSameAsShipping": true
  113. }
  114. if(billingAddress) {
  115. const bs = getBillingAddressFormData(billingAddress);
  116. defaultValues.billingAddressId = bs.billingAddressId;
  117. defaultValues.billingEmail = bs.billingEmail;
  118. defaultValues.billingFirstName = bs.billingFirstName;
  119. defaultValues.billingLastName = bs.billingLastName;
  120. defaultValues.billingCompanyName = bs.billingCompanyName;
  121. defaultValues.billingAddress = bs.billingAddress;
  122. defaultValues.billingCountry = bs.billingCountry;
  123. defaultValues.billingState = bs.billingState;
  124. defaultValues.billingCity = bs.billingCity;
  125. defaultValues.billingPostcode = bs.billingPostcode;
  126. defaultValues.billingPhoneNumber = bs.billingPhoneNumber;
  127. // defaultValues.billingSameAsShipping = true;
  128. }
  129. if(shippingAddress) {
  130. const ss = getShippingAddressFormData(shippingAddress);
  131. defaultValues.shippingAddressId = ss.shippingAddressId;
  132. defaultValues.shippingEmail = ss.shippingEmail;
  133. defaultValues.shippingFirstName = ss.shippingFirstName;
  134. defaultValues.shippingLastName = ss.shippingLastName;
  135. defaultValues.shippingCompanyName = ss.shippingCompanyName;
  136. defaultValues.shippingAddress = ss.shippingAddress;
  137. defaultValues.shippingCountry = ss.shippingCountry;
  138. defaultValues.shippingState = ss.shippingState;
  139. defaultValues.shippingCity = ss.shippingCity;
  140. defaultValues.shippingPostcode = ss.shippingPostcode;
  141. defaultValues.shippingPhoneNumber = ss.shippingPhoneNumber;
  142. }
  143. if( defaultValues.billingEmail === defaultValues.shippingEmail &&
  144. defaultValues.billingFirstName === defaultValues.shippingFirstName &&
  145. defaultValues.billingLastName === defaultValues.shippingLastName &&
  146. defaultValues.billingCompanyName === defaultValues.shippingCompanyName &&
  147. defaultValues.billingAddress === defaultValues.shippingAddress &&
  148. defaultValues.billingCountry === defaultValues.shippingCountry &&
  149. defaultValues.billingState === defaultValues.shippingState &&
  150. defaultValues.billingCity === defaultValues.shippingCity &&
  151. defaultValues.billingPostcode === defaultValues.shippingPostcode &&
  152. defaultValues.billingPhoneNumber === defaultValues.shippingPhoneNumber
  153. ) {
  154. defaultValues.billingSameAsShipping = true;
  155. }
  156. return defaultValues;
  157. }
  158. /**
  159. * 生成createCheckoutAddress接口的参数
  160. */
  161. function generateSaveCheckoutAddressParam(formData: FullAddressFormData):CreateCheckoutAddressVariables {
  162. const formDataShippingAddress:ShipAddressFormData = {
  163. shippingAddressId: formData.shippingAddressId,
  164. shippingEmail: formData.shippingEmail,
  165. shippingFirstName: formData.shippingFirstName,
  166. shippingLastName: formData.shippingLastName,
  167. shippingCompanyName: formData.shippingCompanyName,
  168. shippingAddress: formData.shippingAddress,
  169. shippingCountry: formData.shippingCountry,
  170. shippingState: formData.shippingState,
  171. shippingCity: formData.shippingCity,
  172. shippingPostcode: formData.shippingPostcode,
  173. shippingPhoneNumber: formData.shippingPhoneNumber,
  174. };
  175. let formDataBillingAddress = {
  176. billingAddressId: formData.billingAddressId,
  177. billingEmail: formData.billingEmail,
  178. billingFirstName: formData.billingFirstName,
  179. billingLastName: formData.billingLastName,
  180. billingCompanyName : formData.billingCompanyName,
  181. billingAddress: formData.billingAddress,
  182. billingCountry: formData.billingCountry,
  183. billingState: formData.billingState,
  184. billingCity: formData.billingCity,
  185. billingPostcode: formData.billingPostcode,
  186. billingPhoneNumber: formData.billingPhoneNumber,
  187. useForShipping: false,
  188. };
  189. if(formData.billingSameAsShipping) {
  190. formDataBillingAddress = {
  191. billingAddressId: formData.billingAddressId,
  192. billingEmail: formDataShippingAddress.shippingEmail,
  193. billingFirstName: formDataShippingAddress.shippingFirstName,
  194. billingLastName: formDataShippingAddress.shippingLastName,
  195. billingCompanyName : formDataShippingAddress.shippingCompanyName,
  196. billingAddress: formDataShippingAddress.shippingAddress,
  197. billingCountry: formDataShippingAddress.shippingCountry,
  198. billingState: formDataShippingAddress.shippingState,
  199. billingCity: formDataShippingAddress.shippingCity,
  200. billingPostcode: formDataShippingAddress.shippingPostcode,
  201. billingPhoneNumber: formDataShippingAddress.shippingPhoneNumber,
  202. useForShipping: true,
  203. }
  204. }
  205. return {
  206. ...formDataShippingAddress,
  207. ...formDataBillingAddress
  208. };
  209. }
  210. export default function CheckoutWrapper({
  211. loginEmail,
  212. cartDetailData,
  213. }: {
  214. loginEmail: string;
  215. cartDetailData: CartDetail;
  216. }) {
  217. const erudaCount = useRef(0);
  218. const {loadSdk} = usePaymentSDKContext();
  219. const { createOrder } = usePlaceOrder();
  220. const addressFormDefaultValues = getAddressFormDataFromCart(cartDetailData,loginEmail);
  221. const dispatch = useAppDispatch();
  222. const [cartData, setCartData] = useState<CartDetail>(cartDetailData);
  223. const {saveCheckoutCart} = useSaveCheckoutCart();
  224. const {
  225. data: shippingMethodDatas,
  226. loading: shippingMethodLoading,
  227. error: shippingMethodError,
  228. getShippingMethod,
  229. } = useCheckoutShippingMethod();
  230. const selectedPaymentMethod = cartData.paymentMethod || 'paypal_smart_button';
  231. const selectedShippingRate = cartData.selectedShippingRate;
  232. const {
  233. data: paymentMethodDatas,
  234. loading: paymentMethodLoading,
  235. error: paymentMethodError,
  236. getPaymentMethod,
  237. } = useCheckoutPaymentMethod();
  238. const { showToast } = useCustomToast();
  239. const { saveCheckoutAddress } = useCheckoutAddress(loginEmail);
  240. const methodShiippingRef = useRef<RefShippingMethodsHandle>(null);
  241. const methodPaymentRef = useRef<RefPaymentMethodsHandle>(null);
  242. // useForm() 只会在组件初始化时读取一次 defaultValues
  243. const addressForm = useForm<FullAddressFormData>({
  244. mode: "onChange", // 或 "onChange"
  245. reValidateMode: "onChange",
  246. defaultValues: addressFormDefaultValues
  247. });
  248. const [addressModalOpen, setAddressModalOpen] = useState(false);
  249. const [addressSaving, setAddressSaving] = useState(false); // 保存地址时的loading状态
  250. const changePaymentMethodToloadSdk = (method:string) => {
  251. if(method === 'klarna' ||
  252. method === 'afterpay' ||
  253. method === 'clearpay' ||
  254. method === 'applepay'
  255. ) {
  256. loadSdk(method);
  257. }
  258. };
  259. useEffect(() => {
  260. changePaymentMethodToloadSdk(selectedPaymentMethod);
  261. }, [selectedPaymentMethod]);
  262. const dispatchCartStore = useCallback((action: "clear" | "update", payload?:Partial<CartDetail>) => {
  263. if(action === "clear") {
  264. dispatch(clearCart());
  265. } else if(action === "update" && payload) {
  266. dispatch(updateCart(payload));
  267. }
  268. },[dispatch]);
  269. const openAddressModal = () => {
  270. setAddressModalOpen(true);
  271. if(cartData.billingAddress && cartData.shippingAddress) {
  272. addressForm.reset({
  273. ...getAddressFormDataFromCart(cartData)
  274. });
  275. }
  276. };
  277. const closeAddressModal = (e:boolean) => {
  278. setAddressModalOpen(e);
  279. // 重置表单
  280. addressForm.reset();
  281. };
  282. const addressFormOnSubmit = async (formData: FullAddressFormData) => {
  283. // console.log('addressFormOnSubmit ---- ',formData); return;
  284. if(addressSaving) {
  285. return;
  286. }
  287. const saveAddressParam = generateSaveCheckoutAddressParam(formData);
  288. setAddressSaving(true);
  289. try {
  290. const saveRes = await saveCheckoutAddress(saveAddressParam);
  291. console.log('CREATE_CHECKOUT_ADDRESS res ====== ',saveRes);
  292. if(!saveRes.error) {
  293. // 地址保存成功后重新获取运输方式和支付方式
  294. // await getShippingMethod();
  295. // await getPaymentMethod();
  296. await Promise.all([getShippingMethod(),getPaymentMethod()]);
  297. // 保存完地址之后,把保存后的地址id同步到表单里;
  298. const createAddressData = saveRes.data;
  299. if(createAddressData !== null) {
  300. addressForm.resetField('shippingAddressId',{
  301. defaultValue: createAddressData.shippingAddressId
  302. });
  303. addressForm.resetField('billingAddressId',{
  304. defaultValue: createAddressData.billingAddressId
  305. });
  306. // 同步地址到购物车详情
  307. const newBillingAddress = {
  308. id: String(createAddressData.billingAddressId),
  309. firstName: createAddressData.billingFirstName,
  310. lastName: createAddressData.billingLastName,
  311. email: createAddressData.billingEmail,
  312. address: createAddressData.billingAddress,
  313. city: createAddressData.billingCity,
  314. state: createAddressData.billingState,
  315. country: createAddressData.billingCountry,
  316. postcode: createAddressData.billingPostcode,
  317. phone: createAddressData.billingPhoneNumber
  318. };
  319. const newShippingAddress = {
  320. id: String(createAddressData.shippingAddressId),
  321. firstName: createAddressData.shippingFirstName,
  322. lastName: createAddressData.shippingLastName,
  323. email: createAddressData.shippingEmail,
  324. address: createAddressData.shippingAddress,
  325. city: createAddressData.shippingCity,
  326. state: createAddressData.shippingState,
  327. country: createAddressData.shippingCountry,
  328. postcode: createAddressData.shippingPostcode,
  329. phone: createAddressData.shippingPhoneNumber,
  330. };
  331. dispatchCartStore('update',{
  332. billingAddress: newBillingAddress,
  333. shippingAddress: newShippingAddress
  334. });
  335. setCartData(Object.assign({...cartData},{
  336. billingAddress: {...newBillingAddress},
  337. shippingAddress: {...newShippingAddress}
  338. }));
  339. } else {
  340. // 提示用户出错了,刷新页面
  341. showToast('Something wrong. Please refresh the page.', 'danger');
  342. }
  343. } else {
  344. showToast(saveRes.msg, 'danger');
  345. }
  346. setAddressModalOpen(false);
  347. } catch(err) {
  348. // 错误处理
  349. console.error("save address error", err);
  350. showToast('Save address failed. Please try again.', 'danger');
  351. } finally {
  352. setAddressSaving(false);
  353. }
  354. };
  355. const handlePaymentMethodChange = async (method: string) => {
  356. changePaymentMethodToloadSdk(method);
  357. // let shippingMethod = '-1';
  358. // if(methodShiippingRef.current) {
  359. // shippingMethod = methodShiippingRef.current.getSelectShipMethod();
  360. // }
  361. overlayLoading.start();
  362. const saveRes = await saveCheckoutCart({
  363. shippingMethod: '-1',
  364. paymentMethod: method,
  365. couponCode: '-1',
  366. giftCardNumber: '-1',
  367. memberDiscount: '-1'
  368. });
  369. overlayLoading.stop();
  370. if(!saveRes.error) {
  371. if(saveRes.data) {
  372. const newCartDetail = formatCartDetail(saveRes.data);
  373. dispatchCartStore('update',newCartDetail);
  374. setCartData({...newCartDetail});
  375. }
  376. return true;
  377. } else {
  378. showToast(saveRes.msg, 'danger');
  379. return false;
  380. }
  381. };
  382. const handleShippingMethodChange = useCallback(async (method: string) => {
  383. overlayLoading.start();
  384. const saveRes = await saveCheckoutCart({
  385. shippingMethod: method,
  386. paymentMethod: '-1',
  387. couponCode: '-1',
  388. giftCardNumber: '-1',
  389. memberDiscount: '-1'
  390. });
  391. overlayLoading.stop();
  392. if(!saveRes.error) {
  393. if(saveRes.data) {
  394. const newCartDetail = formatCartDetail(saveRes.data);
  395. dispatchCartStore('update',newCartDetail);
  396. setCartData({...newCartDetail});
  397. }
  398. // 保存成功之后需要重新获取支付方式
  399. getPaymentMethod();
  400. } else {
  401. showToast(saveRes.msg, 'danger');
  402. }
  403. },[saveCheckoutCart,showToast,getPaymentMethod,dispatchCartStore]);
  404. // 校验地址 运输方式,支付方式;
  405. const validateCheckout = useCallback(async () => {
  406. const addressValid = await addressForm.trigger();
  407. if(!addressValid) {
  408. showToast('Please fill in the address information correctly.', 'danger');
  409. return false;
  410. }
  411. if(methodShiippingRef.current) {
  412. const shippingMethodValid = await methodShiippingRef.current.validateShipMethod();
  413. if(!shippingMethodValid) {
  414. showToast('Please select a shipping method.', 'danger');
  415. return false;
  416. }
  417. } else {
  418. showToast('Please wait for shipping method loading.', 'danger');
  419. return false;
  420. }
  421. if(methodPaymentRef.current) {
  422. const paymentMethodValid = await methodPaymentRef.current.validatePaymentMethod();
  423. const paymentMethod = methodPaymentRef.current.getSelectPaymentMethod();
  424. if(!paymentMethodValid) {
  425. showToast('Please select a payment method.', 'danger');
  426. return false;
  427. }
  428. if(paymentMethod === 'airwallex') {
  429. const paymentMethodValid = methodPaymentRef.current.validateAirwallexCard();
  430. return paymentMethodValid;
  431. }
  432. } else {
  433. showToast('Please wait for payment method loading.', 'danger');
  434. return false;
  435. }
  436. if(!cartData) {
  437. showToast('Please add product to your cart.', 'danger');
  438. return false;
  439. }
  440. //如果购物车详情没有保存过支付方式,则需要保存支付方式
  441. if(cartData.paymentMethod === null) {
  442. const pmethod = methodPaymentRef.current.getSelectPaymentMethod();
  443. const saveRes = await saveCheckoutCart({
  444. shippingMethod: '-1',
  445. paymentMethod: pmethod,
  446. couponCode: '-1',
  447. giftCardNumber: '-1',
  448. memberDiscount: '-1'
  449. });
  450. if(!saveRes.error) {
  451. if(saveRes.data) {
  452. const newCartDetail = formatCartDetail(saveRes.data);
  453. dispatchCartStore('update',newCartDetail);
  454. setCartData({...newCartDetail});
  455. }
  456. } else {
  457. showToast(saveRes.msg, 'danger');
  458. }
  459. }
  460. return true;
  461. },[addressForm,showToast,saveCheckoutCart,dispatchCartStore]);
  462. // 苹果支付下单
  463. const applepayPlaceOrder = useCallback(async () => {
  464. const res = await createOrder();
  465. if(!res.error) {
  466. //创建订单成功后清空redux 购物车
  467. dispatchCartStore('clear');
  468. return {
  469. orderId: res.data?.gatewayOrderId ?? '',
  470. webOrderId: res.data?.orderId ?? ''
  471. };
  472. } else{
  473. // 抛出错误让第三方支付能够捕获
  474. throw new Error("CREATE_ORDER_ERROR: " + res.msg);
  475. }
  476. },[createOrder,dispatchCartStore]);
  477. // 下单(下单和校验分开)
  478. const handlePlaceOrder: PlaceOrderFunction = async <T,>(params?: CreatePaymentInitiateVariables): Promise<PlaceOrderResult<T>> => {
  479. const res = await createOrder(params);
  480. if(res.error) {
  481. overlayLoading.stop();
  482. confirmDialog({
  483. title: "Warning",
  484. content: res.msg + ". Create order failed. Please try again.",
  485. noCancel: true,
  486. }).then(() => {});
  487. } else {
  488. //创建订单成功后清空redux 购物车
  489. dispatchCartStore('clear');
  490. }
  491. const pmethod = methodPaymentRef.current?.getSelectPaymentMethod();
  492. let resultData: PlaceOrderResult<T>;
  493. if(pmethod === 'airwallex' && methodPaymentRef.current) {
  494. const airwallexCardElements = methodPaymentRef.current.getAirwallexCardElements();
  495. const airCardNumber = airwallexCardElements.cardNumber as T;
  496. resultData = {
  497. data: res.data,
  498. otherData: airCardNumber
  499. };
  500. } else if(pmethod === 'klarna') {
  501. const klaData = {
  502. billingAddress: cartData.billingAddress,
  503. shippingAddress: cartData.shippingAddress
  504. } as T;
  505. resultData = {
  506. data: res.data,
  507. otherData: klaData
  508. };
  509. } else {
  510. resultData = {
  511. data: res.data,
  512. otherData: null
  513. };
  514. }
  515. return resultData;
  516. }
  517. const onCartDataChange = async (paylod: CartDetail) => {
  518. dispatchCartStore('update',paylod);
  519. setCartData({...paylod});
  520. // @todo 还要请求支付方式和运输方式
  521. // await getShippingMethod();
  522. // await getPaymentMethod();
  523. await Promise.all([getShippingMethod(),getPaymentMethod()]);
  524. };
  525. // function tt() {
  526. // const arr = [
  527. // ['US','3803800217'],
  528. // ['CH','+4186043315'],
  529. // ['US','334-208-6177'],
  530. // ['AU','+61 404103617'],
  531. // ['US','1-9166705105'],
  532. // ['CA','819 384 3221'],
  533. // ['US','(409) 239-9482'],
  534. // ['US','1-(651) 421-2762'],
  535. // ['CH', '1-792930242'],
  536. // ['US', '+1 4570438892'],
  537. // ['US', '1-+1 (404) 276-1068'],
  538. // ['CH', '+49 015164340021'], // 国家与phonecode不一致情况
  539. // ];
  540. // arr.forEach((item) => {
  541. // normalizePhoneForForm(item[1], item[0]);
  542. // });
  543. // }
  544. const testEruda = () => {
  545. erudaCount.current++
  546. if(window.eruda && erudaCount.current > 10) {
  547. window.eruda.init();
  548. }
  549. };
  550. return (
  551. <section className="w-full">
  552. <div>
  553. {/* <button className="w-25 h-8 bg-amber-500 flex items-center justify-center" onClick={tt}>test</button> */}
  554. {/* {loadingAddress ?
  555. <AddressResultDisplayLoading />
  556. :
  557. <AddressResultDisplay shippingAddress={cartData.shippingAddress || null}
  558. onAddressModalOpenClick={openAddressModal}
  559. />
  560. } */}
  561. <AddressResultDisplay shippingAddress={cartData.shippingAddress || null}
  562. onAddressModalOpenClick={openAddressModal}
  563. />
  564. <CommonModal
  565. isOpen={addressModalOpen}
  566. onClose={closeAddressModal}
  567. header={
  568. <div className="w-full box-border p-3.75 h-14.5 flex justify-between items-center">
  569. <span>Shipping Address</span>
  570. <button className="w-6 h-6" onClick={() => closeAddressModal(false)}>
  571. <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon" className="h-6 transition-all ease-in-out hover:scale-110"><path strokeLinecap="round" strokeLinejoin="round" d="M6 18 18 6M6 6l12 12"></path></svg>
  572. </button>
  573. </div>
  574. }
  575. body={
  576. <div className="box-border w-full h-full p-3.75">
  577. <p className="text-ly-12 leading-ly-20">
  578. Select a billing address from your address book or enter a new address.
  579. </p>
  580. <div>
  581. <FormProvider {...addressForm}>
  582. <form>
  583. <ShippingAddressCheckout />
  584. <BillingAddressCheckout />
  585. </form>
  586. </FormProvider>
  587. </div>
  588. </div>
  589. }
  590. footer={
  591. <div className="box-border w-full p-3.75">
  592. <button className="block flex justify-center items-center w-full h-12 bg-ly-green rounded-3xl text-white text-ly-16 font-bold"
  593. onClick={addressForm.handleSubmit(addressFormOnSubmit)}
  594. >
  595. {addressSaving ? <LoadingSpinner /> : 'Save'}
  596. </button>
  597. </div>
  598. }
  599. />
  600. </div>
  601. <div className="mt-6 box-border px-4">
  602. <h3 className="text-ly-24 font-medium" onClick={testEruda}>Shipping Method</h3>
  603. <ShippingMethodCheckout
  604. ref={methodShiippingRef}
  605. shipMethods={shippingMethodDatas}
  606. errorMsg={shippingMethodError}
  607. selectedShippingRate={selectedShippingRate}
  608. shippingMethodLoading={shippingMethodLoading}
  609. onShipMethodChange={handleShippingMethodChange}
  610. />
  611. </div>
  612. {!cartData.isGuest &&
  613. <div className="mt-6 box-border px-4">
  614. <CheckoutGiftCardGate
  615. giftcardNumber={cartData.giftcardNumber || ''}
  616. formattedGiftcardAmount={cartData.formattedGiftcardAmount}
  617. onChangeGiftcard={onCartDataChange}
  618. />
  619. </div>
  620. }
  621. <div className="mt-6 box-border px-4">
  622. <CheckoutVipPlus
  623. cartData={cartData}
  624. onSwitchVip={onCartDataChange}
  625. />
  626. </div>
  627. <div className="mt-6 box-border px-4">
  628. <h3 className="text-ly-24 font-medium">Promotion Details</h3>
  629. <div className="w-full box-border mt-3">
  630. <PromotionsDetails cartData={cartData} />
  631. </div>
  632. </div>
  633. <div className="mt-6 box-border px-4">
  634. <h3 className="text-ly-24 font-medium">Payment Method</h3>
  635. <PaymentMethodCheckout
  636. ref={methodPaymentRef}
  637. paymentMethods={paymentMethodDatas}
  638. selectedPaymentMethod={selectedPaymentMethod}
  639. paymentMethodLoading={paymentMethodLoading}
  640. loadError={paymentMethodError}
  641. onPaymentMethodChange={handlePaymentMethodChange}
  642. />
  643. </div>
  644. <div className="mt-3 box-border px-4 pb-8">
  645. <p className="text-ly-12 text-[#666666] leading-ly-20">By providing your information, you agree to Wiggins'sPrivacy Policyand Terms of Use.</p>
  646. <div className="mt-6 w-full">
  647. { paymentMethodLoading && <LoadingCheckoutPlaceOrder />}
  648. <div className={clsx("w-full",{
  649. "hidden": selectedPaymentMethod !== 'applepay' || paymentMethodLoading
  650. })}>
  651. <PaypalApplepayButton
  652. grandTotal={cartData.grandTotal}
  653. billingContact={cartData.billingAddress}
  654. shippingContact={cartData.shippingAddress}
  655. validateCheckout={validateCheckout}
  656. createOrder={applepayPlaceOrder}
  657. />
  658. </div>
  659. <div className={clsx("w-full",{
  660. "hidden": selectedPaymentMethod === 'applepay' || paymentMethodLoading
  661. })}>
  662. <CheckoutPlaceOrder
  663. paymentMethod={selectedPaymentMethod}
  664. validateCheckout={validateCheckout}
  665. clickPlaceOrder={handlePlaceOrder}
  666. />
  667. </div>
  668. </div>
  669. </div>
  670. </section>
  671. );
  672. };