Skip to content

Commit

Permalink
Fall Festival Gem Promo (#138)
Browse files Browse the repository at this point in the history
* content: add gems blocks

* gemsBlocks: include ios and android identifiers

* wip: promo code

* split common constants into multiple files

* add second promo part

* geCurrentEvent, refactor promo

* fix lint

* fix exports, use world state api

* start adding world state tests

* remove console.log

* use gems block for purchases

* remove comments

* fix most unit tests

* restore comment

* fix lint

* prevent apple/google gift tests from breaking other tests when stub is not reset

* fix unit tests, clarify tests names

* iap: use gift object when gifting gems

* allow gift object with less data

* fix iap tests, remove findById stubs

* iap: require less data from the mobile apps

* apply discounts

* add missing worldState file

* fix lint

* add test event

* start removing 20 gems option for web

* start adding support for all gems packages on web

* fix unit tests for apple, stripe and google

* amazon: support all gems blocks

* paypal: support all gems blocks

* fix payments unit tests, add tests for getGemsBlock

* web: add gems plans with discounts, update stripe

* fix amazon and paypal clients, payments success modals

* amazon pay: disabled state

* update icons, start abstracting payments buttons

* begin redesign

* redesign gems modal

* fix buttons

* fix hover color for gems modal close icon

* add key to world state current event

* extend test event length

* implement gems modals designs

* early test fall2020

* fix header banner position

* add missing files

* use iso 8601 for dates, minor ui fixes

* fix time zones

* events: fix ISO8601 format

* fix css indentation

* start abstracting banners

* refactor payments buttons

* test spooky, fix group plans box

* implement gems promo banners, refactor banners, fixes

* fix lint

* fix dates

* remove unused i18n strings

* fix stripe integration test

* fix world state integration tests

* the current active event

* add missing unit tests

* add storybook story for payments buttons component

* fix typo

* fix(stripe): correct label when gifting subscriptions
  • Loading branch information
paglias committed Sep 21, 2020
1 parent 82e6d54 commit 83aca20
Show file tree
Hide file tree
Showing 95 changed files with 1,755 additions and 1,078 deletions.
32 changes: 27 additions & 5 deletions test/api/unit/libs/payments/amazon/checkout.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,22 @@ import { model as User } from '../../../../../../website/server/models/user';
import amzLib from '../../../../../../website/server/libs/payments/amazon';
import payments from '../../../../../../website/server/libs/payments/payments';
import common from '../../../../../../website/common';
import apiError from '../../../../../../website/server/libs/apiError';

const { i18n } = common;

describe('Amazon Payments - Checkout', () => {
const subKey = 'basic_3mo';
let user; let orderReferenceId; let
headers;
headers; const gemsBlockKey = '21gems'; const gemsBlock = common.content.gems[gemsBlockKey];
let setOrderReferenceDetailsSpy;
let confirmOrderReferenceSpy;
let authorizeSpy;
let closeOrderReferenceSpy;

let paymentBuyGemsStub;
let paymentCreateSubscritionStub;
let amount = 5;
let amount = gemsBlock.price / 100;

function expectOrderReferenceSpy () {
expect(setOrderReferenceDetailsSpy).to.be.calledOnce;
Expand Down Expand Up @@ -107,13 +108,20 @@ describe('Amazon Payments - Checkout', () => {
paymentMethod,
headers,
};
if (gift) expectedArgs.gift = gift;
if (gift) {
expectedArgs.gift = gift;
expectedArgs.gemsBlock = undefined;
} else {
expectedArgs.gemsBlock = gemsBlock;
}
expect(paymentBuyGemsStub).to.be.calledWith(expectedArgs);
}

it('should purchase gems', async () => {
sinon.stub(user, 'canGetGems').resolves(true);
await amzLib.checkout({ user, orderReferenceId, headers });
await amzLib.checkout({
user, orderReferenceId, headers, gemsBlock: gemsBlockKey,
});

expectBuyGemsStub(amzLib.constants.PAYMENT_METHOD);
expectAmazonStubs();
Expand Down Expand Up @@ -144,7 +152,9 @@ describe('Amazon Payments - Checkout', () => {

it('should error if user cannot get gems gems', async () => {
sinon.stub(user, 'canGetGems').resolves(false);
await expect(amzLib.checkout({ user, orderReferenceId, headers }))
await expect(amzLib.checkout({
user, orderReferenceId, headers, gemsBlock: gemsBlockKey,
}))
.to.eventually.be.rejected.and.to.eql({
httpCode: 401,
message: i18n.t('groupPolicyCannotGetGems'),
Expand All @@ -153,6 +163,17 @@ describe('Amazon Payments - Checkout', () => {
user.canGetGems.restore();
});

it('should error if gems block is not valid', async () => {
await expect(amzLib.checkout({
user, orderReferenceId, headers, gemsBlock: 'invalid',
}))
.to.eventually.be.rejected.and.to.eql({
httpCode: 400,
message: apiError('invalidGemsBlock'),
name: 'BadRequest',
});
});

it('should gift gems', async () => {
const receivingUser = new User();
await receivingUser.save();
Expand Down Expand Up @@ -195,6 +216,7 @@ describe('Amazon Payments - Checkout', () => {
paymentMethod: amzLib.constants.PAYMENT_METHOD_GIFT,
headers,
gift,
gemsBlock: undefined,
});
expectAmazonStubs();
});
Expand Down
29 changes: 16 additions & 13 deletions test/api/unit/libs/payments/apple.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import applePayments from '../../../../../website/server/libs/payments/apple';
import iap from '../../../../../website/server/libs/inAppPurchases';
import { model as User } from '../../../../../website/server/models/user';
import common from '../../../../../website/common';
import { mockFindById, restoreFindById } from '../../../../helpers/mongoose.helper';

const { i18n } = common;

Expand Down Expand Up @@ -84,7 +83,7 @@ describe('Apple Payments', () => {
user.canGetGems.restore();
});

it('errors if amount does not exist', async () => {
it('errors if gemsBlock does not exist', async () => {
sinon.stub(user, 'canGetGems').resolves(true);
iapGetPurchaseDataStub.restore();
iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData')
Expand All @@ -106,23 +105,23 @@ describe('Apple Payments', () => {
const gemsCanPurchase = [
{
productId: 'com.habitrpg.ios.Habitica.4gems',
amount: 1,
gemsBlock: '4gems',
},
{
productId: 'com.habitrpg.ios.Habitica.20gems',
amount: 5.25,
gemsBlock: '21gems',
},
{
productId: 'com.habitrpg.ios.Habitica.21gems',
amount: 5.25,
gemsBlock: '21gems',
},
{
productId: 'com.habitrpg.ios.Habitica.42gems',
amount: 10.5,
gemsBlock: '42gems',
},
{
productId: 'com.habitrpg.ios.Habitica.84gems',
amount: 21,
gemsBlock: '84gems',
},
];

Expand All @@ -149,8 +148,9 @@ describe('Apple Payments', () => {
expect(paymentBuyGemsStub).to.be.calledWith({
user,
paymentMethod: applePayments.constants.PAYMENT_METHOD_APPLE,
amount: gemTest.amount,
gemsBlock: common.content.gems[gemTest.gemsBlock],
headers,
gift: undefined,
});
expect(user.canGetGems).to.be.calledOnce;
user.canGetGems.restore();
Expand All @@ -161,8 +161,6 @@ describe('Apple Payments', () => {
const receivingUser = new User();
await receivingUser.save();

mockFindById(receivingUser);

iapGetPurchaseDataStub.restore();
iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData')
.returns([{
Expand All @@ -184,12 +182,17 @@ describe('Apple Payments', () => {

expect(paymentBuyGemsStub).to.be.calledOnce;
expect(paymentBuyGemsStub).to.be.calledWith({
user: receivingUser,
user,
paymentMethod: applePayments.constants.PAYMENT_METHOD_APPLE,
amount: gemsCanPurchase[0].amount,
headers,
gift: {
type: 'gems',
gems: { amount: 4 },
member: sinon.match({ _id: receivingUser._id }),
uuid: receivingUser._id,
},
gemsBlock: common.content.gems['4gems'],
});
restoreFindById();
});
});

Expand Down
14 changes: 14 additions & 0 deletions test/api/unit/libs/payments/gems.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import common from '../../../../../website/common';
import { getGemsBlock } from '../../../../../website/server/libs/payments/gems';

describe('payments/gems', () => {
describe('#getGemsBlock', () => {
it('throws an error if the gem block key is invalid', () => {
expect(() => getGemsBlock('invalid')).to.throw;
});

it('returns the gem block for the given key', () => {
expect(getGemsBlock('21gems')).to.equal(common.content.gems['21gems']);
});
});
});
19 changes: 11 additions & 8 deletions test/api/unit/libs/payments/google.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import googlePayments from '../../../../../website/server/libs/payments/google';
import iap from '../../../../../website/server/libs/inAppPurchases';
import { model as User } from '../../../../../website/server/models/user';
import common from '../../../../../website/common';
import { mockFindById, restoreFindById } from '../../../../helpers/mongoose.helper';

const { i18n } = common;

Expand All @@ -14,7 +13,7 @@ describe('Google Payments', () => {

describe('verifyGemPurchase', () => {
let sku; let user; let token; let receipt; let signature; let
headers;
headers; const gemsBlock = common.content.gems['21gems'];
let iapSetupStub; let iapValidateStub; let iapIsValidatedStub; let
paymentBuyGemsStub;

Expand Down Expand Up @@ -103,8 +102,9 @@ describe('Google Payments', () => {
expect(paymentBuyGemsStub).to.be.calledWith({
user,
paymentMethod: googlePayments.constants.PAYMENT_METHOD_GOOGLE,
amount: 5.25,
gemsBlock,
headers,
gift: undefined,
});
expect(user.canGetGems).to.be.calledOnce;
user.canGetGems.restore();
Expand All @@ -114,8 +114,6 @@ describe('Google Payments', () => {
const receivingUser = new User();
await receivingUser.save();

mockFindById(receivingUser);

const gift = { uuid: receivingUser._id };
await googlePayments.verifyGemPurchase({
user, gift, receipt, signature, headers,
Expand All @@ -132,12 +130,17 @@ describe('Google Payments', () => {

expect(paymentBuyGemsStub).to.be.calledOnce;
expect(paymentBuyGemsStub).to.be.calledWith({
user: receivingUser,
user,
paymentMethod: googlePayments.constants.PAYMENT_METHOD_GOOGLE,
amount: 5.25,
gemsBlock,
headers,
gift: {
type: 'gems',
gems: { amount: 21 },
member: sinon.match({ _id: receivingUser._id }),
uuid: receivingUser._id,
},
});
restoreFindById();
});
});

Expand Down
52 changes: 42 additions & 10 deletions test/api/unit/libs/payments/payments.test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import moment from 'moment';

import * as sender from '../../../../../website/server/libs/email';
import common from '../../../../../website/common';
import api from '../../../../../website/server/libs/payments/payments';
import * as analytics from '../../../../../website/server/libs/analyticsService';
import * as notifications from '../../../../../website/server/libs/pushNotifications';
Expand All @@ -9,6 +10,7 @@ import { translate as t } from '../../../../helpers/api-integration/v3';
import {
generateGroup,
} from '../../../../helpers/api-unit.helper';
import * as worldState from '../../../../../website/server/libs/worldState';

describe('payments/index', () => {
let user; let group; let data; let
Expand Down Expand Up @@ -555,6 +557,7 @@ describe('payments/index', () => {
beforeEach(() => {
data = {
user,
gemsBlock: common.content.gems['21gems'],
paymentMethod: 'payment',
headers: {
'x-client': 'habitica-web',
Expand All @@ -564,27 +567,56 @@ describe('payments/index', () => {
});

context('Self Purchase', () => {
it('amount property defaults to 5', async () => {
expect(user.balance).to.eql(0);

it('sends a donation email', async () => {
await api.buyGems(data);

expect(user.balance).to.eql(5);
expect(sender.sendTxn).to.be.calledOnce;
expect(sender.sendTxn).to.be.calledWith(data.user, 'donation');
});
});

context('No Active Promotion', () => {
beforeEach(() => {
sinon.stub(worldState, 'getCurrentEvent').returns(null);
});

afterEach(() => {
worldState.getCurrentEvent.restore();
});

it('can set amount that is purchased', async () => {
data.amount = 13;
it('does not apply a discount', async () => {
const balanceBefore = user.balance;

await api.buyGems(data);

expect(user.balance).to.eql(13);
const balanceAfter = user.balance;
const balanceDiff = balanceAfter - balanceBefore;

expect(balanceDiff * 4).to.eql(21);
});
});

context('Active Promotion', () => {
beforeEach(() => {
sinon.stub(worldState, 'getCurrentEvent').returns({
...common.content.events.fall2020,
event: 'fall2020',
});
});

afterEach(() => {
worldState.getCurrentEvent.restore();
});

it('applies a discount', async () => {
const balanceBefore = user.balance;

it('sends a donation email', async () => {
await api.buyGems(data);

expect(sender.sendTxn).to.be.calledOnce;
expect(sender.sendTxn).to.be.calledWith(data.user, 'donation');
const balanceAfter = user.balance;
const balanceDiff = balanceAfter - balanceBefore;

expect(balanceDiff * 4).to.eql(30);
});
});

Expand Down
7 changes: 5 additions & 2 deletions test/api/unit/libs/payments/paypal/checkout-success.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@
import paypalPayments from '../../../../../../website/server/libs/payments/paypal';
import payments from '../../../../../../website/server/libs/payments/payments';
import { model as User } from '../../../../../../website/server/models/user';
import common from '../../../../../../website/common';

describe('checkout success', () => {
describe('paypal - checkout success', () => {
const subKey = 'basic_3mo';
let user; let gift; let customerId; let
paymentId;
const gemsBlockKey = '21gems'; const gemsBlock = common.content.gems[gemsBlockKey];
let paypalPaymentExecuteStub; let paymentBuyGemsStub; let
paymentsCreateSubscritionStub;

Expand All @@ -28,7 +30,7 @@ describe('checkout success', () => {

it('purchases gems', async () => {
await paypalPayments.checkoutSuccess({
user, gift, paymentId, customerId,
user, gift, paymentId, customerId, gemsBlock: gemsBlockKey,
});

expect(paypalPaymentExecuteStub).to.be.calledOnce;
Expand All @@ -38,6 +40,7 @@ describe('checkout success', () => {
user,
customerId,
paymentMethod: 'Paypal',
gemsBlock,
});
});

Expand Down
Loading

0 comments on commit 83aca20

Please sign in to comment.