@unchainedshop/plugins 4.6.1 → 4.6.2

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 (209) hide show
  1. package/lib/delivery/post/adapter.d.ts +2 -0
  2. package/lib/delivery/post/adapter.js +22 -0
  3. package/lib/delivery/post/index.d.ts +4 -0
  4. package/lib/delivery/post/index.js +10 -0
  5. package/lib/delivery/send-message/adapter.d.ts +2 -0
  6. package/lib/delivery/send-message/adapter.js +39 -0
  7. package/lib/delivery/send-message/index.d.ts +4 -0
  8. package/lib/delivery/send-message/index.js +10 -0
  9. package/lib/delivery/stores/adapter.d.ts +2 -0
  10. package/lib/delivery/stores/adapter.js +42 -0
  11. package/lib/delivery/stores/index.d.ts +4 -0
  12. package/lib/delivery/stores/index.js +10 -0
  13. package/lib/enrollments/licensed/adapter.d.ts +6 -0
  14. package/lib/enrollments/licensed/adapter.js +51 -0
  15. package/lib/enrollments/licensed/index.d.ts +4 -0
  16. package/lib/enrollments/licensed/index.js +10 -0
  17. package/lib/files/gridfs/api.d.ts +7 -0
  18. package/lib/files/gridfs/api.js +163 -0
  19. package/lib/files/minio/adapter.d.ts +4 -0
  20. package/lib/files/minio/adapter.js +160 -0
  21. package/lib/files/minio/api.d.ts +2 -0
  22. package/lib/files/minio/api.js +41 -0
  23. package/lib/files/temp-upload/api.d.ts +6 -0
  24. package/lib/files/temp-upload/api.js +49 -0
  25. package/lib/files/temp-upload/index.d.ts +3 -0
  26. package/lib/files/temp-upload/index.js +15 -0
  27. package/lib/filters/local-search/adapter.d.ts +2 -0
  28. package/lib/filters/local-search/adapter.js +56 -0
  29. package/lib/filters/local-search/index.d.ts +4 -0
  30. package/lib/filters/local-search/index.js +16 -0
  31. package/lib/filters/strict-equal/adapter.d.ts +2 -0
  32. package/lib/filters/strict-equal/adapter.js +23 -0
  33. package/lib/filters/strict-equal/index.d.ts +4 -0
  34. package/lib/filters/strict-equal/index.js +10 -0
  35. package/lib/payment/apple-iap/api.d.ts +2 -0
  36. package/lib/payment/apple-iap/api.js +119 -0
  37. package/lib/payment/cryptopay/adapter.d.ts +2 -0
  38. package/lib/payment/cryptopay/adapter.js +157 -0
  39. package/lib/payment/cryptopay/api.d.ts +2 -0
  40. package/lib/payment/cryptopay/api.js +23 -0
  41. package/lib/payment/datatrans-v2/adapter.d.ts +2 -0
  42. package/lib/payment/datatrans-v2/adapter.js +406 -0
  43. package/lib/payment/datatrans-v2/api.d.ts +2 -0
  44. package/lib/payment/datatrans-v2/api.js +108 -0
  45. package/lib/payment/invoice/adapter.d.ts +2 -0
  46. package/lib/payment/invoice/adapter.js +26 -0
  47. package/lib/payment/invoice/index.d.ts +4 -0
  48. package/lib/payment/invoice/index.js +10 -0
  49. package/lib/payment/invoice-prepaid/adapter.d.ts +2 -0
  50. package/lib/payment/invoice-prepaid/adapter.js +29 -0
  51. package/lib/payment/invoice-prepaid/index.d.ts +4 -0
  52. package/lib/payment/invoice-prepaid/index.js +10 -0
  53. package/lib/payment/payrexx/adapter.d.ts +2 -0
  54. package/lib/payment/payrexx/adapter.js +161 -0
  55. package/lib/payment/payrexx/api.d.ts +2 -0
  56. package/lib/payment/payrexx/api.js +97 -0
  57. package/lib/payment/postfinance-checkout/adapter.d.ts +2 -0
  58. package/lib/payment/postfinance-checkout/adapter.js +189 -0
  59. package/lib/payment/postfinance-checkout/webhook.d.ts +2 -0
  60. package/lib/payment/postfinance-checkout/webhook.js +48 -0
  61. package/lib/payment/saferpay/webhook.d.ts +2 -0
  62. package/lib/payment/saferpay/webhook.js +64 -0
  63. package/lib/payment/stripe/adapter.d.ts +2 -0
  64. package/lib/payment/stripe/adapter.js +121 -0
  65. package/lib/payment/stripe/api.d.ts +6 -0
  66. package/lib/payment/stripe/api.js +165 -0
  67. package/lib/pricing/delivery-swiss-tax/adapter.d.ts +3 -0
  68. package/lib/pricing/delivery-swiss-tax/adapter.js +62 -0
  69. package/lib/pricing/delivery-swiss-tax/index.d.ts +4 -0
  70. package/lib/pricing/delivery-swiss-tax/index.js +10 -0
  71. package/lib/pricing/discount-100-off/adapter.d.ts +3 -0
  72. package/lib/pricing/discount-100-off/adapter.js +30 -0
  73. package/lib/pricing/discount-100-off/index.d.ts +4 -0
  74. package/lib/pricing/discount-100-off/index.js +10 -0
  75. package/lib/pricing/discount-half-price-manual/adapter.d.ts +3 -0
  76. package/lib/pricing/discount-half-price-manual/adapter.js +30 -0
  77. package/lib/pricing/discount-half-price-manual/index.d.ts +4 -0
  78. package/lib/pricing/discount-half-price-manual/index.js +10 -0
  79. package/lib/pricing/free-delivery/adapter.d.ts +3 -0
  80. package/lib/pricing/free-delivery/adapter.js +27 -0
  81. package/lib/pricing/free-delivery/index.d.ts +4 -0
  82. package/lib/pricing/free-delivery/index.js +10 -0
  83. package/lib/pricing/free-payment/adapter.d.ts +3 -0
  84. package/lib/pricing/free-payment/adapter.js +27 -0
  85. package/lib/pricing/free-payment/index.d.ts +4 -0
  86. package/lib/pricing/free-payment/index.js +10 -0
  87. package/lib/pricing/order-delivery/adapter.d.ts +3 -0
  88. package/lib/pricing/order-delivery/adapter.js +33 -0
  89. package/lib/pricing/order-delivery/index.d.ts +4 -0
  90. package/lib/pricing/order-delivery/index.js +10 -0
  91. package/lib/pricing/order-discount/adapter.d.ts +3 -0
  92. package/lib/pricing/order-discount/adapter.js +74 -0
  93. package/lib/pricing/order-discount/index.d.ts +4 -0
  94. package/lib/pricing/order-discount/index.js +10 -0
  95. package/lib/pricing/order-items/adapter.d.ts +3 -0
  96. package/lib/pricing/order-items/adapter.js +43 -0
  97. package/lib/pricing/order-items/index.d.ts +4 -0
  98. package/lib/pricing/order-items/index.js +10 -0
  99. package/lib/pricing/order-payment/adapter.d.ts +3 -0
  100. package/lib/pricing/order-payment/adapter.js +33 -0
  101. package/lib/pricing/order-payment/index.d.ts +4 -0
  102. package/lib/pricing/order-payment/index.js +10 -0
  103. package/lib/pricing/product-catalog-price/adapter.d.ts +3 -0
  104. package/lib/pricing/product-catalog-price/adapter.js +60 -0
  105. package/lib/pricing/product-catalog-price/index.d.ts +4 -0
  106. package/lib/pricing/product-catalog-price/index.js +10 -0
  107. package/lib/pricing/product-discount/adapter.d.ts +3 -0
  108. package/lib/pricing/product-discount/adapter.js +69 -0
  109. package/lib/pricing/product-discount/index.d.ts +4 -0
  110. package/lib/pricing/product-discount/index.js +10 -0
  111. package/lib/pricing/product-price-rateconversion/adapter.d.ts +3 -0
  112. package/lib/pricing/product-price-rateconversion/adapter.js +53 -0
  113. package/lib/pricing/product-price-rateconversion/index.d.ts +4 -0
  114. package/lib/pricing/product-price-rateconversion/index.js +10 -0
  115. package/lib/pricing/product-swiss-tax/adapter.d.ts +3 -0
  116. package/lib/pricing/product-swiss-tax/adapter.js +76 -0
  117. package/lib/pricing/product-swiss-tax/index.d.ts +4 -0
  118. package/lib/pricing/product-swiss-tax/index.js +10 -0
  119. package/lib/quotations/manual/adapter.d.ts +2 -0
  120. package/lib/quotations/manual/adapter.js +19 -0
  121. package/lib/quotations/manual/index.d.ts +4 -0
  122. package/lib/quotations/manual/index.js +10 -0
  123. package/lib/warehousing/erc-metadata/api.d.ts +12 -0
  124. package/lib/warehousing/erc-metadata/api.js +43 -0
  125. package/lib/warehousing/erc-metadata/index.d.ts +3 -0
  126. package/lib/warehousing/erc-metadata/index.js +15 -0
  127. package/lib/warehousing/eth-minter/adapter.d.ts +2 -0
  128. package/lib/warehousing/eth-minter/adapter.js +119 -0
  129. package/lib/warehousing/eth-minter/index.d.ts +4 -0
  130. package/lib/warehousing/eth-minter/index.js +10 -0
  131. package/lib/warehousing/store/adapter.d.ts +2 -0
  132. package/lib/warehousing/store/adapter.js +33 -0
  133. package/lib/warehousing/store/index.d.ts +4 -0
  134. package/lib/warehousing/store/index.js +10 -0
  135. package/lib/worker/budgetsms/adapter.d.ts +22 -0
  136. package/lib/worker/budgetsms/adapter.js +169 -0
  137. package/lib/worker/budgetsms/index.d.ts +4 -0
  138. package/lib/worker/budgetsms/index.js +10 -0
  139. package/lib/worker/bulk-export/adapter.d.ts +3 -0
  140. package/lib/worker/bulk-export/adapter.js +64 -0
  141. package/lib/worker/bulk-export/index.d.ts +4 -0
  142. package/lib/worker/bulk-export/index.js +10 -0
  143. package/lib/worker/bulk-import/adapter.d.ts +3 -0
  144. package/lib/worker/bulk-import/adapter.js +65 -0
  145. package/lib/worker/bulk-import/api.d.ts +7 -0
  146. package/lib/worker/bulk-import/api.js +53 -0
  147. package/lib/worker/bulk-import/index.d.ts +4 -0
  148. package/lib/worker/bulk-import/index.js +18 -0
  149. package/lib/worker/bulkgate/adapter.d.ts +12 -0
  150. package/lib/worker/bulkgate/adapter.js +91 -0
  151. package/lib/worker/bulkgate/index.d.ts +4 -0
  152. package/lib/worker/bulkgate/index.js +10 -0
  153. package/lib/worker/email/adapter.d.ts +9 -0
  154. package/lib/worker/email/adapter.js +150 -0
  155. package/lib/worker/email/index.d.ts +4 -0
  156. package/lib/worker/email/index.js +10 -0
  157. package/lib/worker/enrollment-order-generator/adapter.d.ts +4 -0
  158. package/lib/worker/enrollment-order-generator/adapter.js +77 -0
  159. package/lib/worker/enrollment-order-generator/index.d.ts +4 -0
  160. package/lib/worker/enrollment-order-generator/index.js +13 -0
  161. package/lib/worker/error-notifications/adapter.d.ts +10 -0
  162. package/lib/worker/error-notifications/adapter.js +62 -0
  163. package/lib/worker/error-notifications/index.d.ts +4 -0
  164. package/lib/worker/error-notifications/index.js +13 -0
  165. package/lib/worker/export-token/adapter.d.ts +4 -0
  166. package/lib/worker/export-token/adapter.js +25 -0
  167. package/lib/worker/export-token/index.d.ts +4 -0
  168. package/lib/worker/export-token/index.js +13 -0
  169. package/lib/worker/external/adapter.d.ts +3 -0
  170. package/lib/worker/external/adapter.js +13 -0
  171. package/lib/worker/external/index.d.ts +4 -0
  172. package/lib/worker/external/index.js +10 -0
  173. package/lib/worker/heartbeat/adapter.d.ts +8 -0
  174. package/lib/worker/heartbeat/adapter.js +26 -0
  175. package/lib/worker/heartbeat/index.d.ts +4 -0
  176. package/lib/worker/heartbeat/index.js +10 -0
  177. package/lib/worker/http-request/adapter.d.ts +8 -0
  178. package/lib/worker/http-request/adapter.js +55 -0
  179. package/lib/worker/http-request/index.d.ts +4 -0
  180. package/lib/worker/http-request/index.js +10 -0
  181. package/lib/worker/message/adapter.d.ts +11 -0
  182. package/lib/worker/message/adapter.js +42 -0
  183. package/lib/worker/message/index.d.ts +4 -0
  184. package/lib/worker/message/index.js +10 -0
  185. package/lib/worker/push-notification/adapter.d.ts +9 -0
  186. package/lib/worker/push-notification/adapter.js +87 -0
  187. package/lib/worker/push-notification/index.d.ts +4 -0
  188. package/lib/worker/push-notification/index.js +10 -0
  189. package/lib/worker/twilio/adapter.d.ts +8 -0
  190. package/lib/worker/twilio/adapter.js +59 -0
  191. package/lib/worker/twilio/index.d.ts +4 -0
  192. package/lib/worker/twilio/index.js +10 -0
  193. package/lib/worker/update-coinbase-rates/adapter.d.ts +4 -0
  194. package/lib/worker/update-coinbase-rates/adapter.js +61 -0
  195. package/lib/worker/update-coinbase-rates/index.d.ts +4 -0
  196. package/lib/worker/update-coinbase-rates/index.js +13 -0
  197. package/lib/worker/update-ecb-rates/adapter.d.ts +4 -0
  198. package/lib/worker/update-ecb-rates/adapter.js +94 -0
  199. package/lib/worker/update-ecb-rates/index.d.ts +4 -0
  200. package/lib/worker/update-ecb-rates/index.js +13 -0
  201. package/lib/worker/update-token-ownership/adapter.d.ts +5 -0
  202. package/lib/worker/update-token-ownership/adapter.js +58 -0
  203. package/lib/worker/update-token-ownership/index.d.ts +4 -0
  204. package/lib/worker/update-token-ownership/index.js +13 -0
  205. package/lib/worker/zombie-killer/adapter.d.ts +13 -0
  206. package/lib/worker/zombie-killer/adapter.js +90 -0
  207. package/lib/worker/zombie-killer/index.d.ts +4 -0
  208. package/lib/worker/zombie-killer/index.js +10 -0
  209. package/package.json +1 -1
@@ -0,0 +1,49 @@
1
+ import { createLogger } from '@unchainedshop/logger';
2
+ import { getFileAdapter } from '@unchainedshop/core';
3
+ import { Roles } from '@unchainedshop/roles';
4
+ const logger = createLogger('unchained:temp-upload');
5
+ export async function tempUploadHandler(request, context) {
6
+ try {
7
+ const hasPermission = await Roles.userHasPermission(context, 'uploadTempFile', []);
8
+ if (!hasPermission) {
9
+ return Response.json({ error: 'Permission denied', message: 'User does not have uploadTempFile permission' }, { status: 403 });
10
+ }
11
+ const formData = await request.formData();
12
+ let file = formData.get('file');
13
+ if (!file || !(file instanceof File)) {
14
+ for (const [, value] of formData.entries()) {
15
+ if (value instanceof File) {
16
+ file = value;
17
+ break;
18
+ }
19
+ }
20
+ }
21
+ if (!file) {
22
+ return Response.json({ error: 'No file uploaded' }, { status: 400 });
23
+ }
24
+ const buffer = Buffer.from(await file.arrayBuffer());
25
+ const uploadedFile = await context.services.files.uploadFileFromStream({
26
+ directoryName: 'temp-uploads',
27
+ rawFile: {
28
+ filename: file.name,
29
+ buffer,
30
+ mimetype: file.type,
31
+ },
32
+ meta: { encoding: 'binary' },
33
+ });
34
+ const expires = new Date(Date.now() + 24 * 60 * 60 * 1000);
35
+ await context.modules.files.update(uploadedFile._id, {
36
+ expires,
37
+ });
38
+ const fileUploadAdapter = getFileAdapter();
39
+ const rawUrl = await fileUploadAdapter.createDownloadURL(uploadedFile);
40
+ if (!rawUrl)
41
+ throw new Error('Could not create download URL for file');
42
+ const url = context.modules.files.normalizeUrl(rawUrl, {});
43
+ return Response.json({ fileId: uploadedFile._id, url, expires: expires.toISOString() }, { status: 200 });
44
+ }
45
+ catch (e) {
46
+ logger.error(e);
47
+ return Response.json({ name: e.name, code: e.code, message: e.message }, { status: 503 });
48
+ }
49
+ }
@@ -0,0 +1,3 @@
1
+ import type { IPlugin } from '@unchainedshop/core';
2
+ export declare const TempUploadPlugin: IPlugin;
3
+ export default TempUploadPlugin;
@@ -0,0 +1,15 @@
1
+ import { tempUploadHandler } from "./api.js";
2
+ const { TEMP_UPLOAD_API_PATH = '/temp-upload' } = process.env;
3
+ export const TempUploadPlugin = {
4
+ key: 'shop.unchained.files.temp-upload',
5
+ label: 'Temp Upload Plugin',
6
+ version: '1.0.0',
7
+ routes: [
8
+ {
9
+ path: TEMP_UPLOAD_API_PATH,
10
+ method: 'POST',
11
+ handler: tempUploadHandler,
12
+ },
13
+ ],
14
+ };
15
+ export default TempUploadPlugin;
@@ -0,0 +1,2 @@
1
+ import { type IFilterAdapter } from '@unchainedshop/core';
2
+ export declare const LocalSearch: IFilterAdapter;
@@ -0,0 +1,56 @@
1
+ import { FilterAdapter } from '@unchainedshop/core';
2
+ export const LocalSearch = {
3
+ ...FilterAdapter,
4
+ key: 'shop.unchained.filters.local-search',
5
+ label: 'Simple Fulltext search with MongoDB',
6
+ version: '1.0.0',
7
+ orderIndex: 10,
8
+ actions: (params) => {
9
+ return {
10
+ ...FilterAdapter.actions(params),
11
+ searchProducts: async ({ productIds }) => {
12
+ const { queryString } = params.searchQuery;
13
+ if (!queryString)
14
+ return productIds;
15
+ const products = await params.modules.products.texts.findTexts({
16
+ queryString,
17
+ productIds: productIds ? [...new Set(productIds)] : undefined,
18
+ }, {
19
+ projection: {
20
+ productId: 1,
21
+ },
22
+ });
23
+ return products.map(({ productId }) => productId);
24
+ },
25
+ searchAssortments: async ({ assortmentIds }) => {
26
+ const { queryString } = params.searchQuery;
27
+ if (!queryString) {
28
+ return assortmentIds;
29
+ }
30
+ const assortments = await params.modules.assortments.texts.findTexts({
31
+ queryString,
32
+ assortmentIds,
33
+ }, {
34
+ projection: {
35
+ assortmentId: 1,
36
+ },
37
+ });
38
+ return assortments.map(({ assortmentId }) => assortmentId);
39
+ },
40
+ async transformFilterSelector(last) {
41
+ const { queryString, filterIds, includeInactive } = params.searchQuery;
42
+ if (queryString && !filterIds) {
43
+ const selector = { ...last };
44
+ if (selector?.key) {
45
+ delete selector.key;
46
+ }
47
+ if (!includeInactive) {
48
+ selector.isActive = true;
49
+ }
50
+ return selector;
51
+ }
52
+ return last;
53
+ },
54
+ };
55
+ },
56
+ };
@@ -0,0 +1,4 @@
1
+ import { type IPlugin } from '@unchainedshop/core';
2
+ export declare const LocalSearchPlugin: IPlugin;
3
+ export default LocalSearchPlugin;
4
+ export { LocalSearch } from './adapter.ts';
@@ -0,0 +1,16 @@
1
+ import {} from '@unchainedshop/core';
2
+ import { LocalSearch } from "./adapter.js";
3
+ import { isDocumentDBCompatModeEnabled } from '@unchainedshop/mongodb';
4
+ export const LocalSearchPlugin = {
5
+ key: 'shop.unchained.filters.local-search',
6
+ label: 'Local Search Filter Plugin',
7
+ version: '1.0.0',
8
+ adapters: [LocalSearch],
9
+ onRegister: () => {
10
+ if (isDocumentDBCompatModeEnabled()) {
11
+ throw new Error('Full-text search queries have been disabled due to DocumentDB compatibility mode (env UNCHAINED_DOCUMENTDB_COMPAT_MODE is trueish)');
12
+ }
13
+ },
14
+ };
15
+ export default LocalSearchPlugin;
16
+ export { LocalSearch } from "./adapter.js";
@@ -0,0 +1,2 @@
1
+ import { type IFilterAdapter } from '@unchainedshop/core';
2
+ export declare const StrictQualFilter: IFilterAdapter;
@@ -0,0 +1,23 @@
1
+ import { FilterAdapter } from '@unchainedshop/core';
2
+ export 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
+ };
@@ -0,0 +1,4 @@
1
+ import { type IPlugin } from '@unchainedshop/core';
2
+ export declare const StrictQualFilterPlugin: IPlugin;
3
+ export default StrictQualFilterPlugin;
4
+ export { StrictQualFilter } from './adapter.ts';
@@ -0,0 +1,10 @@
1
+ import {} from '@unchainedshop/core';
2
+ import { StrictQualFilter } from "./adapter.js";
3
+ export const StrictQualFilterPlugin = {
4
+ key: 'shop.unchained.filters.strict-qual',
5
+ label: 'Strict Equal Filter Plugin',
6
+ version: '1.0.0',
7
+ adapters: [StrictQualFilter],
8
+ };
9
+ export default StrictQualFilterPlugin;
10
+ export { StrictQualFilter } from "./adapter.js";
@@ -0,0 +1,2 @@
1
+ import type { UnchainedCore } from '@unchainedshop/core';
2
+ export declare function appleIAPWebhookHandler(request: Request, context: UnchainedCore): Promise<Response>;
@@ -0,0 +1,119 @@
1
+ import { createLogger } from '@unchainedshop/logger';
2
+ import { EnrollmentStatus } from '@unchainedshop/core-enrollments';
3
+ import { fixPeriods } from "./fix-periods.js";
4
+ const logger = createLogger('unchained:apple-iap');
5
+ const { APPLE_IAP_SHARED_SECRET } = process.env;
6
+ const AppleNotificationTypes = {
7
+ INITIAL_BUY: 'INITIAL_BUY',
8
+ DID_RECOVER: 'DID_RECOVER',
9
+ DID_CHANGE_RENEWAL_STATUS: 'DID_CHANGE_RENEWAL_STATUS',
10
+ DID_FAIL_TO_RENEW: 'DID_FAIL_TO_RENEW',
11
+ DID_CHANGE_RENEWAL_PREF: 'DID_CHANGE_RENEWAL_PREF',
12
+ };
13
+ export async function appleIAPWebhookHandler(request, context) {
14
+ try {
15
+ const { modules, services } = context;
16
+ const responseBody = await request.json();
17
+ if (responseBody.password !== APPLE_IAP_SHARED_SECRET) {
18
+ logger.warn('Apple IAP Webhook: Invalid shared secret');
19
+ return new Response(JSON.stringify({
20
+ error: 'Invalid shared secret',
21
+ name: 'AuthenticationError',
22
+ code: 'INVALID_SECRET',
23
+ }), {
24
+ status: 403,
25
+ headers: { 'Content-Type': 'application/json' },
26
+ });
27
+ }
28
+ const transactions = responseBody?.unified_receipt?.latest_receipt_info;
29
+ const latestTransaction = transactions?.[0];
30
+ if (!latestTransaction) {
31
+ throw new Error('No transaction found in receipt');
32
+ }
33
+ if (responseBody.notification_type === AppleNotificationTypes.INITIAL_BUY) {
34
+ const orderPayment = await modules.orders.payments.findOrderPaymentByTransactionId(latestTransaction.transaction_id);
35
+ if (!orderPayment) {
36
+ throw new Error('Could not find any matching order payment');
37
+ }
38
+ const order = await services.orders.checkoutOrder(orderPayment.orderId, {
39
+ paymentContext: {
40
+ receiptData: responseBody?.unified_receipt?.latest_receipt,
41
+ },
42
+ });
43
+ if (!order) {
44
+ throw new Error(`Order with id ${orderPayment.orderId} not found`);
45
+ }
46
+ const orderId = order._id;
47
+ const enrollment = await modules.enrollments.findEnrollment({
48
+ orderId,
49
+ });
50
+ if (!enrollment) {
51
+ throw new Error('Could not find any matching enrollment');
52
+ }
53
+ await fixPeriods({
54
+ transactionId: latestTransaction.original_transaction_id,
55
+ transactions,
56
+ enrollmentId: enrollment._id,
57
+ orderId,
58
+ }, context);
59
+ logger.info(`Apple IAP Webhook: Confirmed checkout for order ${order.orderNumber}`, {
60
+ orderId: order._id,
61
+ });
62
+ }
63
+ else {
64
+ const originalOrderPayment = await modules.orders.payments.findOrderPaymentByTransactionId(latestTransaction.original_transaction_id);
65
+ if (!originalOrderPayment) {
66
+ throw new Error('Could not find any matching order payment');
67
+ }
68
+ const originalOrder = await modules.orders.findOrder({
69
+ orderId: originalOrderPayment.orderId,
70
+ });
71
+ const enrollment = originalOrder &&
72
+ (await modules.enrollments.findEnrollment({
73
+ orderId: originalOrder._id,
74
+ }));
75
+ if (!enrollment?.payment) {
76
+ throw new Error('Could not find a valid enrollment payment method');
77
+ }
78
+ await services.orders.registerPaymentCredentials(enrollment.payment.paymentProviderId, {
79
+ transactionContext: {
80
+ receiptData: responseBody?.unified_receipt?.latest_receipt,
81
+ },
82
+ userId: enrollment.userId,
83
+ });
84
+ await fixPeriods({
85
+ transactionId: latestTransaction.original_transaction_id,
86
+ transactions,
87
+ enrollmentId: enrollment._id,
88
+ orderId: originalOrder._id,
89
+ }, context);
90
+ logger.info(`Apple IAP Webhook: Processed notification for ${latestTransaction.original_transaction_id} and type ${responseBody.notification_type}`);
91
+ if (responseBody.notification_type === AppleNotificationTypes.DID_RECOVER) {
92
+ if (enrollment.status !== EnrollmentStatus.TERMINATED &&
93
+ responseBody.auto_renew_status === 'false') {
94
+ await services.enrollments.terminateEnrollment(enrollment);
95
+ }
96
+ }
97
+ if (responseBody.notification_type === AppleNotificationTypes.DID_CHANGE_RENEWAL_STATUS) {
98
+ if (enrollment.status !== EnrollmentStatus.TERMINATED &&
99
+ responseBody.auto_renew_status === 'false') {
100
+ await services.enrollments.terminateEnrollment(enrollment);
101
+ }
102
+ }
103
+ logger.info('Apple IAP Webhook: Updated enrollment from Apple');
104
+ }
105
+ return new Response(null, { status: 200 });
106
+ }
107
+ catch (error) {
108
+ logger.error('Apple IAP Webhook error:', error);
109
+ return new Response(JSON.stringify({
110
+ success: false,
111
+ error: error.message,
112
+ name: error.name,
113
+ code: error.code,
114
+ }), {
115
+ status: 503,
116
+ headers: { 'Content-Type': 'application/json' },
117
+ });
118
+ }
119
+ }
@@ -0,0 +1,2 @@
1
+ import { type IPaymentAdapter } from '@unchainedshop/core';
2
+ export declare const Cryptopay: IPaymentAdapter;
@@ -0,0 +1,157 @@
1
+ import { PaymentAdapter, PaymentError, OrderPricingSheet, } from '@unchainedshop/core';
2
+ import { createLogger } from '@unchainedshop/logger';
3
+ import {} from "./module.js";
4
+ import deriveBtcAddress from "./derive-btc-address.js";
5
+ import deriveEthAddress from "./derive-eth-address.js";
6
+ import denoteAmount from "./denote-amount.js";
7
+ const logger = createLogger('unchained:cryptopay');
8
+ const { CRYPTOPAY_SECRET, CRYPTOPAY_BTC_XPUB, CRYPTOPAY_ETH_XPUB } = process.env;
9
+ const CryptopayCurrencies = {
10
+ BTC: 'BTC',
11
+ ETH: 'ETH',
12
+ };
13
+ export const Cryptopay = {
14
+ ...PaymentAdapter,
15
+ key: 'shop.unchained.payment.cryptopay',
16
+ label: 'Cryptopay',
17
+ version: '1.0.0',
18
+ typeSupported(type) {
19
+ return type === 'GENERIC';
20
+ },
21
+ actions: (config, context) => {
22
+ const { modules } = context;
23
+ const setConversionRates = async (currencyCode, existingAddresses) => {
24
+ const originCurrencyObj = await modules.currencies.findCurrency({ isoCode: currencyCode });
25
+ if (!originCurrencyObj?.isActive)
26
+ throw new Error('Origin currency is not supported');
27
+ const updatedAddresses = await Promise.all(existingAddresses.map(async (addressData) => {
28
+ const targetCurrencyObj = await modules.currencies.findCurrency({
29
+ isoCode: addressData.currencyCode,
30
+ });
31
+ if (!targetCurrencyObj?.isActive)
32
+ return null;
33
+ const rateData = await modules.products.prices.rates.getRate(originCurrencyObj, targetCurrencyObj);
34
+ return {
35
+ ...addressData,
36
+ currencyConversionRate: rateData?.rate,
37
+ currencyConversionExpiryDate: rateData?.expiresAt,
38
+ };
39
+ }));
40
+ return updatedAddresses.filter(Boolean);
41
+ };
42
+ const updateTransactionsWithOrderPaymentId = async (orderPaymentId, cryptoAddresses) => {
43
+ for (const cryptoAddress of cryptoAddresses) {
44
+ const { address, currencyCode } = cryptoAddress;
45
+ await modules.cryptopay.mapOrderPaymentToWalletAddress({
46
+ address,
47
+ currencyCode,
48
+ orderPaymentId,
49
+ });
50
+ }
51
+ };
52
+ const adapterActions = {
53
+ ...PaymentAdapter.actions(config, context),
54
+ configurationError() {
55
+ if (!CRYPTOPAY_SECRET) {
56
+ return PaymentError.INCOMPLETE_CONFIGURATION;
57
+ }
58
+ if (!CRYPTOPAY_BTC_XPUB && !CRYPTOPAY_ETH_XPUB) {
59
+ return PaymentError.INCOMPLETE_CONFIGURATION;
60
+ }
61
+ return null;
62
+ },
63
+ isActive() {
64
+ if (adapterActions.configurationError() !== null)
65
+ return false;
66
+ if (!context.order)
67
+ return true;
68
+ return true;
69
+ },
70
+ isPayLaterAllowed() {
71
+ return false;
72
+ },
73
+ sign: async () => {
74
+ const { orderPayment, order } = context;
75
+ if (!orderPayment)
76
+ throw new Error('Payment Credential Registration is not yet supported for Cryptopay');
77
+ if (!order)
78
+ throw new Error('Order context is required for signing with Cryptopay');
79
+ const existingAddresses = await modules.cryptopay.getWalletAddressesByOrderPaymentId(orderPayment._id);
80
+ if (existingAddresses.length) {
81
+ const existingAddressesWithNewExpiration = await setConversionRates(order.currencyCode, existingAddresses.map(({ _id, currencyCode }) => ({
82
+ address: _id,
83
+ currencyCode,
84
+ })));
85
+ return JSON.stringify(existingAddressesWithNewExpiration);
86
+ }
87
+ const cryptoAddresses = [];
88
+ if (CRYPTOPAY_BTC_XPUB) {
89
+ const btcDerivationNumber = await modules.cryptopay.getNextDerivationNumber(CryptopayCurrencies.BTC);
90
+ const address = deriveBtcAddress(CRYPTOPAY_BTC_XPUB, btcDerivationNumber);
91
+ cryptoAddresses.push({
92
+ currencyCode: CryptopayCurrencies.BTC,
93
+ address,
94
+ });
95
+ }
96
+ if (CRYPTOPAY_ETH_XPUB) {
97
+ const ethDerivationNumber = await modules.cryptopay.getNextDerivationNumber(CryptopayCurrencies.ETH);
98
+ const address = deriveEthAddress(CRYPTOPAY_ETH_XPUB, ethDerivationNumber);
99
+ cryptoAddresses.push({
100
+ currencyCode: CryptopayCurrencies.ETH,
101
+ address,
102
+ });
103
+ }
104
+ const cryptoAddressesWithExpiration = await setConversionRates(order.currencyCode, cryptoAddresses);
105
+ await updateTransactionsWithOrderPaymentId(orderPayment._id, cryptoAddresses);
106
+ return JSON.stringify(cryptoAddressesWithExpiration);
107
+ },
108
+ charge: async () => {
109
+ const { order, orderPayment } = context;
110
+ if (!orderPayment)
111
+ throw new Error('Order Payment context is required for charging with Cryptopay');
112
+ if (!order)
113
+ throw new Error('Order context is required for charging with Cryptopay');
114
+ const foundWalletsWithBalances = await modules.cryptopay.getWalletAddressesByOrderPaymentId(orderPayment._id);
115
+ const walletForOrderPayment = foundWalletsWithBalances.find((wallet) => BigInt(wallet.amount.toString()) > 0n);
116
+ const pricing = OrderPricingSheet({
117
+ calculation: order.calculation,
118
+ currencyCode: order.currencyCode,
119
+ });
120
+ const totalAmount = BigInt(pricing?.total({ useNetPrice: false }).amount);
121
+ if (walletForOrderPayment && walletForOrderPayment.currencyCode !== order.currencyCode) {
122
+ const baseCurrency = await modules.currencies.findCurrency({
123
+ isoCode: walletForOrderPayment.currencyCode,
124
+ });
125
+ if (!baseCurrency)
126
+ throw new Error('Base currency not found');
127
+ const quoteCurrency = await modules.currencies.findCurrency({ isoCode: order.currencyCode });
128
+ if (!quoteCurrency)
129
+ throw new Error('Quote currency not found');
130
+ const rate = await modules.products.prices.rates.getRateRange(baseCurrency, quoteCurrency, order.confirmed || new Date());
131
+ if (!rate)
132
+ throw new Error('No conversion rate found');
133
+ const convertedAmount = denoteAmount(walletForOrderPayment.amount.toString(), walletForOrderPayment.decimals);
134
+ const maxAmount = parseFloat(convertedAmount.toString()) * rate.max * 1.001;
135
+ if (maxAmount && maxAmount >= totalAmount) {
136
+ return {
137
+ transactionId: walletForOrderPayment._id,
138
+ };
139
+ }
140
+ logger.info(`Cryptopay Plugin: Wallet ${walletForOrderPayment._id} balance too low (yet): ${maxAmount} < ${totalAmount}`);
141
+ }
142
+ if (walletForOrderPayment) {
143
+ const convertedAmount = denoteAmount(walletForOrderPayment.amount.toString(), walletForOrderPayment.decimals);
144
+ if (convertedAmount && convertedAmount >= totalAmount) {
145
+ return {
146
+ transactionId: walletForOrderPayment._id,
147
+ };
148
+ }
149
+ logger.info(`Cryptopay Plugin: Wallet ${walletForOrderPayment._id} balance too low (yet): ${convertedAmount} < ${totalAmount}`);
150
+ }
151
+ logger.info(`Cryptopay Plugin: No confirmed payments found for currency ${order.currencyCode} and addresses ${JSON.stringify(foundWalletsWithBalances)}`);
152
+ return false;
153
+ },
154
+ };
155
+ return adapterActions;
156
+ },
157
+ };
@@ -0,0 +1,2 @@
1
+ import type { UnchainedCore } from '@unchainedshop/core';
2
+ export declare function cryptopayWebhookHandler(request: Request, context: UnchainedCore): Promise<Response>;
@@ -0,0 +1,23 @@
1
+ import { createLogger } from '@unchainedshop/logger';
2
+ import handleWebhook from "./handle-webhook.js";
3
+ const logger = createLogger('unchained:cryptopay');
4
+ export async function cryptopayWebhookHandler(request, context) {
5
+ try {
6
+ const body = await request.json();
7
+ await handleWebhook(body, context);
8
+ return new Response(JSON.stringify({ success: true }), {
9
+ status: 200,
10
+ headers: { 'Content-Type': 'application/json' },
11
+ });
12
+ }
13
+ catch (error) {
14
+ logger.error('Cryptopay webhook error:', error);
15
+ return new Response(JSON.stringify({
16
+ success: false,
17
+ error: error.message,
18
+ }), {
19
+ status: 500,
20
+ headers: { 'Content-Type': 'application/json' },
21
+ });
22
+ }
23
+ }
@@ -0,0 +1,2 @@
1
+ import { type IPaymentAdapter } from '@unchainedshop/core';
2
+ export declare const Datatrans: IPaymentAdapter;