You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
612 lines
22 KiB
612 lines
22 KiB
/**
|
|
* 订阅 / 积分支付弹窗
|
|
*
|
|
* 微信:同意后下单拉二维码;拿到有效二维码后移除蒙层并轮询。
|
|
* 支付宝:无二维码,蒙层始终存在;每次点「同意并支付」才下单拿链接并跳转,再轮询。
|
|
*/
|
|
import { Message, Modal } from "@arco-design/web-react";
|
|
import { useTranslation } from "react-i18next";
|
|
import { lazy, Suspense, useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
import { getToken } from "@/utils/auth";
|
|
import { useUserStore } from "@/store/useUserStore";
|
|
import { checkGatewayPayment, checkGatewayWxPayment, checkPointsPayment, checkPointsWxPayment, createPointsOrder, createPointsOrderByWxPay, createSubscriptionOrder, createSubscriptionOrderByWxPay } from "@/api/api";
|
|
import type { PaymentOrderData, PaymentStatusData, ProductPlan } from "@/api/api";
|
|
import { openLatestPrivacyPolicyLink, PRIVACY_POLICY_KEY_FUTURE_CREATOR, PRIVACY_POLICY_KEY_PAYMENT_TERMS } from "@/utils/openLatestPrivacyPolicyLink";
|
|
import "./index.scss";
|
|
|
|
type PayType = "subscribe" | "points";
|
|
type PaymentMethod = "WXPAY" | "ALIPAY";
|
|
|
|
type PaymentInfo = { type: "subscribe"; price: number; subscriptionId?: number } | { type: "points"; price: number; packageId?: number };
|
|
|
|
type PollContext = { paymentMethod: PaymentMethod; payType: PayType };
|
|
|
|
const QrCodeSvg = lazy(() => import("qrcode.react").then((m) => ({ default: m.QRCodeSVG })));
|
|
|
|
export interface PaySubscribeProps {
|
|
onClose: () => void;
|
|
type?: PayType;
|
|
plan?: ProductPlan;
|
|
afterPaySuccess?: () => void;
|
|
}
|
|
|
|
const PAYMENT_POLL_MAX_MS = 5 * 60 * 1000;
|
|
const PAYMENT_POLL_INTERVAL_MS = 8000;
|
|
const PAY_RESULT_MODAL_WRAP = "pay_subscribe_result_modal";
|
|
|
|
const sleep = (ms: number) => new Promise<void>((r) => setTimeout(r, ms));
|
|
|
|
type PayNoticeKind = "success" | "warning" | "error";
|
|
|
|
function openPayNoticeModal(
|
|
kind: PayNoticeKind,
|
|
options: {
|
|
title: string;
|
|
content: string;
|
|
okText: string;
|
|
onOk?: () => void;
|
|
},
|
|
): void {
|
|
const payload = {
|
|
title: options.title,
|
|
content: options.content,
|
|
okText: options.okText,
|
|
wrapClassName: PAY_RESULT_MODAL_WRAP,
|
|
alignCenter: true,
|
|
maskClosable: false,
|
|
escToExit: true,
|
|
simple: true,
|
|
okButtonProps: { type: "primary" as const, shape: "round" as const },
|
|
onOk: () => options.onOk?.(),
|
|
};
|
|
if (kind === "success") Modal.success(payload);
|
|
else if (kind === "warning") Modal.warning(payload);
|
|
else Modal.error(payload);
|
|
}
|
|
|
|
function buildPaymentReturnUrl(payType: PayType, paymentMethod: PaymentMethod): string {
|
|
if (typeof window === "undefined") return "";
|
|
const u = new URL(window.location.origin + window.location.pathname);
|
|
u.searchParams.set("type", payType);
|
|
u.searchParams.set("paymentMethod", paymentMethod);
|
|
return u.toString();
|
|
}
|
|
|
|
function extractTradeStatus(statusRes: PaymentStatusData | undefined, method: PaymentMethod): string {
|
|
if (method === "ALIPAY") return statusRes?.trade_status ?? statusRes?.tradeStatus ?? "";
|
|
return statusRes?.data?.trade_state ?? statusRes?.data?.tradeState ?? statusRes?.trade_state ?? statusRes?.tradeState ?? "";
|
|
}
|
|
|
|
function isWaitingStatus(tradeStatus: string, method: PaymentMethod): boolean {
|
|
if (method === "ALIPAY") return tradeStatus === "WAIT_BUYER_PAY";
|
|
return tradeStatus === "NOTPAY" || tradeStatus === "USERPAYING";
|
|
}
|
|
|
|
function isSuccessStatus(tradeStatus: string, method: PaymentMethod): boolean {
|
|
if (method === "ALIPAY") return tradeStatus === "TRADE_SUCCESS" || tradeStatus === "TRADE_FINISHED";
|
|
return tradeStatus === "SUCCESS";
|
|
}
|
|
|
|
function isFailedStatus(tradeStatus: string, method: PaymentMethod): boolean {
|
|
if (method === "ALIPAY") return tradeStatus === "TRADE_CLOSED" || tradeStatus === "TRADE_REFOUND";
|
|
return tradeStatus === "REFUND" || tradeStatus === "CLOSED" || tradeStatus === "PAYERROR";
|
|
}
|
|
|
|
function fetchPaymentStatus(tradeNo: string, method: PaymentMethod, payType: PayType) {
|
|
if (method === "ALIPAY") {
|
|
return payType === "subscribe" ? checkGatewayPayment({ tradeNo }) : checkPointsPayment({ tradeNo });
|
|
}
|
|
return payType === "subscribe" ? checkGatewayWxPayment({ tradeNo }) : checkPointsWxPayment({ tradeNo });
|
|
}
|
|
|
|
export default function PaySubscribe({ onClose, type, plan, afterPaySuccess }: PaySubscribeProps) {
|
|
const { t } = useTranslation();
|
|
const payType: PayType = type ?? "subscribe";
|
|
|
|
const user = useUserStore((s) => s.user);
|
|
const setUser = useUserStore((s) => s.setUser);
|
|
|
|
const afterPaySuccessRef = useRef(afterPaySuccess);
|
|
useEffect(() => {
|
|
afterPaySuccessRef.current = afterPaySuccess;
|
|
}, [afterPaySuccess]);
|
|
|
|
const [paymentMethod, setPaymentMethod] = useState<PaymentMethod>("WXPAY");
|
|
/** 微信侧是否已点过「同意并支付」(本会话内切回微信不再出蒙层) */
|
|
const [wxAgreed, setWxAgreed] = useState(false);
|
|
const [submitting, setSubmitting] = useState(false);
|
|
const [codeUrl, setCodeUrl] = useState("");
|
|
|
|
const wxOrderRef = useRef<PaymentOrderData | null>(null);
|
|
const mountedRef = useRef(true);
|
|
const requestSeqRef = useRef(0);
|
|
const activePollSeqRef = useRef(0);
|
|
const submittingRef = useRef(false);
|
|
const pollAbortRef = useRef(false);
|
|
const pollStartTimeRef = useRef(0);
|
|
const pollTimerRef = useRef<number | null>(null);
|
|
|
|
const paymentInfo = useMemo<PaymentInfo>(() => {
|
|
if (payType === "subscribe") {
|
|
return {
|
|
type: "subscribe" as const,
|
|
price: Number(plan?.price ?? 0) / 100,
|
|
subscriptionId: plan?.id == null ? undefined : Number(plan.id),
|
|
};
|
|
}
|
|
return {
|
|
type: "points" as const,
|
|
price: Number(plan?.price_amount ?? 0),
|
|
packageId: plan?.id == null ? undefined : Number(plan.id),
|
|
};
|
|
}, [plan?.id, plan?.price, plan?.price_amount, payType]);
|
|
|
|
const planId = useMemo(() => {
|
|
if (paymentInfo.type === "subscribe") return paymentInfo.subscriptionId;
|
|
return paymentInfo.packageId;
|
|
}, [paymentInfo]);
|
|
|
|
const planTitle = useMemo(() => {
|
|
if (payType === "subscribe") return (plan?.title ?? "").trim();
|
|
return (plan?.name ?? t("subscribe.points_package")).trim();
|
|
}, [payType, plan?.name, plan?.title, t]);
|
|
const originalPrice = Number(plan?.original_price_amount ?? 0);
|
|
const discountInfo = plan?.custom_info?.discount_info;
|
|
const isFutureCreatorPlan = Number(plan?.level ?? 0) === 4;
|
|
|
|
/** 支付宝始终蒙层;微信仅首次同意前蒙层 */
|
|
const showAgreementOverlay = paymentMethod === "ALIPAY" || !wxAgreed;
|
|
|
|
const requireLogin = useCallback((): boolean => {
|
|
if (!getToken() || !user) {
|
|
Message.warning(t("auth.please_login_first"));
|
|
return false;
|
|
}
|
|
return true;
|
|
}, [t, user]);
|
|
|
|
const stopPolling = useCallback(() => {
|
|
pollAbortRef.current = true;
|
|
activePollSeqRef.current += 1;
|
|
if (pollTimerRef.current != null) {
|
|
window.clearTimeout(pollTimerRef.current);
|
|
pollTimerRef.current = null;
|
|
}
|
|
}, []);
|
|
const isPollingActive = useCallback((pollSeq: number) => mountedRef.current && !pollAbortRef.current && activePollSeqRef.current === pollSeq, []);
|
|
const setSubmittingState = useCallback((nextSubmitting: boolean) => {
|
|
submittingRef.current = nextSubmitting;
|
|
if (mountedRef.current) {
|
|
setSubmitting(nextSubmitting);
|
|
}
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
return () => {
|
|
mountedRef.current = false;
|
|
requestSeqRef.current += 1;
|
|
stopPolling();
|
|
submittingRef.current = false;
|
|
};
|
|
}, [stopPolling]);
|
|
|
|
const safeClose = useCallback(() => {
|
|
onClose();
|
|
}, [onClose]);
|
|
|
|
const nextPaymentRequestSeq = useCallback(() => {
|
|
requestSeqRef.current += 1;
|
|
return requestSeqRef.current;
|
|
}, []);
|
|
|
|
const isPaymentRequestActive = useCallback((requestSeq: number) => mountedRef.current && requestSeqRef.current === requestSeq, []);
|
|
|
|
// 轮询支付状态
|
|
const startPaymentPoll = useCallback(
|
|
(orderRes: PaymentOrderData, ctx: PollContext) => {
|
|
const tradeNo = orderRes.trade_no ?? orderRes.tradeNo;
|
|
if (!tradeNo) return;
|
|
|
|
stopPolling();
|
|
pollAbortRef.current = false;
|
|
pollStartTimeRef.current = Date.now();
|
|
const pollSeq = activePollSeqRef.current + 1;
|
|
activePollSeqRef.current = pollSeq;
|
|
|
|
void (async () => {
|
|
const { paymentMethod: method, payType: ctxPayType } = ctx;
|
|
|
|
for (;;) {
|
|
if (!isPollingActive(pollSeq)) return;
|
|
|
|
if (Date.now() - pollStartTimeRef.current >= PAYMENT_POLL_MAX_MS) {
|
|
openPayNoticeModal("warning", {
|
|
title: t("subscribe.pay_notice_timeout_title"),
|
|
content: t("subscribe.pay_result_timeout"),
|
|
okText: t("button.okay"),
|
|
onOk: safeClose,
|
|
});
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const { data: statusRes } = await fetchPaymentStatus(tradeNo, method, ctxPayType);
|
|
if (!isPollingActive(pollSeq)) return;
|
|
|
|
const tradeStatus = extractTradeStatus(statusRes, method);
|
|
if (!tradeStatus) {
|
|
await sleep(PAYMENT_POLL_INTERVAL_MS);
|
|
continue;
|
|
}
|
|
|
|
if (isWaitingStatus(tradeStatus, method)) {
|
|
await sleep(PAYMENT_POLL_INTERVAL_MS);
|
|
continue;
|
|
}
|
|
|
|
if (isSuccessStatus(tradeStatus, method)) {
|
|
void setUser();
|
|
openPayNoticeModal("success", {
|
|
title: t("subscribe.pay_success"),
|
|
content: t("subscribe.pay_success_detail"),
|
|
okText: t("button.okay"),
|
|
onOk: () => {
|
|
afterPaySuccessRef.current?.();
|
|
safeClose();
|
|
},
|
|
});
|
|
return;
|
|
}
|
|
|
|
if (isFailedStatus(tradeStatus, method)) {
|
|
openPayNoticeModal("error", {
|
|
title: t("subscribe.pay_failed"),
|
|
content: t("subscribe.pay_failed_detail"),
|
|
okText: t("button.okay"),
|
|
onOk: safeClose,
|
|
});
|
|
return;
|
|
}
|
|
|
|
await sleep(PAYMENT_POLL_INTERVAL_MS);
|
|
} catch {
|
|
if (!isPollingActive(pollSeq)) return;
|
|
await sleep(PAYMENT_POLL_INTERVAL_MS);
|
|
}
|
|
}
|
|
})();
|
|
},
|
|
[isPollingActive, safeClose, setUser, stopPolling, t],
|
|
);
|
|
|
|
// 调度轮询
|
|
const schedulePoll = useCallback(
|
|
(orderRes: PaymentOrderData, method: PaymentMethod) => {
|
|
if (!mountedRef.current) return;
|
|
stopPolling();
|
|
const ctx: PollContext = { paymentMethod: method, payType };
|
|
pollTimerRef.current = window.setTimeout(() => {
|
|
if (!mountedRef.current) return;
|
|
startPaymentPoll(orderRes, ctx);
|
|
}, PAYMENT_POLL_INTERVAL_MS);
|
|
},
|
|
[payType, startPaymentPoll, stopPolling],
|
|
);
|
|
|
|
// 解析套餐ID
|
|
const resolvePlanIds = useCallback(() => {
|
|
const subscriptionId = paymentInfo.type === "subscribe" ? paymentInfo.subscriptionId : undefined;
|
|
const packageId = paymentInfo.type === "points" ? paymentInfo.packageId : undefined;
|
|
const id = payType === "subscribe" ? subscriptionId : packageId;
|
|
return { subscriptionId, packageId, id };
|
|
}, [paymentInfo, payType]);
|
|
|
|
// 断言套餐并登录
|
|
const assertPlanAndLogin = useCallback((): boolean => {
|
|
const { id } = resolvePlanIds();
|
|
if (!id) {
|
|
openPayNoticeModal("warning", {
|
|
title: t("subscribe.pay_notice_missing_plan_title"),
|
|
content: t("subscribe.missing_plan_info"),
|
|
okText: t("button.okay"),
|
|
onOk: safeClose,
|
|
});
|
|
return false;
|
|
}
|
|
return requireLogin();
|
|
}, [requireLogin, resolvePlanIds, safeClose, t]);
|
|
|
|
/** 微信下单:仅拉 code_url,不跳转 */
|
|
const fetchWxOrder = useCallback(
|
|
async () => {
|
|
if (submittingRef.current) return null;
|
|
|
|
const requestSeq = nextPaymentRequestSeq();
|
|
stopPolling();
|
|
const { id, subscriptionId, packageId } = resolvePlanIds();
|
|
if (!id) {
|
|
openPayNoticeModal("warning", {
|
|
title: t("subscribe.pay_notice_missing_plan_title"),
|
|
content: t("subscribe.missing_plan_info"),
|
|
okText: t("button.okay"),
|
|
onOk: safeClose,
|
|
});
|
|
return null;
|
|
}
|
|
|
|
if (!requireLogin()) {
|
|
return null;
|
|
}
|
|
setSubmittingState(true);
|
|
const returnUrl = buildPaymentReturnUrl(payType, "WXPAY");
|
|
|
|
try {
|
|
if (payType === "subscribe") {
|
|
const { data: res } = await createSubscriptionOrderByWxPay({
|
|
returnUrl,
|
|
subscriptionId,
|
|
});
|
|
if (!isPaymentRequestActive(requestSeq)) return null;
|
|
if (!res) return null;
|
|
if (!res.code_url) {
|
|
openPayNoticeModal("error", {
|
|
title: t("subscribe.pay_notice_start_failed_title"),
|
|
content: t("subscribe.pay_start_failed"),
|
|
okText: t("button.okay"),
|
|
});
|
|
return null;
|
|
}
|
|
wxOrderRef.current = res;
|
|
setCodeUrl(res.code_url);
|
|
return res;
|
|
}
|
|
const { data: res } = await createPointsOrderByWxPay({ returnUrl, packageId });
|
|
if (!isPaymentRequestActive(requestSeq)) return null;
|
|
if (!res) return null;
|
|
if (!res.code_url) {
|
|
openPayNoticeModal("error", {
|
|
title: t("subscribe.pay_notice_start_failed_title"),
|
|
content: t("subscribe.pay_start_failed"),
|
|
okText: t("button.okay"),
|
|
});
|
|
return null;
|
|
}
|
|
wxOrderRef.current = res;
|
|
setCodeUrl(res.code_url);
|
|
return res;
|
|
} catch {
|
|
if (isPaymentRequestActive(requestSeq)) {
|
|
openPayNoticeModal("error", {
|
|
title: t("subscribe.pay_notice_start_failed_title"),
|
|
content: t("subscribe.pay_start_failed"),
|
|
okText: t("button.okay"),
|
|
});
|
|
}
|
|
return null;
|
|
} finally {
|
|
if (isPaymentRequestActive(requestSeq)) {
|
|
setSubmittingState(false);
|
|
}
|
|
}
|
|
},
|
|
[isPaymentRequestActive, nextPaymentRequestSeq, payType, requireLogin, resolvePlanIds, safeClose, setSubmittingState, stopPolling, t],
|
|
);
|
|
|
|
/** 支付宝下单:同意后才调用,拿链接并跳转 */
|
|
const fetchAlipayAndRedirect = useCallback(async () => {
|
|
if (submittingRef.current) return null;
|
|
if (!assertPlanAndLogin()) return null;
|
|
|
|
const requestSeq = nextPaymentRequestSeq();
|
|
stopPolling();
|
|
const { subscriptionId, packageId } = resolvePlanIds();
|
|
setSubmittingState(true);
|
|
const returnUrl = buildPaymentReturnUrl(payType, "ALIPAY");
|
|
|
|
try {
|
|
const res = payType === "subscribe" ? (await createSubscriptionOrder({ returnUrl, subscriptionId })).data : (await createPointsOrder({ returnUrl, packageId })).data;
|
|
if (!isPaymentRequestActive(requestSeq)) return null;
|
|
|
|
if (res?.url) {
|
|
schedulePoll(res, "ALIPAY");
|
|
location.href = res.url;
|
|
return res;
|
|
}
|
|
openPayNoticeModal("error", {
|
|
title: t("subscribe.pay_notice_start_failed_title"),
|
|
content: t("subscribe.pay_start_failed"),
|
|
okText: t("button.okay"),
|
|
});
|
|
return null;
|
|
} catch {
|
|
if (isPaymentRequestActive(requestSeq)) {
|
|
openPayNoticeModal("error", {
|
|
title: t("subscribe.pay_notice_start_failed_title"),
|
|
content: t("subscribe.pay_start_failed"),
|
|
okText: t("button.okay"),
|
|
});
|
|
}
|
|
return null;
|
|
} finally {
|
|
if (isPaymentRequestActive(requestSeq)) {
|
|
setSubmittingState(false);
|
|
}
|
|
}
|
|
}, [assertPlanAndLogin, isPaymentRequestActive, nextPaymentRequestSeq, payType, resolvePlanIds, schedulePoll, setSubmittingState, stopPolling, t]);
|
|
|
|
const resumeWxPolling = useCallback(() => {
|
|
if (wxOrderRef.current) {
|
|
schedulePoll(wxOrderRef.current, "WXPAY");
|
|
}
|
|
}, [schedulePoll]);
|
|
|
|
/** 换套餐:重置支付态,不自动创建订单 */
|
|
useEffect(() => {
|
|
nextPaymentRequestSeq();
|
|
stopPolling();
|
|
setSubmittingState(false);
|
|
setWxAgreed(false);
|
|
setPaymentMethod("WXPAY");
|
|
wxOrderRef.current = null;
|
|
setCodeUrl("");
|
|
}, [nextPaymentRequestSeq, planId, setSubmittingState, stopPolling]);
|
|
|
|
const handlePaymentMethodChange = useCallback(
|
|
(method: PaymentMethod) => {
|
|
if (method === paymentMethod) return;
|
|
if (submittingRef.current) return;
|
|
|
|
nextPaymentRequestSeq();
|
|
stopPolling();
|
|
setPaymentMethod(method);
|
|
setWxAgreed(false);
|
|
setCodeUrl("");
|
|
wxOrderRef.current = null;
|
|
},
|
|
[nextPaymentRequestSeq, paymentMethod, stopPolling],
|
|
);
|
|
|
|
const handleAgreeAndPay = useCallback(async () => {
|
|
if (submittingRef.current) return;
|
|
|
|
if (paymentMethod === "ALIPAY") {
|
|
await fetchAlipayAndRedirect();
|
|
return;
|
|
}
|
|
|
|
const order = await fetchWxOrder();
|
|
if (order?.code_url) {
|
|
setWxAgreed(true);
|
|
resumeWxPolling();
|
|
}
|
|
}, [fetchAlipayAndRedirect, fetchWxOrder, paymentMethod, resumeWxPolling]);
|
|
|
|
const wxLogo = new URL("@/assets/images/logo_wxpay.png", import.meta.url).href;
|
|
const alipayLogo = new URL("@/assets/images/logo_alipay.png", import.meta.url).href;
|
|
const loadingImg = new URL("@/assets/images/wxgh_loading.png", import.meta.url).href;
|
|
|
|
return (
|
|
<div className="pay_subscribe_modal">
|
|
{planTitle ? <h2 className="pay_subscribe_modal__title">{planTitle}</h2> : null}
|
|
|
|
<div className="pay_subscribe_modal__methods">
|
|
<button
|
|
type="button"
|
|
className={["pay_subscribe_modal__method", paymentMethod === "WXPAY" ? "is_active" : ""].filter(Boolean).join(" ")}
|
|
disabled={submitting}
|
|
onClick={() => {
|
|
handlePaymentMethodChange("WXPAY");
|
|
}}
|
|
>
|
|
<img src={wxLogo} alt="" className="pay_subscribe_modal__method-icon" />
|
|
<span>微信</span>
|
|
</button>
|
|
<button
|
|
type="button"
|
|
className={["pay_subscribe_modal__method", paymentMethod === "ALIPAY" ? "is_active" : ""].filter(Boolean).join(" ")}
|
|
disabled={submitting}
|
|
onClick={() => {
|
|
handlePaymentMethodChange("ALIPAY");
|
|
}}
|
|
>
|
|
<img src={alipayLogo} alt="" className="pay_subscribe_modal__method-icon" />
|
|
<span>支付宝</span>
|
|
</button>
|
|
</div>
|
|
|
|
<div className="pay_subscribe_modal__price-row">
|
|
<span className="pay_subscribe_modal__price-current">¥{paymentInfo.price}</span>
|
|
{payType === "subscribe" && originalPrice > paymentInfo.price ? <span className="pay_subscribe_modal__price-original">¥{originalPrice}</span> : null}
|
|
{discountInfo ? <span className="pay_subscribe_modal__price-badge">{discountInfo}</span> : null}
|
|
</div>
|
|
|
|
<div className="pay_subscribe_modal__qrcode-box">
|
|
{paymentMethod === "WXPAY" ? (
|
|
<div className="pay_subscribe_modal__qrcode-inner">
|
|
{codeUrl ? (
|
|
<Suspense fallback={<img src={loadingImg} alt="" className="pay_subscribe_modal__loading" />}>
|
|
<QrCodeSvg value={codeUrl} size={207} level="M" />
|
|
</Suspense>
|
|
) : (
|
|
<img src={loadingImg} alt="" className="pay_subscribe_modal__loading" />
|
|
)}
|
|
</div>
|
|
) : (
|
|
<div className="pay_subscribe_modal__qrcode-inner is_alipay" aria-hidden />
|
|
)}
|
|
|
|
{showAgreementOverlay ? (
|
|
<div className="pay_subscribe_modal__overlay">
|
|
<p className="pay_subscribe_modal__overlay-text">
|
|
{t("subscribe.read_before_pay")}
|
|
<br />
|
|
|
|
{isFutureCreatorPlan ? (
|
|
<a
|
|
href="#"
|
|
className="pay_subscribe_modal__overlay-link"
|
|
onClick={(e) => {
|
|
e.preventDefault();
|
|
void openLatestPrivacyPolicyLink(PRIVACY_POLICY_KEY_FUTURE_CREATOR);
|
|
}}
|
|
>
|
|
{t("subscribe.future_creator_payment_terms")}
|
|
</a>
|
|
) : (
|
|
<a
|
|
href="#"
|
|
className="pay_subscribe_modal__overlay-link"
|
|
onClick={(e) => {
|
|
e.preventDefault();
|
|
void openLatestPrivacyPolicyLink(PRIVACY_POLICY_KEY_PAYMENT_TERMS);
|
|
}}
|
|
>
|
|
{t("subscribe.payment_terms")}
|
|
</a>
|
|
)}
|
|
</p>
|
|
<button type="button" className="pay_subscribe_modal__agree-btn" disabled={submitting} onClick={() => void handleAgreeAndPay()}>
|
|
{t("subscribe.agree_and_pay")}
|
|
</button>
|
|
</div>
|
|
) : null}
|
|
</div>
|
|
|
|
{paymentMethod === "WXPAY" ? (
|
|
<div className="pay_subscribe_modal__scan-tip">
|
|
<img src={wxLogo} alt="" className="pay_subscribe_modal__scan-tip-icon" />
|
|
<span>{t("subscribe.scan_code_tip")}</span>
|
|
</div>
|
|
) : (
|
|
<p className="pay_subscribe_modal__scan-tip is_alipay">{t("subscribe.please_complete_alipay_payment")}</p>
|
|
)}
|
|
|
|
<p className="pay_subscribe_modal__footer-terms">
|
|
{t("subscribe.read_before_pay")}
|
|
{isFutureCreatorPlan ? (
|
|
<a
|
|
href="#"
|
|
className="pay_subscribe_modal__footer-link"
|
|
onClick={(e) => {
|
|
e.preventDefault();
|
|
void openLatestPrivacyPolicyLink(PRIVACY_POLICY_KEY_FUTURE_CREATOR);
|
|
}}
|
|
>
|
|
{t("subscribe.future_creator_payment_terms")}
|
|
</a>
|
|
) : (
|
|
<a
|
|
href="#"
|
|
className="pay_subscribe_modal__footer-link"
|
|
onClick={(e) => {
|
|
e.preventDefault();
|
|
void openLatestPrivacyPolicyLink(PRIVACY_POLICY_KEY_PAYMENT_TERMS);
|
|
}}
|
|
>
|
|
{t("subscribe.payment_terms")}
|
|
</a>
|
|
)}
|
|
</p>
|
|
</div>
|
|
);
|
|
}
|
|
|