Fixed members import-with-tier alpha creating unexpected invoices (#20695)
ref https://linear.app/tryghost/issue/ONC-199 The `updateSubscriptionItemPrice()` method in our Stripe library used by the importer when moving a subscription over to a Ghost product/price was setting `proration_behavior: 'always_invoice'`. This resulted in invoices being created when changing the subscription (even though no prices were changing as far as the customer is concerned) and in some cases where a customer previously had a one-off discount the customer was incorrectly charged the proration difference because the discount was no longer applied to the new invoice. - updated `updateSubscriptionItemPrice()` to accept an `options` param allowing the `proration_behavior` property passed to the Stripe API to be overridden on a per-call basis - updated the `forceStripeSubscriptionToProduct()` method used by the importer to pass an options object with `prorationBehavior: 'none'` when updating the subscription item price so that no invoice and no unexpected charges occur when importing
This commit is contained in:
parent
ffbcb5a69e
commit
ae1ac83fc5
@ -153,7 +153,8 @@ module.exports = class MembersCSVImporterStripeUtils {
|
|||||||
await this._stripeAPIService.updateSubscriptionItemPrice(
|
await this._stripeAPIService.updateSubscriptionItemPrice(
|
||||||
stripeSubscription.id,
|
stripeSubscription.id,
|
||||||
stripeSubscriptionItem.id,
|
stripeSubscriptionItem.id,
|
||||||
newStripePrice.id
|
newStripePrice.id,
|
||||||
|
{prorationBehavior: 'none'}
|
||||||
);
|
);
|
||||||
|
|
||||||
stripePriceId = newStripePrice.id;
|
stripePriceId = newStripePrice.id;
|
||||||
@ -167,7 +168,8 @@ module.exports = class MembersCSVImporterStripeUtils {
|
|||||||
await this._stripeAPIService.updateSubscriptionItemPrice(
|
await this._stripeAPIService.updateSubscriptionItemPrice(
|
||||||
stripeSubscription.id,
|
stripeSubscription.id,
|
||||||
stripeSubscriptionItem.id,
|
stripeSubscriptionItem.id,
|
||||||
stripePriceId
|
stripePriceId,
|
||||||
|
{prorationBehavior: 'none'}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -304,7 +304,8 @@ describe('MembersCSVImporterStripeUtils', function () {
|
|||||||
stripeAPIServiceStub.updateSubscriptionItemPrice.calledWithExactly(
|
stripeAPIServiceStub.updateSubscriptionItemPrice.calledWithExactly(
|
||||||
stripeCustomer.subscriptions.data[0].id,
|
stripeCustomer.subscriptions.data[0].id,
|
||||||
stripeCustomerSubscriptionItem.id,
|
stripeCustomerSubscriptionItem.id,
|
||||||
GHOST_PRODUCT_STRIPE_PRICE_ID
|
GHOST_PRODUCT_STRIPE_PRICE_ID,
|
||||||
|
{prorationBehavior: 'none'}
|
||||||
).should.be.true();
|
).should.be.true();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -346,7 +347,8 @@ describe('MembersCSVImporterStripeUtils', function () {
|
|||||||
stripeAPIServiceStub.updateSubscriptionItemPrice.calledWithExactly(
|
stripeAPIServiceStub.updateSubscriptionItemPrice.calledWithExactly(
|
||||||
stripeCustomer.subscriptions.data[0].id,
|
stripeCustomer.subscriptions.data[0].id,
|
||||||
stripeCustomerSubscriptionItem.id,
|
stripeCustomerSubscriptionItem.id,
|
||||||
NEW_STRIPE_PRICE_ID
|
NEW_STRIPE_PRICE_ID,
|
||||||
|
{prorationBehavior: 'none'}
|
||||||
).should.be.true();
|
).should.be.true();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -698,20 +698,23 @@ module.exports = class StripeAPI {
|
|||||||
* @param {string} subscriptionId - The ID of the Subscription to modify
|
* @param {string} subscriptionId - The ID of the Subscription to modify
|
||||||
* @param {string} id - The ID of the SubscriptionItem
|
* @param {string} id - The ID of the SubscriptionItem
|
||||||
* @param {string} price - The ID of the new Price
|
* @param {string} price - The ID of the new Price
|
||||||
|
* @param {object} [options={}] - Additional data to set on the subscription object
|
||||||
|
* @param {('always_invoice'|'create_prorations'|'none')} [options.prorationBehavior='always_invoice'] - The proration behavior to use. See [Stripe docs](https://docs.stripe.com/api/subscriptions/update#update_subscription-proration_behavior) for more info
|
||||||
|
* @param {string} [options.cancellationReason=null] - The user defined cancellation reason
|
||||||
*
|
*
|
||||||
* @returns {Promise<import('stripe').Stripe.Subscription>}
|
* @returns {Promise<import('stripe').Stripe.Subscription>}
|
||||||
*/
|
*/
|
||||||
async updateSubscriptionItemPrice(subscriptionId, id, price) {
|
async updateSubscriptionItemPrice(subscriptionId, id, price, options = {}) {
|
||||||
await this._rateLimitBucket.throttle();
|
await this._rateLimitBucket.throttle();
|
||||||
const subscription = await this._stripe.subscriptions.update(subscriptionId, {
|
const subscription = await this._stripe.subscriptions.update(subscriptionId, {
|
||||||
proration_behavior: 'always_invoice',
|
proration_behavior: options.prorationBehavior || 'always_invoice',
|
||||||
items: [{
|
items: [{
|
||||||
id,
|
id,
|
||||||
price
|
price
|
||||||
}],
|
}],
|
||||||
cancel_at_period_end: false,
|
cancel_at_period_end: false,
|
||||||
metadata: {
|
metadata: {
|
||||||
cancellation_reason: null
|
cancellation_reason: options.cancellationReason ?? null
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return subscription;
|
return subscription;
|
||||||
|
Loading…
Reference in New Issue
Block a user