├── .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 | //}) --------------------------------------------------------------------------------