feat(analytics): add behavior and payment funnel tracking

This commit is contained in:
2026-07-14 16:54:13 +08:00
parent ca55723e48
commit 81d6489978
70 changed files with 1576 additions and 81 deletions
+59 -5
View File
@@ -8,6 +8,7 @@ import {
isEzpayPayment,
launchEzpayRedirect,
} from "@/lib/payment/payment_launch";
import { behaviorAnalytics } from "@/lib/analytics";
import type {
PendingPaymentReturnTo,
PendingPaymentSubscriptionType,
@@ -52,6 +53,38 @@ export interface PaymentLaunchFlow {
stripeClientSecret: string | null;
}
function trackPaymentCheckoutOpened(
payment: PaymentContextState,
checkoutUrl: string,
): void {
const plan = payment.plans.find(
(item) => item.planId === payment.selectedPlanId,
);
if (!payment.currentOrderId || !plan) return;
behaviorAnalytics.checkoutOpened({
orderId: payment.currentOrderId,
plan,
payChannel: payment.payChannel,
checkoutUrl,
});
}
function trackPaymentCheckoutFailed(
payment: PaymentContextState,
reason: "missing_checkout_url" | "payment_redirect_failed",
): void {
const plan = payment.plans.find(
(item) => item.planId === payment.selectedPlanId,
);
if (!payment.currentOrderId || !plan) return;
behaviorAnalytics.checkoutFailed({
orderId: payment.currentOrderId,
plan,
payChannel: payment.payChannel,
reason,
});
}
export function shouldShowEzpayConfirmation({
currentOrderId,
isProduction,
@@ -87,7 +120,6 @@ export function usePaymentLaunchFlow({
const ezpayPaymentUrl = payment.payParams
? getPaymentUrl(payment.payParams)
: null;
useEffect(() => {
if (!payment.payParams || payment.launchNonce <= launchedNonceRef.current) {
return;
@@ -95,7 +127,10 @@ export function usePaymentLaunchFlow({
launchedNonceRef.current = payment.launchNonce;
const clientSecret = getStripeClientSecret(payment.payParams);
if (clientSecret) return;
if (clientSecret) {
trackPaymentCheckoutOpened(payment, "stripe_embedded");
return;
}
const paymentUrl = getPaymentUrl(payment.payParams);
if (paymentUrl) {
@@ -117,16 +152,29 @@ export function usePaymentLaunchFlow({
subscriptionType,
...(tipCoffeeType ? { tipCoffeeType } : {}),
...(returnTo ? { returnTo } : {}),
onFailed: (errorMessage) =>
paymentDispatch({ type: "PaymentLaunchFailed", errorMessage }),
onOpened: () => trackPaymentCheckoutOpened(payment, paymentUrl),
onFailed: (errorMessage) => {
trackPaymentCheckoutFailed(payment, "payment_redirect_failed");
paymentDispatch({ type: "PaymentLaunchFailed", errorMessage });
},
});
return;
}
window.location.href = paymentUrl;
try {
window.location.href = paymentUrl;
trackPaymentCheckoutOpened(payment, paymentUrl);
} catch {
trackPaymentCheckoutFailed(payment, "payment_redirect_failed");
paymentDispatch({
type: "PaymentLaunchFailed",
errorMessage: "Could not open the payment page. Please try again.",
});
}
return;
}
trackPaymentCheckoutFailed(payment, "missing_checkout_url");
paymentDispatch({
type: "PaymentLaunchFailed",
errorMessage: UNSUPPORTED_PAYMENT_PARAMS_MESSAGE,
@@ -135,8 +183,12 @@ export function usePaymentLaunchFlow({
log,
logScope,
payment.currentOrderId,
payment,
payment.launchNonce,
payment.payChannel,
payment.payParams,
payment.plans,
payment.selectedPlanId,
paymentDispatch,
returnTo,
subscriptionType,
@@ -183,7 +235,9 @@ export function usePaymentLaunchFlow({
subscriptionType,
...(tipCoffeeType ? { tipCoffeeType } : {}),
...(returnTo ? { returnTo } : {}),
onOpened: () => trackPaymentCheckoutOpened(payment, ezpayPaymentUrl),
onFailed: (errorMessage) => {
trackPaymentCheckoutFailed(payment, "payment_redirect_failed");
setIsConfirmingEzpay(false);
paymentDispatch({ type: "PaymentLaunchFailed", errorMessage });
},