mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-04-15 03:01:37 -05:00
Wired up ProductRepository to members-api
refs https://github.com/TryGhost/Team/issues/616 Working with ProductRepository as a separate package was more trouble than it was worth, so it's been moved into members-api. We expose the product repository so that Ghost Admin API can access it.
This commit is contained in:
parent
bd3b3738e5
commit
dc0e5b0ec8
11 changed files with 123 additions and 112 deletions
|
@ -10,6 +10,7 @@ const TokenService = require('./lib/services/token');
|
|||
const GeolocationSerice = require('./lib/services/geolocation');
|
||||
const MemberRepository = require('./lib/repositories/member');
|
||||
const EventRepository = require('./lib/repositories/event');
|
||||
const ProductRepository = require('./lib/repositories/product');
|
||||
const RouterController = require('./lib/controllers/router');
|
||||
const MemberController = require('./lib/controllers/member');
|
||||
const StripeMigrations = require('./lib/migrations');
|
||||
|
@ -78,6 +79,13 @@ module.exports = function MembersApi({
|
|||
stripeAPIService
|
||||
});
|
||||
|
||||
const productRepository = new ProductRepository({
|
||||
Product,
|
||||
StripeProduct,
|
||||
StripePrice,
|
||||
stripeAPIService
|
||||
});
|
||||
|
||||
const memberRepository = new MemberRepository({
|
||||
stripeAPIService,
|
||||
stripePlansService,
|
||||
|
@ -367,6 +375,7 @@ module.exports = function MembersApi({
|
|||
getMagicLink,
|
||||
hasActiveStripeSubscriptions,
|
||||
members: users,
|
||||
events: eventRepository
|
||||
events: eventRepository,
|
||||
productRepository
|
||||
};
|
||||
};
|
||||
|
|
|
@ -142,7 +142,7 @@ module.exports = class MemberRepository {
|
|||
});
|
||||
}
|
||||
|
||||
if (this._stripeAPIService && member._changed.email) {
|
||||
if (this._stripeAPIService.configured && member._changed.email) {
|
||||
await member.related('stripeCustomers').fetch();
|
||||
const customers = member.related('stripeCustomers');
|
||||
for (const customer of customers.models) {
|
||||
|
@ -168,7 +168,7 @@ module.exports = class MemberRepository {
|
|||
return;
|
||||
}
|
||||
|
||||
if (this._stripeAPIService && options.cancelStripeSubscriptions) {
|
||||
if (this._stripeAPIService.configured && options.cancelStripeSubscriptions) {
|
||||
await member.related('stripeSubscriptions').fetch();
|
||||
const subscriptions = member.related('stripeSubscriptions');
|
||||
for (const subscription of subscriptions.models) {
|
||||
|
|
|
@ -2,6 +2,17 @@
|
|||
* @typedef {object} ProductModel
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {object} StripePriceInput
|
||||
* @param {string} nickname
|
||||
* @param {string} currency
|
||||
* @param {number} amount
|
||||
* @param {'recurring'|'one-time'} type
|
||||
* @param {string | null} interval
|
||||
* @param {string?} stripe_product_id
|
||||
* @param {string?} stripe_price_id
|
||||
*/
|
||||
|
||||
class ProductRepository {
|
||||
/**
|
||||
* @param {object} deps
|
||||
|
@ -77,6 +88,7 @@ class ProductRepository {
|
|||
*
|
||||
* @param {object} data
|
||||
* @param {string} data.name
|
||||
* @param {StripePriceInput[]} data.stripe_prices
|
||||
*
|
||||
* @param {object} options
|
||||
*
|
||||
|
@ -99,7 +111,32 @@ class ProductRepository {
|
|||
stripe_product_id: stripeProduct.id
|
||||
}, options);
|
||||
|
||||
await product.related('stripeProducts').fetch(options);
|
||||
if (data.stripe_prices) {
|
||||
for (const newPrice of data.stripe_prices) {
|
||||
const price = await this._stripeAPIService.createPrice({
|
||||
product: stripeProduct.id,
|
||||
active: true,
|
||||
nickname: newPrice.nickname,
|
||||
currency: newPrice.currency,
|
||||
amount: newPrice.amount,
|
||||
type: newPrice.type,
|
||||
interval: newPrice.interval
|
||||
});
|
||||
|
||||
await this._StripePrice.add({
|
||||
stripe_price_id: price.id,
|
||||
stripe_product_id: stripeProduct.id,
|
||||
active: true,
|
||||
nickname: newPrice.nickname,
|
||||
currency: newPrice.currency,
|
||||
amount: newPrice.amount,
|
||||
type: newPrice.type,
|
||||
interval: newPrice.interval
|
||||
}, options);
|
||||
}
|
||||
}
|
||||
|
||||
await product.related('stripePrices').fetch(options);
|
||||
}
|
||||
|
||||
return product;
|
||||
|
@ -112,13 +149,7 @@ class ProductRepository {
|
|||
* @param {string} data.id
|
||||
* @param {string} data.name
|
||||
*
|
||||
* @param {object} data.stripe_price
|
||||
* @param {string} data.stripe_price.nickname
|
||||
* @param {string} data.stripe_price.currency
|
||||
* @param {number} data.stripe_price.amount
|
||||
* @param {'recurring'|'one-time'} data.stripe_price.type
|
||||
* @param {string | null} data.stripe_price.interval
|
||||
* @param {string?} data.stripe_price.stripe_product_id
|
||||
* @param {StripePriceInput[]=} data.stripe_prices
|
||||
*
|
||||
* @param {object} options
|
||||
*
|
||||
|
@ -131,10 +162,10 @@ class ProductRepository {
|
|||
|
||||
const product = await this._Product.edit(productData, {
|
||||
...options,
|
||||
id: data.id
|
||||
id: data.id || options.id
|
||||
});
|
||||
|
||||
if (this._stripeAPIService.configured && data.stripe_price) {
|
||||
if (this._stripeAPIService.configured && data.stripe_prices) {
|
||||
await product.related('stripeProducts').fetch(options);
|
||||
|
||||
if (!product.related('stripeProducts').first()) {
|
||||
|
@ -151,24 +182,35 @@ class ProductRepository {
|
|||
}
|
||||
|
||||
const defaultStripeProduct = product.related('stripeProducts').first();
|
||||
const productId = data.stripe_price.stripe_product_id;
|
||||
const stripeProduct = productId ?
|
||||
await this._StripeProduct.findOne({stripe_product_id: productId}, options) : defaultStripeProduct;
|
||||
|
||||
const price = await this._stripeAPIService.createPrice({
|
||||
product: defaultStripeProduct.stripe_product_id,
|
||||
active: true,
|
||||
nickname: data.stripe_price.nickname,
|
||||
currency: data.stripe_price.currency,
|
||||
amount: data.stripe_price.amount,
|
||||
type: data.stripe_price.type,
|
||||
interval: data.stripe_price.interval
|
||||
});
|
||||
const newPrices = data.stripe_prices.filter(price => !price.stripe_price_id);
|
||||
|
||||
await this._StripePrice.add({
|
||||
stripe_price_id: price.id,
|
||||
stripe_product_id: stripeProduct.stripe_product_id
|
||||
}, options);
|
||||
for (const newPrice of newPrices) {
|
||||
const productId = newPrice.stripe_product_id;
|
||||
const stripeProduct = productId ?
|
||||
await this._StripeProduct.findOne({stripe_product_id: productId}, options) : defaultStripeProduct;
|
||||
|
||||
const price = await this._stripeAPIService.createPrice({
|
||||
product: stripeProduct.get('stripe_product_id'),
|
||||
active: true,
|
||||
nickname: newPrice.nickname,
|
||||
currency: newPrice.currency,
|
||||
amount: newPrice.amount,
|
||||
type: newPrice.type,
|
||||
interval: newPrice.interval
|
||||
});
|
||||
|
||||
await this._StripePrice.add({
|
||||
stripe_price_id: price.id,
|
||||
stripe_product_id: stripeProduct.get('stripe_product_id'),
|
||||
active: price.active,
|
||||
nickname: price.nickname,
|
||||
currency: price.currency,
|
||||
amount: price.unit_amount,
|
||||
type: price.type,
|
||||
interval: price.recurring && price.recurring.interval || null
|
||||
}, options);
|
||||
}
|
||||
|
||||
await product.related('stripePrices').fetch(options);
|
||||
}
|
|
@ -46,6 +46,8 @@ module.exports = class StripeAPIService {
|
|||
* @param {boolean} params.config.enablePromoCodes
|
||||
*/
|
||||
constructor({config, logger}) {
|
||||
/** @type {Stripe} */
|
||||
this._stripe = null;
|
||||
this.logging = logger;
|
||||
this._configured = false;
|
||||
if (config.secretKey) {
|
||||
|
@ -75,6 +77,47 @@ module.exports = class StripeAPIService {
|
|||
this._configured = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {object} options
|
||||
* @param {string} options.name
|
||||
*
|
||||
* @returns {Promise<IProduct>}
|
||||
*/
|
||||
async createProduct(options) {
|
||||
await this._rateLimitBucket.throttle();
|
||||
const product = await this._stripe.products.create(options);
|
||||
|
||||
return product;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {object} options
|
||||
* @param {string} options.product
|
||||
* @param {boolean} options.active
|
||||
* @param {string} options.nickname
|
||||
* @param {string} options.currency
|
||||
* @param {number} options.amount
|
||||
* @param {'recurring'|'one-time'} options.type
|
||||
* @param {Stripe.Price.Recurring.Interval|null} options.interval
|
||||
*
|
||||
* @returns {Promise<IPrice>}
|
||||
*/
|
||||
async createPrice(options) {
|
||||
await this._rateLimitBucket.throttle();
|
||||
const price = await this._stripe.prices.create({
|
||||
currency: options.currency,
|
||||
product: options.product,
|
||||
unit_amount: options.amount,
|
||||
active: options.active,
|
||||
nickname: options.nickname,
|
||||
recurring: options.type === 'recurring' ? {
|
||||
interval: options.interval
|
||||
} : undefined
|
||||
});
|
||||
|
||||
return price;
|
||||
}
|
||||
|
||||
/**
|
||||
* ensureProduct.
|
||||
*
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
module.exports = {
|
||||
plugins: ['ghost'],
|
||||
extends: [
|
||||
'plugin:ghost/node'
|
||||
]
|
||||
};
|
|
@ -1,21 +0,0 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2013-2021 Ghost Foundation
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -1,3 +0,0 @@
|
|||
# Product Repository
|
||||
|
||||
This module is designed to be used inside the `@tryghost/members-api` module. It is not for public use.
|
|
@ -1,25 +0,0 @@
|
|||
{
|
||||
"name": "@tryghost/product-repository",
|
||||
"version": "0.1.1",
|
||||
"repository": "https://github.com/TryGhost/Members/tree/master/packages/product-repository",
|
||||
"author": "Ghost Foundation",
|
||||
"license": "MIT",
|
||||
"main": "ProductRespository.js",
|
||||
"scripts": {
|
||||
"test": "NODE_ENV=testing mocha './test/**/*.test.js'",
|
||||
"lint": "eslint . --ext .js --cache",
|
||||
"posttest": "yarn lint"
|
||||
},
|
||||
"files": [
|
||||
"ProductRepository.js"
|
||||
],
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/mocha": "^8.2.2",
|
||||
"mocha": "^8.3.2",
|
||||
"should": "^13.2.3",
|
||||
"typescript": "^4.2.4"
|
||||
}
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
module.exports = {
|
||||
plugins: ['ghost'],
|
||||
extends: [
|
||||
'plugin:ghost/test'
|
||||
]
|
||||
};
|
|
@ -1,12 +0,0 @@
|
|||
const should = require('should');
|
||||
const ProductRepository = require('../ProductRepository');
|
||||
|
||||
describe('ProductRespository', function () {
|
||||
it('Has create, update, get, list, destroy method', function () {
|
||||
should.exist(ProductRepository.prototype.create);
|
||||
should.exist(ProductRepository.prototype.update);
|
||||
should.exist(ProductRepository.prototype.get);
|
||||
should.exist(ProductRepository.prototype.list);
|
||||
should.exist(ProductRepository.prototype.destroy);
|
||||
});
|
||||
});
|
|
@ -1,10 +0,0 @@
|
|||
{
|
||||
"include": ["index.js", "lib/**/*", "test/**/*"],
|
||||
"compilerOptions": {
|
||||
"checkJs": true,
|
||||
"allowJs": true,
|
||||
"declaration": true,
|
||||
"emitDeclarationOnly": true,
|
||||
"outDir": "types"
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue