mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-12-21 09:52:06 +03:00
a565da06b2
refs https://github.com/TryGhost/Team/issues/1257 Offer Redemptions were being overcounted due to the way we were updating Stripe configuration for the Members service. We would create a new instance of the members-api, which would have event handlers for creating Offer Redemptions - by creating a new instance each time Stripe config changed, we would overcount them. Here we've pulled out Stripe related logic into the Stripe service, and updated it internally - rather than creating a new instance. This means that we've been able to remove all of the logic for re-instantiating the members-api. - Bumped members-api & stripe-service - Removed reinstantiation of members-api - Used stripe service to execute migrations - Updated Stripe Service to handle webhooks & migrations - Used webhook controller from stripe service - Used disconnect method from stripe service - Removed unused stripe dependency - Removed Stripe webhook config from members-api
188 lines
6.7 KiB
JavaScript
188 lines
6.7 KiB
JavaScript
const errors = require('@tryghost/errors');
|
|
const tpl = require('@tryghost/tpl');
|
|
const MembersSSR = require('@tryghost/members-ssr');
|
|
const db = require('../../data/db');
|
|
const MembersConfigProvider = require('./config');
|
|
const MembersCSVImporter = require('@tryghost/members-importer');
|
|
const MembersStats = require('./stats/members-stats');
|
|
const createMembersSettingsInstance = require('./settings');
|
|
const logging = require('@tryghost/logging');
|
|
const urlUtils = require('../../../shared/url-utils');
|
|
const labsService = require('../../../shared/labs');
|
|
const settingsCache = require('../../../shared/settings-cache');
|
|
const config = require('../../../shared/config');
|
|
const models = require('../../models');
|
|
const _ = require('lodash');
|
|
const {GhostMailer} = require('../mail');
|
|
const jobsService = require('../jobs');
|
|
|
|
const messages = {
|
|
noLiveKeysInDevelopment: 'Cannot use live stripe keys in development. Please restart in production mode.',
|
|
sslRequiredForStripe: 'Cannot run Ghost without SSL when Stripe is connected. Please update your url config to use "https://".',
|
|
remoteWebhooksInDevelopment: 'Cannot use remote webhooks in development. See https://ghost.org/docs/webhooks/#stripe-webhooks for developing with Stripe.',
|
|
emailVerificationNeeded: `We're hard at work processing your import. To make sure you get great deliverability on a list of that size, we'll need to enable some extra features for your account. A member of our team will be in touch with you by email to review your account make sure everything is configured correctly so you're ready to go.`,
|
|
emailVerificationEmailMessage: `Email verification needed for site: {siteUrl}, just imported: {importedNumber} members.`
|
|
};
|
|
|
|
const ghostMailer = new GhostMailer();
|
|
|
|
const membersConfig = new MembersConfigProvider({
|
|
config,
|
|
settingsCache,
|
|
urlUtils
|
|
});
|
|
|
|
let membersApi;
|
|
let membersSettings;
|
|
|
|
/**
|
|
* @description Calculates threshold based on following formula
|
|
* Threshold = max{[current number of members], [volume threshold]}
|
|
*
|
|
* @returns {Promise<number>}
|
|
*/
|
|
const fetchImportThreshold = async () => {
|
|
const membersTotal = await module.exports.stats.getTotalMembers();
|
|
const configThreshold = _.get(config.get('hostSettings'), 'emailVerification.importThreshold');
|
|
const volumeThreshold = (configThreshold === undefined) ? Infinity : configThreshold;
|
|
const threshold = Math.max(membersTotal, volumeThreshold);
|
|
|
|
return threshold;
|
|
};
|
|
|
|
const membersImporter = new MembersCSVImporter({
|
|
storagePath: config.getContentPath('data'),
|
|
getTimezone: () => settingsCache.get('timezone'),
|
|
getMembersApi: () => module.exports.api,
|
|
sendEmail: ghostMailer.send.bind(ghostMailer),
|
|
isSet: labsService.isSet.bind(labsService),
|
|
addJob: jobsService.addJob.bind(jobsService),
|
|
knex: db.knex,
|
|
urlFor: urlUtils.urlFor.bind(urlUtils),
|
|
fetchThreshold: fetchImportThreshold
|
|
});
|
|
|
|
const startEmailVerification = async (importedNumber) => {
|
|
const isVerifiedEmail = config.get('hostSettings:emailVerification:verified') === true;
|
|
|
|
if ((!isVerifiedEmail)) {
|
|
// Only trigger flag change and escalation email the first time
|
|
if (settingsCache.get('email_verification_required') !== true) {
|
|
await models.Settings.edit([{
|
|
key: 'email_verification_required',
|
|
value: true
|
|
}], {context: {internal: true}});
|
|
|
|
const escalationAddress = config.get('hostSettings:emailVerification:escalationAddress');
|
|
const fromAddress = config.get('user_email');
|
|
|
|
if (escalationAddress) {
|
|
ghostMailer.send({
|
|
subject: 'Email needs verification',
|
|
html: tpl(messages.emailVerificationEmailMessage, {
|
|
importedNumber,
|
|
siteUrl: urlUtils.getSiteUrl()
|
|
}),
|
|
forceTextContent: true,
|
|
from: fromAddress,
|
|
to: escalationAddress
|
|
});
|
|
}
|
|
}
|
|
|
|
throw new errors.ValidationError({
|
|
message: tpl(messages.emailVerificationNeeded)
|
|
});
|
|
}
|
|
};
|
|
|
|
const processImport = async (options) => {
|
|
const result = await membersImporter.process(options);
|
|
const freezeTriggered = result.meta.freeze;
|
|
const importSize = result.meta.originalImportSize;
|
|
delete result.meta.freeze;
|
|
delete result.meta.originalImportSize;
|
|
|
|
if (freezeTriggered) {
|
|
await startEmailVerification(importSize);
|
|
}
|
|
|
|
return result;
|
|
};
|
|
|
|
module.exports = {
|
|
async init() {
|
|
const stripeService = require('../stripe');
|
|
const createMembersApiInstance = require('./api');
|
|
const env = config.get('env');
|
|
|
|
// @TODO Move to stripe service
|
|
if (env !== 'production') {
|
|
if (stripeService.api.configured && stripeService.api.mode === 'live') {
|
|
throw new errors.IncorrectUsageError({
|
|
message: tpl(messages.noLiveKeysInDevelopment)
|
|
});
|
|
}
|
|
} else {
|
|
const siteUrl = urlUtils.getSiteUrl();
|
|
if (!/^https/.test(siteUrl) && stripeService.api.configured) {
|
|
throw new errors.IncorrectUsageError({
|
|
message: tpl(messages.sslRequiredForStripe)
|
|
});
|
|
}
|
|
}
|
|
if (!membersApi) {
|
|
membersApi = createMembersApiInstance(membersConfig);
|
|
|
|
membersApi.bus.on('error', function (err) {
|
|
logging.error(err);
|
|
});
|
|
}
|
|
|
|
(async () => {
|
|
try {
|
|
const collection = await models.SingleUseToken.fetchAll();
|
|
await collection.invokeThen('destroy');
|
|
} catch (err) {
|
|
logging.error(err);
|
|
}
|
|
})();
|
|
|
|
await stripeService.migrations.execute();
|
|
},
|
|
contentGating: require('./content-gating'),
|
|
|
|
config: membersConfig,
|
|
|
|
get api() {
|
|
return membersApi;
|
|
},
|
|
|
|
get settings() {
|
|
if (!membersSettings) {
|
|
membersSettings = createMembersSettingsInstance(membersConfig);
|
|
}
|
|
return membersSettings;
|
|
},
|
|
|
|
ssr: MembersSSR({
|
|
cookieSecure: urlUtils.isSSL(urlUtils.getSiteUrl()),
|
|
cookieKeys: [settingsCache.get('theme_session_secret')],
|
|
cookieName: 'ghost-members-ssr',
|
|
cookieCacheName: 'ghost-members-ssr-cache',
|
|
getMembersApi: () => module.exports.api
|
|
}),
|
|
|
|
stripeConnect: require('./stripe-connect'),
|
|
|
|
processImport: processImport,
|
|
|
|
stats: new MembersStats({
|
|
db: db,
|
|
settingsCache: settingsCache,
|
|
isSQLite: config.get('database:client') === 'sqlite3'
|
|
})
|
|
|
|
};
|
|
module.exports.middleware = require('./middleware');
|