0
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2025-04-01 02:41:39 -05:00

Populate stripe prices and products for existing customers (#258)

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

On Ghost Boot, as part of configuring Stripe, this populates stripe products and prices for existing stripe customers in the newly created `stripe_prices` and `stripe_products` table, which allows us to map existing customers to default Ghost product and on current prices. The population script on boot is only run if we find -

- A Ghost Product
- No rows in `stripe_products`
- No rows in `stripe_prices`
- One or more rows in `members_stripe_customers_subscriptions`
This commit is contained in:
Rishabh Garg 2021-04-12 20:38:01 +05:30 committed by GitHub
parent 3f6498c12f
commit 836b7f235e
3 changed files with 122 additions and 1 deletions

View file

@ -12,6 +12,7 @@ const MemberRepository = require('./lib/repositories/member');
const EventRepository = require('./lib/repositories/event');
const RouterController = require('./lib/controllers/router');
const MemberController = require('./lib/controllers/member');
const StripeMigrations = require('./lib/migrations');
module.exports = function MembersApi({
tokenConfig: {
@ -41,7 +42,10 @@ module.exports = function MembersApi({
MemberPaidSubscriptionEvent,
MemberPaymentEvent,
MemberStatusEvent,
MemberEmailChangeEvent
MemberEmailChangeEvent,
StripeProduct,
StripePrice,
Product
},
logger
}) {
@ -61,6 +65,15 @@ module.exports = function MembersApi({
logger
});
const stripeMigrations = new StripeMigrations({
stripeAPIService,
StripeCustomerSubscription,
StripeProduct,
StripePrice,
Product,
logger
});
const stripePlansService = new StripePlansService({
stripeAPIService
});
@ -143,6 +156,7 @@ module.exports = function MembersApi({
plans: stripeConfig.plans,
mode: process.env.NODE_ENV || 'development'
}),
stripeMigrations.populateProductsAndPrices(),
stripeWebhookService.configure({
webhookSecret: process.env.WEBHOOK_SECRET,
webhookHandlerUrl: stripeConfig.webhookHandlerUrl,

View file

@ -0,0 +1,93 @@
const _ = require('lodash');
/**
* @typedef {object} ILogger
* @prop {(x: any) => void} error
* @prop {(x: any) => void} info
* @prop {(x: any) => void} warn
*/
module.exports = class StripeMigrations {
/**
* StripeMigrations
*
* @param {object} params
*
* @param {ILogger} params.logger
* @param {any} params.StripeCustomerSubscription
* @param {any} params.StripeProduct
* @param {any} params.StripePrice
* @param {any} params.Product
* @param {any} params.stripeAPIService
*/
constructor({
StripeCustomerSubscription,
StripeProduct,
StripePrice,
Product,
stripeAPIService,
logger
}) {
this._logging = logger;
this._StripeCustomerSubscription = StripeCustomerSubscription;
this._StripeProduct = StripeProduct;
this._StripePrice = StripePrice;
this._Product = Product;
this._StripeAPIService = stripeAPIService;
}
async populateProductsAndPrices() {
const subscriptionModels = await this._StripeCustomerSubscription.findAll();
const priceModels = await this._StripePrice.findAll();
const productModels = await this._StripeProduct.findAll();
const subscriptions = subscriptionModels.toJSON();
const prices = priceModels.toJSON();
const products = productModels.toJSON();
const {data} = await this._Product.findPage({
limit: 1
});
const defaultProduct = data[0] && data[0].toJSON();
/** Only run when -
* No rows in stripe_products,
* No rows in stripe_prices,
* One or more rows in members_stripe_customers_subscriptions
* */
if (subscriptions.length > 0 && products.length === 0 && prices.length === 0 && defaultProduct) {
try {
this._logging.info(`Populating products and prices for existing stripe customers`);
const uniquePlans = _.uniq(subscriptions.map(d => _.get(d, 'plan.id')));
let stripePlans = [];
for (const plan of uniquePlans) {
const stripePlan = await this._StripeAPIService.getPlan(plan, {
expand: ['product']
});
stripePlans.push(stripePlan);
}
this._logging.info(`Adding ${stripePlans.length} plans from Stripe`);
for (const stripePlan of stripePlans) {
const stripeProduct = stripePlan.product;
await this._StripeProduct.upsert({
product_id: defaultProduct.id,
stripe_product_id: stripeProduct.id
});
await this._StripePrice.add({
stripe_price_id: stripePlan.id,
stripe_product_id: stripeProduct.id,
active: stripePlan.active,
nickname: stripePlan.nickname,
currency: stripePlan.currency,
amount: stripePlan.amount,
type: 'recurring',
interval: stripePlan.interval
});
}
} catch (e) {
this._logging.error(`Failed to populate products/prices from stripe`);
this._logging.error(e);
}
}
}
};

View file

@ -392,6 +392,20 @@ module.exports = class StripeAPIService {
return this._config.publicKey;
}
/**
* getPlan
*
* @param {string} id
* @param {object} options
*
* @returns {Promise<import('stripe').Stripe.Plan>}
*/
async getPlan(id, options = {}) {
debug(`getSubscription(${id}, ${JSON.stringify(options)})`);
return await this._stripe.plans.retrieve(id, options);
}
/**
* getSubscription.
*