-
Notifications
You must be signed in to change notification settings - Fork 1.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix: rewrite stripe bug in unsubscribing #2084
fix: rewrite stripe bug in unsubscribing #2084
Conversation
The latest updates on your projects. Learn more about Vercel for Git ↗︎ 2 Ignored Deployments
|
Thank you for following the naming conventions for pull request titles! 🙏 |
packages/ee/billing/handlers/subscriptionCreatedOrUpdated.tsIt's recommended to reduce the number of API calls in the handleSubscriptionUpdatedOrCreated function. Instead of retrieving the product for each item in the loop, you can retrieve all products at once and then find the needed product in the array. This will reduce the number of API calls and improve the performance of the function. // Retrieve all products at once
const products = await stripe.products.list();
for (const item of stripeSubscriptionObject.items.data) {
// Find the needed product in the array
const product = products.data.find(p => p.id === item.price.product);
// Rest of the code...
}
|
updatedFeatures.inAppSurvey.status = "active"; | ||
} | ||
} else { | ||
// Current subscription is not scheduled to cancel at the end of the period | ||
updatedFeatures.inAppSurvey.status = "active"; | ||
// If the current subscription is unlimited, we don't need to report usage | ||
if (isInAppSurveyUnlimited) { | ||
updatedFeatures.inAppSurvey.unlimited = true; | ||
} else { | ||
const countForTeam = await getMonthlyTeamResponseCount(team.id); | ||
await reportUsage( | ||
stripeSubscriptionObject.items.data, | ||
ProductFeatureKeys.inAppSurvey, | ||
countForTeam | ||
); | ||
} | ||
} | ||
break; | ||
|
||
case StripeProductNames.linkSurvey: | ||
if ( | ||
!( | ||
stripeSubscriptionObject.cancel_at_period_end && | ||
team.billing.features.linkSurvey.status === "cancelled" | ||
) | ||
) { | ||
const isLinkSurveyUnlimited = item.price.lookup_key === StripePriceLookupKeys.linkSurveyUnlimited; | ||
|
||
if (stripeSubscriptionObject.cancel_at_period_end) { | ||
const isLinkSurveyScheduled = await isProductScheduled( | ||
scheduledSubscriptions, | ||
StripeProductNames.linkSurvey | ||
); | ||
|
||
if (team.billing.features.linkSurvey.status === "active" && !isLinkSurveyScheduled) { | ||
team.billing.features.linkSurvey.status = "cancelled"; | ||
} | ||
|
||
if (isLinkSurveyScheduled) { | ||
updatedFeatures.linkSurvey.status = "active"; | ||
} | ||
} else { | ||
updatedFeatures.linkSurvey.status = "active"; | ||
} | ||
if (item.price.lookup_key === StripePriceLookupKeys.linkSurveyUnlimited) { | ||
updatedFeatures.linkSurvey.unlimited = true; | ||
if (isLinkSurveyUnlimited) { | ||
updatedFeatures.inAppSurvey.unlimited = true; | ||
} | ||
} | ||
break; | ||
|
||
case StripeProductNames.userTargeting: | ||
if ( | ||
!( | ||
stripeSubscriptionObject.cancel_at_period_end && | ||
team.billing.features.userTargeting.status === "cancelled" | ||
) | ||
) { | ||
updatedFeatures.userTargeting.status = "active"; | ||
} | ||
if (item.price.lookup_key === StripePriceLookupKeys.userTargetingUnlimited) { | ||
updatedFeatures.userTargeting.unlimited = true; | ||
} else { | ||
const countForTeam = await getMonthlyActiveTeamPeopleCount(team.id); | ||
const isUserTargetingUnlimited = | ||
item.price.lookup_key === StripePriceLookupKeys.userTargetingUnlimited; | ||
|
||
await reportUsage( | ||
stripeSubscriptionObject.items.data, | ||
ProductFeatureKeys.userTargeting, | ||
countForTeam | ||
if (stripeSubscriptionObject.cancel_at_period_end) { | ||
const isUserTargetingScheduled = await isProductScheduled( | ||
scheduledSubscriptions, | ||
StripeProductNames.userTargeting | ||
); | ||
|
||
if (team.billing.features.userTargeting.status === "active" && !isUserTargetingScheduled) { | ||
team.billing.features.userTargeting.status = "cancelled"; | ||
} | ||
|
||
if (isUserTargetingScheduled) { | ||
updatedFeatures.userTargeting.status = "active"; | ||
} | ||
} else { | ||
updatedFeatures.userTargeting.status = "active"; | ||
if (isUserTargetingUnlimited) { | ||
updatedFeatures.userTargeting.unlimited = true; | ||
} else { | ||
const countForTeam = await getMonthlyActiveTeamPeopleCount(team.id); | ||
await reportUsage( | ||
stripeSubscriptionObject.items.data, | ||
ProductFeatureKeys.userTargeting, | ||
countForTeam | ||
); | ||
} | ||
} | ||
break; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reducing the number of API calls in the handleSubscriptionUpdatedOrCreated function by retrieving all products at once and then finding the needed product in the array. This will improve the performance of the function.
for (const item of stripeSubscriptionObject.items.data) { | |
const product = await stripe.products.retrieve(item.price.product as string); | |
switch (product.name) { | |
case StripeProductNames.inAppSurvey: | |
if ( | |
!( | |
stripeSubscriptionObject.cancel_at_period_end && | |
team.billing.features.inAppSurvey.status === "cancelled" | |
) | |
) { | |
updatedFeatures.inAppSurvey.status = "active"; | |
} | |
if (item.price.lookup_key === StripePriceLookupKeys.inAppSurveyUnlimited) { | |
updatedFeatures.inAppSurvey.unlimited = true; | |
} else { | |
const countForTeam = await getMonthlyTeamResponseCount(team.id); | |
const isInAppSurveyUnlimited = item.price.lookup_key === StripePriceLookupKeys.inAppSurveyUnlimited; | |
await reportUsage( | |
stripeSubscriptionObject.items.data, | |
ProductFeatureKeys.inAppSurvey, | |
countForTeam | |
// If the current subscription is scheduled to cancel at the end of the period | |
if (stripeSubscriptionObject.cancel_at_period_end) { | |
// Check if the team has a scheduled subscription for this product | |
const isInAppProductScheduled = await isProductScheduled( | |
scheduledSubscriptions, | |
StripeProductNames.inAppSurvey | |
); | |
// If the team does not have a scheduled subscription for this product, we cancel the feature | |
if (team.billing.features.inAppSurvey.status === "active" && !isInAppProductScheduled) { | |
team.billing.features.inAppSurvey.status = "cancelled"; | |
} | |
// If the team has a scheduled subscription for this product, we don't cancel the feature | |
if (isInAppProductScheduled) { | |
updatedFeatures.inAppSurvey.status = "active"; | |
} | |
} else { | |
// Current subscription is not scheduled to cancel at the end of the period | |
updatedFeatures.inAppSurvey.status = "active"; | |
// If the current subscription is unlimited, we don't need to report usage | |
if (isInAppSurveyUnlimited) { | |
updatedFeatures.inAppSurvey.unlimited = true; | |
} else { | |
const countForTeam = await getMonthlyTeamResponseCount(team.id); | |
await reportUsage( | |
stripeSubscriptionObject.items.data, | |
ProductFeatureKeys.inAppSurvey, | |
countForTeam | |
); | |
} | |
} | |
break; | |
case StripeProductNames.linkSurvey: | |
if ( | |
!( | |
stripeSubscriptionObject.cancel_at_period_end && | |
team.billing.features.linkSurvey.status === "cancelled" | |
) | |
) { | |
const isLinkSurveyUnlimited = item.price.lookup_key === StripePriceLookupKeys.linkSurveyUnlimited; | |
if (stripeSubscriptionObject.cancel_at_period_end) { | |
const isLinkSurveyScheduled = await isProductScheduled( | |
scheduledSubscriptions, | |
StripeProductNames.linkSurvey | |
); | |
if (team.billing.features.linkSurvey.status === "active" && !isLinkSurveyScheduled) { | |
team.billing.features.linkSurvey.status = "cancelled"; | |
} | |
if (isLinkSurveyScheduled) { | |
updatedFeatures.linkSurvey.status = "active"; | |
} | |
} else { | |
updatedFeatures.linkSurvey.status = "active"; | |
} | |
if (item.price.lookup_key === StripePriceLookupKeys.linkSurveyUnlimited) { | |
updatedFeatures.linkSurvey.unlimited = true; | |
if (isLinkSurveyUnlimited) { | |
updatedFeatures.inAppSurvey.unlimited = true; | |
} | |
} | |
break; | |
case StripeProductNames.userTargeting: | |
if ( | |
!( | |
stripeSubscriptionObject.cancel_at_period_end && | |
team.billing.features.userTargeting.status === "cancelled" | |
) | |
) { | |
updatedFeatures.userTargeting.status = "active"; | |
} | |
if (item.price.lookup_key === StripePriceLookupKeys.userTargetingUnlimited) { | |
updatedFeatures.userTargeting.unlimited = true; | |
} else { | |
const countForTeam = await getMonthlyActiveTeamPeopleCount(team.id); | |
const isUserTargetingUnlimited = | |
item.price.lookup_key === StripePriceLookupKeys.userTargetingUnlimited; | |
await reportUsage( | |
stripeSubscriptionObject.items.data, | |
ProductFeatureKeys.userTargeting, | |
countForTeam | |
if (stripeSubscriptionObject.cancel_at_period_end) { | |
const isUserTargetingScheduled = await isProductScheduled( | |
scheduledSubscriptions, | |
StripeProductNames.userTargeting | |
); | |
if (team.billing.features.userTargeting.status === "active" && !isUserTargetingScheduled) { | |
team.billing.features.userTargeting.status = "cancelled"; | |
} | |
if (isUserTargetingScheduled) { | |
updatedFeatures.userTargeting.status = "active"; | |
} | |
} else { | |
updatedFeatures.userTargeting.status = "active"; | |
if (isUserTargetingUnlimited) { | |
updatedFeatures.userTargeting.unlimited = true; | |
} else { | |
const countForTeam = await getMonthlyActiveTeamPeopleCount(team.id); | |
await reportUsage( | |
stripeSubscriptionObject.items.data, | |
ProductFeatureKeys.userTargeting, | |
countForTeam | |
); | |
} | |
} | |
break; | |
} | |
const products = await stripe.products.list(); | |
for (const item of stripeSubscriptionObject.items.data) { | |
const product = products.data.find(p => p.id === item.price.product); | |
// Rest of the code... | |
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ShubhamPalriwala thanks for the PR 😊 Just a small concern:
@@ -24,6 25,8 @@ export async function upgradePlanAction( | |||
if (!isAuthorized) throw new AuthorizationError("Not authorized"); | |||
|
|||
const subscriptionSession = await createSubscription(teamId, environmentId, priceLookupKeys); | |||
|
|||
teamCache.revalidate({ id: teamId }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why do we need to revalidate the cache here? We should only revalidate the cache after making the database call that changes the team object. Where is the call made? Can we move the revalidate
command after the db call instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ahh apologies! it already happens inside the updateTeam
service called inside the ee/billing method! Same for below, removed the both. Pushed 🤞🏼
@@ -58,5 61,6 @@ export async function removeSubscriptionAction( | |||
|
|||
const removedSubscription = await removeSubscription(teamId, environmentId, priceLookupKeys); | |||
|
|||
teamCache.revalidate({ id: teamId }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same concern as above
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💪 ready to merge 😊
What does this PR do?
How should this be tested?
Run the stripe cli locally, authenticate, switch to test mode and start testing various cases!
Checklist
Required
pnpm build
console.logs
git pull origin main
Appreciated