remove DEPRECATED message for bodyParser limit option

This commit is contained in:
eric-burel 2019-01-09 17:00:22 +01:00
parent b0e63d8b61
commit 9f31de189c
4 changed files with 330 additions and 132 deletions

View file

@ -81,7 +81,9 @@ const createApolloServer = ({
WebApp.connectHandlers.use(universalCookiesMiddleware());
// parse request
WebApp.connectHandlers.use(bodyParser.json({ limit: getSetting('apolloServer.jsonParserOptions.limit') }));
WebApp.connectHandlers.use(
bodyParser.json({limit: getSetting('apolloServer.jsonParserOptions.limit')})
);
WebApp.connectHandlers.use(
config.path,
bodyParser.text({type: 'application/graphql'})

View file

@ -10,7 +10,7 @@ import _merge from 'lodash/merge';
// 'Tracing by Apollo. Default is true on development and false on prod',
// true
//);
// registerSetting('apolloServer.jsonParserOptions.limit', undefined, 'DEPRECATED bodyParser jsonParser limit');
// registerSetting('apolloServer.jsonParserOptions.limit', undefined, 'bodyParser jsonParser limit');
// NOTE: some option can be functions, so they cannot be
// defined as Meteor settings, which are pure JSON (no function)

View file

@ -79,7 +79,7 @@ Migration function
const migrateIntlFields = async defaultLocale => {
if (!defaultLocale) {
throw new Error(
'Please pass the id of the locale to which to migrate your current content (e.g. migrateIntlFields(\'en\'))'
"Please pass the id of the locale to which to migrate your current content (e.g. migrateIntlFields('en'))"
);
}

View file

@ -36,26 +36,53 @@ Stripe charge lifecycle
*/
import { webAppConnectHandlersUse, debug, debugGroup, debugGroupEnd, getSetting, registerSetting, newMutation, updateMutator, Collections, registerCallback, runCallbacks, runCallbacksAsync, Connectors } from 'meteor/vulcan:core';
import {
webAppConnectHandlersUse,
debug,
debugGroup,
debugGroupEnd,
getSetting,
registerSetting,
newMutation,
updateMutator,
Collections,
registerCallback,
runCallbacks,
runCallbacksAsync,
Connectors,
} from 'meteor/vulcan:core';
import express from 'express';
import Stripe from 'stripe';
import Charges from '../../modules/charges/collection.js';
import Users from 'meteor/vulcan:users';
import { Products } from '../../modules/products.js';
import { Promise } from 'meteor/promise';
import {Products} from '../../modules/products.js';
import {Promise} from 'meteor/promise';
registerSetting('stripe', null, 'Stripe settings');
registerSetting('stripe.publishableKey', null, 'Publishable key', true);
registerSetting('stripe.publishableKeyTest', null, 'Publishable key (test)', true);
registerSetting(
'stripe.publishableKeyTest',
null,
'Publishable key (test)',
true
);
registerSetting('stripe.secretKey', null, 'Secret key');
registerSetting('stripe.secretKeyTest', null, 'Secret key (test)');
registerSetting('stripe.endpointSecret', null, 'Endpoint secret for webhook');
registerSetting('stripe.alwaysUseTest', false, 'Always use test keys in all environments', true);
registerSetting(
'stripe.alwaysUseTest',
false,
'Always use test keys in all environments',
true
);
const stripeSettings = getSetting('stripe');
// initialize Stripe
const keySecret = Meteor.isDevelopment || stripeSettings.alwaysUseTest ? stripeSettings.secretKeyTest : stripeSettings.secretKey;
const keySecret =
Meteor.isDevelopment || stripeSettings.alwaysUseTest
? stripeSettings.secretKeyTest
: stripeSettings.secretKey;
export const stripe = new Stripe(keySecret);
const sampleProduct = {
@ -70,17 +97,24 @@ const sampleProduct = {
Receive the action and call the appropriate handler
*/
export const receiveAction = async (args) => {
let collection, document, returnDocument = {};
export const receiveAction = async args => {
let collection,
document,
returnDocument = {};
const { userId, productKey, associatedCollection, associatedId, properties } = args;
const {
userId,
productKey,
associatedCollection,
associatedId,
properties,
} = args;
if (!stripeSettings) {
throw new Error('Please fill in your Stripe settings');
}
// if an associated collection name and document id have been provided,
// if an associated collection name and document id have been provided,
// get the associated collection and document
if (associatedCollection && associatedId) {
collection = _.findWhere(Collections, {_name: associatedCollection});
@ -90,7 +124,10 @@ export const receiveAction = async (args) => {
// get the product from Products (either object or function applied to doc)
// or default to sample product
const definedProduct = Products[productKey];
const product = typeof definedProduct === 'function' ? definedProduct(document) : definedProduct || sampleProduct;
const product =
typeof definedProduct === 'function'
? definedProduct(document)
: definedProduct || sampleProduct;
// get the user performing the transaction
const user = await Connectors.get(Users, userId);
@ -101,7 +138,7 @@ export const receiveAction = async (args) => {
userName: Users.getDisplayName(user),
userProfile: Users.getProfileUrl(user, true),
productKey,
...properties
...properties,
};
if (associatedCollection && associatedId) {
@ -109,17 +146,44 @@ export const receiveAction = async (args) => {
metadata.associatedId = associatedId;
}
metadata = await runCallbacks('stripe.receive.sync', metadata, { user, product, collection, document, args });
metadata = await runCallbacks('stripe.receive.sync', metadata, {
user,
product,
collection,
document,
args,
});
if (product.type === 'subscription') {
// if product is a subscription product, subscribe user to its plan
returnDocument = await createSubscription({ user, product, collection, document, metadata, args });
returnDocument = await createSubscription({
user,
product,
collection,
document,
metadata,
args,
});
} else {
// else, perform charge
returnDocument = await createCharge({ user, product, collection, document, metadata, args });
returnDocument = await createCharge({
user,
product,
collection,
document,
metadata,
args,
});
}
runCallbacks('stripe.receive.async', { metadata, user, product, collection, document, args });
runCallbacks('stripe.receive.async', {
metadata,
user,
product,
collection,
document,
args,
});
return returnDocument;
};
@ -129,32 +193,29 @@ Retrieve or create a Stripe customer
*/
export const getCustomer = async (user, token) => {
const {id} = token;
const { id } = token;
let customer;
try {
// try retrieving customer from Stripe
customer = await stripe.customers.retrieve(user.stripeCustomerId);
} catch (error) {
// if user doesn't have a stripeCustomerId; or if id doesn't match up with Stripe
// create new customer object
const customerOptions = { email: user.email };
if (id) { customerOptions.source = id; }
const customerOptions = {email: user.email};
if (id) {
customerOptions.source = id;
}
customer = await stripe.customers.create(customerOptions);
// add stripe customer id to user object
await updateMutator({
collection: Users,
documentId: user._id,
data: { stripeCustomerId: customer.id },
validate: false
data: {stripeCustomerId: customer.id},
validate: false,
});
}
return customer;
@ -165,9 +226,18 @@ export const getCustomer = async (user, token) => {
Create one-time charge.
*/
export const createCharge = async ({user, product, collection, document, metadata, args}) => {
const { token, /* userId, productKey, associatedId, properties, */ coupon } = args;
export const createCharge = async ({
user,
product,
collection,
document,
metadata,
args,
}) => {
const {
token,
/* userId, productKey, associatedId, properties, */ coupon,
} = args;
const customer = await getCustomer(user, token);
@ -186,7 +256,7 @@ export const createCharge = async ({user, product, collection, document, metadat
description: product.description,
currency: product.currency,
customer: customer.id,
metadata
metadata,
};
// create Stripe charge
@ -194,10 +264,21 @@ export const createCharge = async ({user, product, collection, document, metadat
charge.objectType = 'charge';
runCallbacksAsync('stripe.charge.async', { charge, collection, document, args, user });
return processAction({collection, document, stripeObject: charge, args, user});
runCallbacksAsync('stripe.charge.async', {
charge,
collection,
document,
args,
user,
});
return processAction({
collection,
document,
stripeObject: charge,
args,
user,
});
};
/*
@ -205,13 +286,19 @@ export const createCharge = async ({user, product, collection, document, metadat
Subscribe a user to a Stripe plan
*/
export const createSubscription = async ({user, product, collection, document, metadata, args }) => {
export const createSubscription = async ({
user,
product,
collection,
document,
metadata,
args,
}) => {
let returnDocument = document;
try {
const customer = await getCustomer(user, args.token.id);
// if product has an initial cost,
// if product has an initial cost,
// create an invoice item and attach it to the customer first
// see https://stripe.com/docs/subscriptions/invoices#adding-invoice-items
if (product.initialAmount) {
@ -227,16 +314,14 @@ export const createSubscription = async ({user, product, collection, document, m
// eslint-disable-next-line no-unused-vars
const subscription = await stripe.subscriptions.create({
customer: customer.id,
items: [
{ plan: product.plan },
],
items: [{plan: product.plan}],
metadata,
...product.subscriptionProperties,
});
subscription.objectType = 'subscription';
// // if an associated collection and id have been provided,
// // if an associated collection and id have been provided,
// // update the associated document
// if (collection && document) {
@ -260,12 +345,23 @@ export const createSubscription = async ({user, product, collection, document, m
// }
runCallbacksAsync('stripe.subscribe.async', {subscription, collection, returnDocument, args, user});
returnDocument = await processAction({collection, document, stripeObject: subscription, args, user});
runCallbacksAsync('stripe.subscribe.async', {
subscription,
collection,
returnDocument,
args,
user,
});
returnDocument = await processAction({
collection,
document,
stripeObject: subscription,
args,
user,
});
return returnDocument;
} catch (error) {
// eslint-disable-next-line no-console
console.log('// Stripe createSubscription error');
@ -287,69 +383,101 @@ const createPlan = async ({
amount,
interval_count,
statement_descriptor,
...metadata,
}) => stripe.plans.create({
id,
currency,
interval,
amount,
interval_count,
product: {
name,
statement_descriptor,
...metadata
}) =>
stripe.plans.create({
id,
currency,
interval,
amount,
interval_count,
product: {
name,
statement_descriptor,
metadata,
},
metadata,
},
metadata,
});
});
export const createSubscriptionPlan = async (maybePlanObject) => typeof maybePlanObject === 'object' && createPlan(maybePlanObject);
const retrievePlan = async (planObject) => stripe.plans.retrieve(planObject.plan);
export const retrieveSubscriptionPlan = async (maybePlanObject) => typeof maybePlanObject === 'object' && retrievePlan(maybePlanObject);
const createOrRetrievePlan = async (planObject) => {
return retrievePlan(planObject)
.catch(error => {
// Plan does not exist, create it
if (error.statusCode === 404) {
// eslint-disable-next-line no-console
console.log(`Creating subscription plan ${planObject.plan} for ${(planObject.amount && (planObject.amount / 100).toLocaleString('en-US', { style: 'currency', currency: planObject.currency })) || 'free'}`);
return createPlan(planObject);
}
// Something else went wrong
export const createSubscriptionPlan = async maybePlanObject =>
typeof maybePlanObject === 'object' && createPlan(maybePlanObject);
const retrievePlan = async planObject => stripe.plans.retrieve(planObject.plan);
export const retrieveSubscriptionPlan = async maybePlanObject =>
typeof maybePlanObject === 'object' && retrievePlan(maybePlanObject);
const createOrRetrievePlan = async planObject => {
return retrievePlan(planObject).catch(error => {
// Plan does not exist, create it
if (error.statusCode === 404) {
// eslint-disable-next-line no-console
console.error(error);
throw error;
});
console.log(
`Creating subscription plan ${
planObject.plan
} for ${(planObject.amount &&
(planObject.amount / 100).toLocaleString('en-US', {
style: 'currency',
currency: planObject.currency,
})) ||
'free'}`
);
return createPlan(planObject);
}
// Something else went wrong
// eslint-disable-next-line no-console
console.error(error);
throw error;
});
};
export const createOrRetrieveSubscriptionPlan = async (maybePlanObject) => typeof maybePlanObject === 'object' && createOrRetrievePlan(maybePlanObject);
export const createOrRetrieveSubscriptionPlan = async maybePlanObject =>
typeof maybePlanObject === 'object' && createOrRetrievePlan(maybePlanObject);
/*
Process charges, subscriptions, etc. on Vulcan's side
*/
export const processAction = async ({collection, document, stripeObject, args, user}) => {
export const processAction = async ({
collection,
document,
stripeObject,
args,
user,
}) => {
debug('');
debugGroup('--------------- start\x1b[35m processAction \x1b[0m ---------------');
debugGroup(
'--------------- start\x1b[35m processAction \x1b[0m ---------------'
);
debug(`Collection: ${collection.options.collectionName}`);
debug(`documentId: ${document._id}`);
debug(`Charge: ${stripeObject}`);
let returnDocument = {};
// make sure charge hasn't already been processed
// (could happen with multiple endpoints listening)
const existingCharge = await Connectors.get(Charges, { 'data.id': stripeObject.id });
const existingCharge = await Connectors.get(Charges, {
'data.id': stripeObject.id,
});
if (existingCharge) {
// eslint-disable-next-line no-console
console.log(`// Charge with Stripe id ${stripeObject.id} already exists in db; aborting processAction`);
console.log(
`// Charge with Stripe id ${
stripeObject.id
} already exists in db; aborting processAction`
);
return collection && document ? document : {};
}
const {token, userId, productKey, associatedCollection, associatedId, properties, livemode } = args;
const {
token,
userId,
productKey,
associatedCollection,
associatedId,
properties,
livemode,
} = args;
// create charge document for storing in our own Charges collection
const chargeDoc = {
@ -373,37 +501,46 @@ export const processAction = async ({collection, document, stripeObject, args, u
// insert
const chargeSavedData = await newMutation({
collection: Charges,
document: chargeDoc,
document: chargeDoc,
validate: false,
});
const chargeSaved = chargeSavedData.data;
// if an associated collection and id have been provided,
// if an associated collection and id have been provided,
// update the associated document
if (collection && document) {
// note: assume a single document can have multiple successive charges associated to it
const chargeIds = document.chargeIds ? [...document.chargeIds, chargeSaved._id] : [chargeSaved._id];
const chargeIds = document.chargeIds
? [...document.chargeIds, chargeSaved._id]
: [chargeSaved._id];
let data = { chargeIds };
let data = {chargeIds};
// run collection.charge.sync callbacks
data = await runCallbacks({ name: 'stripe.process.sync', iterator: data, properties: { collection, document, chargeDoc, user }});
data = await runCallbacks({
name: 'stripe.process.sync',
iterator: data,
properties: {collection, document, chargeDoc, user},
});
const updateResult = await updateMutator({
collection,
documentId: associatedId,
data,
validate: false
validate: false,
});
returnDocument = updateResult.data;
returnDocument.__typename = collection.typeName;
}
runCallbacksAsync('stripe.process.async', {collection, returnDocument, chargeDoc, user});
runCallbacksAsync('stripe.process.async', {
collection,
returnDocument,
chargeDoc,
user,
});
debugGroupEnd();
debug('--------------- end\x1b[35m processAction \x1b[0m ---------------');
@ -443,15 +580,17 @@ function addRawBody(req, res, next) {
// app.use(addRawBody);
app.post('/stripe', async function(req, res) {
// eslint-disable-next-line no-console
console.log('////////////// stripe webhook');
const sig = req.headers['stripe-signature'];
try {
const event = stripe.webhooks.constructEvent(req.rawBody, sig, stripeSettings.endpointSecret);
const event = stripe.webhooks.constructEvent(
req.rawBody,
sig,
stripeSettings.endpointSecret
);
// eslint-disable-next-line no-console
console.log('event ///////////////////');
@ -459,21 +598,18 @@ app.post('/stripe', async function(req, res) {
console.log(event);
switch (event.type) {
case 'charge.succeeded':
// eslint-disable-next-line no-console
console.log('////// charge succeeded');
const charge = event.data.object;
charge.objectType = 'charge';
// eslint-disable-next-line no-console
console.log(charge);
try {
// look up corresponding invoice
const invoice = await stripe.invoices.retrieve(charge.invoice);
// eslint-disable-next-line no-console
@ -482,34 +618,46 @@ app.post('/stripe', async function(req, res) {
console.log(invoice);
// look up corresponding subscription
const subscription = await stripe.subscriptions.retrieve(invoice.subscription);
const subscription = await stripe.subscriptions.retrieve(
invoice.subscription
);
// eslint-disable-next-line no-console
console.log('////// subscription');
// eslint-disable-next-line no-console
console.log(subscription);
const { userId, productKey, associatedCollection, associatedId } = subscription.metadata;
const {
userId,
productKey,
associatedCollection,
associatedId,
} = subscription.metadata;
if (associatedCollection && associatedId) {
const collection = _.findWhere(Collections, {_name: associatedCollection});
const collection = _.findWhere(Collections, {
_name: associatedCollection,
});
const document = await Connectors.get(collection, associatedId);
// make sure document actually exists
if (!document) {
throw new Error(`Could not find ${associatedCollection} document with id ${associatedId} associated with subscription id ${subscription.id}; Not processing charge.`);
throw new Error(
`Could not find ${associatedCollection} document with id ${associatedId} associated with subscription id ${
subscription.id
}; Not processing charge.`
);
}
const args = {
userId,
userId,
productKey,
associatedCollection,
associatedId,
livemode: subscription.livemode,
};
processAction({ collection, document, stripeObject: charge, args});
}
processAction({collection, document, stripeObject: charge, args});
}
} catch (error) {
// eslint-disable-next-line no-console
console.log('// Stripe webhook error');
@ -518,9 +666,7 @@ app.post('/stripe', async function(req, res) {
}
break;
}
}
} catch (error) {
// eslint-disable-next-line no-console
console.log('///// Stripe webhook error');
@ -531,7 +677,10 @@ app.post('/stripe', async function(req, res) {
res.sendStatus(200);
});
webAppConnectHandlersUse(Meteor.bindEnvironment(app), {name: 'stripe_endpoint', order: 100});
webAppConnectHandlersUse(Meteor.bindEnvironment(app), {
name: 'stripe_endpoint',
order: 100,
});
// Picker.middleware(bodyParser.json());
@ -589,7 +738,7 @@ webAppConnectHandlersUse(Meteor.bindEnvironment(app), {name: 'stripe_endpoint',
// const document = collection.findOne(associatedId);
// const args = {
// userId,
// userId,
// productKey,
// associatedCollection,
// associatedId,
@ -598,7 +747,7 @@ webAppConnectHandlersUse(Meteor.bindEnvironment(app), {name: 'stripe_endpoint',
// processAction({ collection, document, charge, args});
// }
// }
// } catch (error) {
// console.log('// Stripe webhook error')
// console.log(error)
@ -614,11 +763,17 @@ webAppConnectHandlersUse(Meteor.bindEnvironment(app), {name: 'stripe_endpoint',
// });
Meteor.startup(() => {
registerCallback({
name: 'stripe.receive.sync',
description: 'Modify any metadata before calling Stripe\'s API',
arguments: [{metadata: 'Metadata about the action'},{user: 'The user'}, {product: 'Product created with addProduct'}, {collection: 'Associated collection of the charge'}, {document: 'Associated document in collection to the charge'}, {args: 'Original mutation arguments'}],
description: "Modify any metadata before calling Stripe's API",
arguments: [
{metadata: 'Metadata about the action'},
{user: 'The user'},
{product: 'Product created with addProduct'},
{collection: 'Associated collection of the charge'},
{document: 'Associated document in collection to the charge'},
{args: 'Original mutation arguments'},
],
runs: 'sync',
newSyntax: true,
returns: 'The modified metadata to be sent to Stripe',
@ -626,24 +781,45 @@ Meteor.startup(() => {
registerCallback({
name: 'stripe.receive.async',
description: 'Run after calling Stripe\'s API',
arguments: [{metadata: 'Metadata about the charge'}, {user: 'The user'}, {product: 'Product created with addProduct'}, {collection: 'Associated collection of the charge'}, {document: 'Associated document in collection to the charge'}, {args: 'Original mutation arguments'}],
description: "Run after calling Stripe's API",
arguments: [
{metadata: 'Metadata about the charge'},
{user: 'The user'},
{product: 'Product created with addProduct'},
{collection: 'Associated collection of the charge'},
{document: 'Associated document in collection to the charge'},
{args: 'Original mutation arguments'},
],
runs: 'sync',
newSyntax: true,
});
registerCallback({
name: 'stripe.charge.async',
description: 'Perform operations immediately after the stripe subscription has completed',
arguments: [{charge: 'The charge'}, {collection: 'Associated collection of the subscription'}, {document: 'Associated document in collection to the charge'}, {args: 'Original mutation arguments'}, {user: 'The user'}],
description:
'Perform operations immediately after the stripe subscription has completed',
arguments: [
{charge: 'The charge'},
{collection: 'Associated collection of the subscription'},
{document: 'Associated document in collection to the charge'},
{args: 'Original mutation arguments'},
{user: 'The user'},
],
runs: 'async',
newSyntax: true,
});
registerCallback({
name: 'stripe.subscribe.async',
description: 'Perform operations immediately after the stripe subscription has completed',
arguments: [{subscription: 'The subscription'}, {collection: 'Associated collection of the subscription'}, {document: 'Associated document in collection to the charge'}, {args: 'Original mutation arguments'}, {user: 'The user'}],
description:
'Perform operations immediately after the stripe subscription has completed',
arguments: [
{subscription: 'The subscription'},
{collection: 'Associated collection of the subscription'},
{document: 'Associated document in collection to the charge'},
{args: 'Original mutation arguments'},
{user: 'The user'},
],
runs: 'async',
newSyntax: true,
});
@ -651,7 +827,16 @@ Meteor.startup(() => {
registerCallback({
name: 'stripe.process.sync',
description: 'Modify any metadata before sending the charge to stripe',
arguments: [{modifier: 'The modifier object used to update the associated collection'}, {collection: 'Collection associated to the product'}, {document: 'Associated document'}, {chargeDoc: 'Charge document returned by Stripe\'s API'}, {user: 'The user'}],
arguments: [
{
modifier:
'The modifier object used to update the associated collection',
},
{collection: 'Collection associated to the product'},
{document: 'Associated document'},
{chargeDoc: "Charge document returned by Stripe's API"},
{user: 'The user'},
],
runs: 'sync',
returns: 'The modified arguments to be sent to stripe',
});
@ -659,7 +844,12 @@ Meteor.startup(() => {
registerCallback({
name: 'stripe.process.async',
description: 'Modify any metadata before sending the charge to stripe',
arguments: [{collection: 'Collection associated to the product'}, {document: 'Associated document'}, {chargeDoc: 'Charge document returned by Stripe\'s API'}, {user: 'The user'}],
arguments: [
{collection: 'Collection associated to the product'},
{document: 'Associated document'},
{chargeDoc: "Charge document returned by Stripe's API"},
{user: 'The user'},
],
runs: 'async',
returns: 'The modified arguments to be sent to stripe',
});
@ -668,11 +858,17 @@ Meteor.startup(() => {
if (stripeSettings.createPlans) {
// eslint-disable-next-line no-console
console.log('Creating stripe plans...');
Promise.awaitAll(Object.keys(Products)
// Filter out function type products and those without a plan defined (non-subscription)
.filter(productKey => typeof Products[productKey] === 'object' && Products[productKey].plan)
.map(productKey => createOrRetrievePlan(Products[productKey])));
Promise.awaitAll(
Object.keys(Products)
// Filter out function type products and those without a plan defined (non-subscription)
.filter(
productKey =>
typeof Products[productKey] === 'object' &&
Products[productKey].plan
)
.map(productKey => createOrRetrievePlan(Products[productKey]))
);
// eslint-disable-next-line no-console
console.log('Finished creating stripe plans.');
}
});
});