Purchase API Refactoring: Gems [Gold] (#10271)

* remove `keyRequired` - change to `missingKeyParam` - i18n-string

* extract & convert buyGemsOperation

* fix lint
This commit is contained in:
negue
2018-04-27 19:29:26 +02:00
committed by Matteo Pagliazzi
parent 58ce3a9a42
commit 4f963e99dc
11 changed files with 289 additions and 261 deletions

View File

@@ -74,6 +74,10 @@ export class AbstractBuyOperation {
return resultObj;
}
analyticsLabel () {
return 'acquire item';
}
sendToAnalytics (additionalData = {}) {
// spread-operator produces an "unexpected token" error
let analyticsData = _merge(additionalData, {
@@ -87,7 +91,7 @@ export class AbstractBuyOperation {
analyticsData.quantityPurchased = this.quantity;
}
this.analytics.track('acquire item', analyticsData);
this.analytics.track(this.analyticsLabel(), analyticsData);
}
}
@@ -100,6 +104,10 @@ export class AbstractGoldItemOperation extends AbstractBuyOperation {
return item.value;
}
getIemKey (item) {
return item.key;
}
canUserPurchase (user, item) {
this.item = item;
let itemValue = this.getItemValue(item);
@@ -110,20 +118,20 @@ export class AbstractGoldItemOperation extends AbstractBuyOperation {
throw new NotAuthorized(this.i18n('messageNotEnoughGold'));
}
if (item.canOwn && !item.canOwn(user)) {
if (item && item.canOwn && !item.canOwn(user)) {
throw new NotAuthorized(this.i18n('cannotBuyItem'));
}
}
subtractCurrency (user, item, quantity = 1) {
subtractCurrency (user, item) {
let itemValue = this.getItemValue(item);
user.stats.gp -= itemValue * quantity;
user.stats.gp -= itemValue * this.quantity;
}
analyticsData () {
return {
itemKey: this.item.key,
itemKey: this.getIemKey(this.item),
itemType: 'Market',
acquireMethod: 'Gold',
goldCost: this.getItemValue(this.item),

View File

@@ -11,6 +11,7 @@ import {BuyQuestWithGoldOperation} from './buyQuest';
import buySpecialSpell from './buySpecialSpell';
import purchaseOp from './purchase';
import hourglassPurchase from './hourglassPurchase';
import {BuyGemOperation} from './buyGem';
// @TODO: remove the req option style. Dependency on express structure is an anti-pattern
// We should either have more parms or a set structure validated by a Type checker
@@ -45,13 +46,18 @@ module.exports = function buy (user, req = {}, analytics) {
buyRes = buyOp.purchase();
break;
}
case 'gems': {
const buyOp = new BuyGemOperation(user, req, analytics);
buyRes = buyOp.purchase();
break;
}
case 'eggs':
case 'hatchingPotions':
case 'food':
case 'quests':
case 'gear':
case 'bundles':
case 'gems':
buyRes = purchaseOp(user, req, analytics);
break;
case 'pets':

View File

@@ -0,0 +1,81 @@
import pick from 'lodash/pick';
import splitWhitespace from '../../libs/splitWhitespace';
import {
BadRequest,
NotAuthorized,
} from '../../libs/errors';
import {AbstractGoldItemOperation} from './abstractBuyOperation';
import get from 'lodash/get';
import planGemLimits from '../../libs/planGemLimits';
export class BuyGemOperation extends AbstractGoldItemOperation {
constructor (user, req, analytics) {
super(user, req, analytics);
}
multiplePurchaseAllowed () {
return true;
}
getItemValue () {
return planGemLimits.convRate;
}
getIemKey () {
return 'gem';
}
extractAndValidateParams (user, req) {
let key = this.key = get(req, 'params.key');
if (!key) throw new BadRequest(this.i18n('missingKeyParam'));
let convCap = planGemLimits.convCap;
convCap += user.purchased.plan.consecutive.gemCapExtra;
// todo better name?
this.convCap = convCap;
this.canUserPurchase(user);
}
canUserPurchase (user, item) {
if (!user.purchased || !user.purchased.plan || !user.purchased.plan.customerId) {
throw new NotAuthorized(this.i18n('mustSubscribeToPurchaseGems'));
}
super.canUserPurchase(user, item);
if (user.purchased.plan.gemsBought >= this.convCap) {
throw new NotAuthorized(this.i18n('reachedGoldToGemCap', {convCap: this.convCap}));
}
if (user.purchased.plan.gemsBought + this.quantity >= this.convCap) {
throw new NotAuthorized(this.i18n('reachedGoldToGemCapQuantity', {
convCap: this.convCap,
quantity: this.quantity,
}));
}
}
executeChanges (user, item) {
user.balance += 0.25 * this.quantity;
user.purchased.plan.gemsBought += this.quantity;
this.subtractCurrency(user, item);
return [
pick(user, splitWhitespace('stats balance')),
this.i18n('plusGem', {count: this.quantity}),
];
}
analyticsLabel () {
return 'purchase gems';
}
analyticsData () {
let data = super.analyticsData();
data.itemKey = 'gem';
return data;
}
}

View File

@@ -4,7 +4,6 @@ import get from 'lodash/get';
import pick from 'lodash/pick';
import forEach from 'lodash/forEach';
import splitWhitespace from '../../libs/splitWhitespace';
import planGemLimits from '../../libs/planGemLimits';
import {
NotFound,
NotAuthorized,
@@ -14,48 +13,6 @@ import {
import { removeItemByPath } from '../pinnedGearUtils';
import getItemInfo from '../../libs/getItemInfo';
function buyGems (user, analytics, req, key) {
let convRate = planGemLimits.convRate;
let convCap = planGemLimits.convCap;
convCap += user.purchased.plan.consecutive.gemCapExtra;
// Some groups limit their members ability to obtain gems
// The check is async so it's done on the server (in server/controllers/api-v3/user#purchase)
// only and not on the client,
// resulting in a purchase that will seem successful until the request hit the server.
if (!user.purchased || !user.purchased.plan || !user.purchased.plan.customerId) {
throw new NotAuthorized(i18n.t('mustSubscribeToPurchaseGems', req.language));
}
if (user.stats.gp < convRate) {
throw new NotAuthorized(i18n.t('messageNotEnoughGold', req.language));
}
if (user.purchased.plan.gemsBought >= convCap) {
throw new NotAuthorized(i18n.t('reachedGoldToGemCap', {convCap}, req.language));
}
user.balance += 0.25;
user.purchased.plan.gemsBought++;
user.stats.gp -= convRate;
if (analytics) {
analytics.track('purchase gems', {
uuid: user._id,
itemKey: key,
acquireMethod: 'Gold',
goldCost: convRate,
category: 'behavior',
headers: req.headers,
});
}
return [
pick(user, splitWhitespace('stats balance')),
i18n.t('plusOneGem', req.language),
];
}
function getItemAndPrice (user, type, key, req) {
let item;
let price;
@@ -120,15 +77,7 @@ module.exports = function purchase (user, req = {}, analytics) {
}
if (!key) {
throw new BadRequest(i18n.t('keyRequired', req.language));
}
if (type === 'gems' && key === 'gem') {
let gemResponse;
for (let i = 0; i < quantity; i += 1) {
gemResponse = buyGems(user, analytics, req, key);
}
return gemResponse;
throw new BadRequest(i18n.t('missingKeyParam', req.language));
}
if (!acceptedTypes.includes(type)) {

View File

@@ -26,7 +26,7 @@ module.exports = function sell (user, req = {}) {
}
if (!key) {
throw new BadRequest(i18n.t('keyRequired', req.language));
throw new BadRequest(i18n.t('missingKeyParam', req.language));
}
if (ACCEPTEDTYPES.indexOf(type) === -1) {