PaypalButton.tsx 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251
  1. "use client";
  2. import {useRef,useState} from "react";
  3. import { useRouter } from 'next/navigation';
  4. import { usePlaceOrder } from "@/utils/hooks/usePlaceOrder";
  5. import {
  6. usePayPal,
  7. useEligibleMethods,
  8. INSTANCE_LOADING_STATE,
  9. usePayPalOneTimePaymentSession,
  10. usePayPalGuestPaymentSession,
  11. usePayLaterOneTimePaymentSession,
  12. type OnApproveDataOneTimePayments,
  13. type OnErrorData,
  14. type OnCompleteData,
  15. type OnCancelDataOneTimePayments,
  16. } from "@paypal/react-paypal-js/sdk-v6";
  17. import { overlayLoading } from "@/components/theme/ui/kernel/loading/api";
  18. import { confirmDialog } from "@/components/theme/ui/kernel/confirm/api";
  19. import { useStore } from "@utils/hooks/useConfig";
  20. export default function PaypalButton({
  21. createOrder,
  22. isRePay
  23. }:{
  24. createOrder: () => Promise<{orderId: string;webOrderId: string;error: boolean;msg: string;}>;
  25. isRePay?: boolean;
  26. }) {
  27. const router = useRouter();
  28. const {store} = useStore();
  29. const {createPaymentCallback} = usePlaceOrder();
  30. const [isPaying, setIsPaying] = useState(false);
  31. const gatewayOrderIdRef = useRef("");
  32. const webOrderIdRef = useRef("");
  33. // paypal sdk 是否加载完成
  34. const { loadingStatus } = usePayPal();
  35. // Fetch eligibility(资格) for one-time payment flow
  36. const {
  37. error: eligibilityError,
  38. eligiblePaymentMethods,
  39. isLoading: isEligibilityLoading,
  40. } = useEligibleMethods({
  41. payload: {
  42. currencyCode: store.currentCurrency,//"USD", // 需要根据用户选择的货币走
  43. paymentFlow: "ONE_TIME_PAYMENT",
  44. },
  45. });
  46. const isLoading = loadingStatus === INSTANCE_LOADING_STATE.PENDING;
  47. const isPayLaterEligible =
  48. !isEligibilityLoading && eligiblePaymentMethods?.isEligible("paylater");
  49. // const isCreditEligible =
  50. // !isEligibilityLoading && eligiblePaymentMethods?.isEligible("credit");
  51. const handlerPaypalClick = async (buttonHandlerClick: () => Promise<void | {
  52. redirectURL?: string | undefined;
  53. }>) => {
  54. setIsPaying(true);
  55. const res = await createOrder();
  56. if(!res.error) {
  57. gatewayOrderIdRef.current = res.orderId;
  58. webOrderIdRef.current = res.webOrderId;
  59. return buttonHandlerClick();
  60. } else {
  61. setIsPaying(false);
  62. }
  63. }
  64. // paypal支付回调配置
  65. const paypalOnHandlerConfig = {
  66. createOrder: async () => {
  67. const res = {
  68. orderId: gatewayOrderIdRef.current,
  69. webOrderId: webOrderIdRef.current
  70. };
  71. return res;
  72. },
  73. onApprove: async (data: OnApproveDataOneTimePayments) => {
  74. console.log("Payment approved:", data);
  75. const resCallback = await createPaymentCallback({
  76. orderId: Number(webOrderIdRef.current),
  77. gatewayOrderId: data.orderId || gatewayOrderIdRef.current,
  78. status: 'success',
  79. });
  80. overlayLoading.stop();
  81. if(!resCallback.error) {
  82. // 支付成功,跳转到成功落地页
  83. confirmDialog({
  84. title: "Payment Success",
  85. content: "Your payment was successful. Will redirect to success page.",
  86. noCancel: true,
  87. }).then(() => {
  88. router.replace('/paymentresult/result?orderId=' + webOrderIdRef.current + '&return_result=success');
  89. });
  90. } else {
  91. // callback 失败,提示错误
  92. confirmDialog({
  93. title: "Somthing Wrong",
  94. content: resCallback.msg + " Please contact customer service." + " OrderId: "+ webOrderIdRef.current,
  95. noCancel: true,
  96. }).then(() => {
  97. router.replace('/');
  98. });
  99. }
  100. },
  101. onCancel: async (data: OnCancelDataOneTimePayments) => {
  102. /**
  103. * {oederId: paypalOrderId}
  104. */
  105. console.log("Payment cancelled:", data);
  106. await createPaymentCallback({
  107. orderId: Number(webOrderIdRef.current),
  108. gatewayOrderId: data.orderId || gatewayOrderIdRef.current,
  109. status: 'cancel',
  110. });
  111. if(!isRePay) {
  112. overlayLoading.stop();
  113. router.replace('/paymentresult/result?orderId=' + webOrderIdRef.current + '&return_result=cancel');
  114. } else {
  115. // 登录用户在二次支付页面
  116. confirmDialog({
  117. title: "Payment cancelled",
  118. content: "You canceled the payment.",
  119. noCancel: true,
  120. }).then(() => { });
  121. }
  122. },
  123. onError: async (data: OnErrorData) => {
  124. console.error("Payment error:", data);
  125. /**
  126. * code: string;
  127. name: string;
  128. isRecoverable: boolean;
  129. */
  130. await createPaymentCallback({
  131. orderId: Number(webOrderIdRef.current),
  132. gatewayOrderId: gatewayOrderIdRef.current,
  133. status: 'failure',
  134. });
  135. if(!isRePay) {
  136. overlayLoading.stop();
  137. router.replace('/paymentresult/result?orderId=' + webOrderIdRef.current + '&return_result=failure');
  138. } else {
  139. // 登录用户 二次支付页面
  140. confirmDialog({
  141. title: "Payment Failed",
  142. content: data.message,
  143. noCancel: true,
  144. }).then(() => { });
  145. }
  146. },
  147. onComplete: (data: OnCompleteData) => {
  148. overlayLoading.stop();
  149. setIsPaying(false);
  150. console.log("Payment session completed");
  151. console.log("On Complete data:", data);
  152. },
  153. // onShippingAddressChange: (data) => {},
  154. // onShippingOptionsChange: (data) => {
  155. // }
  156. };
  157. const {
  158. // isPending: paypalButtonPending,
  159. error: paypalButtonError,
  160. handleClick: paypalButtonHandleClick,
  161. } = usePayPalOneTimePaymentSession({
  162. presentationMode: "auto",
  163. ...paypalOnHandlerConfig
  164. });
  165. const {
  166. buttonRef: guestButtonRef,
  167. // isPending: guestButtonPending,
  168. error: guestButtonError,
  169. handleClick: guestButtonHandleClick,
  170. } = usePayPalGuestPaymentSession(paypalOnHandlerConfig);
  171. const payLaterDetails = eligiblePaymentMethods?.getDetails("paylater");
  172. const {
  173. // isPending: paylaterButtonPending,
  174. // error: paylaterButtonError,
  175. handleClick: paylaterButtonHandleClick,
  176. } = usePayLaterOneTimePaymentSession({
  177. presentationMode: "auto",
  178. ...paypalOnHandlerConfig
  179. });
  180. return (
  181. <div className="w-full">
  182. {
  183. isLoading ? (
  184. <div className="test-ly-12 font-medium text-center">
  185. Loading paypal payment methods...
  186. </div>
  187. ) : eligibilityError ? (
  188. <div className="test-ly-12 font-medium text-center text-ly-errorcolor">
  189. Failed to load paypal payment options. Please refresh the page.
  190. </div>
  191. ) : (
  192. <>
  193. {paypalButtonError ?
  194. <p className="text-ly-errorcolor text-ly-12">Error: {paypalButtonError.message}</p>
  195. :
  196. <paypal-button onClick={() => {
  197. return handlerPaypalClick(paypalButtonHandleClick);
  198. }} type="pay" className="w-full" disabled={isPaying} />
  199. }
  200. {guestButtonError ?
  201. <p className="text-ly-errorcolor text-ly-12">Error: {guestButtonError.message}</p>
  202. :
  203. <paypal-basic-card-container className="w-full mt-3">
  204. <paypal-basic-card-button disabled={isPaying} ref={guestButtonRef} onClick={() => handlerPaypalClick(guestButtonHandleClick)} />
  205. </paypal-basic-card-container>
  206. }
  207. {isPayLaterEligible &&
  208. <paypal-pay-later-button className="w-full mt-3" disabled={isPaying}
  209. onClick={() => handlerPaypalClick(paylaterButtonHandleClick)}
  210. countryCode={payLaterDetails?.countryCode}
  211. productCode={payLaterDetails?.productCode}
  212. />
  213. }
  214. </>
  215. )
  216. }
  217. </div>
  218. );
  219. }