fix: Stripe IDs and admin overrides
All checks were successful
Deploy to Staging / Build Images (push) Successful in 3m38s
Deploy to Staging / Deploy to Staging (push) Successful in 53s
Deploy to Staging / Verify Staging (push) Successful in 9s
Deploy to Staging / Notify Staging Ready (push) Successful in 8s
Deploy to Staging / Notify Staging Failure (push) Has been skipped
All checks were successful
Deploy to Staging / Build Images (push) Successful in 3m38s
Deploy to Staging / Deploy to Staging (push) Successful in 53s
Deploy to Staging / Verify Staging (push) Successful in 9s
Deploy to Staging / Notify Staging Ready (push) Successful in 8s
Deploy to Staging / Notify Staging Failure (push) Has been skipped
This commit is contained in:
@@ -134,7 +134,8 @@ export class SubscriptionsController {
|
|||||||
userId,
|
userId,
|
||||||
tier,
|
tier,
|
||||||
billingCycle,
|
billingCycle,
|
||||||
paymentMethodId || ''
|
paymentMethodId || '',
|
||||||
|
email
|
||||||
);
|
);
|
||||||
|
|
||||||
reply.status(200).send(updatedSubscription);
|
reply.status(200).send(updatedSubscription);
|
||||||
@@ -207,6 +208,7 @@ export class SubscriptionsController {
|
|||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
try {
|
try {
|
||||||
const userId = (request as any).user.sub;
|
const userId = (request as any).user.sub;
|
||||||
|
const email = (request as any).user.email;
|
||||||
const { paymentMethodId } = request.body;
|
const { paymentMethodId } = request.body;
|
||||||
|
|
||||||
// Validate input
|
// Validate input
|
||||||
@@ -218,19 +220,8 @@ export class SubscriptionsController {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get subscription
|
// Update payment method via service (handles admin_override_ customer IDs)
|
||||||
const subscription = await this.service.getSubscription(userId);
|
await this.service.updatePaymentMethod(userId, paymentMethodId, email);
|
||||||
if (!subscription) {
|
|
||||||
reply.status(404).send({
|
|
||||||
error: 'Subscription not found',
|
|
||||||
message: 'No subscription exists for this user',
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update payment method via Stripe
|
|
||||||
const stripeClient = new StripeClient();
|
|
||||||
await stripeClient.updatePaymentMethod(subscription.stripeCustomerId, paymentMethodId);
|
|
||||||
|
|
||||||
reply.status(200).send({
|
reply.status(200).send({
|
||||||
message: 'Payment method updated successfully',
|
message: 'Payment method updated successfully',
|
||||||
|
|||||||
@@ -146,6 +146,10 @@ export class SubscriptionsRepository {
|
|||||||
const values = [];
|
const values = [];
|
||||||
let paramCount = 1;
|
let paramCount = 1;
|
||||||
|
|
||||||
|
if (data.stripeCustomerId !== undefined) {
|
||||||
|
fields.push(`stripe_customer_id = $${paramCount++}`);
|
||||||
|
values.push(data.stripeCustomerId);
|
||||||
|
}
|
||||||
if (data.stripeSubscriptionId !== undefined) {
|
if (data.stripeSubscriptionId !== undefined) {
|
||||||
fields.push(`stripe_subscription_id = $${paramCount++}`);
|
fields.push(`stripe_subscription_id = $${paramCount++}`);
|
||||||
values.push(data.stripeSubscriptionId);
|
values.push(data.stripeSubscriptionId);
|
||||||
|
|||||||
@@ -165,6 +165,38 @@ export class SubscriptionsService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve admin_override_ placeholder customer IDs to real Stripe customers.
|
||||||
|
* When an admin overrides a user's tier without Stripe, a placeholder ID is stored.
|
||||||
|
* This method creates a real Stripe customer and updates the subscription record.
|
||||||
|
*/
|
||||||
|
private async resolveStripeCustomerId(
|
||||||
|
subscription: Subscription,
|
||||||
|
email: string
|
||||||
|
): Promise<string> {
|
||||||
|
if (!subscription.stripeCustomerId.startsWith('admin_override_')) {
|
||||||
|
return subscription.stripeCustomerId;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info('Replacing admin_override_ placeholder with real Stripe customer', {
|
||||||
|
subscriptionId: subscription.id,
|
||||||
|
userId: subscription.userId,
|
||||||
|
});
|
||||||
|
|
||||||
|
const stripeCustomer = await this.stripeClient.createCustomer(email);
|
||||||
|
|
||||||
|
await this.repository.update(subscription.id, {
|
||||||
|
stripeCustomerId: stripeCustomer.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.info('Stripe customer created for admin-overridden subscription', {
|
||||||
|
subscriptionId: subscription.id,
|
||||||
|
stripeCustomerId: stripeCustomer.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
return stripeCustomer.id;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Upgrade from current tier to new tier
|
* Upgrade from current tier to new tier
|
||||||
*/
|
*/
|
||||||
@@ -172,7 +204,8 @@ export class SubscriptionsService {
|
|||||||
userId: string,
|
userId: string,
|
||||||
newTier: 'pro' | 'enterprise',
|
newTier: 'pro' | 'enterprise',
|
||||||
billingCycle: 'monthly' | 'yearly',
|
billingCycle: 'monthly' | 'yearly',
|
||||||
paymentMethodId: string
|
paymentMethodId: string,
|
||||||
|
email: string
|
||||||
): Promise<Subscription> {
|
): Promise<Subscription> {
|
||||||
try {
|
try {
|
||||||
logger.info('Upgrading subscription', { userId, newTier, billingCycle });
|
logger.info('Upgrading subscription', { userId, newTier, billingCycle });
|
||||||
@@ -183,12 +216,15 @@ export class SubscriptionsService {
|
|||||||
throw new Error('No subscription found for user');
|
throw new Error('No subscription found for user');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Resolve admin_override_ placeholder to real Stripe customer if needed
|
||||||
|
const stripeCustomerId = await this.resolveStripeCustomerId(currentSubscription, email);
|
||||||
|
|
||||||
// Determine price ID from environment variables
|
// Determine price ID from environment variables
|
||||||
const priceId = this.getPriceId(newTier, billingCycle);
|
const priceId = this.getPriceId(newTier, billingCycle);
|
||||||
|
|
||||||
// Create or update Stripe subscription
|
// Create or update Stripe subscription
|
||||||
const stripeSubscription = await this.stripeClient.createSubscription(
|
const stripeSubscription = await this.stripeClient.createSubscription(
|
||||||
currentSubscription.stripeCustomerId,
|
stripeCustomerId,
|
||||||
priceId,
|
priceId,
|
||||||
paymentMethodId
|
paymentMethodId
|
||||||
);
|
);
|
||||||
@@ -923,6 +959,19 @@ export class SubscriptionsService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update payment method for a user's subscription
|
||||||
|
*/
|
||||||
|
async updatePaymentMethod(userId: string, paymentMethodId: string, email: string): Promise<void> {
|
||||||
|
const subscription = await this.repository.findByUserId(userId);
|
||||||
|
if (!subscription) {
|
||||||
|
throw new Error('No subscription found for user');
|
||||||
|
}
|
||||||
|
|
||||||
|
const stripeCustomerId = await this.resolveStripeCustomerId(subscription, email);
|
||||||
|
await this.stripeClient.updatePaymentMethod(stripeCustomerId, paymentMethodId);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get invoices for a user's subscription
|
* Get invoices for a user's subscription
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -118,6 +118,7 @@ export interface CreateTierVehicleSelectionRequest {
|
|||||||
|
|
||||||
// Service layer types
|
// Service layer types
|
||||||
export interface UpdateSubscriptionData {
|
export interface UpdateSubscriptionData {
|
||||||
|
stripeCustomerId?: string;
|
||||||
stripeSubscriptionId?: string;
|
stripeSubscriptionId?: string;
|
||||||
tier?: SubscriptionTier;
|
tier?: SubscriptionTier;
|
||||||
billingCycle?: BillingCycle;
|
billingCycle?: BillingCycle;
|
||||||
|
|||||||
Reference in New Issue
Block a user