Skip to content

Commit 8585610

Browse files
committed
Merge branch 'develop' into 'master'
Develop See merge request Modules/fasterpay-node!4
2 parents 8c5d831 + 89b7c5e commit 8585610

8 files changed

Lines changed: 677 additions & 11 deletions

File tree

lib/fasterpay/Gateway.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ const Config = require('./Config.js').Config;
22
const PaymentForm = require('./PaymentForm.js').PaymentForm;
33
const Signature = require('./Signature.js').Signature;
44
const Pingback = require('./Pingback.js').Pingback;
5+
const Subscription = require('./Subscription.js').Subscription;
6+
const Payment = require('./Payment.js').Payment;
57

68
class Gateway {
79
constructor(config) {
@@ -20,6 +22,14 @@ class Gateway {
2022
return new Pingback(this);
2123
}
2224

25+
Subscription() {
26+
return new Subscription(this);
27+
}
28+
29+
Payment() {
30+
return new Payment(this);
31+
}
32+
2333
getConfig() {
2434
return this.config;
2535
}

lib/fasterpay/Payment.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
const request = require('sync-request');
2+
3+
class Payment {
4+
constructor(gateway) {
5+
this.gateway = gateway;
6+
}
7+
8+
refund(orderId, amount){
9+
let refundUrl = `${this.gateway.getConfig().getApiBaseUrl()}/payment/${orderId.toString(8)}/refund`;
10+
11+
let apiResponse = request('POST', refundUrl, { headers: { 'X-ApiKey': this.gateway.getConfig().getPrivateKey() } }, { 'amount': amount });
12+
13+
if (apiResponse.statusCode === 200) {
14+
return JSON.parse(apiResponse.getBody('utf8'));
15+
}
16+
17+
return false;
18+
}
19+
}
20+
21+
module.exports = { Payment: Payment };

lib/fasterpay/PaymentForm.js

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ class PaymentForm {
1717

1818
this.formRecurringTrialAmountField = 'recurring_trial_amount';
1919
this.formRecurringTrialPeriodField = 'recurring_trial_period';
20+
this.formSignatureField = 'sign_version';
2021

2122
this.gateway = gateway;
2223
}
@@ -47,17 +48,25 @@ class PaymentForm {
4748
];
4849
}
4950

50-
buildForm(parameters) {
51+
buildForm(parameters, options) {
5152
parameters[this.formApiKeyField] = this.gateway.getConfig().getPublicKey();
52-
parameters[this.formHashField] = this.gateway.Signature().calculateHash(parameters);
53+
parameters[this.formHashField] = this.gateway.Signature().calculateHash(parameters, parameters[this.formSignatureField]);
5354

5455
let form = `<form align="center" method="post" action="${this.gateway.getConfig().getApiBaseUrl()}${this.endPoint}" name="fasterpay_payment_form" id="fasterpay_payment_form">`;
5556

5657
forIn(parameters, function(value, key) {
5758
form += `<input type="hidden" name="${key}" value="${value}" />`;
5859
});
5960

60-
form += '<input type="Submit" value="Pay Now" id="fasterpay_submit"/></form>';
61+
if ('autoSubmit' in options && options['autoSubmit'] === true) {
62+
form += '<script>document.getElementById("fasterpay_payment_form").submit();</script>'
63+
}
64+
65+
if ('hidePayButton' in options && options['hidePayButton'] === true) {
66+
form += '';
67+
} else {
68+
form += '<input type="Submit" value="Pay Now" id="fasterpay_submit"/></form>';
69+
}
6170

6271
return form;
6372
}

lib/fasterpay/Pingback.js

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,58 @@ class Pingback {
33
this.gateway = gateway;
44
}
55

6-
validate() {
7-
return true;
6+
validate(requestParams) {
7+
let signVersion = 'v1';
8+
let headers = requestParams.headers;
9+
10+
if ('x-fasterpay-signature-version' in headers) {
11+
signVersion = headers['x-fasterpay-signature-version'];
12+
}
13+
14+
if (signVersion === 'v1'){
15+
return this.validateV1(headers);
16+
} else {
17+
return this.validateV2(requestParams);
18+
}
19+
20+
return false;
21+
}
22+
23+
validateV1(headers) {
24+
if (Object.keys(headers).length === 0) {
25+
return false;
26+
}
27+
28+
if (!('x-apikey' in headers)) {
29+
return false;
30+
}
31+
32+
if (headers['x-apikey'] === `${this.gateway.getConfig().getPrivateKey()}`) {
33+
return true;
34+
}
35+
36+
return false;
37+
}
38+
39+
validateV2(requestParams) {
40+
let headers = requestParams.headers;
41+
let body = requestParams.body;
42+
43+
if (Object.keys(headers).length === 0) {
44+
return false;
45+
}
46+
47+
if (!('x-fasterpay-signature' in headers)) {
48+
return false;
49+
}
50+
51+
let currentTs = Math.round(new Date().getTime()/1000);
52+
53+
if ((currentTs - body.pingback_ts) > 300) {
54+
return false;
55+
}
56+
57+
return this.gateway.Signature().calculatePingbackHash(requestParams['rawBody'], headers['x-fasterpay-signature']);
858
}
959
}
1060

lib/fasterpay/Signature.js

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,50 @@
11
const crypto = require('crypto');
2-
// @TODO Replace 3rd party http-build-query with native querystring
32
const httpBuildQuery = require('http-build-query');
43

54
class Signature {
65
constructor(gateway) {
76
this.gateway = gateway;
87
}
98

10-
calculateHash(parameters) {
9+
calculateHash(parameters, sign_version='v1') {
1110
let keys = Object.keys(parameters);
12-
let keysSorted = keys.sort(function(a, b) { return a[0] > b[0]; });
11+
let keysSorted = keys.sort(function(a, b) {
12+
a = a.toLowerCase();
13+
b = b.toLowerCase();
14+
15+
return (a < b) ? -1 : (a > b) ? 1 : 0;
16+
});
17+
1318
let parametersSorted = {};
1419

1520
keysSorted.forEach(key => {
1621
parametersSorted[key] = parameters[key];
1722
});
1823

19-
return crypto.createHash('sha256').update(`${httpBuildQuery(parametersSorted)}${this.gateway.getConfig().getPrivateKey()}`).digest('hex');
24+
if(sign_version == 'v2'){
25+
let encodedString = this.getEncodedString(parametersSorted);
26+
return crypto.createHmac('sha256', `${this.gateway.getConfig().getPrivateKey()}`).update(encodedString).digest('hex');
27+
} else {
28+
return crypto.createHash('sha256').update(`${httpBuildQuery(parametersSorted)}${this.gateway.getConfig().getPrivateKey()}`).digest('hex');
29+
}
30+
}
31+
32+
getEncodedString(parameters) {
33+
let encodedString = '';
34+
let keys = Object.keys(parameters);
35+
36+
keys.forEach(key => {
37+
encodedString += `${key}=${parameters[key]};`;
38+
});
39+
40+
return encodedString;
41+
}
42+
43+
calculatePingbackHash(pingbackData, expectedSignature) {
44+
let expectedSignatureBuffer = Buffer.from(expectedSignature, 'hex');
45+
let calculatedHashBuffer = Buffer.from(crypto.createHmac('sha256', `${this.gateway.getConfig().getPrivateKey()}`).update(pingbackData).digest('hex'), 'hex');
46+
47+
return crypto.timingSafeEqual(expectedSignatureBuffer, calculatedHashBuffer);
2048
}
2149
}
2250

lib/fasterpay/Subscription.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
const request = require('sync-request');
2+
3+
class Subscription {
4+
constructor(gateway) {
5+
this.gateway = gateway;
6+
}
7+
8+
cancel(subcsriptionId){
9+
let cancelUrl = `${this.gateway.getConfig().getApiBaseUrl()}/api/subscription/${subcsriptionId.toString(8)}/cancel`;
10+
11+
let apiResponse = request('POST', cancelUrl, { headers: {'X-ApiKey': this.gateway.getConfig().getPrivateKey() }});
12+
13+
if(apiResponse.statusCode == 200) {
14+
return JSON.parse(apiResponse.getBody('utf8'));
15+
}
16+
return false;
17+
}
18+
}
19+
20+
module.exports = { Subscription: Subscription };

0 commit comments

Comments
 (0)