Ghost/core/server/api/canary/settings.js
Fabien 'egg' O'Carroll a565da06b2
🐛 Fixed Offer Redemptions being over counted (#13988)
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
2022-01-18 17:56:47 +02:00

222 lines
6.7 KiB
JavaScript

const Promise = require('bluebird');
const _ = require('lodash');
const models = require('../../models');
const routeSettings = require('../../services/route-settings');
const tpl = require('@tryghost/tpl');
const {BadRequestError} = require('@tryghost/errors');
const settingsService = require('../../services/settings');
const membersService = require('../../services/members');
const stripeService = require('../../services/stripe');
const settingsBREADService = settingsService.getSettingsBREADServiceInstance();
const messages = {
failedSendingEmail: 'Failed Sending Email'
};
module.exports = {
docName: 'settings',
browse: {
options: ['group'],
permissions: true,
query(frame) {
return settingsBREADService.browse(frame.options.context);
}
},
read: {
options: ['key'],
validation: {
options: {
key: {
required: true
}
}
},
permissions: {
identifier(frame) {
return frame.options.key;
}
},
query(frame) {
return settingsBREADService.read(frame.options.key, frame.options.context);
}
},
validateMembersEmailUpdate: {
options: [
'token',
'action'
],
permissions: false,
validation: {
options: {
token: {
required: true
},
action: {
values: ['fromaddressupdate', 'supportaddressupdate']
}
}
},
async query(frame) {
// This is something you have to do if you want to use the "framework" with access to the raw req/res
frame.response = async function (req, res) {
try {
const {token, action} = frame.options;
const updatedEmailAddress = await membersService.settings.getEmailFromToken({token});
const actionToKeyMapping = {
fromAddressUpdate: 'members_from_address',
supportAddressUpdate: 'members_support_address'
};
if (updatedEmailAddress) {
return models.Settings.edit({
key: actionToKeyMapping[action],
value: updatedEmailAddress
}).then(() => {
// Redirect to Ghost-Admin settings page
const adminLink = membersService.settings.getAdminRedirectLink({type: action});
res.redirect(adminLink);
});
} else {
return Promise.reject(new BadRequestError({
message: 'Invalid token!'
}));
}
} catch (err) {
return Promise.reject(new BadRequestError({
err,
message: 'Invalid token!'
}));
}
};
}
},
updateMembersEmail: {
permissions: {
method: 'edit'
},
data: [
'email',
'type'
],
async query(frame) {
const {email, type} = frame.data;
try {
// Send magic link to update fromAddress
await membersService.settings.sendEmailAddressUpdateMagicLink({
email,
type
});
} catch (err) {
throw new BadRequestError({
err,
message: tpl(messages.failedSendingEmail)
});
}
}
},
disconnectStripeConnectIntegration: {
permissions: {
method: 'edit'
},
async query(frame) {
const hasActiveStripeSubscriptions = await membersService.api.hasActiveStripeSubscriptions();
if (hasActiveStripeSubscriptions) {
throw new BadRequestError({
message: 'Cannot disconnect Stripe whilst you have active subscriptions.'
});
}
await stripeService.disconnect();
return models.Settings.edit([{
key: 'stripe_connect_publishable_key',
value: null
}, {
key: 'stripe_connect_secret_key',
value: null
}, {
key: 'stripe_connect_livemode',
value: null
}, {
key: 'stripe_connect_display_name',
value: null
}, {
key: 'stripe_connect_account_id',
value: null
}, {
key: 'members_stripe_webhook_id',
value: null
}, {
key: 'members_stripe_webhook_secret',
value: null
}], frame.options);
}
},
edit: {
headers: {
cacheInvalidate: true
},
permissions: {
unsafeAttrsObject(frame) {
return _.find(frame.data.settings, {key: 'labs'});
}
},
async query(frame) {
let stripeConnectData;
const stripeConnectIntegrationToken = frame.data.settings.find(setting => setting.key === 'stripe_connect_integration_token');
if (stripeConnectIntegrationToken && stripeConnectIntegrationToken.value) {
const getSessionProp = prop => frame.original.session[prop];
stripeConnectData = await settingsBREADService.getStripeConnectData(
stripeConnectIntegrationToken,
getSessionProp,
membersService.stripeConnect.getStripeConnectTokenData
);
}
return await settingsBREADService.edit(frame.data.settings, frame.options, stripeConnectData);
}
},
upload: {
headers: {
cacheInvalidate: true
},
permissions: {
method: 'edit'
},
async query(frame) {
await routeSettings.api.setFromFilePath(frame.file.path);
const getRoutesHash = () => routeSettings.api.getCurrentHash();
await settingsService.syncRoutesHash(getRoutesHash);
}
},
download: {
headers: {
disposition: {
type: 'yaml',
value: 'routes.yaml'
}
},
response: {
format: 'plain'
},
permissions: {
method: 'browse'
},
query() {
return routeSettings.api.get();
}
}
};