@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.
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 +9 -14
@@ -1,25 +1,164 @@
1
+ import { Readable } from 'node:stream';
2
+ import {} from '@unchainedshop/file-upload';
3
+ import { FileAdapter, FileDirector, buildHashedFilename, resolveExpirationDate, } from '@unchainedshop/file-upload';
4
+ import mime from 'mime/lite';
5
+ import { Client } from 'minio';
6
+ import { AssumeRoleProvider } from 'minio/dist/esm/AssumeRoleProvider.mjs';
7
+ import { expiryOffsetInMs } from '@unchainedshop/file-upload/lib/put-expiration.js';
1
8
  import { createLogger } from '@unchainedshop/logger';
2
- import { MinioAdapter } from "./adapter.js";
3
- import { minioWebhookHandler } from "./api.js";
4
9
  const logger = createLogger('unchained:minio');
5
- const { MINIO_WEBHOOK_AUTH_TOKEN, MINIO_WEBHOOK_PATH = '/minio/webhook' } = process.env;
6
- export const MinioPlugin = {
10
+ const { MINIO_ACCESS_KEY, MINIO_REGION, MINIO_STS_ENDPOINT, MINIO_SECRET_KEY, MINIO_ENDPOINT, MINIO_BUCKET_NAME, MINIO_UPLOAD_PREFIX, NODE_ENV, AMAZON_S3_SESSION_TOKEN, } = process.env;
11
+ let client = null;
12
+ export async function connectToMinio() {
13
+ if (!MINIO_ENDPOINT || !MINIO_BUCKET_NAME) {
14
+ logger.error('Please configure Minio/S3 by providing MINIO_ENDPOINT & MINIO_BUCKET_NAME to use upload features');
15
+ return null;
16
+ }
17
+ try {
18
+ const resolvedUrl = new URL(MINIO_ENDPOINT);
19
+ const minioClient = new Client({
20
+ endPoint: resolvedUrl.hostname,
21
+ useSSL: resolvedUrl.protocol === 'https:',
22
+ port: parseInt(resolvedUrl.port, 10) || undefined,
23
+ region: MINIO_REGION,
24
+ sessionToken: AMAZON_S3_SESSION_TOKEN,
25
+ accessKey: MINIO_ACCESS_KEY,
26
+ secretKey: MINIO_SECRET_KEY,
27
+ });
28
+ if (NODE_ENV === 'development')
29
+ minioClient?.traceOn(process.stdout);
30
+ if (MINIO_STS_ENDPOINT) {
31
+ const ap = new AssumeRoleProvider({
32
+ stsEndpoint: MINIO_STS_ENDPOINT,
33
+ accessKey: MINIO_ACCESS_KEY,
34
+ secretKey: MINIO_SECRET_KEY,
35
+ });
36
+ await minioClient.setCredentialsProvider(ap);
37
+ }
38
+ return minioClient;
39
+ }
40
+ catch (error) {
41
+ logger.error(`Exception while creating Minio client: ${error.message}`);
42
+ }
43
+ return null;
44
+ }
45
+ const generateMinioPath = (directoryName, fileName) => {
46
+ const prefix = MINIO_UPLOAD_PREFIX || '';
47
+ return [prefix, directoryName, fileName].filter(Boolean).join('/');
48
+ };
49
+ const generateMinioUrl = (directoryName, hashedFilename) => {
50
+ return `${MINIO_ENDPOINT}/${MINIO_BUCKET_NAME}/${generateMinioPath(directoryName, hashedFilename)}`;
51
+ };
52
+ connectToMinio().then(function setClient(c) {
53
+ client = c || client;
54
+ });
55
+ const getObjectStats = async (fileName) => {
56
+ if (!client)
57
+ throw new Error('Minio not connected, check env variables');
58
+ return client.statObject(MINIO_BUCKET_NAME, fileName);
59
+ };
60
+ const bufferToStream = (buffer) => {
61
+ const stream = new Readable();
62
+ stream.push(buffer);
63
+ stream.push(null);
64
+ return stream;
65
+ };
66
+ export const MinioAdapter = {
7
67
  key: 'shop.unchained.file-upload-plugin.minio',
8
- label: 'Minio/S3 File Upload Plugin with Webhook',
68
+ label: 'Uploads files into an S3 bucket using minio',
9
69
  version: '1.1.0',
10
- adapters: [MinioAdapter],
11
- routes: [
12
- {
13
- path: MINIO_WEBHOOK_PATH,
14
- method: 'POST',
15
- handler: minioWebhookHandler,
16
- },
17
- ],
18
- onRegister: () => {
19
- if (!MINIO_WEBHOOK_AUTH_TOKEN) {
20
- logger.warn('MINIO_WEBHOOK_AUTH_TOKEN not set - webhooks will be disabled');
70
+ ...FileAdapter,
71
+ async createDownloadURL(file) {
72
+ if (file.meta?.isPrivate)
73
+ throw new Error("Minio Plugin doesn't support private files yet");
74
+ return generateMinioUrl(file.path, file._id);
75
+ },
76
+ async createSignedURL(directoryName, fileName) {
77
+ if (!client)
78
+ throw new Error('Minio not connected, check env variables');
79
+ const expiryDate = resolveExpirationDate();
80
+ const _id = await buildHashedFilename(directoryName, fileName, expiryDate);
81
+ const url = await client.presignedPutObject(MINIO_BUCKET_NAME, generateMinioPath(directoryName, _id), expiryOffsetInMs() / 1000);
82
+ return {
83
+ _id,
84
+ directoryName,
85
+ expiryDate,
86
+ fileName,
87
+ type: mime.getType(fileName),
88
+ putURL: url,
89
+ url: generateMinioUrl(directoryName, _id),
90
+ };
91
+ },
92
+ async removeFiles(files) {
93
+ if (!client)
94
+ throw new Error('Minio not connected, check env variables');
95
+ const fileIds = files.map(({ path, _id }) => {
96
+ return `${path}/${_id}`;
97
+ });
98
+ await client.removeObjects(MINIO_BUCKET_NAME, fileIds);
99
+ },
100
+ async uploadFileFromStream(directoryName, rawFile) {
101
+ if (!client)
102
+ throw new Error('Minio not connected, check env variables');
103
+ let stream;
104
+ let fileName;
105
+ if (rawFile instanceof Promise) {
106
+ const { filename: fname, createReadStream } = await rawFile;
107
+ fileName = fname;
108
+ stream = createReadStream();
109
+ }
110
+ else {
111
+ fileName = rawFile.filename;
112
+ stream = bufferToStream(Buffer.from(rawFile.buffer, 'base64'));
21
113
  }
114
+ const _id = await buildHashedFilename(directoryName, fileName, new Date());
115
+ const type = mime.getType(fileName) || (await Promise.resolve(rawFile)).mimetype;
116
+ const metaData = {
117
+ 'Content-Type': type,
118
+ };
119
+ await client.putObject(MINIO_BUCKET_NAME, generateMinioPath(directoryName, _id), stream, undefined, metaData);
120
+ const { size } = await getObjectStats(generateMinioPath(directoryName, _id));
121
+ return {
122
+ _id,
123
+ directoryName,
124
+ expiryDate: null,
125
+ fileName,
126
+ size,
127
+ type,
128
+ url: generateMinioUrl(directoryName, _id),
129
+ };
130
+ },
131
+ async uploadFileFromURL(directoryName, { fileLink, fileName: fname, fileId, headers }) {
132
+ if (!client)
133
+ throw new Error('Minio not connected, check env variables');
134
+ const { href } = new URL(fileLink);
135
+ const fileName = fname || href.split('/').pop();
136
+ const hashedFilename = await buildHashedFilename(directoryName, fileName, new Date());
137
+ const url = new URL(fileLink);
138
+ const response = await fetch(url, { headers });
139
+ const type = mime.getType(fileName) || response.headers['content-type'];
140
+ const readable = Readable.fromWeb(response.body);
141
+ const metaData = {
142
+ 'Content-Type': type,
143
+ };
144
+ await client.putObject(MINIO_BUCKET_NAME, generateMinioPath(directoryName, hashedFilename), readable, undefined, metaData);
145
+ const { size } = await getObjectStats(generateMinioPath(directoryName, hashedFilename));
146
+ return {
147
+ _id: fileId || hashedFilename,
148
+ directoryName,
149
+ expiryDate: null,
150
+ fileName,
151
+ size,
152
+ type,
153
+ url: generateMinioUrl(directoryName, hashedFilename),
154
+ };
155
+ },
156
+ async createDownloadStream(file) {
157
+ if (!client)
158
+ throw new Error('Minio not connected, check env variables');
159
+ const stream = await client.getObject(MINIO_BUCKET_NAME, generateMinioPath(file.path, file._id));
160
+ return stream;
22
161
  },
23
162
  };
24
- export default MinioPlugin;
25
- export { MinioAdapter, connectToMinio } from "./adapter.js";
163
+ FileDirector.registerAdapter(MinioAdapter);
164
+ export default MinioAdapter;
@@ -0,0 +1,3 @@
1
+ import { type IFilterAdapter } from '@unchainedshop/core';
2
+ declare const LocalSearch: IFilterAdapter;
3
+ export default LocalSearch;
@@ -0,0 +1,66 @@
1
+ import { FilterDirector, FilterAdapter } from '@unchainedshop/core';
2
+ import { createLogger } from '@unchainedshop/logger';
3
+ import { isDocumentDBCompatModeEnabled } from '@unchainedshop/mongodb';
4
+ const logger = createLogger('unchained:local-search');
5
+ const LocalSearch = {
6
+ ...FilterAdapter,
7
+ key: 'shop.unchained.filters.local-search',
8
+ label: 'Simple Fulltext search with MongoDB',
9
+ version: '1.0.0',
10
+ orderIndex: 10,
11
+ actions: (params) => {
12
+ return {
13
+ ...FilterAdapter.actions(params),
14
+ searchProducts: async ({ productIds }) => {
15
+ const { queryString } = params.searchQuery;
16
+ if (!queryString)
17
+ return productIds;
18
+ const products = await params.modules.products.texts.findTexts({
19
+ queryString,
20
+ productIds: productIds ? [...new Set(productIds)] : undefined,
21
+ }, {
22
+ projection: {
23
+ productId: 1,
24
+ },
25
+ });
26
+ return products.map(({ productId }) => productId);
27
+ },
28
+ searchAssortments: async ({ assortmentIds }) => {
29
+ const { queryString } = params.searchQuery;
30
+ if (!queryString) {
31
+ return assortmentIds;
32
+ }
33
+ const assortments = await params.modules.assortments.texts.findTexts({
34
+ queryString,
35
+ assortmentIds,
36
+ }, {
37
+ projection: {
38
+ assortmentId: 1,
39
+ },
40
+ });
41
+ return assortments.map(({ assortmentId }) => assortmentId);
42
+ },
43
+ async transformFilterSelector(last) {
44
+ const { queryString, filterIds, includeInactive } = params.searchQuery;
45
+ if (queryString && !filterIds) {
46
+ const selector = { ...last };
47
+ if (selector?.key) {
48
+ delete selector.key;
49
+ }
50
+ if (!includeInactive) {
51
+ selector.isActive = true;
52
+ }
53
+ return selector;
54
+ }
55
+ return last;
56
+ },
57
+ };
58
+ },
59
+ };
60
+ export default LocalSearch;
61
+ if (!isDocumentDBCompatModeEnabled()) {
62
+ FilterDirector.registerAdapter(LocalSearch);
63
+ }
64
+ else {
65
+ logger.warn('Free text search queries have been disabled due to DocumentDB compatibility mode (env UNCHAINED_DOCUMENTDB_COMPAT_MODE is trueish)');
66
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,24 @@
1
+ import { FilterDirector, FilterAdapter } from '@unchainedshop/core';
2
+ const StrictQualFilter = {
3
+ ...FilterAdapter,
4
+ key: 'shop.unchained.filters.strict-qual',
5
+ label: 'Simple Strict Equal DB Filter',
6
+ version: '1.0.0',
7
+ orderIndex: 0,
8
+ actions: (params) => {
9
+ return {
10
+ ...FilterAdapter.actions(params),
11
+ transformProductSelector: async (lastSelector, options) => {
12
+ const { key, value } = options || {};
13
+ if (key) {
14
+ return {
15
+ ...lastSelector,
16
+ [key]: value !== undefined ? value : { $exists: true },
17
+ };
18
+ }
19
+ return lastSelector;
20
+ },
21
+ };
22
+ },
23
+ };
24
+ FilterDirector.registerAdapter(StrictQualFilter);
@@ -1,2 +1 @@
1
- import { type IPaymentAdapter } from '@unchainedshop/core';
2
- export declare const AppleIAP: IPaymentAdapter;
1
+ export {};
@@ -1,10 +1,10 @@
1
- import { PaymentAdapter, PaymentError } from '@unchainedshop/core';
1
+ import { PaymentAdapter, PaymentDirector, PaymentError, } from '@unchainedshop/core';
2
2
  import { createLogger } from '@unchainedshop/logger';
3
3
  import {} from "./module.js";
4
4
  import { verifyReceipt } from "./verify-receipt.js";
5
5
  const logger = createLogger('unchained:apple-iap');
6
6
  const { APPLE_IAP_SHARED_SECRET } = process.env;
7
- export const AppleIAP = {
7
+ const AppleIAP = {
8
8
  ...PaymentAdapter,
9
9
  key: 'shop.unchained.apple-iap',
10
10
  label: 'Apple In-App-Purchase',
@@ -111,3 +111,4 @@ export const AppleIAP = {
111
111
  return adapterActions;
112
112
  },
113
113
  };
114
+ PaymentDirector.registerAdapter(AppleIAP);
@@ -0,0 +1,2 @@
1
+ export declare const appleIAPHandler: (req: any, res: any) => Promise<void>;
2
+ export default appleIAPHandler;
@@ -0,0 +1,103 @@
1
+ import {} from '@unchainedshop/api';
2
+ import { EnrollmentStatus } from '@unchainedshop/core-enrollments';
3
+ import { createLogger } from '@unchainedshop/logger';
4
+ import { fixPeriods } from "./fix-periods.js";
5
+ const logger = createLogger('unchained:apple-iap:handler');
6
+ const { APPLE_IAP_SHARED_SECRET } = process.env;
7
+ const AppleNotificationTypes = {
8
+ INITIAL_BUY: 'INITIAL_BUY',
9
+ DID_RECOVER: 'DID_RECOVER',
10
+ DID_CHANGE_RENEWAL_STATUS: 'DID_CHANGE_RENEWAL_STATUS',
11
+ DID_FAIL_TO_RENEW: 'DID_FAIL_TO_RENEW',
12
+ DID_CHANGE_RENEWAL_PREF: 'DID_CHANGE_RENEWAL_PREF',
13
+ };
14
+ export const appleIAPHandler = async (req, res) => {
15
+ if (req.method === 'POST') {
16
+ try {
17
+ const resolvedContext = req.unchainedContext;
18
+ const { modules, services } = resolvedContext;
19
+ const responseBody = req.body || {};
20
+ if (responseBody.password !== APPLE_IAP_SHARED_SECRET) {
21
+ throw new Error('shared secret not valid');
22
+ }
23
+ const transactions = responseBody?.unified_receipt?.latest_receipt_info;
24
+ const latestTransaction = transactions[0];
25
+ if (responseBody.notification_type === AppleNotificationTypes.INITIAL_BUY) {
26
+ const orderPayment = await modules.orders.payments.findOrderPaymentByTransactionId(latestTransaction.transaction_id);
27
+ if (!orderPayment)
28
+ throw new Error('Could not find any matching order payment');
29
+ const order = await services.orders.checkoutOrder(orderPayment.orderId, {
30
+ paymentContext: {
31
+ receiptData: responseBody?.unified_receipt?.latest_receipt,
32
+ },
33
+ });
34
+ if (!order)
35
+ throw new Error(`Order with id ${orderPayment.orderId} not found`);
36
+ const orderId = order._id;
37
+ const enrollment = await modules.enrollments.findEnrollment({
38
+ orderId,
39
+ });
40
+ if (!enrollment)
41
+ throw new Error('Could not find any matching enrollment');
42
+ await fixPeriods({
43
+ transactionId: latestTransaction.original_transaction_id,
44
+ transactions,
45
+ enrollmentId: enrollment._id,
46
+ orderId,
47
+ }, resolvedContext);
48
+ logger.info(`Apple IAP Webhook: Confirmed checkout for order ${order.orderNumber}`, {
49
+ orderId: order._id,
50
+ });
51
+ }
52
+ else {
53
+ const originalOrderPayment = await modules.orders.payments.findOrderPaymentByTransactionId(latestTransaction.original_transaction_id);
54
+ if (!originalOrderPayment)
55
+ throw new Error('Could not find any matching order payment');
56
+ const originalOrder = await modules.orders.findOrder({
57
+ orderId: originalOrderPayment.orderId,
58
+ });
59
+ const enrollment = originalOrder &&
60
+ (await modules.enrollments.findEnrollment({
61
+ orderId: originalOrder._id,
62
+ }));
63
+ if (!enrollment?.payment)
64
+ throw new Error('Could not find a valid enrollment payment method');
65
+ await services.orders.registerPaymentCredentials(enrollment.payment.paymentProviderId, {
66
+ transactionContext: {
67
+ receiptData: responseBody?.unified_receipt?.latest_receipt,
68
+ },
69
+ userId: enrollment.userId,
70
+ });
71
+ await fixPeriods({
72
+ transactionId: latestTransaction.original_transaction_id,
73
+ transactions,
74
+ enrollmentId: enrollment._id,
75
+ orderId: originalOrder._id,
76
+ }, resolvedContext);
77
+ logger.info(`Apple IAP Webhook: Processed notification for ${latestTransaction.original_transaction_id} and type ${responseBody.notification_type}`);
78
+ if (responseBody.notification_type === AppleNotificationTypes.DID_RECOVER) {
79
+ if (enrollment.status !== EnrollmentStatus.TERMINATED &&
80
+ responseBody.auto_renew_status === 'false') {
81
+ await services.enrollments.terminateEnrollment(enrollment);
82
+ }
83
+ }
84
+ if (responseBody.notification_type === AppleNotificationTypes.DID_CHANGE_RENEWAL_STATUS) {
85
+ if (enrollment.status !== EnrollmentStatus.TERMINATED &&
86
+ responseBody.auto_renew_status === 'false') {
87
+ await services.enrollments.terminateEnrollment(enrollment);
88
+ }
89
+ }
90
+ logger.info(`Apple IAP Webhook: Updated enrollment from Apple`);
91
+ }
92
+ res.status(200).end();
93
+ return;
94
+ }
95
+ catch (e) {
96
+ logger.warn(`Apple IAP Webhook: ${e.name} - ${e.message}`);
97
+ res.status(503).send({ name: e.name, code: e.code, message: e.message });
98
+ return;
99
+ }
100
+ }
101
+ res.status(404).end();
102
+ };
103
+ export default appleIAPHandler;
@@ -0,0 +1,3 @@
1
+ import { type RouteHandlerMethod } from 'fastify';
2
+ export declare const appleIAPHandler: RouteHandlerMethod;
3
+ export default appleIAPHandler;
@@ -0,0 +1,102 @@
1
+ import {} from '@unchainedshop/api';
2
+ import { EnrollmentStatus } from '@unchainedshop/core-enrollments';
3
+ import { createLogger } from '@unchainedshop/logger';
4
+ import { fixPeriods } from "./fix-periods.js";
5
+ import {} from 'fastify';
6
+ import {} from "./module.js";
7
+ const logger = createLogger('unchained:apple-iap:handler');
8
+ const { APPLE_IAP_SHARED_SECRET } = process.env;
9
+ const AppleNotificationTypes = {
10
+ INITIAL_BUY: 'INITIAL_BUY',
11
+ DID_RECOVER: 'DID_RECOVER',
12
+ DID_CHANGE_RENEWAL_STATUS: 'DID_CHANGE_RENEWAL_STATUS',
13
+ DID_FAIL_TO_RENEW: 'DID_FAIL_TO_RENEW',
14
+ DID_CHANGE_RENEWAL_PREF: 'DID_CHANGE_RENEWAL_PREF',
15
+ };
16
+ export const appleIAPHandler = async (req, reply) => {
17
+ try {
18
+ const resolvedContext = req.unchainedContext;
19
+ const { modules, services } = resolvedContext;
20
+ const responseBody = req.body || {};
21
+ if (responseBody.password !== APPLE_IAP_SHARED_SECRET) {
22
+ throw new Error('shared secret not valid');
23
+ }
24
+ const transactions = responseBody?.unified_receipt?.latest_receipt_info;
25
+ const latestTransaction = transactions[0];
26
+ if (responseBody.notification_type === AppleNotificationTypes.INITIAL_BUY) {
27
+ const orderPayment = await modules.orders.payments.findOrderPaymentByTransactionId(latestTransaction.transaction_id);
28
+ if (!orderPayment)
29
+ throw new Error('Could not find any matching order payment');
30
+ const order = await services.orders.checkoutOrder(orderPayment.orderId, {
31
+ paymentContext: {
32
+ receiptData: responseBody?.unified_receipt?.latest_receipt,
33
+ },
34
+ });
35
+ if (!order)
36
+ throw new Error(`Order with id ${orderPayment.orderId} not found`);
37
+ const orderId = order._id;
38
+ const enrollment = await modules.enrollments.findEnrollment({
39
+ orderId,
40
+ });
41
+ if (!enrollment)
42
+ throw new Error('Enrollent not found');
43
+ await fixPeriods({
44
+ transactionId: latestTransaction.original_transaction_id,
45
+ transactions,
46
+ enrollmentId: enrollment._id,
47
+ orderId,
48
+ }, resolvedContext);
49
+ logger.info(`Webhook confirmed checkout for order ${order.orderNumber}`, {
50
+ orderId: order._id,
51
+ });
52
+ }
53
+ else {
54
+ const originalOrderPayment = await modules.orders.payments.findOrderPaymentByTransactionId(latestTransaction.original_transaction_id);
55
+ if (!originalOrderPayment)
56
+ throw new Error('Could not find any matching order payment');
57
+ const originalOrder = await modules.orders.findOrder({
58
+ orderId: originalOrderPayment.orderId,
59
+ });
60
+ const enrollment = originalOrder &&
61
+ (await modules.enrollments.findEnrollment({
62
+ orderId: originalOrder._id,
63
+ }));
64
+ if (!enrollment?.payment)
65
+ throw new Error('Could not find a valid enrollment payment method');
66
+ await services.orders.registerPaymentCredentials(enrollment.payment.paymentProviderId, {
67
+ transactionContext: {
68
+ receiptData: responseBody?.unified_receipt?.latest_receipt,
69
+ },
70
+ userId: enrollment.userId,
71
+ });
72
+ await fixPeriods({
73
+ transactionId: latestTransaction.original_transaction_id,
74
+ transactions,
75
+ enrollmentId: enrollment._id,
76
+ orderId: originalOrder._id,
77
+ }, resolvedContext);
78
+ logger.info(`Webhook rocessed notification for ${latestTransaction.original_transaction_id} and type ${responseBody.notification_type}`);
79
+ if (responseBody.notification_type === AppleNotificationTypes.DID_RECOVER) {
80
+ if (enrollment.status !== EnrollmentStatus.TERMINATED &&
81
+ responseBody.auto_renew_status === 'false') {
82
+ await services.enrollments.terminateEnrollment(enrollment);
83
+ }
84
+ }
85
+ if (responseBody.notification_type === AppleNotificationTypes.DID_CHANGE_RENEWAL_STATUS) {
86
+ if (enrollment.status !== EnrollmentStatus.TERMINATED &&
87
+ responseBody.auto_renew_status === 'false') {
88
+ await services.enrollments.terminateEnrollment(enrollment);
89
+ }
90
+ }
91
+ logger.info(`Apple IAP Webhook: Updated enrollment from Apple`);
92
+ }
93
+ reply.status(200);
94
+ return reply.send();
95
+ }
96
+ catch (e) {
97
+ logger.error(e);
98
+ reply.status(503);
99
+ return reply.send({ success: false, name: e.name, code: e.code, message: e.message });
100
+ }
101
+ };
102
+ export default appleIAPHandler;
@@ -1,6 +1,4 @@
1
- import type { IPlugin } from '@unchainedshop/core';
2
- import { type AppleTransactionsModule } from './module.ts';
3
- export declare const AppleIAPPlugin: IPlugin;
4
- export default AppleIAPPlugin;
5
- export { AppleIAP } from './adapter.ts';
6
- export { type AppleTransactionsModule };
1
+ export * from './adapter.ts';
2
+ import appleTransactionsModule, { type AppleTransactionsModule } from './module.ts';
3
+ export type { AppleTransactionsModule };
4
+ export default appleTransactionsModule;
@@ -1,28 +1,3 @@
1
- import { AppleIAP } from "./adapter.js";
2
- import { configureAppleTransactionsModule } from "./module.js";
3
- import { appleIAPWebhookHandler } from "./api.js";
4
- const { APPLE_IAP_WEBHOOK_PATH = '/payment/apple-iap', APPLE_IAP_SHARED_SECRET } = process.env;
5
- export const AppleIAPPlugin = {
6
- key: 'shop.unchained.payment.apple-iap',
7
- label: 'Apple In-App-Purchase Payment Plugin',
8
- version: '1.0.0',
9
- adapters: [AppleIAP],
10
- module: ({ db }) => ({
11
- appleTransactions: configureAppleTransactionsModule({ db }),
12
- }),
13
- routes: [
14
- {
15
- path: APPLE_IAP_WEBHOOK_PATH,
16
- method: 'POST',
17
- handler: appleIAPWebhookHandler,
18
- },
19
- ],
20
- onRegister: () => {
21
- if (!APPLE_IAP_SHARED_SECRET) {
22
- throw new Error('APPLE_IAP_SHARED_SECRET not set');
23
- }
24
- },
25
- };
26
- export default AppleIAPPlugin;
27
- export { AppleIAP } from "./adapter.js";
28
- export {};
1
+ export * from "./adapter.js";
2
+ import appleTransactionsModule, {} from "./module.js";
3
+ export default appleTransactionsModule;
@@ -0,0 +1 @@
1
+ export {};