├── .gitignore ├── README.md ├── package.json ├── lib └── liqpay.js └── test └── liqpay.test.js /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | .idea 3 | coverage 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | sdk-nodejs 2 | ======= 3 | 4 | LiqPay SDK-NodeJS 5 | 6 | Documentation https://www.liqpay.ua/documentation/en 7 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "liqpay-sdk-nodejs", 3 | "version": "2.0.0", 4 | "description": "Node.js sdk for liqpay.ua api", 5 | "main": "lib/liqpay.js", 6 | "directories": { 7 | "lib": "lib" 8 | }, 9 | "scripts": { 10 | "test": "jest" 11 | }, 12 | "jest": { 13 | "collectCoverage":true 14 | }, 15 | "keywords": [], 16 | "author": "", 17 | "license": "Open Software License (OSL 3.0)", 18 | "dependencies": { 19 | "axios": "^1.4.0", 20 | "jest": "^29.6.2" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /lib/liqpay.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /** 3 | * Liqpay Payment Module 4 | * 5 | * NOTICE OF LICENSE 6 | * 7 | * This source file is subject to the Open Software License (OSL 3.0) 8 | * that is available through the world-wide-web at this URL: 9 | * http://opensource.org/licenses/osl-3.0.php 10 | * 11 | * @module liqpay 12 | * @category LiqPay 13 | * @package liqpay/liqpay 14 | * @version 3.1 15 | * @author Liqpay 16 | * @copyright Copyright (c) 2014 Liqpay 17 | * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) 18 | * 19 | * EXTENSION INFORMATION 20 | * 21 | * LIQPAY API https://www.liqpay.ua/documentation/uk 22 | * 23 | */ 24 | 25 | const axios = require('axios'); 26 | const crypto = require('crypto'); 27 | 28 | /** 29 | * Creates object with helpers for accessing to Liqpay API 30 | * 31 | * @param {string} public_key 32 | * @param {string} private_key 33 | * 34 | * @throws {Error} 35 | */ 36 | module.exports = function LiqPay(public_key, private_key) { 37 | /** 38 | * @member {string} API host 39 | */ 40 | this.host = "https://www.liqpay.ua/api/"; 41 | 42 | this.availableLanguages = ['ru', 'uk', 'en']; 43 | 44 | this.buttonTranslations = {'ru': 'Оплатить', 'uk': 'Сплатити', 'en': 'Pay'}; 45 | /** 46 | * Call API 47 | * 48 | * @param {string} path 49 | * @param {object} params 50 | * @return {object} 51 | * @throws {Error} 52 | */ 53 | this.api = async function api(path, params) { 54 | if (!params.version) { 55 | throw new Error('version is null'); 56 | } 57 | 58 | params.public_key = public_key; 59 | const data = Buffer.from(JSON.stringify(params)).toString('base64'); 60 | const signature = this.str_to_sign(private_key + data + private_key); 61 | 62 | const dataToSend = new URLSearchParams(); 63 | dataToSend.append('data', data); 64 | dataToSend.append('signature', signature); 65 | 66 | try { 67 | const response = await axios.post(this.host + path, dataToSend, { 68 | headers: { 69 | 'Content-Type': 'application/x-www-form-urlencoded' 70 | } 71 | }); 72 | 73 | if (response.status === 200) { 74 | return response.data; 75 | } else { 76 | throw new Error(`Request failed with status code: ${response.status}`); 77 | } 78 | } catch (error) { 79 | throw error; 80 | } 81 | } 82 | 83 | /** 84 | * cnb_form 85 | * 86 | * @param {object} params 87 | * 88 | * @return {string} 89 | * 90 | * @throws {Error} 91 | */ 92 | this.cnb_form = function cnb_form(params) { 93 | let buttonText = this.buttonTranslations['uk']; 94 | if (params.language) { 95 | buttonText = this.buttonTranslations[params.language] || this.buttonTranslations['uk']; 96 | } 97 | 98 | params = this.cnb_params(params); 99 | const data = Buffer.from(JSON.stringify(params)).toString('base64'); 100 | const signature = this.str_to_sign(private_key + data + private_key); 101 | 102 | return '
'; 108 | }; 109 | 110 | /** 111 | * cnb_signature 112 | * 113 | * @param {object} params 114 | * 115 | * @return {string} 116 | * 117 | * @throws {InvalidArgumentException} 118 | */ 119 | this.cnb_signature = function cnb_signature(params) { 120 | params = this.cnb_params(params); 121 | const data = Buffer.from(JSON.stringify(params)).toString('base64'); 122 | return this.str_to_sign(private_key + data + private_key); 123 | }; 124 | 125 | /** 126 | * cnb_params 127 | * 128 | * @param {object} params 129 | * 130 | * @return {object} params 131 | * 132 | * @throws {Error} 133 | */ 134 | this.cnb_params = function cnb_params(params) { 135 | params.public_key = public_key; 136 | 137 | // Validate and convert version to number 138 | if (params.version) { 139 | if (typeof params.version === 'string' && !isNaN(Number(params.version))) { 140 | params.version = Number(params.version); 141 | } else if (typeof params.version !== 'number') { 142 | throw new Error('version must be a number or a string that can be converted to a number'); 143 | } 144 | } else { 145 | throw new Error('version is null'); 146 | } 147 | 148 | // Validate and convert amount to number 149 | if (params.amount) { 150 | if (typeof params.amount === 'string' && !isNaN(Number(params.amount))) { 151 | params.amount = Number(params.amount); 152 | } else if (typeof params.amount !== 'number') { 153 | throw new Error('amount must be a number or a string that can be converted to a number'); 154 | } 155 | } else { 156 | throw new Error('amount is null'); 157 | } 158 | 159 | // Ensure other parameters are strings 160 | const stringParams = ['action', 'currency', 'description', 'language']; 161 | for (const param of stringParams) { 162 | if (params[param] && typeof params[param] !== 'string') { 163 | params[param] = String(params[param]); 164 | } else if (!params[param] && param !== 'language') { // language is optional 165 | throw new Error(`${param} is null or not provided`); 166 | } 167 | } 168 | 169 | // Check if language is set and is valid 170 | if (params.language && !this.availableLanguages.includes(params.language)) { 171 | params.language= 'uk'; 172 | } 173 | 174 | return params; 175 | }; 176 | 177 | 178 | /** 179 | * str_to_sign 180 | * 181 | * @param {string} str 182 | * 183 | * @return {string} 184 | * 185 | * @throws {Error} 186 | */ 187 | this.str_to_sign = function str_to_sign(str) { 188 | if (typeof str !== 'string') { 189 | throw new Error('Input must be a string'); 190 | } 191 | 192 | const sha1 = crypto.createHash('sha1'); 193 | sha1.update(str); 194 | return sha1.digest('base64'); 195 | }; 196 | 197 | /** 198 | * Return Form Object 199 | * 200 | * @param {object} params 201 | * 202 | * @returns {{ data: string, signature: string }} Form Object 203 | */ 204 | this.cnb_object = function cnb_object(params) { 205 | params.language = params.language || "uk"; 206 | params = this.cnb_params(params); 207 | const data = Buffer.from(JSON.stringify(params)).toString('base64'); 208 | const signature = this.str_to_sign(private_key + data + private_key); 209 | return {data: data, signature: signature}; 210 | }; 211 | 212 | return this; 213 | }; 214 | -------------------------------------------------------------------------------- /test/liqpay.test.js: -------------------------------------------------------------------------------- 1 | const LiqPay = require('../lib/liqpay'); // Adjust the path to your LiqPay class file 2 | const crypto = require('crypto'); 3 | const axios = require('axios'); 4 | 5 | jest.mock('axios'); // This will mock the axios module 6 | describe('LiqPay class', () => { 7 | 8 | let liqPayInstance; 9 | 10 | beforeEach(() => { 11 | liqPayInstance = new LiqPay('public key', 'Private key'); // Assuming you have a constructor in your LiqPay class 12 | }); 13 | 14 | describe('cnb_form method', () => { 15 | 16 | let liqPayInstance; 17 | 18 | beforeEach(() => { 19 | liqPayInstance = new LiqPay('public key', 'Private key'); // Assuming you have a constructor in your LiqPay class 20 | }); 21 | 22 | it('should return a form with correct data and signature', () => { 23 | const params = { 24 | action: 'pay', 25 | amount: '100', 26 | currency: 'USD', 27 | description: 'Test payment', 28 | order_id: 'order12345', 29 | version: '3', 30 | language: 'ru' 31 | }; 32 | 33 | const form = liqPayInstance.cnb_form(params); 34 | 35 | // Check if form contains the correct data and signature 36 | expect(form).toContain('name="data"'); 37 | expect(form).toContain('name="signature"'); 38 | 39 | // Check if the button label is set correctly 40 | expect(form).toContain('