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);