From 322a568824cc0e610f959d562c650fcf8f223beb Mon Sep 17 00:00:00 2001 From: Jeff Zabel Date: Thu, 24 Jul 2014 12:38:03 -0600 Subject: [PATCH 01/11] Clean up spaces and missing semi-colons --- lib/recurly.js | 40 +++++++++++++------------- lib/routes/index.js | 2 +- lib/routes/v2.js | 20 ++++++------- lib/transparent.js | 68 ++++++++++++++++++++++----------------------- lib/utils.js | 4 +-- 5 files changed, 67 insertions(+), 67 deletions(-) diff --git a/lib/recurly.js b/lib/recurly.js index 81e4cce..b27e38b 100644 --- a/lib/recurly.js +++ b/lib/recurly.js @@ -26,9 +26,9 @@ t.request(utils.addParams(routes.accounts.close, {account_code: accountcode}), callback); }, reopen: function(accountcode, callback){ - t.request(utils.addParams(routes.accounts.reopen, {account_code: accountcode}), callback) + t.request(utils.addParams(routes.accounts.reopen, {account_code: accountcode}), callback); } - } + }; this.adjustments = { get: function(accountcode, callback){ @@ -40,7 +40,7 @@ remove: function(uuid, callback){ t.request(utils.addParams(routes.adjustments.remove, {uuid: uuid}), callback); } - } + }; //http://docs.recurly.com/api/billing-info this.billingInfo = { @@ -53,7 +53,7 @@ remove: function(accountcode, callback){ t.request(utils.addParams(routes.billingInfo.remove, {account_code: accountcode} ), callback); } - } + }; //http://docs.recurly.com/api/coupons this.coupons = { @@ -69,7 +69,7 @@ deactivate: function(couponcode, callback){ t.request(utils.addParams(routes.coupons.deactivate, {coupon_code: couponcode}), callback); } - } + }; this.couponRedemption = { redeem: function(couponcode, details, callback){ @@ -82,9 +82,9 @@ t.request(utils.addParams(routes.couponRedemption.remove, {account_code: accountcode}), callback); }, getByInvoice: function(invoicenumber, callback){ - t.request(utils.addParams(routes.couponRedemption.getByInvoice, {invoice_number: invoicenumber}), callback) + t.request(utils.addParams(routes.couponRedemption.getByInvoice, {invoice_number: invoicenumber}), callback); } - } + }; this.invoices = { list: function(callback, filter){ @@ -93,9 +93,9 @@ listByAccount: function(accountcode, callback, filter){ t.request( utils.addParams( - utils.addQueryParams(routes.invoices.listByAccount, filter) - , {account_code: accountcode}) - , callback) + utils.addQueryParams(routes.invoices.listByAccount, filter), + {account_code: accountcode}), + callback); }, get: function(invoicenumber, callback){ t.request(utils.addParams(routes.invoices.get, {invoice_number: invoicenumber}), callback); @@ -109,7 +109,7 @@ markFailed: function(invoicenumber, callback){ t.request(utils.addParams(routes.invoices.markFailed, {invoice_number: invoicenumber}), callback); } - } + }; this.plans = { list: function(callback, filter){ @@ -127,7 +127,7 @@ remove: function(plancode, callback){ t.request(utils.addParams(routes.plans.remove, {plan_code: plancode}), callback); } - } + }; this.planAddons = { list: function(plancode, callback, filter){ @@ -141,16 +141,16 @@ }, update: function(plancode, addoncode, details, callback){ t.request(utils.addParams( - routes.planAddons.update, + routes.planAddons.update, { plan_code: plancode, add_on_code: addoncode - }), + }), callback, new Js2xml('add_on', details).toString()); }, remove: function(plancode, addoncode, callback){ t.request(utils.addParams(routes.planAddons.remove, {plan_code: plancode, add_on_code: addoncode}), callback); } - } + }; this.subscriptions = { list: function(callback, filter){ @@ -181,7 +181,7 @@ postpone: function(uuid, nextRenewalDate, callback){ t.request(utils.addParams(routes.subscriptions.postpone, {uuid: uuid, next_renewal_date: nextRenewalDate}), callback); } - } + }; this.transactions = { list: function(callback, filter){ @@ -204,8 +204,8 @@ if(amount){ route = utils.addQueryParams(route, { amount_in_cents: amount }); } - t.request(route, callback) + t.request(route, callback); } - } - }//end class -})(); + }; + }; //end class +})(); \ No newline at end of file diff --git a/lib/routes/index.js b/lib/routes/index.js index 91cf80d..d40a54f 100644 --- a/lib/routes/index.js +++ b/lib/routes/index.js @@ -1,5 +1,5 @@ var routes = { - 'v2': require('./v2') + 'v2': require('./v2') }; diff --git a/lib/routes/v2.js b/lib/routes/v2.js index d3fa0f6..93f4b22 100644 --- a/lib/routes/v2.js +++ b/lib/routes/v2.js @@ -5,33 +5,33 @@ exports.accounts = { update: ['/v2/accounts/:account_code', 'PUT'], close: ['/v2/accounts/:account_code', 'DELETE'], reopen: ['/v2/accounts/:account_code/reopen', 'PUT'] -} +}; exports.adjustments = { get: ['/v2/accounts/:account_code/adjustments', 'GET'], create: ['/v2/accounts/:account_code/adjustments', 'POST'], remove: ['/v2/adjustments/:uuid', 'DELETE'] -} +}; exports.billingInfo = { get: ['/v2/accounts/:account_code/billing_info', 'GET'], update: ['/v2/accounts/:account_code/billing_info', 'PUT'], remove: ['/v2/accounts/:account_code/billing_info', 'DELETE'] -} +}; exports.coupons = { list: ['/v2/coupons', 'GET'], get: ['/v2/coupons/:coupon_code', 'GET'], create: ['/v2/coupons', 'POST'], deactivate: ['/v2/coupons/:coupon_code', 'DELETE'] -} +}; exports.couponRedemption = { redeem: ['/v2/coupons/:coupon_code/redeem', 'POST'], get: ['/v2/accounts/:account_code/redemption', 'GET'], remove: ['/v2/accounts/:account_code/redemption', 'DELETE'], getByInvoice: ['/v2/invoices/:invoice_number/redemption', 'GET'] -} +}; exports.invoices = { list: ['/v2/invoices', 'GET'], @@ -40,7 +40,7 @@ exports.invoices = { create: ['/v2/accounts/:account_code/invoices', 'POST'], markSuccessful: ['/v2/invoices/:invoice_number/mark_successful', 'PUT'], markFailed: ['/v2/invoices/:invoice_number/mark_failed', 'PUT'] -} +}; exports.plans = { list: ['/v2/plans', 'GET'], @@ -48,7 +48,7 @@ exports.plans = { create: ['/v2/plans', 'POST'], update: ['/v2/plans/:plan_code', 'PUT'], remove: ['/v2/plans/:plan_code', 'DELETE'] -} +}; exports.planAddons = { list: ['/v2/plans/:plan_code/add_ons', 'GET'], @@ -56,7 +56,7 @@ exports.planAddons = { create: ['/v2/plans/:plan_code/add_ons', 'POST'], update: ['/v2/plans/:plan_code/add_ons/:add_on_code', 'PUT'], remove: ['/v2/plans/:plan_code/add_ons/:add_on_code', 'DELETE'] -} +}; exports.subscriptions = { list: ['/v2/subscriptions', 'GET'], @@ -68,7 +68,7 @@ exports.subscriptions = { reactivate: ['/v2/subscriptions/:uuid/reactivate', 'PUT'], terminate: ['/v2/subscriptions/:uuid/terminate?refund=:refund_type', 'PUT'], postpone: ['/v2/subscriptions/:uuid/postpone?next_renewal_date=:next_renewal_date', 'PUT'] -} +}; exports.transactions = { list: ['/v2/transactions', 'GET'], @@ -76,4 +76,4 @@ exports.transactions = { get: ['/v2/transactions/:id', 'GET'], create: ['/v2/transactions', 'POST'], refund: ['/v2/transactions/:id', 'DELETE'] -} +}; diff --git a/lib/transparent.js b/lib/transparent.js index 01f6d93..63471a9 100644 --- a/lib/transparent.js +++ b/lib/transparent.js @@ -20,29 +20,29 @@ t.debug(TRANSACTION_URL); t.debug('============================'); - this.billingInfoUrl = function(){return BILLING_INFO_URL}; - this.subscribeUrl = function(){return SUBSCRIBE_URL}; - this.transactionUrl = function(){return TRANSACTION_URL}; + this.billingInfoUrl = function(){return BILLING_INFO_URL;}; + this.subscribeUrl = function(){return SUBSCRIBE_URL;}; + this.transactionUrl = function(){return TRANSACTION_URL;}; this.hidden_field = function(data){ return ''; - } + }; this.getResults = function(confirm, result, status, type, callback){ - validateQueryString(confirm, type, status, result) + validateQueryString(confirm, type, status, result); t.request('/transparent/results/' + result, 'GET', callback); - } + }; this.getFormValuesFromResult = function getFormValuesFromResult(result, type){ var fields = {}; var errors = []; t.traverse(result.data,function(key, value, parent){ var shouldprint = false; - var toprint = '' + var toprint = ''; if(value instanceof Object){ if(Object.keys(value).length === 0){ shouldprint = true; - toprint = '' + toprint = ''; } if(Object.hasOwnProperty('@') || Object.hasOwnProperty('#')){ shouldprint = true; @@ -95,7 +95,7 @@ }); errors = handleFuzzyLogicSpecialCases(errors); return {fields: fields, errors: errors}; - } + }; function processErrors(errors, parent){ var acc = []; @@ -113,7 +113,7 @@ errors.forEach(function(item){ if(item instanceof Array){ //loop through the error list - item.forEach(processSingleError) + item.forEach(processSingleError); } else{ //its a single error so grab it out @@ -151,13 +151,13 @@ function makeDate(){ var d = new Date(); - var addleadingzero = function(n){ return (n<10)?'0'+n:''+n }; + var addleadingzero = function(n){ return (n<10)?'0'+n:''+n ;}; return d.getUTCFullYear() + '-' + - addleadingzero(d.getUTCMonth()+1) + '-' + - addleadingzero(d.getUTCDate()) + 'T' + - addleadingzero(d.getUTCHours()) + ':' + - addleadingzero(d.getUTCMinutes()) + ':' + - addleadingzero(d.getUTCSeconds()) + 'Z'; + addleadingzero(d.getUTCMonth()+1) + '-' + + addleadingzero(d.getUTCDate()) + 'T' + + addleadingzero(d.getUTCHours()) + ':' + + addleadingzero(d.getUTCMinutes()) + ':' + + addleadingzero(d.getUTCSeconds()) + 'Z'; } function hash(data) { @@ -171,7 +171,7 @@ return hmac.digest('hex'); //php: 03021207ad681f2ea9b9e1fc20ac7ae460d8d988 <== Yes this sign is identical to the php version //node: 03021207ad681f2ea9b9e1fc20ac7ae460d8d988 - } + } function buildQueryStringFromSortedObject(params){ return params.map(function(p){ @@ -180,32 +180,32 @@ } function makeSortedObject(obj, casesensitive){ - return Object.keys(obj).map(function(key){ - return {key: key, value: obj[key]}; - }).sort(function(a,b){ - return (casesensitive? a.key : a.key.toLowerCase()) > (casesensitive? b.key : b.key.toLowerCase()); - }); + return Object.keys(obj).map(function(key){ + return {key: key, value: obj[key]}; + }).sort(function(a,b){ + return (casesensitive? a.key : a.key.toLowerCase()) > (casesensitive? b.key : b.key.toLowerCase()); + }); } //Used for validating return params from Recurly - function validateQueryString(confirm, type, status, result_key) - { - var values = { + function validateQueryString(confirm, type, status, result_key) + { + var values = { result: result_key, status: status, type: type - } - var query_values = buildQueryStringFromSortedObject(makeSortedObject(values, true)); - hashed_values = hash(query_values); + }; + var query_values = buildQueryStringFromSortedObject(makeSortedObject(values, true)); + hashed_values = hash(query_values); - if(hashed_values !== confirm) { - throw "Error: Forged query string"; - } + if(hashed_values !== confirm) { + throw "Error: Forged query string"; + } return true; - } + } function handleFuzzyLogicSpecialCases(errors){ - var toreturn = [] + var toreturn = []; errors.forEach(function(e){ switch(e.field){ case 'billing_info[verification_value]': @@ -228,6 +228,6 @@ return {field: name,reason: error.reason}; } - }//END CLASS + }; //END CLASS })(); \ No newline at end of file diff --git a/lib/utils.js b/lib/utils.js index 329e404..848d109 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -6,7 +6,7 @@ exports.addParams = function(route, keys){ return keys[key]; }); return newRoute; -} +}; exports.addQueryParams = function(route, params){ var newRoute = route.slice(); @@ -18,4 +18,4 @@ exports.addQueryParams = function(route, params){ } if(_params.length > 0) return [newRoute[0] + '?' + _params.join('&'), newRoute[1]]; else return newRoute; -} \ No newline at end of file +}; \ No newline at end of file From e86561cc50daefe6006f01d7517673c40987d35f Mon Sep 17 00:00:00 2001 From: Jeff Zabel Date: Mon, 28 Jul 2014 13:52:52 -0600 Subject: [PATCH 02/11] Update recurly library to support recursion of multiple pages with a listAll function. --- lib/recurly.js | 24 ++++++++++++++++++++- lib/utils.js | 57 +++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 79 insertions(+), 2 deletions(-) diff --git a/lib/recurly.js b/lib/recurly.js index b27e38b..ed4ef99 100644 --- a/lib/recurly.js +++ b/lib/recurly.js @@ -2,6 +2,8 @@ var Js2xml = require('js2xml').Js2Xml, Client = require('./client'), utils = require('./utils'), + _ = require('lodash'), + url = require('url'), router = require('./routes/'); module.exports = function(config){ @@ -13,6 +15,9 @@ list: function(callback, filter){ t.request(utils.addQueryParams(routes.accounts.list, filter), callback); }, + listAll: function(callback, filter) { + utils.iterateRequest(t, routes.accounts.list, filter, 'accounts', 'account', callback); + }, get: function(accountcode, callback){ t.request(utils.addParams(routes.accounts.get, {account_code: accountcode}), callback); }, @@ -60,6 +65,9 @@ list: function(callback, filter){ t.request(utils.addQueryParams(routes.coupons.list, filter), callback); }, + listAll: function(callback, filter) { + utils.iterateRequest(t, routes.coupons.list, filter, 'coupons', 'coupon', callback); + }, get: function(couponcode, callback){ t.request(utils.addParams(routes.coupons.get, {coupon_code: couponcode}), callback); }, @@ -90,6 +98,9 @@ list: function(callback, filter){ t.request(utils.addQueryParams(routes.invoices.list, filter), callback); }, + listAll: function(callback, filter) { + utils.iterateRequest(t, routes.invoices.list, filter, 'invoices', 'invoice', callback); + }, listByAccount: function(accountcode, callback, filter){ t.request( utils.addParams( @@ -115,6 +126,9 @@ list: function(callback, filter){ t.request(utils.addQueryParams(routes.plans.list, filter), callback); }, + listAll: function(callback, filter) { + utils.iterateRequest(t, routes.plans.list, filter, 'plans', 'plan', callback); + }, get: function(plancode, callback){ t.request(utils.addParams(routes.plans.get, {plan_code: plancode}), callback); }, @@ -133,6 +147,9 @@ list: function(plancode, callback, filter){ t.request(utils.addParams(utils.addQueryParams(routes.planAddons.list, filter), {plan_code: plancode}), callback); }, + listAll: function(plancode, callback, filter) { + utils.iterateRequest(t, routes.planAddons.list, filter, 'add_ons', 'ad_on', callback); + }, get: function(plancode, addoncode, callback){ t.request(utils.addParams(routes.planAddons.get, {plan_code: plancode, addon_code: addoncode}), callback); }, @@ -156,9 +173,11 @@ list: function(callback, filter){ t.request(utils.addQueryParams(routes.subscriptions.list, filter), callback); }, + listAll: function(callback, filter) { + utils.iterateRequest(t, routes.subscriptions.list, filter, 'subscriptions', 'subscription', callback); + }, listByAccount: function(accountcode, callback){ t.request(utils.addParams(routes.subscriptions.listByAccount, {account_code: accountcode}), callback); - }, get: function(uuid, callback){ t.request(utils.addParams(routes.subscriptions.get, {uuid: uuid}), callback); @@ -187,6 +206,9 @@ list: function(callback, filter){ t.request(utils.addQueryParams(routes.transactions.list, filter), callback); }, + listAll: function(callback, filter) { + utils.iterateRequest(t, routes.transactions.list, filter, 'transactions', 'transaction', callback); + }, listByAccount: function(accountCode, callback, filter){ t.request( utils.addParams( diff --git a/lib/utils.js b/lib/utils.js index 848d109..3c13f04 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -8,7 +8,7 @@ exports.addParams = function(route, keys){ return newRoute; }; -exports.addQueryParams = function(route, params){ +var addQueryParams = exports.addQueryParams = function(route, params){ var newRoute = route.slice(); var _params = []; if(params){ @@ -18,4 +18,59 @@ exports.addQueryParams = function(route, params){ } if(_params.length > 0) return [newRoute[0] + '?' + _params.join('&'), newRoute[1]]; else return newRoute; +}; + +var parseUrlLink = exports.parseUrlLink = function(result, callback) { + var links = {}; + // Parse out our link + if(result.headers.link) { + // link format: + // '; rel="next", ; rel="last"' + result.headers.link.replace(/<([^>]*)>;\s*rel="([\w]*)\"/g, function(m, uri, type) { + links[type] = uri; + }); + return links; + } else { + return null; + } +}; + +exports.iterateRequest = function(client, route, queryString, endpointName, singleItemName, callback) { + var url = require('url'); + + // Set up our results that we'll return + var results = { + data: {} + }; + results.data[endpointName] = {}; + results.data[endpointName][singleItemName] = []; + + var doListAll = function(cursor, callback) { + client.request(cursor, function(error, result) { + if(error) { + return callback(error); + } else { + // Test to see if we have more pages + if (result.headers.link) { + var links = parseUrlLink(result); + // Store off our current results + results.data[endpointName][singleItemName] = results.data[endpointName][singleItemName].concat(result.data[endpointName][singleItemName]); + if(links.next) { + // Let's just get our querystring params + queryString = url.parse(links.next, true).query; + // Recurse + doListAll(addQueryParams(route, queryString), callback); + } else { + // We are done so return + return callback(null, results); + } + } else { // We didn't have a next link - just return the results + return callback(null, result); + } + } + }, null); + }; + + //Kick off the listing + doListAll(addQueryParams(route, queryString), callback); }; \ No newline at end of file From 8e0e3689c4c561ab11eed824873d1a2c0992c2be Mon Sep 17 00:00:00 2001 From: Jeff Zabel Date: Mon, 28 Jul 2014 13:54:40 -0600 Subject: [PATCH 03/11] Bump version number for recurly recursion fixes --- package.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index d869925..1896667 100644 --- a/package.json +++ b/package.json @@ -1,13 +1,14 @@ { "name" : "node-recurly" , "description" : "Library for accessing the api for the Recurly recurring billing service." , "keywords" : [ "recurly", "e-commerce", "recurring billing" ] -, "version" : "2.1.0" +, "version" : "2.1.1" , "homepage" : "https://github.com/robrighter/node-recurly" , "author" : "Rob Righter (http://github.com/robrighter)" , "contributors" : [ "Iván Guardado (http://github.com/IvanGuardado)", "Rob Righter (http://github.com/robrighter)", - "Dmitriy Shekhovtsov (https://github.com/valorkin)" + "Dmitriy Shekhovtsov (https://github.com/valorkin)", + "Jeff Zabel (https://github.com/jzabel)" ] , "bugs" : { "web" : "https://github.com/robrighter/node-recurly/issues" } From fd66899354aa6c5ff251ce5d33393733320a1e89 Mon Sep 17 00:00:00 2001 From: Islam Sharabash Date: Fri, 26 Sep 2014 17:04:27 -0700 Subject: [PATCH 04/11] adding a makeForEachIterator --- lib/recurly.js | 30 +++++------------- lib/utils.js | 83 ++++++++++++++++++++++++++++++-------------------- package.json | 53 +++++++++++++++++++------------- 3 files changed, 90 insertions(+), 76 deletions(-) diff --git a/lib/recurly.js b/lib/recurly.js index ed4ef99..90347f8 100644 --- a/lib/recurly.js +++ b/lib/recurly.js @@ -15,9 +15,7 @@ list: function(callback, filter){ t.request(utils.addQueryParams(routes.accounts.list, filter), callback); }, - listAll: function(callback, filter) { - utils.iterateRequest(t, routes.accounts.list, filter, 'accounts', 'account', callback); - }, + forEach: utils.makeForEachIterator(t, routes.accounts.list, 'accounts', 'account'), get: function(accountcode, callback){ t.request(utils.addParams(routes.accounts.get, {account_code: accountcode}), callback); }, @@ -65,9 +63,7 @@ list: function(callback, filter){ t.request(utils.addQueryParams(routes.coupons.list, filter), callback); }, - listAll: function(callback, filter) { - utils.iterateRequest(t, routes.coupons.list, filter, 'coupons', 'coupon', callback); - }, + forEach: utils.makeForEachIterator(t, routes.coupons.list, 'coupons', 'coupon'), get: function(couponcode, callback){ t.request(utils.addParams(routes.coupons.get, {coupon_code: couponcode}), callback); }, @@ -98,9 +94,7 @@ list: function(callback, filter){ t.request(utils.addQueryParams(routes.invoices.list, filter), callback); }, - listAll: function(callback, filter) { - utils.iterateRequest(t, routes.invoices.list, filter, 'invoices', 'invoice', callback); - }, + forEach: utils.makeForEachIterator(t, routes.invoices.list, 'invoices', 'invoice'), listByAccount: function(accountcode, callback, filter){ t.request( utils.addParams( @@ -126,9 +120,7 @@ list: function(callback, filter){ t.request(utils.addQueryParams(routes.plans.list, filter), callback); }, - listAll: function(callback, filter) { - utils.iterateRequest(t, routes.plans.list, filter, 'plans', 'plan', callback); - }, + forEach: utils.makeForEachIterator(t, routes.plans.list, 'plans', 'plan'), get: function(plancode, callback){ t.request(utils.addParams(routes.plans.get, {plan_code: plancode}), callback); }, @@ -147,9 +139,7 @@ list: function(plancode, callback, filter){ t.request(utils.addParams(utils.addQueryParams(routes.planAddons.list, filter), {plan_code: plancode}), callback); }, - listAll: function(plancode, callback, filter) { - utils.iterateRequest(t, routes.planAddons.list, filter, 'add_ons', 'ad_on', callback); - }, + forEach: utils.makeForEachIterator(t, routes.planAddons.list, 'add_ons', 'ad_on'), get: function(plancode, addoncode, callback){ t.request(utils.addParams(routes.planAddons.get, {plan_code: plancode, addon_code: addoncode}), callback); }, @@ -173,9 +163,7 @@ list: function(callback, filter){ t.request(utils.addQueryParams(routes.subscriptions.list, filter), callback); }, - listAll: function(callback, filter) { - utils.iterateRequest(t, routes.subscriptions.list, filter, 'subscriptions', 'subscription', callback); - }, + forEach: utils.makeForEachIterator(t, routes.subscriptions.list, 'subscriptions', 'subscription'), listByAccount: function(accountcode, callback){ t.request(utils.addParams(routes.subscriptions.listByAccount, {account_code: accountcode}), callback); }, @@ -206,9 +194,7 @@ list: function(callback, filter){ t.request(utils.addQueryParams(routes.transactions.list, filter), callback); }, - listAll: function(callback, filter) { - utils.iterateRequest(t, routes.transactions.list, filter, 'transactions', 'transaction', callback); - }, + forEach: utils.makeForEachIterator(t, routes.transactions.list, 'transactions', 'transaction'), listByAccount: function(accountCode, callback, filter){ t.request( utils.addParams( @@ -230,4 +216,4 @@ } }; }; //end class -})(); \ No newline at end of file +})(); diff --git a/lib/utils.js b/lib/utils.js index 3c13f04..d506d0d 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -1,3 +1,5 @@ +var url = require('url'); + exports.addParams = function(route, keys){ var newRoute = route.slice(); var path = newRoute[0]; @@ -35,42 +37,57 @@ var parseUrlLink = exports.parseUrlLink = function(result, callback) { } }; -exports.iterateRequest = function(client, route, queryString, endpointName, singleItemName, callback) { - var url = require('url'); +exports.makeForEachIterator = function(client, route, endpointName, singleItemName) { + return function(options, iterator, callback) { - // Set up our results that we'll return - var results = { - data: {} - }; - results.data[endpointName] = {}; - results.data[endpointName][singleItemName] = []; + options = options || {}; + + var perPage = options.per_page || 200; + limit = options.limit, + initialCursor = addQueryParams(route, {per_page: perPage}), + count = 0; + + nextPage(initialCursor); - var doListAll = function(cursor, callback) { - client.request(cursor, function(error, result) { - if(error) { - return callback(error); - } else { - // Test to see if we have more pages + function nextPage(cursor) { + client.request(cursor, function(error, result) { + if (error) { + return callback(error); + } + + var links, + nextQueryString, + nextCursor, + records, + isLimitHit; + + // Parse out the next cursor if (result.headers.link) { - var links = parseUrlLink(result); - // Store off our current results - results.data[endpointName][singleItemName] = results.data[endpointName][singleItemName].concat(result.data[endpointName][singleItemName]); - if(links.next) { - // Let's just get our querystring params - queryString = url.parse(links.next, true).query; - // Recurse - doListAll(addQueryParams(route, queryString), callback); - } else { - // We are done so return - return callback(null, results); + links = parseUrlLink(result); + if (links.next) { + nextQueryString = url.parse(links.next, true).query; + nextCursor = addQueryParams(route, nextQueryString); } - } else { // We didn't have a next link - just return the results - return callback(null, result); } - } - }, null); - }; - //Kick off the listing - doListAll(addQueryParams(route, queryString), callback); -}; \ No newline at end of file + records = result.data[endpointName][singleItemName]; + isLimitHit = limit && (count + records.length) > limit; + if (isLimitHit) { + // We will be over the limit, so slice down + records = records.slice(0, limit - count); + } + + // Iterate through results + count += records.length; + records.forEach(iterator); + + if (isLimitHit || !nextCursor) { + // Finished + callback(); + } else { + nextPage(nextCursor); + } + }); + } + }; +}; diff --git a/package.json b/package.json index 1896667..a349d51 100644 --- a/package.json +++ b/package.json @@ -1,22 +1,33 @@ -{ "name" : "node-recurly" -, "description" : "Library for accessing the api for the Recurly recurring billing service." -, "keywords" : [ "recurly", "e-commerce", "recurring billing" ] -, "version" : "2.1.1" -, "homepage" : "https://github.com/robrighter/node-recurly" -, "author" : "Rob Righter (http://github.com/robrighter)" -, "contributors" : [ - "Iván Guardado (http://github.com/IvanGuardado)", - "Rob Righter (http://github.com/robrighter)", - "Dmitriy Shekhovtsov (https://github.com/valorkin)", - "Jeff Zabel (https://github.com/jzabel)" - ] -, "bugs" : - { "web" : "https://github.com/robrighter/node-recurly/issues" } -, "directories" : { "lib" : "./lib" } -, "main" : "./lib/recurly.js" -, "dependencies" : { - "xml2js": ">= 0.4.0", - "js2xml": "valorkin/js2xml" -} -, "engines" : { "node" : ">= 0.4" } +{ + "name": "node-recurly", + "description": "Library for accessing the api for the Recurly recurring billing service.", + "keywords": [ + "recurly", + "e-commerce", + "recurring billing" + ], + "version": "2.1.1", + "homepage": "https://github.com/robrighter/node-recurly", + "author": "Rob Righter (http://github.com/robrighter)", + "contributors": [ + "Iván Guardado (http://github.com/IvanGuardado)", + "Rob Righter (http://github.com/robrighter)", + "Dmitriy Shekhovtsov (https://github.com/valorkin)", + "Jeff Zabel (https://github.com/jzabel)" + ], + "bugs": { + "web": "https://github.com/robrighter/node-recurly/issues" + }, + "directories": { + "lib": "./lib" + }, + "main": "./lib/recurly.js", + "dependencies": { + "js2xml": "valorkin/js2xml", + "lodash": "^2.4.1", + "xml2js": ">= 0.4.0" + }, + "engines": { + "node": ">= 0.4" + } } From 7e728b554d68adb3355466ed425d0697e6bda52b Mon Sep 17 00:00:00 2001 From: Islam Sharabash Date: Mon, 29 Sep 2014 09:30:11 -0700 Subject: [PATCH 05/11] bumping version number for breaking change --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a349d51..664bff9 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "e-commerce", "recurring billing" ], - "version": "2.1.1", + "version": "3.0.0", "homepage": "https://github.com/robrighter/node-recurly", "author": "Rob Righter (http://github.com/robrighter)", "contributors": [ From 9ca23e2e1e9897626f42ddf0dd7ec6d6618ecb13 Mon Sep 17 00:00:00 2001 From: Islam Sharabash Date: Mon, 29 Sep 2014 12:59:45 -0700 Subject: [PATCH 06/11] adding async library and using it for iterator, making iterator follow each series --- lib/recurly.js | 14 +++++++------- lib/utils.js | 23 ++++++++++++++--------- package.json | 1 + 3 files changed, 22 insertions(+), 16 deletions(-) diff --git a/lib/recurly.js b/lib/recurly.js index 90347f8..074d059 100644 --- a/lib/recurly.js +++ b/lib/recurly.js @@ -15,7 +15,7 @@ list: function(callback, filter){ t.request(utils.addQueryParams(routes.accounts.list, filter), callback); }, - forEach: utils.makeForEachIterator(t, routes.accounts.list, 'accounts', 'account'), + eachSeries: utils.makeEachSeriesIterator(t, routes.accounts.list, 'accounts', 'account'), get: function(accountcode, callback){ t.request(utils.addParams(routes.accounts.get, {account_code: accountcode}), callback); }, @@ -63,7 +63,7 @@ list: function(callback, filter){ t.request(utils.addQueryParams(routes.coupons.list, filter), callback); }, - forEach: utils.makeForEachIterator(t, routes.coupons.list, 'coupons', 'coupon'), + eachSeries: utils.makeEachSeriesIterator(t, routes.coupons.list, 'coupons', 'coupon'), get: function(couponcode, callback){ t.request(utils.addParams(routes.coupons.get, {coupon_code: couponcode}), callback); }, @@ -94,7 +94,7 @@ list: function(callback, filter){ t.request(utils.addQueryParams(routes.invoices.list, filter), callback); }, - forEach: utils.makeForEachIterator(t, routes.invoices.list, 'invoices', 'invoice'), + eachSeries: utils.makeEachSeriesIterator(t, routes.invoices.list, 'invoices', 'invoice'), listByAccount: function(accountcode, callback, filter){ t.request( utils.addParams( @@ -120,7 +120,7 @@ list: function(callback, filter){ t.request(utils.addQueryParams(routes.plans.list, filter), callback); }, - forEach: utils.makeForEachIterator(t, routes.plans.list, 'plans', 'plan'), + eachSeries: utils.makeEachSeriesIterator(t, routes.plans.list, 'plans', 'plan'), get: function(plancode, callback){ t.request(utils.addParams(routes.plans.get, {plan_code: plancode}), callback); }, @@ -139,7 +139,7 @@ list: function(plancode, callback, filter){ t.request(utils.addParams(utils.addQueryParams(routes.planAddons.list, filter), {plan_code: plancode}), callback); }, - forEach: utils.makeForEachIterator(t, routes.planAddons.list, 'add_ons', 'ad_on'), + eachSeries: utils.makeEachSeriesIterator(t, routes.planAddons.list, 'add_ons', 'ad_on'), get: function(plancode, addoncode, callback){ t.request(utils.addParams(routes.planAddons.get, {plan_code: plancode, addon_code: addoncode}), callback); }, @@ -163,7 +163,7 @@ list: function(callback, filter){ t.request(utils.addQueryParams(routes.subscriptions.list, filter), callback); }, - forEach: utils.makeForEachIterator(t, routes.subscriptions.list, 'subscriptions', 'subscription'), + eachSeries: utils.makeEachSeriesIterator(t, routes.subscriptions.list, 'subscriptions', 'subscription'), listByAccount: function(accountcode, callback){ t.request(utils.addParams(routes.subscriptions.listByAccount, {account_code: accountcode}), callback); }, @@ -194,7 +194,7 @@ list: function(callback, filter){ t.request(utils.addQueryParams(routes.transactions.list, filter), callback); }, - forEach: utils.makeForEachIterator(t, routes.transactions.list, 'transactions', 'transaction'), + eachSeries: utils.makeEachSeriesIterator(t, routes.transactions.list, 'transactions', 'transaction'), listByAccount: function(accountCode, callback, filter){ t.request( utils.addParams( diff --git a/lib/utils.js b/lib/utils.js index d506d0d..b821c9b 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -1,4 +1,5 @@ -var url = require('url'); +var async = require('async'), + url = require('url'); exports.addParams = function(route, keys){ var newRoute = route.slice(); @@ -37,7 +38,7 @@ var parseUrlLink = exports.parseUrlLink = function(result, callback) { } }; -exports.makeForEachIterator = function(client, route, endpointName, singleItemName) { +exports.makeEachSeriesIterator = function(client, route, endpointName, singleItemName) { return function(options, iterator, callback) { options = options || {}; @@ -79,14 +80,18 @@ exports.makeForEachIterator = function(client, route, endpointName, singleItemNa // Iterate through results count += records.length; - records.forEach(iterator); + async.eachSeries(records, iterator, function(error) { + if (error) { + return callback(error); + } - if (isLimitHit || !nextCursor) { - // Finished - callback(); - } else { - nextPage(nextCursor); - } + if (isLimitHit || !nextCursor) { + // Finished + callback(); + } else { + nextPage(nextCursor); + } + }); }); } }; diff --git a/package.json b/package.json index 664bff9..d679705 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ }, "main": "./lib/recurly.js", "dependencies": { + "async": "^0.9.0", "js2xml": "valorkin/js2xml", "lodash": "^2.4.1", "xml2js": ">= 0.4.0" From 95f26d766e23e42f8523a778a6f8b95bd82b8de4 Mon Sep 17 00:00:00 2001 From: Islam Sharabash Date: Tue, 30 Sep 2014 08:58:18 -0700 Subject: [PATCH 07/11] fix semicolon, save the world --- lib/utils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/utils.js b/lib/utils.js index b821c9b..3fc88d8 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -43,7 +43,7 @@ exports.makeEachSeriesIterator = function(client, route, endpointName, singleIte options = options || {}; - var perPage = options.per_page || 200; + var perPage = options.per_page || 200, limit = options.limit, initialCursor = addQueryParams(route, {per_page: perPage}), count = 0; From 6dec80dbc142608370413fc6f197feb121a92348 Mon Sep 17 00:00:00 2001 From: Islam Sharabash Date: Thu, 2 Oct 2014 10:06:53 -0700 Subject: [PATCH 08/11] adding jsdoc for recurly makeEachSeriesIterator --- lib/utils.js | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/lib/utils.js b/lib/utils.js index 3fc88d8..d7f9546 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -38,8 +38,28 @@ var parseUrlLink = exports.parseUrlLink = function(result, callback) { } }; +/** + * makeEachSeriesIterator + * + * Creates a function that will get all records for `route` and call `iterator` on each record. + * + * @param {Client} client Initialized recurly client. + * @param {String} route URL of resource + * @param {String} endpointName Plural name of resouce. + * @param {String} singleItemName Signular name of resource. + * @return {function(options, iterator, callback)} + */ exports.makeEachSeriesIterator = function(client, route, endpointName, singleItemName) { - return function(options, iterator, callback) { + /** + * + * @param {Object} options like: + * per_page: {Number} Default 200. Max number of items to get per page. (Currently recurly api limits you to 200). + * limit: {Number} Default everything. Total number of items to get before calling the final callback. + * @param {function(item, callback)} iterator Function to call for each item returned from recurly. Call callback when + * you finish processing the item. Feeds items to iterator sequentially. + * @param {function(error)} callback Called when all items (up to limit) have been fetched and fed through `iterator`. + */ + return function(options, iterator, callback) { options = options || {}; From 77d9972ae0547638870eb25fb05bbd6024d51a69 Mon Sep 17 00:00:00 2001 From: Islam Sharabash Date: Fri, 10 Apr 2015 09:28:08 -0700 Subject: [PATCH 09/11] Update js2xml --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d679705..bbed2b2 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,7 @@ "main": "./lib/recurly.js", "dependencies": { "async": "^0.9.0", - "js2xml": "valorkin/js2xml", + "js2xml": "^1.0.2", "lodash": "^2.4.1", "xml2js": ">= 0.4.0" }, From 28f06fa8224c26d85d3f2013411210dfb7a25251 Mon Sep 17 00:00:00 2001 From: Huy Nguyen Date: Wed, 15 Jul 2015 14:36:20 -0700 Subject: [PATCH 10/11] Correct error handling for when Recurly returns no records --- lib/client.js | 4 ++-- lib/utils.js | 7 +++++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/lib/client.js b/lib/client.js index 9106023..5d4c466 100644 --- a/lib/client.js +++ b/lib/client.js @@ -43,7 +43,7 @@ // 200–299 success if (res.statusCode >= 200 && res.statusCode <= 299) { if (responsedata === '') { - return _cb(res); + return _cb(res, new Error('Response has no data')); } return parser.parseString(responsedata, function (err, result) { return _cb(res, null, result); @@ -83,7 +83,7 @@ } if (callback.length === 2) { if (err) { - return callback(_wrap_response(res, err)); + return callback(err, _wrap_response(res, null)); } return callback(null, _wrap_response(res, data)); diff --git a/lib/utils.js b/lib/utils.js index d7f9546..4834cdc 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -76,10 +76,14 @@ exports.makeEachSeriesIterator = function(client, route, endpointName, singleIte return callback(error); } + var records = result.data[endpointName][singleItemName]; + if (!records) { + return callback(new Error('Response has no data')); + } + var links, nextQueryString, nextCursor, - records, isLimitHit; // Parse out the next cursor @@ -91,7 +95,6 @@ exports.makeEachSeriesIterator = function(client, route, endpointName, singleIte } } - records = result.data[endpointName][singleItemName]; isLimitHit = limit && (count + records.length) > limit; if (isLimitHit) { // We will be over the limit, so slice down From ddd97f8ddfc2e17e62cc168f1adcfe0148918b8b Mon Sep 17 00:00:00 2001 From: Chase Relock Date: Sat, 21 Nov 2015 23:10:42 -0800 Subject: [PATCH 11/11] Update package.json --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index bbed2b2..ad03cf1 100644 --- a/package.json +++ b/package.json @@ -24,8 +24,8 @@ "main": "./lib/recurly.js", "dependencies": { "async": "^0.9.0", - "js2xml": "^1.0.2", - "lodash": "^2.4.1", + "js2xml": "~1.0.4", + "lodash": "~2.4.1", "xml2js": ">= 0.4.0" }, "engines": {