From 4f963e99dc44ac6193a743034d5d97b26be59990 Mon Sep 17 00:00:00 2001 From: negue Date: Fri, 27 Apr 2018 19:29:26 +0200 Subject: [PATCH] Purchase API Refactoring: Gems [Gold] (#10271) * remove `keyRequired` - change to `missingKeyParam` - i18n-string * extract & convert buyGemsOperation * fix lint --- package-lock.json | 151 +++++------------- test/common/ops/buy/buyGem.js | 141 ++++++++++++++++ test/common/ops/buy/purchase.js | 90 ----------- test/common/ops/sell.js | 2 +- website/common/locales/en/npc.json | 3 +- website/common/locales/en/subscriber.json | 1 + .../script/ops/buy/abstractBuyOperation.js | 18 ++- website/common/script/ops/buy/buy.js | 8 +- website/common/script/ops/buy/buyGem.js | 81 ++++++++++ website/common/script/ops/buy/purchase.js | 53 +----- website/common/script/ops/sell.js | 2 +- 11 files changed, 289 insertions(+), 261 deletions(-) create mode 100644 test/common/ops/buy/buyGem.js create mode 100644 website/common/script/ops/buy/buyGem.js diff --git a/package-lock.json b/package-lock.json index 9ed38b3e58..9f677fe836 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7353,7 +7353,6 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.1.3.tgz", "integrity": "sha512-WIr7iDkdmdbxu/Gh6eKEZJL6KPE74/5MEsf2whTOFNxbIoIixogroLdKYqB6FDav4Wavh/lZdzzd3b2KxIXC5Q==", - "optional": true, "requires": { "nan": "2.6.2", "node-pre-gyp": "0.6.39" @@ -7362,14 +7361,12 @@ "abbrev": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.0.tgz", - "integrity": "sha1-0FVMIlZjbi9W58LlrRg/hZQo2B8=", - "optional": true + "integrity": "sha1-0FVMIlZjbi9W58LlrRg/hZQo2B8=" }, "ajv": { "version": "4.11.8", "resolved": "https://registry.npmjs.org/ajv/-/ajv-4.11.8.tgz", "integrity": "sha1-gv+wKynmYq5TvcIK8VlHcGc5xTY=", - "optional": true, "requires": { "co": "4.6.0", "json-stable-stringify": "1.0.1" @@ -7383,14 +7380,12 @@ "aproba": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.1.1.tgz", - "integrity": "sha1-ldNgDwdxCqDpKYxyatXs8urLq6s=", - "optional": true + "integrity": "sha1-ldNgDwdxCqDpKYxyatXs8urLq6s=" }, "are-we-there-yet": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.4.tgz", "integrity": "sha1-u13KOCu5TwXhUZQ3PRb9O6HKEQ0=", - "optional": true, "requires": { "delegates": "1.0.0", "readable-stream": "2.2.9" @@ -7399,32 +7394,27 @@ "asn1": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", - "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=", - "optional": true + "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=" }, "assert-plus": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.2.0.tgz", - "integrity": "sha1-104bh+ev/A24qttwIfP+SBAasjQ=", - "optional": true + "integrity": "sha1-104bh+ev/A24qttwIfP+SBAasjQ=" }, "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", - "optional": true + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" }, "aws-sign2": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz", - "integrity": "sha1-FDQt0428yU0OW4fXY81jYSwOeU8=", - "optional": true + "integrity": "sha1-FDQt0428yU0OW4fXY81jYSwOeU8=" }, "aws4": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.6.0.tgz", - "integrity": "sha1-g+9cqGCysy5KDe7e6MdxudtXRx4=", - "optional": true + "integrity": "sha1-g+9cqGCysy5KDe7e6MdxudtXRx4=" }, "balanced-match": { "version": "0.4.2", @@ -7473,14 +7463,12 @@ "caseless": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", - "optional": true + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" }, "co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", - "optional": true + "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=" }, "code-point-at": { "version": "1.1.0", @@ -7522,7 +7510,6 @@ "version": "1.14.1", "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", - "optional": true, "requires": { "assert-plus": "1.0.0" }, @@ -7530,8 +7517,7 @@ "assert-plus": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", - "optional": true + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" } } }, @@ -7539,7 +7525,6 @@ "version": "2.6.8", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.8.tgz", "integrity": "sha1-5zFTHKLt4n0YgiJCfaF4IdaP9Pw=", - "optional": true, "requires": { "ms": "2.0.0" } @@ -7547,8 +7532,7 @@ "deep-extend": { "version": "0.4.2", "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.4.2.tgz", - "integrity": "sha1-SLaZwn4zS/ifEIkr5DL25MfTSn8=", - "optional": true + "integrity": "sha1-SLaZwn4zS/ifEIkr5DL25MfTSn8=" }, "delayed-stream": { "version": "1.0.0", @@ -7558,14 +7542,12 @@ "delegates": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", - "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", - "optional": true + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" }, "detect-libc": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.2.tgz", - "integrity": "sha1-ca1dIEvxempsqPRQxhRUBm70YeE=", - "optional": true + "integrity": "sha1-ca1dIEvxempsqPRQxhRUBm70YeE=" }, "ecc-jsbn": { "version": "0.1.1", @@ -7579,8 +7561,7 @@ "extend": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", - "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=", - "optional": true + "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=" }, "extsprintf": { "version": "1.0.2", @@ -7590,14 +7571,12 @@ "forever-agent": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", - "optional": true + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" }, "form-data": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.1.4.tgz", "integrity": "sha1-M8GDrPGTJ27KqYFDpp6Uv+4XUNE=", - "optional": true, "requires": { "asynckit": "0.4.0", "combined-stream": "1.0.5", @@ -7624,7 +7603,6 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/fstream-ignore/-/fstream-ignore-1.0.5.tgz", "integrity": "sha1-nDHa40dnAY/h0kmyTa2mfQktoQU=", - "optional": true, "requires": { "fstream": "1.0.11", "inherits": "2.0.3", @@ -7635,7 +7613,6 @@ "version": "2.7.4", "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", - "optional": true, "requires": { "aproba": "1.1.1", "console-control-strings": "1.1.0", @@ -7651,7 +7628,6 @@ "version": "0.1.7", "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", - "optional": true, "requires": { "assert-plus": "1.0.0" }, @@ -7659,8 +7635,7 @@ "assert-plus": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", - "optional": true + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" } } }, @@ -7685,14 +7660,12 @@ "har-schema": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-1.0.5.tgz", - "integrity": "sha1-0mMTX0MwfALGAq/I/pWXDAFRNp4=", - "optional": true + "integrity": "sha1-0mMTX0MwfALGAq/I/pWXDAFRNp4=" }, "har-validator": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-4.2.1.tgz", "integrity": "sha1-M0gdDxu/9gDdID11gSpqX7oALio=", - "optional": true, "requires": { "ajv": "4.11.8", "har-schema": "1.0.5" @@ -7701,8 +7674,7 @@ "has-unicode": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", - "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=", - "optional": true + "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=" }, "hawk": { "version": "3.1.3", @@ -7724,7 +7696,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz", "integrity": "sha1-33LiZwZs0Kxn+3at+OE0qPvPkb8=", - "optional": true, "requires": { "assert-plus": "0.2.0", "jsprim": "1.4.0", @@ -7748,8 +7719,7 @@ "ini": { "version": "1.3.4", "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.4.tgz", - "integrity": "sha1-BTfLedr1m1mhpRff9wbIbsA5Fi4=", - "optional": true + "integrity": "sha1-BTfLedr1m1mhpRff9wbIbsA5Fi4=" }, "is-fullwidth-code-point": { "version": "1.0.0", @@ -7762,8 +7732,7 @@ "is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", - "optional": true + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" }, "isarray": { "version": "1.0.0", @@ -7773,8 +7742,7 @@ "isstream": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", - "optional": true + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" }, "jodid25519": { "version": "1.0.2", @@ -7794,14 +7762,12 @@ "json-schema": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", - "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", - "optional": true + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" }, "json-stable-stringify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz", "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=", - "optional": true, "requires": { "jsonify": "0.0.0" } @@ -7809,20 +7775,17 @@ "json-stringify-safe": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", - "optional": true + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" }, "jsonify": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", - "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=", - "optional": true + "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=" }, "jsprim": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.0.tgz", "integrity": "sha1-o7h+QCmNjDgFUtjMdiigu5WiKRg=", - "optional": true, "requires": { "assert-plus": "1.0.0", "extsprintf": "1.0.2", @@ -7833,8 +7796,7 @@ "assert-plus": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", - "optional": true + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" } } }, @@ -7875,14 +7837,12 @@ "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "optional": true + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" }, "node-pre-gyp": { "version": "0.6.39", "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.6.39.tgz", "integrity": "sha512-OsJV74qxnvz/AMGgcfZoDaeDXKD3oY3QVIbBmwszTFkRisTSXbMQyn4UWzUMOtA5SVhrBZOTp0wcoSBgfMfMmQ==", - "optional": true, "requires": { "detect-libc": "1.0.2", "hawk": "3.1.3", @@ -7901,7 +7861,6 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.1.tgz", "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=", - "optional": true, "requires": { "abbrev": "1.1.0", "osenv": "0.1.4" @@ -7911,7 +7870,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.0.tgz", "integrity": "sha512-ocolIkZYZt8UveuiDS0yAkkIjid1o7lPG8cYm05yNYzBn8ykQtaiPMEGp8fY9tKdDgm8okpdKzkvu1y9hUYugA==", - "optional": true, "requires": { "are-we-there-yet": "1.1.4", "console-control-strings": "1.1.0", @@ -7927,14 +7885,12 @@ "oauth-sign": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", - "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=", - "optional": true + "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=" }, "object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", - "optional": true + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" }, "once": { "version": "1.4.0", @@ -7947,20 +7903,17 @@ "os-homedir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", - "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", - "optional": true + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=" }, "os-tmpdir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", - "optional": true + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" }, "osenv": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.4.tgz", "integrity": "sha1-Qv5tWVPfBsgGS+bxdsPQWqqjRkQ=", - "optional": true, "requires": { "os-homedir": "1.0.2", "os-tmpdir": "1.0.2" @@ -7974,8 +7927,7 @@ "performance-now": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-0.2.0.tgz", - "integrity": "sha1-M+8wxcd9TqIcWlOGnZG1bY8lVeU=", - "optional": true + "integrity": "sha1-M+8wxcd9TqIcWlOGnZG1bY8lVeU=" }, "process-nextick-args": { "version": "1.0.7", @@ -7985,20 +7937,17 @@ "punycode": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", - "optional": true + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" }, "qs": { "version": "6.4.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.4.0.tgz", - "integrity": "sha1-E+JtKK1rD/qpExLNO/cI7TUecjM=", - "optional": true + "integrity": "sha1-E+JtKK1rD/qpExLNO/cI7TUecjM=" }, "rc": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.1.tgz", "integrity": "sha1-LgPo5C7kULjLPc5lvhv4l04d/ZU=", - "optional": true, "requires": { "deep-extend": "0.4.2", "ini": "1.3.4", @@ -8009,8 +7958,7 @@ "minimist": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", - "optional": true + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" } } }, @@ -8032,7 +7980,6 @@ "version": "2.81.0", "resolved": "https://registry.npmjs.org/request/-/request-2.81.0.tgz", "integrity": "sha1-xpKJRqDgbF+Nb4qTM0af/aRimKA=", - "optional": true, "requires": { "aws-sign2": "0.6.0", "aws4": "1.6.0", @@ -8074,20 +8021,17 @@ "semver": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz", - "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=", - "optional": true + "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=" }, "set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", - "optional": true + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" }, "signal-exit": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", - "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", - "optional": true + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" }, "sntp": { "version": "1.0.9", @@ -8101,7 +8045,6 @@ "version": "1.13.0", "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.13.0.tgz", "integrity": "sha1-/yo+T9BEl1Vf7Zezmg/YL6+zozw=", - "optional": true, "requires": { "asn1": "0.2.3", "assert-plus": "1.0.0", @@ -8117,8 +8060,7 @@ "assert-plus": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", - "optional": true + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" } } }, @@ -8143,8 +8085,7 @@ "stringstream": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz", - "integrity": "sha1-TkhM1N5aC7vuGORjB3EKioFiGHg=", - "optional": true + "integrity": "sha1-TkhM1N5aC7vuGORjB3EKioFiGHg=" }, "strip-ansi": { "version": "3.0.1", @@ -8157,8 +8098,7 @@ "strip-json-comments": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", - "optional": true + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=" }, "tar": { "version": "2.2.1", @@ -8174,7 +8114,6 @@ "version": "3.4.0", "resolved": "https://registry.npmjs.org/tar-pack/-/tar-pack-3.4.0.tgz", "integrity": "sha1-I74tf2cagzk3bL2wuP4/3r8xeYQ=", - "optional": true, "requires": { "debug": "2.6.8", "fstream": "1.0.11", @@ -8190,7 +8129,6 @@ "version": "2.3.2", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.2.tgz", "integrity": "sha1-8IH3bkyFcg5sN6X6ztc3FQ2EByo=", - "optional": true, "requires": { "punycode": "1.4.1" } @@ -8199,7 +8137,6 @@ "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", - "optional": true, "requires": { "safe-buffer": "5.0.1" } @@ -8213,8 +8150,7 @@ "uid-number": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/uid-number/-/uid-number-0.0.6.tgz", - "integrity": "sha1-DqEOgDXo61uOREnwbaHHMGY7qoE=", - "optional": true + "integrity": "sha1-DqEOgDXo61uOREnwbaHHMGY7qoE=" }, "util-deprecate": { "version": "1.0.2", @@ -8224,14 +8160,12 @@ "uuid": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.0.1.tgz", - "integrity": "sha1-ZUS7ot/ajBzxfmKaOjBeK7H+5sE=", - "optional": true + "integrity": "sha1-ZUS7ot/ajBzxfmKaOjBeK7H+5sE=" }, "verror": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/verror/-/verror-1.3.6.tgz", "integrity": "sha1-z/XfEpRtKX0rqu+qJoniW+AcAFw=", - "optional": true, "requires": { "extsprintf": "1.0.2" } @@ -8240,7 +8174,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.2.tgz", "integrity": "sha512-ijDLlyQ7s6x1JgCLur53osjm/UXUYD9+0PbYKrBsYisYXzCxN+HC3mYDNy/dWdmf3AwqwU3CXwDCvsNgGK1S0w==", - "optional": true, "requires": { "string-width": "1.0.2" } diff --git a/test/common/ops/buy/buyGem.js b/test/common/ops/buy/buyGem.js new file mode 100644 index 0000000000..fc3d00f708 --- /dev/null +++ b/test/common/ops/buy/buyGem.js @@ -0,0 +1,141 @@ +/* eslint-disable camelcase */ + +import sinon from 'sinon'; // eslint-disable-line no-shadow +import { + generateUser, +} from '../../../helpers/common.helper'; +import { + BadRequest, NotAuthorized, +} from '../../../../website/common/script/libs/errors'; +import i18n from '../../../../website/common/script/i18n'; +import {BuyGemOperation} from '../../../../website/common/script/ops/buy/buyGem'; +import planGemLimits from '../../../../website/common/script/libs/planGemLimits'; + +function buyGem (user, req, analytics) { + let buyOp = new BuyGemOperation(user, req, analytics); + + return buyOp.purchase(); +} + +describe('shared.ops.buyGem', () => { + let user; + let analytics = {track () {}}; + let goldPoints = 40; + let gemsBought = 40; + let userGemAmount = 10; + + beforeEach(() => { + user = generateUser({ + stats: { gp: goldPoints }, + balance: userGemAmount, + purchased: { + plan: { + gemsBought: 0, + customerId: 'costumer-id', + }, + }, + }); + + sinon.stub(analytics, 'track'); + }); + + afterEach(() => { + analytics.track.restore(); + }); + + context('Gems', () => { + it('purchases gems', () => { + let [, message] = buyGem(user, {params: {type: 'gems', key: 'gem'}}, analytics); + + expect(message).to.equal(i18n.t('plusGem', {count: 1})); + expect(user.balance).to.equal(userGemAmount + 0.25); + expect(user.purchased.plan.gemsBought).to.equal(1); + expect(user.stats.gp).to.equal(goldPoints - planGemLimits.convRate); + expect(analytics.track).to.be.calledOnce; + }); + + it('purchases gems with a different language than the default', () => { + let [, message] = buyGem(user, {params: {type: 'gems', key: 'gem'}, language: 'de'}); + + expect(message).to.equal(i18n.t('plusGem', {count: 1}, 'de')); + expect(user.balance).to.equal(userGemAmount + 0.25); + expect(user.purchased.plan.gemsBought).to.equal(1); + expect(user.stats.gp).to.equal(goldPoints - planGemLimits.convRate); + }); + + it('makes bulk purchases of gems', () => { + let [, message] = buyGem(user, { + params: {type: 'gems', key: 'gem'}, + quantity: 2, + }); + + expect(message).to.equal(i18n.t('plusGem', {count: 2})); + expect(user.balance).to.equal(userGemAmount + 0.50); + expect(user.purchased.plan.gemsBought).to.equal(2); + expect(user.stats.gp).to.equal(goldPoints - planGemLimits.convRate * 2); + }); + + + context('Failure conditions', () => { + it('returns an error when key is not provided', (done) => { + try { + buyGem(user, {params: {type: 'gems'}}); + } catch (err) { + expect(err).to.be.an.instanceof(BadRequest); + expect(err.message).to.equal(i18n.t('missingKeyParam')); + done(); + } + }); + + it('prevents unsubscribed user from buying gems', (done) => { + delete user.purchased.plan.customerId; + + try { + buyGem(user, {params: {type: 'gems', key: 'gem'}}); + } catch (err) { + expect(err).to.be.an.instanceof(NotAuthorized); + expect(err.message).to.equal(i18n.t('mustSubscribeToPurchaseGems')); + done(); + } + }); + + it('prevents user with not enough gold from buying gems', (done) => { + user.stats.gp = 15; + + try { + buyGem(user, {params: {type: 'gems', key: 'gem'}}); + } catch (err) { + expect(err).to.be.an.instanceof(NotAuthorized); + expect(err.message).to.equal(i18n.t('messageNotEnoughGold')); + done(); + } + }); + + it('prevents user that have reached the conversion cap from buying gems', (done) => { + user.stats.gp = goldPoints; + user.purchased.plan.gemsBought = gemsBought; + + try { + buyGem(user, {params: {type: 'gems', key: 'gem'}}); + } catch (err) { + expect(err).to.be.an.instanceof(NotAuthorized); + expect(err.message).to.equal(i18n.t('reachedGoldToGemCap', {convCap: planGemLimits.convCap})); + done(); + } + }); + + it('prevents user from buying an invalid quantity', (done) => { + user.stats.gp = goldPoints; + user.purchased.plan.gemsBought = gemsBought; + + try { + buyGem(user, {params: {type: 'gems', key: 'gem'}, quantity: 'a'}); + } catch (err) { + expect(err).to.be.an.instanceof(BadRequest); + expect(err.message).to.equal(i18n.t('invalidQuantity')); + done(); + } + }); + }); + }); +}); diff --git a/test/common/ops/buy/purchase.js b/test/common/ops/buy/purchase.js index f8a6c903a0..900011daa5 100644 --- a/test/common/ops/buy/purchase.js +++ b/test/common/ops/buy/purchase.js @@ -1,6 +1,5 @@ import purchase from '../../../../website/common/script/ops/buy/purchase'; import pinnedGearUtils from '../../../../website/common/script/ops/pinnedGearUtils'; -import planGemLimits from '../../../../website/common/script/libs/planGemLimits'; import { BadRequest, NotAuthorized, @@ -17,7 +16,6 @@ describe('shared.ops.purchase', () => { const SEASONAL_FOOD = 'Meat'; let user; let goldPoints = 40; - let gemsBought = 40; let analytics = {track () {}}; before(() => { @@ -45,63 +43,6 @@ describe('shared.ops.purchase', () => { } }); - it('returns an error when key is not provided', (done) => { - try { - purchase(user, {params: {type: 'gems'}}); - } catch (err) { - expect(err).to.be.an.instanceof(BadRequest); - expect(err.message).to.equal(i18n.t('keyRequired')); - done(); - } - }); - - it('prevents unsubscribed user from buying gems', (done) => { - try { - purchase(user, {params: {type: 'gems', key: 'gem'}}); - } catch (err) { - expect(err).to.be.an.instanceof(NotAuthorized); - expect(err.message).to.equal(i18n.t('mustSubscribeToPurchaseGems')); - done(); - } - }); - - it('prevents user with not enough gold from buying gems', (done) => { - user.purchased.plan.customerId = 'customer-id'; - - try { - purchase(user, {params: {type: 'gems', key: 'gem'}}); - } catch (err) { - expect(err).to.be.an.instanceof(NotAuthorized); - expect(err.message).to.equal(i18n.t('messageNotEnoughGold')); - done(); - } - }); - - it('prevents user that have reached the conversion cap from buying gems', (done) => { - user.stats.gp = goldPoints; - user.purchased.plan.gemsBought = gemsBought; - - try { - purchase(user, {params: {type: 'gems', key: 'gem'}}); - } catch (err) { - expect(err).to.be.an.instanceof(NotAuthorized); - expect(err.message).to.equal(i18n.t('reachedGoldToGemCap', {convCap: planGemLimits.convCap})); - done(); - } - }); - - it('prevents user from buying an invalid quantity', (done) => { - user.stats.gp = goldPoints; - user.purchased.plan.gemsBought = gemsBought; - - try { - purchase(user, {params: {type: 'gems', key: 'gem'}, quantity: 'a'}); - } catch (err) { - expect(err).to.be.an.instanceof(BadRequest); - expect(err.message).to.equal(i18n.t('invalidQuantity')); - done(); - } - }); it('returns error when unknown type is provided', (done) => { try { @@ -185,25 +126,6 @@ describe('shared.ops.purchase', () => { user.pinnedItems.push({type: 'bundles', key: 'featheredFriends'}); }); - it('purchases gems', () => { - let [, message] = purchase(user, {params: {type: 'gems', key: 'gem'}}, analytics); - - expect(message).to.equal(i18n.t('plusOneGem')); - expect(user.balance).to.equal(userGemAmount + 0.25); - expect(user.purchased.plan.gemsBought).to.equal(1); - expect(user.stats.gp).to.equal(goldPoints - planGemLimits.convRate); - expect(analytics.track).to.be.calledOnce; - }); - - it('purchases gems with a different language than the default', () => { - let [, message] = purchase(user, {params: {type: 'gems', key: 'gem'}, language: 'de'}); - - expect(message).to.equal(i18n.t('plusOneGem', 'de')); - expect(user.balance).to.equal(userGemAmount + 0.5); - expect(user.purchased.plan.gemsBought).to.equal(2); - expect(user.stats.gp).to.equal(goldPoints - planGemLimits.convRate * 2); - }); - it('purchases eggs', () => { let type = 'eggs'; let key = 'Wolf'; @@ -307,18 +229,6 @@ describe('shared.ops.purchase', () => { } }); - it('makes bulk purchases of gems', () => { - let [, message] = purchase(user, { - params: {type: 'gems', key: 'gem'}, - quantity: 2, - }); - - expect(message).to.equal(i18n.t('plusOneGem')); - expect(user.balance).to.equal(userGemAmount + 0.50); - expect(user.purchased.plan.gemsBought).to.equal(2); - expect(user.stats.gp).to.equal(goldPoints - planGemLimits.convRate * 2); - }); - it('makes bulk purchases of eggs', () => { let type = 'eggs'; let key = 'TigerCub'; diff --git a/test/common/ops/sell.js b/test/common/ops/sell.js index 9631fb9650..f2a67c9d8e 100644 --- a/test/common/ops/sell.js +++ b/test/common/ops/sell.js @@ -36,7 +36,7 @@ describe('shared.ops.sell', () => { sell(user, {params: { type } }); } catch (err) { expect(err).to.be.an.instanceof(BadRequest); - expect(err.message).to.equal(i18n.t('keyRequired')); + expect(err.message).to.equal(i18n.t('missingKeyParam')); done(); } }); diff --git a/website/common/locales/en/npc.json b/website/common/locales/en/npc.json index ee6ed35624..0fdb31496d 100644 --- a/website/common/locales/en/npc.json +++ b/website/common/locales/en/npc.json @@ -93,10 +93,9 @@ "mustPurchaseToSet": "Must purchase <%= val %> to set it on <%= key %>.", "typeRequired": "Type is required", "positiveAmountRequired": "Positive amount is required", - "keyRequired": "Key is required", "notAccteptedType": "Type must be in [eggs, hatchingPotions, premiumHatchingPotions, food, quests, gear]", "contentKeyNotFound": "Key not found for Content <%= type %>", - "plusOneGem": "+1 Gem", + "plusGem": "+<%= count %> Gem", "typeNotSellable": "Type is not sellable. Must be one of the following <%= acceptedTypes %>", "userItemsKeyNotFound": "Key not found for user.items <%= type %>", "userItemsNotEnough": "You do not have enough <%= type %>", diff --git a/website/common/locales/en/subscriber.json b/website/common/locales/en/subscriber.json index 970ca21bb7..a216d3dd6b 100644 --- a/website/common/locales/en/subscriber.json +++ b/website/common/locales/en/subscriber.json @@ -7,6 +7,7 @@ "buyGemsGoldText": "Alexander the Merchant will sell you Gems at a cost of 20 Gold per Gem. His monthly shipments are initially capped at 25 Gems per month, but for every 3 consecutive months that you are subscribed, this cap increases by 5 Gems, up to a maximum of 50 Gems per month!", "mustSubscribeToPurchaseGems": "Must subscribe to purchase gems with GP", "reachedGoldToGemCap": "You've reached the Gold=>Gem conversion cap <%= convCap %> for this month. We have this to prevent abuse / farming. The cap resets within the first three days of each month.", + "reachedGoldToGemCapQuantity": "Your requested amount <%= quantity %> exceeds the Gold=>Gem conversion cap <%= convCap %> for this month. We have this to prevent abuse / farming. The cap resets within the first three days of each month.", "retainHistory": "Retain additional history entries", "retainHistoryText": "Makes completed To-Dos and task history available for longer.", "doubleDrops": "Daily drop caps doubled", diff --git a/website/common/script/ops/buy/abstractBuyOperation.js b/website/common/script/ops/buy/abstractBuyOperation.js index f4627e2d2d..9b0a2d9fe2 100644 --- a/website/common/script/ops/buy/abstractBuyOperation.js +++ b/website/common/script/ops/buy/abstractBuyOperation.js @@ -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), diff --git a/website/common/script/ops/buy/buy.js b/website/common/script/ops/buy/buy.js index b8f56aab7b..f0ec31e5f2 100644 --- a/website/common/script/ops/buy/buy.js +++ b/website/common/script/ops/buy/buy.js @@ -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': diff --git a/website/common/script/ops/buy/buyGem.js b/website/common/script/ops/buy/buyGem.js new file mode 100644 index 0000000000..806b0cbc5c --- /dev/null +++ b/website/common/script/ops/buy/buyGem.js @@ -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; + } +} diff --git a/website/common/script/ops/buy/purchase.js b/website/common/script/ops/buy/purchase.js index 22b0803ef5..eb48ee72e8 100644 --- a/website/common/script/ops/buy/purchase.js +++ b/website/common/script/ops/buy/purchase.js @@ -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)) { diff --git a/website/common/script/ops/sell.js b/website/common/script/ops/sell.js index a36f2c5fb6..b777a32e76 100644 --- a/website/common/script/ops/sell.js +++ b/website/common/script/ops/sell.js @@ -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) {