| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549 |
- "use client";
- import clsx from "clsx";
- import Image from "next/image";
- import { use,useState,useCallback } from "react";
- import { useForm, get, useWatch } from "react-hook-form";
- import { useRouter } from 'next/navigation'
- import { useCustomToast } from "@/utils/hooks/useToast";
- import AirwallexCardInput from "../PaymentMethodAdditional/AirwallexCardInput";
- import {
- airwallexManager,
- useAirwallexCardState
- } from "@/lib/Airwallex/useAirwallexCard";
- import {RepayOrderPaymentMethodsData} from "@/types/checkout/type";
- import { OrderDetails } from "@/types/customer/type";
- import {FetchGraphqlResult} from "@/types/graphqlFetch/type";
- import { usePlaceOrder } from "@/utils/hooks/usePlaceOrder";
- import PaypalButton from "../PaymentButton/PaypalButton";
- import { overlayLoading } from "@/components/theme/ui/kernel/loading/api";
- import { confirmDialog } from "@/components/theme/ui/kernel/confirm/api";
- import PaypalApplepayButton from "../PaymentButton/PaypalApplepayButton";
- import KlarnaButton from "../PaymentButton/KlarnaButton";
- import {getAddressFromOrderDetailAddressList} from "@/utils/orderDetailTools";
- import { useConfig } from '@utils/hooks/useConfig';
- import {usePaymentSDKContext} from "@/providers/PaymentSDKProvider";
- /**@todo 优化目标:不使用 react-hook-form */
- export function PaymentMethodContinueTpPay({
- paymentMethodsDataPromise,
- defaultPaymentMethod,
- orderId,
- orderDetail
- }: {
- paymentMethodsDataPromise: Promise<FetchGraphqlResult<RepayOrderPaymentMethodsData>>;
- defaultPaymentMethod: {method: string; methodTitle: string;}
- orderId: number;
- orderDetail: OrderDetails;
- }) {
- const router = useRouter();
- const { showToast } = useCustomToast();
- const paymentMethodsData = use(paymentMethodsDataPromise);
- const paymentMethods = paymentMethodsData.data?.collectionRepayOrderPaymentMethods || [];
- const { paymentRepay, createPaymentCallback } = usePlaceOrder();
- const {loadSdk} = usePaymentSDKContext();
- const {store,currencies} = useConfig();
- const currentCurrencyCode = store.currentCurrency;
- const orderCurrency = currencies.find(item => item.code === orderDetail.orderCurrencyCode) || null;
- const orderCurrencyCode = orderCurrency?.code || currentCurrencyCode;
- const airwallexCardState = useAirwallexCardState();
- const [isPay, setIsPay] = useState(false);
- const shippingContact = getAddressFromOrderDetailAddressList(orderDetail.addresses,"order_shipping");
- const billingContact = getAddressFromOrderDetailAddressList(orderDetail.addresses,"order_billing");
- const {
- register,
- // watch,
- control,
- formState: { errors }
- } = useForm({
- mode: "onChange",
- reValidateMode: "onChange",
- defaultValues:{
- paymentMethod: defaultPaymentMethod.method
- }
- });
- // 参考 https://github.com/react-hook-form/error-message/blob/master/src/ErrorMessage.tsx
- const paymentMethodError = get(errors, "paymentMethod");
- // react 19中使用watch 会提示React Hook Form's `useForm()` API returns a `watch()` function which cannot be memoized safely.
- // const selectedPaymentMethod = watch("paymentMethod");
- const selectedPaymentMethod = useWatch({
- control,
- name:"paymentMethod",
- });
- const paypalRepayOrder = async () => {
- overlayLoading.start();
- const res = await paymentRepay({
- orderId: Number(orderId),
- paymentMethod: selectedPaymentMethod
- });
- overlayLoading.stop();
- if(!res.error && res.data !== null) {
- return {
- error: false,
- msg: '',
- orderId: res.data.gatewayOrderId,
- webOrderId: res.data.orderId
- };
- } else {
- confirmDialog({
- title: "Warning",
- content: res.msg + " Create order failed. Please try again.",
- noCancel: true,
- }).then(() => {});
- return {
- error: true,
- msg: '',
- orderId: '',
- webOrderId: ''
- };
- }
- };
- const airwallexRepay = async () => {
- setIsPay(true);
- overlayLoading.start();
- const res = await paymentRepay({
- orderId: Number(orderId),
- paymentMethod: selectedPaymentMethod,
- paymentSuccessUrl: window.location.origin + '/paymentresult/result'
- });
-
- if(!res.error && res.data !== null) {
- // 创建订单成功了,跳转支付页面
- window.location.href = res.data.gatewayOrderId;
- } else {
- //创建订单出错了
- overlayLoading.stop();
- confirmDialog({
- title: "Warning",
- content: res.msg + " Create order failed. Please try again.",
- noCancel: true,
- }).then(() => {});
- }
- setIsPay(false);
- };
- const validateAirwallexCard = () => {
- const res = airwallexManager.validateCard();
- if(res.msg) {
- showToast(res.msg, 'danger');
- }
- return res.valid;
-
- };
- const cardAirwallexRepay = async () => {
- setIsPay(true);
- overlayLoading.start();
- const validRes = validateAirwallexCard();
- if(validRes) {
-
- const res = await paymentRepay({
- orderId: Number(orderId),
- paymentMethod: selectedPaymentMethod,
- });
-
- if(!res.error && res.data !== null) {
- // 创建订单成功
- const cardNumberElement = airwallexManager.getCardNumberElement();
- const paymentIntentIdClientSecret = res.data.gatewayOrderId.split(':');
- const paymentIntentId = paymentIntentIdClientSecret[0];
- const paymentIntentClientSecret = paymentIntentIdClientSecret[1];
- cardNumberElement?.confirm({
- intent_id: paymentIntentId,
- client_secret: paymentIntentClientSecret,
- }).then((result) => {
- overlayLoading.stop();
- console.log('cardNumberElement.confirm result ---- ', result);
- // 支付成功 跳转页面
- router.replace('/paymentresult/result?orderId=' + orderId + '&return_result=success');
- }).catch((error) => {
- overlayLoading.stop();
- console.log('cardNumberElement.confirm error ---- ', error);
- // 支付失败
- router.replace('/paymentresult/result?orderId=' + orderId + '&return_result=failure');
- });
- } else {
- //创建订单出错了
- overlayLoading.stop();
- confirmDialog({
- title: "Warning",
- content: res.msg + " Create order failed. Please try again.",
- noCancel: true,
- }).then(() => {});
- }
- }else {
- overlayLoading.stop();
- }
- setIsPay(false);
- };
- const afterpayPlaceOrder = async () => {
- setIsPay(true);
- overlayLoading.start();
- if(window.AfterPay) {
-
- const res = await paymentRepay({
- orderId: Number(orderId),
- paymentMethod: selectedPaymentMethod,
- paymentSuccessUrl: window.location.origin + '/paymentresult/result',
- paymentCancelUrl: window.location.origin + '/paymentresult/result'
- });
-
- if(!res.error && res.data !== null) {
- const afterpayToken = res.data.gatewayOrderId;
- const webOrderId = res.data.orderId;
- let countryCode = "US";
- if(orderCurrencyCode === "CAD") {
- countryCode = "CA";
- } else if(orderCurrencyCode === "AUD") {
- countryCode = "AU";
- } else if(orderCurrencyCode === "NZD") {
- countryCode = "NZ";
- } else if(orderCurrencyCode === "GBP") {
- countryCode = "GB";
- }
- window.AfterPay.initialize({countryCode: countryCode});
- // To avoid triggering browser anti-popup rules, the AfterPay.open()
- // function must be directly called inside the click event listener
- window.AfterPay.open();
- // If you don't already have a checkout token at this point, you can
- // AJAX to your backend to retrieve one here. The spinning animation
- // will continue until `AfterPay.transfer` is called.
- // If you fail to get a token you can call AfterPay.close()
- window.AfterPay.onComplete = async (event) => {
- console.log('window.AfterPay.onComplete event ---- ', event);
- const status: "success" | "cancel" | "failure" = event.data.status === "SUCCESS" ? 'success' : 'cancel';
- const resCallback = await createPaymentCallback({
- orderId: Number(webOrderId),
- gatewayOrderId: event.data.orderToken,
- status: status,
- });
- overlayLoading.stop();
- if (event.data.status == "SUCCESS") {
- // The customer confirmed the payment schedule.
- // The token is now ready to be captured from your server backend.
- // 跳转结果页 成功
- if(!resCallback.error) {
- // 后端capture成功,跳转到成功落地页
- router.replace('/paymentresult/result?orderId=' + webOrderId + '&return_result=success');
- } else {
- // capture 失败,提示错误
- confirmDialog({
- title: "Somthing Wrong",
- content: resCallback.msg + " Please contact customer service." + " OrderId: "+ webOrderId,
- noCancel: true,
- }).then(() => {
- router.replace('/');
- });
-
- }
-
- } else {
- // The customer cancelled the payment or closed the popup window.
- confirmDialog({
- title: "Payment Cancelled",
- content: "You canceled the payment.",
- noCancel: true,
- }).then(() => { });
- }
- }
- window.AfterPay.transfer({token: afterpayToken});
- } else {
- //创建订单出错了
- overlayLoading.stop();
- confirmDialog({
- title: "Warning",
- content: res.msg + " Create order failed. Please try again.",
- noCancel: true,
- }).then(() => {});
- }
- } else {
- overlayLoading.stop();
- showToast("Please wait for the Afterpay to load completely.","warning");
- }
- };
- const applepayPlaceOrder = async () => {
- const res = await paymentRepay({
- orderId: Number(orderId),
- paymentMethod: selectedPaymentMethod
- });
-
- if(!res.error && res.data !== null) {
- return {
- orderId: res.data.gatewayOrderId,
- webOrderId: res.data.orderId
- };
- } else {
- // 抛出错误让第三方支付能够捕获
- throw new Error("REPAY_ORDER_ERROR: " + res.msg);
- }
- };
- const validateCheckout = useCallback(async() => {
- return true;
- }, []);
- const klarnaPlaceOrder = async () => {
- overlayLoading.start();
-
- const resRepay = await paymentRepay({
- orderId:Number(orderId),
- paymentMethod: selectedPaymentMethod,
- paymentSuccessUrl: window.location.origin + '/paymentresult/result',
- paymentCancelUrl: window.location.origin + '/paymentresult/result'
- });
-
- if(!resRepay.error && resRepay.data !== null) {
- const klarnaToken = resRepay.data.gatewayOrderId;
- const webOrderId = resRepay.data.orderId;
- const billingAddress = billingContact;
- const shippingAddress = shippingContact;
- const klarnaClientToken = klarnaToken.split("|")[0];
- // const klarnaVerifyToken = klarnaToken.split("|")[1];
- window.Klarna.Payments.init({
- client_token: klarnaClientToken,
- });
- //klarna-widget-container
- window.Klarna.Payments.load({
- container: '#klarna-widget-container',
- }, (response) => {
- if(!response.show_form) { // klarna不可用
- // 跳转结果页
- router.replace('/paymentresult/result?orderId=' + webOrderId + '&return_result=failure');
- } else {
-
- window.Klarna.Payments.authorize(
- {},
- {
- billing_address: {
- given_name: billingAddress.firstName,
- family_name: billingAddress.lastName,
- email: billingAddress.email,
- phone: billingAddress.phone,
- street_address: billingAddress.address,
- postal_code: billingAddress.postcode,
- city: billingAddress.city,
- country: billingAddress.country
- },
- shipping_address: {
- given_name: shippingAddress.firstName,
- family_name: shippingAddress.lastName,
- email: shippingAddress.email,
- street_address: shippingAddress.address,
- postal_code: shippingAddress.postcode,
- city: shippingAddress.city,
- phone: shippingAddress.phone,
- region: shippingAddress.state,
- country: shippingAddress.country
- },
- // customer: {
- // date_of_birth: "1941-03-21",
- // },
- },
- async function(res) {
- console.log('klarna authorize res ---- ',res);
- if(res.approved && res.authorization_token) { // 可以支付
- overlayLoading.stop();
- router.replace('/paymentresult/result?orderId=' + webOrderId + '&return_result=success');
- /*
- const resCallback = await createPaymentCallback({
- orderId: Number(webOrderId),
- gatewayOrderId: res.authorization_token,
- verifyToken: klarnaVerifyToken,
- status: "success",
- });
- // 跳转结果页
- overlayLoading.stop();
- if(!resCallback.error) {
- router.replace('/paymentresult/result?orderId=' + webOrderId + '&return_result=success');
- } else {
- // callback 失败,提示错误
- confirmDialog({
- title: "Somthing Wrong",
- content: resCallback.msg + " Please contact customer service." + " OrderId: "+ webOrderId,
- noCancel: true,
- }).then(() => {
- router.replace('/');
- });
-
- }
- */
- } else {
- // if(res.show_form) { // klarna可用,但是有报错
- // } else {
- // // klarna 不可用 跳转结果页
- // }
- overlayLoading.stop();
- router.replace('/paymentresult/result?orderId=' + webOrderId + '&return_result=failure');
- }
- }
- );
-
- }
- });
-
- } else {
- //创建订单出错了
- overlayLoading.stop();
- confirmDialog({
- title: "Warning",
- content: resRepay.msg + " Create order failed. Please try again.",
- noCancel: true,
- }).then(() => {});
- }
-
- };
- const changePaymentMethod = async (method: string) => {
- if(method === 'klarna' ||
- method === 'afterpay' ||
- method === 'clearpay' ||
- method === 'applepay'
- ) {
- loadSdk(method);
- }
- };
- const airwallexCardElementShow = selectedPaymentMethod === 'airwallex' && airwallexCardState.cardNumberReady && airwallexCardState.expiryReady && airwallexCardState.cvcReady;
- return (
- <>
- <form name="shipping method" className="w-full box-border px-4 mt-4">
- <div className="mt-3 w-full">
- {paymentMethodsData.error && <p className="text-red-500 text-ly-12">{paymentMethodsData.error.message}</p>}
- <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"
- src={item.icon || ''} alt={"Payment Method " + item.title}
- width={32}
- height={32}
- />
- </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 shipping method.',
- onChange: (event) => {
- changePaymentMethod(event.target.value);
- },
- })}
- />
- </label>
- { item.method === "airwallex" &&
- <div className="w-full mt-3"
- style={{display: airwallexCardElementShow ? 'block' : 'none'}}
- >
- <AirwallexCardInput />
- </div>
- }
- </li>)
- })
- }
- </ul>
-
- {paymentMethodError && <p className="text-red-500 text-ly-12">{paymentMethodError.message}</p>}
- </div>
- </form>
-
- <div className="w-full box-border px-4 mt-3">
- <p className="text-ly-12 text-[#666666] leading-4.75">
- By providing your information, you agree to Wiggins'sPrivacy Policyand Terms of Use.
- </p>
- <div className="mt-6 w-full">
- <div className={clsx("w-full",{
- "hidden": selectedPaymentMethod !== 'paypal_smart_button'
- })}>
- <PaypalButton
- createOrder={paypalRepayOrder}
- isRePay={true}
- />
- </div>
-
- {(selectedPaymentMethod === 'awxklarna' || selectedPaymentMethod === 'awxafterpay') &&
- <button className="flex items-center justify-center w-full h-12 bg-ly-green text-white rounded-3xl text-ly-16 font-bold"
- onClick={airwallexRepay}
- disabled={isPay}
- >
- Pay
- </button>
- }
- {selectedPaymentMethod === 'airwallex' &&
- <button className="flex items-center justify-center w-full h-12 bg-ly-green text-white rounded-3xl text-ly-16 font-bold"
- onClick={cardAirwallexRepay}
- disabled={isPay}
- >
- Pay
- </button>
- }
- {(selectedPaymentMethod === 'afterpay' || selectedPaymentMethod === 'clearpay') &&
- <button className="flex items-center justify-center w-full h-12 bg-ly-green text-white rounded-3xl text-ly-16 font-bold"
- onClick={afterpayPlaceOrder}
- >
- Place Order
- </button>
- }
- <div className={clsx("w-full",{
- "hidden": selectedPaymentMethod !== 'applepay'
- })}>
- <PaypalApplepayButton
- grandTotal={orderDetail.grandTotal}
- billingContact={billingContact}
- shippingContact={shippingContact}
- validateCheckout={validateCheckout}
- createOrder={applepayPlaceOrder}
- isRePay={true}
- />
- </div>
-
- <div className={clsx("w-full",{
- "hidden": selectedPaymentMethod !== 'klarna'
- })}>
- <KlarnaButton
- clickHandler={klarnaPlaceOrder}
- />
- </div>
- </div>
- </div>
- </>
- );
-
- }
|