@opengis/pay 1.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.
- package/README.md +28 -0
- package/config.js +7 -0
- package/controllers/portmone/portmone.redirect.js +80 -0
- package/controllers/portmone/portmone.status.js +113 -0
- package/package.json +43 -0
- package/plugin.js +35 -0
- package/server/migrations/users.sql +89 -0
- package/server/plugins/hook.js +16 -0
- package/server/templates/cls/billing.trx_status.json +14 -0
- package/server/templates/pt/marketplace-service-payment-client-template.html +149 -0
- package/server/templates/select/billing.account_id.sql +5 -0
- package/server/templates/select/billing.service_id.sql +1 -0
package/README.md
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# settings
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@opengis/billing)
|
|
4
|
+
[](http://standardjs.com/)
|
|
5
|
+
|
|
6
|
+
It standardizes the entire authorization process, while taking care of everything from login to logout
|
|
7
|
+
|
|
8
|
+
## Install
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
npm i @opengis/billing
|
|
12
|
+
```
|
|
13
|
+
## Publish
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm run build
|
|
17
|
+
npm publish
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Usage
|
|
21
|
+
|
|
22
|
+
```js
|
|
23
|
+
fastify.register(import('@opengis/billing'), config);
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Documenation
|
|
27
|
+
|
|
28
|
+
For a detailed understanding of `billing`, its features, and how to use them, refer to our [Documentation](https://apidocs.softpro.ua/opengis/plugin/).
|
package/config.js
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
const q = `insert into billing.trx(refill_sum, account_id, uid, referer_url, trx_status)
|
|
2
|
+
select $1::numeric, $2, $3, $4, 'inprogress' returning trx_id`;
|
|
3
|
+
const q1 = 'select account_id from billing.account_user where client_uid=$1 limit 1';
|
|
4
|
+
|
|
5
|
+
import request from 'request-promise';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Апі перенаправлює до стороннього сервісу оплати карткою онлайн
|
|
9
|
+
*
|
|
10
|
+
* @method GET|POST
|
|
11
|
+
* @alias PortmonePaymentRedirect
|
|
12
|
+
* @summary redirect на сервіс оплати карткою
|
|
13
|
+
* @type api
|
|
14
|
+
* @param {String} price - Сума поповнення рахунку
|
|
15
|
+
* @returns {Number} status - номер помилки. Повертається, якщо була допущенна помилка в отриманих параметрах, або з бази. Це може бути 400 або 500
|
|
16
|
+
* @returns {String} error - опис помилки
|
|
17
|
+
* @returns {String} redirect - шлях до переадресації
|
|
18
|
+
* @returns {String} message - повідомлення про успішне виконання і передача певних даних або помилку
|
|
19
|
+
* @returns {String} headers - заголовки HTTP
|
|
20
|
+
* @returns {String} file - шлях файла або його назва
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
export default async function PortmoneRedirect(req, res) {
|
|
24
|
+
const {
|
|
25
|
+
pg, funcs, user = {}, query = {}, headers = {}, log,
|
|
26
|
+
} = req;
|
|
27
|
+
|
|
28
|
+
if (!user?.uid) {
|
|
29
|
+
return { message: 'uid is required', status: 403 };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (!query?.price) {
|
|
33
|
+
return { message: 'price is required', status: 400 };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const {
|
|
37
|
+
host = 'https://www.portmone.com.ua',
|
|
38
|
+
payeeId = 1185, // test account payee id
|
|
39
|
+
} = funcs.config?.integration?.portmone || {};
|
|
40
|
+
|
|
41
|
+
const { account_id: accountId } = await pg.query(q1, [user.uid]).then((res1) => res1.rows?.[0] || {});
|
|
42
|
+
|
|
43
|
+
const args = [query?.price, accountId, user.uid, headers?.referer];
|
|
44
|
+
const { trx_id: trxId } = await pg.query(q, args).then((res1) => res1.rows?.[0] || {});
|
|
45
|
+
|
|
46
|
+
if (!trxId) {
|
|
47
|
+
return { message: 'Не вірне замовлення', status: 400 };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const domain = `${req.protocol}://${req.hostname}`;
|
|
51
|
+
const body = {
|
|
52
|
+
payee_id: payeeId,
|
|
53
|
+
description: `Послуга - поповнення рахунку на суму ${query?.price} грн`,
|
|
54
|
+
shop_order_number: trxId,
|
|
55
|
+
bill_refill_sum: query?.price,
|
|
56
|
+
success_url: `${domain}${funcs.config?.prefix || '/api'}/portmone/${trxId}/success`,
|
|
57
|
+
failure_url: `${domain}${funcs.config?.prefix || '/api'}/portmone/${trxId}/error`,
|
|
58
|
+
lang: 'ua',
|
|
59
|
+
};
|
|
60
|
+
try {
|
|
61
|
+
const data = await request({
|
|
62
|
+
url: `${host}/gateway/`,
|
|
63
|
+
method: 'POST',
|
|
64
|
+
formData: body,
|
|
65
|
+
followAllRedirects: false,
|
|
66
|
+
jar: true,
|
|
67
|
+
timeout: 6000,
|
|
68
|
+
});
|
|
69
|
+
log.info('integrations/portmone/redirect', {
|
|
70
|
+
payeeId, trxId, accountId, price: query?.price, domain, response: data, status: 302,
|
|
71
|
+
});
|
|
72
|
+
return res.redirect(data);
|
|
73
|
+
}
|
|
74
|
+
catch (err) {
|
|
75
|
+
log.info('integrations/portmone/redirect', {
|
|
76
|
+
payeeId, trxId, accountId, price: query?.price, domain, response: err.response.headers?.location, status: err.statusCode,
|
|
77
|
+
});
|
|
78
|
+
return res.redirect(err.response.headers?.location);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
const template = 'marketplace-service-payment-client-template';
|
|
2
|
+
const q = 'select trx_id as trx, refill_sum, referer_url, trx_status, account_id from billing.trx where trx_id=$1';
|
|
3
|
+
const q1 = `select total_balance from billing.trx where account_id=$1
|
|
4
|
+
and total_balance is not null order by cdate desc limit 1`;
|
|
5
|
+
const q2 = 'select email from crm_acc.crm_account where account_id=$1';
|
|
6
|
+
|
|
7
|
+
import getSelectVal from '@opengis/fastify-table/table/funcs/metaFormat/getSelectVal.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Апі перенаправлює з portmone назад на сторінку замовлення
|
|
11
|
+
*
|
|
12
|
+
* @method POST
|
|
13
|
+
* @alias PortmoneStatus
|
|
14
|
+
* @summary redirect з portmone на сторінку замовлення
|
|
15
|
+
* @type api
|
|
16
|
+
* @param {String} trxId - Ідентифікатор замовлення
|
|
17
|
+
* @param {String} refillSum - Сума поповнення рахунку
|
|
18
|
+
* @returns {Number} status - номер помилки. Повертається, якщо була допущенна помилка в отриманих параметрах, або з бази. Це може бути 400 або 500
|
|
19
|
+
* @returns {String} error - опис помилки
|
|
20
|
+
* @returns {String} redirect - шлях до переадресації
|
|
21
|
+
* @returns {String} message - повідомлення про успішне виконання і передача певних даних або помилку
|
|
22
|
+
* @returns {String} headers - заголовки HTTP
|
|
23
|
+
* @returns {String} file - шлях файла або його назва
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
export default async function portmoneStatus(req, res) {
|
|
27
|
+
const {
|
|
28
|
+
body = {}, pg, log, params = {}, funcs = {}, headers = {},
|
|
29
|
+
} = req;
|
|
30
|
+
|
|
31
|
+
const {
|
|
32
|
+
host = 'https://www.portmone.com.ua',
|
|
33
|
+
} = funcs.config?.integration?.portmone || {};
|
|
34
|
+
|
|
35
|
+
const origin = headers?.origin || headers?.referer;
|
|
36
|
+
log.debug('integrations/portmone/status', { body, params, origin });
|
|
37
|
+
|
|
38
|
+
const responseStatus = body?.status || params?.status;
|
|
39
|
+
const trxId = body?.shopOrderNumber || params?.id;
|
|
40
|
+
|
|
41
|
+
const updateTrx = `
|
|
42
|
+
update billing.trx
|
|
43
|
+
set payment_data=$2, trx_status=$3
|
|
44
|
+
${responseStatus === 'success'
|
|
45
|
+
? ',transaction_success_date=now(),refill_sum=$4,total_balance=( $4::numeric + coalesce($5::numeric,0) ) '
|
|
46
|
+
: ''}
|
|
47
|
+
where trx_id=$1`;
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
const {
|
|
51
|
+
trx, refill_sum: refillSum, referer_url: redirectUrl, trx_status: trxStatus, account_id: accountId,
|
|
52
|
+
} = await pg.query(q, [trxId]).then((res1) => res1.rows?.[0] || {});
|
|
53
|
+
|
|
54
|
+
if (!trx) {
|
|
55
|
+
return { message: 'Transaction not found', status: 400 };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const refill = body?.billAmount || refillSum || 0;
|
|
59
|
+
|
|
60
|
+
// request portmone trx info to recheck request data is valid?
|
|
61
|
+
if (!req.unittest && !origin?.startsWith(host) && false) {
|
|
62
|
+
return { message: 'access restricted', status: 403 };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// last trx contains total balance
|
|
66
|
+
const { total_balance: totalBalance } = await pg.query(q1, [accountId]).then((res1) => res1.rows?.[0] || {});
|
|
67
|
+
|
|
68
|
+
if (trxStatus === 'success') {
|
|
69
|
+
return { message: 'Transaction was already processed', status: 400 };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
await pg.query(updateTrx, [trxId, JSON.stringify(body), responseStatus, refill, totalBalance]);
|
|
73
|
+
|
|
74
|
+
const { email } = await pg.query(q2, [accountId]).then((res1) => res1.rows?.[0] || {});
|
|
75
|
+
|
|
76
|
+
log.debug('integrations/portmone/status', {
|
|
77
|
+
email, redirect: redirectUrl, template, trxId,
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
const data = await pg.one('select * from billing.trx where trx_id=$1', [trxId]);
|
|
81
|
+
|
|
82
|
+
const cls1 = await getSelectVal({ name: 'billing.service_id', values: [data.service_id] });
|
|
83
|
+
const cls2 = await getSelectVal({ name: 'billing.account_id', values: [data.account_id] });
|
|
84
|
+
const cls3 = await getSelectVal({ name: 'billing.trx_status', values: [data.trx_status] });
|
|
85
|
+
|
|
86
|
+
Object.assign(data, {
|
|
87
|
+
domain: req.hostname,
|
|
88
|
+
service_id_text: data.service_id ? cls1?.[data.service_id] || data.service_id : 'Інформація відсутня',
|
|
89
|
+
account_id_text: data.account_id ? cls2?.[data.account_id] || data.account_id : 'Інформація відсутня',
|
|
90
|
+
trx_status_text: data.trx_status ? cls3?.[data.trx_status] || data.trx_status : 'Інформація відсутня',
|
|
91
|
+
});
|
|
92
|
+
if (email) {
|
|
93
|
+
const options = {
|
|
94
|
+
pg,
|
|
95
|
+
funcs,
|
|
96
|
+
log,
|
|
97
|
+
to: email,
|
|
98
|
+
title: `Поповнення рахунку на порталі ${req.hostname}`,
|
|
99
|
+
template,
|
|
100
|
+
data,
|
|
101
|
+
// table: 'billing.trx',
|
|
102
|
+
// id: trxId,
|
|
103
|
+
nocache: funcs.config?.local,
|
|
104
|
+
};
|
|
105
|
+
await funcs.notification(options);
|
|
106
|
+
}
|
|
107
|
+
return res.redirect(redirectUrl);
|
|
108
|
+
}
|
|
109
|
+
catch (err) {
|
|
110
|
+
log.debug('integrations/portmone/status', { trxId, responseStatus, error: err.toString() });
|
|
111
|
+
return { error: err.toString(), status: 500 };
|
|
112
|
+
}
|
|
113
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@opengis/pay",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "pay plugins for billing",
|
|
6
|
+
"main": "plugin.js",
|
|
7
|
+
"browser": "plugin.js",
|
|
8
|
+
"files": [
|
|
9
|
+
"controllers/*",
|
|
10
|
+
"funcs/*",
|
|
11
|
+
"server/*",
|
|
12
|
+
"plugin.js",
|
|
13
|
+
"config.js"
|
|
14
|
+
],
|
|
15
|
+
"scripts": {
|
|
16
|
+
"test": "node --test",
|
|
17
|
+
"start": "node --watch-path=server server",
|
|
18
|
+
"dev": "fastify start -w -l info -P server/app.js",
|
|
19
|
+
"debug": "cross-env NODE_ENV=production node server --inspect-brk",
|
|
20
|
+
"prod": "cross-env NODE_ENV=production npm run start",
|
|
21
|
+
"release": "npm run build && npm publish",
|
|
22
|
+
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore"
|
|
23
|
+
},
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"@opengis/fastify-auth": "^1.0.22",
|
|
26
|
+
"@opengis/fastify-table": "^1.0.82",
|
|
27
|
+
"cross-env": "^7.0.3",
|
|
28
|
+
"fastify": "^4.26.1",
|
|
29
|
+
"fastify-plugin": "^4.0.0",
|
|
30
|
+
"request-promise": "^4.2.6"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"@vue/eslint-config-prettier": "^9.0.0",
|
|
34
|
+
"@vue/eslint-config-typescript": "^12.0.0",
|
|
35
|
+
"eslint": "^8.49.0",
|
|
36
|
+
"eslint-config-airbnb": "^19.0.4",
|
|
37
|
+
"eslint-plugin-import": "^2.25.3",
|
|
38
|
+
"eslint-plugin-playwright": "^1.5.2",
|
|
39
|
+
"eslint-plugin-vue": "^9.17.0"
|
|
40
|
+
},
|
|
41
|
+
"author": "Softpro",
|
|
42
|
+
"license": "ISC"
|
|
43
|
+
}
|
package/plugin.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import fp from 'fastify-plugin';
|
|
2
|
+
|
|
3
|
+
import portmoneRedirect from './controllers/portmone/portmone.redirect.js';
|
|
4
|
+
import portmoneStatus from './controllers/portmone/portmone.status.js';
|
|
5
|
+
|
|
6
|
+
import config from './config.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* This plugins adds some utilities to handle http errors
|
|
10
|
+
*
|
|
11
|
+
* @see https://github.com/fastify/fastify-sensible
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
async function plugin(fastify, opt = {}) {
|
|
15
|
+
config.redis = opt.redis;
|
|
16
|
+
config.pg = opt.pg;
|
|
17
|
+
const prefix = opt?.prefix || config.prefix || '/api';
|
|
18
|
+
|
|
19
|
+
fastify.register(import('./server/plugins/hook.js'));
|
|
20
|
+
|
|
21
|
+
fastify.route({
|
|
22
|
+
method: 'GET',
|
|
23
|
+
path: `${prefix}/portmone-redirect`,
|
|
24
|
+
config: { policy: ['public'] },
|
|
25
|
+
handler: portmoneRedirect,
|
|
26
|
+
});
|
|
27
|
+
fastify.route({
|
|
28
|
+
method: 'POST',
|
|
29
|
+
path: `${prefix}/portmone/:id/:status`,
|
|
30
|
+
config: { policy: ['public'] },
|
|
31
|
+
handler: portmoneStatus,
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export default fp(plugin);
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
create schema if not exists admin;
|
|
2
|
+
|
|
3
|
+
-- DROP TABLE is exists admin.users;
|
|
4
|
+
CREATE TABLE if not exists admin.users();
|
|
5
|
+
ALTER TABLE admin.users add column if not exists uid text NOT NULL DEFAULT next_id();
|
|
6
|
+
ALTER TABLE admin.users DROP CONSTRAINT if exists admin_user_uid_pkey cascade;
|
|
7
|
+
ALTER TABLE admin.users DROP CONSTRAINT if exists user_pk cascade;
|
|
8
|
+
|
|
9
|
+
ALTER TABLE admin.users add column if not exists login text;
|
|
10
|
+
ALTER TABLE admin.users add column if not exists password text NOT NULL DEFAULT ''::text;
|
|
11
|
+
ALTER TABLE admin.users add column if not exists user_name text;
|
|
12
|
+
ALTER TABLE admin.users add column if not exists sur_name text;
|
|
13
|
+
ALTER TABLE admin.users add column if not exists father_name text;
|
|
14
|
+
ALTER TABLE admin.users add column if not exists email text;
|
|
15
|
+
ALTER TABLE admin.users add column if not exists phone text;
|
|
16
|
+
ALTER TABLE admin.users add column if not exists avatar text;
|
|
17
|
+
ALTER TABLE admin.users add column if not exists enabled boolean;
|
|
18
|
+
ALTER TABLE admin.users add column if not exists user_personal_code text;
|
|
19
|
+
ALTER TABLE admin.users add column if not exists last_activity_date timestamp without time zone;
|
|
20
|
+
ALTER TABLE admin.users add column if not exists user_type text DEFAULT 'regular'::text;
|
|
21
|
+
ALTER TABLE admin.users add column if not exists salt text;
|
|
22
|
+
ALTER TABLE admin.users add column if not exists cdate timestamp without time zone DEFAULT date_trunc('seconds'::text, now());
|
|
23
|
+
ALTER TABLE admin.users add column if not exists editor_id text;
|
|
24
|
+
ALTER TABLE admin.users add column if not exists editor_date timestamp without time zone;
|
|
25
|
+
|
|
26
|
+
ALTER TABLE admin.users add CONSTRAINT admin_user_uid_pkey PRIMARY KEY (uid);
|
|
27
|
+
|
|
28
|
+
COMMENT ON TABLE admin.users IS 'Користувачі';
|
|
29
|
+
|
|
30
|
+
COMMENT ON COLUMN admin.users.uid IS 'ID користувача';
|
|
31
|
+
COMMENT ON COLUMN admin.users.login IS 'Логін користувача';
|
|
32
|
+
COMMENT ON COLUMN admin.users.password IS 'Пароль користувача';
|
|
33
|
+
COMMENT ON COLUMN admin.users.user_name IS 'Ім''я користувача';
|
|
34
|
+
COMMENT ON COLUMN admin.users.sur_name IS 'Прізвище користувача';
|
|
35
|
+
COMMENT ON COLUMN admin.users.father_name IS 'По-батькові користувача';
|
|
36
|
+
COMMENT ON COLUMN admin.users.email IS 'Ел. пошта користувача';
|
|
37
|
+
COMMENT ON COLUMN admin.users.phone IS 'Номер телефону користувача';
|
|
38
|
+
COMMENT ON COLUMN admin.users.avatar IS 'Аватар';
|
|
39
|
+
COMMENT ON COLUMN admin.users.enabled IS 'On / Off';
|
|
40
|
+
COMMENT ON COLUMN admin.users.last_activity_date IS 'Дата останньої активності';
|
|
41
|
+
COMMENT ON COLUMN admin.users.user_type IS 'Тип користувача';
|
|
42
|
+
COMMENT ON COLUMN admin.users.salt IS 'Сіль';
|
|
43
|
+
|
|
44
|
+
CREATE EXTENSION if not exists pgcrypto SCHEMA public VERSION "1.3";
|
|
45
|
+
CREATE OR REPLACE FUNCTION admin.crypt(text, text) RETURNS text AS '$libdir/pgcrypto', 'pg_crypt' LANGUAGE c IMMUTABLE STRICT COST 1;
|
|
46
|
+
|
|
47
|
+
-- DROP FUNCTION admin.insert_update_user_before();
|
|
48
|
+
CREATE OR REPLACE FUNCTION admin.insert_update_user_before()
|
|
49
|
+
RETURNS trigger AS
|
|
50
|
+
|
|
51
|
+
$BODY$
|
|
52
|
+
DECLARE
|
|
53
|
+
|
|
54
|
+
iterations int;
|
|
55
|
+
hash character varying;
|
|
56
|
+
|
|
57
|
+
BEGIN
|
|
58
|
+
|
|
59
|
+
if(TG_OP='INSERT' or (TG_OP='UPDATE' and new.password<>old.password)) then
|
|
60
|
+
if(char_length(new.password) <> 0 and char_length(new.password) < 8) then
|
|
61
|
+
--raise exception 'password must be longer than 8 characters';
|
|
62
|
+
end if;
|
|
63
|
+
new.salt=md5(now()::text);
|
|
64
|
+
--raise exception '%','change pass';
|
|
65
|
+
if(new.salt ='') then
|
|
66
|
+
new.salt=gen_salt('md5');
|
|
67
|
+
end if;
|
|
68
|
+
iterations = 10;
|
|
69
|
+
hash='';
|
|
70
|
+
|
|
71
|
+
loop
|
|
72
|
+
if iterations=0 then
|
|
73
|
+
exit;
|
|
74
|
+
end if;
|
|
75
|
+
hash = md5(new.password||hash||new.salt);
|
|
76
|
+
iterations=iterations-1;
|
|
77
|
+
end loop;
|
|
78
|
+
new.password=admin.crypt(hash,new.salt);
|
|
79
|
+
|
|
80
|
+
end if;
|
|
81
|
+
RETURN new;
|
|
82
|
+
END
|
|
83
|
+
$BODY$
|
|
84
|
+
|
|
85
|
+
LANGUAGE plpgsql VOLATILE COST 100;
|
|
86
|
+
|
|
87
|
+
DROP TRIGGER if exists insert_update_user_before on admin.users;
|
|
88
|
+
CREATE TRIGGER insert_update_user_before BEFORE INSERT OR UPDATE ON admin.users FOR EACH ROW
|
|
89
|
+
EXECUTE PROCEDURE admin.insert_update_user_before();
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import fp from 'fastify-plugin';
|
|
2
|
+
|
|
3
|
+
// import pgClients from '@opengis/fastify-table/pg/pgClients.js';
|
|
4
|
+
// import getTemplate from '@opengis/fastify-table/table/controllers/utils/getTemplate.js';
|
|
5
|
+
|
|
6
|
+
async function plugin(fastify) {
|
|
7
|
+
fastify.addHook('onListen', async () => {
|
|
8
|
+
if (fastify.execMigrations) {
|
|
9
|
+
await fastify.execMigrations();
|
|
10
|
+
}
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
// fastify.addHook('onListen', async () => {});
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export default fp(plugin);
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
<div>
|
|
2
|
+
<br>
|
|
3
|
+
</div>
|
|
4
|
+
<div>
|
|
5
|
+
<table style="width:100%;">
|
|
6
|
+
<tbody>
|
|
7
|
+
<tr>
|
|
8
|
+
<td style="width:20px;"> </td>
|
|
9
|
+
<td align="center">
|
|
10
|
+
<table style="width:100%" bgcolor="#ffffff">
|
|
11
|
+
<tbody>
|
|
12
|
+
<tr>
|
|
13
|
+
<td align="center"><a href="//{{domain}}" class="logo"><img src="https://softpro.ua/tpl/img/logo.svg"/></a>
|
|
14
|
+
</td>
|
|
15
|
+
</tr>
|
|
16
|
+
|
|
17
|
+
<tr>
|
|
18
|
+
<td style="padding:7px 0" align="center"><span style="color:#555454;font-family:'Open-sans',sans-serif;font-size:small">
|
|
19
|
+
<span style="font-weight:500;font-size:16px;text-transform:uppercase;line-height:25px">{{account_id_text}}, дякуємо за здійснення замовлення на порталі <a href="//{{domain}}">{{domain}}</a></span> </span>
|
|
20
|
+
</td>
|
|
21
|
+
</tr>
|
|
22
|
+
<tr>
|
|
23
|
+
<td style="padding:0"> </td>
|
|
24
|
+
</tr>
|
|
25
|
+
<tr>
|
|
26
|
+
<td style="box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);padding:7px 0;border-radius:30px">
|
|
27
|
+
<table style="width:100%">
|
|
28
|
+
<tbody>
|
|
29
|
+
<tr >
|
|
30
|
+
<td style="padding:7px 0" width="10"> </td>
|
|
31
|
+
<td style="padding:7px 0">
|
|
32
|
+
<span style="color:#555454;font-family:'Open-sans',sans-serif;font-size:small"> </span>
|
|
33
|
+
{{#if service_id}}
|
|
34
|
+
<p style="color:#61677b;border-bottom:1px solid #61677b;margin:3px 0 7px;text-transform:uppercase;font-weight:500;font-size:18px;padding-bottom:10px">Послуга: {{service_id_text}}</p>
|
|
35
|
+
{{/if}}
|
|
36
|
+
<br>
|
|
37
|
+
<span style="color:#61677b"><strong>Дата замовлення:</strong> {{formatDate cdate format='dd.mm.yy hh:mi'}}</span>
|
|
38
|
+
<br>
|
|
39
|
+
<span style="color:#61677b"><strong>Статус замовлення:</strong> {{trx_status_text}}</span>
|
|
40
|
+
{{#if withdrawal_sum}}
|
|
41
|
+
<br>
|
|
42
|
+
<span style="color:#61677b"><strong>Вартість послуги:</strong> {{num_format withdrawal_sum dec="2"}}</span>
|
|
43
|
+
{{/if}}
|
|
44
|
+
{{#if refill_sum}}
|
|
45
|
+
<br>
|
|
46
|
+
<span style="color:#61677b"><strong>Поповнення рахунку:</strong> {{num_format refill_sum dec="2"}}</span>
|
|
47
|
+
{{/if}}
|
|
48
|
+
{{#if total_balance}}
|
|
49
|
+
<br>
|
|
50
|
+
<span style="color:#61677b"><strong>Поточний баланс:</strong> {{num_format total_balance dec="2"}}</span>
|
|
51
|
+
{{/if}}
|
|
52
|
+
</td>
|
|
53
|
+
<td style="padding:7px 0" width="10"> </td>
|
|
54
|
+
</tr>
|
|
55
|
+
</tbody>
|
|
56
|
+
</table>
|
|
57
|
+
</td>
|
|
58
|
+
</tr>
|
|
59
|
+
|
|
60
|
+
<tr>
|
|
61
|
+
<td style="padding:7px 0">
|
|
62
|
+
<span style="color:#555454;font-family:'Open-sans',sans-serif;font-size:small"> </span>
|
|
63
|
+
<table style="width:100%;border-collapse:collapse" bgcolor="#ffffff">
|
|
64
|
+
<tbody>
|
|
65
|
+
<tr>
|
|
66
|
+
<th style="border:1px solid #d6d4d4;background-color:#fbfbfb;color:#61677b;font-family:Arial;font-size:13px;padding:10px" bgcolor="#f8f8f8">Назва послуги</th>
|
|
67
|
+
<th style="border:1px solid #d6d4d4;background-color:#fbfbfb;color:#61677b;font-family:Arial;font-size:13px;padding:10px" bgcolor="#f8f8f8" width="17%">Ціна послуги</th>
|
|
68
|
+
</tr>
|
|
69
|
+
<tr>
|
|
70
|
+
<td style="border:1px solid #d6d4d4">
|
|
71
|
+
<table>
|
|
72
|
+
<tbody>
|
|
73
|
+
<tr>
|
|
74
|
+
<td style="padding: 0 10px">
|
|
75
|
+
<font size="2" face="Open-sans, sans-serif" color="#555454">
|
|
76
|
+
<strong>
|
|
77
|
+
{{#if service_id}}
|
|
78
|
+
{{service_id_text}}
|
|
79
|
+
{{^}}
|
|
80
|
+
{{#if refill_sum}}
|
|
81
|
+
Поповнення рахунку
|
|
82
|
+
{{/if}}
|
|
83
|
+
{{/if}}
|
|
84
|
+
</strong>
|
|
85
|
+
</font>
|
|
86
|
+
</td>
|
|
87
|
+
</tr>
|
|
88
|
+
</tbody>
|
|
89
|
+
</table>
|
|
90
|
+
</td>
|
|
91
|
+
<td style="border:1px solid #d6d4d4">
|
|
92
|
+
<table>
|
|
93
|
+
<tbody>
|
|
94
|
+
<tr>
|
|
95
|
+
<td width="10"> </td>
|
|
96
|
+
<td align="right">
|
|
97
|
+
<font size="2" face="Open-sans, sans-serif" color="#555454"> {{num_format (coalesce withdrawal_sum refill_sum)}} грн.</font>
|
|
98
|
+
</td>
|
|
99
|
+
<td width="10"> </td>
|
|
100
|
+
</tr>
|
|
101
|
+
</tbody>
|
|
102
|
+
</table>
|
|
103
|
+
</td>
|
|
104
|
+
</tr>
|
|
105
|
+
|
|
106
|
+
<tr>
|
|
107
|
+
<td colspan="2" style="border:1px solid #d6d4d4;text-align:center;color:#777;padding:7px 0"> </td>
|
|
108
|
+
</tr>
|
|
109
|
+
|
|
110
|
+
<tr>
|
|
111
|
+
<td colspan="1" style="border:1px solid #d6d4d4;color:#61677b;padding:7px 0" bgcolor="#f8f8f8">
|
|
112
|
+
<table style="width:100%;border-collapse:collapse">
|
|
113
|
+
<tbody>
|
|
114
|
+
<tr>
|
|
115
|
+
<td style="color:#61677b;padding:0" width="10"> </td>
|
|
116
|
+
<td style="color:#61677b;padding:0" align="right"><span style="color:#555454;font-family:'Open-sans',sans-serif;font-size:small"> <strong>Поточний баланс</strong> </span></td>
|
|
117
|
+
<td style="color:#61677b;padding:0" width="10"> </td>
|
|
118
|
+
</tr>
|
|
119
|
+
</tbody>
|
|
120
|
+
</table>
|
|
121
|
+
</td>
|
|
122
|
+
<td colspan="1" style="border:1px solid #d6d4d4;color:#61677b;padding:7px 0" bgcolor="#f8f8f8">
|
|
123
|
+
<table style="width:100%;border-collapse:collapse">
|
|
124
|
+
<tbody>
|
|
125
|
+
<tr>
|
|
126
|
+
<td style="color:#61677b;padding:0" width="10"> </td>
|
|
127
|
+
<td style="color:#61677b;padding:0" align="right"><span style="color:#555454;font-family:'Open-sans',sans-serif;font-size:large">{{total_balance}} грн. </span></td>
|
|
128
|
+
<td style="color:#61677b;padding:0" width="10"> </td>
|
|
129
|
+
</tr>
|
|
130
|
+
</tbody>
|
|
131
|
+
</table>
|
|
132
|
+
</td>
|
|
133
|
+
</tr>
|
|
134
|
+
</tbody>
|
|
135
|
+
</table>
|
|
136
|
+
</td>
|
|
137
|
+
</tr>
|
|
138
|
+
|
|
139
|
+
<tr>
|
|
140
|
+
<td style="border-top:4px solid #61677b;padding:7px 0"><span><a href="//{{domain}}"><img src="https://softpro.ua/tpl/img/logo.svg"/></a></span></td>
|
|
141
|
+
</tr>
|
|
142
|
+
</tbody>
|
|
143
|
+
</table>
|
|
144
|
+
</td>
|
|
145
|
+
<td style="width:20px;"> </td>
|
|
146
|
+
</tr>
|
|
147
|
+
</tbody>
|
|
148
|
+
</table>
|
|
149
|
+
</div>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
select service_id, service_name from billing.service order by service_name
|