Ghost/core/server/services/members/config.js

223 lines
7.7 KiB
JavaScript
Raw Normal View History

const {URL} = require('url');
const crypto = require('crypto');
const path = require('path');
const COMPLIMENTARY_PLAN = {
name: 'Complimentary',
currency: 'usd',
interval: 'year',
amount: '0'
};
class MembersConfigProvider {
/**
* @param {object} options
* @param {{get: (key: string) => any}} options.settingsCache
* @param {{get: (key: string) => any}} options.config
* @param {any} options.urlUtils
* @param {any} options.logging
* @param {{original: string}} options.ghostVersion
*/
constructor(options) {
this._settingsCache = options.settingsCache;
this._config = options.config;
this._urlUtils = options.urlUtils;
this._logging = options.logging;
this._ghostVersion = options.ghostVersion;
}
/**
* @private
*/
_getDomain() {
const domain = this._urlUtils.urlFor('home', true).match(new RegExp('^https?://([^/:?#]+)(?:[/:?#]|$)', 'i'));
return domain && domain[1];
}
/**
*/
getEmailFromAddress() {
const subscriptionSettings = this._settingsCache.get('members_subscription_settings') || {};
const fromAddress = subscriptionSettings.fromAddress || 'noreply';
// Any fromAddress without domain uses site domain, like default setting `noreply`
if (fromAddress.indexOf('@') < 0) {
return `${fromAddress}@${this._getDomain()}`;
}
return fromAddress;
}
getPublicPlans() {
const CURRENCY_SYMBOLS = {
USD: '$',
AUD: '$',
CAD: '$',
GBP: '£',
EUR: '€',
INR: '₹'
};
const defaultPriceData = {
monthly: 0,
yearly: 0
};
try {
const membersSettings = this._settingsCache.get('members_subscription_settings');
const stripeProcessor = membersSettings.paymentProcessors.find(
processor => processor.adapter === 'stripe'
);
const priceData = stripeProcessor.config.plans.reduce((prices, plan) => {
const numberAmount = 0 + plan.amount;
const dollarAmount = numberAmount ? Math.round(numberAmount / 100) : 0;
return Object.assign(prices, {
[plan.name.toLowerCase()]: dollarAmount
});
}, {});
priceData.currency = String.prototype.toUpperCase.call(stripeProcessor.config.currency || 'usd');
priceData.currency_symbol = CURRENCY_SYMBOLS[priceData.currency];
if (Number.isInteger(priceData.monthly) && Number.isInteger(priceData.yearly)) {
return priceData;
}
return defaultPriceData;
} catch (err) {
return defaultPriceData;
}
}
/**
* @function getStripeAPIKeys
* @desc Gets the stripe api keys from settings, respecting the stripeDirect config
*
* @param {string} publicKey - The publicKey to use if stripeDirect is enabled
* @param {string} secretKey - The secretKey to use if stripeDirect is enabled
*
* @returns {{publicKey: string, secretKey: string}}
*/
getStripeAPIKeys(publicKey, secretKey) {
const stripeDirect = this._config.get('stripeDirect');
const stripeConnectIntegration = this._settingsCache.get('stripe_connect_integration');
const hasStripeConnectKeys = stripeConnectIntegration.secret_key && stripeConnectIntegration.public_key;
if (stripeDirect || !hasStripeConnectKeys) {
return {
publicKey,
secretKey
};
}
return {
publicKey: stripeConnectIntegration.public_key,
secretKey: stripeConnectIntegration.secret_key
};
}
isStripeConnected() {
const paymentConfig = this.getStripePaymentConfig();
return (paymentConfig && paymentConfig.publicKey && paymentConfig.secretKey);
}
getStripePaymentConfig() {
const subscriptionSettings = this._settingsCache.get('members_subscription_settings');
const stripePaymentProcessor = subscriptionSettings.paymentProcessors.find(
paymentProcessor => paymentProcessor.adapter === 'stripe'
);
if (!stripePaymentProcessor || !stripePaymentProcessor.config) {
return null;
}
// NOTE: "Complimentary" plan has to be first in the queue so it is created even if regular plans are not configured
stripePaymentProcessor.config.plans.unshift(COMPLIMENTARY_PLAN);
const siteUrl = this._urlUtils.getSiteUrl();
const webhookHandlerUrl = new URL('/members/webhooks/stripe', siteUrl);
const checkoutSuccessUrl = new URL(siteUrl);
checkoutSuccessUrl.searchParams.set('stripe', 'success');
const checkoutCancelUrl = new URL(siteUrl);
checkoutCancelUrl.searchParams.set('stripe', 'cancel');
const billingSuccessUrl = new URL(siteUrl);
billingSuccessUrl.searchParams.set('stripe', 'billing-update-success');
const billingCancelUrl = new URL(siteUrl);
billingCancelUrl.searchParams.set('stripe', 'billing-update-cancel');
const stripeApiKeys = this.getStripeAPIKeys(
stripePaymentProcessor.config.public_token,
stripePaymentProcessor.config.secret_token
);
if (!stripeApiKeys.publicKey || !stripeApiKeys.secretKey) {
return null;
}
return {
publicKey: stripeApiKeys.publicKey,
secretKey: stripeApiKeys.secretKey,
checkoutSuccessUrl: checkoutSuccessUrl.href,
checkoutCancelUrl: checkoutCancelUrl.href,
billingSuccessUrl: billingSuccessUrl.href,
billingCancelUrl: billingCancelUrl.href,
webhookHandlerUrl: webhookHandlerUrl.href,
product: stripePaymentProcessor.config.product,
plans: stripePaymentProcessor.config.plans,
appInfo: {
name: 'Ghost',
partner_id: 'pp_partner_DKmRVtTs4j9pwZ',
version: this._ghostVersion.original,
url: 'https://ghost.org/'
}
};
}
getAuthSecret() {
const hexSecret = this._settingsCache.get('members_email_auth_secret');
if (!hexSecret) {
this._logging.warn('Could not find members_email_auth_secret, using dynamically generated secret');
return crypto.randomBytes(64);
}
const secret = Buffer.from(hexSecret, 'hex');
if (secret.length < 64) {
this._logging.warn('members_email_auth_secret not large enough (64 bytes), using dynamically generated secret');
return crypto.randomBytes(64);
}
return secret;
}
getAllowSelfSignup() {
const subscriptionSettings = this._settingsCache.get('members_subscription_settings');
return subscriptionSettings.allowSelfSignup;
}
getTokenConfig() {
const {href: membersApiUrl} = new URL(
this._urlUtils.getApiPath({version: 'v3', type: 'members'}),
this._urlUtils.urlFor('admin', true)
);
return {
issuer: membersApiUrl,
publicKey: this._settingsCache.get('members_public_key'),
privateKey: this._settingsCache.get('members_private_key')
};
}
getSigninURL(token, type) {
const siteUrl = this._urlUtils.getSiteUrl();
const signinURL = new URL(siteUrl);
signinURL.pathname = path.join(signinURL.pathname, '/members/');
signinURL.searchParams.set('token', token);
signinURL.searchParams.set('action', type);
return signinURL.href;
}
}
module.exports = MembersConfigProvider;