From 83aca20ce5dc13ea38a1543ac46a8295853c62a2 Mon Sep 17 00:00:00 2001 From: Matteo Pagliazzi Date: Mon, 21 Sep 2020 16:22:13 +0200 Subject: [PATCH] Fall Festival Gem Promo (#138) * 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 --- .../libs/payments/amazon/checkout.test.js | 32 +- test/api/unit/libs/payments/apple.test.js | 29 +- test/api/unit/libs/payments/gems.test.js | 14 + test/api/unit/libs/payments/google.test.js | 19 +- test/api/unit/libs/payments/payments.test.js | 64 +- .../payments/paypal/checkout-success.test.js | 7 +- .../libs/payments/paypal/checkout.test.js | 30 +- .../api/unit/libs/payments/paypal/ipn.test.js | 2 +- .../payments/paypal/subscribe-cancel.test.js | 2 +- .../payments/paypal/subscribe-success.test.js | 2 +- .../libs/payments/paypal/subscribe.test.js | 2 +- ...lculateSubscriptionTerminationDate.test.js | 2 +- .../stripe/cancel-subscription.test.js | 2 +- .../stripe/checkout-subscription.test.js | 2 +- .../libs/payments/stripe/checkout.test.js | 31 +- .../payments/stripe/edit-subscription.test.js | 2 +- .../POST-payments_stripe_checkout.test.js | 5 +- .../world-state/GET-world-state.test.js | 39 ++ website/client/src/app.vue | 104 +--- website/client/src/assets/images/gemfall.png | Bin 22892 -> 0 bytes .../gems/fall-confetti-left/confetti.png | Bin 0 -> 2514 bytes .../gems/fall-confetti-left/confetti@2x.png | Bin 0 -> 6308 bytes .../gems/fall-confetti-left/confetti@3x.png | Bin 0 -> 10616 bytes .../gems/fall-confetti-right/confetti.png | Bin 0 -> 2553 bytes .../gems/fall-confetti-right/confetti@2x.png | Bin 0 -> 6307 bytes .../gems/fall-confetti-right/confetti@3x.png | Bin 0 -> 10628 bytes .../assets/images/gems/fall-header-bg@2x.png | Bin 0 -> 20128 bytes .../src/assets/images/gems/fall-header.png | Bin 0 -> 10012 bytes .../src/assets/images/gems/fall-header@2x.png | Bin 0 -> 23294 bytes .../src/assets/images/gems/fall-header@3x.png | Bin 0 -> 40557 bytes .../src/assets/images/gems/fall-text/text.png | Bin 0 -> 6802 bytes .../assets/images/gems/fall-text/text@2x.png | Bin 0 -> 12229 bytes .../assets/images/gems/fall-text/text@3x.png | Bin 0 -> 17778 bytes .../gems/spooky-confetti-left/confetti.png | Bin 0 -> 2669 bytes .../gems/spooky-confetti-left/confetti@2x.png | Bin 0 -> 6005 bytes .../gems/spooky-confetti-left/confetti@3x.png | Bin 0 -> 10049 bytes .../gems/spooky-confetti-right/confetti.png | Bin 0 -> 2668 bytes .../spooky-confetti-right/confetti@2x.png | Bin 0 -> 6051 bytes .../spooky-confetti-right/confetti@3x.png | Bin 0 -> 10099 bytes .../images/gems/spooky-header-bg@2x.png | Bin 0 -> 16997 bytes .../src/assets/images/gems/spooky-header.png | Bin 0 -> 7163 bytes .../assets/images/gems/spooky-header@2x.png | Bin 0 -> 10845 bytes .../assets/images/gems/spooky-header@3x.png | Bin 0 -> 13327 bytes .../assets/images/gems/spooky-text/text.png | Bin 0 -> 6929 bytes .../images/gems/spooky-text/text@2x.png | Bin 0 -> 9624 bytes .../images/gems/spooky-text/text@3x.png | Bin 0 -> 10718 bytes .../assets/images/gems/support-habitica.png | Bin 0 -> 6022 bytes .../images/gems/support-habitica@2x.png | Bin 0 -> 13248 bytes .../images/gems/support-habitica@3x.png | Bin 0 -> 21151 bytes website/client/src/assets/scss/icon.scss | 1 - website/client/src/assets/scss/index.scss | 1 - website/client/src/assets/scss/modal.scss | 2 +- website/client/src/assets/scss/payments.scss | 44 -- website/client/src/assets/scss/variables.scss | 1 - .../src/components/achievements/levelUp.vue | 6 +- .../group-plans/createGroupModalPages.vue | 29 +- .../src/components/groups/groupPlan.vue | 49 +- .../src/components/header/banners/base.vue | 122 ++++ .../header/banners/damagePaused.vue | 84 +++ .../components/header/banners/gemsPromo.vue | 132 +++++ website/client/src/components/header/menu.vue | 25 +- .../src/components/payments/amazonModal.vue | 16 +- .../{amazonButton.vue => buttons/amazon.vue} | 26 +- .../payments/buttons/list.stories.js | 49 ++ .../src/components/payments/buttons/list.vue | 127 ++++ .../src/components/payments/buyGemsModal.vue | 560 ++++++++++-------- .../src/components/payments/sendGemsModal.vue | 62 +- .../src/components/payments/successModal.vue | 2 +- .../settings/subscriptionOptions.vue | 68 +-- .../client/src/components/shops/buyModal.vue | 6 - .../client/src/components/ui/datepicker.vue | 12 +- website/client/src/mixins/payments.js | 44 +- website/client/src/pages/private-messages.vue | 9 +- website/common/locales/en/subscriber.json | 7 +- website/common/script/content/constants.js | 351 ----------- .../constants/animalColorAchievements.js | 46 ++ .../common/script/content/constants/events.js | 58 ++ .../common/script/content/constants/index.js | 34 ++ .../script/content/constants/itemList.js | 21 + .../constants/questSeriesAchievements.js | 53 ++ .../script/content/constants/seasonalSets.js | 171 ++++++ website/common/script/content/gems.js | 40 ++ website/common/script/content/index.js | 16 +- .../common/script/errors/apiErrorMessages.js | 1 + website/server/controllers/api-v3/world.js | 20 +- .../controllers/top-level/payments/amazon.js | 5 +- .../controllers/top-level/payments/paypal.js | 9 +- .../controllers/top-level/payments/stripe.js | 5 +- website/server/libs/payments/amazon.js | 10 +- website/server/libs/payments/apple.js | 28 +- website/server/libs/payments/gems.js | 28 +- website/server/libs/payments/google.js | 28 +- website/server/libs/payments/paypal.js | 13 +- .../server/libs/payments/stripe/checkout.js | 31 +- website/server/libs/worldState.js | 33 ++ 95 files changed, 1776 insertions(+), 1099 deletions(-) create mode 100644 test/api/unit/libs/payments/gems.test.js delete mode 100644 website/client/src/assets/images/gemfall.png create mode 100644 website/client/src/assets/images/gems/fall-confetti-left/confetti.png create mode 100644 website/client/src/assets/images/gems/fall-confetti-left/confetti@2x.png create mode 100644 website/client/src/assets/images/gems/fall-confetti-left/confetti@3x.png create mode 100644 website/client/src/assets/images/gems/fall-confetti-right/confetti.png create mode 100644 website/client/src/assets/images/gems/fall-confetti-right/confetti@2x.png create mode 100644 website/client/src/assets/images/gems/fall-confetti-right/confetti@3x.png create mode 100644 website/client/src/assets/images/gems/fall-header-bg@2x.png create mode 100644 website/client/src/assets/images/gems/fall-header.png create mode 100644 website/client/src/assets/images/gems/fall-header@2x.png create mode 100644 website/client/src/assets/images/gems/fall-header@3x.png create mode 100644 website/client/src/assets/images/gems/fall-text/text.png create mode 100644 website/client/src/assets/images/gems/fall-text/text@2x.png create mode 100644 website/client/src/assets/images/gems/fall-text/text@3x.png create mode 100644 website/client/src/assets/images/gems/spooky-confetti-left/confetti.png create mode 100644 website/client/src/assets/images/gems/spooky-confetti-left/confetti@2x.png create mode 100644 website/client/src/assets/images/gems/spooky-confetti-left/confetti@3x.png create mode 100644 website/client/src/assets/images/gems/spooky-confetti-right/confetti.png create mode 100644 website/client/src/assets/images/gems/spooky-confetti-right/confetti@2x.png create mode 100644 website/client/src/assets/images/gems/spooky-confetti-right/confetti@3x.png create mode 100644 website/client/src/assets/images/gems/spooky-header-bg@2x.png create mode 100644 website/client/src/assets/images/gems/spooky-header.png create mode 100644 website/client/src/assets/images/gems/spooky-header@2x.png create mode 100644 website/client/src/assets/images/gems/spooky-header@3x.png create mode 100644 website/client/src/assets/images/gems/spooky-text/text.png create mode 100644 website/client/src/assets/images/gems/spooky-text/text@2x.png create mode 100644 website/client/src/assets/images/gems/spooky-text/text@3x.png create mode 100644 website/client/src/assets/images/gems/support-habitica.png create mode 100644 website/client/src/assets/images/gems/support-habitica@2x.png create mode 100644 website/client/src/assets/images/gems/support-habitica@3x.png delete mode 100644 website/client/src/assets/scss/payments.scss create mode 100644 website/client/src/components/header/banners/base.vue create mode 100644 website/client/src/components/header/banners/damagePaused.vue create mode 100644 website/client/src/components/header/banners/gemsPromo.vue rename website/client/src/components/payments/{amazonButton.vue => buttons/amazon.vue} (88%) create mode 100644 website/client/src/components/payments/buttons/list.stories.js create mode 100644 website/client/src/components/payments/buttons/list.vue delete mode 100644 website/common/script/content/constants.js create mode 100644 website/common/script/content/constants/animalColorAchievements.js create mode 100644 website/common/script/content/constants/events.js create mode 100644 website/common/script/content/constants/index.js create mode 100644 website/common/script/content/constants/itemList.js create mode 100644 website/common/script/content/constants/questSeriesAchievements.js create mode 100644 website/common/script/content/constants/seasonalSets.js create mode 100644 website/common/script/content/gems.js create mode 100644 website/server/libs/worldState.js diff --git a/test/api/unit/libs/payments/amazon/checkout.test.js b/test/api/unit/libs/payments/amazon/checkout.test.js index ee4ab9cee7..1a29543e79 100644 --- a/test/api/unit/libs/payments/amazon/checkout.test.js +++ b/test/api/unit/libs/payments/amazon/checkout.test.js @@ -2,13 +2,14 @@ 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; @@ -16,7 +17,7 @@ describe('Amazon Payments - Checkout', () => { let paymentBuyGemsStub; let paymentCreateSubscritionStub; - let amount = 5; + let amount = gemsBlock.price / 100; function expectOrderReferenceSpy () { expect(setOrderReferenceDetailsSpy).to.be.calledOnce; @@ -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(); @@ -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'), @@ -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(); @@ -195,6 +216,7 @@ describe('Amazon Payments - Checkout', () => { paymentMethod: amzLib.constants.PAYMENT_METHOD_GIFT, headers, gift, + gemsBlock: undefined, }); expectAmazonStubs(); }); diff --git a/test/api/unit/libs/payments/apple.test.js b/test/api/unit/libs/payments/apple.test.js index 82fea35eec..437e83001d 100644 --- a/test/api/unit/libs/payments/apple.test.js +++ b/test/api/unit/libs/payments/apple.test.js @@ -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; @@ -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') @@ -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', }, ]; @@ -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(); @@ -161,8 +161,6 @@ describe('Apple Payments', () => { const receivingUser = new User(); await receivingUser.save(); - mockFindById(receivingUser); - iapGetPurchaseDataStub.restore(); iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData') .returns([{ @@ -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(); }); }); diff --git a/test/api/unit/libs/payments/gems.test.js b/test/api/unit/libs/payments/gems.test.js new file mode 100644 index 0000000000..29d632d5cc --- /dev/null +++ b/test/api/unit/libs/payments/gems.test.js @@ -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']); + }); + }); +}); diff --git a/test/api/unit/libs/payments/google.test.js b/test/api/unit/libs/payments/google.test.js index 7b290f1106..8396ba29a9 100644 --- a/test/api/unit/libs/payments/google.test.js +++ b/test/api/unit/libs/payments/google.test.js @@ -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; @@ -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; @@ -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(); @@ -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, @@ -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(); }); }); diff --git a/test/api/unit/libs/payments/payments.test.js b/test/api/unit/libs/payments/payments.test.js index 335deecb05..c641991fb9 100644 --- a/test/api/unit/libs/payments/payments.test.js +++ b/test/api/unit/libs/payments/payments.test.js @@ -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'; @@ -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 @@ -555,6 +557,7 @@ describe('payments/index', () => { beforeEach(() => { data = { user, + gemsBlock: common.content.gems['21gems'], paymentMethod: 'payment', headers: { 'x-client': 'habitica-web', @@ -564,22 +567,6 @@ describe('payments/index', () => { }); context('Self Purchase', () => { - it('amount property defaults to 5', async () => { - expect(user.balance).to.eql(0); - - await api.buyGems(data); - - expect(user.balance).to.eql(5); - }); - - it('can set amount that is purchased', async () => { - data.amount = 13; - - await api.buyGems(data); - - expect(user.balance).to.eql(13); - }); - it('sends a donation email', async () => { await api.buyGems(data); @@ -588,6 +575,51 @@ describe('payments/index', () => { }); }); + context('No Active Promotion', () => { + beforeEach(() => { + sinon.stub(worldState, 'getCurrentEvent').returns(null); + }); + + afterEach(() => { + worldState.getCurrentEvent.restore(); + }); + + it('does not apply a discount', async () => { + const balanceBefore = user.balance; + + await api.buyGems(data); + + 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; + + await api.buyGems(data); + + const balanceAfter = user.balance; + const balanceDiff = balanceAfter - balanceBefore; + + expect(balanceDiff * 4).to.eql(30); + }); + }); + context('Gift', () => { let recipient; diff --git a/test/api/unit/libs/payments/paypal/checkout-success.test.js b/test/api/unit/libs/payments/paypal/checkout-success.test.js index 3c896f4128..9951619ab0 100644 --- a/test/api/unit/libs/payments/paypal/checkout-success.test.js +++ b/test/api/unit/libs/payments/paypal/checkout-success.test.js @@ -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; @@ -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; @@ -38,6 +40,7 @@ describe('checkout success', () => { user, customerId, paymentMethod: 'Paypal', + gemsBlock, }); }); diff --git a/test/api/unit/libs/payments/paypal/checkout.test.js b/test/api/unit/libs/payments/paypal/checkout.test.js index b6173f6d8e..da8e57fe66 100644 --- a/test/api/unit/libs/payments/paypal/checkout.test.js +++ b/test/api/unit/libs/payments/paypal/checkout.test.js @@ -4,12 +4,14 @@ import nconf from 'nconf'; import paypalPayments from '../../../../../../website/server/libs/payments/paypal'; import { model as User } from '../../../../../../website/server/models/user'; import common from '../../../../../../website/common'; +import apiError from '../../../../../../website/server/libs/apiError'; const BASE_URL = nconf.get('BASE_URL'); const { i18n } = common; -describe('checkout', () => { +describe('paypal - checkout', () => { const subKey = 'basic_3mo'; + const gemsBlockKey = '21gems'; let paypalPaymentCreateStub; let approvalHerf; @@ -53,10 +55,10 @@ describe('checkout', () => { }); it('creates a link for gem purchases', async () => { - const link = await paypalPayments.checkout({ user: new User() }); + const link = await paypalPayments.checkout({ user: new User(), gemsBlock: gemsBlockKey }); expect(paypalPaymentCreateStub).to.be.calledOnce; - expect(paypalPaymentCreateStub).to.be.calledWith(getPaypalCreateOptions('Habitica Gems', 5.00)); + expect(paypalPaymentCreateStub).to.be.calledWith(getPaypalCreateOptions('Habitica Gems', 4.99)); expect(link).to.eql(approvalHerf); }); @@ -83,11 +85,23 @@ describe('checkout', () => { const user = new User(); sinon.stub(user, 'canGetGems').resolves(false); - await expect(paypalPayments.checkout({ user })).to.eventually.be.rejected.and.to.eql({ - httpCode: 401, - message: i18n.t('groupPolicyCannotGetGems'), - name: 'NotAuthorized', - }); + await expect(paypalPayments.checkout({ user, gemsBlock: gemsBlockKey })) + .to.eventually.be.rejected.and.to.eql({ + httpCode: 401, + message: i18n.t('groupPolicyCannotGetGems'), + name: 'NotAuthorized', + }); + }); + + it('should error if the gems block is not valid', async () => { + const user = new User(); + + await expect(paypalPayments.checkout({ user, gemsBlock: 'invalid' })) + .to.eventually.be.rejected.and.to.eql({ + httpCode: 400, + message: apiError('invalidGemsBlock'), + name: 'BadRequest', + }); }); it('creates a link for gifting gems', async () => { diff --git a/test/api/unit/libs/payments/paypal/ipn.test.js b/test/api/unit/libs/payments/paypal/ipn.test.js index 31769ee593..158a8d8fd7 100644 --- a/test/api/unit/libs/payments/paypal/ipn.test.js +++ b/test/api/unit/libs/payments/paypal/ipn.test.js @@ -6,7 +6,7 @@ import { } from '../../../../../helpers/api-unit.helper'; import { model as User } from '../../../../../../website/server/models/user'; -describe('ipn', () => { +describe('paypal - ipn', () => { const subKey = 'basic_3mo'; let user; let group; let txn_type; let userPaymentId; let groupPaymentId; diff --git a/test/api/unit/libs/payments/paypal/subscribe-cancel.test.js b/test/api/unit/libs/payments/paypal/subscribe-cancel.test.js index f9d2f86979..30618252d1 100644 --- a/test/api/unit/libs/payments/paypal/subscribe-cancel.test.js +++ b/test/api/unit/libs/payments/paypal/subscribe-cancel.test.js @@ -10,7 +10,7 @@ import { createNonLeaderGroupMember } from '../paymentHelpers'; const { i18n } = common; -describe('subscribeCancel', () => { +describe('paypal - subscribeCancel', () => { const subKey = 'basic_3mo'; let user; let group; let groupId; let customerId; let groupCustomerId; let nextBillingDate; diff --git a/test/api/unit/libs/payments/paypal/subscribe-success.test.js b/test/api/unit/libs/payments/paypal/subscribe-success.test.js index 80a09ab7cb..87e7342e03 100644 --- a/test/api/unit/libs/payments/paypal/subscribe-success.test.js +++ b/test/api/unit/libs/payments/paypal/subscribe-success.test.js @@ -7,7 +7,7 @@ import { import { model as User } from '../../../../../../website/server/models/user'; import common from '../../../../../../website/common'; -describe('subscribeSuccess', () => { +describe('paypal - subscribeSuccess', () => { const subKey = 'basic_3mo'; let user; let group; let block; let groupId; let token; let headers; let customerId; diff --git a/test/api/unit/libs/payments/paypal/subscribe.test.js b/test/api/unit/libs/payments/paypal/subscribe.test.js index 23ad11c60c..2b79985de5 100644 --- a/test/api/unit/libs/payments/paypal/subscribe.test.js +++ b/test/api/unit/libs/payments/paypal/subscribe.test.js @@ -8,7 +8,7 @@ import common from '../../../../../../website/common'; const { i18n } = common; -describe('subscribe', () => { +describe('paypal - subscribe', () => { const subKey = 'basic_3mo'; let coupon; let sub; let approvalHerf; diff --git a/test/api/unit/libs/payments/stripe/calculateSubscriptionTerminationDate.test.js b/test/api/unit/libs/payments/stripe/calculateSubscriptionTerminationDate.test.js index e044cac353..c25574d07c 100644 --- a/test/api/unit/libs/payments/stripe/calculateSubscriptionTerminationDate.test.js +++ b/test/api/unit/libs/payments/stripe/calculateSubscriptionTerminationDate.test.js @@ -4,7 +4,7 @@ import api from '../../../../../../website/server/libs/payments/payments'; const groupPlanId = api.constants.GROUP_PLAN_CUSTOMER_ID; -describe('#calculateSubscriptionTerminationDate', () => { +describe('stripe - #calculateSubscriptionTerminationDate', () => { let plan; let nextBill; diff --git a/test/api/unit/libs/payments/stripe/cancel-subscription.test.js b/test/api/unit/libs/payments/stripe/cancel-subscription.test.js index c392502f1d..c1dc1c5f86 100644 --- a/test/api/unit/libs/payments/stripe/cancel-subscription.test.js +++ b/test/api/unit/libs/payments/stripe/cancel-subscription.test.js @@ -10,7 +10,7 @@ import common from '../../../../../../website/common'; const { i18n } = common; -describe('cancel subscription', () => { +describe('stripe - cancel subscription', () => { const subKey = 'basic_3mo'; const stripe = stripeModule('test'); let user; let groupId; let diff --git a/test/api/unit/libs/payments/stripe/checkout-subscription.test.js b/test/api/unit/libs/payments/stripe/checkout-subscription.test.js index cffb9a9d35..887b1c2e8e 100644 --- a/test/api/unit/libs/payments/stripe/checkout-subscription.test.js +++ b/test/api/unit/libs/payments/stripe/checkout-subscription.test.js @@ -12,7 +12,7 @@ import common from '../../../../../../website/common'; const { i18n } = common; -describe('checkout with subscription', () => { +describe('stripe - checkout with subscription', () => { const subKey = 'basic_3mo'; const stripe = stripeModule('test'); let user; let group; let data; let gift; let sub; diff --git a/test/api/unit/libs/payments/stripe/checkout.test.js b/test/api/unit/libs/payments/stripe/checkout.test.js index 72585007ad..4e9f9d7585 100644 --- a/test/api/unit/libs/payments/stripe/checkout.test.js +++ b/test/api/unit/libs/payments/stripe/checkout.test.js @@ -4,16 +4,17 @@ import { model as User } from '../../../../../../website/server/models/user'; import stripePayments from '../../../../../../website/server/libs/payments/stripe'; import payments from '../../../../../../website/server/libs/payments/payments'; import common from '../../../../../../website/common'; +import apiError from '../../../../../../website/server/libs/apiError'; const { i18n } = common; -describe('checkout', () => { +describe('stripe - checkout', () => { const subKey = 'basic_3mo'; const stripe = stripeModule('test'); let stripeChargeStub; let paymentBuyGemsStub; let paymentCreateSubscritionStub; let user; let gift; let groupId; let email; let headers; let coupon; let customerIdResponse; let - token; + token; const gemsBlockKey = '21gems'; const gemsBlock = common.content.gems[gemsBlockKey]; beforeEach(() => { user = new User(); @@ -89,6 +90,7 @@ describe('checkout', () => { await expect(stripePayments.checkout({ token, user, + gemsBlock: gemsBlockKey, gift, groupId, email, @@ -101,6 +103,25 @@ describe('checkout', () => { }); }); + it('should error if the gems block is invalid', async () => { + gift = undefined; + + await expect(stripePayments.checkout({ + token, + user, + gemsBlock: 'invalid', + gift, + groupId, + email, + headers, + coupon, + }, stripe)).to.eventually.be.rejected.and.to.eql({ + httpCode: 400, + message: apiError('invalidGemsBlock'), + name: 'BadRequest', + }); + }); + it('should purchase gems', async () => { gift = undefined; sinon.stub(user, 'canGetGems').resolves(true); @@ -108,6 +129,7 @@ describe('checkout', () => { await stripePayments.checkout({ token, user, + gemsBlock: gemsBlockKey, gift, groupId, email, @@ -117,7 +139,7 @@ describe('checkout', () => { expect(stripeChargeStub).to.be.calledOnce; expect(stripeChargeStub).to.be.calledWith({ - amount: 500, + amount: 499, currency: 'usd', card: token, }); @@ -128,6 +150,7 @@ describe('checkout', () => { customerId: customerIdResponse, paymentMethod: 'Stripe', gift, + gemsBlock, }); expect(user.canGetGems).to.be.calledOnce; user.canGetGems.restore(); @@ -167,6 +190,7 @@ describe('checkout', () => { customerId: customerIdResponse, paymentMethod: 'Gift', gift, + gemsBlock: undefined, }); }); @@ -205,6 +229,7 @@ describe('checkout', () => { customerId: customerIdResponse, paymentMethod: 'Gift', gift, + gemsBlock: undefined, }); }); }); diff --git a/test/api/unit/libs/payments/stripe/edit-subscription.test.js b/test/api/unit/libs/payments/stripe/edit-subscription.test.js index 1db3a86b17..d0dabed6a5 100644 --- a/test/api/unit/libs/payments/stripe/edit-subscription.test.js +++ b/test/api/unit/libs/payments/stripe/edit-subscription.test.js @@ -9,7 +9,7 @@ import common from '../../../../../../website/common'; const { i18n } = common; -describe('edit subscription', () => { +describe('stripe - edit subscription', () => { const subKey = 'basic_3mo'; const stripe = stripeModule('test'); let user; let groupId; let group; let diff --git a/test/api/v3/integration/payments/stripe/POST-payments_stripe_checkout.test.js b/test/api/v3/integration/payments/stripe/POST-payments_stripe_checkout.test.js index b73d6f8533..c04399a4b3 100644 --- a/test/api/v3/integration/payments/stripe/POST-payments_stripe_checkout.test.js +++ b/test/api/v3/integration/payments/stripe/POST-payments_stripe_checkout.test.js @@ -14,7 +14,10 @@ describe('payments - stripe - #checkout', () => { }); it('verifies credentials', async () => { - await expect(user.post(endpoint, { id: 123 })).to.eventually.be.rejected.and.include({ + await expect(user.post( + `${endpoint}?gemsBlock=4gems`, + { id: 123 }, + )).to.eventually.be.rejected.and.include({ code: 401, error: 'Error', // message: 'Invalid API Key provided: aaaabbbb********************1111', diff --git a/test/api/v3/integration/world-state/GET-world-state.test.js b/test/api/v3/integration/world-state/GET-world-state.test.js index 2b8f9cdfbd..ef2f9aca46 100644 --- a/test/api/v3/integration/world-state/GET-world-state.test.js +++ b/test/api/v3/integration/world-state/GET-world-state.test.js @@ -4,6 +4,8 @@ import { requester, resetHabiticaDB, } from '../../../../helpers/api-integration/v3'; +import * as worldState from '../../../../../website/server/libs/worldState'; +import common from '../../../../../website/common'; describe('GET /world-state', () => { before(async () => { @@ -39,4 +41,41 @@ describe('GET /world-state', () => { expect(res).to.have.nested.property('npcImageSuffix'); expect(res.npcImageSuffix).to.be.a('string'); }); + + context('no current event', () => { + beforeEach(async () => { + sinon.stub(worldState, 'getCurrentEvent').returns(null); + }); + + afterEach(() => { + worldState.getCurrentEvent.restore(); + }); + + it('returns null for the current event when there is none active', async () => { + const res = await requester().get('/world-state'); + + expect(res.currentEvent).to.be.null; + }); + }); + + context('no current event', () => { + const evt = { + ...common.content.events.fall2020, + event: 'fall2020', + }; + + beforeEach(async () => { + sinon.stub(worldState, 'getCurrentEvent').returns(evt); + }); + + afterEach(() => { + worldState.getCurrentEvent.restore(); + }); + + it('returns the current event when there is an active one', async () => { + const res = await requester().get('/world-state'); + + expect(res.currentEvent).to.eql(evt); + }); + }); }); diff --git a/website/client/src/app.vue b/website/client/src/app.vue index dd704123ed..af99eab93e 100644 --- a/website/client/src/app.vue +++ b/website/client/src/app.vue @@ -26,7 +26,6 @@ id="app" :class="{ 'casting-spell': castingSpell, - 'resting': showRestingBanner }" > @@ -38,31 +37,8 @@