feat(subscriptions): add paypal recurring cancelation. refactor stripe & paypal code to use auth.authWithUrl.

This commit is contained in:
Tyler Renelle 2014-03-24 20:00:11 -06:00
parent b686b56282
commit 0f430e102e
11 changed files with 65 additions and 43 deletions

View file

@ -16,7 +16,6 @@
"SMTP_TLS": true,
"STRIPE_API_KEY":"aaaabbbbccccddddeeeeffff00001111",
"STRIPE_PUB_KEY":"22223333444455556666777788889999",
"PAYPAL_MERCHANT":"paypal-merchant@gmail.com",
"NEW_RELIC_LICENSE_KEY":"NEW_RELIC_LICENSE_KEY",
"GA_ID": "GA_ID",
"PAYPAL_USERNAME": "PAYPAL_USERNAME",

View file

@ -35,7 +35,6 @@
"validator": "~1.5.1",
"nodemailer": "~0.5.2",
"grunt-cli": "~0.1.9",
"paypal-ipn": "~1.0.1",
"express-csv": "~0.6.0",
"pretty-data": "git://github.com/vkiryukhin/pretty-data#master",
"js2xmlparser": "~0.1.2",
@ -51,7 +50,7 @@
"domain-middleware": "~0.1.0",
"universal-analytics": "~0.3.2",
"paypal-express-checkout": "git://github.com/HabitRPG/node-paypal-express-checkout#habitrpg",
"paypal-recurring": "~1.1.0"
"paypal-recurring": "git://github.com/jaybryant/paypal-recurring#656b496f43440893c984700191666a5c5c535dca"
},
"private": true,
"subdomain": "habitrpg",

View file

@ -107,9 +107,8 @@ habitrpg.controller("RootCtrl", ['$scope', '$rootScope', '$location', 'User', '$
window.env.t('donationDesc'),
panelLabel: subscription ? window.env.t('subscribe') : window.env.t('checkout'),
token: function(data) {
var url = '/api/v2/user/buy-gems';
var url = '/stripe/checkout';
if (subscription) url += '?plan=basic_earned';
// if (subscription) url += '?plan=test';
$scope.$apply(function(){
$http.post(url, data).success(function() {
window.location.reload(true);
@ -123,10 +122,7 @@ habitrpg.controller("RootCtrl", ['$scope', '$rootScope', '$location', 'User', '$
$scope.cancelSubscription = function(){
if (!confirm(window.env.t('sureCancelSub'))) return;
//TODO use Stripe API to keep subscription till end of their month
$http.post('/api/v2/user/cancel-subscription').success(function(){
window.location.reload(true);
})
window.location.href = '/' + user.purchased.plan.paymentMethod.toLowerCase() + '/subscribe/cancel?_id=' + user._id + '&apiToken=' + user.apiToken;
}
$scope.contribText = function(contrib, backer){

View file

@ -16,10 +16,6 @@ var NO_TOKEN_OR_UID = { err: "You must include a token and uid (user id) in your
var NO_USER_FOUND = {err: "No user found."};
var NO_SESSION_FOUND = { err: "You must be logged in." };
/*
beforeEach auth interceptor
*/
api.auth = function(req, res, next) {
var uid = req.headers['x-api-user'];
var token = req.headers['x-api-key'];
@ -47,6 +43,15 @@ api.authWithSession = function(req, res, next) { //[todo] there is probably a mo
});
};
api.authWithUrl = function(req, res, next) {
User.findOne({_id:req.query._id, apiToken:req.query.apiToken}, function(err,user){
if (err) return next(err);
if (_.isEmpty(user)) return res.json(401, NO_USER_FOUND);
res.locals.user = user;
next();
})
}
api.registerUser = function(req, res, next) {
var confirmPassword, e, email, password, username, _ref;
_ref = req.body, email = _ref.email, username = _ref.username, password = _ref.password, confirmPassword = _ref.confirmPassword;

View file

@ -312,7 +312,8 @@ function createSubscription(user, data) {
planId:'basic_earned',
customerId: data.customerId,
dateUpdated: new Date,
gemsBought: 0
gemsBought: 0,
paymentMethod: data.paymentMethod
})
.defaults({ // allow non-override if a plan was previously used
dateCreated: new Date,
@ -324,6 +325,12 @@ function createSubscription(user, data) {
ga.transaction(data.customerId, 5).item(5, 1, data.paymentMethod.toLowerCase() + '-subscription', data.paymentMethod + " > Stripe").send();
}
function cancelSubscription(user, data){
_.merge(user.purchased.plan, {planId:null, customerId:null, paymentMethod:null});
user.markModified('purchased.plan');
ga.event('unsubscribe', 'Stripe').send();
}
function buyGems(user, data) {
user.balance += 5;
user.purchased.txnCount++;
@ -334,7 +341,7 @@ function buyGems(user, data) {
/*
Setup Stripe response when posting payment
*/
api.buyGems = function(req, res, next) {
api.stripeCheckout = function(req, res, next) {
var api_key = nconf.get('STRIPE_API_KEY');
var stripe = require("stripe")(api_key);
var token = req.body.id;
@ -367,13 +374,12 @@ api.buyGems = function(req, res, next) {
}
], function(err, saved){
if (err) return res.send(500, err.toString()); // don't json this, let toString() handle errors
res.send(200, saved);
res.send(200);
});
};
api.cancelSubscription = function(req, res, next) {
var api_key = nconf.get('STRIPE_API_KEY');
var stripe = require("stripe")(api_key);
api.stripeSubscribeCancel = function(req, res, next) {
var stripe = require("stripe")(nconf.get('STRIPE_API_KEY'));
var user = res.locals.user;
if (!user.purchased.plan.customerId)
return res.json(401, {err: "User does not have a plan subscription"});
@ -383,20 +389,17 @@ api.cancelSubscription = function(req, res, next) {
stripe.customers.del(user.purchased.plan.customerId, cb);
},
function(response, cb) {
_.merge(user.purchased.plan, {planId:null, customerId:null});
user.markModified('purchased.plan');
cancelSubscription(user);
user.save(cb);
}
], function(err, saved){
if (err) return res.send(500, err.toString()); // don't json this, let toString() handle errors
res.send(200, saved);
ga.event('unsubscribe', 'Stripe').send()
res.redirect('/');
});
}
api.paypalSubscribe = function(req,res,next) {
var uuid = req.query.uuid;
if (!uuid) return next("UUID required");
var uuid = res.locals.user._id;
// Authenticate a future subscription of ~5 USD
paypalRecurring.authenticate({
RETURNURL: nconf.get('BASE_URL') + '/paypal/subscribe/success?uuid=' + uuid,
@ -432,8 +435,28 @@ api.paypalSubscribeSuccess = function(req,res,next) {
});
}
api.paypalSubscribeCancel = function(req, res, next) {
var user = res.locals.user;
if (!user.purchased.plan.customerId)
return res.json(401, {err: "User does not have a plan subscription"});
async.waterfall([
function(cb) {
paypalRecurring.modifySubscription(user.purchased.plan.customerId, 'cancel', cb);
},
function(response, cb) {
cancelSubscription(user);
user.save(cb);
}
], function(err, saved){
if (err) return next(err);
res.redirect('/');
});
}
api.paypalCheckout = function(req, res, next) {
var opts = {RETURNURL:nconf.get('BASE_URL') + '/paypal/checkout/success?uuid=' + req.query.uuid};
var uuid = res.locals.user._id;
var opts = {RETURNURL:nconf.get('BASE_URL') + '/paypal/checkout/success?uuid=' + uuid};
paypalCheckout.pay(+new Date, 5, 'HabitRPG Gems', 'USD', opts, function(err, url) {
if (err) return next(err);
res.redirect(url);

View file

@ -234,7 +234,6 @@ module.exports.locals = function(req, res, next) {
res.locals.habitrpg = {
NODE_ENV: nconf.get('NODE_ENV'),
BASE_URL: nconf.get('BASE_URL'),
PAYPAL_MERCHANT: nconf.get('PAYPAL_MERCHANT'),
GA_ID: nconf.get("GA_ID"),
IS_MOBILE: /Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(req.header('User-Agent')),
STRIPE_PUB_KEY: nconf.get('STRIPE_PUB_KEY'),

View file

@ -85,6 +85,7 @@ var UserSchema = new Schema({
mobileChat: Boolean,
plan: {
planId: String,
paymentMethod: String, //enum: ['Paypal','Stripe', '']}
customerId: String,
dateCreated: Date,
dateUpdated: Date,

View file

@ -299,16 +299,6 @@ module.exports = (swagger, v2) ->
]
action: user.unlock
"/user/buy-gems":
spec: method: 'POST', description: "Do not use this route"
middleware: auth.auth
action:user.buyGems
"/user/cancel-subscription":
spec: method: 'POST', description: "Do not use this route"
middleware: auth.auth
action:user.cancelSubscription
"/user/batch-update":
spec:
method: 'POST'

View file

@ -59,9 +59,14 @@ router.get('/static/extensions', function(req, res) {
// --------- PayPal --------
router.get('/paypal/checkout', user.paypalCheckout);
router.get('/paypal/checkout', auth.authWithUrl, user.paypalCheckout);
router.get('/paypal/checkout/success', user.paypalCheckoutSuccess);
router.get('/paypal/subscribe', user.paypalSubscribe);
router.get('/paypal/subscribe', auth.authWithUrl, user.paypalSubscribe);
router.get('/paypal/subscribe/success', user.paypalSubscribeSuccess);
router.get('/paypal/subscribe/cancel', auth.authWithUrl, user.paypalSubscribeCancel);
router.post("/stripe/checkout", auth.auth, user.stripeCheckout);
//router.get("/stripe/subscribe", auth.authWithUrl, user.stripeSubscribe); // checkout route is used (above) with ?plan= instead
router.get("/stripe/subscribe/cancel", auth.authWithUrl, user.stripeSubscribeCancel);
module.exports = router;

View file

@ -145,10 +145,12 @@ script(id='partials/options.settings.subscription.html',type='text/ng-template')
h2=env.t('individualSub')
div(ng-if='!user.purchased.plan.customerId')
div(ng-include="'partials/options.settings.subscription.perks.html'")
.btn.btn-primary(ng-click='showStripe(true)')=env.t('subscribe')
p
small.muted Payment Methods:
.btn.btn-primary(ng-click='showStripe(true)') Card
//-small.muted PayPal coming soon.
//a.btn.btn-warning(ng-click='paypalSubscribe()') PayPal
a.btn.btn-warning(href='/paypal/subscribe?uuid={{user._id}}') PayPal
a.btn.btn-warning(href='/paypal/subscribe?_id={{user._id}}&apiToken={{user.apiToken}}') PayPal
div(ng-if='user.purchased.plan.customerId')
.well

View file

@ -19,8 +19,11 @@ script(id='modals/buyGems.html', type='text/ng-template')
=env.t('USD')
tr
td
.btn.btn-primary(ng-click='showStripe()')=env.t('payWithCard')
a.btn.btn-warning(href='/paypal/checkout?uuid={{user._id}}') PayPal
p
small.muted Payment Methods:
//.btn.btn-primary(ng-click='showStripe()')=env.t('payWithCard')
.btn.btn-primary(ng-click='showStripe()') Card
a.btn.btn-warning(href='/paypal/checkout?_id={{user._id}}&apiToken={{user.apiToken}}') PayPal
div(ng-include="'partials/options.settings.subscription.html'")