@unchainedshop/plugins 2.8.8 → 2.8.11
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/lib/payment/datatrans-v2/api/makeFetcher.d.ts +1 -1
- package/lib/payment/datatrans-v2/middleware.js +1 -1
- package/lib/payment/datatrans-v2/middleware.js.map +1 -1
- package/lib/payment/payrexx/api/index.d.ts +0 -2
- package/lib/payment/payrexx/api/index.js +35 -71
- package/lib/payment/payrexx/api/index.js.map +1 -1
- package/lib/payment/payrexx/api/makeFetcher.d.ts +3 -0
- package/lib/payment/payrexx/api/makeFetcher.js +72 -0
- package/lib/payment/payrexx/api/makeFetcher.js.map +1 -0
- package/lib/payment/payrexx/index.js +72 -36
- package/lib/payment/payrexx/index.js.map +1 -1
- package/lib/payment/payrexx/middleware.js +46 -29
- package/lib/payment/payrexx/middleware.js.map +1 -1
- package/lib/payment/payrexx/payrexx.d.ts +16 -1
- package/lib/payment/payrexx/payrexx.js +29 -6
- package/lib/payment/payrexx/payrexx.js.map +1 -1
- package/lib/payment/postfinance-checkout/api.d.ts +0 -2
- package/lib/payment/postfinance-checkout/utils.d.ts +0 -2
- package/package.json +24 -24
- package/src/payment/datatrans-v2/middleware.ts +1 -1
- package/src/payment/payrexx/api/index.ts +39 -87
- package/src/payment/payrexx/api/makeFetcher.ts +87 -0
- package/src/payment/payrexx/index.ts +81 -37
- package/src/payment/payrexx/middleware.ts +57 -34
- package/src/payment/payrexx/payrexx.ts +34 -7
|
@@ -1,5 +1,12 @@
|
|
|
1
|
+
const getRedirects = () => {
|
|
2
|
+
const { EMAIL_WEBSITE_URL, DATATRANS_SUCCESS_PATH = '/payrexx/success', DATATRANS_ERROR_PATH = '/payrexx/error', DATATRANS_CANCEL_PATH = '/payrexx/cancel', } = process.env;
|
|
3
|
+
return {
|
|
4
|
+
successRedirectUrl: `${EMAIL_WEBSITE_URL}${DATATRANS_SUCCESS_PATH}`,
|
|
5
|
+
failedRedirectUrl: `${EMAIL_WEBSITE_URL}${DATATRANS_ERROR_PATH}`,
|
|
6
|
+
cancelRedirectUrl: `${EMAIL_WEBSITE_URL}${DATATRANS_CANCEL_PATH}`,
|
|
7
|
+
};
|
|
8
|
+
};
|
|
1
9
|
export const mapOrderDataToGatewayObject = ({ order, orderPayment, pricing }, options = {}) => {
|
|
2
|
-
const { EMAIL_WEBSITE_NAME = 'Unchained', EMAIL_WEBSITE_URL, DATATRANS_SUCCESS_PATH = '/payrexx/success', DATATRANS_ERROR_PATH = '/payrexx/error', DATATRANS_CANCEL_PATH = '/payrexx/cancel', } = process.env;
|
|
3
10
|
const { currency, amount } = pricing.total({ useNetPrice: false });
|
|
4
11
|
const customerData = {};
|
|
5
12
|
if (order.contact?.emailAddress) {
|
|
@@ -14,17 +21,33 @@ export const mapOrderDataToGatewayObject = ({ order, orderPayment, pricing }, op
|
|
|
14
21
|
const gatewayObject = {
|
|
15
22
|
amount: Math.round(amount),
|
|
16
23
|
currency: currency.toUpperCase(),
|
|
17
|
-
purpose: encodeURIComponent(`${EMAIL_WEBSITE_NAME} #${order._id}`),
|
|
18
|
-
// preAuthorization: true -> tokenization!
|
|
24
|
+
purpose: encodeURIComponent(`${process.env.EMAIL_WEBSITE_NAME || 'Unchained'} #${order._id}`),
|
|
19
25
|
reservation: true,
|
|
20
26
|
skipResultPage: true,
|
|
21
|
-
|
|
22
|
-
failedRedirectUrl: `${EMAIL_WEBSITE_URL}${DATATRANS_ERROR_PATH}`,
|
|
23
|
-
cancelRedirectUrl: `${EMAIL_WEBSITE_URL}${DATATRANS_CANCEL_PATH}`,
|
|
27
|
+
...getRedirects(),
|
|
24
28
|
referenceId: orderPayment._id,
|
|
25
29
|
...customerData,
|
|
26
30
|
...options,
|
|
27
31
|
};
|
|
28
32
|
return gatewayObject;
|
|
29
33
|
};
|
|
34
|
+
export const mapUserToGatewayObject = ({ userId, emailAddress, currencyCode }, options = {}) => {
|
|
35
|
+
const customerData = {};
|
|
36
|
+
if (emailAddress) {
|
|
37
|
+
customerData['fields[email][value]'] = emailAddress;
|
|
38
|
+
}
|
|
39
|
+
customerData['fields[custom_field_1][name][0]'] = 'userId';
|
|
40
|
+
customerData['fields[custom_field_1][value]'] = userId;
|
|
41
|
+
const gatewayObject = {
|
|
42
|
+
amount: Math.round(0),
|
|
43
|
+
currency: currencyCode,
|
|
44
|
+
purpose: encodeURIComponent(`${process.env.EMAIL_WEBSITE_NAME || 'Unchained'} ${userId}`),
|
|
45
|
+
preAuthorization: true,
|
|
46
|
+
skipResultPage: true,
|
|
47
|
+
...getRedirects(),
|
|
48
|
+
referenceId: userId,
|
|
49
|
+
...options,
|
|
50
|
+
};
|
|
51
|
+
return gatewayObject;
|
|
52
|
+
};
|
|
30
53
|
//# sourceMappingURL=payrexx.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"payrexx.js","sourceRoot":"","sources":["../../../src/payment/payrexx/payrexx.ts"],"names":[],"mappings":"AAAA,MAAM,
|
|
1
|
+
{"version":3,"file":"payrexx.js","sourceRoot":"","sources":["../../../src/payment/payrexx/payrexx.ts"],"names":[],"mappings":"AAAA,MAAM,YAAY,GAAG,GAAG,EAAE;IACxB,MAAM,EACJ,iBAAiB,EACjB,sBAAsB,GAAG,kBAAkB,EAC3C,oBAAoB,GAAG,gBAAgB,EACvC,qBAAqB,GAAG,iBAAiB,GAC1C,GAAG,OAAO,CAAC,GAAG,CAAC;IAEhB,OAAO;QACL,kBAAkB,EAAE,GAAG,iBAAiB,GAAG,sBAAsB,EAAE;QACnE,iBAAiB,EAAE,GAAG,iBAAiB,GAAG,oBAAoB,EAAE;QAChE,iBAAiB,EAAE,GAAG,iBAAiB,GAAG,qBAAqB,EAAE;KAClE,CAAC;AACJ,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,2BAA2B,GAAG,CAAC,EAAE,KAAK,EAAE,YAAY,EAAE,OAAO,EAAE,EAAE,OAAO,GAAG,EAAE,EAAE,EAAE;IAC5F,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,KAAK,CAAC,EAAE,WAAW,EAAE,KAAK,EAAE,CAAC,CAAC;IAEnE,MAAM,YAAY,GAAG,EAAE,CAAC;IACxB,IAAI,KAAK,CAAC,OAAO,EAAE,YAAY,EAAE,CAAC;QAChC,YAAY,CAAC,sBAAsB,CAAC,GAAG,KAAK,CAAC,OAAO,EAAE,YAAY,CAAC;IACrE,CAAC;IACD,IAAI,KAAK,CAAC,cAAc,EAAE,SAAS,EAAE,CAAC;QACpC,YAAY,CAAC,yBAAyB,CAAC,GAAG,KAAK,CAAC,cAAc,EAAE,SAAS,CAAC;IAC5E,CAAC;IACD,IAAI,KAAK,CAAC,cAAc,EAAE,QAAQ,EAAE,CAAC;QACnC,YAAY,CAAC,wBAAwB,CAAC,GAAG,KAAK,CAAC,cAAc,EAAE,QAAQ,CAAC;IAC1E,CAAC;IAED,MAAM,aAAa,GAAG;QACpB,MAAM,EAAE,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC;QAC1B,QAAQ,EAAE,QAAQ,CAAC,WAAW,EAAE;QAChC,OAAO,EAAE,kBAAkB,CAAC,GAAG,OAAO,CAAC,GAAG,CAAC,kBAAkB,IAAI,WAAW,KAAK,KAAK,CAAC,GAAG,EAAE,CAAC;QAC7F,WAAW,EAAE,IAAI;QACjB,cAAc,EAAE,IAAI;QACpB,GAAG,YAAY,EAAE;QACjB,WAAW,EAAE,YAAY,CAAC,GAAG;QAC7B,GAAG,YAAY;QACf,GAAG,OAAO;KACX,CAAC;IACF,OAAO,aAAa,CAAC;AACvB,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,sBAAsB,GAAG,CAAC,EAAE,MAAM,EAAE,YAAY,EAAE,YAAY,EAAE,EAAE,OAAO,GAAG,EAAE,EAAE,EAAE;IAC7F,MAAM,YAAY,GAAG,EAAE,CAAC;IAExB,IAAI,YAAY,EAAE,CAAC;QACjB,YAAY,CAAC,sBAAsB,CAAC,GAAG,YAAY,CAAC;IACtD,CAAC;IAED,YAAY,CAAC,iCAAiC,CAAC,GAAG,QAAQ,CAAC;IAC3D,YAAY,CAAC,+BAA+B,CAAC,GAAG,MAAM,CAAC;IAEvD,MAAM,aAAa,GAAG;QACpB,MAAM,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;QACrB,QAAQ,EAAE,YAAY;QACtB,OAAO,EAAE,kBAAkB,CAAC,GAAG,OAAO,CAAC,GAAG,CAAC,kBAAkB,IAAI,WAAW,IAAI,MAAM,EAAE,CAAC;QACzF,gBAAgB,EAAE,IAAI;QACtB,cAAc,EAAE,IAAI;QACpB,GAAG,YAAY,EAAE;QACjB,WAAW,EAAE,MAAM;QACnB,GAAG,OAAO;KACX,CAAC;IACF,OAAO,aAAa,CAAC;AACvB,CAAC,CAAC"}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import * as pf from 'postfinancecheckout';
|
|
2
|
-
declare const PostFinanceCheckout: typeof pf.PostFinanceCheckout;
|
|
3
2
|
export declare const getTransaction: (transactionId: string) => Promise<pf.PostFinanceCheckout.model.Transaction>;
|
|
4
3
|
export declare const getTransactionCompletion: (entityId: string) => Promise<pf.PostFinanceCheckout.model.TransactionCompletion>;
|
|
5
4
|
export declare const getToken: (spaceId: number, tokenId: number) => Promise<pf.PostFinanceCheckout.model.Token>;
|
|
@@ -10,4 +9,3 @@ export declare const confirmDeferredTransaction: (transactionId: string) => Prom
|
|
|
10
9
|
export declare const getPaymentPageUrl: (transactionId: number) => Promise<string>;
|
|
11
10
|
export declare const getLightboxJavascriptUrl: (transactionId: number) => Promise<string>;
|
|
12
11
|
export declare const getIframeJavascriptUrl: (transactionId: number) => Promise<string>;
|
|
13
|
-
export {};
|
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
import { Order, OrdersModule } from '@unchainedshop/types/orders.js';
|
|
2
2
|
import * as pf from 'postfinancecheckout';
|
|
3
|
-
declare const PostFinanceCheckout: typeof pf.PostFinanceCheckout;
|
|
4
3
|
export declare const transactionIsPaid: (transaction: pf.PostFinanceCheckout.model.Transaction, expectedCurrency: string, expectedAmount: number) => Promise<boolean>;
|
|
5
4
|
export declare const orderIsPaid: (order: Order, transaction: pf.PostFinanceCheckout.model.Transaction, orderModule: OrdersModule) => Promise<boolean>;
|
|
6
|
-
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@unchainedshop/plugins",
|
|
3
|
-
"version": "2.8.
|
|
3
|
+
"version": "2.8.11",
|
|
4
4
|
"main": "lib/plugins-index.js",
|
|
5
5
|
"types": "lib/plugins-index.d.ts",
|
|
6
6
|
"exports": {
|
|
@@ -80,44 +80,44 @@
|
|
|
80
80
|
"lru-cache": "^10.0.0",
|
|
81
81
|
"open": "^10.0.0",
|
|
82
82
|
"postfinancecheckout": "^4.1.1",
|
|
83
|
-
"stripe": "^14.
|
|
83
|
+
"stripe": "^14.25.0",
|
|
84
84
|
"tiny-secp256k1": "^2.2.3",
|
|
85
|
-
"twilio": "^
|
|
85
|
+
"twilio": "^5.0.3",
|
|
86
86
|
"web-push": "^3.6.3"
|
|
87
87
|
},
|
|
88
88
|
"devDependencies": {
|
|
89
89
|
"@redis/client": "^1.5.14",
|
|
90
|
-
"@types/node": "^20.
|
|
91
|
-
"@unchainedshop/api": "^2.8.
|
|
92
|
-
"@unchainedshop/core-delivery": "^2.8.
|
|
93
|
-
"@unchainedshop/core-enrollments": "^2.8.
|
|
94
|
-
"@unchainedshop/core-filters": "^2.8.
|
|
95
|
-
"@unchainedshop/core-messaging": "^2.8.
|
|
96
|
-
"@unchainedshop/core-orders": "^2.8.
|
|
97
|
-
"@unchainedshop/core-payment": "^2.8.
|
|
98
|
-
"@unchainedshop/core-products": "^2.8.
|
|
99
|
-
"@unchainedshop/core-quotations": "^2.8.
|
|
100
|
-
"@unchainedshop/core-warehousing": "^2.8.
|
|
101
|
-
"@unchainedshop/core-worker": "^2.8.
|
|
102
|
-
"@unchainedshop/events": "^2.8.
|
|
103
|
-
"@unchainedshop/file-upload": "^2.8.
|
|
104
|
-
"@unchainedshop/logger": "^2.8.
|
|
105
|
-
"@unchainedshop/types": "^2.8.
|
|
106
|
-
"@unchainedshop/utils": "^2.8.
|
|
90
|
+
"@types/node": "^20.12.7",
|
|
91
|
+
"@unchainedshop/api": "^2.8.11",
|
|
92
|
+
"@unchainedshop/core-delivery": "^2.8.11",
|
|
93
|
+
"@unchainedshop/core-enrollments": "^2.8.11",
|
|
94
|
+
"@unchainedshop/core-filters": "^2.8.11",
|
|
95
|
+
"@unchainedshop/core-messaging": "^2.8.11",
|
|
96
|
+
"@unchainedshop/core-orders": "^2.8.11",
|
|
97
|
+
"@unchainedshop/core-payment": "^2.8.11",
|
|
98
|
+
"@unchainedshop/core-products": "^2.8.11",
|
|
99
|
+
"@unchainedshop/core-quotations": "^2.8.11",
|
|
100
|
+
"@unchainedshop/core-warehousing": "^2.8.11",
|
|
101
|
+
"@unchainedshop/core-worker": "^2.8.11",
|
|
102
|
+
"@unchainedshop/events": "^2.8.11",
|
|
103
|
+
"@unchainedshop/file-upload": "^2.8.11",
|
|
104
|
+
"@unchainedshop/logger": "^2.8.11",
|
|
105
|
+
"@unchainedshop/types": "^2.8.11",
|
|
106
|
+
"@unchainedshop/utils": "^2.8.11",
|
|
107
107
|
"bluebird": "^3.7.2",
|
|
108
108
|
"event-iterator": "^2.0.0",
|
|
109
|
-
"express": "^4.
|
|
109
|
+
"express": "^4.19.2",
|
|
110
110
|
"jest": "^29.7.0",
|
|
111
111
|
"JSONStream": "^1.3.5",
|
|
112
112
|
"lru-cache": "^10.2.0",
|
|
113
113
|
"minio": "^7.1.3",
|
|
114
114
|
"node-sheets": "^1.2.0",
|
|
115
|
-
"nodemailer": "^6.9.
|
|
116
|
-
"open": "^10.0
|
|
115
|
+
"nodemailer": "^6.9.13",
|
|
116
|
+
"open": "^10.1.0",
|
|
117
117
|
"postfinancecheckout": "^4.5.0",
|
|
118
118
|
"request": "^2.88.2",
|
|
119
119
|
"ts-jest": "^29.1.2",
|
|
120
|
-
"typescript": "^5.
|
|
120
|
+
"typescript": "^5.4.5",
|
|
121
121
|
"web-push": "^3.6.7",
|
|
122
122
|
"xml-js": "^1.6.11"
|
|
123
123
|
}
|
|
@@ -49,7 +49,7 @@ export const datatransHandler = async (req, res) => {
|
|
|
49
49
|
const paymentCredentials = await modules.payment.registerCredentials(
|
|
50
50
|
paymentProviderId,
|
|
51
51
|
{ userId, transactionContext: { transactionId: transaction.transactionId } },
|
|
52
|
-
|
|
52
|
+
resolvedContext,
|
|
53
53
|
);
|
|
54
54
|
logger.info(`registered payment credentials for ${userId}`, {
|
|
55
55
|
userId,
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import makeFetcher from './makeFetcher.js';
|
|
2
|
+
|
|
1
3
|
const baseUrl = 'https://api.payrexx.com/v1.0/';
|
|
2
4
|
|
|
3
5
|
export enum GatewayObjectStatus {
|
|
@@ -29,93 +31,43 @@ export type GatewayObject = {
|
|
|
29
31
|
|
|
30
32
|
export type TransactionObject = any;
|
|
31
33
|
|
|
32
|
-
const createPayrexxAPI = (instance: string, secret: string) =>
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
}
|
|
70
|
-
throw new Error(await result.text());
|
|
71
|
-
},
|
|
72
|
-
|
|
73
|
-
async deleteReservation(id) {
|
|
74
|
-
const url = this.buildUrl(`Transaction/${id}/`, { ApiSignature: await this.buildSignature() });
|
|
75
|
-
const result = await fetch(url, {
|
|
76
|
-
method: 'DELETE',
|
|
77
|
-
headers: {
|
|
78
|
-
accept: 'application/json',
|
|
79
|
-
'content-type': 'application/x-www-form-urlencoded',
|
|
80
|
-
},
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
if (result.ok) {
|
|
84
|
-
return result.json();
|
|
85
|
-
}
|
|
86
|
-
throw new Error(await result.text());
|
|
87
|
-
},
|
|
88
|
-
|
|
89
|
-
async getGateway(id): Promise<GatewayObject> {
|
|
90
|
-
const url = this.buildUrl(`Gateway/${id}/`, { ApiSignature: await this.buildSignature() });
|
|
91
|
-
const result = await fetch(url);
|
|
92
|
-
if (!result.ok) throw new Error(await result.text());
|
|
93
|
-
const { status, data } = await result.json();
|
|
94
|
-
if (status !== 'success') return null;
|
|
95
|
-
return data?.[0];
|
|
96
|
-
},
|
|
97
|
-
|
|
98
|
-
async createGateway(params): Promise<GatewayObject> {
|
|
99
|
-
const queryParams = { ...params };
|
|
100
|
-
const signature = await this.buildSignature(new URLSearchParams(queryParams).toString());
|
|
101
|
-
queryParams.ApiSignature = signature;
|
|
102
|
-
|
|
103
|
-
const url = this.buildUrl('Gateway/');
|
|
104
|
-
const result = await fetch(url, {
|
|
105
|
-
method: 'POST',
|
|
106
|
-
body: new URLSearchParams(queryParams).toString(),
|
|
107
|
-
headers: {
|
|
108
|
-
accept: 'application/json',
|
|
109
|
-
'content-type': 'application/x-www-form-urlencoded',
|
|
110
|
-
},
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
if (result.ok) {
|
|
114
|
-
return result.json();
|
|
115
|
-
}
|
|
116
|
-
throw new Error(await result.text());
|
|
117
|
-
},
|
|
118
|
-
});
|
|
34
|
+
const createPayrexxAPI = (instance: string, secret: string) => {
|
|
35
|
+
const fetchPayrexx = makeFetcher(baseUrl, instance, secret);
|
|
36
|
+
|
|
37
|
+
return {
|
|
38
|
+
async chargePreAuthorized(id, params) {
|
|
39
|
+
const result = await fetchPayrexx(`Transaction/${id}`, 'POST', params);
|
|
40
|
+
if (result.ok) {
|
|
41
|
+
return result.json();
|
|
42
|
+
}
|
|
43
|
+
throw new Error(await result.text());
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
async deleteReservation(id) {
|
|
47
|
+
const result = await fetchPayrexx(`Transaction/${id}`, 'DELETE');
|
|
48
|
+
if (result.ok) {
|
|
49
|
+
return result.json();
|
|
50
|
+
}
|
|
51
|
+
throw new Error(await result.text());
|
|
52
|
+
},
|
|
53
|
+
|
|
54
|
+
async getGateway(id): Promise<GatewayObject> {
|
|
55
|
+
const result = await fetchPayrexx(`Gateway/${id}`, 'GET');
|
|
56
|
+
if (!result.ok) throw new Error(await result.text());
|
|
57
|
+
const { status, data } = await result.json();
|
|
58
|
+
if (status !== 'success') return null;
|
|
59
|
+
return data?.[0];
|
|
60
|
+
},
|
|
61
|
+
|
|
62
|
+
async createGateway(params): Promise<GatewayObject> {
|
|
63
|
+
const result = await fetchPayrexx('Gateway', 'POST', params);
|
|
64
|
+
if (result.ok) {
|
|
65
|
+
return result.json();
|
|
66
|
+
}
|
|
67
|
+
throw new Error(await result.text());
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
};
|
|
119
71
|
|
|
120
72
|
export default createPayrexxAPI;
|
|
121
73
|
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/// <reference lib="dom" />
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import util from 'util';
|
|
4
|
+
import { resolve } from 'path';
|
|
5
|
+
import { createLogger } from '@unchainedshop/logger';
|
|
6
|
+
|
|
7
|
+
const readFile = util.promisify(fs.readFile);
|
|
8
|
+
|
|
9
|
+
const { PAYREXX_API_MOCKS_PATH } = process.env;
|
|
10
|
+
|
|
11
|
+
const logger = createLogger('unchained:payrexx');
|
|
12
|
+
|
|
13
|
+
export default (
|
|
14
|
+
baseUrl: string = 'https://api.payrexx.com/v1.0/',
|
|
15
|
+
instance: string = null,
|
|
16
|
+
secret: string = null,
|
|
17
|
+
): ((path: string, method: 'GET' | 'DELETE' | 'PUT' | 'POST', data?: any) => Promise<Response>) => {
|
|
18
|
+
if (PAYREXX_API_MOCKS_PATH) {
|
|
19
|
+
return async (path): Promise<Response> => {
|
|
20
|
+
try {
|
|
21
|
+
const filePath = resolve(process.env.PWD, PAYREXX_API_MOCKS_PATH, `${path}.json`);
|
|
22
|
+
const content = await readFile(filePath);
|
|
23
|
+
const textData = content.toString();
|
|
24
|
+
const jsonData = JSON.parse(textData);
|
|
25
|
+
return {
|
|
26
|
+
json: async () => jsonData,
|
|
27
|
+
text: async () => textData,
|
|
28
|
+
ok: !jsonData?.error,
|
|
29
|
+
status: jsonData?.error ? 500 : 204,
|
|
30
|
+
} as any;
|
|
31
|
+
} catch (error) {
|
|
32
|
+
logger.error('Mock: Error while trying reading and parsing file', {
|
|
33
|
+
...error,
|
|
34
|
+
});
|
|
35
|
+
return {
|
|
36
|
+
json: async () => ({ error: { code: 'MOCK', message: 'MOCK' } }),
|
|
37
|
+
text: async () => 'MOCK',
|
|
38
|
+
ok: false,
|
|
39
|
+
status: 500,
|
|
40
|
+
} as any;
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const buildSignature = async (query = '') => {
|
|
46
|
+
const key = await crypto.subtle.importKey(
|
|
47
|
+
'raw',
|
|
48
|
+
new TextEncoder().encode(secret),
|
|
49
|
+
{ name: 'HMAC', hash: 'SHA-256' },
|
|
50
|
+
false,
|
|
51
|
+
['sign', 'verify'],
|
|
52
|
+
);
|
|
53
|
+
const signature = await crypto.subtle.sign('HMAC', key, new TextEncoder().encode(query));
|
|
54
|
+
return btoa(String.fromCharCode(...new Uint8Array(signature)));
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const buildUrl = (path, params = {}) => {
|
|
58
|
+
const url = new URL(`${path}/`, baseUrl);
|
|
59
|
+
url.search = new URLSearchParams({ instance, ...params }).toString();
|
|
60
|
+
return url.href;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
return async (path: string, method: 'GET' | 'DELETE' | 'POST', data?: any): Promise<Response> => {
|
|
64
|
+
logger.verbose(`${method} ${path}`);
|
|
65
|
+
if (method === 'POST') {
|
|
66
|
+
const queryParams = { ...data };
|
|
67
|
+
const signature = await buildSignature(new URLSearchParams(queryParams).toString());
|
|
68
|
+
queryParams.ApiSignature = signature;
|
|
69
|
+
const url = buildUrl(path);
|
|
70
|
+
return fetch(url, {
|
|
71
|
+
method,
|
|
72
|
+
body: new URLSearchParams(queryParams).toString(),
|
|
73
|
+
headers: {
|
|
74
|
+
accept: 'application/json',
|
|
75
|
+
'content-type': 'application/x-www-form-urlencoded',
|
|
76
|
+
},
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
if (method === 'DELETE' || method === 'GET') {
|
|
80
|
+
const url = buildUrl(path, { ApiSignature: await buildSignature() });
|
|
81
|
+
return fetch(url, {
|
|
82
|
+
method,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
throw new Error('Method not supported');
|
|
86
|
+
};
|
|
87
|
+
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { IPaymentAdapter } from '@unchainedshop/types/payments.js';
|
|
2
2
|
import { PaymentAdapter, PaymentDirector, PaymentError } from '@unchainedshop/core-payment';
|
|
3
3
|
import { createLogger } from '@unchainedshop/logger';
|
|
4
|
-
import { mapOrderDataToGatewayObject } from './payrexx.js';
|
|
4
|
+
import { mapOrderDataToGatewayObject, mapUserToGatewayObject } from './payrexx.js';
|
|
5
5
|
import createPayrexxAPI, { GatewayObjectStatus } from './api/index.js';
|
|
6
6
|
|
|
7
7
|
export * from './middleware.js';
|
|
@@ -50,17 +50,10 @@ const Payrexx: IPaymentAdapter = {
|
|
|
50
50
|
return false;
|
|
51
51
|
},
|
|
52
52
|
|
|
53
|
-
validate: async () => {
|
|
54
|
-
throw new Error('Token Registration Flow not implemented yet');
|
|
55
|
-
},
|
|
56
|
-
|
|
57
|
-
register: async () => {
|
|
58
|
-
throw new Error('Token Registration Flow not implemented yet');
|
|
59
|
-
},
|
|
60
|
-
|
|
61
53
|
sign: async (transactionContext = {}) => {
|
|
62
|
-
const { orderPayment, order } = params.paymentContext;
|
|
54
|
+
const { orderPayment, userId, order } = params.paymentContext;
|
|
63
55
|
if (orderPayment) {
|
|
56
|
+
// Order Checkout signing (One-time payment)
|
|
64
57
|
const pricing = await modules.orders.pricingSheet(order);
|
|
65
58
|
const gatewayObject = mapOrderDataToGatewayObject(
|
|
66
59
|
{ order, orderPayment, pricing },
|
|
@@ -70,7 +63,41 @@ const Payrexx: IPaymentAdapter = {
|
|
|
70
63
|
return JSON.stringify(gateway);
|
|
71
64
|
}
|
|
72
65
|
|
|
73
|
-
|
|
66
|
+
// Payment Credential Registration signing
|
|
67
|
+
const user = await modules.users.findUserById(userId);
|
|
68
|
+
const emailAddress = modules.users.primaryEmail(user)?.address;
|
|
69
|
+
const gateway = await api.createGateway(
|
|
70
|
+
mapUserToGatewayObject({
|
|
71
|
+
userId,
|
|
72
|
+
emailAddress,
|
|
73
|
+
currencyCode: 'CHF',
|
|
74
|
+
}),
|
|
75
|
+
);
|
|
76
|
+
return JSON.stringify(gateway);
|
|
77
|
+
},
|
|
78
|
+
|
|
79
|
+
// async validate(credentials) {
|
|
80
|
+
// if (!credentials.meta) return false;
|
|
81
|
+
// const { objectKey, currency } = credentials.meta;
|
|
82
|
+
// const result = await api().validate({
|
|
83
|
+
// refno: Buffer.from(`valid-${new Date().getTime()}`, 'hex').toString('base64'),
|
|
84
|
+
// currency,
|
|
85
|
+
// [objectKey]: JSON.parse(credentials.token),
|
|
86
|
+
// });
|
|
87
|
+
// return Boolean((result as ValidateResponseSuccess)?.transactionId);
|
|
88
|
+
// },
|
|
89
|
+
|
|
90
|
+
async register(transactionResponse) {
|
|
91
|
+
// const { transactionId } = transactionResponse;
|
|
92
|
+
console.log({ transactionResponse });
|
|
93
|
+
throw new Error('Not implemented');
|
|
94
|
+
// const result = (await api().status({
|
|
95
|
+
// transactionId,
|
|
96
|
+
// })) as StatusResponseSuccess;
|
|
97
|
+
// if (result.transactionId) {
|
|
98
|
+
// return parseRegistrationData(result);
|
|
99
|
+
// }
|
|
100
|
+
return null;
|
|
74
101
|
},
|
|
75
102
|
|
|
76
103
|
async confirm() {
|
|
@@ -88,16 +115,22 @@ const Payrexx: IPaymentAdapter = {
|
|
|
88
115
|
}
|
|
89
116
|
|
|
90
117
|
if (
|
|
91
|
-
gatewayObject.status === GatewayObjectStatus.authorized ||
|
|
118
|
+
// gatewayObject.status === GatewayObjectStatus.authorized || // authorized is only used for tokenization!
|
|
92
119
|
gatewayObject.status === GatewayObjectStatus.reserved
|
|
93
120
|
) {
|
|
94
121
|
const allTransactions = gatewayObject.invoices?.flatMap((invoice) => invoice.transactions);
|
|
95
122
|
await Promise.all(
|
|
96
|
-
allTransactions.map(async (transaction) =>
|
|
123
|
+
allTransactions.map(async (transaction) =>
|
|
124
|
+
api.chargePreAuthorized(transaction.id, { referenceId: '__IGNORE_WEBHOOK__' }),
|
|
125
|
+
),
|
|
97
126
|
);
|
|
98
127
|
return true;
|
|
99
128
|
}
|
|
100
|
-
|
|
129
|
+
|
|
130
|
+
if (gatewayObject.status === GatewayObjectStatus.confirmed) {
|
|
131
|
+
return true; // already confirmed, no need to confirm
|
|
132
|
+
}
|
|
133
|
+
return false; // some other status with refund or anything, can't confirm!
|
|
101
134
|
},
|
|
102
135
|
|
|
103
136
|
async cancel() {
|
|
@@ -120,7 +153,11 @@ const Payrexx: IPaymentAdapter = {
|
|
|
120
153
|
);
|
|
121
154
|
return true;
|
|
122
155
|
}
|
|
123
|
-
|
|
156
|
+
|
|
157
|
+
if (gatewayObject.status === GatewayObjectStatus.confirmed) {
|
|
158
|
+
return false; // already confirmed, can't cancel anymore
|
|
159
|
+
}
|
|
160
|
+
return true; // some other status with refund or anything, no need to cancel
|
|
124
161
|
},
|
|
125
162
|
|
|
126
163
|
charge: async ({ gatewayId, paymentCredentials }) => {
|
|
@@ -143,24 +180,21 @@ const Payrexx: IPaymentAdapter = {
|
|
|
143
180
|
const { currency, amount } = pricing.total({ useNetPrice: false });
|
|
144
181
|
|
|
145
182
|
if (
|
|
146
|
-
gatewayObject.
|
|
147
|
-
gatewayObject.
|
|
183
|
+
// gatewayObject.status === 'authorized' || // authorized is only used for tokenization!
|
|
184
|
+
gatewayObject.status === 'reserved' ||
|
|
185
|
+
gatewayObject.status === 'confirmed'
|
|
148
186
|
) {
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
187
|
+
try {
|
|
188
|
+
if (
|
|
189
|
+
gatewayObject.currency !== currency.toUpperCase() ||
|
|
190
|
+
gatewayObject.amount !== Math.round(amount)
|
|
191
|
+
) {
|
|
192
|
+
throw new Error('The price has changed since the intent has been created');
|
|
193
|
+
}
|
|
194
|
+
if (gatewayObject.referenceId !== orderPayment?._id) {
|
|
195
|
+
throw new Error('The order payment is different from the initiating intent');
|
|
196
|
+
}
|
|
154
197
|
|
|
155
|
-
switch (gatewayObject.status) {
|
|
156
|
-
case GatewayObjectStatus.waiting:
|
|
157
|
-
logger.verbose('Charge postponed because Gateway is pending', {
|
|
158
|
-
orderPaymentId: gatewayObject.referenceId,
|
|
159
|
-
});
|
|
160
|
-
return false;
|
|
161
|
-
case GatewayObjectStatus.authorized:
|
|
162
|
-
case GatewayObjectStatus.reserved:
|
|
163
|
-
case GatewayObjectStatus.confirmed:
|
|
164
198
|
// confirm will do the transition, to do a checkout those stati above are fine
|
|
165
199
|
logger.verbose(`Mark as charged, status is ${gatewayObject.status}`, {
|
|
166
200
|
orderPaymentId: gatewayObject.referenceId,
|
|
@@ -169,13 +203,23 @@ const Payrexx: IPaymentAdapter = {
|
|
|
169
203
|
transactionId: gatewayId,
|
|
170
204
|
gatewayObject,
|
|
171
205
|
};
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
206
|
+
} catch (e) {
|
|
207
|
+
if (gatewayObject.status === GatewayObjectStatus.reserved) {
|
|
208
|
+
// Cancel the reservation if it's reserved and the order has been manipulated
|
|
209
|
+
const allTransactions = gatewayObject.invoices?.flatMap((invoice) => invoice.transactions);
|
|
210
|
+
await Promise.all(
|
|
211
|
+
allTransactions.map(async (transaction) => api.deleteReservation(transaction.id)),
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
throw e;
|
|
215
|
+
}
|
|
178
216
|
}
|
|
217
|
+
|
|
218
|
+
logger.verbose('Charge not possible', {
|
|
219
|
+
orderPaymentId: gatewayObject.referenceId,
|
|
220
|
+
status: gatewayObject.status,
|
|
221
|
+
});
|
|
222
|
+
throw new Error(`Gateway Status ${gatewayObject.status} does not allow checkout`);
|
|
179
223
|
},
|
|
180
224
|
};
|
|
181
225
|
|