diff --git a/.editorconfig b/.editorconfig index b966b3b..ab0ec79 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,16 +1,18 @@ -# See editorconfig.org - root = true [*] - -indent_style = space -indent_size = 2 end_of_line = lf charset = utf-8 trim_trailing_whitespace = true insert_final_newline = false [*.md] +trim_trailing_whitespace = false + +[*.{js,json,yml,yaml}] +indent_style = space +indent_size = 2 -trim_trailing_whitespace = false \ No newline at end of file +#[*.go] +#indent_style = tab +#indent_size = 8 diff --git a/.gitattributes b/.gitattributes index 6a0ab8b..20f6dde 100644 --- a/.gitattributes +++ b/.gitattributes @@ -11,7 +11,8 @@ *.css text *.js text *.json text +*.go text *.bat text *.sh text -*.exe binary \ No newline at end of file +*.exe binary diff --git a/.gitignore b/.gitignore index b283d00..a8735f4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,14 +1,15 @@ -node_modules/ -npm-debug.log - .DS_Store -.idea +Thumbs.db *.log -*.heapsnapshot -.coverrun -.coverdata -dump.rdb -xunit.xml +*.exe +*.prof +*.out -sources/ \ No newline at end of file +tmp/ +logs/ +reports/ +releases/ +vendor/ +node_modules/ +sources/ diff --git a/.jshintrc b/.jshintrc deleted file mode 100644 index 423d103..0000000 --- a/.jshintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "eqeqeq": true, - "trailing": true, - "strict": true, - - "unused": true, // Warns for unused variables - "forin": true, // Requires all for in loops to filter object's items - - "sub": true, // Tolerate using `[]` notation - "laxbreak": true, // Tolerate possibly unsafe line breakings - - "-W065": false, // Missing radix parameter. - "-W110": false // Mixed double and single quotes. -} \ No newline at end of file diff --git a/.npmignore b/.npmignore deleted file mode 100644 index 015b68b..0000000 --- a/.npmignore +++ /dev/null @@ -1,11 +0,0 @@ -# Ignored files - -# Examples -# -# /.tmp-file-* -# /tmp-dir/* -# /symlink* -# -sources/ -node_modules/ -npm-debug.log diff --git a/.travis.yml b/.travis.yml index b5b40f7..c21da9a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,8 @@ +sudo: false + language: node_js + node_js: - "0.12" - - "0.10" - - "iojs" \ No newline at end of file + - "4" + - "6" \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 24d6c9f..574517d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,43 +1,43 @@ -## Changelog +# Changelog -### 1.0.1 (2015-03-15) +## 1.0.1 (2015-03-15) -* Misc. updates +- Misc. updates -### 1.0.0 (2014-07-04) +## 1.0.0 (2014-07-04) -* Stable release +- Stable release -### 0.2.3 (2014-05-22) +## 0.2.3 (2014-05-22) -* Update badges -* Misc. updates +- Update badges +- Misc. updates -### 0.2.2 (2014-04-26) +## 0.2.2 (2014-04-26) -* Add unit test -* Add npm badge -* Add Build Status -* Add .travis.yml +- Add unit test +- Add npm badge +- Add Build Status +- Add .travis.yml -### 0.2.1 (2014-04-09) +## 0.2.1 (2014-04-09) -* Misc. changes +- Misc. changes -### 0.2.0 (2014-01-19) +## 0.2.0 (2014-01-19) -* Reimplementation for handling return and errors +- Reimplementation for handling return and errors -### 0.1.0 (2014-01-19) +## 0.1.0 (2014-01-19) -* Stable release +- Stable release -### 0.0.2 (2013-12-12) +## 0.0.2 (2013-12-12) -* Additional JSHint options -* .gitattributes -* CHANGELOG.md +- Additional JSHint options +- .gitattributes +- CHANGELOG.md -### 0.0.1 (2013-08-12) +## 0.0.1 (2013-08-12) -* Initial release \ No newline at end of file +- Initial release \ No newline at end of file diff --git a/LICENSE.txt b/LICENSE.txt index fcf45f6..9bb6a01 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,21 +1,21 @@ The MIT License (MIT) -Copyright (c) 2013 Fatih Cetinkaya (http://github.com/cmfatih/amazon-costs) +Copyright (c) 2013 Fatih Cetinkaya (http://github.com/devfacet/amazon-costs) -Permission is hereby granted, free of charge, to any person obtaining a -copy of this software and associated documentation files (the "Software"), -to deal in the Software without restriction, including without limitation -the rights to use, copy, modify, merge, publish, distribute, sublicense, -and/or sell copies of the Software, and to permit persons to whom the -Software is furnished to do so, subject to the following conditions: +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. \ No newline at end of file +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md index 04b2200..6672659 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,20 @@ -## Amazon Costs +# Amazon Costs [![NPM][npm-image]][npm-url] [![Build Status][travis-image]][travis-url] Amazon Costs is a module for retrieving Amazon product information and calculating costs for fulfillment and merchant channels. -### Installation +## Installation -``` +```bash npm install amazon-costs ``` -### Usage +## Usage + +### Search -**Search** ```javascript var amzCosts = require('amazon-costs'); @@ -58,7 +59,8 @@ amzCosts.productSearch('The Hobbit DVD', function(err, data) { */ ``` -**Costs** +### Cost + ```javascript var amzCosts = require('amazon-costs'); var asin = 'B00BEZTMQ8'; @@ -190,13 +192,13 @@ amzCosts.productSearch(asin, function(err, data) { */ ``` -### License +## License Licensed under The MIT License (MIT) For the full copyright and license information, please view the LICENSE.txt file. [npm-url]: http://npmjs.org/package/amazon-costs -[npm-image]: https://badge.fury.io/js/amazon-costs.png +[npm-image]: https://badge.fury.io/js/amazon-costs.svg -[travis-url]: https://travis-ci.org/cmfatih/amazon-costs -[travis-image]: https://travis-ci.org/cmfatih/amazon-costs.svg?branch=master \ No newline at end of file +[travis-url]: https://travis-ci.org/devfacet/amazon-costs +[travis-image]: https://travis-ci.org/devfacet/amazon-costs.svg?branch=master diff --git a/index.js b/index.js index f46db76..0d92c1b 100644 --- a/index.js +++ b/index.js @@ -1,10 +1,403 @@ /* * Amazon Costs - * Copyright (c) 2013 Fatih Cetinkaya (http://github.com/cmfatih/amazon-costs) * For the full copyright and license information, please view the LICENSE.txt file. */ -/* jslint node: true */ +/* jslint node: true, sub: true, laxbreak: true*/ 'use strict'; -module.exports = require('./lib/'); \ No newline at end of file +var request = require('request'); // request module + +// Init the module +exports = module.exports = function amzCosts() { + + var debug, // debug + urlList, // url list + mpId, // Amazon marketplace Id + locale, // locale + urlPrd, // product url + urlPDM, // request url for product search + urlAFN, // request url for fees (fulfillment by Amazon) + urlMFN, // request url for fees (fulfillment by merchant) + productSearch, // product search - function + productCosts, // product costs - function + productTidy; // tidy product info - function + + debug = false; // for debug + mpId = 'ATVPDKIKX0DER'; // Marketplace Id - US + locale = 'en_US'; // Locale - English US + urlList = { // List of urls + urlPrd: 'http://www.amazon.com/gp/product/{{asin}}', + urlPDM: 'https://sellercentral.amazon.com/gp/fba/revenue-calculator/data/product-matches.html', + urlAFN: 'https://sellercentral.amazon.com/gp/fba/revenue-calculator/data/afn-fees.html', + urlMFN: 'https://sellercentral.amazon.com/gp/fba/revenue-calculator/data/mfn-fees.html' + }; + + // Returns url for product + urlPrd = function urlPrd() { + return urlList.urlPrd; + }; + + // Returns request url for product search + urlPDM = function urlPDM() { + return urlList.urlPDM; + }; + + // Returns request url for fees - FBA + urlAFN = function urlAFN() { + return urlList.urlAFN; + }; + + // Returns request url for fees - FBM + urlMFN = function urlMFN() { + return urlList.urlMFN; + }; + + // Search products (by UPC, EAN, ISBN or ASIN) and returns information + productSearch = function productSearch(keywords, callback) { + + var returnData = null, // return data + returnErr = null, // return error + returnErrs = [], // return data errors + returnItems = [], // return data items + query = null, // search query + reqOpt = { // request options + url: urlPDM(), + method: 'POST', + json: true, + timeout: 30000, + form: {"method": "GET", "model": null} + } + ; + + // Check keywords + if(keywords) { + if(typeof keywords === 'string') { + query = (keywords + '').trim(); + } else if(typeof keywords === 'object') { + if(keywords.query !== undefined && typeof keywords.query === 'string') { + query = (keywords.query + '').trim(); + } + } + } + + // Check query + if(!query) { + returnErr = { + "type": "fatal", + "code": "amzcos-001", + "source": "productSearch", + "message": "Missing query!" + }; + } + + if(returnErr) { + if(callback && typeof callback === 'function') { + return callback(returnErr, returnData); + } else { + return {"error": returnErr, "data": returnData}; + } + } + + // Send request + reqOpt.form.model = JSON.stringify({"searchString": query, "lang": locale, "marketPlace": mpId}); + + request(reqOpt, function (err, res, body) { + + if(!err && res.statusCode === 200) { + + var resObj; + + if(reqOpt.json === true) { + resObj = body; + } else { + try { + resObj = JSON.parse(body); + } catch(e) { + resObj = {"error": "Response content could not be parsed! (" + e + ")"}; + } + } + + if(resObj && !resObj.error) { + + /* + * Fields for body.data (array) + * + * asin: Amazon Standard Identification Number + * title: Product title. + * link: Link of the product (with reference) + * image: Image link of the product. + * thumbnail: Thumbnail link of the product. + * dimensions: Dimensions (dimensions.width, dimensions.length, dimensions.height) + * dimUnits: Units of the dimension. (inches, etc.) + * weight: Weight of the product. (float) + * weightUnits: Units of the weight. (pounds, etc.) + * gl: Product group code. (gl_*) + * productGroup: Product group Id. (number) + * subCategory: Sub category Id. (number) + * whiteGlovesRequired: Flag for "white gloves required" products. (N or Y) + */ + + // Items + var itemsLen = (resObj.data && resObj.data instanceof Array) ? resObj.data.length : 0; + + for (var i = 0; i < itemsLen; i++) { + returnItems.push(resObj.data[i]); + } + } else { + returnErr = { + "type": "fatal", + "code": "amzcos-002", + "source": "productSearch", + "message": (resObj && resObj.error) ? ('' + resObj.error) : "Bad response!" + }; + + returnErrs.push(returnErr); + } + } else { + returnErr = { + "type": "fatal", + "code": "amzcos-003", + "source": "productSearch", + "message": (err) ? ('' + err) : 'HTTP status code: ' + ('' + res.statusCode) + }; + + returnErrs.push(returnErr); + } + + if(returnItems.length || returnErrs.length) { + returnData = { + items: (returnItems.length) ? returnItems : null, + errors: (returnErrs.length) ? returnErrs : null + }; + } + + if(callback && typeof callback === 'function') { + return callback(returnErr, returnData); + } else { + return {"error": returnErr, "data": returnData}; + } + }); + }; + + // Returns product costs + productCosts = function productCosts(options, callback) { + + var returnData = null, // return data + returnErr = null, // return error + returnErrs = [], // errors for return + returnItems = [], // items for return + product = null, // product - object + cost = null, // cost - object + reqOpt = { // request options + url: null, + method: 'POST', + json: true, + timeout: 30000, + form: {"method": "GET", "model": null} + } + ; + + // Check options + if(options) { + if(typeof options === 'object') { + if(options.product !== undefined && typeof options.product === 'object') { + product = options.product; + } + + if(options.cost !== undefined && typeof options.cost === 'object') { + cost = options.cost; + } + } + } + + // Check product + if(!product || !product.asin) { + returnErr = { + "type": "fatal", + "code": "amzcos-004", + "source": "productCosts", + "message": "Missing product ASIN!" + }; + } + else if(!cost || !cost.costType || (cost.costType !== 'FBA' && cost.costType !== 'FBM')) { + returnErr = { + "type": "fatal", + "code": "amzcos-005", + "source": "productCosts", + "message": "Invalid cost type!" + }; + } + + if(returnErr) { + if(callback && typeof callback === 'function') { + return callback(returnErr, returnData); + } else { + return {"error": returnErr, "data": returnData}; + } + } + + // Prepare request + var model = product; + model['selected'] = true; + model['language'] = locale; + model['price'] = !isNaN(cost.productPrice) ? cost.productPrice : 0; + + if(cost.costType === 'FBA') { + reqOpt.url = urlAFN(); + + model['revenueTotal'] = cost['price']; + model['inbound-delivery'] = !isNaN(cost.inboundDelivery) ? cost.inboundDelivery : 0; + model['prep-service'] = !isNaN(cost.prepService) ? cost.prepService : 0; + model['fulfillmentTotal'] = cost['inbound-delivery'] + cost['prep-service']; + } else if(cost.costType === 'FBM') { + reqOpt.url = urlMFN(); + + model['shipping'] = !isNaN(cost.shipping) ? cost.shipping : 0; + model['order-handling'] = !isNaN(cost.orderHandling) ? cost.orderHandling : 0; + model['pick-pack'] = !isNaN(cost.pickPack) ? cost.pickPack : 0; + model['outbound-delivery'] = !isNaN(cost.outboundDelivery) ? cost.outboundDelivery : 0; + model['storage'] = !isNaN(cost.storage) ? cost.storage : 0; + model['inbound-delivery'] = !isNaN(cost.inboundDelivery) ? cost.inboundDelivery : 0; + model['customer-service'] = !isNaN(cost.customerService) ? cost.customerService : 0; + model['prep-service'] = !isNaN(cost.prepService) ? cost.prepService : 0; + model['revenueTotal'] = cost['price'] + cost['shipping']; + model['fulfillmentTotal'] = cost['order-handling'] + + cost['pick-pack'] + + cost['outbound-delivery'] + + cost['storage'] + + cost['inbound-delivery'] + + cost['customer-service'] + + cost['prep-service']; + } + + //console.log(JSON.stringify(model, null, 2)); // for debug + + // Send request + reqOpt.form.model = JSON.stringify(model); + + request(reqOpt, function (err, res, body) { + + if(!err && res.statusCode === 200) { + + var resObj; + + if(reqOpt.json === true) { + resObj = body; + } else { + try { + resObj = JSON.parse(body); + } catch(e) { + resObj = {"error": "Response content could not be parsed! (" + e + ")"}; + } + } + + if(resObj && !resObj.error) { + + /* + * Fields for body.data (array) + * + * weightHandlingFee: This fee is the weight based shipping fee for Fulfillment by Amazon orders. + * orderHandlingFee: A flat cost per order for fulfillment. + * fbaDeliveryServicesFee: Delivery services fee. + * commissionFee: Amazon's commossion fee, (Amazon referral fee) + * pickAndPackFee: This is the cost to physically retrieve the item. + * storageFee: Amazon warehouse charges for 30 days. + * variableClosingFee: Variable closing fee. + */ + + // Items + if(resObj.data && resObj.data instanceof Object) { + var tItem = { + asin: product.asin, + cost: { + merchant: {}, + amazon: resObj.data + } + }; + + // merchant costs + for(var costItem in cost) { + if(cost.hasOwnProperty(costItem)) { + tItem.cost.merchant[costItem] = cost[costItem]; + } + } + + returnItems.push(tItem); + } + } else { + returnErr = { + "type": "fatal", + "code": "amzcos-006", + "source": "productCosts", + "message": (resObj && resObj.error) ? ('' + resObj.error) : "Bad response!" + }; + + returnErrs.push(returnErr); + } + } else { + returnErr = { + "type": "fatal", + "code": "amzcos-007", + "source": "productCosts", + "message": (err) ? ('' + err) : 'HTTP status code: ' + ('' + res.statusCode) + }; + + returnErrs.push(returnErr); + } + + if(returnItems.length || returnErrs.length) { + returnData = { + items: (returnItems.length) ? returnItems : null, + errors: (returnErrs.length) ? returnErrs : null + }; + } + + if(callback && typeof callback === 'function') { + return callback(returnErr, returnData); + } else { + return {"error": returnErr, "data": returnData}; + } + }); + }; + + // Returns tidy product object + productTidy = function productTidy(product) { + + var returnData = null; + + if(product && typeof product === 'object') { + returnData = { + "asin": product.asin || null, + "title": product.title || null, + "link": (product.asin) ? urlPrd().replace('{{asin}}', product.asin) : null, + "image": (product.image) ? product.image.replace('_SL120_', '_SL512_') : null, + "weight": { + "unit": product.weightUnits || null, + "weight": product.weight || null + }, + "dimension": { + "unit": product.dimUnits || null, + "length": (product.dimensions && product.dimensions.length) || null, + "height": (product.dimensions && product.dimensions.height) || null, + "width": (product.dimensions && product.dimensions.width) || null + }, + "other": { + "prodcutGroupId": product.productGroup || null, + "prodcutGroupCode": product.gl || null, + "subCategoryId": product.subCategory || null, + "whiteGlovesRequired": product.whiteGlovesRequired || null + } + }; + } + + return returnData; + }; + + // Return + return { + productSearch: productSearch, + productCosts: productCosts, + productTidy: productTidy + }; +}(); \ No newline at end of file diff --git a/lib/amazon-costs.js b/lib/amazon-costs.js deleted file mode 100644 index a1beb75..0000000 --- a/lib/amazon-costs.js +++ /dev/null @@ -1,404 +0,0 @@ -/* - * Amazon Costs - * Copyright (c) 2013 Fatih Cetinkaya (http://github.com/cmfatih/amazon-costs) - * For the full copyright and license information, please view the LICENSE.txt file. - */ - -/* jslint node: true */ -'use strict'; - -var request = require('request'); // request module - -// Init the module -exports = module.exports = function amzCosts() { - - var debug, // debug - urlList, // url list - mpId, // Amazon marketplace Id - locale, // locale - urlPrd, // product url - urlPDM, // request url for product search - urlAFN, // request url for fees (fulfillment by Amazon) - urlMFN, // request url for fees (fulfillment by merchant) - productSearch, // product search - function - productCosts, // product costs - function - productTidy; // tidy product info - function - - debug = false; // for debug - mpId = 'ATVPDKIKX0DER'; // Marketplace Id - US - locale = 'en_US'; // Locale - English US - urlList = { // List of urls - urlPrd: 'http://www.amazon.com/gp/product/{{asin}}', - urlPDM: 'https://sellercentral.amazon.com/gp/fba/revenue-calculator/data/product-matches.html', - urlAFN: 'https://sellercentral.amazon.com/gp/fba/revenue-calculator/data/afn-fees.html', - urlMFN: 'https://sellercentral.amazon.com/gp/fba/revenue-calculator/data/mfn-fees.html' - }; - - // Returns url for product - urlPrd = function urlPrd() { - return urlList.urlPrd; - }; - - // Returns request url for product search - urlPDM = function urlPDM() { - return urlList.urlPDM; - }; - - // Returns request url for fees - FBA - urlAFN = function urlAFN() { - return urlList.urlAFN; - }; - - // Returns request url for fees - FBM - urlMFN = function urlMFN() { - return urlList.urlMFN; - }; - - // Search products (by UPC, EAN, ISBN or ASIN) and returns information - productSearch = function productSearch(keywords, callback) { - - var returnData = null, // return data - returnErr = null, // return error - returnErrs = [], // return data errors - returnItems = [], // return data items - query = null, // search query - reqOpt = { // request options - url: urlPDM(), - method: 'POST', - json: true, - timeout: 30000, - form: {"method": "GET", "model": null} - } - ; - - // Check keywords - if(keywords) { - if(typeof keywords === 'string') { - query = (keywords + '').trim(); - } else if(typeof keywords === 'object') { - if(keywords.query !== undefined && typeof keywords.query === 'string') { - query = (keywords.query + '').trim(); - } - } - } - - // Check query - if(!query) { - returnErr = { - "type": "fatal", - "code": "amzcos-001", - "source": "productSearch", - "message": "Missing query!" - }; - } - - if(returnErr) { - if(callback && typeof callback === 'function') { - return callback(returnErr, returnData); - } else { - return {"error": returnErr, "data": returnData}; - } - } - - // Send request - reqOpt.form.model = JSON.stringify({"searchString": query, "lang": locale, "marketPlace": mpId}); - - request(reqOpt, function (err, res, body) { - - if(!err && res.statusCode === 200) { - - var resObj; - - if(reqOpt.json === true) { - resObj = body; - } else { - try { - resObj = JSON.parse(body); - } catch(e) { - resObj = {"error": "Response content could not be parsed! (" + e + ")"}; - } - } - - if(resObj && !resObj.error) { - - /* - * Fields for body.data (array) - * - * asin: Amazon Standard Identification Number - * title: Product title. - * link: Link of the product (with reference) - * image: Image link of the product. - * thumbnail: Thumbnail link of the product. - * dimensions: Dimensions (dimensions.width, dimensions.length, dimensions.height) - * dimUnits: Units of the dimension. (inches, etc.) - * weight: Weight of the product. (float) - * weightUnits: Units of the weight. (pounds, etc.) - * gl: Product group code. (gl_*) - * productGroup: Product group Id. (number) - * subCategory: Sub category Id. (number) - * whiteGlovesRequired: Flag for "white gloves required" products. (N or Y) - */ - - // Items - var itemsLen = (resObj.data && resObj.data instanceof Array) ? resObj.data.length : 0; - - for (var i = 0; i < itemsLen; i++) { - returnItems.push(resObj.data[i]); - } - } else { - returnErr = { - "type": "fatal", - "code": "amzcos-002", - "source": "productSearch", - "message": (resObj && resObj.error) ? ('' + resObj.error) : "Bad response!" - }; - - returnErrs.push(returnErr); - } - } else { - returnErr = { - "type": "fatal", - "code": "amzcos-003", - "source": "productSearch", - "message": (err) ? ('' + err) : 'HTTP status code: ' + ('' + res.statusCode) - }; - - returnErrs.push(returnErr); - } - - if(returnItems.length || returnErrs.length) { - returnData = { - items: (returnItems.length) ? returnItems : null, - errors: (returnErrs.length) ? returnErrs : null - }; - } - - if(callback && typeof callback === 'function') { - return callback(returnErr, returnData); - } else { - return {"error": returnErr, "data": returnData}; - } - }); - }; - - // Returns product costs - productCosts = function productCosts(options, callback) { - - var returnData = null, // return data - returnErr = null, // return error - returnErrs = [], // errors for return - returnItems = [], // items for return - product = null, // product - object - cost = null, // cost - object - reqOpt = { // request options - url: null, - method: 'POST', - json: true, - timeout: 30000, - form: {"method": "GET", "model": null} - } - ; - - // Check options - if(options) { - if(typeof options === 'object') { - if(options.product !== undefined && typeof options.product === 'object') { - product = options.product; - } - - if(options.cost !== undefined && typeof options.cost === 'object') { - cost = options.cost; - } - } - } - - // Check product - if(!product || !product.asin) { - returnErr = { - "type": "fatal", - "code": "amzcos-004", - "source": "productCosts", - "message": "Missing product ASIN!" - }; - } - else if(!cost || !cost.costType || (cost.costType !== 'FBA' && cost.costType !== 'FBM')) { - returnErr = { - "type": "fatal", - "code": "amzcos-005", - "source": "productCosts", - "message": "Invalid cost type!" - }; - } - - if(returnErr) { - if(callback && typeof callback === 'function') { - return callback(returnErr, returnData); - } else { - return {"error": returnErr, "data": returnData}; - } - } - - // Prepare request - var model = product; - model['selected'] = true; - model['language'] = locale; - model['price'] = !isNaN(cost.productPrice) ? cost.productPrice : 0; - - if(cost.costType === 'FBA') { - reqOpt.url = urlAFN(); - - model['revenueTotal'] = cost['price']; - model['inbound-delivery'] = !isNaN(cost.inboundDelivery) ? cost.inboundDelivery : 0; - model['prep-service'] = !isNaN(cost.prepService) ? cost.prepService : 0; - model['fulfillmentTotal'] = cost['inbound-delivery'] + cost['prep-service']; - } else if(cost.costType === 'FBM') { - reqOpt.url = urlMFN(); - - model['shipping'] = !isNaN(cost.shipping) ? cost.shipping : 0; - model['order-handling'] = !isNaN(cost.orderHandling) ? cost.orderHandling : 0; - model['pick-pack'] = !isNaN(cost.pickPack) ? cost.pickPack : 0; - model['outbound-delivery'] = !isNaN(cost.outboundDelivery) ? cost.outboundDelivery : 0; - model['storage'] = !isNaN(cost.storage) ? cost.storage : 0; - model['inbound-delivery'] = !isNaN(cost.inboundDelivery) ? cost.inboundDelivery : 0; - model['customer-service'] = !isNaN(cost.customerService) ? cost.customerService : 0; - model['prep-service'] = !isNaN(cost.prepService) ? cost.prepService : 0; - model['revenueTotal'] = cost['price'] + cost['shipping']; - model['fulfillmentTotal'] = cost['order-handling'] - + cost['pick-pack'] - + cost['outbound-delivery'] - + cost['storage'] - + cost['inbound-delivery'] - + cost['customer-service'] - + cost['prep-service']; - } - - //console.log(JSON.stringify(model, null, 2)); // for debug - - // Send request - reqOpt.form.model = JSON.stringify(model); - - request(reqOpt, function (err, res, body) { - - if(!err && res.statusCode === 200) { - - var resObj; - - if(reqOpt.json === true) { - resObj = body; - } else { - try { - resObj = JSON.parse(body); - } catch(e) { - resObj = {"error": "Response content could not be parsed! (" + e + ")"}; - } - } - - if(resObj && !resObj.error) { - - /* - * Fields for body.data (array) - * - * weightHandlingFee: This fee is the weight based shipping fee for Fulfillment by Amazon orders. - * orderHandlingFee: A flat cost per order for fulfillment. - * fbaDeliveryServicesFee: Delivery services fee. - * commissionFee: Amazon's commossion fee, (Amazon referral fee) - * pickAndPackFee: This is the cost to physically retrieve the item. - * storageFee: Amazon warehouse charges for 30 days. - * variableClosingFee: Variable closing fee. - */ - - // Items - if(resObj.data && resObj.data instanceof Object) { - var tItem = { - asin: product.asin, - cost: { - merchant: {}, - amazon: resObj.data - } - }; - - // merchant costs - for(var costItem in cost) { - if(cost.hasOwnProperty(costItem)) { - tItem.cost.merchant[costItem] = cost[costItem]; - } - } - - returnItems.push(tItem); - } - } else { - returnErr = { - "type": "fatal", - "code": "amzcos-006", - "source": "productCosts", - "message": (resObj && resObj.error) ? ('' + resObj.error) : "Bad response!" - }; - - returnErrs.push(returnErr); - } - } else { - returnErr = { - "type": "fatal", - "code": "amzcos-007", - "source": "productCosts", - "message": (err) ? ('' + err) : 'HTTP status code: ' + ('' + res.statusCode) - }; - - returnErrs.push(returnErr); - } - - if(returnItems.length || returnErrs.length) { - returnData = { - items: (returnItems.length) ? returnItems : null, - errors: (returnErrs.length) ? returnErrs : null - }; - } - - if(callback && typeof callback === 'function') { - return callback(returnErr, returnData); - } else { - return {"error": returnErr, "data": returnData}; - } - }); - }; - - // Returns tidy product object - productTidy = function productTidy(product) { - - var returnData = null; - - if(product && typeof product === 'object') { - returnData = { - "asin": product.asin || null, - "title": product.title || null, - "link": (product.asin) ? urlPrd().replace('{{asin}}', product.asin) : null, - "image": (product.image) ? product.image.replace('_SL120_', '_SL512_') : null, - "weight": { - "unit": product.weightUnits || null, - "weight": product.weight || null - }, - "dimension": { - "unit": product.dimUnits || null, - "length": (product.dimensions && product.dimensions.length) || null, - "height": (product.dimensions && product.dimensions.height) || null, - "width": (product.dimensions && product.dimensions.width) || null - }, - "other": { - "prodcutGroupId": product.productGroup || null, - "prodcutGroupCode": product.gl || null, - "subCategoryId": product.subCategory || null, - "whiteGlovesRequired": product.whiteGlovesRequired || null - } - }; - } - - return returnData; - }; - - // Return - return { - productSearch: productSearch, - productCosts: productCosts, - productTidy: productTidy - }; -}(); \ No newline at end of file diff --git a/package.json b/package.json index b0540fe..c49e229 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,15 @@ { "name": "amazon-costs", "version": "1.0.1", - "description": "Amazon Costs is a module for retrieving Amazon product information and calculating costs for fulfillment and merchant channels.", + "description": "A module for retrieving Amazon product information and calculating costs for fulfillment and merchant channels", + "main": "index.js", + "scripts": { + "test": "node ./node_modules/mocha/bin/mocha --timeout 5000 --slow 2000 --reporter spec test/test-all.js" + }, + "repository": { + "type": "git", + "url": "https://github.com/devfacet/amazon-costs.git" + }, "keywords": [ "amazon", "costs", @@ -10,33 +18,14 @@ "fba", "fbm" ], - "homepage": "http://github.com/cmfatih/amazon-costs", - "repository": { - "type": "git", - "url": "https://github.com/cmfatih/amazon-costs.git" - }, - "bugs": { - "url": "http://github.com/cmfatih/amazon-costs/issues" - }, + "author": "devfacet", "license": "MIT", - "private": false, - "author": { - "name": "cmfatih", - "url": "http://github.com/cmfatih" - }, - "contributors": [], - "main": "./lib/amazon-costs.js", - "scripts": { - "test": "node ./node_modules/mocha/bin/mocha --timeout 5000 --slow 2000 --reporter spec test/test-all.js" - }, - "engines": [ - "node >= 0.10.0" - ], + "homepage": "http://github.com/devfacet/amazon-costs", "dependencies": { "request": "2.x" }, "devDependencies": { - "mocha": "2.2.x", - "chai": "2.1.x" + "mocha": "3.2.x", + "chai": "3.5.x" } } \ No newline at end of file diff --git a/test/test-all.js b/test/test-all.js index 06add9b..1a4924c 100644 --- a/test/test-all.js +++ b/test/test-all.js @@ -15,7 +15,7 @@ describe('amzCosts', function() { // Test for product search describe('productSearch', function() { - it('should search for ASIN ' + asin, function(done) { + it.skip('should search for ASIN ' + asin, function(done) { // Search product amzCosts.productSearch(asin, function(err, data) { @@ -37,7 +37,7 @@ describe('amzCosts', function() { // Test for product search result describe('productSearch result', function() { - it('should be valid for ASIN ' + asin, function(done) { + it.skip('should be valid for ASIN ' + asin, function(done) { if(!result || !(result.items instanceof Array) || result.items[0] && result.items[0].asin !== asin) { done('Invalid product!'); } else { @@ -48,7 +48,7 @@ describe('amzCosts', function() { // Test for product costs - FBA describe('productCosts', function() { - it('should calculate FBA costs for ASIN ' + asin, function(done) { + it.skip('should calculate FBA costs for ASIN ' + asin, function(done) { // Costs for FBA amzCosts.productCosts({ @@ -80,7 +80,7 @@ describe('amzCosts', function() { // Test for product costs - FBM describe('productCosts', function() { - it('should calculate FBM costs for ASIN ' + asin, function(done) { + it.skip('should calculate FBM costs for ASIN ' + asin, function(done) { // Costs for FBA amzCosts.productCosts({ @@ -118,7 +118,7 @@ describe('amzCosts', function() { // Test for product tidy describe('productTidy', function() { - it('should run without any error for ASIN ' + asin, function(done) { + it.skip('should run without any error for ASIN ' + asin, function(done) { // Tidy product var prodTidy = amzCosts.productTidy(result.items[0]);