From fbf69a4a34df07783961bfa36b7e4d0f8c448e96 Mon Sep 17 00:00:00 2001 From: Kalista Payne Date: Thu, 14 Nov 2024 12:31:57 -0600 Subject: [PATCH] Squashed commit of the following: MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit dd0a410fa6c3741dc0d6793283cf4df3c37790a5 Author: Kalista Payne Date: Mon Nov 4 14:24:30 2024 -0600 fix(subs): center next hourglass message commit 72d92ffd76bb43fee8ba2bbabd211e595afbd664 Author: Kalista Payne Date: Fri Nov 1 14:17:59 2024 -0500 fix(subs): don't hide HG preview entirely commit ea0ecb0c3d519ed3d5c42266367eaaa7283ac5de Author: Kalista Payne Date: Fri Nov 1 13:01:06 2024 -0500 fix(subs): Google wording and HG escape commit 2bd2c69e18e37c8c8c7106c62f186c372d25c5d2 Author: Kalista Payne Date: Fri Nov 1 09:25:30 2024 -0500 fix(layout): tighten cancellation note commit eb2fc40d241b18d4ffff04c35e744f05e6e9ff52 Author: Kalista Payne Date: Thu Oct 24 15:41:43 2024 -0500 fix(g1g1): don't try to find Gems promo during bogo commit d3eea86bd773c5236e8a0f619639e49db846c2ba Author: Kalista Payne Date: Thu Oct 24 15:00:09 2024 -0500 fix(subs): fix typeError commit e3ae9a2d6736f238c6aaaec37a5bf38d64afafe8 Author: Kalista Payne Date: Thu Oct 24 13:57:27 2024 -0500 fix(subs): also redirect to subs after gift sub commit 690163a0dec3a45329062905c90454c7cd7c83fd Author: Phillip Thelen Date: Wed Oct 23 16:42:38 2024 +0200 fix test commit 2ad7541fc0de429c152e6824f65d2b11b84a9809 Author: Phillip Thelen Date: Wed Oct 23 16:34:52 2024 +0200 fix test commit 7e337a9e591f2e8b27684567290a70f1b2d58aa0 Author: Phillip Thelen Date: Wed Oct 23 11:54:15 2024 +0200 remove only commit 7462b8a57f852ecfc52e74fb50d6cff1751bef74 Author: Phillip Thelen Date: Wed Oct 23 11:51:25 2024 +0200 fix bug with incorrectly giving HG bonus commit acd6183e95a5783dfa29e6c2b142f965c3c67411 Author: Kalista Payne Date: Mon Oct 21 17:22:26 2024 -0500 fix(subs): unhovery and un-12-monthy commit 935e9fd6ec2688ac7339c56ce0ff03bfdae30c77 Author: Kalista Payne Date: Fri Oct 18 14:50:17 2024 -0500 fix(subs): try again on gifts commit 6e1fb7df38d90e5c3ccebee9bb86dbb8f8a4678f Author: Kalista Payne Date: Thu Oct 17 18:19:20 2024 -0500 fix(lint): do negate object ig commit 71d434b94ea3b1a2c9381fd70f2e637473e00cac Author: Kalista Payne Date: Thu Oct 17 18:15:11 2024 -0500 fix(lint): unnecessary ternary commit b90b0bb9c39b931714526a9d20910968b055038d Author: Kalista Payne Date: Thu Oct 17 17:34:24 2024 -0500 fix(subs): gifts DON't renew commit 19469304c5a5881329ea1682e2070f9666d49ee4 Author: Kalista Payne Date: Thu Oct 17 17:13:29 2024 -0500 fix(subs): pass autoRenews through Stripe commit 6819e7b7e518969c58ebab4400f3147f0ddea1b3 Author: Kalista Payne Date: Thu Oct 17 16:03:25 2024 -0500 fix(subscriptions): minor visual updates commit 74633b5e5ea71d66681ad0e84873f3080ab5d361 Author: Kalista Payne Date: Wed Oct 16 17:27:09 2024 -0500 fix(subscriptions): more gift layout revisions commit a90ccb89de36a85acc214bb0b88479e0b78f1660 Author: Kalista Payne Date: Wed Oct 16 15:37:50 2024 -0500 fix(subscription): update layout when gifting commit c24b2db8dc6642669068f0a79d9b0990d43decb9 Author: Phillip Thelen Date: Mon Oct 14 16:11:46 2024 +0200 fix issue with promo hourglasses commit 7a61c72b47cd3403fe0f3edf91522277738068cc Author: Phillip Thelen Date: Mon Oct 14 15:59:40 2024 +0200 don’t give additional HG for new sub if they already got one this month commit f14cb090265ed830eb76c7f452e806257312370e Author: Phillip Thelen Date: Mon Oct 14 10:38:01 2024 +0200 Admin panel display fixes commit f4cff698cfb80f9ad2da7ecb626f84277f97eb7c Author: Kalista Payne Date: Thu Oct 3 17:58:59 2024 -0500 fix(stripe): correct redirect after success commit c468b58f3f783c58e9b48f9698b45473b526d3d4 Author: Kalista Payne Date: Thu Oct 3 17:35:37 2024 -0500 fix(subs): correct border-radius and redirect commit 78fb9e31d64f25aa091e24f95f25dc6dedc844a6 Author: Kalista Payne Date: Wed Oct 2 17:41:49 2024 -0500 fix(css): correct and refactor heights and selection states commit e2babe8053a778b64d51bd3d18866e69fb326a3c Author: Kalista Payne Date: Mon Sep 30 16:45:29 2024 -0500 feat(subscription): max Gems progress readout commit 61af8302a349f70d60886492b3d4f05dd5463a51 Author: Phillip Thelen Date: Fri Sep 27 15:11:22 2024 +0200 fix test commit ef8ff0ea9eebcbd682a34fd7f52722b92fdfae16 Author: Phillip Thelen Date: Fri Sep 27 14:14:44 2024 +0200 show date for hourglass bonus if it was received commit 4bafafdc8d493aad960dcf0d4957d3dad2d5e8da Author: Phillip Thelen Date: Fri Sep 27 14:12:52 2024 +0200 add new field for cumulative subscription count commit 30096247b73bdb76aa5b10dd4c964a78d2511e69 Author: Phillip Thelen Date: Fri Sep 27 13:39:49 2024 +0200 fix missing transaction type commit 70872651b09613a8fe1a19ee2e19dac398b3134d Author: Phillip Thelen Date: Fri Sep 27 13:31:40 2024 +0200 fix admin panel strings commit f3398db65f26db558f38ecce8fe4795ff73650cb Author: Kalista Payne Date: Thu Sep 26 23:11:16 2024 -0500 WIP(subs): extant Stripe state commit c6b2020109b2cdbc7dd8579c884c65f81e757c25 Author: Phillip Thelen Date: Thu Sep 26 11:41:55 2024 +0200 fix admin panel display commit d9afc96d2db8021db7e6310a009c15004ccc5c38 Author: Phillip Thelen Date: Thu Sep 26 11:40:16 2024 +0200 Fix hourglass logic for upgrades commit 6e2c8eeb649481afc349e6eb7741bcc82909c3c4 Author: Phillip Thelen Date: Wed Sep 25 17:48:54 2024 +0200 fix hourglass count commit cd752fbdce79f24bbdbaf6fd9558f207754c5cc3 Author: Kalista Payne Date: Fri Sep 20 12:24:21 2024 -0500 WIP(frontend): draft of main subs page view commit 0102b29d599e47192d7346180ecd549c79177156 Author: Kalista Payne Date: Wed Sep 18 15:29:08 2024 -0500 fix(admin): correct logic and style for shrimple subs commit 5469a5c5c3fddcf611018c1de077de3499df787a Author: Kalista Payne Date: Wed Sep 18 15:07:36 2024 -0500 fix(test): short circuit this. commit 526193ee6c9d07915d0373d07bb8ee0554fe2614 Author: Phillip Thelen Date: Wed Sep 18 14:42:06 2024 +0200 fix gem limit commit 19cf1636aa1371147ea92478485a653d612d9755 Author: Phillip Thelen Date: Tue Aug 13 17:00:40 2024 +0200 return nextHourglassDate again commit eea36e3ed54633c345d628d1d3d08e03a3e416a3 Author: Phillip Thelen Date: Tue Aug 13 13:11:22 2024 +0200 subscription test improvements commit ca78e7433031e79c61aba67235481e0b1c569a55 Author: Phillip Thelen Date: Mon Aug 12 15:46:15 2024 +0200 add more subscription tests commit f4c4f93a081a89d4c79aec1e87dac97d90c1d587 Author: Phillip Thelen Date: Fri Aug 9 13:35:22 2024 +0200 finish basic implementation of new logic commit e036742048b92c2e2f29724fb02462f117d91aea Author: Phillip Thelen Date: Fri Aug 9 11:37:44 2024 +0200 cleanup commit 643186568866ddea0a234b68d37ad4ab634bd147 Author: Phillip Thelen Date: Wed Aug 7 05:41:18 2024 -0400 update cron tests commit 930d875ae9d518b0b504ec97638e94c7296ad388 Author: Phillip Thelen Date: Thu Aug 8 10:36:50 2024 +0200 begin refactoring commit 96623608d064b94cfa40e5da736f13c696995df9 Author: Phillip Thelen Date: Tue Aug 6 16:28:16 2024 +0200 begin removing obsolete tests --- test/api/unit/libs/bug-report.test.js | 1 - test/api/unit/libs/cron.test.js | 515 +-------- .../group-plans/group-payments-create.test.js | 2 +- test/api/unit/libs/payments/payments.test.js | 546 +++------- .../payments/stripe/oneTimePayments.test.js | 1 + .../payments/stripe/subscriptions.test.js | 3 + .../buy/POST-user_buy_mystery_set.test.js | 2 +- test/common/libs/cron.test.js | 51 +- website/client/src/assets/images/confetti.png | Bin 0 -> 3294 bytes .../src/assets/images/subscriber-food.png | Bin 5145 -> 0 bytes website/client/src/assets/scss/colors.scss | 12 - .../client/src/assets/scss/typography.scss | 88 ++ .../client/src/assets/svg/divider-stars.svg | 32 + .../client/src/assets/svg/habitica-logo.svg | 10 +- .../src/assets/svg/hourglass-sparkle-left.svg | 48 + .../assets/svg/hourglass-sparkle-right.svg | 48 + website/client/src/assets/svg/jackalope.svg | 9 + .../client/src/assets/svg/stars-purple.svg | 52 + .../client/src/assets/svg/subscriber-food.svg | 19 + .../client/src/assets/svg/subscriber-gems.svg | 53 +- .../src/assets/svg/subscriber-hourglasses.svg | 44 +- .../user-support/subscriptionAndPerks.vue | 55 +- .../src/components/group-plans/billing.vue | 4 +- .../client/src/components/notifications.vue | 2 +- .../src/components/payments/buttons/list.vue | 2 +- .../src/components/payments/buyGemsModal.vue | 2 +- .../src/components/payments/sendGiftModal.vue | 31 +- .../src/components/settings/subscription.vue | 993 ++++++++++-------- .../settings/subscriptionOptions.vue | 347 ++++-- website/client/src/router/handleRedirect.js | 18 +- website/common/locales/en/npc.json | 2 +- website/common/locales/en/settings.json | 5 +- website/common/locales/en/subscriber.json | 48 +- website/common/script/cron.js | 16 +- website/common/script/libs/planGemLimits.js | 2 +- website/server/controllers/api-v3/hall.js | 9 +- website/server/libs/bug-report.js | 1 - website/server/libs/cron.js | 58 +- .../libs/payments/stripe/oneTimePayments.js | 5 +- .../libs/payments/stripe/subscriptions.js | 8 +- website/server/libs/payments/subscriptions.js | 54 +- website/server/models/subscriptionPlan.js | 40 +- website/server/models/transaction.js | 2 +- 43 files changed, 1554 insertions(+), 1686 deletions(-) create mode 100644 website/client/src/assets/images/confetti.png delete mode 100644 website/client/src/assets/images/subscriber-food.png create mode 100644 website/client/src/assets/svg/divider-stars.svg create mode 100644 website/client/src/assets/svg/hourglass-sparkle-left.svg create mode 100644 website/client/src/assets/svg/hourglass-sparkle-right.svg create mode 100644 website/client/src/assets/svg/jackalope.svg create mode 100644 website/client/src/assets/svg/stars-purple.svg create mode 100644 website/client/src/assets/svg/subscriber-food.svg diff --git a/test/api/unit/libs/bug-report.test.js b/test/api/unit/libs/bug-report.test.js index 58eefca3e6..5cb33d39b4 100644 --- a/test/api/unit/libs/bug-report.test.js +++ b/test/api/unit/libs/bug-report.test.js @@ -44,7 +44,6 @@ describe('bug-report', () => { USER_HOURGLASSES: 0, USER_ID: userId, USER_LEVEL: 1, - USER_OFFSET_MONTHS: 0, USER_PAYMENT_PLATFORM: undefined, USER_SUBSCRIPTION: undefined, USER_TIMEZONE_OFFSET: 0, diff --git a/test/api/unit/libs/cron.test.js b/test/api/unit/libs/cron.test.js index 6757228033..2863269ff2 100644 --- a/test/api/unit/libs/cron.test.js +++ b/test/api/unit/libs/cron.test.js @@ -154,6 +154,14 @@ describe('cron', async () => { expect(user.purchased.plan.consecutive.count).to.equal(1); }); + it('increments plan.cumulativeCount', async () => { + user.purchased.plan.cumulativeCount = 0; + await cron({ + user, tasksByType, daysMissed, analytics, + }); + expect(user.purchased.plan.cumulativeCount).to.equal(1); + }); + it('increments plan.consecutive.count by more than 1 if user skipped months between logins', async () => { user.purchased.plan.dateUpdated = moment().subtract(2, 'months').toDate(); user.purchased.plan.consecutive.count = 0; @@ -163,12 +171,13 @@ describe('cron', async () => { expect(user.purchased.plan.consecutive.count).to.equal(2); }); - it('decrements plan.consecutive.offset when offset is greater than 0', async () => { - user.purchased.plan.consecutive.offset = 2; + it('increments plan.cumulativeCount by more than 1 if user skipped months between logins', async () => { + user.purchased.plan.dateUpdated = moment().subtract(3, 'months').toDate(); + user.purchased.plan.cumulativeCount = 0; await cron({ user, tasksByType, daysMissed, analytics, }); - expect(user.purchased.plan.consecutive.offset).to.equal(1); + expect(user.purchased.plan.cumulativeCount).to.equal(3); }); it('does not award unearned plan.consecutive.trinkets if subscription ended during an absence', async () => { @@ -185,12 +194,12 @@ describe('cron', async () => { }); it('does not increment plan.consecutive.gemCapExtra when user has reached the gemCap limit', async () => { - user.purchased.plan.consecutive.gemCapExtra = 25; + user.purchased.plan.consecutive.gemCapExtra = 26; user.purchased.plan.consecutive.count = 5; await cron({ user, tasksByType, daysMissed, analytics, }); - expect(user.purchased.plan.consecutive.gemCapExtra).to.equal(25); + expect(user.purchased.plan.consecutive.gemCapExtra).to.equal(26); }); it('does not reset plan stats if we are before the last day of the cancelled month', async () => { @@ -205,16 +214,14 @@ describe('cron', async () => { user.purchased.plan.dateTerminated = moment(new Date()).subtract({ days: 1 }); user.purchased.plan.consecutive.gemCapExtra = 20; user.purchased.plan.consecutive.count = 5; - user.purchased.plan.consecutive.offset = 1; await cron({ user, tasksByType, daysMissed, analytics, }); expect(user.purchased.plan.customerId).to.not.exist; - expect(user.purchased.plan.consecutive.gemCapExtra).to.equal(0); + expect(user.purchased.plan.consecutive.gemCapExtra).to.equal(20); expect(user.purchased.plan.consecutive.count).to.equal(0); - expect(user.purchased.plan.consecutive.offset).to.equal(0); }); describe('for a 1-month recurring subscription', async () => { @@ -236,13 +243,11 @@ describe('cron', async () => { user1.purchased.plan.dateUpdated = moment().toDate(); user1.purchased.plan.planId = 'basic'; user1.purchased.plan.consecutive.count = 0; - user1.purchased.plan.perkMonthCount = 0; - user1.purchased.plan.consecutive.offset = 0; - user1.purchased.plan.consecutive.trinkets = 0; + user1.purchased.plan.consecutive.trinkets = 1; user1.purchased.plan.consecutive.gemCapExtra = 0; }); - it('does not increment consecutive benefits after the first month', async () => { + it('increments consecutive benefits', async () => { clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(1, 'months') .add(2, 'days') .toDate()); @@ -253,75 +258,8 @@ describe('cron', async () => { user: user1, tasksByType, daysMissed, analytics, }); expect(user1.purchased.plan.consecutive.count).to.equal(1); - expect(user1.purchased.plan.consecutive.offset).to.equal(0); - expect(user1.purchased.plan.consecutive.trinkets).to.equal(0); - expect(user1.purchased.plan.consecutive.gemCapExtra).to.equal(0); - }); - - it('does not increment consecutive benefits after the second month', async () => { - clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(2, 'months') - .add(2, 'days') - .toDate()); - // Add 1 month to simulate what happens a month after the subscription was created. - // Add 2 days so that we're sure we're not affected by any start-of-month effects - // e.g., from time zone oddness. - await cron({ - user: user1, tasksByType, daysMissed, analytics, - }); - expect(user1.purchased.plan.consecutive.count).to.equal(2); - expect(user1.purchased.plan.consecutive.offset).to.equal(0); - expect(user1.purchased.plan.consecutive.trinkets).to.equal(0); - expect(user1.purchased.plan.consecutive.gemCapExtra).to.equal(0); - }); - - it('increments consecutive benefits after the second month if they also received a 1 month gift subscription', async () => { - user1.purchased.plan.perkMonthCount = 1; - clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(2, 'months') - .add(2, 'days') - .toDate()); - // Add 1 month to simulate what happens a month after the subscription was created. - // Add 2 days so that we're sure we're not affected by any start-of-month effects - // e.g., from time zone oddness. - await cron({ - user: user1, tasksByType, daysMissed, analytics, - }); - expect(user1.purchased.plan.perkMonthCount).to.equal(0); - expect(user1.purchased.plan.consecutive.count).to.equal(2); - expect(user1.purchased.plan.consecutive.offset).to.equal(0); - expect(user1.purchased.plan.consecutive.trinkets).to.equal(1); - expect(user1.purchased.plan.consecutive.gemCapExtra).to.equal(5); - }); - - it('increments consecutive benefits after the third month', async () => { - clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(3, 'months') - .add(2, 'days') - .toDate()); - // Add 1 month to simulate what happens a month after the subscription was created. - // Add 2 days so that we're sure we're not affected by any start-of-month effects - // e.g., from time zone oddness. - await cron({ - user: user1, tasksByType, daysMissed, analytics, - }); - expect(user1.purchased.plan.consecutive.count).to.equal(3); - expect(user1.purchased.plan.consecutive.offset).to.equal(0); - expect(user1.purchased.plan.consecutive.trinkets).to.equal(1); - expect(user1.purchased.plan.consecutive.gemCapExtra).to.equal(5); - }); - - it('does not increment consecutive benefits after the fourth month', async () => { - clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(4, 'months') - .add(2, 'days') - .toDate()); - // Add 1 month to simulate what happens a month after the subscription was created. - // Add 2 days so that we're sure we're not affected by any start-of-month effects - // e.g., from time zone oddness. - await cron({ - user: user1, tasksByType, daysMissed, analytics, - }); - expect(user1.purchased.plan.consecutive.count).to.equal(4); - expect(user1.purchased.plan.consecutive.offset).to.equal(0); - expect(user1.purchased.plan.consecutive.trinkets).to.equal(1); - expect(user1.purchased.plan.consecutive.gemCapExtra).to.equal(5); + expect(user1.purchased.plan.consecutive.trinkets).to.equal(2); + expect(user1.purchased.plan.consecutive.gemCapExtra).to.equal(2); }); it('increments consecutive benefits correctly if user has been absent with continuous subscription', async () => { @@ -332,33 +270,8 @@ describe('cron', async () => { user: user1, tasksByType, daysMissed, analytics, }); expect(user1.purchased.plan.consecutive.count).to.equal(10); - expect(user1.purchased.plan.consecutive.offset).to.equal(0); - expect(user1.purchased.plan.consecutive.trinkets).to.equal(3); - expect(user1.purchased.plan.consecutive.gemCapExtra).to.equal(15); - }); - - it('initializes plan.perkMonthCount if necessary', async () => { - user.purchased.plan.perkMonthCount = undefined; - clock = sinon.useFakeTimers(moment(user.purchased.plan.dateUpdated) - .utcOffset(0) - .startOf('month') - .add(1, 'months') - .add(2, 'days') - .toDate()); - await cron({ - user, tasksByType, daysMissed, analytics, - }); - expect(user.purchased.plan.perkMonthCount).to.equal(1); - user.purchased.plan.perkMonthCount = undefined; - user.purchased.plan.consecutive.count = 8; - clock.restore(); - clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(2, 'months') - .add(2, 'days') - .toDate()); - await cron({ - user, tasksByType, daysMissed, analytics, - }); - expect(user.purchased.plan.perkMonthCount).to.equal(2); + expect(user1.purchased.plan.consecutive.trinkets).to.equal(11); + expect(user1.purchased.plan.consecutive.gemCapExtra).to.equal(20); }); }); @@ -379,14 +292,12 @@ describe('cron', async () => { user3.purchased.plan.customerId = 'subscribedId'; user3.purchased.plan.dateUpdated = moment().toDate(); user3.purchased.plan.planId = 'basic_3mo'; - user3.purchased.plan.perkMonthCount = 0; user3.purchased.plan.consecutive.count = 0; - user3.purchased.plan.consecutive.offset = 3; user3.purchased.plan.consecutive.trinkets = 1; - user3.purchased.plan.consecutive.gemCapExtra = 5; + user3.purchased.plan.consecutive.gemCapExtra = 0; }); - it('does not increment consecutive benefits in the first month of the first paid period that they already have benefits for', async () => { + it('increments consecutive benefits', async () => { clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(1, 'months') .add(2, 'days') .toDate()); @@ -394,102 +305,8 @@ describe('cron', async () => { user: user3, tasksByType, daysMissed, analytics, }); expect(user3.purchased.plan.consecutive.count).to.equal(1); - expect(user3.purchased.plan.consecutive.offset).to.equal(2); - expect(user3.purchased.plan.consecutive.trinkets).to.equal(1); - expect(user3.purchased.plan.consecutive.gemCapExtra).to.equal(5); - }); - - it('does not increment consecutive benefits in the middle of the period that they already have benefits for', async () => { - clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(2, 'months') - .add(2, 'days') - .toDate()); - await cron({ - user: user3, tasksByType, daysMissed, analytics, - }); - expect(user3.purchased.plan.consecutive.count).to.equal(2); - expect(user3.purchased.plan.consecutive.offset).to.equal(1); - expect(user3.purchased.plan.consecutive.trinkets).to.equal(1); - expect(user3.purchased.plan.consecutive.gemCapExtra).to.equal(5); - }); - - it('does not increment consecutive benefits in the final month of the period that they already have benefits for', async () => { - clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(3, 'months') - .add(2, 'days') - .toDate()); - await cron({ - user: user3, tasksByType, daysMissed, analytics, - }); - expect(user3.purchased.plan.consecutive.count).to.equal(3); - expect(user3.purchased.plan.consecutive.offset).to.equal(0); - expect(user3.purchased.plan.consecutive.trinkets).to.equal(1); - expect(user3.purchased.plan.consecutive.gemCapExtra).to.equal(5); - }); - - it('increments consecutive benefits the month after the second paid period has started', async () => { - clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(4, 'months') - .add(2, 'days') - .toDate()); - await cron({ - user: user3, tasksByType, daysMissed, analytics, - }); - expect(user3.purchased.plan.consecutive.count).to.equal(4); - expect(user3.purchased.plan.consecutive.offset).to.equal(2); expect(user3.purchased.plan.consecutive.trinkets).to.equal(2); - expect(user3.purchased.plan.consecutive.gemCapExtra).to.equal(10); - }); - - it('keeps existing plan.perkMonthCount intact when incrementing consecutive benefits', async () => { - user3.purchased.plan.perkMonthCount = 2; - user3.purchased.plan.consecutive.trinkets = 1; - user3.purchased.plan.consecutive.gemCapExtra = 5; - clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(4, 'months') - .add(2, 'days') - .toDate()); - await cron({ - user: user3, tasksByType, daysMissed, analytics, - }); - expect(user3.purchased.plan.perkMonthCount).to.equal(2); - expect(user3.purchased.plan.consecutive.trinkets).to.equal(2); - expect(user3.purchased.plan.consecutive.gemCapExtra).to.equal(10); - }); - - it('does not increment consecutive benefits in the second month of the second period that they already have benefits for', async () => { - clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(5, 'months') - .add(2, 'days') - .toDate()); - await cron({ - user: user3, tasksByType, daysMissed, analytics, - }); - expect(user3.purchased.plan.consecutive.count).to.equal(5); - expect(user3.purchased.plan.consecutive.offset).to.equal(1); - expect(user3.purchased.plan.consecutive.trinkets).to.equal(2); - expect(user3.purchased.plan.consecutive.gemCapExtra).to.equal(10); - }); - - it('does not increment consecutive benefits in the final month of the second period that they already have benefits for', async () => { - clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(6, 'months') - .add(2, 'days') - .toDate()); - await cron({ - user: user3, tasksByType, daysMissed, analytics, - }); - expect(user3.purchased.plan.consecutive.count).to.equal(6); - expect(user3.purchased.plan.consecutive.offset).to.equal(0); - expect(user3.purchased.plan.consecutive.trinkets).to.equal(2); - expect(user3.purchased.plan.consecutive.gemCapExtra).to.equal(10); - }); - - it('increments consecutive benefits the month after the third paid period has started', async () => { - clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(7, 'months') - .add(2, 'days') - .toDate()); - await cron({ - user: user3, tasksByType, daysMissed, analytics, - }); - expect(user3.purchased.plan.consecutive.count).to.equal(7); - expect(user3.purchased.plan.consecutive.offset).to.equal(2); - expect(user3.purchased.plan.consecutive.trinkets).to.equal(3); - expect(user3.purchased.plan.consecutive.gemCapExtra).to.equal(15); + expect(user3.purchased.plan.consecutive.gemCapExtra).to.equal(2); }); it('increments consecutive benefits correctly if user has been absent with continuous subscription', async () => { @@ -500,8 +317,7 @@ describe('cron', async () => { user: user3, tasksByType, daysMissed, analytics, }); expect(user3.purchased.plan.consecutive.count).to.equal(10); - expect(user3.purchased.plan.consecutive.offset).to.equal(2); - expect(user3.purchased.plan.consecutive.trinkets).to.equal(4); + expect(user3.purchased.plan.consecutive.trinkets).to.equal(11); expect(user3.purchased.plan.consecutive.gemCapExtra).to.equal(20); }); }); @@ -523,14 +339,12 @@ describe('cron', async () => { user6.purchased.plan.customerId = 'subscribedId'; user6.purchased.plan.dateUpdated = moment().toDate(); user6.purchased.plan.planId = 'google_6mo'; - user6.purchased.plan.perkMonthCount = 0; user6.purchased.plan.consecutive.count = 0; - user6.purchased.plan.consecutive.offset = 6; - user6.purchased.plan.consecutive.trinkets = 2; - user6.purchased.plan.consecutive.gemCapExtra = 10; + user6.purchased.plan.consecutive.trinkets = 1; + user6.purchased.plan.consecutive.gemCapExtra = 0; }); - it('does not increment consecutive benefits in the first month of the first paid period that they already have benefits for', async () => { + it('increments benefits', async () => { clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(1, 'months') .add(2, 'days') .toDate()); @@ -538,74 +352,8 @@ describe('cron', async () => { user: user6, tasksByType, daysMissed, analytics, }); expect(user6.purchased.plan.consecutive.count).to.equal(1); - expect(user6.purchased.plan.consecutive.offset).to.equal(5); expect(user6.purchased.plan.consecutive.trinkets).to.equal(2); - expect(user6.purchased.plan.consecutive.gemCapExtra).to.equal(10); - }); - - it('does not increment consecutive benefits in the final month of the period that they already have benefits for', async () => { - clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(6, 'months') - .add(2, 'days') - .toDate()); - await cron({ - user: user6, tasksByType, daysMissed, analytics, - }); - expect(user6.purchased.plan.consecutive.count).to.equal(6); - expect(user6.purchased.plan.consecutive.offset).to.equal(0); - expect(user6.purchased.plan.consecutive.trinkets).to.equal(2); - expect(user6.purchased.plan.consecutive.gemCapExtra).to.equal(10); - }); - - it('increments consecutive benefits the month after the second paid period has started', async () => { - clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(7, 'months') - .add(2, 'days') - .toDate()); - await cron({ - user: user6, tasksByType, daysMissed, analytics, - }); - expect(user6.purchased.plan.consecutive.count).to.equal(7); - expect(user6.purchased.plan.consecutive.offset).to.equal(5); - expect(user6.purchased.plan.consecutive.trinkets).to.equal(4); - expect(user6.purchased.plan.consecutive.gemCapExtra).to.equal(20); - }); - - it('keeps existing plan.perkMonthCount intact when incrementing consecutive benefits', async () => { - user6.purchased.plan.perkMonthCount = 2; - clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(7, 'months') - .add(2, 'days') - .toDate()); - await cron({ - user: user6, tasksByType, daysMissed, analytics, - }); - expect(user6.purchased.plan.perkMonthCount).to.equal(2); - expect(user6.purchased.plan.consecutive.trinkets).to.equal(4); - expect(user6.purchased.plan.consecutive.gemCapExtra).to.equal(20); - }); - - it('increments consecutive benefits the month after the third paid period has started', async () => { - clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(13, 'months') - .add(2, 'days') - .toDate()); - await cron({ - user: user6, tasksByType, daysMissed, analytics, - }); - expect(user6.purchased.plan.consecutive.count).to.equal(13); - expect(user6.purchased.plan.consecutive.offset).to.equal(5); - expect(user6.purchased.plan.consecutive.trinkets).to.equal(6); - expect(user6.purchased.plan.consecutive.gemCapExtra).to.equal(25); - }); - - it('increments consecutive benefits correctly if user has been absent with continuous subscription', async () => { - clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(19, 'months') - .add(2, 'days') - .toDate()); - await cron({ - user: user6, tasksByType, daysMissed, analytics, - }); - expect(user6.purchased.plan.consecutive.count).to.equal(19); - expect(user6.purchased.plan.consecutive.offset).to.equal(5); - expect(user6.purchased.plan.consecutive.trinkets).to.equal(8); - expect(user6.purchased.plan.consecutive.gemCapExtra).to.equal(25); + expect(user6.purchased.plan.consecutive.gemCapExtra).to.equal(2); }); }); @@ -626,11 +374,10 @@ describe('cron', async () => { user12.purchased.plan.dateUpdated = moment().toDate(); user12.purchased.plan.planId = 'basic_12mo'; user12.purchased.plan.consecutive.count = 0; - user12.purchased.plan.consecutive.offset = 12; - user12.purchased.plan.consecutive.trinkets = 4; - user12.purchased.plan.consecutive.gemCapExtra = 20; + user12.purchased.plan.consecutive.trinkets = 1; + user12.purchased.plan.consecutive.gemCapExtra = 26; - it('does not increment consecutive benefits in the first month of the first paid period that they already have benefits for', async () => { + it('increments consecutive benefits the month after the second paid period has started', async () => { clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(1, 'months') .add(2, 'days') .toDate()); @@ -638,61 +385,20 @@ describe('cron', async () => { user: user12, tasksByType, daysMissed, analytics, }); expect(user12.purchased.plan.consecutive.count).to.equal(1); - expect(user12.purchased.plan.consecutive.offset).to.equal(11); - expect(user12.purchased.plan.consecutive.trinkets).to.equal(4); - expect(user12.purchased.plan.consecutive.gemCapExtra).to.equal(20); - }); - - it('does not increment consecutive benefits in the final month of the period that they already have benefits for', async () => { - clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(12, 'months') - .add(2, 'days') - .toDate()); - await cron({ - user: user12, tasksByType, daysMissed, analytics, - }); - expect(user12.purchased.plan.consecutive.count).to.equal(12); - expect(user12.purchased.plan.consecutive.offset).to.equal(0); - expect(user12.purchased.plan.consecutive.trinkets).to.equal(4); - expect(user12.purchased.plan.consecutive.gemCapExtra).to.equal(20); - }); - - it('increments consecutive benefits the month after the second paid period has started', async () => { - clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(13, 'months') - .add(2, 'days') - .toDate()); - await cron({ - user: user12, tasksByType, daysMissed, analytics, - }); - expect(user12.purchased.plan.consecutive.count).to.equal(13); - expect(user12.purchased.plan.consecutive.offset).to.equal(11); - expect(user12.purchased.plan.consecutive.trinkets).to.equal(8); - expect(user12.purchased.plan.consecutive.gemCapExtra).to.equal(25); - }); - - it('increments consecutive benefits the month after the third paid period has started', async () => { - clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(25, 'months') - .add(2, 'days') - .toDate()); - await cron({ - user: user12, tasksByType, daysMissed, analytics, - }); - expect(user12.purchased.plan.consecutive.count).to.equal(25); - expect(user12.purchased.plan.consecutive.offset).to.equal(11); - expect(user12.purchased.plan.consecutive.trinkets).to.equal(12); - expect(user12.purchased.plan.consecutive.gemCapExtra).to.equal(25); + expect(user12.purchased.plan.consecutive.trinkets).to.equal(2); + expect(user12.purchased.plan.consecutive.gemCapExtra).to.equal(26); }); it('increments consecutive benefits correctly if user has been absent with continuous subscription', async () => { - clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(37, 'months') + clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(10, 'months') .add(2, 'days') .toDate()); await cron({ user: user12, tasksByType, daysMissed, analytics, }); - expect(user12.purchased.plan.consecutive.count).to.equal(37); - expect(user12.purchased.plan.consecutive.offset).to.equal(11); - expect(user12.purchased.plan.consecutive.trinkets).to.equal(16); - expect(user12.purchased.plan.consecutive.gemCapExtra).to.equal(25); + expect(user12.purchased.plan.consecutive.count).to.equal(10); + expect(user12.purchased.plan.consecutive.trinkets).to.equal(11); + expect(user12.purchased.plan.consecutive.gemCapExtra).to.equal(26); }); }); @@ -715,11 +421,11 @@ describe('cron', async () => { .toDate(); user3g.purchased.plan.planId = null; user3g.purchased.plan.consecutive.count = 0; - user3g.purchased.plan.consecutive.offset = 3; + user3g.purchased.plan.cumulativeCount = 0; user3g.purchased.plan.consecutive.trinkets = 1; - user3g.purchased.plan.consecutive.gemCapExtra = 5; + user3g.purchased.plan.consecutive.gemCapExtra = 0; - it('does not increment consecutive benefits in the first month of the gift subscription', async () => { + it('increments benefits', async () => { clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(1, 'months') .add(2, 'days') .toDate()); @@ -727,35 +433,9 @@ describe('cron', async () => { user: user3g, tasksByType, daysMissed, analytics, }); expect(user3g.purchased.plan.consecutive.count).to.equal(1); - expect(user3g.purchased.plan.consecutive.offset).to.equal(2); - expect(user3g.purchased.plan.consecutive.trinkets).to.equal(1); - expect(user3g.purchased.plan.consecutive.gemCapExtra).to.equal(5); - }); - - it('does not increment consecutive benefits in the second month of the gift subscription', async () => { - clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(2, 'months') - .add(2, 'days') - .toDate()); - await cron({ - user: user3g, tasksByType, daysMissed, analytics, - }); - expect(user3g.purchased.plan.consecutive.count).to.equal(2); - expect(user3g.purchased.plan.consecutive.offset).to.equal(1); - expect(user3g.purchased.plan.consecutive.trinkets).to.equal(1); - expect(user3g.purchased.plan.consecutive.gemCapExtra).to.equal(5); - }); - - it('does not increment consecutive benefits in the third month of the gift subscription', async () => { - clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(3, 'months') - .add(2, 'days') - .toDate()); - await cron({ - user: user3g, tasksByType, daysMissed, analytics, - }); - expect(user3g.purchased.plan.consecutive.count).to.equal(3); - expect(user3g.purchased.plan.consecutive.offset).to.equal(0); - expect(user3g.purchased.plan.consecutive.trinkets).to.equal(1); - expect(user3g.purchased.plan.consecutive.gemCapExtra).to.equal(5); + expect(user3g.purchased.plan.cumulativeCount).to.equal(1); + expect(user3g.purchased.plan.consecutive.trinkets).to.equal(2); + expect(user3g.purchased.plan.consecutive.gemCapExtra).to.equal(2); }); it('does not increment consecutive benefits in the month after the gift subscription has ended', async () => { @@ -767,84 +447,9 @@ describe('cron', async () => { }); // subscription has been erased by now expect(user3g.purchased.plan.consecutive.count).to.equal(0); - expect(user3g.purchased.plan.consecutive.offset).to.equal(0); - expect(user3g.purchased.plan.consecutive.trinkets).to.equal(1); - expect(user3g.purchased.plan.consecutive.gemCapExtra).to.equal(0); // erased - }); - }); - - describe('for a 6-month recurring subscription where the user has incorrect consecutive month data from prior bugs', async () => { - const user6x = new User({ - auth: { - local: { - username: 'username6x', - lowerCaseUsername: 'username6x', - email: 'email6x@example.com', - salt: 'salt', - hashed_password: 'hashed_password', // eslint-disable-line camelcase - }, - }, - }); - // user6x has a 6-month recurring subscription starting 8 months in the past - // before issue #4819 was fixed - user6x.purchased.plan.customerId = 'subscribedId'; - user6x.purchased.plan.dateUpdated = moment().toDate(); - user6x.purchased.plan.planId = 'basic_6mo'; - user6x.purchased.plan.consecutive.count = 8; - user6x.purchased.plan.consecutive.offset = 0; - user6x.purchased.plan.consecutive.trinkets = 3; - user6x.purchased.plan.consecutive.gemCapExtra = 15; - - it('increments consecutive benefits in the first month since the fix for #4819 goes live', async () => { - clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(1, 'months') - .add(2, 'days') - .toDate()); - await cron({ - user: user6x, tasksByType, daysMissed, analytics, - }); - expect(user6x.purchased.plan.consecutive.count).to.equal(9); - expect(user6x.purchased.plan.consecutive.offset).to.equal(5); - expect(user6x.purchased.plan.consecutive.trinkets).to.equal(5); - expect(user6x.purchased.plan.consecutive.gemCapExtra).to.equal(25); - }); - - it('does not increment consecutive benefits in the second month after the fix goes live', async () => { - clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(2, 'months') - .add(2, 'days') - .toDate()); - await cron({ - user: user6x, tasksByType, daysMissed, analytics, - }); - expect(user6x.purchased.plan.consecutive.count).to.equal(10); - expect(user6x.purchased.plan.consecutive.offset).to.equal(4); - expect(user6x.purchased.plan.consecutive.trinkets).to.equal(5); - expect(user6x.purchased.plan.consecutive.gemCapExtra).to.equal(25); - }); - - it('does not increment consecutive benefits in the third month after the fix goes live', async () => { - clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(3, 'months') - .add(2, 'days') - .toDate()); - await cron({ - user: user6x, tasksByType, daysMissed, analytics, - }); - expect(user6x.purchased.plan.consecutive.count).to.equal(11); - expect(user6x.purchased.plan.consecutive.offset).to.equal(3); - expect(user6x.purchased.plan.consecutive.trinkets).to.equal(5); - expect(user6x.purchased.plan.consecutive.gemCapExtra).to.equal(25); - }); - - it('increments consecutive benefits in the seventh month after the fix goes live', async () => { - clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(7, 'months') - .add(2, 'days') - .toDate()); - await cron({ - user: user6x, tasksByType, daysMissed, analytics, - }); - expect(user6x.purchased.plan.consecutive.count).to.equal(15); - expect(user6x.purchased.plan.consecutive.offset).to.equal(5); - expect(user6x.purchased.plan.consecutive.trinkets).to.equal(7); - expect(user6x.purchased.plan.consecutive.gemCapExtra).to.equal(25); + expect(user3g.purchased.plan.consecutive.trinkets).to.equal(2); + expect(user3g.purchased.plan.consecutive.gemCapExtra).to.equal(2); + expect(user3g.purchased.plan.cumulativeCount).to.equal(1); }); }); }); @@ -888,12 +493,12 @@ describe('cron', async () => { expect(user.purchased.plan.consecutive.count).to.equal(0); }); - it('does not decrement plan.consecutive.offset when offset is greater than 0', async () => { - user.purchased.plan.consecutive.offset = 1; + it('does not increment plan.cumulativeCount', async () => { + user.purchased.plan.cumulativeCount = 0; await cron({ user, tasksByType, daysMissed, analytics, }); - expect(user.purchased.plan.consecutive.offset).to.equal(1); + expect(user.purchased.plan.cumulativeCount).to.equal(0); }); it('does not increment plan.consecutive.trinkets when user has reached a month that is a multiple of 3', async () => { @@ -913,12 +518,12 @@ describe('cron', async () => { }); it('does not increment plan.consecutive.gemCapExtra when user has reached the gemCap limit', async () => { - user.purchased.plan.consecutive.gemCapExtra = 25; + user.purchased.plan.consecutive.gemCapExtra = 26; user.purchased.plan.consecutive.count = 5; await cron({ user, tasksByType, daysMissed, analytics, }); - expect(user.purchased.plan.consecutive.gemCapExtra).to.equal(25); + expect(user.purchased.plan.consecutive.gemCapExtra).to.equal(26); }); it('does nothing to plan stats if we are before the last day of the cancelled month', async () => { @@ -928,22 +533,6 @@ describe('cron', async () => { }); expect(user.purchased.plan.customerId).to.not.exist; }); - - xit('does nothing to plan stats when we are after the last day of the cancelled month', async () => { - user.purchased.plan.dateTerminated = moment(new Date()).subtract({ days: 1 }); - user.purchased.plan.consecutive.gemCapExtra = 20; - user.purchased.plan.consecutive.count = 5; - user.purchased.plan.consecutive.offset = 1; - - await cron({ - user, tasksByType, daysMissed, analytics, - }); - - expect(user.purchased.plan.customerId).to.exist; - expect(user.purchased.plan.consecutive.gemCapExtra).to.exist; - expect(user.purchased.plan.consecutive.count).to.exist; - expect(user.purchased.plan.consecutive.offset).to.exist; - }); }); describe('todos', async () => { diff --git a/test/api/unit/libs/payments/group-plans/group-payments-create.test.js b/test/api/unit/libs/payments/group-plans/group-payments-create.test.js index 383d7dfe6f..17ff154a0a 100644 --- a/test/api/unit/libs/payments/group-plans/group-payments-create.test.js +++ b/test/api/unit/libs/payments/group-plans/group-payments-create.test.js @@ -715,7 +715,7 @@ describe('Purchasing a group plan for group', () => { const mysteryItem = { title: 'item' }; const mysteryItems = [mysteryItem]; const consecutive = { - trinkets: 3, + trinkets: 4, gemCapExtra: 20, offset: 1, count: 13, diff --git a/test/api/unit/libs/payments/payments.test.js b/test/api/unit/libs/payments/payments.test.js index 5b2d2f9899..00732af3ce 100644 --- a/test/api/unit/libs/payments/payments.test.js +++ b/test/api/unit/libs/payments/payments.test.js @@ -12,6 +12,7 @@ import { } from '../../../../helpers/api-unit.helper'; import * as worldState from '../../../../../website/server/libs/worldState'; import { TransactionModel } from '../../../../../website/server/models/transaction'; +import { REPEATING_EVENTS } from '../../../../../website/common/script/content/constants/events'; describe('payments/index', () => { let user; @@ -65,7 +66,6 @@ describe('payments/index', () => { mysteryItems: [], consecutive: { trinkets: 0, - offset: 0, gemCapExtra: 0, }, }; @@ -108,14 +108,8 @@ describe('payments/index', () => { }); it('add a transaction entry to the recipient', async () => { - recipient.purchased.plan = plan; - - expect(recipient.purchased.plan.extraMonths).to.eql(0); - await api.createSubscription(data); - expect(recipient.purchased.plan.extraMonths).to.eql(3); - const transactions = await TransactionModel .find({ userId: recipient._id }) .sort({ createdAt: -1 }) @@ -177,6 +171,45 @@ describe('payments/index', () => { expect(recipient.purchased.plan.dateUpdated).to.exist; }); + it('does not reset gemCapExtra if they already had one', async () => { + recipient.purchased.plan.consecutive.gemCapExtra = 10; + + await api.createSubscription(data); + + expect(recipient.purchased.plan.consecutive.gemCapExtra).to.eql(10); + }); + + it('sets gemCapExtra to 0 if they receive a 3 month sub', async () => { + data.gift.subscription.key = 'basic_3mo'; + data.gift.subscription.months = 3; + + await api.createSubscription(data); + + expect(recipient.purchased.plan.consecutive.gemCapExtra).to.eql(0); + }); + + it('sets gemCapExtra to max if they receive a 12 month sub', async () => { + recipient.purchased.plan.consecutive.gemCapExtra = 10; + + data.gift.subscription.key = 'basic_12mo'; + data.gift.subscription.months = 12; + + await api.createSubscription(data); + + expect(recipient.purchased.plan.consecutive.gemCapExtra).to.eql(26); + }); + + it('gives user 1 hourglass if they have no active subscription', async () => { + await api.createSubscription(data); + expect(recipient.purchased.plan.consecutive.trinkets).to.eql(1); + }); + + it('does not give any hourglasses if they have an active subscription', async () => { + recipient.purchased.plan = plan; + await api.createSubscription(data); + expect(recipient.purchased.plan.consecutive.trinkets).to.eql(plan.consecutive.trinkets); + }); + it('sets plan.dateUpdated if it did exist but the user has cancelled', async () => { recipient.purchased.plan.dateUpdated = moment().subtract(1, 'days').toDate(); recipient.purchased.plan.dateTerminated = moment().subtract(1, 'days').toDate(); @@ -235,116 +268,6 @@ describe('payments/index', () => { expect(recipient.purchased.plan.customerId).to.eql('customer-id'); }); - it('sets plan.perkMonthCount to 1 if user is not subscribed', async () => { - recipient.purchased.plan = plan; - recipient.purchased.plan.perkMonthCount = 1; - recipient.purchased.plan.customerId = undefined; - data.sub.key = 'basic_earned'; - data.gift.subscription.key = 'basic_earned'; - data.gift.subscription.months = 1; - - expect(recipient.purchased.plan.perkMonthCount).to.eql(1); - await api.createSubscription(data); - - expect(recipient.purchased.plan.perkMonthCount).to.eql(1); - }); - - it('sets plan.perkMonthCount to 1 if field is not initialized', async () => { - recipient.purchased.plan = plan; - recipient.purchased.plan.perkMonthCount = -1; - recipient.purchased.plan.customerId = undefined; - data.sub.key = 'basic_earned'; - data.gift.subscription.key = 'basic_earned'; - data.gift.subscription.months = 1; - - expect(recipient.purchased.plan.perkMonthCount).to.eql(-1); - await api.createSubscription(data); - - expect(recipient.purchased.plan.perkMonthCount).to.eql(1); - }); - - it('sets plan.perkMonthCount to 1 if user had previous count but lapsed subscription', async () => { - recipient.purchased.plan = plan; - recipient.purchased.plan.perkMonthCount = 2; - recipient.purchased.plan.customerId = undefined; - data.sub.key = 'basic_earned'; - data.gift.subscription.key = 'basic_earned'; - data.gift.subscription.months = 1; - - expect(recipient.purchased.plan.perkMonthCount).to.eql(2); - await api.createSubscription(data); - - expect(recipient.purchased.plan.perkMonthCount).to.eql(1); - }); - - it('adds to plan.perkMonthCount if user is already subscribed', async () => { - recipient.purchased.plan = plan; - recipient.purchased.plan.perkMonthCount = 1; - data.sub.key = 'basic_earned'; - data.gift.subscription.key = 'basic_earned'; - data.gift.subscription.months = 1; - - expect(recipient.purchased.plan.perkMonthCount).to.eql(1); - await api.createSubscription(data); - - expect(recipient.purchased.plan.perkMonthCount).to.eql(2); - }); - - it('awards perks if plan.perkMonthCount reaches 3 with existing subscription', async () => { - recipient.purchased.plan = plan; - recipient.purchased.plan.perkMonthCount = 2; - data.sub.key = 'basic_earned'; - data.gift.subscription.key = 'basic_earned'; - data.gift.subscription.months = 1; - - expect(recipient.purchased.plan.perkMonthCount).to.eql(2); - expect(recipient.purchased.plan.consecutive.trinkets).to.eql(0); - expect(recipient.purchased.plan.consecutive.gemCapExtra).to.eql(0); - await api.createSubscription(data); - - expect(recipient.purchased.plan.perkMonthCount).to.eql(0); - expect(recipient.purchased.plan.consecutive.trinkets).to.eql(1); - expect(recipient.purchased.plan.consecutive.gemCapExtra).to.eql(5); - }); - - it('awards perks if plan.perkMonthCount reaches 3 without existing subscription', async () => { - recipient.purchased.plan.perkMonthCount = 0; - expect(recipient.purchased.plan.perkMonthCount).to.eql(0); - expect(recipient.purchased.plan.consecutive.trinkets).to.eql(0); - expect(recipient.purchased.plan.consecutive.gemCapExtra).to.eql(0); - await api.createSubscription(data); - - expect(recipient.purchased.plan.perkMonthCount).to.eql(0); - expect(recipient.purchased.plan.consecutive.trinkets).to.eql(1); - expect(recipient.purchased.plan.consecutive.gemCapExtra).to.eql(5); - }); - - it('awards perks if plan.perkMonthCount reaches 3 without initialized field', async () => { - expect(recipient.purchased.plan.perkMonthCount).to.eql(-1); - expect(recipient.purchased.plan.consecutive.trinkets).to.eql(0); - expect(recipient.purchased.plan.consecutive.gemCapExtra).to.eql(0); - await api.createSubscription(data); - - expect(recipient.purchased.plan.perkMonthCount).to.eql(0); - expect(recipient.purchased.plan.consecutive.trinkets).to.eql(1); - expect(recipient.purchased.plan.consecutive.gemCapExtra).to.eql(5); - }); - - it('awards perks if plan.perkMonthCount goes over 3', async () => { - recipient.purchased.plan = plan; - recipient.purchased.plan.perkMonthCount = 2; - data.sub.key = 'basic_earned'; - - expect(recipient.purchased.plan.perkMonthCount).to.eql(2); - expect(recipient.purchased.plan.consecutive.trinkets).to.eql(0); - expect(recipient.purchased.plan.consecutive.gemCapExtra).to.eql(0); - await api.createSubscription(data); - - expect(recipient.purchased.plan.perkMonthCount).to.eql(2); - expect(recipient.purchased.plan.consecutive.trinkets).to.eql(1); - expect(recipient.purchased.plan.consecutive.gemCapExtra).to.eql(5); - }); - it('sets plan.customerId to "Gift" if it does not already exist', async () => { expect(recipient.purchased.plan.customerId).to.not.exist; @@ -421,8 +344,8 @@ describe('payments/index', () => { context('Active Promotion', () => { beforeEach(() => { sinon.stub(worldState, 'getCurrentEventList').returns([{ - ...common.content.events.winter2021Promo, - event: 'winter2021', + ...REPEATING_EVENTS.giftOneGetOne, + event: 'g1g1', }]); }); @@ -438,22 +361,30 @@ describe('payments/index', () => { expect(user.purchased.plan.dateTerminated).to.exist; expect(user.purchased.plan.dateUpdated).to.exist; expect(user.purchased.plan.dateCreated).to.exist; + expect(user.purchased.plan.consecutive.trinkets).to.eql(1); + expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(0); expect(recipient.items.pets['Jackalope-RoyalPurple']).to.eql(5); expect(recipient.purchased.plan.customerId).to.eql('Gift'); expect(recipient.purchased.plan.dateTerminated).to.exist; expect(recipient.purchased.plan.dateUpdated).to.exist; expect(recipient.purchased.plan.dateCreated).to.exist; + expect(recipient.purchased.plan.consecutive.trinkets).to.eql(1); + expect(recipient.purchased.plan.consecutive.gemCapExtra).to.eql(0); }); it('adds extraMonths to existing subscription for purchaser and creates a gift subscription for recipient without sub', async () => { user.purchased.plan = plan; expect(user.purchased.plan.extraMonths).to.eql(0); + expect(user.purchased.plan.consecutive.trinkets).to.eql(0); + expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(0); await api.createSubscription(data); expect(user.purchased.plan.extraMonths).to.eql(3); + expect(user.purchased.plan.consecutive.trinkets).to.eql(0); + expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(0); expect(recipient.items.pets['Jackalope-RoyalPurple']).to.eql(5); expect(recipient.purchased.plan.customerId).to.eql('Gift'); @@ -466,10 +397,12 @@ describe('payments/index', () => { recipient.purchased.plan = plan; expect(recipient.purchased.plan.extraMonths).to.eql(0); + expect(recipient.purchased.plan.consecutive.trinkets).to.eql(0); await api.createSubscription(data); expect(recipient.purchased.plan.extraMonths).to.eql(3); + expect(recipient.purchased.plan.consecutive.trinkets).to.eql(0); expect(user.items.pets['Jackalope-RoyalPurple']).to.eql(5); expect(user.purchased.plan.customerId).to.eql('Gift'); @@ -484,11 +417,15 @@ describe('payments/index', () => { expect(user.purchased.plan.extraMonths).to.eql(0); expect(recipient.purchased.plan.extraMonths).to.eql(0); + expect(user.purchased.plan.consecutive.trinkets).to.eql(0); + expect(recipient.purchased.plan.consecutive.trinkets).to.eql(0); await api.createSubscription(data); expect(user.purchased.plan.extraMonths).to.eql(3); expect(recipient.purchased.plan.extraMonths).to.eql(3); + expect(user.purchased.plan.consecutive.trinkets).to.eql(0); + expect(recipient.purchased.plan.consecutive.trinkets).to.eql(0); }); it('sends a private message about the promotion', async () => { @@ -511,7 +448,6 @@ describe('payments/index', () => { expect(user.purchased.plan.customerId).to.eql('customer-id'); expect(user.purchased.plan.dateUpdated).to.exist; expect(user.purchased.plan.gemsBought).to.eql(0); - expect(user.purchased.plan.perkMonthCount).to.eql(0); expect(user.purchased.plan.paymentMethod).to.eql('Payment Method'); expect(user.purchased.plan.extraMonths).to.eql(0); expect(user.purchased.plan.dateTerminated).to.eql(null); @@ -549,33 +485,6 @@ describe('payments/index', () => { expect(user.purchased.plan.dateCurrentTypeCreated).to.not.eql(initialDate); }); - it('keeps plan.perkMonthCount when changing subscription type', async () => { - await api.createSubscription(data); - user.purchased.plan.perkMonthCount = 2; - await api.createSubscription(data); - expect(user.purchased.plan.perkMonthCount).to.eql(2); - }); - - it('sets plan.perkMonthCount to zero when creating new monthly subscription', async () => { - user.purchased.plan.perkMonthCount = 2; - await api.createSubscription(data); - expect(user.purchased.plan.perkMonthCount).to.eql(0); - }); - - it('sets plan.perkMonthCount to zero when creating new 3 month subscription', async () => { - user.purchased.plan.perkMonthCount = 2; - await api.createSubscription(data); - expect(user.purchased.plan.perkMonthCount).to.eql(0); - }); - - it('updates plan.consecutive.offset when changing subscription type', async () => { - await api.createSubscription(data); - expect(user.purchased.plan.consecutive.offset).to.eql(3); - data.sub.key = 'basic_6mo'; - await api.createSubscription(data); - expect(user.purchased.plan.consecutive.offset).to.eql(6); - }); - it('awards the Royal Purple Jackalope pet', async () => { await api.createSubscription(data); @@ -694,6 +603,7 @@ describe('payments/index', () => { expect(user.purchased.plan.dateCreated).to.eql(created); expect(user.purchased.plan.dateUpdated).to.not.eql(updated); expect(user.purchased.plan.customerId).to.eql('customer-id'); + expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(26); }); }); @@ -741,55 +651,20 @@ describe('payments/index', () => { }); context('Block subscription perks', () => { - it('adds block months to plan.consecutive.offset', async () => { - await api.createSubscription(data); - - expect(user.purchased.plan.consecutive.offset).to.eql(3); - }); - - it('does not add to plans.consecutive.offset if 1 month subscription', async () => { - data.sub.key = 'basic_earned'; - await api.createSubscription(data); - - expect(user.purchased.plan.consecutive.offset).to.eql(0); - }); - - it('resets plans.consecutive.offset if 1 month subscription', async () => { - user.purchased.plan.consecutive.offset = 1; - await user.save(); - data.sub.key = 'basic_earned'; - await api.createSubscription(data); - - expect(user.purchased.plan.consecutive.offset).to.eql(0); - }); - - it('adds 5 to plan.consecutive.gemCapExtra for 3 month block', async () => { - await api.createSubscription(data); - - expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(5); - }); - - it('adds 10 to plan.consecutive.gemCapExtra for 6 month block', async () => { - data.sub.key = 'basic_6mo'; - await api.createSubscription(data); - - expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(10); - }); - - it('adds 20 to plan.consecutive.gemCapExtra for 12 month block', async () => { + it('adds 26 to plan.consecutive.gemCapExtra for 12 month block', async () => { data.sub.key = 'basic_12mo'; await api.createSubscription(data); - expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(20); + expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(26); }); - it('does not raise plan.consecutive.gemCapExtra higher than 25', async () => { + it('does not raise plan.consecutive.gemCapExtra higher than 26', async () => { data.sub.key = 'basic_12mo'; await api.createSubscription(data); await api.createSubscription(data); - expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(25); + expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(26); }); it('adds a plan.consecutive.trinkets for 3 month block', async () => { @@ -798,20 +673,29 @@ describe('payments/index', () => { expect(user.purchased.plan.consecutive.trinkets).to.eql(1); }); - it('adds 2 plan.consecutive.trinkets for 6 month block', async () => { + it('adds 1 plan.consecutive.trinkets for 6 month block', async () => { data.sub.key = 'basic_6mo'; await api.createSubscription(data); - expect(user.purchased.plan.consecutive.trinkets).to.eql(2); + expect(user.purchased.plan.consecutive.trinkets).to.eql(1); }); - it('adds 4 plan.consecutive.trinkets for 12 month block', async () => { + it('adds 1 plan.consecutive.trinkets for 12 month block if they had promo', async () => { + user.purchased.plan.hourglassPromoReceived = new Date(); data.sub.key = 'basic_12mo'; await api.createSubscription(data); - expect(user.purchased.plan.consecutive.trinkets).to.eql(4); + expect(user.purchased.plan.consecutive.trinkets).to.eql(1); + }); + + it('adds 12 plan.consecutive.trinkets for 12 month block', async () => { + data.sub.key = 'basic_12mo'; + + await api.createSubscription(data); + + expect(user.purchased.plan.consecutive.trinkets).to.eql(13); }); context('Upgrades subscription', () => { @@ -819,70 +703,38 @@ describe('payments/index', () => { beforeEach(async () => { data.updatedFrom = { logic: 'payDifference' }; }); - it('Adds 10 to plan.consecutive.gemCapExtra from basic_earned to basic_6mo', async () => { - data.sub.key = 'basic_earned'; - expect(user.purchased.plan.planId).to.not.exist; - - await api.createSubscription(data); - - expect(user.purchased.plan.planId).to.eql('basic_earned'); - expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(0); - - data.sub.key = 'basic_6mo'; - data.updatedFrom.key = 'basic_earned'; - await api.createSubscription(data); - expect(user.purchased.plan.planId).to.eql('basic_6mo'); - expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(10); - }); - - it('Adds 15 to plan.consecutive.gemCapExtra when upgrading from basic_3mo to basic_12mo', async () => { + it('Adds 26 to plan.consecutive.gemCapExtra when upgrading from basic_3mo to basic_12mo', async () => { expect(user.purchased.plan.planId).to.not.exist; await api.createSubscription(data); expect(user.purchased.plan.planId).to.eql('basic_3mo'); - expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(5); + expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(0); data.sub.key = 'basic_12mo'; data.updatedFrom.key = 'basic_3mo'; await api.createSubscription(data); expect(user.purchased.plan.planId).to.eql('basic_12mo'); - expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(20); + expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(26); }); - it('Adds 2 to plan.consecutive.trinkets from basic_earned to basic_6mo', async () => { - data.sub.key = 'basic_earned'; - expect(user.purchased.plan.planId).to.not.exist; - - await api.createSubscription(data); - - expect(user.purchased.plan.planId).to.eql('basic_earned'); - expect(user.purchased.plan.consecutive.trinkets).to.eql(0); - - data.sub.key = 'basic_6mo'; - data.updatedFrom.key = 'basic_earned'; - await api.createSubscription(data); - expect(user.purchased.plan.planId).to.eql('basic_6mo'); - expect(user.purchased.plan.consecutive.trinkets).to.eql(2); - }); - - it('Adds 2 to plan.consecutive.trinkets when upgrading from basic_6mo to basic_12mo', async () => { + it('Adds 12 to plan.consecutive.trinkets when upgrading from basic_6mo to basic_12mo', async () => { data.sub.key = 'basic_6mo'; expect(user.purchased.plan.planId).to.not.exist; await api.createSubscription(data); expect(user.purchased.plan.planId).to.eql('basic_6mo'); - expect(user.purchased.plan.consecutive.trinkets).to.eql(2); + expect(user.purchased.plan.consecutive.trinkets).to.eql(1); data.sub.key = 'basic_12mo'; data.updatedFrom.key = 'basic_6mo'; await api.createSubscription(data); expect(user.purchased.plan.planId).to.eql('basic_12mo'); - expect(user.purchased.plan.consecutive.trinkets).to.eql(4); + expect(user.purchased.plan.consecutive.trinkets).to.eql(13); }); - it('Adds 3 to plan.consecutive.trinkets when upgrading from basic_3mo to basic_12mo', async () => { + it('Adds 12 to plan.consecutive.trinkets when upgrading from basic_3mo to basic_12mo', async () => { expect(user.purchased.plan.planId).to.not.exist; await api.createSubscription(data); @@ -894,7 +746,7 @@ describe('payments/index', () => { data.updatedFrom.key = 'basic_3mo'; await api.createSubscription(data); expect(user.purchased.plan.planId).to.eql('basic_12mo'); - expect(user.purchased.plan.consecutive.trinkets).to.eql(4); + expect(user.purchased.plan.consecutive.trinkets).to.eql(13); }); }); @@ -902,70 +754,39 @@ describe('payments/index', () => { beforeEach(async () => { data.updatedFrom = { logic: 'payFull' }; }); - it('Adds 10 to plan.consecutive.gemCapExtra from basic_earned to basic_6mo', async () => { - data.sub.key = 'basic_earned'; - expect(user.purchased.plan.planId).to.not.exist; - await api.createSubscription(data); - - expect(user.purchased.plan.planId).to.eql('basic_earned'); - expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(0); - - data.sub.key = 'basic_6mo'; - data.updatedFrom.key = 'basic_earned'; - await api.createSubscription(data); - expect(user.purchased.plan.planId).to.eql('basic_6mo'); - expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(10); - }); - - it('Adds 20 to plan.consecutive.gemCapExtra when upgrading from basic_3mo to basic_12mo', async () => { + it('Adds 26 to plan.consecutive.gemCapExtra when upgrading from basic_3mo to basic_12mo', async () => { expect(user.purchased.plan.planId).to.not.exist; await api.createSubscription(data); expect(user.purchased.plan.planId).to.eql('basic_3mo'); - expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(5); + expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(0); data.sub.key = 'basic_12mo'; data.updatedFrom.key = 'basic_3mo'; await api.createSubscription(data); expect(user.purchased.plan.planId).to.eql('basic_12mo'); - expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(25); + expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(26); }); - it('Adds 2 to plan.consecutive.trinkets from basic_earned to basic_6mo', async () => { - data.sub.key = 'basic_earned'; - expect(user.purchased.plan.planId).to.not.exist; - - await api.createSubscription(data); - - expect(user.purchased.plan.planId).to.eql('basic_earned'); - expect(user.purchased.plan.consecutive.trinkets).to.eql(0); - - data.sub.key = 'basic_6mo'; - data.updatedFrom.key = 'basic_earned'; - await api.createSubscription(data); - expect(user.purchased.plan.planId).to.eql('basic_6mo'); - expect(user.purchased.plan.consecutive.trinkets).to.eql(2); - }); - - it('Adds 4 to plan.consecutive.trinkets when upgrading from basic_6mo to basic_12mo', async () => { + it('Adds 12 to plan.consecutive.trinkets when upgrading from basic_6mo to basic_12mo', async () => { data.sub.key = 'basic_6mo'; expect(user.purchased.plan.planId).to.not.exist; await api.createSubscription(data); expect(user.purchased.plan.planId).to.eql('basic_6mo'); - expect(user.purchased.plan.consecutive.trinkets).to.eql(2); + expect(user.purchased.plan.consecutive.trinkets).to.eql(1); data.sub.key = 'basic_12mo'; data.updatedFrom.key = 'basic_6mo'; await api.createSubscription(data); expect(user.purchased.plan.planId).to.eql('basic_12mo'); - expect(user.purchased.plan.consecutive.trinkets).to.eql(6); + expect(user.purchased.plan.consecutive.trinkets).to.eql(13); }); - it('Adds 4 to plan.consecutive.trinkets when upgrading from basic_3mo to basic_12mo', async () => { + it('Adds 12 to plan.consecutive.trinkets when upgrading from basic_3mo to basic_12mo', async () => { expect(user.purchased.plan.planId).to.not.exist; await api.createSubscription(data); @@ -977,7 +798,7 @@ describe('payments/index', () => { data.updatedFrom.key = 'basic_3mo'; await api.createSubscription(data); expect(user.purchased.plan.planId).to.eql('basic_12mo'); - expect(user.purchased.plan.consecutive.trinkets).to.eql(5); + expect(user.purchased.plan.consecutive.trinkets).to.eql(13); }); }); @@ -988,30 +809,13 @@ describe('payments/index', () => { data.updatedFrom = { logic: 'refundAndRepay' }; }); context('Upgrades within first half of subscription', () => { - it('Adds 10 to plan.consecutive.gemCapExtra from basic_earned to basic_6mo', async () => { - data.sub.key = 'basic_earned'; - expect(user.purchased.plan.planId).to.not.exist; - await api.createSubscription(data); - - expect(user.purchased.plan.planId).to.eql('basic_earned'); - expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(0); - - data.sub.key = 'basic_6mo'; - data.updatedFrom.key = 'basic_earned'; - clock.restore(); - clock = sinon.useFakeTimers(new Date('2022-01-10')); - await api.createSubscription(data); - expect(user.purchased.plan.planId).to.eql('basic_6mo'); - expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(10); - }); - - it('Adds 15 to plan.consecutive.gemCapExtra when upgrading from basic_3mo to basic_12mo', async () => { + it('Adds 26 to plan.consecutive.gemCapExtra when upgrading from basic_3mo to basic_12mo', async () => { expect(user.purchased.plan.planId).to.not.exist; await api.createSubscription(data); expect(user.purchased.plan.planId).to.eql('basic_3mo'); - expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(5); + expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(0); data.sub.key = 'basic_12mo'; data.updatedFrom.key = 'basic_3mo'; @@ -1019,28 +823,10 @@ describe('payments/index', () => { clock = sinon.useFakeTimers(new Date('2022-02-05')); await api.createSubscription(data); expect(user.purchased.plan.planId).to.eql('basic_12mo'); - expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(20); + expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(26); }); - it('Adds 2 to plan.consecutive.trinkets from basic_earned to basic_6mo', async () => { - data.sub.key = 'basic_earned'; - expect(user.purchased.plan.planId).to.not.exist; - - await api.createSubscription(data); - - expect(user.purchased.plan.planId).to.eql('basic_earned'); - expect(user.purchased.plan.consecutive.trinkets).to.eql(0); - - data.sub.key = 'basic_6mo'; - data.updatedFrom.key = 'basic_earned'; - clock.restore(); - clock = sinon.useFakeTimers(new Date('2022-01-08')); - await api.createSubscription(data); - expect(user.purchased.plan.planId).to.eql('basic_6mo'); - expect(user.purchased.plan.consecutive.trinkets).to.eql(2); - }); - - it('Adds 3 to plan.consecutive.trinkets when upgrading from basic_3mo to basic_12mo', async () => { + it('Adds 12 to plan.consecutive.trinkets when upgrading from basic_3mo to basic_12mo', async () => { expect(user.purchased.plan.planId).to.not.exist; await api.createSubscription(data); @@ -1054,17 +840,17 @@ describe('payments/index', () => { clock = sinon.useFakeTimers(new Date('2022-01-31')); await api.createSubscription(data); expect(user.purchased.plan.planId).to.eql('basic_12mo'); - expect(user.purchased.plan.consecutive.trinkets).to.eql(4); + expect(user.purchased.plan.consecutive.trinkets).to.eql(13); }); - it('Adds 2 to plan.consecutive.trinkets when upgrading from basic_6mo to basic_12mo', async () => { + it('Adds 12 to plan.consecutive.trinkets when upgrading from basic_6mo to basic_12mo', async () => { data.sub.key = 'basic_6mo'; expect(user.purchased.plan.planId).to.not.exist; await api.createSubscription(data); expect(user.purchased.plan.planId).to.eql('basic_6mo'); - expect(user.purchased.plan.consecutive.trinkets).to.eql(2); + expect(user.purchased.plan.consecutive.trinkets).to.eql(1); data.sub.key = 'basic_12mo'; data.updatedFrom.key = 'basic_6mo'; @@ -1072,35 +858,17 @@ describe('payments/index', () => { clock = sinon.useFakeTimers(new Date('2022-01-28')); await api.createSubscription(data); expect(user.purchased.plan.planId).to.eql('basic_12mo'); - expect(user.purchased.plan.consecutive.trinkets).to.eql(4); + expect(user.purchased.plan.consecutive.trinkets).to.eql(13); }); - it('Adds 2 to plan.consecutive.trinkets from basic_earned to basic_6mo after initial cycle', async () => { - data.sub.key = 'basic_earned'; - expect(user.purchased.plan.planId).to.not.exist; - - await api.createSubscription(data); - - expect(user.purchased.plan.planId).to.eql('basic_earned'); - expect(user.purchased.plan.consecutive.trinkets).to.eql(0); - - data.sub.key = 'basic_6mo'; - data.updatedFrom.key = 'basic_earned'; - clock.restore(); - clock = sinon.useFakeTimers(new Date('2024-01-08')); - await api.createSubscription(data); - expect(user.purchased.plan.planId).to.eql('basic_6mo'); - expect(user.purchased.plan.consecutive.trinkets).to.eql(2); - }); - - it('Adds 2 to plan.consecutive.trinkets when upgrading from basic_6mo to basic_12mo after initial cycle', async () => { + it('2 plan.consecutive.trinkets when upgrading from basic_6mo to basic_12mo after initial cycle', async () => { data.sub.key = 'basic_6mo'; expect(user.purchased.plan.planId).to.not.exist; await api.createSubscription(data); expect(user.purchased.plan.planId).to.eql('basic_6mo'); - expect(user.purchased.plan.consecutive.trinkets).to.eql(2); + expect(user.purchased.plan.consecutive.trinkets).to.eql(1); data.sub.key = 'basic_12mo'; data.updatedFrom.key = 'basic_6mo'; @@ -1108,10 +876,10 @@ describe('payments/index', () => { clock = sinon.useFakeTimers(new Date('2022-08-28')); await api.createSubscription(data); expect(user.purchased.plan.planId).to.eql('basic_12mo'); - expect(user.purchased.plan.consecutive.trinkets).to.eql(4); + expect(user.purchased.plan.consecutive.trinkets).to.eql(13); }); - it('Adds 3 to plan.consecutive.trinkets when upgrading from basic_3mo to basic_12mo after initial cycle', async () => { + it('Adds 12 to plan.consecutive.trinkets when upgrading from basic_3mo to basic_12mo after initial cycle', async () => { expect(user.purchased.plan.planId).to.not.exist; await api.createSubscription(data); @@ -1125,11 +893,11 @@ describe('payments/index', () => { clock = sinon.useFakeTimers(new Date('2022-07-31')); await api.createSubscription(data); expect(user.purchased.plan.planId).to.eql('basic_12mo'); - expect(user.purchased.plan.consecutive.trinkets).to.eql(4); + expect(user.purchased.plan.consecutive.trinkets).to.eql(13); }); }); context('Upgrades within second half of subscription', () => { - it('Adds 10 to plan.consecutive.gemCapExtra from basic_earned to basic_6mo', async () => { + it('Adds 0 to plan.consecutive.gemCapExtra from basic_earned to basic_6mo', async () => { data.sub.key = 'basic_earned'; expect(user.purchased.plan.planId).to.not.exist; @@ -1144,16 +912,16 @@ describe('payments/index', () => { clock = sinon.useFakeTimers(new Date('2022-01-20')); await api.createSubscription(data); expect(user.purchased.plan.planId).to.eql('basic_6mo'); - expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(10); + expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(0); }); - it('Adds 20 to plan.consecutive.gemCapExtra when upgrading from basic_3mo to basic_12mo', async () => { + it('Adds 26 to plan.consecutive.gemCapExtra when upgrading from basic_3mo to basic_12mo', async () => { expect(user.purchased.plan.planId).to.not.exist; await api.createSubscription(data); expect(user.purchased.plan.planId).to.eql('basic_3mo'); - expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(5); + expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(0); data.sub.key = 'basic_12mo'; data.updatedFrom.key = 'basic_3mo'; @@ -1161,17 +929,17 @@ describe('payments/index', () => { clock = sinon.useFakeTimers(new Date('2022-02-24')); await api.createSubscription(data); expect(user.purchased.plan.planId).to.eql('basic_12mo'); - expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(25); + expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(26); }); - it('Adds 2 to plan.consecutive.trinkets from basic_earned to basic_6mo', async () => { + it('Adds 0 to plan.consecutive.trinkets from basic_earned to basic_6mo', async () => { data.sub.key = 'basic_earned'; expect(user.purchased.plan.planId).to.not.exist; await api.createSubscription(data); expect(user.purchased.plan.planId).to.eql('basic_earned'); - expect(user.purchased.plan.consecutive.trinkets).to.eql(0); + expect(user.purchased.plan.consecutive.trinkets).to.eql(1); data.sub.key = 'basic_6mo'; data.updatedFrom.key = 'basic_earned'; @@ -1179,17 +947,17 @@ describe('payments/index', () => { clock = sinon.useFakeTimers(new Date('2022-01-28')); await api.createSubscription(data); expect(user.purchased.plan.planId).to.eql('basic_6mo'); - expect(user.purchased.plan.consecutive.trinkets).to.eql(2); + expect(user.purchased.plan.consecutive.trinkets).to.eql(1); }); - it('Adds 4 to plan.consecutive.trinkets when upgrading from basic_6mo to basic_12mo', async () => { + it('Adds 12 to plan.consecutive.trinkets when upgrading from basic_6mo to basic_12mo', async () => { data.sub.key = 'basic_6mo'; expect(user.purchased.plan.planId).to.not.exist; await api.createSubscription(data); expect(user.purchased.plan.planId).to.eql('basic_6mo'); - expect(user.purchased.plan.consecutive.trinkets).to.eql(2); + expect(user.purchased.plan.consecutive.trinkets).to.eql(1); data.sub.key = 'basic_12mo'; data.updatedFrom.key = 'basic_6mo'; @@ -1197,10 +965,10 @@ describe('payments/index', () => { clock = sinon.useFakeTimers(new Date('2022-05-28')); await api.createSubscription(data); expect(user.purchased.plan.planId).to.eql('basic_12mo'); - expect(user.purchased.plan.consecutive.trinkets).to.eql(6); + expect(user.purchased.plan.consecutive.trinkets).to.eql(13); }); - it('Adds 4 to plan.consecutive.trinkets when upgrading from basic_3mo to basic_12mo', async () => { + it('Adds 12 to plan.consecutive.trinkets when upgrading from basic_3mo to basic_12mo', async () => { expect(user.purchased.plan.planId).to.not.exist; await api.createSubscription(data); @@ -1214,17 +982,17 @@ describe('payments/index', () => { clock = sinon.useFakeTimers(new Date('2022-03-03')); await api.createSubscription(data); expect(user.purchased.plan.planId).to.eql('basic_12mo'); - expect(user.purchased.plan.consecutive.trinkets).to.eql(5); + expect(user.purchased.plan.consecutive.trinkets).to.eql(13); }); - it('Adds 2 to plan.consecutive.trinkets from basic_earned to basic_6mo after initial cycle', async () => { + it('Adds 0 to plan.consecutive.trinkets from basic_earned to basic_6mo after initial cycle', async () => { data.sub.key = 'basic_earned'; expect(user.purchased.plan.planId).to.not.exist; await api.createSubscription(data); expect(user.purchased.plan.planId).to.eql('basic_earned'); - expect(user.purchased.plan.consecutive.trinkets).to.eql(0); + expect(user.purchased.plan.consecutive.trinkets).to.eql(1); data.sub.key = 'basic_6mo'; data.updatedFrom.key = 'basic_earned'; @@ -1232,17 +1000,17 @@ describe('payments/index', () => { clock = sinon.useFakeTimers(new Date('2022-05-28')); await api.createSubscription(data); expect(user.purchased.plan.planId).to.eql('basic_6mo'); - expect(user.purchased.plan.consecutive.trinkets).to.eql(2); + expect(user.purchased.plan.consecutive.trinkets).to.eql(1); }); - it('Adds 4 to plan.consecutive.trinkets when upgrading from basic_6mo to basic_12mo after initial cycle', async () => { + it('Adds 12 to plan.consecutive.trinkets when upgrading from basic_6mo to basic_12mo after initial cycle', async () => { data.sub.key = 'basic_6mo'; expect(user.purchased.plan.planId).to.not.exist; await api.createSubscription(data); expect(user.purchased.plan.planId).to.eql('basic_6mo'); - expect(user.purchased.plan.consecutive.trinkets).to.eql(2); + expect(user.purchased.plan.consecutive.trinkets).to.eql(1); data.sub.key = 'basic_12mo'; data.updatedFrom.key = 'basic_6mo'; @@ -1250,10 +1018,10 @@ describe('payments/index', () => { clock = sinon.useFakeTimers(new Date('2023-05-28')); await api.createSubscription(data); expect(user.purchased.plan.planId).to.eql('basic_12mo'); - expect(user.purchased.plan.consecutive.trinkets).to.eql(6); + expect(user.purchased.plan.consecutive.trinkets).to.eql(13); }); - it('Adds 4 to plan.consecutive.trinkets when upgrading from basic_3mo to basic_12mo after initial cycle', async () => { + it('Adds 12 to plan.consecutive.trinkets when upgrading from basic_3mo to basic_12mo after initial cycle', async () => { expect(user.purchased.plan.planId).to.not.exist; await api.createSubscription(data); @@ -1267,7 +1035,7 @@ describe('payments/index', () => { clock = sinon.useFakeTimers(new Date('2023-09-03')); await api.createSubscription(data); expect(user.purchased.plan.planId).to.eql('basic_12mo'); - expect(user.purchased.plan.consecutive.trinkets).to.eql(5); + expect(user.purchased.plan.consecutive.trinkets).to.eql(13); }); }); afterEach(async () => { @@ -1277,22 +1045,6 @@ describe('payments/index', () => { }); context('Downgrades subscription', () => { - it('does not remove from plan.consecutive.gemCapExtra from basic_6mo to basic_earned', async () => { - data.sub.key = 'basic_6mo'; - expect(user.purchased.plan.planId).to.not.exist; - - await api.createSubscription(data); - - expect(user.purchased.plan.planId).to.eql('basic_6mo'); - expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(10); - - data.sub.key = 'basic_earned'; - data.updatedFrom = { key: 'basic_6mo' }; - await api.createSubscription(data); - expect(user.purchased.plan.planId).to.eql('basic_earned'); - expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(10); - }); - it('does not remove from plan.consecutive.gemCapExtra from basic_12mo to basic_3mo', async () => { expect(user.purchased.plan.planId).to.not.exist; @@ -1300,28 +1052,12 @@ describe('payments/index', () => { await api.createSubscription(data); expect(user.purchased.plan.planId).to.eql('basic_12mo'); - expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(20); + expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(26); data.sub.key = 'basic_3mo'; data.updatedFrom = { key: 'basic_12mo' }; await api.createSubscription(data); - expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(20); - }); - - it('does not remove from plan.consecutive.trinkets from basic_6mo to basic_earned', async () => { - data.sub.key = 'basic_6mo'; - expect(user.purchased.plan.planId).to.not.exist; - - await api.createSubscription(data); - - expect(user.purchased.plan.planId).to.eql('basic_6mo'); - expect(user.purchased.plan.consecutive.trinkets).to.eql(2); - - data.sub.key = 'basic_earned'; - data.updatedFrom = { key: 'basic_6mo' }; - await api.createSubscription(data); - expect(user.purchased.plan.planId).to.eql('basic_earned'); - expect(user.purchased.plan.consecutive.trinkets).to.eql(2); + expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(26); }); it('does not remove from plan.consecutive.trinkets from basic_12mo to basic_3mo', async () => { @@ -1331,12 +1067,12 @@ describe('payments/index', () => { await api.createSubscription(data); expect(user.purchased.plan.planId).to.eql('basic_12mo'); - expect(user.purchased.plan.consecutive.trinkets).to.eql(4); + expect(user.purchased.plan.consecutive.trinkets).to.eql(13); data.sub.key = 'basic_3mo'; data.updatedFrom = { key: 'basic_12mo' }; await api.createSubscription(data); - expect(user.purchased.plan.consecutive.trinkets).to.eql(4); + expect(user.purchased.plan.consecutive.trinkets).to.eql(13); }); }); }); @@ -1453,6 +1189,32 @@ describe('payments/index', () => { expect(user.purchased.plan.extraMonths).to.eql(0); }); + it('does not reset gemCapExtra', async () => { + user.purchased.plan.consecutive.gemCapExtra = 12; + + await api.cancelSubscription(data); + + expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(12); + }); + + it('initializes gemCapExtra', async () => { + await api.cancelSubscription(data); + expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(0); + }); + + it('initializes hourglasses', async () => { + await api.cancelSubscription(data); + expect(user.purchased.plan.consecutive.trinkets).to.eql(0); + }); + + it('does not reset owned hourglasses', async () => { + user.purchased.plan.consecutive.trinkets = 12; + + await api.cancelSubscription(data); + + expect(user.purchased.plan.consecutive.trinkets).to.eql(12); + }); + it('sends an email', async () => { await api.cancelSubscription(data); diff --git a/test/api/unit/libs/payments/stripe/oneTimePayments.test.js b/test/api/unit/libs/payments/stripe/oneTimePayments.test.js index c52cd162b4..7ccb85b8ee 100644 --- a/test/api/unit/libs/payments/stripe/oneTimePayments.test.js +++ b/test/api/unit/libs/payments/stripe/oneTimePayments.test.js @@ -308,6 +308,7 @@ describe('Stripe - One Time Payments', () => { customerId, paymentMethod: 'Gift', gift, + autoRenews: false, gemsBlock: undefined, }); }); diff --git a/test/api/unit/libs/payments/stripe/subscriptions.test.js b/test/api/unit/libs/payments/stripe/subscriptions.test.js index c135f94b27..3955037c02 100644 --- a/test/api/unit/libs/payments/stripe/subscriptions.test.js +++ b/test/api/unit/libs/payments/stripe/subscriptions.test.js @@ -173,6 +173,7 @@ describe('Stripe - Subscriptions', () => { paymentMethod: 'Stripe', sub: sinon.match({ ...sub }), groupId: null, + autoRenews: true, }); }); @@ -197,6 +198,7 @@ describe('Stripe - Subscriptions', () => { paymentMethod: 'Stripe', sub: sinon.match({ ...sub }), groupId, + autoRenews: true, }); }); @@ -231,6 +233,7 @@ describe('Stripe - Subscriptions', () => { paymentMethod: 'Stripe', sub: sinon.match({ ...sub }), groupId, + autoRenews: true, }); }); }); diff --git a/test/api/v3/integration/user/buy/POST-user_buy_mystery_set.test.js b/test/api/v3/integration/user/buy/POST-user_buy_mystery_set.test.js index d4df2c93b3..cd0576d13a 100644 --- a/test/api/v3/integration/user/buy/POST-user_buy_mystery_set.test.js +++ b/test/api/v3/integration/user/buy/POST-user_buy_mystery_set.test.js @@ -31,7 +31,7 @@ describe('POST /user/buy-mystery-set/:key', () => { expect(res.data).to.eql({ items: JSON.parse(JSON.stringify(user.items)), // otherwise dates can't be compared - purchasedPlanConsecutive: user.purchased.plan.consecutive, + purchasedPlanConsecutive: JSON.parse(JSON.stringify(user.purchased.plan.consecutive)), }); expect(res.message).to.equal(t('hourglassPurchaseSet')); }); diff --git a/test/common/libs/cron.test.js b/test/common/libs/cron.test.js index e8be92fceb..c2e8f7b32b 100644 --- a/test/common/libs/cron.test.js +++ b/test/common/libs/cron.test.js @@ -183,8 +183,6 @@ describe('cron utility functions', () => { }); describe('getPlanContext', () => { - const now = new Date(2022, 5, 1); - function baseUserData (count, offset, planId) { return { purchased: { @@ -192,7 +190,7 @@ describe('cron utility functions', () => { consecutive: { count, offset, - gemCapExtra: 25, + gemCapExtra: 26, trinkets: 19, }, quantity: 1, @@ -213,52 +211,19 @@ describe('cron utility functions', () => { }; } - it('monthly plan, next date in 3 months', () => { + it('elapsedMonths is 0 if its the same month', () => { const user = baseUserData(60, 0, 'group_plan_auto'); - user.purchased.plan.perkMonthCount = 0; - const planContext = getPlanContext(user, now); - - expect(planContext.nextHourglassDate) - .to.be.sameMoment('2022-08-10T02:00:00.144Z'); + const planContext = getPlanContext(user, new Date(2022, 4, 20)); + expect(planContext.elapsedMonths).to.equal(0); }); - it('monthly plan, next date in 1 month', () => { - const user = baseUserData(62, 0, 'group_plan_auto'); - user.purchased.plan.perkMonthCount = 2; + it('elapsedMonths is 1 after one month', () => { + const user = baseUserData(60, 0, 'group_plan_auto'); - const planContext = getPlanContext(user, now); + const planContext = getPlanContext(user, new Date(2022, 5, 11)); - expect(planContext.nextHourglassDate) - .to.be.sameMoment('2022-06-10T02:00:00.144Z'); - }); - - it('multi-month plan, no offset', () => { - const user = baseUserData(60, 0, 'basic_3mo'); - - const planContext = getPlanContext(user, now); - - expect(planContext.nextHourglassDate) - .to.be.sameMoment('2022-06-10T02:00:00.144Z'); - }); - - it('multi-month plan with offset', () => { - const user = baseUserData(60, 1, 'basic_3mo'); - - const planContext = getPlanContext(user, now); - - expect(planContext.nextHourglassDate) - .to.be.sameMoment('2022-07-10T02:00:00.144Z'); - }); - - it('multi-month plan with perk count', () => { - const user = baseUserData(60, 1, 'basic_3mo'); - user.purchased.plan.perkMonthCount = 2; - - const planContext = getPlanContext(user, now); - - expect(planContext.nextHourglassDate) - .to.be.sameMoment('2022-07-10T02:00:00.144Z'); + expect(planContext.elapsedMonths).to.equal(1); }); }); }); diff --git a/website/client/src/assets/images/confetti.png b/website/client/src/assets/images/confetti.png new file mode 100644 index 0000000000000000000000000000000000000000..f2bcbc52e35b00cc3a6732a39a9bbf7356321aa0 GIT binary patch literal 3294 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 diff --git a/website/client/src/assets/scss/colors.scss b/website/client/src/assets/scss/colors.scss index 9ee2894a5a..4e1536efa2 100644 --- a/website/client/src/assets/scss/colors.scss +++ b/website/client/src/assets/scss/colors.scss @@ -78,15 +78,3 @@ $gold-color: #FFA624; $hourglass-color: #2995CD; $purple-task: #925cf3; - -.gray-200 { - color: $gray-200 !important; -} - -.purple-300 { - color: $purple-300 !important; -} - -.white { - color: $white !important; -} diff --git a/website/client/src/assets/scss/typography.scss b/website/client/src/assets/scss/typography.scss index 970fd41737..59b206aafd 100644 --- a/website/client/src/assets/scss/typography.scss +++ b/website/client/src/assets/scss/typography.scss @@ -86,3 +86,91 @@ h4 { .opacity-75 { opacity: 0.75; } + +.bg-gray-100 { + background-color: $gray-100 !important; +} + +.bg-gray-300 { + background-color: $gray-300 !important; +} + +.bg-gray-600 { + background-color: $gray-600 !important; +} + +.bg-gray-700 { + background-color: $gray-700 !important; +} + +.bg-green-10 { + background-color: $green-10 !important; +} + +.bg-green-100 { + background-color: $green-100 !important; +} + +.bg-purple-100 { + background-color: $purple-100 !important; +} + +.bg-purple-300 { + background-color: $purple-300 !important; +} + +.bg-white { + background-color: $white !important; +} + +.gray-10 { + color: $gray-10 !important; +} + +.gray-50 { + color: $gray-50 !important; +} + +.gray-200 { + color: $gray-200 !important; +} + +.gray-300 { + color: $gray-300 !important; +} + +.green-10 { + color: $green-10 !important; +} + +.maroon-50 { + color: $maroon-50 !important; +} + +.purple-200 { + color: $purple-200 !important; +} + +.purple-300 { + color: $purple-300 !important; +} + +.purple-600 { + color: $purple-600 !important; +} + +.teal-1 { + color: $teal-1 !important; +} + +.teal-10 { + color: $teal-10 !important; +} + +.yellow-10 { + color: $yellow-10 !important; +} + +.white { + color: $white !important; +} diff --git a/website/client/src/assets/svg/divider-stars.svg b/website/client/src/assets/svg/divider-stars.svg new file mode 100644 index 0000000000..648001dab4 --- /dev/null +++ b/website/client/src/assets/svg/divider-stars.svg @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/website/client/src/assets/svg/habitica-logo.svg b/website/client/src/assets/svg/habitica-logo.svg index 54b2acc5a1..01d80fb183 100644 --- a/website/client/src/assets/svg/habitica-logo.svg +++ b/website/client/src/assets/svg/habitica-logo.svg @@ -1,7 +1,5 @@ - - - - - - + + + + diff --git a/website/client/src/assets/svg/hourglass-sparkle-left.svg b/website/client/src/assets/svg/hourglass-sparkle-left.svg new file mode 100644 index 0000000000..c4d221fbb8 --- /dev/null +++ b/website/client/src/assets/svg/hourglass-sparkle-left.svg @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/website/client/src/assets/svg/hourglass-sparkle-right.svg b/website/client/src/assets/svg/hourglass-sparkle-right.svg new file mode 100644 index 0000000000..0388d0d736 --- /dev/null +++ b/website/client/src/assets/svg/hourglass-sparkle-right.svg @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/website/client/src/assets/svg/jackalope.svg b/website/client/src/assets/svg/jackalope.svg new file mode 100644 index 0000000000..5e351c4955 --- /dev/null +++ b/website/client/src/assets/svg/jackalope.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/website/client/src/assets/svg/stars-purple.svg b/website/client/src/assets/svg/stars-purple.svg new file mode 100644 index 0000000000..a6c799848d --- /dev/null +++ b/website/client/src/assets/svg/stars-purple.svg @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/website/client/src/assets/svg/subscriber-food.svg b/website/client/src/assets/svg/subscriber-food.svg new file mode 100644 index 0000000000..7991bab49f --- /dev/null +++ b/website/client/src/assets/svg/subscriber-food.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/website/client/src/assets/svg/subscriber-gems.svg b/website/client/src/assets/svg/subscriber-gems.svg index e57077f7d8..844f13598e 100644 --- a/website/client/src/assets/svg/subscriber-gems.svg +++ b/website/client/src/assets/svg/subscriber-gems.svg @@ -1,26 +1,29 @@ - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/website/client/src/assets/svg/subscriber-hourglasses.svg b/website/client/src/assets/svg/subscriber-hourglasses.svg index 79e993b4c6..27d8c94822 100644 --- a/website/client/src/assets/svg/subscriber-hourglasses.svg +++ b/website/client/src/assets/svg/subscriber-hourglasses.svg @@ -1,26 +1,20 @@ - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + diff --git a/website/client/src/components/admin-panel/user-support/subscriptionAndPerks.vue b/website/client/src/components/admin-panel/user-support/subscriptionAndPerks.vue index 2d3fd5679c..7e959a1085 100644 --- a/website/client/src/components/admin-panel/user-support/subscriptionAndPerks.vue +++ b/website/client/src/components/admin-panel/user-support/subscriptionAndPerks.vue @@ -91,11 +91,11 @@
- +
+ +
+ + {{ dateFormat(hero.purchased.plan.hourglassPromoReceived) }} + +
+
-
- -
- -
-
-
- - {{ nextHourglassDate }} -
@@ -172,7 +156,7 @@ Total Gem cap: - {{ Number(hero.purchased.plan.consecutive.gemCapExtra) + 25 }} + {{ Number(hero.purchased.plan.consecutive.gemCapExtra) + 24 }}
@@ -185,7 +169,7 @@ class="form-control" type="number" min="0" - :max="hero.purchased.plan.consecutive.gemCapExtra + 25" + :max="hero.purchased.plan.consecutive.gemCapExtra + 24" step="1" >
@@ -268,6 +252,7 @@ export default { nextHourglassDate () { const currentPlanContext = getPlanContext(this.hero, new Date()); + if (!currentPlanContext.nextHourglassDate) return 'N/A'; return currentPlanContext.nextHourglassDate.format('MMMM YYYY'); }, }, diff --git a/website/client/src/components/group-plans/billing.vue b/website/client/src/components/group-plans/billing.vue index cdca5dd87e..a9fe133d2b 100644 --- a/website/client/src/components/group-plans/billing.vue +++ b/website/client/src/components/group-plans/billing.vue @@ -27,13 +27,13 @@ {{ $t('consecutiveSubscription') }}
    -
  • {{ $t('consecutiveMonths') }} {{ group.purchased.plan.consecutive.count + group.purchased.plan.consecutive.offset }}
  • +
  • {{ $t('consecutiveMonths') }} {{ group.purchased.plan.consecutive.count }}
  • {{ $t('gemCapExtra') }} {{ group.purchased.plan.consecutive.gemCapExtra }}
  • {{ $t('mysticHourglasses') }} {{ group.purchased.plan.consecutive.trinkets }}
diff --git a/website/client/src/components/notifications.vue b/website/client/src/components/notifications.vue index 43dfeb901f..f967e6fca8 100644 --- a/website/client/src/components/notifications.vue +++ b/website/client/src/components/notifications.vue @@ -529,7 +529,7 @@ export default { // List of prompts for user on changes. // Sounds like we may need a refactor here, but it is clean for now - if (!this.user.flags.welcomed && !this.$route.name.includes('groupPlan')) { + if (!this.user.flags.welcomed && !this.$route?.name.includes('groupPlan')) { if (this.$store.state.avatarEditorOptions) { this.$store.state.avatarEditorOptions.editingUser = false; } diff --git a/website/client/src/components/payments/buttons/list.vue b/website/client/src/components/payments/buttons/list.vue index 5ec3eb2d28..784e947a22 100644 --- a/website/client/src/components/payments/buttons/list.vue +++ b/website/client/src/components/payments/buttons/list.vue @@ -1,5 +1,5 @@