@opengis/pay 1.3.3 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/README.md +45 -12
  2. package/module/pay/pt/marketplace-service-payment-client-template.html +5 -5
  3. package/package.json +38 -36
  4. package/plugin.js +2 -0
  5. package/server/migrations/7_transactions.sql +35 -0
  6. package/server/plugins/hook.js +27 -10
  7. package/server/plugins/payment/createPayment.js +29 -0
  8. package/server/plugins/payment/createTransaction.js +26 -0
  9. package/server/plugins/payment/liqpay.checkStatus.js +43 -0
  10. package/server/plugins/payment/liqpay.createTransaction.js +103 -0
  11. package/server/plugins/payment/monopay.checkStatus.js +48 -0
  12. package/server/plugins/payment/monopay.createTransaction.js +116 -0
  13. package/server/plugins/payment/paymentInfo.js +26 -0
  14. package/server/plugins/payment/portmone.checkStatus.js +51 -0
  15. package/server/plugins/payment/portmone.createTransaction.js +94 -0
  16. package/server/plugins/payment/processTransaction.js +73 -0
  17. package/server/routes/{portmone → core}/controllers/payment.info.js +32 -29
  18. package/server/routes/core/index.mjs +12 -0
  19. package/server/routes/liqpay/controllers/liqpay.redirect.js +25 -115
  20. package/server/routes/liqpay/controllers/liqpay.status.js +31 -150
  21. package/server/routes/monopay/controllers/monopay.redirect.js +25 -117
  22. package/server/routes/monopay/controllers/monopay.status.js +8 -56
  23. package/server/routes/portmone/controllers/portmone.redirect.js +27 -116
  24. package/server/routes/portmone/controllers/portmone.status.js +36 -136
  25. package/server/routes/portmone/index.mjs +5 -6
  26. package/utils.js +11 -0
  27. package/server/migrations/1_users.sql +0 -121
  28. package/server/migrations/2_accounts.sql +0 -59
  29. package/server/migrations/3_utils.sql +0 -74
  30. package/server/migrations/4_subscriptions.sql +0 -36
  31. package/server/migrations/5_payments.sql +0 -39
  32. package/server/migrations/6_invoices.sql +0 -55
  33. package/server/routes/liqpay/utils/check.status.js +0 -34
  34. package/server/routes/portmone/utils/portmone.check.status.js +0 -34
@@ -0,0 +1,26 @@
1
+ import config from '../../../config.js';
2
+ import pgClients from '../pg/pgClients.js';
3
+
4
+ const { table = 'pay.transactions' } = config.integrations?.pay || {};
5
+
6
+ export default async function paymentInfo(id, pg = pgClients.client) {
7
+ if (!id) throw new Error('not enough params: id');
8
+
9
+ const {
10
+ amount,
11
+ status,
12
+ service,
13
+ referer_url: referer,
14
+ user_id: userId,
15
+ } = id && pg.pk?.[table] ? await pg.query(`select ${pg.pk?.[table]} as id, * from ${table} where ${pg.pk?.[table]}=$1`, [id])
16
+ .then(el => el.rows?.[0] || {}) : {};
17
+
18
+ return {
19
+ id, // id for existing transtaction
20
+ price: amount, // price for new transtaction as is, else - preCreated
21
+ status, // payment status
22
+ service, // service name
23
+ referer, // redirect user after finish of transaction
24
+ uid: userId,
25
+ };
26
+ }
@@ -0,0 +1,51 @@
1
+ import request from 'request-promise';
2
+
3
+ import config from '../../../config.js';
4
+ import pgClients from '../pg/pgClients.js';
5
+
6
+ const { table = 'pay.transactions' } = config.integrations?.pay || {};
7
+
8
+ const {
9
+ host = 'https://www.portmone.com.ua',
10
+ login,
11
+ password,
12
+ payeeId,
13
+ } = config.integrations?.portmone || {};
14
+
15
+ export default async function portmoneCheckStatus(id, origin, pg = pgClients.client) {
16
+ if (!payeeId) throw new Error('empty integration params: payeeId');
17
+ if (!id) throw new Error('not enough params: id');
18
+
19
+ const data = pg.pk?.[table] && id ? await pg.query(`select ${pg.pk?.[table]} as id, * from ${table} where ${pg.pk?.[table]}=$1`, [id]).then(el => el.rows?.[0] || {}) : undefined;
20
+ if (!data?.id) throw new Error('transaction not found');
21
+
22
+ const formData = {
23
+ method: 'result',
24
+ login,
25
+ password,
26
+ payeeId,
27
+ shopOrderNumber: data.id,
28
+ // shopbillId,
29
+ id: data.id,
30
+ };
31
+
32
+ const isValidOrigin = origin && origin === host;
33
+
34
+ if (!(login && password) && !isValidOrigin) {
35
+ throw new Error('empty integration params: portmone 2');
36
+ }
37
+
38
+ // ? likely cause timeout, skip be origin check or check credentials i.e.: login, password
39
+ const response = isValidOrigin ? { billAmount: data.amount, status: 'success' } : await request({
40
+ url: `${host}/gateway/`,
41
+ method: 'POST',
42
+ formData,
43
+ followAllRedirects: false,
44
+ jar: true,
45
+ timeout: 6000,
46
+ simple: false, // prevent not 2xx code trigger catch
47
+ resolveWithFullResponse: true,
48
+ });
49
+
50
+ return { ...response, status: response.status, amount: +response.billAmount };
51
+ }
@@ -0,0 +1,94 @@
1
+ import request from 'request-promise';
2
+
3
+ import config from '../../../config.js';
4
+ import pgClients from '../pg/pgClients.js';
5
+
6
+ import createPayment from './createPayment.js';
7
+
8
+ const { prefix = '/api' } = config;
9
+
10
+ const { table = 'pay.transactions' } = config.integrations?.pay || {};
11
+ const { host = 'https://www.portmone.com.ua', payeeId } = config.integrations?.portmone || {};
12
+
13
+ export default async function portmoneCreateTransaction({
14
+ id, price, rootUrl, referer, uid, debug,
15
+ }, pg = pgClients.client) {
16
+ if (!host) {
17
+ throw new Error('empty integration host');
18
+ }
19
+
20
+ if (!payeeId) {
21
+ throw new Error('empty integration payeeId');
22
+ }
23
+
24
+ if (!id && !price) {
25
+ throw new Error('not enough params: id or price required');
26
+ }
27
+
28
+ if (!rootUrl) {
29
+ throw new Error('not enough params: rootUrl required');
30
+ }
31
+
32
+ if (!uid) {
33
+ throw new Error('not enough params: uid required');
34
+ }
35
+
36
+ const paymentId = !id ? await pg.query('select next_id()').then(el => el.rows?.[0]?.next_id) : id;
37
+
38
+ const paymentData = id && pg.pk?.[table]
39
+ ? await pg.query(`select ${pg.pk?.[table]} as id, * from ${table} where ${pg.pk?.[table]}=$1`, [id]).then(el => el.rows?.[0])
40
+ : await createPayment({
41
+ paymentId,
42
+ price,
43
+ referer,
44
+ uid,
45
+ service: 'portmone',
46
+ });
47
+
48
+ if (!paymentData?.id) {
49
+ throw new Error('payment not found / creation failed');
50
+ }
51
+
52
+ const paymentAmount = id ? paymentData.amount : price;
53
+
54
+ if (!paymentAmount) {
55
+ throw new Error('invalid payment amount');
56
+ }
57
+
58
+ const formData = {
59
+ payee_id: payeeId,
60
+ description: `Послуга - поповнення рахунку на суму ${paymentAmount} грн`,
61
+ shop_order_number: paymentData.id,
62
+ bill_amount: paymentAmount,
63
+ success_url: `${rootUrl}${prefix}/portmone/${paymentData.id}/success`,
64
+ failure_url: `${rootUrl}${prefix}/portmone/${paymentData.id}/error`,
65
+ lang: 'ua',
66
+ };
67
+
68
+ // portmone always returns error without simple: false options
69
+ const response = await request({
70
+ url: `${host}/gateway/`,
71
+ method: 'POST',
72
+ formData,
73
+ followAllRedirects: false,
74
+ jar: true,
75
+ timeout: 6000,
76
+ simple: false, // prevent not 2xx code trigger catch
77
+ resolveWithFullResponse: true,
78
+ });
79
+
80
+ if (process.env.NODE_ENV !== 'test' && debug) {
81
+ return response;
82
+ }
83
+
84
+ // handle exceptions
85
+ if (response.statusCode !== 302 || !response.headers?.location) {
86
+ throw new Error(response);
87
+ }
88
+
89
+ if (process.env.NODE_ENV === 'test' && debug) {
90
+ return { id: paymentData?.id, url: response.headers.location };
91
+ }
92
+
93
+ return response.headers.location;
94
+ }
@@ -0,0 +1,73 @@
1
+ import config from '../../../config.js';
2
+ import pgClients from '../pg/pgClients.js';
3
+ import dataUpdate from '../crud/dataUpdate.js';
4
+
5
+ import liqpayCheckStatus from './liqpay.checkStatus.js';
6
+ import monopayCheckStatus from './monopay.checkStatus.js';
7
+ import portmoneCheckStatus from './portmone.checkStatus.js';
8
+
9
+ const controller = {
10
+ liqpay: liqpayCheckStatus,
11
+ monopay: monopayCheckStatus,
12
+ portmone: portmoneCheckStatus,
13
+ };
14
+
15
+ const newDate = () => {
16
+ const date = new Date();
17
+ const tzOffset = date.getTimezoneOffset();
18
+ const currentTimeWithTimezome = date - tzOffset * 60 * 1000;
19
+ return new Date(currentTimeWithTimezome).toISOString();
20
+ };
21
+
22
+ const { table = 'pay.transactions' } = config.integrations?.liqpay || {};
23
+
24
+ export default async function processTransaction(id, origin, pg = pgClients.client) {
25
+ if (!id) throw new Error('not enough params: id');
26
+
27
+ const {
28
+ id: paymentId, user_id: userId, status: trxStatus, service,
29
+ } = pg.pk?.[table] ? await pg.query(`select ${pg.pk?.[table]} as id, * from ${table} where ${pg.pk?.[table]}=$1`, [id]).then(el => el.rows?.[0] || {}) : {};
30
+
31
+ if (!paymentId) {
32
+ throw new Error('payment not found');
33
+ }
34
+
35
+ if (!service) throw new Error('invalid payment: service required');
36
+ if (!controller[service]) { throw new Error('invalid params: service'); }
37
+
38
+ if (trxStatus === 'success') {
39
+ throw new Error('Transaction was already processed');
40
+ }
41
+
42
+ const result = await controller[service](id, origin);
43
+
44
+ const { status = 'error', err_description: err } = result || {};
45
+
46
+ if (status !== 'success') {
47
+ throw new Error(err || 'Помилка перевірки статусу платежу');
48
+ }
49
+
50
+ const balance = await pg.query(`select balance from ${table}
51
+ where user_id=$1
52
+ and status in ('success', 'error')
53
+ and balance is not null
54
+ order by created_at desc limit 1`, [userId]).then(el => el.rows?.[0]?.balance || 0);
55
+
56
+ const row = await dataUpdate({
57
+ table,
58
+ data: {
59
+ data: result,
60
+ status,
61
+ balance: +result.amount + +balance,
62
+ success_date: newDate(),
63
+ },
64
+ id,
65
+ uid: userId,
66
+ });
67
+
68
+ if (process.env.NODE_ENV === 'test') {
69
+ console.info(`id:${id}, status: ${result.status}, before: ${balance || 0}, sum: ${+result.amount}, after: ${+result.amount + +balance}`);
70
+ }
71
+
72
+ return { ...row, balance: +result.amount + +balance };
73
+ }
@@ -1,7 +1,10 @@
1
+ import config from '../../../../config.js';
1
2
  import pgClients from '../../../plugins/pg/pgClients.js';
2
3
  import dataUpdate from '../../../plugins/crud/dataUpdate.js';
3
4
 
4
- import liqpayCheckStatus from '../../liqpay/utils/check.status.js';
5
+ import liqpayCheckStatus from '../../../plugins/payment/liqpay.checkStatus.js';
6
+ import monopayCheckStatus from '../../../plugins/payment/monopay.checkStatus.js';
7
+ import portmoneCheckStatus from '../../../plugins/payment/portmone.checkStatus.js';
5
8
 
6
9
  const newDate = (dateNum) => {
7
10
  const date = dateNum ? new Date(dateNum) : new Date();
@@ -10,6 +13,8 @@ const newDate = (dateNum) => {
10
13
  return new Date(currentTimeWithTimezome).toISOString();
11
14
  };
12
15
 
16
+ const { table = 'pay.transactions' } = config.integrations?.pay || {};
17
+
13
18
  /**
14
19
  * Апі для отримання інформації за ID
15
20
  *
@@ -33,58 +38,56 @@ export default async function paymentInfo({
33
38
  return reply.status(400).send('not enough query params: id');
34
39
  }
35
40
 
36
- const { rows = [] } = await pg.query(`select
37
- payment_id,
38
- account_id,
39
- payment_amount,
40
- payment_status,
41
- total_balance,
42
- payment_num,
43
- payment_service,
44
- payment_data,
45
- subscription_id
46
- from billing.payments
47
- where payment_id = $1
48
- and account_id in (select account_id from billing.account_user where user_id = $2)`, [params.id, user.uid]);
41
+ const { rows = [] } = await pg.query(
42
+ `select ${pg.pk?.[table]} as id, * from ${table} where ${pg.pk?.[table]} = $1 and user_id = $2`,
43
+ [params.id, user.uid],
44
+ );
49
45
 
50
46
  if (!rows.length) {
51
47
  return reply.status(404).send('payment not found');
52
48
  }
53
49
 
54
50
  const {
55
- account_id: accountId,
56
- payment_service: service,
57
- payment_status: status,
51
+ user_id: userId,
52
+ service,
53
+ status,
58
54
  } = rows[0];
59
55
 
60
56
  if (status !== 'success' && service === 'liqpay') {
61
- const result = await liqpayCheckStatus(params.id);
57
+ const result = await liqpayCheckStatus({ id: params.id });
62
58
  const { amount = 0 } = result;
63
59
 
64
- const { total_balance: totalBalance = 0 } = await pg.query(`select total_balance from billing.payments
65
- where account_id=$1
66
- and payment_status in ('success', 'error')
67
- and total_balance is not null
68
- order by cdate desc limit 1`, [accountId]).then(el => el.rows?.[0] || {});
60
+ const balance = await pg.query(`select balance from ${table}
61
+ where user_id=$1
62
+ and status = 'success'
63
+ and balance is not null
64
+ order by created_at desc limit 1`, [userId]).then(el => el.rows?.[0]?.balance || 0);
69
65
 
70
66
  const res = await dataUpdate({
71
67
  pg,
72
- table: 'billing.payments',
68
+ table,
73
69
  id: params.id,
74
70
  data: {
75
- payment_data: result,
76
- payment_status: 'success',
77
- total_balance: +amount + +totalBalance,
71
+ data: result,
72
+ status: 'success',
73
+ balance: +amount + +balance,
78
74
  success_date: newDate(result.end_date),
79
75
  },
80
76
  uid: user?.uid,
81
77
  });
82
78
 
83
- // const res1 = await metaFormat({ rows: [res].filter(el => el), cls: { account_id: 'billing.account_id', payment_status: 'billing.payment_status', subscription_id: 'billing.subscription_id' } });
84
79
  return res;
85
80
  }
86
81
 
87
- // await metaFormat({ rows, cls: { account_id: 'billing.account_id', payment_status: 'billing.payment_status', subscription_id: 'billing.subscription_id' } });
82
+ if (status !== 'success' && service === 'portmone' && false) {
83
+ const result = await portmoneCheckStatus({ id: params.id });
84
+ return; // !test
85
+ }
86
+
87
+ if (status !== 'success' && service === 'monopay' && false) {
88
+ const result = await monopayCheckStatus({ id: params.id });
89
+ return; // !test
90
+ }
88
91
 
89
92
  return rows[0];
90
93
  }
@@ -0,0 +1,12 @@
1
+ import paymentInfo from './controllers/payment.info.js';
2
+
3
+ export default async function plugin(app, opts) {
4
+ app.route({
5
+ method: 'GET',
6
+ url: '/payment-info/:id',
7
+ config: {
8
+ policy: ['site'],
9
+ },
10
+ handler: paymentInfo,
11
+ });
12
+ }
@@ -1,137 +1,47 @@
1
- import request from 'request-promise';
2
- import LiqPay from 'liqpayjs-sdk';
3
-
4
1
  import config from '../../../../config.js';
5
2
  import pgClients from '../../../plugins/pg/pgClients.js';
6
- import dataInsert from '../../../plugins/crud/dataInsert.js';
7
- import dataUpdate from '../../../plugins/crud/dataUpdate.js';
8
-
9
- const { prefix = '/api' } = config;
10
-
11
- const { publicKey, privateKey, paymentRedirect } = config.integrations?.liqpay || {};
12
- const liqpay = new LiqPay(publicKey, privateKey);
13
3
 
14
- const host = 'https://www.liqpay.ua';
4
+ import paymentInfo from '../../../plugins/payment/paymentInfo.js';
5
+ import createTransaction from '../../../plugins/payment/liqpay.createTransaction.js';
15
6
 
16
7
  export default async function liqpayRedirect(req, reply) {
17
8
  const {
18
- pg = pgClients.client, query = {}, user = {}, headers = {},
9
+ hostname, headers, pg = pgClients.client, query = {}, user = {}, protocol,
19
10
  } = req;
20
11
 
21
- if (!publicKey || !privateKey) {
22
- return reply.status(400).send('invalid liqpay integration settings');
12
+ if (!user?.uid) {
13
+ return reply.status(401).send('unauthorized');
23
14
  }
24
15
 
25
- const domain = req.hostname.split(':').shift();
26
-
27
- 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])
28
- .then(el => el.rows?.[0] || {}) : {};
29
-
30
- if (paymentData.payment_status && paymentData.payment_status === 'success') {
31
- return { message: 'already processed', status: 400 };
32
- }
16
+ const domain = hostname?.split?.(':')?.shift?.();
17
+ const rootUrl = `${protocol || 'https'}://${domain}`;
33
18
 
34
- const paymentAmount = paymentData.payment_amount || query.price;
35
-
36
- if (!paymentAmount) {
37
- return { message: 'price is required', status: 400 };
38
- }
19
+ const paymentData = query.id ? await paymentInfo(query.id) : {
20
+ price: query.price, // price for new transtaction as is, else - preCreated
21
+ service: 'portmone',
22
+ referer: headers?.referer, // redirect user after finish of transaction
23
+ uid: user.uid, // user id
24
+ };
39
25
 
40
- const accountId = paymentData.account_id
41
- || (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);
26
+ // redirect back from payment service to API endpoint, provide protocol + domain
27
+ Object.assign(paymentData, { rootUrl });
42
28
 
43
- if (!accountId) {
44
- return { message: 'account_id is required', status: 400 };
29
+ if (query.id && paymentData.status === 'success') {
30
+ return reply.status(400).send('already processed');
45
31
  }
46
32
 
47
- const { payment_id: paymentId } = !paymentData.payment_id
48
- ? await dataInsert({
49
- table: 'billing.payments',
50
- data: {
51
- payment_amount: paymentAmount,
52
- account_id: accountId,
53
- payment_service: 'liqpay',
54
- payment_status: 'inprogress',
55
- },
56
- uid: user?.uid || '0',
57
- }).then(el => el.rows?.[0] || {})
58
- : { payment_id: paymentData.payment_id };
59
-
60
- if (!paymentId) {
61
- return { message: 'payment not found', status: 404 };
33
+ if (!paymentData?.price) {
34
+ return reply.status(400).send('price is required');
62
35
  }
63
36
 
64
- await dataUpdate({
65
- table: 'billing.payments',
66
- id: paymentId,
67
- data: {
68
- referer_url: paymentRedirect ? paymentRedirect.replace(/{{id}}/g, paymentId) : headers?.referer,
69
- payment_status: 'inprogress',
70
- },
71
- uid: user?.uid || '0',
72
- });
73
-
74
- const protocol = headers?.referer?.split?.('://')?.shift?.() || req.protocol || 'https';
75
- const rootPageUrl = `${protocol || 'https'}://${domain}`;
76
-
77
- // redirect to api to check payment status and redirect user to page afterwards
78
- const serverUrl = `${rootPageUrl}${prefix}/liqpay/${paymentId}/success`;
79
-
80
- const paymentOptions = {
81
- action: 'pay',
82
- amount: paymentAmount,
83
- currency: 'UAH',
84
- description: 'regular payment',
85
- order_id: paymentId,
86
- version: '3',
87
- // server_url: serverUrl, // update payment info - api url, does not work on sdk package out of the box
88
- result_url: serverUrl, // redirect user after payment - page url
89
- };
90
- const { data, signature } = liqpay.cnb_object(paymentOptions) || {};
91
-
92
- const logObj = {
93
- type: 'redirect',
94
- paymentId,
95
- accountId,
96
- price: paymentAmount,
97
- service: 'liqpay',
98
- domain,
99
- paymentRedirectUrl: serverUrl,
100
- serverUrl,
101
- uid: user?.uid || '0',
102
- };
37
+ // create if not exists, then request to payment service
38
+ const result = await createTransaction({ ...paymentData, debug: config.debug && query.debug }, pg);
103
39
 
40
+ // html
104
41
  if (config.debug && query?.debug) {
105
- const html = liqpay.cnb_form(paymentOptions);
106
- return reply.status(302).headers({ 'Content-Type': 'text/html' }).send(html);
42
+ return reply.status(302).headers({ 'Content-Type': 'text/html' }).send(result);
107
43
  }
108
44
 
109
- try {
110
- const response = await request({
111
- url: `${host}/api/3/checkout`,
112
- method: 'POST',
113
- form: { data, signature },
114
- followAllRedirects: false,
115
- jar: true,
116
- timeout: 6000,
117
- simple: false, // prevent not 2xx code trigger catch
118
- resolveWithFullResponse: true,
119
- });
120
-
121
- if (response.statusCode !== 302 || !response.headers?.location) {
122
- throw new Error(response);
123
- }
124
-
125
- if (response.headers?.location?.includes?.('throw_error')) {
126
- throw new Error(response.headers?.location);
127
- }
128
-
129
- Object.assign(logObj, { paymentId, redirect: response.headers?.location, status: 302 });
130
- console.log('payments/info', JSON.stringify(logObj));
131
- return reply.redirect(response.headers.location);
132
- }
133
- catch (err) {
134
- console.error('payments/error', JSON.stringify(logObj), err.toString(), err.stack);
135
- return reply.status(500).send(err.toString());
136
- }
45
+ // link to payment service
46
+ return reply.redirect(result);
137
47
  }