diff --git a/Readme.md b/Readme.md index 687e3f3..b56d0d1 100644 --- a/Readme.md +++ b/Readme.md @@ -30,6 +30,12 @@ cohort(new Date('1/1/2014'), new Date('2/1/2014'), function (err, customers) { And the returned `customers` object lets you dive deeper into the cohort. +#### Options + +You can include options as the second param when initializing a cohort. + + * `ignoreStripeFees` (default:false) If `true`, this will include the Stripe fees in your MRR. + #### Number of Customers You can query the total amount of customers returned: @@ -129,4 +135,4 @@ console.log('We made $' + mrr + ' off the startup plan!'); ## License -MIT \ No newline at end of file +MIT diff --git a/lib/customers.js b/lib/customers.js index 9d3f07b..1ead5ad 100644 --- a/lib/customers.js +++ b/lib/customers.js @@ -12,11 +12,13 @@ module.exports = Customers; * A cohort of Stripe `customers`. * * @param {Array|Customer} customers + * @param {Object} options * */ -function Customers (customers) { +function Customers (customers, options) { this.customers = customers; + this.options = options; } /** @@ -34,7 +36,7 @@ Customers.prototype.created = function (start, end) { return new Customers(this.customers.filter(function (customer) { var created = customer.created * 1000; return created >= s && created <= e; - })); + }), this.options); }; /** @@ -48,7 +50,7 @@ Customers.prototype.delinquent = function (delinquent) { if (typeof delinquent !== 'boolean') delinquent = true; return new Customers(this.customers.filter(function (customer) { return customer.delinquent === delinquent; - })); + }), this.options); }; /** @@ -59,7 +61,7 @@ Customers.prototype.delinquent = function (delinquent) { */ Customers.prototype.filter = function (fn) { - return new Customers(this.customers.filter(fn)); + return new Customers(this.customers.filter(fn), this.options); }; /** @@ -104,5 +106,5 @@ Customers.prototype.subscriptions = function (start, end) { }); all.push.apply(all, subscriptions); }); - return new Subscriptions(all).started(start, end); -}; \ No newline at end of file + return new Subscriptions(all, this.options).started(start, end); +}; diff --git a/lib/index.js b/lib/index.js index 73532e1..c4ebaca 100644 --- a/lib/index.js +++ b/lib/index.js @@ -20,13 +20,14 @@ module.exports = Cohorter; * @param {String} key * @param {Object} options * @param {Number} count + * @param {Boolean} ignoreStripeFees */ function Cohorter (key, options) { if (!(this instanceof Cohorter)) return new Cohorter(key, options); if (!key) throw new Error('Stripe cohort requires a Stripe key.'); this.stripe = Stripe(key); - this.options = defaults(options, { count: 100, concurrency: 1 }); + this.options = defaults(options, { count: 100, concurrency: 1, ignoreStripeFees: false }); var self = this; return function () { self.cohort.apply(self, arguments); }; } @@ -47,7 +48,7 @@ Cohorter.prototype.cohort = function (start, end, callback) { this.customers(start, end, function (err, customers) { if (err) return callback(err); debug('created cohort'); - callback(null, new Customers(customers)); + callback(null, new Customers(customers, self.options)); }); }; @@ -114,4 +115,4 @@ Cohorter.prototype.query = function (start, end, offset, callback) { debug('loaded %d customers with offset %d', customers.length, offset); callback(null, res); }); -}; \ No newline at end of file +}; diff --git a/lib/subscriptions.js b/lib/subscriptions.js index 036b816..26657e2 100644 --- a/lib/subscriptions.js +++ b/lib/subscriptions.js @@ -10,11 +10,13 @@ module.exports = Subscriptions; * A list of Stripe `subscriptions`. * * @param {Array|Customer} subscriptions + * @param {Object} options * */ -function Subscriptions (subscriptions) { +function Subscriptions (subscriptions, options) { this.subscriptions = subscriptions; + this.options = options; } /** @@ -57,7 +59,7 @@ Subscriptions.prototype.started = function (start, end) { if (subscription.status !== 'active') return false; var started = subscription.start * 1000; return started >= s && started <= e; - })); + }), this.options); }; /** @@ -70,7 +72,7 @@ Subscriptions.prototype.started = function (start, end) { Subscriptions.prototype.status = function (status) { return new Subscriptions(this.subscriptions.filter(function (subscription) { return subscription.status === status; - })); + }), this.options); }; /** @@ -83,7 +85,7 @@ Subscriptions.prototype.status = function (status) { Subscriptions.prototype.plan = function (planId) { return new Subscriptions(this.subscriptions.filter(function (subscription) { return subscription.plan.id === planId; - })); + }), this.options); }; /** @@ -97,7 +99,7 @@ Subscriptions.prototype.plan = function (planId) { Subscriptions.prototype.withoutPlan = function (planId) { return new Subscriptions(this.subscriptions.filter(function (subscription) { return subscription.plan.id !== planId; - })); + }), this.options); }; /** @@ -112,7 +114,7 @@ Subscriptions.prototype.paid = function (amount) { if (!amount) amount = 1; return new Subscriptions(this.subscriptions.filter(function (subscription) { return subscription.plan.amount >= amount; - })); + }), this.options); }; /** @@ -151,10 +153,13 @@ Subscriptions.prototype.print = function () { for (var i = 0; i < list.length; i += 1) { var s = list[i]; var c = s.customer; - var a = Math.round(amount(s) * 100) / 100.0; + var a = Math.round(amountMrr(s, this.options) * 100) / 100.0; console.log([c.email, s.status, s.plan.name, '$' + a].join(' - ')); total += a; } + + total = Math.round(total * 100) / 100.0; // to two decimal points + console.log('Total MRR: $' + total); }; @@ -168,7 +173,7 @@ Subscriptions.prototype.print = function () { Subscriptions.prototype.mrr = function (start, end) { var mrr = this.started(start, end).subscriptions.reduce(function (memo, subscription) { - return memo + amountMrr(subscription); + return memo + amountMrr(subscription, this.options); }, 0.00); return Math.round(mrr * 100) / 100.0; // to two decimal points @@ -179,26 +184,47 @@ Subscriptions.prototype.mrr = function (start, end) { * and discounts. * * @param {Subscription} subscription + * @param {Object} options * @return {Number} */ -function amountMrr (subscription) { +function amountMrr (subscription, options) { var res = (subscription.plan.amount / 100.0); res = normalizeMonths(subscription, res); if (res > 0.0) { - var discount = subscription.customer.discount; - if (discount && discount.coupon) { - var coupon = discount.coupon; - if (coupon.valid) { - if (coupon.amount_off) res -= (coupon.amount_off / 100.0); - else if (coupon.percent_off) res *= (1.00 - (coupon.percent_off / 100.0)); - } + res = applyDiscount(res, subscription.discount); + res = applyDiscount(res, subscription.customer.discount); + + if(!options.ignoreStripeFees && res > 0) { + res *= (1.00 - 0.029); // Stripe fees (2.9%) + res -= 0.30; // Stripe fees (30 cents) } - res *= (1.00 - 0.029); // Stripe fees } return res; } +/** + * Apply a specified discount + * + * @param {Discount} discount + * @param {Number} res + * @return {Number} + */ + +function applyDiscount (res, discount) { + if (discount && discount.coupon) { + var coupon = discount.coupon; + + var amount_off = coupon.amount_off / 100.0; + if(amount_off && amount_off > res) amount_off = res; + + if (coupon.amount_off) res -= amount_off; + else if (coupon.percent_off) res *= (1.00 - (coupon.percent_off / 100.0)); + } + return res; +} + + // function normalizeMonths (subscription, res) { var interval = subscription.plan.interval;