PaypalButton.tsx 8.8 KB

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