@unchainedshop/plugins 2.8.7 → 2.8.10

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.
@@ -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
- successRedirectUrl: `${EMAIL_WEBSITE_URL}${DATATRANS_SUCCESS_PATH}`,
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,CAAC,MAAM,2BAA2B,GAAG,CAAC,EAAE,KAAK,EAAE,YAAY,EAAE,OAAO,EAAE,EAAE,OAAO,GAAG,EAAE,EAAE,EAAE;IAC5F,MAAM,EACJ,kBAAkB,GAAG,WAAW,EAChC,iBAAiB,EACjB,sBAAsB,GAAG,kBAAkB,EAC3C,oBAAoB,GAAG,gBAAgB,EACvC,qBAAqB,GAAG,iBAAiB,GAC1C,GAAG,OAAO,CAAC,GAAG,CAAC;IAEhB,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,kBAAkB,KAAK,KAAK,CAAC,GAAG,EAAE,CAAC;QAClE,0CAA0C;QAC1C,WAAW,EAAE,IAAI;QACjB,cAAc,EAAE,IAAI;QACpB,kBAAkB,EAAE,GAAG,iBAAiB,GAAG,sBAAsB,EAAE;QACnE,iBAAiB,EAAE,GAAG,iBAAiB,GAAG,oBAAoB,EAAE;QAChE,iBAAiB,EAAE,GAAG,iBAAiB,GAAG,qBAAqB,EAAE;QACjE,WAAW,EAAE,YAAY,CAAC,GAAG;QAC7B,GAAG,YAAY;QACf,GAAG,OAAO;KACX,CAAC;IACF,OAAO,aAAa,CAAC;AACvB,CAAC,CAAC"}
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.7",
3
+ "version": "2.8.10",
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.20.0",
83
+ "stripe": "^14.24.0",
84
84
  "tiny-secp256k1": "^2.2.3",
85
- "twilio": "^4.23.0",
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.11.25",
91
- "@unchainedshop/api": "^2.8.5",
92
- "@unchainedshop/core-delivery": "^2.8.5",
93
- "@unchainedshop/core-enrollments": "^2.8.5",
94
- "@unchainedshop/core-filters": "^2.8.5",
95
- "@unchainedshop/core-messaging": "^2.8.5",
96
- "@unchainedshop/core-orders": "^2.8.5",
97
- "@unchainedshop/core-payment": "^2.8.5",
98
- "@unchainedshop/core-products": "^2.8.5",
99
- "@unchainedshop/core-quotations": "^2.8.5",
100
- "@unchainedshop/core-warehousing": "^2.8.5",
101
- "@unchainedshop/core-worker": "^2.8.5",
102
- "@unchainedshop/events": "^2.8.5",
103
- "@unchainedshop/file-upload": "^2.8.5",
104
- "@unchainedshop/logger": "^2.8.5",
105
- "@unchainedshop/types": "^2.8.5",
106
- "@unchainedshop/utils": "^2.8.5",
90
+ "@types/node": "^20.12.5",
91
+ "@unchainedshop/api": "^2.8.10",
92
+ "@unchainedshop/core-delivery": "^2.8.10",
93
+ "@unchainedshop/core-enrollments": "^2.8.10",
94
+ "@unchainedshop/core-filters": "^2.8.10",
95
+ "@unchainedshop/core-messaging": "^2.8.10",
96
+ "@unchainedshop/core-orders": "^2.8.10",
97
+ "@unchainedshop/core-payment": "^2.8.10",
98
+ "@unchainedshop/core-products": "^2.8.10",
99
+ "@unchainedshop/core-quotations": "^2.8.10",
100
+ "@unchainedshop/core-warehousing": "^2.8.10",
101
+ "@unchainedshop/core-worker": "^2.8.10",
102
+ "@unchainedshop/events": "^2.8.10",
103
+ "@unchainedshop/file-upload": "^2.8.10",
104
+ "@unchainedshop/logger": "^2.8.10",
105
+ "@unchainedshop/types": "^2.8.10",
106
+ "@unchainedshop/utils": "^2.8.10",
107
107
  "bluebird": "^3.7.2",
108
108
  "event-iterator": "^2.0.0",
109
- "express": "^4.18.3",
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.11",
116
- "open": "^10.0.4",
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.3.3",
120
+ "typescript": "^5.4.4",
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
- { ...resolvedContext },
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
- async buildSignature(query = '') {
34
- const key = await crypto.subtle.importKey(
35
- 'raw',
36
- new TextEncoder().encode(secret),
37
- { name: 'HMAC', hash: 'SHA-256' },
38
- false,
39
- ['sign', 'verify'],
40
- );
41
- const signature = await crypto.subtle.sign('HMAC', key, new TextEncoder().encode(query));
42
- return btoa(String.fromCharCode(...new Uint8Array(signature)));
43
- },
44
-
45
- buildUrl(path, params = {}) {
46
- const url = new URL(path, baseUrl);
47
- url.search = new URLSearchParams({ instance, ...params }).toString();
48
- return url.href;
49
- },
50
-
51
- async chargePreAuthorized(id, params) {
52
- const queryParams = { ...params };
53
- const signature = await this.buildSignature(new URLSearchParams(queryParams).toString());
54
- queryParams.ApiSignature = signature;
55
-
56
- const url = this.buildUrl(`Transaction/${id}/`);
57
-
58
- const result = await fetch(url, {
59
- method: 'POST',
60
- body: new URLSearchParams(queryParams).toString(),
61
- headers: {
62
- accept: 'application/json',
63
- 'content-type': 'application/x-www-form-urlencoded',
64
- },
65
- });
66
-
67
- if (result.ok) {
68
- return result.json();
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
- throw new Error('Token Registration Flow not implemented yet');
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) => api.chargePreAuthorized(transaction.id, {})),
123
+ allTransactions.map(async (transaction) =>
124
+ api.chargePreAuthorized(transaction.id, { referenceId: '__IGNORE_WEBHOOK__' }),
125
+ ),
97
126
  );
98
127
  return true;
99
128
  }
100
- return false;
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
- return false;
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.currency !== currency.toUpperCase() ||
147
- gatewayObject.amount !== Math.round(amount)
183
+ // gatewayObject.status === 'authorized' || // authorized is only used for tokenization!
184
+ gatewayObject.status === 'reserved' ||
185
+ gatewayObject.status === 'confirmed'
148
186
  ) {
149
- throw new Error('The price has changed since the intent has been created');
150
- }
151
- if (gatewayObject.referenceId !== orderPayment?._id) {
152
- throw new Error('The order payment is different from the initiating intent');
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
- default:
173
- logger.verbose('Charge not possible', {
174
- orderPaymentId: gatewayObject.referenceId,
175
- status: gatewayObject.status,
176
- });
177
- throw new Error(`Gateway Status ${gatewayObject.status} does not allow checkout`);
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