begin refactoring

This commit is contained in:
Phillip Thelen
2024-08-08 10:36:50 +02:00
committed by Kalista Payne
parent 96623608d0
commit 930d875ae9
7 changed files with 61 additions and 154 deletions
+18 -22
View File
@@ -197,16 +197,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 () => {
@@ -229,7 +227,7 @@ describe('cron', async () => {
user1.purchased.plan.planId = 'basic';
user1.purchased.plan.consecutive.count = 0;
user1.purchased.plan.perkMonthCount = 0;
user1.purchased.plan.consecutive.trinkets = 0;
user1.purchased.plan.consecutive.trinkets = 1;
user1.purchased.plan.consecutive.gemCapExtra = 0;
});
@@ -256,7 +254,7 @@ describe('cron', async () => {
user: user1, tasksByType, daysMissed, analytics,
});
expect(user1.purchased.plan.consecutive.count).to.equal(10);
expect(user1.purchased.plan.consecutive.trinkets).to.equal(10);
expect(user1.purchased.plan.consecutive.trinkets).to.equal(11);
expect(user1.purchased.plan.consecutive.gemCapExtra).to.equal(20);
});
});
@@ -281,7 +279,7 @@ describe('cron', async () => {
user3.purchased.plan.perkMonthCount = 0;
user3.purchased.plan.consecutive.count = 0;
user3.purchased.plan.consecutive.trinkets = 1;
user3.purchased.plan.consecutive.gemCapExtra = 5;
user3.purchased.plan.consecutive.gemCapExtra = 0;
});
it('increments consecutive benefits', async () => {
@@ -304,7 +302,7 @@ describe('cron', async () => {
user: user3, tasksByType, daysMissed, analytics,
});
expect(user3.purchased.plan.consecutive.count).to.equal(10);
expect(user3.purchased.plan.consecutive.trinkets).to.equal(10);
expect(user3.purchased.plan.consecutive.trinkets).to.equal(11);
expect(user3.purchased.plan.consecutive.gemCapExtra).to.equal(20);
});
});
@@ -328,8 +326,8 @@ describe('cron', async () => {
user6.purchased.plan.planId = 'google_6mo';
user6.purchased.plan.perkMonthCount = 0;
user6.purchased.plan.consecutive.count = 0;
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('increments benefits', async () => {
@@ -362,8 +360,8 @@ 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.trinkets = 4;
user12.purchased.plan.consecutive.gemCapExtra = 20;
user12.purchased.plan.consecutive.trinkets = 1;
user12.purchased.plan.consecutive.gemCapExtra = 26;
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')
@@ -373,7 +371,7 @@ describe('cron', async () => {
user: user12, tasksByType, daysMissed, analytics,
});
expect(user12.purchased.plan.consecutive.count).to.equal(1);
expect(user12.purchased.plan.consecutive.trinkets).to.equal(13);
expect(user12.purchased.plan.consecutive.trinkets).to.equal(2);
expect(user12.purchased.plan.consecutive.gemCapExtra).to.equal(26);
});
@@ -385,8 +383,8 @@ describe('cron', async () => {
user: user12, tasksByType, daysMissed, analytics,
});
expect(user12.purchased.plan.consecutive.count).to.equal(10);
expect(user12.purchased.plan.consecutive.trinkets).to.equal(22);
expect(user12.purchased.plan.consecutive.gemCapExtra).to.equal(65);
expect(user12.purchased.plan.consecutive.trinkets).to.equal(11);
expect(user12.purchased.plan.consecutive.gemCapExtra).to.equal(26);
});
});
@@ -410,7 +408,7 @@ describe('cron', async () => {
user3g.purchased.plan.planId = null;
user3g.purchased.plan.consecutive.count = 0;
user3g.purchased.plan.consecutive.trinkets = 1;
user3g.purchased.plan.consecutive.gemCapExtra = 5;
user3g.purchased.plan.consecutive.gemCapExtra = 0;
it('increments benefits', async () => {
clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(1, 'months')
@@ -419,10 +417,9 @@ describe('cron', async () => {
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.consecutive.count).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 () => {
@@ -434,9 +431,8 @@ 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
expect(user3g.purchased.plan.consecutive.trinkets).to.equal(2);
expect(user3g.purchased.plan.consecutive.gemCapExtra).to.equal(2);
});
});
});
+21 -16
View File
@@ -13,7 +13,7 @@ import {
import * as worldState from '../../../../../website/server/libs/worldState';
import { TransactionModel } from '../../../../../website/server/models/transaction';
describe('payments/index', () => {
describe.only('payments/index', () => {
let user;
let group;
let data;
@@ -108,10 +108,6 @@ 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);
@@ -199,13 +195,13 @@ describe('payments/index', () => {
it('gives user 1 hourglass if they have no active subscription', async () => {
await api.createSubscription(data);
expect(recipient.purchased.plan.trinket).to.eql(1);
expect(recipient.purchased.plan.consecutive.trinkets).to.eql(1);
});
it('does not giec any hourglasses if they have an active subscription', async () => {
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.trinket).to.eql(plan.trinket);
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 () => {
@@ -666,6 +662,15 @@ describe('payments/index', () => {
expect(user.purchased.plan.consecutive.trinkets).to.eql(1);
});
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(1);
});
it('adds 12 plan.consecutive.trinkets for 12 month block', async () => {
data.sub.key = 'basic_12mo';
@@ -944,7 +949,7 @@ describe('payments/index', () => {
expect(user.purchased.plan.consecutive.trinkets).to.eql(6);
});
it('Adds 4 to plan.consecutive.trinkets when upgrading from basic_3mo to basic_12mo', async () => {
it('Adds 11 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);
@@ -958,17 +963,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(12);
});
it('Adds 2 to plan.consecutive.trinkets from basic_earned to basic_6mo after initial cycle', async () => {
it('Adds 11 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';
@@ -976,17 +981,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(12);
});
it('Adds 4 to plan.consecutive.trinkets when upgrading from basic_6mo to basic_12mo after initial cycle', async () => {
it('Adds 11 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';
@@ -994,7 +999,7 @@ 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(12);
});
it('Adds 4 to plan.consecutive.trinkets when upgrading from basic_3mo to basic_12mo after initial cycle', async () => {
-14
View File
@@ -8,7 +8,6 @@ import defaults from 'lodash/defaults';
import invert from 'lodash/invert';
import moment from 'moment';
import 'moment-recur';
import subscriptionBlocks from './content/subscriptionBlocks';
export const DAY_MAPPING = {
0: 'su',
@@ -287,23 +286,10 @@ export function getPlanContext (user, now) {
const dateUpdatedMoment = moment(plan.dateUpdated).startOf('month');
const elapsedMonths = moment(subscriptionEndDate).diff(dateUpdatedMoment, 'months');
const planMonths = subscriptionBlocks[plan.planId] ? subscriptionBlocks[plan.planId].months : 1;
let monthsTillNextHourglass;
if (planMonths > 1) {
monthsTillNextHourglass = Number(plan.consecutive.offset) + 1;
} else {
monthsTillNextHourglass = 3 - plan.perkMonthCount;
}
const possibleNextHourglassDate = moment(plan.dateUpdated)
.add(monthsTillNextHourglass, 'months');
return {
plan,
subscriptionEndDate,
dateUpdatedMoment,
elapsedMonths,
offset: plan.consecutive.offset, // months until the new hourglass is added
nextHourglassDate: possibleNextHourglassDate,
};
}
+2 -55
View File
@@ -15,7 +15,6 @@ const {
shouldDo,
i18n,
getPlanContext,
getPlanMonths,
} = common;
const { scoreTask } = common.ops;
const { loginIncentives } = common.content;
@@ -67,59 +66,11 @@ async function grantEndOfTheMonthPerks (user, now) {
if (elapsedMonths > 0) {
plan.dateUpdated = now;
// For every month, inc their "consecutive months" counter.
// Give perks based on consecutive blocks
// If they already got perks for those blocks (eg, 6mo subscription,
// subscription gifts, etc) - then dec the offset until it hits 0
// Award mystery items
revealMysteryItems(user, elapsedMonths);
// 1 for one-month recurring or gift subscriptions; later set to 3 for 3-month recurring, etc.
let planMonthsLength = 1;
for (let i = 0; i < elapsedMonths; i += 1) {
plan.consecutive.count += 1;
plan.consecutive.offset -= 1;
// If offset is now greater than 0, the user is within a period
// for which they have already been given the consecutive months perks.
//
// If offset now equals 0, this is the final month for which
// the user has already been given the consecutive month perks.
// We do not give them more perks yet because they might cancel
// the subscription before the next payment is taken.
//
// If offset is now less than 0, the user EITHER has
// a single-month recurring subscription and MIGHT be due for perks,
// OR has a multi-month subscription that renewed some time
// in the previous calendar month and so they are due for a new set of perks
// (strictly speaking, they should have been given the perks
// at the time that next payment was taken, but we don't have support for
// tracking payments like that - giving the perks when offset is < 0 is a workaround).
if (plan.consecutive.offset < 0) {
if (plan.planId) {
planMonthsLength = getPlanMonths(plan);
}
if (planMonthsLength === 1) {
plan.consecutive.offset = 0; // allow the same logic to be run next month
} else {
// User has a multi-month recurring subscription
// and it renewed in the previous calendar month.
// don't need to check for perks again for this many months
// (subtract 1 because we should have run this when the payment was taken last month)
plan.consecutive.offset = planMonthsLength - 1;
}
if (!plan.gift && plan.customerId.indexOf('Gift') === -1) {
// Don't process gifted subs here, since they already got their perks.
// eslint-disable-next-line no-await-in-loop
await plan.incrementPerkCounterAndReward(user._id, planMonthsLength);
}
}
}
plan.consecutive.count += elapsedMonths;
await plan.rewardPerks(user._id, elapsedMonths);
}
}
@@ -135,8 +86,6 @@ function removeTerminatedSubscription (user) {
_.merge(plan.consecutive, {
count: 0,
offset: 0,
gemCapExtra: 0,
});
user.markModified('purchased.plan');
@@ -283,8 +232,6 @@ export async function cron (options = {}) {
if (user.isSubscribed()) {
await grantEndOfTheMonthPerks(user, now);
} if (!user.isSubscribed() && user.purchased.plan.perkMonthCount > 0) {
user.purchased.plan.perkMonthCount = 0;
}
const { plan } = user.purchased;
+11 -17
View File
@@ -249,24 +249,7 @@ async function createSubscription (data) {
itemPurchased,
purchaseType,
emailType,
isNewSubscription,
} = await prepareSubscriptionValues(data);
// Block sub perks
if (months > 1 && (!data.gift || !isNewSubscription)) {
if (!data.gift && !groupId) {
plan.consecutive.offset = block.months;
}
} else if (months === 1) {
plan.consecutive.offset = 0;
}
if (months > 1 || data.gift) {
await plan.incrementPerkCounterAndReward(recipient._id, months);
} else {
// Make sure the perkMonthCount field is initialized.
await plan.incrementPerkCounterAndReward(recipient._id, 0);
}
if (recipient !== group) {
recipient.items.pets['Jackalope-RoyalPurple'] = 5;
recipient.markModified('items.pets');
@@ -278,6 +261,17 @@ async function createSubscription (data) {
txnEmail(data.user, emailType);
}
if (months === 12) {
plan.consecutive.gemCapExtra = 26;
}
if (months === 12 && autoRenews && !recipient.purchased.plan.hourglassPromoReceived) {
recipient.purchased.plan.hourglassPromoReceived = new Date();
await plan.updateHourglasses(recipient._id, 12, '12_month_subscription');
} else if (!data.gift || (data.gift && !recipient.isSubscribed())) {
await plan.updateHourglasses(recipient._id, 1, 'subscribed');
}
if (!group && !data.promo) data.user.purchased.txnCount += 1;
if (!data.promo) {
+8 -29
View File
@@ -3,9 +3,6 @@ import validator from 'validator';
import baseModel from '../libs/baseModel';
import { TransactionModel as Transaction } from './transaction';
// multi-month subscriptions are for multiples of 3 months
const SUBSCRIPTION_BASIC_BLOCK_LENGTH = 3;
export const schema = new mongoose.Schema({
planId: String,
subscriptionId: String,
@@ -18,7 +15,6 @@ export const schema = new mongoose.Schema({
dateUpdated: Date,
dateCurrentTypeCreated: Date,
extraMonths: { $type: Number, default: 0 },
perkMonthCount: { $type: Number, default: -1 },
gemsBought: { $type: Number, default: 0 },
mysteryItems: { $type: Array, default: () => [] },
lastReminderDate: Date, // indicates the last time a subscription reminder was sent
@@ -28,6 +24,7 @@ export const schema = new mongoose.Schema({
// indicates when the queue server should process this subscription again.
nextPaymentProcessing: Date,
nextBillingDate: Date, // Next time google will bill this user.
hourglassPromoReceived: Date,
consecutive: {
count: { $type: Number, default: 0 },
// when gifted subs, offset++ for each month. offset-- each new-month (cron).
@@ -50,36 +47,18 @@ schema.plugin(baseModel, {
_id: false,
});
schema.methods.incrementPerkCounterAndReward = async function incrementPerkCounterAndReward
schema.methods.rewardPerks = async function rewardPerks
(userID, adding) {
let addingNumber = adding;
let perks = adding;
if (typeof adding === 'string' || adding instanceof String) {
addingNumber = parseInt(adding, 10);
perks = parseInt(adding, 10);
}
const isSingleMonthPlan = this.planId === 'basic_earned' || this.planId === 'group_plan_auto' || this.planId === 'group_monthly';
// if perkMonthCount wasn't used before, initialize it.
if (this.perkMonthCount === undefined || this.perkMonthCount === -1) {
if (isSingleMonthPlan && this.consecutive.count > 0) {
this.perkMonthCount = (this.consecutive.count - 1) % SUBSCRIPTION_BASIC_BLOCK_LENGTH;
} else {
this.perkMonthCount = 0;
}
} else if (isSingleMonthPlan) {
const expectedPerkMonthCount = (this.consecutive.count - 1) % SUBSCRIPTION_BASIC_BLOCK_LENGTH;
if (this.perkMonthCount === (expectedPerkMonthCount - 1)) {
// User was affected by a bug that makes their perkMonthCount off by one
this.perkMonthCount += 1;
}
}
this.perkMonthCount += addingNumber;
const perks = Math.floor(this.perkMonthCount / 3);
if (perks > 0) {
this.consecutive.gemCapExtra += 5 * perks; // 5 extra Gems every 3 months
// cap it at 50 (hard 25 limit + extra 25)
if (this.consecutive.gemCapExtra > 25) this.consecutive.gemCapExtra = 25;
this.perkMonthCount -= (perks * 3);
// one Hourglass every 3 months
this.consecutive.gemCapExtra += 2 * perks; // 2 extra Gems every month
// cap it at 50 (hard 24 limit + extra 26)
if (this.consecutive.gemCapExtra > 26) this.consecutive.gemCapExtra = 26;
// one Hourglass every month
await this.updateHourglasses(userID, perks, 'subscription_perks'); // eslint-disable-line no-await-in-loop
}
};
+1 -1
View File
@@ -5,7 +5,7 @@ import baseModel from '../libs/baseModel';
const { Schema } = mongoose;
export const currencies = ['gems', 'hourglasses'];
export const transactionTypes = ['buy_money', 'buy_gold', 'spend', 'gift_send', 'gifted_with_money', 'gift_receive', 'debug', 'create_challenge', 'create_bank_challenge', 'create_guild', 'change_class', 'rebirth', 'release_pets', 'release_mounts', 'reroll', 'contribution', 'subscription_perks', 'admin_update_balance', 'admin_update_hourglasses'];
export const transactionTypes = ['buy_money', 'buy_gold', 'spend', 'gift_send', 'gifted_with_money', 'gift_receive', 'debug', 'create_challenge', 'create_bank_challenge', 'create_guild', 'change_class', 'rebirth', 'release_pets', 'release_mounts', 'reroll', 'contribution', 'subscription_perks', 'subscribed', '12_month_subscription', 'admin_update_balance', 'admin_update_hourglasses'];
export const schema = new Schema({
currency: { $type: String, enum: currencies, required: true },