fix: charge immediately on subscription and read item-level period dates
All checks were successful
Deploy to Staging / Build Images (push) Successful in 3m39s
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 3m39s
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
Three fixes to the Stripe subscription flow: 1. Change payment_behavior from 'default_incomplete' to 'error_if_incomplete' so Stripe charges the card immediately instead of leaving the subscription in incomplete status waiting for frontend payment confirmation that never happens. 2. Read currentPeriodStart/End from subscription items instead of the top-level subscription object. Stripe moved these fields to items.data[0] in API version 2025-03-31.basil, causing epoch-zero dates (Dec 31, 1969). 3. Map Stripe 'incomplete' status to 'active' in mapStripeStatus() so it doesn't fall through to the default 'canceled' mapping. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -570,11 +570,13 @@ export class SubscriptionsService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Update subscription with Stripe subscription ID
|
// Update subscription with Stripe subscription ID
|
||||||
|
// Period dates moved from subscription to items in API 2025-03-31.basil
|
||||||
|
const item = stripeSubscription.items?.data?.[0];
|
||||||
await this.repository.update(subscription.id, {
|
await this.repository.update(subscription.id, {
|
||||||
stripeSubscriptionId: stripeSubscription.id,
|
stripeSubscriptionId: stripeSubscription.id,
|
||||||
status: this.mapStripeStatus(stripeSubscription.status),
|
status: this.mapStripeStatus(stripeSubscription.status),
|
||||||
currentPeriodStart: new Date(stripeSubscription.current_period_start * 1000),
|
currentPeriodStart: new Date((item?.current_period_start ?? 0) * 1000),
|
||||||
currentPeriodEnd: new Date(stripeSubscription.current_period_end * 1000),
|
currentPeriodEnd: new Date((item?.current_period_end ?? 0) * 1000),
|
||||||
});
|
});
|
||||||
|
|
||||||
// Log event
|
// Log event
|
||||||
@@ -608,11 +610,13 @@ export class SubscriptionsService {
|
|||||||
const tier = this.determineTierFromStripeSubscription(stripeSubscription);
|
const tier = this.determineTierFromStripeSubscription(stripeSubscription);
|
||||||
|
|
||||||
// Update subscription
|
// Update subscription
|
||||||
|
// Period dates moved from subscription to items in API 2025-03-31.basil
|
||||||
|
const item = stripeSubscription.items?.data?.[0];
|
||||||
const updateData: UpdateSubscriptionData = {
|
const updateData: UpdateSubscriptionData = {
|
||||||
status: this.mapStripeStatus(stripeSubscription.status),
|
status: this.mapStripeStatus(stripeSubscription.status),
|
||||||
tier,
|
tier,
|
||||||
currentPeriodStart: new Date(stripeSubscription.current_period_start * 1000),
|
currentPeriodStart: new Date((item?.current_period_start ?? 0) * 1000),
|
||||||
currentPeriodEnd: new Date(stripeSubscription.current_period_end * 1000),
|
currentPeriodEnd: new Date((item?.current_period_end ?? 0) * 1000),
|
||||||
cancelAtPeriodEnd: stripeSubscription.cancel_at_period_end || false,
|
cancelAtPeriodEnd: stripeSubscription.cancel_at_period_end || false,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -849,6 +853,7 @@ export class SubscriptionsService {
|
|||||||
switch (stripeStatus) {
|
switch (stripeStatus) {
|
||||||
case 'active':
|
case 'active':
|
||||||
case 'trialing':
|
case 'trialing':
|
||||||
|
case 'incomplete':
|
||||||
return 'active';
|
return 'active';
|
||||||
case 'past_due':
|
case 'past_due':
|
||||||
return 'past_due';
|
return 'past_due';
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ export class StripeClient {
|
|||||||
const subscriptionParams: Stripe.SubscriptionCreateParams = {
|
const subscriptionParams: Stripe.SubscriptionCreateParams = {
|
||||||
customer: customerId,
|
customer: customerId,
|
||||||
items: [{ price: priceId }],
|
items: [{ price: priceId }],
|
||||||
payment_behavior: 'default_incomplete',
|
payment_behavior: 'error_if_incomplete',
|
||||||
payment_settings: {
|
payment_settings: {
|
||||||
save_default_payment_method: 'on_subscription',
|
save_default_payment_method: 'on_subscription',
|
||||||
},
|
},
|
||||||
@@ -101,13 +101,16 @@ export class StripeClient {
|
|||||||
|
|
||||||
logger.info('Stripe subscription created', { subscriptionId: subscription.id });
|
logger.info('Stripe subscription created', { subscriptionId: subscription.id });
|
||||||
|
|
||||||
|
// Period dates moved from subscription to items in API 2025-03-31.basil
|
||||||
|
const item = subscription.items?.data?.[0];
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: subscription.id,
|
id: subscription.id,
|
||||||
customer: subscription.customer as string,
|
customer: subscription.customer as string,
|
||||||
status: subscription.status as StripeSubscription['status'],
|
status: subscription.status as StripeSubscription['status'],
|
||||||
items: subscription.items,
|
items: subscription.items,
|
||||||
currentPeriodStart: (subscription as any).current_period_start || 0,
|
currentPeriodStart: item?.current_period_start ?? 0,
|
||||||
currentPeriodEnd: (subscription as any).current_period_end || 0,
|
currentPeriodEnd: item?.current_period_end ?? 0,
|
||||||
cancelAtPeriodEnd: subscription.cancel_at_period_end,
|
cancelAtPeriodEnd: subscription.cancel_at_period_end,
|
||||||
canceledAt: subscription.canceled_at || undefined,
|
canceledAt: subscription.canceled_at || undefined,
|
||||||
created: subscription.created,
|
created: subscription.created,
|
||||||
@@ -148,13 +151,15 @@ export class StripeClient {
|
|||||||
logger.info('Stripe subscription canceled immediately', { subscriptionId });
|
logger.info('Stripe subscription canceled immediately', { subscriptionId });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const item = subscription.items?.data?.[0];
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: subscription.id,
|
id: subscription.id,
|
||||||
customer: subscription.customer as string,
|
customer: subscription.customer as string,
|
||||||
status: subscription.status as StripeSubscription['status'],
|
status: subscription.status as StripeSubscription['status'],
|
||||||
items: subscription.items,
|
items: subscription.items,
|
||||||
currentPeriodStart: (subscription as any).current_period_start || 0,
|
currentPeriodStart: item?.current_period_start ?? 0,
|
||||||
currentPeriodEnd: (subscription as any).current_period_end || 0,
|
currentPeriodEnd: item?.current_period_end ?? 0,
|
||||||
cancelAtPeriodEnd: subscription.cancel_at_period_end,
|
cancelAtPeriodEnd: subscription.cancel_at_period_end,
|
||||||
canceledAt: subscription.canceled_at || undefined,
|
canceledAt: subscription.canceled_at || undefined,
|
||||||
created: subscription.created,
|
created: subscription.created,
|
||||||
@@ -294,14 +299,15 @@ export class StripeClient {
|
|||||||
logger.info('Retrieving Stripe subscription', { subscriptionId });
|
logger.info('Retrieving Stripe subscription', { subscriptionId });
|
||||||
|
|
||||||
const subscription = await this.stripe.subscriptions.retrieve(subscriptionId);
|
const subscription = await this.stripe.subscriptions.retrieve(subscriptionId);
|
||||||
|
const item = subscription.items?.data?.[0];
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: subscription.id,
|
id: subscription.id,
|
||||||
customer: subscription.customer as string,
|
customer: subscription.customer as string,
|
||||||
status: subscription.status as StripeSubscription['status'],
|
status: subscription.status as StripeSubscription['status'],
|
||||||
items: subscription.items,
|
items: subscription.items,
|
||||||
currentPeriodStart: (subscription as any).current_period_start || 0,
|
currentPeriodStart: item?.current_period_start ?? 0,
|
||||||
currentPeriodEnd: (subscription as any).current_period_end || 0,
|
currentPeriodEnd: item?.current_period_end ?? 0,
|
||||||
cancelAtPeriodEnd: subscription.cancel_at_period_end,
|
cancelAtPeriodEnd: subscription.cancel_at_period_end,
|
||||||
canceledAt: subscription.canceled_at || undefined,
|
canceledAt: subscription.canceled_at || undefined,
|
||||||
created: subscription.created,
|
created: subscription.created,
|
||||||
|
|||||||
Reference in New Issue
Block a user