@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.
- package/README.md +45 -12
- package/module/pay/pt/marketplace-service-payment-client-template.html +5 -5
- package/package.json +9 -7
- 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
|
@@ -1,38 +1,26 @@
|
|
|
1
|
-
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
import { readFileSync } from 'node:fs';
|
|
4
|
-
import { fileURLToPath } from 'url';
|
|
1
|
+
/* eslint-disable no-console */
|
|
2
|
+
// import path from 'node:path';
|
|
3
|
+
// import { readFileSync } from 'node:fs';
|
|
4
|
+
// import { fileURLToPath } from 'url';
|
|
5
5
|
|
|
6
|
-
import config from '../../../../config.js';
|
|
6
|
+
// import config from '../../../../config.js';
|
|
7
7
|
import pgClients from '../../../plugins/pg/pgClients.js';
|
|
8
|
-
import dataUpdate from '../../../plugins/crud/dataUpdate.js';
|
|
9
8
|
|
|
10
|
-
import
|
|
9
|
+
import processTransaction from '../../../plugins/payment/processTransaction.js';
|
|
11
10
|
|
|
12
|
-
import
|
|
11
|
+
// import sendNotification from '../../../utils/sendNotification.js';
|
|
13
12
|
|
|
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'), { encoding: 'utf8' });
|
|
16
|
-
|
|
17
|
-
const newDate = () => {
|
|
18
|
-
const date = new Date();
|
|
19
|
-
const tzOffset = date.getTimezoneOffset();
|
|
20
|
-
const currentTimeWithTimezome = date - tzOffset * 60 * 1000;
|
|
21
|
-
return new Date(currentTimeWithTimezome).toISOString();
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
const { publicKey, privateKey } = config.integrations?.liqpay || {};
|
|
25
|
-
const liqpay = new LiqPay(publicKey, privateKey);
|
|
13
|
+
// const dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
14
|
+
// const ptBody = readFileSync(path.join(dirname, '../../../../module/pay/pt/marketplace-service-payment-client-template.html'), { encoding: 'utf8' });
|
|
26
15
|
|
|
27
16
|
/**
|
|
28
|
-
* Апі перенаправлює з liqpay назад на сторінку
|
|
17
|
+
* Апі перенаправлює з liqpay назад на сторінку транзакції
|
|
29
18
|
*
|
|
30
19
|
* @method POST
|
|
31
20
|
* @alias liqpayStatus
|
|
32
|
-
* @summary redirect з liqpay на сторінку
|
|
21
|
+
* @summary redirect з liqpay на сторінку транзакції
|
|
33
22
|
* @type api
|
|
34
|
-
* @param {String}
|
|
35
|
-
* @param {String} refillSum - Сума поповнення рахунку
|
|
23
|
+
* @param {String} params.id - Ідентифікатор транзакції
|
|
36
24
|
* @returns {Number} status - номер помилки. Повертається, якщо була допущенна помилка в отриманих параметрах, або з бази. Це може бути 400 або 500
|
|
37
25
|
* @returns {String} error - опис помилки
|
|
38
26
|
* @returns {String} redirect - шлях до переадресації
|
|
@@ -43,146 +31,38 @@ const liqpay = new LiqPay(publicKey, privateKey);
|
|
|
43
31
|
|
|
44
32
|
export default async function liqpayStatus(req, reply) {
|
|
45
33
|
const {
|
|
46
|
-
pg = pgClients.client,
|
|
34
|
+
pg = pgClients.client, headers = {}, params = {},
|
|
47
35
|
} = req;
|
|
48
36
|
|
|
49
|
-
|
|
50
|
-
return reply.status(400).send('invalid liqpay integration settings');
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
const origin = headers?.origin || headers?.referer;
|
|
54
|
-
const { status: responseStatus, id: paymentId } = params;
|
|
37
|
+
const { origin = headers?.referer } = headers;
|
|
55
38
|
|
|
56
|
-
if (!
|
|
39
|
+
if (!params?.id) {
|
|
57
40
|
return reply.status(400).send('not enough params: id');
|
|
58
41
|
}
|
|
59
42
|
|
|
60
|
-
if (
|
|
61
|
-
|
|
62
|
-
const { status, err_description: err } = result;
|
|
63
|
-
if (status !== 'success' && !unittest) {
|
|
64
|
-
return reply.status(400).send(err || 'Помилка перевірки статусу платежу');
|
|
65
|
-
}
|
|
66
|
-
Object.assign(body, result);
|
|
43
|
+
if (params.status !== 'success') {
|
|
44
|
+
return reply.status(400).send('transaction aborted');
|
|
67
45
|
}
|
|
68
46
|
|
|
69
|
-
|
|
70
|
-
if (!body?.data || !body?.signature) {
|
|
71
|
-
return reply.status(400).send('not enough body params: data, signature');
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
const sign = liqpay.str_to_sign(
|
|
75
|
-
privateKey
|
|
76
|
-
+ body.data
|
|
77
|
-
+ privateKey,
|
|
78
|
-
);
|
|
79
|
-
|
|
80
|
-
if (body.signature !== sign) {
|
|
81
|
-
console.warn('payments/warn', JSON.stringify({
|
|
82
|
-
type: 'status',
|
|
83
|
-
service: 'liqpay',
|
|
84
|
-
paymentId,
|
|
85
|
-
origin,
|
|
86
|
-
uid: user?.uid || '0',
|
|
87
|
-
message: 'Transaction signature does not match',
|
|
88
|
-
}));
|
|
89
|
-
return reply.status(403).send('access restricted: signature mismatch');
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
const data = await pg.query('select * from billing.payments where payment_id=$1', [paymentId])
|
|
94
|
-
.then(el => el.rows?.[0] || {});
|
|
95
|
-
|
|
96
|
-
const {
|
|
97
|
-
payment_id: trx,
|
|
98
|
-
payment_amount: refillSum = 0,
|
|
99
|
-
referer_url: redirectUrl,
|
|
100
|
-
payment_status: trxStatus,
|
|
101
|
-
payment_service: paymentService,
|
|
102
|
-
account_id: accountId,
|
|
103
|
-
} = data;
|
|
104
|
-
|
|
105
|
-
if (trxStatus === 'success') {
|
|
106
|
-
console.info('payments/skip', JSON.stringify({
|
|
107
|
-
type: 'status',
|
|
108
|
-
service: 'liqpay',
|
|
109
|
-
paymentId,
|
|
110
|
-
origin,
|
|
111
|
-
uid: user?.uid || '0',
|
|
112
|
-
message: 'Transaction was already processed',
|
|
113
|
-
}));
|
|
114
|
-
return reply.status(400).send('Transaction was already processed');
|
|
115
|
-
}
|
|
47
|
+
const trx = await processTransaction(params.id);
|
|
116
48
|
|
|
117
49
|
if (!trx) {
|
|
118
50
|
return reply.status(404).send('Transaction not found');
|
|
119
51
|
}
|
|
120
52
|
|
|
121
|
-
if (
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
type: 'status',
|
|
127
|
-
service: 'liqpay',
|
|
128
|
-
paymentId,
|
|
129
|
-
origin,
|
|
130
|
-
uid: user?.uid || '0',
|
|
131
|
-
refill: refillSum,
|
|
132
|
-
body,
|
|
133
|
-
params,
|
|
134
|
-
}));
|
|
135
|
-
|
|
136
|
-
// last trx contains total balance
|
|
137
|
-
const { total_balance: totalBalance = 0 } = await pg.query(`select total_balance from billing.payments
|
|
138
|
-
where account_id=$1
|
|
139
|
-
and payment_status in ('success', 'error')
|
|
140
|
-
and total_balance is not null
|
|
141
|
-
order by cdate desc limit 1`, [accountId]).then(el => el.rows?.[0] || {});
|
|
142
|
-
|
|
143
|
-
await dataUpdate({
|
|
144
|
-
table: 'billing.payments',
|
|
145
|
-
data: {
|
|
146
|
-
payment_data: body,
|
|
147
|
-
payment_status: body.status || responseStatus,
|
|
148
|
-
total_balance: +refillSum + +totalBalance,
|
|
149
|
-
success_date: newDate(),
|
|
150
|
-
},
|
|
151
|
-
id: paymentId,
|
|
152
|
-
uid: user?.uid || '0',
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
if (unittest) {
|
|
156
|
-
console.log(`id:${trx}, status: ${responseStatus}, before: ${totalBalance || 0}, after: ${+refillSum + +totalBalance} (${refillSum})`);
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
const { account_email: to } = await pg.query('select account_email from billing.accounts where account_id=$1', [accountId])
|
|
160
|
-
.then(el => el.rows?.[0] || {});
|
|
161
|
-
|
|
162
|
-
console.log('payments/info2', JSON.stringify({
|
|
163
|
-
type: 'status',
|
|
164
|
-
service: 'liqpay',
|
|
165
|
-
paymentId,
|
|
166
|
-
responseStatus,
|
|
167
|
-
to,
|
|
168
|
-
origin,
|
|
169
|
-
redirect: redirectUrl,
|
|
170
|
-
uid: user.uid || '0',
|
|
171
|
-
}));
|
|
172
|
-
|
|
173
|
-
Object.assign(data, {
|
|
174
|
-
domain: req.hostname,
|
|
175
|
-
total_balance: +refillSum + +totalBalance,
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
if (to) {
|
|
53
|
+
/* if (to) {
|
|
54
|
+
Object.assign(trx, {
|
|
55
|
+
domain: req.hostname,
|
|
56
|
+
total_balance: trx.balance,
|
|
57
|
+
});
|
|
179
58
|
const pt = (ptBody || 'empty template')
|
|
180
59
|
.replace(/{{domain}}/g, req.hostname)
|
|
181
|
-
.replace(/{{
|
|
60
|
+
.replace(/{{balance}}/g, trx.balance || '0')
|
|
182
61
|
.replace(/{{username}}/g, to)
|
|
183
|
-
.replace(/{{created_at}}/g, (
|
|
184
|
-
.replace(/{{
|
|
185
|
-
.replace(/{{
|
|
62
|
+
.replace(/{{created_at}}/g, (trx?.created_at || new Date()).toLocaleString('uk-UA'))
|
|
63
|
+
.replace(/{{status}}/g, trx.status)
|
|
64
|
+
.replace(/{{amount}}/g, trx.amount || 0);
|
|
65
|
+
|
|
186
66
|
const options = {
|
|
187
67
|
pg,
|
|
188
68
|
to,
|
|
@@ -191,6 +71,7 @@ export default async function liqpayStatus(req, reply) {
|
|
|
191
71
|
nocache: config?.local,
|
|
192
72
|
};
|
|
193
73
|
await sendNotification(options);
|
|
194
|
-
}
|
|
195
|
-
|
|
74
|
+
} */
|
|
75
|
+
|
|
76
|
+
return reply.redirect(trx.referer_url || '/');
|
|
196
77
|
}
|
|
@@ -1,141 +1,49 @@
|
|
|
1
1
|
import config from '../../../../config.js';
|
|
2
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
3
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
host = 'https://api.monobank.ua', token, paymentRedirect, merchantPaymInfo,
|
|
9
|
-
} = config.integrations?.monopay || {};
|
|
4
|
+
import paymentInfo from '../../../plugins/payment/paymentInfo.js';
|
|
5
|
+
import createTransaction from '../../../plugins/payment/monopay.createTransaction.js';
|
|
10
6
|
|
|
11
7
|
// http://billing.local.ua/api/monopay-redirect?price=50
|
|
12
8
|
|
|
13
9
|
export default async function monopayRedirect(req, reply) {
|
|
14
10
|
const {
|
|
15
|
-
pg = pgClients.client, query = {}, user = {},
|
|
11
|
+
hostname, headers, pg = pgClients.client, query = {}, user = {}, protocol,
|
|
16
12
|
} = req;
|
|
17
13
|
|
|
18
|
-
if (!
|
|
19
|
-
return reply.status(
|
|
14
|
+
if (!user?.uid) {
|
|
15
|
+
return reply.status(401).send('unauthrized');
|
|
20
16
|
}
|
|
21
17
|
|
|
22
|
-
const domain =
|
|
18
|
+
const domain = hostname?.split?.(':')?.shift?.();
|
|
19
|
+
const rootUrl = `${protocol || 'https'}://${domain}`;
|
|
23
20
|
|
|
24
|
-
const paymentData = query.id ? await
|
|
25
|
-
.
|
|
21
|
+
const paymentData = query.id ? await paymentInfo(query.id) : {
|
|
22
|
+
price: query.price, // price for new transtaction as is, else - preCreated
|
|
23
|
+
service: 'portmone',
|
|
24
|
+
referer: headers?.referer, // redirect user after finish of transaction
|
|
25
|
+
uid: user.uid, // user id
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
// redirect back from payment service to API endpoint, provide protocol + domain
|
|
29
|
+
Object.assign(paymentData, { rootUrl });
|
|
26
30
|
|
|
27
|
-
if (
|
|
31
|
+
if (query.id && paymentData.status === 'success') {
|
|
28
32
|
return reply.status(400).send('already processed');
|
|
29
33
|
}
|
|
30
34
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
if (!paymentAmount) {
|
|
35
|
+
if (!paymentData?.price) {
|
|
34
36
|
return reply.status(400).send('price is required');
|
|
35
37
|
}
|
|
36
38
|
|
|
37
|
-
|
|
38
|
-
|
|
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 };
|
|
39
|
+
// create if not exists, then request to payment service
|
|
40
|
+
const result = await createTransaction(paymentData, pg);
|
|
56
41
|
|
|
57
|
-
|
|
58
|
-
|
|
42
|
+
// raw response from payment service
|
|
43
|
+
if (config.debug && query?.debug) {
|
|
44
|
+
return result;
|
|
59
45
|
}
|
|
60
46
|
|
|
61
|
-
|
|
62
|
-
|
|
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
|
-
}
|
|
47
|
+
// link to payment service
|
|
48
|
+
return reply.redirect(result);
|
|
141
49
|
}
|
|
@@ -1,70 +1,22 @@
|
|
|
1
|
-
import
|
|
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;
|
|
1
|
+
import processTransaction from '../../../plugins/payment/processTransaction.js';
|
|
11
2
|
|
|
12
3
|
export default async function monopayStatus({
|
|
13
|
-
|
|
4
|
+
params = {},
|
|
14
5
|
}, reply) {
|
|
15
6
|
const { id, status = 'success' } = params;
|
|
16
|
-
|
|
17
7
|
if (!id) {
|
|
18
8
|
return reply.status(400).send('not enough params: id');
|
|
19
9
|
}
|
|
20
10
|
|
|
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
11
|
if (status !== 'success') {
|
|
38
|
-
return reply.
|
|
12
|
+
return reply.status(400).send('transaction aborted');
|
|
39
13
|
}
|
|
40
14
|
|
|
41
|
-
const
|
|
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');
|
|
15
|
+
const trx = await processTransaction(id);
|
|
48
16
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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
|
-
});
|
|
17
|
+
if (!trx) {
|
|
18
|
+
return reply.status(404).send('Transaction not found');
|
|
19
|
+
}
|
|
68
20
|
|
|
69
|
-
return reply.redirect(
|
|
21
|
+
return reply.redirect(trx.referer_url || '/');
|
|
70
22
|
}
|
|
@@ -1,11 +1,8 @@
|
|
|
1
|
-
import request from 'request-promise';
|
|
2
|
-
|
|
3
1
|
import config from '../../../../config.js';
|
|
4
2
|
import pgClients from '../../../plugins/pg/pgClients.js';
|
|
5
|
-
import dataInsert from '../../../plugins/crud/dataInsert.js';
|
|
6
|
-
import dataUpdate from '../../../plugins/crud/dataUpdate.js';
|
|
7
3
|
|
|
8
|
-
|
|
4
|
+
import paymentInfo from '../../../plugins/payment/paymentInfo.js';
|
|
5
|
+
import createTransaction from '../../../plugins/payment/portmone.createTransaction.js';
|
|
9
6
|
|
|
10
7
|
/**
|
|
11
8
|
* Апі перенаправлює до стороннього сервісу оплати карткою онлайн
|
|
@@ -23,129 +20,43 @@ const { host, payeeId, paymentRedirect = config.paymentRedirect } = config.integ
|
|
|
23
20
|
* @returns {String} file - шлях файла або його назва
|
|
24
21
|
*/
|
|
25
22
|
|
|
26
|
-
export default async function
|
|
23
|
+
export default async function portmoneRedirect(req, reply) {
|
|
27
24
|
const {
|
|
28
|
-
pg = pgClients.client, user = {}, query = {},
|
|
25
|
+
hostname, headers, pg = pgClients.client, user = {}, query = {}, protocol,
|
|
29
26
|
} = req;
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
return reply.status(400).send('empty integration host');
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
if (!payeeId) {
|
|
36
|
-
return reply.status(400).send('empty integration payeeId');
|
|
27
|
+
if (!user?.uid) {
|
|
28
|
+
return reply.status(401).send('unauthrized');
|
|
37
29
|
}
|
|
38
30
|
|
|
39
|
-
const domain =
|
|
40
|
-
|
|
41
|
-
const data = query.id ? await pg.query('select payment_id, account_id, payment_amount, payment_status from billing.payments where payment_id=$1', [query.id])
|
|
42
|
-
.then(el => el.rows?.[0] || {}) : {};
|
|
31
|
+
const domain = hostname?.split?.(':')?.shift?.();
|
|
32
|
+
const rootUrl = `${protocol || 'https'}://${domain}`;
|
|
43
33
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
if (!paymentAmount) {
|
|
51
|
-
return { message: 'price is required', status: 400 };
|
|
52
|
-
}
|
|
34
|
+
const paymentData = query.id ? await paymentInfo(query.id) : {
|
|
35
|
+
price: query.price, // price for new transtaction as is, else - preCreated
|
|
36
|
+
service: 'portmone',
|
|
37
|
+
referer: headers?.referer, // redirect user after finish of transaction
|
|
38
|
+
uid: user.uid, // user id
|
|
39
|
+
};
|
|
53
40
|
|
|
54
|
-
|
|
55
|
-
|
|
41
|
+
// redirect back from payment service to API endpoint, provide protocol + domain
|
|
42
|
+
Object.assign(paymentData, { rootUrl });
|
|
56
43
|
|
|
57
|
-
if (
|
|
58
|
-
return
|
|
44
|
+
if (query.id && paymentData.status === 'success') {
|
|
45
|
+
return reply.status(400).send('already processed');
|
|
59
46
|
}
|
|
60
47
|
|
|
61
|
-
if (
|
|
62
|
-
|
|
63
|
-
'payments/warn',
|
|
64
|
-
'invalid referer',
|
|
65
|
-
'portmone',
|
|
66
|
-
domain,
|
|
67
|
-
headers?.referer,
|
|
68
|
-
user?.uid,
|
|
69
|
-
);
|
|
70
|
-
if (!config.auth?.disableRestricted && !unittest) {
|
|
71
|
-
return reply.status(403).send('access restricted: referer');
|
|
72
|
-
}
|
|
48
|
+
if (!paymentData?.price) {
|
|
49
|
+
return reply.status(400).send('price is required');
|
|
73
50
|
}
|
|
74
51
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
table: 'billing.payments',
|
|
78
|
-
data: {
|
|
79
|
-
payment_amount: paymentAmount,
|
|
80
|
-
account_id: accountId,
|
|
81
|
-
payment_service: 'portmone',
|
|
82
|
-
payment_status: 'inprogress',
|
|
83
|
-
},
|
|
84
|
-
uid: user?.uid || '0',
|
|
85
|
-
}).then(el => el.rows?.[0] || {})
|
|
86
|
-
: { payment_id: data.payment_id };
|
|
52
|
+
// create if not exists, then request to payment service
|
|
53
|
+
const result = await createTransaction(paymentData, pg);
|
|
87
54
|
|
|
88
|
-
|
|
89
|
-
|
|
55
|
+
// raw response from payment service
|
|
56
|
+
if (config.debug && query.debug) {
|
|
57
|
+
return result;
|
|
90
58
|
}
|
|
91
59
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
id: paymentId,
|
|
95
|
-
data: {
|
|
96
|
-
referer_url: paymentRedirect ? paymentRedirect.replace(/{{id}}/g, paymentId) : headers?.referer,
|
|
97
|
-
payment_status: 'inprogress',
|
|
98
|
-
},
|
|
99
|
-
uid: user?.uid || '0',
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
const protocol = headers?.referer.split('://').shift();
|
|
103
|
-
|
|
104
|
-
const formData = {
|
|
105
|
-
payee_id: payeeId,
|
|
106
|
-
description: `Послуга - поповнення рахунку на суму ${paymentAmount} грн`,
|
|
107
|
-
shop_order_number: paymentId,
|
|
108
|
-
bill_amount: paymentAmount,
|
|
109
|
-
success_url: `${protocol || 'https'}://${domain}${config?.prefix || '/api'}/portmone/${paymentId}/success`,
|
|
110
|
-
failure_url: `${protocol || 'https'}://${domain}${config?.prefix || '/api'}/portmone/${paymentId}/error`,
|
|
111
|
-
lang: 'ua',
|
|
112
|
-
};
|
|
113
|
-
|
|
114
|
-
const logObj = {
|
|
115
|
-
type: 'redirect',
|
|
116
|
-
service: 'portmone',
|
|
117
|
-
payeeId,
|
|
118
|
-
paymentId,
|
|
119
|
-
accountId,
|
|
120
|
-
price: paymentAmount,
|
|
121
|
-
domain,
|
|
122
|
-
paymentRedirectUrl: paymentRedirect ? paymentRedirect.replace(/{{id}}/g, paymentId) : headers?.referer,
|
|
123
|
-
uid: user?.uid || '0',
|
|
124
|
-
};
|
|
125
|
-
|
|
126
|
-
try {
|
|
127
|
-
// portmone always returns error...
|
|
128
|
-
const response = await request({
|
|
129
|
-
url: `${host}/gateway/`,
|
|
130
|
-
method: 'POST',
|
|
131
|
-
formData,
|
|
132
|
-
followAllRedirects: false,
|
|
133
|
-
jar: true,
|
|
134
|
-
timeout: 6000,
|
|
135
|
-
simple: false, // prevent not 2xx code trigger catch
|
|
136
|
-
resolveWithFullResponse: true,
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
// unusable code, just in case
|
|
140
|
-
if (response.statusCode !== 302 || !response.headers?.location) {
|
|
141
|
-
throw new Error(response);
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
console.log('payments/redirect', paymentId, response.headers?.location);
|
|
145
|
-
return reply.redirect(response.headers.location);
|
|
146
|
-
}
|
|
147
|
-
catch (err) {
|
|
148
|
-
console.error('payments/error', JSON.stringify(logObj), err.toString(), err.stack);
|
|
149
|
-
return { error: err.toString(), status: 500 };
|
|
150
|
-
}
|
|
60
|
+
// link to payment service
|
|
61
|
+
return reply.redirect(result);
|
|
151
62
|
}
|