@opengis/pay 1.3.2 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/README.md +45 -12
  2. package/module/pay/pt/marketplace-service-payment-client-template.html +5 -5
  3. package/package.json +9 -7
  4. package/plugin.js +2 -0
  5. package/server/migrations/7_transactions.sql +35 -0
  6. package/server/plugins/hook.js +27 -10
  7. package/server/plugins/payment/createPayment.js +29 -0
  8. package/server/plugins/payment/createTransaction.js +26 -0
  9. package/server/plugins/payment/liqpay.checkStatus.js +43 -0
  10. package/server/plugins/payment/liqpay.createTransaction.js +103 -0
  11. package/server/plugins/payment/monopay.checkStatus.js +48 -0
  12. package/server/plugins/payment/monopay.createTransaction.js +116 -0
  13. package/server/plugins/payment/paymentInfo.js +26 -0
  14. package/server/plugins/payment/portmone.checkStatus.js +51 -0
  15. package/server/plugins/payment/portmone.createTransaction.js +94 -0
  16. package/server/plugins/payment/processTransaction.js +73 -0
  17. package/server/routes/{portmone → core}/controllers/payment.info.js +32 -29
  18. package/server/routes/core/index.mjs +12 -0
  19. package/server/routes/liqpay/controllers/liqpay.redirect.js +25 -115
  20. package/server/routes/liqpay/controllers/liqpay.status.js +31 -150
  21. package/server/routes/monopay/controllers/monopay.redirect.js +25 -117
  22. package/server/routes/monopay/controllers/monopay.status.js +8 -56
  23. package/server/routes/portmone/controllers/portmone.redirect.js +27 -116
  24. package/server/routes/portmone/controllers/portmone.status.js +36 -136
  25. package/server/routes/portmone/index.mjs +5 -6
  26. package/utils.js +11 -0
  27. package/server/migrations/1_users.sql +0 -121
  28. package/server/migrations/2_accounts.sql +0 -59
  29. package/server/migrations/3_utils.sql +0 -74
  30. package/server/migrations/4_subscriptions.sql +0 -36
  31. package/server/migrations/5_payments.sql +0 -39
  32. package/server/migrations/6_invoices.sql +0 -55
  33. package/server/routes/liqpay/utils/check.status.js +0 -34
  34. package/server/routes/portmone/utils/portmone.check.status.js +0 -34
package/README.md CHANGED
@@ -1,29 +1,62 @@
1
- # pay
1
+ # OpenGIS Pay
2
2
 
3
3
  [![NPM version](https://img.shields.io/npm/v/@opengis/pay)](https://www.npmjs.com/package/@opengis/pay)
4
4
  [![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat)](http://standardjs.com/)
5
5
 
6
- It standardizes the entire payment process
6
+ Стандартизований NPM модуль для інтеграції різних платіжних систем з єдиним API.
7
7
 
8
- ## Install
8
+ ## Особливості
9
+
10
+ - **Підтримка різних платіжних систем**: Liqpay, Portmone, Monopay
11
+ - **Проста інтеграція**: NPM пакет з мінімальними налаштуваннями
12
+ - **Стандартизовані API**: Єдиний інтерфейс для роботи з різними платіжними системами
13
+ - **Безпека**: Надіжна обробка платежів з підтримкою webhook'ів
14
+
15
+ ## Встановлення
9
16
 
10
17
  ```bash
11
18
  npm i @opengis/pay
12
19
  ```
13
20
 
14
- ## Publish
21
+ ## Швидкий старт
15
22
 
16
- ```bash
17
- npm run build
18
- npm publish
23
+ ```js
24
+ fastify.register(import('@opengis/pay'), config);
19
25
  ```
20
26
 
21
- ## Usage
22
-
23
27
  ```js
24
- app.register(import('@opengis/pay'), config);
28
+ // config/env
29
+ {
30
+ "integrations": {
31
+ "pay": {
32
+ "host": "https://www.portmone.com.ua", // Посилання на portmone
33
+ "payeeId": 1185, // Ідентифікатор інтернет-магазину
34
+ "paymentRedirect": "/payments-success?id={{id}}"
35
+ },
36
+ "monopay": {
37
+ "host": "https://api.monobank.ua",
38
+ "token": "",
39
+ "paymentRedirect": "/payments-success?id={{id}}"
40
+ },
41
+ "liqpay": {
42
+ "publicKey": "sandbox_i7577029572",
43
+ "privateKey": "sandbox_JPqUTRby6eI0fMyIM1zRabka9rhtmjjt2ddDmmZL",
44
+ "paymentRedirect": "/payments-success?id={{id}}"
45
+ }
46
+ }
47
+ }
25
48
  ```
26
49
 
27
- ## Documenation
50
+ ## Документація
51
+
52
+ Для детального розуміння можливостей `@opengis/pay`, його функцій та способів використання, зверніться до нашої [Документації](https://apidocs.softpro.ua/pay).
53
+
54
+ ## Підтримувані платіжні системи
55
+
56
+ - **Liqpay** - Українська платіжна система
57
+ - **Portmone** - Міжнародна платіжна система
58
+ - **Monopay** - API Monobank для прийому платежів
59
+
60
+ ## Ліцензія
28
61
 
29
- For a detailed understanding of `pay plugin` for billing, its features, and how to use them, refer to our [Documentation](https://apidocs.softpro.ua/opengis/plugin/pay.html).
62
+ MIT
@@ -33,14 +33,14 @@
33
33
  <br>
34
34
  <span style="color:#61677b"><strong>Дата створення платежу:</strong> {{created_at}}</span>
35
35
  <br>
36
- <span style="color:#61677b"><strong>Статус платежу:</strong> {{payment_status}}</span>
37
- {{#if payment_amount}}
36
+ <span style="color:#61677b"><strong>Статус платежу:</strong> {{status}}</span>
37
+ {{#if amount}}
38
38
  <br>
39
- <span style="color:#61677b"><strong>Поповнення рахунку:</strong> {{payment_amount}}</span>
39
+ <span style="color:#61677b"><strong>Поповнення рахунку:</strong> {{amount}}</span>
40
40
  {{/if}}
41
- {{#if total_balance}}
41
+ {{#if balance}}
42
42
  <br>
43
- <span style="color:#61677b"><strong>Поточний баланс:</strong> {{total_balance}}</span>
43
+ <span style="color:#61677b"><strong>Поточний баланс:</strong> {{balance}}</span>
44
44
  {{/if}}
45
45
  </td>
46
46
  <td style="padding:7px 0" width="10">&nbsp;</td>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opengis/pay",
3
- "version": "1.3.2",
3
+ "version": "2.0.0",
4
4
  "type": "module",
5
5
  "description": "pay plugins for billing",
6
6
  "main": "plugin.js",
@@ -20,18 +20,20 @@
20
20
  "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore"
21
21
  },
22
22
  "dependencies": {
23
- "ioredis": "5.3.2",
24
23
  "liqpayjs-sdk": "3.3.1",
25
- "nodemailer": "6.5.0",
26
- "pg": "8.11.3",
27
24
  "request-promise": "4.2.6"
28
25
  },
29
26
  "devDependencies": {
27
+ "dotenv": "^17.2.2",
28
+ "eslint": "8.49.0",
29
+ "eslint-config-airbnb": "19.0.4",
30
30
  "fastify": "5.3.3",
31
31
  "fastify-plugin": "5.0.1",
32
- "eslint": "8.49.0",
33
- "eslint-config-airbnb": "19.0.4"
32
+ "ioredis": "5.3.2",
33
+ "nodemailer": "6.5.0",
34
+ "pg": "8.11.3",
35
+ "vitest": "^3.2.4"
34
36
  },
35
37
  "author": "Softpro",
36
38
  "license": "ISC"
37
- }
39
+ }
package/plugin.js CHANGED
@@ -1,3 +1,4 @@
1
+ /* eslint-disable no-param-reassign */
1
2
  import fp from 'fastify-plugin';
2
3
  import path from 'node:path';
3
4
  import { fileURLToPath } from 'url';
@@ -22,6 +23,7 @@ async function plugin(app, config = {}) {
22
23
  app.register(import('./server/routes/portmone/index.mjs'), config);
23
24
  app.register(import('./server/routes/liqpay/index.mjs'), config);
24
25
  app.register(import('./server/routes/monopay/index.mjs'), config);
26
+ app.register(import('./server/routes/core/index.mjs'), config);
25
27
 
26
28
  execMigrations(path.join(dirname, 'server/migrations'), pg).catch(err => console.log(err));
27
29
  }
@@ -0,0 +1,35 @@
1
+ create schema if not exists pay;
2
+
3
+ CREATE TABLE if not exists pay.transactions();
4
+ alter table pay.transactions drop constraint if exists pay_transactions_transaction_id_pkey cascade;
5
+
6
+ alter table pay.transactions add column if not exists transaction_id text NOT NULL default next_id();
7
+ alter table pay.transactions add column if not exists user_id text;
8
+ alter table pay.transactions add column if not exists status text NOT NULL DEFAULT 'inprogress'::text;
9
+ alter table pay.transactions add column if not exists success_date timestamp with time zone;
10
+ alter table pay.transactions add column if not exists data json;
11
+ alter table pay.transactions add column if not exists amount numeric;
12
+ alter table pay.transactions add column if not exists service text;
13
+ alter table pay.transactions add column if not exists balance numeric;
14
+ alter table pay.transactions add column if not exists referer_url text;
15
+ alter table pay.transactions add column if not exists created_at timestamp without time zone DEFAULT now();
16
+ alter table pay.transactions add column if not exists created_by text NOT NULL default '0';
17
+ alter table pay.transactions add column if not exists updated_by text;
18
+ alter table pay.transactions add column if not exists updated_at timestamp without time zone;
19
+ alter table pay.transactions add column if not exists invoice_id text;
20
+
21
+ COMMENT ON TABLE pay.transactions IS 'Платежі';
22
+ COMMENT ON COLUMN pay.transactions.transaction_id IS 'ID';
23
+ COMMENT ON COLUMN pay.transactions.user_id IS 'ID користувача';
24
+ COMMENT ON COLUMN pay.transactions.status IS 'Статус транзації';
25
+ COMMENT ON COLUMN pay.transactions.success_date IS 'Дата успішного завершення транзакції';
26
+ COMMENT ON COLUMN pay.transactions.data IS 'Дані від сервісу оплати';
27
+ COMMENT ON COLUMN pay.transactions.service IS 'Сервіс оплати (portmone, liqpay)';
28
+ COMMENT ON COLUMN pay.transactions.amount IS 'Сума поповнення рахунку';
29
+ COMMENT ON COLUMN pay.transactions.balance IS 'Поточний баланс';
30
+ COMMENT ON COLUMN pay.transactions.referer_url IS 'Посилання';
31
+ COMMENT ON COLUMN pay.transactions.invoice_id IS 'ID транзакції з сервісу оплати';
32
+
33
+ CREATE INDEX if not exists pay_transactions_transactions_status_btree_idx ON pay.transactions USING btree (status COLLATE pg_catalog."default");
34
+
35
+ alter table pay.transactions add CONSTRAINT pay_transactions_transaction_id_pkey PRIMARY KEY (transaction_id);
@@ -1,24 +1,41 @@
1
1
  // import addCron from '../utils/cron/addCron.js';
2
+ import config from '../../config.js';
2
3
  import pgClients from './pg/pgClients.js';
3
4
 
4
- import updateTrxStatus from '../routes/portmone/utils/portmone.check.status.js';
5
+ import updatePortmoneTrxStatus from './payment/portmone.checkStatus.js';
6
+ import updateLiqPayTrxStatus from './payment/liqpay.checkStatus.js';
7
+ import updateMonoPayTrxStatus from './payment/monopay.checkStatus.js';
5
8
 
6
- // to do: for liqpay and monopay
7
- async function updateOldTrxsStatus() {
8
- const inprogress = pgClients.client.pk?.['billing.payments']
9
- ? await pgClients.client.query(`select array_agg(payment_id) as inprogress from billing.payments
10
- where payment_service='portmone' and payment_status = 'inprogress' and (now() - cdate) > '1 hour'::interval`).then(el => el.rows?.[0]?.inprogress || [])
9
+ const { table = 'pay.transactions' } = config.integrations?.pay || {};
10
+
11
+ async function updateOldTrxsStatus(service) {
12
+ const inprogress = pgClients.client.pk?.[table]
13
+ ? await pgClients.client.query(`select ${pgClients.client.pk?.[table]} as id, service from ${table}
14
+ where service=$1 and status = 'inprogress' and (now() - created_at) > '1 hour'::interval`, [service]).then(el => el.rows || [])
11
15
  : [];
12
16
 
13
17
  if (!inprogress?.length) return 'empty rows';
14
18
 
15
- const res = await Promise.all(inprogress.map(async (paymentId) => {
16
- const data = await updateTrxStatus({ paymentId });
17
- return { paymentId, data };
19
+ return; // !refactor required
20
+ const res = await Promise.all(inprogress.map(async ({ id, service }) => {
21
+ if (service === 'portmone') {
22
+ const data = await updatePortmoneTrxStatus(id);
23
+ return { paymentId: id, data };
24
+ }
25
+ if (service === 'liqpay') {
26
+ const data = await updateLiqPayTrxStatus(id);
27
+ return { paymentId: id, data };
28
+ }
29
+ if (service === 'monopay') {
30
+ const data = await updateMonoPayTrxStatus(id);
31
+ return { paymentId: id, data };
32
+ }
33
+ return null;
18
34
  }));
19
35
  return res;
20
36
  }
21
37
 
22
38
  export default async function cron() {
23
- // addCron(updateOldTrxsStatus, 60 * 60 * 24);
39
+ // !disabled
40
+ // addCron(updateOldTrxsStatus, 60 * 60 * 24);
24
41
  }
@@ -0,0 +1,29 @@
1
+ import config from '../../../config.js';
2
+ import pgClients from '../pg/pgClients.js';
3
+ import dataInsert from '../crud/dataInsert.js';
4
+
5
+ const { paymentRedirect, table = 'pay.transactions' } = config.integrations?.pay || {};
6
+
7
+ export default async function createPayment({
8
+ paymentId, price, referer, uid, service,
9
+ }, pg = pgClients.client) {
10
+ if (!paymentId) { throw new Error('not enough params: paymentId'); }
11
+ if (!uid) { throw new Error('not enough params: uid'); }
12
+ if (!service) { throw new Error('not enough params: service'); }
13
+ if (!price) { throw new Error('not enough params: price'); }
14
+
15
+ const result = await dataInsert({
16
+ id: paymentId,
17
+ pg,
18
+ table,
19
+ data: {
20
+ referer_url: paymentRedirect ? paymentRedirect.replace(/{{id}}/g, paymentId) : referer,
21
+ amount: price,
22
+ user_id: uid,
23
+ service,
24
+ status: 'inprogress',
25
+ },
26
+ uid,
27
+ }).then(el => el.rows?.[0]);
28
+ return { id: paymentId, ...result || {} };
29
+ }
@@ -0,0 +1,26 @@
1
+ import pgClients from '../pg/pgClients.js';
2
+
3
+ import liqpayCreateTransaction from './liqpay.createTransaction.js';
4
+
5
+ import portmoneCreateTransaction from './portmone.createTransaction.js';
6
+
7
+ import monopayCreateTransaction from './monopay.createTransaction.js';
8
+
9
+ const controller = {
10
+ liqpay: liqpayCreateTransaction,
11
+ monopay: monopayCreateTransaction,
12
+ portmone: portmoneCreateTransaction,
13
+ };
14
+
15
+ export default async function createTransaction({
16
+ id, price, rootUrl, referer, uid, debug, service,
17
+ }, pg = pgClients.client) {
18
+ if (!service) throw new Error('not enough params: service');
19
+ if (!controller[service]) { throw new Error('invalid params: service'); }
20
+ if (price && !uid) throw new Error('not enough params: uid');
21
+
22
+ const result = await controller[service]({
23
+ id, price, rootUrl, referer, uid, debug,
24
+ }, pg);
25
+ return result;
26
+ }
@@ -0,0 +1,43 @@
1
+ import LiqPay from 'liqpayjs-sdk';
2
+ import request from 'request-promise';
3
+
4
+ import config from '../../../config.js';
5
+ import pgClients from '../pg/pgClients.js';
6
+
7
+ const { table = 'pay.transactions' } = config.integrations?.pay || {};
8
+ const { host = 'https://www.liqpay.ua', publicKey, privateKey } = config.integrations?.liqpay || {};
9
+
10
+ const liqpay = new LiqPay(publicKey, privateKey);
11
+
12
+ export default async function liqpayCheckStatus(id, origin, pg = pgClients.client) {
13
+ if (!id) throw new Error('not enough params: id');
14
+ if (!publicKey || !privateKey) throw new Error('empty integration params: liqpay');
15
+
16
+ const options = {
17
+ public_key: publicKey, // in case of liqpay.api is auto-assigned
18
+ action: 'status',
19
+ version: '3',
20
+ order_id: id,
21
+ };
22
+ /* const result = liqpay.api('request', options); */
23
+
24
+ const data = Buffer.from(JSON.stringify(options)).toString('base64');
25
+ const signature = liqpay.str_to_sign(privateKey + data + privateKey);
26
+
27
+ const isValidOrigin = origin && origin === host;
28
+
29
+ if (process.env.NODE_ENV === 'test' && isValidOrigin) {
30
+ const amount = pg?.pk?.[table] ? await pg.query(`select amount from ${table} where ${pg?.pk?.[table]}=$1`, [id]).then(el => el.rows?.[0]?.amount) : 0;
31
+ return { unittest: true, amount, status: 'success' };
32
+ }
33
+
34
+ const response = await request(`${host}/api/request`, {
35
+ method: 'POST',
36
+ form: { data, signature },
37
+ headers: {
38
+ 'Content-Type': 'application/x-www-form-urlencoded',
39
+ },
40
+ });
41
+ const result = response?.startsWith?.('{') ? JSON.parse(response) : response;
42
+ return result;
43
+ }
@@ -0,0 +1,103 @@
1
+ import request from 'request-promise';
2
+ import LiqPay from 'liqpayjs-sdk';
3
+
4
+ import config from '../../../config.js';
5
+ import pgClients from '../pg/pgClients.js';
6
+
7
+ import createPayment from './createPayment.js';
8
+
9
+ const { prefix = '/api' } = config;
10
+ const { table = 'pay.transactions' } = config.integrations?.pay || {};
11
+
12
+ const {
13
+ host = 'https://www.liqpay.ua',
14
+ publicKey,
15
+ privateKey,
16
+ } = config.integrations?.liqpay || {};
17
+ const liqpay = new LiqPay(publicKey, privateKey);
18
+
19
+ export default async function liqpayCreateTransaction({
20
+ id, price, rootUrl, referer, uid, debug,
21
+ }, pg = pgClients.client) {
22
+ if (!publicKey || !privateKey) {
23
+ throw new Error('invalid liqpay integration settings');
24
+ }
25
+
26
+ if (!id && !price) {
27
+ throw new Error('not enough params: id or price required');
28
+ }
29
+
30
+ if (!rootUrl) {
31
+ throw new Error('not enough params: rootUrl required');
32
+ }
33
+
34
+ if (!uid) {
35
+ throw new Error('not enough params: uid required');
36
+ }
37
+
38
+ const paymentId = !id ? await pg.query('select next_id()').then(el => el.rows?.[0]?.next_id) : id;
39
+
40
+ const paymentData = id && pg.pk?.[table]
41
+ ? await pg.query(`select ${pg.pk?.[table]} as id, * from ${table} where ${pg.pk?.[table]}=$1`, [id]).then(el => el.rows?.[0])
42
+ : await createPayment({
43
+ paymentId,
44
+ price,
45
+ referer,
46
+ uid,
47
+ service: 'liqpay',
48
+ });
49
+
50
+ if (!paymentData?.id) {
51
+ throw new Error('payment not found / creation failed');
52
+ }
53
+
54
+ const paymentAmount = id ? paymentData.amount : price;
55
+
56
+ if (!paymentAmount) {
57
+ throw new Error('invalid payment amount');
58
+ }
59
+
60
+ const paymentOptions = {
61
+ action: 'pay',
62
+ amount: paymentAmount,
63
+ currency: 'UAH',
64
+ description: 'regular payment',
65
+ order_id: paymentData.id,
66
+ version: '3',
67
+ // server_url: `${rootUrl}${prefix}/liqpay/${paymentData.id}/success`, // update payment info - api url, does not work on sdk package out of the box
68
+ result_url: `${rootUrl}${prefix}/liqpay/${paymentData.id}/success`, // redirect user after payment - page url
69
+ };
70
+
71
+ // html interface for debug via browser only
72
+ if (process.env.NODE_ENV !== 'test' && debug) {
73
+ return liqpay.cnb_form(paymentOptions);
74
+ }
75
+
76
+ const { data, signature } = liqpay.cnb_object(paymentOptions) || {};
77
+
78
+ const response = await request({
79
+ url: `${host}/api/3/checkout`,
80
+ method: 'POST',
81
+ form: { data, signature },
82
+ followAllRedirects: false,
83
+ jar: true,
84
+ timeout: 6000,
85
+ simple: false, // prevent not 2xx code trigger catch
86
+ resolveWithFullResponse: true,
87
+ });
88
+
89
+ if (response.statusCode !== 302 || !response.headers?.location) {
90
+ throw new Error(response);
91
+ }
92
+
93
+ if (response.headers?.location?.includes?.('throw_error')) {
94
+ throw new Error(response.headers?.location);
95
+ }
96
+
97
+ // debug via unt test
98
+ if (process.env.NODE_ENV === 'test' && debug) {
99
+ return { url: response.headers.location, id: paymentData.id };
100
+ }
101
+
102
+ return response.headers.location;
103
+ }
@@ -0,0 +1,48 @@
1
+ import config from '../../../config.js';
2
+ import pgClients from '../pg/pgClients.js';
3
+
4
+ const { table = 'pay.transactions' } = config.integrations?.pay || {};
5
+
6
+ const {
7
+ host = 'https://api.monobank.ua',
8
+ token,
9
+ } = config.integrations?.monopay || {};
10
+
11
+ const timeoutMs = 5000;
12
+
13
+ export default async function monopayCheckStatus(id, origin, pg = pgClients.client) {
14
+ if (!id) { throw new Error('not enough params: id'); }
15
+
16
+ const {
17
+ paymentId, invoice_id: invoiceId, amount,
18
+ } = await pg.query(`select ${pg.pk?.[table]} as "paymentId", * from ${table} where ${pg.pk?.[table]}=$1`, [id])
19
+ .then(el => el.rows?.[0] || {});
20
+
21
+ if (!paymentId) {
22
+ throw new Error('payment not found');
23
+ }
24
+
25
+ const isValidOrigin = origin && origin === host;
26
+
27
+ if (process.env.NODE_ENV === 'test' && isValidOrigin) {
28
+ return { unittest: true, amount, status: 'success' };
29
+ }
30
+
31
+ if (!invoiceId) {
32
+ throw new Error('invoice not found');
33
+ }
34
+
35
+ const resp = await fetch(`${host}/api/merchant/invoice/status?invoiceId=${invoiceId}`, {
36
+ method: 'GET',
37
+ headers: { 'x-token': token },
38
+ signal: AbortSignal.timeout(timeoutMs),
39
+ });
40
+
41
+ const contentType = resp.headers.get('Content-Type');
42
+
43
+ const body = contentType.includes('application/json') && resp.status === 200
44
+ ? await resp.json()
45
+ : { error: await resp.text() };
46
+
47
+ return { ...body, amount: body.amount / 100, status: body.status };
48
+ }
@@ -0,0 +1,116 @@
1
+ import config from '../../../config.js';
2
+ import pgClients from '../pg/pgClients.js';
3
+ import dataUpdate from '../crud/dataUpdate.js';
4
+
5
+ import createPayment from './createPayment.js';
6
+
7
+ const { prefix = '/api' } = config;
8
+ const { table = 'pay.transactions' } = config.integrations?.pay || {};
9
+
10
+ const {
11
+ host = 'https://api.monobank.ua',
12
+ token,
13
+ merchantPaymInfo,
14
+ } = config.integrations?.monopay || {};
15
+
16
+ const timeoutMs = 5000;
17
+
18
+ export default async function monopayCreateTransaction({
19
+ id, price, rootUrl, referer, uid, debug,
20
+ }, pg = pgClients.client) {
21
+ if (!host) {
22
+ throw new Error('empty integration host');
23
+ }
24
+
25
+ if (!token) {
26
+ throw new Error('empty integration token');
27
+ }
28
+
29
+ if (!id && !price) {
30
+ throw new Error('not enough params: id or price required');
31
+ }
32
+
33
+ if (!rootUrl) {
34
+ throw new Error('not enough params: rootUrl required');
35
+ }
36
+
37
+ if (!uid) {
38
+ throw new Error('not enough params: uid required');
39
+ }
40
+
41
+ const paymentId = !id ? await pg.query('select next_id()').then(el => el.rows?.[0]?.next_id) : id;
42
+
43
+ const paymentData = id && pg.pk?.[table]
44
+ ? await pg.query(`select ${pg.pk?.[table]} as id, * from ${table} where ${pg.pk?.[table]}=$1`, [id]).then(el => el.rows?.[0])
45
+ : await createPayment({
46
+ paymentId,
47
+ price,
48
+ referer,
49
+ uid,
50
+ service: 'monopay',
51
+ });
52
+
53
+ if (!paymentData?.id) {
54
+ throw new Error('payment not found / creation failed');
55
+ }
56
+ const paymentAmount = id ? paymentData.amount : price;
57
+
58
+ if (!paymentAmount) {
59
+ throw new Error('invalid payment amount');
60
+ }
61
+
62
+ // redirect to api to check payment status and redirect user to page afterwards
63
+ const serverUrl = `${rootUrl}${prefix}/monopay/${paymentId}/success`;
64
+
65
+ const paymentOptions = {
66
+ amount: paymentAmount * 100, // price=1 === 0.01 uah
67
+ ccy: 980, // ISO 4217, 980 = UAH
68
+ merchantPaymInfo,
69
+ redirectUrl: serverUrl,
70
+ // redirectUrl: (paymentRedirect ? `${rootPageUrl}${paymentRedirect.replace(/{{id}}/g, paymentId)}` : headers?.referer) || rootPageUrl,
71
+ webHookUrl: serverUrl,
72
+ validity: 86400, // 1 day by default, then invalid
73
+ paymentType: 'debit',
74
+ // displayType: 'iframe', // default = null > url to mono
75
+ };
76
+
77
+ const resp = await fetch(`${host}/api/merchant/invoice/create`, {
78
+ method: 'POST',
79
+ headers: { 'x-token': token },
80
+ body: JSON.stringify(paymentOptions),
81
+ signal: AbortSignal.timeout(timeoutMs),
82
+ });
83
+
84
+ const contentType = resp.headers.get('Content-Type');
85
+
86
+ const response = contentType.includes('application/json') && resp.status === 200
87
+ ? await resp.json()
88
+ : await resp.text();
89
+
90
+ // !save invoice ID to check status later
91
+ await dataUpdate({
92
+ table,
93
+ id: paymentId,
94
+ data: {
95
+ invoice_id: response?.invoiceId,
96
+ data: resp.status === 200 ? undefined : { response },
97
+ status: resp.status === 200 ? 'inprogress' : 'error',
98
+ },
99
+ uid,
100
+ });
101
+
102
+ if (process.env.NODE_ENV !== 'test' && debug) {
103
+ return response;
104
+ }
105
+
106
+ // handle exceptions
107
+ if (!response?.pageUrl) {
108
+ throw new Error(response);
109
+ }
110
+
111
+ if (process.env.NODE_ENV === 'test' && debug) {
112
+ return { id: paymentId, url: response.pageUrl };
113
+ }
114
+
115
+ return response.pageUrl;
116
+ }
@@ -0,0 +1,26 @@
1
+ import config from '../../../config.js';
2
+ import pgClients from '../pg/pgClients.js';
3
+
4
+ const { table = 'pay.transactions' } = config.integrations?.pay || {};
5
+
6
+ export default async function paymentInfo(id, pg = pgClients.client) {
7
+ if (!id) throw new Error('not enough params: id');
8
+
9
+ const {
10
+ amount,
11
+ status,
12
+ service,
13
+ referer_url: referer,
14
+ user_id: userId,
15
+ } = id && pg.pk?.[table] ? await pg.query(`select ${pg.pk?.[table]} as id, * from ${table} where ${pg.pk?.[table]}=$1`, [id])
16
+ .then(el => el.rows?.[0] || {}) : {};
17
+
18
+ return {
19
+ id, // id for existing transtaction
20
+ price: amount, // price for new transtaction as is, else - preCreated
21
+ status, // payment status
22
+ service, // service name
23
+ referer, // redirect user after finish of transaction
24
+ uid: userId,
25
+ };
26
+ }