mirror of
https://github.com/sudoxnym/habitica.git
synced 2026-08-05 03:52:14 +00:00
feat(gifts): allow gifting gems from paypal/stripe
This commit is contained in:
parent
7e9b92fcad
commit
e8dbd5ca1a
10 changed files with 85 additions and 52 deletions
|
|
@ -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'})
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
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();
|
||||
utils.ga.transaction(data.customerId, 5).item(5, 1, data.paymentMethod.toLowerCase() + "-checkout", "Gems > " + 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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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'});
|
||||
} else {
|
||||
payments.buyGems(user, {customerId: response.id, paymentMethod: 'Stripe'});
|
||||
}
|
||||
user.save(cb);
|
||||
} else {
|
||||
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);
|
||||
}
|
||||
}
|
||||
], function(err, saved){
|
||||
if (err) return res.send(500, err.toString()); // don't json this, let toString() handle errors
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -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'")
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
Loading…
Reference in a new issue