@opengis/pay 1.3.3 → 2.0.1
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.
- package/README.md +45 -12
- package/module/pay/pt/marketplace-service-payment-client-template.html +5 -5
- package/package.json +39 -37
- package/plugin.js +2 -0
- package/server/migrations/7_transactions.sql +35 -0
- package/server/plugins/hook.js +27 -10
- package/server/plugins/payment/createPayment.js +29 -0
- package/server/plugins/payment/createTransaction.js +26 -0
- package/server/plugins/payment/liqpay.checkStatus.js +43 -0
- package/server/plugins/payment/liqpay.createTransaction.js +103 -0
- package/server/plugins/payment/monopay.checkStatus.js +48 -0
- package/server/plugins/payment/monopay.createTransaction.js +116 -0
- package/server/plugins/payment/paymentInfo.js +26 -0
- package/server/plugins/payment/portmone.checkStatus.js +51 -0
- package/server/plugins/payment/portmone.createTransaction.js +94 -0
- package/server/plugins/payment/processTransaction.js +73 -0
- package/server/routes/{portmone → core}/controllers/payment.info.js +32 -29
- package/server/routes/core/index.mjs +12 -0
- package/server/routes/liqpay/controllers/liqpay.redirect.js +25 -115
- package/server/routes/liqpay/controllers/liqpay.status.js +31 -150
- package/server/routes/monopay/controllers/monopay.redirect.js +25 -117
- package/server/routes/monopay/controllers/monopay.status.js +8 -56
- package/server/routes/portmone/controllers/portmone.redirect.js +27 -116
- package/server/routes/portmone/controllers/portmone.status.js +36 -136
- package/server/routes/portmone/index.mjs +5 -6
- package/utils.js +11 -0
- package/server/migrations/1_users.sql +0 -121
- package/server/migrations/2_accounts.sql +0 -59
- package/server/migrations/3_utils.sql +0 -74
- package/server/migrations/4_subscriptions.sql +0 -36
- package/server/migrations/5_payments.sql +0 -39
- package/server/migrations/6_invoices.sql +0 -55
- package/server/routes/liqpay/utils/check.status.js +0 -34
- package/server/routes/portmone/utils/portmone.check.status.js +0 -34
package/README.md
CHANGED
|
@@ -1,29 +1,62 @@
|
|
|
1
|
-
#
|
|
1
|
+
# OpenGIS Pay
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/@opengis/pay)
|
|
4
4
|
[](http://standardjs.com/)
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
Стандартизований NPM модуль для інтеграції різних платіжних систем з єдиним API.
|
|
7
7
|
|
|
8
|
-
##
|
|
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
|
-
##
|
|
21
|
+
## Швидкий старт
|
|
15
22
|
|
|
16
|
-
```
|
|
17
|
-
|
|
18
|
-
npm publish
|
|
23
|
+
```js
|
|
24
|
+
fastify.register(import('@opengis/pay'), config);
|
|
19
25
|
```
|
|
20
26
|
|
|
21
|
-
## Usage
|
|
22
|
-
|
|
23
27
|
```js
|
|
24
|
-
|
|
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
|
-
##
|
|
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
|
-
|
|
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> {{
|
|
37
|
-
{{#if
|
|
36
|
+
<span style="color:#61677b"><strong>Статус платежу:</strong> {{status}}</span>
|
|
37
|
+
{{#if amount}}
|
|
38
38
|
<br>
|
|
39
|
-
<span style="color:#61677b"><strong>Поповнення рахунку:</strong> {{
|
|
39
|
+
<span style="color:#61677b"><strong>Поповнення рахунку:</strong> {{amount}}</span>
|
|
40
40
|
{{/if}}
|
|
41
|
-
{{#if
|
|
41
|
+
{{#if balance}}
|
|
42
42
|
<br>
|
|
43
|
-
<span style="color:#61677b"><strong>Поточний баланс:</strong> {{
|
|
43
|
+
<span style="color:#61677b"><strong>Поточний баланс:</strong> {{balance}}</span>
|
|
44
44
|
{{/if}}
|
|
45
45
|
</td>
|
|
46
46
|
<td style="padding:7px 0" width="10"> </td>
|
package/package.json
CHANGED
|
@@ -1,37 +1,39 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@opengis/pay",
|
|
3
|
-
"version": "
|
|
4
|
-
"type": "module",
|
|
5
|
-
"description": "pay plugins for billing",
|
|
6
|
-
"main": "plugin.js",
|
|
7
|
-
"browser": "plugin.js",
|
|
8
|
-
"files": [
|
|
9
|
-
"server/*",
|
|
10
|
-
"module/*",
|
|
11
|
-
"plugin.js",
|
|
12
|
-
"config.js",
|
|
13
|
-
"utils.js"
|
|
14
|
-
],
|
|
15
|
-
"scripts": {
|
|
16
|
-
"patch": "npm version patch && git push && npm publish",
|
|
17
|
-
"test": "node --test",
|
|
18
|
-
"start": "node --watch-path=server server",
|
|
19
|
-
"admin": "set NODE_ENV=admin&& npm run start",
|
|
20
|
-
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore"
|
|
21
|
-
},
|
|
22
|
-
"dependencies": {
|
|
23
|
-
"liqpayjs-sdk": "3.3.1",
|
|
24
|
-
"request-promise": "4.2.6"
|
|
25
|
-
},
|
|
26
|
-
"devDependencies": {
|
|
27
|
-
"
|
|
28
|
-
"
|
|
29
|
-
"
|
|
30
|
-
"fastify": "5.3.3",
|
|
31
|
-
"fastify-plugin": "5.0.1",
|
|
32
|
-
"
|
|
33
|
-
"
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "@opengis/pay",
|
|
3
|
+
"version": "2.0.1",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "pay plugins for billing",
|
|
6
|
+
"main": "plugin.js",
|
|
7
|
+
"browser": "plugin.js",
|
|
8
|
+
"files": [
|
|
9
|
+
"server/*",
|
|
10
|
+
"module/*",
|
|
11
|
+
"plugin.js",
|
|
12
|
+
"config.js",
|
|
13
|
+
"utils.js"
|
|
14
|
+
],
|
|
15
|
+
"scripts": {
|
|
16
|
+
"patch": "npm version patch && git push && npm publish",
|
|
17
|
+
"test": "node --test",
|
|
18
|
+
"start": "node --watch-path=server server",
|
|
19
|
+
"admin": "set NODE_ENV=admin&& npm run start",
|
|
20
|
+
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore"
|
|
21
|
+
},
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"liqpayjs-sdk": "3.3.1",
|
|
24
|
+
"request-promise": "4.2.6"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"dotenv": "^17.2.2",
|
|
28
|
+
"eslint": "8.49.0",
|
|
29
|
+
"eslint-config-airbnb": "19.0.4",
|
|
30
|
+
"fastify": "5.3.3",
|
|
31
|
+
"fastify-plugin": "5.0.1",
|
|
32
|
+
"ioredis": "5.3.2",
|
|
33
|
+
"nodemailer": "6.5.0",
|
|
34
|
+
"pg": "8.11.3",
|
|
35
|
+
"vitest": "^3.2.4"
|
|
36
|
+
},
|
|
37
|
+
"author": "Softpro",
|
|
38
|
+
"license": "ISC"
|
|
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);
|
package/server/plugins/hook.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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
|
-
//
|
|
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
|
+
}
|