@opengis/pay 1.2.3 → 1.3.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.
- package/config.js +7 -0
- package/module/pay/pt/marketplace-service-payment-client-template.html +7 -20
- package/package.json +15 -31
- package/plugin.js +13 -10
- package/server/plugins/crud/dataDelete.js +82 -0
- package/server/plugins/crud/dataInsert.js +76 -0
- package/server/plugins/crud/dataUpdate.js +112 -0
- package/server/plugins/{cron.js → hook.js} +24 -22
- package/server/plugins/pg/funcs/autoIndex.js +102 -0
- package/server/plugins/pg/funcs/getDBParams.js +15 -0
- package/server/plugins/pg/funcs/getMeta.js +48 -0
- package/server/plugins/pg/funcs/getPG.js +39 -0
- package/server/plugins/pg/funcs/getPGAsync.js +40 -0
- package/server/plugins/pg/funcs/init.js +113 -0
- package/server/plugins/pg/index.js +24 -0
- package/server/plugins/pg/pgClients.js +22 -0
- package/server/plugins/redis/client.js +8 -0
- package/server/plugins/redis/funcs/getRedis.js +24 -0
- package/server/plugins/redis/funcs/redisClients.js +3 -0
- package/server/plugins/redis/index.js +11 -0
- package/server/routes/liqpay/controllers/liqpay.redirect.js +7 -11
- package/server/routes/liqpay/controllers/liqpay.status.js +26 -15
- package/server/routes/liqpay/utils/check.status.js +1 -1
- package/server/routes/monopay/controllers/monopay.redirect.js +141 -0
- package/server/routes/monopay/controllers/monopay.status.js +70 -0
- package/server/routes/monopay/index.mjs +32 -0
- package/server/routes/portmone/controllers/payment.info.js +9 -9
- package/server/routes/portmone/controllers/portmone.redirect.js +23 -17
- package/server/routes/portmone/controllers/portmone.status.js +49 -38
- package/server/routes/portmone/utils/portmone.check.status.js +34 -33
- package/server/utils/cron/addCron.js +48 -0
- package/server/utils/cron/cronList.js +1 -0
- package/server/utils/cron/interval2ms.js +40 -0
- package/server/utils/cron/runCron.js +24 -0
- package/server/utils/cron/verifyUnique.js +23 -0
- package/server/utils/getFolder.js +11 -0
- package/server/utils/getInsertQuery.js +44 -0
- package/server/utils/migration/exec.migrations.js +38 -0
- package/server/utils/migration/exec.sql.js +48 -0
- package/server/utils/sendNotification.js +69 -0
- package/module/pay1/card/billing.accounts.table/general_info.hbs +0 -11
- package/module/pay1/card/billing.accounts.table/index.yml +0 -15
- package/module/pay1/card/billing.accounts.table/users.hbs +0 -12
- package/module/pay1/card/billing.payments.table/general_info.hbs +0 -13
- package/module/pay1/card/billing.payments.table/index.yml +0 -10
- package/module/pay1/card/billing.users.table/general_info.hbs +0 -11
- package/module/pay1/card/billing.users.table/index.yml +0 -10
- package/module/pay1/cls/billing.payment_status.json +0 -22
- package/module/pay1/form/billing.account_user.form.json +0 -15
- package/module/pay1/form/billing.accounts.form.json +0 -31
- package/module/pay1/form/billing.payment.form.json +0 -23
- package/module/pay1/form/billing.users.form.json +0 -39
- package/module/pay1/menu.json +0 -24
- package/module/pay1/pt/marketplace-service-payment-client-template.html +0 -147
- package/module/pay1/select/billing.account_id.sql +0 -1
- package/module/pay1/select/billing.service_id.sql +0 -1
- package/module/pay1/select/billing.subscription_id.sql +0 -1
- package/module/pay1/select/billing.user_id.sql +0 -1
- package/module/pay1/table/billing.accounts.table.json +0 -61
- package/module/pay1/table/billing.payments.table.json +0 -68
- package/module/pay1/table/billing.users.table.json +0 -71
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import pg from 'pg';
|
|
2
|
+
|
|
3
|
+
const { types } = pg;
|
|
4
|
+
types.setTypeParser(1082, (stringValue) => stringValue);
|
|
5
|
+
types.setTypeParser(1114, (stringValue) => stringValue);
|
|
6
|
+
|
|
7
|
+
import config from '../../../../config.js';
|
|
8
|
+
import pgClients from '../pgClients.js';
|
|
9
|
+
import init from './init.js';
|
|
10
|
+
import getDBParams from './getDBParams.js';
|
|
11
|
+
|
|
12
|
+
function getPG(param) {
|
|
13
|
+
if (!config.pg) return null;
|
|
14
|
+
const {
|
|
15
|
+
user, password, host, port, db, database, name: origin,
|
|
16
|
+
} = (typeof param === 'string' ? getDBParams(param) : param || {});
|
|
17
|
+
const name = origin || db || database || param || 'client';
|
|
18
|
+
if (pgClients[name]) return pgClients[name];
|
|
19
|
+
|
|
20
|
+
const dbConfig = {
|
|
21
|
+
user: user || config.pg?.user || 'postgres',
|
|
22
|
+
password: password || config.pg?.password || 'postgres',
|
|
23
|
+
host: host || config.pg?.host,
|
|
24
|
+
port: port || config.pg?.port,
|
|
25
|
+
database: db || database || config.pg?.db || config.pg?.database,
|
|
26
|
+
statement_timeout: config.pg?.statement_timeout || 10000,
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
if (!dbConfig.database) { return null; }
|
|
30
|
+
|
|
31
|
+
pgClients[name] = new pg.Pool(dbConfig);
|
|
32
|
+
pgClients[name].init = async () => {
|
|
33
|
+
await init(pgClients[name]);
|
|
34
|
+
};
|
|
35
|
+
init(pgClients[name]);
|
|
36
|
+
return pgClients[name];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export default getPG;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import pg from 'pg';
|
|
2
|
+
|
|
3
|
+
const { types } = pg;
|
|
4
|
+
types.setTypeParser(1082, (stringValue) => stringValue);
|
|
5
|
+
types.setTypeParser(1114, (stringValue) => stringValue);
|
|
6
|
+
|
|
7
|
+
import config from '../../../../config.js';
|
|
8
|
+
import pgClients from '../pgClients.js';
|
|
9
|
+
import init from './init.js';
|
|
10
|
+
import getDBParams from './getDBParams.js';
|
|
11
|
+
|
|
12
|
+
async function getPGAsync(param) {
|
|
13
|
+
if (!config.pg) return null;
|
|
14
|
+
|
|
15
|
+
const {
|
|
16
|
+
user, password, host, port, db, database, name: origin,
|
|
17
|
+
} = (typeof param === 'string' ? getDBParams(param) : param || {});
|
|
18
|
+
const name = origin || db || database || param || 'client';
|
|
19
|
+
|
|
20
|
+
if (pgClients[name]?.tlist) return pgClients[name];
|
|
21
|
+
|
|
22
|
+
const dbConfig = {
|
|
23
|
+
user: user || config.pg?.user || 'postgres',
|
|
24
|
+
password: password || config.pg?.password || 'postgres',
|
|
25
|
+
host: host || config.pg?.host,
|
|
26
|
+
port: port || config.pg?.port,
|
|
27
|
+
database: db || database || config.pg?.db || config.pg?.database,
|
|
28
|
+
statement_timeout: config.pg?.statement_timeout || 10000,
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
if (!dbConfig.database) { return null; }
|
|
32
|
+
|
|
33
|
+
pgClients[name] = new pg.Pool(dbConfig);
|
|
34
|
+
|
|
35
|
+
await init(pgClients[name]);
|
|
36
|
+
|
|
37
|
+
return pgClients[name];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export default getPGAsync;
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
|
|
3
|
+
import config from '../../../../config.js';
|
|
4
|
+
import getRedis from '../../redis/funcs/getRedis.js';
|
|
5
|
+
|
|
6
|
+
const rclient = getRedis({ db: 0 });
|
|
7
|
+
|
|
8
|
+
async function init(client) {
|
|
9
|
+
if (!client?.options?.database) { return; }
|
|
10
|
+
|
|
11
|
+
const textQuery = `select
|
|
12
|
+
(select json_object_agg(conrelid::regclass ,(SELECT attname FROM pg_attribute WHERE attrelid = c.conrelid and attnum = c.conkey[1]))
|
|
13
|
+
from pg_constraint c where contype='p' and connamespace::regnamespace::text not in ('sde')) as pk,
|
|
14
|
+
(SELECT json_object_agg(t.oid::text,pg_catalog.format_type(t.oid, NULL)) FROM pg_catalog.pg_type t) as "pgType"`;
|
|
15
|
+
const { pgType, pk } = await client.query(textQuery).then((d) => d.rows[0]);
|
|
16
|
+
|
|
17
|
+
const tlist = await client.query(`select array_agg((select nspname from pg_namespace where oid=relnamespace)||'.'||relname) tlist
|
|
18
|
+
from pg_class where relkind in ('r','v')`).then((d) => d.rows[0].tlist);
|
|
19
|
+
|
|
20
|
+
const { rows = [] } = await client.query(`select (select nspname from pg_namespace where oid=relnamespace)||'.'||relname as tname, relkind
|
|
21
|
+
from pg_class where relkind in ('r','v')`);
|
|
22
|
+
const relkinds = rows.reduce((acc, curr) => Object.assign(acc, { [curr.tname]: curr.relkind }), {});
|
|
23
|
+
|
|
24
|
+
async function query(q, args = [], isstream = false) {
|
|
25
|
+
try {
|
|
26
|
+
if (isstream) {
|
|
27
|
+
await client.query('set statement_timeout to 100000000');
|
|
28
|
+
}
|
|
29
|
+
const data = await client.query(q, args);
|
|
30
|
+
await client.query('set statement_timeout to 0');
|
|
31
|
+
return data;
|
|
32
|
+
}
|
|
33
|
+
catch (err) {
|
|
34
|
+
await client.query('set statement_timeout to 0');
|
|
35
|
+
if (err.message === 'canceling statement due to statement timeout') {
|
|
36
|
+
console.warn('timeout/query', q, err.stack);
|
|
37
|
+
return { rows: [], timeout: true };
|
|
38
|
+
}
|
|
39
|
+
throw new Error(err);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async function querySafe(q, param = {}) {
|
|
44
|
+
const { args, isstream } = param;
|
|
45
|
+
const data = await query(q, args, isstream);
|
|
46
|
+
return data;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async function one(q, param = {}) {
|
|
50
|
+
const data = await query(q, Array.isArray(param) ? param : param.args || []);
|
|
51
|
+
const result = ((Array.isArray(data) ? data.pop() : data)?.rows || [])[0] || {};
|
|
52
|
+
return result;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async function queryNotice(q, args = [], cb = () => { }) {
|
|
56
|
+
const clientCb = await client.connect();
|
|
57
|
+
clientCb.on('notice', (e) => {
|
|
58
|
+
cb(e.message);
|
|
59
|
+
});
|
|
60
|
+
let result;
|
|
61
|
+
try {
|
|
62
|
+
result = await clientCb.query(q, args);
|
|
63
|
+
clientCb.end();
|
|
64
|
+
}
|
|
65
|
+
catch (err) {
|
|
66
|
+
clientCb.end();
|
|
67
|
+
cb(err.toString(), 1);
|
|
68
|
+
throw err;
|
|
69
|
+
}
|
|
70
|
+
return result;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async function queryCache(q, param = {}) {
|
|
74
|
+
const { table, args = [], time = 15 } = param;
|
|
75
|
+
const seconds = typeof time !== 'number' || time < 0 ? 0 : time * 60;
|
|
76
|
+
|
|
77
|
+
if (seconds === 0 || config.disableCache) {
|
|
78
|
+
const data = await query(q, args || []);
|
|
79
|
+
return data;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// CRUD table state
|
|
83
|
+
const keyCacheTable = `pg:${table}:crud`;
|
|
84
|
+
const crudInc = table && config.redis ? (await rclient.get(keyCacheTable) || 0) : 0;
|
|
85
|
+
|
|
86
|
+
//
|
|
87
|
+
const hash = createHash('sha1').update([q, JSON.stringify(args)].join()).digest('base64');
|
|
88
|
+
const keyCache = `pg:${hash}:${crudInc}`;
|
|
89
|
+
|
|
90
|
+
const cacheData = config.redis ? await rclient.get(keyCache) : null;
|
|
91
|
+
|
|
92
|
+
if (cacheData && !config.local) {
|
|
93
|
+
// console.log('from cache', table, query);
|
|
94
|
+
return JSON.parse(cacheData);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const data = await query(q, args || []);
|
|
98
|
+
|
|
99
|
+
if (seconds > 0 && config.redis) {
|
|
100
|
+
rclient.set(keyCache, JSON.stringify(data), 'EX', seconds);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// console.log('no cache', table, crudInc, query);
|
|
104
|
+
return data;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
Object.assign(client, {
|
|
108
|
+
one, pgType, pk, tlist, relkinds, queryCache, queryNotice, querySafe,
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// export default client;
|
|
113
|
+
export default init;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import pgClients from './pgClients.js';
|
|
2
|
+
import getPGAsync from './funcs/getPGAsync.js';
|
|
3
|
+
|
|
4
|
+
function close() {
|
|
5
|
+
Object.keys(pgClients).forEach((el) => {
|
|
6
|
+
pgClients[el].end();
|
|
7
|
+
});
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export default async function plugin(app, config) {
|
|
11
|
+
const client = await getPGAsync({ ...config.pg || {}, name: 'client' });
|
|
12
|
+
|
|
13
|
+
app.addHook('onRequest', async (req) => {
|
|
14
|
+
req.pg = req.pg || client;
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
app.addHook('onError', async (req, reply, err) => {
|
|
18
|
+
if (err.message === 'canceling statement due to statement timeout') {
|
|
19
|
+
console.warn('request timeout', req.method, req.url, req.headers?.referer, err.stack);
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
app.addHook('onClose', close);
|
|
24
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import pg from 'pg';
|
|
2
|
+
|
|
3
|
+
import config from '../../../config.js';
|
|
4
|
+
import init from './funcs/init.js';
|
|
5
|
+
|
|
6
|
+
const pgClients = {};
|
|
7
|
+
if (config.pg) {
|
|
8
|
+
const client = new pg.Pool({
|
|
9
|
+
host: config.pg?.host || '127.0.0.1',
|
|
10
|
+
port: config.pg?.port || 5432,
|
|
11
|
+
database: config.pg?.database || 'postgres',
|
|
12
|
+
user: config.pg?.user || 'postgres',
|
|
13
|
+
password: config.pg?.password || 'postgres',
|
|
14
|
+
statement_timeout: config.pg?.statement_timeout || 10000,
|
|
15
|
+
});
|
|
16
|
+
client.init = async () => {
|
|
17
|
+
await init(client);
|
|
18
|
+
};
|
|
19
|
+
client.init();
|
|
20
|
+
pgClients.client = client;
|
|
21
|
+
}
|
|
22
|
+
export default pgClients;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import Redis from 'ioredis';
|
|
2
|
+
|
|
3
|
+
import config from '../../../../config.js';
|
|
4
|
+
import redisClients from './redisClients.js';
|
|
5
|
+
|
|
6
|
+
function getRedis({ db } = { db: 0 }) {
|
|
7
|
+
if (!config.redis) return null;
|
|
8
|
+
if (redisClients[db]) return redisClients[db];
|
|
9
|
+
|
|
10
|
+
const redisConfig = {
|
|
11
|
+
db,
|
|
12
|
+
keyPrefix: `${config.db}:`,
|
|
13
|
+
host: config.redis?.host || '127.0.0.1',
|
|
14
|
+
port: config.redis?.port || 6379, // Redis port
|
|
15
|
+
family: 4, // 4 (IPv4) or 6 (IPv6)
|
|
16
|
+
closeClient: true,
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
redisClients[db] = new Redis(redisConfig);
|
|
20
|
+
|
|
21
|
+
return redisClients[db];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export default getRedis;
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import request from 'request-promise';
|
|
2
2
|
import LiqPay from 'liqpayjs-sdk';
|
|
3
3
|
|
|
4
|
-
import
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
import config from '../../../../config.js';
|
|
5
|
+
import pgClients from '../../../plugins/pg/pgClients.js';
|
|
6
|
+
import dataInsert from '../../../plugins/crud/dataInsert.js';
|
|
7
|
+
import dataUpdate from '../../../plugins/crud/dataUpdate.js';
|
|
7
8
|
|
|
8
9
|
const { prefix = '/api' } = config;
|
|
9
10
|
|
|
@@ -64,7 +65,7 @@ export default async function liqpayRedirect(req, reply) {
|
|
|
64
65
|
table: 'billing.payments',
|
|
65
66
|
id: paymentId,
|
|
66
67
|
data: {
|
|
67
|
-
referer_url: paymentRedirect ?
|
|
68
|
+
referer_url: paymentRedirect ? paymentRedirect.replace(/{{id}}/g, paymentId) : headers?.referer,
|
|
68
69
|
payment_status: 'inprogress',
|
|
69
70
|
},
|
|
70
71
|
uid: user?.uid || '0',
|
|
@@ -73,10 +74,6 @@ export default async function liqpayRedirect(req, reply) {
|
|
|
73
74
|
const protocol = headers?.referer?.split?.('://')?.shift?.() || req.protocol || 'https';
|
|
74
75
|
const rootPageUrl = `${protocol || 'https'}://${domain}`;
|
|
75
76
|
|
|
76
|
-
/* const resultUrl = paymentRedirect
|
|
77
|
-
? `${rootPageUrl}${handlebarsSync.compile(paymentRedirect)({ id: paymentId })}`
|
|
78
|
-
: headers?.referer || `${rootPageUrl}`; */
|
|
79
|
-
|
|
80
77
|
// redirect to api to check payment status and redirect user to page afterwards
|
|
81
78
|
const serverUrl = `${rootPageUrl}${prefix}/liqpay/${paymentId}/success`;
|
|
82
79
|
|
|
@@ -130,12 +127,11 @@ export default async function liqpayRedirect(req, reply) {
|
|
|
130
127
|
}
|
|
131
128
|
|
|
132
129
|
Object.assign(logObj, { paymentId, redirect: response.headers?.location, status: 302 });
|
|
133
|
-
|
|
130
|
+
console.log('payments/info', JSON.stringify(logObj));
|
|
134
131
|
return reply.redirect(response.headers.location);
|
|
135
132
|
}
|
|
136
133
|
catch (err) {
|
|
137
|
-
|
|
138
|
-
logger.file('payments/error', logObj);
|
|
134
|
+
console.error('payments/error', JSON.stringify(logObj), err.toString(), err.stack);
|
|
139
135
|
return reply.status(500).send(err.toString());
|
|
140
136
|
}
|
|
141
137
|
}
|
|
@@ -1,13 +1,19 @@
|
|
|
1
1
|
import LiqPay from 'liqpayjs-sdk';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { readFileSync } from 'node:fs';
|
|
4
|
+
import { fileURLToPath } from 'url';
|
|
2
5
|
|
|
3
|
-
import
|
|
4
|
-
|
|
5
|
-
|
|
6
|
+
import config from '../../../../config.js';
|
|
7
|
+
import pgClients from '../../../plugins/pg/pgClients.js';
|
|
8
|
+
import dataUpdate from '../../../plugins/crud/dataUpdate.js';
|
|
6
9
|
|
|
7
|
-
import
|
|
10
|
+
import sendNotification from '../../../utils/sendNotification.js';
|
|
8
11
|
|
|
9
12
|
import checkStatus from '../utils/check.status.js';
|
|
10
13
|
|
|
14
|
+
const dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
15
|
+
const ptBody = readFileSync(path.join(dirname, '../../../../module/pay/pt/marketplace-service-payment-client-template.html'));
|
|
16
|
+
|
|
11
17
|
const newDate = () => {
|
|
12
18
|
const date = new Date();
|
|
13
19
|
const tzOffset = date.getTimezoneOffset();
|
|
@@ -72,14 +78,14 @@ export default async function liqpayStatus(req, reply) {
|
|
|
72
78
|
);
|
|
73
79
|
|
|
74
80
|
if (body.signature !== sign) {
|
|
75
|
-
|
|
81
|
+
console.warn('payments/warn', JSON.stringify({
|
|
76
82
|
type: 'status',
|
|
77
83
|
service: 'liqpay',
|
|
78
84
|
paymentId,
|
|
79
85
|
origin,
|
|
80
86
|
uid: user?.uid || '0',
|
|
81
87
|
message: 'Transaction signature does not match',
|
|
82
|
-
});
|
|
88
|
+
}));
|
|
83
89
|
return reply.status(403).send('access restricted: signature mismatch');
|
|
84
90
|
}
|
|
85
91
|
}
|
|
@@ -97,14 +103,14 @@ export default async function liqpayStatus(req, reply) {
|
|
|
97
103
|
} = data;
|
|
98
104
|
|
|
99
105
|
if (trxStatus === 'success') {
|
|
100
|
-
|
|
106
|
+
console.info('payments/skip', JSON.stringify({
|
|
101
107
|
type: 'status',
|
|
102
108
|
service: 'liqpay',
|
|
103
109
|
paymentId,
|
|
104
110
|
origin,
|
|
105
111
|
uid: user?.uid || '0',
|
|
106
112
|
message: 'Transaction was already processed',
|
|
107
|
-
});
|
|
113
|
+
}));
|
|
108
114
|
return reply.status(400).send('Transaction was already processed');
|
|
109
115
|
}
|
|
110
116
|
|
|
@@ -116,7 +122,7 @@ export default async function liqpayStatus(req, reply) {
|
|
|
116
122
|
return reply.status(400).send('Payment not registered via liqpay service');
|
|
117
123
|
}
|
|
118
124
|
|
|
119
|
-
|
|
125
|
+
console.log('payments/info', JSON.stringify({
|
|
120
126
|
type: 'status',
|
|
121
127
|
service: 'liqpay',
|
|
122
128
|
paymentId,
|
|
@@ -125,7 +131,7 @@ export default async function liqpayStatus(req, reply) {
|
|
|
125
131
|
refill: refillSum,
|
|
126
132
|
body,
|
|
127
133
|
params,
|
|
128
|
-
});
|
|
134
|
+
}));
|
|
129
135
|
|
|
130
136
|
// last trx contains total balance
|
|
131
137
|
const { total_balance: totalBalance = 0 } = await pg.query(`select total_balance from billing.payments
|
|
@@ -153,7 +159,7 @@ export default async function liqpayStatus(req, reply) {
|
|
|
153
159
|
const { account_email: to } = await pg.query('select account_email from billing.accounts where account_id=$1', [accountId])
|
|
154
160
|
.then(el => el.rows?.[0] || {});
|
|
155
161
|
|
|
156
|
-
|
|
162
|
+
console.log('payments/info2', JSON.stringify({
|
|
157
163
|
type: 'status',
|
|
158
164
|
service: 'liqpay',
|
|
159
165
|
paymentId,
|
|
@@ -162,7 +168,7 @@ export default async function liqpayStatus(req, reply) {
|
|
|
162
168
|
origin,
|
|
163
169
|
redirect: redirectUrl,
|
|
164
170
|
uid: user.uid || '0',
|
|
165
|
-
});
|
|
171
|
+
}));
|
|
166
172
|
|
|
167
173
|
Object.assign(data, {
|
|
168
174
|
domain: req.hostname,
|
|
@@ -170,13 +176,18 @@ export default async function liqpayStatus(req, reply) {
|
|
|
170
176
|
});
|
|
171
177
|
|
|
172
178
|
if (to) {
|
|
173
|
-
const pt =
|
|
179
|
+
const pt = ptBody
|
|
180
|
+
.replace(/{{domain}}/g, req.hostname)
|
|
181
|
+
.replace(/{{total_balance}}/g, +(refillSum || 0) + (+totalBalance || 0))
|
|
182
|
+
.replace(/{{username}}/g, to)
|
|
183
|
+
.replace(/{{created_at}}/g, (data?.cdate || data?.created_at || new Date()).toLocaleString('uk-UA'))
|
|
184
|
+
.replace(/{{payment_status}}/g, responseStatus)
|
|
185
|
+
.replace(/{{payment_amount}}/g, refillSum || 0);
|
|
174
186
|
const options = {
|
|
175
187
|
pg,
|
|
176
188
|
to,
|
|
177
189
|
title: `Поповнення рахунку на порталі ${req.hostname}`,
|
|
178
|
-
|
|
179
|
-
data,
|
|
190
|
+
pt,
|
|
180
191
|
nocache: config?.local,
|
|
181
192
|
};
|
|
182
193
|
await sendNotification(options);
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import config from '../../../../config.js';
|
|
2
|
+
import pgClients from '../../../plugins/pg/pgClients.js';
|
|
3
|
+
import dataInsert from '../../../plugins/crud/dataInsert.js';
|
|
4
|
+
import dataUpdate from '../../../plugins/crud/dataUpdate.js';
|
|
5
|
+
|
|
6
|
+
const { prefix = '/api' } = config;
|
|
7
|
+
const {
|
|
8
|
+
host = 'https://api.monobank.ua', token, paymentRedirect, merchantPaymInfo,
|
|
9
|
+
} = config.integrations?.monopay || {};
|
|
10
|
+
|
|
11
|
+
// http://billing.local.ua/api/monopay-redirect?price=50
|
|
12
|
+
|
|
13
|
+
export default async function monopayRedirect(req, reply) {
|
|
14
|
+
const {
|
|
15
|
+
pg = pgClients.client, query = {}, user = {}, headers = {},
|
|
16
|
+
} = req;
|
|
17
|
+
|
|
18
|
+
if (!token) {
|
|
19
|
+
return reply.status(400).send('invalid monopay integration settings');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const domain = req.hostname.split(':').shift();
|
|
23
|
+
|
|
24
|
+
const paymentData = query.id ? await pg.query('select payment_id, account_id, payment_amount, payment_status from billing.payments where payment_id=$1', [query.id])
|
|
25
|
+
.then(el => el.rows?.[0] || {}) : {};
|
|
26
|
+
|
|
27
|
+
if (paymentData.payment_status && paymentData.payment_status === 'success') {
|
|
28
|
+
return reply.status(400).send('already processed');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const paymentAmount = paymentData.payment_amount || query.price;
|
|
32
|
+
|
|
33
|
+
if (!paymentAmount) {
|
|
34
|
+
return reply.status(400).send('price is required');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const accountId = paymentData.account_id
|
|
38
|
+
|| (user?.uid ? await pg.query('select account_id from billing.account_user where user_id=$1 limit 1', [user?.uid]).then(el => el.rows?.[0]?.account_id) : null);
|
|
39
|
+
|
|
40
|
+
if (!accountId) {
|
|
41
|
+
return reply.status(400).send('account_id is required');
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const { payment_id: paymentId } = !paymentData.payment_id
|
|
45
|
+
? await dataInsert({
|
|
46
|
+
table: 'billing.payments',
|
|
47
|
+
data: {
|
|
48
|
+
payment_amount: paymentAmount,
|
|
49
|
+
account_id: accountId,
|
|
50
|
+
payment_service: 'monopay',
|
|
51
|
+
payment_status: 'inprogress',
|
|
52
|
+
},
|
|
53
|
+
uid: user?.uid || '0',
|
|
54
|
+
}).then(el => el.rows?.[0] || {})
|
|
55
|
+
: { payment_id: paymentData.payment_id };
|
|
56
|
+
|
|
57
|
+
if (!paymentId) {
|
|
58
|
+
return { message: 'payment not found', status: 404 };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
await dataUpdate({
|
|
62
|
+
table: 'billing.payments',
|
|
63
|
+
id: paymentId,
|
|
64
|
+
data: {
|
|
65
|
+
referer_url: paymentRedirect ? paymentRedirect.replace(/{{id}}/g, paymentId) : headers?.referer,
|
|
66
|
+
payment_status: 'inprogress',
|
|
67
|
+
payment_service: 'monopay',
|
|
68
|
+
},
|
|
69
|
+
uid: user?.uid || '0',
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
const protocol = headers?.referer?.split?.('://')?.shift?.() || req.protocol || 'https';
|
|
73
|
+
const rootPageUrl = `${protocol || 'https'}://${domain}`;
|
|
74
|
+
|
|
75
|
+
// redirect to api to check payment status and redirect user to page afterwards
|
|
76
|
+
const serverUrl = `${rootPageUrl}${prefix}/monopay/${paymentId}/success`;
|
|
77
|
+
|
|
78
|
+
const paymentOptions = {
|
|
79
|
+
amount: paymentAmount * 100, // price=1 === 0.01 uah
|
|
80
|
+
ccy: 980, // ISO 4217, 980 = UAH
|
|
81
|
+
merchantPaymInfo,
|
|
82
|
+
redirectUrl: serverUrl,
|
|
83
|
+
// redirectUrl: (paymentRedirect ? `${rootPageUrl}${paymentRedirect.replace(/{{id}}/g, paymentId)}` : headers?.referer) || rootPageUrl,
|
|
84
|
+
webHookUrl: serverUrl,
|
|
85
|
+
validity: 86400, // 1 day by default, then invalid
|
|
86
|
+
paymentType: 'debit',
|
|
87
|
+
// displayType: 'iframe', // default = null > url to mono
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const logObj = {
|
|
91
|
+
type: 'redirect',
|
|
92
|
+
paymentId,
|
|
93
|
+
accountId,
|
|
94
|
+
price: paymentAmount,
|
|
95
|
+
service: 'monopay',
|
|
96
|
+
domain,
|
|
97
|
+
paymentRedirectUrl: serverUrl,
|
|
98
|
+
uid: user?.uid || '0',
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
try {
|
|
102
|
+
// create payment, get redirect url
|
|
103
|
+
const resp = await fetch(`${host}/api/merchant/invoice/create`, {
|
|
104
|
+
method: 'POST',
|
|
105
|
+
headers: { 'x-token': token },
|
|
106
|
+
body: JSON.stringify(paymentOptions),
|
|
107
|
+
signal: AbortSignal.timeout(5000),
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
const contentType = resp.headers.get('Content-Type');
|
|
111
|
+
|
|
112
|
+
const body = contentType.includes('application/json') && resp.status === 200
|
|
113
|
+
? await resp.json()
|
|
114
|
+
: await resp.text();
|
|
115
|
+
|
|
116
|
+
if (query.debug) {
|
|
117
|
+
return body;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
await dataUpdate({
|
|
121
|
+
table: 'billing.payments',
|
|
122
|
+
id: paymentId,
|
|
123
|
+
data: {
|
|
124
|
+
payment_data: resp.status === 200 ? body : { response: body },
|
|
125
|
+
},
|
|
126
|
+
uid: user?.uid || '0',
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
console.log('payments/info', JSON.stringify({ ...logObj, response: body }));
|
|
130
|
+
|
|
131
|
+
if (!body.pageUrl) {
|
|
132
|
+
return reply.status(501).send('monopay service temporary unavailable');
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return reply.redirect(body.pageUrl);
|
|
136
|
+
}
|
|
137
|
+
catch (err) {
|
|
138
|
+
console.error('payments/error', JSON.stringify(logObj), err.toString(), err.stack);
|
|
139
|
+
return reply.status(500).send(err.toString());
|
|
140
|
+
}
|
|
141
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import config from '../../../../config.js';
|
|
2
|
+
import pgClients from '../../../plugins/pg/pgClients.js';
|
|
3
|
+
import dataUpdate from '../../../plugins/crud/dataUpdate.js';
|
|
4
|
+
|
|
5
|
+
const {
|
|
6
|
+
host = 'https://api.monobank.ua',
|
|
7
|
+
token,
|
|
8
|
+
} = config.integrations?.monopay || {};
|
|
9
|
+
|
|
10
|
+
const timeoutMs = 5000;
|
|
11
|
+
|
|
12
|
+
export default async function monopayStatus({
|
|
13
|
+
pg = pgClients.client, params = {}, user = {},
|
|
14
|
+
}, reply) {
|
|
15
|
+
const { id, status = 'success' } = params;
|
|
16
|
+
|
|
17
|
+
if (!id) {
|
|
18
|
+
return reply.status(400).send('not enough params: id');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const {
|
|
22
|
+
paymentId, refererUrl, paymentStatus, invoiceId, accountId,
|
|
23
|
+
} = await pg.query('select payment_id as "paymentId", account_id as "accountId", payment_status as "paymentStatus", referer_url as "refererUrl", payment_data->>\'invoiceId\' as "invoiceId" from billing.payments where payment_id=$1', [id])
|
|
24
|
+
.then(el => el.rows?.[0] || {});
|
|
25
|
+
|
|
26
|
+
if (!paymentId) {
|
|
27
|
+
return reply.status(404).send('payment not found');
|
|
28
|
+
}
|
|
29
|
+
if (!invoiceId) {
|
|
30
|
+
return reply.status(404).send('invoice not found');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (['success', 'error'].includes(paymentStatus)) {
|
|
34
|
+
return reply.status(404).send('payment already processed');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (status !== 'success') {
|
|
38
|
+
return reply.redirect(refererUrl);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const resp = await fetch(`${host}/api/merchant/invoice/status?invoiceId=${invoiceId}`, {
|
|
42
|
+
method: 'GET',
|
|
43
|
+
headers: { 'x-token': token },
|
|
44
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
45
|
+
}).catch(err => reply.status(501).send('monopay request timeout'));
|
|
46
|
+
|
|
47
|
+
const contentType = resp.headers.get('Content-Type');
|
|
48
|
+
|
|
49
|
+
const body = contentType.includes('application/json') && resp.status === 200
|
|
50
|
+
? await resp.json()
|
|
51
|
+
: await resp.text();
|
|
52
|
+
|
|
53
|
+
const totalBalanceBefore = await pg.query(`select total_balance from billing.payments
|
|
54
|
+
where account_id=$1 and payment_status in ('success', 'error') and total_balance is not null order by cdate desc limit 1`, [accountId])
|
|
55
|
+
.then(el => el.rows?.[0]?.total_balance || 0);
|
|
56
|
+
|
|
57
|
+
await dataUpdate({
|
|
58
|
+
table: 'billing.payments',
|
|
59
|
+
id: paymentId,
|
|
60
|
+
data: {
|
|
61
|
+
total_balance: (body.amount / 100) + +totalBalanceBefore,
|
|
62
|
+
success_date: new Date(), // newDate(),
|
|
63
|
+
payment_status: body?.status || 'error',
|
|
64
|
+
payment_data: resp.status === 200 ? body : { response: body },
|
|
65
|
+
},
|
|
66
|
+
uid: user?.uid || '0',
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
return reply.redirect(refererUrl || '/');
|
|
70
|
+
}
|