@unchainedshop/plugins 4.7.2 → 4.8.0

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.
Files changed (184) hide show
  1. package/README.md +2 -0
  2. package/lib/delivery/post.d.ts +1 -0
  3. package/lib/delivery/post.js +23 -0
  4. package/lib/delivery/send-message.d.ts +1 -0
  5. package/lib/delivery/send-message.js +40 -0
  6. package/lib/delivery/stores.d.ts +1 -0
  7. package/lib/delivery/stores.js +43 -0
  8. package/lib/enrollments/licensed.d.ts +4 -0
  9. package/lib/enrollments/licensed.js +52 -0
  10. package/lib/events/node-event-emitter.d.ts +1 -3
  11. package/lib/events/node-event-emitter.js +3 -3
  12. package/lib/files/gridfs/adapter.d.ts +2 -2
  13. package/lib/files/gridfs/adapter.js +5 -2
  14. package/lib/files/gridfs/handler-express.d.ts +9 -0
  15. package/lib/files/gridfs/handler-express.js +110 -0
  16. package/lib/files/gridfs/handler-fastify.d.ts +3 -0
  17. package/lib/files/gridfs/handler-fastify.js +138 -0
  18. package/lib/files/gridfs/index.d.ts +4 -5
  19. package/lib/files/gridfs/index.js +3 -30
  20. package/lib/files/minio/handler-express.d.ts +2 -0
  21. package/lib/files/minio/handler-express.js +36 -0
  22. package/lib/files/minio/handler-fastify.d.ts +3 -0
  23. package/lib/files/minio/handler-fastify.js +40 -0
  24. package/lib/files/minio/index.d.ts +5 -4
  25. package/lib/files/minio/index.js +157 -18
  26. package/lib/filters/local-search.d.ts +3 -0
  27. package/lib/filters/local-search.js +66 -0
  28. package/lib/filters/strict-equal.d.ts +1 -0
  29. package/lib/filters/strict-equal.js +24 -0
  30. package/lib/payment/apple-iap/adapter.d.ts +1 -2
  31. package/lib/payment/apple-iap/adapter.js +3 -2
  32. package/lib/payment/apple-iap/handler-express.d.ts +2 -0
  33. package/lib/payment/apple-iap/handler-express.js +103 -0
  34. package/lib/payment/apple-iap/handler-fastify.d.ts +3 -0
  35. package/lib/payment/apple-iap/handler-fastify.js +102 -0
  36. package/lib/payment/apple-iap/index.d.ts +4 -6
  37. package/lib/payment/apple-iap/index.js +3 -28
  38. package/lib/payment/braintree.d.ts +1 -0
  39. package/lib/payment/braintree.js +138 -0
  40. package/lib/payment/cryptopay/handler-express.d.ts +2 -0
  41. package/lib/payment/cryptopay/handler-express.js +19 -0
  42. package/lib/payment/cryptopay/handler-fastify.d.ts +3 -0
  43. package/lib/payment/cryptopay/handler-fastify.js +17 -0
  44. package/lib/payment/cryptopay/index.d.ts +4 -5
  45. package/lib/payment/cryptopay/index.js +3 -31
  46. package/lib/payment/cryptopay/plugin.d.ts +3 -0
  47. package/lib/payment/cryptopay/plugin.js +159 -0
  48. package/lib/payment/datatrans-v2/handler-express.d.ts +1 -0
  49. package/lib/payment/datatrans-v2/handler-express.js +71 -0
  50. package/lib/payment/datatrans-v2/handler-fastify.d.ts +2 -0
  51. package/lib/payment/datatrans-v2/handler-fastify.js +71 -0
  52. package/lib/payment/datatrans-v2/index.d.ts +1 -4
  53. package/lib/payment/datatrans-v2/index.js +400 -17
  54. package/lib/payment/invoice-prepaid.d.ts +1 -0
  55. package/lib/payment/invoice-prepaid.js +30 -0
  56. package/lib/payment/invoice.d.ts +1 -0
  57. package/lib/payment/invoice.js +27 -0
  58. package/lib/payment/paypal-checkout.d.ts +1 -0
  59. package/lib/payment/paypal-checkout.js +87 -0
  60. package/lib/payment/payrexx/handler-express.d.ts +1 -0
  61. package/lib/payment/payrexx/handler-express.js +85 -0
  62. package/lib/payment/payrexx/handler-fastify.d.ts +2 -0
  63. package/lib/payment/payrexx/handler-fastify.js +91 -0
  64. package/lib/payment/payrexx/index.d.ts +1 -4
  65. package/lib/payment/payrexx/index.js +158 -19
  66. package/lib/payment/postfinance-checkout/handler-express.d.ts +1 -0
  67. package/lib/payment/postfinance-checkout/handler-express.js +37 -0
  68. package/lib/payment/postfinance-checkout/handler-fastify.d.ts +2 -0
  69. package/lib/payment/postfinance-checkout/handler-fastify.js +40 -0
  70. package/lib/payment/postfinance-checkout/index.d.ts +0 -5
  71. package/lib/payment/postfinance-checkout/index.js +186 -25
  72. package/lib/payment/saferpay/adapter.js +2 -1
  73. package/lib/payment/saferpay/handler-express.d.ts +1 -0
  74. package/lib/payment/saferpay/handler-express.js +59 -0
  75. package/lib/payment/saferpay/handler-fastify.d.ts +2 -0
  76. package/lib/payment/saferpay/handler-fastify.js +62 -0
  77. package/lib/payment/saferpay/index.d.ts +4 -5
  78. package/lib/payment/saferpay/index.js +3 -29
  79. package/lib/payment/stripe/handler-express.d.ts +5 -0
  80. package/lib/payment/stripe/handler-express.js +121 -0
  81. package/lib/payment/stripe/handler-fastify.d.ts +6 -0
  82. package/lib/payment/stripe/handler-fastify.js +131 -0
  83. package/lib/payment/stripe/index.d.ts +1 -4
  84. package/lib/payment/stripe/index.js +116 -22
  85. package/lib/payment/stripe/stripe.js +4 -1
  86. package/lib/presets/all-express.d.ts +6 -0
  87. package/lib/presets/all-express.js +26 -0
  88. package/lib/presets/all-fastify.d.ts +6 -0
  89. package/lib/presets/all-fastify.js +49 -0
  90. package/lib/presets/all.d.ts +70 -1
  91. package/lib/presets/all.js +27 -41
  92. package/lib/presets/base-express.d.ts +2 -0
  93. package/lib/presets/base-express.js +5 -0
  94. package/lib/presets/base-fastify.d.ts +3 -0
  95. package/lib/presets/base-fastify.js +16 -0
  96. package/lib/presets/base.d.ts +25 -1
  97. package/lib/presets/base.js +25 -56
  98. package/lib/presets/countries/ch.d.ts +2 -1
  99. package/lib/presets/countries/ch.js +2 -7
  100. package/lib/presets/crypto-express.d.ts +3 -0
  101. package/lib/presets/crypto-express.js +8 -0
  102. package/lib/presets/crypto-fastify.d.ts +3 -0
  103. package/lib/presets/crypto-fastify.js +11 -0
  104. package/lib/presets/crypto.d.ts +7 -3
  105. package/lib/presets/crypto.js +7 -18
  106. package/lib/pricing/delivery-swiss-tax.d.ts +2 -0
  107. package/lib/pricing/delivery-swiss-tax.js +62 -0
  108. package/lib/pricing/discount-100-off.d.ts +2 -0
  109. package/lib/pricing/discount-100-off.js +30 -0
  110. package/lib/pricing/discount-half-price-manual.d.ts +2 -0
  111. package/lib/pricing/discount-half-price-manual.js +30 -0
  112. package/lib/pricing/discount-half-price.d.ts +2 -0
  113. package/lib/pricing/discount-half-price.js +33 -0
  114. package/lib/pricing/free-delivery.d.ts +2 -0
  115. package/lib/pricing/free-delivery.js +27 -0
  116. package/lib/pricing/free-payment.d.ts +2 -0
  117. package/lib/pricing/free-payment.js +27 -0
  118. package/lib/pricing/order-delivery.d.ts +2 -0
  119. package/lib/pricing/order-delivery.js +33 -0
  120. package/lib/pricing/order-discount.d.ts +2 -0
  121. package/lib/pricing/order-discount.js +74 -0
  122. package/lib/pricing/order-items-discount.d.ts +2 -0
  123. package/lib/pricing/order-items-discount.js +50 -0
  124. package/lib/pricing/order-items.d.ts +2 -0
  125. package/lib/pricing/order-items.js +43 -0
  126. package/lib/pricing/order-payment.d.ts +2 -0
  127. package/lib/pricing/order-payment.js +33 -0
  128. package/lib/pricing/order-round.d.ts +10 -0
  129. package/lib/pricing/order-round.js +135 -0
  130. package/lib/pricing/product-catalog-price-options.d.ts +2 -0
  131. package/lib/pricing/product-catalog-price-options.js +51 -0
  132. package/lib/pricing/product-catalog-price.d.ts +2 -0
  133. package/lib/pricing/product-catalog-price.js +60 -0
  134. package/lib/pricing/product-discount.d.ts +2 -0
  135. package/lib/pricing/product-discount.js +69 -0
  136. package/lib/pricing/product-price-rateconversion.d.ts +2 -0
  137. package/lib/pricing/product-price-rateconversion.js +53 -0
  138. package/lib/pricing/product-round.d.ts +10 -0
  139. package/lib/pricing/product-round.js +44 -0
  140. package/lib/pricing/product-swiss-tax.d.ts +2 -0
  141. package/lib/pricing/product-swiss-tax.js +76 -0
  142. package/lib/quotations/manual.d.ts +1 -0
  143. package/lib/quotations/manual.js +20 -0
  144. package/lib/warehousing/eth-minter.d.ts +1 -0
  145. package/lib/warehousing/eth-minter.js +120 -0
  146. package/lib/warehousing/store.d.ts +1 -0
  147. package/lib/warehousing/store.js +34 -0
  148. package/lib/worker/budgetsms.d.ts +9 -0
  149. package/lib/worker/budgetsms.js +169 -0
  150. package/lib/worker/bulk-export.d.ts +2 -0
  151. package/lib/worker/bulk-export.js +64 -0
  152. package/lib/worker/bulk-import.d.ts +2 -0
  153. package/lib/worker/bulk-import.js +65 -0
  154. package/lib/worker/bulkgate.d.ts +1 -0
  155. package/lib/worker/bulkgate.js +91 -0
  156. package/lib/worker/email.d.ts +1 -0
  157. package/lib/worker/email.js +136 -0
  158. package/lib/worker/enrollment-order-generator.d.ts +1 -0
  159. package/lib/worker/enrollment-order-generator.js +77 -0
  160. package/lib/worker/error-notifications.d.ts +9 -0
  161. package/lib/worker/error-notifications.js +61 -0
  162. package/lib/worker/export-token.d.ts +4 -0
  163. package/lib/worker/export-token.js +26 -0
  164. package/lib/worker/external.d.ts +2 -0
  165. package/lib/worker/external.js +13 -0
  166. package/lib/worker/heartbeat.d.ts +1 -0
  167. package/lib/worker/heartbeat.js +26 -0
  168. package/lib/worker/http-request.d.ts +8 -0
  169. package/lib/worker/http-request.js +56 -0
  170. package/lib/worker/message.d.ts +10 -0
  171. package/lib/worker/message.js +42 -0
  172. package/lib/worker/push-notification.d.ts +1 -0
  173. package/lib/worker/push-notification.js +87 -0
  174. package/lib/worker/twilio.d.ts +1 -0
  175. package/lib/worker/twilio.js +59 -0
  176. package/lib/worker/update-coinbase-rates.d.ts +1 -0
  177. package/lib/worker/update-coinbase-rates.js +59 -0
  178. package/lib/worker/update-ecb-rates.d.ts +1 -0
  179. package/lib/worker/update-ecb-rates.js +92 -0
  180. package/lib/worker/update-token-ownership.d.ts +3 -0
  181. package/lib/worker/update-token-ownership.js +57 -0
  182. package/lib/worker/zombie-killer.d.ts +13 -0
  183. package/lib/worker/zombie-killer.js +91 -0
  184. package/package.json +10 -15
@@ -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,2 @@
1
+ import type { RouteHandlerMethod } from 'fastify';
2
+ export declare const saferpayHandler: RouteHandlerMethod;
@@ -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
- import type { IPlugin } from '@unchainedshop/core';
2
- export declare const SaferpayPlugin: IPlugin;
3
- export default SaferpayPlugin;
4
- export { WordlineSaferpay } from './adapter.ts';
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
- import { WordlineSaferpay } from "./adapter.js";
2
- import saferpayModules from "./module.js";
3
- import { saferpayWebhookHandler } from "./webhook.js";
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,5 @@
1
+ export declare const WebhookEventTypes: {
2
+ PAYMENT_INTENT_SUCCEEDED: string;
3
+ SETUP_INTENT_SUCCEEDED: string;
4
+ };
5
+ export declare const stripeHandler: (request: any, response: any) => Promise<void>;
@@ -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,6 @@
1
+ import type { RouteHandlerMethod } from 'fastify';
2
+ export declare const WebhookEventTypes: {
3
+ PAYMENT_INTENT_SUCCEEDED: string;
4
+ SETUP_INTENT_SUCCEEDED: string;
5
+ };
6
+ export declare const stripeHandler: RouteHandlerMethod;
@@ -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,4 +1 @@
1
- import type { IPlugin } from '@unchainedshop/core';
2
- export declare const StripePlugin: IPlugin;
3
- export default StripePlugin;
4
- export { Stripe } from './adapter.ts';
1
+ export {};
@@ -1,28 +1,122 @@
1
1
  import { createLogger } from '@unchainedshop/logger';
2
- import { Stripe } from "./adapter.js";
3
- import { stripeWebhookHandler } from "./api.js";
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
- export const StripePlugin = {
5
+ const Stripe = {
6
+ ...PaymentAdapter,
7
7
  key: 'shop.unchained.payment.stripe',
8
- label: 'Stripe Payment Plugin',
8
+ label: 'Stripe',
9
9
  version: '2.0.0',
10
- adapters: [Stripe],
11
- routes: [
12
- {
13
- path: STRIPE_WEBHOOK_PATH,
14
- method: 'POST',
15
- handler: stripeWebhookHandler,
16
- },
17
- ],
18
- onRegister: () => {
19
- if (!process.env.STRIPE_SECRET) {
20
- throw new Error('STRIPE_SECRET not set');
21
- }
22
- if (!process.env.STRIPE_ENDPOINT_SECRET) {
23
- logger.warn('STRIPE_ENDPOINT_SECRET not set - webhooks will not work');
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
- export default StripePlugin;
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,6 @@
1
+ import express from 'express';
2
+ import type { UnchainedCore } from '@unchainedshop/core';
3
+ declare const _default: (app: express.Express, { unchainedAPI }: {
4
+ unchainedAPI: UnchainedCore;
5
+ }) => void;
6
+ export default _default;
@@ -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
+ };
@@ -0,0 +1,6 @@
1
+ import type { UnchainedCore } from '@unchainedshop/core';
2
+ import type { FastifyInstance } from 'fastify';
3
+ declare const _default: (fastify: FastifyInstance, { unchainedAPI }: {
4
+ unchainedAPI: UnchainedCore;
5
+ }) => void;
6
+ export default _default;