PaymentMethodCheckout.tsx 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172
  1. "use client";
  2. import Image from "next/image";
  3. import { Ref, useEffect, useImperativeHandle } from "react";
  4. import { useForm, get } from "react-hook-form";
  5. import {useConfig} from "@/utils/hooks/useConfig";
  6. import {getPaymentMethodLogo,getPaymentDescription} from "@/utils/paymentTools";
  7. import { CheckoutPaymentMethod } from "@/types/checkout/type";
  8. import AirwallexCardInput from "./PaymentMethodAdditional/AirwallexCardInput";
  9. import {useAirwallexCardState} from "@/lib/Airwallex/useAirwallexCard";
  10. /**
  11. * 关于如何确定默认选中哪个支付方式:
  12. * 从购物车详情中获取支付方式 A,支付方式列表中有A,那么把A标记为默认选中;如果支付方式列表中没有A,把paypal标记为默认选中;
  13. */
  14. export interface RefPaymentMethodsHandle {
  15. getSelectPaymentMethod: () => string;
  16. getSelectPaymentTitle: () => string;
  17. validatePaymentMethod: () => Promise<boolean>;
  18. }
  19. export function PaymentMethodCheckout({
  20. ref,
  21. paymentMethods,
  22. selectedPaymentMethod,
  23. grandTotal,
  24. onPaymentMethodChange,
  25. }: {
  26. ref: Ref<RefPaymentMethodsHandle>;
  27. paymentMethods: CheckoutPaymentMethod[];
  28. selectedPaymentMethod: string;
  29. grandTotal: number;
  30. onPaymentMethodChange: (value: string) => Promise<boolean>;
  31. }) {
  32. const {getCurrentCurrencyItem} = useConfig();
  33. const currentCurrency = getCurrentCurrencyItem();
  34. const currencySymbol = currentCurrency.symbol;
  35. const airwallexCardState = useAirwallexCardState();
  36. const {
  37. register,
  38. getValues,
  39. trigger,
  40. reset,
  41. formState: { errors }
  42. } = useForm({
  43. mode: "onChange",
  44. reValidateMode: "onChange",
  45. // defaultValues: {
  46. // paymentMethod: defaultValue,
  47. // }
  48. });
  49. // 参考 https://github.com/react-hook-form/error-message/blob/master/src/ErrorMessage.tsx
  50. const paymentMethodError = get(errors, "paymentMethod");
  51. const paymentMethodOnChange = async (event: React.ChangeEvent<HTMLInputElement>) => {
  52. onPaymentMethodChange(event.target.value);
  53. };
  54. useEffect(() => {
  55. let defaultValue = '';
  56. const findItem = paymentMethods.find((item) => item.method === selectedPaymentMethod);
  57. if(findItem !== undefined) {
  58. defaultValue = selectedPaymentMethod;
  59. } else {
  60. paymentMethods.forEach((item) => {
  61. if(item.method === 'paypal_smart_button') {
  62. defaultValue = item.method;
  63. }
  64. });
  65. }
  66. reset({ paymentMethod: defaultValue });
  67. },[paymentMethods,selectedPaymentMethod]);
  68. useImperativeHandle(ref, () => {
  69. return {
  70. // 获取用户选择的支付方式
  71. getSelectPaymentMethod: () => {
  72. return getValues('paymentMethod');
  73. },
  74. getSelectPaymentTitle: () => {
  75. const nowValue = getValues('paymentMethod');
  76. const findItem = paymentMethods.find((item) => item.method === nowValue);
  77. return findItem?.title ?? '';
  78. },
  79. // 触发校验
  80. validatePaymentMethod: () => {
  81. return trigger();
  82. }
  83. }
  84. },[paymentMethods]);
  85. const airwallexOtherInfoShow = selectedPaymentMethod === 'airwallex' && airwallexCardState.cardNumberReady && airwallexCardState.expiryReady && airwallexCardState.cvcReady;
  86. const genetePaymentTips = (payment: string) => {
  87. return getPaymentDescription({
  88. payment: payment,
  89. grandTotal: grandTotal,
  90. symble: currencySymbol
  91. })
  92. };
  93. return (<>
  94. <form name="shipping method" className="w-full">
  95. <div className="mt-3 w-full">
  96. <ul className="w-full">
  97. {
  98. paymentMethods.map((item) => {
  99. return (
  100. <li className="w-full mt-3" key={item.method}>
  101. <label htmlFor={"paymentmethod-" + item.method}
  102. className="group box-border flex items-center relative bg-ly-lightgray p-3 rounded-sm has-checked:bg-white has-checked:border-1 has-checked:border-ly-green"
  103. >
  104. <p className="pr-2 flex-none relative">
  105. {/**这里的width=32 height=32仅仅是让next.js有一个值计算图片的比例,并不能决定图片的渲染尺寸 */}
  106. <Image className="block h-8 w-auto"
  107. width={32}
  108. height={32}
  109. src={getPaymentMethodLogo(item.method)}
  110. alt={"Payment Method " + item.title}
  111. />
  112. </p>
  113. <p className="text-ly-12 text-[#666666] ml-1">
  114. {genetePaymentTips(item.method)}
  115. </p>
  116. <div className="absolute top-1/2 -translate-y-1/2 right-3 flex items-center">
  117. <svg className="check w-4 h-4 hidden group-has-[:checked]:block" xmlns="http://www.w3.org/2000/svg" xmlnsXlink="http://www.w3.org/1999/xlink" width="16" height="16" viewBox="0 0 16 16" fill="none"><rect x="1" y="1" width="14" height="14" rx="1" fill="#000000" ></rect><path stroke="rgba(255, 255, 255, 1)" strokeWidth="1.5" strokeLinecap="square" d="M5.3335 8L7.3335 10L11.3335 6"></path></svg>
  118. <svg className="uncheck w-4 h-4 block group-has-[:checked]:hidden" xmlns="http://www.w3.org/2000/svg" xmlnsXlink="http://www.w3.org/1999/xlink" width="16" height="16" viewBox="0 0 16 16" fill="none"><rect x="1" y="1" width="14" height="14" rx="1" fill="#FFFFFF" ></rect><path fill="rgba(0, 0, 0, 1)" d="M2 0.5L14 0.5C14.8284 0.5 15.5 1.17157 15.5 2L15.5 14C15.5 14.8284 14.8284 15.5 14 15.5L2 15.5C1.17157 15.5 0.5 14.8284 0.5 14L0.5 2C0.5 1.17157 1.17157 0.5 2 0.5ZM2 1.5C1.72386 1.5 1.5 1.72386 1.5 2L1.5 14C1.5 14.2761 1.72386 14.5 2 14.5L14 14.5C14.2761 14.5 14.5 14.2761 14.5 14L14.5 2C14.5 1.72386 14.2761 1.5 14 1.5L2 1.5Z"></path></svg>
  119. </div>
  120. <input id={"paymentmethod-" + item.method}
  121. className="absolute w-6 h-6 opacity-0"
  122. type="radio"
  123. value={item.method}
  124. {...register('paymentMethod', {
  125. required: 'Please select a payment method.',
  126. onChange: (event) => {
  127. paymentMethodOnChange(event);
  128. },
  129. })}
  130. />
  131. </label>
  132. { item.method === "airwallex" &&
  133. <div className="w-full mt-3"
  134. style={{display: airwallexOtherInfoShow ? 'block' : 'none'}}
  135. >
  136. <AirwallexCardInput />
  137. </div>
  138. }
  139. </li>
  140. )
  141. })
  142. }
  143. </ul>
  144. {paymentMethodError && <p className="text-red-500 text-ly-12">{paymentMethodError.message}</p>}
  145. </div>
  146. </form>
  147. </>);
  148. }