diff --git a/.gitpod.yml b/.gitpod.yml index e945a79e..c358afc4 100644 --- a/.gitpod.yml +++ b/.gitpod.yml @@ -26,4 +26,9 @@ vscode: tasks: - init: npm run gitpod-init; docker pull deepf/deeplinks:main; docker run -v $(pwd)/packages/deeplinks:/deeplinks --rm --name links --entrypoint "sh" deepf/deeplinks:main -c "cp -r /snapshots/* /deeplinks/snapshots/"; (cd packages/deeplinks && npm run snapshot:last); npm install -g concurrently; - - command: (cd packages/deeplinks; if git merge-base --is-ancestor origin/main main; then echo ''; else ( echo 'NEED TO PULL DEEPLINKS!'; git checkout main; git pull; cd ../../; npm run apply-deeplinks); fi) && (cd packages/deepcase; if (git merge-base --is-ancestor origin/main main); then echo ''; else ( echo 'NEED TO PULL DEEPCASE!'; git checkout main; git pull; npm ci); fi) && npm run gitpod-start; + + - command: (cd packages/deeplinks; if git merge-base --is-ancestor origin/main main; then echo ''; else ( echo 'NEED TO PULL DEEPLINKS!'; git checkout main; git pull; cd ../../; npm run apply-deeplinks); fi) && (cd packages/deepcase; if (git merge-base --is-ancestor origin/main main); then echo ''; else ( echo 'NEED TO PULL DEEPCASE!'; git checkout main; git pull; npm ci); fi) && | + npm run gitpod-start; + sudo apt update; sudo apt install -y libgbm-dev chromium-browser gconf-service libasound2 libatk1.0-0 libc6 libcairo2 libcups2 libdbus-1-3 libexpat1 libfontconfig1 libgcc1 libgconf-2-4 libgdk-pixbuf2.0-0 libglib2.0-0 libgtk-3-0 libnspr4 libpango-1.0-0 libpangocairo-1.0-0 libstdc++6 libx11-6 libx11-xcb1 libxcb1 libxcomposite1 libxcursor1 libxdamage1 libxext6 libxfixes3 libxi6 libxrandr2 libxrender1 libxss1 libxtst6 ca-certificates fonts-liberation libappindicator1 libnss3 lsb-release xdg-utils wget; # For puppeteer + + diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 442837fe..00000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "workbench.colorTheme": "Starfall Ocean", - "editor.tabSize": 2, - "files.autoSave": "off", - "workbench.colorCustomizations": { - "activityBar.background": "#181818", - "titleBar.activeBackground": "#181818", - "titleBar.activeForeground": "#F9FAFF" - }, - "editor.wordWrap": "on" -} \ No newline at end of file diff --git a/deep-packages/insertHandler.cjs b/deep-packages/insertHandler.cjs new file mode 100644 index 00000000..e5b5d28b --- /dev/null +++ b/deep-packages/insertHandler.cjs @@ -0,0 +1,47 @@ +exports.insertHandler = async ({deep,fileTypeLinkId, fileName, handlerName, handleName, triggerTypeLinkId, code, supportsId, handleOperationTypeLinkId, containTypeLinkId, packageId, handlerTypeLinkId}) => { + return await deep.insert({ + type_id: fileTypeLinkId, + in: { + data: [ + { + type_id: containTypeLinkId, + from_id: packageId, // before created package + string: { data: { value: fileName } }, + }, + { + from_id: supportsId, + type_id: handlerTypeLinkId, + in: { + data: [ + { + type_id: containTypeLinkId, + from_id: packageId, // before created package + string: { data: { value: handlerName } }, + }, + { + type_id: handleOperationTypeLinkId, + from_id: triggerTypeLinkId, + in: { + data: [ + { + type_id: containTypeLinkId, + from_id: packageId, // before created package + string: { data: { value: handleName } }, + }, + ], + }, + }, + ], + }, + }, + ], + }, + string: { + data: { + value: code, + }, + }, + }); + }; + + \ No newline at end of file diff --git a/deep-packages/insertNotificationHandler.cjs b/deep-packages/insertNotificationHandler.cjs new file mode 100644 index 00000000..c4820881 --- /dev/null +++ b/deep-packages/insertNotificationHandler.cjs @@ -0,0 +1,128 @@ +exports.insertNotificationHandler = async ({deep, packageId,notificationPort, notificationRoute, portTypeLinkId, routerListeningTypeLinkId, routerTypeLinkId, routerStringUseTypeLinkId, routeTypeLinkId, handleRouteTypeLinkId, handlerTypeLinkId, supportsId, containTypeLinkId, adminId, fileTypeLinkId, handlerName, code}) => { +return await deep.insert( + { + type_id: portTypeLinkId, + number: { + data: { value: notificationPort }, + }, + in: { + data: [ + { + type_id: routerListeningTypeLinkId, + in: { + data: { + type_id: containTypeLinkId, + from_id: packageId, + }, + }, + from: { + data: { + type_id: routerTypeLinkId, + in: { + data: [ + { + type_id: routerStringUseTypeLinkId, + in: { + data: { + type_id: containTypeLinkId, + from_id: packageId, + string: { + data: { + value: handlerName, + }, + }, + }, + }, + string: { + data: { + value: + notificationRoute, + }, + }, + from: { + data: { + type_id: routeTypeLinkId, + in: { + data: { + type_id: containTypeLinkId, + from_id: packageId, + }, + }, + out: { + data: { + type_id: handleRouteTypeLinkId, + in: { + data: [ + { + type_id: containTypeLinkId, + from_id: packageId, + } + ] + }, + to: { + data: { + type_id: handlerTypeLinkId, + from_id: supportsId, + in: { + data: { + type_id: containTypeLinkId, + // from_id: deep.linkId, + from_id: packageId, + string: { + data: { + value: handlerName, + }, + }, + }, + }, + to: { + data: { + type_id: fileTypeLinkId, + string: { + data: { + value: code, + }, + }, + in: { + data: { + type_id: containTypeLinkId, + from_id: packageId, + string: { + data: { + value: handlerName, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + { + type_id: containTypeLinkId, + from_id: packageId, + } + ], + }, + }, + }, + } + , + { + type_id: containTypeLinkId, + from_id: packageId, + } + ], + }, + }, + { + name: 'INSERT_HANDLE_ROUTE_HIERARCHICAL', + } + ) +} + diff --git a/deep-packages/payments/tinkoff/addCustomer.cjs b/deep-packages/payments/tinkoff/addCustomer.cjs new file mode 100644 index 00000000..4e777971 --- /dev/null +++ b/deep-packages/payments/tinkoff/addCustomer.cjs @@ -0,0 +1,33 @@ +const axios = require('axios'); +const { generateToken } = require("./generateToken.cjs"); +const { getError } = require("./getError.cjs"); +const { getUrl } = require("./getUrl.cjs"); + +exports.addCustomer = async (options) => { + try { + const response = await axios({ + method: 'post', + url: getUrl('AddCustomer'), + headers: { + 'Content-Type': 'application/json', + }, + data: { ...options, Token: generateToken(options) }, + }); + + const error = getError(response.data.ErrorCode); + + return { + error, + request: options, + response: response.data, + }; + } catch (error) { + return { + error, + request: options, + response: null, + }; + } + }; + + \ No newline at end of file diff --git a/deep-packages/payments/tinkoff/cancel.cjs b/deep-packages/payments/tinkoff/cancel.cjs new file mode 100644 index 00000000..c954bf24 --- /dev/null +++ b/deep-packages/payments/tinkoff/cancel.cjs @@ -0,0 +1,30 @@ +const axios = require('axios'); +const { generateToken } = require("./generateToken.cjs"); +const { getError } = require("./getError.cjs"); +const { getUrl } = require("./getUrl.cjs"); + +const cancel = async (options) => { + try { + const response = await axios({ + method: 'post', + url: getUrl('Cancel'), + data: { ...options, Token: generateToken(options) }, + }); + + const error = getError(response.data.ErrorCode); + + return { + error, + request: options, + response: response.data, + }; + } catch (error) { + return { + error, + request: options, + response: null, + }; + } + }; + + exports.cancel = cancel; \ No newline at end of file diff --git a/deep-packages/payments/tinkoff/cancelling/cancellingPayInsertHandler.js b/deep-packages/payments/tinkoff/cancelling/cancellingPayInsertHandler.js new file mode 100644 index 00000000..4df5e151 --- /dev/null +++ b/deep-packages/payments/tinkoff/cancelling/cancellingPayInsertHandler.js @@ -0,0 +1,180 @@ + +async ({ deep, require, data: { newLink: payLink, triggeredByLinkId } }) => { + + const crypto = require('crypto'); + const axios = require('axios'); + + const tinkoffApiUrlTypeLinkId = await deep.id("@deep-foundation/payments-tinkoff-c2b-cancelling", "TinkoffApiUrl"); + const { data: [tinkoffApiUrlLink] } = await deep.select({ + type_id: tinkoffApiUrlTypeLinkId + }); + if (!tinkoffApiUrlLink) { + throw new Error(`A link with type ##${tinkoffApiUrlTypeLinkId} is not found`); + } + if (!tinkoffApiUrlLink.value?.value) { + throw new Error(`##${tinkoffApiUrlLink.id} must have a value`); + } + const tinkoffApiUrl = tinkoffApiUrlLink.value.value; + + const terminalPasswordTypeLinkId = await deep.id("@deep-foundation/payments-tinkoff-c2b-cancelling", "TerminalPassword"); + const usesTerminalPasswordTypeLinkId = await deep.id("@deep-foundation/payments-tinkoff-c2b-cancelling", "UsesTerminalPassword"); + const { data: [terminalPasswordLink] } = await deep.select({ + type_id: terminalPasswordTypeLinkId, + in: { + type_id: usesTerminalPasswordTypeLinkId, + from_id: storageBusinessLink.id + } + }); + if (!terminalPasswordLink) { + throw new Error(`A link with type ##${terminalPasswordTypeLinkId} is not found`); + } + if (!terminalPasswordLink.value?.value) { + throw new Error(`##${terminalPasswordLink.id} must have a value`); + } + const terminalPassword = terminalPasswordLink.value.value; + + const generateToken = (data) => { + const { Receipt, DATA, Shops, ...restData } = data; + const dataWithPassword = { + Password: terminalPassword, + ...restData, + }; + console.log({ dataWithPassword }); + + const dataString = Object.keys(dataWithPassword) + .sort((a, b) => a.localeCompare(b)) + .map((key) => dataWithPassword[key]) + .reduce((acc, item) => `${acc}${item}`, ''); + console.log({ dataString }); + const hash = crypto.createHash('sha256').update(dataString).digest('hex'); + console.log({ hash }); + return hash; + }; + + + const { data: linksDownToLinkPay } = await deep.select({ + down: { + link_id: { _eq: payLink.id }, + tree_id: { _eq: await deep.id("@deep-foundation/payments-tinkoff-c2b-cancelling", "paymentTree") }, // TODO + }, + }); + console.log({ mpDownPay: linksDownToLinkPay }); + + const cancellingPaymentTypeLinkId = await deep.id("@deep-foundation/payments-tinkoff-c2b-cancelling-cancelling", "CancellingPayment"); + const cancellingPaymentLink = linksDownToLinkPay.find(link => link.type_id === cancellingPaymentTypeLinkId); + console.log({ cancellingPaymentLink }); + if (!cancellingPaymentLink) { + return; + } + + const tinkoffProviderTypeLinkId = await deep.id("@deep-foundation/payments-tinkoff-c2b-cancelling", "TinkoffProvider"); + const { data: [tinkoffProviderLink] } = await deep.select({ + type_id: tinkoffProviderTypeLinkId + }); + if (!tinkoffProviderLink) { + throw new Error(`A link with type ##${tinkoffProviderTypeLinkId} is not found`) + } + + const sumTypeLinkId = await deep.id("@deep-foundation/payments-tinkoff-c2b-cancelling", "Sum"); + const sumLink = linksDownToLinkPay.find(link => link.type_id === sumTypeLinkId); + console.log({ sumLink }); + if (!sumLink) throw new Error(`A link with type ##${sumTypeLinkId} associated with the link ##${payLink.id} is not found`); + if (!sumLink.value?.value) { + throw new Error(`##${sumLink.id} must have a value`) + } + + + const {data: [cancelledPaymentLink]} = await deep.select({ + id: cancellingPaymentLink.from_id + }); + + const terminalKeyTypeLinkId = await deep.id("@deep-foundation/payments-tinkoff-c2b-cancelling", "TerminalKey"); + const usesTerminalKeyTypeLinkId = await deep.id("@deep-foundation/payments-tinkoff-c2b-cancelling", "UsesTerminalKey"); + const { data: [terminalKeyLink] } = await deep.select({ + type_id: terminalKeyTypeLinkId, + in: { + type_id: usesTerminalKeyTypeLinkId, + from_id: storageBusinessLink.id + } + }); + console.log({ terminalKeyLink }) + if (!terminalKeyLink) { + throw new Error(`A link with type ##${terminalKeyTypeLinkId} is not found`); + } + if (!terminalKeyLink.value?.value) { + throw new Error(`##${terminalKeyLink.id} must have a value`); + } + const terminalKey = terminalKeyLink.value.value; + + const containTypeLinkId = await deep.id("@deep-foundation/core", "Contain"); + + const incomeTypeLinkId = await deep.id("@deep-foundation/payments-tinkoff-c2b-cancelling", "Income"); + await deep.insert({ + type_id: incomeTypeLinkId, + from_id: cancellingPaymentLink.id, + to_id: cancelledPaymentLink.to_id, + in: { + data: { + type_id: containTypeLinkId, + from_id: triggeredByLinkId + } + } + }); + + const {data: [userLink]} = await deep.select({ + id: cancellingPaymentLink.to_id + }); + + const cancel = async (options) => { + try { + const response = await axios({ + method: 'post', + url: `${tinkoffApiUrl}/Cancel`, + data: { ...options, Token: generateToken(options) }, + }); + + const error = response.data.Details; + + return { + error, + request: options, + response: response.data, + }; + } catch (error) { + return { + error, + request: options, + response: null, + }; + } + }; + + await deep.insert({ link_id: cancellingPaymentLink.id, value: cancelledPaymentLink.value.value }, { table: "objects" }); + + const bankPaymentId = cancelledPaymentLink.value.value.bankPaymentId; + const cancelOptions = { + TerminalKey: terminalKey, + PaymentId: bankPaymentId, + Amount: sumLink.value.value, + }; + console.log({ cancelOptions }); + + const cancelResult = await cancel(cancelOptions); + console.log({ cancelResult }); + if (cancelResult.error) { + throw new Error(cancelResult.error); + } + + await deep.insert({ + type_id: await deep.id("@deep-foundation/payments-tinkoff-c2b-cancelling", "Payed"), + from_id: tinkoffProviderLink.id, + to_id: payLink.id, + in: { + data: { + type_id: containTypeLinkId, + from_id: triggeredByLinkId + } + } + }); + +}; diff --git a/deep-packages/payments/tinkoff/cancelling/notificationHandler.js b/deep-packages/payments/tinkoff/cancelling/notificationHandler.js new file mode 100644 index 00000000..ef599e9a --- /dev/null +++ b/deep-packages/payments/tinkoff/cancelling/notificationHandler.js @@ -0,0 +1,101 @@ + +async ( + req, + res, + next, + { deep, require, gql } +) => { + // Canceled is used instead of Cancelled because tinkoff team is not goos at english + const allowedStatuses = ['CANCELED'] + if (!allowedStatuses.includes(req.body.Status)) { + return next(); + } + + res.send('ok'); + + const crypto = require('crypto'); + + const terminalPasswordTypeLinkId = await deep.id("@deep-foundation/payments-tinkoff-c2b-cancelling", "TerminalPassword"); + const usesTerminalPasswordTypeLinkId = await deep.id("@deep-foundation/payments-tinkoff-c2b-cancelling", "UsesTerminalPassword"); + const { data: [terminalPasswordLink] } = await deep.select({ + type_id: terminalPasswordTypeLinkId, + in: { + type_id: usesTerminalPasswordTypeLinkId, + from_id: storageBusinessLink.id + } + }); + if (!terminalPasswordLink) { + throw new Error(`A link with type ##${terminalPasswordTypeLinkId} is not found`); + } + if (!terminalPasswordLink.value?.value) { + throw new Error(`##${terminalPasswordLink.id} must have a value`); + } + const terminalPassword = terminalPasswordLink.value.value; + + const generateToken = (data) => { + const { Receipt, DATA, Shops, ...restData } = data; + const dataWithPassword = { + Password: terminalPassword, + ...restData, + }; + console.log({ dataWithPassword }); + + const dataString = Object.keys(dataWithPassword) + .sort((a, b) => a.localeCompare(b)) + .map((key) => dataWithPassword[key]) + .reduce((acc, item) => `${acc}${item}`, ''); + console.log({ dataString }); + const hash = crypto.createHash('sha256').update(dataString).digest('hex'); + console.log({ hash }); + return hash; + }; + + const tinkoffProviderTypeLinkId = await deep.id("@deep-foundation/payments-tinkoff-c2b-cancelling", "TinkoffProvider"); + const { data: [tinkoffProviderLink] } = await deep.select({ + type_id: tinkoffProviderTypeLinkId + }); + if (!tinkoffProviderLink) { + throw new Error(`A link with type ##${tinkoffProviderTypeLinkId} is not found`) + } + + const cancellingPaymentTypeLinkId = await deep.id("@deep-foundation/payments-tinkoff-c2b-cancelling-cancelling", "Payment"); + const { data: [cancellingPaymentLink] } = await deep.select({ + type_id: cancellingPaymentTypeLinkId, + object: { value: { _contains: { orderId: req.body.OrderId } } } + }); + if (!cancellingPaymentLink) { + throw new Error(`A link with type ##${cancellingPaymentTypeLinkId} and object value ${{ orderId: req.body.OrderId }} is not found`) + } + + const { data: linksDownToCancellingPaymentMp } = await deep.select({ + up: { + parent_id: { _eq: cancellingPaymentLink.id }, + tree_id: { _eq: await deep.id("@deep-foundation/payments-tinkoff-c2b-cancelling-cancelling", "paymentTree") } + } + }); + + const payTypeLinkId = await deep.id("@deep-foundation/payments-tinkoff-c2b-cancelling-cancelling", "Pay"); + const payLink = linksDownToCancellingPaymentMp.find(link => link.type_id === payTypeLinkId); + if (!payLink) { throw new Error(`A link with type ##${payTypeLinkId} associated with ##${cancellingPaymentLink} is not found.`) } + + const sumTypeLinkId = await deep.id("@deep-foundation/payments-tinkoff-c2b-cancelling", "Sum"); + const sumLink = linksUpToPayMp.find(link => link.type_id === sumTypeLinkId); + console.log({ sumLink }); + if (!sumLink) throw new Error(`A link with type ##${sumTypeLinkId} associated with the link ##${payLink.id} is not found`); + if (!sumLink.value?.value) { + throw new Error(`##${sumLink.id} must have a value`) + } + + const payedTypeLinkId = await deep.id("@deep-foundation/payments-tinkoff-c2b-cancelling-cancelling", "Payed") + await deep.insert({ + type_id: payedTypeLinkId, + from_id: tinkoffProviderLink.id, + to_id: sumLink.id, + in: { + data: { + type_id: containTypeLinkId, + from_id: triggeredByLinkId + } + } + }); +}; diff --git a/deep-packages/payments/tinkoff/charge.cjs b/deep-packages/payments/tinkoff/charge.cjs new file mode 100644 index 00000000..05218ffe --- /dev/null +++ b/deep-packages/payments/tinkoff/charge.cjs @@ -0,0 +1,33 @@ +const axios = require('axios'); +const { generateToken } = require("./generateToken.cjs"); +const { getError } = require("./getError.cjs"); +const { getUrl } = require("./getUrl.cjs"); + +exports.charge = async (options) => { + try { + const response = await axios({ + method: 'post', + url: getUrl('Charge'), + headers: { + 'Content-Type': 'application/json', + }, + data: { ...options, Token: generateToken(options) }, + }); + + const error = getError(response.data.ErrorCode); + + return { + error, + request: options, + response: response.data, + }; + } catch (error) { + return { + error, + request: options, + response: null, + }; + } + }; + + \ No newline at end of file diff --git a/deep-packages/payments/tinkoff/checkOrder.cjs b/deep-packages/payments/tinkoff/checkOrder.cjs new file mode 100644 index 00000000..3be6a611 --- /dev/null +++ b/deep-packages/payments/tinkoff/checkOrder.cjs @@ -0,0 +1,33 @@ +const axios = require('axios'); +const { generateToken } = require("./generateToken.cjs"); +const { getError } = require("./getError.cjs"); +const { getUrl } = require("./getUrl.cjs"); + +exports.checkOrder = async (options) => { + try { + const response = await axios({ + method: 'post', + url: getUrl('CheckOrder'), + headers: { + 'Content-Type': 'application/json', + }, + data: { ...options, Token: generateToken(options) }, + }); + + const error = getError(response.data.ErrorCode); + + return { + error, + request: options, + response: response.data, + }; + } catch (error) { + return { + error, + request: options, + response: null, + }; + } + }; + + \ No newline at end of file diff --git a/deep-packages/payments/tinkoff/confirm.cjs b/deep-packages/payments/tinkoff/confirm.cjs new file mode 100644 index 00000000..eaf55b42 --- /dev/null +++ b/deep-packages/payments/tinkoff/confirm.cjs @@ -0,0 +1,28 @@ +const axios = require('axios'); +const { generateToken } = require("./generateToken.cjs"); +const { getError } = require("./getError.cjs"); +const { getUrl } = require("./getUrl.cjs"); + +exports.confirm = async (options) => { + try { + const response = await axios({ + method: 'post', + url: getUrl('Confirm'), + data: { ...options, Token: generateToken(options) }, + }); + + const error = getError(response.data.ErrorCode); + + return { + error, + request: options, + response: response.data, + }; + } catch (error) { + return { + error, + request: options, + response: null, + }; + } + }; \ No newline at end of file diff --git a/deep-packages/payments/tinkoff/errors.cjs b/deep-packages/payments/tinkoff/errors.cjs new file mode 100644 index 00000000..cab4c5df --- /dev/null +++ b/deep-packages/payments/tinkoff/errors.cjs @@ -0,0 +1,58 @@ +exports.errors = { + 7: 'Покупатель не найден', + 53: 'Обратитесь к продавцу', + 99: 'Платеж отклонен', + 100: 'Повторите попытку позже', + 101: 'Не пройдена идентификация 3DS', + 102: 'Операция отклонена, пожалуйста обратитесь в интернет-магазин или воспользуйтесь другой картой', + 103: 'Повторите попытку позже', + 119: 'Превышено кол-во запросов на авторизацию', + 191: 'Некорректный статус договора, обратитесь к вашему менеджеру', + 1001: 'Свяжитесь с банком, выпустившим карту, чтобы провести платеж', + 1003: 'Неверный merchant ID', + 1004: 'Карта украдена. Свяжитесь с банком, выпустившим карту', + 1005: 'Платеж отклонен банком, выпустившим карту', + 1006: 'Свяжитесь с банком, выпустившим карту, чтобы провести платеж', + 1007: 'Карта украдена. Свяжитесь с банком, выпустившим карту', + 1008: 'Платеж отклонен, необходима идентификация', + 1012: 'Такие операции запрещены для этой карты', + 1013: 'Повторите попытку позже', + 1014: 'Карта недействительна. Свяжитесь с банком, выпустившим карту', + 1015: 'Попробуйте снова или свяжитесь с банком, выпустившим карту', + 1019: 'Платеж отклонен — попробуйте снова', + 1030: 'Повторите попытку позже', + 1033: 'Истек срок действия карты. Свяжитесь с банком, выпустившим карту', + 1034: 'Попробуйте повторить попытку позже', + 1038: 'Превышено количество попыток ввода ПИН-кода', + 1039: 'Платеж отклонен — счет не найден', + 1041: 'Карта утеряна. Свяжитесь с банком, выпустившим карту', + 1043: 'Карта украдена. Свяжитесь с банком, выпустившим карту', + 1051: 'Недостаточно средств на карте', + 1053: 'Платеж отклонен — счет не найден', + 1054: 'Истек срок действия карты', + 1055: 'Неверный ПИН', + 1057: 'Такие операции запрещены для этой карты', + 1058: 'Такие операции запрещены для этой карты', + 1059: 'Подозрение в мошенничестве. Свяжитесь с банком, выпустившим карту', + 1061: 'Превышен дневной лимит платежей по карте', + 1062: 'Платежи по карте ограничены', + 1063: 'Операции по карте ограничены', + 1064: 'Проверьте сумму', + 1065: 'Превышен дневной лимит транзакций', + 1075: 'Превышено число попыток ввода ПИН-кода', + 1076: 'Платеж отклонен — попробуйте снова', + 1077: 'Коды не совпадают — попробуйте снова', + 1080: 'Неверный срок действия', + 1082: 'Неверный CVV', + 1086: 'Платеж отклонен — не получилось подтвердить ПИН-код', + 1088: 'Ошибка шифрования. Попробуйте снова', + 1089: 'Попробуйте повторить попытку позже', + 1091: 'Банк, выпустивший карту недоступен для проведения авторизации', + 1092: 'Платеж отклонен — попробуйте снова', + 1093: 'Подозрение в мошенничестве. Свяжитесь с банком, выпустившим карту', + 1094: 'Системная ошибка', + 1096: 'Повторите попытку позже', + 9999: 'Внутренняя ошибка системы', + }; + + \ No newline at end of file diff --git a/deep-packages/payments/tinkoff/generateToken.cjs b/deep-packages/payments/tinkoff/generateToken.cjs new file mode 100644 index 00000000..cecc9879 --- /dev/null +++ b/deep-packages/payments/tinkoff/generateToken.cjs @@ -0,0 +1,29 @@ +const crypto = require('crypto'); + +exports.generateToken = (data) => { + const { Receipt, DATA, Shops, ...restData } = data; + const dataWithPassword = { + Password: process.env.PAYMENTS_C2B_TERMINAL_PASSWORD, + ...restData, + }; + console.log({ dataWithPassword }); + + const dataString = Object.keys(dataWithPassword) + .sort((a, b) => a.localeCompare(b)) + .map((key) => dataWithPassword[key]) + .reduce((acc, item) => `${acc}${item}`, ''); + console.log({ dataString }); + const hash = crypto.createHash('sha256').update(dataString).digest('hex'); + console.log({ hash }); + return hash; +}; + +exports.generateTokenStringWithInsertedTerminalPassword = exports.generateToken +.toString() +.replace( + 'process.env.PAYMENTS_C2B_TERMINAL_PASSWORD', + `"${process.env.PAYMENTS_C2B_TERMINAL_PASSWORD}"` +); + + + \ No newline at end of file diff --git a/deep-packages/payments/tinkoff/getCardList.cjs b/deep-packages/payments/tinkoff/getCardList.cjs new file mode 100644 index 00000000..e97f6cd5 --- /dev/null +++ b/deep-packages/payments/tinkoff/getCardList.cjs @@ -0,0 +1,33 @@ +const axios = require('axios'); +const { generateToken } = require("./generateToken.cjs"); +const { getError } = require("./getError.cjs"); +const { getUrl } = require("./getUrl.cjs"); + +exports.getCardList = async (options) => { + try { + const response = await axios({ + method: 'post', + url: getUrl('GetCardList'), + headers: { + 'Content-Type': 'application/json', + }, + data: { ...options, Token: generateToken(options) }, + }); + + const error = getError(response.data.ErrorCode || '0'); + + return { + error, + request: options, + response: response.data, + }; + } catch (error) { + return { + error, + request: options, + response: null, + }; + } + }; + + \ No newline at end of file diff --git a/deep-packages/payments/tinkoff/getCustomer.cjs b/deep-packages/payments/tinkoff/getCustomer.cjs new file mode 100644 index 00000000..0d06f7f0 --- /dev/null +++ b/deep-packages/payments/tinkoff/getCustomer.cjs @@ -0,0 +1,33 @@ +const axios = require('axios'); +const { generateToken } = require("./generateToken.cjs"); +const { getError } = require("./getError.cjs"); +const { getUrl } = require("./getUrl.cjs"); + +exports.getCustomer = async (options) => { + try { + const response = await axios({ + method: 'post', + url: getUrl('GetCustomer'), + headers: { + 'Content-Type': 'application/json', + }, + data: { ...options, Token: generateToken(options) }, + }); + + const error = getError(response.data.ErrorCode); + + return { + error, + request: options, + response: response.data, + }; + } catch (error) { + return { + error, + request: options, + response: null, + }; + } + }; + + \ No newline at end of file diff --git a/deep-packages/payments/tinkoff/getError.cjs b/deep-packages/payments/tinkoff/getError.cjs new file mode 100644 index 00000000..c6080a94 --- /dev/null +++ b/deep-packages/payments/tinkoff/getError.cjs @@ -0,0 +1,6 @@ +const {errors} = require("./errors.cjs"); + +exports.getError = (errorCode) => + errorCode === '0' ? undefined : errors[errorCode] || 'broken'; + + diff --git a/deep-packages/payments/tinkoff/getState.cjs b/deep-packages/payments/tinkoff/getState.cjs new file mode 100644 index 00000000..12448e81 --- /dev/null +++ b/deep-packages/payments/tinkoff/getState.cjs @@ -0,0 +1,30 @@ +const axios = require('axios'); +const { generateToken } = require("./generateToken.cjs"); +const { getError } = require("./getError.cjs"); +const { getUrl } = require("./getUrl.cjs"); + +exports.getState = async (options) => { + try { + const response = await axios({ + method: 'post', + url: getUrl('GetState'), + data: { ...options, Token: generateToken(options) }, + }); + + const error = getError(response.data.ErrorCode); + + return { + error, + request: options, + response: response.data, + }; + } catch (error) { + return { + error, + request: options, + response: null, + }; + } + }; + + \ No newline at end of file diff --git a/deep-packages/payments/tinkoff/getUrl.cjs b/deep-packages/payments/tinkoff/getUrl.cjs new file mode 100644 index 00000000..3dc0988e --- /dev/null +++ b/deep-packages/payments/tinkoff/getUrl.cjs @@ -0,0 +1,11 @@ +exports.getUrl = (method) => + `${process.env.PAYMENTS_C2B_URL}/${method}`; +exports.getUrlString = exports.getUrl + .toString() + .replace( + '${process.env.PAYMENTS_C2B_URL}', + process.env.PAYMENTS_C2B_URL + ); + + + \ No newline at end of file diff --git a/deep-packages/payments/tinkoff/handlersDependencies.cjs b/deep-packages/payments/tinkoff/handlersDependencies.cjs new file mode 100644 index 00000000..d810878a --- /dev/null +++ b/deep-packages/payments/tinkoff/handlersDependencies.cjs @@ -0,0 +1,15 @@ +const { errors } = require("./errors.cjs"); +const { getError } = require("./getError.cjs"); +const { getUrlString } = require("./getUrl.cjs"); +const { generateTokenStringWithInsertedTerminalPassword } = require("./generateToken.cjs"); + + +exports.handlersDependencies = ` +const crypto = require('crypto'); +const axios = require('axios'); +const errors = ${JSON.stringify(errors)}; +const getError = ${getError.toString()}; +const getUrl = ${getUrlString}; +const generateToken = ${generateTokenStringWithInsertedTerminalPassword}; +`; + diff --git a/deep-packages/payments/tinkoff/init.cjs b/deep-packages/payments/tinkoff/init.cjs new file mode 100644 index 00000000..f9b727af --- /dev/null +++ b/deep-packages/payments/tinkoff/init.cjs @@ -0,0 +1,33 @@ +const axios = require('axios'); +const { generateToken } = require("./generateToken.cjs"); +const { getError } = require("./getError.cjs"); +const { getUrl } = require("./getUrl.cjs"); + +exports.init = async (options) => { + try { + const response = await axios({ + method: 'post', + url: getUrl('Init'), + headers: { + 'Content-Type': 'application/json', + }, + data: { ...options, Token: generateToken(options) }, + }); + + const error = getError(response.data.ErrorCode); + + return { + error, + request: options, + response: response.data, + }; + } catch (error) { + return { + error, + request: options, + response: null, + }; + } + }; + + \ No newline at end of file diff --git a/deep-packages/payments/tinkoff/payInBrowser.cjs b/deep-packages/payments/tinkoff/payInBrowser.cjs new file mode 100644 index 00000000..d67a7590 --- /dev/null +++ b/deep-packages/payments/tinkoff/payInBrowser.cjs @@ -0,0 +1,100 @@ +const {sleep} = require('./../../sleep.cjs'); + +exports.payInBrowser = async ({ page, browser, url }) => { + await page.goto(url, { waitUntil: 'networkidle2' }); + await sleep(5000); + const oldForm = await page.evaluate(() => { + return !!document.querySelector( + 'input[automation-id="tui-input-card-grouped__card"]' + ); + }); + if (oldForm) { + console.log('OLD FORM!!!!!!!'); + // Старая форма используется на тестовом сервере + const cvc1 = await page.evaluate(() => { + return !!document.querySelector( + 'button[automation-id="pay-card__submit"]' + ); + }); + + // const saveCardTextDiv = [...document.querySelectorAll("div.t-content")].find(element => element.innerText == "Сохранить карту "); + // if(saveCardTextDiv) { + // const saveCardInput = saveCardTextDiv.parentElement.querySelector("input"); + // if(saveCardInput.checked) {saveCardInput.click()} + // } + + if (cvc1) { + await page.waitForSelector( + 'input[automation-id="tui-input-card-grouped__card"]' + ); + await sleep(300); + await page.type( + 'input[automation-id="tui-input-card-grouped__card"]', + process.env.PAYMENTS_C2B_CARD_NUMBER_SUCCESS + ); // card number + await sleep(300); + await page.keyboard.press('Tab'); + await sleep(300); + await page.type( + 'input[automation-id="tui-input-card-grouped__expire"]', + process.env.PAYMENTS_C2B_CARD_EXPDATE + ); // expired date + await sleep(300); + await page.keyboard.press('Tab'); + await sleep(300); + await page.type( + 'input[automation-id="tui-input-card-grouped__cvc"]', + process.env.PAYMENTS_C2B_CARD_CVC + ); // CVC code + await sleep(300); + await page.click('button[automation-id="pay-card__submit"]'); // submit button + } else { + await page.waitForSelector( + 'input[automation-id="tui-input-card-grouped__card"]' + ); + await sleep(300); + await page.type( + 'input[automation-id="tui-input-card-grouped__card"]', + process.env.PAYMENTS_C2B_CARD_NUMBER_SUCCESS + ); // card number + await sleep(300); + await page.keyboard.press('Tab'); + await sleep(300); + await page.type( + 'input[automation-id="tui-input-card-grouped__expire"]', + process.env.PAYMENTS_C2B_CARD_EXPDATE + ); // expired date + await sleep(300); + await page.keyboard.press('Tab'); + await sleep(300); + await page.type( + 'input[automation-id="tui-input-card-grouped__cvc"]', + process.env.PAYMENTS_C2B_CARD_CVC + ); // CVC code + await sleep(300); + await page.click('button[automation-id="card-form__submit"]'); // submit button + // await sleep(300); + // await page.waitForSelector('input[name="password"]'); + // const code = prompt('enter code '); + // console.log('code', code); + // await page.type('input[name="password"]', code); + // await sleep(1000); + } + // TODO: пока старая форма вызывалась только на тестовой карте, где ввод смс кода не нужен + await sleep(1000); + } else { + console.log('NEW FORM!!!!!!!'); + await page.type('#pan', process.env.PAYMENTS_C2B_CARD_NUMBER_SUCCESS); // card number + await page.type('#expDate', process.env.PAYMENTS_C2B_CARD_EXPDATE); // expired date + await page.type('#card_cvc', process.env.PAYMENTS_C2B_CARD_CVC); // CVC code + await page.click('button[type=submit]'); // submit button + await page.waitForSelector('input[name="password"]'); + const code = prompt('enter code '); + console.log('code', code); + await page.type('input[name="password"]', code); + await sleep(3000); + } + await browser.close(); + }; + + \ No newline at end of file diff --git a/deep-packages/payments/tinkoff/payInsertHandler.js b/deep-packages/payments/tinkoff/payInsertHandler.js new file mode 100644 index 00000000..59df2f13 --- /dev/null +++ b/deep-packages/payments/tinkoff/payInsertHandler.js @@ -0,0 +1,231 @@ + +async ({ deep, require, data: { newLink: payLink, triggeredByLinkId } }) => { + + const crypto = require('crypto'); + const axios = require('axios'); + + const paymentTreeId = await deep.id("@deep-foundation/payments-tinkoff-c2b", "paymentTree"); + const { data: linksUpToPayMp } = await deep.select({ + down: { + link_id: { _eq: payLink.id }, + tree_id: { _eq: paymentTreeId } + } + }); + if (linksUpToPayMp.length === 0) { + throw new Error(`There is no links up to ##${payLink.id} materialized path`); + } + + const paymentTypeLinkId = await deep.id("@deep-foundation/payments-tinkoff-c2b", "Payment"); + const paymentLink = linksUpToPayMp.find(link => link.type_id === paymentTypeLinkId); + if (!paymentLink) throw new Error(`A link with type ##${paymentTypeLinkId} associated with the link ##${payLink.id} is not found`); + + const { data: [storageBusinessLink] } = await deep.select({ + id: paymentLink.to_id + }); + console.log({ storageBusinessLink }) + + const terminalPasswordTypeLinkId = await deep.id("@deep-foundation/payments-tinkoff-c2b", "TerminalPassword"); + const usesTerminalPasswordTypeLinkId = await deep.id("@deep-foundation/payments-tinkoff-c2b", "UsesTerminalPassword"); + const { data: [terminalPasswordLink] } = await deep.select({ + type_id: terminalPasswordTypeLinkId, + in: { + type_id: usesTerminalPasswordTypeLinkId, + from_id: storageBusinessLink.id + } + }); + if (!terminalPasswordLink) { + throw new Error(`A link with type ##${terminalPasswordTypeLinkId} is not found`); + } + if (!terminalPasswordLink.value?.value) { + throw new Error(`##${terminalPasswordLink.id} must have a value`); + } + const terminalPassword = terminalPasswordLink.value.value; + + const generateToken = (data) => { + const { Receipt, DATA, Shops, ...restData } = data; + const dataWithPassword = { + Password: terminalPassword, + ...restData, + }; + console.log({ dataWithPassword }); + + const dataString = Object.keys(dataWithPassword) + .sort((a, b) => a.localeCompare(b)) + .map((key) => dataWithPassword[key]) + .reduce((acc, item) => `${acc}${item}`, ''); + console.log({ dataString }); + const hash = crypto.createHash('sha256').update(dataString).digest('hex'); + console.log({ hash }); + return hash; + }; + + const tinkoffProviderTypeLinkId = await deep.id("@deep-foundation/payments-tinkoff-c2b", "TinkoffProvider"); + const { data: [tinkoffProviderLink] } = await deep.select({ + type_id: tinkoffProviderTypeLinkId + }); + if (!tinkoffProviderLink) { + throw new Error(`A link with type ##${tinkoffProviderTypeLinkId} is not found`) + } + + const sumTypeLinkId = await deep.id("@deep-foundation/payments-tinkoff-c2b", "Sum"); + const sumLink = linksUpToPayMp.find(link => link.type_id === sumTypeLinkId); + console.log({ sumLink }); + if (!sumLink) throw new Error(`A link with type ##${sumTypeLinkId} associated with the link ##${payLink.id} is not found`); + if (!sumLink.value?.value) { + throw new Error(`##${sumLink.id} must have a value`) + } + + const terminalKeyTypeLinkId = await deep.id("@deep-foundation/payments-tinkoff-c2b", "TerminalKey"); + const usesTerminalKeyTypeLinkId = await deep.id("@deep-foundation/payments-tinkoff-c2b", "UsesTerminalKey"); + const { data: [terminalKeyLink] } = await deep.select({ + type_id: terminalKeyTypeLinkId, + in: { + type_id: usesTerminalKeyTypeLinkId, + from_id: storageBusinessLink.id + } + }); + console.log({ terminalKeyLink }) + if (!terminalKeyLink) { + throw new Error(`A link with type ##${terminalKeyTypeLinkId} is not found`); + } + if (!terminalKeyLink.value?.value) { + throw new Error(`##${terminalKeyLink.id} must have a value`); + } + const terminalKey = terminalKeyLink.value.value; + + const tinkoffApiUrlTypeLinkId = await deep.id("@deep-foundation/payments-tinkoff-c2b", "TinkoffApiUrl"); + const { data: [tinkoffApiUrlLink] } = await deep.select({ + type_id: tinkoffApiUrlTypeLinkId + }); + if (!tinkoffApiUrlLink) { + throw new Error(`A link with type ##${tinkoffApiUrlTypeLinkId} is not found`); + } + if (!tinkoffApiUrlLink.value?.value) { + throw new Error(`##${tinkoffApiUrlLink.id} must have a value`); + } + const tinkoffApiUrl = tinkoffApiUrlLink.value.value; + + const init = async (options) => { + try { + const response = await axios({ + method: 'post', + url: `${tinkoffApiUrl}/Init`, + headers: { + 'Content-Type': 'application/json', + }, + data: { ...options, Token: generateToken(options) }, + }); + + const error = response.data.Details; + + return { + error, + request: options, + response: response.data, + }; + } catch (error) { + return { + error, + request: options, + response: null, + }; + } + }; + + const containTypeLinkId = await deep.id("@deep-foundation/core", "Contain"); + const notificationUrlTypeLinkId = await deep.id("@deep-foundation/payments-tinkoff-c2b", "NotificationUrl"); + const { data: [notificationUrlLink] } = await deep.select({ + type_id: notificationUrlTypeLinkId, + in: { + type_id: containTypeLinkId, + from_id: triggeredByLinkId + } + }); + if (!notificationUrlLink) { + throw new Error(`A link with type ##${notificationUrlTypeLinkId} is not found`); + } + if (!notificationUrlLink.value?.value) { + throw new Error(`##${notificationUrlLink.id} must have a value`) + } + + const emailTypeLinkId = await deep.id("@deep-foundation/payments-tinkoff-c2b", "Email"); + const { data: [emailLink] } = await deep.select({ + type_id: emailTypeLinkId, + in: { + type_id: containTypeLinkId, + from_id: triggeredByLinkId + } + }); + if (!emailLink) { + throw new Error(`A link with type ##${emailTypeLinkId} is not found`); + } + if (!emailLink.value?.value) { + throw new Error(`##${emailLink.id} must have a value`); + } + + const phoneNumberTypeLinkId = await deep.id("@deep-foundation/payments-tinkoff-c2b", "PhoneNumber"); + const { data: [phoneNumberLink] } = await deep.select({ + type_id: phoneNumberTypeLinkId, + in: { + type_id: containTypeLinkId, + from_id: triggeredByLinkId + } + }); + if (!phoneNumberLink) { + throw new Error(`A link with type ##${phoneNumberTypeLinkId} is not found`); + } + if (!phoneNumberLink.value?.value) { + throw new Error(`##${phoneNumberLink.id} must have a value`); + } + + const options = { + TerminalKey: terminalKey, + OrderId: "" + Date.now() + paymentLink.id, + CustomerKey: triggeredByLinkId, + NotificationURL: notificationUrlLink.value.value, + PayType: 'T', + Amount: sumLink.value.value, + Language: 'ru', + Recurrent: 'Y', + DATA: { + Email: emailLink.value.value, + Phone: phoneNumberLink.value.value, + }, + // Receipt: { + // Items: [{ + // Name: 'Test item', + // Price: sum, + // Quantity: 1, + // Amount: sumLink.value.value, + // PaymentMethod: 'prepayment', + // PaymentObject: 'service', + // Tax: 'none', + // }], + // Email: emailLinkId.value.value, + // Phone: phoneLinkId.value.value, + // Taxation: 'usn_income', + // } + }; + console.log({ options }); + + let initResult = await init(options); + console.log({ initResult }); + if (initResult.error) { + throw new Error(initResult.error); + } + + const urlTypeLinkId = await deep.id("@deep-foundation/payments-tinkoff-c2b", "Url"); + await deep.insert({ + type_id: urlTypeLinkId, + from_id: tinkoffProviderLink.id, + to_id: payLink.id, + string: { data: { value: initResult.response.PaymentURL } }, + }); + + await deep.insert( + { link_id: paymentLink.id, value: { bankPaymentId: parseInt(initResult.response.PaymentId) } }, + { table: "objects" } + ); + + return initResult; +}; diff --git a/deep-packages/payments/tinkoff/payments-tinkoff-c2b.cjs b/deep-packages/payments/tinkoff/payments-tinkoff-c2b.cjs new file mode 100644 index 00000000..8fd4ded6 --- /dev/null +++ b/deep-packages/payments/tinkoff/payments-tinkoff-c2b.cjs @@ -0,0 +1,1164 @@ +require('react'); +require('graphql'); +require('lodash'); +require('subscriptions-transport-ws'); +const dotenv = require('dotenv'); +const { generateApolloClient } = require('@deep-foundation/hasura/client'); +const { DeepClient } = require('@deep-foundation/deeplinks/imports/client'); +const { + minilinks, + Link, +} = require('@deep-foundation/deeplinks/imports/minilinks'); +const puppeteer = require('puppeteer'); +const crypto = require('crypto'); +const axios = require('axios'); +const uniqid = require('uniqid'); +const { expect } = require('chai'); +const { get } = require('lodash'); +const { + default: links, +} = require('@deep-foundation/deeplinks/imports/router/links'); +var myEnv = dotenv.config(); +const { payInBrowser } = require("./payInBrowser.cjs"); +const { getError } = require("./getError.cjs"); +const { generateToken, generateTokenStringWithInsertedTerminalPassword } = require("./generateToken.cjs"); +const { getUrl } = require("./getUrl.cjs"); +const { getState } = require("./getState.cjs"); +const { checkOrder } = require("./checkOrder.cjs"); +const { getCardList } = require("./getCardList.cjs"); +const { init } = require("./init.cjs"); +const { charge } = require("./charge.cjs"); +const { addCustomer } = require("./addCustomer.cjs"); +const { getCustomer } = require("./getCustomer.cjs"); +const { removeCustomer } = require("./removeCustomer.cjs"); +const { handlersDependencies } = require("./handlersDependencies.cjs"); +const { insertTinkoffPayInsertHandler } = require("./insertTinkoffPayInsertHandler.cjs"); +const { insertTinkoffNotificationHandler } = require("./insertTinkoffNotificationHandler.cjs"); +const { sleep } = require("../../sleep.cjs"); +const { confirm } = require("./confirm.cjs"); +const { testInit: callRealizationTestInit } = require('./tests/realization/testInit.cjs'); +const { testConfirm: callRealizationTestConfirm } = require('./tests/realization/testConfirm.cjs'); +const { testGetState: callRealizationTestGetState } = require('./tests/realization/testGetState.cjs'); +const { testGetCardList: callRealizationTestGetCardList } = require('./tests/realization/testGetCardList.cjs'); +const { testResend: callRealizationTestResend } = require('./tests/realization/testResend.cjs'); +const { testCharge: callRealizationTestCharge } = require('./tests/realization/testCharge.cjs'); +const { testAddCustomer: callRealizationTestAddCustomer } = require('./tests/realization/testAddCustomer.cjs'); +const { testGetCustomer: callRealizationTestGetCustomer } = require('./tests/realization/testGetCustomer.cjs'); +const { testRemoveCustomer: callRealizationTestRemoveCustomer } = require('./tests/realization/testRemoveCustomer.cjs'); +const fs = require('fs'); +const { errors } = require('./errors.cjs'); + +console.log('Installing payments-tinkoff-c2b package'); + +const requiredEnvVariableNames = [ + "PAYMENTS_C2B_TERMINAL_KEY", + "PAYMENTS_C2B_TERMINAL_PASSWORD", + "PAYMENTS_C2B_URL", + "PAYMENTS_C2B_NOTIFICATION_ROUTE", + "PAYMENTS_C2B_NOTIFICATION_PORT", + "PAYMENTS_C2B_NOTIFICATION_URL", + "PAYMENTS_C2B_CARD_NUMBER_SUCCESS", + "PAYMENTS_C2B_CARD_EXPDATE", + "PAYMENTS_C2B_CARD_CVC", + "PAYMENTS_C2B_PHONE_NUMBER", + "PAYMENTS_C2B_EMAIL", +]; + +for (const requiredEnvVariableName of requiredEnvVariableNames) { + if (!process.env[requiredEnvVariableName]) { + throw new Error(`The environment variable ${requiredEnvVariableName} is required. All the required environment variables: \n${requiredEnvVariableNames.join("\n")}`); + } +} + +// console.log(process.env.PAYMENTS_C2B_NOTIFICATION_URL); +// process.exit(1); + +const allCreatedLinkIds = []; + +const installPackage = async () => { + const apolloClient = generateApolloClient({ + path: process.env.NEXT_PUBLIC_GQL_PATH || '', // <<= HERE PATH TO UPDATE + ssl: !!~process.env.NEXT_PUBLIC_GQL_PATH.indexOf('localhost') + ? false + : true, + // admin token in prealpha deep secret key + // token: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwczovL2hhc3VyYS5pby9qd3QvY2xhaW1zIjp7IngtaGFzdXJhLWFsbG93ZWQtcm9sZXMiOlsibGluayJdLCJ4LWhhc3VyYS1kZWZhdWx0LXJvbGUiOiJsaW5rIiwieC1oYXN1cmEtdXNlci1pZCI6IjI2MiJ9LCJpYXQiOjE2NTYxMzYyMTl9.dmyWwtQu9GLdS7ClSLxcXgQiKxmaG-JPDjQVxRXOpxs', + }); + + const unloginedDeep = new DeepClient({ apolloClient }); + const guest = await unloginedDeep.guest(); + const guestDeep = new DeepClient({ deep: unloginedDeep, ...guest }); + const admin = await guestDeep.login({ + linkId: await guestDeep.id('deep', 'admin'), + }); + const deep = new DeepClient({ deep: guestDeep, ...admin }); + + try { + const typeTypeLinkId = await deep.id('@deep-foundation/core', 'Type'); + const anyTypeLinkId = await deep.id('@deep-foundation/core', 'Any'); + const joinTypeLinkId = await deep.id('@deep-foundation/core', 'Join'); + const containTypeLinkId = await deep.id('@deep-foundation/core', 'Contain'); + const packageTypeLinkId = await deep.id('@deep-foundation/core', 'Package'); + const valueTypeLinkId = await deep.id("@deep-foundation/core", "Value"); + const stringTypeLinkId = await deep.id("@deep-foundation/core", "String"); + const numberTypeLinkId = await deep.id("@deep-foundation/core", "Number"); + const objectTypeLinkId = await deep.id("@deep-foundation/core", "Object"); + const userTypeLinkId = await deep.id("@deep-foundation/core", "User"); + + const syncTextFileTypeLinkId = await deep.id('@deep-foundation/core', 'SyncTextFile'); + const dockerSupportsJs = await deep.id( + '@deep-foundation/core', + 'dockerSupportsJs' + ); + const handleInsertTypeLinkId = await deep.id('@deep-foundation/core', 'HandleInsert'); + const portTypeLinkId = await deep.id('@deep-foundation/core', 'Port'); + const routerListeningTypeLinkId = await deep.id('@deep-foundation/core', 'RouterListening'); + const routerTypeLinkId = await deep.id('@deep-foundation/core', 'Router'); + const routerStringUseTypeLinkId = await deep.id( + '@deep-foundation/core', + 'RouterStringUse' + ); + const routeTypeLinkId = await deep.id('@deep-foundation/core', 'Route'); + const handleRouteTypeLinkId = await deep.id( + '@deep-foundation/core', + 'HandleRoute' + ); + const handlerTypeLinkId = await deep.id( + '@deep-foundation/core', + 'Handler' + ); + const dockerSupportsJsId = await deep.id( + '@deep-foundation/core', + 'dockerSupportsJs' + ); + + const treeTypeLinkId = await deep.id('@deep-foundation/core', 'Tree'); + const treeIncludeNodeTypeLinkId = await deep.id( + '@deep-foundation/core', + 'TreeIncludeNode' + ); + const treeIncludeUpTypeLinkId = await deep.id('@deep-foundation/core', 'TreeIncludeUp'); + const treeIncludeDownTypeLinkId = await deep.id( + '@deep-foundation/core', + 'TreeIncludeDown' + ); + + const basePaymentTypeLinkId = await deep.id('@deep-foundation/payments', 'Payment'); + const baseObjectTypeLinkId = await deep.id('@deep-foundation/payments', 'Object'); + const baseSumTypeLinkId = await deep.id('@deep-foundation/payments', 'Sum'); + const basePayTypeLinkId = await deep.id('@deep-foundation/payments', 'Pay'); + const baseUrlTypeLinkId = await deep.id('@deep-foundation/payments', 'Url'); + const basePayedTypeLinkId = await deep.id('@deep-foundation/payments', 'Payed'); + const storageTypeLinkId = await deep.id('@deep-foundation/payments', 'Storage'); + + const { + data: [{ id: packageLinkId }], + } = await deep.insert({ + type_id: packageTypeLinkId, + string: { data: { value: '@deep-foundation/payments-tinkoff-c2b' } }, + in: { + data: [ + { + type_id: containTypeLinkId, + from_id: deep.linkId, + }, + ], + }, + out: { + data: [ + { + type_id: joinTypeLinkId, + to_id: await deep.id('deep', 'users', 'packages'), + }, + { + type_id: joinTypeLinkId, + to_id: await deep.id('deep', 'admin'), + }, + ], + }, + }); + + console.log({ packageLinkId }); + + const { + data: [{ id: sumProviderTypeLinkId }], + } = await deep.insert({ + type_id: typeTypeLinkId, + in: { + data: { + type_id: containTypeLinkId, + from_id: packageLinkId, + string: { data: { value: 'SumProvider' } }, + }, + }, + }); + + console.log({ sumProviderTypeLinkId }); + + const { + data: [{ id: tinkoffProviderTypeLinkId }], + } = await deep.insert({ + type_id: typeTypeLinkId, + in: { + data: { + type_id: containTypeLinkId, + from_id: packageLinkId, + string: { data: { value: 'TinkoffProvider' } }, + }, + }, + }); + + console.log({ tinkoffProviderTypeLinkId }); + + const { + data: [{ id: storageBusinessTypeLinkId }], + } = await deep.insert({ + type_id: storageTypeLinkId, + in: { + data: { + type_id: containTypeLinkId, + from_id: packageLinkId, + string: { data: { value: 'StorageBusiness' } }, + }, + }, + }); + console.log({ storageBusinessTypeLinkId }); + + const { + data: [{ id: terminalPasswordTypeLinkId }], + } = await deep.insert({ + type_id: typeTypeLinkId, + in: { + data: { + type_id: containTypeLinkId, + from_id: packageLinkId, + string: { data: { value: 'TerminalPassword' } }, + }, + }, + }); + console.log({ terminalPasswordTypeLinkId }); + + const { + data: [{ id: storageClientTypeLinkId }], + } = await deep.insert({ + type_id: storageTypeLinkId, + in: { + data: { + type_id: containTypeLinkId, + from_id: packageLinkId, + string: { data: { value: 'StorageClient' } }, + }, + }, + }); + console.log({ storageClientTypeLinkId }); + + const { + data: [{ id: storageClientTitleTypeLinkId }], + } = await deep.insert({ + type_id: typeTypeLinkId, + from_id: storageClientTypeLinkId, + to_id: storageClientTypeLinkId, // TODO + in: { + data: { + type_id: containTypeLinkId, + from_id: packageLinkId, + string: { data: { value: 'StorageClientTitle' } }, + }, + }, + }); + console.log({ titleTypeLinkId: storageClientTitleTypeLinkId }); + + const { + data: [{ id: incomeTypeLinkId }], + } = await deep.insert({ + type_id: typeTypeLinkId, + from_id: anyTypeLinkId, + to_id: anyTypeLinkId, + in: { + data: { + type_id: containTypeLinkId, + from_id: packageLinkId, + string: { data: { value: 'Income' } }, + }, + }, + }); + console.log({ incomeTypeLinkId }); + + const { + data: [{ id: storageBusinessLinkId }], + } = await deep.insert({ + type_id: storageBusinessTypeLinkId, + in: { + data: [ + { + type_id: containTypeLinkId, + from_id: deep.linkId, + }, + ], + }, + }); + console.log({ storageBusinessLinkId }); + + const { + data: [{ id: terminalKeyTypeLinkId }], + } = await deep.insert({ + type_id: typeTypeLinkId, + from_id: anyTypeLinkId, + to_id: anyTypeLinkId, + in: { + data: { + type_id: containTypeLinkId, + from_id: packageLinkId, + string: { data: { value: 'TerminalKey' } }, + }, + }, + }); + console.log({ terminalKeyTypeLinkId }); + + const { + data: [{ id: usesTerminalKeyTypeLinkId }], + } = await deep.insert({ + type_id: typeTypeLinkId, + from_id: anyTypeLinkId, + to_id: anyTypeLinkId, + in: { + data: { + type_id: containTypeLinkId, + from_id: packageLinkId, + string: { data: { value: 'UsesTerminalKey' } }, + }, + }, + }); + console.log({ usesTerminalKeyTypeLinkId }); + + const { + data: [{ id: terminalKeyLinkId }], + } = await deep.insert({ + type_id: terminalKeyTypeLinkId, + string: { data: { value: process.env.PAYMENTS_C2B_TERMINAL_KEY } }, + in: { + data: [ + { + type_id: containTypeLinkId, + from_id: deep.linkId, + }, + { + type_id: usesTerminalKeyTypeLinkId, + from_id: storageBusinessLinkId + }, + ], + }, + }); + console.log({ terminalKeyLinkId }); + + const { + data: [{ id: usesTerminalPasswordTypeLinkId }], + } = await deep.insert({ + type_id: typeTypeLinkId, + from_id: anyTypeLinkId, + to_id: anyTypeLinkId, + in: { + data: { + type_id: containTypeLinkId, + from_id: packageLinkId, + string: { data: { value: 'UsesTerminalPassword' } }, + }, + }, + }); + console.log({ usesTerminalPasswordTypeLinkId }); + + const { + data: [{ id: terminalPasswordLinkId }], + } = await deep.insert({ + type_id: terminalPasswordTypeLinkId, + string: { data: { value: process.env.PAYMENTS_C2B_TERMINAL_PASSWORD } }, + in: { + data: [ + { + type_id: containTypeLinkId, + from_id: deep.linkId, + }, + { + type_id: usesTerminalPasswordTypeLinkId, + from_id: storageBusinessLinkId + }, + ], + }, + }); + console.log({ terminalPasswordLinkId }); + + const { + data: [{ id: paymentTypeLinkId }], + } = await deep.insert({ + type_id: basePaymentTypeLinkId, + from_id: userTypeLinkId, + to_id: storageBusinessTypeLinkId, + in: { + data: { + type_id: containTypeLinkId, + from_id: packageLinkId, + string: { data: { value: 'Payment' } }, + }, + }, + out: { + data: [ + { + type_id: valueTypeLinkId, + to_id: objectTypeLinkId + } + ] + } + }); + console.log({ paymentTypeLinkId }); + + const { + data: [{ id: paymentObjectTypeLinkId }], + } = await deep.insert({ + type_id: baseObjectTypeLinkId, + from_id: paymentTypeLinkId, + to_id: anyTypeLinkId, + in: { + data: { + type_id: containTypeLinkId, + from_id: packageLinkId, + string: { data: { value: 'Object' } }, + }, + }, + }); + + console.log({ objectTypeLinkId: paymentObjectTypeLinkId }); + + const { + data: [{ id: sumTypeLinkId }], + } = await deep.insert({ + type_id: baseSumTypeLinkId, + from_id: sumProviderTypeLinkId, + to_id: paymentTypeLinkId, + in: { + data: { + type_id: containTypeLinkId, + from_id: packageLinkId, + string: { data: { value: 'Sum' } }, + }, + }, + out: { + data: [ + { + type_id: valueTypeLinkId, + to_id: numberTypeLinkId + } + ] + } + }); + + console.log({ sumTypeLinkId }); + + const { + data: [{ id: payTypeLinkId }], + } = await deep.insert({ + type_id: basePayTypeLinkId, + from_id: userTypeLinkId, + to_id: sumTypeLinkId, + in: { + data: { + type_id: containTypeLinkId, + from_id: packageLinkId, + string: { data: { value: 'Pay' } }, + }, + }, + }); + + console.log({ payTypeLinkId }); + + const { + data: [{ id: urlTypeLinkId }], + } = await deep.insert({ + type_id: baseUrlTypeLinkId, + from_id: tinkoffProviderTypeLinkId, + to_id: payTypeLinkId, + in: { + data: { + type_id: containTypeLinkId, + from_id: packageLinkId, + string: { data: { value: 'Url' } }, + }, + }, + out: { + data: [ + { + type_id: valueTypeLinkId, + to_id: stringTypeLinkId + } + ] + } + }); + + console.log({ urlTypeLinkId }); + + const { + data: [{ id: payedTypeLinkId }], + } = await deep.insert({ + type_id: basePayedTypeLinkId, + from_id: tinkoffProviderTypeLinkId, + to_id: payTypeLinkId, + in: { + data: { + type_id: containTypeLinkId, + from_id: packageLinkId, + string: { data: { value: 'Payed' } }, + }, + }, + }); + + console.log({ payedTypeLinkId }); + + const { + data: [{ id: paymentTreeId }], + } = await deep.insert({ + type_id: treeTypeLinkId, + in: { + data: { + type_id: containTypeLinkId, + from_id: packageLinkId, + string: { data: { value: 'paymentTree' } }, + }, + }, + out: { + data: [ + { + type_id: treeIncludeNodeTypeLinkId, + to_id: paymentTypeLinkId, + in: { + data: [ + { + type_id: containTypeLinkId, + from_id: packageLinkId, + }, + ], + }, + }, + { + type_id: treeIncludeUpTypeLinkId, + to_id: sumTypeLinkId, + in: { + data: [ + { + type_id: containTypeLinkId, + from_id: packageLinkId, + }, + ], + }, + }, + { + type_id: treeIncludeDownTypeLinkId, + to_id: paymentObjectTypeLinkId, + in: { + data: [ + { + type_id: containTypeLinkId, + from_id: packageLinkId, + }, + ], + }, + }, + { + type_id: treeIncludeUpTypeLinkId, + to_id: payedTypeLinkId, + in: { + data: [ + { + type_id: containTypeLinkId, + from_id: packageLinkId, + }, + ], + }, + }, + { + type_id: treeIncludeUpTypeLinkId, + to_id: payTypeLinkId, + in: { + data: [ + { + type_id: containTypeLinkId, + from_id: packageLinkId, + }, + ], + }, + }, + { + type_id: treeIncludeUpTypeLinkId, + to_id: urlTypeLinkId, + in: { + data: [ + { + type_id: containTypeLinkId, + from_id: packageLinkId, + }, + ], + }, + }, + ], + }, + }); + + const { + data: [{ id: tinkoffApiUrlTypeLinkId }], + } = await deep.insert({ + type_id: typeTypeLinkId, + in: { + data: { + type_id: containTypeLinkId, + from_id: packageLinkId, + string: { data: { value: 'TinkoffApiUrl' } }, + }, + }, + }); + console.log({ tinkoffApiUrlTypeLinkId }); + + const { + data: [{ id: tinkoffApiUrlLinkId }], + } = await deep.insert({ + type_id: tinkoffApiUrlTypeLinkId, + string: { + data: { + value: process.env.PAYMENTS_C2B_URL + } + }, + in: { + data: { + type_id: containTypeLinkId, + from_id: deep.linkId, + }, + }, + }); + console.log({ tinkoffApiUrlLinkId }); + + const { + data: [{ id: notificationUrlTypeLinkId }], + } = await deep.insert({ + type_id: typeTypeLinkId, + in: { + data: { + type_id: containTypeLinkId, + from_id: packageLinkId, + string: { data: { value: 'NotificationUrl' } }, + }, + }, + }); + console.log({ notificationUrlTypeLinkId }); + + const { + data: [{ id: notificationUrlLinkId }], + } = await deep.insert({ + type_id: notificationUrlTypeLinkId, + string: { + data: { + value: process.env.PAYMENTS_C2B_NOTIFICATION_URL + } + }, + in: { + data: { + type_id: containTypeLinkId, + from_id: deep.linkId, + }, + }, + }); + console.log({ notificationUrlLinkId }); + + const { + data: [{ id: emailTypeLinkId }], + } = await deep.insert({ + type_id: typeTypeLinkId, + in: { + data: { + type_id: containTypeLinkId, + from_id: packageLinkId, + string: { data: { value: 'Email' } }, + }, + }, + }); + console.log({ emailTypeLinkId }); + + const { + data: [{ id: emailLinkId }], + } = await deep.insert({ + type_id: emailTypeLinkId, + string: { + data: { + value: process.env.PAYMENTS_C2B_EMAIL + } + }, + in: { + data: { + type_id: containTypeLinkId, + from_id: deep.linkId, + }, + }, + }); + console.log({ emailLinkId }); + + const { + data: [{ id: phoneNumberTypeLinkId }], + } = await deep.insert({ + type_id: typeTypeLinkId, + in: { + data: { + type_id: containTypeLinkId, + from_id: packageLinkId, + string: { data: { value: 'PhoneNumber' } }, + }, + }, + }); + console.log({ phoneNumberTypeLinkId }); + + const { + data: [{ id: phoneNumberLinkId }], + } = await deep.insert({ + type_id: phoneNumberTypeLinkId, + string: { + data: { + value: process.env.PAYMENTS_C2B_PHONE_NUMBER + } + }, + in: { + data: { + type_id: containTypeLinkId, + from_id: deep.linkId, + }, + }, + }); + console.log({ phoneNumberLinkId }); + + + + await deep.insert({ + type_id: await deep.id("@deep-foundation/core", "SyncTextFile"), + string: { + data: { + value: fs.readFileSync('./deep-packages/payments/tinkoff/payInsertHandler.js', {encoding: 'utf-8'}), + }, + }, + in: { + data: [ + { + type_id: containTypeLinkId, + from_id: packageLinkId, + string: { data: { value: "TokenizerHandlerCode" } }, + }, + { + from_id: await deep.id("@deep-foundation/core", "dockerSupportsJs"), + type_id: await deep.id("@deep-foundation/core", "Handler"), + in: { + data: [ + { + type_id: containTypeLinkId, + from_id: packageLinkId, + string: { data: { value: "TokenizerHandler" } }, + }, + { + type_id: await deep.id("@deep-foundation/core", "HandleInsert"), + from_id: payTypeLinkId, + in: { + data: [ + { + type_id: containTypeLinkId, + from_id: packageLinkId, + string: { data: { value: "TokenizerHandle" } }, + }, + ], + }, + }, + ], + }, + }, + ], + }, + + }); + + await deep.insert({ + type_id: await deep.id('@deep-foundation/core', 'Port'), + number: { data: { value: process.env.PAYMENTS_C2B_NOTIFICATION_PORT } }, + in: { data: [ + { + type_id: await deep.id('@deep-foundation/core', 'Contain'), + from_id: deep.linkId, + }, + { + type_id: await deep.id('@deep-foundation/core', 'RouterListening'), + in: { data: { + type_id: await deep.id('@deep-foundation/core', 'Contain'), + from_id: packageLinkId, + } }, + from: { data: { + type_id: await deep.id('@deep-foundation/core', 'Router'), + in: { data: { + type_id: await deep.id('@deep-foundation/core', 'Contain'), + from_id: packageLinkId, + } }, + in: { data: { + type_id: await deep.id('@deep-foundation/core', 'RouterStringUse'), + string: { data: { value: process.env.PAYMENTS_C2B_NOTIFICATION_ROUTE } }, + in: { data: { + type_id: await deep.id('@deep-foundation/core', 'Contain'), + from_id: deep.linkId, + } }, + from: { data: { + type_id: await deep.id('@deep-foundation/core', 'Route'), + in: { data: { + type_id: await deep.id('@deep-foundation/core', 'Contain'), + from_id: packageLinkId, + } }, + out: { data: { + type_id: await deep.id('@deep-foundation/core', 'HandleRoute'), + in: { data: { + type_id: await deep.id('@deep-foundation/core', 'Contain'), + from_id: packageLinkId, + } }, + to: { data: { + type_id: await deep.id('@deep-foundation/core', 'Handler'), + from_id: await deep.id('@deep-foundation/core', 'dockerSupportsJs'), + in: { data: { + type_id: await deep.id('@deep-foundation/core', 'Contain'), + from_id: packageLinkId, + } }, + to: { data: { + type_id: await deep.id('@deep-foundation/core', 'SyncTextFile'), + string: { data: { + value: fs.readFileSync('./deep-packages/payments/tinkoff/tinkoffNotificationHandler.js', {encoding: 'utf-8'}), + } }, + in: { data: { + type_id: await deep.id('@deep-foundation/core', 'Contain'), + from_id: packageLinkId, + } }, + } }, + } }, + } }, + } }, + } }, + } }, + } + ] }, + }) + + + const callTests = async () => { + console.log('callTests-start'); + + const PRICE = 5500; + + const callRealizationTests = async () => { + await callRealizationTestInit(); + await callRealizationTestConfirm(); + await callRealizationTestGetState(); + await callRealizationTestGetCardList(); + await callRealizationTestResend(); + await callRealizationTestCharge(); + await callRealizationTestAddCustomer(); + await callRealizationTestGetCustomer(); + await callRealizationTestRemoveCustomer(); + }; + + const callIntegrationTests = async () => { + + const createdLinkIds = []; + + const { + data: [{ id: tinkoffProviderLinkId }], + } = await deep.insert({ + type_id: tinkoffProviderTypeLinkId, + in: { + data: [ + { + type_id: containTypeLinkId, + from_id: deep.linkId, + }, + ], + }, + }); + console.log({ tinkoffProviderLinkId }); + createdLinkIds.push(tinkoffProviderLinkId); + allCreatedLinkIds.push(tinkoffProviderLinkId); + + const { + data: [{ id: sumProviderLinkId }], + } = await deep.insert({ + type_id: sumProviderTypeLinkId, + in: { + data: [ + { + type_id: containTypeLinkId, + from_id: deep.linkId, + }, + ], + }, + }); + console.log({ sumProviderLinkId }); + createdLinkIds.push(sumProviderLinkId); + allCreatedLinkIds.push(sumProviderLinkId); + + + createdLinkIds.push(storageBusinessLinkId); + allCreatedLinkIds.push(storageBusinessLinkId); + + const { + data: [{ id: terminalPasswordLinkId }], + } = await deep.insert({ + type_id: terminalPasswordTypeLinkId, + string: { data: { value: process.env.PAYMENTS_C2B_TERMINAL_KEY } }, + in: { + data: [ + { + type_id: containTypeLinkId, + from_id: deep.linkId, + }, + { + type_id: usesTerminalPasswordTypeLinkId, + from_id: storageBusinessLinkId + }, + ], + }, + }); + console.log({ terminalPasswordLinkId }); + createdLinkIds.push(terminalPasswordLinkId); + allCreatedLinkIds.push(terminalPasswordLinkId); + + const { + data: [{ id: productTypeLinkId }], + } = await deep.insert({ + type_id: typeTypeLinkId, + from_id: anyTypeLinkId, + to_id: anyTypeLinkId, + in: { + data: [ + { + type_id: containTypeLinkId, + from_id: deep.linkId, + }, + ], + }, + }); + console.log({ productTypeLinkId }); + createdLinkIds.push(productTypeLinkId); + allCreatedLinkIds.push(productTypeLinkId); + + const { + data: [{ id: productLinkId }], + } = await deep.insert({ + type_id: productTypeLinkId, + in: { + data: [ + { + type_id: containTypeLinkId, + from_id: deep.linkId, + }, + ], + }, + }); + console.log({ productLinkId }); + createdLinkIds.push(productLinkId); + allCreatedLinkIds.push(productLinkId); + + const testInit = async () => { + console.log('testInit-start'); + + const createdLinkIds = []; + + const { + data: [{ id: paymentLinkId }], + } = await deep.insert({ + type_id: paymentTypeLinkId, + from_id: deep.linkId, + to_id: storageBusinessLinkId, + in: { + data: [ + { + type_id: containTypeLinkId, + from_id: deep.linkId, + }, + ], + }, + }); + console.log({ paymentLinkId }); + createdLinkIds.push(paymentLinkId); + allCreatedLinkIds.push(paymentLinkId); + + const { + data: [{ id: sumLinkId }], + } = await deep.insert({ + type_id: sumTypeLinkId, + from_id: sumProviderLinkId, + to_id: paymentLinkId, + number: { data: { value: PRICE } }, + in: { + data: [ + { + type_id: containTypeLinkId, + from_id: deep.linkId, + }, + ], + }, + }); + console.log({ sumLinkId }); + createdLinkIds.push(sumLinkId); + allCreatedLinkIds.push(sumLinkId); + + const { + data: [{ id: objectLinkId }], + } = await deep.insert({ + type_id: paymentObjectTypeLinkId, + from_id: paymentLinkId, + to_id: productLinkId, + in: { + data: [ + { + type_id: containTypeLinkId, + from_id: deep.linkId, + }, + ], + }, + }); + console.log({ objectLinkId }); + createdLinkIds.push(objectLinkId); + allCreatedLinkIds.push(objectLinkId); + + const { + data: [{ id: payLinkId }], + } = await deep.insert({ + type_id: payTypeLinkId, + from_id: deep.linkId, + to_id: sumLinkId, + in: { + data: [ + { + type_id: containTypeLinkId, + from_id: deep.linkId, + }, + ], + }, + }); + console.log({ payLinkId }); + createdLinkIds.push(payLinkId); + allCreatedLinkIds.push(payLinkId); + + var urlLinkSelectQuery; + for (let i = 0; i < 10; i++) { + urlLinkSelectQuery = await deep.select({ + type_id: urlTypeLinkId, + to_id: payLinkId, + }); + + if (urlLinkSelectQuery.data.length > 0) { + break; + } + + await sleep(1000); + } + + expect(urlLinkSelectQuery.data.length).to.greaterThan(0); + + createdLinkIds.push(urlLinkSelectQuery.data[0].id); + allCreatedLinkIds.push(urlLinkSelectQuery.data[0].id); + + const createdLinks = (await deep.select(createdLinkIds)).data; + console.log({ createdLinks }); + + console.log('testInit-end'); + + return { + createdLinks + } + }; + + const testFinishAuthorize = async () => { + console.log('testFinishAuthorize-start'); + const { createdLinks } = await testInit(); + + const urlLink = createdLinks.find(link => link.type_id === urlTypeLinkId); + expect(urlLink).to.not.be.equal(undefined) + + const url = urlLink.value.value; + console.log({ url }); + + const browser = await puppeteer.launch({ args: ['--no-sandbox'] }); + const page = await browser.newPage(); + await payInBrowser({ + browser, + page, + url, + }); + + console.log({ createdLinks }); + + console.log('testFinishAuthorize-end'); + + return { + createdLinks + } + }; + + const testConfirm = async () => { + console.log('testConfirm-start'); + const { createdLinks } = await testFinishAuthorize(); + + const createdLinkIds = []; + + const payLink = createdLinks.find(link => link.type_id === payTypeLinkId); + expect(payLink).to.not.be.equal(undefined); + + var payedLinkSelectQuery; + for (let i = 0; i < 10; i++) { + payedLinkSelectQuery = await deep.select({ + type_id: payedTypeLinkId, + to_id: payLink.id + }); + + if (payedLinkSelectQuery.data.length > 0) { + break; + } + + await sleep(1000); + } + + expect(payedLinkSelectQuery.data.length).to.greaterThan(0); + + createdLinkIds.push(payedLinkSelectQuery.data[0].id); + allCreatedLinkIds.push(payedLinkSelectQuery.data[0].id); + + createdLinks.push(...(await deep.select(createdLinkIds)).data); + + console.log({ createdLinks }); + + console.log('testConfirm-end'); + + return { + createdLinks + } + }; + + const callTestAndCleanup = async (testFunction) => { + const { createdLinks } = await testFunction(); + await deep.delete(createdLinks.map((link) => link.id)); + } + + await callTestAndCleanup(testInit); + await callTestAndCleanup(testFinishAuthorize); + await callTestAndCleanup(testConfirm); + + // await deep.delete(createdLinkIds); + }; + + // await callRealizationTests(); + await callIntegrationTests(); + }; + + await callTests(); + + } catch (error) { + // await deep.delete(allCreatedLinkIds); + console.log(error); + // process.exit(1); + } +}; + +installPackage(); \ No newline at end of file diff --git a/deep-packages/payments/tinkoff/removeCustomer.cjs b/deep-packages/payments/tinkoff/removeCustomer.cjs new file mode 100644 index 00000000..2df30f3b --- /dev/null +++ b/deep-packages/payments/tinkoff/removeCustomer.cjs @@ -0,0 +1,33 @@ +const axios = require('axios'); +const { generateToken } = require("./generateToken.cjs"); +const { getError } = require("./getError.cjs"); +const { getUrl } = require("./getUrl.cjs"); + +exports.removeCustomer = async (options) => { + try { + const response = await axios({ + method: 'post', + url: getUrl('RemoveCustomer'), + headers: { + 'Content-Type': 'application/json', + }, + data: { ...options, Token: generateToken(options) }, + }); + + const error = getError(response.data.ErrorCode); + + return { + error, + request: options, + response: response.data, + }; + } catch (error) { + return { + error, + request: options, + response: null, + }; + } + }; + + \ No newline at end of file diff --git a/deep-packages/payments/tinkoff/resend.cjs b/deep-packages/payments/tinkoff/resend.cjs new file mode 100644 index 00000000..d0d68998 --- /dev/null +++ b/deep-packages/payments/tinkoff/resend.cjs @@ -0,0 +1,30 @@ +const axios = require('axios'); +const { generateToken } = require("./generateToken.cjs"); +const { getError } = require("./getError.cjs"); +const { getUrl } = require("./getUrl.cjs"); + +exports.resend = async (options) => { + try { + const response = await axios({ + method: 'post', + url: getUrl('Resend'), + data: { ...options, Token: generateToken(options) }, + }); + + const error = getError(response.data.ErrorCode); + + return { + error, + request: options, + response: response.data, + }; + } catch (error) { + return { + error, + request: options, + response: null, + }; + } + }; + + \ No newline at end of file diff --git a/deep-packages/payments/tinkoff/tests/realization/testAddCustomer.cjs b/deep-packages/payments/tinkoff/tests/realization/testAddCustomer.cjs new file mode 100644 index 00000000..3452a78e --- /dev/null +++ b/deep-packages/payments/tinkoff/tests/realization/testAddCustomer.cjs @@ -0,0 +1,18 @@ +const { expect } = require('chai'); +const { addCustomer } = require('../../addCustomer.cjs'); + +exports.testAddCustomer = async ({customerKey,terminalKey}) => { + console.log('testAddCustomer-start'); + + const addCustomerOptions = { + TerminalKey: terminalKey, + CustomerKey: customerKey, + }; + console.log({ addCustomerOptions }); + + const addCustomerResult = await addCustomer(addCustomerOptions); + console.log({ addCustomerResult }); + + expect(addCustomerResult.error).to.equal(undefined); + console.log('testAddCustomer-end'); + }; \ No newline at end of file diff --git a/deep-packages/payments/tinkoff/tests/realization/testCharge.cjs b/deep-packages/payments/tinkoff/tests/realization/testCharge.cjs new file mode 100644 index 00000000..af15238a --- /dev/null +++ b/deep-packages/payments/tinkoff/tests/realization/testCharge.cjs @@ -0,0 +1,53 @@ +const { expect } = require('chai'); +const { charge } = require('../../charge.cjs'); +const { getCardList } = require('../../getCardList.cjs'); +const { getState } = require('../../getState.cjs'); +const { payInBrowser } = require('../../payInBrowser.cjs'); +const { testInit } = require('./testInit.cjs'); +const puppeteer = require('puppeteer'); + +exports.testCharge = async ({amount, customerKey,orderId,terminalKey, email, phone}) => { + console.log('testCharge-start'); + const browser = await puppeteer.launch({ args: ['--no-sandbox'] }); + const page = await browser.newPage(); + + const initResult = await testInit({amount, customerKey,orderId,terminalKey, email, phone}); + + await payInBrowser({ + browser, + page, + url: initResult.response.PaymentURL, + }); + + const getCardListOptions = { + TerminalKey: terminalKey, + CustomerKey: customerKey, + }; + + const getCardListResult = await getCardList(getCardListOptions); + + expect(getCardListResult.response[0].RebillId).to.have.length.above(0); + + const getStateOptions = { + TerminalKey: terminalKey, + PaymentId: initResult.response.PaymentId, + }; + + const getStateResult = await getState(getStateOptions); + + expect(getStateResult.response.Status).to.equal('AUTHORIZED'); + + const newInitResult = await testInit({amount, customerKey,orderId,terminalKey, email, phone}); + + + const newChargeOptions = { + TerminalKey: terminalKey, + PaymentId: newInitResult.response.PaymentId, + RebillId: Number(getCardListResult.response[0].RebillId), + }; + + const chargeResult = await charge(newChargeOptions); + + expect(chargeResult.error).to.equal(undefined); + console.log('testCharge-end'); + }; \ No newline at end of file diff --git a/deep-packages/payments/tinkoff/tests/realization/testConfirm.cjs b/deep-packages/payments/tinkoff/tests/realization/testConfirm.cjs new file mode 100644 index 00000000..d84b9773 --- /dev/null +++ b/deep-packages/payments/tinkoff/tests/realization/testConfirm.cjs @@ -0,0 +1,30 @@ +const { expect } = require('chai'); +const { confirm } = require('../../confirm.cjs'); +const { payInBrowser } = require('../../payInBrowser.cjs'); +const { testInit } = require('./testInit.cjs'); +const puppeteer = require('puppeteer'); + +exports.testConfirm = async ({amount, customerKey,orderId,terminalKey, email, phone}) => { + const browser = await puppeteer.launch({ args: ['--no-sandbox'] }); + const page = await browser.newPage(); + + const initResult = await testInit({amount, customerKey,orderId,terminalKey, email, phone}); + + await payInBrowser({ + browser, + page, + url: initResult.response.PaymentURL, + }); + + const confirmOptions = { + TerminalKey: terminalKey, + PaymentId: initResult.response.PaymentId, + }; + + const confirmResult = await confirm(confirmOptions); + + expect(confirmResult.error).to.equal(undefined); + expect(confirmResult.response.Status).to.equal('CONFIRMED'); + + return confirmResult; + }; \ No newline at end of file diff --git a/deep-packages/payments/tinkoff/tests/realization/testGetCardList.cjs b/deep-packages/payments/tinkoff/tests/realization/testGetCardList.cjs new file mode 100644 index 00000000..0eea14cd --- /dev/null +++ b/deep-packages/payments/tinkoff/tests/realization/testGetCardList.cjs @@ -0,0 +1,25 @@ +const { expect } = require('chai'); +const { payInBrowser } = require('../../payInBrowser.cjs'); +const { testInit } = require('./testInit.cjs'); +const puppeteer = require('puppeteer'); + +exports.testGetCardList = async ({amount, customerKey,orderId,terminalKey, email, phone}) => { + const initResult = await testInit({amount, customerKey,orderId,terminalKey, email, phone}); + + const browser = await puppeteer.launch({ args: ['--no-sandbox'] }); + const page = await browser.newPage(); + await payInBrowser({ + browser, + page, + url: initResult.response.PaymentURL, + }); + + const getCardListOptions = { + TerminalKey: terminalKey, + CustomerKey: customerKey, + }; + + const getCardListResult = await getCardList(getCardListOptions); + + expect(getCardListResult.error).to.equal(undefined); + }; \ No newline at end of file diff --git a/deep-packages/payments/tinkoff/tests/realization/testGetCustomer.cjs b/deep-packages/payments/tinkoff/tests/realization/testGetCustomer.cjs new file mode 100644 index 00000000..5da286c2 --- /dev/null +++ b/deep-packages/payments/tinkoff/tests/realization/testGetCustomer.cjs @@ -0,0 +1,29 @@ +const {testAddCustomer} = require('./testAddCustomer.cjs'); +const {getCustomer} = require('./../../getCustomer.cjs'); + +exports.testGetCustomer = async ({customerKey,terminalKey, phone}) => { + console.log('testGetCustomer-start'); + + const customerOptions = { + TerminalKey: terminalKey, + CustomerKey: customerKey, + }; + + const addCustomerDataOptions = { + ...customerOptions, + Phone: phone, + }; + + const addResult = await testAddCustomer(addCustomerDataOptions); + + expect(addResult.error).to.equal(undefined); + + const getResult = await getCustomer(customerOptions); + + expect(getResult.error).to.equal(undefined); + expect(getResult.response.Phone).to.equal( + phone + ); + + console.log('testGetCustomer-end'); + }; \ No newline at end of file diff --git a/deep-packages/payments/tinkoff/tests/realization/testGetState.cjs b/deep-packages/payments/tinkoff/tests/realization/testGetState.cjs new file mode 100644 index 00000000..04704ddf --- /dev/null +++ b/deep-packages/payments/tinkoff/tests/realization/testGetState.cjs @@ -0,0 +1,25 @@ +const { expect } = require('chai'); +const { payInBrowser } = require('../../payInBrowser.cjs'); +const { testInit } = require('./testInit.cjs'); +const puppeteer = require('puppeteer'); + +exports.testGetState = async ({amount, customerKey,orderId,terminalKey, email, phone}) => { + const initResult = await testInit({amount, customerKey,orderId,terminalKey, email, phone}); + + const browser = await puppeteer.launch({ args: ['--no-sandbox'] }); + const page = await browser.newPage(); + await payInBrowser({ + browser, + page, + url: initResult.response.PaymentURL, + }); + + const getStateOptions = { + TerminalKey: terminalKey, + PaymentId: initResult.response.PaymentId, + }; + + const getStateResult = await getState(getStateOptions); + + expect(getStateResult.error).to.equal(undefined); + }; \ No newline at end of file diff --git a/deep-packages/payments/tinkoff/tests/realization/testInit.cjs b/deep-packages/payments/tinkoff/tests/realization/testInit.cjs new file mode 100644 index 00000000..50f83e68 --- /dev/null +++ b/deep-packages/payments/tinkoff/tests/realization/testInit.cjs @@ -0,0 +1,37 @@ +const { expect } = require('chai'); + +exports.testInit = async ({amount, customerKey,orderId,terminalKey, email, phone}) => { + const initOptions = { + TerminalKey: terminalKey, + OrderId: orderId, + Amount: amount, + Description: 'Test shopping', + CustomerKey: customerKey, + Language: 'ru', + Recurrent: 'Y', + DATA: { + Email: email, + Phone: phone, + }, + // Receipt: { + // Items: [{ + // Name: 'Test item', + // Price: PRICE, + // Quantity: 1, + // Amount: PRICE, + // PaymentMethod: 'prepayment', + // PaymentObject: 'service', + // Tax: 'none', + // }], + // Email: process.env.PAYMENTS_C2B_EMAIL, + // Phone: process.env.PAYMENTS_C2B_PHONE, + // Taxation: 'usn_income', + // }, + }; + + const initResult = await init(initOptions); + + expect(initResult.error).to.equal(undefined); + + return initResult; + }; \ No newline at end of file diff --git a/deep-packages/payments/tinkoff/tests/realization/testRemoveCustomer.cjs b/deep-packages/payments/tinkoff/tests/realization/testRemoveCustomer.cjs new file mode 100644 index 00000000..051524d5 --- /dev/null +++ b/deep-packages/payments/tinkoff/tests/realization/testRemoveCustomer.cjs @@ -0,0 +1,26 @@ +const { removeCustomer } = require("../../removeCustomer.cjs"); +const { testAddCustomer } = require("./testAddCustomer.cjs"); + +exports.testRemoveCustomer = async ({customerKey,terminalKey, phone}) => { + console.log('testRemoveCustomer-start'); + + const removeCustomerData = { + TerminalKey: terminalKey, + CustomerKey: customerKey, + }; + + const newAddCustomerData = { + ...removeCustomerData, + Phone: phone, + }; + + const addResult = await testAddCustomer(newAddCustomerData); + + expect(addResult.error).to.equal(undefined); + + const removeResult = await removeCustomer(removeCustomerData); + + expect(removeResult.error).to.equal(undefined); + + console.log('testRemoveCustomer-end'); + }; \ No newline at end of file diff --git a/deep-packages/payments/tinkoff/tests/realization/testResend.cjs b/deep-packages/payments/tinkoff/tests/realization/testResend.cjs new file mode 100644 index 00000000..5a70c541 --- /dev/null +++ b/deep-packages/payments/tinkoff/tests/realization/testResend.cjs @@ -0,0 +1,16 @@ +const { expect } = require('chai'); +const {resend} = require('../../resend.cjs'); + +exports.testResend = async ({terminalKey}) => { + console.log('testResend-start'); + const resendOptions = { + TerminalKey: terminalKey, + }; + console.log({ resendOptions }); + + const resendResult = await resend(resendOptions); + console.log({ resendResult }); + + expect(resendResult.error).to.equal(undefined); + console.log('testResend-end'); + }; \ No newline at end of file diff --git a/deep-packages/payments/tinkoff/tinkoffNotificationHandler.js b/deep-packages/payments/tinkoff/tinkoffNotificationHandler.js new file mode 100644 index 00000000..22235179 --- /dev/null +++ b/deep-packages/payments/tinkoff/tinkoffNotificationHandler.js @@ -0,0 +1,223 @@ + +async ( + req, + res, + next, + { deep, require, gql } +) => { + + const crypto = require('crypto'); + const axios = require('axios'); + + const tinkoffApiUrlTypeLinkId = await deep.id("@deep-foundation/payments-tinkoff-c2b", "TinkoffApiUrl"); + const { data: [tinkoffApiUrlLinkId] } = await deep.select({ + type_id: tinkoffApiUrlTypeLinkId + }); + if (!tinkoffApiUrlLinkId) { + throw new Error(`A link with type ##${tinkoffApiUrlTypeLinkId} is not found`); + } + if (!tinkoffApiUrlLinkId.value?.value) { + throw new Error(`##${tinkoffApiUrlLinkId} must have a value`); + } + const tinkoffApiUrl = tinkoffApiUrlLinkId.value.value; + + const allowedStatuses = ["AUTHORIZED", "CONFIRMED"]; + if (!allowedStatuses.includes(req.body.Status)) { + return next(); + } + + const tinkoffProviderTypeLinkId = await deep.id("@deep-foundation/payments-tinkoff-c2b", "TinkoffProvider"); + const { data: [tinkoffProviderLink] } = await deep.select({ + type_id: tinkoffProviderTypeLinkId + }); + if (!tinkoffProviderLink) { + throw new Error(`A link with type link id ##${tinkoffProviderTypeLinkId} is not found.`) + } + + const paymentTypeLinkId = await deep.id("@deep-foundation/payments-tinkoff-c2b", "Payment"); + const { data: [paymentLink] } = await deep.select({ + type_id: paymentTypeLinkId, + object: { value: { _contains: { bankPaymentId: parseInt(req.body.PaymentId) } } } + }); + if (!paymentLink) { throw new Error(`A link with type ##${paymentTypeLinkId} associated with the bank payment id ${req.body.PaymentId} is not found.`); } + + const paymentTreeId = await deep.id("@deep-foundation/payments-tinkoff-c2b", "paymentTree"); + const { data: linksDownToPaymentMp } = await deep.select({ + up: { + parent_id: { _eq: paymentLink.id }, + tree_id: { _eq: paymentTreeId } + } + }); + if (linksDownToPaymentMp.length === 0) { + throw new Error(`There is no links down to ##${paymentLink.id} materialized path`); + } + + const payTypeLinkId = await deep.id("@deep-foundation/payments-tinkoff-c2b", "Pay"); + const payLink = linksDownToPaymentMp.find(link => link.type_id === payTypeLinkId); + console.log({ payLink }); + if (!payLink) { throw new Error(`A link with type ##${payTypeLinkId} associated with payment link ${paymentLink} is not found`) } + + console.log({ tinkoffApiUrl }) + console.log(`${tinkoffApiUrl}/Confirm`) + + const { data: [storageBusinessLink] } = await deep.select({ + id: paymentLink.to_id + }); + console.log({storageBusinessLink}) + + const terminalKeyTypeLinkId = await deep.id("@deep-foundation/payments-tinkoff-c2b", "TerminalKey"); + const usesTerminalKeyTypeLinkId = await deep.id("@deep-foundation/payments-tinkoff-c2b", "UsesTerminalKey"); + const { data: [terminalKeyLink] } = await deep.select({ + type_id: terminalKeyTypeLinkId, + in: { + type_id: usesTerminalKeyTypeLinkId, + from_id: storageBusinessLink.id + } + }); + console.log({terminalKeyLink}) + if (!terminalKeyLink) { + throw new Error(`A link with type ##${terminalKeyTypeLinkId} is not found`); + } + if(!terminalKeyLink.value?.value) { + throw new Error(`##${terminalKeyLink.id} must have a value`); + } + const terminalKey = terminalKeyLink.value.value; + + const terminalPasswordTypeLinkId = await deep.id("@deep-foundation/payments-tinkoff-c2b", "TerminalPassword"); + const usesTerminalPasswordTypeLinkId = await deep.id("@deep-foundation/payments-tinkoff-c2b", "UsesTerminalPassword"); + const { data: [terminalPasswordLink] } = await deep.select({ + type_id: terminalPasswordTypeLinkId, + in: { + type_id: usesTerminalPasswordTypeLinkId, + from_id: storageBusinessLink.id + } + }); + console.log({terminalPasswordLink}) + if (!terminalPasswordLink) { + throw new Error(`A link with type ##${terminalPasswordTypeLinkId} is not found`); + } + if(!terminalPasswordLink.value?.value) { + throw new Error(`##${terminalPasswordLink.id} must have a value`); + } + const terminalPassword = terminalPasswordLink.value.value; + + const generateToken = (data) => { + const { Receipt, DATA, Shops, ...restData } = data; + const dataWithPassword = { + Password: terminalPassword, + ...restData, + }; + console.log({ dataWithPassword }); + + const dataString = Object.keys(dataWithPassword) + .sort((a, b) => a.localeCompare(b)) + .map((key) => dataWithPassword[key]) + .reduce((acc, item) => `${acc}${item}`, ''); + console.log({ dataString }); + const hash = crypto.createHash('sha256').update(dataString).digest('hex'); + console.log({ hash }); + return hash; + }; + + if (req.body.Status === 'AUTHORIZED') { + const confirm = async (options) => { + try { + const response = await axios({ + method: 'post', + url: `${tinkoffApiUrl}/Confirm`, + data: { ...options, Token: generateToken(options) }, + }); + + const error = response.data.Details; + + return { + error, + request: options, + response: response.data, + }; + } catch (error) { + return { + error, + request: options, + response: null, + }; + } + }; + + const confirmOptions = { + TerminalKey: terminalKey, + PaymentId: req.body.PaymentId, + Amount: req.body.Amount, + // Receipt: req.body.Receipt, + }; + console.log({ confirmOptions }); + + const confirmResult = await confirm(confirmOptions); + console.log({ confirmResult }); + + if (confirmResult.error) { + throw new Error(confirmResult.error); + } + + return confirmResult; + } else if (req.body.Status === 'CONFIRMED') { + + const payedTypeLinkId = await deep.id("@deep-foundation/payments-tinkoff-c2b", "Payed"); + const { data: [insertedPayedLink] } = await deep.insert({ + type_id: payedTypeLinkId, + from_id: tinkoffProviderLink.id, + to_id: payLink.id, + }); + if (payedLinkInsertQuery.error) { + throw new Error(payedLinkInsertQuery.error.message); + } + if (!insertedPayedLink) { + throw new Error(`Unable to insert a link with type ##${payedTypeLinkId}`) + } + + const storageClientTypeLinkId = await deep.id("@deep-foundation/payments-tinkoff-c2b", "StorageClient"); + const incomeTypeLinkId = await deep.id("@deep-foundation/payments-tinkoff-c2b", "Income"); + const {data: [storageClientLink]} = await deep.select({ + type_id: storageClientTypeLinkId, + number: { value: req.body.CardId } + }); + if(!storageClientLink) { + await deep.insert({ + type_id: storageClientTypeLinkId, + number: { data: { value: req.body.CardId } }, + in: { + data: [{ + type_id: containTypeLinkId, + from_id: triggeredByLinkId + }, + { + type_id: incomeTypeLinkId, + from_id: paymentLink.id, + }] + }, + out: { + data: { + type_id: storageClientTitleTypeLinkId, + from_id: storageClientLinkId, // TODO how to make loop-link without doing multiple queries? + to_id: storageClientLinkId, + string: { data: { value: req.body.Pan } }, + in: { + data: { + type_id: containTypeLinkId, + from_id: triggeredByLinkId + } + } + } + } + }); + } else { + await deep.insert({ + type_id: incomeTypeLinkId, + from_id: paymentLink.id, + to_id: storageClientLink.id, + }); + } + + } + res.send('ok'); +}; diff --git a/deep-packages/sleep.cjs b/deep-packages/sleep.cjs new file mode 100644 index 00000000..2363f045 --- /dev/null +++ b/deep-packages/sleep.cjs @@ -0,0 +1,2 @@ +exports.sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); + diff --git a/package-lock.json b/package-lock.json index 5f8cec23..085c442e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -299,6 +299,15 @@ "requires": { "follow-redirects": "^1.14.0" } + }, + "react": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react/-/react-17.0.2.tgz", + "integrity": "sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==", + "requires": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1" + } } } }, @@ -318,6 +327,11 @@ "typescript": "^4.9.4" }, "dependencies": { + "graphql": { + "version": "15.8.0", + "resolved": "https://registry.npmjs.org/graphql/-/graphql-15.8.0.tgz", + "integrity": "sha512-5gghUc24tP9HRznNpV2+FIoq3xKkj5dTQqf4v0CpdPbFVwFkWoxOM+o+2OC9ZSvjEMTjfmG9QT+gcvggTwW1zw==" + }, "typescript": { "version": "4.9.5", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", @@ -978,6 +992,15 @@ "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz", "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==" }, + "@types/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-Cn6WYCm0tXv8p6k+A8PvbDG763EDpBoTzHdA+Q/MF6H3sapGjCm9NzoaJncJS9tUKSuCoDs9XHxYYsQDgxR6kw==", + "optional": true, + "requires": { + "@types/node": "*" + } + }, "@types/zen-observable": { "version": "0.8.3", "resolved": "https://registry.npmjs.org/@types/zen-observable/-/zen-observable-0.8.3.tgz", @@ -1030,6 +1053,14 @@ "negotiator": "0.6.3" } }, + "agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "requires": { + "debug": "4" + } + }, "aggregate-error": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", @@ -1414,6 +1445,20 @@ "sha.js": "^2.4.11", "subscriptions-transport-ws": "^0.9.19", "uuid": "^8.0.0" + }, + "dependencies": { + "subscriptions-transport-ws": { + "version": "0.9.19", + "resolved": "https://registry.npmjs.org/subscriptions-transport-ws/-/subscriptions-transport-ws-0.9.19.tgz", + "integrity": "sha512-dxdemxFFB0ppCLg10FTtRqH/31FNRL1y1BQv8209MK5I4CwALb7iihQg+7p65lFcIl8MHatINWBLOqpgU4Kyyw==", + "requires": { + "backo2": "^1.0.2", + "eventemitter3": "^3.1.0", + "iterall": "^1.2.1", + "symbol-observable": "^1.0.4", + "ws": "^5.2.0 || ^6.0.0 || ^7.0.0" + } + } } }, "apollo-server-express": { @@ -1438,7 +1483,26 @@ "parseurl": "^1.3.2", "subscriptions-transport-ws": "^0.9.19", "type-is": "^1.6.16" + }, + "dependencies": { + "subscriptions-transport-ws": { + "version": "0.9.19", + "resolved": "https://registry.npmjs.org/subscriptions-transport-ws/-/subscriptions-transport-ws-0.9.19.tgz", + "integrity": "sha512-dxdemxFFB0ppCLg10FTtRqH/31FNRL1y1BQv8209MK5I4CwALb7iihQg+7p65lFcIl8MHatINWBLOqpgU4Kyyw==", + "requires": { + "backo2": "^1.0.2", + "eventemitter3": "^3.1.0", + "iterall": "^1.2.1", + "symbol-observable": "^1.0.4", + "ws": "^5.2.0 || ^6.0.0 || ^7.0.0" + } + } } + }, + "symbol-observable": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz", + "integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==" } } }, @@ -1725,7 +1789,26 @@ "sha.js": "^2.4.11", "subscriptions-transport-ws": "^0.9.19", "uuid": "^8.0.0" + }, + "dependencies": { + "subscriptions-transport-ws": { + "version": "0.9.19", + "resolved": "https://registry.npmjs.org/subscriptions-transport-ws/-/subscriptions-transport-ws-0.9.19.tgz", + "integrity": "sha512-dxdemxFFB0ppCLg10FTtRqH/31FNRL1y1BQv8209MK5I4CwALb7iihQg+7p65lFcIl8MHatINWBLOqpgU4Kyyw==", + "requires": { + "backo2": "^1.0.2", + "eventemitter3": "^3.1.0", + "iterall": "^1.2.1", + "symbol-observable": "^1.0.4", + "ws": "^5.2.0 || ^6.0.0 || ^7.0.0" + } + } } + }, + "symbol-observable": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz", + "integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==" } } }, @@ -2309,6 +2392,37 @@ "file-uri-to-path": "1.0.0" } }, + "bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "requires": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + }, + "dependencies": { + "buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } + } + }, "body-parser": { "version": "1.20.2", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", @@ -2423,6 +2537,11 @@ "ieee754": "^1.2.1" } }, + "buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==" + }, "buffer-equal": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-1.0.1.tgz", @@ -2559,6 +2678,11 @@ "readdirp": "~3.6.0" } }, + "chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" + }, "ci-info": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", @@ -2890,6 +3014,14 @@ "cross-spawn": "^7.0.1" } }, + "cross-fetch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.5.tgz", + "integrity": "sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==", + "requires": { + "node-fetch": "2.6.7" + } + }, "cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -3150,6 +3282,11 @@ "repeating": "^2.0.0" } }, + "devtools-protocol": { + "version": "0.0.1019158", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1019158.tgz", + "integrity": "sha512-wvq+KscQ7/6spEV7czhnZc9RM/woz1AY+/Vpd8/h2HFMwJSdTliu7f/yr1A6vDdJfKICZsShqsYpEQbdhg8AFQ==" + }, "dicer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/dicer/-/dicer-0.3.0.tgz", @@ -3181,6 +3318,11 @@ "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.6.0.tgz", "integrity": "sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==" }, + "dotenv-expand": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-8.0.3.tgz", + "integrity": "sha512-SErOMvge0ZUyWd5B0NXMQlDkN+8r+HhVUsxgOO7IoPDOdDRD2JjExpN6y3KnFR66jsJMwSn1pqIivhU5rcJiNg==" + }, "duplexify": { "version": "3.7.1", "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", @@ -3677,6 +3819,36 @@ } } }, + "extract-zip": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", + "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", + "requires": { + "@types/yauzl": "^2.9.1", + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" + }, + "dependencies": { + "get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "requires": { + "pump": "^3.0.0" + } + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + } + } + }, "extsprintf": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", @@ -3733,6 +3905,14 @@ "reusify": "^1.0.4" } }, + "fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", + "requires": { + "pend": "~1.2.0" + } + }, "file-uri-to-path": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", @@ -3995,6 +4175,11 @@ "resolved": "https://registry.npmjs.org/fs-capacitor/-/fs-capacitor-2.0.4.tgz", "integrity": "sha512-8S4f4WsCryNw2mJJchi46YgB6CR5Ze+4L1h8ewl9tEpL4SJ3ZO+c/bS4BWhB8bK+O3TMqhuZarTitd0S0eh2pA==" }, + "fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" + }, "fs-mkdirp-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs-mkdirp-stream/-/fs-mkdirp-stream-1.0.0.tgz", @@ -4461,9 +4646,9 @@ "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==" }, "graphql": { - "version": "15.8.0", - "resolved": "https://registry.npmjs.org/graphql/-/graphql-15.8.0.tgz", - "integrity": "sha512-5gghUc24tP9HRznNpV2+FIoq3xKkj5dTQqf4v0CpdPbFVwFkWoxOM+o+2OC9ZSvjEMTjfmG9QT+gcvggTwW1zw==" + "version": "16.6.0", + "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.6.0.tgz", + "integrity": "sha512-KPIBPDlW7NxrbT/eh4qPXz5FiFdL5UbaA0XUNz2Rp3Z3hqBSkbj0GVjwFDztsWVauZUWsbKHgMg++sk8UX0bkw==" }, "graphql-extensions": { "version": "0.16.0", @@ -4913,6 +5098,15 @@ "sshpk": "^1.7.0" } }, + "https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "requires": { + "agent-base": "6", + "debug": "4" + } + }, "human-signals": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", @@ -6141,6 +6335,11 @@ "minimist": "^1.2.6" } }, + "mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==" + }, "moesif-nodejs": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/moesif-nodejs/-/moesif-nodejs-3.3.2.tgz", @@ -8637,6 +8836,11 @@ "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==" }, + "pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==" + }, "performance-now": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", @@ -8739,6 +8943,11 @@ "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" }, + "progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==" + }, "prompt": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/prompt/-/prompt-1.3.0.tgz", @@ -8770,6 +8979,11 @@ "ipaddr.js": "1.9.1" } }, + "proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, "pseudomap": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", @@ -8809,6 +9023,31 @@ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==" }, + "puppeteer": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-16.2.0.tgz", + "integrity": "sha512-7Au6iC98rS6WEAD110V4Bxd0iIbqoFtzz9XzkG1BSofidS1VAJ881E1+GFR7Xn2Yea0hbj8n0ErzRyseMp1Ctg==", + "requires": { + "cross-fetch": "3.1.5", + "debug": "4.3.4", + "devtools-protocol": "0.0.1019158", + "extract-zip": "2.0.1", + "https-proxy-agent": "5.0.1", + "progress": "2.0.3", + "proxy-from-env": "1.1.0", + "rimraf": "3.0.2", + "tar-fs": "2.1.1", + "unbzip2-stream": "1.4.3", + "ws": "8.8.1" + }, + "dependencies": { + "ws": { + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.8.1.tgz", + "integrity": "sha512-bGy2JzvzkPowEJV++hF07hAD6niYSr0JzBNo/J29WsB57A2r7Wlc1UFcTR9IzrPvuNVO4B8LGqF8qcpsVOhJCA==" + } + } + }, "qs": { "version": "6.11.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", @@ -8974,6 +9213,14 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } } } }, @@ -9759,18 +10006,11 @@ } }, "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", "requires": { - "safe-buffer": "~5.1.0" - }, - "dependencies": { - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - } + "safe-buffer": "~5.2.0" } }, "strip-ansi": { @@ -9800,9 +10040,9 @@ "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==" }, "subscriptions-transport-ws": { - "version": "0.9.19", - "resolved": "https://registry.npmjs.org/subscriptions-transport-ws/-/subscriptions-transport-ws-0.9.19.tgz", - "integrity": "sha512-dxdemxFFB0ppCLg10FTtRqH/31FNRL1y1BQv8209MK5I4CwALb7iihQg+7p65lFcIl8MHatINWBLOqpgU4Kyyw==", + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/subscriptions-transport-ws/-/subscriptions-transport-ws-0.11.0.tgz", + "integrity": "sha512-8D4C6DIH5tGiAIpp5I0wD/xRlNiZAPGHygzCe7VzyzUoxHtawzjNAY9SUTXU05/EY2NMY9/9GF0ycizkXr1CWQ==", "requires": { "backo2": "^1.0.2", "eventemitter3": "^3.1.0", @@ -9842,6 +10082,57 @@ "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-4.0.0.tgz", "integrity": "sha512-b19dMThMV4HVFynSAM1++gBHAbk2Tc/osgLIBZMKsyqh34jb2e8Os7T6ZW/Bt3pJFdBTd2JwAnAAEQV7rSNvcQ==" }, + "tar-fs": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", + "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", + "requires": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + }, + "dependencies": { + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + } + } + }, + "tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "requires": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "dependencies": { + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } + } + }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==" + }, "through2": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", @@ -10100,6 +10391,26 @@ "which-boxed-primitive": "^1.0.2" } }, + "unbzip2-stream": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz", + "integrity": "sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==", + "requires": { + "buffer": "^5.2.1", + "through": "^2.3.8" + }, + "dependencies": { + "buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + } + } + }, "unc-path-regex": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz", @@ -10591,6 +10902,15 @@ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==" }, + "yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", + "requires": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, "yn": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", diff --git a/package.json b/package.json index e7cb9809..9918cb3d 100644 --- a/package.json +++ b/package.json @@ -76,21 +76,25 @@ "url": "git+https://github.com/deep-foundation/dev.git" }, "type": "module", - "devDependencies": {}, "dependencies": { "@deep-foundation/deeplinks": "0.0.113", "@types/gulp": "^4.0.8", "concurrently": "^5.3.0", "cross-env": "^7.0.3", "del": "^6.0.0", + "dotenv-expand": "^8.0.3", + "graphql": "^16.6.0", "gulp": "^4.0.2", "gulp-run-command": "^0.0.10", "i": "^0.3.6", "minimist": "^1.2.5", "npm": "^7.5.4", "prompt": "^1.2.0", + "puppeteer": "^16.2.0", + "react": "^18.2.0", "rimraf": "^3.0.2", "simple-git": "^2.45.0", + "subscriptions-transport-ws": "^0.11.0", "ts-node": "^9.1.1", "typescript": "^4.4.3" } diff --git a/payments-tinkoff-c2b-cancelling.cjs b/payments-tinkoff-c2b-cancelling.cjs new file mode 100644 index 00000000..08fe8b84 --- /dev/null +++ b/payments-tinkoff-c2b-cancelling.cjs @@ -0,0 +1,1353 @@ +require('react'); +require('graphql'); +require('lodash'); +require('subscriptions-transport-ws'); +const dotenv = require('dotenv'); +const dotenvExpand = require('dotenv-expand'); +const { generateApolloClient } = require('@deep-foundation/hasura/client'); +const { DeepClient } = require('@deep-foundation/deeplinks/imports/client'); +const { + minilinks, + Link, +} = require('@deep-foundation/deeplinks/imports/minilinks'); +const puppeteer = require('puppeteer'); +const crypto = require('crypto'); +const axios = require('axios'); +const uniqid = require('uniqid'); +const { expect } = require('chai'); + +var myEnv = dotenv.config(); +dotenvExpand.expand(myEnv); + +const { payInBrowser } = require("./deep-packages/payments/tinkoff/payInBrowser.cjs"); +const { getError } = require("./deep-packages/payments/tinkoff/getError.cjs"); +const { init } = require("./deep-packages/payments/tinkoff/init.cjs"); +const { cancel } = require("./deep-packages/payments/tinkoff/cancel.cjs"); +const { confirm } = require("./deep-packages/payments/tinkoff/confirm.cjs"); +const { handlersDependencies } = require("./deep-packages/payments/tinkoff/handlersDependencies.cjs"); +const { insertTinkoffCancellingPayInsertHandler } = require("./deep-packages/payments/tinkoff/cancelling/insertTinkoffCancellingPayInsertHandler.cjs"); +const { insertTinkoffCancellingNotificationHandler } = require("./deep-packages/payments/tinkoff/cancelling/insertTinkoffCancellingNotificationHandler.cjs"); +const fs = require('fs') + +console.log("Installing @deep-foundation/payments-tinkoff-c2b-cancelling package"); + +const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); + +const requiredEnvVariableNames = [ + "PAYMENTS_C2B_TERMINAL_KEY", + "PAYMENTS_C2B_TERMINAL_PASSWORD", + "PAYMENTS_C2B_URL", + "PAYMENTS_C2B_NOTIFICATION_ROUTE", + "PAYMENTS_C2B_NOTIFICATION_PORT", + "PAYMENTS_C2B_NOTIFICATION_URL", + "PAYMENTS_C2B_CANCELLING_NOTIFICATION_PORT", + "PAYMENTS_C2B_CANCELLING_NOTIFICATION_URL", + "PAYMENTS_C2B_CARD_NUMBER_SUCCESS", + "PAYMENTS_C2B_CARD_EXPDATE", + "PAYMENTS_C2B_CARD_CVC", + "PAYMENTS_C2B_PHONE_NUMBER", + "PAYMENTS_C2B_EMAIL", + ]; + + for (const requiredEnvVariableName of requiredEnvVariableNames) { + if(!process.env[requiredEnvVariableName]) { + throw new Error(`The environment variable ${requiredEnvVariableName} is required. All the required environment variables: \n${requiredEnvVariableNames.join("\n")}`); + } + } + +const allCreatedLinkIds = []; + +const installPackage = async () => { + const apolloClient = generateApolloClient({ + path: process.env.NEXT_PUBLIC_GQL_PATH || '', // <<= HERE PATH TO UPDATE + ssl: !!~process.env.NEXT_PUBLIC_GQL_PATH.indexOf('localhost') + ? false + : true, + // admin token in prealpha deep secret key + // token: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwczovL2hhc3VyYS5pby9qd3QvY2xhaW1zIjp7IngtaGFzdXJhLWFsbG93ZWQtcm9sZXMiOlsibGluayJdLCJ4LWhhc3VyYS1kZWZhdWx0LXJvbGUiOiJsaW5rIiwieC1oYXN1cmEtdXNlci1pZCI6IjI2MiJ9LCJpYXQiOjE2NTYxMzYyMTl9.dmyWwtQu9GLdS7ClSLxcXgQiKxmaG-JPDjQVxRXOpxs', + }); + + const unloginedDeep = new DeepClient({ apolloClient }); + const guest = await unloginedDeep.guest(); + const guestDeep = new DeepClient({ deep: unloginedDeep, ...guest }); + const admin = await guestDeep.login({ + linkId: await guestDeep.id('deep', 'admin'), + }); + const deep = new DeepClient({ deep: guestDeep, ...admin }); + + try { + + const userTypeLinkId = await deep.id('@deep-foundation/core', 'User'); + const typeTypeLinkId = await deep.id('@deep-foundation/core', 'Type'); + const anyTypeLinkId = await deep.id('@deep-foundation/core', 'Any'); + const joinTypeLinkId = await deep.id('@deep-foundation/core', 'Join'); + const containTypeLinkId = await deep.id('@deep-foundation/core', 'Contain'); + const Value = await deep.id('@deep-foundation/core', 'Value'); + const String = await deep.id('@deep-foundation/core', 'String'); + const packageTypeLinkId = await deep.id('@deep-foundation/core', 'Package'); + + const HandleDelete = await deep.id('@deep-foundation/core', 'HandleDelete'); + const syncTextFileTypeLinkId = await deep.id('@deep-foundation/core', 'SyncTextFile'); + const dockerSupportsJs = await deep.id( + '@deep-foundation/core', + 'dockerSupportsJs' + ); + const handleInsertTypeLinkId = await deep.id('@deep-foundation/core', 'HandleInsert'); + const portTypeLinkId = await deep.id('@deep-foundation/core', 'Port'); + const routerListeningTypeLinkId = await deep.id('@deep-foundation/core', 'RouterListening'); + const routerTypeLinkId = await deep.id('@deep-foundation/core', 'Router'); + const routerStringUseTypeLinkId = await deep.id( + '@deep-foundation/core', + 'RouterStringUse' + ); + const routeTypeLinkId = await deep.id('@deep-foundation/core', 'Route'); + const handleRouteTypeLinkId = await deep.id( + '@deep-foundation/core', + 'HandleRoute' + ); + const handlerTypeLinkId = await deep.id( + '@deep-foundation/core', + 'Handler' + ); + const dockerSupportsJsId = await deep.id( + '@deep-foundation/core', + 'dockerSupportsJs' + ); + + const Tree = await deep.id('@deep-foundation/core', 'Tree'); + const TreeIncludeNode = await deep.id( + '@deep-foundation/core', + 'TreeIncludeNode' + ); + const treeIncludeUpTypeLinkId = await deep.id('@deep-foundation/core', 'TreeIncludeUp'); + const TreeIncludeDown = await deep.id( + '@deep-foundation/core', + 'TreeIncludeDown' + ); + + const Rule = await deep.id('@deep-foundation/core', 'Rule'); + const RuleSubject = await deep.id('@deep-foundation/core', 'RuleSubject'); + const RuleObject = await deep.id('@deep-foundation/core', 'RuleObject'); + const RuleAction = await deep.id('@deep-foundation/core', 'RuleAction'); + const Selector = await deep.id('@deep-foundation/core', 'Selector'); + const SelectorInclude = await deep.id( + '@deep-foundation/core', + 'SelectorInclude' + ); + const SelectorExclude = await deep.id( + '@deep-foundation/core', + 'SelectorExclude' + ); + const SelectorTree = await deep.id('@deep-foundation/core', 'SelectorTree'); + const containTree = await deep.id('@deep-foundation/core', 'containTree'); + const AllowInsertType = await deep.id( + '@deep-foundation/core', + 'AllowInsertType' + ); + const AllowDeleteType = await deep.id( + '@deep-foundation/core', + 'AllowDeleteType' + ); + const SelectorFilter = await deep.id( + '@deep-foundation/core', + 'SelectorFilter' + ); + const Query = await deep.id('@deep-foundation/core', 'Query'); + const usersId = await deep.id('deep', 'users'); + + const { + data: [{ id: packageLinkId }], + } = await deep.insert({ + type_id: packageTypeLinkId, + string: { data: { value: '@deep-foundation/payments-tinkoff-c2b-cancelling' } }, + in: { + data: [ + { + type_id: containTypeLinkId, + from_id: deep.linkId, + }, + ], + }, + out: { + data: [ + { + type_id: joinTypeLinkId, + to_id: await deep.id('deep', 'users', 'packages'), + }, + { + type_id: joinTypeLinkId, + to_id: await deep.id('deep', 'admin'), + }, + ], + }, + }); + + console.log({ packageId: packageLinkId }); + + const sumProviderTypeLinkId = await deep.id("@deep-foundation/payments-tinkoff-c2b", "SumProvider"); + console.log({ sumProviderTypeLinkId }); + + const tinkoffProviderTypeLinkId = await deep.id("@deep-foundation/payments-tinkoff-c2b", "TinkoffProvider"); + console.log({ tinkoffProviderTypeLinkId }); + + const paymentTypeLinkId = await deep.id("@deep-foundation/payments-tinkoff-c2b", "Payment"); + console.log({ paymentTypeLinkId }); + + const objectTypeLinkId = await deep.id("@deep-foundation/payments-tinkoff-c2b", "Object"); + console.log({ objectTypeLinkId }); + + const sumTypeid = await deep.id("@deep-foundation/payments-tinkoff-c2b", "Sum"); + console.log({ sumTypeid }); + + const payTypeLinkId = await deep.id("@deep-foundation/payments-tinkoff-c2b", "Pay"); + console.log({ payTypeLinkId }); + + const { + data: [{ id: cancellingPayTypeLinkId }], + } = await deep.insert({ + type_id: /* Pay */ typeTypeLinkId, + from_id: userTypeLinkId, + to_id: sumTypeid, + in: { + data: { + type_id: containTypeLinkId, + from_id: packageLinkId, + string: { data: { value: 'CancellingPay' } }, + }, + }, + }); + console.log({ cancellingPayTypeLinkId }); + + const urlTypeLinkId = await deep.id("@deep-foundation/payments-tinkoff-c2b", "Url"); + console.log({ urlTypeLinkId }); + + const payedTypeLinkId = await deep.id("@deep-foundation/payments-tinkoff-c2b", "Payed"); + console.log({ payedTypeLinkId }); + + const errorTypeLinkId = await deep.id("@deep-foundation/payments-tinkoff-c2b", "Error"); + console.log({ errorTypeLinkId }); + + const paymentTreeId = await deep.id("@deep-foundation/payments-tinkoff-c2b", "paymentTree"); + console.log({ paymentTreeId }); + + const storageBusinessTypeLinkId = await deep.id("@deep-foundation/payments-tinkoff-c2b", "StorageBusiness"); + console.log({ storageBusinessTypeLinkId }); + + + const terminalKeyTypeLinkId = await deep.id("@deep-foundation/payments-tinkoff-c2b", "TerminalKey"); + console.log({ terminalKeyTypeLinkId: terminalKeyTypeLinkId }); + + const storageClientTypeLinkId = await deep.id("@deep-foundation/payments-tinkoff-c2b", "StorageClient"); + console.log({ storageClientTypeLinkId }); + + const storageClientTitleTypeLinkId = await deep.id("@deep-foundation/payments-tinkoff-c2b", "StorageClientTitle"); + console.log({ titleTypeLinkId: storageClientTitleTypeLinkId }); + + const { + data: [{ id: cancellingPaymentTypeLinkId }], + } = await deep.insert({ + type_id: typeTypeLinkId, + from_id: paymentTypeLinkId, + to_id: userTypeLinkId, + in: { + data: { + type_id: containTypeLinkId, + from_id: packageLinkId, + string: { data: { value: 'CancellingPayment' } }, + }, + }, + }); + console.log({ cancellingPaymentTypeLinkId }); + + await deep.insert({ + type_id: treeIncludeUpTypeLinkId, + from_id: paymentTreeId, + to_id: cancellingPaymentTypeLinkId, + in: { + data: [ + { + type_id: containTypeLinkId, + from_id: deep.linkId, + }, + ], + }, + }); + + await deep.insert({ + type_id: treeIncludeUpTypeLinkId, + from_id: paymentTreeId, + to_id: cancellingPayTypeLinkId, + in: { + data: [ + { + type_id: containTypeLinkId, + from_id: deep.linkId, + }, + ], + }, + }); + console.log("Before insert handler") + + await deep.insert({ + type_id: await deep.id("@deep-foundation/core", "SyncTextFile"), + string: { + data: { + value: fs.readFileSync('./deep-packages/payments/tinkoff/cancelling/cancellingPayInsertHandler.js', {encoding: 'utf-8'}), + }, + }, + in: { + data: [ + { + type_id: containTypeLinkId, + from_id: packageLinkId, + string: { data: { value: "CancellingPayInsertHandlerCode" } }, + }, + { + from_id: await deep.id("@deep-foundation/core", "dockerSupportsJs"), + type_id: await deep.id("@deep-foundation/core", "Handler"), + in: { + data: [ + { + type_id: containTypeLinkId, + from_id: packageLinkId, + string: { data: { value: "CancellingPayInsertHandler" } }, + }, + { + type_id: await deep.id("@deep-foundation/core", "HandleInsert"), + from_id: payTypeLinkId, + in: { + data: [ + { + type_id: containTypeLinkId, + from_id: packageLinkId, + string: { data: { value: "HandleCancellingPay" } }, + }, + ], + }, + }, + ], + }, + }, + ], + }, + }) + + await deep.insert({ + type_id: await deep.id('@deep-foundation/core', 'Port'), + number: { data: { value: process.env.PAYMENTS_C2B_CANCELLING_NOTIFICATION_PORT } }, + in: { data: [ + { + type_id: await deep.id('@deep-foundation/core', 'Contain'), + from_id: deep.linkId, + }, + { + type_id: await deep.id('@deep-foundation/core', 'RouterListening'), + in: { data: { + type_id: await deep.id('@deep-foundation/core', 'Contain'), + from_id: packageLinkId, + } }, + from: { data: { + type_id: await deep.id('@deep-foundation/core', 'Router'), + in: { data: { + type_id: await deep.id('@deep-foundation/core', 'Contain'), + from_id: packageLinkId, + } }, + in: { data: { + type_id: await deep.id('@deep-foundation/core', 'RouterStringUse'), + string: { data: { value: process.env.PAYMENTS_C2B_CANCELLING_NOTIFICATION_ROUTE } }, + in: { data: { + type_id: await deep.id('@deep-foundation/core', 'Contain'), + from_id: deep.linkId, + } }, + from: { data: { + type_id: await deep.id('@deep-foundation/core', 'Route'), + in: { data: { + type_id: await deep.id('@deep-foundation/core', 'Contain'), + from_id: packageLinkId, + } }, + out: { data: { + type_id: await deep.id('@deep-foundation/core', 'HandleRoute'), + in: { data: { + type_id: await deep.id('@deep-foundation/core', 'Contain'), + from_id: packageLinkId, + } }, + to: { data: { + type_id: await deep.id('@deep-foundation/core', 'Handler'), + from_id: await deep.id('@deep-foundation/core', 'dockerSupportsJs'), + in: { data: { + type_id: await deep.id('@deep-foundation/core', 'Contain'), + from_id: packageLinkId, + } }, + to: { data: { + type_id: await deep.id('@deep-foundation/core', 'SyncTextFile'), + string: { data: { + value: fs.readFileSync('./deep-packages/payments/tinkoff/cancelling/notificationHandler.js', {encoding: 'utf-8'}), + } }, + in: { data: { + type_id: await deep.id('@deep-foundation/core', 'Contain'), + from_id: packageLinkId, + } }, + } }, + } }, + } }, + } }, + } }, + } }, + } + ] }, + }) + + const callTests = async () => { + console.log('callTests-start'); + + const PRICE = 5500; + + const callRealizationTests = async () => { + const testInit = async () => { + const initOptions = { + TerminalKey: process.env.PAYMENTS_C2B_TERMINAL_KEY, + OrderId: uniqid(), + Amount: PRICE, + Description: 'Test shopping', + CustomerKey: deep.linkId, + Language: 'ru', + Recurrent: 'Y', + DATA: { + Email: process.env.PAYMENTS_C2B_EMAIL, + Phone: process.env.PAYMENTS_C2B_PHONE_NUMBER, + }, + // Receipt: { + // Items: [{ + // Name: 'Test item', + // Price: PRICE, + // Quantity: 1, + // Amount: PRICE, + // PaymentMethod: 'prepayment', + // PaymentObject: 'service', + // Tax: 'none', + // }], + // Email: process.env.PAYMENTS_C2B_EMAIL, + // Phone: process.env.PAYMENTS_C2B_PHONE_NUMBER, + // Taxation: 'usn_income', + // }, + }; + + const initResult = await init(initOptions); + + expect(initResult.error).to.equal(undefined); + + return initResult; + }; + + const testConfirm = async () => { + const browser = await puppeteer.launch({ args: ['--no-sandbox'] }); + const page = await browser.newPage(); + + const initOptions = { + TerminalKey: process.env.PAYMENTS_C2B_TERMINAL_KEY, + Amount: PRICE, + OrderId: uniqid(), + CustomerKey: deep.linkId, + PayType: 'T', + // Receipt: { + // Items: [{ + // Name: 'Test item', + // Price: PRICE, + // Quantity: 1, + // Amount: PRICE, + // PaymentMethod: 'prepayment', + // PaymentObject: 'service', + // Tax: 'none', + // }], + // Email: process.env.PAYMENTS_C2B_EMAIL, + // Phone: process.env.PAYMENTS_C2B_PHONE_NUMBER, + // Taxation: 'usn_income', + // }, + }; + + const initResult = await init(initOptions); + + await payInBrowser({ + browser, + page, + url: initResult.response.PaymentURL, + }); + + const confirmOptions = { + TerminalKey: process.env.PAYMENTS_C2B_TERMINAL_KEY, + PaymentId: initResult.response.PaymentId, + }; + + const confirmResult = await confirm(confirmOptions); + + expect(confirmResult.error).to.equal(undefined); + expect(confirmResult.response.Status).to.equal('CONFIRMED'); + + return confirmResult; + }; + + const testCancel = async () => { + console.log('testCancel-start'); + const testCancelAfterPayBeforeConfirmFullPrice = async () => { + console.log('testCanselAfterPayBeforeConfirmFullPrice-start'); + const initOptions = { + TerminalKey: process.env.PAYMENTS_C2B_TERMINAL_KEY, + OrderId: uniqid(), + CustomerKey: deep.linkId, + PayType: 'T', + Amount: PRICE, + Description: 'Test shopping', + Language: 'ru', + Recurrent: 'Y', + DATA: { + Email: process.env.PAYMENTS_C2B_EMAIL, + Phone: process.env.PAYMENTS_C2B_PHONE_NUMBER, + }, + // Receipt: { + // Items: [{ + // Name: 'Test item', + // Price: sum, + // Quantity: 1, + // Amount: PRICE, + // PaymentMethod: 'prepayment', + // PaymentObject: 'service', + // Tax: 'none', + // }], + // Email: process.env.PAYMENTS_C2B_EMAIL, + // Phone: process.env.PAYMENTS_C2B_PHONE_NUMBER, + // Taxation: 'usn_income', + // } + }; + + console.log({ initOptions }); + + let initResult = await init(initOptions); + + console.log({ initResult }); + + expect(initResult.error).to.equal(undefined); + + const url = initResult.response.PaymentURL; + + const browser = await puppeteer.launch({ args: ['--no-sandbox'] }); + const page = await browser.newPage(); + + await payInBrowser({ + browser, + page, + url, + }); + + const bankPaymentId = initResult.response.PaymentId; + + const cancelOptions = { + TerminalKey: process.env.PAYMENTS_C2B_TERMINAL_KEY, + PaymentId: bankPaymentId, + Amount: PRICE, + }; + + console.log({ cancelOptions }); + + const cancelResult = await cancel(cancelOptions); + + console.log({ cancelResult }); + + expect(cancelResult.error).to.equal(undefined); + expect(cancelResult.response.Status).to.equal('REVERSED'); + console.log('testCanselAfterPayBeforeConfirmFullPrice-end'); + }; + + const testCancelAfterPayBeforeConfirmCustomPriceX2 = async () => { + console.log('testCanselAfterPayBeforeConfirmCustomPriceX2-start'); + const initOptions = { + TerminalKey: process.env.PAYMENTS_C2B_TERMINAL_KEY, + OrderId: uniqid(), + CustomerKey: deep.linkId, + PayType: 'T', + Amount: PRICE, + Description: 'Test shopping', + Language: 'ru', + Recurrent: 'Y', + DATA: { + Email: process.env.PAYMENTS_C2B_EMAIL, + Phone: process.env.PAYMENTS_C2B_PHONE_NUMBER, + }, + // Receipt: { + // Items: [{ + // Name: 'Test item', + // Price: sum, + // Quantity: 1, + // Amount: PRICE, + // PaymentMethod: 'prepayment', + // PaymentObject: 'service', + // Tax: 'none', + // }], + // Email: process.env.PAYMENTS_C2B_EMAIL, + // Phone: process.env.PAYMENTS_C2B_PHONE_NUMBER, + // Taxation: 'usn_income', + // } + }; + + console.log({ initOptions }); + + let initResult = await init(initOptions); + + console.log({ initResult }); + + expect(initResult.error).to.equal(undefined); + + const url = initResult.response.PaymentURL; + + const browser = await puppeteer.launch({ args: ['--no-sandbox'] }); + const page = await browser.newPage(); + await payInBrowser({ + browser, + page, + url, + }); + + const bankPaymentId = initResult.response.PaymentId; + + const cancelOptions = { + TerminalKey: process.env.PAYMENTS_C2B_TERMINAL_KEY, + PaymentId: bankPaymentId, + Amount: Math.floor(PRICE / 3), + }; + + console.log({ cancelOptions }); + + { + const cancelResult = await cancel(cancelOptions); + + console.log({ cancelResult }); + + expect(cancelResult.error).to.equal(undefined); + expect(cancelResult.response.Status).to.equal('PARTIAL_REVERSED'); + } + { + const cancelResult = await cancel(cancelOptions); + + console.log({ cancelResult }); + + expect(cancelResult.error).to.equal(undefined); + expect(cancelResult.response.Status).to.equal('PARTIAL_REVERSED'); + } + console.log('testCanselAfterPayBeforeConfirmCustomPriceX2-end'); + }; + + const testCancelAfterPayAfterConfirmFullPrice = async () => { + console.log('testCancelAfterPayAfterConfirmFullPrice-start'); + const confirmResult = await testConfirm(); + console.log({ confirmResult }); + + const bankPaymentId = confirmResult.response.PaymentId; + console.log({ bankPaymentId }); + + const cancelOptions = { + TerminalKey: process.env.PAYMENTS_C2B_TERMINAL_KEY, + PaymentId: bankPaymentId, + Amount: PRICE, + }; + console.log({ cancelOptions }); + + const cancelResult = await cancel(cancelOptions); + + expect(cancelResult.error).to.equal(undefined); + expect(cancelResult.response.Status).to.equal('REFUNDED'); + console.log('testCancelAfterPayAfterConfirmFullPrice-end'); + }; + + const testCancelAfterPayAfterConfirmCustomPriceX2 = async () => { + console.log('testCancelAfterPayAfterConfirmCustomPriceX2-start'); + const confirmResult = await testConfirm(); + + const bankPaymentId = confirmResult.response.PaymentId; + + const cancelOptions = { + TerminalKey: process.env.PAYMENTS_C2B_TERMINAL_KEY, + PaymentId: bankPaymentId, + Amount: Math.floor(PRICE / 3), + }; + + console.log({ cancelOptions }); + + { + const cancelResult = await cancel(cancelOptions); + + expect(cancelResult.error).to.equal(undefined); + expect(cancelResult.response.Status).to.equal('PARTIAL_REFUNDED'); + } + { + const cancelResult = await cancel(cancelOptions); + + expect(cancelResult.error).to.equal(undefined); + expect(cancelResult.response.Status).to.equal('PARTIAL_REFUNDED'); + } + console.log('testCancelAfterPayAfterConfirmCustomPriceX2-end'); + }; + + const testCancelBeforePay = async () => { + console.log('testCancelBeforePay-start'); + const initResult = await testInit(); + + const bankPaymentId = initResult.response.PaymentId;; + + const cancelOptions = { + TerminalKey: process.env.PAYMENTS_C2B_TERMINAL_KEY, + PaymentId: bankPaymentId, + Amount: PRICE, + }; + + console.log({ cancelOptions }); + + const cancelResult = await cancel(cancelOptions); + + expect(cancelResult.error).to.equal(undefined); + expect(cancelResult.response.Status).to.equal('CANCELED'); + console.log('testCancelBeforePay-end'); + }; + await testCancelAfterPayBeforeConfirmFullPrice(); + await testCancelAfterPayBeforeConfirmCustomPriceX2(); + await testCancelAfterPayAfterConfirmFullPrice(); + await testCancelAfterPayAfterConfirmCustomPriceX2(); + await testCancelBeforePay(); + + console.log('testCancel-end'); + }; + + await testInit(); + await testConfirm(); + await testCancel(); + }; + + const callIntegrationTests = async () => { + + const createdLinkIds = []; + + const { + data: [{ id: tinkoffProviderId }], + } = await deep.insert({ + type_id: tinkoffProviderTypeLinkId, + in: { + data: [ + { + type_id: containTypeLinkId, + from_id: deep.linkId, + }, + ], + }, + }); + console.log({ tinkoffProviderId }); + createdLinkIds.push(tinkoffProviderId); + allCreatedLinkIds.push(tinkoffProviderId); + + const { + data: [{ id: sumProviderId }], + } = await deep.insert({ + type_id: sumProviderTypeLinkId, + in: { + data: [ + { + type_id: containTypeLinkId, + from_id: deep.linkId, + }, + ], + }, + }); + console.log({ sumProviderId }); + createdLinkIds.push(sumProviderId); + allCreatedLinkIds.push(sumProviderId); + + const { + data: [{ id: storageBusinessLinkId }], + } = await deep.insert({ + type_id: storageBusinessTypeLinkId, + in: { + data: [ + { + type_id: containTypeLinkId, + from_id: deep.linkId, + }, + ], + }, + }); + console.log({ storageBusinessId: storageBusinessLinkId }); + createdLinkIds.push(storageBusinessLinkId); + allCreatedLinkIds.push(storageBusinessLinkId); + + const { + data: [{ id: usesTerminalKeyTypeLinkId }], + } = await deep.insert({ + type_id: typeTypeLinkId, + from_id: anyTypeLinkId, + to_id: anyTypeLinkId, + in: { + data: { + type_id: containTypeLinkId, + from_id: packageLinkId, + string: { data: { value: 'UsesTerminalKey' } }, + }, + }, + }); + console.log({ usesTerminalKeyTypeLinkId }); + + const { + data: [{ id: tokenId }], + } = await deep.insert({ + type_id: terminalKeyTypeLinkId, + from_id: storageBusinessLinkId, + to_id: storageBusinessLinkId, + string: { data: { value: process.env.PAYMENTS_C2B_TERMINAL_KEY } }, + in: { + data: [ + { + type_id: containTypeLinkId, + from_id: deep.linkId, + }, + { + type_id: usesTerminalKeyTypeLinkId, + from_id: storageBusinessLinkId + }, + ], + }, + }); + console.log({ tokenId }); + createdLinkIds.push(tokenId); + allCreatedLinkIds.push(tokenId); + + const { + data: [{ id: Product }], + } = await deep.insert({ + type_id: typeTypeLinkId, + from_id: anyTypeLinkId, + to_id: anyTypeLinkId, + in: { + data: [ + { + type_id: containTypeLinkId, + from_id: deep.linkId, + }, + ], + }, + }); + console.log({ Product }); + createdLinkIds.push(Product); + allCreatedLinkIds.push(Product); + + const { + data: [{ id: productId }], + } = await deep.insert({ + type_id: Product, + in: { + data: [ + { + type_id: containTypeLinkId, + from_id: deep.linkId, + }, + ], + }, + }); + console.log({ productId }); + createdLinkIds.push(productId); + allCreatedLinkIds.push(productId); + + const testInit = async () => { + console.log('testInit-start'); + + const createdLinkIds = []; + + const { + data: [{ id: paymentId }], + } = await deep.insert({ + type_id: paymentTypeLinkId, + object: { data: { value: { orderId: uniqid() } } }, + from_id: deep.linkId, + to_id: storageBusinessLinkId, + in: { + data: [ + { + type_id: containTypeLinkId, + from_id: deep.linkId, + }, + ], + }, + }); + console.log({ paymentId }); + createdLinkIds.push(paymentId); + allCreatedLinkIds.push(paymentId); + + const { + data: [{ id: sumId }], + } = await deep.insert({ + type_id: sumTypeid, + from_id: sumProviderId, + to_id: paymentId, + number: { data: { value: PRICE } }, + in: { + data: [ + { + type_id: containTypeLinkId, + from_id: deep.linkId, + }, + ], + }, + }); + console.log({ sumId }); + createdLinkIds.push(sumId); + allCreatedLinkIds.push(sumId); + + const { + data: [{ id: objectId }], + } = await deep.insert({ + type_id: objectTypeLinkId, + from_id: paymentId, + to_id: productId, + in: { + data: [ + { + type_id: containTypeLinkId, + from_id: deep.linkId, + }, + ], + }, + }); + console.log({ objectId }); + createdLinkIds.push(objectId); + allCreatedLinkIds.push(objectId); + + const { + data: [{ id: payId }], + } = await deep.insert({ + type_id: payTypeLinkId, + from_id: deep.linkId, + to_id: sumId, + in: { + data: [ + { + type_id: containTypeLinkId, + from_id: deep.linkId, + }, + ], + }, + }); + console.log({ payId }); + createdLinkIds.push(payId); + allCreatedLinkIds.push(payId); + + var urlLinkSelectQuery; + for (let i = 0; i < 10; i++) { + urlLinkSelectQuery = await deep.select({ + type_id: urlTypeLinkId, + to_id: payId, + }); + + if (urlLinkSelectQuery.data.length > 0) { + break; + } + + await sleep(1000); + } + + expect(urlLinkSelectQuery.data.length).to.greaterThan(0); + + createdLinkIds.push(urlLinkSelectQuery.data[0].id); + allCreatedLinkIds.push(urlLinkSelectQuery.data[0].id); + + const createdLinks = (await deep.select(createdLinkIds)).data; + console.log({ createdLinks }); + + console.log('testInit-end'); + + return { + createdLinks + } + }; + + const testFinishAuthorize = async () => { + console.log('testFinishAuthorize-start'); + const { createdLinks } = await testInit(); + + const urlLink = createdLinks.find(link => link.type_id === urlTypeLinkId); + expect(urlLink).to.not.be.equal(undefined) + + const url = urlLink.value.value; + console.log({ url }); + + const browser = await puppeteer.launch({ args: ['--no-sandbox'] }); + const page = await browser.newPage(); + await payInBrowser({ + browser, + page, + url, + }); + + console.log({ createdLinks }); + + console.log('testFinishAuthorize-end'); + + return { + createdLinks + } + }; + + const testConfirm = async () => { + console.log('testConfirm-start'); + const { createdLinks } = await testFinishAuthorize(); + + const createdLinkIds = []; + + const payLink = createdLinks.find(link => link.type_id === payTypeLinkId); + expect(payLink).to.not.equal(undefined); + + var payedLinkSelectQuery; + for (let i = 0; i < 30; i++) { + payedLinkSelectQuery = await deep.select({ + type_id: payedTypeLinkId, + to_id: payLink.id + }); + + if (payedLinkSelectQuery.data.length > 0) { + break; + } + + await sleep(1000); + } + + expect(payedLinkSelectQuery.data.length).to.greaterThan(0); + + createdLinkIds.push(payedLinkSelectQuery.data[0].id); + allCreatedLinkIds.push(payedLinkSelectQuery.data[0].id); + + createdLinks.push(...(await deep.select(createdLinkIds)).data); + + console.log({ createdLinks }); + + console.log('testConfirm-end'); + + return { + createdLinks + } + }; + + const callCancelTests = async () => { + console.log('testCancel-start'); + const testCancelAfterPayAfterConfirmFullPrice = async () => { + console.log('testCancelAfterPayAfterConfirmFullPrice-start'); + const { createdLinks } = await testConfirm(); + + const createdLinkIds = []; + + const paymentLink = createdLinks.find(link => link.type_id === paymentTypeLinkId); + console.log({ paymentLink }); + + const cancellingPaymentLinkInsertQuery = await deep.insert({ + type_id: cancellingPaymentTypeLinkId, + from_id: paymentLink.id, + to_id: deep.linkId, + in: { + data: [ + { + type_id: containTypeLinkId, + from_id: deep.linkId, + }, + ], + }, + }); + if (cancellingPaymentLinkInsertQuery.error) { throw new errorTypeLinkId(cancellingPaymentLinkInsertQuery.error.message); } + const cancellingPaymentId = cancellingPaymentLinkInsertQuery.data[0].id; + console.log({ cancellingPaymentId }); + createdLinkIds.push(cancellingPaymentId); + allCreatedLinkIds.push(cancellingPaymentId); + + const sumLinkOfCancellingPaymentQuery = await deep.insert({ + type_id: sumTypeid, + from_id: sumProviderId, + to_id: cancellingPaymentId, + number: { data: { value: PRICE } }, + in: { + data: [ + { + type_id: containTypeLinkId, + from_id: deep.linkId, + }, + ], + }, + }); + if (sumLinkOfCancellingPaymentQuery.error) { throw new errorTypeLinkId(sumLinkOfCancellingPaymentQuery.error.message); } + const sumLinkIdOfCancellingPayment = sumLinkOfCancellingPaymentQuery.data[0].id; + console.log({ sumLinkIdOfCancellingPayment }); + createdLinkIds.push(sumLinkIdOfCancellingPayment); + allCreatedLinkIds.push(sumLinkIdOfCancellingPayment); + + const cancellingPayLinkInsertQuery = await deep.insert({ + type_id: cancellingPayTypeLinkId, + from_id: deep.linkId, + to_id: sumLinkIdOfCancellingPayment, + in: { + data: [ + { + type_id: containTypeLinkId, + from_id: deep.linkId, + }, + ], + }, + }); + if (cancellingPayLinkInsertQuery.error) { throw new errorTypeLinkId(cancellingPayLinkInsertQuery.error.message); } + const cancellingPayId = cancellingPayLinkInsertQuery.data[0].id; + console.log({ cancellingPayId }); + createdLinkIds.push(cancellingPayId); + allCreatedLinkIds.push(cancellingPayId); + + var payedLinkSelectQuery; + for (let i = 0; i < 10; i++) { + payedLinkSelectQuery = await deep.select({ + type_id: payedTypeLinkId, + to_id: cancellingPayId + }); + + if (payedLinkSelectQuery.data.length > 0) { + break; + } + + await sleep(1000); + } + if (payedLinkSelectQuery.error) { throw new errorTypeLinkId(payedLinkSelectQuery.error.message); } + const payedLink = payedLinkSelectQuery.data[0]; + expect(payedLink).to.not.equal(undefined); + createdLinks.push(payedLink); + + createdLinks.push(...(await deep.select(createdLinkIds)).data) + + console.log('testCancelAfterPayAfterConfirmFullPrice-end'); + + return { + createdLinks + }; + }; + + const testCancelAfterPayAfterConfirmCustomPriceX2 = async () => { + console.log('testCancelAfterPayAfterConfirmCustomPriceX2-start'); + const { createdLinks } = await testConfirm({ customerKey }); + + const createdLinkIds = []; + + const paymentLink = createdLinks.find(link => link.type_id === paymentTypeLinkId); + console.log({ paymentLink }); + + for (let i = 0; i < 2; i++) { + const cancellingPaymentLinkInsertQuery = await deep.insert({ + type_id: cancellingPaymentTypeLinkId, + from_id: paymentLink.id, + to_id: deep.linkId, + in: { + data: [ + { + type_id: containTypeLinkId, + from_id: deep.linkId, + }, + ], + }, + }); + if (cancellingPaymentLinkInsertQuery.error) { throw new errorTypeLinkId(cancellingPaymentLinkInsertQuery.error.message); } + const cancellingPaymentId = cancellingPaymentLinkInsertQuery.data[0].id; + console.log({ cancellingPaymentId }); + createdLinkIds.push(cancellingPaymentId); + allCreatedLinkIds.push(cancellingPaymentId); + + const { + data: [{ id: sumLinkIdOfCancellingPayment }] + } = await deep.insert({ + type_id: sumTypeid, + from_id: sumProviderId, + to_id: cancellingPaymentId, + number: { data: { value: Math.floor(PRICE / 3) } }, + in: { + data: [ + { + type_id: containTypeLinkId, + from_id: deep.linkId, + }, + ], + }, + }); + console.log({ sumLinkIdOfCancellingPayment }); + createdLinkIds.push(sumLinkIdOfCancellingPayment); + allCreatedLinkIds.push(sumLinkIdOfCancellingPayment); + + const cancellingPayLinkInsertQuery = await deep.insert({ + type_id: cancellingPayTypeLinkId, + from_id: deep.linkId, + to_id: sumLinkIdOfCancellingPayment, + in: { + data: [ + { + type_id: containTypeLinkId, + from_id: deep.linkId, + }, + ], + }, + }); + if (cancellingPayLinkInsertQuery.error) { throw new errorTypeLinkId(cancellingPayLinkInsertQuery.error.message); } + const cancellingPayId = cancellingPayLinkInsertQuery.data[0].id; + console.log({ cancellingPayId }); + createdLinkIds.push(cancellingPayId); + allCreatedLinkIds.push(cancellingPayId); + + var payedLinkSelectQuery; + for (let i = 0; i < 10; i++) { + payedLinkSelectQuery = await deep.select({ + type_id: payedTypeLinkId, + to_id: cancellingPayId + }); + + if (payedLinkSelectQuery.data.length > 0) { + break; + } + + await sleep(1000); + } + if (payedLinkSelectQuery.error) { throw new errorTypeLinkId(payedLinkSelectQuery.error.message); } + const payedLink = payedLinkSelectQuery.data[0]; + expect(payedLink).to.not.equal(undefined); + createdLinks.push(payedLink); + } + + createdLinks.push(...(await deep.select(createdLinkIds)).data) + + console.log({ createdLinks }); + + console.log('testCancelAfterPayAfterConfirmCustomPriceX2-end'); + + return { + createdLinks + } + }; + + const testCancelBeforePay = async () => { + console.log('testCancelBeforePay-start'); + const { createdLinks } = await testInit({ customerKey }); + + const createdLinkIds = []; + + const paymentLink = createdLinks.find(link => link.type_id === paymentTypeLinkId); + console.log({ paymentLink }); + + const cancellingPaymentLinkInsertQuery = await deep.insert({ + type_id: cancellingPaymentTypeLinkId, + from_id: paymentLink.id, + to_id: deep.linkId, + in: { + data: [ + { + type_id: containTypeLinkId, + from_id: deep.linkId, + }, + ], + }, + }); + if (cancellingPaymentLinkInsertQuery.error) { throw new errorTypeLinkId(cancellingPaymentLinkInsertQuery.error.message); } + const cancellingPaymentId = cancellingPaymentLinkInsertQuery.data[0].id; + console.log({ cancellingPaymentId }); + createdLinkIds.push(cancellingPaymentId); + allCreatedLinkIds.push(cancellingPaymentId); + + const sumLinkOfCancellingPaymentSelectQuery = await deep.insert({ + type_id: sumTypeid, + from_id: sumProviderId, + to_id: cancellingPaymentId, + number: { data: { value: PRICE } }, + in: { + data: [ + { + type_id: containTypeLinkId, + from_id: deep.linkId, + }, + ], + }, + }); + if (sumLinkOfCancellingPaymentSelectQuery.error) { throw new errorTypeLinkId(sumLinkOfCancellingPaymentSelectQuery.error.message); } + const sumLinkIdOfCancellingPayment = sumLinkOfCancellingPaymentSelectQuery.data[0].id; + console.log({ sumLinkIdOfCancellingPayment }); + createdLinkIds.push(sumLinkIdOfCancellingPayment); + allCreatedLinkIds.push(sumLinkIdOfCancellingPayment); + + const cancellingPayLinkInsertQuery = await deep.insert({ + type_id: cancellingPayTypeLinkId, + from_id: deep.linkId, + to_id: sumLinkIdOfCancellingPayment, + in: { + data: [ + { + type_id: containTypeLinkId, + from_id: deep.linkId, + }, + ], + }, + }); + if (cancellingPayLinkInsertQuery.error) { throw new errorTypeLinkId(cancellingPayLinkInsertQuery.error.message); } + const cancellingPayId = cancellingPayLinkInsertQuery.data[0].id; + console.log({ cancellingPayId }); + createdLinkIds.push(cancellingPayId); + allCreatedLinkIds.push(cancellingPayId); + + var payedLinkSelectQuery; + for (let i = 0; i < 10; i++) { + payedLinkSelectQuery = await deep.select({ + type_id: payedTypeLinkId, + to_id: cancellingPayId + }); + + if (payedLinkSelectQuery.data.length > 0) { + break; + } + + await sleep(1000); + } + if (payedLinkSelectQuery.error) { throw new errorTypeLinkId(payedLinkSelectQuery.error.message); } + const payedLink = payedLinkSelectQuery.data[0]; + expect(payedLink).to.not.equal(undefined); + createdLinks.push(payedLink); + + createdLinks.push(...(await deep.select(createdLinkIds)).data) + + console.log('testCancelBeforePay-end'); + + return { + createdLinks + }; + }; + + { + const { createdLinks } = await testCancelAfterPayAfterConfirmFullPrice(); + await deep.delete(createdLinks.map(link => link.id)); + } + { + const { createdLinks } = await testCancelAfterPayAfterConfirmCustomPriceX2(); + await deep.delete(createdLinks.map(link => link.id)); + } + { + const { createdLinks } = await testCancelBeforePay(); + await deep.delete(createdLinks.map(link => link.id)); + } + + console.log('testCancel-end'); + }; + + await callCancelTests(); + await deep.delete(createdLinkIds); + }; + + // await callRealizationTests(); + await callIntegrationTests(); + }; + + await callTests(); + + } catch (error) { + await deep.delete(allCreatedLinkIds); + console.log(error); + process.exit(1); + } +}; + +installPackage(); \ No newline at end of file diff --git a/payments.cjs b/payments.cjs index 5f9b567e..5c22b036 100644 --- a/payments.cjs +++ b/payments.cjs @@ -160,6 +160,19 @@ const f = async () => { console.log({ PPayed: PPayed }); + const { data: [{ id: PCancelled }] } = await deep.insert({ + type_id: Type, + from_id: Any, + to_id: Any, + in: { data: { + type_id: Contain, + from_id: packageId, // before created package + string: { data: { value: 'Cancelled' } }, + } }, + }); + + console.log({ PPayed: PPayed }); + const { data: [{ id: PError }] } = await deep.insert({ type_id: Type, from_id: Any, @@ -172,5 +185,18 @@ const f = async () => { }); console.log({ PError: PError }); + + const { data: [{ id: PStorage }] } = await deep.insert({ + type_id: Type, + from_id: Any, + to_id: Any, + in: { data: { + type_id: Contain, + from_id: packageId, // before created package + string: { data: { value: 'Storage' } }, + } }, + }); + + console.log({ PError: PStorage }); }; f(); \ No newline at end of file diff --git a/tinkoff-split/_utils.ts b/tinkoff-split/_utils.ts new file mode 100644 index 00000000..58bd0df7 --- /dev/null +++ b/tinkoff-split/_utils.ts @@ -0,0 +1,129 @@ +import crypto from 'crypto'; + +export const getUrl = method => `${process.env.PAYMENT_EACQ_AND_TEST_URL}/${method}`; +export const getMarketUrl = method => `${process.env.PAYMENT_TINKOFF_MARKET_URL}/${method}`; + +export const sleep = (ms: any) => new Promise(resolve => setTimeout(resolve, ms)); +const SHAKE_PART = 0.07; + +export const errorsConverter = { + 7: 'Покупатель не найден', + 53: 'Обратитесь к продавцу', + 99: 'Платеж отклонен', + 100: 'Повторите попытку позже', + 101: 'Не пройдена идентификация 3DS', + 102: 'Операция отклонена, пожалуйста обратитесь в интернет-магазин или воспользуйтесь другой картой', + 103: 'Повторите попытку позже', + 119: 'Превышено кол-во запросов на авторизацию', + 191: 'Некорректный статус договора, обратитесь к вашему менеджеру', + 1001: 'Свяжитесь с банком, выпустившим карту, чтобы провести платеж', + 1003: 'Неверный merchant ID', + 1004: 'Карта украдена. Свяжитесь с банком, выпустившим карту', + 1005: 'Платеж отклонен банком, выпустившим карту', + 1006: 'Свяжитесь с банком, выпустившим карту, чтобы провести платеж', + 1007: 'Карта украдена. Свяжитесь с банком, выпустившим карту', + 1008: 'Платеж отклонен, необходима идентификация', + 1012: 'Такие операции запрещены для этой карты', + 1013: 'Повторите попытку позже', + 1014: 'Карта недействительна. Свяжитесь с банком, выпустившим карту', + 1015: 'Попробуйте снова или свяжитесь с банком, выпустившим карту', + 1019: 'Платеж отклонен — попробуйте снова', + 1030: 'Повторите попытку позже', + 1033: 'Истек срок действия карты. Свяжитесь с банком, выпустившим карту', + 1034: 'Попробуйте повторить попытку позже', + 1038: 'Превышено количество попыток ввода ПИН-кода', + 1039: 'Платеж отклонен — счет не найден', + 1041: 'Карта утеряна. Свяжитесь с банком, выпустившим карту', + 1043: 'Карта украдена. Свяжитесь с банком, выпустившим карту', + 1051: 'Недостаточно средств на карте', + 1053: 'Платеж отклонен — счет не найден', + 1054: 'Истек срок действия карты', + 1055: 'Неверный ПИН', + 1057: 'Такие операции запрещены для этой карты', + 1058: 'Такие операции запрещены для этой карты', + 1059: 'Подозрение в мошенничестве. Свяжитесь с банком, выпустившим карту', + 1061: 'Превышен дневной лимит платежей по карте', + 1062: 'Платежи по карте ограничены', + 1063: 'Операции по карте ограничены', + 1064: 'Проверьте сумму', + 1065: 'Превышен дневной лимит транзакций', + 1075: 'Превышено число попыток ввода ПИН-кода', + 1076: 'Платеж отклонен — попробуйте снова', + 1077: 'Коды не совпадают — попробуйте снова', + 1080: 'Неверный срок действия', + 1082: 'Неверный CVV', + 1086: 'Платеж отклонен — не получилось подтвердить ПИН-код', + 1088: 'Ошибка шифрования. Попробуйте снова', + 1089: 'Попробуйте повторить попытку позже', + 1091: 'Банк, выпустивший карту недоступен для проведения авторизации', + 1092: 'Платеж отклонен — попробуйте снова', + 1093: 'Подозрение в мошенничестве. Свяжитесь с банком, выпустившим карту', + 1094: 'Системная ошибка', + 1096: 'Повторите попытку позже', + 9999: 'Внутренняя ошибка системы', +}; + +export const getError = errorCode => errorCode === '0' ? undefined : (errorsConverter[errorCode] || 'broken'); + +export const _generateToken = (dataWithPassword) => { + const dataString = Object.keys(dataWithPassword) + .sort((a, b) => a.localeCompare(b)) + .map(key => dataWithPassword[key]) + .reduce((acc, item) => `${acc}${item}`, ''); + const hash = crypto + .createHash('sha256') + .update(dataString) + .digest('hex'); + return hash; +}; + +export const generateToken = (data) => { + const { Receipt, DATA, Shops, ...restData } = data; + const dataWithPassword = { ...restData, Password: process.env.PAYMENT_EACQ_TERMINAL_PASSWORD }; + return _generateToken(dataWithPassword); +}; + +export const generateTestToken = (data) => { + const { Receipt, DATA, Shops, ...restData } = data; + const dataWithPassword = { ...restData, Password: process.env.PAYMENT_TEST_TERMINAL_PASSWORD }; + return _generateToken(dataWithPassword); +}; + +export const tokenize = (options) => { + return { + ...options, + Token: generateToken(options), + }; +}; + +export interface IReceipt { + Items: IItem[]; + Phone?: string; + Email?: string; + Taxation: string; +} + +export interface IItem { + Name: string; + Price: number; + Quantity: number; + Amount: number; + PaymentMethod?: string; + PaymentObject?: string; + Tax: string; +} +export interface IShops { + ShopCode: String; + Amount: number; + Name?: string; + Fee?: number; +} + +export function createShops({ splitToken, amount, needFee = true }) { + const shops = [{ + ShopCode: splitToken?.json?.shopCode, + Amount: Math.abs(amount) * 100, + ...(amount > 0 && needFee ? { Fee: Math.round(+(SHAKE_PART * (amount * 100)).toFixed(2)) } : {}), + }]; + return shops; +} diff --git a/tinkoff-split/add-customer.ts b/tinkoff-split/add-customer.ts new file mode 100644 index 00000000..6fe6a5e2 --- /dev/null +++ b/tinkoff-split/add-customer.ts @@ -0,0 +1,68 @@ +import axios from 'axios'; +import { getUrl, getError } from './_utils'; +import Debug from 'debug'; + +const debug = Debug('payments:tinkoff-split:add-customer'); + +export interface IAddCustomerRequest { + (options: IAddCustomerOptions): Promise; +} + +export interface IAddCustomerOptions { + TerminalKey: string; + CustomerKey: string; + Email?: string; + Phone?: string; + Token: string; + log?: (data) => any; +} + +export interface IAddCustomerResponse { + error: string; + request: IAddCustomerOptions; + response: IAddCustomerPaymentResponse; +} + +export interface IAddCustomerPaymentResponse { + TerminalKey: string; + CustomerKey: string; + Success: boolean; + ErrorCode: string; + Message?: string; + Details?: string; +} + +export const addCustomer: IAddCustomerRequest = async (options: IAddCustomerOptions): Promise => { + try { + const response = await axios({ + method: 'post', + url: getUrl('AddCustomer'), + headers: { + 'Content-Type': 'application/json', + }, + data: options, + }); + + const error = getError(response.data.ErrorCode); + + const d = { + error, + request: options, + response: response.data, + }; + debug(d); + options?.log && options.log(d); + + return { + error, + request: options, + response: response.data, + }; + } catch (error) { + return { + error, + request: options, + response: null, + }; + } +}; diff --git a/tinkoff-split/auth-partners-register.ts b/tinkoff-split/auth-partners-register.ts new file mode 100644 index 00000000..faa4cbf0 --- /dev/null +++ b/tinkoff-split/auth-partners-register.ts @@ -0,0 +1,51 @@ +import axios from 'axios'; +import { getError, getMarketUrl } from './_utils'; +import Debug from 'debug'; + +const debug = Debug('payments:tinkoff-split:auth-partners-register'); + +export interface IAuthPartnersRegisterPartner { + (options: IAuthPartnersRegisterPartnerOptions): Promise; +} + +export interface IAuthPartnersRegisterPartnerOptions { + username: string; + password: string; +} + +export interface IAuthPartnersRegisterPartnerResponse { + access_token: string; + token_type: string; + refresh_token: string; + expires_in: number; + scope: string; + jti: string; +} + +// only for unsafe usage in registerPartner +export const authPartnerRegister: IAuthPartnersRegisterPartner = async (options: IAuthPartnersRegisterPartnerOptions): Promise => { + const response = await axios({ + method: 'post', + url: `${getMarketUrl('oauth/token')}?grant_type=password&username=${options.username}&password=${options.password}`, + auth: { + username: 'partner', + password: 'partner', + }, + headers: { + Authorization: 'Basic', + 'Content-Type': 'application/json', + }, + validateStatus: () => true, + }); + + const error = getError(response.data.ErrorCode); + + const d = { + error, + request: options, + response: response.data, + }; + debug(d); + + return response.data; +}; diff --git a/tinkoff-split/cancel.ts b/tinkoff-split/cancel.ts new file mode 100644 index 00000000..771df10c --- /dev/null +++ b/tinkoff-split/cancel.ts @@ -0,0 +1,70 @@ +import axios from 'axios'; +import { getUrl, IReceipt, getError, IShops } from './_utils'; +import Debug from 'debug'; + +const debug = Debug('payments:tinkoff-split:cancel'); + +export interface ICancelRequest { + (options: ICancelOptions): Promise; +} + +export interface ICancelOptions { + TerminalKey: string; + PaymentId: number; + Amount?: number; + Token: string; + Receipt?: IReceipt; + shops: IShops[]; + log?: (data) => any; +} + +export interface ICancelResponse { + error: string; + request: ICancelOptions; + response: ICancelPaymentResponse; +} + +export interface ICancelPaymentResponse { + TerminalKey: string; + Success: boolean; + Status: string; + PaymentId: number; + ErrorCode: string; + OrderId: string; + OriginalAmount: number; + NewAmount: number; + Message?: string; + Details?: string; +} + +export const cancel: ICancelRequest = async (options: ICancelOptions): Promise => { + try { + const response = await axios({ + method: 'post', + url: getUrl('Cancel'), + data: options, + }); + + const error = getError(response.data.ErrorCode); + + const d = { + error, + request: options, + response: response.data, + }; + debug(d); + options?.log && options.log(d); + + return { + error, + request: options, + response: response.data, + }; + } catch (error) { + return { + error, + request: options, + response: null, + }; + } +}; diff --git a/tinkoff-split/charge.ts b/tinkoff-split/charge.ts new file mode 100644 index 00000000..4ffed9a2 --- /dev/null +++ b/tinkoff-split/charge.ts @@ -0,0 +1,72 @@ +import axios from 'axios'; +import Debug from 'debug'; +import { getError, getUrl } from './_utils'; + +const debug = Debug('payments:tinkoff-split:charge'); + +export interface IChargeRequest { + (options: IChargeOptions): Promise; +} + +export interface IChargeOptions { + TerminalKey: string; + PaymentId: number; + RebillId: number; + SendEmail?: boolean; + InfoEmail?: boolean; + Token: string; + log?: (data) => any; +} + +export interface IChargeResponse { + error: string; + request: IChargeOptions; + response: IChargePaymentResponse; +} + +export interface IChargePaymentResponse { + TerminalKey: string; + OrderId: string; + Success: boolean; + Status: string; + PaymentId: number; + ErrorCode: string; + Amount: number; + Message?: string; + Details?: string; +} + +export const charge: IChargeRequest = async (options: IChargeOptions): Promise => { + try { + const response = await axios({ + method: 'post', + url: getUrl('Charge'), + headers: { + 'Content-Type': 'application/json', + }, + data: options, + }); + + const error = getError(response.data.ErrorCode); + + const d = { + error, + request: options, + response: response.data, + }; + debug(d); + options?.log && options.log(d); + + return { + error, + request: options, + response: response.data, + }; + } catch (error) { + return { + error, + request: options, + response: null, + }; + } +}; diff --git a/tinkoff-split/confirm.ts b/tinkoff-split/confirm.ts new file mode 100644 index 00000000..47b8f32c --- /dev/null +++ b/tinkoff-split/confirm.ts @@ -0,0 +1,68 @@ +import axios from 'axios'; +import { getUrl, IReceipt, getError, IShops } from './_utils'; +import Debug from 'debug'; + +const debug = Debug('payments:tinkoff-split:confirm'); + +export interface IConfirmRequest { + (options: IConfirmOptions): Promise; +} + +export interface IConfirmOptions { + TerminalKey: string; + PaymentId: number; + Amount?: number; + Token: string; + Receipt?: IReceipt; + shops: IShops[]; + log?: (data) => any; +} + +export interface IConfirmResponse { + error: string; + request: IConfirmOptions; + response: IConfirmPaymentResponse; +} + +export interface IConfirmPaymentResponse { + TerminalKey: string; + OrderId: string; + Success: boolean; + Status: string; + PaymentId: number; + ErrorCode: string; + Message?: string; + Details?: string; +} + +export const confirm: IConfirmRequest = async (options: IConfirmOptions): Promise => { + try { + const response = await axios({ + method: 'post', + url: getUrl('Confirm'), + data: options, + }); + + const error = getError(response.data.ErrorCode); + + const d = { + error, + request: options, + response: response.data, + }; + debug(d); + options?.log && options.log(d); + + return { + error, + request: options, + response: response.data, + }; + } catch (error) { + return { + error, + request: options, + response: null, + }; + } +}; diff --git a/tinkoff-split/finishAuth.ts b/tinkoff-split/finishAuth.ts new file mode 100644 index 00000000..9f586806 --- /dev/null +++ b/tinkoff-split/finishAuth.ts @@ -0,0 +1,68 @@ +import axios from 'axios'; +import { getUrl, getError } from './_utils'; +import Debug from 'debug'; + +const debug = Debug('payments:tinkoff-split:finishAuth'); + +export interface IFinishAuthRequest { + (options: IFinishAuthOptions): Promise; +} + +export interface IFinishAuthOptions { + CardData: string; + PaymentId: number; + Token: string; + TerminalKey: string; + log?: (data) => any; +} + +export interface IFinishAuthResponse { + error: string; + request: IFinishAuthOptions; + response: IFinishAuthPaymentResponse; +} + +export interface IFinishAuthPaymentResponse { + Success: boolean; + ErrorCode: number; + TerminalKey: string; + Status: string; + PaymentId: number; + OrderId: string; + Amount: number; + ACSUrl: string; + MD: string; + PaReq: string; +} + +export const finishAuth: IFinishAuthRequest = async (options: IFinishAuthOptions): Promise => { + try { + const response = await axios({ + method: 'post', + url: getUrl('FinishAuthorize'), + data: options, + }); + + const error = getError(response.data.ErrorCode); + + const d = { + error, + request: options, + response: response.data, + }; + debug(d); + options?.log && options.log(d); + + return { + error, + request: options, + response: response.data, + }; + } catch (error) { + return { + error, + request: options, + response: null, + }; + } +}; diff --git a/tinkoff-split/get-card-list.ts b/tinkoff-split/get-card-list.ts new file mode 100644 index 00000000..d2e57827 --- /dev/null +++ b/tinkoff-split/get-card-list.ts @@ -0,0 +1,66 @@ +import axios from 'axios'; +import { getUrl, getError } from './_utils'; +import Debug from 'debug'; + +const debug = Debug('payments:tinkoff-split:get-card-list'); + +export interface IGetCardListRequest { + (options: IGetCardListOptions): Promise; +} + +export interface IGetCardListOptions { + TerminalKey: string; + CustomerKey: string; + Token: string; + log?: (data) => any; +} + +export interface IGetCardListResponse { + error: string; + request: IGetCardListOptions; + response: IGetCardListPaymentResponse[]; +} + +export interface IGetCardListPaymentResponse { + Pan: string; + CardId: string; + Status: string; + RebillId?: string; + ExpDate: string; + CardType: number; +} + +export const getCardList: IGetCardListRequest = async (options: IGetCardListOptions): Promise => { + try { + const response = await axios({ + method: 'post', + url: getUrl('GetCardList'), + headers: { + 'Content-Type': 'application/json', + }, + data: options, + }); + + const error = getError(response.data.ErrorCode || '0'); + + const d = { + error, + request: options, + response: response.data, + }; + debug(d); + options?.log && options.log(d); + + return { + error, + request: options, + response: response.data, + }; + } catch (error) { + return { + error, + request: options, + response: null, + }; + } +}; diff --git a/tinkoff-split/get-customer.ts b/tinkoff-split/get-customer.ts new file mode 100644 index 00000000..70048368 --- /dev/null +++ b/tinkoff-split/get-customer.ts @@ -0,0 +1,68 @@ +import axios from 'axios'; +import { getUrl, getError } from './_utils'; +import Debug from 'debug'; + +const debug = Debug('payments:tinkoff-split:get-customer'); + +export interface IGetCustomerRequest { + (options: IGetCustomerOptions): Promise; +} + +export interface IGetCustomerOptions { + TerminalKey: string; + CustomerKey: string; + Token: string; + log?: (data) => any; +} + +export interface IGetCustomerResponse { + error: string; + request: IGetCustomerOptions; + response: IGetCustomerPaymentResponse; +} + +export interface IGetCustomerPaymentResponse { + TerminalKey: string; + CustomerKey: string; + Success: boolean; + ErrorCode: string; + Email?: string; + Phone?: string; + Message?: string; + Details?: string; +} + +export const getCustomer: IGetCustomerRequest = async (options: IGetCustomerOptions): Promise => { + try { + const response = await axios({ + method: 'post', + url: getUrl('GetCustomer'), + headers: { + 'Content-Type': 'application/json', + }, + data: options, + }); + + const error = getError(response.data.ErrorCode); + + const d = { + error, + request: options, + response: response.data, + }; + debug(d); + options?.log && options.log(d); + + return { + error, + request: options, + response: response.data, + }; + } catch (error) { + return { + error, + request: options, + response: null, + }; + } +}; diff --git a/tinkoff-split/get-state.ts b/tinkoff-split/get-state.ts new file mode 100644 index 00000000..74804024 --- /dev/null +++ b/tinkoff-split/get-state.ts @@ -0,0 +1,66 @@ +import axios from 'axios'; +import { getUrl, getError } from './_utils'; +import Debug from 'debug'; + +const debug = Debug('payments:tinkoff-split:get-state'); + +export interface IGetStateRequest { + (options: IGetStateOptions): Promise; +} + +export interface IGetStateOptions { + TerminalKey: string; + PaymentId: number; + Amount?: number; + Token: string; + log?: (data) => any; +} + +export interface IGetStateResponse { + error: string; + request: IGetStateOptions; + response: IGetStatePaymentResponse; +} + +export interface IGetStatePaymentResponse { + TerminalKey: string; + OrderId: string; + Success: boolean; + Status: string; + PaymentId: number; + ErrorCode: string; + Message?: string; + Details?: string; +} + +export const getState: IGetStateRequest = async (options: IGetStateOptions): Promise => { + try { + const response = await axios({ + method: 'post', + url: getUrl('GetState'), + data: options, + }); + + const error = getError(response.data.ErrorCode); + + const d = { + error, + request: options, + response: response.data, + }; + debug(d); + options?.log && options.log(d); + + return { + error, + request: options, + response: response.data, + }; + } catch (error) { + return { + error, + request: options, + response: null, + }; + } +}; diff --git a/tinkoff-split/init.ts b/tinkoff-split/init.ts new file mode 100644 index 00000000..1a2ed79c --- /dev/null +++ b/tinkoff-split/init.ts @@ -0,0 +1,87 @@ +import axios from 'axios'; +import { getUrl, getError, IReceipt, IShops } from './_utils'; +import Debug from 'debug'; + +const debug = Debug('payments:tinkoff-split:init'); + +export interface IInitRequest { + (options: IInitOptions): Promise; +} + +export interface IInitOptions { + TerminalKey: string; + OrderId: string; + Amount: number; + Description?: string; + CustomerKey?: string; + Language?: string; + Token?: string; + Recurrent?: string; + DATA?: IUserData; + PayType?: string; + Receipt?: IReceipt; + NotificationURL?: string; + SuccessURL?: string; + FailURL?: string; + shops: IShops[]; + log?: (data) => any; +} + +export interface IUserData { + Phone: string; + Email: string; +} + +export interface IInitResponse { + error: string; + request: IInitOptions; + response: IInitPaymentResponse; +} + +export interface IInitPaymentResponse { + TerminalKey: string; + Amount: number; + OrderId: string; + Success: boolean; + Status: string; + PaymentId: number; + ErrorCode: string; + PaymentURL?: string; + Message?: string; + Details?: string; +} + +export const init: IInitRequest = async (options: IInitOptions): Promise => { + try { + const response = await axios({ + method: 'post', + url: getUrl('Init'), + headers: { + 'Content-Type': 'application/json', + }, + data: options, + }); + + const error = getError(response.data.ErrorCode); + + const d = { + error, + request: options, + response: response.data, + }; + debug(d); + options?.log && options.log(d); + + return { + error, + request: options, + response: response.data, + }; + } catch (error) { + return { + error, + request: options, + response: null, + }; + } +}; diff --git a/tinkoff-split/register-partner.ts b/tinkoff-split/register-partner.ts new file mode 100644 index 00000000..d300084a --- /dev/null +++ b/tinkoff-split/register-partner.ts @@ -0,0 +1,132 @@ +import axios from 'axios'; +import { getMarketUrl } from './_utils'; +import { authPartnerRegister } from './auth-partners-register'; +import Debug from 'debug'; + +const debug = Debug('payments:tinkoff-split:register-partner'); + +export interface IRegisterPartner { + (options: IRegisterPartnerOptions): Promise; +} + +export interface IRegisterPartnerOptions { + serviceProviderEmail?: string; + shopArticleId?: string; + billingDescriptor: string; + fullName: string; + name: string; + inn: string; + kpp: string; + okved?: string; + ogrn: number; + regDepartment?: string; + regDate?: string; + addresses: { + type: string; + zip: string; + country: string; + city: string; + street: string; + description?: string; + }[]; + phones?: { + type?: string; + phone?: string; + description?: string; + }[]; + email: string; + assets?: string; + founders?: { + individuals: { + firstName: string; + lastName: string; + middleName?: string; + birthDate?: string; + birthPlace?: string; + citizenship: string; + docType?: string; + docNumber?: string; + issueDate?: string; + issuedBy?: string; + address: string; + }[]; + }; + ceo: { + firstName: string; + lastName: string; + middleName: string; + birthDate: string; + birthPlace?: string; + docType?: string; + docNumber?: string; + issueDate?: string; + issuedBy?: string; + address?: string; + phone: string; + }; + licenses?: { + type?: string; + number?: string; + issueDate?: string; + issuedBy?: string; + expiryDate?: string; + description?: string; + }; + siteUrl: string; + primaryActivities?: string; + bankAccount: { + account: string; + korAccount?: string; + bankName: string; + bik: string; + details: string; + tax: string; + }; + comment?: string; + nonResident?: boolean; +} + +export interface IRegisterPartnerResponse { + code?: string; + shopCode?: string; + terminals?: any[]; + + timestamp?: string; + status?: number; + error?: string; + errors?: { + field?: string; + defaultMessage?: string; + rejectedValue?: string; + code?: string; + }[]; + message?: string; + path?: string; +} + +export const registerPartner: IRegisterPartner = async (options: IRegisterPartnerOptions): Promise => { + const access_token = (await authPartnerRegister({ + username: process.env.PAYMENT_TINKOFF_MARKET_USERNAME || '', + password: process.env.PAYMENT_TINKOFF_MARKET_PASSWORD || '', + })); + + console.log({ access_token }); + + const response = await axios({ + method: 'post', + url: getMarketUrl('register'), + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${access_token?.access_token}`, + }, + data: options, + validateStatus: () => true, + }); + + debug({ + request: options, + response: response.data, + }); + + return response.data; +}; diff --git a/tinkoff-split/remove-customer.ts b/tinkoff-split/remove-customer.ts new file mode 100644 index 00000000..ce77e3d1 --- /dev/null +++ b/tinkoff-split/remove-customer.ts @@ -0,0 +1,63 @@ +import axios from 'axios'; +import { getUrl, getError } from './_utils'; +import Debug from 'debug'; + +const debug = Debug('payments:tinkoff-split:remove-customer'); + +export interface IRemoveCustomerRequest { + (options: IRemoveCustomerOptions): Promise; +} + +export interface IRemoveCustomerOptions { + TerminalKey: string; + CustomerKey: string; + Token: string; +} + +export interface IRemoveCustomerResponse { + error: string; + request: IRemoveCustomerOptions; + response: IRemoveCustomerPaymentResponse; +} + +export interface IRemoveCustomerPaymentResponse { + TerminalKey: string; + CustomerKey: string; + Success: boolean; + ErrorCode: string; + Message?: string; + Details?: string; +} + +export const removeCustomer: IRemoveCustomerRequest = async (options: IRemoveCustomerOptions): Promise => { + try { + const response = await axios({ + method: 'post', + url: getUrl('RemoveCustomer'), + headers: { + 'Content-Type': 'application/json', + }, + data: options, + }); + + const error = getError(response.data.ErrorCode); + + debug({ + error, + request: options, + response: response.data, + }); + + return { + error, + request: options, + response: response.data, + }; + } catch (error) { + return { + error, + request: options, + response: null, + }; + } +}; diff --git a/tinkoff-split/resend.ts b/tinkoff-split/resend.ts new file mode 100644 index 00000000..644c8d78 --- /dev/null +++ b/tinkoff-split/resend.ts @@ -0,0 +1,62 @@ +import axios from 'axios'; +import { getUrl, getError } from './_utils'; +import Debug from 'debug'; + +const debug = Debug('payments:tinkoff-split:add-customer'); + +export interface IResendRequest { + (options: IResendOptions): Promise; +} + +export interface IResendOptions { + TerminalKey: string; + Token: string; + log?: (data) => any; +} + +export interface IResendResponse { + error: string; + request: IResendOptions; + response: IResendPaymentResponse; +} + +export interface IResendPaymentResponse { + TerminalKey: string; + Count: number; + Success: boolean; + ErrorCode: string; + Message?: string; + Details?: string; +} + +export const resend: IResendRequest = async (options: IResendOptions): Promise => { + try { + const response = await axios({ + method: 'post', + url: getUrl('Resend'), + data: options, + }); + + const error = getError(response.data.ErrorCode); + + const d = { + error, + request: options, + response: response.data, + }; + debug(d); + options?.log && options.log(d); + + return { + error, + request: options, + response: response.data, + }; + } catch (error) { + return { + error, + request: options, + response: null, + }; + } +}; diff --git a/tinkoff/_utils.ts b/tinkoff/_utils.ts new file mode 100644 index 00000000..03efe7b8 --- /dev/null +++ b/tinkoff/_utils.ts @@ -0,0 +1,101 @@ +import crypto from 'crypto'; + +export const getUrl = method => `${process.env.PAYMENT_EACQ_AND_TEST_URL}/${method}`; + +export const errorsConverter = { + 4: 'invalid', + 7: 'invalid', + 53: 'broken', + 99: 'invalid', + 100: 'broken', + 101: 'invalid', + 102: 'invalid', + 103: 'broken', + 308: 'invalid', + 1006: 'invalid', + 1012: 'broken', + 1013: 'broken', + 1014: 'invalid', + 1030: 'broken', + 1033: 'invalid', + 1034: 'broken', + 1041: 'broken', + 1043: 'broken', + 1051: 'no-money', + 1054: 'invalid', + 1057: 'broken', + 1065: 'broken', + 1082: 'invalid', + 1089: 'broken', + 1091: 'broken', + 1096: 'broken', + 9999: 'invalid', +}; + +export const getError = errorCode => errorCode === '0' ? undefined : (errorsConverter[errorCode] || 'broken'); + +const _generateToken = (dataWithPassword) => { + const dataString = Object.keys(dataWithPassword) + .sort((a, b) => a.localeCompare(b)) + .map(key => dataWithPassword[key]) + .reduce((acc, item) => `${acc}${item}`, ''); + const hash = crypto + .createHash('sha256') + .update(dataString) + .digest('hex'); + return hash; +}; + +export const generateToken = (data) => { + const { Receipt, DATA, ...restData } = data; + console.log(restData); + const dataWithPassword = { ...restData, Password: process.env.PAYMENT_EACQ_TERMINAL_PASSWORD }; + return _generateToken(dataWithPassword); +}; + +export const generateTestToken = (data) => { + const { Receipt, DATA, ...restData } = data; + const dataWithPassword = { ...restData, Password: process.env.PAYMENT_TEST_TERMINAL_PASSWORD }; + return _generateToken(dataWithPassword); +}; + +export const tokenize = (options) => { + return { + ...options, + Token: generateToken(options), + }; +}; + +export interface IReceipt { + Items: IItem[]; + FfdVersion?: string; + Email?: string; + Phone?: string; + Taxation: string; + Payments?: object; +} + +export interface IItem { + Name: string; + Price: number; + Quantity: number; + Amount: number; + PaymentMethod?: string; + PaymentObject?: string; + Tax: string; +} + +export interface IReceipts { + ShopCode: string; + Items: IItem[]; + Email?: string; + Phone?: string; + Taxation: string; +} + +export interface IShops { + ShopCode: string; + Amount: number; + Name?: string; + Fee?: string; +} diff --git a/tinkoff/add-customer.ts b/tinkoff/add-customer.ts new file mode 100644 index 00000000..01c759a5 --- /dev/null +++ b/tinkoff/add-customer.ts @@ -0,0 +1,56 @@ +import axios from 'axios'; +import { getUrl, getError } from './_utils'; + +export interface IAddCustomerRequest { + (options: IAddCustomerOptions): Promise; +} + +export interface IAddCustomerOptions { + TerminalKey: string; + CustomerKey: string; + Email?: string; + Phone?: string; + Token: string; +} + +export interface IAddCustomerResponse { + error: string; + request: IAddCustomerOptions; + response: IAddCustomerPaymentResponse; +} + +export interface IAddCustomerPaymentResponse { + TerminalKey: string; + CustomerKey: string; + Success: boolean; + ErrorCode: string; + Message?: string; + Details?: string; +} + +export const addCustomer: IAddCustomerRequest = async (options: IAddCustomerOptions): Promise => { + try { + const response = await axios({ + method: 'post', + url: getUrl('AddCustomer'), + headers: { + 'Content-Type': 'application/json', + }, + data: options, + }); + + const error = getError(response.data.ErrorCode); + + return { + error, + request: options, + response: response.data, + }; + } catch (error) { + return { + error, + request: options, + response: null, + }; + } +}; diff --git a/tinkoff/cancel.ts b/tinkoff/cancel.ts new file mode 100644 index 00000000..85842d6e --- /dev/null +++ b/tinkoff/cancel.ts @@ -0,0 +1,57 @@ +import axios from 'axios'; +import { getUrl, IReceipt, getError } from './_utils'; + +export interface ICancelRequest { + (options: ICancelOptions): Promise; +} + +export interface ICancelOptions { + TerminalKey: string; + PaymentId: number; + Amount?: number; + Token: string; + Receipt?: IReceipt; +} + +export interface ICancelResponse { + error: string; + request: ICancelOptions; + response: ICancelPaymentResponse; +} + +export interface ICancelPaymentResponse { + TerminalKey: string; + Success: boolean; + Status: string; + PaymentId: number; + ErrorCode: string; + OrderId: string; + OriginalAmount: number; + NewAmount: number; + Message?: string; + Details?: string; +} + +export const cancel: ICancelRequest = async (options: ICancelOptions): Promise => { + try { + const response = await axios({ + method: 'post', + url: getUrl('Cancel'), + data: options, + }); + + const error = getError(response.data.ErrorCode); + + return { + error, + request: options, + response: response.data, + }; + } catch (error) { + return { + error, + request: options, + response: null, + }; + } +}; diff --git a/tinkoff/charge.ts b/tinkoff/charge.ts new file mode 100644 index 00000000..14bfceae --- /dev/null +++ b/tinkoff/charge.ts @@ -0,0 +1,60 @@ +import axios from 'axios'; +import { getUrl, getError } from './_utils'; + +export interface IChargeRequest { + (options: IChargeOptions): Promise; +} + +export interface IChargeOptions { + TerminalKey: string; + PaymentId: number; + RebillId: number; + SendEmail?: boolean; + InfoEmail?: boolean; + Token: string; +} + +export interface IChargeResponse { + error: string; + request: IChargeOptions; + response: IChargePaymentResponse; +} + +export interface IChargePaymentResponse { + TerminalKey: string; + OrderId: string; + Success: boolean; + Status: string; + PaymentId: number; + ErrorCode: string; + Amount: number; + Message?: string; + Details?: string; +} + +export const charge: IChargeRequest = async (options: IChargeOptions): Promise => { + try { + const response = await axios({ + method: 'post', + url: getUrl('Charge'), + headers: { + 'Content-Type': 'application/json', + }, + data: options, + }); + + const error = getError(response.data.ErrorCode); + + return { + error, + request: options, + response: response.data, + }; + } catch (error) { + return { + error, + request: options, + response: null, + }; + } +}; diff --git a/tinkoff/confirm.ts b/tinkoff/confirm.ts new file mode 100644 index 00000000..201d1f08 --- /dev/null +++ b/tinkoff/confirm.ts @@ -0,0 +1,60 @@ +import axios from 'axios'; +import { getUrl, IReceipt, IShops, getError } from './_utils'; + +export interface IConfirmRequest { + (options: IConfirmOptions): Promise; +} + +export interface IConfirmOptions { + TerminalKey: string; + PaymentId: number; + Token: string; + IP?: string; + Amount?: number; + Receipt?: IReceipt; + Shops?: IShops; + Receipts?: object; + Route?: 'ТСВ' | 'BNPL'; + Source?: 'Installment' | 'BNPL'; +} + +export interface IConfirmResponse { + error: string; + request: IConfirmOptions; + response: IConfirmPaymentResponse; +} + +export interface IConfirmPaymentResponse { + TerminalKey: string; + OrderId: string; + Success: boolean; + Status: string; + PaymentId: number; + ErrorCode: string; + Message?: string; + Details?: string; +} + +export const confirm: IConfirmRequest = async (options: IConfirmOptions): Promise => { + try { + const response = await axios({ + method: 'post', + url: getUrl('Confirm'), + data: options, + }); + + const error = getError(response.data.ErrorCode); + + return { + error, + request: options, + response: response.data, + }; + } catch (error) { + return { + error, + request: options, + response: null, + }; + } +}; diff --git a/tinkoff/finishAuth.ts b/tinkoff/finishAuth.ts new file mode 100644 index 00000000..23eacd24 --- /dev/null +++ b/tinkoff/finishAuth.ts @@ -0,0 +1,56 @@ +import axios from 'axios'; +import { getUrl, getError } from './_utils'; + +export interface IFinishAuthRequest { + (options: IFinishAuthOptions): Promise; +} + +export interface IFinishAuthOptions { + CardData: string; + PaymentId: number; + Token: string; + TerminalKey: string; +} + +export interface IFinishAuthResponse { + error: string; + request: IFinishAuthOptions; + response: IFinishAuthPaymentResponse; +} + +export interface IFinishAuthPaymentResponse { + Success: boolean; + ErrorCode: number; + TerminalKey: string; + Status: string; + PaymentId: number; + OrderId: string; + Amount: number; + ACSUrl: string; + MD: string; + PaReq: string; +} + +export const finishAuth: IFinishAuthRequest = async (options: IFinishAuthOptions): Promise => { + try { + const response = await axios({ + method: 'post', + url: getUrl('FinishAuthorize'), + data: options, + }); + + const error = getError(response.data.ErrorCode); + + return { + error, + request: options, + response: response.data, + }; + } catch (error) { + return { + error, + request: options, + response: null, + }; + } +}; diff --git a/tinkoff/get-card-list.ts b/tinkoff/get-card-list.ts new file mode 100644 index 00000000..9d9aca56 --- /dev/null +++ b/tinkoff/get-card-list.ts @@ -0,0 +1,56 @@ +import axios from 'axios'; +import { getUrl, getError } from './_utils'; + +export interface IGetCardListRequest { + (options: IGetCardListOptions): Promise; +} + +export interface IGetCardListOptions { + TerminalKey: string; + CustomerKey: string; + Token: string; +} + +export interface IGetCardListResponse { + error: string; + request: IGetCardListOptions; + response: IGetCardListPaymentResponse; +} + +export interface IGetCardListPaymentResponse { + Pan: string; + CardId: string; + Status: string; + RebillId?: string; + ExpDate: string; + CardType: number; +} + +export const getCardList: IGetCardListRequest = async (options: IGetCardListOptions): Promise => { + try { + const response = await axios({ + method: 'post', + url: getUrl('GetCardList'), + headers: { + 'Content-Type': 'application/json', + }, + data: options, + }); + + const error = getError(response.data.ErrorCode || '0'); + + console.log('response.data getCardList', response.data); + + return { + error, + request: options, + response: response.data, + }; + } catch (error) { + return { + error, + request: options, + response: null, + }; + } +}; diff --git a/tinkoff/get-customer.ts b/tinkoff/get-customer.ts new file mode 100644 index 00000000..59b6cd48 --- /dev/null +++ b/tinkoff/get-customer.ts @@ -0,0 +1,56 @@ +import axios from 'axios'; +import { getUrl, getError } from './_utils'; + +export interface IGetCustomerRequest { + (options: IGetCustomerOptions): Promise; +} + +export interface IGetCustomerOptions { + TerminalKey: string; + CustomerKey: string; + Token: string; +} + +export interface IGetCustomerResponse { + error: string; + request: IGetCustomerOptions; + response: IGetCustomerPaymentResponse; +} + +export interface IGetCustomerPaymentResponse { + TerminalKey: string; + CustomerKey: string; + Success: boolean; + ErrorCode: string; + Email?: string; + Phone?: string; + Message?: string; + Details?: string; +} + +export const getCustomer: IGetCustomerRequest = async (options: IGetCustomerOptions): Promise => { + try { + const response = await axios({ + method: 'post', + url: getUrl('GetCustomer'), + headers: { + 'Content-Type': 'application/json', + }, + data: options, + }); + + const error = getError(response.data.ErrorCode); + + return { + error, + request: options, + response: response.data, + }; + } catch (error) { + return { + error, + request: options, + response: null, + }; + } +}; diff --git a/tinkoff/get-state.ts b/tinkoff/get-state.ts new file mode 100644 index 00000000..4ceefe45 --- /dev/null +++ b/tinkoff/get-state.ts @@ -0,0 +1,54 @@ +import axios from 'axios'; +import { getUrl, getError } from './_utils'; + +export interface IGetStateRequest { + (options: IGetStateOptions): Promise; +} + +export interface IGetStateOptions { + TerminalKey: string; + PaymentId: number; + Amount?: number; + Token: string; +} + +export interface IGetStateResponse { + error: string; + request: IGetStateOptions; + response: IGetStatePaymentResponse; +} + +export interface IGetStatePaymentResponse { + TerminalKey: string; + OrderId: string; + Success: boolean; + Status: string; + PaymentId: number; + ErrorCode: string; + Message?: string; + Details?: string; +} + +export const getState: IGetStateRequest = async (options: IGetStateOptions): Promise => { + try { + const response = await axios({ + method: 'post', + url: getUrl('GetState'), + data: options, + }); + + const error = getError(response.data.ErrorCode); + + return { + error, + request: options, + response: response.data, + }; + } catch (error) { + return { + error, + request: options, + response: null, + }; + } +}; diff --git a/tinkoff/init.ts b/tinkoff/init.ts new file mode 100644 index 00000000..357dafca --- /dev/null +++ b/tinkoff/init.ts @@ -0,0 +1,80 @@ +import axios from 'axios'; +import { getUrl, getError, IReceipt } from './_utils'; + +const log = require('debug')('shakeapp-payments-event-handler'); +const debugResponse = log.extend('response.data'); + +export interface IInitRequest { + (options: IInitOptions): Promise; +} + +export interface IInitOptions { + TerminalKey: string; + OrderId: string; + Amount: number; + Description?: string; + CustomerKey?: string; + Language?: string; + Token?: string; + Recurrent?: string; + DATA?: IUserData; + PayType?: string; + Receipt?: IReceipt; + NotificationURL?: string; + SuccessURL?: string; + FailURL?: string; +} + +export interface IUserData { + Phone: string; + Email: string; +} + +export interface IInitResponse { + error: string; + request: IInitOptions; + response: IInitPaymentResponse; +} + +export interface IInitPaymentResponse { + TerminalKey: string; + Amount: number; + OrderId: string; + Success: boolean; + Status: string; + PaymentId: number; + ErrorCode: string; + PaymentURL?: string; + Message?: string; + Details?: string; +} + +export const init: IInitRequest = async (options: IInitOptions): Promise => { + try { + console.log('!!!!!!', getUrl('Init')); + const response = await axios({ + method: 'post', + url: getUrl('Init'), + headers: { + 'Content-Type': 'application/json', + }, + data: options, + }); + + const error = getError(response.data.ErrorCode); + + debugResponse(response.data); + + return { + error, + request: options, + response: response.data, + }; + } catch (error) { + return { + error, + request: options, + response: null, + }; + } +}; diff --git a/tinkoff/remove-customer.ts b/tinkoff/remove-customer.ts new file mode 100644 index 00000000..59b31670 --- /dev/null +++ b/tinkoff/remove-customer.ts @@ -0,0 +1,54 @@ +import axios from 'axios'; +import { getUrl, getError } from './_utils'; + +export interface IRemoveCustomerRequest { + (options: IRemoveCustomerOptions): Promise; +} + +export interface IRemoveCustomerOptions { + TerminalKey: string; + CustomerKey: string; + Token: string; +} + +export interface IRemoveCustomerResponse { + error: string; + request: IRemoveCustomerOptions; + response: IRemoveCustomerPaymentResponse; +} + +export interface IRemoveCustomerPaymentResponse { + TerminalKey: string; + CustomerKey: string; + Success: boolean; + ErrorCode: string; + Message?: string; + Details?: string; +} + +export const removeCustomer: IRemoveCustomerRequest = async (options: IRemoveCustomerOptions): Promise => { + try { + const response = await axios({ + method: 'post', + url: getUrl('RemoveCustomer'), + headers: { + 'Content-Type': 'application/json', + }, + data: options, + }); + + const error = getError(response.data.ErrorCode); + + return { + error, + request: options, + response: response.data, + }; + } catch (error) { + return { + error, + request: options, + response: null, + }; + } +}; diff --git a/tinkoff/resend.ts b/tinkoff/resend.ts new file mode 100644 index 00000000..f6f591a2 --- /dev/null +++ b/tinkoff/resend.ts @@ -0,0 +1,50 @@ +import axios from 'axios'; +import { getUrl, getError } from './_utils'; + +export interface IResendRequest { + (options: IResendOptions): Promise; +} + +export interface IResendOptions { + TerminalKey: string; + Token: string; +} + +export interface IResendResponse { + error: string; + request: IResendOptions; + response: IResendPaymentResponse; +} + +export interface IResendPaymentResponse { + TerminalKey: string; + Count: number; + Success: boolean; + ErrorCode: string; + Message?: string; + Details?: string; +} + +export const resend: IResendRequest = async (options: IResendOptions): Promise => { + try { + const response = await axios({ + method: 'post', + url: getUrl('Resend'), + data: options, + }); + + const error = getError(response.data.ErrorCode); + + return { + error, + request: options, + response: response.data, + }; + } catch (error) { + return { + error, + request: options, + response: null, + }; + } +};