2019-11-07 13:13:20 +03:00
|
|
|
const {URL} = require('url');
|
|
|
|
const crypto = require('crypto');
|
2020-04-29 20:23:55 +03:00
|
|
|
const path = require('path');
|
2019-11-07 13:13:20 +03:00
|
|
|
|
2020-01-28 07:25:00 +03:00
|
|
|
const COMPLIMENTARY_PLAN = {
|
|
|
|
name: 'Complimentary',
|
|
|
|
currency: 'usd',
|
|
|
|
interval: 'year',
|
|
|
|
amount: '0'
|
|
|
|
};
|
|
|
|
|
2020-05-28 18:55:23 +03:00
|
|
|
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;
|
|
|
|
}
|
2019-11-07 13:13:20 +03:00
|
|
|
|
2020-05-28 18:55:23 +03:00
|
|
|
/**
|
|
|
|
* @private
|
|
|
|
*/
|
|
|
|
_getDomain() {
|
|
|
|
const domain = this._urlUtils.urlFor('home', true).match(new RegExp('^https?://([^/:?#]+)(?:[/:?#]|$)', 'i'));
|
|
|
|
return domain && domain[1];
|
|
|
|
}
|
2020-04-17 12:29:23 +03:00
|
|
|
|
2020-05-28 18:55:23 +03:00
|
|
|
/**
|
|
|
|
*/
|
|
|
|
getEmailFromAddress() {
|
|
|
|
const subscriptionSettings = this._settingsCache.get('members_subscription_settings') || {};
|
2020-06-05 19:20:04 +03:00
|
|
|
const fromAddress = subscriptionSettings.fromAddress || 'noreply';
|
2020-04-17 12:29:23 +03:00
|
|
|
|
2020-06-05 19:20:04 +03:00
|
|
|
// Any fromAddress without domain uses site domain, like default setting `noreply`
|
|
|
|
if (fromAddress.indexOf('@') < 0) {
|
|
|
|
return `${fromAddress}@${this._getDomain()}`;
|
|
|
|
}
|
|
|
|
return fromAddress;
|
2020-05-28 18:55:23 +03:00
|
|
|
}
|
2020-04-17 12:29:23 +03:00
|
|
|
|
2020-05-28 18:55:23 +03:00
|
|
|
getPublicPlans() {
|
|
|
|
const CURRENCY_SYMBOLS = {
|
|
|
|
USD: '$',
|
|
|
|
AUD: '$',
|
|
|
|
CAD: '$',
|
|
|
|
GBP: '£',
|
|
|
|
EUR: '€'
|
|
|
|
};
|
|
|
|
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;
|
2020-04-17 12:29:23 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-28 18:58:51 +03:00
|
|
|
/**
|
|
|
|
* @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
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2020-06-09 14:02:38 +03:00
|
|
|
isStripeConnected() {
|
|
|
|
const paymentConfig = this.getStripePaymentConfig();
|
|
|
|
|
|
|
|
return (paymentConfig && paymentConfig.publicKey && paymentConfig.secretKey);
|
|
|
|
}
|
|
|
|
|
2020-05-28 18:55:23 +03:00
|
|
|
getStripePaymentConfig() {
|
|
|
|
const subscriptionSettings = this._settingsCache.get('members_subscription_settings');
|
2019-11-07 13:13:20 +03:00
|
|
|
|
2020-05-28 18:55:23 +03:00
|
|
|
const stripePaymentProcessor = subscriptionSettings.paymentProcessors.find(
|
|
|
|
paymentProcessor => paymentProcessor.adapter === 'stripe'
|
|
|
|
);
|
2019-11-07 13:13:20 +03:00
|
|
|
|
2020-05-28 18:55:23 +03:00
|
|
|
if (!stripePaymentProcessor || !stripePaymentProcessor.config) {
|
|
|
|
return null;
|
|
|
|
}
|
2019-11-07 13:13:20 +03:00
|
|
|
|
2020-05-28 18:55:23 +03:00
|
|
|
if (!stripePaymentProcessor.config.public_token || !stripePaymentProcessor.config.secret_token) {
|
|
|
|
return null;
|
|
|
|
}
|
2019-11-07 13:13:20 +03:00
|
|
|
|
2020-05-28 18:55:23 +03:00
|
|
|
// 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');
|
|
|
|
|
2020-05-28 18:58:51 +03:00
|
|
|
const stripeApiKeys = this.getStripeAPIKeys(
|
|
|
|
stripePaymentProcessor.config.public_token,
|
|
|
|
stripePaymentProcessor.config.secret_token
|
|
|
|
);
|
|
|
|
|
2020-05-28 18:55:23 +03:00
|
|
|
return {
|
2020-05-28 18:58:51 +03:00
|
|
|
publicKey: stripeApiKeys.publicKey,
|
|
|
|
secretKey: stripeApiKeys.secretKey,
|
2020-05-28 18:55:23 +03:00
|
|
|
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/'
|
|
|
|
}
|
|
|
|
};
|
2019-11-07 13:13:20 +03:00
|
|
|
}
|
|
|
|
|
2020-05-28 18:55:23 +03:00
|
|
|
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);
|
2019-11-07 13:13:20 +03:00
|
|
|
}
|
2020-05-28 18:55:23 +03:00
|
|
|
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;
|
2019-11-07 13:13:20 +03:00
|
|
|
}
|
2020-05-28 18:55:23 +03:00
|
|
|
|
|
|
|
getAllowSelfSignup() {
|
|
|
|
const subscriptionSettings = this._settingsCache.get('members_subscription_settings');
|
|
|
|
return subscriptionSettings.allowSelfSignup;
|
2019-11-07 13:13:20 +03:00
|
|
|
}
|
|
|
|
|
2020-05-28 18:55:23 +03:00
|
|
|
getTokenConfig() {
|
|
|
|
const {href: membersApiUrl} = new URL(
|
|
|
|
this._urlUtils.getApiPath({version: 'v3', type: 'members'}),
|
|
|
|
this._urlUtils.urlFor('admin', true)
|
|
|
|
);
|
2019-11-07 13:13:20 +03:00
|
|
|
|
2020-05-28 18:55:23 +03:00
|
|
|
return {
|
|
|
|
issuer: membersApiUrl,
|
|
|
|
publicKey: this._settingsCache.get('members_public_key'),
|
|
|
|
privateKey: this._settingsCache.get('members_private_key')
|
|
|
|
};
|
|
|
|
}
|
2019-11-07 13:13:20 +03:00
|
|
|
|
2020-05-28 18:55:23 +03:00
|
|
|
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;
|
|
|
|
}
|
2019-11-07 13:13:20 +03:00
|
|
|
}
|
|
|
|
|
2020-05-28 18:55:23 +03:00
|
|
|
module.exports = MembersConfigProvider;
|