fix(platform-service): harden register/stripe flows for multi-product correctness

- Make auth register provisioning truly best-effort (warn on failure, do not fail signup)
- Process Stripe webhook events for all products (remove non-default skip)
- Derive updated subscription plan from Stripe price IDs on subscription.updated
- Sync derived plan to auth users and backend plan sync endpoint
- Verified: tsc --noEmit clean, 20 test files / 183 tests passing
This commit is contained in:
saravanakumardb1 2026-02-15 14:59:27 -08:00
parent 0c3c109bf1
commit a699dd9073
2 changed files with 56 additions and 35 deletions

View File

@ -96,36 +96,50 @@ export async function authRoutes(app: FastifyInstance) {
// Registration hook: initialize subscription + license using product defaults.
// Best-effort during migration; auth account creation remains primary.
await subscriptionRepo.createSubscription({
id: `sub_${user.id}_${Date.now()}`,
productId,
userId: user.id,
plan: initialPlan,
status: hasTrial ? 'trialing' : 'active',
currentPeriodStart: nowIso,
currentPeriodEnd: hasTrial ? trialEnd.toISOString() : nowIso,
cancelAtPeriodEnd: false,
monthlyPrice: 0,
tokensIncluded: 0,
tokensUsed: 0,
createdAt: nowIso,
updatedAt: nowIso,
});
try {
await subscriptionRepo.createSubscription({
id: `sub_${user.id}_${Date.now()}`,
productId,
userId: user.id,
plan: initialPlan,
status: hasTrial ? 'trialing' : 'active',
currentPeriodStart: nowIso,
currentPeriodEnd: hasTrial ? trialEnd.toISOString() : nowIso,
cancelAtPeriodEnd: false,
monthlyPrice: 0,
tokensIncluded: 0,
tokensUsed: 0,
createdAt: nowIso,
updatedAt: nowIso,
});
} catch (err) {
req.log.warn(
{ err, userId: user.id, productId },
'Subscription provisioning failed during register'
);
}
await licenseRepo.create({
id: `lic_${crypto.randomUUID()}`,
productId,
key: licenseRepo.generateKey(product.licensePrefix),
userId: user.id,
plan: initialPlan,
status: 'active',
activatedAt: null,
expiresAt: hasTrial ? trialEnd.toISOString() : null,
deviceIds: [],
maxDevices: product.deviceLimits[initialPlan],
createdAt: nowIso,
updatedAt: nowIso,
});
try {
await licenseRepo.create({
id: `lic_${crypto.randomUUID()}`,
productId,
key: licenseRepo.generateKey(product.licensePrefix),
userId: user.id,
plan: initialPlan,
status: 'active',
activatedAt: null,
expiresAt: hasTrial ? trialEnd.toISOString() : null,
deviceIds: [],
maxDevices: product.deviceLimits[initialPlan],
createdAt: nowIso,
updatedAt: nowIso,
});
} catch (err) {
req.log.warn(
{ err, userId: user.id, productId },
'License provisioning failed during register'
);
}
const accessToken = await jwt.createAccessToken({
sub: user.id,

View File

@ -110,10 +110,6 @@ export async function stripeRoutes(app: FastifyInstance) {
// Route by productId in metadata (multi-tenant)
const metadata = getEventMetadata(event);
const eventProductId = metadata?.productId || DEFAULT_PRODUCT_ID;
if (eventProductId !== DEFAULT_PRODUCT_ID) {
app.log.info(`Ignoring event for product ${eventProductId}`);
return { received: true, skipped: true };
}
switch (event.type) {
case 'checkout.session.completed': {
@ -183,19 +179,21 @@ export async function stripeRoutes(app: FastifyInstance) {
const customerId = typeof sub.customer === 'string' ? sub.customer : sub.customer.id;
const existing = await subRepo.getByStripeCustomerId(customerId, eventProductId);
if (existing) {
const updatedPlan = getPlanFromSubscription(sub) ?? existing.plan;
const newStatus = sub.cancel_at_period_end
? 'cancelled'
: sub.status === 'active'
? 'active'
: 'past_due';
await subRepo.updateSubscription(existing.id, existing.userId, {
plan: updatedPlan,
status: newStatus,
cancelAtPeriodEnd: sub.cancel_at_period_end,
currentPeriodEnd: new Date(sub.current_period_end * 1000).toISOString(),
});
await authRepo.updatePlan(existing.userId, eventProductId, existing.plan);
await authRepo.updatePlan(existing.userId, eventProductId, updatedPlan);
// Sync plan to backend users container
await syncUserPlan(existing.userId, existing.plan, app.log);
await syncUserPlan(existing.userId, updatedPlan, app.log);
}
break;
}
@ -279,3 +277,12 @@ function getEventMetadata(event: Stripe.Event): Record<string, string> | null {
}
return null;
}
function getPlanFromSubscription(sub: Stripe.Subscription): SubscriptionDoc['plan'] | null {
const priceId = sub.items.data[0]?.price?.id;
if (!priceId) return null;
const priceIds = getPriceIds();
if (priceId === priceIds.enterprise) return 'enterprise';
if (priceId === priceIds.pro) return 'pro';
return null;
}