feat(FN-281): add payment_success and payment_failed PostHog events (+1 more)
Some checks failed
Sync dev → Gitea / Mirror dev to Gitea (push) Has been cancelled

Commits merged:
- fix(FN-281): make result search param truly optional to fix typecheck
- feat(FN-281): add payment_success and payment_failed PostHog events

Files changed:
apps/api/package.json                              |    1 +
 apps/api/src/app.module.ts                         |    2 +
 apps/api/src/payments/payments.service.spec.ts     |   99 +-
 apps/api/src/payments/payments.service.ts          |   40 +
 apps/api/src/posthog/posthog.module.ts             |    9 +
 apps/api/src/posthog/posthog.service.ts            |   64 ++
 .../web/src/components/payment/payment-content.tsx |   24 +-
 apps/web/src/routeTree.gen.ts                      | 1110 ++++++++++----------
 apps/web/src/routes/dashboard/subscription/pay.tsx |   24 +-
 packages/config/src/index.ts                       |    4 +
 pnpm-lock.yaml                                     |   30 +
 11 files changed, 827 insertions(+), 580 deletions(-)

Fusion-Task-Id: FN-281
This commit is contained in:
Fusion
2026-05-13 06:30:48 +00:00
parent 7707443be6
commit d855b564bf
11 changed files with 827 additions and 580 deletions

View File

@@ -23,7 +23,7 @@ import {
FileText,
Upload,
} from "lucide-react";
import { useCallback, useRef, useState } from "react";
import { useCallback, useEffect, useRef, useState } from "react";
interface Brand {
id: string;
@@ -36,6 +36,7 @@ interface PaymentContentProps {
planKey: string;
period: "monthly" | "yearly";
brandIds: string[];
result?: string;
}
const planConfig: Record<string, { priceMonthly: number; priceYearly: number }> = {
@@ -54,7 +55,7 @@ const bankDetails = {
type Step = "summary" | "payment" | "confirmation";
export function PaymentContent({ planKey, period, brandIds }: PaymentContentProps) {
export function PaymentContent({ planKey, period, brandIds, result }: PaymentContentProps) {
const { t } = useTranslation();
const navigate = useNavigate();
const fileInputRef = useRef<HTMLInputElement>(null);
@@ -65,6 +66,18 @@ export function PaymentContent({ planKey, period, brandIds }: PaymentContentProp
const [isDragging, setIsDragging] = useState(false);
const [eftPaymentId, setEftPaymentId] = useState<string | null>(null);
// biome-ignore lint/correctness/useExhaustiveDependencies: only re-run when result changes
useEffect(() => {
if (!result) return;
if (result === "success") {
capture("payment_success", { method: "iyzico", plan: planKey, period, amount: totalAmount });
setStep("confirmation");
} else {
capture("payment_failed", { method: "iyzico", plan: planKey, period, reason: result });
toast.error(t("payment.paymentFailed"));
}
}, [result]);
const config = planConfig[planKey];
const totalAmount = config
? period === "monthly"
@@ -119,6 +132,13 @@ export function PaymentContent({ planKey, period, brandIds }: PaymentContentProp
return api.upload<{ success: boolean }>(`/payments/eft/${eftPaymentId}/receipt`, formData);
},
onSuccess: () => {
capture("payment_success", {
method: "eft",
plan: planKey,
period,
amount: totalAmount,
payment_id: eftPaymentId,
});
toast.success(t("payment.receiptUploaded"));
setStep("confirmation");
},

File diff suppressed because it is too large Load Diff

View File

@@ -9,16 +9,26 @@ const PaymentContent = lazy(() =>
);
export const Route = createFileRoute("/dashboard/subscription/pay")({
validateSearch: (search: Record<string, unknown>) => ({
plan: (search.plan as string) || "",
period: ((search.period as string) || "monthly") as "monthly" | "yearly",
brands: (search.brands as string) || "",
}),
validateSearch: (search: Record<string, unknown>) => {
const params: {
plan: string;
period: "monthly" | "yearly";
brands: string;
result?: string;
} = {
plan: (search.plan as string) || "",
period: ((search.period as string) || "monthly") as "monthly" | "yearly",
brands: (search.brands as string) || "",
};
const result = search.result as string | undefined;
if (result) params.result = result;
return params;
},
component: PaymentPage,
});
function PaymentPage() {
const { plan, period, brands } = Route.useSearch();
const { plan, period, brands, result } = Route.useSearch();
const brandIds = brands.split(",").filter(Boolean);
return (
@@ -37,7 +47,7 @@ function PaymentPage() {
</div>
}
>
<PaymentContent planKey={plan} period={period} brandIds={brandIds} />
<PaymentContent planKey={plan} period={period} brandIds={brandIds} result={result} />
</Suspense>
);
}