diff --git a/README.md b/README.md index 1c1de61..cd3abe8 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,8 @@ Helps manage e-payment transactions through the [Barion Smart Gateway](https://w - [Get existing accounts of the user](#get-existing-accounts-of-the-user---bariongetaccountsoptions-callback) - [Send money to a Barion user or email address](#send-e-money-to-an-email-address---barionemailtransferoptions-callback) - [Download statement files](#download-statement-files---bariondownloadstatementoptions-callback) + - [Get POS details](#get-pos-details---bariongetposdetailsoptions-callback) + - [Create a new POS](#create-a-new-pos---barioncreateposoptions-callback) - [Get transaction history](#get-transaction-history---bariongethistoryoptions-callback) - [Handling errors](#handling-errors) - [Secure vs. insecure mode](#secure-vs-insecure-mode) @@ -85,6 +87,8 @@ barion.getPaymentState({ - get existing accounts of the user - send money to existing Barion account or email address - download monthly or daily statements of your account +- get details of a specific POS (shop) +- create a new POS (shop) in Barion - get transaction history for wallet accounts. > **IMPORTANT**: ``node-barion`` is completely consistent with [Barion Docs](https://docs.barion.com/Main_Page), so you can use exactly the same field names, that are specified in it. **Reading the official Barion documentation is highly recommended** before starting to use ``node-barion`` module.
@@ -678,6 +682,205 @@ barion.downloadStatement({ }); ``` +### Get POS details - barion.getPosDetails(options, \[callback\]) +To retrieve details and the current state of a specific POS (shop), call the ``getPosDetails`` function. [[Barion Docs](https://docs.barion.com/Pos-Get-v1)] + +**Authentication**: This endpoint requires Api Key authentication. + +**Parameters**: + - ``ApiKey``: Api Key for authentication (string). (required) + + - ``PublicKey``: The public key (identifier) of the POS to retrieve details for (string, GUID format). (required) + +**Output**: [Read at Barion Docs](https://docs.barion.com/Pos-Get-v1#Output_properties) + +#### Usage example +##### With callback +```js +barion.getPosDetails({ + ApiKey: 'your-api-key-here', + PublicKey: '12345678-1234-1234-1234-123456789012' +}, function (err, data) { + //handle error / process data +}); +``` +##### With promise +```js +barion.getPosDetails({ + ApiKey: 'your-api-key-here', + PublicKey: '12345678-1234-1234-1234-123456789012' +}).then(data => { + //process data +}).catch(err => { + //handle error +}); +``` + +### Create a new POS - barion.createPos(options, \[callback\]) +To create a new POS (shop) in the Barion system, call the ``createPos`` function. [[Barion Docs](https://docs.barion.com/Pos-Create-v1)] + +**Authentication**: This endpoint requires Api Key authentication. + +**Parameters**: + - ``ApiKey``: Api Key for authentication (string). (required) + + - ``Name``: The name of the shop (string, max 200 characters). (required) + + - ``Url``: The URL of the shop (string, max 2000 characters, must be HTTPS). (required) + + - ``Description``: Description of the shop (string, min 20, max 200 characters). (required) + + - ``Logo``: Base64 encoded logo image of the shop (string). (required) + + - ``Category``: Array of shop categories (string[]). (required)
+ Allowed values are: + - ``'Agriculture'`` + - ``'BookNewsPaper'`` + - ``'Ad'`` + - ``'BonusCoupon'`` + - ``'Dating'`` + - ``'Electronics'`` + - ``'FashionClothes'`` + - ``'FoodDrink'`` + - ``'FurnitureAntiquity'`` + - ``'GiftToyFlower'`` + - ``'BeautyHealth'`` + - ``'HomeDesignGarden'`` + - ``'JobEducation'`` + - ``'BuildingMaterialMachine'`` + - ``'Baby'`` + - ``'FilmMusic'`` + - ``'Other'`` + - ``'Pet'`` + - ``'Property'`` + - ``'Service'`` + - ``'SportLeisureTravel'`` + - ``'BettingGambling'`` + - ``'Tobacco'`` + - ``'Vehicle'`` + - ``'WatchJewelry'`` + + - ``BusinessContact``: Contact information for business matters (object). (required) + - ``Name``: Contact person name (string). (required) + - ``PhoneNumber``: Contact phone number (string, max 30 characters). (required) + - ``Email``: Contact email address (string, valid email format). (required) + + - ``TechnicalContact``: Contact information for technical matters (object). (required) + - ``Name``: Contact person name (string). (required) + - ``PhoneNumber``: Contact phone number (string, max 30 characters). (required) + - ``Email``: Contact email address (string, valid email format). (required) + + - ``CustomerServiceContact``: Contact information for customer service (object). (required) + - ``Name``: Contact person name (string). (required) + - ``PhoneNumber``: Contact phone number (string, max 30 characters). (required) + - ``Email``: Contact email address (string, valid email format). (required) + + - ``PrimaryCurrency``: The primary currency of the shop (string). (required)
+ Allowed values are: + - ``'HUF'`` (Hungarian forint) + - ``'CZK'`` (Czech crown) + - ``'EUR'`` (Euro) + - ``'USD'`` (U.S. dollar) + + - ``ExpectedTurnover``: The expected turnover tier for the shop (number, integer, 1-6). (required)
+ The value represents predefined turnover ranges based on PrimaryCurrency: + - For HUF: 1 (1-100K), 2 (100K-1M), 3 (1M-10M), 4 (10M-29M), 5 (29M-99M), 6 (99M+) + - For EUR: 1 (1-300), 2 (301-3K), 3 (3K-30K), 4 (30K-90K), 5 (90K-300K), 6 (300K+) + - For CZK: 1 (1-8K), 2 (8K-80K), 3 (80K-800K), 4 (800K-2.2M), 5 (2.2M-7.7M), 6 (7.7M+) + - For USD: 1 (1-350), 2 (351-3.5K), 3 (3.5K-35K), 4 (35K-100K), 5 (100K-345K), 6 (345K+) + + - ``FullPixelImplemented``: Indicates if full pixel tracking is implemented (boolean). (required) + + - ``UseForEInvoicing``: Indicates if the shop will be used for e-invoicing (boolean). (required) + + - ``AverageBasketValue``: Average basket value in the shop (number, integer, greater than 0). (optional) + + - ``PercentageOfB2BCustomers``: Percentage of B2B customers (number, 0-100). (optional) + + - ``PercentageOfNonEuCards``: Percentage of non-EU cards (number, 0-100). (optional) + + - ``CallBackUrl``: Callback URL for the shop (string, max 2000 characters, valid URI). (required) + + - ``ReferenceId``: Reference identifier for the shop (string). (optional) + + - ``NoteForApproval``: Note for the approval process (string). (optional) + + - ``CustomTemplate``: Custom template for the shop (string). (optional) + + - ``CustomCss``: Custom CSS for the shop (string). (optional) + +**Output**: [Read at Barion Docs](https://docs.barion.com/Pos-Create-v1#Output_properties) + +#### Usage example +##### With callback +```js +barion.createPos({ + ApiKey: 'your-api-key-here', + Name: 'My Awesome Shop', + Url: 'https://example.com', + Description: 'This is a shop that sells awesome products and provides great service.', + Logo: 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==', + Category: [ 'Electronics' ], + BusinessContact: { + Name: 'John Doe', + PhoneNumber: '36301234567', + Email: 'business@example.com' + }, + TechnicalContact: { + Name: 'Jane Smith', + PhoneNumber: '36301234568', + Email: 'tech@example.com' + }, + CustomerServiceContact: { + Name: 'Support Team', + PhoneNumber: '36301234569', + Email: 'support@example.com' + }, + PrimaryCurrency: 'HUF', + ExpectedTurnover: 3, + FullPixelImplemented: false, + UseForEInvoicing: false, + CallBackUrl: 'https://example.com/callback' +}, function (err, data) { + //handle error / process data +}); +``` +##### With promise +```js +barion.createPos({ + ApiKey: 'your-api-key-here', + Name: 'My Awesome Shop', + Url: 'https://example.com', + Description: 'This is a shop that sells awesome products and provides great service.', + Logo: 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==', + Category: [ 'Electronics' ], + BusinessContact: { + Name: 'John Doe', + PhoneNumber: '36301234567', + Email: 'business@example.com' + }, + TechnicalContact: { + Name: 'Jane Smith', + PhoneNumber: '36301234568', + Email: 'tech@example.com' + }, + CustomerServiceContact: { + Name: 'Support Team', + PhoneNumber: '36301234569', + Email: 'support@example.com' + }, + PrimaryCurrency: 'HUF', + ExpectedTurnover: 3, + FullPixelImplemented: false, + UseForEInvoicing: false, + CallBackUrl: 'https://example.com/callback' +}).then(data => { + //process data +}).catch(err => { + //handle error +}); +``` + ### Get transaction history - barion.getHistory(options, \[callback\]) To query the transaction history of a wallet account, call the ``getHistory`` function. [[Barion Docs](https://docs.barion.com/UserHistory-GetHistory-v3)] diff --git a/lib/barion.js b/lib/barion.js index 6312dff..8264d3a 100644 --- a/lib/barion.js +++ b/lib/barion.js @@ -14,6 +14,8 @@ const { StartPaymentWithGoogleToken, StartPaymentWithAppleToken, ValidateApplePaySession, + GetPosDetails, + CreatePos, GetHistory } = require('./domain'); const { buildRequest, buildRequestWithoutValidation } = require('./build'); @@ -259,8 +261,9 @@ Barion.prototype.downloadStatement = function (options, callback) { /** * Starts a new payment in Barion using Google Pay token. * @param {Object} options - Options that are required to start payment with Google Pay. - * @param {Function} [callback] - Callback that handles the response. If not passed, the function returns a *Promise*. - * @see {@link https://docs.barion.com/Payment-StartPaymentWithGoogleToken-v3|Barion API: Start payment with Google Pay?} + * @param {Function} [callback] - Callback that handles the response. + * If not passed, the function returns a *Promise*. + * @see {@link https://docs.barion.com/Payment-StartPaymentWithGoogleToken-v3} */ Barion.prototype.startPaymentWithGoogleToken = function (options, callback) { return doRequestAndReturn({ @@ -301,6 +304,36 @@ Barion.prototype.validateApplePaySession = function (options, callback) { }, callback); }; +/** + * Gets the details of a specific POS. + * @param {Object} options - Options that are required to get POS details. + * @param {Function} [callback] - Callback that handles the response. If not passed, the function returns a *Promise*. + * @see {@link https://docs.barion.com/Pos-Get-v1|Barion API Documentation} + */ +Barion.prototype.getPosDetails = function (options, callback) { + return doRequestAndReturn({ + defaults: this.defaults, + customs: options, + schema: GetPosDetails, + service: services.getPosDetails + }, callback); +}; + +/** + * Creates a new POS (shop) in Barion. + * @param {Object} options - Options that are required to create a POS. + * @param {Function} [callback] - Callback that handles the response. If not passed, the function returns a *Promise*. + * @see {@link https://docs.barion.com/Pos-Create-v1|Barion API Documentation} + */ +Barion.prototype.createPos = function (options, callback) { + return doRequestAndReturn({ + defaults: this.defaults, + customs: options, + schema: CreatePos, + service: services.createPos + }, callback); +}; + /** * Gets the transaction history of a wallet account. * @param {Object} options - Options that are required to get history. diff --git a/lib/constants.js b/lib/constants.js index 0d762bc..edda927 100644 --- a/lib/constants.js +++ b/lib/constants.js @@ -13,6 +13,8 @@ const { StartPaymentWithAppleToken, ValidateApplePaySession, StartPaymentWithGoogleToken, + GetPosDetails, + CreatePos, GetHistory } = require('./domain'); @@ -97,6 +99,16 @@ module.exports = { path: '/v2/ApplePay/ValidateSession', model: ValidateApplePaySession }, + getPosDetails: { + method: 'GET', + path: '/v1/Pos/{PublicKey}', + model: GetPosDetails + }, + createPos: { + method: 'POST', + path: '/v1/Pos', + model: CreatePos + }, getHistory: { method: 'GET', path: '/v3/UserHistory/GetHistory', diff --git a/lib/domain/CreatePos.js b/lib/domain/CreatePos.js new file mode 100644 index 0000000..4946530 --- /dev/null +++ b/lib/domain/CreatePos.js @@ -0,0 +1,58 @@ +const Joi = require('joi'); +const { CaseInsensitiveSchema } = require('../schema'); +const { ShopCategory } = require('./_constraints'); +const { NamedContactSchema } = require('./pos/BaseContact'); + +/** + * Used to create a shop (POS) in Barion. + * + * Note: Uses API key authentication (wallet endpoint). + * + * @see {@link https://docs.barion.com/Pos-Create-v1|Barion API: Create a POS in Barion} + */ +const schema = Joi.object({ + ApiKey: Joi.string().required() + .pattern(/^[0-9a-fA-F]{32}$/), + Name: Joi.string().required() + .max(200), + Url: Joi.string().required() + .max(2000) + .uri({ scheme: [ 'https' ] }), + Description: Joi.string().required() + .min(20) + .max(200), + Logo: Joi.string().required() + .base64(), + Category: Joi.array().required() + .items(Joi.string().valid(...ShopCategory)) + .min(1), + BusinessContact: NamedContactSchema.required(), + TechnicalContact: NamedContactSchema.required(), + CustomerServiceContact: NamedContactSchema.required(), + PrimaryCurrency: Joi.string().required() + .valid('HUF', 'CZK', 'EUR', 'USD'), + ExpectedTurnover: Joi.number().required() + .integer() + .min(1) + .max(6), + AverageBasketValue: Joi.number().optional() + .integer() + .greater(0), + PercentageOfB2BCustomers: Joi.number().optional() + .min(0) + .max(100), + PercentageOfNonEuCards: Joi.number().optional() + .min(0) + .max(100), + FullPixelImplemented: Joi.boolean().required(), + UseForEInvoicing: Joi.boolean().required(), + CallBackUrl: Joi.string().required() + .max(2000) + .uri(), + ReferenceId: Joi.string().optional(), + NoteForApproval: Joi.string().optional(), + CustomTemplate: Joi.string().optional(), + CustomCss: Joi.string().optional() +}); + +module.exports = new CaseInsensitiveSchema(schema); diff --git a/lib/domain/GetPosDetails.js b/lib/domain/GetPosDetails.js new file mode 100644 index 0000000..4182bfb --- /dev/null +++ b/lib/domain/GetPosDetails.js @@ -0,0 +1,18 @@ +const Joi = require('joi'); +const { CaseInsensitiveSchema } = require('../schema'); + +/** + * Used to retrieve all details and the current state of a point of sale (POS). + * + * Note: Uses API key authentication (wallet endpoint). + * + * @see {@link https://docs.barion.com/Pos-Get-v1|Barion API: Request all details of a POS} + */ +const schema = Joi.object({ + ApiKey: Joi.string().required() + .pattern(/^[0-9a-fA-F]{32}$/), + PublicKey: Joi.string().required() + .guid() +}); + +module.exports = new CaseInsensitiveSchema(schema); diff --git a/lib/domain/_constraints.js b/lib/domain/_constraints.js index ee7fa9c..91f2f7c 100644 --- a/lib/domain/_constraints.js +++ b/lib/domain/_constraints.js @@ -110,5 +110,49 @@ module.exports = { 'NoPreference', 'ChallengeRequired', 'NoChallengeNeeded' + ], + ShopStatus: [ + 'Unspecified', + 'NewUnderApproval', + 'NewForbidden', + 'Open', + 'OpenWaitForModificationApproval', + 'OpenModificationForbidden', + 'TemporaryClosed', + 'TemporaryClosedWaitForModificationApproval', + 'TemporaryClosedModificationForbidden', + 'PermanentlyClosed', + 'PermanentlyClosedByUser', + 'PermanentlyClosedByOfficer', + 'ClosedByOfficer', + 'ReOpen_WaitingForApproval', + 'Draft' + ], + ShopCategory: [ + 'Agriculture', + 'BookNewsPaper', + 'Ad', + 'BonusCoupon', + 'Dating', + 'Electronics', + 'FashionClothes', + 'FoodDrink', + 'FurnitureAntiquity', + 'GiftToyFlower', + 'BeautyHealth', + 'HomeDesignGarden', + 'JobEducation', + 'BuildingMaterialMachine', + 'Baby', + 'FilmMusic', + 'Other', + 'Pet', + 'Property', + 'Service', + 'SportLeisureTravel', + 'BettingGambling', + 'Tobacco', + 'Vehicle', + 'WatchJewelry' ] }; diff --git a/lib/domain/index.js b/lib/domain/index.js index 859ff1b..91dc08b 100644 --- a/lib/domain/index.js +++ b/lib/domain/index.js @@ -13,6 +13,8 @@ const StatementDownload = require('./StatementDownload'); const StartPaymentWithGoogleToken = require('./StartPaymentWithGoogleToken'); const StartPaymentWithAppleToken = require('./StartPaymentWithAppleToken'); const ValidateApplePaySession = require('./ValidateApplePaySession'); +const GetPosDetails = require('./GetPosDetails'); +const CreatePos = require('./CreatePos'); const GetHistory = require('./GetHistory'); module.exports = { @@ -31,5 +33,7 @@ module.exports = { StartPaymentWithGoogleToken, StartPaymentWithAppleToken, ValidateApplePaySession, + GetPosDetails, + CreatePos, GetHistory }; diff --git a/lib/domain/pos/BaseContact.js b/lib/domain/pos/BaseContact.js new file mode 100644 index 0000000..aea39f2 --- /dev/null +++ b/lib/domain/pos/BaseContact.js @@ -0,0 +1,25 @@ +const Joi = require('joi'); +const { CaseInsensitiveSchema } = require('../../schema'); +/** + * Base schema for contact details with common fields. + * This serves as the foundation for all contact types in Barion. + */ +const baseContactSchema = Joi.object({ + PhoneNumber: Joi.string().required() + .max(30), + Email: Joi.string().required() + .email() +}); + +/** + * Creates a contact schema with a required Name field. + * Used for contacts that require a name (Business and Technical contacts). + */ +const namedContactSchema = baseContactSchema.keys({ + Name: Joi.string().required() +}); + +module.exports = { + BaseContactSchema: new CaseInsensitiveSchema(baseContactSchema), + NamedContactSchema: new CaseInsensitiveSchema(namedContactSchema) +}; diff --git a/lib/errors.js b/lib/errors.js index 9a860d9..f708d1c 100644 --- a/lib/errors.js +++ b/lib/errors.js @@ -2,12 +2,14 @@ * Represents error, sent by Barion API. * @param {String} message - Error message. * @param {Array} errors - Barion's "Errors" array. + * @param {Number} statusCode - HTTP status code. */ class BarionError extends Error { - constructor (message, errors) { + constructor (message, errors, statusCode) { super(message); this.name = this.constructor.name; this.errors = Array.isArray(errors) ? errors : []; + this.statusCode = statusCode; } } diff --git a/lib/fetch-api.js b/lib/fetch-api.js index d270cb8..c7e731f 100644 --- a/lib/fetch-api.js +++ b/lib/fetch-api.js @@ -101,6 +101,7 @@ function isErrorResponse (res) { */ function fetchBarion (url, options, apiKey, binary = false) { let success; + let statusCode; if (apiKey) { options = setApiKeyAuthentication(options, apiKey); @@ -109,6 +110,7 @@ function fetchBarion (url, options, apiKey, binary = false) { return fetch(url, options) .then(res => { success = res.ok; + statusCode = res.status; if (binary && success) { return res.buffer().then(buffer => { @@ -124,7 +126,7 @@ function fetchBarion (url, options, apiKey, binary = false) { .catch(handleError) // response is not a valid JSON or network error occured .then(data => { if (!success || isErrorResponse(data)) { - throw new BarionError('Barion request errored out', data.Errors || data.ErrorList); + throw new BarionError('Barion request errored out', data.Errors || data.ErrorList, statusCode); } return data; diff --git a/lib/services.js b/lib/services.js index 7b86f4e..d6cd7c3 100644 --- a/lib/services.js +++ b/lib/services.js @@ -14,6 +14,25 @@ function getBaseUrl (environment) { return baseUrls[environment]; } +/** + * Replaces path parameters in the path template with values from options. + * @param {String} path - The path template with placeholders like '/api/{id}/details'. + * @param {Object} options - The options object containing parameter values. + * @returns {String} The path with parameters replaced and properly URL encoded. + */ +function replacePathParameters (path, options) { + let finalPath = path; + const pathParamRegex = /\{(\w+)\}/g; + let match; + while ((match = pathParamRegex.exec(path)) !== null) { + const paramName = match[1]; + if (options[paramName]) { + finalPath = finalPath.replace(match[0], encodeURIComponent(options[paramName])); + } + } + return finalPath; +} + /** * Sends the request to Barion API based on given information. * @param {String} environment - The environment to use ('prod' or 'test'). @@ -24,7 +43,7 @@ function doRequest (environment, endpoint, options) { const { method, path } = endpoint; const url = new URL(getBaseUrl(environment)); - url.pathname = path; + url.pathname = replacePathParameters(path, options); if (endpoint.binary) { return getBinaryFromBarion(url, options); @@ -144,7 +163,7 @@ function downloadStatement (environment, options) { * @param {String} environment - The environment to use ('test' or 'prod'). * @param {Object} options - The final request body to send to the Barion API. */ -function startPaymentWithGoogleToken(environment, options) { +function startPaymentWithGoogleToken (environment, options) { return doRequest(environment, endPoints.startPaymentWithGoogleToken, options); } @@ -153,7 +172,7 @@ function startPaymentWithGoogleToken(environment, options) { * @param {String} environment - The environment to use ('test' or 'prod'). * @param {Object} options - The final request body to send to the Barion API. */ -function startPaymentWithAppleToken(environment, options) { +function startPaymentWithAppleToken (environment, options) { return doRequest(environment, endPoints.startPaymentWithAppleToken, options); } @@ -162,10 +181,28 @@ function startPaymentWithAppleToken(environment, options) { * @param {String} environment - The environment to use ('test' or 'prod'). * @param {Object} options - The final request body to send to the Barion API. */ -function validateApplePaySession(environment, options) { +function validateApplePaySession (environment, options) { return doRequest(environment, endPoints.validateApplePaySession, options); } +/** + * Gets the details of a specific POS. + * @param {String} environment - The environment to use ('test' or 'prod'). + * @param {Object} options - The final request object to send to the Barion API. + */ +function getPosDetails (environment, options) { + return doRequest(environment, endPoints.getPosDetails, options); +} + +/** + * Creates a new POS (shop) in Barion. + * @param {String} environment - The environment to use ('test' or 'prod'). + * @param {Object} options - The final request object to send to the Barion API. + */ +function createPos (environment, options) { + return doRequest(environment, endPoints.createPos, options); +} + /** * Gets the transaction history of a wallet account. * @param {String} environment - The environment to use ('test' or 'prod'). @@ -190,9 +227,12 @@ module.exports = { startPaymentWithGoogleToken, startPaymentWithAppleToken, validateApplePaySession, + getPosDetails, + createPos, getHistory, _private: { getBaseUrl, + replacePathParameters, doRequest } }; diff --git a/test/barion.test.js b/test/barion.test.js index ead3d6c..d6932ce 100644 --- a/test/barion.test.js +++ b/test/barion.test.js @@ -56,6 +56,8 @@ const Barions = { startPaymentWithGoogleToken: returnSuccess, startPaymentWithAppleToken: returnSuccess, validateApplePaySession: returnSuccess, + getPosDetails: returnSuccess, + createPos: returnSuccess, getHistory: returnSuccess } }), @@ -75,6 +77,8 @@ const Barions = { startPaymentWithGoogleToken: returnError, startPaymentWithAppleToken: returnError, validateApplePaySession: returnError, + getPosDetails: returnError, + createPos: returnError, getHistory: returnError } }), @@ -94,6 +98,8 @@ const Barions = { startPaymentWithGoogleToken: returnSuccess, startPaymentWithAppleToken: returnSuccess, validateApplePaySession: returnSuccess, + getPosDetails: returnSuccess, + createPos: returnSuccess, getHistory: returnSuccess }, './build': { @@ -116,6 +122,8 @@ const Barions = { startPaymentWithGoogleToken: returnSuccess, startPaymentWithAppleToken: returnSuccess, validateApplePaySession: returnSuccess, + getPosDetails: returnSuccess, + createPos: returnSuccess, getHistory: returnSuccess }, './build': { @@ -1021,7 +1029,8 @@ describe('lib/barion.js', function () { ], Currency: 'HUF', PayerEmailAddress: 'payer@example.com', - GooglePayToken: 'eyJwcm90b2NvbFZlcnNpb24iOiJFQ192MiIsInNpZ25lZE1lc3NhZ2UiOiJ7XCJlbmNyeXB0ZWRNZXNzYWdlXCI6XCJleGFtcGxlLXRva2VuXCJ9In0=' + GooglePayToken: 'eyJwcm90b2NvbFZlcnNpb24iOiJFQ192MiIsInNpZ25lZE1lc3NhZ2UiOiJ7XCJl' + + 'bmNyeXB0ZWRNZXNzYWdlXCI6XCJleGFtcGxlLXRva2VuXCJ9In0=' }; it('should answer with callback on success', function (done) { @@ -1283,6 +1292,174 @@ describe('lib/barion.js', function () { }); }); + describe('#getPosDetails(options, [callback])', function () { + const request = { + ApiKey: '277a6ae112b041928e6cbc7d0612afa1', + PublicKey: '277a6ae1-12b0-4192-8e6c-bc7d0612afa1' + }; + + it('should answer with callback on success', function (done) { + okBarion.getPosDetails(request, (err, res) => { + expect(err).to.be.null; + expect(res).to.deep.equal(successObject); + done(); + }); + }); + + it('should answer with callback on success when validation is turned off', function (done) { + okBarionWithoutValidation.getPosDetails(request, (err, res) => { + expect(err).to.be.null; + expect(res).to.deep.equal(successObject); + done(); + }); + }); + + it('should answer with callback on error', function (done) { + serviceErrorBarion.getPosDetails(request, (err, res) => { + expect(err).to.deep.equal(errorObject); + expect(res).to.be.null; + done(); + }); + }); + + it('should answer with callback on validation error', function (done) { + validationErrorBarion.getPosDetails(request, (err, res) => { + expect(err).to.deep.equal(validationErrorObject); + expect(res).to.be.null; + done(); + }); + }); + + it('should answer with callback on sanitization error when validation is turned off', function (done) { + sanitizationErrorBarion.getPosDetails(request, (err, res) => { + expect(err).to.deep.equal(sanitizationErrorObject); + expect(res).to.be.null; + done(); + }); + }); + + it('should answer with Promise on success', function (done) { + const promise = okBarion.getPosDetails(request); + expect(promise).to.eventually.deep.equal(successObject).notify(done); + }); + + it('should answer with Promise on success when validation is turned off', function (done) { + const promise = okBarionWithoutValidation.getPosDetails(request); + expect(promise).to.eventually.deep.equal(successObject).notify(done); + }); + + it('should answer with Promise on error', function (done) { + const promise = serviceErrorBarion.getPosDetails(request); + expect(promise).to.eventually.rejectedWith(errorObject).notify(done); + }); + + it('should answer with Promise on validation error', function (done) { + const promise = validationErrorBarion.getPosDetails(request); + expect(promise).to.eventually.rejectedWith(validationErrorObject).notify(done); + }); + + it('should answer with Promise on sanitization error when validation is turned off', function (done) { + const promise = sanitizationErrorBarion.getPosDetails(request); + expect(promise).to.eventually.rejectedWith(sanitizationErrorObject).notify(done); + }); + }); + + describe('#createPos(options, [callback])', function () { + const request = { + ApiKey: '277a6ae112b041928e6cbc7d0612afa1', + Name: 'Test Shop', + Url: 'https://example.com', + Description: 'This is a test shop for unit testing purposes.', + Logo: 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==', + Category: [ 'Electronics', 'Other' ], + BusinessContact: { + Name: 'John Doe', + PhoneNumber: '36701234567', + Email: 'business@example.com' + }, + TechnicalContact: { + Name: 'Jane Smith', + PhoneNumber: '36701234568', + Email: 'tech@example.com' + }, + CustomerServiceContact: { + Name: 'Support Team', + PhoneNumber: '36701234569', + Email: 'support@example.com' + }, + PrimaryCurrency: 'EUR', + ExpectedTurnover: 3, + FullPixelImplemented: true, + UseForEInvoicing: false, + CallBackUrl: 'https://example.com/callback' + }; + + it('should answer with callback on success', function (done) { + okBarion.createPos(request, (err, res) => { + expect(err).to.be.null; + expect(res).to.deep.equal(successObject); + done(); + }); + }); + + it('should answer with callback on success when validation is turned off', function (done) { + okBarionWithoutValidation.createPos(request, (err, res) => { + expect(err).to.be.null; + expect(res).to.deep.equal(successObject); + done(); + }); + }); + + it('should answer with callback on error', function (done) { + serviceErrorBarion.createPos(request, (err, res) => { + expect(err).to.deep.equal(errorObject); + expect(res).to.be.null; + done(); + }); + }); + + it('should answer with callback on validation error', function (done) { + validationErrorBarion.createPos(request, (err, res) => { + expect(err).to.deep.equal(validationErrorObject); + expect(res).to.be.null; + done(); + }); + }); + + it('should answer with callback on sanitization error when validation is turned off', function (done) { + sanitizationErrorBarion.createPos(request, (err, res) => { + expect(err).to.deep.equal(sanitizationErrorObject); + expect(res).to.be.null; + done(); + }); + }); + + it('should answer with Promise on success', function (done) { + const promise = okBarion.createPos(request); + expect(promise).to.eventually.deep.equal(successObject).notify(done); + }); + + it('should answer with Promise on success when validation is turned off', function (done) { + const promise = okBarionWithoutValidation.createPos(request); + expect(promise).to.eventually.deep.equal(successObject).notify(done); + }); + + it('should answer with Promise on error', function (done) { + const promise = serviceErrorBarion.createPos(request); + expect(promise).to.eventually.rejectedWith(errorObject).notify(done); + }); + + it('should answer with Promise on validation error', function (done) { + const promise = validationErrorBarion.createPos(request); + expect(promise).to.eventually.rejectedWith(validationErrorObject).notify(done); + }); + + it('should answer with Promise on sanitization error when validation is turned off', function (done) { + const promise = sanitizationErrorBarion.createPos(request); + expect(promise).to.eventually.rejectedWith(sanitizationErrorObject).notify(done); + }); + }); + describe('#getHistory(options, [callback])', function () { const request = { ApiKey: '277a6ae112b041928e6cbc7d0612afa1' diff --git a/test/integration/integration.test.js b/test/integration/integration.test.js index c68d983..be78d8f 100644 --- a/test/integration/integration.test.js +++ b/test/integration/integration.test.js @@ -581,6 +581,258 @@ describe('Integration tests', function () { ); }); + describe('Create POS (callback)', function () { + it('should create a POS when validation is turned on', function (done) { + const requestBody = { + ...testData.createPos.successRequestBody, + Name: `Test Shop ${Date.now()}` + }; + validatedBarion.createPos(requestBody, (err, res) => { + expect(err).to.be.null; + expect(res).to.deep.include(testData.createPos.successResponseBody); + expect(res.PublicKey).to.be.a('string'); + expect(res.SecretKey).to.be.a('string'); + expect(res.Name).to.equal(requestBody.Name); + done(); + }); + }); + + it('should answer with BarionModelError when request object is not proper and validation is turned on', + function (done) { + validatedBarion.createPos(testData.createPos.errorRequestBody, (err, res) => { + expect(res).to.be.null; + expect(err.name).to.equal('BarionModelError'); + expect(err.errors).to.be.an('array').and.have.length.greaterThan(0); + done(); + }); + } + ); + + it('should create a POS when validation is turned off', function (done) { + const requestBody = { + ...testData.createPos.successRequestBody, + Name: `Test Shop ${Date.now()}` + }; + notValidatedBarion.createPos(requestBody, (err, res) => { + expect(err).to.be.null; + expect(res).to.deep.include(testData.createPos.successResponseBody); + expect(res.PublicKey).to.be.a('string'); + expect(res.SecretKey).to.be.a('string'); + expect(res.Name).to.equal(requestBody.Name); + done(); + }); + }); + + it('should answer with BarionError when request object is not proper and validation is turned off', + function (done) { + notValidatedBarion.createPos(testData.createPos.errorRequestBody, (err, res) => { + expect(res).to.be.null; + expect(err.name).to.equal('BarionError'); + expect(err.errors).to.be.an('array'); + expect(err.errors[0]).to.deep.include(testData.createPos.expectedError); + done(); + }); + } + ); + }); + + describe('Create POS (Promise)', function () { + it('should create a POS when validation is turned on', function (done) { + const requestBody = { + ...testData.createPos.successRequestBody, + Name: `Test Shop ${Date.now()}` + }; + validatedBarion.createPos(requestBody) + .then(res => { + expect(res).to.deep.include(testData.createPos.successResponseBody); + expect(res.PublicKey).to.be.a('string'); + expect(res.SecretKey).to.be.a('string'); + expect(res.Name).to.equal(requestBody.Name); + done(); + }); + }); + + it('should answer with BarionModelError when request object is not proper and validation is turned on', + function (done) { + validatedBarion.createPos(testData.createPos.errorRequestBody) + .catch(err => { + expect(err.name).to.equal('BarionModelError'); + expect(err.errors).to.be.an('array').and.have.length.greaterThan(0); + done(); + }); + } + ); + + it('should create a POS when validation is turned off', function (done) { + const requestBody = { + ...testData.createPos.successRequestBody, + Name: `Test Shop ${Date.now()}` + }; + notValidatedBarion.createPos(requestBody) + .then(res => { + expect(res).to.deep.include(testData.createPos.successResponseBody); + expect(res.PublicKey).to.be.a('string'); + expect(res.SecretKey).to.be.a('string'); + expect(res.Name).to.equal(requestBody.Name); + done(); + }); + }); + + it('should answer with BarionError when request object is not proper and validation is turned off', + function (done) { + notValidatedBarion.createPos(testData.createPos.errorRequestBody) + .catch(err => { + expect(err.name).to.equal('BarionError'); + expect(err.errors).to.be.an('array'); + expect(err.errors[0]).to.deep.include(testData.createPos.expectedError); + done(); + }); + } + ); + }); + + describe('Get POS details (callback)', function () { + + let successRequestBody = {}; + + beforeEach(function (done) { + const createPosRequest = { + ...testData.createPos.successRequestBody, + Name: `Test Shop ${Date.now()}` + }; + notValidatedBarion.createPos(createPosRequest, function (err, data) { + if (err) { + return done(err); + } + + successRequestBody = { + ApiKey: testData.createPos.successRequestBody.ApiKey, + PublicKey: data.PublicKey + }; + done(); + }); + }); + + afterEach(function () { + successRequestBody = null; + }); + + it('should get POS details when validation is turned on', function (done) { + validatedBarion.getPosDetails(successRequestBody, (err, res) => { + expect(err).to.be.null; + expect(res).to.deep.include(testData.getPosDetails.successResponseBody); + expect(res.PublicKey).to.equal(successRequestBody.PublicKey); + expect(res.Name).to.be.a('string'); + expect(res.Status).to.be.a('string'); + done(); + }); + }); + + it('should answer with BarionError when request object is not proper and validation is turned on', + function (done) { + validatedBarion.getPosDetails(testData.getPosDetails.errorRequestBody, (err, res) => { + expect(res).to.be.null; + expect(err.name).to.equal('BarionError'); + expect(err.statusCode).to.equal(testData.getPosDetails.expectedError.StatusCode); + done(); + }); + } + ); + + it('should get POS details when validation is turned off', function (done) { + notValidatedBarion.getPosDetails(successRequestBody, (err, res) => { + expect(err).to.be.null; + expect(res).to.deep.include(testData.getPosDetails.successResponseBody); + expect(res.PublicKey).to.equal(successRequestBody.PublicKey); + expect(res.Name).to.be.a('string'); + expect(res.Status).to.be.a('string'); + done(); + }); + }); + + it('should answer with BarionError when request object is not proper and validation is turned off', + function (done) { + notValidatedBarion.getPosDetails(testData.getPosDetails.errorRequestBody, (err, res) => { + expect(res).to.be.null; + expect(err.name).to.equal('BarionError'); + expect(err.statusCode).to.equal(testData.getPosDetails.expectedError.StatusCode); + done(); + }); + } + ); + }); + + describe('Get POS details (Promise)', function () { + + let successRequestBody = {}; + + beforeEach(function (done) { + const createPosRequest = { + ...testData.createPos.successRequestBody, + Name: `Test Shop ${Date.now()}` + }; + notValidatedBarion.createPos(createPosRequest, function (err, data) { + if (err) { + return done(err); + } + + successRequestBody = { + ApiKey: testData.createPos.successRequestBody.ApiKey, + PublicKey: data.PublicKey + }; + done(); + }); + }); + + afterEach(function () { + successRequestBody = null; + }); + + it('should get POS details when validation is turned on', function (done) { + validatedBarion.getPosDetails(successRequestBody) + .then(res => { + expect(res).to.deep.include(testData.getPosDetails.successResponseBody); + expect(res.PublicKey).to.equal(successRequestBody.PublicKey); + expect(res.Name).to.be.a('string'); + expect(res.Status).to.be.a('string'); + done(); + }); + }); + + it('should answer with BarionError when request object is not proper and validation is turned on', + function (done) { + validatedBarion.getPosDetails(testData.getPosDetails.errorRequestBody) + .catch(err => { + expect(err.name).to.equal('BarionError'); + expect(err.statusCode).to.equal(testData.getPosDetails.expectedError.StatusCode); + done(); + }); + } + ); + + it('should get POS details when validation is turned off', function (done) { + notValidatedBarion.getPosDetails(successRequestBody) + .then(res => { + expect(res).to.deep.include(testData.getPosDetails.successResponseBody); + expect(res.PublicKey).to.equal(successRequestBody.PublicKey); + expect(res.Name).to.be.a('string'); + expect(res.Status).to.be.a('string'); + done(); + }); + }); + + it('should answer with BarionError when request object is not proper and validation is turned off', + function (done) { + notValidatedBarion.getPosDetails(testData.getPosDetails.errorRequestBody) + .catch(err => { + expect(err.name).to.equal('BarionError'); + expect(err.statusCode).to.equal(testData.getPosDetails.expectedError.StatusCode); + done(); + }); + } + ); + }); + describe('Get history (callback)', function () { it('should get transaction history when validation is turned on', function (done) { validatedBarion.getHistory(testData.getHistory.successRequestBody, (err, res) => { diff --git a/test/integration/test-data.js b/test/integration/test-data.js index 26aaf64..27c32a8 100644 --- a/test/integration/test-data.js +++ b/test/integration/test-data.js @@ -192,6 +192,62 @@ module.exports = { ErrorCode: 'StatementNotFound' } }, + createPos: { + successRequestBody: { + ApiKey, + Name: `Test Shop ${Date.now()}`, + Url: 'https://example.com', + Description: 'Integration test shop for automated testing purposes.', + Logo: 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==', + Category: [ 'Other' ], + BusinessContact: { + Name: 'Test Business Contact', + PhoneNumber: '36301234567', + Email: 'business@example.com' + }, + TechnicalContact: { + Name: 'Test Technical Contact', + PhoneNumber: '36301234568', + Email: 'tech@example.com' + }, + CustomerServiceContact: { + Name: 'Test Support Team', + PhoneNumber: '36301234569', + Email: 'support@example.com' + }, + PrimaryCurrency: 'HUF', + ExpectedTurnover: 3, + FullPixelImplemented: false, + UseForEInvoicing: false, + CallBackUrl: 'https://example.com/callback' + }, + successResponseBody: { + Errors: [] + }, + errorRequestBody: { + ApiKey, + Name: 'Test Shop', + Url: 'https://example.com', + Description: 'Too short' + }, + expectedError: { + ErrorCode: 'ModelValidationError' + } + }, + getPosDetails: { + // successRequestBody: to be defined runtime, after creating a POS + successResponseBody: { + Errors: [] + }, + errorRequestBody: { + ApiKey, + PublicKey: '00000000-0000-0000-0000-000000000000' + + }, + expectedError: { + StatusCode: 404 + } + }, getHistory: { successRequestBody: { ApiKey, diff --git a/test/services.test.js b/test/services.test.js index e5713bc..196db2a 100644 --- a/test/services.test.js +++ b/test/services.test.js @@ -250,6 +250,34 @@ describe('lib/services.js', function () { }); }); + describe('#getPosDetails(environment, options)', function () { + it('should return a Promise on success', function (done) { + const services = serviceMocks.okService; + services.getPosDetails('test', {}) + .then(() => done()); + }); + + it('should return a Promise on failure', function (done) { + const services = serviceMocks.errorService; + services.getPosDetails('test', {}) + .catch(() => done()); + }); + }); + + describe('#createPos(environment, options)', function () { + it('should return a Promise on success', function (done) { + const services = serviceMocks.okService; + services.createPos('test', {}) + .then(() => done()); + }); + + it('should return a Promise on failure', function (done) { + const services = serviceMocks.errorService; + services.createPos('test', {}) + .catch(() => done()); + }); + }); + describe('#getHistory(environment, options)', function () { it('should return a Promise on success', function (done) { const services = serviceMocks.okService; @@ -331,4 +359,36 @@ describe('lib/services.js', function () { expect(doRequest).to.throw(/HTTP method/); }); }); + + describe('#replacePathParameters', function () { + const services = require('../lib/services'); + + it('should replace path parameters with values from options', function () { + const path = '/api/{userId}/orders/{orderId}'; + const options = { userId: '123', orderId: '456' }; + const result = services._private.replacePathParameters(path, options); + expect(result).to.equal('/api/123/orders/456'); + }); + + it('should return original path when no parameters are in path', function () { + const path = '/api/orders'; + const options = { userId: '123' }; + const result = services._private.replacePathParameters(path, options); + expect(result).to.equal('/api/orders'); + }); + + it('should not replace parameters that are not in options', function () { + const path = '/api/{userId}/orders/{orderId}'; + const options = { userId: '123' }; + const result = services._private.replacePathParameters(path, options); + expect(result).to.equal('/api/123/orders/{orderId}'); + }); + + it('should handle empty options object', function () { + const path = '/api/{userId}/orders'; + const options = {}; + const result = services._private.replacePathParameters(path, options); + expect(result).to.equal('/api/{userId}/orders'); + }); + }); });