diff --git a/src/app/subscription/components/subscription-checkout-button.tsx b/src/app/subscription/components/subscription-checkout-button.tsx index e89d3539..a4ed49ce 100644 --- a/src/app/subscription/components/subscription-checkout-button.tsx +++ b/src/app/subscription/components/subscription-checkout-button.tsx @@ -14,10 +14,10 @@ import { import { AppEnvUtil, Logger, Result } from "@/utils"; import { StripePaymentDialog } from "./stripe-payment-dialog"; +import dialogStyles from "./stripe-payment-dialog.module.css"; import { SubscriptionCtaButton } from "./subscription-cta-button"; import styles from "./subscription-cta-button.module.css"; -const EZPAY_DEVELOPMENT_REDIRECT_DELAY_MS = 10000; const log = new Logger("SubscriptionCheckoutButton"); export interface SubscriptionCheckoutButtonProps { @@ -33,18 +33,10 @@ export function SubscriptionCheckoutButton({ const payment = usePaymentState(); const paymentDispatch = usePaymentDispatch(); const launchedNonceRef = useRef(0); - const ezpayRedirectTimeoutRef = useRef(null); const [hiddenStripeClientSecret, setHiddenStripeClientSecret] = useState< string | null >(null); - - useEffect(() => { - return () => { - if (ezpayRedirectTimeoutRef.current !== null) { - window.clearTimeout(ezpayRedirectTimeoutRef.current); - } - }; - }, []); + const [isConfirmingEzpay, setIsConfirmingEzpay] = useState(false); useEffect(() => { if (!payment.payParams || payment.launchNonce <= launchedNonceRef.current) { @@ -62,18 +54,10 @@ export function SubscriptionCheckoutButton({ const isEzpay = isEzpayPayment(payment.payParams); if (!AppEnvUtil.isProduction() && isEzpay) { - if (ezpayRedirectTimeoutRef.current !== null) { - window.clearTimeout(ezpayRedirectTimeoutRef.current); - } - - void redirectToEzpay({ + log.debug("[subscription-checkout] ezpay confirmation required", { orderId: payment.currentOrderId, paymentUrl, subscriptionType, - delayMs: EZPAY_DEVELOPMENT_REDIRECT_DELAY_MS, - setTimeoutId: (timeoutId) => { - ezpayRedirectTimeoutRef.current = timeoutId; - }, }); return; } @@ -118,10 +102,7 @@ export function SubscriptionCheckoutButton({ const handleClick = () => { if (disabled || isLoading) return; - if (ezpayRedirectTimeoutRef.current !== null) { - window.clearTimeout(ezpayRedirectTimeoutRef.current); - ezpayRedirectTimeoutRef.current = null; - } + setIsConfirmingEzpay(false); setHiddenStripeClientSecret(null); paymentDispatch({ type: "PaymentCreateOrderSubmitted" }); }; @@ -148,14 +129,36 @@ export function SubscriptionCheckoutButton({ stripeClientSecret !== null && stripeClientSecret !== hiddenStripeClientSecret && !payment.isPaid; - const pendingEzpayRedirectOrderId = + const ezpayPaymentUrl = payment.payParams + ? getPaymentUrl(payment.payParams) + : null; + const shouldShowEzpayConfirmDialog = !AppEnvUtil.isProduction() && payment.payParams && isEzpayPayment(payment.payParams) && - getPaymentUrl(payment.payParams) && + ezpayPaymentUrl && payment.currentOrderId - ? payment.currentOrderId - : null; + ? true + : false; + + const handleEzpayConfirm = () => { + if (!payment.currentOrderId || !ezpayPaymentUrl) return; + setIsConfirmingEzpay(true); + void redirectToEzpay({ + orderId: payment.currentOrderId, + paymentUrl: ezpayPaymentUrl, + subscriptionType, + }); + }; + + const handleEzpayCancel = () => { + log.debug("[subscription-checkout] ezpay confirmation cancelled", { + orderId: payment.currentOrderId, + subscriptionType, + }); + setIsConfirmingEzpay(false); + paymentDispatch({ type: "PaymentReset" }); + }; return ( <> @@ -181,15 +184,13 @@ export function SubscriptionCheckoutButton({ {payment.errorMessage}

) : null} - {pendingEzpayRedirectOrderId ? ( -
- - Ezpay order created. Redirecting in 5 seconds. - - - Order No. {pendingEzpayRedirectOrderId} - -
+ {shouldShowEzpayConfirmDialog ? ( + ) : null} {shouldShowStripeDialog ? ( void; } async function redirectToEzpay({ orderId, paymentUrl, subscriptionType, - delayMs = 0, - setTimeoutId, }: RedirectToEzpayInput): Promise { log.debug("[subscription-checkout] redirectToEzpay START", { hasOrderId: Boolean(orderId), orderId, subscriptionType, - delayMs, paymentUrl, }); @@ -279,25 +275,6 @@ async function redirectToEzpay({ }); } - 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, @@ -306,6 +283,60 @@ async function redirectToEzpay({ window.location.href = paymentUrl; } +interface EzpayRedirectConfirmDialogProps { + orderId: string; + isConfirming: boolean; + onCancel: () => void; + onConfirm: () => void; +} + +function EzpayRedirectConfirmDialog({ + orderId, + isConfirming, + onCancel, + onConfirm, +}: EzpayRedirectConfirmDialogProps) { + return ( +
+
+
+

+ Continue to Ezpay? +

+

+ Your order has been created. Continue to Ezpay to finish the + payment. +

+

Order No. {orderId}

+
+
+ + +
+
+
+ ); +} + function getStripeClientSecret( payParams: Record, ): string | null { diff --git a/src/app/subscription/components/subscription-cta-button.module.css b/src/app/subscription/components/subscription-cta-button.module.css index c61fde6e..9af87f5a 100644 --- a/src/app/subscription/components/subscription-cta-button.module.css +++ b/src/app/subscription/components/subscription-cta-button.module.css @@ -60,32 +60,6 @@ animation: spin 0.8s linear infinite; } -.pendingRedirect { - display: flex; - flex-direction: column; - gap: 4px; - margin-top: var(--spacing-sm); - padding: 10px 12px; - border: 1px solid rgba(246, 87, 160, 0.2); - border-radius: 14px; - background: #ffffff; - color: #3c3b3b; - text-align: center; -} - -.pendingRedirectLabel { - font-size: var(--font-size-sm); - line-height: 1.3; -} - -.pendingRedirectOrder { - color: #1e1e1e; - font-size: var(--font-size-md); - font-weight: 700; - line-height: 1.3; - word-break: break-all; -} - @keyframes spin { to { transform: rotate(360deg);