mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-01-06 22:40:14 -05:00
Moved StripeAPIService to its own service
refs https://github.com/TryGhost/Team/issues/1083 The Offers service is going to need access to the StripeAPIService too, so we need to move it out of the @tryghost/members-api module and make it accessible to both.
This commit is contained in:
parent
025faec7c3
commit
cda041d424
11 changed files with 246 additions and 140 deletions
|
@ -177,6 +177,7 @@ async function initServices({config}) {
|
|||
debug(`Default API Version: ${defaultApiVersion}`);
|
||||
|
||||
debug('Begin: Services');
|
||||
const stripe = require('./server/services/stripe');
|
||||
const members = require('./server/services/members');
|
||||
const permissions = require('./server/services/permissions');
|
||||
const xmlrpc = require('./server/services/xmlrpc');
|
||||
|
@ -193,6 +194,10 @@ async function initServices({config}) {
|
|||
// in case it limits initialization of any other service (e.g. webhooks)
|
||||
await limits.init();
|
||||
|
||||
// NOTE: stripe service has to be initialized before members
|
||||
// as it is a dependency
|
||||
await stripe.init();
|
||||
|
||||
await Promise.all([
|
||||
members.init(),
|
||||
permissions.init(),
|
||||
|
|
|
@ -5,24 +5,19 @@ const config = require('../../../../shared/config');
|
|||
|
||||
let UNO_MEMBERINO;
|
||||
|
||||
module.exports = {
|
||||
get authenticateMembersToken() {
|
||||
if (!UNO_MEMBERINO) {
|
||||
async function createMiddleware() {
|
||||
const url = require('url');
|
||||
const {protocol, host} = url.parse(config.get('url'));
|
||||
const siteOrigin = `${protocol}//${host}`;
|
||||
|
||||
UNO_MEMBERINO = membersService.api.getPublicConfig().then(({issuer}) => jwt({
|
||||
const membersConfig = await membersService.api.getPublicConfig();
|
||||
return jwt({
|
||||
credentialsRequired: false,
|
||||
requestProperty: 'member',
|
||||
audience: siteOrigin,
|
||||
issuer,
|
||||
issuer: membersConfig.issuer,
|
||||
algorithms: ['RS512'],
|
||||
secret(req, payload, done) {
|
||||
membersService.api.getPublicConfig().then(({publicKey}) => {
|
||||
done(null, publicKey);
|
||||
}).catch(done);
|
||||
},
|
||||
secret: membersConfig.publicKey,
|
||||
getToken(req) {
|
||||
if (!req.get('authorization')) {
|
||||
return null;
|
||||
|
@ -36,11 +31,17 @@ module.exports = {
|
|||
|
||||
return credentials;
|
||||
}
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
get authenticateMembersToken() {
|
||||
return async function (req, res, next) {
|
||||
if (!UNO_MEMBERINO) {
|
||||
UNO_MEMBERINO = await createMiddleware();
|
||||
}
|
||||
try {
|
||||
const middleware = await UNO_MEMBERINO;
|
||||
const middleware = UNO_MEMBERINO;
|
||||
|
||||
middleware(req, res, function (err, ...rest) {
|
||||
if (err && err.name === 'UnauthorizedError') {
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
const stripeService = require('../stripe');
|
||||
const settingsCache = require('../../../shared/settings-cache');
|
||||
const MembersApi = require('@tryghost/members-api');
|
||||
const logging = require('@tryghost/logging');
|
||||
|
@ -183,6 +184,7 @@ function createApiInstance(config) {
|
|||
Product: models.Product,
|
||||
Settings: models.Settings
|
||||
},
|
||||
stripeAPIService: stripeService.api,
|
||||
logger: logging,
|
||||
labsService: labsService
|
||||
});
|
||||
|
|
|
@ -174,8 +174,6 @@ class MembersConfigProvider {
|
|||
}
|
||||
|
||||
return {
|
||||
publicKey: stripeApiKeys.publicKey,
|
||||
secretKey: stripeApiKeys.secretKey,
|
||||
checkoutSuccessUrl: urls.checkoutSuccess,
|
||||
checkoutCancelUrl: urls.checkoutCancel,
|
||||
billingSuccessUrl: urls.billingSuccess,
|
||||
|
@ -185,17 +183,10 @@ class MembersConfigProvider {
|
|||
id: this._settingsCache.get('members_stripe_webhook_id'),
|
||||
secret: this._settingsCache.get('members_stripe_webhook_secret')
|
||||
},
|
||||
enablePromoCodes: this._config.get('enableStripePromoCodes'),
|
||||
product: {
|
||||
name: this._settingsCache.get('stripe_product_name')
|
||||
},
|
||||
plans: this._settingsCache.get('stripe_plans') || [],
|
||||
appInfo: {
|
||||
name: 'Ghost',
|
||||
partner_id: 'pp_partner_DKmRVtTs4j9pwZ',
|
||||
version: this._ghostVersion.original,
|
||||
url: 'https://ghost.org/'
|
||||
}
|
||||
plans: this._settingsCache.get('stripe_plans') || []
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@ const ghostVersion = require('@tryghost/version');
|
|||
const _ = require('lodash');
|
||||
const {GhostMailer} = require('../mail');
|
||||
const jobsService = require('../jobs');
|
||||
const stripeService = require('../stripe');
|
||||
|
||||
const messages = {
|
||||
noLiveKeysInDevelopment: 'Cannot use live stripe keys in development. Please restart in production mode.',
|
||||
|
@ -135,15 +136,8 @@ events.on('settings.edited', function updateSettingFromModel(settingModel) {
|
|||
'members_from_address',
|
||||
'members_support_address',
|
||||
'members_reply_address',
|
||||
'stripe_publishable_key',
|
||||
'stripe_secret_key',
|
||||
'stripe_product_name',
|
||||
'stripe_plans',
|
||||
'stripe_connect_publishable_key',
|
||||
'stripe_connect_secret_key',
|
||||
'stripe_connect_livemode',
|
||||
'stripe_connect_display_name',
|
||||
'stripe_connect_account_id'
|
||||
'stripe_plans'
|
||||
].includes(settingModel.get('key'))) {
|
||||
return;
|
||||
}
|
||||
|
@ -151,32 +145,27 @@ events.on('settings.edited', function updateSettingFromModel(settingModel) {
|
|||
debouncedReconfigureMembersAPI();
|
||||
});
|
||||
|
||||
events.on('services.stripe.reconfigured', reconfigureMembersAPI);
|
||||
|
||||
const membersService = {
|
||||
async init() {
|
||||
const env = config.get('env');
|
||||
const paymentConfig = membersConfig.getStripePaymentConfig();
|
||||
|
||||
if (env !== 'production') {
|
||||
if (!process.env.WEBHOOK_SECRET && membersConfig.isStripeConnected()) {
|
||||
if (!process.env.WEBHOOK_SECRET && stripeService.api.configured) {
|
||||
process.env.WEBHOOK_SECRET = 'DEFAULT_WEBHOOK_SECRET';
|
||||
logging.warn(tpl(messages.remoteWebhooksInDevelopment));
|
||||
}
|
||||
|
||||
if (paymentConfig && paymentConfig.secretKey.startsWith('sk_live')) {
|
||||
if (stripeService.api.configured && stripeService.api.mode === 'live') {
|
||||
throw new errors.IncorrectUsageError(tpl(messages.noLiveKeysInDevelopment));
|
||||
}
|
||||
} else {
|
||||
const siteUrl = urlUtils.getSiteUrl();
|
||||
if (!/^https/.test(siteUrl) && membersConfig.isStripeConnected()) {
|
||||
if (!/^https/.test(siteUrl) && stripeService.api.configured) {
|
||||
throw new errors.IncorrectUsageError(tpl(messages.sslRequiredForStripe));
|
||||
}
|
||||
}
|
||||
},
|
||||
contentGating: require('./content-gating'),
|
||||
|
||||
config: membersConfig,
|
||||
|
||||
get api() {
|
||||
if (!membersApi) {
|
||||
membersApi = createMembersApiInstance(membersConfig);
|
||||
|
||||
|
@ -184,6 +173,12 @@ const membersService = {
|
|||
logging.error(err);
|
||||
});
|
||||
}
|
||||
},
|
||||
contentGating: require('./content-gating'),
|
||||
|
||||
config: membersConfig,
|
||||
|
||||
get api() {
|
||||
return membersApi;
|
||||
},
|
||||
|
||||
|
|
57
core/server/services/stripe/config.js
Normal file
57
core/server/services/stripe/config.js
Normal file
|
@ -0,0 +1,57 @@
|
|||
const ghostVersion = require('@tryghost/version');
|
||||
|
||||
module.exports = {
|
||||
getConfig(settings, config) {
|
||||
/**
|
||||
* @param {'direct' | 'connect'} type - The "type" of keys to fetch from settings
|
||||
* @returns {{publicKey: string, secretKey: string} | null}
|
||||
*/
|
||||
function getStripeKeys(type) {
|
||||
const secretKey = settings.get(`stripe_${type === 'connect' ? 'connect_' : ''}secret_key`);
|
||||
const publicKey = settings.get(`stripe_${type === 'connect' ? 'connect_' : ''}publishable_key`);
|
||||
|
||||
if (!secretKey || !publicKey) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
secretKey,
|
||||
publicKey
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {{publicKey: string, secretKey: string} | null}
|
||||
*/
|
||||
function getActiveStripeKeys() {
|
||||
const stripeDirect = config.get('stripeDirect');
|
||||
|
||||
if (stripeDirect) {
|
||||
return getStripeKeys('direct');
|
||||
}
|
||||
|
||||
const connectKeys = getStripeKeys('connect');
|
||||
|
||||
if (!connectKeys) {
|
||||
return getStripeKeys('direct');
|
||||
}
|
||||
|
||||
return connectKeys;
|
||||
}
|
||||
const keys = getActiveStripeKeys();
|
||||
if (!keys) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
secretKey: keys.secretKey,
|
||||
publicKey: keys.publicKey,
|
||||
appInfo: {
|
||||
name: 'Ghost',
|
||||
partner_id: 'pp_partner_DKmRVtTs4j9pwZ',
|
||||
version: ghostVersion.original,
|
||||
url: 'https://ghost.org/'
|
||||
},
|
||||
enablePromoCodes: config.get('enableStripePromoCodes')
|
||||
};
|
||||
}
|
||||
};
|
45
core/server/services/stripe/index.js
Normal file
45
core/server/services/stripe/index.js
Normal file
|
@ -0,0 +1,45 @@
|
|||
const _ = require('lodash');
|
||||
const logging = require('@tryghost/logging');
|
||||
const StripeAPIService = require('@tryghost/members-stripe-service');
|
||||
|
||||
const config = require('../../../shared/config');
|
||||
const settings = require('../../../shared/settings-cache');
|
||||
const events = require('../../lib/common/events');
|
||||
|
||||
const {getConfig} = require('./config');
|
||||
|
||||
const api = new StripeAPIService({
|
||||
logger: logging,
|
||||
config: {}
|
||||
});
|
||||
|
||||
const stripeKeySettings = [
|
||||
'stripe_publishable_key',
|
||||
'stripe_secret_key',
|
||||
'stripe_connect_publishable_key',
|
||||
'stripe_connect_secret_key'
|
||||
];
|
||||
|
||||
function configureApi() {
|
||||
const cfg = getConfig(settings, config);
|
||||
if (cfg) {
|
||||
api.configure(cfg);
|
||||
}
|
||||
}
|
||||
|
||||
const debouncedConfigureApi = _.debounce(configureApi, 600);
|
||||
|
||||
module.exports = {
|
||||
async init() {
|
||||
configureApi();
|
||||
events.on('settings.edited', function (model) {
|
||||
if (!stripeKeySettings.includes(model.get('key'))) {
|
||||
return;
|
||||
}
|
||||
debouncedConfigureApi();
|
||||
events.emit('services.stripe.reconfigured');
|
||||
});
|
||||
},
|
||||
|
||||
api
|
||||
};
|
|
@ -75,7 +75,7 @@
|
|||
"@tryghost/limit-service": "0.6.4",
|
||||
"@tryghost/logging": "0.1.7",
|
||||
"@tryghost/magic-link": "1.0.13",
|
||||
"@tryghost/members-api": "1.39.1",
|
||||
"@tryghost/members-api": "2.0.0",
|
||||
"@tryghost/members-csv": "1.1.7",
|
||||
"@tryghost/members-importer": "0.3.3",
|
||||
"@tryghost/members-ssr": "1.0.14",
|
||||
|
|
|
@ -71,81 +71,6 @@ describe('Members - config', function () {
|
|||
afterEach(function () {
|
||||
configUtils.restore();
|
||||
});
|
||||
it('Uses direct keys when stripeDirect is true, regardles of which keys exist', function () {
|
||||
configUtils.set({stripeDirect: true});
|
||||
|
||||
const settingsCache = createSettingsMock({setDirect: true, setConnect: Math.random() < 0.5});
|
||||
const urlUtils = createUrlUtilsMock();
|
||||
|
||||
const membersConfig = new MembersConfigProvider({
|
||||
config: configUtils.config,
|
||||
settingsCache,
|
||||
urlUtils,
|
||||
ghostVersion: {original: 'v7357'},
|
||||
logging: console
|
||||
});
|
||||
|
||||
const paymentConfig = membersConfig.getStripePaymentConfig();
|
||||
|
||||
should.equal(paymentConfig.publicKey, 'direct_publishable');
|
||||
should.equal(paymentConfig.secretKey, 'direct_secret');
|
||||
});
|
||||
|
||||
it('Does not use connect keys if stripeDirect is true, and the direct keys do not exist', function () {
|
||||
configUtils.set({stripeDirect: true});
|
||||
const settingsCache = createSettingsMock({setDirect: false, setConnect: true});
|
||||
const urlUtils = createUrlUtilsMock();
|
||||
|
||||
const membersConfig = new MembersConfigProvider({
|
||||
config: configUtils.config,
|
||||
settingsCache,
|
||||
urlUtils,
|
||||
ghostVersion: {original: 'v7357'},
|
||||
logging: console
|
||||
});
|
||||
|
||||
const paymentConfig = membersConfig.getStripePaymentConfig();
|
||||
|
||||
should.equal(paymentConfig, null);
|
||||
});
|
||||
|
||||
it('Uses connect keys when stripeDirect is false, and the connect keys exist', function () {
|
||||
configUtils.set({stripeDirect: false});
|
||||
const settingsCache = createSettingsMock({setDirect: true, setConnect: true});
|
||||
const urlUtils = createUrlUtilsMock();
|
||||
|
||||
const membersConfig = new MembersConfigProvider({
|
||||
config: configUtils.config,
|
||||
settingsCache,
|
||||
urlUtils,
|
||||
ghostVersion: {original: 'v7357'},
|
||||
logging: console
|
||||
});
|
||||
|
||||
const paymentConfig = membersConfig.getStripePaymentConfig();
|
||||
|
||||
should.equal(paymentConfig.publicKey, 'connect_publishable');
|
||||
should.equal(paymentConfig.secretKey, 'connect_secret');
|
||||
});
|
||||
|
||||
it('Uses direct keys when stripeDirect is false, but the connect keys do not exist', function () {
|
||||
configUtils.set({stripeDirect: false});
|
||||
const settingsCache = createSettingsMock({setDirect: true, setConnect: false});
|
||||
const urlUtils = createUrlUtilsMock();
|
||||
|
||||
const membersConfig = new MembersConfigProvider({
|
||||
config: configUtils.config,
|
||||
settingsCache,
|
||||
urlUtils,
|
||||
ghostVersion: {original: 'v7357'},
|
||||
logging: console
|
||||
});
|
||||
|
||||
const paymentConfig = membersConfig.getStripePaymentConfig();
|
||||
|
||||
should.equal(paymentConfig.publicKey, 'direct_publishable');
|
||||
should.equal(paymentConfig.secretKey, 'direct_secret');
|
||||
});
|
||||
|
||||
it('Includes the subdirectory in the webhookHandlerUrl', function () {
|
||||
configUtils.set({
|
||||
|
|
85
test/unit/services/stripe/config.test.js
Normal file
85
test/unit/services/stripe/config.test.js
Normal file
|
@ -0,0 +1,85 @@
|
|||
const should = require('should');
|
||||
const sinon = require('sinon');
|
||||
|
||||
const {getConfig} = require('../../../../core/server/services/stripe/config');
|
||||
|
||||
describe('Stripe - config', function () {
|
||||
it('Uses direct keys when stripeDirect is true, regardles of which keys exist', function () {
|
||||
const fakeSettings = {
|
||||
get: sinon.stub()
|
||||
};
|
||||
const fakeConfig = {
|
||||
get: sinon.stub()
|
||||
};
|
||||
|
||||
fakeSettings.get.withArgs('stripe_connect_secret_key').returns('connect_secret');
|
||||
fakeSettings.get.withArgs('stripe_connect_publishable_key').returns('connect_publishable');
|
||||
fakeSettings.get.withArgs('stripe_secret_key').returns('direct_secret');
|
||||
fakeSettings.get.withArgs('stripe_publishable_key').returns('direct_publishable');
|
||||
fakeConfig.get.withArgs('stripeDirect').returns(true);
|
||||
|
||||
const config = getConfig(fakeSettings, fakeConfig);
|
||||
|
||||
should.equal(config.publicKey, 'direct_publishable');
|
||||
should.equal(config.secretKey, 'direct_secret');
|
||||
});
|
||||
|
||||
it('Does not use connect keys if stripeDirect is true, and the direct keys do not exist', function () {
|
||||
const fakeSettings = {
|
||||
get: sinon.stub()
|
||||
};
|
||||
const fakeConfig = {
|
||||
get: sinon.stub()
|
||||
};
|
||||
|
||||
fakeSettings.get.withArgs('stripe_connect_secret_key').returns('connect_secret');
|
||||
fakeSettings.get.withArgs('stripe_connect_publishable_key').returns('connect_publishable');
|
||||
fakeSettings.get.withArgs('stripe_secret_key').returns(null);
|
||||
fakeSettings.get.withArgs('stripe_publishable_key').returns(null);
|
||||
fakeConfig.get.withArgs('stripeDirect').returns(true);
|
||||
|
||||
const config = getConfig(fakeSettings, fakeConfig);
|
||||
|
||||
should.equal(config, null);
|
||||
});
|
||||
|
||||
it('Uses connect keys when stripeDirect is false, and the connect keys exist', function () {
|
||||
const fakeSettings = {
|
||||
get: sinon.stub()
|
||||
};
|
||||
const fakeConfig = {
|
||||
get: sinon.stub()
|
||||
};
|
||||
|
||||
fakeSettings.get.withArgs('stripe_connect_secret_key').returns('connect_secret');
|
||||
fakeSettings.get.withArgs('stripe_connect_publishable_key').returns('connect_publishable');
|
||||
fakeSettings.get.withArgs('stripe_secret_key').returns('direct_secret');
|
||||
fakeSettings.get.withArgs('stripe_publishable_key').returns('direct_publishable');
|
||||
fakeConfig.get.withArgs('stripeDirect').returns(false);
|
||||
|
||||
const config = getConfig(fakeSettings, fakeConfig);
|
||||
|
||||
should.equal(config.publicKey, 'connect_publishable');
|
||||
should.equal(config.secretKey, 'connect_secret');
|
||||
});
|
||||
|
||||
it('Uses direct keys when stripeDirect is false, but the connect keys do not exist', function () {
|
||||
const fakeSettings = {
|
||||
get: sinon.stub()
|
||||
};
|
||||
const fakeConfig = {
|
||||
get: sinon.stub()
|
||||
};
|
||||
|
||||
fakeSettings.get.withArgs('stripe_connect_secret_key').returns(null);
|
||||
fakeSettings.get.withArgs('stripe_connect_publishable_key').returns(null);
|
||||
fakeSettings.get.withArgs('stripe_secret_key').returns('direct_secret');
|
||||
fakeSettings.get.withArgs('stripe_publishable_key').returns('direct_publishable');
|
||||
fakeConfig.get.withArgs('stripeDirect').returns(false);
|
||||
|
||||
const config = getConfig(fakeSettings, fakeConfig);
|
||||
|
||||
should.equal(config.publicKey, 'direct_publishable');
|
||||
should.equal(config.secretKey, 'direct_secret');
|
||||
});
|
||||
});
|
|
@ -1493,10 +1493,10 @@
|
|||
"@tryghost/domain-events" "^0.1.2"
|
||||
"@tryghost/member-events" "^0.2.1"
|
||||
|
||||
"@tryghost/members-api@1.39.1":
|
||||
version "1.39.1"
|
||||
resolved "https://registry.yarnpkg.com/@tryghost/members-api/-/members-api-1.39.1.tgz#5afcb41db0dad037c7ff42ba0ebaaaa60789448b"
|
||||
integrity sha512-FCtI81Lhu/LbM1eQ2LxRKvK7wIM0lRv22WK/l4BUC5cYFMUhlbhtyhr45s9TH6P4/7B3YoXJkmBjEfq6MYntlA==
|
||||
"@tryghost/members-api@2.0.0":
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@tryghost/members-api/-/members-api-2.0.0.tgz#01a7d46004705be86243e0f77f20685829b09fe0"
|
||||
integrity sha512-lDGJtf6lO2vNVK4cqn7X5nJUIpZbRSnc3z32/3r5dL7b4xTR0wwHrDR9WvsROBkCxbPVvg7XyxGO9t316l5jDw==
|
||||
dependencies:
|
||||
"@tryghost/debug" "^0.1.2"
|
||||
"@tryghost/errors" "^0.2.9"
|
||||
|
|
Loading…
Reference in a new issue