├── .gitignore ├── src ├── Api │ ├── index.ts │ ├── request.ts │ ├── response.ts │ ├── notification.ts │ └── constants.ts ├── Client │ ├── ClientOptions.ts │ ├── index.ts │ ├── Response │ │ ├── PayoutClientResponse.ts │ │ ├── PaymentWith3DSClientResponse.ts │ │ ├── PaymentClientResponse.ts │ │ └── PaymentHistoryClientResponse.ts │ ├── ClientAbstract.ts │ ├── ClientResponse.ts │ └── ClientRequestAbstract.ts ├── index.ts ├── utils.ts ├── ClientService.ts ├── ReceiptApi.ts ├── NotificationHandlers.ts └── ClientApi.ts ├── test ├── tsconfig.json └── src │ ├── lib │ ├── functions.ts │ ├── options.ts │ └── ServiceRequestMock.ts │ ├── Service.test.ts │ ├── Handlers.test.ts │ └── Respone.test.ts ├── jest.config.js ├── tsconfig.json ├── .editorconfig ├── tsconfig.base.json ├── .eslintrc ├── LICENSE ├── package.json └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea/ 2 | /node_modules/ 3 | /dist/ 4 | /tsconfig.tsbuildinfo 5 | -------------------------------------------------------------------------------- /src/Api/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./constants"; 2 | export * from "./notification"; 3 | export * from "./request"; 4 | export * from "./response"; 5 | -------------------------------------------------------------------------------- /test/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.base.json", 3 | "compilerOptions": { 4 | "declaration": false, 5 | "sourceMap": false, 6 | "noEmit": true 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: "ts-jest", 3 | testEnvironment: "node", 4 | globals: { 5 | "ts-jest": { 6 | tsconfig: "test/tsconfig.json", 7 | }, 8 | }, 9 | roots: [ 10 | "/test", 11 | ], 12 | }; 13 | -------------------------------------------------------------------------------- /test/src/lib/functions.ts: -------------------------------------------------------------------------------- 1 | import {BaseResponse} from "../../../src"; 2 | 3 | export function createResponse(data: object, success = true): T { 4 | return { 5 | Model: data, 6 | Success: success, 7 | Message: success ? null : "Error message", 8 | } as any; 9 | } 10 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.base.json", 3 | "compilerOptions": { 4 | "outDir": "./dist", 5 | "rootDir": "./src", 6 | "incremental": true, 7 | "declaration": true, 8 | "sourceMap": true, 9 | }, 10 | "include": [ 11 | "src" 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /test/src/lib/options.ts: -------------------------------------------------------------------------------- 1 | import {TaxationSystem} from "../../../src"; 2 | 3 | export const options = { 4 | endpoint: "https://fakeapi.com", 5 | publicId: "public id", 6 | privateKey: "private key", 7 | org: { 8 | inn: 12345678, 9 | taxationSystem: TaxationSystem.GENERAL, 10 | }, 11 | }; 12 | -------------------------------------------------------------------------------- /src/Client/ClientOptions.ts: -------------------------------------------------------------------------------- 1 | import {TaxationSystemType} from "../Api"; 2 | 3 | export interface ClientOptions { 4 | publicId: string; 5 | privateKey: string; 6 | endpoint?: string; 7 | org?: ClientOptionsOrg; 8 | } 9 | 10 | export interface ClientOptionsOrg { 11 | inn: number; 12 | taxationSystem: TaxationSystemType; 13 | } 14 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import {ClientService} from "./ClientService"; 2 | 3 | export * from "./Client"; 4 | export * from "./ClientApi"; 5 | export * from "./ReceiptApi"; 6 | export * from "./ClientService"; 7 | export * from "./NotificationHandlers"; 8 | export * from "./Client/ClientOptions"; 9 | export * from "./Api"; 10 | 11 | export default ClientService; 12 | -------------------------------------------------------------------------------- /src/Client/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./Response/PaymentClientResponse"; 2 | export * from "./Response/PaymentWith3DSClientResponse"; 3 | export * from "./Response/PaymentHistoryClientResponse"; 4 | export * from "./Response/PayoutClientResponse"; 5 | export * from "./ClientAbstract"; 6 | export * from "./ClientOptions"; 7 | export * from "./ClientResponse"; 8 | export {ClientRequestAbstract} from "./ClientRequestAbstract"; 9 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: http://EditorConfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | end_of_line = lf 7 | insert_final_newline = true 8 | trim_trailing_whitespace = true 9 | indent_style = space 10 | indent_size = 4 11 | max_line_length = 120 12 | ij_javascript_comma_on_new_line = true 13 | 14 | [*.md] 15 | trim_trailing_whitespace = false 16 | 17 | [*.yml] 18 | indent_size = 2 19 | -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | import * as crypto from "crypto"; 2 | 3 | export function signString(privateKey: string, data: string) { 4 | return crypto 5 | .createHmac("sha256", privateKey) 6 | .update(data) 7 | .digest("base64"); 8 | } 9 | 10 | export function checkSignedString(privateKey: string, signature: string, data: string) { 11 | return signString(privateKey, data) === signature; 12 | } 13 | -------------------------------------------------------------------------------- /tsconfig.base.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2018", 4 | "module": "commonjs", 5 | "strict": true, 6 | "noEmitOnError": true, 7 | "noImplicitAny": true, 8 | "strictNullChecks": true, 9 | "strictPropertyInitialization": true, 10 | "alwaysStrict": false, 11 | "strictBindCallApply": true, 12 | "strictFunctionTypes": true, 13 | "newLine": "LF" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/Client/Response/PayoutClientResponse.ts: -------------------------------------------------------------------------------- 1 | import {PayoutResponse, PayoutSuccessResponse} from "../../Api"; 2 | import {ClientResponse} from "../ClientResponse"; 3 | 4 | export class PayoutClientResponse extends ClientResponse { 5 | public isPayoutSuccessResponse(): this is ClientResponse { 6 | const {Model} = this.getResponse(); 7 | return this.isSuccess() && PayoutClientResponse.has(["TransactionId", "AuthCode"], Model); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/Client/ClientAbstract.ts: -------------------------------------------------------------------------------- 1 | import {ClientOptions, ClientOptionsOrg} from "./ClientOptions"; 2 | 3 | export class ClientAbstract { 4 | protected options: ClientOptions & { endpoint: string }; 5 | 6 | constructor(_options: ClientOptions) { 7 | this.options = { 8 | endpoint: "https://api.cloudpayments.ru", 9 | ..._options, 10 | }; 11 | } 12 | 13 | public getPublicId(): string { 14 | return this.options.publicId; 15 | } 16 | 17 | public getEndpoint(): string { 18 | return this.options.endpoint; 19 | } 20 | 21 | public getOrgOptions(): ClientOptionsOrg | null { 22 | if (this.options.org) { 23 | return this.options.org; 24 | } 25 | 26 | return null; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Client/Response/PaymentWith3DSClientResponse.ts: -------------------------------------------------------------------------------- 1 | import {Payment3DSResponse, PaymentSuccessResponse, PaymentWith3DSResponse} from "../../Api"; 2 | import {PaymentClientResponse} from "./PaymentClientResponse"; 3 | import {ClientResponse} from "../ClientResponse"; 4 | 5 | export class PaymentWith3DSClientResponse 6 | extends PaymentClientResponse { 7 | public declare readonly isPaymentSuccessResponse: () => 8 | this is PaymentWith3DSClientResponse; 9 | 10 | public isPayment3DSResponse(): this is PaymentWith3DSClientResponse { 11 | const {Model} = this.getResponse(); 12 | return !this.isSuccess() && ClientResponse.has(["TransactionId", "PaReq", "AcsUrl"], Model); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/Client/Response/PaymentClientResponse.ts: -------------------------------------------------------------------------------- 1 | import { 2 | PaymentFailedResponse, 3 | PaymentModel, 4 | PaymentResponse, PaymentSuccessModel, 5 | PaymentSuccessResponse, 6 | PaymentWith3DSResponse, 7 | } from "../../Api"; 8 | import {ClientResponse} from "../ClientResponse"; 9 | 10 | export class PaymentClientResponse 11 | extends ClientResponse { 12 | public isPaymentSuccessResponse(): this is PaymentClientResponse { 13 | const {Model} = this.getResponse(); 14 | return super.isSuccess() && ClientResponse.has(["TransactionId", "AuthCode"], Model); 15 | } 16 | 17 | public static isPaymentSuccessResponse(data: PaymentModel | PaymentSuccessModel): data is PaymentSuccessModel { 18 | return this.has(["TransactionId", "AuthCode"], data); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Client/ClientResponse.ts: -------------------------------------------------------------------------------- 1 | import {BaseResponse} from "../Api"; 2 | 3 | export class ClientResponse { 4 | protected readonly response: T; 5 | 6 | constructor(response: T) { 7 | this.response = response; 8 | } 9 | 10 | public getResponse(): T { 11 | return this.response; 12 | } 13 | 14 | public isSuccess(): boolean { 15 | return this.response.Success; 16 | } 17 | 18 | public getMessage(): string | null { 19 | return this.response.Message; 20 | } 21 | 22 | protected static has(key: string | string[], object: object | null): boolean { 23 | if (typeof object !== "object" || object === null) { 24 | return false; 25 | } 26 | 27 | if (Array.isArray(key)) { 28 | return key.every(k => Reflect.has(object, k)); 29 | } 30 | 31 | return Reflect.has(object, key); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Client/Response/PaymentHistoryClientResponse.ts: -------------------------------------------------------------------------------- 1 | import {ClientResponse} from "../ClientResponse"; 2 | import {HistoryPaymentModel, PaymentHistoryResponse, PaymentSuccessModel} from "../../Api"; 3 | import {PaymentClientResponse} from "./PaymentClientResponse"; 4 | 5 | export class PaymentHistoryClientResponse 6 | extends ClientResponse { 7 | public getSuccessPayments(): PaymentSuccessModel[] { 8 | return this.getPayments().filter(PaymentClientResponse.isPaymentSuccessResponse); 9 | } 10 | 11 | public getFailPayments() { 12 | return this.getPayments().filter((data) => !PaymentClientResponse.isPaymentSuccessResponse(data)); 13 | } 14 | 15 | public getPayments(): HistoryPaymentModel[] { 16 | if (this.isSuccess()) { 17 | const {Model = []} = this.getResponse(); 18 | return Model; 19 | } 20 | 21 | return []; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "@typescript-eslint/parser", 3 | "plugins": [ 4 | "@typescript-eslint" 5 | ], 6 | "extends": [ 7 | "plugin:@typescript-eslint/recommended" 8 | ], 9 | "rules": { 10 | "semi": "off", 11 | "@typescript-eslint/explicit-function-return-type": "off", 12 | "@typescript-eslint/explicit-module-boundary-types": "off", 13 | "@typescript-eslint/ban-types": "off", 14 | "@typescript-eslint/no-explicit-any": "off", 15 | "@typescript-eslint/semi": [ 16 | 2, 17 | "always" 18 | ], 19 | "max-len": [ 20 | 2, 21 | 120 22 | ], 23 | "comma-dangle": [ 24 | "error", 25 | { 26 | "arrays": "always-multiline", 27 | "objects": "always-multiline", 28 | "imports": "always-multiline", 29 | "exports": "always-multiline", 30 | "functions": "always-multiline" 31 | } 32 | ] 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Artur Bier 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /test/src/lib/ServiceRequestMock.ts: -------------------------------------------------------------------------------- 1 | import * as stream from "stream"; 2 | import {IncomingHttpHeaders, IncomingMessage} from "http"; 3 | import {signString} from "../../../src/utils"; 4 | 5 | export class ServiceRequestMock extends stream.Readable { 6 | public readonly headers: IncomingHttpHeaders = {}; 7 | public readonly method?: string; 8 | public readonly url?: string; 9 | protected readonly raw: string; 10 | 11 | constructor(privateKey: string, raw: string) { 12 | super(); 13 | this.headers = { 14 | "content-hmac": signString(privateKey, raw), 15 | }; 16 | 17 | this.method = "POST"; 18 | this.raw = raw; 19 | process.nextTick(() => this.writeRequest()); 20 | } 21 | 22 | public static create(privateKey: string, raw: string) { 23 | return (new this(privateKey, raw) as any) as IncomingMessage & { 24 | writeRequest(): void; 25 | }; 26 | } 27 | 28 | public writeRequest() { 29 | this.emit("data", Buffer.from(this.raw, "utf-8")); 30 | this.emit("end"); 31 | } 32 | 33 | public destroy(): this { 34 | return this; 35 | } 36 | 37 | public _read(): void { 38 | return; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/ClientService.ts: -------------------------------------------------------------------------------- 1 | import {ClientAbstract, ClientOptions} from "./Client"; 2 | import {ClientApi} from "./ClientApi"; 3 | import {ReceiptApi} from "./ReceiptApi"; 4 | import {NotificationHandlers} from "./NotificationHandlers"; 5 | 6 | export class ClientService extends ClientAbstract { 7 | protected client: ClientApi; 8 | protected receipt: ReceiptApi; 9 | protected handlers: NotificationHandlers; 10 | 11 | constructor(options: ClientOptions) { 12 | super(options); 13 | this.client = ClientService.createClientApi(this.options); 14 | this.handlers = ClientService.createNotificationHandlers(this.options); 15 | this.receipt = ClientService.createReceiptApi(this.options); 16 | } 17 | 18 | public static createClientApi(options: ClientOptions) { 19 | return new ClientApi(options); 20 | } 21 | 22 | public static createReceiptApi(options: ClientOptions) { 23 | return new ReceiptApi(options); 24 | } 25 | 26 | public static createNotificationHandlers(options: ClientOptions) { 27 | return new NotificationHandlers(options); 28 | } 29 | 30 | public getClientApi() { 31 | return this.client; 32 | } 33 | 34 | public getNotificationHandlers() { 35 | return this.handlers; 36 | } 37 | 38 | public getReceiptApi() { 39 | return this.receipt; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /test/src/Service.test.ts: -------------------------------------------------------------------------------- 1 | import {ClientApi, ClientService, NotificationHandlers, ReceiptApi} from "../../src"; 2 | import {options} from "./lib/options"; 3 | 4 | test("Service", async () => { 5 | const service = new ClientService(options); 6 | 7 | expect(service.getEndpoint()).toBe(options.endpoint); 8 | expect(service.getPublicId()).toBe(options.publicId); 9 | 10 | expect(service.getClientApi()).toBeInstanceOf(ClientApi); 11 | expect(ClientService.createClientApi(options)).toBeInstanceOf(ClientApi); 12 | expect(service.getClientApi().getEndpoint()).toBe(options.endpoint); 13 | 14 | expect(service.getReceiptApi()).toBeInstanceOf(ReceiptApi); 15 | expect(ClientService.createReceiptApi(options)).toBeInstanceOf(ReceiptApi); 16 | expect(service.getReceiptApi().getEndpoint()).toBe(options.endpoint.concat("/kkt")); 17 | 18 | expect(service.getNotificationHandlers()).toBeInstanceOf(NotificationHandlers); 19 | expect(ClientService.createNotificationHandlers(options)).toBeInstanceOf(NotificationHandlers); 20 | expect(service.getNotificationHandlers().getEndpoint()).toBe(options.endpoint); 21 | 22 | expect(service.getClientApi() === service.getClientApi()).toBe(true); 23 | expect(service.getReceiptApi() === service.getReceiptApi()).toBe(true); 24 | expect(service.getNotificationHandlers() === service.getNotificationHandlers()).toBe(true); 25 | }); 26 | -------------------------------------------------------------------------------- /test/src/Handlers.test.ts: -------------------------------------------------------------------------------- 1 | import {ClientService, ResponseCodes} from "../../src"; 2 | import {options} from "./lib/options"; 3 | import {signString} from "../../src/utils"; 4 | import {ServiceRequestMock} from "./lib/ServiceRequestMock"; 5 | 6 | test("Notification Handlers", async () => { 7 | const service = new ClientService(options); 8 | const clientHandlers = service.getNotificationHandlers(); 9 | const validator1 = async () => ResponseCodes.SUCCESS; 10 | const validator2 = async () => ResponseCodes.EXPIRED; 11 | 12 | expect( 13 | await clientHandlers.handleCheckRequest( 14 | ServiceRequestMock.create(options.privateKey, "key=value1"), 15 | validator1, 16 | ), 17 | ).toEqual({ 18 | response: {code: ResponseCodes.SUCCESS}, 19 | request: {key: "value1"}, 20 | }); 21 | 22 | expect( 23 | await clientHandlers.handleCheckRequest( 24 | { 25 | signature: signString(options.privateKey, JSON.stringify({key: "value2"})), 26 | payload: {key: "value2"}, 27 | }, 28 | validator2, 29 | ), 30 | ).toEqual({ 31 | response: {code: ResponseCodes.EXPIRED}, 32 | request: {key: "value2"}, 33 | }); 34 | 35 | expect( 36 | clientHandlers.handleCheckRequest(ServiceRequestMock.create("fake key", "key=value"), validator1), 37 | ).rejects.toThrow("Invalid signature"); 38 | }); 39 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cloudpayments", 3 | "version": "6.0.1", 4 | "description": "CloudPayments API for Node.js with typings", 5 | "main": "dist/index.js", 6 | "types": "dist/index.d.ts", 7 | "files": [ 8 | "dist" 9 | ], 10 | "scripts": { 11 | "test": "jest", 12 | "test:watch": "jest -w", 13 | "lint": "eslint '{src,test}/**/*.ts'", 14 | "build": "rimraf dist/* && tsc -p tsconfig.json", 15 | "watch": "tsc -w", 16 | "prepublishOnly": "npm test", 17 | "version": "git add -A", 18 | "postversion": "git push && npm pub ./" 19 | }, 20 | "publishConfig": { 21 | "registry": "https://registry.npmjs.org" 22 | }, 23 | "repository": { 24 | "type": "git", 25 | "url": "git+https://github.com/izatop/cloudpayments.git" 26 | }, 27 | "keywords": [ 28 | "payments", 29 | "cloudpayments", 30 | "typescript", 31 | "es6", 32 | "api" 33 | ], 34 | "author": "Artur F. Bier ", 35 | "license": "MIT", 36 | "bugs": { 37 | "url": "https://github.com/izatop/cloudpayments/issues" 38 | }, 39 | "homepage": "https://github.com/izatop/cloudpayments#readme", 40 | "dependencies": { 41 | "@types/node": "^17.0.8", 42 | "@types/node-fetch": "^2.5.12", 43 | "@types/object-hash": "^2.2.1", 44 | "@types/qs": "^6.9.7", 45 | "node-fetch": "3.1.1", 46 | "object-hash": "^2.2.0", 47 | "qs": "^6.10.3" 48 | }, 49 | "devDependencies": { 50 | "@types/jest": "^27.4.0", 51 | "@types/tape": "^4.13.2", 52 | "@typescript-eslint/eslint-plugin": "^5.9.1", 53 | "@typescript-eslint/parser": "^5.9.1", 54 | "eslint": "^8.6.0", 55 | "jest": "^27.4.7", 56 | "rimraf": "^3.0.0", 57 | "source-map-support": "^0.5.21", 58 | "ts-jest": "^27.1.2", 59 | "ts-node": "^10.4.0", 60 | "typescript": "^4.5.4" 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/Client/ClientRequestAbstract.ts: -------------------------------------------------------------------------------- 1 | import fetch from "node-fetch"; 2 | import {ClientResponse} from "./ClientResponse"; 3 | import {BaseResponse} from "../Api"; 4 | import {join} from "path"; 5 | import {ClientAbstract} from "./ClientAbstract"; 6 | 7 | export class ClientRequestAbstract extends ClientAbstract { 8 | /** 9 | * HTTP Client 10 | * 11 | * @returns {(url: (string | Request), init?: RequestInit) => Promise} 12 | */ 13 | public get client(): typeof fetch { 14 | return fetch; 15 | } 16 | 17 | /** 18 | * 19 | */ 20 | public async ping(): Promise> { 21 | const response = await this.client(this.getEndpoint().concat(join("/test")), { 22 | method: "POST", 23 | headers: {"Content-Type": "application/json"}, 24 | body: JSON.stringify({}), 25 | }); 26 | 27 | return new ClientResponse(await response.json() as BaseResponse); 28 | } 29 | 30 | /** 31 | * Create request to an API endpoint. 32 | * 33 | * @param {string} url 34 | * @param {Object} data 35 | * @param {string} requestId 36 | * @returns {Promise>} 37 | */ 38 | protected async call(url: string, data?: object, requestId?: string): Promise { 39 | const authorization = Buffer.from(`${this.options.publicId}:${this.options.privateKey}`, "utf-8").toString( 40 | "base64", 41 | ); 42 | 43 | const headers: { [key: string]: string } = { 44 | "Content-Type": "application/json", 45 | Authorization: `Basic ${authorization}`, 46 | }; 47 | 48 | if (requestId) { 49 | headers["X-Request-ID"] = requestId; 50 | } 51 | 52 | const response = await this.client(this.getEndpoint().concat(join("/", url)), { 53 | headers, 54 | method: "POST", 55 | body: data ? JSON.stringify(data) : undefined, 56 | }); 57 | 58 | return await response.json() as R; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/ReceiptApi.ts: -------------------------------------------------------------------------------- 1 | import {ok} from "assert"; 2 | import * as objectHash from "object-hash"; 3 | import {ClientRequestAbstract, ClientResponse} from "./Client"; 4 | import { 5 | BaseResponse, 6 | CustomerReceipt, 7 | ReceiptApiRequest, 8 | ReceiptRequest, 9 | validateTaxationSystem, 10 | validateVAT, 11 | } from "./Api"; 12 | 13 | export class ReceiptApi extends ClientRequestAbstract { 14 | public getEndpoint() { 15 | return this.options.endpoint.replace(/\/$/, "").concat("/kkt"); 16 | } 17 | 18 | /** 19 | * Create receipt 20 | * 21 | * @param {Receipt} request Common request fields 22 | * @param {Receipt} receipt Receipt fields 23 | * @param {string} requestId Idempotent request id (calculated automatically if not provided) 24 | * @returns {Promise} 25 | */ 26 | async createReceipt(request: ReceiptRequest, receipt: CustomerReceipt, requestId?: string) { 27 | const {..._request} = request; 28 | const {..._receipt} = receipt; 29 | if (this.options.org) { 30 | if (!_request.Inn && this.options.org.inn) { 31 | _request.Inn = this.options.org.inn; 32 | } 33 | 34 | if (!_receipt.taxationSystem && validateTaxationSystem(this.options.org.taxationSystem)) { 35 | _receipt.taxationSystem = this.options.org.taxationSystem; 36 | } 37 | } 38 | 39 | ok(_request.Type, "Type is required"); 40 | ok(_request.Inn, "Inn is required"); 41 | 42 | ok(validateTaxationSystem(_receipt.taxationSystem), "A receipt field taxationSystem should be valid"); 43 | ok(_receipt.Items && _receipt.Items.length > 0, "A receipt field Items should be filled"); 44 | 45 | ok(_receipt.Items.filter(x => !validateVAT(x.vat)).length === 0, "You should fill VAT with valid values"); 46 | 47 | const data: ReceiptApiRequest = { 48 | ..._request, 49 | CustomerReceipt: _receipt, 50 | }; 51 | 52 | return new ClientResponse(await this.call("receipt", data, requestId || objectHash(receipt))); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /test/src/Respone.test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Payment3DSResponse, 3 | PaymentClientResponse, 4 | PaymentFailedResponse, 5 | PaymentSuccessResponse, 6 | PaymentWith3DSClientResponse, 7 | PayoutClientResponse, 8 | PayoutSuccessResponse, 9 | } from "../../src"; 10 | import {createResponse} from "./lib/functions"; 11 | 12 | test("Payment Response", () => { 13 | const dataPaymentSuccess = createResponse({ 14 | TransactionId: 1, 15 | AuthCode: "1", 16 | }); 17 | const response = new PaymentClientResponse(dataPaymentSuccess); 18 | expect(response.isPaymentSuccessResponse()).toBe(true); 19 | expect(response.getResponse()).toEqual(dataPaymentSuccess); 20 | expect(response.getResponse().Model.TransactionId).toEqual(dataPaymentSuccess.Model.TransactionId); 21 | }); 22 | 23 | test("Payment with 3DS Response", () => { 24 | const dataSuccess = createResponse( 25 | { 26 | TransactionId: 1, 27 | PaReq: "PaReq", 28 | AcsUrl: "AcsUrl", 29 | }, 30 | false, 31 | ); 32 | 33 | const response = new PaymentWith3DSClientResponse(dataSuccess); 34 | expect(response.isPayment3DSResponse()).toBe(true); 35 | expect(response.getResponse()).toEqual(dataSuccess); 36 | expect(response.getResponse().Model.AcsUrl).toBe(dataSuccess.Model.AcsUrl); 37 | }); 38 | 39 | test("Payout Response", () => { 40 | const dataSuccess = createResponse({ 41 | TransactionId: 1, 42 | AuthCode: "1", 43 | }); 44 | 45 | const response = new PayoutClientResponse(dataSuccess); 46 | expect(response.isPayoutSuccessResponse()).toBe(true); 47 | expect(response.getResponse()).toEqual(dataSuccess); 48 | expect(response.getResponse().Model.TransactionId).toBe(dataSuccess.Model.TransactionId); 49 | }); 50 | 51 | test("Payment Fail", () => { 52 | const data = createResponse({Reason: "Error"}, false); 53 | const response = new PaymentWith3DSClientResponse(data); 54 | 55 | expect(response.isSuccess()).toBe(false); 56 | expect(response.getResponse().Model.Reason).toBe("Error"); 57 | }); 58 | -------------------------------------------------------------------------------- /src/Api/request.ts: -------------------------------------------------------------------------------- 1 | import {ReceiptTypes, TaxationSystemType, ValidCurrency, VATType} from "./constants"; 2 | 3 | export interface BaseRequest { 4 | CultureName?: "ru-RU" | "en-US" | "lv" | "az" | "kk" | "uk" | "pl"; 5 | } 6 | 7 | /** 8 | * Параметры формирования кассовго чека 9 | * 10 | * @see https://cloudpayments.ru/docs/api/kassa#receipt 11 | */ 12 | export interface ReceiptRequest extends BaseRequest { 13 | Inn?: number; 14 | Type: ReceiptTypes; 15 | InvoiceId?: string; 16 | AccountId?: string; 17 | } 18 | 19 | export interface CustomerReceipt { 20 | taxationSystem?: TaxationSystemType; 21 | email?: string; 22 | phone?: string; 23 | Items: CustomerReceiptItem[]; 24 | } 25 | 26 | export interface CustomerReceiptItem { 27 | label: string; 28 | price: number; 29 | quantity: number; 30 | amount: number; 31 | vat?: VATType; 32 | ean13?: string; 33 | } 34 | 35 | export interface ReceiptApiRequest extends ReceiptRequest { 36 | CustomerReceipt: CustomerReceipt; 37 | } 38 | 39 | /** 40 | * Payments 41 | */ 42 | export interface PaymentRequest extends BaseRequest { 43 | Amount: number; 44 | Currency: ValidCurrency; 45 | IpAddress?: string; 46 | Name?: string; 47 | InvoiceId?: string; 48 | Description?: string; 49 | Email?: string; 50 | JsonData?: string; 51 | } 52 | 53 | export interface CryptogramPaymentRequest extends PaymentRequest { 54 | IpAddress: string; 55 | CardCryptogramPacket: string; 56 | AccountId?: string; 57 | } 58 | 59 | export interface TokenPaymentRequest extends PaymentRequest { 60 | AccountId: string; 61 | Token: string; 62 | } 63 | 64 | export interface Confirm3DSRequest extends BaseRequest { 65 | TransactionId: string; 66 | PaRes: string; 67 | } 68 | 69 | export interface ConfirmPaymentRequest extends BaseRequest { 70 | TransactionId: number; 71 | Amount: number; 72 | JsonData?: string; 73 | } 74 | 75 | export interface RefundPaymentRequest extends BaseRequest { 76 | TransactionId: number; 77 | Amount: number; 78 | JsonData?: string; 79 | } 80 | 81 | export interface VoidPaymentRequest extends BaseRequest { 82 | TransactionId: number; 83 | } 84 | 85 | export interface LinkPaymentRequest extends BaseRequest { 86 | Amount: number; 87 | Currency: ValidCurrency; 88 | JsonData?: string; 89 | Description: string; 90 | email?: string; 91 | phone?: string; 92 | } 93 | 94 | /** 95 | * Payouts 96 | */ 97 | export interface PayoutRequest extends BaseRequest { 98 | Amount: number; 99 | Currency: ValidCurrency; 100 | AccountId: string; 101 | Description?: string; 102 | Email?: string; 103 | JsonData?: string; 104 | InvoiceId?: string; 105 | } 106 | 107 | export interface CryptogramPayoutRequest extends PayoutRequest { 108 | Name: string; 109 | CardCryptogramPacket: string; 110 | } 111 | 112 | export interface TokenPayoutRequest extends PayoutRequest { 113 | Token: string; 114 | } 115 | -------------------------------------------------------------------------------- /src/Api/response.ts: -------------------------------------------------------------------------------- 1 | import {ErrorCodes, TransactionStatus, ValidCurrency} from "./constants"; 2 | import {SubscriptionModel} from "./notification"; 3 | 4 | export interface BaseResponse { 5 | Message: string | null; 6 | Success: boolean; 7 | } 8 | 9 | export interface PaymentModel { 10 | TransactionId: number; 11 | Amount: number; 12 | Currency: ValidCurrency; 13 | CurrencyCode: number; 14 | InvoiceId?: string; 15 | AccountId?: string; 16 | Email?: string; 17 | Description?: string; 18 | JsonData?: string; 19 | CreatedDate: string; 20 | CreatedDateIso: string; 21 | TestMode: boolean; 22 | IpAddress: string; 23 | IpCountry: string; 24 | IpCity: string; 25 | IpRegion: string; 26 | IpDistrict: string; 27 | IpLatitude: number; 28 | IpLongitude: number; 29 | CardFirstSix: string; 30 | CardLastFour: string; 31 | CardExpDate: string; 32 | CardType: string; 33 | CardTypeCode: number; 34 | Issuer: string; 35 | IssuerBankCountry: string; 36 | Status: TransactionStatus; 37 | StatusCode: number; 38 | Reason: string; 39 | ReasonCode: ErrorCodes; 40 | CardHolderMessage: string; 41 | Name: string; 42 | } 43 | 44 | export interface PaymentSuccessModel extends PaymentModel { 45 | AuthDate: string; 46 | AuthDateIso: string; 47 | AuthCode: string; 48 | ConfirmDate: string; 49 | ConfirmDateIso: string; 50 | Token: string; 51 | } 52 | 53 | export interface Payment3DSModel { 54 | TransactionId: number; 55 | PaReq: string; 56 | AcsUrl: string; 57 | } 58 | 59 | export interface Payment3DSResponse extends BaseResponse { 60 | Success: false; 61 | Message: null; 62 | Model: Payment3DSModel; 63 | } 64 | 65 | export interface PaymentSuccessResponse extends BaseResponse { 66 | Success: true; 67 | Message: null; 68 | Model: PaymentSuccessModel; 69 | } 70 | 71 | export interface PaymentFailedResponse extends BaseResponse { 72 | Message: string; 73 | Success: false; 74 | Model: PaymentModel; 75 | } 76 | 77 | export type PaymentResponse = PaymentFailedResponse | PaymentSuccessResponse; 78 | export type PaymentWith3DSResponse = PaymentFailedResponse | PaymentSuccessResponse | Payment3DSResponse; 79 | 80 | export type HistoryPaymentModel = PaymentModel | PaymentSuccessModel; 81 | 82 | export type PaymentGetResponse = { 83 | Success: boolean; 84 | Message: null; 85 | Model: HistoryPaymentModel; 86 | }; 87 | 88 | export type PaymentHistoryResponse = { 89 | Success: boolean; 90 | Message: never; 91 | Model: HistoryPaymentModel[]; 92 | }; 93 | 94 | export type LinkPaymentModel = { 95 | Success: boolean; 96 | Message: never; 97 | Model: HistoryPaymentModel[]; 98 | }; 99 | 100 | export interface SubscriptionResponse extends BaseResponse { 101 | Model: SubscriptionModel; 102 | } 103 | 104 | export interface SubscriptionsListGetResponse extends BaseResponse { 105 | Model: SubscriptionModel[]; 106 | } 107 | 108 | export interface PayoutModel { 109 | PublicId?: string; 110 | TransactionId: number; 111 | Amount: number; 112 | Currency: ValidCurrency; 113 | InvoiceId?: string; 114 | AccountId?: string; 115 | Email?: string; 116 | Description?: string; 117 | JsonData?: string; 118 | CreatedDate: string; 119 | CreatedDateIso: string; 120 | TestMode: boolean; 121 | IpAddress: string; 122 | IpCountry: string; 123 | IpCity: string; 124 | IpRegion: string; 125 | IpDistrict: string; 126 | IpLatitude: number; 127 | IpLongitude: number; 128 | CardFirstSix: string; 129 | CardLastFour: string; 130 | CardExpDate: string; 131 | CardType: string; 132 | CardTypeCode: number; 133 | Issuer: string; 134 | IssuerBankCountry: string; 135 | Status: TransactionStatus; 136 | StatusCode: number; 137 | Reason: string; 138 | ReasonCode: ErrorCodes; 139 | CardHolderMessage: string; 140 | Name: string; 141 | 142 | PaymentAmount?: number; 143 | PaymentCurrency?: ValidCurrency; 144 | PayoutDate?: string; 145 | PayoutDateIso?: string; 146 | PayoutAmount?: number; 147 | Rrn?: string; 148 | OriginalTransactionId?: number | null; 149 | CultureName?: "ru-RU" | "en-US" | "lv" | "az" | "kk" | "uk" | "pl"; 150 | Type?: number; 151 | Refunded?: boolean; 152 | SubscriptionId?: number; 153 | GatewayName?: string; 154 | } 155 | 156 | export interface PayoutSuccessModel extends PayoutModel { 157 | AuthDate: string; 158 | AuthDateIso: string; 159 | AuthCode: string; 160 | ConfirmDate: string; 161 | ConfirmDateIso: string; 162 | Token?: string; 163 | } 164 | 165 | export interface PayoutSuccessResponse extends BaseResponse { 166 | Success: boolean; 167 | Message: string | null; 168 | Model: PayoutSuccessModel; 169 | } 170 | 171 | export interface PayoutFailedResponse extends BaseResponse { 172 | Message: string; 173 | Success: boolean; 174 | Model: PayoutModel; 175 | } 176 | 177 | export type PayoutResponse = PayoutSuccessResponse | PayoutFailedResponse; 178 | -------------------------------------------------------------------------------- /src/NotificationHandlers.ts: -------------------------------------------------------------------------------- 1 | import {ClientAbstract} from "./Client"; 2 | import {IncomingHttpHeaders, IncomingMessage} from "http"; 3 | import * as qs from "qs"; 4 | import {checkSignedString} from "./utils"; 5 | import * as ApiTypes from "./Api/notification"; 6 | import {parse} from "url"; 7 | import {ok} from "assert"; 8 | import {ResponseCodes} from "./Api"; 9 | 10 | export type NotificationHandlerValidator = (request: TRequest) => Promise; 11 | 12 | export interface NotificationCustomPayload { 13 | payload: Record | string; 14 | headers?: { [key: string]: string }; 15 | signature?: string; 16 | } 17 | 18 | export type NotificationPayload = NotificationCustomPayload | IncomingMessage; 19 | 20 | export class NotificationHandlers extends ClientAbstract { 21 | public async handleCheckRequest( 22 | req: NotificationPayload, 23 | validator?: NotificationHandlerValidator, 24 | ) { 25 | return this.handle(req, validator); 26 | } 27 | 28 | public async handlePayRequest( 29 | req: NotificationPayload, 30 | validator?: NotificationHandlerValidator, 31 | ) { 32 | return this.handle(req, validator); 33 | } 34 | 35 | public async handleConfirmRequest( 36 | req: NotificationPayload, 37 | validator?: NotificationHandlerValidator, 38 | ) { 39 | return this.handle(req, validator); 40 | } 41 | 42 | public async handleFailRequest( 43 | req: NotificationPayload, 44 | validator?: NotificationHandlerValidator, 45 | ) { 46 | return this.handle(req, validator); 47 | } 48 | 49 | public async handleRefundRequest( 50 | req: NotificationPayload, 51 | validator?: NotificationHandlerValidator, 52 | ) { 53 | return this.handle(req, validator); 54 | } 55 | 56 | public async handleRecurrentRequest( 57 | req: NotificationPayload, 58 | validator?: NotificationHandlerValidator, 59 | ) { 60 | return this.handle(req, validator); 61 | } 62 | 63 | public async handleReceiptRequest( 64 | req: NotificationPayload, 65 | validator?: NotificationHandlerValidator>, 66 | ) { 67 | return this.handle(req, validator); 68 | } 69 | 70 | protected async handle( 71 | req: NotificationPayload, 72 | validator?: NotificationHandlerValidator, 73 | ) { 74 | try { 75 | const request: TRequest = 76 | "payload" in req ? await this.checkPayload(req) : await this.parseRequest(req); 77 | 78 | if (validator) { 79 | const code = await validator(request); 80 | return {request, response: {code}}; 81 | } 82 | 83 | return {request, response: {}}; 84 | } catch (error) { 85 | throw error; 86 | } 87 | } 88 | 89 | private async checkPayload(req: NotificationCustomPayload) { 90 | let signature = ""; 91 | if (req.headers && !req.signature) { 92 | ok("content-hmac" in req.headers, "Request headers should contain Content-HMAC field."); 93 | signature = req.headers["content-hmac"] as string; 94 | } 95 | 96 | if (req.signature) { 97 | signature = req.signature; 98 | } 99 | 100 | const payload = typeof req.payload === "string" ? req.payload : JSON.stringify(req.payload); 101 | ok(signature, "Custom payload should provide signature or header key."); 102 | ok(checkSignedString(this.options.privateKey, signature, payload), "Invalid signature"); 103 | 104 | return req.payload as T; 105 | } 106 | 107 | private async parseRequest>(req: IncomingMessage): Promise { 108 | ok("content-hmac" in req.headers, "Request headers should contain Content-HMAC field."); 109 | 110 | const signature: string = req.headers["content-hmac"] as string; 111 | const method = (req.method || "").toUpperCase(); 112 | 113 | ok(["GET", "POST"].includes(method), "Request method should be GET or POST"); 114 | 115 | if (method === "POST") { 116 | let chunksLength = 0; 117 | const chunks: Buffer[] = []; 118 | const body = await new Promise((resolve, reject) => { 119 | req.on("data", (chunk: Buffer) => { 120 | chunks.push(chunk); 121 | chunksLength += chunk.length; 122 | }); 123 | req.on("end", () => resolve(Buffer.concat(chunks, chunksLength).toString("utf-8"))); 124 | req.on("error", reject); 125 | }); 126 | 127 | const headers: { [key: string]: string } | IncomingHttpHeaders = req.headers || {}; 128 | 129 | ok(checkSignedString(this.options.privateKey, signature, body), "Invalid signature"); 130 | if (typeof headers["content-type"] === "string" && headers["content-type"].indexOf("json") !== -1) { 131 | return JSON.parse(body); 132 | } else { 133 | return qs.parse(body) as T; 134 | } 135 | } 136 | 137 | ok( 138 | checkSignedString(this.options.privateKey, signature, parse(req.url || "").query || ""), 139 | "Invalid signature", 140 | ); 141 | 142 | return parse(req.url || "", true).query as T; 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /src/Api/notification.ts: -------------------------------------------------------------------------------- 1 | import {ReceiptTypes, RecurrentStatusType, TransactionStatus, ValidCurrency} from "./constants"; 2 | 3 | export interface CustomDataNotification { 4 | Data?: string; 5 | } 6 | 7 | export interface AccountRequest { 8 | AccountId?: string; 9 | Email?: string; 10 | } 11 | 12 | export interface TransactionNotification extends AccountRequest, CustomDataNotification { 13 | TransactionId: number; 14 | Amount: number; 15 | Currency: ValidCurrency; 16 | DateTime: string; 17 | CardFirstSix: string; 18 | CardLastFour: string; 19 | CardType: string; 20 | CardExpDate: string; 21 | TestMode: 1 | 0; 22 | InvoiceId?: string; 23 | SubscriptionId?: string; 24 | Name?: string; 25 | IpAddress?: string; 26 | IpCountry?: string; 27 | IpCity?: string; 28 | IpRegion?: string; 29 | IpDistrict?: string; 30 | Issuer?: string; 31 | IssuerBankCountry?: string; 32 | Description?: string; 33 | } 34 | 35 | /** 36 | * Check 37 | * 38 | * Выполняется после того, как держатель заполнил платежную форму и нажал кнопку «Оплатить». 39 | * Служит для контроля прохождения платежа: система отправляет запрос на адрес сайта ТСП с 40 | * информацией об оплате, а сайт должен подтвердить или отклонить возможность принять платеж. 41 | */ 42 | export interface CheckNotification extends TransactionNotification { 43 | Status: TransactionStatus; 44 | } 45 | 46 | /** 47 | * Pay 48 | * Выполняется после того, как оплата была успешно проведена — получена авторизация эмитента. 49 | * Служит для информирования о проведенном платеже: система отправляет запрос 50 | * на адрес ТСП с информацией об оплате, а сайт должен зафиксировать факт платежа. 51 | */ 52 | export interface PayNotification extends TransactionNotification { 53 | Status: TransactionStatus.Authorized | TransactionStatus.Completed; 54 | Token?: string; 55 | } 56 | 57 | /** 58 | * Pay 59 | * Выполняется после того, как оплата была успешно проведена — получена авторизация эмитента. 60 | * Служит для информирования о проведенном платеже: система отправляет запрос 61 | * на адрес ТСП с информацией об оплате, а сайт должен зафиксировать факт платежа. 62 | */ 63 | export interface ConfirmNotification extends TransactionNotification { 64 | Status: TransactionStatus.Completed; 65 | Token?: string; 66 | } 67 | 68 | /** 69 | * Fail 70 | * 71 | * Выполняется в случае, если оплата была отклонена и используется для анализа 72 | * количества и причин отказов. 73 | * 74 | * Стоит учитывать, что факт отказа в оплате не является конечным — пользователь 75 | * может оплатить со второго раза. 76 | */ 77 | export interface FailNotification extends TransactionNotification { 78 | Reason: string; 79 | ReasonCode: number; 80 | } 81 | 82 | /** 83 | * Refund 84 | * 85 | * Выполняется в случае, если платеж был возвращен (полностью или частично) 86 | * по вашей инициативе через API или личный кабинет. 87 | */ 88 | export interface RefundNotification extends AccountRequest, CustomDataNotification { 89 | TransactionId: number; 90 | PaymentTransactionId: number; 91 | Amount: number; 92 | DateTime: string; 93 | InvoiceId?: string; 94 | } 95 | 96 | /** 97 | * Recurrent 98 | * 99 | * Выполняется в случае, если статус подписки на рекуррентный платеж был изменен. 100 | */ 101 | export interface RecurrentNotification extends AccountRequest { 102 | Id: number; 103 | Description: string; 104 | Amount: number; 105 | Currency: ValidCurrency; 106 | RequireConfirmation: boolean; 107 | StartDate: string; 108 | Interval: string; 109 | Period: number; 110 | Status: RecurrentStatusType; 111 | SuccessfulTransactionsNumber: number; 112 | FailedTransactionsNumber: number; 113 | MaxPeriods?: number; 114 | LastTransactionDate?: string; 115 | NextTransactionDate?: string; 116 | } 117 | 118 | /** 119 | * Subscription 120 | * 121 | * Выполняется в случае, если статус подписки на рекуррентный платеж был изменен. 122 | */ 123 | export interface SubscriptionBase extends AccountRequest { 124 | Description: string; 125 | Amount: number; 126 | Currency: ValidCurrency; 127 | RequireConfirmation: boolean; 128 | StartDate: string; 129 | Interval: string; 130 | Period: number; 131 | MaxPeriods?: number; 132 | } 133 | 134 | export interface SubscriptionCreateRequest extends SubscriptionBase { 135 | Token: string; 136 | } 137 | 138 | export interface SubscriptionUpdateRequest extends Partial { 139 | Id: string; 140 | } 141 | 142 | export interface SubscriptionModel extends SubscriptionBase { 143 | Id: string; 144 | CurrencyCode: number; 145 | StartDateIso: string; 146 | IntervalCode: number; 147 | StatusCode: number; 148 | Status: RecurrentStatusType; 149 | SuccessfulTransactionsNumber: number; 150 | FailedTransactionsNumber: number; 151 | LastTransactionDate?: string; 152 | LastTransactionDateIso?: string; 153 | NextTransactionDate?: string; 154 | NextTransactionDateIso?: string; 155 | } 156 | 157 | /** 158 | * Receipt 159 | * 160 | * Выполняется после выдачи кассового чека. 161 | * Служит для информирования о сформированных онлайн-чеках: система отправляет 162 | * запрос на адрес ТСП с информацией о чеке, а сайт должен зафиксировать информацию. 163 | */ 164 | export interface ReceiptNotification { 165 | DocumentNumber: number; 166 | SessionNumber: number; 167 | FiscalSign: string; 168 | DeviceNumber: number; 169 | RegNumber: string; 170 | Inn: number; 171 | Type: ReceiptTypes; 172 | Ofd: string; 173 | Url: string; 174 | QrCodeUrl: string; 175 | Amount: number; 176 | DateTime: string; 177 | Receipt: TReceipt; 178 | TransactionId?: number; 179 | InvoiceId?: string; 180 | AccountId?: string; 181 | } 182 | -------------------------------------------------------------------------------- /src/ClientApi.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ClientRequestAbstract, 3 | ClientResponse, 4 | PaymentClientResponse, 5 | PaymentHistoryClientResponse, 6 | PaymentWith3DSClientResponse, 7 | PayoutClientResponse, 8 | } from "./Client"; 9 | import { 10 | BaseRequest, 11 | BaseResponse, 12 | Confirm3DSRequest, 13 | ConfirmPaymentRequest, 14 | CryptogramPaymentRequest, 15 | CryptogramPayoutRequest, 16 | LinkPaymentRequest, 17 | PaymentGetResponse, 18 | PaymentHistoryResponse, 19 | PaymentResponse, 20 | PaymentWith3DSResponse, 21 | PayoutResponse, 22 | RefundPaymentRequest, 23 | SubscriptionCreateRequest, 24 | SubscriptionResponse, 25 | SubscriptionsListGetResponse, 26 | SubscriptionUpdateRequest, 27 | TokenPaymentRequest, 28 | TokenPayoutRequest, 29 | VoidPaymentRequest, 30 | } from "./Api"; 31 | 32 | export class ClientApi extends ClientRequestAbstract { 33 | /** 34 | * Charge cryptogram payment 35 | * 36 | * @param {CryptogramPaymentRequest} data 37 | * @returns {Promise>} 38 | */ 39 | public async chargeCryptogramPayment(data: CryptogramPaymentRequest) { 40 | return new PaymentWith3DSClientResponse( 41 | await this.call("/payments/cards/charge", data), 42 | ); 43 | } 44 | 45 | /** 46 | * Authorize cryptogram payment 47 | * 48 | * @param {CryptogramPaymentRequest} data 49 | * @returns {Promise>} 50 | */ 51 | public async authorizeCryptogramPayment(data: CryptogramPaymentRequest) { 52 | return new PaymentWith3DSClientResponse( 53 | await this.call("/payments/cards/auth", data), 54 | ); 55 | } 56 | 57 | /** 58 | * Charge token payment 59 | * 60 | * @param {TokenPaymentRequest} data 61 | * @returns {Promise>} 62 | */ 63 | public async chargeTokenPayment(data: TokenPaymentRequest) { 64 | return new PaymentClientResponse( 65 | await this.call("/payments/tokens/charge", data), 66 | ); 67 | } 68 | 69 | /** 70 | * Authorize token payment 71 | * 72 | * @param {TokenPaymentRequest} data 73 | * @returns Promise> 74 | */ 75 | public async authorizeTokenPayment(data: TokenPaymentRequest) { 76 | return new PaymentClientResponse( 77 | await this.call("/payments/tokens/auth", data), 78 | ); 79 | } 80 | 81 | /** 82 | * Confirm a 3DS payment 83 | * 84 | * @param {Confirm3DSRequest} data 85 | * @returns Promise> 86 | */ 87 | public async confirm3DSPayment(data: Confirm3DSRequest) { 88 | return new PaymentClientResponse( 89 | await this.call("/payments/cards/post3ds", data), 90 | ); 91 | } 92 | 93 | /** 94 | * Confirm an authorized payment 95 | * 96 | * @param {ConfirmPaymentRequest} data 97 | * @returns {Promise>} 98 | */ 99 | public async confirmPayment(data: ConfirmPaymentRequest) { 100 | return new ClientResponse(await this.call("/payments/confirm", data)); 101 | } 102 | 103 | /** 104 | * Refund a payment 105 | * 106 | * @param {RefundPaymentRequest} data 107 | * @returns {Promise>} 108 | */ 109 | public async refundPayment(data: RefundPaymentRequest) { 110 | return new ClientResponse(await this.call("/payments/refund", data)); 111 | } 112 | 113 | /** 114 | * Void a payment 115 | * 116 | * @param {VoidPaymentRequest} data 117 | * @returns {Promise>} 118 | */ 119 | public async voidPayment(data: VoidPaymentRequest) { 120 | return new ClientResponse(await this.call("/payments/void", data)); 121 | } 122 | 123 | /** 124 | * Get a payment history 125 | * 126 | * @param {{TransactionId: number}} data 127 | * @returns {Promise>} 128 | */ 129 | public async getPayment(data: BaseRequest & { TransactionId: number }) { 130 | return new ClientResponse(await this.call("/payments/get", data)); 131 | } 132 | 133 | /** 134 | * Find a payment by invoice id 135 | * 136 | * @param {{InvoiceId: string}} data 137 | * @returns Promise> 138 | */ 139 | public async findPaymentByInvoiceId(data: BaseRequest & { InvoiceId: string }) { 140 | return new PaymentClientResponse( 141 | await this.call("/payments/find", data), 142 | ); 143 | } 144 | 145 | /** 146 | * @deprecated see getPaymentsList 147 | * 148 | * @param {{Date: string | Date, TimeZone: string}} data 149 | * @returns {Promise>} 150 | */ 151 | public async getPaymentList(data: BaseRequest & { Date: string | Date; TimeZone?: string }) { 152 | return new PaymentHistoryClientResponse(await this.call("/payments/list", data)); 153 | } 154 | 155 | /** 156 | * Get a filtered payment list 157 | * 158 | * @param {{Date: string | Date, TimeZone: string}} data 159 | * @returns {Promise>} 160 | */ 161 | public async getPaymentsList(data: BaseRequest & { Date: string | Date; TimeZone?: string }) { 162 | return new PaymentHistoryClientResponse(await this.call("/payments/list", data)); 163 | } 164 | 165 | /** 166 | * Get a filtered payment list 167 | * 168 | * @param {LinkPaymentRequest} data 169 | * @returns {Promise>} 170 | */ 171 | public async createOrder(data: LinkPaymentRequest) { 172 | return new ClientResponse(await this.call("/orders/create", data)); 173 | } 174 | 175 | /** 176 | * Create Subscription 177 | * @param data 178 | */ 179 | public async createSubscription(data: BaseRequest & SubscriptionCreateRequest) { 180 | return new ClientResponse(await this.call("/subscriptions/create", data)); 181 | } 182 | 183 | /** 184 | * Update Subscription 185 | * @param data 186 | */ 187 | public async updateSubscription(data: BaseRequest & SubscriptionUpdateRequest) { 188 | return new ClientResponse(await this.call("/subscriptions/update", data)); 189 | } 190 | 191 | /** 192 | * Cancel Subscription 193 | * @param data 194 | */ 195 | public async cancelSubscription(data: BaseRequest & SubscriptionUpdateRequest) { 196 | return new ClientResponse(await this.call("/subscriptions/cancel", data)); 197 | } 198 | 199 | /** 200 | * Get Subscription 201 | * @param data 202 | */ 203 | public async getSubscription(data: BaseRequest & { Id: string }) { 204 | return new ClientResponse(await this.call("/subscriptions/get", data)); 205 | } 206 | 207 | /** 208 | * Get Subscriptions List 209 | * @param data 210 | */ 211 | public async getSubscriptionsList(data: BaseRequest & { accountId: string }) { 212 | return new ClientResponse(await this.call("/subscriptions/find", data)); 213 | } 214 | 215 | /** 216 | * Charge Cryptogram Payout 217 | * 218 | * @param {CryptogramPayoutRequest} data 219 | * @returns Promise> 220 | */ 221 | public async chargeCryptogramPayout(data: CryptogramPayoutRequest) { 222 | return new PayoutClientResponse(await this.call("/payments/cards/topup", data)); 223 | } 224 | 225 | /** 226 | * Charge token payout 227 | * 228 | * @param {TokenPayoutRequest} data 229 | * @returns Promise> 230 | */ 231 | public async chargeTokenPayout(data: TokenPayoutRequest) { 232 | return new PayoutClientResponse( 233 | await this.call("/payments/token/topup", data), 234 | ); 235 | } 236 | } 237 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CloudPayments 2 | 3 | Библиотека для работы с API и обработки уведомлений от платежного сервиса 4 | [CloudPayments](https://developers.cloudpayments.ru/#api). 5 | 6 | Проект написан на языке TypeScript и включает описание всех доступных интерфейсов. 7 | Все примеры приведены по стандарту es7. Версия поддерживаемой платформы Node.js 6 и выше. 8 | 9 | ## Install 10 | 11 | Для установки пакета используйте стандартный механизм NPM: 12 | 13 | `npm i -S cloudpayments` 14 | 15 | ## Usage 16 | 17 | Подключение библиотеки 18 | 19 | ```typescript 20 | import {ClientService} from 'cloudpayments'; 21 | 22 | const client = new ClientService({/* options */}); 23 | 24 | // бизнес-логика приложения ... 25 | ``` 26 | 27 | ### ClientService 28 | 29 | Общий [интерфейс](src/ClientService.ts) для доступа к API библиотеки, 30 | принимает единственный [аргумент](src/Client/ClientOptions.ts) `ClientOptions`. 31 | 32 | #### Methods 33 | 34 | | Method | Options | Return | Description | 35 | |---|---|---|---| 36 | | `getClientApi` | | `ClientApi` | Возвращает экземпляр класса `ClientApi` для работы со стандартным [API](https://developers.cloudpayments.ru/#api) | 37 | | `getReceiptApi` | | `ReceiptApi` | Возвращает экземпляр класса `ReceiptApi` для работы с [API кассы](https://developers.cloudkassir.ru/#api-kassy) | 38 | | `getNotificationHandlers` | | `NotificationHandlers` | Возвращает экземпляр класса `ClientHandlers` для обработки [уведомлений](https://developers.cloudpayments.ru/#uvedomleniya) | 39 | | `createClientApi` | `ClientOptions` | `ClientApi` | Создает отдельный экземпляр класса `ClientApi` | 40 | | `createReceiptApi` | `ClientOptions` | `ReceiptApi` | Создает отдельный экземпляр класса `ReceiptApi` | 41 | | `createNotificationHandlers` | `ClientOptions` | `NotificationHandlers` | Создает отдельный экземпляр класса `NotificationHandlers` | 42 | 43 | ### ClientOptions 44 | 45 | Параметры подключения к платежному сервису. 46 | 47 | | Option | Type | Description | 48 | |---|---|---| 49 | | `endpoint` | `string` | Адрес сервера API, по-умолчанию `https://api.cloudpayments.ru` | 50 | | `privateKey` | `string` | Ваш приватный ключ | 51 | | `publicId` | `string` | Ваш публичный ключ | 52 | | `org.taxationSystem` | `TaxationSystem` | Система налогооблажения | 53 | | `org.inn` | `number` | ИНН | 54 | 55 | ## ClientApi 56 | 57 | Доступные методы клиентского API: 58 | 59 | | Метод | Описание | Документация | 60 | |-------|----------|--------------| 61 | | chargeCryptogramPayment | Оплата по криптограмме | https://developers.cloudpayments.ru/#oplata-po-kriptogramme | 62 | | authorizeCryptogramPayment | Оплата по криптограмме (преавторизация) | https://developers.cloudpayments.ru/#oplata-po-kriptogramme | 63 | | chargeTokenPayment | Оплата по токену | https://developers.cloudpayments.ru/#oplata-po-tokenu-rekarring | 64 | | authorizeTokenPayment | Оплата по токену (преавторизация) | https://developers.cloudpayments.ru/#oplata-po-tokenu-rekarring | 65 | | confirm3DSPayment | Обработка 3-D Secure | https://developers.cloudpayments.ru/#obrabotka-3-d-secure | 66 | | confirmPayment | Подтверждение оплаты | https://developers.cloudpayments.ru/#podtverzhdenie-oplaty | 67 | | refundPayment | Возврат денег | https://developers.cloudpayments.ru/#vozvrat-deneg | 68 | | voidPayment | Отмена оплаты | https://developers.cloudpayments.ru/#otmena-oplaty | 69 | | getPayment | Просмотр информации об операции | https://developers.cloudpayments.ru/#prosmotr-tranzaktsii | 70 | | findPaymentByInvoiceId | Проверка статуса платежа | https://developers.cloudpayments.ru/#proverka-statusa-platezha | 71 | | getPaymentsList | Выгрузка списка транзакций | https://developers.cloudpayments.ru/#vygruzka-spiska-tranzaktsiy | 72 | | createOrder | Создание счета для отправки по почте | https://developers.cloudpayments.ru/#sozdanie-scheta-dlya-otpravki-po-pochte | 73 | | createSubscription | Создание подписки на рекуррентные платежи | https://developers.cloudpayments.ru/#sozdanie-podpiski-na-rekurrentnye-platezhi | 74 | | updateSubscription | Изменение подписки на рекуррентные платежи | https://developers.cloudpayments.ru/#izmenenie-podpiski-na-rekurrentnye-platezhi | 75 | | cancelSubscription | Отмена подписки на рекуррентные платежи | https://developers.cloudpayments.ru/#izmenenie-podpiski-na-rekurrentnye-platezhi | 76 | | getSubscription | Запрос информации о подписке | https://developers.cloudpayments.ru/#zapros-informatsii-o-podpiske | 77 | | getSubscriptionsList | Поиск подписок | https://developers.cloudpayments.ru/#poisk-podpisok | 78 | | chargeCryptogramPayout | Выплата по криптограмме | https://developers.cloudpayments.ru/#vyplata-po-kriptogramme | 79 | | chargeTokenPayout | Выплата по токену | https://developers.cloudpayments.ru/#vyplata-po-tokenu | 80 | 81 | 82 | ## ReceiptApi 83 | 84 | Интерфейс `ReceiptApi` предназначен для работы с API касс. 85 | 86 | Пример использования: 87 | 88 | ```typescript 89 | import {createServer} from 'http'; 90 | import {ClientService, TaxationSystem, VAT, ResponseCodes, ReceiptTypes} from 'cloudpayments'; 91 | 92 | const client = new ClientService({ 93 | privateKey: 'private key', 94 | publicId: 'public id', 95 | org: { 96 | taxationSystem: TaxationSystem.GENERAL, 97 | inn: 123456789 98 | } 99 | }); 100 | 101 | const handlers = client.getNotificationHandlers(); 102 | const receiptApi = client.getReceiptApi(); 103 | const server = createServer(async (req, res) => { 104 | const response = await handlers.handlePayRequest(req, async (request) => { 105 | // Проверям запрос, например на совпадение цены заказа 106 | if (request.Amount > 0) { 107 | return ResponseCodes.INVALID_AMOUNT; 108 | } 109 | 110 | // Отправляем запрос на создание чека 111 | const response = await receiptApi.createReceipt( 112 | { 113 | Type: ReceiptTypes.Income, 114 | invoiceId: request.InvoiceId, 115 | accountId: request.AccountId, 116 | }, 117 | { 118 | // если система налогооблажения не указана, 119 | // берется из настроек ClientOptions 120 | taxationSystem: TaxationSystem.GENERAL, 121 | inn: 123456789, 122 | email: 'mail@example.com', 123 | phone: '+7123456789', 124 | Items: [ 125 | { 126 | label: 'Наименование товара или сервиса', 127 | quantity: 2, 128 | price: 1200, 129 | amount: 2400, 130 | vat: VAT.VAT18, 131 | ean13: '1234456363', 132 | } 133 | ] 134 | } 135 | ); 136 | 137 | // Проверяем, что запрос встал в очередь, 138 | // иначе обрабатываем исключение 139 | 140 | // Если все прошло успешно, возвращаем 0 141 | return ResponseCodes.SUCCESS; 142 | }); 143 | res.setHeader('Content-Type', 'application/json'); 144 | res.end(JSON.stringify(response)); 145 | }); 146 | ``` 147 | 148 | #### Methods 149 | 150 | | Method | Arguments | Return | Description | 151 | |---|---|---|---| 152 | | `createReceipt` | `ReceiptTypes`, `Receipt` | `Response<{}>` | Отправляет запрос на [создание чека](https://developers.cloudkassir.ru/#formirovanie-kassovogo-cheka) | 153 | 154 | #### Receipt 155 | 156 | Смотрите [Receipt](src/ReceiptApi.ts) 157 | 158 | ## Handlers 159 | 160 | В библиотеку `cloudpayments` встроен механизм обработки 161 | уведомлений о платежах (смотрите [документацию](https://developers.cloudpayments.ru/#uvedomleniya)). 162 | 163 | Список доступных методов для обработки уведомлений: 164 | 165 | | Метод | Параметры запроса | Ссылка на описание | 166 | |---|---|---| 167 | | `handleCheckRequest` | [CheckNotification](src/Api/notification.ts) | https://developers.cloudpayments.ru/#check | 168 | | `handlePayRequest` | [PayNotification](src/Api/notification.ts) | https://developers.cloudpayments.ru/#pay | 169 | | `handleFailRequest` | [FailNotification](src/Api/notification.ts) | https://developers.cloudpayments.ru/#fail | 170 | | `handleRecurrentRequest` | [RecurrentNotification](src/Api/notification.ts) | https://developers.cloudpayments.ru/#recurrent | 171 | | `handleRefundRequest` | [RefundNotification](src/Api/notification.ts) | https://developers.cloudpayments.ru/#refund | 172 | | `handleReceiptRequest` | [ReceiptNotification](src/Api/notification.ts) | https://developers.cloudpayments.ru/#receipt | 173 | | `handleConfirmRequest` | [ConfirmNotification](src/Api/notification.ts) | https://developers.cloudpayments.ru/#confirm | 174 | 175 | Пример использования: 176 | 177 | ```typescript 178 | import {createServer} from 'http'; 179 | import {ClientService, TaxationSystem, ResponseCodes} from 'cloudpayments'; 180 | 181 | const client = new ClientService({ 182 | privateKey: 'private key', 183 | publicId: 'public id', 184 | org: { 185 | taxationSystem: TaxationSystem.GENERAL, 186 | inn: 123456789 187 | } 188 | }); 189 | 190 | const handlers = client.getNotificationHandlers(); 191 | const server = createServer(async (req, res) => { 192 | if (req.url == '/cloudpayments/fail') { 193 | const response = await handlers.handleFailRequest(req, async (request) => { 194 | // Делаем что-то с инфомацией о неудачном платеже 195 | return ResponseCodes.SUCCESS; 196 | }); 197 | 198 | res.setHeader('Content-Type', 'application/json'); 199 | res.end(JSON.stringify(response)); 200 | } 201 | }); 202 | 203 | ``` 204 | 205 | ## Response 206 | 207 | Базовый интерфейс для всех типов ответов. 208 | 209 | | Field | Type | Description | 210 | |---|---|---| 211 | | `Success` | `boolean` | Успех операции | 212 | | `Message` | `string` | Сообщение | 213 | 214 | # License 215 | MIT 216 | -------------------------------------------------------------------------------- /src/Api/constants.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Коды ошибок 3 | * Ниже предоставлены коды ошибок, которые определяют причину отказа в проведении платежа. 4 | * Код возвращается в Fail уведомлении и при оплате через API. 5 | * Сообщение для плательщика виджет показывает самостоятельно, а в API за него отвечает 6 | * параметр CardHolderMessage. 7 | */ 8 | export enum ErrorCodes { 9 | /** 10 | * Отказ эмитента проводить онлайн операцию 11 | */ 12 | ReferToCardIssuer = 5001, 13 | 14 | /** 15 | * Отказ эмитента без объяснения причин 16 | * Данный код возвращается по следующим причинам: 17 | * — неверно указан код CVV на картах MasterCard; 18 | * — внутренние ограничения банка, выпустившего карту; 19 | * — карта заблокирована или еще не активирована; 20 | * — на карте не включены интернет-платежи или не подключен 3DS. 21 | */ 22 | DoNotHonor = 5005, 23 | 24 | /** 25 | * Отказ сети проводить операцию или неправильный CVV код 26 | */ 27 | Error = 5006, 28 | 29 | /** 30 | * Карта не предназначена для онлайн платежей 31 | */ 32 | InvalidTransaction = 5012, 33 | 34 | /** 35 | * Слишком маленькая или слишком большая сумма операции 36 | */ 37 | AmountError = 5013, 38 | 39 | /** 40 | * Ошибка на стороне эквайера — неверно сформирована транзакция 41 | */ 42 | FormatError = 5030, 43 | 44 | /** 45 | * Неизвестный эмитент карты 46 | */ 47 | BankNotSupportedBySwitch = 5031, 48 | 49 | /** 50 | * Отказ эмитента — подозрение на мошенничество 51 | */ 52 | SuspectedFraud = 5034, 53 | 54 | /** 55 | * Карта потеряна 56 | */ 57 | LostCard = 5041, 58 | 59 | /** 60 | * Карта украдена 61 | */ 62 | StolenCard = 5043, 63 | 64 | /** 65 | * Недостаточно средств 66 | */ 67 | InsufficientFunds = 5051, 68 | 69 | /** 70 | * Карта просрочена или неверно указан срок действия 71 | */ 72 | ExpiredCard = 5054, 73 | 74 | /** 75 | * Ограничение на карте 76 | * 77 | * Данный код возвращается по следующим причинам: 78 | * — внутренние ограничения банка, выпустившего карту; 79 | * — карта заблокирована или еще не активирована; 80 | * — на карте не включены интернет-платежи или не подключен 3DS. 81 | */ 82 | TransactionNotPermitted = 5057, 83 | 84 | /** 85 | * Превышен лимит операций по карте 86 | */ 87 | ExceedWithdrawalFrequency = 5065, 88 | 89 | /** 90 | * Неверный CVV код 91 | */ 92 | IncorrectCVV = 5082, 93 | 94 | /** 95 | * Эмитент недоступен 96 | */ 97 | Timeout = 5091, 98 | 99 | /** 100 | * Эмитент недоступен 101 | */ 102 | CannotReachNetwork = 5092, 103 | 104 | /** 105 | * Ошибка банка-эквайера или сети 106 | */ 107 | SystemError = 5096, 108 | 109 | /** 110 | * Операция не может быть обработана по прочим причинам 111 | */ 112 | UnableToProcess = 5204, 113 | 114 | /** 115 | * 3-D Secure авторизация не пройдена 116 | */ 117 | AuthenticationFailed = 5206, 118 | 119 | /** 120 | * 3-D Secure авторизация недоступна 121 | */ 122 | AuthenticationUnavailable = 5207, 123 | 124 | /** 125 | * Лимиты эквайера на проведение операций 126 | */ 127 | AntiFraud = 5300 128 | } 129 | 130 | export const ErrorCodesTranscript = { 131 | [ErrorCodes.ReferToCardIssuer]: "Свяжитесь с вашим банком или воспользуйтесь другой картой", 132 | [ErrorCodes.DoNotHonor]: "Свяжитесь с вашим банком или воспользуйтесь другой картой", 133 | [ErrorCodes.Error]: "Проверьте правильность введенных данных карты или воспользуйтесь другой картой", 134 | [ErrorCodes.InvalidTransaction]: "Воспользуйтесь другой картой или свяжитесь с банком, выпустившим карту", 135 | [ErrorCodes.AmountError]: "Проверьте корректность суммы", 136 | [ErrorCodes.FormatError]: "Повторите попытку позже", 137 | [ErrorCodes.BankNotSupportedBySwitch]: "Воспользуйтесь другой картой", 138 | [ErrorCodes.SuspectedFraud]: "Свяжитесь с вашим банком или воспользуйтесь другой картой", 139 | [ErrorCodes.LostCard]: "Свяжитесь с вашим банком или воспользуйтесь другой картой", 140 | [ErrorCodes.StolenCard]: "Свяжитесь с вашим банком или воспользуйтесь другой картой", 141 | [ErrorCodes.InsufficientFunds]: "Недостаточно средств на карте", 142 | [ErrorCodes.ExpiredCard]: "Проверьте правильность введенных данных карты или воспользуйтесь другой картой", 143 | [ErrorCodes.TransactionNotPermitted]: "Свяжитесь с вашим банком или воспользуйтесь другой картой", 144 | [ErrorCodes.ExceedWithdrawalFrequency]: "Свяжитесь с вашим банком или воспользуйтесь другой картой", 145 | [ErrorCodes.IncorrectCVV]: "Неверно указан код CVV", 146 | [ErrorCodes.Timeout]: "Повторите попытку позже или воспользуйтесь другой картой", 147 | [ErrorCodes.CannotReachNetwork]: "Повторите попытку позже или воспользуйтесь другой картой", 148 | [ErrorCodes.SystemError]: "Повторите попытку позже", 149 | [ErrorCodes.UnableToProcess]: "Свяжитесь с вашим банком или воспользуйтесь другой картой", 150 | [ErrorCodes.AuthenticationFailed]: "Свяжитесь с вашим банком или воспользуйтесь другой картой", 151 | [ErrorCodes.AuthenticationUnavailable]: "Свяжитесь с вашим банком или воспользуйтесь другой картой", 152 | [ErrorCodes.AntiFraud]: "Воспользуйтесь другой картой", 153 | }; 154 | 155 | export type TransactionStatusType = "AwaitingAuthentication" | "Authorized" | "Completed" | "Cancelled" | "Declined"; 156 | 157 | /** 158 | * Статусы операций 159 | * В таблице ниже представлены статусы транзакций, условия применения и возможные действия. 160 | */ 161 | export enum TransactionStatus { 162 | /** 163 | * Ожидает аутентификации 164 | * 165 | * После перехода плательщика на сайт эмитента в ожидании результатов 3-D Secure 166 | * 167 | */ 168 | AwaitingAuthentication = "AwaitingAuthentication", 169 | 170 | /** 171 | * Авторизована 172 | * 173 | * После получения авторизации 174 | * 175 | * Подтверждение, Отмена 176 | */ 177 | Authorized = "Authorized", 178 | 179 | /** 180 | * Завершена 181 | * 182 | * После подтверждения операции 183 | * 184 | * Возврат денег 185 | */ 186 | Completed = "Completed", 187 | 188 | /** 189 | * Отменена 190 | * 191 | * В случае отмены операции 192 | */ 193 | Cancelled = "Cancelled", 194 | 195 | /** 196 | * Отклонена 197 | * 198 | * В случае невозможности провести операцию (нет денег на счете карты и т.п.) 199 | */ 200 | Declined = "Declined" 201 | } 202 | 203 | export type RecurrentStatusType = "Active" | "PastDue" | "Cancelled" | "Rejected" | "Expired"; 204 | 205 | /** 206 | * Статусы подписок (рекуррент) 207 | * В таблице ниже представлены статусы подписок, условия применения и возможные действия. 208 | */ 209 | export enum RecurrentStatus { 210 | /** 211 | * Подписка активна 212 | * 213 | * После создания и очередной успешной оплаты 214 | * 215 | * Отмена 216 | */ 217 | Active = "Active", 218 | 219 | /** 220 | * Просрочена 221 | * 222 | * После одной или двух подряд неуспешных попыток оплаты 223 | * 224 | * Отмена 225 | */ 226 | PastDue = "PastDue", 227 | 228 | /** 229 | * Отменена 230 | * 231 | * В случае отмены по запросу 232 | */ 233 | Cancelled = "Cancelled", 234 | 235 | /** 236 | * Отклонена 237 | * 238 | * В случае трех неудачных попыток оплаты, идущих подряд 239 | */ 240 | Rejected = "Rejected", 241 | 242 | /** 243 | * Завершена 244 | * 245 | * В случае завершения максимального количества периодов (если были указаны) 246 | */ 247 | Expired = "Expired", 248 | } 249 | 250 | /** 251 | * Типы онлайн-чеков 252 | * 253 | * В таблице ниже представлены типы чеков и соответствующие им признаки расчета, которые используются для выдачи 254 | * кассовых чеков. 255 | */ 256 | export enum ReceiptTypes { 257 | /** 258 | * Приход 259 | * 260 | * Выдается при получении средств от покупателя (клиента) 261 | */ 262 | Income = "Income", 263 | 264 | /** 265 | * Возврат прихода 266 | * 267 | * Выдается при возврате покупателю (клиенту) средств, полученных от него 268 | */ 269 | IncomeReturn = "IncomeReturn", 270 | 271 | /** 272 | * Расход 273 | * 274 | * Выдается при выдаче средств покупателю (клиенту) 275 | */ 276 | Expense = "Expense", 277 | 278 | /** 279 | * Вовзрат расхода 280 | * 281 | * Выдается при получениеи средств от покупателя (клиента), выданных ему 282 | */ 283 | ExpenseReturn = "ExpenseReturn" 284 | } 285 | 286 | /** 287 | * Системы налогообложения 288 | * 289 | * В таблице ниже представлены варианты систем налогообложения юридических лиц и индивидуальных предпринимателей, 290 | * которые используются при формировании кассовых чеков. 291 | */ 292 | export enum TaxationSystem { 293 | GENERAL = 0, // Общая система налогообложения 294 | SIMPLIFIED_INCOME = 1, // Упрощенная система налогообложения (Доход) 295 | SIMPLIFIED_INCOME_CONSUMPTION = 2, // Упрощенная система налогообложения (Доход минус Расход) 296 | UNIFIED_IMPUTED_INCOME = 3, // Единый налог на вмененный доход 297 | UNIFIED_AGRICULTURAL = 4, // Единый сельскохозяйственный налог 298 | PATENT = 5 // Патентная система налогообложения 299 | } 300 | 301 | export type TaxationSystemType = TaxationSystem; 302 | 303 | export function validateTaxationSystem(value: any) { 304 | switch (value) { 305 | case TaxationSystem.GENERAL: 306 | case TaxationSystem.SIMPLIFIED_INCOME: 307 | case TaxationSystem.SIMPLIFIED_INCOME_CONSUMPTION: 308 | case TaxationSystem.UNIFIED_AGRICULTURAL: 309 | case TaxationSystem.UNIFIED_IMPUTED_INCOME: 310 | case TaxationSystem.PATENT: 311 | return true; 312 | default: 313 | return false; 314 | } 315 | } 316 | 317 | /** 318 | * Значения ставки НДС 319 | * 320 | * При указании ставки НДС будьте внимательны: "НДС 0%" и "НДС не облагается" — это не равнозначные варианты. 321 | */ 322 | export enum VAT { 323 | VAT0 = 0, 324 | VAT10 = 10, 325 | VAT18 = 18, 326 | VAT110 = 110, 327 | VAT118 = 118, 328 | VAT20 = 20, 329 | VAT120 = 120 330 | } 331 | 332 | export type VATType = null | VAT; 333 | 334 | /** 335 | * Validate VAT value 336 | * 337 | * @param value 338 | * @returns {boolean} 339 | */ 340 | export function validateVAT(value: any) { 341 | switch (value) { 342 | case VAT.VAT0: 343 | case VAT.VAT10: 344 | case VAT.VAT18: 345 | case VAT.VAT110: 346 | case VAT.VAT118: 347 | case VAT.VAT20: 348 | case VAT.VAT120: 349 | case null: 350 | return true; 351 | default: 352 | return false; 353 | } 354 | } 355 | 356 | /** 357 | * Response codes 358 | */ 359 | export enum ResponseCodes { 360 | SUCCESS = 0, 361 | UNKNOWN_INVOICE_ID = 10, 362 | INVALID_ACCOUNT_ID = 11, 363 | INVALID_AMOUNT = 12, 364 | REJECTED = 13, 365 | EXPIRED = 20 366 | } 367 | 368 | /** 369 | * Currencies 370 | */ 371 | export type ValidCurrency = 372 | | "RUB" 373 | | "EUR" 374 | | "USD" 375 | | "GBP" 376 | | "UAH" 377 | | "BYR" 378 | | "BYN" 379 | | "AZN" 380 | | "CHF" 381 | | "CZK" 382 | | "CAD" 383 | | "PLN" 384 | | "SEK" 385 | | "TRY" 386 | | "CNY" 387 | | "INR"; 388 | 389 | export const CurrencyList = Object.freeze({ 390 | RUB: "RUB", 391 | EUR: "EUR", 392 | USD: "USD", 393 | GBP: "GBP", 394 | UAH: "UAH", 395 | BYR: "BYR", 396 | BYN: "BYN", 397 | AZN: "AZN", 398 | CHF: "CHF", 399 | CZK: "CZK", 400 | CAD: "CAD", 401 | PLN: "PLN", 402 | SEK: "SEK", 403 | TRY: "TRY", 404 | CNY: "CNY", 405 | INR: "INR", 406 | }); 407 | 408 | export const validateCurrency = (value: string): boolean => Reflect.has(CurrencyList, value); 409 | --------------------------------------------------------------------------------