dev #86
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -65,6 +65,10 @@ services:
|
||||
- NOVU_API_KEY=${NOVU_API_KEY:-}
|
||||
- APP_PUBLIC_URL=${APP_PUBLIC_URL:-https://sase.tr}
|
||||
- MAILTRACK_SECRET=${MAILTRACK_SECRET:-}
|
||||
# PostHog server-side analytics (payments, subscription lifecycle, $revenue).
|
||||
# Empty key → PostHogService no-ops (server-side analytics disabled).
|
||||
- POSTHOG_API_KEY=${POSTHOG_API_KEY:-}
|
||||
- POSTHOG_HOST=${POSTHOG_HOST:-https://eu.i.posthog.com}
|
||||
- OTEL_ENABLED=${OTEL_ENABLED:-false}
|
||||
- OTEL_EXPORTER_OTLP_ENDPOINT=${OTEL_EXPORTER_OTLP_ENDPOINT:-}
|
||||
- OTEL_EXPORTER_OTLP_HEADERS=${OTEL_EXPORTER_OTLP_HEADERS:-}
|
||||
@@ -144,6 +148,10 @@ services:
|
||||
- NOVU_API_KEY=${NOVU_API_KEY:-}
|
||||
- APP_PUBLIC_URL=${APP_PUBLIC_URL:-https://sase.tr}
|
||||
- MAILTRACK_SECRET=${MAILTRACK_SECRET:-}
|
||||
# PostHog server-side analytics (payments, subscription lifecycle, $revenue).
|
||||
# Empty key → PostHogService no-ops (server-side analytics disabled).
|
||||
- POSTHOG_API_KEY=${POSTHOG_API_KEY:-}
|
||||
- POSTHOG_HOST=${POSTHOG_HOST:-https://eu.i.posthog.com}
|
||||
- OTEL_ENABLED=${OTEL_ENABLED:-false}
|
||||
- OTEL_EXPORTER_OTLP_ENDPOINT=${OTEL_EXPORTER_OTLP_ENDPOINT:-}
|
||||
- OTEL_EXPORTER_OTLP_HEADERS=${OTEL_EXPORTER_OTLP_HEADERS:-}
|
||||
|
||||
Reference in New Issue
Block a user