@unchainedshop/plugins 4.7.2 → 4.8.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/README.md +2 -0
- package/lib/delivery/post.d.ts +1 -0
- package/lib/delivery/post.js +23 -0
- package/lib/delivery/send-message.d.ts +1 -0
- package/lib/delivery/send-message.js +40 -0
- package/lib/delivery/stores.d.ts +1 -0
- package/lib/delivery/stores.js +43 -0
- package/lib/enrollments/licensed.d.ts +4 -0
- package/lib/enrollments/licensed.js +52 -0
- package/lib/events/node-event-emitter.d.ts +1 -3
- package/lib/events/node-event-emitter.js +3 -3
- package/lib/files/gridfs/adapter.d.ts +2 -2
- package/lib/files/gridfs/adapter.js +5 -2
- package/lib/files/gridfs/handler-express.d.ts +9 -0
- package/lib/files/gridfs/handler-express.js +110 -0
- package/lib/files/gridfs/handler-fastify.d.ts +3 -0
- package/lib/files/gridfs/handler-fastify.js +138 -0
- package/lib/files/gridfs/index.d.ts +4 -5
- package/lib/files/gridfs/index.js +3 -30
- package/lib/files/minio/handler-express.d.ts +2 -0
- package/lib/files/minio/handler-express.js +36 -0
- package/lib/files/minio/handler-fastify.d.ts +3 -0
- package/lib/files/minio/handler-fastify.js +40 -0
- package/lib/files/minio/index.d.ts +5 -4
- package/lib/files/minio/index.js +157 -18
- package/lib/filters/local-search.d.ts +3 -0
- package/lib/filters/local-search.js +66 -0
- package/lib/filters/strict-equal.d.ts +1 -0
- package/lib/filters/strict-equal.js +24 -0
- package/lib/payment/apple-iap/adapter.d.ts +1 -2
- package/lib/payment/apple-iap/adapter.js +3 -2
- package/lib/payment/apple-iap/handler-express.d.ts +2 -0
- package/lib/payment/apple-iap/handler-express.js +103 -0
- package/lib/payment/apple-iap/handler-fastify.d.ts +3 -0
- package/lib/payment/apple-iap/handler-fastify.js +102 -0
- package/lib/payment/apple-iap/index.d.ts +4 -6
- package/lib/payment/apple-iap/index.js +3 -28
- package/lib/payment/braintree.d.ts +1 -0
- package/lib/payment/braintree.js +138 -0
- package/lib/payment/cryptopay/handler-express.d.ts +2 -0
- package/lib/payment/cryptopay/handler-express.js +19 -0
- package/lib/payment/cryptopay/handler-fastify.d.ts +3 -0
- package/lib/payment/cryptopay/handler-fastify.js +17 -0
- package/lib/payment/cryptopay/index.d.ts +4 -5
- package/lib/payment/cryptopay/index.js +3 -31
- package/lib/payment/cryptopay/plugin.d.ts +3 -0
- package/lib/payment/cryptopay/plugin.js +159 -0
- package/lib/payment/datatrans-v2/handler-express.d.ts +1 -0
- package/lib/payment/datatrans-v2/handler-express.js +71 -0
- package/lib/payment/datatrans-v2/handler-fastify.d.ts +2 -0
- package/lib/payment/datatrans-v2/handler-fastify.js +71 -0
- package/lib/payment/datatrans-v2/index.d.ts +1 -4
- package/lib/payment/datatrans-v2/index.js +400 -17
- package/lib/payment/invoice-prepaid.d.ts +1 -0
- package/lib/payment/invoice-prepaid.js +30 -0
- package/lib/payment/invoice.d.ts +1 -0
- package/lib/payment/invoice.js +27 -0
- package/lib/payment/paypal-checkout.d.ts +1 -0
- package/lib/payment/paypal-checkout.js +87 -0
- package/lib/payment/payrexx/handler-express.d.ts +1 -0
- package/lib/payment/payrexx/handler-express.js +85 -0
- package/lib/payment/payrexx/handler-fastify.d.ts +2 -0
- package/lib/payment/payrexx/handler-fastify.js +91 -0
- package/lib/payment/payrexx/index.d.ts +1 -4
- package/lib/payment/payrexx/index.js +158 -19
- package/lib/payment/postfinance-checkout/handler-express.d.ts +1 -0
- package/lib/payment/postfinance-checkout/handler-express.js +37 -0
- package/lib/payment/postfinance-checkout/handler-fastify.d.ts +2 -0
- package/lib/payment/postfinance-checkout/handler-fastify.js +40 -0
- package/lib/payment/postfinance-checkout/index.d.ts +0 -5
- package/lib/payment/postfinance-checkout/index.js +186 -25
- package/lib/payment/saferpay/adapter.js +2 -1
- package/lib/payment/saferpay/handler-express.d.ts +1 -0
- package/lib/payment/saferpay/handler-express.js +59 -0
- package/lib/payment/saferpay/handler-fastify.d.ts +2 -0
- package/lib/payment/saferpay/handler-fastify.js +62 -0
- package/lib/payment/saferpay/index.d.ts +4 -5
- package/lib/payment/saferpay/index.js +3 -29
- package/lib/payment/stripe/handler-express.d.ts +5 -0
- package/lib/payment/stripe/handler-express.js +121 -0
- package/lib/payment/stripe/handler-fastify.d.ts +6 -0
- package/lib/payment/stripe/handler-fastify.js +131 -0
- package/lib/payment/stripe/index.d.ts +1 -4
- package/lib/payment/stripe/index.js +116 -22
- package/lib/payment/stripe/stripe.js +4 -1
- package/lib/presets/all-express.d.ts +6 -0
- package/lib/presets/all-express.js +26 -0
- package/lib/presets/all-fastify.d.ts +6 -0
- package/lib/presets/all-fastify.js +49 -0
- package/lib/presets/all.d.ts +70 -1
- package/lib/presets/all.js +27 -41
- package/lib/presets/base-express.d.ts +2 -0
- package/lib/presets/base-express.js +5 -0
- package/lib/presets/base-fastify.d.ts +3 -0
- package/lib/presets/base-fastify.js +16 -0
- package/lib/presets/base.d.ts +25 -1
- package/lib/presets/base.js +25 -56
- package/lib/presets/countries/ch.d.ts +2 -1
- package/lib/presets/countries/ch.js +2 -7
- package/lib/presets/crypto-express.d.ts +3 -0
- package/lib/presets/crypto-express.js +8 -0
- package/lib/presets/crypto-fastify.d.ts +3 -0
- package/lib/presets/crypto-fastify.js +11 -0
- package/lib/presets/crypto.d.ts +7 -3
- package/lib/presets/crypto.js +7 -18
- package/lib/pricing/delivery-swiss-tax.d.ts +2 -0
- package/lib/pricing/delivery-swiss-tax.js +62 -0
- package/lib/pricing/discount-100-off.d.ts +2 -0
- package/lib/pricing/discount-100-off.js +30 -0
- package/lib/pricing/discount-half-price-manual.d.ts +2 -0
- package/lib/pricing/discount-half-price-manual.js +30 -0
- package/lib/pricing/discount-half-price.d.ts +2 -0
- package/lib/pricing/discount-half-price.js +33 -0
- package/lib/pricing/free-delivery.d.ts +2 -0
- package/lib/pricing/free-delivery.js +27 -0
- package/lib/pricing/free-payment.d.ts +2 -0
- package/lib/pricing/free-payment.js +27 -0
- package/lib/pricing/order-delivery.d.ts +2 -0
- package/lib/pricing/order-delivery.js +33 -0
- package/lib/pricing/order-discount.d.ts +2 -0
- package/lib/pricing/order-discount.js +74 -0
- package/lib/pricing/order-items-discount.d.ts +2 -0
- package/lib/pricing/order-items-discount.js +50 -0
- package/lib/pricing/order-items.d.ts +2 -0
- package/lib/pricing/order-items.js +43 -0
- package/lib/pricing/order-payment.d.ts +2 -0
- package/lib/pricing/order-payment.js +33 -0
- package/lib/pricing/order-round.d.ts +10 -0
- package/lib/pricing/order-round.js +135 -0
- package/lib/pricing/product-catalog-price-options.d.ts +2 -0
- package/lib/pricing/product-catalog-price-options.js +51 -0
- package/lib/pricing/product-catalog-price.d.ts +2 -0
- package/lib/pricing/product-catalog-price.js +60 -0
- package/lib/pricing/product-discount.d.ts +2 -0
- package/lib/pricing/product-discount.js +69 -0
- package/lib/pricing/product-price-rateconversion.d.ts +2 -0
- package/lib/pricing/product-price-rateconversion.js +53 -0
- package/lib/pricing/product-round.d.ts +10 -0
- package/lib/pricing/product-round.js +44 -0
- package/lib/pricing/product-swiss-tax.d.ts +2 -0
- package/lib/pricing/product-swiss-tax.js +76 -0
- package/lib/quotations/manual.d.ts +1 -0
- package/lib/quotations/manual.js +20 -0
- package/lib/warehousing/eth-minter.d.ts +1 -0
- package/lib/warehousing/eth-minter.js +120 -0
- package/lib/warehousing/store.d.ts +1 -0
- package/lib/warehousing/store.js +34 -0
- package/lib/worker/budgetsms.d.ts +9 -0
- package/lib/worker/budgetsms.js +169 -0
- package/lib/worker/bulk-export.d.ts +2 -0
- package/lib/worker/bulk-export.js +64 -0
- package/lib/worker/bulk-import.d.ts +2 -0
- package/lib/worker/bulk-import.js +65 -0
- package/lib/worker/bulkgate.d.ts +1 -0
- package/lib/worker/bulkgate.js +91 -0
- package/lib/worker/email.d.ts +1 -0
- package/lib/worker/email.js +136 -0
- package/lib/worker/enrollment-order-generator.d.ts +1 -0
- package/lib/worker/enrollment-order-generator.js +77 -0
- package/lib/worker/error-notifications.d.ts +9 -0
- package/lib/worker/error-notifications.js +61 -0
- package/lib/worker/export-token.d.ts +4 -0
- package/lib/worker/export-token.js +26 -0
- package/lib/worker/external.d.ts +2 -0
- package/lib/worker/external.js +13 -0
- package/lib/worker/heartbeat.d.ts +1 -0
- package/lib/worker/heartbeat.js +26 -0
- package/lib/worker/http-request.d.ts +8 -0
- package/lib/worker/http-request.js +56 -0
- package/lib/worker/message.d.ts +10 -0
- package/lib/worker/message.js +42 -0
- package/lib/worker/push-notification.d.ts +1 -0
- package/lib/worker/push-notification.js +87 -0
- package/lib/worker/twilio.d.ts +1 -0
- package/lib/worker/twilio.js +59 -0
- package/lib/worker/update-coinbase-rates.d.ts +1 -0
- package/lib/worker/update-coinbase-rates.js +59 -0
- package/lib/worker/update-ecb-rates.d.ts +1 -0
- package/lib/worker/update-ecb-rates.js +92 -0
- package/lib/worker/update-token-ownership.d.ts +3 -0
- package/lib/worker/update-token-ownership.js +57 -0
- package/lib/worker/zombie-killer.d.ts +13 -0
- package/lib/worker/zombie-killer.js +91 -0
- package/package.json +9 -14
|
@@ -1,24 +1,407 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
import { createLogger } from '@unchainedshop/logger';
|
|
2
|
+
import createDatatransAPI from "./api/index.js";
|
|
3
|
+
import parseRegistrationData from "./parseRegistrationData.js";
|
|
4
|
+
import roundedAmountFromOrder from "./roundedAmountFromOrder.js";
|
|
5
|
+
import { PaymentAdapter, PaymentDirector, PaymentError, PaymentPricingRowCategory, PaymentPricingSheet, OrderPricingSheet, } from '@unchainedshop/core';
|
|
6
|
+
const logger = createLogger('unchained:datatrans');
|
|
7
|
+
const { DATATRANS_SECRET, DATATRANS_SIGN_KEY, DATATRANS_API_ENDPOINT = 'https://api.sandbox.datatrans.com', DATATRANS_MERCHANT_ID, } = process.env;
|
|
8
|
+
const newDatatransError = ({ code, message }) => {
|
|
9
|
+
const error = new Error(message);
|
|
10
|
+
error.name = `DATATRANS_${code}`;
|
|
11
|
+
return error;
|
|
12
|
+
};
|
|
13
|
+
const throwIfResponseError = (result) => {
|
|
14
|
+
if (result.error) {
|
|
15
|
+
const rawError = result.error;
|
|
16
|
+
throw newDatatransError(rawError);
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
const getMarketplaceSplits = async ({ order, orderPayment, config, }) => {
|
|
20
|
+
const pricingForOrderPayment = PaymentPricingSheet({
|
|
21
|
+
calculation: orderPayment.calculation,
|
|
22
|
+
currencyCode: order.currencyCode,
|
|
23
|
+
});
|
|
24
|
+
const pricing = OrderPricingSheet({
|
|
25
|
+
calculation: order.calculation,
|
|
26
|
+
currencyCode: order.currencyCode,
|
|
27
|
+
});
|
|
28
|
+
const { amount: total } = pricing.total({ useNetPrice: false });
|
|
29
|
+
return Promise.all(config
|
|
30
|
+
.filter((item) => item.key === 'marketplaceSplit')
|
|
31
|
+
.map((item) => {
|
|
32
|
+
const [subMerchantId, staticDiscountId, sharePercentage] = item.value || ''.split(';').map((f) => f.trim());
|
|
33
|
+
const { amount: discountSum } = pricingForOrderPayment.total({
|
|
34
|
+
category: PaymentPricingRowCategory.Discount,
|
|
35
|
+
discountId: staticDiscountId,
|
|
36
|
+
});
|
|
37
|
+
const shareFactor = sharePercentage ? parseInt(sharePercentage, 10) / 100 : 1;
|
|
38
|
+
const amount = Math.round(total * shareFactor);
|
|
39
|
+
const commission = Math.round(discountSum * -1 * shareFactor);
|
|
40
|
+
return {
|
|
41
|
+
subMerchantId,
|
|
42
|
+
amount,
|
|
43
|
+
commission,
|
|
44
|
+
};
|
|
45
|
+
}));
|
|
46
|
+
};
|
|
47
|
+
const Datatrans = {
|
|
48
|
+
...PaymentAdapter,
|
|
6
49
|
key: 'shop.unchained.datatrans',
|
|
7
|
-
label: 'Datatrans
|
|
50
|
+
label: 'Datatrans',
|
|
8
51
|
version: '2.0.0',
|
|
9
|
-
|
|
10
|
-
routes: [
|
|
52
|
+
initialConfiguration: [
|
|
11
53
|
{
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
handler: datatransWebhookHandler,
|
|
54
|
+
key: 'merchantId',
|
|
55
|
+
value: null,
|
|
15
56
|
},
|
|
16
57
|
],
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
58
|
+
typeSupported(type) {
|
|
59
|
+
return type === 'GENERIC';
|
|
60
|
+
},
|
|
61
|
+
actions: (config, context) => {
|
|
62
|
+
const getMerchantId = () => {
|
|
63
|
+
return config.find((item) => item.key === 'merchantId')?.value || DATATRANS_MERCHANT_ID;
|
|
64
|
+
};
|
|
65
|
+
const api = () => {
|
|
66
|
+
const merchantId = getMerchantId();
|
|
67
|
+
if (!DATATRANS_SECRET)
|
|
68
|
+
throw new Error('Credential DATATRANS_SECRET not Set');
|
|
69
|
+
if (!merchantId)
|
|
70
|
+
throw new Error('Credential Merchant ID not configured');
|
|
71
|
+
if (!DATATRANS_API_ENDPOINT)
|
|
72
|
+
throw new Error('Credential DATATRANS_API_ENDPOINT not Set');
|
|
73
|
+
return createDatatransAPI(DATATRANS_API_ENDPOINT, merchantId, DATATRANS_SECRET);
|
|
74
|
+
};
|
|
75
|
+
const shouldSettleInUnchained = () => {
|
|
76
|
+
return config.reduce((current, item) => {
|
|
77
|
+
if (item.key === 'settleInUnchained')
|
|
78
|
+
return Boolean(item.value);
|
|
79
|
+
return current;
|
|
80
|
+
}, true);
|
|
81
|
+
};
|
|
82
|
+
const authorize = async ({ paymentCredentials, ...arbitraryFields }) => {
|
|
83
|
+
const { order, orderPayment } = context;
|
|
84
|
+
if (!orderPayment)
|
|
85
|
+
throw new Error('Order Payment missing in context');
|
|
86
|
+
if (!order)
|
|
87
|
+
throw new Error('Order missing in context');
|
|
88
|
+
const refno = Buffer.from(orderPayment._id, 'hex').toString('base64');
|
|
89
|
+
const userId = order?.userId || context?.userId;
|
|
90
|
+
const refno2 = userId;
|
|
91
|
+
const { currencyCode, amount } = roundedAmountFromOrder(order);
|
|
92
|
+
const splits = await getMarketplaceSplits({
|
|
93
|
+
orderPayment,
|
|
94
|
+
order,
|
|
95
|
+
config,
|
|
96
|
+
});
|
|
97
|
+
const result = await api().authorize({
|
|
98
|
+
...arbitraryFields,
|
|
99
|
+
amount,
|
|
100
|
+
currency: currencyCode,
|
|
101
|
+
refno,
|
|
102
|
+
refno2,
|
|
103
|
+
autoSettle: false,
|
|
104
|
+
customer: {
|
|
105
|
+
id: userId,
|
|
106
|
+
},
|
|
107
|
+
marketplace: splits.length
|
|
108
|
+
? {
|
|
109
|
+
splits,
|
|
110
|
+
}
|
|
111
|
+
: undefined,
|
|
112
|
+
[paymentCredentials.meta.objectKey]: JSON.parse(paymentCredentials.token),
|
|
113
|
+
});
|
|
114
|
+
throwIfResponseError(result);
|
|
115
|
+
return result.transactionId;
|
|
116
|
+
};
|
|
117
|
+
const authorizeAuth = async ({ transactionId, refno, refno2, ...arbitraryFields }) => {
|
|
118
|
+
const { order } = context;
|
|
119
|
+
if (!order)
|
|
120
|
+
throw new Error('Order missing in context');
|
|
121
|
+
const { currencyCode, amount } = roundedAmountFromOrder(order);
|
|
122
|
+
const result = await api().authorizeAuthenticated({
|
|
123
|
+
...arbitraryFields,
|
|
124
|
+
transactionId,
|
|
125
|
+
amount,
|
|
126
|
+
currency: currencyCode,
|
|
127
|
+
refno,
|
|
128
|
+
refno2,
|
|
129
|
+
autoSettle: false,
|
|
130
|
+
});
|
|
131
|
+
throwIfResponseError(result);
|
|
132
|
+
return result.acquirerAuthorizationCode;
|
|
133
|
+
};
|
|
134
|
+
const isTransactionAmountValid = (transaction) => {
|
|
135
|
+
const { order } = context;
|
|
136
|
+
if (!order)
|
|
137
|
+
throw new Error('Order missing in context');
|
|
138
|
+
const { currencyCode, amount } = roundedAmountFromOrder(order);
|
|
139
|
+
if (transaction.currency !== currencyCode ||
|
|
140
|
+
transaction.detail.authorize?.amount !== amount) {
|
|
141
|
+
logger.info(`currency: ${transaction.currency} === ${currencyCode} => ${transaction.currency === currencyCode}, amount: ${transaction.detail.authorize?.amount} === ${amount} => ${transaction.detail.authorize?.amount === amount}`);
|
|
142
|
+
return false;
|
|
143
|
+
}
|
|
144
|
+
return true;
|
|
145
|
+
};
|
|
146
|
+
const checkIfTransactionAmountValid = (transactionId, transaction) => {
|
|
147
|
+
if (!isTransactionAmountValid(transaction)) {
|
|
148
|
+
logger.error(`Transaction declined / Transaction ID ${transactionId} because of amount/currency mismatch`);
|
|
149
|
+
throw newDatatransError({
|
|
150
|
+
code: `YOU_HAVE_TO_PAY_THE_FULL_AMOUNT_DUDE`,
|
|
151
|
+
message: 'Amount / Currency Mismatch with Cart',
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
const settle = async ({ transactionId, refno, refno2, extensions }) => {
|
|
156
|
+
const { order, orderPayment } = context;
|
|
157
|
+
if (!orderPayment)
|
|
158
|
+
throw new Error('Order Payment missing in context');
|
|
159
|
+
if (!order)
|
|
160
|
+
throw new Error('Order missing in context');
|
|
161
|
+
const { currencyCode, amount } = roundedAmountFromOrder(order);
|
|
162
|
+
const splits = await getMarketplaceSplits({
|
|
163
|
+
orderPayment,
|
|
164
|
+
order,
|
|
165
|
+
config,
|
|
166
|
+
});
|
|
167
|
+
const result = await api().settle({
|
|
168
|
+
transactionId,
|
|
169
|
+
amount,
|
|
170
|
+
refno,
|
|
171
|
+
refno2,
|
|
172
|
+
currency: currencyCode,
|
|
173
|
+
marketplace: splits.length
|
|
174
|
+
? {
|
|
175
|
+
splits,
|
|
176
|
+
}
|
|
177
|
+
: undefined,
|
|
178
|
+
extensions,
|
|
179
|
+
});
|
|
180
|
+
throwIfResponseError(result);
|
|
181
|
+
return result;
|
|
182
|
+
};
|
|
183
|
+
const cancel = async ({ transactionId, refno }) => {
|
|
184
|
+
const result = await api().cancel({
|
|
185
|
+
transactionId,
|
|
186
|
+
refno,
|
|
187
|
+
});
|
|
188
|
+
throwIfResponseError(result);
|
|
189
|
+
return result;
|
|
190
|
+
};
|
|
191
|
+
const adapterActions = {
|
|
192
|
+
...PaymentAdapter.actions(config, context),
|
|
193
|
+
configurationError() {
|
|
194
|
+
if (!getMerchantId() || !DATATRANS_SECRET || !DATATRANS_SIGN_KEY) {
|
|
195
|
+
return PaymentError.INCOMPLETE_CONFIGURATION;
|
|
196
|
+
}
|
|
197
|
+
return null;
|
|
198
|
+
},
|
|
199
|
+
isActive() {
|
|
200
|
+
if (adapterActions.configurationError() === null)
|
|
201
|
+
return true;
|
|
202
|
+
return false;
|
|
203
|
+
},
|
|
204
|
+
isPayLaterAllowed() {
|
|
205
|
+
return false;
|
|
206
|
+
},
|
|
207
|
+
async sign(transactionContext = {}) {
|
|
208
|
+
const { useSecureFields = false, ...arbitraryFields } = transactionContext || {};
|
|
209
|
+
const { orderPayment, paymentProvider, order } = context;
|
|
210
|
+
const refno = Buffer.from(orderPayment ? orderPayment._id : paymentProvider._id, 'hex').toString('base64');
|
|
211
|
+
const userId = order?.userId || context?.userId;
|
|
212
|
+
const refno2 = userId;
|
|
213
|
+
const price = order
|
|
214
|
+
? roundedAmountFromOrder(order)
|
|
215
|
+
: {};
|
|
216
|
+
if (useSecureFields) {
|
|
217
|
+
const result = await api().secureFields({
|
|
218
|
+
...arbitraryFields,
|
|
219
|
+
currency: price.currencyCode || 'CHF',
|
|
220
|
+
refno,
|
|
221
|
+
refno2,
|
|
222
|
+
customer: {
|
|
223
|
+
id: userId,
|
|
224
|
+
},
|
|
225
|
+
amount: price.amount,
|
|
226
|
+
});
|
|
227
|
+
throwIfResponseError(result);
|
|
228
|
+
return JSON.stringify(result);
|
|
229
|
+
}
|
|
230
|
+
const result = await api().init({
|
|
231
|
+
...arbitraryFields,
|
|
232
|
+
currency: price.currencyCode || 'CHF',
|
|
233
|
+
refno,
|
|
234
|
+
refno2,
|
|
235
|
+
customer: {
|
|
236
|
+
id: userId,
|
|
237
|
+
},
|
|
238
|
+
amount: price.amount,
|
|
239
|
+
});
|
|
240
|
+
throwIfResponseError(result);
|
|
241
|
+
return JSON.stringify(result);
|
|
242
|
+
},
|
|
243
|
+
async validate(credentials) {
|
|
244
|
+
if (!credentials.meta)
|
|
245
|
+
return false;
|
|
246
|
+
const { objectKey, currency } = credentials.meta;
|
|
247
|
+
const result = await api().validate({
|
|
248
|
+
refno: Buffer.from(`valid-${new Date().getTime()}`, 'hex').toString('base64'),
|
|
249
|
+
currency,
|
|
250
|
+
[objectKey]: JSON.parse(credentials.token),
|
|
251
|
+
});
|
|
252
|
+
return Boolean(result?.transactionId);
|
|
253
|
+
},
|
|
254
|
+
async register(transactionResponse) {
|
|
255
|
+
const { transactionId } = transactionResponse;
|
|
256
|
+
const result = (await api().status({
|
|
257
|
+
transactionId,
|
|
258
|
+
}));
|
|
259
|
+
if (result.transactionId) {
|
|
260
|
+
return parseRegistrationData(result);
|
|
261
|
+
}
|
|
262
|
+
return null;
|
|
263
|
+
},
|
|
264
|
+
async confirm() {
|
|
265
|
+
if (!shouldSettleInUnchained())
|
|
266
|
+
return false;
|
|
267
|
+
const { orderPayment, transactionContext } = context;
|
|
268
|
+
if (!orderPayment)
|
|
269
|
+
throw new Error('Order Payment missing in context');
|
|
270
|
+
const { transactionId } = orderPayment;
|
|
271
|
+
const { extensions } = transactionContext || {};
|
|
272
|
+
if (!transactionId) {
|
|
273
|
+
return false;
|
|
274
|
+
}
|
|
275
|
+
const transaction = (await api().status({
|
|
276
|
+
transactionId,
|
|
277
|
+
}));
|
|
278
|
+
throwIfResponseError(transaction);
|
|
279
|
+
const { status } = transaction;
|
|
280
|
+
if (status === 'authorized') {
|
|
281
|
+
await settle({
|
|
282
|
+
transactionId,
|
|
283
|
+
refno: transaction.refno,
|
|
284
|
+
refno2: transaction.refno2,
|
|
285
|
+
extensions,
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
return true;
|
|
289
|
+
},
|
|
290
|
+
async cancel() {
|
|
291
|
+
if (!shouldSettleInUnchained())
|
|
292
|
+
return false;
|
|
293
|
+
const { orderPayment } = context;
|
|
294
|
+
if (!orderPayment)
|
|
295
|
+
throw new Error('Order Payment missing in context');
|
|
296
|
+
const { transactionId } = orderPayment;
|
|
297
|
+
if (!transactionId) {
|
|
298
|
+
return false;
|
|
299
|
+
}
|
|
300
|
+
const transaction = (await api().status({
|
|
301
|
+
transactionId,
|
|
302
|
+
}));
|
|
303
|
+
throwIfResponseError(transaction);
|
|
304
|
+
const { status } = transaction;
|
|
305
|
+
if (status === 'authorized') {
|
|
306
|
+
await cancel({
|
|
307
|
+
transactionId,
|
|
308
|
+
refno: transaction.refno,
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
return true;
|
|
312
|
+
},
|
|
313
|
+
async charge({ transactionId: rawTransactionId, paymentCredentials, authorizeAuthenticated, ...arbitraryFields }) {
|
|
314
|
+
if (!rawTransactionId && !paymentCredentials) {
|
|
315
|
+
logger.warn('Not trying to charge because of missing payment authorization response, return false to provide later');
|
|
316
|
+
return false;
|
|
317
|
+
}
|
|
318
|
+
const transactionId = rawTransactionId ||
|
|
319
|
+
(await authorize({
|
|
320
|
+
...arbitraryFields,
|
|
321
|
+
paymentCredentials,
|
|
322
|
+
}));
|
|
323
|
+
const transaction = (await api().status({
|
|
324
|
+
transactionId,
|
|
325
|
+
}));
|
|
326
|
+
throwIfResponseError(transaction);
|
|
327
|
+
if (!transaction) {
|
|
328
|
+
throw newDatatransError({
|
|
329
|
+
code: `TRANSACTION_NOT_FOUND`,
|
|
330
|
+
message: 'Amount / Currency Mismatch with Cart',
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
let { status } = transaction;
|
|
334
|
+
if (status === 'canceled' || status === 'failed') {
|
|
335
|
+
logger.error(`Payment declined or canceled with Transaction ID ${transactionId} and status ${status}`);
|
|
336
|
+
throw newDatatransError({
|
|
337
|
+
code: `STATUS_${status.toUpperCase()}`,
|
|
338
|
+
message: 'Payment declined or canceled',
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
if (status === 'authenticated') {
|
|
342
|
+
if (authorizeAuthenticated) {
|
|
343
|
+
checkIfTransactionAmountValid(transactionId, transaction);
|
|
344
|
+
await authorizeAuth({
|
|
345
|
+
...(authorizeAuthenticated || {}),
|
|
346
|
+
transactionId,
|
|
347
|
+
refno: transaction.refno,
|
|
348
|
+
refno2: transaction.refno2,
|
|
349
|
+
});
|
|
350
|
+
status = 'authorized';
|
|
351
|
+
}
|
|
352
|
+
else {
|
|
353
|
+
logger.error(`Transaction declined / Transaction ID ${transactionId} not authorized yet`);
|
|
354
|
+
throw newDatatransError({
|
|
355
|
+
code: `STATUS_${status.toUpperCase()}`,
|
|
356
|
+
message: 'Payment authenticated but not authorized',
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
if (status === 'authorized' || status === 'settled') {
|
|
361
|
+
let settledTransaction = transaction;
|
|
362
|
+
let credentials;
|
|
363
|
+
try {
|
|
364
|
+
checkIfTransactionAmountValid(transactionId, settledTransaction);
|
|
365
|
+
credentials = await parseRegistrationData(settledTransaction);
|
|
366
|
+
const result = await api().status({
|
|
367
|
+
transactionId,
|
|
368
|
+
});
|
|
369
|
+
if (result?.error) {
|
|
370
|
+
settledTransaction = transaction;
|
|
371
|
+
}
|
|
372
|
+
else {
|
|
373
|
+
settledTransaction = result;
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
catch {
|
|
377
|
+
await cancel({ transactionId, refno: settledTransaction.refno });
|
|
378
|
+
logger.error(`Transaction declined / Transaction ID ${transactionId} authorization cancelled`);
|
|
379
|
+
throw newDatatransError({
|
|
380
|
+
code: `STATUS_${status.toUpperCase()}`,
|
|
381
|
+
message: 'Payment cancelled server-side because of amount missmatch',
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
return {
|
|
385
|
+
transactionId,
|
|
386
|
+
settledTransaction,
|
|
387
|
+
arbitraryFields,
|
|
388
|
+
credentials,
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
if (status === 'initialized' ||
|
|
392
|
+
status === 'challenge_required' ||
|
|
393
|
+
status === 'challenge_ongoing' ||
|
|
394
|
+
status === 'transmitted') {
|
|
395
|
+
logger.error(`Transaction ID ${transactionId} in transit with status ${status}`);
|
|
396
|
+
throw newDatatransError({
|
|
397
|
+
code: `STATUS_${status.toUpperCase()}`,
|
|
398
|
+
message: 'Transaction status invalid for checkout',
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
return false;
|
|
402
|
+
},
|
|
403
|
+
};
|
|
404
|
+
return adapterActions;
|
|
21
405
|
},
|
|
22
406
|
};
|
|
23
|
-
|
|
24
|
-
export { Datatrans } from "./adapter.js";
|
|
407
|
+
PaymentDirector.registerAdapter(Datatrans);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { PaymentAdapter, PaymentDirector } from '@unchainedshop/core';
|
|
2
|
+
import { PaymentProviderType } from '@unchainedshop/core-payment';
|
|
3
|
+
const InvoicePrepaid = {
|
|
4
|
+
...PaymentAdapter,
|
|
5
|
+
key: 'shop.unchained.invoice-prepaid',
|
|
6
|
+
label: 'Invoice pre-paid',
|
|
7
|
+
version: '1.0.0',
|
|
8
|
+
initialConfiguration: [],
|
|
9
|
+
typeSupported: (type) => {
|
|
10
|
+
return type === PaymentProviderType.INVOICE;
|
|
11
|
+
},
|
|
12
|
+
actions: (config, context) => {
|
|
13
|
+
return {
|
|
14
|
+
...PaymentAdapter.actions(config, context),
|
|
15
|
+
configurationError: () => {
|
|
16
|
+
return null;
|
|
17
|
+
},
|
|
18
|
+
isActive: () => {
|
|
19
|
+
return true;
|
|
20
|
+
},
|
|
21
|
+
isPayLaterAllowed: () => {
|
|
22
|
+
return false;
|
|
23
|
+
},
|
|
24
|
+
charge: async () => {
|
|
25
|
+
return false;
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
PaymentDirector.registerAdapter(InvoicePrepaid);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { PaymentAdapter, PaymentDirector } from '@unchainedshop/core';
|
|
2
|
+
import { PaymentProviderType } from '@unchainedshop/core-payment';
|
|
3
|
+
const Invoice = {
|
|
4
|
+
...PaymentAdapter,
|
|
5
|
+
key: 'shop.unchained.invoice',
|
|
6
|
+
label: 'Invoice',
|
|
7
|
+
version: '1.0.0',
|
|
8
|
+
initialConfiguration: [],
|
|
9
|
+
typeSupported: (type) => {
|
|
10
|
+
return type === PaymentProviderType.INVOICE;
|
|
11
|
+
},
|
|
12
|
+
actions: (config, context) => {
|
|
13
|
+
return {
|
|
14
|
+
...PaymentAdapter.actions(config, context),
|
|
15
|
+
configurationError: () => {
|
|
16
|
+
return null;
|
|
17
|
+
},
|
|
18
|
+
isActive: () => {
|
|
19
|
+
return true;
|
|
20
|
+
},
|
|
21
|
+
isPayLaterAllowed: () => {
|
|
22
|
+
return true;
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
PaymentDirector.registerAdapter(Invoice);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { OrderPricingSheet, PaymentAdapter, PaymentDirector, PaymentError, } from '@unchainedshop/core';
|
|
2
|
+
import { createLogger } from '@unchainedshop/logger';
|
|
3
|
+
const logger = createLogger('unchained:paypal-checkout');
|
|
4
|
+
let checkoutNodeJssdk;
|
|
5
|
+
try {
|
|
6
|
+
checkoutNodeJssdk = await import('@paypal/checkout-server-sdk');
|
|
7
|
+
}
|
|
8
|
+
catch {
|
|
9
|
+
logger.warn("npm dependency '@paypal/checkout-server-sdk' is not installed, paypal adapter will not work");
|
|
10
|
+
}
|
|
11
|
+
const { PAYPAL_CLIENT_ID, PAYPAL_SECRET, PAYPAL_ENVIRONMENT = 'sandbox' } = process.env;
|
|
12
|
+
const environment = () => {
|
|
13
|
+
const clientId = PAYPAL_CLIENT_ID;
|
|
14
|
+
const clientSecret = PAYPAL_SECRET;
|
|
15
|
+
return PAYPAL_ENVIRONMENT !== 'live'
|
|
16
|
+
? new checkoutNodeJssdk.core.SandboxEnvironment(clientId, clientSecret)
|
|
17
|
+
: new checkoutNodeJssdk.core.LiveEnvironment(clientId, clientSecret);
|
|
18
|
+
};
|
|
19
|
+
const roundToDecimals = (number, decimals) => {
|
|
20
|
+
const num = Math.pow(10, decimals);
|
|
21
|
+
return Math.round(number * num) / num;
|
|
22
|
+
};
|
|
23
|
+
const PaypalCheckout = {
|
|
24
|
+
...PaymentAdapter,
|
|
25
|
+
key: 'shop.unchained.payment.paypal',
|
|
26
|
+
label: 'Paypal',
|
|
27
|
+
version: '1.0.1',
|
|
28
|
+
initialConfiguration: [],
|
|
29
|
+
typeSupported: (type) => {
|
|
30
|
+
return type === 'GENERIC';
|
|
31
|
+
},
|
|
32
|
+
actions: (config, context) => {
|
|
33
|
+
const adapter = {
|
|
34
|
+
...PaymentAdapter.actions(config, context),
|
|
35
|
+
configurationError: () => {
|
|
36
|
+
const publicCredentialsValid = PAYPAL_CLIENT_ID && PAYPAL_SECRET;
|
|
37
|
+
if (!publicCredentialsValid) {
|
|
38
|
+
return PaymentError.WRONG_CREDENTIALS;
|
|
39
|
+
}
|
|
40
|
+
return null;
|
|
41
|
+
},
|
|
42
|
+
isActive: () => {
|
|
43
|
+
if (adapter.configurationError() === null)
|
|
44
|
+
return true;
|
|
45
|
+
return false;
|
|
46
|
+
},
|
|
47
|
+
isPayLaterAllowed: () => {
|
|
48
|
+
return false;
|
|
49
|
+
},
|
|
50
|
+
sign: async () => {
|
|
51
|
+
return PAYPAL_CLIENT_ID || null;
|
|
52
|
+
},
|
|
53
|
+
charge: async ({ orderID }) => {
|
|
54
|
+
const { order } = context;
|
|
55
|
+
if (!order)
|
|
56
|
+
throw new Error('Order missing in payment context');
|
|
57
|
+
if (!orderID) {
|
|
58
|
+
throw new Error('You have to provide orderID in paymentContext');
|
|
59
|
+
}
|
|
60
|
+
try {
|
|
61
|
+
const request = new checkoutNodeJssdk.orders.OrdersGetRequest(orderID);
|
|
62
|
+
const client = new checkoutNodeJssdk.core.PayPalHttpClient(environment());
|
|
63
|
+
const paypalOrder = await client.execute(request);
|
|
64
|
+
const pricing = OrderPricingSheet({
|
|
65
|
+
calculation: order.calculation,
|
|
66
|
+
currencyCode: order.currencyCode,
|
|
67
|
+
});
|
|
68
|
+
const ourTotal = roundToDecimals(pricing.total({ useNetPrice: false }).amount / 100, 2);
|
|
69
|
+
const paypalTotal = roundToDecimals(paypalOrder.result.purchase_units[0].amount.value, 2);
|
|
70
|
+
if (ourTotal === paypalTotal) {
|
|
71
|
+
return order;
|
|
72
|
+
}
|
|
73
|
+
logger.warn('Missmatch PAYPAL ORDER', JSON.stringify(paypalOrder.result, null, 2));
|
|
74
|
+
logger.debug('OUR ORDER', order);
|
|
75
|
+
logger.debug('OUR PRICE', pricing);
|
|
76
|
+
throw new Error(`Payment mismatch`);
|
|
77
|
+
}
|
|
78
|
+
catch (e) {
|
|
79
|
+
logger.warn(e);
|
|
80
|
+
throw new Error(e);
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
return adapter;
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
PaymentDirector.registerAdapter(PaypalCheckout);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const payrexxHandler: (request: any, response: any) => Promise<void>;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { createLogger } from '@unchainedshop/logger';
|
|
2
|
+
const logger = createLogger('unchained:payrexx:handler');
|
|
3
|
+
export const payrexxHandler = async (request, response) => {
|
|
4
|
+
const resolvedContext = request.unchainedContext;
|
|
5
|
+
const { modules, services } = resolvedContext;
|
|
6
|
+
const { transaction } = request.body;
|
|
7
|
+
if (!transaction) {
|
|
8
|
+
logger.info(`unhandled event type`, {
|
|
9
|
+
type: Object.keys(request.body).join(','),
|
|
10
|
+
});
|
|
11
|
+
response.status(200).send({
|
|
12
|
+
ignored: true,
|
|
13
|
+
message: `Unhandled event type: ${Object.keys(request.body).join(',')}. Supported type: transaction`,
|
|
14
|
+
});
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
if (transaction.referenceId === '__IGNORE_WEBHOOK__' || transaction.status === 'waiting') {
|
|
18
|
+
logger.info(`unhandled transaction state: ${transaction.status}`);
|
|
19
|
+
response.status(200).send({
|
|
20
|
+
ignored: true,
|
|
21
|
+
message: `Unhandled transaction state: ${transaction.status}`,
|
|
22
|
+
});
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
logger.info(`Processing event`, {
|
|
26
|
+
transactionId: transaction.id,
|
|
27
|
+
});
|
|
28
|
+
try {
|
|
29
|
+
if (transaction.preAuthorizationId) {
|
|
30
|
+
const { referenceId: paymentProviderId, invoice } = transaction;
|
|
31
|
+
const userId = '';
|
|
32
|
+
logger.info(`register credentials for: ${userId}`);
|
|
33
|
+
await services.orders.registerPaymentCredentials(paymentProviderId, {
|
|
34
|
+
userId,
|
|
35
|
+
transactionContext: { gatewayId: invoice.paymentRequestId },
|
|
36
|
+
});
|
|
37
|
+
logger.info(`registration successful`, {
|
|
38
|
+
paymentProviderId,
|
|
39
|
+
userId,
|
|
40
|
+
});
|
|
41
|
+
response.status(200).send({
|
|
42
|
+
message: 'registration successful',
|
|
43
|
+
paymentProviderId,
|
|
44
|
+
userId,
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
const { referenceId: orderPaymentId, invoice } = transaction;
|
|
49
|
+
logger.info(`checkout with orderPaymentId: ${orderPaymentId}`);
|
|
50
|
+
await modules.orders.payments.logEvent(orderPaymentId, {
|
|
51
|
+
transactionId: transaction.id,
|
|
52
|
+
});
|
|
53
|
+
const orderPayment = await modules.orders.payments.findOrderPayment({
|
|
54
|
+
orderPaymentId,
|
|
55
|
+
});
|
|
56
|
+
if (!orderPayment) {
|
|
57
|
+
throw new Error(`order payment not found with orderPaymentId: ${orderPaymentId}`);
|
|
58
|
+
}
|
|
59
|
+
const order = await services.orders.checkoutOrder(orderPayment.orderId, {
|
|
60
|
+
paymentContext: {
|
|
61
|
+
gatewayId: invoice.paymentRequestId,
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
if (!order)
|
|
65
|
+
throw new Error(`Order with id ${orderPayment.orderId} not found`);
|
|
66
|
+
logger.info(`checkout successful`, {
|
|
67
|
+
orderPaymentId,
|
|
68
|
+
orderId: order._id,
|
|
69
|
+
});
|
|
70
|
+
response.status(200).send({
|
|
71
|
+
message: 'checkout successful',
|
|
72
|
+
orderId: order._id,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
logger.error(error, {
|
|
78
|
+
transactionId: transaction.id,
|
|
79
|
+
});
|
|
80
|
+
response.status(500).send({
|
|
81
|
+
message: error.message,
|
|
82
|
+
name: error.name,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
};
|