0
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2025-01-27 22:49:56 -05:00

Stored offer_id in subscriptions (#389)

refs https://github.com/TryGhost/Team/issues/1519

- Added offer repository dependency to member repository (offerAPI didn't work because it creates a new transaction that resulted in a deadlock during tests)
- Store the offer id from the Stripe subscription metadata in the subscription (only if the discount is still active)
- Also added the offer id to the metadata for a Stripe coupon, this will make adding and removing coupons a bit more foolproof
- Prefer the usage of the offer metadata from a coupon if it is present
- When no discount is applied to a subscription, it always sets the offer id to null, even when the metadata still contains the offer
- The offer_id remains stored when a subscription is canceled/expired
This commit is contained in:
Simon Backx 2022-04-19 09:15:33 +02:00 committed by GitHub
parent db02d61e4b
commit f74b00fea6
3 changed files with 30 additions and 6 deletions

View file

@ -92,7 +92,8 @@ module.exports = function MembersAPI({
MemberProductEvent,
OfferRedemption,
StripeCustomer,
StripeCustomerSubscription
StripeCustomerSubscription,
offerRepository: offersAPI.repository
});
const eventRepository = new EventRepository({

View file

@ -33,11 +33,13 @@ module.exports = class MemberRepository {
* @param {any} deps.MemberProductEvent
* @param {any} deps.StripeCustomer
* @param {any} deps.StripeCustomerSubscription
* @param {any} deps.productRepository
* @param {any} deps.newslettersService
* @param {any} deps.labsService
* @param {any} deps.OfferRedemption
* @param {import('../../services/stripe-api')} deps.stripeAPIService
* @param {any} deps.labsService
* @param {any} deps.productRepository
* @param {any} deps.offerRepository
* @param {ITokenService} deps.tokenService
* @param {any} deps.newslettersService
*/
constructor({
Member,
@ -53,6 +55,7 @@ module.exports = class MemberRepository {
stripeAPIService,
labsService,
productRepository,
offerRepository,
tokenService,
newslettersService
}) {
@ -67,6 +70,7 @@ module.exports = class MemberRepository {
this._StripeCustomerSubscription = StripeCustomerSubscription;
this._stripeAPIService = stripeAPIService;
this._productRepository = productRepository;
this._offerRepository = offerRepository;
this.tokenService = tokenService;
this._newslettersService = newslettersService;
this._labsService = labsService;
@ -698,6 +702,17 @@ module.exports = class MemberRepository {
logging.error(e);
}
let offerId = subscription.discount && (subscription.discount.coupon.metadata.offer || subscription.metadata.offer) ? (subscription.discount.coupon.metadata.offer ? subscription.discount.coupon.metadata.offer : subscription.metadata.offer) : null;
if (offerId) {
// Validate the offer id from the metadata
const offer = await this._offerRepository.getById(offerId, {transacting: options.transacting});
if (!offer) {
logging.error(`Received an invalid offer id (${offerId}) in the metadata of a subscription - ${subscription.id}.`);
offerId = null;
}
}
const subscriptionData = {
customer_id: subscription.customer,
subscription_id: subscription.id,
@ -723,8 +738,12 @@ module.exports = class MemberRepository {
status: subscription.status,
canceled: subscription.cancel_at_period_end,
discount: subscription.discount
})
}),
// We try to use the offer_id that was stored in Stripe coupon and fallback to the one stored in the subscription
// This allows us to catch the offer_id from discounts (created by Ghost) that were applied via the Stripe dashboard
offer_id: offerId
};
let eventData = {};
if (model) {
const updated = await this._StripeCustomerSubscription.edit(subscriptionData, {

View file

@ -48,7 +48,11 @@ class PaymentsService {
/** @type {import('stripe').Stripe.CouponCreateParams} */
const couponData = {
name: offer.name,
duration: offer.duration
duration: offer.duration,
// Note that the metadata is not present for older coupons
metadata: {
offer: offer.id
}
};
if (offer.duration === 'repeating') {