| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207 |
- "use client";
- import clsx from "clsx";
- import Image from "next/image";
- 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";
- /**
- * 关于如何确定默认选中哪个支付方式:
- * 从购物车详情中获取支付方式 A,支付方式列表中有A,那么把A标记为默认选中;如果支付方式列表中没有A,把paypal标记为默认选中;
- */
- export interface RefPaymentMethodsHandle {
- getSelectPaymentMethod: () => string;
- getSelectPaymentTitle: () => string;
- validatePaymentMethod: () => Promise<boolean>;
- getAirwallexCardElements: () => AirwallexCardElements;
- validateAirwallexCard: () => boolean;
- }
- export function PaymentMethodCheckout({
- ref,
- paymentMethods,
- selectedPaymentMethod,
- paymentMethodLoading,
- loadError,
- onPaymentMethodChange,
- }: {
- ref: Ref<RefPaymentMethodsHandle>;
- paymentMethods: CheckoutPaymentMethod[];
- selectedPaymentMethod: string;
- paymentMethodLoading: boolean;
- loadError: string | null;
- onPaymentMethodChange: (value: string) => Promise<boolean>;
- }) {
- const { showToast } = useCustomToast();
- const {airwallexCardState,getCardElements} = useAirwallexCard(!paymentMethodLoading,{
- cardNumber: 'airwallex_cardNumber',
- cvc: 'airwallex_cvc',
- expiry: 'airwallex_expiry'
- });
-
- const {
- register,
- getValues,
- trigger,
- reset,
- formState: { errors }
- } = useForm({
- mode: "onChange",
- reValidateMode: "onChange",
- // defaultValues: {
- // paymentMethod: defaultValue,
- // }
- });
- // 参考 https://github.com/react-hook-form/error-message/blob/master/src/ErrorMessage.tsx
- const paymentMethodError = get(errors, "paymentMethod");
- const paymentMethodOnChange = async (event: React.ChangeEvent<HTMLInputElement>) => {
- 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 {
- // 获取用户选择的支付方式
- getSelectPaymentMethod: () => {
- return getValues('paymentMethod');
- },
- getSelectPaymentTitle: () => {
- const nowValue = getValues('paymentMethod');
- const findItem = paymentMethods.find((item) => item.method === nowValue);
- return findItem?.title ?? '';
- },
- // 触发校验
- validatePaymentMethod: () => {
- return trigger();
- },
- // 获取airwallex card 支付创建的elements
- getAirwallexCardElements: () => {
- return getCardElements();
- },
- validateAirwallexCard: () => {
- if(!airwallexCardState.cardNumberReady) {
- showToast('Card number input has not ready. Please wait a minute.', 'danger');
- return false;
- }
- if(!airwallexCardState.expiryReady) {
- showToast('Card expiry date input has not ready. Please wait a minute.', 'danger');
- return false;
- }
- if(!airwallexCardState.cvcReady) {
- showToast('Card cvc input has not ready. Please wait a minute.', 'danger');
- return false;
- }
- if(!airwallexCardState.cardNumberValid) {
- showToast('Please input your card number.', 'danger');
- return false;
- }
- if(!airwallexCardState.cvcValid) {
- showToast('Please input cvc.', 'danger');
- return false;
- }
- if(!airwallexCardState.expiryValid) {
- showToast('Please input expiry date.', 'danger');
- return false;
- }
- return true;
- }
- }
-
- },[getCardElements,paymentMethods,airwallexCardState]);
- const airwallexOtherInfoShow = selectedPaymentMethod === 'airwallex' && airwallexCardState.cardNumberReady && airwallexCardState.expiryReady && airwallexCardState.cvcReady;
-
- return (<>
- { paymentMethodLoading && <LoadingPaymentMethod /> }
- { (!paymentMethodLoading && loadError) && <p className="mt-3 text-red-500 text-ly-12">{loadError}</p>}
- <form name="shipping method" className={clsx("w-full", {
- "hidden": (paymentMethodLoading || loadError)
- })}>
-
- <div className="mt-3 w-full">
- <ul className="w-full">
- {
- paymentMethods.map((item) => {
- return (
- <li className="w-full mt-3" key={item.method}>
- <label htmlFor={"paymentmethod-" + item.method}
- 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"
- >
- <p className="pr-2 flex-none relative">
- <Image className="block h-8 w-auto" width={32} height={32} src={item.icon || ''} alt={"Payment Method " + item.title} />
- </p>
- <p className="text-ly-12 text-[#666666] ml-1">{item.description}</p>
- <div className="absolute top-1/2 -translate-y-1/2 right-3 flex items-center">
- <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>
- <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>
- </div>
- <input id={"paymentmethod-" + item.method}
- className="absolute w-6 h-6 opacity-0"
- type="radio"
- value={item.method}
- {...register('paymentMethod', {
- required: 'Please select a payment method.',
- onChange: (event) => {
- paymentMethodOnChange(event);
- },
- })}
- />
- </label>
- { item.method === "airwallex" &&
- <div className="w-full mt-3"
- style={{display: airwallexOtherInfoShow ? 'block' : 'none'}}
- >
- <AirwallexCardInput
- cardNumberErrorMsg={airwallexCardState.cardNumberErrorMsg}
- expiryErrorMsg={airwallexCardState.expiryErrorMsg}
- cvcErrorMsg={airwallexCardState.cvcErrorMsg}
- />
- </div>
- }
- </li>
- )
- })
- }
- </ul>
-
- {paymentMethodError && <p className="text-red-500 text-ly-12">{paymentMethodError.message}</p>}
- </div>
-
- </form>
- </>);
-
- }
|