@unchainedshop/plugins 2.6.1 → 2.7.1
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/payrexx/api/index.d.ts +35 -0
- package/lib/payment/payrexx/api/index.js +100 -0
- package/lib/payment/payrexx/api/index.js.map +1 -0
- package/lib/payment/payrexx/index.d.ts +1 -0
- package/lib/payment/payrexx/index.js +139 -0
- package/lib/payment/payrexx/index.js.map +1 -0
- package/lib/payment/payrexx/middleware.d.ts +1 -0
- package/lib/payment/payrexx/middleware.js +55 -0
- package/lib/payment/payrexx/middleware.js.map +1 -0
- package/lib/payment/payrexx/payrexx.d.ts +16 -0
- package/lib/payment/payrexx/payrexx.js +19 -0
- package/lib/payment/payrexx/payrexx.js.map +1 -0
- package/lib/payment/stripe/middleware.d.ts +4 -0
- package/lib/payment/stripe/middleware.js +54 -47
- package/lib/payment/stripe/middleware.js.map +1 -1
- package/lib/plugins-index.js +3 -1
- package/lib/plugins-index.js.map +1 -1
- package/lib/pricing/delivery-swiss-tax.js +3 -0
- package/lib/pricing/delivery-swiss-tax.js.map +1 -1
- package/lib/pricing/discount-100-off.d.ts +2 -1
- package/lib/pricing/discount-100-off.js.map +1 -1
- package/lib/pricing/discount-half-price-manual.d.ts +2 -1
- package/lib/pricing/discount-half-price-manual.js.map +1 -1
- package/lib/pricing/discount-half-price.d.ts +2 -1
- package/lib/pricing/discount-half-price.js.map +1 -1
- package/lib/pricing/order-discount.d.ts +2 -1
- package/lib/pricing/order-discount.js +2 -8
- package/lib/pricing/order-discount.js.map +1 -1
- package/lib/pricing/order-items-discount.js +4 -3
- package/lib/pricing/order-items-discount.js.map +1 -1
- package/lib/pricing/product-discount.js +35 -18
- package/lib/pricing/product-discount.js.map +1 -1
- package/lib/pricing/product-swiss-tax.js +3 -0
- package/lib/pricing/product-swiss-tax.js.map +1 -1
- package/package.json +22 -22
- package/src/payment/payrexx/api/index.ts +132 -0
- package/src/payment/payrexx/index.ts +184 -0
- package/src/payment/payrexx/middleware.ts +64 -0
- package/src/payment/payrexx/payrexx.ts +25 -0
- package/src/payment/payrexx/readme.md +4 -0
- package/src/payment/stripe/middleware.ts +58 -51
- package/src/plugins-index.ts +10 -0
- package/src/pricing/delivery-swiss-tax.ts +6 -1
- package/src/pricing/discount-100-off.ts +2 -1
- package/src/pricing/discount-half-price-manual.ts +2 -1
- package/src/pricing/discount-half-price.ts +2 -1
- package/src/pricing/order-discount.ts +4 -10
- package/src/pricing/order-items-discount.ts +9 -4
- package/src/pricing/product-discount.ts +48 -20
- package/src/pricing/product-swiss-tax.ts +6 -1
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { IPaymentAdapter } from '@unchainedshop/types/payments.js';
|
|
2
|
+
import { PaymentAdapter, PaymentDirector, PaymentError } from '@unchainedshop/core-payment';
|
|
3
|
+
import { createLogger } from '@unchainedshop/logger';
|
|
4
|
+
import { mapOrderDataToGatewayObject } from './payrexx.js';
|
|
5
|
+
import createPayrexxAPI, { GatewayObjectStatus } from './api/index.js';
|
|
6
|
+
|
|
7
|
+
export * from './middleware.js';
|
|
8
|
+
|
|
9
|
+
const logger = createLogger('unchained:core-payment:payrexx');
|
|
10
|
+
|
|
11
|
+
const Payrexx: IPaymentAdapter = {
|
|
12
|
+
...PaymentAdapter,
|
|
13
|
+
|
|
14
|
+
key: 'shop.unchained.payment.payrexx',
|
|
15
|
+
label: 'Payrexx',
|
|
16
|
+
version: '1.0.0',
|
|
17
|
+
|
|
18
|
+
typeSupported(type) {
|
|
19
|
+
return type === 'GENERIC';
|
|
20
|
+
},
|
|
21
|
+
|
|
22
|
+
actions: (params) => {
|
|
23
|
+
const { modules } = params.context;
|
|
24
|
+
|
|
25
|
+
const getInstance = () => {
|
|
26
|
+
return params.config.find((c) => c.key === 'instance')?.value;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const api = createPayrexxAPI(getInstance(), process.env.PAYREXX_SECRET);
|
|
30
|
+
|
|
31
|
+
const adapterActions = {
|
|
32
|
+
...PaymentAdapter.actions(params),
|
|
33
|
+
|
|
34
|
+
configurationError() {
|
|
35
|
+
if (!process.env.PAYREXX_SECRET) {
|
|
36
|
+
return PaymentError.INCOMPLETE_CONFIGURATION;
|
|
37
|
+
}
|
|
38
|
+
if (!getInstance()) {
|
|
39
|
+
return PaymentError.INCOMPLETE_CONFIGURATION;
|
|
40
|
+
}
|
|
41
|
+
return null;
|
|
42
|
+
},
|
|
43
|
+
|
|
44
|
+
isActive: () => {
|
|
45
|
+
if (adapterActions.configurationError() === null) return true;
|
|
46
|
+
return false;
|
|
47
|
+
},
|
|
48
|
+
|
|
49
|
+
isPayLaterAllowed() {
|
|
50
|
+
return false;
|
|
51
|
+
},
|
|
52
|
+
|
|
53
|
+
validate: async ({ token }) => {
|
|
54
|
+
throw new Error('Token Registration Flow not implemented yet');
|
|
55
|
+
},
|
|
56
|
+
|
|
57
|
+
register: async ({ setupIntentId }) => {
|
|
58
|
+
throw new Error('Token Registration Flow not implemented yet');
|
|
59
|
+
},
|
|
60
|
+
|
|
61
|
+
sign: async (transactionContext = {}) => {
|
|
62
|
+
const { orderPayment, order } = params.paymentContext;
|
|
63
|
+
if (orderPayment) {
|
|
64
|
+
const pricing = await modules.orders.pricingSheet(order);
|
|
65
|
+
const gatewayObject = mapOrderDataToGatewayObject(
|
|
66
|
+
{ order, orderPayment, pricing },
|
|
67
|
+
transactionContext,
|
|
68
|
+
);
|
|
69
|
+
const gateway = await api.createGateway(gatewayObject);
|
|
70
|
+
return JSON.stringify(gateway);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
throw new Error('Token Registration Flow not implemented yet');
|
|
74
|
+
},
|
|
75
|
+
|
|
76
|
+
async confirm() {
|
|
77
|
+
const { orderPayment } = params.paymentContext;
|
|
78
|
+
const { transactionId } = orderPayment;
|
|
79
|
+
|
|
80
|
+
if (!transactionId) {
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const gatewayObject = await api.getGateway(transactionId);
|
|
85
|
+
|
|
86
|
+
if (!gatewayObject) {
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (
|
|
91
|
+
gatewayObject.status === GatewayObjectStatus.authorized ||
|
|
92
|
+
gatewayObject.status === GatewayObjectStatus.reserved
|
|
93
|
+
) {
|
|
94
|
+
const allTransactions = gatewayObject.invoices?.flatMap((invoice) => invoice.transactions);
|
|
95
|
+
await Promise.all(
|
|
96
|
+
allTransactions.map(async (transaction) =>
|
|
97
|
+
api.chargePreAuthorized(transaction.id, { referenceId: orderPayment._id }),
|
|
98
|
+
),
|
|
99
|
+
);
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
102
|
+
return false;
|
|
103
|
+
},
|
|
104
|
+
|
|
105
|
+
async cancel() {
|
|
106
|
+
const { orderPayment } = params.paymentContext;
|
|
107
|
+
const { transactionId } = orderPayment;
|
|
108
|
+
if (!transactionId) {
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const gatewayObject = await api.getGateway(transactionId);
|
|
113
|
+
|
|
114
|
+
if (!gatewayObject) {
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (gatewayObject.status === GatewayObjectStatus.reserved) {
|
|
119
|
+
const allTransactions = gatewayObject.invoices?.flatMap((invoice) => invoice.transactions);
|
|
120
|
+
await Promise.all(
|
|
121
|
+
allTransactions.map(async (transaction) => api.deleteReservation(transaction.id)),
|
|
122
|
+
);
|
|
123
|
+
return true;
|
|
124
|
+
}
|
|
125
|
+
return false;
|
|
126
|
+
},
|
|
127
|
+
|
|
128
|
+
charge: async ({ gatewayId, paymentCredentials }) => {
|
|
129
|
+
if (!gatewayId && !paymentCredentials) {
|
|
130
|
+
throw new Error('You have to provide gatewayId or paymentCredentials');
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const { order } = params.paymentContext;
|
|
134
|
+
const orderPayment = await modules.orders.payments.findOrderPayment({
|
|
135
|
+
orderPaymentId: order.paymentId,
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
const gatewayObject = await api.getGateway(gatewayId);
|
|
139
|
+
|
|
140
|
+
const pricing = await modules.orders.pricingSheet(order);
|
|
141
|
+
const { currency, amount } = pricing.total({ useNetPrice: false });
|
|
142
|
+
|
|
143
|
+
if (
|
|
144
|
+
gatewayObject.currency !== currency.toUpperCase() ||
|
|
145
|
+
gatewayObject.amount !== Math.round(amount)
|
|
146
|
+
) {
|
|
147
|
+
throw new Error('The price has changed since the intent has been created');
|
|
148
|
+
}
|
|
149
|
+
if (gatewayObject.referenceId !== orderPayment?._id) {
|
|
150
|
+
throw new Error('The order payment is different from the initiating intent');
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
switch (gatewayObject.status) {
|
|
154
|
+
case GatewayObjectStatus.waiting:
|
|
155
|
+
logger.verbose('Charge postponed because Gateway is pending', {
|
|
156
|
+
orderPaymentId: gatewayObject.referenceId,
|
|
157
|
+
});
|
|
158
|
+
return false;
|
|
159
|
+
case GatewayObjectStatus.authorized:
|
|
160
|
+
case GatewayObjectStatus.reserved:
|
|
161
|
+
case GatewayObjectStatus.confirmed:
|
|
162
|
+
// confirm will do the transition, to do a checkout those stati above are fine
|
|
163
|
+
logger.verbose(`Mark as charged, status is ${gatewayObject.status}`, {
|
|
164
|
+
orderPaymentId: gatewayObject.referenceId,
|
|
165
|
+
});
|
|
166
|
+
return {
|
|
167
|
+
transactionId: gatewayId,
|
|
168
|
+
gatewayObject,
|
|
169
|
+
};
|
|
170
|
+
default:
|
|
171
|
+
logger.verbose('Charge not possible', {
|
|
172
|
+
orderPaymentId: gatewayObject.referenceId,
|
|
173
|
+
status: gatewayObject.status,
|
|
174
|
+
});
|
|
175
|
+
throw new Error(`Gateway Status ${gatewayObject.status} does not allow checkout`);
|
|
176
|
+
}
|
|
177
|
+
},
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
return adapterActions;
|
|
181
|
+
},
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
PaymentDirector.registerAdapter(Payrexx);
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { Context } from '@unchainedshop/types/api.js';
|
|
2
|
+
import { createLogger } from '@unchainedshop/logger';
|
|
3
|
+
|
|
4
|
+
const logger = createLogger('unchained:core-payment:payrexx:webhook');
|
|
5
|
+
|
|
6
|
+
export const payrexxHandler = async (request, response) => {
|
|
7
|
+
const resolvedContext = request.unchainedContext as Context;
|
|
8
|
+
const { modules } = resolvedContext;
|
|
9
|
+
|
|
10
|
+
const { transaction } = request.body;
|
|
11
|
+
|
|
12
|
+
if (!transaction) {
|
|
13
|
+
logger.verbose(`unhandled event type`, {
|
|
14
|
+
type: Object.keys(request.body).join(','),
|
|
15
|
+
});
|
|
16
|
+
response.writeHead(200);
|
|
17
|
+
response.end({
|
|
18
|
+
ignored: true,
|
|
19
|
+
message: `Unhandled event type: ${Object.keys(request.body).join(',')}. Supported type: transaction`,
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
logger.verbose(`Processing event`, {
|
|
24
|
+
transactionId: transaction.id,
|
|
25
|
+
});
|
|
26
|
+
try {
|
|
27
|
+
const { referenceId: orderPaymentId } = transaction;
|
|
28
|
+
|
|
29
|
+
logger.verbose(`checkout with orderPaymentId: ${orderPaymentId}`);
|
|
30
|
+
await modules.orders.payments.logEvent(orderPaymentId, {
|
|
31
|
+
transactionId: transaction.id,
|
|
32
|
+
});
|
|
33
|
+
const orderPayment = await modules.orders.payments.findOrderPayment({
|
|
34
|
+
orderPaymentId,
|
|
35
|
+
});
|
|
36
|
+
if (!orderPayment) {
|
|
37
|
+
throw new Error(`order payment not found with orderPaymentId: ${orderPaymentId}`);
|
|
38
|
+
}
|
|
39
|
+
const order = await modules.orders.checkout(
|
|
40
|
+
orderPayment.orderId,
|
|
41
|
+
{
|
|
42
|
+
paymentContext: {
|
|
43
|
+
transactionId: transaction.id,
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
resolvedContext,
|
|
47
|
+
);
|
|
48
|
+
logger.info(`checkout successful`, {
|
|
49
|
+
orderPaymentId,
|
|
50
|
+
orderId: order._id,
|
|
51
|
+
});
|
|
52
|
+
response.writeHead(200);
|
|
53
|
+
response.end({
|
|
54
|
+
message: 'checkout successful',
|
|
55
|
+
orderId: order._id,
|
|
56
|
+
});
|
|
57
|
+
} catch (error) {
|
|
58
|
+
logger.error(error, {
|
|
59
|
+
transactionId: transaction.id,
|
|
60
|
+
});
|
|
61
|
+
response.writeHead(500);
|
|
62
|
+
response.end(error.message);
|
|
63
|
+
}
|
|
64
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export const mapOrderDataToGatewayObject = ({ order, orderPayment, pricing }, options = {}) => {
|
|
2
|
+
const {
|
|
3
|
+
EMAIL_WEBSITE_NAME = 'Unchained',
|
|
4
|
+
EMAIL_WEBSITE_URL,
|
|
5
|
+
DATATRANS_SUCCESS_PATH = '/payrexx/success',
|
|
6
|
+
DATATRANS_ERROR_PATH = '/payrexx/error',
|
|
7
|
+
DATATRANS_CANCEL_PATH = '/payrexx/cancel',
|
|
8
|
+
} = process.env;
|
|
9
|
+
|
|
10
|
+
const { currency, amount } = pricing.total({ useNetPrice: false });
|
|
11
|
+
const gatewayObject = {
|
|
12
|
+
amount: Math.round(amount),
|
|
13
|
+
currency: currency.toUpperCase(),
|
|
14
|
+
purpose: encodeURIComponent(`${EMAIL_WEBSITE_NAME} #${order._id}`),
|
|
15
|
+
reservation: true,
|
|
16
|
+
skipResultPage: true,
|
|
17
|
+
successRedirectUrl: `${EMAIL_WEBSITE_URL}${DATATRANS_SUCCESS_PATH}`,
|
|
18
|
+
failedRedirectUrl: `${EMAIL_WEBSITE_URL}${DATATRANS_ERROR_PATH}`,
|
|
19
|
+
cancelRedirectUrl: `${EMAIL_WEBSITE_URL}${DATATRANS_CANCEL_PATH}`,
|
|
20
|
+
referenceId: orderPayment._id,
|
|
21
|
+
'fields[email]': order.contact?.emailAddress,
|
|
22
|
+
...options,
|
|
23
|
+
};
|
|
24
|
+
return gatewayObject;
|
|
25
|
+
};
|
|
@@ -4,18 +4,10 @@ import stripe from './stripe.js';
|
|
|
4
4
|
|
|
5
5
|
const logger = createLogger('unchained:core-payment:stripe:webhook');
|
|
6
6
|
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
const environmentInEnv = STRIPE_WEBHOOK_ENVIRONMENT;
|
|
12
|
-
|
|
13
|
-
if (!environmentInMetadata && !environmentInEnv) {
|
|
14
|
-
return true;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
return environmentInEnv === environmentInMetadata;
|
|
18
|
-
}
|
|
7
|
+
export const WebhookEventTypes = {
|
|
8
|
+
PAYMENT_INTENT_SUCCEEDED: 'payment_intent.succeeded',
|
|
9
|
+
SETUP_INTENT_SUCCEEDED: 'setup_intent.succeeded',
|
|
10
|
+
};
|
|
19
11
|
|
|
20
12
|
export const stripeHandler = async (request, response) => {
|
|
21
13
|
const resolvedContext = request.unchainedContext as Context;
|
|
@@ -25,30 +17,50 @@ export const stripeHandler = async (request, response) => {
|
|
|
25
17
|
|
|
26
18
|
try {
|
|
27
19
|
const sig = request.headers['stripe-signature'];
|
|
28
|
-
event = stripe.webhooks.constructEvent(request.body, sig, STRIPE_ENDPOINT_SECRET);
|
|
29
|
-
logger.verbose(`received event`, {
|
|
30
|
-
type: event.type,
|
|
31
|
-
});
|
|
20
|
+
event = stripe.webhooks.constructEvent(request.body, sig, process.env.STRIPE_ENDPOINT_SECRET);
|
|
32
21
|
} catch (err) {
|
|
33
|
-
logger.error(`
|
|
22
|
+
logger.error(`Error constructing event: ${err.message}`);
|
|
34
23
|
response.writeHead(400);
|
|
35
24
|
response.end(err.message);
|
|
36
25
|
return;
|
|
37
26
|
}
|
|
38
27
|
|
|
28
|
+
if (!Object.values(WebhookEventTypes).find(event.type)) {
|
|
29
|
+
logger.verbose(`unhandled event type`, {
|
|
30
|
+
type: event.type,
|
|
31
|
+
});
|
|
32
|
+
response.writeHead(200);
|
|
33
|
+
response.end({
|
|
34
|
+
ignored: true,
|
|
35
|
+
message: `Unhandled event type: ${event.type}. Supported types: ${Object.values(WebhookEventTypes).join(', ')}`,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const environmentInMetadata = event.data?.object?.metadata?.environment || '';
|
|
40
|
+
const environmentInEnv = process.env.STRIPE_WEBHOOK_ENVIRONMENT || '';
|
|
41
|
+
if (environmentInMetadata !== environmentInEnv) {
|
|
42
|
+
logger.verbose(`unhandled event environment`, {
|
|
43
|
+
type: event.type,
|
|
44
|
+
environment: environmentInMetadata,
|
|
45
|
+
});
|
|
46
|
+
response.writeHead(200);
|
|
47
|
+
response.end(
|
|
48
|
+
JSON.stringify({
|
|
49
|
+
ignored: true,
|
|
50
|
+
message: `Unhandled event environment: ${environmentInMetadata}. Supported environment: ${environmentInEnv}`,
|
|
51
|
+
}),
|
|
52
|
+
);
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
logger.verbose(`Processing event`, {
|
|
57
|
+
type: event.type,
|
|
58
|
+
});
|
|
39
59
|
try {
|
|
40
|
-
if (event.type ===
|
|
60
|
+
if (event.type === WebhookEventTypes.PAYMENT_INTENT_SUCCEEDED) {
|
|
41
61
|
const paymentIntent = event.data.object;
|
|
42
62
|
const { orderPaymentId } = paymentIntent.metadata || {};
|
|
43
63
|
|
|
44
|
-
if (!checkEnvironment(paymentIntent.metadata)) {
|
|
45
|
-
logger.verbose(`event ignored because of environment difference`, {
|
|
46
|
-
type: event.type,
|
|
47
|
-
});
|
|
48
|
-
response.end(JSON.stringify({ received: true, ignored: true }));
|
|
49
|
-
return;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
64
|
logger.verbose(`checkout with orderPaymentId: ${orderPaymentId}`, {
|
|
53
65
|
type: event.type,
|
|
54
66
|
});
|
|
@@ -75,25 +87,26 @@ export const stripeHandler = async (request, response) => {
|
|
|
75
87
|
resolvedContext,
|
|
76
88
|
);
|
|
77
89
|
|
|
78
|
-
logger.info(`
|
|
90
|
+
logger.info(`checkout successful`, {
|
|
91
|
+
orderPaymentId,
|
|
79
92
|
orderId: order._id,
|
|
80
93
|
type: event.type,
|
|
81
94
|
});
|
|
82
|
-
|
|
95
|
+
response.writeHead(200);
|
|
96
|
+
response.end({
|
|
97
|
+
message: 'checkout successful',
|
|
98
|
+
orderId: order._id,
|
|
99
|
+
});
|
|
100
|
+
} else if (event.type === WebhookEventTypes.SETUP_INTENT_SUCCEEDED) {
|
|
83
101
|
const setupIntent = event.data.object;
|
|
84
102
|
const { paymentProviderId, userId } = setupIntent.metadata || {};
|
|
85
103
|
|
|
86
|
-
if (!checkEnvironment(setupIntent.metadata)) {
|
|
87
|
-
response.end(JSON.stringify({ received: true, ignored: true }));
|
|
88
|
-
return;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
104
|
logger.verbose(`registered payment credential with paymentProviderId: ${paymentProviderId}`, {
|
|
92
105
|
type: event.type,
|
|
93
106
|
userId,
|
|
94
107
|
});
|
|
95
108
|
|
|
96
|
-
await modules.payment.registerCredentials(
|
|
109
|
+
const paymentCredentials = await modules.payment.registerCredentials(
|
|
97
110
|
paymentProviderId,
|
|
98
111
|
{
|
|
99
112
|
transactionContext: {
|
|
@@ -104,29 +117,23 @@ export const stripeHandler = async (request, response) => {
|
|
|
104
117
|
resolvedContext,
|
|
105
118
|
);
|
|
106
119
|
|
|
107
|
-
logger.info(`
|
|
120
|
+
logger.info(`payment credentials registration successful`, {
|
|
108
121
|
userId,
|
|
122
|
+
paymentProviderId,
|
|
123
|
+
paymentCredentialsId: paymentCredentials?._id,
|
|
109
124
|
type: event.type,
|
|
110
125
|
});
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
126
|
+
response.writeHead(200);
|
|
127
|
+
response.end({
|
|
128
|
+
message: 'payment credentials registration successful',
|
|
129
|
+
paymentCredentialsId: paymentCredentials?._id,
|
|
114
130
|
});
|
|
115
|
-
response.writeHead(404);
|
|
116
|
-
response.end();
|
|
117
|
-
return;
|
|
118
131
|
}
|
|
119
|
-
} catch (
|
|
120
|
-
logger.error(
|
|
132
|
+
} catch (error) {
|
|
133
|
+
logger.error(error, {
|
|
121
134
|
type: event.type,
|
|
122
135
|
});
|
|
123
|
-
response.writeHead(
|
|
124
|
-
response.end(
|
|
125
|
-
return;
|
|
136
|
+
response.writeHead(500);
|
|
137
|
+
response.end(error.message);
|
|
126
138
|
}
|
|
127
|
-
// Return a 200 response to acknowledge receipt of the event
|
|
128
|
-
logger.verbose(`event processed`, {
|
|
129
|
-
type: event.type,
|
|
130
|
-
});
|
|
131
|
-
response.end(JSON.stringify({ received: true }));
|
|
132
139
|
};
|
package/src/plugins-index.ts
CHANGED
|
@@ -19,6 +19,8 @@ import { appleIAPHandler, configureAppleTransactionsModule } from './payment/app
|
|
|
19
19
|
import { stripeHandler } from './payment/stripe/index.js';
|
|
20
20
|
import { postfinanceCheckoutHandler } from './payment/postfinance-checkout/index.js';
|
|
21
21
|
|
|
22
|
+
import { payrexxHandler } from './payment/payrexx/index.js';
|
|
23
|
+
|
|
22
24
|
// Warehousing
|
|
23
25
|
import './warehousing/store.js';
|
|
24
26
|
import './warehousing/eth-minter.js';
|
|
@@ -79,6 +81,7 @@ import './accounts/linkedin-oauth.js';
|
|
|
79
81
|
const {
|
|
80
82
|
CRYPTOPAY_WEBHOOK_PATH = '/payment/cryptopay',
|
|
81
83
|
STRIPE_WEBHOOK_PATH = '/payment/stripe',
|
|
84
|
+
PAYREXX_WEBHOOK_PATH = '/payment/payrexx',
|
|
82
85
|
PFCHECKOUT_WEBHOOK_PATH = '/payment/postfinance-checkout',
|
|
83
86
|
DATATRANS_WEBHOOK_PATH = '/payment/datatrans/webhook',
|
|
84
87
|
APPLE_IAP_WEBHOOK_PATH = '/payment/apple-iap',
|
|
@@ -146,6 +149,13 @@ export const connectDefaultPluginsToExpress4 = (
|
|
|
146
149
|
appleIAPHandler,
|
|
147
150
|
);
|
|
148
151
|
|
|
152
|
+
useMiddlewareWithCurrentContext(
|
|
153
|
+
app,
|
|
154
|
+
PAYREXX_WEBHOOK_PATH,
|
|
155
|
+
express.json({ type: 'application/json' }),
|
|
156
|
+
payrexxHandler,
|
|
157
|
+
);
|
|
158
|
+
|
|
149
159
|
// useMiddlewareWithCurrentContext(
|
|
150
160
|
// app,
|
|
151
161
|
// MINIO_PUT_SERVER_PATH,
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { DeliveryPricingAdapter, DeliveryPricingDirector } from '@unchainedshop/core-delivery';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
DeliveryPricingRowCategory,
|
|
4
|
+
IDeliveryPricingAdapter,
|
|
5
|
+
} from '@unchainedshop/types/delivery.pricing.js';
|
|
3
6
|
|
|
4
7
|
import { Order } from '@unchainedshop/types/orders.js';
|
|
5
8
|
import { DeliveryProvider } from '@unchainedshop/types/delivery.js';
|
|
@@ -62,6 +65,7 @@ export const DeliverySwissTax: IDeliveryPricingAdapter = {
|
|
|
62
65
|
pricingAdapter.resultSheet().addTax({
|
|
63
66
|
amount: taxAmount,
|
|
64
67
|
rate: taxRate,
|
|
68
|
+
baseCategory: DeliveryPricingRowCategory.Delivery,
|
|
65
69
|
meta: { adapter: DeliverySwissTax.key },
|
|
66
70
|
});
|
|
67
71
|
} else {
|
|
@@ -69,6 +73,7 @@ export const DeliverySwissTax: IDeliveryPricingAdapter = {
|
|
|
69
73
|
pricingAdapter.resultSheet().addTax({
|
|
70
74
|
amount: taxAmount,
|
|
71
75
|
rate: taxRate,
|
|
76
|
+
baseCategory: DeliveryPricingRowCategory.Delivery,
|
|
72
77
|
meta: { adapter: DeliverySwissTax.key },
|
|
73
78
|
});
|
|
74
79
|
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { IDiscountAdapter } from '@unchainedshop/types/discount.js';
|
|
2
2
|
import { OrderDiscountDirector, OrderDiscountAdapter } from '@unchainedshop/core-orders';
|
|
3
|
+
import { OrderDiscountConfiguration } from '@unchainedshop/core-orders/director/OrderDiscountConfiguration.js';
|
|
3
4
|
|
|
4
|
-
export const HundredOff: IDiscountAdapter = {
|
|
5
|
+
export const HundredOff: IDiscountAdapter<OrderDiscountConfiguration> = {
|
|
5
6
|
...OrderDiscountAdapter,
|
|
6
7
|
|
|
7
8
|
key: 'shop.unchained.discount.100-off',
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { IDiscountAdapter } from '@unchainedshop/types/discount.js';
|
|
2
2
|
import { OrderDiscountDirector, OrderDiscountAdapter } from '@unchainedshop/core-orders';
|
|
3
|
+
import { ProductDiscountConfiguration } from '@unchainedshop/core-products/director/ProductDiscountConfiguration.js';
|
|
3
4
|
|
|
4
|
-
export const HalfPriceManual: IDiscountAdapter = {
|
|
5
|
+
export const HalfPriceManual: IDiscountAdapter<ProductDiscountConfiguration> = {
|
|
5
6
|
...OrderDiscountAdapter,
|
|
6
7
|
|
|
7
8
|
key: 'shop.unchained.discount.half-price-manual',
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { IDiscountAdapter } from '@unchainedshop/types/discount.js';
|
|
2
2
|
import { OrderDiscountDirector, OrderDiscountAdapter } from '@unchainedshop/core-orders';
|
|
3
|
+
import { ProductDiscountConfiguration } from '@unchainedshop/core-products/director/ProductDiscountConfiguration.js';
|
|
3
4
|
|
|
4
|
-
export const HalfPrice: IDiscountAdapter = {
|
|
5
|
+
export const HalfPrice: IDiscountAdapter<ProductDiscountConfiguration> = {
|
|
5
6
|
...OrderDiscountAdapter,
|
|
6
7
|
|
|
7
8
|
key: 'shop.unchained.discount.half-price',
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { IOrderPricingAdapter, OrderPricingRowCategory } from '@unchainedshop/types/orders.pricing.js';
|
|
2
2
|
import { OrderPricingDirector, OrderPricingAdapter } from '@unchainedshop/core-orders';
|
|
3
3
|
import { calculation as calcUtils } from '@unchainedshop/utils';
|
|
4
|
+
import { OrderDiscountConfiguration } from '@unchainedshop/core-orders/director/OrderDiscountConfiguration.js';
|
|
4
5
|
|
|
5
|
-
export const OrderDiscount: IOrderPricingAdapter = {
|
|
6
|
+
export const OrderDiscount: IOrderPricingAdapter<OrderDiscountConfiguration> = {
|
|
6
7
|
...OrderPricingAdapter,
|
|
7
8
|
|
|
8
9
|
key: 'shop.unchained.pricing.order-discount',
|
|
@@ -22,9 +23,6 @@ export const OrderDiscount: IOrderPricingAdapter = {
|
|
|
22
23
|
...pricingAdapter,
|
|
23
24
|
|
|
24
25
|
calculate: async () => {
|
|
25
|
-
// discounts need to provide a *fixedRate*
|
|
26
|
-
// if you want to add percentual discounts,
|
|
27
|
-
// add it to the order item calculation
|
|
28
26
|
const totalAmountOfItems = params.calculationSheet.total({
|
|
29
27
|
category: OrderPricingRowCategory.Items,
|
|
30
28
|
useNetPrice: false,
|
|
@@ -61,10 +59,8 @@ export const OrderDiscount: IOrderPricingAdapter = {
|
|
|
61
59
|
|
|
62
60
|
params.discounts.forEach(({ configuration, discountId }) => {
|
|
63
61
|
// First, we deduce the discount from the items
|
|
64
|
-
let alreadyDeducted = 0;
|
|
65
|
-
|
|
66
62
|
const leftInDiscountToSplit = calcUtils.calculateAmountToSplit(
|
|
67
|
-
{ ...configuration
|
|
63
|
+
{ ...configuration },
|
|
68
64
|
totalAmountOfItems,
|
|
69
65
|
);
|
|
70
66
|
const [itemsDiscountAmount, itemsTaxAmount] = calcUtils.applyDiscountToMultipleShares(
|
|
@@ -72,11 +68,10 @@ export const OrderDiscount: IOrderPricingAdapter = {
|
|
|
72
68
|
Math.max(0, Math.min(amountLeft, leftInDiscountToSplit)),
|
|
73
69
|
);
|
|
74
70
|
amountLeft -= itemsDiscountAmount;
|
|
75
|
-
alreadyDeducted += itemsDiscountAmount;
|
|
76
71
|
|
|
77
72
|
// After the items, we deduct the remaining discount from payment & delivery fees
|
|
78
73
|
const leftInFeesToSplit = calcUtils.calculateAmountToSplit(
|
|
79
|
-
{ ...configuration
|
|
74
|
+
{ ...configuration },
|
|
80
75
|
totalAmountOfPaymentAndDelivery,
|
|
81
76
|
);
|
|
82
77
|
const [deliveryAndPaymentDiscountAmount, deliveryAndPaymentTaxAmount] =
|
|
@@ -85,7 +80,6 @@ export const OrderDiscount: IOrderPricingAdapter = {
|
|
|
85
80
|
Math.max(0, Math.min(amountLeft, leftInFeesToSplit)),
|
|
86
81
|
);
|
|
87
82
|
amountLeft -= deliveryAndPaymentDiscountAmount;
|
|
88
|
-
alreadyDeducted += itemsDiscountAmount;
|
|
89
83
|
|
|
90
84
|
const discountAmount = (itemsDiscountAmount + deliveryAndPaymentDiscountAmount) * -1;
|
|
91
85
|
const taxAmount = (itemsTaxAmount + deliveryAndPaymentTaxAmount) * -1;
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { IOrderPricingAdapter, OrderPricingRowCategory } from '@unchainedshop/types/orders.pricing.js';
|
|
2
2
|
import { OrderPricingDirector, OrderPricingAdapter } from '@unchainedshop/core-orders';
|
|
3
3
|
import { calculation as calcUtils } from '@unchainedshop/utils';
|
|
4
|
+
import { OrderDiscountConfiguration } from '@unchainedshop/core-orders/director/OrderDiscountConfiguration.js';
|
|
4
5
|
|
|
5
|
-
const OrderItemsDiscount: IOrderPricingAdapter = {
|
|
6
|
+
const OrderItemsDiscount: IOrderPricingAdapter<OrderDiscountConfiguration> = {
|
|
6
7
|
...OrderPricingAdapter,
|
|
7
8
|
|
|
8
9
|
key: 'shop.unchained.pricing.order-items-discount',
|
|
@@ -38,15 +39,19 @@ const OrderItemsDiscount: IOrderPricingAdapter = {
|
|
|
38
39
|
),
|
|
39
40
|
);
|
|
40
41
|
|
|
41
|
-
let
|
|
42
|
+
let amountLeft = totalAmountOfItems;
|
|
42
43
|
|
|
43
44
|
params.discounts.forEach(({ configuration, discountId }) => {
|
|
44
45
|
// First, we deduce the discount from the items
|
|
46
|
+
const leftInItemsToSplit = calcUtils.calculateAmountToSplit(
|
|
47
|
+
{ ...configuration },
|
|
48
|
+
totalAmountOfItems,
|
|
49
|
+
);
|
|
45
50
|
const [itemsDiscountAmount, itemsTaxAmount] = calcUtils.applyDiscountToMultipleShares(
|
|
46
51
|
itemShares,
|
|
47
|
-
|
|
52
|
+
Math.max(0, Math.min(amountLeft, leftInItemsToSplit)),
|
|
48
53
|
);
|
|
49
|
-
|
|
54
|
+
amountLeft -= itemsDiscountAmount;
|
|
50
55
|
|
|
51
56
|
const discountAmount = itemsDiscountAmount * -1;
|
|
52
57
|
const taxAmount = itemsTaxAmount * -1;
|