"use client"; import {useRef,useEffect,useCallback} from "react"; import { useRouter } from 'next/navigation'; import { usePlaceOrder } from "@/utils/hooks/usePlaceOrder"; import { usePayPal, useEligibleMethods, INSTANCE_LOADING_STATE, useApplePayOneTimePaymentSession, type ConfirmOrderResponse, type UseApplePayOneTimePaymentSessionProps, } from "@paypal/react-paypal-js/sdk-v6"; import { overlayLoading } from "@/components/theme/ui/kernel/loading/api"; import { confirmDialog } from "@/components/theme/ui/kernel/confirm/api"; import { CartAddress } from "@/types/cart/type"; import { useConfig } from "@/utils/hooks/useConfig"; import {ApplePayAddressContact} from "@/lib/Applepay/type"; // import {getPaypalApplePayConfig, type PaypalApplePaySession} from "@/lib/Applepay/applepay"; export default function PaypalApplepayButton({ createOrder, validateCheckout, billingContact, shippingContact, grandTotal, isRePay }:{ validateCheckout: () => Promise; createOrder: () => Promise<{orderId: string;webOrderId: string;}>; billingContact: CartAddress | null; shippingContact: CartAddress | null; grandTotal: number; isRePay?: boolean; }) { const router = useRouter(); const {createPaymentCallback} = usePlaceOrder(); const {countries,store,getCurrentCurrencyItem} = useConfig(); const currentCurrency = getCurrentCurrencyItem(); const currencyCode = currentCurrency.code; const storeName = store.storeName; const gatewayOrderIdRef = useRef(""); const webOrderIdRef = useRef(""); // const [configCountryCode, setConfigCountryCode] = useState(''); // paypal sdk 是否加载完成 const { loadingStatus, isHydrated } = usePayPal(); // Fetch eligibility(资格) for one-time payment flow const { error: eligibilityError, eligiblePaymentMethods, isLoading: isEligibilityLoading, } = useEligibleMethods({ payload: { currencyCode: currencyCode, // 需要根据用户选择的货币走 paymentFlow: "ONE_TIME_PAYMENT", // 一次性支付 }, }); const isLoading = loadingStatus === INSTANCE_LOADING_STATE.PENDING; const isApplepayEligible = eligiblePaymentMethods?.isEligible("applepay"); const applePayConfig = eligiblePaymentMethods?.getDetails("applepay"); // 支付回调配置 /** * when the user has authorized the Apple Pay payment with Touch ID, Face ID, or a passcode. * ApplePaySession onpaymentauthorized 时触发 */ const configCreateOrder = useCallback(async () => { const res = await createOrder(); gatewayOrderIdRef.current = res.orderId; webOrderIdRef.current = res.webOrderId; return res; },[createOrder]); /**await paypal.Applepay().confirmOrder() 后调onApprove */ const onApprove = useCallback(async (data: ConfirmOrderResponse) => { console.log("applepay approved:", data); /** * approveApplePayPayment: { * id: string; //gatewayOrderId * status: "APPROVED" * } */ const resCallback = await createPaymentCallback({ orderId: Number(webOrderIdRef.current), gatewayOrderId: data.approveApplePayPayment.id || gatewayOrderIdRef.current, status: 'success', }); overlayLoading.stop(); if(!resCallback.error) { // 支付成功,跳转到成功落地页 confirmDialog({ title: "Payment Success", content: "Your payment was successful. Will redirect to success page.", noCancel: true, }).then(() => { router.replace('/paymentresult/result?orderId=' + webOrderIdRef.current + '&return_result=success'); }); } else { // callback 失败,提示错误 confirmDialog({ title: "Somthing Wrong", content: resCallback.msg + " Please contact customer service." + " OrderId: "+ webOrderIdRef.current, noCancel: true, }).then(() => { router.replace('/'); }); } // overlayLoading.stop(); // router.replace('/paymentresult/result?orderId=' + webOrderIdRef.current + '&return_result=success'); },[router]); /**ApplePaySession oncancel */ const onCancel = useCallback(() => { console.log("applepay cancelled:"); overlayLoading.stop(); confirmDialog({ title: "Payment cancelled", content: "You canceled the payment.", noCancel: true, }).then(() => { }); },[]); /** * 1. ApplePaySession onvalidatemerchant 出错 * 2. ApplePaySession completePayment 出错 * 3. createOrder->paypal.Applepay().confirmOrder()->onApprove() 链条出错 * 4. 用户点击后的整个链条出错 */ const onError = useCallback(async (error: Error) => { console.error("applepay error:", error); if(!isRePay) { if(webOrderIdRef.current || gatewayOrderIdRef.current) { await createPaymentCallback({ orderId: Number(webOrderIdRef.current), gatewayOrderId: gatewayOrderIdRef.current, status: 'failure', }); overlayLoading.stop(); // 订单创建成功了,跳转二次支付页面 confirmDialog({ title: "Payment failed", content: error.message, noCancel: true, }).then(() => { router.replace('/paymentresult/result?orderId=' + webOrderIdRef.current + '&return_result=failure'); }); } else { overlayLoading.stop(); // 订单创建失败,留在当前页面 confirmDialog({ title: "Payment failed", content: error.message + " Please try again.", noCancel: true, }).then(() => { }); } } else { overlayLoading.stop(); // 登录用户 二次支付页面 confirmDialog({ title: "Payment Failed", content: error.message, noCancel: true, }).then(() => { }); } },[router,isRePay]); const addressContact: ApplePayAddressContact = {}; if(billingContact) { const billCountry = countries.find((item) => item.code === billingContact.country); addressContact.billingContact = { addressLines: [billingContact.address], administrativeArea: billingContact.state, country: billCountry?.name, countryCode: billingContact.country, emailAddress: billingContact.email, familyName: billingContact.lastName, givenName: billingContact.firstName, locality: billingContact.city, postalCode:billingContact.postcode, } } if(shippingContact) { const shipCountry = countries.find((item) => item.code === shippingContact.country); addressContact.shippingContact = { addressLines: [shippingContact.address], administrativeArea: shippingContact.state, country: shipCountry?.name, countryCode: shippingContact.country, emailAddress: shippingContact.email, familyName: shippingContact.lastName, givenName: shippingContact.firstName, locality: shippingContact.city, postalCode:shippingContact.postcode, } } console.log('applePayConfig ---- ', applePayConfig?.config); /* useEffect(() => { if(!sdkInstance || !isHydrated) return; const getConfig = async () => { const paypalSdkApplePayPaymentSession = await sdkInstance.createApplePayOneTimePaymentSession() as PaypalApplePaySession; const res = await getPaypalApplePayConfig(paypalSdkApplePayPaymentSession); console.log('res ---- ', res); setConfigCountryCode(res.countryCode); }; getConfig(); }, [isHydrated, sdkInstance]); */ const merchantCountry = applePayConfig?.config.merchantCountry; if(!isApplepayEligible) { return

Applepay is not available on your device.

; } return (
{isLoading || !isHydrated || isEligibilityLoading || applePayConfig === undefined ?

Applepay is loading...

: }
); } // ApplePayButtonBox是 @paypal/react-paypal-js/sdk-v6 中的组件的代码复制过来的 function ApplePayButtonBox({ eligibilityError, onClickHandler, // 点击苹果支付按钮 ...hookProps }: { eligibilityError: Error | null; onClickHandler: () => Promise; } & UseApplePayOneTimePaymentSessionProps ) { const { error, handleClick, handleDestroy } = useApplePayOneTimePaymentSession(hookProps); const buttonRef = useRef(null); const handleClickRef = useRef(handleClick); // handleClickRef.current = handleClick; useEffect(() => { handleClickRef.current = handleClick; }, [handleClick]); // Apple's manages its own enabled/disabled state internally // via canMakePayments(); we deliberately don't add an SDK-level disabled layer // (merchants control presentation themselves). React's onClick also doesn't // work on the element due to its shadow DOM, so we attach the handler directly. useEffect(() => { const el = buttonRef.current; if (!el) { return; } const applepayButtonClick = () => { overlayLoading.start(); onClickHandler().then((res) => { if(res) { handleClickRef.current().catch(() => { // Errors are captured by the hook's setError }); } else { overlayLoading.stop(); } }).catch((err) => { overlayLoading.stop(); console.error(err); }); }; el.addEventListener("click", applepayButtonClick); return () => el.removeEventListener("click", applepayButtonClick); }, [onClickHandler]); useEffect(() => { if (error) { console.error(error); } }, [error]); // Cleanup on unmount useEffect(() => { return () => { handleDestroy(); }; }, [handleDestroy]); return ( <> { eligibilityError &&

{eligibilityError.message}

} ); };