Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pull Request from itswisdomagain #23

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
76 changes: 55 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,16 @@ var paystack = require('paystack')('secret_key');
The resource methods accepts are promisified, but can receive optional callback as the last argument.

```js
// First Option
// paystack.{resource}.{method}
// First Option (with callback)
// paystack.{resource}.{method}(callback)
paystack.customer.list(function(error, body) {
console.log(error);
console.log(body);
});
```
```js
// Second Option
// paystack.{resource}
// Second Option (as promise)
// paystack.{resource}.{method}.then().catch()
paystack.customer.list()
.then(function(body) {
console.log(body);
Expand All @@ -40,7 +40,28 @@ paystack.customer.list()



For resource methods that use POST or PUT, the JSON body can be passed as the first argument.
For GET endpoints with url path parameters (e.g. https://api.paystack.co/plan/{id_or_plan_code}), pass path parameter as string or number
Separate path parameter values by comma if more than 1 path parameter and place them in order as they appear in the url path.

```js
paystack.plan.get(90)
.then(function(error, body) {
console.log(error);
console.log(body);
});
```

For GET endpoints with query string parameters (e.g. https://api.paystack.co/bank/resolve?account_number=0022728151&bank_code=063), pass paramaters as object.

```js
paystack.bank.resolve_account_number({account_number: '0022778151', bank_code: '063'})
.then(function(error, body) {
console.log(error);
console.log(body);
});
```

For POST or PUT endpoints, the JSON body should be passed as the first argument.

```js
paystack.plan.create({
Expand All @@ -54,25 +75,18 @@ paystack.plan.create({
});
```

For GET, you can pass the required ID as string and optional parameters as an optional object argument.

```js
paystack.plan.get(90)
.then(function(error, body) {
console.log(error);
console.log(body);
});
```
For POST or PUT endpoints, if the endpoint also has path parameters (e.g. https://api.paystack.co/customer/{id_or_customer_code}), pass the path parameters as explained above, before passing the JSON body object.

```js
paystack.transactions.list({perPage: 20})
.then(function(error, body) {
console.log(error);
console.log(body);
});
var customer_id = 100;
paystack.customer.update(customer_id, {last_name: 'Kehers'})
.then(function(error, body) {
console.log(error);
console.log(body);
});
```

### Resources
### Resources and Methods

- customer
- create
Expand Down Expand Up @@ -108,11 +122,31 @@ paystack.transactions.list({perPage: 20})
- list
- listBanks
- update
- bank
- list
- resolveAccountNumber
- resolveBin
- Miscellanous
- list_banks
- resolve_bin


To use any endpoint, call
```js
//using callback function
paystack.{resource}.{method}(function(err, body){
console.log(error);
console.log(body);
});

//or as promise
paystack.{resource}.{method}
.then(function(body) {
console.log(body);
})
.catch(function(error) {
console.log(error);
});
```

### Contributing
- To ensure consistent code style, please follow the [editorconfig rules](http://obem.be/2015/06/01/a-quick-note-on-editorconfig.html) in .editorconfig
Expand Down
180 changes: 84 additions & 96 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,14 @@ Paystack API wrapper
*/

'use strict';
const process = require('process');

var
var
request = require('request'),
baseUrl = 'https://api.paystack.co',
acceptedMethods = [ "get", "post", "put" ],
root = 'https://api.paystack.co',
Promise = require('promise')
promstream = require('./lib/promstream')
;

var resources = {
Expand All @@ -19,23 +22,31 @@ var resources = {
subscription: require('./resources/subscription'),
subaccount: require('./resources/subaccount'),
settlements: require('./resources/settlements'),
misc: require('./resources/misc')
misc: require('./resources/misc'),
bank: require('./resources/bank'),
transfer: require('./resources/transfer'),
transferrecipient: require('./resources/transferRecipient'),
transfercontrol: require('./resources/transferControl'),
bulkCharge: require('./resources/bulkCharge'),
charge: require('./resources/charge'),
controlPanel: require('./resources/controlPanel')
}

function Paystack(key) {
if (!(this instanceof Paystack)) {
return new Paystack(key);
}

this.key = key;
this.key = key || process.env["PAYSTACK_SECRET_KEY"];
this.importResources();
}

Paystack.prototype = {

extend: function(params) {
extend: function(endpoint) {
// This looks more sane.
var self = this;
var secretKey = this.key;

return function(){
// Convert argument to array
var args = new Array(arguments.length);
Expand All @@ -44,90 +55,88 @@ Paystack.prototype = {
args[i] = arguments[i];
}

// Check for callback & Pull it out from the array
// Check if last argument is supplied and is a valid callback function & Pull it out from the array
var callback = l > 0 && typeof args.slice(l-1)[0] === "function" ? args.splice(l-1)[0] : undefined;

var body, qs;

// quick fix - method checking
var method = params.method in {"get":'', "post":'', "put":''}
? params.method
: (function () { throw new Error("Method not Allowed! - Resource declaration error") })()
var endpoint = [root, params.endpoint].join('');
// Checking for required params;
if(params.params) {
var paramList = params.params;

// Pull body passed
var body = args.length === 2 ? args[1] : args[0];
paramList.filter(function(item, index, array) {
if(item.indexOf("*") === -1) {
// Not required
return;
}
item = item.replace("*", "");

if(!(item in body)) {
throw new Error("Required Parameters Ommited - " + item);
}
return;

});
// method checking
if (acceptedMethods.indexOf(endpoint.method) < 0) {
throw new Error("Method - " + endpoint.method + " - not Allowed! - Resource declaration error")
}

// Get arguments in endpoint e.g {id} in customer/{id} and pull
// out from array
var argsInEndpoint = endpoint.match(/{[^}]+}/g);
var method = endpoint.method;
var url = [baseUrl, endpoint.path].join('');

// First check path parameters (e.g {id} in customer/{id}) before checking post body or query string paramters
// Pull out all path parameters from url into array
var argsInEndpoint = url.match(/{[^}]+}/g);
if (argsInEndpoint) {
l = argsInEndpoint.length;

// Do we have one or more?
if (l > 0) {
// Confirm resource declaration good
if (!Array.isArray(params.args)) {
// error
throw new Error('Resource declaration error');
}

// Confirm user passed the argument to method
// and replace in endpoint

var match, index;
for (var i=0;i<l;i++) {
match = argsInEndpoint[i].replace(/\W/g, '');
index = params.args.indexOf(match);
if (index != -1) {
if (!args[index]) {
// error
throw new Error('Resource declaration error');
}

// todo: args[index] must be string or int
endpoint = endpoint.replace(new RegExp(argsInEndpoint[i]), args[index]);
args.splice(index, 1);
//get the argument name from the path defined in resource
var argumentName = argsInEndpoint[i].replace(/\W/g, '');

if (!args[i]) {
// caller did not pass in this particular argument
throw new Error('Required path parameter ommited - ' + argumentName);
}

//args[index] must be string or int
var argumentValue = args[i];
var valueType = typeof argumentValue;
if (valueType !== 'string' && valueType !== 'number') {
throw new Error('Invalid path parameter argument for ' + argumentName + '. Expected string or number. Found ' + valueType);
}

url = url.replace(new RegExp(argsInEndpoint[i]), argumentValue);
}

//we've replaced all url path parameters with values from args
//now delete all such used values from args leaving only the optional qs/body parameters as first argument (if exist) in args
args.splice(0, l);
}
}

// Add post/put/[delete?] body
if (args[0]) {
var body, qs;

// Checking for required params;
if(endpoint.params) {
var parametersList = endpoint.params;
var parametersReceived = args[0]; //should now be first argument, having removed all path arguments

parametersList.filter(function(parameterName, index, array) {
if(parameterName.indexOf("*") === -1) {
// Not required
return;
}
parameterName = parameterName.replace("*", "");

if(!(parameterName in parametersReceived)) {
throw new Error("Required parameter ommited - " + parameterName);
}
return;
});

if (method == 'post' || method == 'put') {
// Body
body = args[0];
body = parametersReceived;
}
else if (method == 'get') {
qs = args[0];
qs = parametersReceived;
}
}

// Make request
var options = {
url: endpoint,
url: url,
json: true,
method: method.toUpperCase(),
headers: {
'Authorization': ['Bearer ', self.key].join('')
'Authorization': ['Bearer ', secretKey].join('')
}
}

Expand All @@ -137,48 +146,27 @@ Paystack.prototype = {
if (qs)
options.qs = qs;

return new Promise(function (fulfill, reject){
request(options, function(error, response, body) {
// return body
if (error){
reject(error);
}
else if(!body.status){

// Error from API??
error = body;
body = null;
reject(error);
}
else{
fulfill(body);
}
});
}).then(function(value) {
if(callback) {
return callback(null, value);
}
return value;
}).catch(function(reason) {
if(callback) {
return callback(reason, null);
}
return reason;
});
return new promstream(options, callback);
}
},

importResources: function() {
var anon;
var resourceFunction, resource;
// Looping over all resources
for (var j in resources) {
// each resource contains a collection of endpoints
for (var resourceName in resources) {
//get the resource object
resource = resources[resourceName];
// Creating a surrogate function
anon = function(){};
// Looping over the properties of each resource
for(var i in resources[j]) {
anon.prototype[i] = this.extend(resources[j][i]);
resourceFunction = function(){};

// Looping over the endpoints of each resource
// each endpoint contains information for validating and calling the endpoint
for(var endpoint in resource) {
resourceFunction.prototype[endpoint] = this.extend(resource[endpoint]);
}
Paystack.prototype[j] = new anon();

Paystack.prototype[resourceName] = new resourceFunction();
}
}
};
Expand Down
Loading