Added migration to update Stripe Product names (#376)

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

We had a bug where Tiers would have a name of 'Default Product', and a
Stripe Product would be created with the same name. This migration will
fixes those broken Stripe Products
This commit is contained in:
Fabien 'egg' O'Carroll 2022-03-09 14:41:59 +00:00 committed by GitHub
parent e45f85ce41
commit 633dc08c11
3 changed files with 148 additions and 0 deletions

View File

@ -32,6 +32,7 @@ module.exports = class StripeMigrations {
await this.revertPortalPlansSetting();
await this.removeInvalidSubscriptions();
await this.setDefaultProductName();
await this.updateStripeProductNamesFromDefaultProduct();
}
async populateProductsAndPrices(options) {
@ -572,4 +573,33 @@ module.exports = class StripeMigrations {
}
}
}
async updateStripeProductNamesFromDefaultProduct(options) {
if (!options) {
return this.models.Product.transaction((transacting) => {
return this.updateStripeProductNamesFromDefaultProduct({transacting});
});
}
const {data} = await this.models.StripeProduct.findPage({
...options,
limit: 'all'
});
const siteTitle = await this.models.Settings.findOne({key: 'title'}, options);
if (!siteTitle) {
return;
}
for (const model of data) {
const product = await this.api.getProduct(model.get('stripe_product_id'));
if (product.name === 'Default Product') {
await this.api.updateProduct(product.id, {
name: siteTitle.get('value')
});
}
}
}
};

View File

@ -80,6 +80,18 @@ module.exports = class StripeAPI {
return coupon;
}
/**
* @param {string} id
*
* @returns {Promise<IProduct>}
*/
async getProduct(id) {
await this._rateLimitBucket.throttle();
const product = await this._stripe.products.retrieve(id);
return product;
}
/**
* @param {object} options
* @param {string} options.name

View File

@ -0,0 +1,106 @@
const assert = require('assert');
const sinon = require('sinon');
const Migrations = require('../../../lib/Migrations');
describe('Migrations', function () {
describe('updateStripeProductNamesFromDefaultProduct', function () {
it('Does not update Stripe product if name is not "Default Product"', async function () {
const api = {
getProduct: sinon.stub().resolves({
id: 'prod_123',
name: 'Bronze'
}),
updateProduct: sinon.stub().resolves()
};
const models = {
Product: {
transaction: fn => fn()
},
StripeProduct: {
findPage: sinon.stub().resolves({
data: [{
get(key) {
return key;
}
}],
meta: {}
})
},
Settings: {
findOne: sinon.stub().resolves({
key: 'title',
value: 'Site Title'
})
}
};
const migrations = new Migrations({
models,
api
});
await migrations.updateStripeProductNamesFromDefaultProduct();
assert(
api.updateProduct.called === false,
'Stripe product should not be updated if name is not "Default Product"'
);
});
it('Updates the Stripe Product name if it is Default Product', async function () {
const api = {
getProduct: sinon.stub().resolves({
id: 'prod_123',
name: 'Default Product'
}),
updateProduct: sinon.stub().resolves()
};
const models = {
Product: {
transaction: fn => fn()
},
StripeProduct: {
findPage: sinon.stub().resolves({
data: [{
get(key) {
return key;
}
}],
meta: {}
})
},
Settings: {
findOne: sinon.stub().resolves({
get(key) {
if (key === 'key') {
return 'title';
}
if (key === 'value') {
return 'Site Title';
}
return key;
}
})
}
};
const migrations = new Migrations({
models,
api
});
await migrations.updateStripeProductNamesFromDefaultProduct();
assert(
api.updateProduct.calledOnce,
'Stripe product should be updated if name is "Default Product"'
);
assert(
api.updateProduct.calledWith('prod_123', {
name: 'Site Title'
}),
'Stripe product should have been updated with the site title as name'
);
});
});
});