mirror of
https://github.com/sudoxnym/habitica.git
synced 2026-05-22 13:48:46 +00:00
feat(subscriptions): add paypal recurring cancelation. refactor stripe & paypal code to use auth.authWithUrl.
This commit is contained in:
parent
b686b56282
commit
0f430e102e
11 changed files with 65 additions and 43 deletions
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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){
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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'),
|
||||
|
|
|
|||
|
|
@ -85,6 +85,7 @@ var UserSchema = new Schema({
|
|||
mobileChat: Boolean,
|
||||
plan: {
|
||||
planId: String,
|
||||
paymentMethod: String, //enum: ['Paypal','Stripe', '']}
|
||||
customerId: String,
|
||||
dateCreated: Date,
|
||||
dateUpdated: Date,
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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'")
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue