fix(payment): allow renewing active subscriptions

This commit is contained in:
2026-06-22 18:48:21 +08:00
parent 2cfbaba058
commit 9a3c8f0d12
3 changed files with 92 additions and 35 deletions
@@ -11,13 +11,14 @@ import {
usePaymentDispatch,
usePaymentState,
} from "@/stores/payment/payment-context";
import { AppEnvUtil } from "@/utils";
import { AppEnvUtil, Logger, Result } from "@/utils";
import { StripePaymentDialog } from "./stripe-payment-dialog";
import { SubscriptionCtaButton } from "./subscription-cta-button";
import styles from "./subscription-cta-button.module.css";
const EZPAY_DEVELOPMENT_REDIRECT_DELAY_MS = 5000;
const log = new Logger("SubscriptionCheckoutButton");
export interface SubscriptionCheckoutButtonProps {
/** 是否可用(未选套餐 / 未勾选协议 → false) */
@@ -107,9 +108,7 @@ export function SubscriptionCheckoutButton({
payment.isCreatingOrder ||
payment.isPollingOrder ||
payment.isCheckingVipStatus;
const label = payment.isPaid
? "Activated"
: payment.isPollingOrder
const label = payment.isPollingOrder
? "Processing payment..."
: payment.isCreatingOrder
? "Creating order..."
@@ -246,23 +245,64 @@ async function redirectToEzpay({
delayMs = 0,
setTimeoutId,
}: RedirectToEzpayInput): Promise<void> {
log.debug("[subscription-checkout] redirectToEzpay START", {
hasOrderId: Boolean(orderId),
orderId,
subscriptionType,
delayMs,
paymentUrl,
});
if (orderId) {
await PendingPaymentOrderStorage.setPendingOrder({
const saveResult = await PendingPaymentOrderStorage.setPendingOrder({
orderId,
payChannel: "ezpay",
subscriptionType,
createdAt: Date.now(),
});
if (Result.isOk(saveResult)) {
log.debug("[subscription-checkout] pending ezpay order saved", {
orderId,
subscriptionType,
});
} else {
log.error("[subscription-checkout] pending ezpay order save failed", {
orderId,
subscriptionType,
error: saveResult.error,
});
}
} else {
log.warn("[subscription-checkout] skip pending ezpay order save: missing orderId", {
subscriptionType,
paymentUrl,
});
}
if (delayMs > 0) {
const timeoutId = window.setTimeout(() => {
log.debug("[subscription-checkout] redirectToEzpay DELAY elapsed", {
orderId,
subscriptionType,
paymentUrl,
});
window.location.href = paymentUrl;
}, delayMs);
setTimeoutId?.(timeoutId);
log.debug("[subscription-checkout] redirectToEzpay DELAY scheduled", {
orderId,
subscriptionType,
delayMs,
timeoutId,
});
return;
}
log.debug("[subscription-checkout] redirectToEzpay NOW", {
orderId,
subscriptionType,
paymentUrl,
});
window.location.href = paymentUrl;
}
+16 -30
View File
@@ -32,7 +32,6 @@ import {
SubscriptionBanner,
SubscriptionBenefitsCard,
SubscriptionCheckoutButton,
SubscriptionCtaButton,
SubscriptionPaymentMethod,
SubscriptionPlanCard,
SubscriptionUserRow,
@@ -213,11 +212,6 @@ export function SubscriptionScreen({
payment.isCheckingVipStatus;
const canActivate =
selectedPlan !== null && payment.agreed && !isPaymentBusy;
const isVip =
subscriptionType === "vip" &&
(user.currentUser?.isVip === true ||
payment.vipStatus?.isVip === true ||
payment.isPaid);
const name = user.currentUser?.username ?? FALLBACK_USERNAME;
const avatarUrl = user.currentUser?.avatarUrl ?? null;
@@ -312,32 +306,24 @@ export function SubscriptionScreen({
)}
</section>
{!isVip ? (
<section className={styles.paymentMethodSlot}>
<SubscriptionPaymentMethod
value={payment.payChannel}
disabled={isPaymentBusy}
onChange={(payChannel) =>
paymentDispatch({
type: "PaymentPayChannelChanged",
payChannel,
})
}
/>
</section>
) : null}
<section className={styles.paymentMethodSlot}>
<SubscriptionPaymentMethod
value={payment.payChannel}
disabled={isPaymentBusy}
onChange={(payChannel) =>
paymentDispatch({
type: "PaymentPayChannelChanged",
payChannel,
})
}
/>
</section>
<div className={styles.ctaSlot}>
{isVip ? (
<SubscriptionCtaButton type="button" disabled>
VIP Activated
</SubscriptionCtaButton>
) : (
<SubscriptionCheckoutButton
disabled={!canActivate}
subscriptionType={subscriptionType}
/>
)}
<SubscriptionCheckoutButton
disabled={!canActivate}
subscriptionType={subscriptionType}
/>
</div>
<div className={styles.agreementSlot}>