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.
This commit is contained in:
Fabien O'Carroll 2021-10-20 14:32:41 +02:00
parent e78b2f80bc
commit c154be4581
2 changed files with 49 additions and 2 deletions

View File

@ -120,6 +120,7 @@ module.exports = function MembersAPI({
});
const memberBREADService = new MemberBREADService({
offersAPI,
memberRepository,
emailService: {
sendEmailWithMagicLink

View File

@ -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;
}