dev #86

Merged
root merged 2 commits from dev into main 2026-06-03 17:20:41 +03:00
2 changed files with 44 additions and 3 deletions
Showing only changes of commit 3bfefe6196 - Show all commits

View File

@@ -56,7 +56,8 @@ function createMockDb(overrides: Record<string, unknown> = {}) {
* Creates the service with a given mock db injected via reflection.
*/
function createService(db: unknown): SubscriptionsService {
const service = new SubscriptionsService(db as any);
const posthog = { captureForUser: vi.fn(), capture: vi.fn() };
const service = new SubscriptionsService(db as any, posthog as any);
return service;
}

View File

@@ -7,11 +7,22 @@ import {
} from "@nestjs/common";
import { and, desc, eq, inArray, or } from "drizzle-orm";
import { DATABASE, type Database } from "../database/database.provider";
import { brands, plans, userBrands, userSubscriptions, users } from "../database/schema/core";
import {
brands,
payments,
plans,
userBrands,
userSubscriptions,
users,
} from "../database/schema/core";
import { PostHogService } from "../posthog/posthog.service";
@Injectable()
export class SubscriptionsService {
constructor(@Inject(DATABASE) private db: Database) {}
constructor(
@Inject(DATABASE) private db: Database,
private posthog: PostHogService,
) {}
/**
* Consumes any banked referral reward days for the user (zeroing the balance)
@@ -152,6 +163,35 @@ export class SubscriptionsService {
}
}
// Canonical realized-revenue event. activateSubscription is the shared
// chokepoint for BOTH Stripe (stripe.service) and EFT/manual (billing.service)
// activation, so total paid revenue becomes measurable in PostHog regardless
// of payment method (a Stripe DWH connector alone would miss EFT/havale).
// $revenue is in major TRY (PostHog revenue convention); plan prices are kuruş.
// This is the single source of $revenue — funnel steps (payment_initiated etc.)
// intentionally do NOT carry $revenue so revenue isn't double-counted.
const priceKurus =
sub.billingPeriod === "yearly" ? (plan[0]?.priceYearly ?? 0) : (plan[0]?.priceMonthly ?? 0);
const [lastPayment] = await this.db
.select({ method: payments.method })
.from(payments)
.where(eq(payments.subscriptionId, subscriptionId))
.orderBy(desc(payments.createdAt))
.limit(1);
this.posthog.captureForUser(sub.userId, "subscription_activated", {
$revenue: priceKurus / 100,
currency: "TRY",
amount_kurus: priceKurus,
// Normalised monthly recurring revenue (yearly amortised over 12 months).
mrr: sub.billingPeriod === "yearly" ? priceKurus / 12 / 100 : priceKurus / 100,
plan: plan[0]?.name ?? null,
plan_id: sub.planId,
brand_count: plan[0]?.brandCount ?? null,
billing_period: sub.billingPeriod,
method: lastPayment?.method ?? "unknown",
referral_credit_days: creditDays,
});
return updated;
}