@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
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { createLogger } from '@unchainedshop/logger';
|
|
2
|
+
import { buildSignature } from "./buildSignature.js";
|
|
3
|
+
import { timingSafeStringEqual } from '@unchainedshop/utils';
|
|
4
|
+
const logger = createLogger('unchained:saferpay:handler');
|
|
5
|
+
export const saferpayHandler = async (request, response) => {
|
|
6
|
+
const resolvedContext = request.unchainedContext;
|
|
7
|
+
const { modules, services } = resolvedContext;
|
|
8
|
+
const { orderPaymentId, signature, transactionId } = request.query;
|
|
9
|
+
const isValidRequest = typeof orderPaymentId === 'string' &&
|
|
10
|
+
typeof signature === 'string' &&
|
|
11
|
+
typeof transactionId === 'string' &&
|
|
12
|
+
orderPaymentId &&
|
|
13
|
+
transactionId &&
|
|
14
|
+
signature;
|
|
15
|
+
if ((request.method !== 'GET' && request.method !== 'HEAD') || !isValidRequest) {
|
|
16
|
+
logger.warn(`unhandled http method ${request.method} or orderPaymentId missing in query`);
|
|
17
|
+
response.status(404).end();
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
try {
|
|
21
|
+
logger.info(`checkout with orderPaymentId: ${orderPaymentId}`);
|
|
22
|
+
const orderPayment = await modules.orders.payments.findOrderPayment({
|
|
23
|
+
orderPaymentId,
|
|
24
|
+
});
|
|
25
|
+
if (!orderPayment) {
|
|
26
|
+
throw new Error(`order payment not found with orderPaymentId: ${orderPaymentId}`);
|
|
27
|
+
}
|
|
28
|
+
const correctSignature = await buildSignature(transactionId, orderPaymentId);
|
|
29
|
+
if (!(await timingSafeStringEqual(correctSignature, signature))) {
|
|
30
|
+
throw new Error('Invalid signature');
|
|
31
|
+
}
|
|
32
|
+
const order = await services.orders.checkoutOrder(orderPayment.orderId, {
|
|
33
|
+
paymentContext: {
|
|
34
|
+
transactionId,
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
if (!order)
|
|
38
|
+
throw new Error(`Order with id ${orderPayment.orderId} not found`);
|
|
39
|
+
logger.info(`checkout successful`, {
|
|
40
|
+
orderPaymentId,
|
|
41
|
+
orderId: order._id,
|
|
42
|
+
});
|
|
43
|
+
response.status(200).send({
|
|
44
|
+
message: 'checkout successful',
|
|
45
|
+
orderPaymentId,
|
|
46
|
+
orderId: order._id,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
logger.error(error, {
|
|
51
|
+
orderPaymentId,
|
|
52
|
+
transactionId,
|
|
53
|
+
});
|
|
54
|
+
response.status(500).send({
|
|
55
|
+
message: error.message,
|
|
56
|
+
name: error.name,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { createLogger } from '@unchainedshop/logger';
|
|
2
|
+
import { buildSignature } from "./buildSignature.js";
|
|
3
|
+
import { timingSafeStringEqual } from '@unchainedshop/utils';
|
|
4
|
+
const logger = createLogger('unchained:saferpay:handler');
|
|
5
|
+
export const saferpayHandler = async (request, reply) => {
|
|
6
|
+
const resolvedContext = request.unchainedContext;
|
|
7
|
+
const { modules, services } = resolvedContext;
|
|
8
|
+
const { orderPaymentId, signature, transactionId } = request.query;
|
|
9
|
+
const isValidRequest = typeof orderPaymentId === 'string' &&
|
|
10
|
+
typeof signature === 'string' &&
|
|
11
|
+
typeof transactionId === 'string' &&
|
|
12
|
+
orderPaymentId &&
|
|
13
|
+
transactionId &&
|
|
14
|
+
signature;
|
|
15
|
+
if (!isValidRequest) {
|
|
16
|
+
logger.warn(`orderPaymentId missing in query`);
|
|
17
|
+
reply.status(404);
|
|
18
|
+
return reply.send();
|
|
19
|
+
}
|
|
20
|
+
try {
|
|
21
|
+
logger.info(`checkout with orderPaymentId: ${orderPaymentId}`);
|
|
22
|
+
const orderPayment = await modules.orders.payments.findOrderPayment({
|
|
23
|
+
orderPaymentId,
|
|
24
|
+
});
|
|
25
|
+
if (!orderPayment) {
|
|
26
|
+
throw new Error(`order payment not found with orderPaymentId: ${orderPaymentId}`);
|
|
27
|
+
}
|
|
28
|
+
const correctSignature = await buildSignature(transactionId, orderPaymentId);
|
|
29
|
+
if (!(await timingSafeStringEqual(correctSignature, signature))) {
|
|
30
|
+
throw new Error('Invalid signature');
|
|
31
|
+
}
|
|
32
|
+
const order = await services.orders.checkoutOrder(orderPayment.orderId, {
|
|
33
|
+
paymentContext: {
|
|
34
|
+
transactionId,
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
if (!order)
|
|
38
|
+
throw new Error(`Order with id ${orderPayment.orderId} not found`);
|
|
39
|
+
logger.info(`checkout successful`, {
|
|
40
|
+
orderPaymentId,
|
|
41
|
+
orderId: order._id,
|
|
42
|
+
});
|
|
43
|
+
reply.status(200);
|
|
44
|
+
return reply.send({
|
|
45
|
+
success: true,
|
|
46
|
+
orderPaymentId,
|
|
47
|
+
orderId: order._id,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
logger.error(error, {
|
|
52
|
+
orderPaymentId,
|
|
53
|
+
transactionId,
|
|
54
|
+
});
|
|
55
|
+
reply.status(500);
|
|
56
|
+
return reply.send({
|
|
57
|
+
success: false,
|
|
58
|
+
message: error.message,
|
|
59
|
+
name: error.name,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
};
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
export
|
|
4
|
-
export
|
|
5
|
-
export { type SaferpayTransactionsModule } from './module.ts';
|
|
1
|
+
export * from './adapter.ts';
|
|
2
|
+
import saferpayTransactionsModule, { type SaferpayTransactionsModule } from './module.ts';
|
|
3
|
+
export type { SaferpayTransactionsModule };
|
|
4
|
+
export default saferpayTransactionsModule;
|
|
@@ -1,29 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
-
const { SAFERPAY_WEBHOOK_PATH = '/payment/saferpay/webhook' } = process.env;
|
|
5
|
-
export const SaferpayPlugin = {
|
|
6
|
-
key: 'shop.unchained.payment.saferpay',
|
|
7
|
-
label: 'Wordline Saferpay Payment Plugin',
|
|
8
|
-
version: '1.0.0',
|
|
9
|
-
adapters: [WordlineSaferpay],
|
|
10
|
-
module: ({ db }) => ({
|
|
11
|
-
saferpayTransactions: saferpayModules.saferpayTransactions.configure({ db }),
|
|
12
|
-
}),
|
|
13
|
-
routes: [
|
|
14
|
-
{
|
|
15
|
-
path: SAFERPAY_WEBHOOK_PATH,
|
|
16
|
-
method: 'GET',
|
|
17
|
-
handler: saferpayWebhookHandler,
|
|
18
|
-
},
|
|
19
|
-
],
|
|
20
|
-
onRegister: () => {
|
|
21
|
-
const { SAFERPAY_CUSTOMER_ID, SAFERPAY_TERMINAL_ID, SAFERPAY_API_USER, SAFERPAY_API_PASSWORD } = process.env;
|
|
22
|
-
if (!SAFERPAY_CUSTOMER_ID || !SAFERPAY_TERMINAL_ID || !SAFERPAY_API_USER || !SAFERPAY_API_PASSWORD) {
|
|
23
|
-
throw new Error('Saferpay environment variables not fully configured');
|
|
24
|
-
}
|
|
25
|
-
},
|
|
26
|
-
};
|
|
27
|
-
export default SaferpayPlugin;
|
|
28
|
-
export { WordlineSaferpay } from "./adapter.js";
|
|
29
|
-
export {} from "./module.js";
|
|
1
|
+
export * from "./adapter.js";
|
|
2
|
+
import saferpayTransactionsModule, {} from "./module.js";
|
|
3
|
+
export default saferpayTransactionsModule;
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { createLogger } from '@unchainedshop/logger';
|
|
2
|
+
import { stripe } from "./stripe.js";
|
|
3
|
+
const logger = createLogger('unchained:stripe:handler');
|
|
4
|
+
export const WebhookEventTypes = {
|
|
5
|
+
PAYMENT_INTENT_SUCCEEDED: 'payment_intent.succeeded',
|
|
6
|
+
SETUP_INTENT_SUCCEEDED: 'setup_intent.succeeded',
|
|
7
|
+
};
|
|
8
|
+
export const stripeHandler = async (request, response) => {
|
|
9
|
+
const resolvedContext = request.unchainedContext;
|
|
10
|
+
const { modules, services } = resolvedContext;
|
|
11
|
+
let event;
|
|
12
|
+
try {
|
|
13
|
+
const sig = request.headers['stripe-signature'];
|
|
14
|
+
if (!sig) {
|
|
15
|
+
throw new Error('stripe-signature header was not provided for webhook');
|
|
16
|
+
}
|
|
17
|
+
if (!process.env.STRIPE_ENDPOINT_SECRET) {
|
|
18
|
+
throw new Error('env STRIPE_ENDPOINT_SECRET is required for webhook handling');
|
|
19
|
+
}
|
|
20
|
+
event = stripe.webhooks.constructEvent(request.body, sig, process.env.STRIPE_ENDPOINT_SECRET);
|
|
21
|
+
}
|
|
22
|
+
catch (err) {
|
|
23
|
+
logger.error(`Error constructing event: ${err.message}`);
|
|
24
|
+
response.status(400).send({
|
|
25
|
+
message: err.message,
|
|
26
|
+
name: err.name,
|
|
27
|
+
});
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
if (!Object.values(WebhookEventTypes).includes(event.type)) {
|
|
31
|
+
logger.info(`unhandled event type`, {
|
|
32
|
+
type: event.type,
|
|
33
|
+
});
|
|
34
|
+
response.status(200).send({
|
|
35
|
+
ignored: true,
|
|
36
|
+
message: `Unhandled event type: ${event.type}. Supported types: ${Object.values(WebhookEventTypes).join(', ')}`,
|
|
37
|
+
});
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
const environmentInMetadata = event.data?.object?.metadata?.environment || '';
|
|
41
|
+
const environmentInEnv = process.env.STRIPE_WEBHOOK_ENVIRONMENT || '';
|
|
42
|
+
if (environmentInMetadata !== environmentInEnv) {
|
|
43
|
+
logger.info(`unhandled event environment`, {
|
|
44
|
+
type: event.type,
|
|
45
|
+
environment: environmentInMetadata,
|
|
46
|
+
});
|
|
47
|
+
response.status(200).send({
|
|
48
|
+
ignored: true,
|
|
49
|
+
message: `Unhandled event environment: ${environmentInMetadata}. Supported environment: ${environmentInEnv}`,
|
|
50
|
+
});
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
logger.info(`Processing event`, {
|
|
54
|
+
type: event.type,
|
|
55
|
+
});
|
|
56
|
+
try {
|
|
57
|
+
if (event.type === WebhookEventTypes.PAYMENT_INTENT_SUCCEEDED) {
|
|
58
|
+
const paymentIntent = event.data.object;
|
|
59
|
+
const { orderPaymentId } = paymentIntent.metadata || {};
|
|
60
|
+
logger.info(`checkout with orderPaymentId: ${orderPaymentId}`, {
|
|
61
|
+
type: event.type,
|
|
62
|
+
});
|
|
63
|
+
await modules.orders.payments.logEvent(orderPaymentId, event);
|
|
64
|
+
const orderPayment = await modules.orders.payments.findOrderPayment({
|
|
65
|
+
orderPaymentId,
|
|
66
|
+
});
|
|
67
|
+
if (!orderPayment) {
|
|
68
|
+
throw new Error(`order payment not found with orderPaymentId: ${orderPaymentId}`);
|
|
69
|
+
}
|
|
70
|
+
const order = await services.orders.checkoutOrder(orderPayment.orderId, {
|
|
71
|
+
paymentContext: {
|
|
72
|
+
paymentIntentId: paymentIntent.id,
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
if (!order)
|
|
76
|
+
throw new Error(`Order with id ${orderPayment.orderId} not found`);
|
|
77
|
+
logger.info(`checkout successful`, {
|
|
78
|
+
orderPaymentId,
|
|
79
|
+
orderId: order._id,
|
|
80
|
+
type: event.type,
|
|
81
|
+
});
|
|
82
|
+
response.status(200).send({
|
|
83
|
+
message: 'checkout successful',
|
|
84
|
+
orderId: order._id,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
else if (event.type === WebhookEventTypes.SETUP_INTENT_SUCCEEDED) {
|
|
88
|
+
const setupIntent = event.data.object;
|
|
89
|
+
const { paymentProviderId, userId } = setupIntent.metadata || {};
|
|
90
|
+
logger.info(`registered payment credential with paymentProviderId: ${paymentProviderId}`, {
|
|
91
|
+
type: event.type,
|
|
92
|
+
userId,
|
|
93
|
+
});
|
|
94
|
+
const paymentCredentials = await services.orders.registerPaymentCredentials(paymentProviderId, {
|
|
95
|
+
transactionContext: {
|
|
96
|
+
setupIntentId: setupIntent.id,
|
|
97
|
+
},
|
|
98
|
+
userId,
|
|
99
|
+
});
|
|
100
|
+
logger.info(`payment credentials registration successful`, {
|
|
101
|
+
userId,
|
|
102
|
+
paymentProviderId,
|
|
103
|
+
paymentCredentialsId: paymentCredentials?._id,
|
|
104
|
+
type: event.type,
|
|
105
|
+
});
|
|
106
|
+
response.status(200).send({
|
|
107
|
+
message: 'payment credentials registration successful',
|
|
108
|
+
paymentCredentialsId: paymentCredentials?._id,
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
catch (error) {
|
|
113
|
+
logger.error(error, {
|
|
114
|
+
type: event.type,
|
|
115
|
+
});
|
|
116
|
+
response.status(500).send({
|
|
117
|
+
message: error.message,
|
|
118
|
+
name: error.name,
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
};
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { createLogger } from '@unchainedshop/logger';
|
|
2
|
+
import { stripe } from "./stripe.js";
|
|
3
|
+
const logger = createLogger('unchained:stripe:handler');
|
|
4
|
+
export const WebhookEventTypes = {
|
|
5
|
+
PAYMENT_INTENT_SUCCEEDED: 'payment_intent.succeeded',
|
|
6
|
+
SETUP_INTENT_SUCCEEDED: 'setup_intent.succeeded',
|
|
7
|
+
};
|
|
8
|
+
export const stripeHandler = async (req, reply) => {
|
|
9
|
+
const resolvedContext = req.unchainedContext;
|
|
10
|
+
const { modules, services } = resolvedContext;
|
|
11
|
+
let event;
|
|
12
|
+
try {
|
|
13
|
+
const sig = req.headers['stripe-signature'];
|
|
14
|
+
if (!process.env.STRIPE_ENDPOINT_SECRET) {
|
|
15
|
+
throw new Error('env STRIPE_ENDPOINT_SECRET is required for webhook handling');
|
|
16
|
+
}
|
|
17
|
+
if (!sig) {
|
|
18
|
+
throw new Error('stripe-signature header was not provided for webhook');
|
|
19
|
+
}
|
|
20
|
+
event = stripe.webhooks.constructEvent(req.body, sig, process.env.STRIPE_ENDPOINT_SECRET);
|
|
21
|
+
}
|
|
22
|
+
catch (err) {
|
|
23
|
+
logger.error(`Error constructing event: ${err.message}`);
|
|
24
|
+
reply.status(400);
|
|
25
|
+
return reply.send({
|
|
26
|
+
success: false,
|
|
27
|
+
message: err.message,
|
|
28
|
+
name: err.name,
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
if (!Object.values(WebhookEventTypes).includes(event.type)) {
|
|
32
|
+
logger.info(`unhandled event type`, {
|
|
33
|
+
type: event.type,
|
|
34
|
+
});
|
|
35
|
+
reply.status(200);
|
|
36
|
+
return reply.send({
|
|
37
|
+
success: false,
|
|
38
|
+
ignored: true,
|
|
39
|
+
name: 'UNHANDLED_EVENT_TYPE',
|
|
40
|
+
message: `Unhandled event type: ${event.type}. Supported types: ${Object.values(WebhookEventTypes).join(', ')}`,
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
const environmentInMetadata = event.data?.object?.metadata?.environment || '';
|
|
44
|
+
const environmentInEnv = process.env.STRIPE_WEBHOOK_ENVIRONMENT || '';
|
|
45
|
+
if (environmentInMetadata !== environmentInEnv) {
|
|
46
|
+
logger.info(`unhandled event environment`, {
|
|
47
|
+
type: event.type,
|
|
48
|
+
environment: environmentInMetadata,
|
|
49
|
+
});
|
|
50
|
+
reply.status(200);
|
|
51
|
+
return reply.send({
|
|
52
|
+
success: false,
|
|
53
|
+
ignored: true,
|
|
54
|
+
name: 'UNHANDLED_EVENT_ENVIRONMENT',
|
|
55
|
+
message: `Unhandled event environment: ${environmentInMetadata}. Supported environment: ${environmentInEnv}`,
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
logger.info(`Processing event`, {
|
|
59
|
+
type: event.type,
|
|
60
|
+
});
|
|
61
|
+
try {
|
|
62
|
+
if (event.type === WebhookEventTypes.PAYMENT_INTENT_SUCCEEDED) {
|
|
63
|
+
const paymentIntent = event.data.object;
|
|
64
|
+
const { orderPaymentId } = paymentIntent.metadata || {};
|
|
65
|
+
logger.info(`checkout with orderPaymentId: ${orderPaymentId}`, {
|
|
66
|
+
type: event.type,
|
|
67
|
+
});
|
|
68
|
+
await modules.orders.payments.logEvent(orderPaymentId, event);
|
|
69
|
+
const orderPayment = await modules.orders.payments.findOrderPayment({
|
|
70
|
+
orderPaymentId,
|
|
71
|
+
});
|
|
72
|
+
if (!orderPayment) {
|
|
73
|
+
throw new Error(`order payment not found with orderPaymentId: ${orderPaymentId}`);
|
|
74
|
+
}
|
|
75
|
+
const order = await services.orders.checkoutOrder(orderPayment.orderId, {
|
|
76
|
+
paymentContext: {
|
|
77
|
+
paymentIntentId: paymentIntent.id,
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
if (!order)
|
|
81
|
+
throw new Error(`Order with id ${orderPayment.orderId} not found`);
|
|
82
|
+
logger.info(`checkout successful`, {
|
|
83
|
+
orderPaymentId,
|
|
84
|
+
orderId: order._id,
|
|
85
|
+
type: event.type,
|
|
86
|
+
});
|
|
87
|
+
reply.status(200);
|
|
88
|
+
return reply.send({
|
|
89
|
+
success: true,
|
|
90
|
+
orderId: order._id,
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
else if (event.type === WebhookEventTypes.SETUP_INTENT_SUCCEEDED) {
|
|
94
|
+
const setupIntent = event.data.object;
|
|
95
|
+
const { paymentProviderId, userId } = setupIntent.metadata || {};
|
|
96
|
+
logger.info(`registered payment credential with paymentProviderId: ${paymentProviderId}`, {
|
|
97
|
+
type: event.type,
|
|
98
|
+
userId,
|
|
99
|
+
});
|
|
100
|
+
const paymentCredentials = await services.orders.registerPaymentCredentials(paymentProviderId, {
|
|
101
|
+
transactionContext: {
|
|
102
|
+
setupIntentId: setupIntent.id,
|
|
103
|
+
},
|
|
104
|
+
userId,
|
|
105
|
+
});
|
|
106
|
+
logger.info(`payment credentials registration successful`, {
|
|
107
|
+
userId,
|
|
108
|
+
paymentProviderId,
|
|
109
|
+
paymentCredentialsId: paymentCredentials?._id,
|
|
110
|
+
type: event.type,
|
|
111
|
+
});
|
|
112
|
+
reply.status(200);
|
|
113
|
+
return reply.send({
|
|
114
|
+
success: true,
|
|
115
|
+
message: 'payment credentials registration successful',
|
|
116
|
+
paymentCredentialsId: paymentCredentials?._id,
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
catch (error) {
|
|
121
|
+
logger.error(error, {
|
|
122
|
+
type: event.type,
|
|
123
|
+
});
|
|
124
|
+
reply.status(500);
|
|
125
|
+
return reply.send({
|
|
126
|
+
success: false,
|
|
127
|
+
message: error.message,
|
|
128
|
+
name: error.name,
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
};
|
|
@@ -1,28 +1,122 @@
|
|
|
1
1
|
import { createLogger } from '@unchainedshop/logger';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
const { STRIPE_WEBHOOK_PATH = '/payment/stripe/webhook' } = process.env;
|
|
2
|
+
import { stripe, createOrderPaymentIntent, createRegistrationIntent } from "./stripe.js";
|
|
3
|
+
import { OrderPricingSheet, PaymentAdapter, PaymentDirector, PaymentError, } from '@unchainedshop/core';
|
|
5
4
|
const logger = createLogger('unchained:stripe');
|
|
6
|
-
|
|
5
|
+
const Stripe = {
|
|
6
|
+
...PaymentAdapter,
|
|
7
7
|
key: 'shop.unchained.payment.stripe',
|
|
8
|
-
label: 'Stripe
|
|
8
|
+
label: 'Stripe',
|
|
9
9
|
version: '2.0.0',
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
10
|
+
typeSupported(type) {
|
|
11
|
+
return type === 'GENERIC';
|
|
12
|
+
},
|
|
13
|
+
actions: (config, context) => {
|
|
14
|
+
const { modules } = context;
|
|
15
|
+
const descriptorPrefix = config.find(({ key }) => key === 'descriptorPrefix')?.value || '';
|
|
16
|
+
const assertUserData = async (forcedUserId) => {
|
|
17
|
+
const userId = forcedUserId || context?.userId;
|
|
18
|
+
const user = await modules.users.findUserById(userId);
|
|
19
|
+
if (!user)
|
|
20
|
+
throw new Error('User not found');
|
|
21
|
+
const email = modules.users.primaryEmail(user)?.address;
|
|
22
|
+
const name = user.profile?.displayName || user.username || email;
|
|
23
|
+
return {
|
|
24
|
+
email,
|
|
25
|
+
name,
|
|
26
|
+
userId,
|
|
27
|
+
};
|
|
28
|
+
};
|
|
29
|
+
const adapterActions = {
|
|
30
|
+
...PaymentAdapter.actions(config, context),
|
|
31
|
+
configurationError() {
|
|
32
|
+
if (!stripe)
|
|
33
|
+
return PaymentError.INCOMPLETE_CONFIGURATION;
|
|
34
|
+
return null;
|
|
35
|
+
},
|
|
36
|
+
isActive: () => {
|
|
37
|
+
if (adapterActions.configurationError() === null)
|
|
38
|
+
return true;
|
|
39
|
+
return false;
|
|
40
|
+
},
|
|
41
|
+
isPayLaterAllowed() {
|
|
42
|
+
return false;
|
|
43
|
+
},
|
|
44
|
+
validate: async ({ token }) => {
|
|
45
|
+
const paymentMethod = await stripe.paymentMethods.retrieve(token);
|
|
46
|
+
return !!paymentMethod;
|
|
47
|
+
},
|
|
48
|
+
register: async ({ setupIntentId }) => {
|
|
49
|
+
if (!setupIntentId) {
|
|
50
|
+
throw new Error('You have to provide a setupIntentId');
|
|
51
|
+
}
|
|
52
|
+
const setupIntent = await stripe.setupIntents.retrieve(setupIntentId);
|
|
53
|
+
if (setupIntent.status === 'succeeded') {
|
|
54
|
+
return {
|
|
55
|
+
token: setupIntent.payment_method,
|
|
56
|
+
customer: setupIntent.customer,
|
|
57
|
+
payment_method_types: setupIntent.payment_method_types,
|
|
58
|
+
usage: setupIntent.usage,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
logger.warn('Registration declined', setupIntentId);
|
|
62
|
+
return null;
|
|
63
|
+
},
|
|
64
|
+
sign: async (transactionContext = {}) => {
|
|
65
|
+
const { orderPayment, order, paymentProvider } = context;
|
|
66
|
+
const { userId, name, email } = await assertUserData(order?.userId);
|
|
67
|
+
if (orderPayment) {
|
|
68
|
+
if (!order)
|
|
69
|
+
throw new Error('order not found in context');
|
|
70
|
+
const pricing = OrderPricingSheet({
|
|
71
|
+
calculation: order?.calculation,
|
|
72
|
+
currencyCode: order?.currencyCode,
|
|
73
|
+
});
|
|
74
|
+
const paymentIntent = await createOrderPaymentIntent({ userId, name, email, order, orderPayment, pricing, descriptorPrefix }, transactionContext);
|
|
75
|
+
return paymentIntent.client_secret;
|
|
76
|
+
}
|
|
77
|
+
const paymentIntent = await createRegistrationIntent({ userId, name, email, paymentProviderId: paymentProvider._id, descriptorPrefix }, transactionContext);
|
|
78
|
+
return paymentIntent.client_secret;
|
|
79
|
+
},
|
|
80
|
+
charge: async ({ paymentIntentId, paymentCredentials }) => {
|
|
81
|
+
if (!paymentIntentId && !paymentCredentials) {
|
|
82
|
+
throw new Error('You have to provide paymentIntentId or paymentCredentials');
|
|
83
|
+
}
|
|
84
|
+
const { order, orderPayment } = context;
|
|
85
|
+
if (!order)
|
|
86
|
+
throw new Error('order not found in context');
|
|
87
|
+
if (!orderPayment)
|
|
88
|
+
throw new Error('orderPayment not found in context');
|
|
89
|
+
const { userId, name, email } = await assertUserData(order?.userId);
|
|
90
|
+
const pricing = OrderPricingSheet({
|
|
91
|
+
calculation: order.calculation,
|
|
92
|
+
currencyCode: order.currencyCode,
|
|
93
|
+
});
|
|
94
|
+
const paymentIntentObject = paymentIntentId
|
|
95
|
+
? await stripe.paymentIntents.retrieve(paymentIntentId)
|
|
96
|
+
: await createOrderPaymentIntent({ userId, name, email, orderPayment, order, pricing, descriptorPrefix }, {
|
|
97
|
+
customer: paymentCredentials.meta?.customer,
|
|
98
|
+
confirm: true,
|
|
99
|
+
payment_method: paymentCredentials.token,
|
|
100
|
+
payment_method_types: paymentCredentials.meta?.payment_method_types,
|
|
101
|
+
});
|
|
102
|
+
const { currencyCode, amount } = pricing.total({ useNetPrice: false });
|
|
103
|
+
if (paymentIntentObject.currency !== currencyCode.toLowerCase() ||
|
|
104
|
+
paymentIntentObject.amount !== Math.round(amount)) {
|
|
105
|
+
throw new Error('The price has changed since the intent has been created');
|
|
106
|
+
}
|
|
107
|
+
if (paymentIntentObject.metadata?.orderPaymentId !== orderPayment?._id) {
|
|
108
|
+
throw new Error('The order payment is different from the initiating intent');
|
|
109
|
+
}
|
|
110
|
+
if (paymentIntentObject.status === 'succeeded') {
|
|
111
|
+
return paymentIntentObject;
|
|
112
|
+
}
|
|
113
|
+
logger.info('Charge postponed because paymentIntent has wrong status', {
|
|
114
|
+
orderPaymentId: paymentIntentObject.id,
|
|
115
|
+
});
|
|
116
|
+
return false;
|
|
117
|
+
},
|
|
118
|
+
};
|
|
119
|
+
return adapterActions;
|
|
25
120
|
},
|
|
26
121
|
};
|
|
27
|
-
|
|
28
|
-
export { Stripe } from "./adapter.js";
|
|
122
|
+
PaymentDirector.registerAdapter(Stripe);
|
|
@@ -3,7 +3,10 @@ const logger = createLogger('unchained:stripe');
|
|
|
3
3
|
const { STRIPE_SECRET, STRIPE_WEBHOOK_ENVIRONMENT, EMAIL_WEBSITE_NAME } = process.env;
|
|
4
4
|
export let stripe;
|
|
5
5
|
const environment = STRIPE_WEBHOOK_ENVIRONMENT ?? null;
|
|
6
|
-
if (STRIPE_SECRET) {
|
|
6
|
+
if (!STRIPE_SECRET) {
|
|
7
|
+
logger.warn('STRIPE_SECRET is not set, skipping initialization');
|
|
8
|
+
}
|
|
9
|
+
else {
|
|
7
10
|
try {
|
|
8
11
|
const { default: Stripe } = await import('stripe');
|
|
9
12
|
stripe = new Stripe(STRIPE_SECRET, {
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import express from 'express';
|
|
2
|
+
import connectCryptoToExpress from "./crypto-express.js";
|
|
3
|
+
import connectBaseToExpress from "./base-express.js";
|
|
4
|
+
import { datatransHandler } from "../payment/datatrans-v2/handler-express.js";
|
|
5
|
+
import { stripeHandler } from "../payment/stripe/handler-express.js";
|
|
6
|
+
import { postfinanceCheckoutHandler } from "../payment/postfinance-checkout/handler-express.js";
|
|
7
|
+
import { appleIAPHandler } from "../payment/apple-iap/handler-express.js";
|
|
8
|
+
import { payrexxHandler } from "../payment/payrexx/handler-express.js";
|
|
9
|
+
import { saferpayHandler } from "../payment/saferpay/handler-express.js";
|
|
10
|
+
import { configureGenerateOrderAutoscheduling } from "../worker/enrollment-order-generator.js";
|
|
11
|
+
const { STRIPE_WEBHOOK_PATH = '/payment/stripe', PAYREXX_WEBHOOK_PATH = '/payment/payrexx', PFCHECKOUT_WEBHOOK_PATH = '/payment/postfinance-checkout', DATATRANS_WEBHOOK_PATH = '/payment/datatrans/webhook', APPLE_IAP_WEBHOOK_PATH = '/payment/apple-iap', SAFERPAY_WEBHOOK_PATH = '/payment/saferpay/webhook', } = process.env;
|
|
12
|
+
export default (app, { unchainedAPI }) => {
|
|
13
|
+
connectBaseToExpress(app);
|
|
14
|
+
connectCryptoToExpress(app, unchainedAPI);
|
|
15
|
+
app.use(STRIPE_WEBHOOK_PATH, express.raw({ type: 'application/json' }), stripeHandler);
|
|
16
|
+
app.use(PFCHECKOUT_WEBHOOK_PATH, express.json(), postfinanceCheckoutHandler);
|
|
17
|
+
app.use(DATATRANS_WEBHOOK_PATH, express.text({
|
|
18
|
+
type: 'application/json',
|
|
19
|
+
}), datatransHandler);
|
|
20
|
+
app.use(APPLE_IAP_WEBHOOK_PATH, express.json({
|
|
21
|
+
strict: false,
|
|
22
|
+
}), appleIAPHandler);
|
|
23
|
+
app.use(PAYREXX_WEBHOOK_PATH, express.json({ type: 'application/json' }), payrexxHandler);
|
|
24
|
+
app.use(SAFERPAY_WEBHOOK_PATH, saferpayHandler);
|
|
25
|
+
configureGenerateOrderAutoscheduling();
|
|
26
|
+
};
|