Skip to content

Commit

Permalink
add possibility for group to block members from getting gems
Browse files Browse the repository at this point in the history
  • Loading branch information
paglias committed Jul 13, 2017
1 parent 88c56c9 commit 713e0c2
Show file tree
Hide file tree
Showing 12 changed files with 98 additions and 9 deletions.
3 changes: 2 additions & 1 deletion website/common/locales/en/groups.json
Expand Up @@ -298,5 +298,6 @@
"managerMarker": " - Manager",
"joinedGuild": "Joined a Guild",
"joinedGuildText": "Ventured into the social side of Habitica by joining a Guild!",
"badAmountOfGemsToPurchase": "Amount must be at least 1."
"badAmountOfGemsToPurchase": "Amount must be at least 1.",
"groupPolicyCannotGetGems": "The policy of one group you're part of prevents its members from obtaining gems."
}
5 changes: 5 additions & 0 deletions website/common/script/ops/purchase.js
Expand Up @@ -30,6 +30,11 @@ module.exports = function purchase (user, req = {}, analytics) {
let convCap = planGemLimits.convCap;
convCap += user.purchased.plan.consecutive.gemCapExtra;

// Some groups limit their members ability to obtain gems
// The check is async so it's done on the server (in server/controllers/api-v3/user#purchase)
// only and not on the client,
// resulting in a purchase that will seem successful until the request hit the server.

if (!user.purchased || !user.purchased.plan || !user.purchased.plan.customerId) {
throw new NotAuthorized(i18n.t('mustSubscribeToPurchaseGems', req.language));
}
Expand Down
14 changes: 13 additions & 1 deletion website/server/controllers/api-v3/user.js
Expand Up @@ -19,6 +19,7 @@ import {
sendTxn as txnEmail,
} from '../../libs/email';
import nconf from 'nconf';
import get from 'lodash/get';

const TECH_ASSISTANCE_EMAIL = nconf.get('EMAILS:TECH_ASSISTANCE_EMAIL');

Expand Down Expand Up @@ -1227,7 +1228,18 @@ api.purchase = {
url: '/user/purchase/:type/:key',
async handler (req, res) {
let user = res.locals.user;
let purchaseRes = req.params.type === 'spells' ? common.ops.buySpecialSpell(user, req) : common.ops.purchase(user, req, res.analytics);
const type = get(req.params, 'type');
const key = get(req.params, 'key');

// Some groups limit their members ability to obtain gems
// The check is async so it's done on the server only and not on the client,
// resulting in a purchase that will seem successful until the request hit the server.
if (type === 'gems' && key === 'gem') {
const canGetGems = await user.canGetGems();
if (!canGetGems) throw new NotAuthorized(res.t('groupPolicyCannotGetGems'));
}

let purchaseRes = type === 'spells' ? common.ops.buySpecialSpell(user, req) : common.ops.purchase(user, req, res.analytics);
await user.save();
res.respond(200, ...purchaseRes);
},
Expand Down
2 changes: 1 addition & 1 deletion website/server/controllers/top-level/payments/paypal.js
Expand Up @@ -27,7 +27,7 @@ api.checkout = {
let gift = req.query.gift ? JSON.parse(req.query.gift) : undefined;
req.session.gift = req.query.gift;

let link = await paypalPayments.checkout({gift});
let link = await paypalPayments.checkout({gift, user: res.locals.user});

if (req.query.noRedirect) {
res.respond(200);
Expand Down
8 changes: 8 additions & 0 deletions website/server/libs/amazonPayments.js
Expand Up @@ -97,6 +97,8 @@ api.checkout = async function checkout (options = {}) {
let amount = 5;

if (gift) {
gift.member = await User.findById(gift.uuid).exec();

if (gift.type === this.constants.GIFT_TYPE_GEMS) {
if (gift.gems.amount <= 0) {
throw new BadRequest(i18n.t('badAmountOfGemsToPurchase'));
Expand All @@ -107,6 +109,12 @@ api.checkout = async function checkout (options = {}) {
}
}

if (!gift || gift.type === this.constants.GIFT_TYPE_GEMS) {
const receiver = gift ? user : gift.member;
const receiverCanGetGems = await receiver.canGetGems();
if (!receiverCanGetGems) throw new NotAuthorized(i18n.t('groupPolicyCannotGetGems', receiver.preferences.language));
}

await this.setOrderReferenceDetails({
AmazonOrderReferenceId: orderReferenceId,
OrderReferenceAttributes: {
Expand Down
3 changes: 3 additions & 0 deletions website/server/libs/applePayments.js
Expand Up @@ -21,6 +21,9 @@ api.constants = {
};

api.verifyGemPurchase = async function verifyGemPurchase (user, receipt, headers) {
const userCanGetGems = await user.canGetGems();
if (!userCanGetGems) throw new NotAuthorized(shared.i18n.t('groupPolicyCannotGetGems', user.preferences.language));

await iap.setup();
let appleRes = await iap.validate(iap.APPLE, receipt);
let isValidated = iap.isValidated(appleRes);
Expand Down
3 changes: 3 additions & 0 deletions website/server/libs/googlePayments.js
Expand Up @@ -20,6 +20,9 @@ api.constants = {
};

api.verifyGemPurchase = async function verifyGemPurchase (user, receipt, signature, headers) {
const userCanGetGems = await user.canGetGems();
if (!userCanGetGems) throw new NotAuthorized(shared.i18n.t('groupPolicyCannotGetGems', user.preferences.language));

await iap.setup();

let testObj = {
Expand Down
14 changes: 13 additions & 1 deletion website/server/libs/paypalPayments.js
Expand Up @@ -70,11 +70,15 @@ api.paypalBillingAgreementCancel = Bluebird.promisify(paypal.billingAgreement.ca
api.ipnVerifyAsync = Bluebird.promisify(ipn.verify, {context: ipn});

api.checkout = async function checkout (options = {}) {
let {gift} = options;
let {gift, user} = options;

let amount = 5.00;
let description = 'Habitica Gems';

if (gift) {
const member = await User.findById(gift.uuid).exec();
gift.member = member;

if (gift.type === 'gems') {
if (gift.gems.amount <= 0) {
throw new BadRequest(i18n.t('badAmountOfGemsToPurchase'));
Expand All @@ -87,6 +91,14 @@ api.checkout = async function checkout (options = {}) {
}
}


if (!gift || gift.type === 'gems') {
const receiver = gift ? user : gift.member;
const receiverCanGetGems = await receiver.canGetGems();
if (!receiverCanGetGems) throw new NotAuthorized(shared.i18n.t('groupPolicyCannotGetGems', receiver.preferences.language));
}


let createPayment = {
intent: 'sale',
payer: { payment_method: this.constants.PAYMENT_METHOD },
Expand Down
13 changes: 11 additions & 2 deletions website/server/libs/stripePayments.js
Expand Up @@ -76,6 +76,11 @@ api.checkout = async function checkout (options, stripeInc) {

if (!token) throw new BadRequest('Missing req.body.id');

if (gift) {
const member = await User.findById(gift.uuid).exec();
gift.member = member;
}

if (sub) {
if (sub.discount) {
if (!coupon) throw new BadRequest(shared.i18n.t('couponCodeRequired'));
Expand Down Expand Up @@ -114,6 +119,12 @@ api.checkout = async function checkout (options, stripeInc) {
}
}

if (!gift || gift.type === 'gems') {
const receiver = gift ? user : gift.member;
const receiverCanGetGems = await receiver.canGetGems();
if (!receiverCanGetGems) throw new NotAuthorized(shared.i18n.t('groupPolicyCannotGetGems', receiver.preferences.language));
}

response = await stripeApi.charges.create({
amount,
currency: 'usd',
Expand Down Expand Up @@ -141,8 +152,6 @@ api.checkout = async function checkout (options, stripeInc) {
};

if (gift) {
let member = await User.findById(gift.uuid).exec();
gift.member = member;
if (gift.type === 'subscription') method = 'createSubscription';
data.paymentMethod = 'Gift';
}
Expand Down
10 changes: 9 additions & 1 deletion website/server/models/challenge.js
Expand Up @@ -283,7 +283,15 @@ schema.methods.closeChal = async function closeChal (broken = {}) {
// Award prize to winner and notify
if (winner) {
winner.achievements.challenges.push(challenge.name);
winner.balance += challenge.prize / 4;

// If the winner cannot get gems (because of a group policy)
// reimburse the leader
const winnerCanGetGems = await winner.canGetGems();
if (!winnerCanGetGems) {
await User.update({_id: challenge.leader}, {$inc: {balance: challenge.prize / 4}}).exec();
} else {
winner.balance += challenge.prize / 4;
}

winner.addNotification('WON_CHALLENGE');

Expand Down
4 changes: 3 additions & 1 deletion website/server/models/group.js
Expand Up @@ -76,6 +76,8 @@ export let schema = new Schema({
leaderOnly: { // restrict group actions to leader (members can't do them)
challenges: {type: Boolean, default: false, required: true},
// invites: {type: Boolean, default: false, required: true},
// Some group plans prevent members from getting gems
getGems: {type: Boolean, default: false},
},
memberCount: {type: Number, default: 1},
challengeCount: {type: Number, default: 0},
Expand Down Expand Up @@ -124,7 +126,7 @@ export let schema = new Schema({
});

schema.plugin(baseModel, {
noSet: ['_id', 'balance', 'quest', 'memberCount', 'chat', 'challengeCount', 'tasksOrder', 'purchased', 'managers'],
noSet: ['_id', 'balance', 'quest', 'memberCount', 'chat', 'challengeCount', 'tasksOrder', 'purchased', 'managers', 'blockMembersFromGettingGems'],
private: ['purchased.plan'],
toJSONTransform (plainObj, originalDoc) {
if (plainObj.purchased) plainObj.purchased.active = originalDoc.isSubscribed();
Expand Down
28 changes: 27 additions & 1 deletion website/server/models/user/methods.js
Expand Up @@ -4,6 +4,7 @@ import Bluebird from 'bluebird';
import {
chatDefaults,
TAVERN_ID,
model as Group,
} from '../group';
import { defaults, map, flatten, flow, compact, uniq, partialRight } from 'lodash';
import { model as UserNotification } from '../userNotification';
Expand Down Expand Up @@ -190,7 +191,7 @@ schema.methods.cancelSubscription = async function cancelSubscription (options =
return await payments.cancelSubscription(options);
};

schema.methods.daysUserHasMissed = function daysUserHasMissed (now, req = {}) {
schema.methods.daysUserHasMissed = function daysUserHasMissed (now, req = {}) {
// If the user's timezone has changed (due to travel or daylight savings),
// cron can be triggered twice in one day, so we check for that and use
// both timezones to work out if cron should run.
Expand Down Expand Up @@ -271,3 +272,28 @@ schema.methods.daysUserHasMissed = function daysUserHasMissed (now, req = {}) {

return {daysMissed, timezoneOffsetFromUserPrefs};
};

// Determine if the user can get gems: some groups restrict their members ability to obtain them.
// User is allowed to buy gems if no group has `leaderOnly.getGems` === true or if
// its the group leader
schema.methods.canGetGems = function canObtainGems () {
const user = this;
const plan = user.purchased.plan;


if (!user.isSubscribed() || plan.customerId !== payments.constants.GROUP_PLAN_CUSTOMER_ID) {
return true;
}

const userGroups = user.getGroups();

return Group
.find({_id: {$in: userGroups}})
.select('leaderOnly leader')
.exec()
.then(groups => {
return groups.every(g => {
return g.leader === user._id || g.leaderOnly.getGems !== true;
});
});
};

0 comments on commit 713e0c2

Please sign in to comment.