useAction.tsx 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
  1. import { useCallback } from 'react';
  2. import { Trans, useTranslation } from 'react-i18next';
  3. import LoginForm from '@/components/LoginForm';
  4. import { message } from '@/config/request/antdAppInstance';
  5. import { PayUrlShowType } from '@/defines';
  6. import { useAppUrls } from '@/hooks/useAppUrls';
  7. import { dialogModel } from '@/models/dialogModel';
  8. import { fetchGetUserConfig, fetchPayOrderCreate } from '@/services/config';
  9. import { getToken } from '@/utils/authUtils';
  10. import { currentUnixTimestamp } from '@/utils/timeUtils';
  11. import { PayQrContent } from './components/OrderSummary/PayQrContent';
  12. import { PayWaitingContent } from './components/OrderSummary/PayWaitingContent';
  13. import type { Plan } from './useService';
  14. const ORDER_TYPE_PLAN = 1;
  15. type PayOrder = { orderId: string; payUrl: string; showType: PayUrlShowType };
  16. export interface UseActionReturn {
  17. handlePayNow: (plan: Plan, payMethod: string) => void;
  18. }
  19. function useLoginDialog() {
  20. const { t } = useTranslation();
  21. const { openDialog, closeDialog } = dialogModel.useModel();
  22. const { deeplinkUrl, downloadUrlByPlatform } = useAppUrls();
  23. return useCallback(() => {
  24. const id = openDialog({
  25. title: t('pages.pricing.payFlow.loginTitle'),
  26. content: (
  27. <div className="flex flex-col gap-2">
  28. <p className="text-white/80 text-sm mb-2">
  29. <Trans
  30. i18nKey="pages.pricing.payFlow.loginPrompt"
  31. components={{
  32. linkText: (() => {
  33. const Wrap = ({ children }: { children?: React.ReactNode }) =>
  34. deeplinkUrl ? (
  35. <a
  36. href={deeplinkUrl}
  37. className="text-[#0EA5E9] hover:underline"
  38. target="_blank"
  39. rel="noopener noreferrer"
  40. >
  41. {children}
  42. </a>
  43. ) : (
  44. <span>{children}</span>
  45. );
  46. return <Wrap />;
  47. })(),
  48. downloadLink: (() => {
  49. const Wrap = ({ children }: { children?: React.ReactNode }) =>
  50. downloadUrlByPlatform ? (
  51. <a
  52. href={downloadUrlByPlatform}
  53. className="text-[#0EA5E9] hover:underline"
  54. target="_blank"
  55. rel="noopener noreferrer"
  56. >
  57. {children}
  58. </a>
  59. ) : (
  60. <span>{children}</span>
  61. );
  62. return <Wrap />;
  63. })(),
  64. }}
  65. />
  66. </p>
  67. <LoginForm onSuccess={() => closeDialog(id)} />
  68. </div>
  69. ),
  70. maskClosable: false,
  71. closeable: true,
  72. });
  73. }, [t, openDialog, closeDialog, deeplinkUrl, downloadUrlByPlatform]);
  74. }
  75. function useQrPayDialog() {
  76. const { t } = useTranslation();
  77. const { openDialog, closeDialog } = dialogModel.useModel();
  78. const onPaymentSuccess = useCallback(() => {
  79. return fetchGetUserConfig({}).then(() => {
  80. message.success(t('pages.pricing.payFlow.planPurchaseSuccess'));
  81. });
  82. }, [t]);
  83. return useCallback(
  84. (order: PayOrder) => {
  85. const qrDialogId = openDialog({
  86. title: t('pages.pricing.payFlow.waitingTitle'),
  87. content: (
  88. <PayQrContent
  89. orderId={order.orderId}
  90. payUrl={order.payUrl}
  91. onClose={() => closeDialog(qrDialogId)}
  92. onPaymentSuccess={onPaymentSuccess}
  93. />
  94. ),
  95. buttons: [
  96. {
  97. label: t('pages.pricing.payFlow.closeWaiting'),
  98. variant: 'secondary' as const,
  99. onClick: (_e: React.MouseEvent<HTMLButtonElement>, dialogId: string) => {
  100. closeDialog(dialogId);
  101. },
  102. },
  103. ],
  104. maskClosable: false,
  105. closeable: true,
  106. });
  107. },
  108. [t, openDialog, closeDialog, onPaymentSuccess]
  109. );
  110. }
  111. function useJumpToPayDialog() {
  112. const { t } = useTranslation();
  113. const { openDialog, closeDialog } = dialogModel.useModel();
  114. const onPaymentSuccess = useCallback(() => {
  115. return fetchGetUserConfig({}).then(() => {
  116. message.success(t('pages.pricing.payFlow.planPurchaseSuccess'));
  117. });
  118. }, [t]);
  119. return useCallback(
  120. (order: PayOrder) => {
  121. openDialog({
  122. title: t('pages.pricing.payFlow.goToPayTitle'),
  123. content: (
  124. <p className="text-white/90 text-sm leading-[1.43]">
  125. {t('pages.pricing.payFlow.goToPayDesc')}
  126. </p>
  127. ),
  128. buttons: [
  129. {
  130. label: t('pages.pricing.payFlow.goToPayButton'),
  131. variant: 'primary' as const,
  132. onClick: (_e: React.MouseEvent<HTMLButtonElement>, dialogId: string) => {
  133. window.open(order.payUrl, '_blank');
  134. closeDialog(dialogId);
  135. const waitId = openDialog({
  136. title: t('pages.pricing.payFlow.waitingTitle'),
  137. content: (
  138. <PayWaitingContent
  139. orderId={order.orderId}
  140. onClose={() => closeDialog(waitId)}
  141. onPaymentSuccess={onPaymentSuccess}
  142. />
  143. ),
  144. buttons: [
  145. {
  146. label: t('pages.pricing.payFlow.closeWaiting'),
  147. variant: 'secondary' as const,
  148. onClick: (
  149. _e: React.MouseEvent<HTMLButtonElement>,
  150. d: string
  151. ) => {
  152. closeDialog(d);
  153. },
  154. },
  155. ],
  156. maskClosable: false,
  157. });
  158. },
  159. },
  160. ],
  161. maskClosable: false,
  162. closeable: true,
  163. });
  164. },
  165. [t, openDialog, closeDialog, onPaymentSuccess]
  166. );
  167. }
  168. export function useAction(): UseActionReturn {
  169. const openLoginDialog = useLoginDialog();
  170. const openQrPayDialog = useQrPayDialog();
  171. const openJumpToPayDialog = useJumpToPayDialog();
  172. const handlePayNow = useCallback(
  173. (selectedPlan: Plan, selectedPayMethod: string) => {
  174. const token = getToken();
  175. const expired =
  176. !token?.accessExpires || (token.accessExpires ?? 0) - currentUnixTimestamp() <= 0;
  177. if (!token?.accessToken || expired) {
  178. // 如果用户未登录,则打开登录对话框
  179. openLoginDialog();
  180. return;
  181. }
  182. // 创建订单
  183. fetchPayOrderCreate({
  184. orderType: ORDER_TYPE_PLAN,
  185. payType: selectedPayMethod,
  186. channelItemId: selectedPlan.id,
  187. })
  188. .then((res) => {
  189. const order = res?.data?.userPayOrder;
  190. if (!order?.payUrl || !order?.orderId) return;
  191. if (order.showType === PayUrlShowType.SHOW_QR_CODE) {
  192. // 如果订单支付地址的显示类型为二维码,则打开二维码支付对话框
  193. openQrPayDialog(order);
  194. return;
  195. }
  196. // 如果订单支付地址的显示类型为跳转,则跳转到支付地址并打开等待支付对话框
  197. openJumpToPayDialog(order);
  198. })
  199. .catch(() => {});
  200. },
  201. [openLoginDialog, openQrPayDialog, openJumpToPayDialog]
  202. );
  203. return {
  204. handlePayNow,
  205. };
  206. }