mirror of
https://github.com/HabitRPG/habitica.git
synced 2025-12-16 06:07:21 +01:00
Groups can prevent members from getting gems (#8870)
* add possibility for group to block members from getting gems * fixes * fix tests * adds some tests * unit tests * finish unit tests * remove old code
This commit is contained in:
committed by
Sabe Jones
parent
fe9521a63f
commit
78ba596504
@@ -114,6 +114,26 @@ describe('POST /challenges/:challengeId/winner/:winnerId', () => {
|
|||||||
await expect(winningUser.sync()).to.eventually.have.property('balance', oldBalance + challenge.prize / 4);
|
await expect(winningUser.sync()).to.eventually.have.property('balance', oldBalance + challenge.prize / 4);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('doesn\'t gives winner gems if group policy prevents it', async () => {
|
||||||
|
let oldBalance = winningUser.balance;
|
||||||
|
let oldLeaderBalance = (await groupLeader.sync()).balance;
|
||||||
|
|
||||||
|
await winningUser.update({
|
||||||
|
'purchased.plan.customerId': 'group-plan',
|
||||||
|
});
|
||||||
|
await group.update({
|
||||||
|
'leaderOnly.getGems': true,
|
||||||
|
'purchased.plan.customerId': 123,
|
||||||
|
});
|
||||||
|
|
||||||
|
await groupLeader.post(`/challenges/${challenge._id}/selectWinner/${winningUser._id}`);
|
||||||
|
|
||||||
|
await sleep(0.5);
|
||||||
|
|
||||||
|
await expect(winningUser.sync()).to.eventually.have.property('balance', oldBalance);
|
||||||
|
await expect(groupLeader.sync()).to.eventually.have.property('balance', oldLeaderBalance + challenge.prize / 4);
|
||||||
|
});
|
||||||
|
|
||||||
it('doesn\'t refund gems to group leader', async () => {
|
it('doesn\'t refund gems to group leader', async () => {
|
||||||
let oldBalance = (await groupLeader.sync()).balance;
|
let oldBalance = (await groupLeader.sync()).balance;
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import {
|
import {
|
||||||
generateUser,
|
generateUser,
|
||||||
|
createAndPopulateGroup,
|
||||||
translate as t,
|
translate as t,
|
||||||
} from '../../../../helpers/api-integration/v3';
|
} from '../../../../helpers/api-integration/v3';
|
||||||
|
|
||||||
@@ -31,4 +32,70 @@ describe('POST /user/purchase/:type/:key', () => {
|
|||||||
|
|
||||||
expect(user.items[type][key]).to.equal(1);
|
expect(user.items[type][key]).to.equal(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('can convert gold to gems if subscribed', async () => {
|
||||||
|
let oldBalance = user.balance;
|
||||||
|
await user.update({
|
||||||
|
'purchased.plan.customerId': 'group-plan',
|
||||||
|
'stats.gp': 1000,
|
||||||
|
});
|
||||||
|
await user.post('/user/purchase/gems/gem');
|
||||||
|
await user.sync();
|
||||||
|
expect(user.balance).to.equal(oldBalance + 0.25);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('leader can convert gold to gems even if the group plan prevents it', async () => {
|
||||||
|
let { group, groupLeader } = await createAndPopulateGroup({
|
||||||
|
groupDetails: {
|
||||||
|
name: 'test',
|
||||||
|
type: 'guild',
|
||||||
|
privacy: 'private',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
await group.update({
|
||||||
|
'leaderOnly.getGems': true,
|
||||||
|
'purchased.plan.customerId': 123,
|
||||||
|
});
|
||||||
|
await groupLeader.sync();
|
||||||
|
let oldBalance = groupLeader.balance;
|
||||||
|
|
||||||
|
await groupLeader.update({
|
||||||
|
'purchased.plan.customerId': 'group-plan',
|
||||||
|
'stats.gp': 1000,
|
||||||
|
});
|
||||||
|
await groupLeader.post('/user/purchase/gems/gem');
|
||||||
|
|
||||||
|
await groupLeader.sync();
|
||||||
|
expect(groupLeader.balance).to.equal(oldBalance + 0.25);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('cannot convert gold to gems if the group plan prevents it', async () => {
|
||||||
|
let { group, members } = await createAndPopulateGroup({
|
||||||
|
groupDetails: {
|
||||||
|
name: 'test',
|
||||||
|
type: 'guild',
|
||||||
|
privacy: 'private',
|
||||||
|
},
|
||||||
|
members: 1,
|
||||||
|
});
|
||||||
|
await group.update({
|
||||||
|
'leaderOnly.getGems': true,
|
||||||
|
'purchased.plan.customerId': 123,
|
||||||
|
});
|
||||||
|
let oldBalance = members[0].balance;
|
||||||
|
|
||||||
|
await members[0].update({
|
||||||
|
'purchased.plan.customerId': 'group-plan',
|
||||||
|
'stats.gp': 1000,
|
||||||
|
});
|
||||||
|
await expect(members[0].post('/user/purchase/gems/gem'))
|
||||||
|
.to.eventually.be.rejected.and.eql({
|
||||||
|
code: 401,
|
||||||
|
error: 'NotAuthorized',
|
||||||
|
message: t('groupPolicyCannotGetGems'),
|
||||||
|
});
|
||||||
|
|
||||||
|
await members[0].sync();
|
||||||
|
expect(members[0].balance).to.equal(oldBalance);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -102,6 +102,7 @@ describe('Amazon Payments', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should purchase gems', async () => {
|
it('should purchase gems', async () => {
|
||||||
|
sinon.stub(user, 'canGetGems').returnsPromise().resolves(true);
|
||||||
await amzLib.checkout({user, orderReferenceId, headers});
|
await amzLib.checkout({user, orderReferenceId, headers});
|
||||||
|
|
||||||
expect(paymentBuyGemsStub).to.be.calledOnce;
|
expect(paymentBuyGemsStub).to.be.calledOnce;
|
||||||
@@ -111,6 +112,8 @@ describe('Amazon Payments', () => {
|
|||||||
headers,
|
headers,
|
||||||
});
|
});
|
||||||
expectAmazonStubs();
|
expectAmazonStubs();
|
||||||
|
expect(user.canGetGems).to.be.calledOnce;
|
||||||
|
user.canGetGems.restore();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should error if gem amount is too low', async () => {
|
it('should error if gem amount is too low', async () => {
|
||||||
@@ -132,20 +135,29 @@ describe('Amazon Payments', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should error if user cannot get gems gems', async () => {
|
||||||
|
sinon.stub(user, 'canGetGems').returnsPromise().resolves(false);
|
||||||
|
await expect(amzLib.checkout({user, orderReferenceId, headers})).to.eventually.be.rejected.and.to.eql({
|
||||||
|
httpCode: 401,
|
||||||
|
message: i18n.t('groupPolicyCannotGetGems'),
|
||||||
|
name: 'NotAuthorized',
|
||||||
|
});
|
||||||
|
user.canGetGems.restore();
|
||||||
|
});
|
||||||
|
|
||||||
it('should gift gems', async () => {
|
it('should gift gems', async () => {
|
||||||
let receivingUser = new User();
|
let receivingUser = new User();
|
||||||
receivingUser.save();
|
await receivingUser.save();
|
||||||
let gift = {
|
let gift = {
|
||||||
type: 'gems',
|
type: 'gems',
|
||||||
|
uuid: receivingUser._id,
|
||||||
gems: {
|
gems: {
|
||||||
amount: 16,
|
amount: 16,
|
||||||
uuid: receivingUser._id,
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
amount = 16 / 4;
|
amount = 16 / 4;
|
||||||
await amzLib.checkout({gift, user, orderReferenceId, headers});
|
await amzLib.checkout({gift, user, orderReferenceId, headers});
|
||||||
|
|
||||||
gift.member = receivingUser;
|
|
||||||
expect(paymentBuyGemsStub).to.be.calledOnce;
|
expect(paymentBuyGemsStub).to.be.calledOnce;
|
||||||
expect(paymentBuyGemsStub).to.be.calledWith({
|
expect(paymentBuyGemsStub).to.be.calledWith({
|
||||||
user,
|
user,
|
||||||
|
|||||||
@@ -57,7 +57,20 @@ describe('Apple Payments', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('errors if the user cannot purchase gems', async () => {
|
||||||
|
sinon.stub(user, 'canGetGems').returnsPromise().resolves(false);
|
||||||
|
await expect(applePayments.verifyGemPurchase(user, receipt, headers))
|
||||||
|
.to.eventually.be.rejected.and.to.eql({
|
||||||
|
httpCode: 401,
|
||||||
|
name: 'NotAuthorized',
|
||||||
|
message: i18n.t('groupPolicyCannotGetGems'),
|
||||||
|
});
|
||||||
|
|
||||||
|
user.canGetGems.restore();
|
||||||
|
});
|
||||||
|
|
||||||
it('purchases gems', async () => {
|
it('purchases gems', async () => {
|
||||||
|
sinon.stub(user, 'canGetGems').returnsPromise().resolves(true);
|
||||||
await applePayments.verifyGemPurchase(user, receipt, headers);
|
await applePayments.verifyGemPurchase(user, receipt, headers);
|
||||||
|
|
||||||
expect(iapSetupStub).to.be.calledOnce;
|
expect(iapSetupStub).to.be.calledOnce;
|
||||||
@@ -74,6 +87,8 @@ describe('Apple Payments', () => {
|
|||||||
amount: 5.25,
|
amount: 5.25,
|
||||||
headers,
|
headers,
|
||||||
});
|
});
|
||||||
|
expect(user.canGetGems).to.be.calledOnce;
|
||||||
|
user.canGetGems.restore();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -63,7 +63,21 @@ describe('Google Payments', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should throw an error if user cannot purchase gems', async () => {
|
||||||
|
sinon.stub(user, 'canGetGems').returnsPromise().resolves(false);
|
||||||
|
|
||||||
|
await expect(googlePayments.verifyGemPurchase(user, receipt, signature, headers))
|
||||||
|
.to.eventually.be.rejected.and.to.eql({
|
||||||
|
httpCode: 401,
|
||||||
|
name: 'NotAuthorized',
|
||||||
|
message: i18n.t('groupPolicyCannotGetGems'),
|
||||||
|
});
|
||||||
|
|
||||||
|
user.canGetGems.restore();
|
||||||
|
});
|
||||||
|
|
||||||
it('purchases gems', async () => {
|
it('purchases gems', async () => {
|
||||||
|
sinon.stub(user, 'canGetGems').returnsPromise().resolves(true);
|
||||||
await googlePayments.verifyGemPurchase(user, receipt, signature, headers);
|
await googlePayments.verifyGemPurchase(user, receipt, signature, headers);
|
||||||
|
|
||||||
expect(iapSetupStub).to.be.calledOnce;
|
expect(iapSetupStub).to.be.calledOnce;
|
||||||
@@ -82,6 +96,8 @@ describe('Google Payments', () => {
|
|||||||
amount: 5.25,
|
amount: 5.25,
|
||||||
headers,
|
headers,
|
||||||
});
|
});
|
||||||
|
expect(user.canGetGems).to.be.calledOnce;
|
||||||
|
user.canGetGems.restore();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ describe('Paypal Payments', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('creates a link for gem purchases', async () => {
|
it('creates a link for gem purchases', async () => {
|
||||||
let link = await paypalPayments.checkout();
|
let link = await paypalPayments.checkout({user: new User()});
|
||||||
|
|
||||||
expect(paypalPaymentCreateStub).to.be.calledOnce;
|
expect(paypalPaymentCreateStub).to.be.calledOnce;
|
||||||
expect(paypalPaymentCreateStub).to.be.calledWith(getPaypalCreateOptions('Habitica Gems', 5.00));
|
expect(paypalPaymentCreateStub).to.be.calledWith(getPaypalCreateOptions('Habitica Gems', 5.00));
|
||||||
@@ -87,13 +87,25 @@ describe('Paypal Payments', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should error if the user cannot get gems', async () => {
|
||||||
|
let user = new User();
|
||||||
|
sinon.stub(user, 'canGetGems').returnsPromise().resolves(false);
|
||||||
|
|
||||||
|
await expect(paypalPayments.checkout({user})).to.eventually.be.rejected.and.to.eql({
|
||||||
|
httpCode: 401,
|
||||||
|
message: i18n.t('groupPolicyCannotGetGems'),
|
||||||
|
name: 'NotAuthorized',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('creates a link for gifting gems', async () => {
|
it('creates a link for gifting gems', async () => {
|
||||||
let receivingUser = new User();
|
let receivingUser = new User();
|
||||||
|
await receivingUser.save();
|
||||||
let gift = {
|
let gift = {
|
||||||
type: 'gems',
|
type: 'gems',
|
||||||
|
uuid: receivingUser._id,
|
||||||
gems: {
|
gems: {
|
||||||
amount: 16,
|
amount: 16,
|
||||||
uuid: receivingUser._id,
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -75,8 +75,29 @@ describe('Stripe Payments', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should error if user cannot get gems', async () => {
|
||||||
|
gift = undefined;
|
||||||
|
sinon.stub(user, 'canGetGems').returnsPromise().resolves(false);
|
||||||
|
|
||||||
|
await expect(stripePayments.checkout({
|
||||||
|
token,
|
||||||
|
user,
|
||||||
|
gift,
|
||||||
|
groupId,
|
||||||
|
email,
|
||||||
|
headers,
|
||||||
|
coupon,
|
||||||
|
}, stripe)).to.eventually.be.rejected.and.to.eql({
|
||||||
|
httpCode: 401,
|
||||||
|
message: i18n.t('groupPolicyCannotGetGems'),
|
||||||
|
name: 'NotAuthorized',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('should purchase gems', async () => {
|
it('should purchase gems', async () => {
|
||||||
gift = undefined;
|
gift = undefined;
|
||||||
|
sinon.stub(user, 'canGetGems').returnsPromise().resolves(true);
|
||||||
|
|
||||||
await stripePayments.checkout({
|
await stripePayments.checkout({
|
||||||
token,
|
token,
|
||||||
@@ -102,16 +123,18 @@ describe('Stripe Payments', () => {
|
|||||||
paymentMethod: 'Stripe',
|
paymentMethod: 'Stripe',
|
||||||
gift,
|
gift,
|
||||||
});
|
});
|
||||||
|
expect(user.canGetGems).to.be.calledOnce;
|
||||||
|
user.canGetGems.restore();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should gift gems', async () => {
|
it('should gift gems', async () => {
|
||||||
let receivingUser = new User();
|
let receivingUser = new User();
|
||||||
receivingUser.save();
|
await receivingUser.save();
|
||||||
gift = {
|
gift = {
|
||||||
type: 'gems',
|
type: 'gems',
|
||||||
|
uuid: receivingUser._id,
|
||||||
gems: {
|
gems: {
|
||||||
amount: 16,
|
amount: 16,
|
||||||
uuid: receivingUser._id,
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -125,7 +148,6 @@ describe('Stripe Payments', () => {
|
|||||||
coupon,
|
coupon,
|
||||||
}, stripe);
|
}, stripe);
|
||||||
|
|
||||||
gift.member = receivingUser;
|
|
||||||
expect(stripeChargeStub).to.be.calledOnce;
|
expect(stripeChargeStub).to.be.calledOnce;
|
||||||
expect(stripeChargeStub).to.be.calledWith({
|
expect(stripeChargeStub).to.be.calledWith({
|
||||||
amount: '400',
|
amount: '400',
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import Bluebird from 'bluebird';
|
import Bluebird from 'bluebird';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import { model as User } from '../../../../../website/server/models/user';
|
import { model as User } from '../../../../../website/server/models/user';
|
||||||
|
import { model as Group } from '../../../../../website/server/models/group';
|
||||||
import common from '../../../../../website/common';
|
import common from '../../../../../website/common';
|
||||||
|
|
||||||
describe('User Model', () => {
|
describe('User Model', () => {
|
||||||
@@ -179,6 +180,75 @@ describe('User Model', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
context('canGetGems', () => {
|
||||||
|
let user;
|
||||||
|
let group;
|
||||||
|
beforeEach(() => {
|
||||||
|
user = new User();
|
||||||
|
let leader = new User();
|
||||||
|
group = new Group({
|
||||||
|
name: 'test',
|
||||||
|
type: 'guild',
|
||||||
|
privacy: 'private',
|
||||||
|
leader: leader._id,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns true if user is not subscribed', async () => {
|
||||||
|
expect(await user.canGetGems()).to.equal(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns true if user is not subscribed with a group plan', async () => {
|
||||||
|
user.purchased.plan.customerId = 123;
|
||||||
|
expect(await user.canGetGems()).to.equal(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns true if user is subscribed with a group plan', async () => {
|
||||||
|
user.purchased.plan.customerId = 'group-plan';
|
||||||
|
expect(await user.canGetGems()).to.equal(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns true if user is part of a group', async () => {
|
||||||
|
user.guilds.push(group._id);
|
||||||
|
expect(await user.canGetGems()).to.equal(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns true if user is part of a group with a subscription', async () => {
|
||||||
|
user.guilds.push(group._id);
|
||||||
|
user.purchased.plan.customerId = 'group-plan';
|
||||||
|
group.purchased.plan.customerId = 123;
|
||||||
|
await group.save();
|
||||||
|
expect(await user.canGetGems()).to.equal(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns true if leader is part of a group with a subscription and canGetGems: false', async () => {
|
||||||
|
user.guilds.push(group._id);
|
||||||
|
user.purchased.plan.customerId = 'group-plan';
|
||||||
|
group.purchased.plan.customerId = 123;
|
||||||
|
group.leader = user._id;
|
||||||
|
group.leaderOnly.getGems = true;
|
||||||
|
await group.save();
|
||||||
|
expect(await user.canGetGems()).to.equal(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns true if user is part of a group with no subscription but canGetGems: false', async () => {
|
||||||
|
user.guilds.push(group._id);
|
||||||
|
user.purchased.plan.customerId = 'group-plan';
|
||||||
|
group.leaderOnly.getGems = true;
|
||||||
|
await group.save();
|
||||||
|
expect(await user.canGetGems()).to.equal(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns false if user is part of a group with a subscription and canGetGems: false', async () => {
|
||||||
|
user.guilds.push(group._id);
|
||||||
|
user.purchased.plan.customerId = 'group-plan';
|
||||||
|
group.purchased.plan.customerId = 123;
|
||||||
|
group.leaderOnly.getGems = true;
|
||||||
|
await group.save();
|
||||||
|
expect(await user.canGetGems()).to.equal(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
context('hasNotCancelled', () => {
|
context('hasNotCancelled', () => {
|
||||||
let user;
|
let user;
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
|||||||
@@ -298,5 +298,6 @@
|
|||||||
"managerMarker": " - Manager",
|
"managerMarker": " - Manager",
|
||||||
"joinedGuild": "Joined a Guild",
|
"joinedGuild": "Joined a Guild",
|
||||||
"joinedGuildText": "Ventured into the social side of Habitica by joining 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."
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,6 +30,11 @@ module.exports = function purchase (user, req = {}, analytics) {
|
|||||||
let convCap = planGemLimits.convCap;
|
let convCap = planGemLimits.convCap;
|
||||||
convCap += user.purchased.plan.consecutive.gemCapExtra;
|
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) {
|
if (!user.purchased || !user.purchased.plan || !user.purchased.plan.customerId) {
|
||||||
throw new NotAuthorized(i18n.t('mustSubscribeToPurchaseGems', req.language));
|
throw new NotAuthorized(i18n.t('mustSubscribeToPurchaseGems', req.language));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import {
|
|||||||
sendTxn as txnEmail,
|
sendTxn as txnEmail,
|
||||||
} from '../../libs/email';
|
} from '../../libs/email';
|
||||||
import nconf from 'nconf';
|
import nconf from 'nconf';
|
||||||
|
import get from 'lodash/get';
|
||||||
|
|
||||||
const TECH_ASSISTANCE_EMAIL = nconf.get('EMAILS:TECH_ASSISTANCE_EMAIL');
|
const TECH_ASSISTANCE_EMAIL = nconf.get('EMAILS:TECH_ASSISTANCE_EMAIL');
|
||||||
|
|
||||||
@@ -1227,7 +1228,18 @@ api.purchase = {
|
|||||||
url: '/user/purchase/:type/:key',
|
url: '/user/purchase/:type/:key',
|
||||||
async handler (req, res) {
|
async handler (req, res) {
|
||||||
let user = res.locals.user;
|
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();
|
await user.save();
|
||||||
res.respond(200, ...purchaseRes);
|
res.respond(200, ...purchaseRes);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ api.checkout = {
|
|||||||
let gift = req.query.gift ? JSON.parse(req.query.gift) : undefined;
|
let gift = req.query.gift ? JSON.parse(req.query.gift) : undefined;
|
||||||
req.session.gift = req.query.gift;
|
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) {
|
if (req.query.noRedirect) {
|
||||||
res.respond(200);
|
res.respond(200);
|
||||||
|
|||||||
@@ -97,6 +97,8 @@ api.checkout = async function checkout (options = {}) {
|
|||||||
let amount = 5;
|
let amount = 5;
|
||||||
|
|
||||||
if (gift) {
|
if (gift) {
|
||||||
|
gift.member = await User.findById(gift.uuid).exec();
|
||||||
|
|
||||||
if (gift.type === this.constants.GIFT_TYPE_GEMS) {
|
if (gift.type === this.constants.GIFT_TYPE_GEMS) {
|
||||||
if (gift.gems.amount <= 0) {
|
if (gift.gems.amount <= 0) {
|
||||||
throw new BadRequest(i18n.t('badAmountOfGemsToPurchase'));
|
throw new BadRequest(i18n.t('badAmountOfGemsToPurchase'));
|
||||||
@@ -107,6 +109,12 @@ api.checkout = async function checkout (options = {}) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!gift || gift.type === this.constants.GIFT_TYPE_GEMS) {
|
||||||
|
const receiver = gift ? gift.member : user;
|
||||||
|
const receiverCanGetGems = await receiver.canGetGems();
|
||||||
|
if (!receiverCanGetGems) throw new NotAuthorized(i18n.t('groupPolicyCannotGetGems', receiver.preferences.language));
|
||||||
|
}
|
||||||
|
|
||||||
await this.setOrderReferenceDetails({
|
await this.setOrderReferenceDetails({
|
||||||
AmazonOrderReferenceId: orderReferenceId,
|
AmazonOrderReferenceId: orderReferenceId,
|
||||||
OrderReferenceAttributes: {
|
OrderReferenceAttributes: {
|
||||||
|
|||||||
@@ -21,6 +21,9 @@ api.constants = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
api.verifyGemPurchase = async function verifyGemPurchase (user, receipt, headers) {
|
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();
|
await iap.setup();
|
||||||
let appleRes = await iap.validate(iap.APPLE, receipt);
|
let appleRes = await iap.validate(iap.APPLE, receipt);
|
||||||
let isValidated = iap.isValidated(appleRes);
|
let isValidated = iap.isValidated(appleRes);
|
||||||
|
|||||||
@@ -20,6 +20,9 @@ api.constants = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
api.verifyGemPurchase = async function verifyGemPurchase (user, receipt, signature, headers) {
|
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();
|
await iap.setup();
|
||||||
|
|
||||||
let testObj = {
|
let testObj = {
|
||||||
|
|||||||
@@ -70,11 +70,15 @@ api.paypalBillingAgreementCancel = Bluebird.promisify(paypal.billingAgreement.ca
|
|||||||
api.ipnVerifyAsync = Bluebird.promisify(ipn.verify, {context: ipn});
|
api.ipnVerifyAsync = Bluebird.promisify(ipn.verify, {context: ipn});
|
||||||
|
|
||||||
api.checkout = async function checkout (options = {}) {
|
api.checkout = async function checkout (options = {}) {
|
||||||
let {gift} = options;
|
let {gift, user} = options;
|
||||||
|
|
||||||
let amount = 5.00;
|
let amount = 5.00;
|
||||||
let description = 'Habitica Gems';
|
let description = 'Habitica Gems';
|
||||||
|
|
||||||
if (gift) {
|
if (gift) {
|
||||||
|
const member = await User.findById(gift.uuid).exec();
|
||||||
|
gift.member = member;
|
||||||
|
|
||||||
if (gift.type === 'gems') {
|
if (gift.type === 'gems') {
|
||||||
if (gift.gems.amount <= 0) {
|
if (gift.gems.amount <= 0) {
|
||||||
throw new BadRequest(i18n.t('badAmountOfGemsToPurchase'));
|
throw new BadRequest(i18n.t('badAmountOfGemsToPurchase'));
|
||||||
@@ -87,6 +91,14 @@ api.checkout = async function checkout (options = {}) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (!gift || gift.type === 'gems') {
|
||||||
|
const receiver = gift ? gift.member : user;
|
||||||
|
const receiverCanGetGems = await receiver.canGetGems();
|
||||||
|
if (!receiverCanGetGems) throw new NotAuthorized(shared.i18n.t('groupPolicyCannotGetGems', receiver.preferences.language));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
let createPayment = {
|
let createPayment = {
|
||||||
intent: 'sale',
|
intent: 'sale',
|
||||||
payer: { payment_method: this.constants.PAYMENT_METHOD },
|
payer: { payment_method: this.constants.PAYMENT_METHOD },
|
||||||
|
|||||||
@@ -76,6 +76,11 @@ api.checkout = async function checkout (options, stripeInc) {
|
|||||||
|
|
||||||
if (!token) throw new BadRequest('Missing req.body.id');
|
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) {
|
||||||
if (sub.discount) {
|
if (sub.discount) {
|
||||||
if (!coupon) throw new BadRequest(shared.i18n.t('couponCodeRequired'));
|
if (!coupon) throw new BadRequest(shared.i18n.t('couponCodeRequired'));
|
||||||
@@ -114,6 +119,12 @@ api.checkout = async function checkout (options, stripeInc) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!gift || gift.type === 'gems') {
|
||||||
|
const receiver = gift ? gift.member : user;
|
||||||
|
const receiverCanGetGems = await receiver.canGetGems();
|
||||||
|
if (!receiverCanGetGems) throw new NotAuthorized(shared.i18n.t('groupPolicyCannotGetGems', receiver.preferences.language));
|
||||||
|
}
|
||||||
|
|
||||||
response = await stripeApi.charges.create({
|
response = await stripeApi.charges.create({
|
||||||
amount,
|
amount,
|
||||||
currency: 'usd',
|
currency: 'usd',
|
||||||
@@ -141,8 +152,6 @@ api.checkout = async function checkout (options, stripeInc) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (gift) {
|
if (gift) {
|
||||||
let member = await User.findById(gift.uuid).exec();
|
|
||||||
gift.member = member;
|
|
||||||
if (gift.type === 'subscription') method = 'createSubscription';
|
if (gift.type === 'subscription') method = 'createSubscription';
|
||||||
data.paymentMethod = 'Gift';
|
data.paymentMethod = 'Gift';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -283,7 +283,15 @@ schema.methods.closeChal = async function closeChal (broken = {}) {
|
|||||||
// Award prize to winner and notify
|
// Award prize to winner and notify
|
||||||
if (winner) {
|
if (winner) {
|
||||||
winner.achievements.challenges.push(challenge.name);
|
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');
|
winner.addNotification('WON_CHALLENGE');
|
||||||
|
|
||||||
|
|||||||
@@ -76,6 +76,8 @@ export let schema = new Schema({
|
|||||||
leaderOnly: { // restrict group actions to leader (members can't do them)
|
leaderOnly: { // restrict group actions to leader (members can't do them)
|
||||||
challenges: {type: Boolean, default: false, required: true},
|
challenges: {type: Boolean, default: false, required: true},
|
||||||
// invites: {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},
|
memberCount: {type: Number, default: 1},
|
||||||
challengeCount: {type: Number, default: 0},
|
challengeCount: {type: Number, default: 0},
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import Bluebird from 'bluebird';
|
|||||||
import {
|
import {
|
||||||
chatDefaults,
|
chatDefaults,
|
||||||
TAVERN_ID,
|
TAVERN_ID,
|
||||||
|
model as Group,
|
||||||
} from '../group';
|
} from '../group';
|
||||||
import { defaults, map, flatten, flow, compact, uniq, partialRight } from 'lodash';
|
import { defaults, map, flatten, flow, compact, uniq, partialRight } from 'lodash';
|
||||||
import { model as UserNotification } from '../userNotification';
|
import { model as UserNotification } from '../userNotification';
|
||||||
@@ -190,7 +191,7 @@ schema.methods.cancelSubscription = async function cancelSubscription (options =
|
|||||||
return await payments.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),
|
// 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
|
// cron can be triggered twice in one day, so we check for that and use
|
||||||
// both timezones to work out if cron should run.
|
// both timezones to work out if cron should run.
|
||||||
@@ -271,3 +272,28 @@ schema.methods.daysUserHasMissed = function daysUserHasMissed (now, req = {}) {
|
|||||||
|
|
||||||
return {daysMissed, timezoneOffsetFromUserPrefs};
|
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 = async 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();
|
||||||
|
|
||||||
|
const groups = await Group
|
||||||
|
.find({
|
||||||
|
_id: {$in: userGroups},
|
||||||
|
})
|
||||||
|
.select('leaderOnly leader purchased')
|
||||||
|
.exec();
|
||||||
|
|
||||||
|
return groups.every(g => {
|
||||||
|
return !g.isSubscribed() || g.leader === user._id || g.leaderOnly.getGems !== true;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user