| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348 |
- "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<boolean>;
- 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");
- const applePayConfig = eligiblePaymentMethods?.isEligible("applepay") ? eligiblePaymentMethods.getDetails("applepay").config : null;
-
- // 支付回调配置
- /**
- * 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);
- /*
- 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?.merchantCountry;
- const tips = isLoading || !isHydrated ? (
- <p className="text-ly-12 text-center">
- Applepay is loading...
- </p>
- ) : eligibilityError ? (
- <p className="text-ly-errorcolor text-ly-12 text-center">
- {eligibilityError.message}
- {/* Failed to load payment options. Please refresh the page. */}
- </p>
- ) : !eligiblePaymentMethods ? (
- <p className="text-ly-12 text-center">Applepay is loading2...</p>
- ) : !eligiblePaymentMethods.isEligible("applepay") ? (
- <p className="text-ly-12 text-ly-errorcolor text-center">Apple Pay is not eligible for this transaction.</p>
- ) : applePayConfig ? (
-
- <ApplePayButtonBox
- onClickHandler={validateCheckout}
- applePayConfig={applePayConfig}
- applePaySessionVersion={4}
- paymentRequest={{
- countryCode: applePayConfig.merchantCountry === 'C2' ? 'CN' : applePayConfig.merchantCountry as string,
- currencyCode: currencyCode,
- requiredBillingContactFields: ["postalAddress"],
- requiredShippingContactFields: ["name", "email", "postalAddress"],
- total: {
- label: storeName,
- amount: String(grandTotal),
- type: "final",
- },
- ...addressContact
- }}
- createOrder={configCreateOrder}
- onApprove={onApprove}
- onCancel={onCancel}
- onError={onError}
- />
-
- ) : (
- <p className="text-ly-12 text-center">Loading Apple Pay configuration...</p>
- );
-
- return (
- <div className="box-border w-full"> {tips} </div>
- );
- }
- // ApplePayButtonBox是 @paypal/react-paypal-js/sdk-v6 中的<ApplePayOneTimePaymentButton>组件的代码复制过来的
- function ApplePayButtonBox({
- onClickHandler, // 点击苹果支付按钮
- ...hookProps
- }: {
- onClickHandler: () => Promise<boolean>;
- } & UseApplePayOneTimePaymentSessionProps
- ) {
- const { error, handleClick, handleDestroy } = useApplePayOneTimePaymentSession(hookProps);
- const buttonRef = useRef<HTMLElement>(null);
- const handleClickRef = useRef(handleClick);
- // handleClickRef.current = handleClick;
- useEffect(() => {
- handleClickRef.current = handleClick;
- }, [handleClick]);
- // Apple's <apple-pay-button> 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 (
- <>
- <apple-pay-button
- ref={buttonRef}
- buttonstyle="black"
- type="pay"
- locale="en"
- />
- </>
- );
- };
|