Added support for ignoring migrated (duplicate) subscriptions (#19902)

refs KTLO-19

When we need to migrate subscriptions from a platform with platform
fees, we need to recreate the subscriptions. That can cause the same
subscription to be attached multiple times to the same member in Ghost.

This is a problem because all MRR, subscriptions and cancellations stats
are no longer correct. Ghost will add a MRR event for the duplicated
subscription from the start time, so there is a sudden peak in MRR and a
dip after the migration because all those duplicate subscriptions are
suddenly cancelled 'today'.

The migrator tool adds a ghost_migrated_to metadata field to the old
subscription. Ghost can use this to detect the old subscription and
delete the subscription and corresponding events.
This commit is contained in:
Simon Backx 2024-03-27 10:32:32 +01:00 committed by GitHub
parent 08553f63f8
commit 3b8fb3cedf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -1025,9 +1025,21 @@ module.exports = class MemberRepository {
return 'inactive';
};
let eventData = {};
if (model) {
const shouldBeDeleted = subscription.metadata && !!subscription.metadata.ghost_migrated_to && subscription.status === 'canceled';
if (shouldBeDeleted) {
logging.warn(`Subscription ${subscriptionData.subscription_id} is marked for deletion, skipping linking.`);
if (model) {
// Delete all paid subscription events manually for this subscription
// This is the only related event without a foreign key constraint
await this._MemberPaidSubscriptionEvent.query().where('subscription_id', model.id).delete().transacting(options.transacting);
// Delete the subscription in the database because we don't want to show it in the UI or in our data calculations
await model.destroy(options);
}
} else if (model) {
// CASE: Offer is already mapped against sub, don't overwrite it with NULL
// Needed for trial offers, which don't have a stripe coupon/discount attached to sub
if (!subscriptionData.offer_id) {
@ -1133,7 +1145,7 @@ module.exports = class MemberRepository {
let memberProducts = (await member.related('products').fetch(options)).toJSON();
const oldMemberProducts = member.related('products').toJSON();
let status = memberProducts.length === 0 ? 'free' : 'comped';
if (this.isActiveSubscriptionStatus(subscription.status)) {
if (!shouldBeDeleted && this.isActiveSubscriptionStatus(subscription.status)) {
if (this.isComplimentarySubscription(subscription)) {
status = 'comped';
} else {