├── .gitignore
├── .npmignore
├── LICENSE.txt
├── README.md
├── index.js
├── lib
├── client.js
├── recurly.js
├── routes
│ ├── index.js
│ └── v2.js
├── transparent.js
└── utils.js
├── package.json
└── test
├── config-example.js
└── test.js
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | *.idea
3 | *.log
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .idea
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | Copyright (c) 2014 Rob Righter @robrighter
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is
8 | furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in
11 | all copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | THE SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Node-Recurly
2 | ===============
3 |
4 | node-recurly is a node.js library for using the recurly recurring billing service. This library is intended to follow very closely the recurly documentation found at:
5 | http://dev.recurly.com/
6 |
7 | Installation
8 | ===============
9 |
10 | git clone https://github.com/cgerrior/node-recurly.git
11 |
12 | or you can install with NPM
13 |
14 | npm install https://github.com/cgerrior/node-recurly.git --save
15 |
16 | add a config file to your project that has contents similar to:
17 |
18 | module.exports = {
19 | API_KEY: 'secret',
20 | SUBDOMAIN: '[your_account]',
21 | ENVIRONMENT: 'sandbox',
22 | DEBUG: false
23 | };
24 |
25 |
26 | Usage
27 | ===============
28 |
29 | var Recurly = require('node-recurly');
30 | var recurly = new Recurly(require('./config'));
31 |
32 | After that, just call the methods below:
33 |
34 |
35 | Accounts
36 | ===============
37 | https://dev.recurly.com/docs/list-accounts
38 |
39 | recurly.accounts.list(filter, callback)
40 |
41 | recurly.accounts.create(details, callback)
42 |
43 | recurly.accounts.update(accountcode, details, callback)
44 |
45 | recurly.accounts.get(accountcode, callback)
46 |
47 | recurly.accounts.close(accountcode, callback)
48 |
49 | recurly.accounts.reopen(accountcode, callback)
50 |
51 | recurly.accounts.notes(accountcode, callback)
52 |
53 | Adjustments
54 | ===============
55 | https://dev.recurly.com/docs/list-an-accounts-adjustments
56 |
57 | recurly.adjustments.list(accountcode, callback)
58 |
59 | recurly.adjustments.get(uuid, callback)
60 |
61 | recurly.adjustments.create(accountcode, details, callback)
62 |
63 | recurly.adjustments.remove(uuid, callback)
64 |
65 | Billing Information
66 | ===============
67 | https://dev.recurly.com/docs/lookup-an-accounts-billing-info
68 |
69 | recurly.billingInfo.update(accountcode, details, callback)
70 |
71 | recurly.billingInfo.create(accountcode, details, callback)
72 |
73 | recurly.billingInfo.get(accountcode, callback)
74 |
75 | recurly.billingInfo.remove(accountcode, callback)
76 |
77 |
78 | Coupons
79 | ===============
80 | https://dev.recurly.com/docs/list-active-coupons
81 |
82 | recurly.coupons.list(filter, callback)
83 |
84 | recurly.coupons.get(couponcode, callback)
85 |
86 | recurly.coupons.create(details, callback)
87 |
88 | recurly.coupons.deactivate(couponcode, callback)
89 |
90 | Coupon Redemptions
91 | =================
92 | https://dev.recurly.com/docs/lookup-a-coupon-redemption-on-an-account
93 |
94 | recurly.couponRedemption.redeem(couponcode, details, callback)
95 |
96 | recurly.couponRedemption.get(accountcode, callback)
97 |
98 | recurly.couponRedemption.remove(accountcode, callback)
99 |
100 | recurly.couponRedemption.getByInvoice(invoicenumber, callback)
101 |
102 | Invoices
103 | ===============
104 | https://dev.recurly.com/docs/list-invoices
105 |
106 | recurly.invoices.list(filter, callback)
107 |
108 | recurly.invoices.listByAccount(accountcode, filter, callback)
109 |
110 | recurly.invoices.get(invoicenumber, callback)
111 |
112 | recurly.invoices.create(accountcode, details, callback)
113 |
114 | recurly.invoices.preview(accountcode, callback)
115 |
116 | recurly.invoices.refundLineItems(invoicenumber, details, callback)
117 |
118 | recurly.invoices.refundOpenAmount(invoicenumber, details, callback)
119 |
120 | recurly.invoices.markSuccessful(invoicenumber, callback)
121 |
122 | recurly.invoices.markFailed(invoicenumber, callback)
123 |
124 | recurly.invoices.enterOfflinePayment(invoicenumber, details, callback)
125 |
126 | (Subscription) Plans
127 | ==================
128 | https://dev.recurly.com/docs/list-plans
129 |
130 | recurly.plans.list(filter, callback)
131 |
132 | recurly.plans.get(plancode, callback)
133 |
134 | recurly.plans.create(details, callback)
135 |
136 | recurly.plans.update(plancode, details, callback)
137 |
138 | recurly.plans.remove(plancode, callback)
139 |
140 | Plan Add-ons
141 | ==================
142 | https://dev.recurly.com/docs/list-add-ons-for-a-plan
143 |
144 | recurly.planAddons.list(plancode, filter, callback)
145 |
146 | recurly.planAddons.get(plancode, addoncode, callback)
147 |
148 | recurly.planAddons.create(plancode, details, callback)
149 |
150 | recurly.planAddons.update(plancode, addoncode, details, callback)
151 |
152 | recurly.planAddons.remove(plancode, addoncode, callback)
153 |
154 | Subscriptions
155 | ===============
156 | https://dev.recurly.com/docs/list-subscriptions
157 |
158 | recurly.subscriptions.list(filter, callback)
159 |
160 | recurly.subscriptions.listByAccount(accountcode, filter, callback)
161 |
162 | recurly.subscriptions.get(uuid, callback)
163 |
164 | recurly.subscriptions.create(details, callback)
165 |
166 | recurly.subscriptions.preview(details, callback)
167 |
168 | recurly.subscriptions.update(uuid, details, callback)
169 |
170 | recurly.subscriptions.updateNotes(uuid, details, callback)
171 |
172 | recurly.subscriptions.updatePreview(uuid, details, callback)
173 |
174 | recurly.subscriptions.cancel(uuid, callback)
175 |
176 | recurly.subscriptions.reactivate(uuid, callback)
177 |
178 | recurly.subscriptions.terminate(uuid, refundType, callback)
179 |
180 | recurly.subscriptions.postpone(uuid, nextRenewalDate, callback)
181 |
182 | Subscription Usage
183 | ===============
184 | https://dev.recurly.com/docs/list-add-ons-usage
185 |
186 | recurly.usage.list(uuid, addOnCode, billingStatus, callback)
187 |
188 | recurly.usage.log(uuid, addOnCode, details, callback)
189 |
190 | recurly.usage.get(uuid, addOnCode, usageId, callback)
191 |
192 | recurly.usage.update(uuid, addOnCode, usageId, details, callback)
193 |
194 | recurly.usage.remove(uuid, addOnCode, usageId, callback)
195 |
196 | Transactions
197 | ===============
198 | https://dev.recurly.com/docs/list-transactions
199 |
200 | recurly.transactions.list(filter, callback)
201 |
202 | recurly.transactions.listByAccount(accountcode, filter, callback)
203 |
204 | recurly.transactions.get(id, callback)
205 |
206 | recurly.transactions.create(details, callback)
207 |
208 | recurly.transactions.refund(id, amount, callback)
209 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | module.exports = require('./lib/recurly');
--------------------------------------------------------------------------------
/lib/client.js:
--------------------------------------------------------------------------------
1 | (function () {
2 | 'use strict';
3 | var https = require('https'),
4 | pjson = require('../package.json'),
5 | Xml2js = require('xml2js'),
6 | parser = new Xml2js.Parser({explicitArray: false});
7 |
8 | exports.create = function (config) {
9 | config.RECURLY_HOST = config.SUBDOMAIN + '.recurly.com';
10 | return {
11 |
12 | request: function (route, callback, data) {
13 | var endpoint = route[0];
14 | var method = route[1];
15 | var that = this;
16 | var options = {
17 | host: config.RECURLY_HOST,
18 | port: 443,
19 | path: endpoint,
20 | method: method,
21 | headers: {
22 | Authorization: "Basic " + (new Buffer(config.API_KEY)).toString('base64'),
23 | Accept: 'application/xml',
24 | 'Content-Length': (data) ? data.length : 0,
25 | 'User-Agent': "node-recurly/" + pjson.version
26 | }
27 | };
28 |
29 | if (method.toLowerCase() === 'post' || method.toLowerCase() === 'put') {
30 | options.headers['Content-Type'] = 'application/xml';
31 | that.debug(data);
32 | }
33 | that.debug(options);
34 | var req = https.request(options, function (res) {
35 |
36 | var responsedata = '';
37 | res.on('data', function (d) {
38 | responsedata += d;
39 | });
40 | res.on('end', function () {
41 | responsedata = that.trim(responsedata);
42 | that.debug('Response is: ' + res.statusCode);
43 | that.debug(responsedata);
44 | try {
45 | // 200–299 success
46 | if (res.statusCode >= 200 && res.statusCode <= 299) {
47 | if (responsedata === '') {
48 | return _cb(res);
49 | }
50 | return parser.parseString(responsedata, function (err, result) {
51 | return _cb(res, null, result);
52 | });
53 | }
54 | // 400–499 client request errors
55 | // 500–599 server errors
56 | if ([404, 412, 422, 500].indexOf(res.statusCode) !== -1) {
57 | return parser.parseString(responsedata, function (err, result) {
58 | return _cb(res, result);
59 | });
60 | }
61 | if (res.statusCode >= 400) {
62 | return _cb(res, responsedata);
63 | }
64 | }
65 | catch (e) {
66 | return _cb(null, e);
67 | }
68 | });
69 | });
70 | if (data) {
71 | req.write(data);
72 | }
73 | req.end();
74 | req.on('error', function (e) {
75 | return _cb(null, e);
76 | });
77 | // fallback for backward compatibility
78 | function _cb(res, err, data) {
79 | // callback objects acquired from parent scope
80 | if (typeof callback === 'undefined') {
81 | throw new Error('Missing argument: callback function');
82 | }
83 | if (typeof callback !== 'function') {
84 | throw new Error('Callback should be a function');
85 | }
86 | if (callback.length === 2) {
87 | if (err) {
88 | return callback(_wrap_response(res, err));
89 | }
90 | return callback(null, _wrap_response(res, data));
91 |
92 | }
93 | // backward compatibility for not node.js style callbacks
94 | // TBD: skip in next version?
95 | else if (callback.length === 1) {
96 | var toreturn = {status: 'ok', data: '', headers: res ? res.headers : null };
97 | if (err) {
98 | toreturn.status = 'error';
99 | if (!res || err === Error || err instanceof Error) {
100 | toreturn.description = err;
101 | } else if (res.statusCode >= 400) {
102 | toreturn.data = res.statusCode;
103 | toreturn.additional = err;
104 | } else {
105 | toreturn.data = err;
106 | }
107 | return callback(toreturn);
108 | }
109 | toreturn.data = data;
110 | toreturn.description = res.statusCode;
111 | return callback(toreturn);
112 | }
113 | }
114 |
115 | function _wrap_response(res, data) {
116 | if (!res) {
117 | return {data: data && data.stack ? data.stack : data};
118 | }
119 | return {
120 | statusCode: res.statusCode,
121 | headers: res.headers,
122 | data: data
123 | };
124 | }
125 | },
126 |
127 | debug: function (s) {
128 | if (config.DEBUG) {
129 | console.log(s);
130 | }
131 | },
132 |
133 | trim: function (str) {
134 | str = str.replace(/^\s+/, '');
135 | for (var i = str.length - 1; i >= 0; i--) {
136 | if (/\S/.test(str.charAt(i))) {
137 | str = str.substring(0, i + 1);
138 | break;
139 | }
140 | }
141 | return str;
142 | }
143 |
144 | };
145 | };
146 |
147 | })();
148 |
--------------------------------------------------------------------------------
/lib/recurly.js:
--------------------------------------------------------------------------------
1 | (function(){
2 | var js2xmlparser = require('js2xmlparser'),
3 | Client = require('./client'),
4 | utils = require('./utils'),
5 | router = require('./routes/');
6 |
7 | module.exports = function(config){
8 | var routes = router.routes(config.API_VERSION ? config.API_VERSION : '2');
9 | var t = Client.create(config);
10 |
11 | //https://dev.recurly.com/docs/list-accounts
12 | this.accounts = {
13 | list: function(filter, callback){
14 | t.request(utils.addQueryParams(routes.accounts.list, filter), callback);
15 | },
16 | get: function(accountcode, callback){
17 | t.request(utils.addParams(routes.accounts.get, {account_code: accountcode}), callback);
18 | },
19 | create: function(details, callback){
20 | t.request(routes.accounts.create, callback, js2xmlparser('account',details));
21 | },
22 | update: function(accountcode, details, callback){
23 | t.request(utils.addParams(routes.accounts.update, {account_code: accountcode}), callback, js2xmlparser('account', details));
24 | },
25 | close: function(accountcode, callback){
26 | t.request(utils.addParams(routes.accounts.close, {account_code: accountcode}), callback);
27 | },
28 | reopen: function(accountcode, callback){
29 | t.request(utils.addParams(routes.accounts.reopen, {account_code: accountcode}), callback);
30 | },
31 | notes: function(accountcode, callback){
32 | t.request(utils.addParams(routes.accounts.notes, {account_code: accountcode}), callback);
33 | }
34 | }
35 |
36 | //https://dev.recurly.com/docs/list-an-accounts-adjustments
37 | this.adjustments = {
38 | list: function(accountcode, callback){
39 | t.request(utils.addParams(routes.adjustments.list, {account_code: accountcode}), callback);
40 | },
41 | get: function(uuid, callback){
42 | t.request(utils.addParams(routes.adjustments.get, {uuid: uuid}), callback);
43 | },
44 | create: function(accountcode, details, callback){
45 | t.request(utils.addParams(routes.adjustments.create, {account_code: accountcode}), callback, js2xmlparser('adjustment',details));
46 | },
47 | remove: function(uuid, callback){
48 | t.request(utils.addParams(routes.adjustments.remove, {uuid: uuid}), callback);
49 | }
50 | }
51 |
52 | //https://dev.recurly.com/docs/lookup-an-accounts-billing-info
53 | this.billingInfo = {
54 | update: function(accountcode, details, callback){
55 | t.request(utils.addParams(routes.billingInfo.update, {account_code: accountcode} ), callback, js2xmlparser('billing_info', details));
56 | },
57 | create: function(accountcode, details, callback){
58 | t.request(utils.addParams(routes.billingInfo.update, {account_code: accountcode} ), callback, js2xmlparser('billing_info', details));
59 | },
60 | get: function(accountcode, callback){
61 | t.request(utils.addParams(routes.billingInfo.get, {account_code: accountcode} ), callback);
62 | },
63 | remove: function(accountcode, callback){
64 | t.request(utils.addParams(routes.billingInfo.remove, {account_code: accountcode} ), callback);
65 | }
66 | }
67 |
68 | //https://dev.recurly.com/docs/list-active-coupons
69 | this.coupons = {
70 | list: function(filter, callback){
71 | t.request(utils.addQueryParams(routes.coupons.list, filter), callback);
72 | },
73 | get: function(couponcode, callback){
74 | t.request(utils.addParams(routes.coupons.get, {coupon_code: couponcode}), callback);
75 | },
76 | create: function(details, callback){
77 | t.request(routes.coupons.create, callback, js2xmlparser('coupon', details));
78 | },
79 | deactivate: function(couponcode, callback){
80 | t.request(utils.addParams(routes.coupons.deactivate, {coupon_code: couponcode}), callback);
81 | }
82 | }
83 |
84 | //https://dev.recurly.com/docs/lookup-a-coupon-redemption-on-an-account
85 | this.couponRedemption = {
86 | redeem: function(couponcode, details, callback){
87 | t.request(utils.addParams(routes.couponRedemption.redeem, {coupon_code: couponcode}), callback, js2xmlparser('redemption', details));
88 | },
89 | get: function(accountcode, callback){
90 | t.request(utils.addParams(routes.couponRedemption.get, {account_code: accountcode}), callback);
91 | },
92 | remove: function(accountcode, callback){
93 | t.request(utils.addParams(routes.couponRedemption.remove, {account_code: accountcode}), callback);
94 | },
95 | getByInvoice: function(invoicenumber, callback){
96 | t.request(utils.addParams(routes.couponRedemption.getByInvoice, {invoice_number: invoicenumber}), callback)
97 | }
98 | }
99 |
100 | //https://dev.recurly.com/docs/list-invoices
101 | this.invoices = {
102 | list: function(filter, callback){
103 | t.request(utils.addQueryParams(routes.invoices.list, filter), callback);
104 | },
105 | listByAccount: function(accountcode, filter, callback){
106 | t.request(
107 | utils.addParams(
108 | utils.addQueryParams(routes.invoices.listByAccount, filter)
109 | , {account_code: accountcode})
110 | , callback)
111 | },
112 | get: function(invoicenumber, callback){
113 | t.request(utils.addParams(routes.invoices.get, {invoice_number: invoicenumber}), callback);
114 | },
115 | create: function(accountcode, details, callback){
116 | t.request(utils.addParams(routes.invoices.create, {account_code: accountcode}), callback, js2xmlparser('invoice', details));
117 | },
118 | preview: function(accountcode, callback){
119 | t.request(utils.addParams(routes.invoices.preview, {account_code: accountcode}), callback);
120 | },
121 | refundLineItems: function(invoicenumber, details, callback){
122 | t.request(utils.addParams(routes.invoices.refundLineItems, {invoice_number: invoicenumber}), callback, js2xmlparser('invoice', details));
123 | },
124 | refundOpenAmount: function(invoicenumber, details, callback){
125 | t.request(utils.addParams(routes.invoices.refundOpenAmount, {invoice_number: invoicenumber}), callback, js2xmlparser('invoice', details));
126 | },
127 | markSuccessful: function(invoicenumber, callback){
128 | t.request(utils.addParams(routes.invoices.markSuccessful, {invoice_number: invoicenumber}), callback);
129 | },
130 | markFailed: function(invoicenumber, callback){
131 | t.request(utils.addParams(routes.invoices.markFailed, {invoice_number: invoicenumber}), callback);
132 | },
133 | enterOfflinePayment: function(invoicenumber, details, callback){
134 | t.request(utils.addParams(routes.invoices.enterOfflinePayment, {invoice_number: invoicenumber}), callback, js2xmlparser('transaction', details));
135 | },
136 | }
137 |
138 | //https://dev.recurly.com/docs/list-plans
139 | this.plans = {
140 | list: function(filter, callback){
141 | t.request(utils.addQueryParams(routes.plans.list, filter), callback);
142 | },
143 | get: function(plancode, callback){
144 | t.request(utils.addParams(routes.plans.get, {plan_code: plancode}), callback);
145 | },
146 | create: function(details, callback){
147 | t.request(routes.plans.create, callback, js2xmlparser('plan', details));
148 | },
149 | update: function(plancode, details, callback){
150 | t.request(utils.addParams(routes.plans.update, {plan_code: plancode}), callback, js2xmlparser('plan', details));
151 | },
152 | remove: function(plancode, callback){
153 | t.request(utils.addParams(routes.plans.remove, {plan_code: plancode}), callback);
154 | }
155 | }
156 |
157 | //https://dev.recurly.com/docs/list-add-ons-for-a-plan
158 | this.planAddons = {
159 | list: function(plancode, filter, callback){
160 | t.request(utils.addParams(utils.addQueryParams(routes.planAddons.list, filter), {plan_code: plancode}), callback);
161 | },
162 | get: function(plancode, addoncode, callback){
163 | t.request(utils.addParams(routes.planAddons.get, {plan_code: plancode, addon_code: addoncode}), callback);
164 | },
165 | create: function(plancode, details, callback){
166 | t.request(utils.addParams(routes.planAddons.create, {plan_code: plancode}), callback, js2xmlparser('add_on', details));
167 | },
168 | update: function(plancode, addoncode, details, callback){
169 | t.request(utils.addParams(
170 | routes.planAddons.update,
171 | { plan_code: plancode,
172 | add_on_code: addoncode
173 | }),
174 | callback, js2xmlparser('add_on', details));
175 | },
176 | remove: function(plancode, addoncode, callback){
177 | t.request(utils.addParams(routes.planAddons.remove, {plan_code: plancode, add_on_code: addoncode}), callback);
178 | }
179 | }
180 |
181 | //https://dev.recurly.com/docs/list-subscriptions
182 | this.subscriptions = {
183 | list: function(filter, callback){
184 | t.request(utils.addQueryParams(routes.subscriptions.list, filter), callback);
185 | },
186 | listByAccount: function(accountcode, filter, callback){
187 | t.request(
188 | utils.addParams(
189 | utils.addQueryParams(routes.subscriptions.listByAccount, filter)
190 | , {account_code: accountcode})
191 | , callback)
192 | },
193 | get: function(uuid, callback){
194 | t.request(utils.addParams(routes.subscriptions.get, {uuid: uuid}), callback);
195 | },
196 | create: function(details, callback){
197 | t.request(routes.subscriptions.create, callback, js2xmlparser('subscription', details));
198 | },
199 | preview: function(details, callback){
200 | t.request(routes.subscriptions.preview, callback, js2xmlparser('subscription', details));
201 | },
202 | update: function(uuid, details, callback){
203 | t.request(utils.addParams(routes.subscriptions.update, {uuid: uuid}), callback, js2xmlparser('subscription', details));
204 | },
205 | updateNotes: function(uuid, details, callback){
206 | t.request(utils.addParams(routes.subscriptions.updateNotes, {uuid: uuid}), callback, js2xmlparser('subscription', details));
207 | },
208 | updatePreview: function(uuid, details, callback){
209 | t.request(utils.addParams(routes.subscriptions.updatePreview, {uuid: uuid}), callback, js2xmlparser('subscription', details));
210 | },
211 | cancel: function(uuid, callback){
212 | t.request(utils.addParams(routes.subscriptions.cancel, {uuid: uuid}), callback);
213 | },
214 | reactivate: function(uuid, callback){
215 | t.request(utils.addParams(routes.subscriptions.reactivate, {uuid: uuid}), callback);
216 | },
217 | terminate: function(uuid, refundType, callback){
218 | t.request(utils.addParams(routes.subscriptions.terminate, {uuid: uuid, refund_type: refundType}), callback);
219 | },
220 | postpone: function(uuid, nextRenewalDate, callback){
221 | t.request(utils.addParams(routes.subscriptions.postpone, {uuid: uuid, next_renewal_date: nextRenewalDate}), callback);
222 | }
223 | }
224 |
225 | //https://dev.recurly.com/docs/list-add-ons-usage
226 | this.usage = {
227 | list: function(uuid, addOnCode, billingStatus, callback) {
228 | billingStatus = billingStatus || 'all';
229 | t.request(utils.addParams(routes.usage.list, {uuid: uuid, add_on_code: addOnCode, billing_status: billingStatus}), callback);
230 | },
231 | log: function(uuid, addOnCode, details, callback) {
232 | t.request(utils.addParams(routes.usage.log, {uuid: uuid, add_on_code: addOnCode}), callback, js2xmlparser('usage', details));
233 | },
234 | get: function(uuid, addOnCode, usageId, callback) {
235 | t.request(utils.addParams(routes.usage.get, {uuid: uuid, add_on_code: addOnCode, usage_id: usageId}), callback);
236 | },
237 | update: function(uuid, addOnCode, usageId, details, callback) {
238 | t.request(utils.addParams(routes.usage.update, {uuid: uuid, add_on_code: addOnCode, usage_id: usageId}), callback, js2xmlparser('usage', details));
239 | },
240 | remove: function(uuid, addOnCode, usageId, callback) {
241 | t.request(utils.addParams(routes.usage.remove, {uuid: uuid, add_on_code: addOnCode, usage_id: usageId}), callback);
242 | },
243 | }
244 |
245 | //https://dev.recurly.com/docs/list-transactions
246 | this.transactions = {
247 | list: function(filter, callback){
248 | t.request(utils.addQueryParams(routes.transactions.list, filter), callback);
249 | },
250 | listByAccount: function(accountCode, filter, callback){
251 | t.request(
252 | utils.addParams(
253 | utils.addQueryParams(routes.transactions.listByAccount, filter), {account_code: accountCode}),
254 | callback);
255 | },
256 | get: function(id, callback){
257 | t.request(utils.addParams(routes.transactions.get, {'id': id}), callback);
258 | },
259 | create: function(details, callback){
260 | t.request(routes.transactions.create, callback, js2xmlparser('transaction', details));
261 | },
262 | refund: function(id, amount, callback){
263 | var route = utils.addParams(routes.transactions.refund, {id: id});
264 | if(amount){
265 | route = utils.addQueryParams(route, { amount_in_cents: amount });
266 | }
267 | t.request(route, callback)
268 | }
269 | }
270 | }//end class
271 | })();
272 |
--------------------------------------------------------------------------------
/lib/routes/index.js:
--------------------------------------------------------------------------------
1 | var routes = {
2 | 'v2': require('./v2')
3 | };
4 |
5 |
6 | exports.routes = function(version){
7 | if(routes['v'+ version]){
8 | return routes['v' + version];
9 | }else{
10 | throw new Error('The API version v'+version+' is not available!');
11 | }
12 | };
--------------------------------------------------------------------------------
/lib/routes/v2.js:
--------------------------------------------------------------------------------
1 | exports.accounts = {
2 | list: ['/v2/accounts', 'GET'],
3 | get: ['/v2/accounts/:account_code', 'GET'],
4 | create: ['/v2/accounts', 'POST'],
5 | update: ['/v2/accounts/:account_code', 'PUT'],
6 | close: ['/v2/accounts/:account_code', 'DELETE'],
7 | reopen: ['/v2/accounts/:account_code/reopen', 'PUT'],
8 | notes: ['/v2/accounts/:account_code/notes', 'GET']
9 | }
10 |
11 | exports.adjustments = {
12 | list: ['/v2/accounts/:account_code/adjustments', 'GET'],
13 | get: ['/v2/adjustments/:uuid', 'GET'],
14 | create: ['/v2/accounts/:account_code/adjustments', 'POST'],
15 | remove: ['/v2/adjustments/:uuid', 'DELETE']
16 | }
17 |
18 | exports.billingInfo = {
19 | get: ['/v2/accounts/:account_code/billing_info', 'GET'],
20 | update: ['/v2/accounts/:account_code/billing_info', 'PUT'],
21 | create: ['/v2/accounts/:account_code/billing_info', 'POST'],
22 | remove: ['/v2/accounts/:account_code/billing_info', 'DELETE']
23 | }
24 |
25 | exports.coupons = {
26 | list: ['/v2/coupons', 'GET'],
27 | get: ['/v2/coupons/:coupon_code', 'GET'],
28 | create: ['/v2/coupons', 'POST'],
29 | deactivate: ['/v2/coupons/:coupon_code', 'DELETE']
30 | }
31 |
32 | exports.couponRedemption = {
33 | redeem: ['/v2/coupons/:coupon_code/redeem', 'POST'],
34 | get: ['/v2/accounts/:account_code/redemption', 'GET'],
35 | remove: ['/v2/accounts/:account_code/redemption', 'DELETE'],
36 | getByInvoice: ['/v2/invoices/:invoice_number/redemption', 'GET']
37 | }
38 |
39 | exports.invoices = {
40 | list: ['/v2/invoices', 'GET'],
41 | listByAccount: ['/v2/accounts/:account_code/invoices', 'GET'],
42 | get: ['/v2/invoices/:invoice_number', 'GET'],
43 | create: ['/v2/accounts/:account_code/invoices', 'POST'],
44 | preview: ['/v2/accounts/:account_code/invoices/preview', 'POST'],
45 | refundLineItems: ['/v2/invoices/:invoice_number/refund', 'POST'],
46 | refundOpenAmount: ['/v2/invoices/:invoice_number/refund', 'POST'],
47 | markSuccessful: ['/v2/invoices/:invoice_number/mark_successful', 'PUT'],
48 | markFailed: ['/v2/invoices/:invoice_number/mark_failed', 'PUT'],
49 | enterOfflinePayment: ['/v2/invoices/:invoice_number/transactions', 'POST']
50 | }
51 |
52 | exports.plans = {
53 | list: ['/v2/plans', 'GET'],
54 | get: ['/v2/plans/:plan_code', 'GET'],
55 | create: ['/v2/plans', 'POST'],
56 | update: ['/v2/plans/:plan_code', 'PUT'],
57 | remove: ['/v2/plans/:plan_code', 'DELETE']
58 | }
59 |
60 | exports.planAddons = {
61 | list: ['/v2/plans/:plan_code/add_ons', 'GET'],
62 | get: ['/v2/plans/:plan_code/add_ons/:addon_code', 'GET'],
63 | create: ['/v2/plans/:plan_code/add_ons', 'POST'],
64 | update: ['/v2/plans/:plan_code/add_ons/:add_on_code', 'PUT'],
65 | remove: ['/v2/plans/:plan_code/add_ons/:add_on_code', 'DELETE']
66 | }
67 |
68 | exports.subscriptions = {
69 | list: ['/v2/subscriptions', 'GET'],
70 | listByAccount: ['/v2/accounts/:account_code/subscriptions', 'GET'],
71 | get: ['/v2/subscriptions/:uuid', 'GET'],
72 | create: ['/v2/subscriptions', 'POST'],
73 | preview: ['/v2/subscriptions/preview', 'POST'],
74 | update: ['/v2/subscriptions/:uuid', 'PUT'],
75 | updateNotes: ['/v2/subscriptions/:uuid/notes', 'PUT'],
76 | updatePreview: ['/v2/subscriptions/:uuid/preview', 'POST'],
77 | cancel: ['/v2/subscriptions/:uuid/cancel', 'PUT'],
78 | reactivate: ['/v2/subscriptions/:uuid/reactivate', 'PUT'],
79 | terminate: ['/v2/subscriptions/:uuid/terminate?refund=:refund_type', 'PUT'],
80 | postpone: ['/v2/subscriptions/:uuid/postpone?next_renewal_date=:next_renewal_date', 'PUT']
81 | }
82 |
83 | exports.transactions = {
84 | list: ['/v2/transactions', 'GET'],
85 | listByAccount: ['/v2/accounts/:account_code/transactions', 'GET'],
86 | get: ['/v2/transactions/:id', 'GET'],
87 | create: ['/v2/transactions', 'POST'],
88 | refund: ['/v2/transactions/:id', 'DELETE']
89 | }
90 |
91 | exports.usage = {
92 | list: ['/v2/subscriptions/:uuid/add_ons/:add_on_code/usage?billing_status=:billing_status', 'GET'],
93 | log: ['/v2/subscriptions/:uuid/add_ons/:add_on_code/usage', 'POST'],
94 | get: ['/v2/subscriptions/:uuid/add_ons/:add_on_code/usage/:usage_id', 'GET'],
95 | update: ['/v2/subscriptions/:uuid/add_ons/:add_on_code/usage/:usage_id', 'PUT'],
96 | remove: ['/v2/subscriptions/:uuid/add_ons/:add_on_code/usage/:usage_id', 'DELETE']
97 | }
--------------------------------------------------------------------------------
/lib/transparent.js:
--------------------------------------------------------------------------------
1 | (function(){
2 |
3 | var qs = require('querystring');
4 | var crypto = require('crypto');
5 | var Tools = require('./tools');
6 |
7 | module.exports = function(config){
8 |
9 | var t = new Tools(config);
10 |
11 | var TRANSPARENT_POST_BASE_URL = config.RECURLY_BASE_URL + '/transparent/' + config.SUBDOMAIN;
12 | var BILLING_INFO_URL = TRANSPARENT_POST_BASE_URL + '/billing_info';
13 | var SUBSCRIBE_URL = TRANSPARENT_POST_BASE_URL + '/subscription';
14 | var TRANSACTION_URL = TRANSPARENT_POST_BASE_URL + '/transaction';
15 |
16 | t.debug('============================');
17 | t.debug(TRANSPARENT_POST_BASE_URL);
18 | t.debug(BILLING_INFO_URL);
19 | t.debug(SUBSCRIBE_URL);
20 | t.debug(TRANSACTION_URL);
21 | t.debug('============================');
22 |
23 | this.billingInfoUrl = function(){return BILLING_INFO_URL};
24 | this.subscribeUrl = function(){return SUBSCRIBE_URL};
25 | this.transactionUrl = function(){return TRANSACTION_URL};
26 |
27 | this.hidden_field = function(data){
28 | return '';
29 | }
30 |
31 | this.getResults = function(confirm, result, status, type, callback){
32 | validateQueryString(confirm, type, status, result)
33 | t.request('/transparent/results/' + result, 'GET', callback);
34 | }
35 |
36 | this.getFormValuesFromResult = function getFormValuesFromResult(result, type){
37 | var fields = {};
38 | var errors = [];
39 | t.traverse(result.data,function(key, value, parent){
40 | var shouldprint = false;
41 | var toprint = ''
42 | if(value instanceof Object){
43 | if(Object.keys(value).length === 0){
44 | shouldprint = true;
45 | toprint = ''
46 | }
47 | if(Object.hasOwnProperty('@') || Object.hasOwnProperty('#')){
48 | shouldprint = true;
49 | toprint = value;
50 | }
51 | if(value instanceof Array){
52 | shouldprint = true;
53 | toprint = value;
54 | }
55 | }
56 | else if(!(value instanceof Object)){
57 | shouldprint = true;
58 | toprint = value;
59 | if(key === 'error'){
60 | errors.push( { field: '_general', reason: value } );
61 | shouldprint = false;
62 | }
63 | }
64 | if(key === "@" || key === '#'){
65 | shouldprint = false;
66 | }
67 | if(parent === "@" || parent === '#'){
68 | shouldprint = false;
69 | }
70 |
71 | if(!parent){
72 | switch(type){
73 | case 'subscribe':
74 | parent = 'account';
75 | break;
76 | case 'billing_info':
77 | parent = 'billing_info';
78 | }
79 | }
80 |
81 | if(key === 'errors'){
82 | shouldprint = false;
83 | errors = errors.concat(processErrors(value, parent));
84 | }
85 |
86 | if(shouldprint){
87 | var toadd = {};
88 | try{
89 | fields[parent+'['+key+']'] = toprint.replace(/'/g, ''');
90 | }
91 | catch(e){
92 | t.debug('GET FIELDS: could not process: ' + parent+'['+key+'] : ' + toprint );
93 | }
94 | }
95 | });
96 | errors = handleFuzzyLogicSpecialCases(errors);
97 | return {fields: fields, errors: errors};
98 | }
99 |
100 | function processErrors(errors, parent){
101 | var acc = [];
102 | var processSingleError = function(e){
103 | try{
104 | acc.push({
105 | field: parent+'['+e['@'].field+']',
106 | reason: e['#'].replace(/'/g, ''')
107 | });
108 | }
109 | catch(err){
110 | t.debug('Could not process listed error: ' + e);
111 | }
112 | };
113 | errors.forEach(function(item){
114 | if(item instanceof Array){
115 | //loop through the error list
116 | item.forEach(processSingleError)
117 | }
118 | else{
119 | //its a single error so grab it out
120 | try{
121 | processSingleError(item);
122 | }
123 | catch(err){
124 | t.debug('Could not process single error: ' + item);
125 | }
126 | }
127 | });
128 | return acc;
129 | }
130 |
131 | function encoded_data(data){
132 | verify_required_fields(data);
133 | var query_string = make_query_string(data);
134 | var validation_string = hash(query_string);
135 | return validation_string + "|" + query_string;
136 | }
137 |
138 | function verify_required_fields(params){
139 | if(!params.hasOwnProperty('redirect_url')){
140 | throw "Missing required parameter: redirect_url";
141 | }
142 | if(!params.hasOwnProperty('account[account_code]')){
143 | throw "Missing required parameter: account[account_code]";
144 | }
145 | }
146 |
147 | function make_query_string(params){
148 | params.time = makeDate();
149 | return buildQueryStringFromSortedObject(makeSortedObject(params, true));
150 | }
151 |
152 | function makeDate(){
153 | var d = new Date();
154 | var addleadingzero = function(n){ return (n<10)?'0'+n:''+n };
155 | return d.getUTCFullYear() + '-' +
156 | addleadingzero(d.getUTCMonth()+1) + '-' +
157 | addleadingzero(d.getUTCDate()) + 'T' +
158 | addleadingzero(d.getUTCHours()) + ':' +
159 | addleadingzero(d.getUTCMinutes()) + ':' +
160 | addleadingzero(d.getUTCSeconds()) + 'Z';
161 | }
162 |
163 | function hash(data) {
164 | //get the sha1 of the private key in binary
165 | var shakey = crypto.createHash('sha1');
166 | shakey.update(config.PRIVATE_KEY);
167 | shakey = shakey.digest('binary');
168 | //now make an hmac and return it as hex
169 | var hmac = crypto.createHmac('sha1', shakey);
170 | hmac.update(data);
171 | return hmac.digest('hex');
172 | //php: 03021207ad681f2ea9b9e1fc20ac7ae460d8d988 <== Yes this sign is identical to the php version
173 | //node: 03021207ad681f2ea9b9e1fc20ac7ae460d8d988
174 | }
175 |
176 | function buildQueryStringFromSortedObject(params){
177 | return params.map(function(p){
178 | return escape(p.key) + "=" + t.urlEncode(p.value);
179 | }).join('&');
180 | }
181 |
182 | function makeSortedObject(obj, casesensitive){
183 | return Object.keys(obj).map(function(key){
184 | return {key: key, value: obj[key]};
185 | }).sort(function(a,b){
186 | return (casesensitive? a.key : a.key.toLowerCase()) > (casesensitive? b.key : b.key.toLowerCase());
187 | });
188 | }
189 |
190 | //Used for validating return params from Recurly
191 | function validateQueryString(confirm, type, status, result_key)
192 | {
193 | var values = {
194 | result: result_key,
195 | status: status,
196 | type: type
197 | }
198 | var query_values = buildQueryStringFromSortedObject(makeSortedObject(values, true));
199 | hashed_values = hash(query_values);
200 |
201 | if(hashed_values !== confirm) {
202 | throw "Error: Forged query string";
203 | }
204 | return true;
205 | }
206 |
207 | function handleFuzzyLogicSpecialCases(errors){
208 | var toreturn = []
209 | errors.forEach(function(e){
210 | switch(e.field){
211 | case 'billing_info[verification_value]':
212 | toreturn.push(copyWithNewName('billing_info[credit_card][verification_value]', e));
213 | toreturn.push(copyWithNewName('credit_card[verification_value]', e));
214 | break;
215 | case 'credit_card[number]':
216 | toreturn.push(copyWithNewName('billing_info[credit_card][number]', e));
217 | toreturn.push(e);
218 | break;
219 | default:
220 | toreturn.push(e);
221 | break;
222 | }
223 | });
224 | return toreturn;
225 | }
226 |
227 | function copyWithNewName(name, error){
228 | return {field: name,reason: error.reason};
229 | }
230 |
231 | }//END CLASS
232 |
233 | })();
--------------------------------------------------------------------------------
/lib/utils.js:
--------------------------------------------------------------------------------
1 | exports.addParams = function(route, keys){
2 | var newRoute = route.slice();
3 | var path = newRoute[0];
4 | newRoute[0] = path.replace(/(:[^\/]+)/g, function(){
5 | var key = arguments[0].substr(1);
6 | return keys[key];
7 | });
8 | return newRoute;
9 | }
10 |
11 | exports.addQueryParams = function(route, params){
12 | var newRoute = route.slice();
13 | var _params = [];
14 | if(params){
15 | for(var prop in params){
16 | _params.push(prop+'='+ encodeURIComponent(params[prop]));
17 | }
18 | }
19 | if(_params.length > 0) return [newRoute[0] + '?' + _params.join('&'), newRoute[1]];
20 | else return newRoute;
21 | }
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | { "name" : "node-recurly"
2 | , "description" : "Library for accessing the api for the Recurly recurring billing service."
3 | , "keywords" : [ "recurly", "e-commerce", "recurring billing" ]
4 | , "version" : "2.1.1"
5 | , "homepage" : "https://github.com/cgerrior/node-recurly"
6 | , "author" : "Charlie Gerrior (http://github.com/cgerrior)"
7 | , "contributors" : [
8 | "Iván Guardado (http://github.com/IvanGuardado)",
9 | "Rob Righter (http://github.com/robrighter)",
10 | "Dmitriy Shekhovtsov (https://github.com/valorkin)"
11 | ]
12 | , "bugs" :
13 | { "web" : "https://github.com/cgerrior/node-recurly/issues" }
14 | , "directories" : { "lib" : "./lib" }
15 | , "main" : "./lib/recurly.js"
16 | , "dependencies" : {
17 | "xml2js": ">= 0.4.0",
18 | "js2xmlparser": "0.1.7"
19 | }
20 | , "engines" : { "node" : ">= 0.4" }
21 | }
22 |
--------------------------------------------------------------------------------
/test/config-example.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | API_KEY: '',
3 | SUBDOMAIN: '',
4 | ENVIRONMENT: 'sandbox',
5 | DEBUG: true,
6 | API_VERSION: 2
7 | };
8 |
--------------------------------------------------------------------------------
/test/test.js:
--------------------------------------------------------------------------------
1 | var Recurly = require('../'),
2 | config = require('./config-example'),
3 | recurly = new Recurly(config),
4 | utils = require('../lib/utils');
5 |
6 | //recurly.accounts.list(function(res){
7 | // console.log(res);
8 | //})
9 |
10 | // recurly.accounts.get(11, function(res){
11 | // console.log(res);
12 | // })
13 |
14 | // recurly.accounts.close(11, function(res){
15 | // console.log(res);
16 | // })
17 |
18 | // recurly.accounts.reopen(11, function(res){
19 | // console.log(res);
20 | // })
21 |
22 | // recurly.accounts.notes(11, function(res){
23 | // console.log(res);
24 | // })
25 |
26 | // recurly.adjustments.create(11, {currency: 'EUR', unit_amount_in_cents: 100, description: 'Testing'}, function(res){
27 | // console.log(res);
28 | // })
29 |
30 | // recurly.adjustments.list(11, function(res){
31 | // console.log(res);
32 | // })
33 |
34 | // recurly.adjustments.remove('1b874884827e0b9eee7d974446874231', function(res){
35 | // console.log(res);
36 | // })
37 |
38 | // recurly.billingInfo.get(1, function(res){
39 | // console.log(res);
40 | // })
41 |
42 | // recurly.billingInfo.update(1, { first_name: 'Ivanovich' }, function(res){
43 | // console.log(res);
44 | // })
45 |
46 | // recurly.billingInfo.remove(1, function(res){
47 | // console.log(res);
48 | // })
49 |
50 | // recurly.coupons.list(function(res){
51 | // console.log(res);
52 | // })
53 |
54 | // recurly.coupons.get('coupon_50', function(res){
55 | // console.log(res);
56 | // })
57 |
58 | // recurly.coupons.create({coupon_code: 'test', name: 'Test name', discount_type: 'percent', discount_percent: 20}, function(res){
59 | // console.log(res);
60 | // })
61 |
62 | // recurly.coupons.deactivate('test', function(res){
63 | // console.log(res);
64 | // })
65 |
66 | // recurly.couponRedemption.redeem('coupon_50', {account_code: 1, currency: 'EUR'}, function(res){
67 | // console.log(res);
68 | // })
69 |
70 | // recurly.couponRedemption.get(1, function(res){
71 | // console.log(res);
72 | // })
73 |
74 | // recurly.couponRedemption.remove(1, function(res){
75 | // console.log(res);
76 | // })
77 |
78 | // recurly.couponRedemption.getByInvoice(1070, function(res){
79 | // console.log(res);
80 | // })
81 |
82 | // recurly.plans.list(function(res){
83 | // console.log(res);
84 | // })
85 |
86 | // recurly.plans.get('premium', function(res){
87 | // console.log(res);
88 | // })
89 |
90 | // recurly.plans.create({plan_code: 'testing', name:'Testing', unit_amount_in_cents: {EUR: 6000} }, function(res){
91 | // console.log(res);
92 | // })
93 |
94 | // recurly.plans.update('testing', {name:'Testing2', unit_amount_in_cents: {EUR: 6000} }, function(res){
95 | // console.log(res);
96 | // })
97 |
98 | // recurly.plans.remove('testing', function(res){
99 | // console.log(res);
100 | // })
101 |
102 | // recurly.planAddons.list('premium', function(res){
103 | // console.log(res);
104 | // })
105 |
106 | // recurly.planAddons.create('premium', { add_on_code: 'test', name: 'Test', unit_amount_in_cents: { EUR: 10 } }, function(res){
107 | // console.log(res);
108 | // })
109 |
110 | // recurly.planAddons.get('premium', 'test', function(res){
111 | // console.log(res);
112 | // })
113 |
114 | // recurly.planAddons.update('premium', 'test', {name: 'Testing'}, function(res){
115 | // console.log(res);
116 | // })
117 |
118 | // recurly.planAddons.remove('premium', 'test', function(res){
119 | // console.log(res);
120 | // })
121 |
122 | // recurly.subscriptions.list(function(res){
123 | // console.log(res);
124 | // })
125 |
126 | //recurly.subscriptions.listByAccount(1, {state: 'in_trial'}, function(res){
127 | // console.log(res);
128 | //})
129 |
130 | // recurly.subscriptions.get('1b71110580e1768adb5a224bbc9dd0b0', function(res){
131 | // console.log(res);
132 | // })
133 |
134 | // recurly.subscriptions.create(
135 | // {plan_code: 'premium',
136 | // account: 1,
137 | // currency: 'EUR',
138 | // account: {
139 | // account_code: 1,
140 | // billing_info: {
141 | // number: '4111-1111-1111-1111',
142 | // first_name: 'Ivan',
143 | // last_name: 'Guardado'
144 | // }
145 | // }
146 | // }, function(res){
147 | // console.log(res);
148 | // })
149 |
150 | // recurly.subscriptions.preview(
151 | // {plan_code: 'premium',
152 | // account: 1,
153 | // currency: 'EUR',
154 | // account: {
155 | // account_code: 1,
156 | // billing_info: {
157 | // number: '4111-1111-1111-1111',
158 | // first_name: 'Ivan',
159 | // last_name: 'Guardado'
160 | // }
161 | // }
162 | // }, function(res){
163 | // console.log(res);
164 | // })
165 |
166 | // recurly.subscriptions.reactivate('1b71110580e1768adb5a224bbc9dd0b0', function(res){
167 | // console.log(res);
168 | // })
169 |
170 | // recurly.subscriptions.cancel('1b71110580e1768adb5a224bbc9dd0b0', function(res){
171 | // console.log(res);
172 | // })
173 |
174 | // TODO revisar bien esto
175 | // recurly.subscriptions.postpone('1b71110580e1768adb5a224bbc9dd0b0', new Date().getTime()/1000+1000 , function(res){
176 | // console.log(res);
177 | // })
178 |
179 | // recurly.subscriptions.terminate('1b71110580e1768adb5a224bbc9dd0b0', 'none', function(res){
180 | // console.log(res);
181 | // })
182 |
183 | // recurly.transactions.list(function(res){
184 | // console.log(res);
185 | // })
186 |
187 | // recurly.transactions.listByAccount(1, function(res){
188 | // console.log(res);
189 | // })
190 |
191 | // recurly.transactions.get('1b7111060195de32c642264e468f75f3', function(res){
192 | // console.log(res);
193 | // })
194 |
195 | // recurly.transactions.create({account: { account_code: 1 } , amount_in_cents: 100, currency: 'EUR'}, function(res){
196 | // console.log(res);
197 | // })
198 |
199 | // recurly.transactions.refund('1b87e486fbc4cf6309fdad4787844df5', function(res){
200 | // console.log(res);
201 | // })
202 |
203 | //recurly.usage.list('35f117281fc7a1074ecbfa4079955f13', 'emails', 'all', function(res){
204 | // console.log(res);
205 | //})
206 |
207 | //recurly.usage.log('35f117281fc7a1074ecbfa4079955f13', 'emails', {amount: 10, usage_timestamp: '2016-05-05T19:09:15Z'}, function(res){
208 | // console.log(res);
209 | //})
210 |
211 | //recurly.usage.get('35f117281fc7a1074ecbfa4079955f13', 'emails', '519297694292772131', function(res){
212 | // console.log(res);
213 | //})
214 |
215 | //recurly.usage.update('35f117281fc7a1074ecbfa4079955f13', 'emails', '519297694292772131', {amount: 100, usage_timestamp: '2016-05-05T19:09:15Z'}, function(res){
216 | // console.log(res);
217 | //})
218 |
219 | //recurly.usage.remove('35f117281fc7a1074ecbfa4079955f13', 'emails', '519297694292772131', function(res){
220 | // console.log(res);
221 | //})
--------------------------------------------------------------------------------