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
|
||||
// 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, {
|
||||
stripeSubscriptionId: stripeSubscription.id,
|
||||
status: this.mapStripeStatus(stripeSubscription.status),
|
||||
currentPeriodStart: new Date(stripeSubscription.current_period_start * 1000),
|
||||
currentPeriodEnd: new Date(stripeSubscription.current_period_end * 1000),
|
||||
currentPeriodStart: new Date((item?.current_period_start ?? 0) * 1000),
|
||||
currentPeriodEnd: new Date((item?.current_period_end ?? 0) * 1000),
|
||||
});
|
||||
|
||||
// Log event
|
||||
@@ -608,11 +610,13 @@ export class SubscriptionsService {
|
||||
const tier = this.determineTierFromStripeSubscription(stripeSubscription);
|
||||
|
||||
// Update subscription
|
||||
// Period dates moved from subscription to items in API 2025-03-31.basil
|
||||
const item = stripeSubscription.items?.data?.[0];
|
||||
const updateData: UpdateSubscriptionData = {
|
||||
status: this.mapStripeStatus(stripeSubscription.status),
|
||||
tier,
|
||||
currentPeriodStart: new Date(stripeSubscription.current_period_start * 1000),
|
||||
currentPeriodEnd: new Date(stripeSubscription.current_period_end * 1000),
|
||||
currentPeriodStart: new Date((item?.current_period_start ?? 0) * 1000),
|
||||
currentPeriodEnd: new Date((item?.current_period_end ?? 0) * 1000),
|
||||
cancelAtPeriodEnd: stripeSubscription.cancel_at_period_end || false,
|
||||
};
|
||||
|
||||
@@ -849,6 +853,7 @@ export class SubscriptionsService {
|
||||
switch (stripeStatus) {
|
||||
case 'active':
|
||||
case 'trialing':
|
||||
case 'incomplete':
|
||||
return 'active';
|
||||
case 'past_due':
|
||||
return 'past_due';
|
||||
|
||||
@@ -86,7 +86,7 @@ export class StripeClient {
|
||||
const subscriptionParams: Stripe.SubscriptionCreateParams = {
|
||||
customer: customerId,
|
||||
items: [{ price: priceId }],
|
||||
payment_behavior: 'default_incomplete',
|
||||
payment_behavior: 'error_if_incomplete',
|
||||
payment_settings: {
|
||||
save_default_payment_method: 'on_subscription',
|
||||
},
|
||||
@@ -101,13 +101,16 @@ export class StripeClient {
|
||||
|
||||
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 {
|
||||
id: subscription.id,
|
||||
customer: subscription.customer as string,
|
||||
status: subscription.status as StripeSubscription['status'],
|
||||
items: subscription.items,
|
||||
currentPeriodStart: (subscription as any).current_period_start || 0,
|
||||
currentPeriodEnd: (subscription as any).current_period_end || 0,
|
||||
currentPeriodStart: item?.current_period_start ?? 0,
|
||||
currentPeriodEnd: item?.current_period_end ?? 0,
|
||||
cancelAtPeriodEnd: subscription.cancel_at_period_end,
|
||||
canceledAt: subscription.canceled_at || undefined,
|
||||
created: subscription.created,
|
||||
@@ -148,13 +151,15 @@ export class StripeClient {
|
||||
logger.info('Stripe subscription canceled immediately', { subscriptionId });
|
||||
}
|
||||
|
||||
const item = subscription.items?.data?.[0];
|
||||
|
||||
return {
|
||||
id: subscription.id,
|
||||
customer: subscription.customer as string,
|
||||
status: subscription.status as StripeSubscription['status'],
|
||||
items: subscription.items,
|
||||
currentPeriodStart: (subscription as any).current_period_start || 0,
|
||||
currentPeriodEnd: (subscription as any).current_period_end || 0,
|
||||
currentPeriodStart: item?.current_period_start ?? 0,
|
||||
currentPeriodEnd: item?.current_period_end ?? 0,
|
||||
cancelAtPeriodEnd: subscription.cancel_at_period_end,
|
||||
canceledAt: subscription.canceled_at || undefined,
|
||||
created: subscription.created,
|
||||
@@ -294,14 +299,15 @@ export class StripeClient {
|
||||
logger.info('Retrieving Stripe subscription', { subscriptionId });
|
||||
|
||||
const subscription = await this.stripe.subscriptions.retrieve(subscriptionId);
|
||||
const item = subscription.items?.data?.[0];
|
||||
|
||||
return {
|
||||
id: subscription.id,
|
||||
customer: subscription.customer as string,
|
||||
status: subscription.status as StripeSubscription['status'],
|
||||
items: subscription.items,
|
||||
currentPeriodStart: (subscription as any).current_period_start || 0,
|
||||
currentPeriodEnd: (subscription as any).current_period_end || 0,
|
||||
currentPeriodStart: item?.current_period_start ?? 0,
|
||||
currentPeriodEnd: item?.current_period_end ?? 0,
|
||||
cancelAtPeriodEnd: subscription.cancel_at_period_end,
|
||||
canceledAt: subscription.canceled_at || undefined,
|
||||
created: subscription.created,
|
||||
|
||||
Reference in New Issue
Block a user