mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-11-24 14:43:08 +03:00
Refactored webhook creation (#175)
no-issue * Refactored model dependencies This groups all of the model depenencies into a single models object, and renames the models with more concise identifiers * Fixed spacing * Added webhook support to metadata * Refactored stripe configure to have better logging * Refactored webhook creation to reuse existing webhook * Installed @types/stripe
This commit is contained in:
parent
68dbfb707d
commit
ac923af0f7
@ -27,9 +27,12 @@ module.exports = function MembersApi({
|
||||
getHTML,
|
||||
getSubject
|
||||
},
|
||||
memberStripeCustomerModel,
|
||||
stripeCustomerSubscriptionModel,
|
||||
memberModel,
|
||||
models: {
|
||||
StripeWebhook,
|
||||
StripeCustomer,
|
||||
StripeCustomerSubscription,
|
||||
Member
|
||||
},
|
||||
logger
|
||||
}) {
|
||||
if (logger) {
|
||||
@ -37,10 +40,14 @@ module.exports = function MembersApi({
|
||||
}
|
||||
|
||||
const {encodeIdentityToken, decodeToken} = Tokens({privateKey, publicKey, issuer});
|
||||
const metadata = Metadata({memberStripeCustomerModel, stripeCustomerSubscriptionModel});
|
||||
const metadata = Metadata({
|
||||
StripeWebhook,
|
||||
StripeCustomer,
|
||||
StripeCustomerSubscription
|
||||
});
|
||||
|
||||
async function hasActiveStripeSubscriptions() {
|
||||
const firstActiveSubscription = await stripeCustomerSubscriptionModel.findOne({
|
||||
const firstActiveSubscription = await StripeCustomerSubscription.findOne({
|
||||
status: 'active'
|
||||
});
|
||||
|
||||
@ -48,7 +55,7 @@ module.exports = function MembersApi({
|
||||
return true;
|
||||
}
|
||||
|
||||
const firstTrialingSubscription = await stripeCustomerSubscriptionModel.findOne({
|
||||
const firstTrialingSubscription = await StripeCustomerSubscription.findOne({
|
||||
status: 'trialing'
|
||||
});
|
||||
|
||||
@ -92,7 +99,7 @@ module.exports = function MembersApi({
|
||||
getSubject
|
||||
});
|
||||
|
||||
async function sendEmailWithMagicLink({email, requestedType, payload, options = {forceEmailType: false}}){
|
||||
async function sendEmailWithMagicLink({email, requestedType, payload, options = {forceEmailType: false}}) {
|
||||
if (options.forceEmailType) {
|
||||
return magicLinkService.sendMagicLink({email, payload, subject: email, type: requestedType});
|
||||
}
|
||||
@ -111,7 +118,7 @@ module.exports = function MembersApi({
|
||||
|
||||
const users = Users({
|
||||
stripe,
|
||||
memberModel
|
||||
Member
|
||||
});
|
||||
|
||||
async function getMemberDataFromMagicLinkToken(token) {
|
||||
@ -139,11 +146,11 @@ module.exports = function MembersApi({
|
||||
return getMemberIdentityData(email);
|
||||
}
|
||||
|
||||
async function getMemberIdentityData(email){
|
||||
async function getMemberIdentityData(email) {
|
||||
return users.get({email});
|
||||
}
|
||||
|
||||
async function getMemberIdentityToken(email){
|
||||
async function getMemberIdentityToken(email) {
|
||||
const member = await getMemberIdentityData(email);
|
||||
if (!member) {
|
||||
return null;
|
||||
|
@ -1,13 +1,15 @@
|
||||
let MemberStripeCustomer;
|
||||
let StripeCustomerSubscription;
|
||||
|
||||
async function setMetadata(module, metadata) {
|
||||
module.exports = function ({
|
||||
StripeWebhook,
|
||||
StripeCustomer,
|
||||
StripeCustomerSubscription
|
||||
}) {
|
||||
async function setMetadata(module, metadata) {
|
||||
if (module !== 'stripe') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (metadata.customer) {
|
||||
await MemberStripeCustomer.upsert(metadata.customer, {
|
||||
await StripeCustomer.upsert(metadata.customer, {
|
||||
customer_id: metadata.customer.customer_id
|
||||
});
|
||||
}
|
||||
@ -18,15 +20,21 @@ async function setMetadata(module, metadata) {
|
||||
});
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
if (metadata.webhook) {
|
||||
await StripeWebhook.upsert(metadata.webhook, {
|
||||
webhook_id: metadata.webhook.webhook_id
|
||||
});
|
||||
}
|
||||
|
||||
async function getMetadata(module, member) {
|
||||
return;
|
||||
}
|
||||
|
||||
async function getMetadata(module, member) {
|
||||
if (module !== 'stripe') {
|
||||
return;
|
||||
}
|
||||
|
||||
const customers = (await MemberStripeCustomer.findAll({
|
||||
const customers = (await StripeCustomer.findAll({
|
||||
filter: `member_id:${member.id}`
|
||||
})).toJSON();
|
||||
|
||||
@ -41,14 +49,7 @@ async function getMetadata(module, member) {
|
||||
customers: customers,
|
||||
subscriptions: subscriptions
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = function ({
|
||||
memberStripeCustomerModel,
|
||||
stripeCustomerSubscriptionModel
|
||||
}) {
|
||||
MemberStripeCustomer = memberStripeCustomerModel;
|
||||
StripeCustomerSubscription = stripeCustomerSubscriptionModel;
|
||||
}
|
||||
|
||||
return {
|
||||
setMetadata,
|
||||
|
@ -1,6 +1,6 @@
|
||||
const debug = require('ghost-ignition').debug('stripe');
|
||||
const _ = require('lodash');
|
||||
const {retrieve, list, create, update, del} = require('./api/stripeRequests');
|
||||
const {retrieve, create, update, del} = require('./api/stripeRequests');
|
||||
const api = require('./api');
|
||||
|
||||
const STRIPE_API_VERSION = '2019-09-09';
|
||||
@ -39,33 +39,41 @@ module.exports = class StripePaymentProcessor {
|
||||
this._checkoutCancelUrl = config.checkoutCancelUrl;
|
||||
this._billingSuccessUrl = config.billingSuccessUrl;
|
||||
this._billingCancelUrl = config.billingCancelUrl;
|
||||
this._webhookHandlerUrl = config.webhookHandlerUrl;
|
||||
|
||||
try {
|
||||
this._product = await api.products.ensure(this._stripe, config.product);
|
||||
} catch (err) {
|
||||
this.logging.error('There was an error creating the Stripe Product');
|
||||
this.logging.error(err);
|
||||
return this._rejectReady(err);
|
||||
}
|
||||
|
||||
/**
|
||||
* @type Array<import('stripe').plans.IPlan>
|
||||
*/
|
||||
this._plans = [];
|
||||
for (const planSpec of config.plans) {
|
||||
try {
|
||||
const plan = await api.plans.ensure(this._stripe, planSpec, this._product);
|
||||
this._plans.push(plan);
|
||||
} catch (err) {
|
||||
this.logging.error('There was an error creating the Stripe Plan');
|
||||
this.logging.error(err);
|
||||
return this._rejectReady(err);
|
||||
}
|
||||
}
|
||||
|
||||
const webhooks = await list(this._stripe, 'webhookEndpoints', {
|
||||
limit: 100
|
||||
if (process.env.WEBHOOK_SECRET) {
|
||||
this.logging.warn(`Skipping Stripe webhook creation and validation, using WEBHOOK_SECRET environment variable`);
|
||||
this._webhookSecret = process.env.WEBHOOK_SECRET;
|
||||
return this._resolveReady({
|
||||
product: this._product,
|
||||
plans: this._plans
|
||||
});
|
||||
|
||||
const webhookToDelete = webhooks.data.find((webhook) => {
|
||||
return webhook.url === this._webhookHandlerUrl;
|
||||
});
|
||||
|
||||
if (webhookToDelete) {
|
||||
await del(this._stripe, 'webhookEndpoints', webhookToDelete.id);
|
||||
}
|
||||
|
||||
try {
|
||||
const webhook = await create(this._stripe, 'webhookEndpoints', {
|
||||
url: this._webhookHandlerUrl,
|
||||
api_version: STRIPE_API_VERSION,
|
||||
const webhookConfig = {
|
||||
url: config.webhookHandlerUrl,
|
||||
enabled_events: [
|
||||
'checkout.session.completed',
|
||||
'customer.subscription.deleted',
|
||||
@ -73,15 +81,67 @@ module.exports = class StripePaymentProcessor {
|
||||
'invoice.payment_succeeded',
|
||||
'invoice.payment_failed'
|
||||
]
|
||||
});
|
||||
this._webhookSecret = process.env.WEBHOOK_SECRET || webhook.secret;
|
||||
};
|
||||
|
||||
const setupWebhook = async (id, secret, opts = {}) => {
|
||||
if (!id || !secret || opts.forceCreate) {
|
||||
if (id && !opts.skipDelete) {
|
||||
try {
|
||||
this.logging.info(`Deleting Stripe webhook ${id}`);
|
||||
await del(this._stripe, 'webhookEndpoints', id);
|
||||
} catch (err) {
|
||||
this._webhookSecret = process.env.WEBHOOK_SECRET;
|
||||
this.logging.warn(err);
|
||||
this.logging.error(`Unable to delete Stripe webhook with id: ${id}`);
|
||||
this.logging.error(err);
|
||||
}
|
||||
debug(`Webhook secret set to ${this._webhookSecret}`);
|
||||
}
|
||||
try {
|
||||
this.logging.info(`Creating Stripe webhook with url: ${webhookConfig.url}, version: ${STRIPE_API_VERSION}, events: ${webhookConfig.enabled_events.join(', ')}`);
|
||||
const webhook = await create(this._stripe, 'webhookEndpoints', Object.assign({}, webhookConfig, {
|
||||
api_version: STRIPE_API_VERSION
|
||||
}));
|
||||
return {
|
||||
id: webhook.id,
|
||||
secret: webhook.secret
|
||||
};
|
||||
} catch (err) {
|
||||
this.logging.error('Failed to create Stripe webhook. For local development please see https://ghost.org/docs/members/webhooks/#stripe-webhooks');
|
||||
this.logging.error(err);
|
||||
throw err;
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
this.logging.info(`Updating Stripe webhook ${id} with url: ${webhookConfig.url}, events: ${webhookConfig.enabled_events.join(', ')}`);
|
||||
const updatedWebhook = await update(this._stripe, 'webhookEndpoints', id, webhookConfig);
|
||||
|
||||
if (updatedWebhook.api_version !== STRIPE_API_VERSION) {
|
||||
throw new Error(`Webhook ${id} has api_version ${updatedWebhook.api_version}, expected ${STRIPE_API_VERSION}`);
|
||||
}
|
||||
|
||||
return {
|
||||
id,
|
||||
secret
|
||||
};
|
||||
} catch (err) {
|
||||
this.logging.error(`Unable to update Stripe webhook ${id}`);
|
||||
this.logging.error(err);
|
||||
if (err.code === 'resource_missing') {
|
||||
return setupWebhook(id, secret, {skipDelete: true, forceCreate: true});
|
||||
}
|
||||
return setupWebhook(id, secret, {skipDelete: false, forceCreate: true});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
const webhook = await setupWebhook(config.webhook.id, config.webhook.secret);
|
||||
await this.storage.set({
|
||||
webhook: {
|
||||
webhook_id: webhook.id,
|
||||
secret: webhook.secret
|
||||
}
|
||||
});
|
||||
this._webhookSecret = webhook.secret;
|
||||
} catch (err) {
|
||||
debug(`Error configuring ${err.message}`);
|
||||
return this._rejectReady(err);
|
||||
}
|
||||
|
||||
|
@ -2,9 +2,11 @@ const _ = require('lodash');
|
||||
const debug = require('ghost-ignition').debug('users');
|
||||
const common = require('./common');
|
||||
|
||||
let Member;
|
||||
|
||||
async function createMember({email, name, note, labels, geolocation}) {
|
||||
module.exports = function ({
|
||||
stripe,
|
||||
Member
|
||||
}) {
|
||||
async function createMember({email, name, note, labels, geolocation}) {
|
||||
const model = await Member.add({
|
||||
email,
|
||||
name,
|
||||
@ -14,9 +16,9 @@ async function createMember({email, name, note, labels, geolocation}) {
|
||||
});
|
||||
const member = model.toJSON();
|
||||
return member;
|
||||
}
|
||||
}
|
||||
|
||||
async function getMember(data, options = {}) {
|
||||
async function getMember(data, options = {}) {
|
||||
if (!data.email && !data.id && !data.uuid) {
|
||||
return null;
|
||||
}
|
||||
@ -26,36 +28,30 @@ async function getMember(data, options = {}) {
|
||||
}
|
||||
const member = model.toJSON(options);
|
||||
return member;
|
||||
}
|
||||
}
|
||||
|
||||
async function updateMember(data, options = {}) {
|
||||
async function updateMember(data, options = {}) {
|
||||
const attrs = _.pick(data, ['email', 'name', 'note', 'subscribed', 'geolocation']);
|
||||
|
||||
const model = await Member.edit(attrs, options);
|
||||
|
||||
const member = model.toJSON(options);
|
||||
return member;
|
||||
}
|
||||
}
|
||||
|
||||
function deleteMember(options) {
|
||||
function deleteMember(options) {
|
||||
options = options || {};
|
||||
return Member.destroy(options);
|
||||
}
|
||||
}
|
||||
|
||||
function listMembers(options) {
|
||||
function listMembers(options) {
|
||||
return Member.findPage(options).then((models) => {
|
||||
return {
|
||||
members: models.data.map(model => model.toJSON(options)),
|
||||
meta: models.meta
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = function ({
|
||||
stripe,
|
||||
memberModel
|
||||
}) {
|
||||
Member = memberModel;
|
||||
}
|
||||
|
||||
async function getStripeSubscriptions(member) {
|
||||
if (!stripe) {
|
||||
|
@ -17,6 +17,7 @@
|
||||
"gateway"
|
||||
],
|
||||
"devDependencies": {
|
||||
"@types/stripe": "^7.13.24",
|
||||
"jsdom": "15.2.1",
|
||||
"mocha": "6.2.2",
|
||||
"nock": "12.0.3",
|
||||
|
Loading…
Reference in New Issue
Block a user