CheckoutWrapper.tsx 30 KB

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