From c154be4581b88ebb0fb8c7f0fe4053ffddf60ba1 Mon Sep 17 00:00:00 2001 From: Fabien O'Carroll Date: Wed, 20 Oct 2021 14:32:41 +0200 Subject: [PATCH] Included Offer information for Subscriptions refs https://github.com/TryGhost/Team/issues/1135 We use the OffersAPI to fetch Offers, so that we can be using the same format for Offers in all of our APIs. We will not attach the Offer to the Subscription if either the Tier or the Cadence do not match. This is because the Offer would no longer apply to this Subscription. We do however retain the data, so that a Member can still be filtered on the Offers which they've redeemed. --- ghost/members-api/lib/MembersAPI.js | 1 + .../members-api/lib/services/member-bread.js | 50 ++++++++++++++++++- 2 files changed, 49 insertions(+), 2 deletions(-) diff --git a/ghost/members-api/lib/MembersAPI.js b/ghost/members-api/lib/MembersAPI.js index a50e6a5ed4..c91d284cc9 100644 --- a/ghost/members-api/lib/MembersAPI.js +++ b/ghost/members-api/lib/MembersAPI.js @@ -120,6 +120,7 @@ module.exports = function MembersAPI({ }); const memberBREADService = new MemberBREADService({ + offersAPI, memberRepository, emailService: { sendEmailWithMagicLink diff --git a/ghost/members-api/lib/services/member-bread.js b/ghost/members-api/lib/services/member-bread.js index 61b558e1d9..a54907ad2a 100644 --- a/ghost/members-api/lib/services/member-bread.js +++ b/ghost/members-api/lib/services/member-bread.js @@ -26,11 +26,13 @@ module.exports = class MemberBREADService { /** * @param {object} deps * @param {import('../repositories/member')} deps.memberRepository + * @param {import('@tryghost/members-offers/lib/application/OffersAPI')} deps.offersAPI * @param {ILabsService} deps.labsService * @param {IEmailService} deps.emailService * @param {IStripeService} deps.stripeService */ - constructor({memberRepository, labsService, emailService, stripeService}) { + constructor({memberRepository, labsService, emailService, stripeService, offersAPI}) { + this.offersAPI = offersAPI; /** @private */ this.memberRepository = memberRepository; /** @private */ @@ -97,6 +99,21 @@ module.exports = class MemberBREADService { } } + /** + * @private + */ + attachOffersToSubscriptions(member, subscriptionOffers) { + member.subscriptions = member.subscriptions.map((subscription) => { + const offer = subscriptionOffers[subscription.id]; + if (offer) { + subscription.offer = offer; + } else { + subscription.offer = null; + } + return subscription; + }); + } + async read(data, options = {}) { const defaultWithRelated = [ 'labels', @@ -104,7 +121,8 @@ module.exports = class MemberBREADService { 'stripeSubscriptions.customer', 'stripeSubscriptions.stripePrice', 'stripeSubscriptions.stripePrice.stripeProduct', - 'products' + 'products', + 'offerRedemptions' ]; const withRelated = new Set((options.withRelated || []).concat(defaultWithRelated)); @@ -126,10 +144,38 @@ module.exports = class MemberBREADService { return null; } + const subscriptionOffers = await model.related('offerRedemptions').toJSON().reduce(async (promiseObj, offerRedemption) => { + const obj = await promiseObj; + const offer = await this.offersAPI.getOffer({id: offerRedemption.offer_id}); + return { + ...obj, + [offerRedemption.subscription_id]: offer + }; + }, Promise.resolve({})); + + model.related('stripeSubscriptions').forEach((subscriptionModel) => { + const offer = subscriptionOffers[subscriptionModel.id]; + if (!offer) { + return; + } + + if (offer.cadence !== subscriptionModel.related('stripePrice').get('interval')) { + return; + } + + if (offer.tier.id !== subscriptionModel.related('stripePrice').related('stripeProduct').get('product_id')) { + return; + } + + subscriptionOffers[subscriptionModel.get('subscription_id')] = offer; + }); + const member = model.toJSON(options); this.attachSubscriptionsToMember(member); + this.attachOffersToSubscriptions(member, subscriptionOffers); + return member; }