diff --git a/public/js/controllers/groupsCtrl.js b/public/js/controllers/groupsCtrl.js index e3ee2be624..e4a0a8638f 100644 --- a/public/js/controllers/groupsCtrl.js +++ b/public/js/controllers/groupsCtrl.js @@ -2,7 +2,7 @@ habitrpg.controller("GroupsCtrl", ['$scope', '$rootScope', 'Shared', 'Groups', '$http', '$q', 'User', 'Members', '$state', 'Notification', function($scope, $rootScope, Shared, Groups, $http, $q, User, Members, $state, Notification) { - Members.selectMember('9', function(){ + Members.selectMember('d7f809df-81bd-4d58-992b-851d94bfaccb', function(){ $rootScope.openModal('send-gift',{controller:'MemberModalCtrl'}) }); diff --git a/public/js/controllers/rootCtrl.js b/public/js/controllers/rootCtrl.js index 4e27c81b52..7c5b6c3ed4 100644 --- a/public/js/controllers/rootCtrl.js +++ b/public/js/controllers/rootCtrl.js @@ -3,8 +3,8 @@ /* Make user and settings available for everyone through root scope. */ -habitrpg.controller("RootCtrl", ['$scope', '$rootScope', '$location', 'User', '$http', '$state', '$stateParams', 'Notification', 'Groups', 'Shared', 'Content', '$modal', '$timeout', 'ApiUrlService', - function($scope, $rootScope, $location, User, $http, $state, $stateParams, Notification, Groups, Shared, Content, $modal, $timeout, ApiUrlService) { +habitrpg.controller("RootCtrl", ['$scope', '$rootScope', '$location', 'User', '$http', '$state', '$stateParams', 'Notification', 'Groups', 'Shared', 'Content', '$modal', '$timeout', 'ApiUrlService', '$filter', + function($scope, $rootScope, $location, User, $http, $state, $stateParams, Notification, Groups, Shared, Content, $modal, $timeout, ApiUrlService, $filter) { var user = User.user; var initSticky = _.once(function(){ @@ -139,24 +139,30 @@ habitrpg.controller("RootCtrl", ['$scope', '$rootScope', '$location', 'User', '$ $rootScope.flash[type].splice($index, 1); } - $rootScope.showStripe = function(subscription) { + $rootScope.encodeGift = function(uuid, gift){ + return JSON.stringify({uuid:uuid, amount:gift.gems.amount, message:gift.message}); + } + + $rootScope.showStripe = function(data) { StripeCheckout.open({ key: window.env.STRIPE_PUB_KEY, address: false, - amount: 500, - name: subscription ? window.env.t('subscribe') : window.env.t('checkout'), - description: subscription ? + amount: data.gift ? data.gift.gems.amount/4*100 : 500, + name: data.subscription ? window.env.t('subscribe') : window.env.t('checkout'), + description: data.subscription ? window.env.t('buySubsText') : window.env.t('donationDesc'), - panelLabel: subscription ? window.env.t('subscribe') : window.env.t('checkout'), - token: function(data) { + panelLabel: data.subscription ? window.env.t('subscribe') : window.env.t('checkout'), + token: function(res) { var url = '/stripe/checkout'; - if (subscription) url += '?plan=basic_earned'; + if (data.gift) url += '?gift=' + $rootScope.encodeGift(data.uuid, data.gift); + //TODO else if? ^ + if (data.subscription) url += '?plan=basic_earned'; $scope.$apply(function(){ - $http.post(url, data).success(function() { + $http.post(url, res).success(function() { window.location.reload(true); - }).error(function(data) { - alert(data.err); + }).error(function(res) { + alert(res.err); }); }) } diff --git a/src/controllers/members.js b/src/controllers/members.js index f839d0fbce..61ec8c8cd0 100644 --- a/src/controllers/members.js +++ b/src/controllers/members.js @@ -29,15 +29,16 @@ api.getMember = function(req, res, next) { }) } -var sendMessage = function(user, member, msg){ - var message = groups.chatDefaults(msg, user); - shared.refPush(member.inbox.messages, message); +api.sendMessage = function(user, member, data){ + var msg = data.amount + ? "`Hello " + member.profile.name + ", " + user.profile.name + " has sent you " + data.amount + " gems!` " + data.message + : data.message; + shared.refPush(member.inbox.messages, groups.chatDefaults(msg, user)); member.inbox.newMessages++; member._v++; member.markModified('inbox.messages'); - var message = groups.chatDefaults(msg, member); - shared.refPush(user.inbox.messages, _.defaults({sent:true},message)); + shared.refPush(user.inbox.messages, _.defaults({sent:true}, groups.chatDefaults(msg, member))); user.markModified('inbox.messages'); } @@ -50,7 +51,7 @@ api.sendPrivateMessage = function(req, res, next){ || member.inbox.optOut) { // or if they've opted out of messaging return cb({code: 401, err: "Can't send message to this user."}); } - sendMessage(res.locals.user, member, req.body.message); + api.sendMessage(res.locals.user, member, {message:req.body.message}); async.parallel([ function (cb2) { member.save(cb2) }, function (cb2) { res.locals.user.save(cb2) } @@ -75,12 +76,10 @@ api.sendGift = function(req, res, next){ return cb({code: 401, err: "Amount must be within 0 and your current number of gems."}); member.balance += amt; user.balance -= amt; - var msg = "`Hello "+member.profile.name+", "+user.profile.name+" has sent you "+amt+" gems!` "; - if (req.body.message) msg += req.body.message; - sendMessage(user, member, msg); + api.sendMessage(user, member, {amount:req.body.gems.amount, message:req.body.message}); return async.parallel([ function (cb2) { member.save(cb2) }, - function (cb2) { res.locals.user.save(cb2) } + function (cb2) { user.save(cb2) } ], cb); case "subscription": return cb(); diff --git a/src/controllers/payments/index.js b/src/controllers/payments/index.js index eed12edd4d..23e5925114 100644 --- a/src/controllers/payments/index.js +++ b/src/controllers/payments/index.js @@ -7,6 +7,9 @@ var moment = require('moment'); var isProduction = nconf.get("NODE_ENV") === "production"; var stripe = require('./stripe'); var paypal = require('./paypal'); +var User = require('mongoose').model('User'); +var members = require('../members') +var async = require('async'); function revealMysteryItems(user) { _.each(shared.content.gear.flat, function(item) { @@ -61,12 +64,21 @@ exports.cancelSubscription = function(user, data) { utils.ga.event('unsubscribe', 'Stripe').send(); } -exports.buyGems = function(user, data) { - user.balance += 5; - user.purchased.txnCount++; - if(isProduction) utils.txnEmail(user, 'donation'); - utils.ga.event('checkout', data.paymentMethod).send(); - utils.ga.transaction(data.customerId, 5).item(5, 1, data.paymentMethod.toLowerCase() + "-checkout", "Gems > " + data.paymentMethod).send(); +exports.buyGems = function(data, cb) { + var amt = data.gift ? data.gift.amount/4 : 5; + (data.gift ? data.gift.member : data.user).balance += amt; + data.user.purchased.txnCount++; + if(isProduction) { + utils.txnEmail(data.user, 'donation'); + utils.ga.event('checkout', data.paymentMethod).send(); + //TODO ga.transaction to reflect whether this is gift or self-purchase + utils.ga.transaction(data.customerId, amt).item(amt, 1, data.paymentMethod.toLowerCase() + "-checkout", "Gems > " + data.paymentMethod).send(); + } + if (data.gift) members.sendMessage(data.user, data.gift.member, data.gift); + async.parallel([ + function(cb2){data.user.save(cb2)}, + function(cb2){data.gift ? data.gift.member.save(cb2) : cb2(null);} + ], cb); } exports.stripeCheckout = stripe.checkout; diff --git a/src/controllers/payments/paypal.js b/src/controllers/payments/paypal.js index cfc0b8d4d2..b264f32325 100644 --- a/src/controllers/payments/paypal.js +++ b/src/controllers/payments/paypal.js @@ -3,7 +3,7 @@ var moment = require('moment'); var async = require('async'); var _ = require('lodash'); var url = require('url'); -var mongoose = require('mongoose'); +var User = require('mongoose').model('User'); var payments = require('./index'); var logger = require('../../logging'); var ipn = require('paypal-ipn'); @@ -51,7 +51,7 @@ exports.executeBillingAgreement = function(req,res,next){ paypal.billingAgreement.execute(req.query.token, {}, cb); }, function(billingAgreement, cb){ - mongoose.model('User').findById(req.session.userId, function(err, user){ + User.findById(req.session.userId, function(err, user){ if (err) return cb(err); cb(null, {billingAgreement:billingAgreement, user:user}); }); @@ -67,6 +67,9 @@ exports.executeBillingAgreement = function(req,res,next){ } exports.createPayment = function(req, res, next) { + // if we're gifting to a user, put it in session for the `execute()` + req.session.gift = req.query.gift ? JSON.parse(req.query.gift) : undefined; + var price = req.session.gift ? Number(req.session.gift.amount/4).toFixed(2) : 5.00; var create_payment = { "intent": "sale", "payer": { @@ -81,14 +84,14 @@ exports.createPayment = function(req, res, next) { "items": [{ "name": "HabitRPG Gems", //"sku": "1", - "price": "5.00", + "price": price, "currency": "USD", "quantity": 1 }] }, "amount": { "currency": "USD", - "total": "5.00" + "total": price }, "description": "HabitRPG Gems" }] @@ -102,20 +105,25 @@ exports.createPayment = function(req, res, next) { exports.executePayment = function(req, res, next) { var paymentId = req.query.paymentId, - PayerID = req.query.PayerID; + PayerID = req.query.PayerID, + gift = req.session.gift; async.waterfall([ function(cb){ paypal.payment.execute(paymentId, {payer_id: PayerID}, cb); }, function(payment, cb){ - mongoose.model('User').findById(req.session.userId, cb); + async.parallel([ + function(cb2){ User.findById(req.session.userId, cb2); }, + function(cb2){ User.findById(gift ? gift.uuid : undefined, cb2); } + ], cb); }, - function(user, cb){ - if (_.isEmpty(user)) return cb("user not found when completing paypal transaction"); - payments.buyGems(user, {customerId:PayerID, paymentMethod:'Paypal'}); - user.save(cb); + function(results, cb){ + if (_.isEmpty(results[0])) + return cb("user not found when completing paypal transaction"); + if (gift) gift.member = results[1]; + payments.buyGems({user:results[0], customerId:PayerID, paymentMethod:'Paypal', gift:gift}, cb); } - ],function(err, saved){ + ],function(err, results){ if (err) return next(parseErr(err)); res.redirect('/'); }) @@ -153,7 +161,7 @@ exports.ipn = function(req, res, next) { // TODO what's the diff b/w the two data.txn_types below? The docs recommend subscr_cancel, but I'm getting the other one instead... case 'recurring_payment_profile_cancel': case 'subscr_cancel': - mongoose.model('User').findOne({'purchased.plan.customerId':req.body.recurring_payment_id},function(err, user){ + User.findOne({'purchased.plan.customerId':req.body.recurring_payment_id},function(err, user){ if (err) return logger.error(err); if (_.isEmpty(user)) return; // looks like the cancellation was already handled properly above (see api.paypalSubscribeCancel) payments.cancelSubscription(user); diff --git a/src/controllers/payments/stripe.js b/src/controllers/payments/stripe.js index 4f9a52afeb..404ed80ad9 100644 --- a/src/controllers/payments/stripe.js +++ b/src/controllers/payments/stripe.js @@ -2,6 +2,7 @@ var nconf = require('nconf'); var stripe = require("stripe")(nconf.get('STRIPE_API_KEY')); var async = require('async'); var payments = require('./index'); +var User = require('mongoose').model('User'); /* Setup Stripe response when posting payment @@ -9,19 +10,20 @@ var payments = require('./index'); exports.checkout = function(req, res, next) { var token = req.body.id; var user = res.locals.user; + var gift = req.query.gift ? JSON.parse(req.query.gift) : undefined; async.waterfall([ function(cb){ if (req.query.plan) { stripe.customers.create({ email: req.body.email, - metadata: {uuid: res.locals.user._id}, + metadata: {uuid: user._id}, card: token, plan: req.query.plan }, cb); } else { stripe.charges.create({ - amount: "500", // $5 + amount: gift ? ""+gift.amount/4*100 : "500", //"500" = $5 currency: "usd", card: token }, cb); @@ -30,10 +32,16 @@ exports.checkout = function(req, res, next) { function(response, cb) { if (req.query.plan) { payments.createSubscription(user, {customerId: response.id, paymentMethod: 'Stripe'}); + user.save(cb); } else { - payments.buyGems(user, {customerId: response.id, paymentMethod: 'Stripe'}); + async.waterfall([ + function(cb2){ User.findById(gift ? gift.uuid : undefined, cb2) }, + function(member, cb2){ + if (gift) gift.member = member; + payments.buyGems({user:user, customerId:response.id, paymentMethod:'Stripe', gift:gift},cb2); + } + ], cb); } - user.save(cb); } ], function(err, saved){ if (err) return res.send(500, err.toString()); // don't json this, let toString() handle errors diff --git a/src/models/group.js b/src/models/group.js index fd2518b8ed..1bc275afe4 100644 --- a/src/models/group.js +++ b/src/models/group.js @@ -93,10 +93,10 @@ GroupSchema.methods.toJSON = function(){ return doc; } -var chatDefaults = module.exports.chatDefaults = function(message,user){ +var chatDefaults = module.exports.chatDefaults = function(msg,user){ var message = { id: shared.uuid(), - text: message, + text: msg, timestamp: +new Date, likes: {} }; @@ -105,7 +105,7 @@ var chatDefaults = module.exports.chatDefaults = function(message,user){ uuid: user._id, contributor: user.contributor && user.contributor.toObject(), backer: user.backer && user.backer.toObject(), - user: user.profile.name, + user: user.profile.name }); } else { message.uuid = 'system'; diff --git a/views/options/settings.jade b/views/options/settings.jade index 3cd1d09223..b094bb9e07 100644 --- a/views/options/settings.jade +++ b/views/options/settings.jade @@ -237,7 +237,7 @@ script(id='partials/options.settings.subscription.html',type='text/ng-template') div(ng-if='!p.customerId || (p.customerId && p.dateTerminated)') h3(ng-if='(p.customerId && p.dateTerminated)') Resubscribe - a.btn.btn-primary(ng-click='showStripe(true)') Card + a.btn.btn-primary(ng-click='showStripe({subscription:true})') Card //a.btn.btn-warning(ng-click='paypalSubscribe()') PayPal a.btn.btn-warning(href='/paypal/subscribe?_id={{user._id}}&apiToken={{user.apiToken}}') PayPal div(ng-if='p.customerId') diff --git a/views/shared/modals/buy-gems.jade b/views/shared/modals/buy-gems.jade index c6ade26a00..b1caf50816 100644 --- a/views/shared/modals/buy-gems.jade +++ b/views/shared/modals/buy-gems.jade @@ -20,7 +20,7 @@ script(id='modals/buyGems.html', type='text/ng-template') td p small.muted=env.t('paymentMethods') - .btn.btn-primary(ng-click='showStripe()')=env.t('card') + .btn.btn-primary(ng-click='showStripe({})')=env.t('card') a.btn.btn-warning(href='/paypal/checkout?_id={{user._id}}&apiToken={{user.apiToken}}') PayPal div(ng-include="'partials/options.settings.subscription.html'") diff --git a/views/shared/modals/members.jade b/views/shared/modals/members.jade index e94596ffa1..94c31ec0c8 100644 --- a/views/shared/modals/members.jade +++ b/views/shared/modals/members.jade @@ -96,6 +96,6 @@ script(type='text/ng-template', id='modals/send-gift.html') .modal-footer - var fromBal = "gift.type=='gems' && gift.gems.fromBalance" button.btn.btn-primary(ng-show=fromBal, ng-click='sendGift(profile._id, gift)')=env.t("send") - a.btn.btn-primary(ng-hide=fromBal, ng-click='showStripe({uuid:profile._id})')=env.t('card') - a.btn.btn-warning(ng-hide=fromBal, href='/paypal/checkout?uuid={{profile._id}}') PayPal + a.btn.btn-primary(ng-hide=fromBal, ng-click='showStripe({gift:gift, uuid:profile._id})')=env.t('card') + a.btn.btn-warning(ng-hide=fromBal, href='/paypal/checkout?_id={{::user._id}}&apiToken={{::user.apiToken}}&gift={{encodeGift(profile._id, gift)}}') PayPal button.btn.btn-default(ng-click='$close()')=env.t('cancel') \ No newline at end of file