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:
parent
3f6498c12f
commit
836b7f235e
3 changed files with 122 additions and 1 deletions
|
@ -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,
|
||||
|
|
93
ghost/members-api/lib/migrations/index.js
Normal file
93
ghost/members-api/lib/migrations/index.js
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
|
@ -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.
|
||||
*
|
||||
|
|
Loading…
Add table
Reference in a new issue