feat(gifts): allow gifting subscriptions

This commit is contained in:
Tyler Renelle 2014-11-26 18:13:25 -07:00
parent e8dbd5ca1a
commit 5b01fff700
8 changed files with 86 additions and 61 deletions

View file

@ -133,7 +133,7 @@ habitrpg.controller("GroupsCtrl", ['$scope', '$rootScope', 'Shared', 'Groups', '
$scope.gift = {
type: 'gems',
gems: {amount:0, fromBalance:true},
subscription: {},
subscription: {months:1},
message:''
};
$scope.sendGift = function(uuid, gift){

View file

@ -140,19 +140,21 @@ habitrpg.controller("RootCtrl", ['$scope', '$rootScope', '$location', 'User', '$
}
$rootScope.encodeGift = function(uuid, gift){
return JSON.stringify({uuid:uuid, amount:gift.gems.amount, message:gift.message});
gift.uuid = uuid;
return JSON.stringify(gift);
}
$rootScope.showStripe = function(data) {
var isSub = data.subscription || (data.gift && data.gift.type=='subscription');
StripeCheckout.open({
key: window.env.STRIPE_PUB_KEY,
address: false,
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: data.subscription ? window.env.t('subscribe') : window.env.t('checkout'),
amount: !data.gift ? 500 : // 500 = $5
data.gift.type=='subscription' ? Content.subscriptionBlocks[data.gift.subscription.months].price*100:
data.gift.gems.amount/4*100,
name: isSub ? window.env.t('subscribe') : window.env.t('checkout'),
description: isSub ? window.env.t('buySubsText') : window.env.t('donationDesc'),
panelLabel: isSub ? window.env.t('subscribe') : window.env.t('checkout'),
token: function(res) {
var url = '/stripe/checkout';
if (data.gift) url += '?gift=' + $rootScope.encodeGift(data.uuid, data.gift);

View file

@ -30,9 +30,14 @@ api.getMember = function(req, res, next) {
}
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;
var msg;
if (!data.type) {
msg = data.message
} else {
msg = "`Hello " + member.profile.name + ", " + user.profile.name + " has sent you ";
msg += (data.type=='gems') ? data.gems.amount + " gems!`" : data.subscription.months + " months of subscription!`";
msg += data.message;
}
shared.refPush(member.inbox.messages, groups.chatDefaults(msg, user));
member.inbox.newMessages++;
member._v++;
@ -76,7 +81,7 @@ 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;
api.sendMessage(user, member, {amount:req.body.gems.amount, message:req.body.message});
api.sendMessage(user, member, req.body);
return async.parallel([
function (cb2) { member.save(cb2) },
function (cb2) { user.save(cb2) }

View file

@ -25,26 +25,37 @@ function revealMysteryItems(user) {
});
}
exports.createSubscription = function(user, data) {
if (!user.purchased.plan) user.purchased.plan = {};
var p = user.purchased.plan;
exports.createSubscription = function(data, cb) {
var recipient = data.gift ? data.gift.member : data.user;
if (!recipient.purchased.plan) recipient.purchased.plan = {};
var p = recipient.purchased.plan;
_(p).merge({ // override with these values
planId:'basic_earned',
customerId: data.customerId,
dateUpdated: new Date(),
gemsBought: 0,
paymentMethod: data.paymentMethod,
extraMonths: p.extraMonths + (p.dateTerminated ? moment(p.dateTerminated).diff(new Date(),'months',true) : 0),
extraMonths: +p.extraMonths
+ +(p.dateTerminated ? moment(p.dateTerminated).diff(new Date(),'months',true) : 0)
+ +(data.gift ? data.gift.subscription.months : 0),
dateTerminated: null
}).defaults({ // allow non-override if a plan was previously used
dateCreated: new Date(),
mysteryItems: []
});
revealMysteryItems(user);
if(isProduction) utils.txnEmail(user, 'subscription-begins');
user.purchased.txnCount++;
utils.ga.event('subscribe', data.paymentMethod).send();
utils.ga.transaction(data.customerId, 5).item(5, 1, data.paymentMethod.toLowerCase() + '-subscription', data.paymentMethod + " > Stripe").send();
revealMysteryItems(recipient);
if(isProduction) {
utils.txnEmail(data.user, 'subscription-begins');
//TODO proper ga.event / transaction
utils.ga.event('subscribe', data.paymentMethod).send();
utils.ga.transaction(data.customerId, 5).item(5, 1, data.paymentMethod.toLowerCase() + '-subscription', data.paymentMethod + " > Stripe").send();
}
data.user.purchased.txnCount++;
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);
}
/**
@ -65,7 +76,7 @@ exports.cancelSubscription = function(user, data) {
}
exports.buyGems = function(data, cb) {
var amt = data.gift ? data.gift.amount/4 : 5;
var amt = data.gift ? data.gift.gems.amount/4 : 5;
(data.gift ? data.gift.member : data.user).balance += amt;
data.user.purchased.txnCount++;
if(isProduction) {

View file

@ -8,6 +8,7 @@ var payments = require('./index');
var logger = require('../../logging');
var ipn = require('paypal-ipn');
var paypal = require('paypal-rest-sdk');
var shared = require('habitrpg-shared');
// This is the plan.id for paypal subscriptions. You have to set up billing plans via their REST sdk (they don't have
// a web interface for billing-plan creation), see ./paypalBillingSetup.js for how. After the billing plan is created
@ -68,8 +69,13 @@ 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 gift = req.session.gift = req.query.gift ? JSON.parse(req.query.gift) : undefined;
var price = !gift ? 5.00
: gift.type=='gems' ? Number(gift.gems.amount/4).toFixed(2)
: Number(shared.content.subscriptionBlocks[gift.subscription.months].price).toFixed(2);
var description = !gift ? "HabitRPG Gems"
: gift.type=='gems' ? "HabitRPG Gems (Gift)"
: gift.subscription.months + "mo. HabitRPG Subscription (Gift)";
var create_payment = {
"intent": "sale",
"payer": {
@ -82,7 +88,7 @@ exports.createPayment = function(req, res, next) {
"transactions": [{
"item_list": {
"items": [{
"name": "HabitRPG Gems",
"name": description,
//"sku": "1",
"price": price,
"currency": "USD",
@ -93,7 +99,7 @@ exports.createPayment = function(req, res, next) {
"currency": "USD",
"total": price
},
"description": "HabitRPG Gems"
"description": description
}]
};
paypal.payment.create(create_payment, function (err, payment) {
@ -118,12 +124,17 @@ exports.executePayment = function(req, res, next) {
], 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);
if (_.isEmpty(results[0])) return cb("User not found when completing paypal transaction");
var data = {user:results[0], customerId:PayerID, paymentMethod:'Paypal', gift:gift}
var method = 'buyGems';
if (gift) {
gift.member = results[1];
if (gift.type=='subscription') method = 'createSubscription';
data.paymentMethod = 'Gift';
}
payments[method](data, cb);
}
],function(err, results){
],function(err){
if (err) return next(parseErr(err));
res.redirect('/');
})

View file

@ -3,6 +3,7 @@ var stripe = require("stripe")(nconf.get('STRIPE_API_KEY'));
var async = require('async');
var payments = require('./index');
var User = require('mongoose').model('User');
var shared = require('habitrpg-shared');
/*
Setup Stripe response when posting payment
@ -23,27 +24,32 @@ exports.checkout = function(req, res, next) {
}, cb);
} else {
stripe.charges.create({
amount: gift ? ""+gift.amount/4*100 : "500", //"500" = $5
amount: !gift ? "500" //"500" = $5
: gift.type=='subscription' ? ""+shared.content.subscriptionBlocks[gift.subscription.months].price*100
: ""+gift.gems.amount/4*100,
currency: "usd",
card: token
}, cb);
}
},
function(response, cb) {
if (req.query.plan) {
payments.createSubscription(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);
if (req.query.plan)
return payments.createSubscription({user:user, customerId:response.id, paymentMethod:'Stripe'}, cb);
async.waterfall([
function(cb2){ User.findById(gift ? gift.uuid : undefined, cb2) },
function(member, cb2){
var data = {user:user, customerId:response.id, paymentMethod:'Stripe', gift:gift};
var method = 'buyGems';
if (gift) {
gift.member = member;
if (gift.type=='subscription') method = 'createSubscription';
data.paymentMethod = 'Gift';
}
], cb);
}
payments[method](data, cb2);
}
], cb);
}
], function(err, saved){
], function(err){
if (err) return res.send(500, err.toString()); // don't json this, let toString() handle errors
res.send(200);
user = token = null;

View file

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

View file

@ -75,22 +75,12 @@ script(type='text/ng-template', id='modals/send-gift.html')
.panel-heading Subscription
.panel-body
.form-group
.radio
label
input(type="radio", name="subRadio", value="1", ng-model='gift.subscription.months')
| 1 Month ($5)
.radio
label
input(type="radio", name="subRadio", value="3", ng-model='gift.subscription.months')
| 3 Months ($15)
.radio
label
input(type="radio", name="subRadio", value="6", ng-model='gift.subscription.months')
| 6 Months ($30)
.radio
label
input(type="radio", name="subRadio", value="12", ng-model='gift.subscription.months')
| 1 Year ($48)
each block in env.Content.subscriptionBlocks
.radio
label
input(type="radio", name="subRadio", value="#{block.months}", ng-model='gift.subscription.months')
| #{block.months} Month(s): $#{block.price}
textarea.form-control(rows='3', ng-model='gift.message', placeholder='Personal message (optional)')
.modal-footer