Skip to content

Commit ddbd28b

Browse files
iulian03Iulian Masaralexxmattei
authored
[feature] echo endpoints (#482)
* handle intent authorization and captures * handle settlements * updated fileName for multipart upload * handle fetching of pay in intents * Updates all intent dates to be represented as UNIX timestamps * handle intent cancel * updated the cancel logic * handle creation of intent splits * updated tests --------- Co-authored-by: Iulian Masar <iulian.masar@codegile.com> Co-authored-by: alexandrumatei <alexxmattei@gmail.com>
1 parent 48c7f0a commit ddbd28b

20 files changed

+1408
-6
lines changed

lib/api.js

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ var apiModels = require('./models');
77
var apiServices = require('./services');
88

99
var axios = require('axios');
10+
var FormData = require('form-data');
1011

1112
/**
1213
*
@@ -132,6 +133,78 @@ Api.prototype = {
132133
return this._requestApi(requestOptions, method, callback);
133134
},
134135

136+
/**
137+
* Request method for POST or PUT with multipart file. The file is expected to be in options.data.File, as a Buffer
138+
* @param {string} method Mangopay API method to be called
139+
* @param {function} callback Callback function
140+
* @param {object} options Hash of configuration to be passed to request
141+
* @returns {object} request promise
142+
*/
143+
multipartFormMethod: function (method, callback, options) {
144+
options = this._getOptions(callback, options);
145+
if (options.data === undefined || options.data.file === undefined || !Buffer.isBuffer(options.data.file)) {
146+
throw new Error('options.data.file needs to be a Buffer');
147+
}
148+
if (options.data === undefined || options.data.fileName === undefined) {
149+
throw new Error('options.data.fileName needs to be present');
150+
}
151+
152+
if (this.config.debugMode) {
153+
this.config.logClass(method, options);
154+
}
155+
156+
var self = this;
157+
158+
// If there's no OAuthKey, request one
159+
if (!this.requestOptions.headers.Authorization || this.isExpired()) {
160+
return new Promise(function (resolve, reject) {
161+
self.authorize()
162+
.then(function () {
163+
self.multipartFormMethod.call(self, method, function (data, response) {
164+
// Check if we have to wrap data into a model
165+
if (_.isFunction(callback)) {
166+
callback(data, response);
167+
}
168+
}, options)
169+
.then(resolve)
170+
.catch(reject);
171+
})
172+
.catch(reject);
173+
});
174+
}
175+
176+
const form = new FormData();
177+
form.append('file', options.data.file, {
178+
filename: options.data.fileName
179+
});
180+
181+
// Extend default request options with custom ones, if present
182+
var requestOptions = _.extend({}, this.requestOptions, options);
183+
requestOptions.data = form;
184+
185+
// Append the path placeholders in order to build the proper url for the request
186+
if (options && options.path) {
187+
_.extend(requestOptions.path, this.requestOptions.path, options.path);
188+
}
189+
190+
if (this.config.ukHeaderFlag) {
191+
requestOptions.headers['x-tentant-id'] = 'uk';
192+
}
193+
194+
// keep only the multipart content-type header
195+
if (requestOptions.headers['Content-Type'] === 'application/json') {
196+
delete requestOptions.headers['Content-Type'];
197+
}
198+
199+
requestOptions.headers = Object.assign(
200+
{},
201+
requestOptions.headers,
202+
form.getHeaders()
203+
);
204+
205+
return this._requestApi(requestOptions, method, callback);
206+
},
207+
135208
_requestApi: function (requestOptions, method, callback) {
136209
var self = this;
137210

lib/apiMethods.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,15 @@ module.exports = {
6666
"payins_twint-web_create": ["/${apiVersion}/${clientId}/payins/payment-methods/twint", "POST"],
6767
"payins_paybybank-web_create": ["/${apiVersion}/${clientId}/payins/payment-methods/openbanking", "POST"],
6868

69+
"payins_intent_authorization_create": ["/V3.0/${clientId}/payins/intents", "POST"],
70+
"payins_intent_capture_create": ["/V3.0/${clientId}/payins/intents/${intentId}/captures", "POST"],
71+
"payins_intent_get": ["/V3.0/${clientId}/payins/intents/${intentId}", "GET"],
72+
"payins_intent_cancel": ["/V3.0/${clientId}/payins/intents/${intentId}/cancel", "PUT"],
73+
"payins_intent_create_splits": ["/V3.0/${clientId}/payins/intents/${intentId}/splits", "POST"],
74+
"settlement_create": ["/V3.0/${clientId}/payins/intents/settlements", "POST"],
75+
"settlement_get": ["/V3.0/${clientId}/payins/intents/settlements/${id}", "GET"],
76+
"settlement_update": ["/V3.0/${clientId}/payins/intents/settlements/${id}", "PUT"],
77+
6978
"add_tracking_info": ["/${apiVersion}/${clientId}/payins/${payInId}/trackings", "PUT"],
7079

7180
"payment_method_metadata_get": ["/${apiVersion}/${clientId}/payment-methods/metadata", "POST"],

lib/config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ module.exports = {
3838

3939
/**
4040
* Mangopay REST API version - will be appended in the front of the endpoints
41+
* @deprecated It will be handled internally
4142
*/
4243
apiVersion: 'v2.01',
4344

lib/models/PayInIntent.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
var EntityBase = require('./EntityBase');
2+
3+
module.exports = EntityBase.extend({
4+
defaults: {
5+
Amount: null,
6+
AvailableAmountToSplit: null,
7+
Currency: null,
8+
PlatformFeesAmount: null,
9+
Status: null,
10+
NextActions: null,
11+
ExternalData: null,
12+
Buyer: null,
13+
LineItems: null,
14+
Captures: null,
15+
Refunds: null,
16+
Disputes: null,
17+
Splits: null,
18+
SettlementId: null
19+
}
20+
});

lib/models/PayInIntentSplits.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
var EntityBase = require('./EntityBase');
2+
3+
module.exports = EntityBase.extend({
4+
defaults: {
5+
Splits: null
6+
}
7+
});

lib/models/Settlement.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
var EntityBase = require('./EntityBase');
2+
3+
var Settlement = EntityBase.extend({
4+
defaults: {
5+
SettlementId: null,
6+
Status: null,
7+
SettlementDate: null,
8+
ExternalProviderName: null,
9+
DeclaredIntentAmount: null,
10+
ExternalProcessorFeesAmount: null,
11+
ActualSettlementAmount: null,
12+
FundsMissingAmount: null
13+
}
14+
});
15+
16+
module.exports = Settlement;

lib/services/PayIns.js

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ const PaymentMethodMetadata = require('../models/PaymentMethodMetadata');
3636
const PayInPaymentDetailsSwish = require("../models/PayInPaymentDetailsSwish");
3737
const PayInPaymentDetailsTwint = require("../models/PayInPaymentDetailsTwint");
3838
const PayInPaymentDetailsPayByBank = require("../models/PayInPaymentDetailsPayByBank");
39+
const PayInIntent = require('../models/PayInIntent');
40+
const PayInIntentSplits = require('../models/PayInIntentSplits');
3941

4042

4143
var PayIns = Service.extend({
@@ -371,6 +373,140 @@ var PayIns = Service.extend({
371373
return this._api.method('payins_payconiqv2-web_create', callback, options);
372374
},
373375

376+
/**
377+
* Create a pay in intent authorization
378+
* @param {Object} payInIntentAuthorization PayInIntentAuthorization object
379+
* @param {Function} callback Callback function
380+
* @param {Object} options Request options
381+
* @return {Object} Request promise
382+
*/
383+
createPayInIntentAuthorization: function(payInIntentAuthorization, callback, options) {
384+
options = this._api._getOptions(callback, options, {
385+
data: payInIntentAuthorization,
386+
dataClass: PayInIntent
387+
});
388+
389+
return this._api.method('payins_intent_authorization_create', callback, options);
390+
},
391+
392+
/**
393+
* Create a pay in intent full capture
394+
* @param {Object} payInIntentId PayInIntent identifier (string)
395+
* @param {Object} payInIntentFullCapture PayInIntentFullCapture object
396+
* @param {Function} callback Callback function
397+
* @param {Object} options Request options
398+
* @return {Object} Request promise
399+
*/
400+
createPayInIntentFullCapture: function(payInIntentId, payInIntentFullCapture, callback, options) {
401+
options = this._api._getOptions(callback, options, {
402+
data: payInIntentFullCapture,
403+
dataClass: PayInIntent,
404+
path: {
405+
intentId: payInIntentId
406+
}
407+
});
408+
409+
return this._api.method('payins_intent_capture_create', callback, options);
410+
},
411+
412+
/**
413+
* Create a pay in intent partial capture
414+
* @param {Object} payInIntentId PayInIntent identifier (string)
415+
* @param {Object} payInIntentPartialCapture PayInIntentPartialCapture object
416+
* @param {Function} callback Callback function
417+
* @param {Object} options Request options
418+
* @return {Object} Request promise
419+
*/
420+
createPayInIntentPartialCapture: function(payInIntentId, payInIntentPartialCapture, callback, options) {
421+
options = this._api._getOptions(callback, options, {
422+
data: payInIntentPartialCapture,
423+
dataClass: PayInIntent,
424+
path: {
425+
intentId: payInIntentId
426+
}
427+
});
428+
429+
return this._api.method('payins_intent_capture_create', callback, options);
430+
},
431+
432+
/**
433+
* Get a PayInIntent
434+
* @param {Object} payInIntentId PayInIntent identifier (string)
435+
* @param {Function} callback Callback function
436+
* @param {Object} options Request options
437+
* @return {Object} Request promise
438+
*/
439+
getPayInIntent: function(payInIntentId, callback, options) {
440+
options = this._api._getOptions(callback, options, {
441+
dataClass: PayInIntent,
442+
path: {
443+
intentId: payInIntentId
444+
}
445+
});
446+
447+
return this._api.method('payins_intent_get', callback, options);
448+
},
449+
450+
// /**
451+
// * Cancel a PayInIntent
452+
// * @param {Object} payInIntentId PayInIntent identifier (string)
453+
// * @param intentDetails Details about the intent to be canceled
454+
// * @param {Function} callback Callback function
455+
// * @param {Object} options Request options
456+
// * @return {Object} Request promise
457+
// */
458+
// fullCancelPayInIntent: function(payInIntentId, intentDetails, callback, options) {
459+
// options = this._api._getOptions(callback, options, {
460+
// dataClass: PayInIntent,
461+
// path: {
462+
// intentId: payInIntentId
463+
// },
464+
// data: intentDetails
465+
// });
466+
//
467+
// return this._api.method('payins_intent_cancel', callback, options);
468+
// },
469+
//
470+
// /**
471+
// * Cancel a PayInIntent
472+
// * @param {Object} payInIntentId PayInIntent identifier (string)
473+
// * @param intentDetails Details about the intent to be canceled
474+
// * @param {Function} callback Callback function
475+
// * @param {Object} options Request options
476+
// * @return {Object} Request promise
477+
// */
478+
// partialCancelPayInIntent: function(payInIntentId, intentDetails, callback, options) {
479+
// options = this._api._getOptions(callback, options, {
480+
// dataClass: PayInIntent,
481+
// path: {
482+
// intentId: payInIntentId
483+
// },
484+
// data: intentDetails
485+
// });
486+
//
487+
// return this._api.method('payins_intent_cancel', callback, options);
488+
// },
489+
490+
/**
491+
* Create PayInIntent splits
492+
* @param {Object} payInIntentId PayInIntent identifier (string)
493+
* @param splits Splits to be created
494+
* @param {Function} callback Callback function
495+
* @param {Object} options Request options
496+
* @return {Object} Request promise
497+
*/
498+
createPayInIntentSplits: function(payInIntentId, splits, callback, options) {
499+
options = this._api._getOptions(callback, options, {
500+
dataClass: PayInIntentSplits,
501+
path: {
502+
intentId: payInIntentId
503+
},
504+
data: splits
505+
});
506+
507+
return this._api.method('payins_intent_create_splits', callback, options);
508+
},
509+
374510
getPaymentKey: function(payIn) {
375511
if (payIn.PaymentType) {
376512
return payIn.PaymentType.toLowerCase().replaceAll('_', '');

lib/services/Settlements.js

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/**
2+
* @module Settlements
3+
*/
4+
5+
var Service = require('../service');
6+
var Settlement = require('../models/Settlement');
7+
8+
var Settlements = Service.extend({
9+
/**
10+
* Upload a settlement file
11+
* @param {Buffer} file The settlement file to be uploaded
12+
* @param {Function} callback Callback function
13+
* @param {Object} options Request options
14+
* @return {Object} Request promise
15+
*/
16+
upload: function (file, callback, options) {
17+
if (file === undefined || !Buffer.isBuffer(file)) {
18+
throw new Error('The file needs to be a Buffer');
19+
}
20+
21+
options = this._api._getOptions(callback, options, {
22+
dataClass: Settlement,
23+
data: {
24+
file: file,
25+
fileName: 'settlement_file.csv'
26+
}
27+
});
28+
29+
return this._api.multipartFormMethod('settlement_create', callback, options);
30+
},
31+
32+
/**
33+
* Get a settlement
34+
* @param {string} settlementId Settlement Id
35+
* @param {Function} callback Callback function
36+
* @param {Object} options Request options
37+
* @return {Object} Request promise
38+
*/
39+
get: function (settlementId, callback, options) {
40+
options = this._api._getOptions(callback, options, {
41+
path: {id: settlementId},
42+
dataClass: Settlement
43+
});
44+
45+
return this._api.method('settlement_get', callback, options);
46+
},
47+
48+
/**
49+
* Update a settlement file
50+
* @param {string} settlementId Settlement identifier
51+
* @param {Buffer} file The settlement file to be updated
52+
* @param {Function} callback Callback function
53+
* @param {Object} options Request options
54+
* @return {Object} Request promise
55+
*/
56+
update: function (settlementId, file, callback, options) {
57+
if (file === undefined || !Buffer.isBuffer(file)) {
58+
throw new Error('The file needs to be a Buffer');
59+
}
60+
61+
options = this._api._getOptions(callback, options, {
62+
dataClass: Settlement,
63+
data: {
64+
file: file,
65+
fileName: 'settlement_file.csv'
66+
},
67+
path: {id: settlementId}
68+
});
69+
70+
return this._api.multipartFormMethod('settlement_update', callback, options);
71+
},
72+
});
73+
74+
module.exports = Settlements;

lib/services/index.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,5 +29,6 @@ module.exports = {
2929
VirtualAccounts: require('./VirtualAccounts'),
3030
IdentityVerifications: require('./IdentityVerifications'),
3131
Recipients: require('./Recipients'),
32-
ReportsV2: require('./ReportsV2')
32+
ReportsV2: require('./ReportsV2'),
33+
Settlements: require('./Settlements')
3334
};

0 commit comments

Comments
 (0)