feat: send notifications when subscription tier changes (refs #59)
All checks were successful
Deploy to Staging / Build Images (pull_request) Successful in 7m15s
Deploy to Staging / Deploy to Staging (pull_request) Successful in 30s
Deploy to Staging / Verify Staging (pull_request) Successful in 8s
Deploy to Staging / Notify Staging Ready (pull_request) Successful in 7s
Deploy to Staging / Notify Staging Failure (pull_request) Has been skipped
All checks were successful
Deploy to Staging / Build Images (pull_request) Successful in 7m15s
Deploy to Staging / Deploy to Staging (pull_request) Successful in 30s
Deploy to Staging / Verify Staging (pull_request) Successful in 8s
Deploy to Staging / Notify Staging Ready (pull_request) Successful in 7s
Deploy to Staging / Notify Staging Failure (pull_request) Has been skipped
Adds email and in-app notifications when user subscription tier changes: - Extended TemplateKey type with 'subscription_tier_change' - Added migration for tier change email template with HTML - Added sendTierChangeNotification() to NotificationsService - Integrated notifications into upgradeSubscription, downgradeSubscription, adminOverrideTier - Integrated notifications into grace-period.job.ts for auto-downgrades Notifications include previous tier, new tier, and reason for change. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -8,6 +8,7 @@ import { logger } from '../../../core/logging/logger';
|
||||
import { SubscriptionsRepository } from '../data/subscriptions.repository';
|
||||
import { StripeClient } from '../external/stripe/stripe.client';
|
||||
import { UserProfileRepository } from '../../user-profile/data/user-profile.repository';
|
||||
import { NotificationsService } from '../../notifications/domain/notifications.service';
|
||||
import {
|
||||
Subscription,
|
||||
SubscriptionResponse,
|
||||
@@ -30,6 +31,7 @@ interface StripeWebhookEvent {
|
||||
export class SubscriptionsService {
|
||||
private userProfileRepository: UserProfileRepository;
|
||||
private vehiclesRepository: VehiclesRepository;
|
||||
private notificationsService: NotificationsService;
|
||||
|
||||
constructor(
|
||||
private repository: SubscriptionsRepository,
|
||||
@@ -38,6 +40,7 @@ export class SubscriptionsService {
|
||||
) {
|
||||
this.userProfileRepository = new UserProfileRepository(pool);
|
||||
this.vehiclesRepository = new VehiclesRepository(pool);
|
||||
this.notificationsService = new NotificationsService();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -213,6 +216,14 @@ export class SubscriptionsService {
|
||||
// Sync tier to user profile
|
||||
await this.syncTierToUserProfile(userId, newTier);
|
||||
|
||||
// Send tier change notification
|
||||
await this.sendTierChangeNotificationSafe(
|
||||
userId,
|
||||
currentSubscription.tier,
|
||||
newTier,
|
||||
'user_upgrade'
|
||||
);
|
||||
|
||||
logger.info('Subscription upgraded', {
|
||||
subscriptionId: updatedSubscription.id,
|
||||
userId,
|
||||
@@ -405,6 +416,14 @@ export class SubscriptionsService {
|
||||
// Sync tier to user profile
|
||||
await this.syncTierToUserProfile(userId, targetTier);
|
||||
|
||||
// Send tier change notification
|
||||
await this.sendTierChangeNotificationSafe(
|
||||
userId,
|
||||
currentSubscription.tier,
|
||||
targetTier,
|
||||
'user_downgrade'
|
||||
);
|
||||
|
||||
logger.info('Subscription downgraded', {
|
||||
subscriptionId: updatedSubscription.id,
|
||||
userId,
|
||||
@@ -701,6 +720,48 @@ export class SubscriptionsService {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Send tier change notification safely (non-blocking, logs errors)
|
||||
*/
|
||||
private async sendTierChangeNotificationSafe(
|
||||
userId: string,
|
||||
previousTier: SubscriptionTier,
|
||||
newTier: SubscriptionTier,
|
||||
reason: 'admin_override' | 'user_upgrade' | 'user_downgrade' | 'grace_period_expiration'
|
||||
): Promise<void> {
|
||||
try {
|
||||
// Get user profile for email and name
|
||||
const userProfile = await this.userProfileRepository.getByAuth0Sub(userId);
|
||||
if (!userProfile) {
|
||||
logger.warn('User profile not found for tier change notification', { userId });
|
||||
return;
|
||||
}
|
||||
|
||||
const userEmail = userProfile.notificationEmail || userProfile.email;
|
||||
const userName = userProfile.displayName || userProfile.email.split('@')[0];
|
||||
|
||||
await this.notificationsService.sendTierChangeNotification(
|
||||
userId,
|
||||
userEmail,
|
||||
userName,
|
||||
previousTier,
|
||||
newTier,
|
||||
reason
|
||||
);
|
||||
|
||||
logger.info('Tier change notification sent', { userId, previousTier, newTier, reason });
|
||||
} catch (error: any) {
|
||||
// Log but don't throw - notifications are non-critical
|
||||
logger.error('Failed to send tier change notification', {
|
||||
userId,
|
||||
previousTier,
|
||||
newTier,
|
||||
reason,
|
||||
error: error.message,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync subscription tier to user_profiles table
|
||||
*/
|
||||
@@ -809,9 +870,11 @@ export class SubscriptionsService {
|
||||
logger.info('Admin overriding subscription tier', { userId, newTier });
|
||||
|
||||
// Check if user has a subscription record
|
||||
let subscription = await this.repository.findByUserId(userId);
|
||||
const existingSubscription = await this.repository.findByUserId(userId);
|
||||
const previousTier: SubscriptionTier = existingSubscription?.tier || 'free';
|
||||
|
||||
if (!subscription) {
|
||||
let subscription: Subscription;
|
||||
if (!existingSubscription) {
|
||||
// Create subscription record for user (admin override bypasses Stripe)
|
||||
logger.info('Creating subscription record for admin override', { userId, newTier });
|
||||
subscription = await this.repository.createForAdminOverride(userId, newTier, client);
|
||||
@@ -832,6 +895,14 @@ export class SubscriptionsService {
|
||||
|
||||
await client.query('COMMIT');
|
||||
|
||||
// Send tier change notification (after transaction committed)
|
||||
await this.sendTierChangeNotificationSafe(
|
||||
userId,
|
||||
previousTier,
|
||||
newTier,
|
||||
'admin_override'
|
||||
);
|
||||
|
||||
logger.info('Admin subscription tier override complete', {
|
||||
userId,
|
||||
newTier,
|
||||
|
||||
Reference in New Issue
Block a user