Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Merge pull request #8 from tcurdt/master

merged the other branches, added token_url to response
  • Loading branch information...
commit 593751369bd32c6c6c358f864378cd0b0b945e7b 2 parents 3f8cfe9 + 93c4306
James Carr authored
55 lib/gateway-client.js
View
@@ -1,40 +1,47 @@
var qs = require('querystring'),
- crypto = require('crypto'),
sys = require('sys'),
- http_module = require('http')
+ https = require('https');
function GatewayClient(opts, authInfo){
- verifyRequiredArguments(opts)
- var http = http_module
-
- var createClient = !authInfo?
- function(){ return http.createClient(443, opts.host, true) }
- :
- function(){
- var cred = crypto.createCredentials({cert:authInfo.cert, key:authInfo.key})
- return http.createClient(443, opts.host, true, cred)
- }
-
+ verifyRequiredArguments(opts);
+
this.request = function(request, callback){
- var requestString = qs.stringify(request)
- var req = createClient().request('POST', opts.path, {host:opts.host
- ,'Content-Length':requestString.length
- ,'Content-Type':opts.contentType})
- req.end(requestString)
+ var requestString = qs.stringify(request);
+
+ var options = {
+ host: opts.host,
+ path: opts.path,
+ port: 443,
+ method: 'POST',
+ headers: {
+ 'Host': opts.host,
+ 'Content-Type': opts.contentType,
+ 'Content-Length': requestString.length
+ }
+ };
+
+ if (authInfo) {
+ options.cert = authInfo.cert;
+ options.key = authInfo.key;
+ }
+
+ var req = https.request(options);
+ req.end(requestString);
req.on('response', function(res){
- res.on('data', function(data){
- callback(opts.responseParser.parseResponse(request, data.toString()))
- })
+ res.on('data', function(data){
+ // TODO add error handling
+ callback(opts.responseParser.parseResponse(opts.responseOptions, request, data.toString()))
+ })
})
}
}
-exports.GatewayClient = GatewayClient
+exports.GatewayClient = GatewayClient;
function verifyRequiredArguments(opts){
- var required = ['host', 'path', 'contentType', 'responseParser']
+ var required = [ 'host', 'path', 'contentType', 'responseParser' ]
if(!opts) throw new Error("Required fields missing. Need an option containing configuration details.")
- var missingFields = required.filter(function(s){ return !(s in opts) || !opts[s]})
+ var missingFields = required.filter(function(s){ return !(s in opts) || !opts[s] })
if(missingFields.length > 0){
throw new Error("GatewayClient ctor is missing required fields " + missingFields.join(','))
}
93 lib/payflowpro/client.js
View
@@ -2,56 +2,77 @@ require.paths.unshift(__dirname)
var EventEmitter = require('events').EventEmitter,
PaypalNetworkClient = require('paypal-network-client').PaypalNetworkClient,
- requestBuilder = require("request-builder")
+ requestBuilder = require("request-builder");
var SUPPORTED_API_METHODS = [
- 'transactionSearch','getBalance', 'getTransactionDetails','addressVerify',
- 'doDirectPayment', 'refundTransaction', 'doVoid',
- 'createRecurringPaymentsProfile', 'billOutstandingAmount', 'doAuthorization',
- 'doCapture', 'doNonReferencedCredit', 'doReauthorization',
- 'doReferenceTransaction', 'getBillingAgreementCustomerDetails',
- 'getTransactionDetails', 'getRecurringPaymentsProfileDetails','manageRecurringPaymentsProfileStatus',
- 'managePendingTransactionStatus', 'massPayment','updateRecurringPaymentsProfile',
- 'setExpressCheckout', 'getExpressCheckoutDetails', 'doExpressCheckoutPayment'
+ 'transactionSearch',
+ 'getBalance',
+ 'getTransactionDetails',
+ 'addressVerify',
+ 'doDirectPayment',
+ 'refundTransaction',
+ 'doVoid',
+ 'createRecurringPaymentsProfile',
+ 'billOutstandingAmount',
+ 'doAuthorization',
+ 'doCapture',
+ 'doNonReferencedCredit',
+ 'doReauthorization',
+ 'doReferenceTransaction',
+ 'getBillingAgreementCustomerDetails',
+ 'getTransactionDetails',
+ 'getRecurringPaymentsProfileDetails',
+ 'manageRecurringPaymentsProfileStatus',
+ 'managePendingTransactionStatus',
+ 'massPayment',
+ 'updateRecurringPaymentsProfile',
+ 'setExpressCheckout',
+ 'getExpressCheckoutDetails',
+ 'doExpressCheckoutPayment'
];
-exports.levels = require('levels')
-exports.createClient = function(options){
- options.version='63.0'
- var client = options.cert?
- new PaypalNetworkClient(options.level(options), {cert:options.cert, key:options.key})
- :
- new PaypalNetworkClient(options.level(options))
-
- return new Client(options, client)
-}
+exports.levels = require('levels');
+exports.createClient = function(options){
+ options.version = '64.0';
+ var client = options.cert ?
+ new PaypalNetworkClient(options.level(options), { cert: options.cert, key: options.key }) :
+ new PaypalNetworkClient(options.level(options));
+ return new Client(options, client);
+}
function Client(options, paypal){
- options.__defineGetter__('pwd', function(){ return options.password})
- var self = this
- SUPPORTED_API_METHODS.forEach(function(name){
+ options.__defineGetter__('pwd', function(){ return options.password; });
+ var self = this;
+
+ SUPPORTED_API_METHODS.forEach(function(name){
self[name] = function(){
- var req = {method:name}
- for(var i =0; i < arguments.length; i++){
- merge(req, arguments[i])
+ var req = { method: name };
+
+ for(var i=0; i < arguments.length; i++){
+ merge(req, arguments[i]);
}
- requestBuilder.buildRequest(merge(req, options))
-
- var emitter = new EventEmitter()
+
+ requestBuilder.buildRequest(merge(req, options));
+
+ var emitter = new EventEmitter();
+
paypal.request(req, function(data){
- emitter.emit(data.ack.toLowerCase(), data)
+ emitter.emit((data.ack ? data.ack.toLowerCase() : 'fail'), data);
});
- return emitter
+
+ return emitter;
}
- })
+ });
}
function merge(a, b){
- var no = ['cert', 'key', 'password', 'level']
- Object.keys(b).filter(function(s){ return no.indexOf(s) ==-1}).forEach(function(k){
- a[k] = b[k]
- })
- return a
+ var no = [ 'cert', 'key', 'password', 'level' ]
+
+ Object.keys(b).filter(function(s){ return no.indexOf(s) == -1 }).forEach(function(k){
+ a[k] = b[k];
+ });
+
+ return a;
}
14 lib/payflowpro/levels.js
View
@@ -1,2 +1,12 @@
-exports.sandbox = function(opts){return (opts.cert)? 'api.sandbox.paypal.com' : 'api-3t.sandbox.paypal.com'}
-exports.live =function(opts){ return (opts.cert)? 'api.paypal.com' : 'api-3t.paypal.com'}
+exports.sandbox = function(opts){
+ return {
+ api: (opts.cert) ? 'api.sandbox.paypal.com' : 'api-3t.sandbox.paypal.com',
+ site: 'www.sandbox.paypal.com'
+ }
+}
+exports.live = function(opts){
+ return {
+ api: (opts.cert) ? 'api.paypal.com' : 'api-3t.paypal.com',
+ site: 'www.paypal.com'
+ }
+}
48 lib/payflowpro/paypal-network-client.js
View
@@ -1,39 +1,17 @@
-var qs = require('querystring'),
- https = require("https"),
+var GatewayClient = require('../gateway-client').GatewayClient,
paypalResponse = require('response-parser');
-
-function PaypalNetworkClient(host, authInfo){
- this.request = function(request, callback) {
- var requestString = qs.stringify(request)
- var options = {
- host: host,
- port: 443,
- path: '/nvp',
- method: 'POST',
- headers: {
- "content-type":'application/x-www-form-urlencoded',
- "content-length": requestString.length,
- }
- };
- console.log(options);
- var req = https.request(options, function(res) {
- console.log("statusCode: ", res.statusCode);
- console.log("headers: ", res.headers);
- var dataList = [];
- res.on('data', function(data) {
- dataList.push(data);
- });
- res.on('end', function() {
- var data = dataList.join("");
- callback(paypalResponse.parseResponse(request, data));
- });
- });
- req.end(requestString);
- req.on('error', function(e) {
- console.error(e);
- });
- }
+function PaypalNetworkClient(level, authInfo){
+
+ var client = new GatewayClient({
+ host: level.api,
+ contentType: 'text/namevalue',
+ path: '/nvp',
+ responseParser: paypalResponse,
+ responseOptions: level
+ }, authInfo);
+
+ this.request = client.request;
}
-exports.PaypalNetworkClient = PaypalNetworkClient
+exports.PaypalNetworkClient = PaypalNetworkClient;
8 lib/payflowpro/request-builder.js
View
@@ -33,6 +33,14 @@ exports.buildRequest = function(req){
})
})
}
+ if(request.items){
+ request.items.forEach(function(item, i) {
+ props(item).forEach(function(itemAttr){
+ request["l_"+itemAttr+i] = item[itemAttr];
+ })
+ })
+ delete request.items;
+ }
return request
}
87 lib/payflowpro/response-parser.js
View
@@ -1,9 +1,9 @@
var qs = require('querystring')
var s = require('sys')
-var val = require ('value-matcher').val
+var val = require('value-matcher').val
-var errorField = ['errorcode', 'shortmessage', 'longmessage', 'severitycode']
-var balanceField = ['amt', 'currencycode']
+var errorField = [ 'errorcode', 'shortmessage', 'longmessage', 'severitycode' ]
+var balanceField = [ 'amt', 'currencycode' ]
var is = function(field){
return function(s){
@@ -20,7 +20,7 @@ var isSpecialNestedField = function(s){
return s.search(/_\d_/) > -1
}
-function buildResponse(request, data){
+function buildResponse(responseOptions, request, data){
var lowercaseKeys = function(data){
var result = {}
Object.keys(data).forEach(function(k){
@@ -28,63 +28,67 @@ function buildResponse(request, data){
})
return result
}
-
+
var newData = lowercaseKeys(qs.parse(data));
- if(newData.l_errorcode0)
+ if(newData.l_errorcode0) {
newData.errors = buildFrom(errorField, newData)
+ }
+
+ parsers.forMethod(request.method)(responseOptions, newData);
- parsers.forMethod(request.method)(newData);
return buildAdditionalNestedFields(newData)
-
}
- var buildFrom = function(fieldType, data){
- var obj = []
- Object.keys(data).filter(isListField)
- .filter(is(fieldType))
- .forEach(addNestedFields(obj, data))
- return obj
- }
- var build = function(data){
- var obj = []
- Object.keys(data).filter(isListField)
- .forEach(addNestedFields(obj, data))
- return obj
- }
-
- var addNestedFields = function(nestedFields, data){
- return function(prop){
- var match = prop.match(/^l_(\D*)(\d+)$/);
- if (match) {
- var index = parseInt(match[2]);
- var newKey = match[1];
-
- while(nestedFields.length <= index){
- nestedFields.push({})
- }
+var buildFrom = function(fieldType, data){
+ var obj = []
+ Object.keys(data).filter(isListField)
+ .filter(is(fieldType))
+ .forEach(addNestedFields(obj, data))
+ return obj
+}
+
+var build = function(data){
+ var obj = []
+ Object.keys(data).filter(isListField)
+ .forEach(addNestedFields(obj, data))
+ return obj
+}
- nestedFields[index][newKey] = data[prop]
- delete data[prop]
- }
- }
+var addNestedFields = function(nestedFields, data){
+ return function(prop){
+ var index = prop.charAt(prop.length - 1),
+ newKey = prop.replace(/^l_/, '').replace(/\d$/, '')
+
+ if(nestedFields.length == index){
+ nestedFields.push({})
+ }
+
+ nestedFields[index][newKey] = data[prop]
+ delete data[prop]
}
+}
+
var parsers = {
- 'getBalance':function(newData){
+ 'getBalance': function(responseOptions, newData){
newData.balances = buildFrom(balanceField, newData)
},
- 'transactionSearch':function(newData){
+ 'transactionSearch': function(responseOptions, newData){
newData.results = build(newData);
},
- forMethod:function(method){
- return this[method]?this[method]:(function(a){return a});
+ 'setExpressCheckout': function(responseOptions, newData){
+ newData.token_url = 'https://' + responseOptions.site + '/cgi-bin/webscr?cmd=_express-checkout&token=' + newData.token;
+ },
+ forMethod: function(method){
+ return this[method] ? this[method] : (function(responseOptions, a){ return a });
}
}
+
function buildAdditionalNestedFields(newData){
Object.keys(newData).filter(isSpecialNestedField).forEach(function(field){
var parts = field.split('_')
var name = parts[0], index = parts[1], key = parts[2]
if(!newData[name]) newData[name] = []
- if(!newData[name][index])newData[name][index] = {}
+ if(!newData[name][index]) newData[name][index] = {}
newData[name][index][key] = newData[field]
delete newData[field]
})
@@ -93,4 +97,3 @@ function buildAdditionalNestedFields(newData){
exports.parseResponse = buildResponse
exports.build = build
-
1  lib/paynode.js
View
@@ -3,4 +3,3 @@ require.paths.unshift(__dirname);
exports.use = function(name){
return require(__dirname + '/'+name+'/client')
}
-
8 package.json
View
@@ -1,14 +1,14 @@
{ "name" : "paynode"
,"main" : "./lib/paynode"
,"description" : "Payflow Pro SDK for Node"
- ,"version" : "0.2.0"
+ ,"version" : "0.3.0"
,"author" : "James R. Carr <james.r.carr@gmail.com>"
,"repository" :
{"type" : "git"
,"url" : "http://github.com/jamescarr/paynode"
}
- ,"engines" : { "node" : ">=0.2.6" },
- "dependencies":{
- "braintree":"0.1.1"
+ ,"engines" : { "node" : ">=0.4.8" },
+ "dependencies": {
+ "braintree":"0.2.0"
}
}
8 spec/integration/payflowpro/TransactionRelated.spec.js
View
@@ -16,7 +16,9 @@ function executeTransaction () {
cb(details);
})
.on('failure', function(){
- throw new Error("fail");
+ // this is currently still broken in vows
+ // https://github.com/cloudhead/vows/issues/63
+ // assert.ok(false);
});
}
}
@@ -39,11 +41,11 @@ vows.describe('Transaction related operations').addBatch({
},
'should contain a timestamp': function(result, ignored){
assert.isNotNull(result.results[0].timestamp)
- },
+ },
'should have the results enumartated in an array marked results':function(result, ignored){
delete result.results[0].timestamp
delete result.results[0].transactionid
- assert.deepEqual(result.results,
+ assert.deepEqual(result.results,
[
{
timezone: 'GMT',
2  specs
View
@@ -1,2 +1,2 @@
#!/bin/sh
-find spec/ -name '*spec.js' | xargs vows
+find spec/ -name '*spec.js' | xargs vows --spec
Please sign in to comment.
Something went wrong with that request. Please try again.