refactor(server): saving past_due subscription in db (#10908)
close CLOUD-122
This commit is contained in:
@@ -107,7 +107,7 @@ export class UserFeatureModel extends BaseModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async remove(userId: string, featureName: UserFeatureName) {
|
async remove(userId: string, featureName: UserFeatureName) {
|
||||||
await this.db.userFeature.updateMany({
|
const { count } = await this.db.userFeature.updateMany({
|
||||||
where: {
|
where: {
|
||||||
userId,
|
userId,
|
||||||
name: featureName,
|
name: featureName,
|
||||||
@@ -117,9 +117,11 @@ export class UserFeatureModel extends BaseModel {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
this.logger.verbose(
|
if (count > 0) {
|
||||||
`Feature ${featureName} deactivated for user ${userId}`
|
this.logger.verbose(
|
||||||
);
|
`Feature ${featureName} deactivated for user ${userId}`
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Transactional()
|
@Transactional()
|
||||||
|
|||||||
@@ -194,15 +194,17 @@ export class WorkspaceFeatureModel extends BaseModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async remove(workspaceId: string, featureName: WorkspaceFeatureName) {
|
async remove(workspaceId: string, featureName: WorkspaceFeatureName) {
|
||||||
await this.db.workspaceFeature.deleteMany({
|
const { count } = await this.db.workspaceFeature.deleteMany({
|
||||||
where: {
|
where: {
|
||||||
workspaceId,
|
workspaceId,
|
||||||
name: featureName,
|
name: featureName,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
this.logger.verbose(
|
if (count > 0) {
|
||||||
`Feature ${featureName} removed from workspace ${workspaceId}`
|
this.logger.verbose(
|
||||||
);
|
`Feature ${featureName} removed from workspace ${workspaceId}`
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -213,14 +213,22 @@ export class UserSubscriptionManager extends SubscriptionManager {
|
|||||||
|
|
||||||
// update features first, features modify are idempotent
|
// update features first, features modify are idempotent
|
||||||
// so there is no need to skip if a subscription already exists.
|
// so there is no need to skip if a subscription already exists.
|
||||||
// TODO(@forehalo):
|
if (
|
||||||
// we should move the subscription feature updating logic back to payment module,
|
stripeSubscription.status === SubscriptionStatus.Active ||
|
||||||
// because quota or feature module themself should not be aware of what payment or subscription is.
|
stripeSubscription.status === SubscriptionStatus.Trialing
|
||||||
this.event.emit('user.subscription.activated', {
|
) {
|
||||||
userId,
|
this.event.emit('user.subscription.activated', {
|
||||||
plan: lookupKey.plan,
|
userId,
|
||||||
recurring: lookupKey.recurring,
|
plan: lookupKey.plan,
|
||||||
});
|
recurring: lookupKey.recurring,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.event.emit('user.subscription.canceled', {
|
||||||
|
userId,
|
||||||
|
plan: lookupKey.plan,
|
||||||
|
recurring: lookupKey.recurring,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const subscriptionData = this.transformSubscription(subscription);
|
const subscriptionData = this.transformSubscription(subscription);
|
||||||
|
|
||||||
@@ -247,20 +255,17 @@ export class UserSubscriptionManager extends SubscriptionManager {
|
|||||||
stripeSubscription,
|
stripeSubscription,
|
||||||
}: KnownStripeSubscription) {
|
}: KnownStripeSubscription) {
|
||||||
this.assertUserIdExists(userId);
|
this.assertUserIdExists(userId);
|
||||||
|
this.event.emit('user.subscription.canceled', {
|
||||||
|
userId,
|
||||||
|
plan: lookupKey.plan,
|
||||||
|
recurring: lookupKey.recurring,
|
||||||
|
});
|
||||||
|
|
||||||
const deleted = await this.db.subscription.deleteMany({
|
await this.db.subscription.deleteMany({
|
||||||
where: {
|
where: {
|
||||||
stripeSubscriptionId: stripeSubscription.id,
|
stripeSubscriptionId: stripeSubscription.id,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (deleted.count > 0) {
|
|
||||||
this.event.emit('user.subscription.canceled', {
|
|
||||||
userId,
|
|
||||||
plan: lookupKey.plan,
|
|
||||||
recurring: lookupKey.recurring,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async cancelSubscription(subscription: Subscription) {
|
async cancelSubscription(subscription: Subscription) {
|
||||||
|
|||||||
@@ -137,12 +137,23 @@ export class WorkspaceSubscriptionManager extends SubscriptionManager {
|
|||||||
|
|
||||||
const subscriptionData = this.transformSubscription(subscription);
|
const subscriptionData = this.transformSubscription(subscription);
|
||||||
|
|
||||||
this.event.emit('workspace.subscription.activated', {
|
if (
|
||||||
workspaceId,
|
stripeSubscription.status === SubscriptionStatus.Active ||
|
||||||
plan: lookupKey.plan,
|
stripeSubscription.status === SubscriptionStatus.Trialing
|
||||||
recurring: lookupKey.recurring,
|
) {
|
||||||
quantity: subscriptionData.quantity,
|
this.event.emit('workspace.subscription.activated', {
|
||||||
});
|
workspaceId,
|
||||||
|
plan: lookupKey.plan,
|
||||||
|
recurring: lookupKey.recurring,
|
||||||
|
quantity: subscriptionData.quantity,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.event.emit('workspace.subscription.canceled', {
|
||||||
|
workspaceId,
|
||||||
|
plan: lookupKey.plan,
|
||||||
|
recurring: lookupKey.recurring,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return this.db.subscription.upsert({
|
return this.db.subscription.upsert({
|
||||||
where: {
|
where: {
|
||||||
@@ -176,17 +187,15 @@ export class WorkspaceSubscriptionManager extends SubscriptionManager {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const deleted = await this.db.subscription.deleteMany({
|
this.event.emit('workspace.subscription.canceled', {
|
||||||
where: { stripeSubscriptionId: stripeSubscription.id },
|
workspaceId,
|
||||||
|
plan: lookupKey.plan,
|
||||||
|
recurring: lookupKey.recurring,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (deleted.count > 0) {
|
await this.db.subscription.deleteMany({
|
||||||
this.event.emit('workspace.subscription.canceled', {
|
where: { stripeSubscriptionId: stripeSubscription.id },
|
||||||
workspaceId,
|
});
|
||||||
plan: lookupKey.plan,
|
|
||||||
recurring: lookupKey.recurring,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getSubscription(identity: z.infer<typeof WorkspaceSubscriptionIdentity>) {
|
getSubscription(identity: z.infer<typeof WorkspaceSubscriptionIdentity>) {
|
||||||
|
|||||||
@@ -467,13 +467,17 @@ export class SubscriptionService implements OnApplicationBootstrap {
|
|||||||
throw new InternalServerError('Failed to parse stripe subscription.');
|
throw new InternalServerError('Failed to parse stripe subscription.');
|
||||||
}
|
}
|
||||||
|
|
||||||
const isPlanActive =
|
const shouldSave =
|
||||||
subscription.status === SubscriptionStatus.Active ||
|
subscription.status === SubscriptionStatus.Active ||
|
||||||
subscription.status === SubscriptionStatus.Trialing;
|
subscription.status === SubscriptionStatus.Trialing ||
|
||||||
|
// PastDue is a temporary status, it will be cancelled after all recurring payments retries failed.
|
||||||
|
// Saved in db to let users be able to cancel further retries manually.
|
||||||
|
subscription.status === SubscriptionStatus.PastDue;
|
||||||
|
|
||||||
const manager = this.select(knownSubscription.lookupKey.plan);
|
const manager = this.select(knownSubscription.lookupKey.plan);
|
||||||
|
|
||||||
if (!isPlanActive) {
|
// TODO(@forehalo): trigger 'subscription.status.changed' event to let strategy handle them. after migrated to Model
|
||||||
|
if (!shouldSave) {
|
||||||
await manager.deleteStripeSubscription(knownSubscription);
|
await manager.deleteStripeSubscription(knownSubscription);
|
||||||
} else {
|
} else {
|
||||||
await manager.saveStripeSubscription(knownSubscription);
|
await manager.saveStripeSubscription(knownSubscription);
|
||||||
|
|||||||
Reference in New Issue
Block a user