subscription
Advanced tools
Comparing version 0.0.0 to 0.0.1
@@ -21,3 +21,6 @@ var level = require("level-json"); | ||
return { | ||
define: require("./lib/define"), | ||
balance: require('./lib/balance'), | ||
cancel: require('./lib/cancel'), | ||
charges: require('./lib/charges'), | ||
service: require("./lib/service"), | ||
priceOf: require('./lib/price-of'), | ||
@@ -24,0 +27,0 @@ purchase: require('./lib/purchase'), |
@@ -5,2 +5,3 @@ var debug = require("local-debug")('charges'); | ||
var fatal = require("./fatal"); | ||
var newError = require("new-error"); | ||
@@ -10,15 +11,17 @@ module.exports = { | ||
active: active, | ||
available: available, | ||
create: create, | ||
last: last | ||
lastUnexpired: lastUnexpired, | ||
markAsCancelled: markAsCancelled, | ||
notExpired: notExpired, | ||
read: read | ||
}; | ||
function active (customer, subscription, callback) { | ||
function active (customer, service, callback) { | ||
var now = Date.now(); | ||
read(customer, subscription, function (error, all) { | ||
read(customer, service, function (error, all) { | ||
if (error) return callback(error); | ||
callback(undefined, all.filter(function (charge) { | ||
return charge.start_ts <= now && charge.expire_ts > now; | ||
return !charge.cancelled && charge.start_ts <= now && charge.expire_ts > now; | ||
})); | ||
@@ -29,3 +32,3 @@ }); | ||
function add (charge, callback) { | ||
var key = keyOf({ customer: charge.customer, charges: charge.subscription }); | ||
var key = keyOf({ customer: charge.customer, service: charge.service }); | ||
@@ -47,16 +50,4 @@ debug('Adding the charge %s into %s', charge.amount + charge.currency, key); | ||
function available (customer, subscription, callback) { | ||
var now = Date.now(); | ||
read(customer, subscription, function (error, all) { | ||
if (error) return callback(error); | ||
callback(undefined, all.filter(function (charge) { | ||
return charge.expire_ts > now; | ||
})); | ||
}); | ||
} | ||
function create (charge, callback) { | ||
var key = keyOf({ customer: charge.customer, charges: charge.subscription }); | ||
var key = keyOf({ customer: charge.customer, service: charge.service }); | ||
var charges = [charge]; | ||
@@ -80,26 +71,70 @@ | ||
function read (customer, subscription, callback) { | ||
var key = keyOf({ customer: customer, charges: subscription }); | ||
function markAsCancelled (customer, service, callback) { | ||
debug('Marking all unexpired charges by %s to "%s" as cancelled', customer, service); | ||
var now = Date.now(); | ||
read(customer, service, function (error, charges) { | ||
if (error) return callback(error); | ||
var save = charges.map(function (charge) { | ||
if (charge.expire_ts < now) return charge; | ||
debug('Cancelling %s%s made by %s for "%s', charge.amount, charge.currency, customer, service); | ||
charge.cancelled = Date.now(); | ||
return charge; | ||
}); | ||
var key = keyOf({ customer: customer, service: service }); | ||
io.set(key, save, callback); | ||
}); | ||
} | ||
function read (customer, service, callback) { | ||
var key = keyOf({ customer: customer, service: service }); | ||
debug('Reading %s', key); | ||
io.get(key, callback); | ||
io.get(key, function (error, result) { | ||
if (error) { | ||
return callback(newError('{0} has no charge records for "{1}"', customer, service)); | ||
}; | ||
callback(undefined, result); | ||
}); | ||
} | ||
function last (customer, subscription, callback) { | ||
recent(customer, subscription, function (error, recent) { | ||
function lastUnexpired (customer, service, callback) { | ||
recent(customer, service, function (error, recent) { | ||
if(error) return callback(error); | ||
callback(undefined, recent[0]); | ||
callback(undefined, recent[recent.length - 1]); | ||
}); | ||
} | ||
function recent (customer, subscription, callback) { | ||
read(customer, subscription, function (error, all) { | ||
function recent (customer, service, callback) { | ||
read(customer, service, function (error, all) { | ||
if (error) return callback(error); | ||
callback(undefined, all.sort(function (a, b) { | ||
if (a.expire_ts > b.expire_ts) return 1; | ||
if (a.expire_ts < b.expire_ts) return -1; | ||
return 0; | ||
callback(undefined, all | ||
.filter(function (charge) { | ||
return !charge.cancelled; | ||
}) | ||
.sort(function (a, b) { | ||
if (a.expire_ts > b.expire_ts) return 1; | ||
if (a.expire_ts < b.expire_ts) return -1; | ||
return 0; | ||
})); | ||
}); | ||
} | ||
function notExpired (customer, service, callback) { | ||
var now = Date.now(); | ||
read(customer, service, function (error, all) { | ||
if (error) return callback(error); | ||
callback(undefined, all.filter(function (charge) { | ||
return !charge.cancelled && charge.expire_ts > now; | ||
})); | ||
}); | ||
} |
@@ -0,10 +1,16 @@ | ||
var prefix = 'subscription/'; | ||
module.exports = keyOf; | ||
function keyOf (options) { | ||
if (options.customer && options.charges) | ||
return options.customer + '/charges/' + options.charges; | ||
if (options.customer && options.service) | ||
return prefix + options.customer + '/charges/' + options.service; | ||
if (options.subscription) return 'subscriptions/' + options.subscription; | ||
if (options.service) return prefix + 'services/' + options.service; | ||
if (options.subscriptions) return options.subscriptions + '/subscriptions'; | ||
if (options.services) return prefix + 'services'; | ||
if (options.subscriptionsOf) return prefix + options.subscriptionsOf + '/subscriptions'; | ||
if (options.balance) return prefix + options.balance + '/balance'; | ||
} |
@@ -5,7 +5,8 @@ var englishTime = require("english-time"); | ||
function priceOf (subscription, englishLength) { | ||
var length = englishTime(englishLength); | ||
var amount = Math.floor(length / subscription.period); | ||
function priceOf (service, length) { | ||
typeof length == 'string' && ( length = englishTime(length) ); | ||
return subscription.price * amount; | ||
var amount = Math.floor(length / service.period); | ||
return service.price * amount; | ||
} |
var debug = require("local-debug")('purchase'); | ||
var getSubscription = require("./define").read; | ||
var newError = require("new-error"); | ||
var getService = require("./service").get; | ||
var stripe = require("./stripe-sdk")(); | ||
@@ -9,31 +10,66 @@ var priceOf = require("./price-of"); | ||
var fatal = require("./fatal"); | ||
var remaining = require("./remaining"); | ||
var cancel = require("./cancel"); | ||
var balance = require("./balance"); | ||
module.exports = subscription; | ||
module.exports.extension = extension; | ||
module.exports.upgrade = upgrade; | ||
function extension (subscriptionName, options, callback) { | ||
charges.last(options.customer, subscriptionName, function (error, lastCharge) { | ||
if (error) return callback(error); | ||
function extension (serviceName, options, callback) { | ||
charges.lastUnexpired(options.customer, serviceName, function (error, lastCharge) { | ||
if (error) { | ||
return callback(newError('Oops, unable to extend. {0} doesn\'t have any active subscription to "{1}".', options.customer, serviceName)); | ||
} | ||
options.startTS = lastCharge.expire_ts; | ||
subscription(subscriptionName, options, callback); | ||
subscription(serviceName, options, callback); | ||
}); | ||
} | ||
function subscription (subscriptionName, options, callback) { | ||
debug('%s is purchasing %s subscription for %s', options.customer, subscriptionName, options.length); | ||
function upgrade (options, callback) { | ||
debug('%s is upgrading from %s to %s', options.customer, options.from, options.to); | ||
getSubscription(subscriptionName, function (error, subscription) { | ||
cancel(options.customer, options.from, function (error, result) { | ||
if (error) return callback(error); | ||
charge(subscription, options, function (error, stripeCharge) { | ||
if (error) return callback(error); | ||
debug('Cancelled %s\'s subscription to %s. Now purchasing %s, for %s', options.customer, options.from, options.to, result.remainingTime); | ||
charges.add(stripeCharge, function (error, chargeRecord) { | ||
subscriptions.add(options.customer, subscriptionName, function (error) { | ||
if(error) return callback(error); | ||
callback(undefined, chargeRecord); | ||
subscription(options.to, { token: options.token, customer: options.customer, length: result.remainingTime }, callback); | ||
}); | ||
} | ||
function subscription (serviceName, options, callback) { | ||
debug('%s is purchasing %s subscription for %s', options.customer, serviceName, options.length); | ||
options.startTS || (options.startTS = Date.now()); | ||
charges.lastUnexpired(options.customer, serviceName, function (error, lastCharge) { | ||
getService(serviceName, function (error, service) { | ||
if (error) { | ||
return callback(error); | ||
} | ||
if (lastCharge && lastCharge.expire_ts > options.startTS) { | ||
return callback(newError('You ({0}) already paid "{1}". Use the "extend" option to extend your subscription.', options.customer, serviceName)); | ||
} | ||
charge(service, options, function (error, stripeCharge) { | ||
if (error) return callback(error); | ||
charges.add(stripeCharge, function (error, chargeRecord) { | ||
if (error) { | ||
fatal(error.message); | ||
fatal(error.stack); | ||
} | ||
subscriptions.add(options.customer, serviceName, function (error) { | ||
if(error) return callback(error); | ||
callback(undefined, chargeRecord); | ||
}); | ||
}); | ||
}); | ||
}); | ||
@@ -45,30 +81,56 @@ | ||
function charge (subscription, options, callback) { | ||
function charge (service, options, callback) { | ||
if (!service.price) { | ||
return callback(undefined, { | ||
customer: options.customer, | ||
service: service.name, | ||
create_ts: Date.now(), | ||
start_ts: options.startTS | ||
}); | ||
} | ||
if (!options.card && !options.token) { | ||
return callback(newError('Either card info or a valid Stripe token has to be provided for purchasing a subscription.')); | ||
} | ||
var totalPrice = priceOf(service, options.length); | ||
var stripeOptions = { | ||
amount: priceOf(subscription, options.length), | ||
currency: subscription.currency, | ||
amount: totalPrice, | ||
currency: service.currency, | ||
card: options.token, | ||
description: 'Subscribe "' + subscription.name + '" for ' + options.length | ||
description: 'Subscribe "' + service.name + '" for ' + options.length | ||
}; | ||
stripe.charges.create(stripeOptions, function (error, charge) { | ||
if (error) return callback(error); | ||
balance.charge(options.customer, stripeOptions.amount, function (error, restMoney, usedBalance) { | ||
if (error) { | ||
fatal('Unable to charge existing balance of %s', options.customer); | ||
fatal(error.message); | ||
fatal(error.stack); | ||
}; | ||
debug('%s%s was paid by %s for %s. (ID: %s)', charge.amount, charge.currency, charge.customer, charge.description, charge.id); | ||
if (restMoney != stripeOptions.amount) { | ||
debug('%s used %s%s from his/her balance to pay %s', options.customer, usedBalance, stripeOptions.currency, stripeOptions.amount); | ||
stripeOptions.amount = restMoney; | ||
} | ||
var startTS = options.startTS || Date.now(); | ||
stripe.charges.create(stripeOptions, function (error, charge) { | ||
if (error) return callback(error); | ||
callback(undefined, { | ||
id: charge.id, | ||
customer: options.customer, | ||
subscription: subscription.name, | ||
amount: charge.amount, | ||
currency: charge.currency, | ||
description: charge.description, | ||
create_ts: Date.now(), | ||
start_ts: startTS, | ||
expire_ts: (charge.amount / subscription.price) * subscription.period + startTS | ||
debug('%s%s was paid by %s for "%s". (ID: %s)', charge.amount, charge.currency, options.customer, charge.description, charge.id); | ||
callback(undefined, { | ||
id: charge.id, | ||
customer: options.customer, | ||
service: service.name, | ||
amount: charge.amount, | ||
currency: charge.currency, | ||
description: charge.description, | ||
create_ts: Date.now(), | ||
start_ts: options.startTS, | ||
expire_ts: (totalPrice / service.price) * service.period + options.startTS | ||
}); | ||
}); | ||
}); | ||
} |
var debug = require("local-debug")('remaining'); | ||
var newError = require("new-error"); | ||
var charges = require("./charges"); | ||
@@ -7,14 +8,24 @@ var fatal = require("./fatal"); | ||
function remaining (customer, subscription, callback) { | ||
debug('Calculating the remaining time of %s\'s subscription to %s.', customer, subscription); | ||
function remaining (customer, service, callback) { | ||
debug('Calculating the remaining time of %s\'s subscription to %s.', customer, service); | ||
charges.last(customer, subscription, function (error, charge) { | ||
charges.lastUnexpired(customer, service, function (error, charge) { | ||
if (error) { | ||
fatal(error.message); | ||
fatal(error.stack); | ||
return callback(error); | ||
} | ||
callback(undefined, charge.expire_ts - Date.now()); | ||
if (!charge) { | ||
return callback(undefined, 0); | ||
} | ||
var now = Date.now(); | ||
if (charge.expire_ts <= now) { | ||
return callback(newError('{0} doesn\'t have any active subscription to {1}', customer, service)); | ||
} | ||
debug('Remaining time of %s\'s subscription to %s is %d', customer, service, charge.expire_ts - now); | ||
callback(undefined, charge.expire_ts - now); | ||
}); | ||
} |
var debug = require("local-debug")('subscriptions'); | ||
var newError = require("new-error"); | ||
var io = require("./io")(); | ||
var charges = require("./charges"); | ||
var keyOf = require("./key-of"); | ||
var fatal = require("./fatal"); | ||
@@ -9,18 +11,19 @@ module.exports = { | ||
has: has, | ||
read: read | ||
read: read, | ||
remove: remove | ||
}; | ||
function add (customer, subscription, callback) { | ||
var key = keyOf({ subscriptions: customer }); | ||
function add (customer, serviceName, callback) { | ||
var key = keyOf({ subscriptionsOf: customer }); | ||
debug('Adding %s into %s', subscription, key); | ||
debug('Adding %s into %s', serviceName, key); | ||
io.get(key, function (error, subscriptions) { | ||
if (error) return create(customer, subscription, callback); | ||
if (error) return create(customer, serviceName, callback); | ||
if (subscriptions.indexOf(subscription) > -1) { | ||
if (subscriptions.indexOf(serviceName) > -1) { | ||
return callback(); | ||
} | ||
subscriptions.push(subscription); | ||
subscriptions.push(serviceName); | ||
@@ -31,5 +34,5 @@ io.set(key, subscriptions, callback); | ||
function create (customer, subscription, callback) { | ||
var key = keyOf({ subscriptions: customer }); | ||
var subs = [subscription]; | ||
function create (customer, serviceName, callback) { | ||
var key = keyOf({ subscriptionsOf: customer }); | ||
var subs = [serviceName]; | ||
@@ -43,4 +46,28 @@ io.set(key, subs, function (error) { | ||
function remove (customer, serviceName, callback) { | ||
var key = keyOf({ subscriptionsOf: customer }); | ||
debug('Removing %s from %s', serviceName, key); | ||
io.get(key, function (error, subscriptions) { | ||
if (error) { | ||
fatal(error); | ||
return callback(error); | ||
} | ||
var index = subscriptions.indexOf(serviceName); | ||
if (index == -1) { | ||
return callback(newError('{0} already doesn\'t have any subscription to {1}', customer, serviceName)); | ||
} | ||
subscriptions.splice(index, 1); | ||
io.set(key, subscriptions, callback); | ||
}); | ||
} | ||
function read (customer, callback) { | ||
var key = keyOf({ subscriptions: customer }); | ||
var key = keyOf({ subscriptionsOf: customer }); | ||
@@ -50,8 +77,8 @@ io.get(key, callback); | ||
function has (customer, subscription, callback) { | ||
function has (customer, serviceName, callback) { | ||
read(customer, function (error, all) { | ||
if (error) return callback(); | ||
callback(all.indexOf(subscription) > -1); | ||
callback(all.indexOf(serviceName) > -1); | ||
}); | ||
} |
{ | ||
"name": "subscription", | ||
"version": "0.0.0", | ||
"version": "0.0.1", | ||
"description": "Library to store and manage paid subscriptions via Stripe and LevelDB", | ||
@@ -14,7 +14,10 @@ "main": "index.js", | ||
"english-time": "0.0.8", | ||
"local-debug": "0.0.0" | ||
"local-debug": "0.0.0", | ||
"new-error": "0.0.0" | ||
}, | ||
"devDependencies": { | ||
"fox": "*", | ||
"call-all": "*" | ||
"call-all": "*", | ||
"map": "*", | ||
"iter": "*" | ||
}, | ||
@@ -21,0 +24,0 @@ "keywords": [ |
@@ -18,3 +18,3 @@ ## subscription | ||
subscription.define('atlas magazine', { 'price': 1000, period: '1 month', currency: 'usd' }, function (error, atlas) { | ||
subscription.service.define('atlas magazine', { 'price': 1000, period: '1 month', currency: 'usd' }, function (error, atlas) { | ||
@@ -24,3 +24,3 @@ subscription.priceOf(atlas, '1 month') | ||
subscription.priceOf(atlas, '1 year') | ||
subscription.priceOf(atlas, '1 year') | ||
// => 12000 (One Twenty Dollars) | ||
@@ -37,3 +37,3 @@ | ||
length: '1 year', | ||
token: 'tok_2oWvm6yRBFSMSh' // obtained with Stripe.js | ||
token: 'tok_2oWvm6yRBFSMSh' // obtain it with Stripe.js | ||
} | ||
@@ -63,25 +63,33 @@ | ||
subscription.remaining('azer@kodfabrik.com', 'atlas magazine', function (error, remaining) { | ||
remaining | ||
// => 8035200000 (3 months) | ||
}) | ||
``` | ||
List all subscriptions of a user: | ||
List subscriptions of a user: | ||
```js | ||
subscription.subscriptionsOf('azer@kodfabrik.com', function (error, subs) { | ||
subs | ||
// => ['atlas magazine'] | ||
}) | ||
``` | ||
## More Docs | ||
Extend a subscription: | ||
* `test.js` | ||
* [english-time](http://github.com/azer/english-time): The library used for parsing time inputs. | ||
```js | ||
subscription.purchase.extension('atlas magazine', { customer: 'azer@kodfabrik.com', length: '2 years', token: token }, function (error, purchase) { | ||
purchase.amount | ||
// => 24000 | ||
}); | ||
``` | ||
Upgrade a subscription: | ||
```js | ||
subscription.purchase.upgrade({ customer: 'hi@ada.io', from: 'cheaper', to: 'more expensive service', token: token }, function (error, purchase) { | ||
if (error) return callback(error); | ||
}); | ||
``` | ||
## Debugging | ||
@@ -106,1 +114,6 @@ | ||
``` | ||
## More Docs | ||
* `test.js` | ||
* [english-time](http://github.com/azer/english-time): The library used for parsing time inputs. |
Sorry, the diff of this file is not supported yet
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
20163
18
483
115
6
4
1
+ Addednew-error@0.0.0
+ Addednew-error@0.0.0(transitive)
+ Addednew-format@2.0.0(transitive)