@unchainedshop/plugins 1.2.40 → 2.0.0-beta2

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 (99) hide show
  1. package/lib/files/gridfs/gridfs-webhook.js +1 -1
  2. package/lib/files/gridfs/gridfs-webhook.js.map +1 -1
  3. package/lib/files/minio/minio-webhook.js +1 -1
  4. package/lib/files/minio/minio-webhook.js.map +1 -1
  5. package/lib/payment/apple-iap/adapter.js +1 -1
  6. package/lib/payment/apple-iap/adapter.js.map +1 -1
  7. package/lib/payment/bity/adapter.js +2 -2
  8. package/lib/payment/bity/adapter.js.map +1 -1
  9. package/lib/payment/cryptopay/db/CryptopayTransactions.d.ts +13 -0
  10. package/lib/payment/cryptopay/db/CryptopayTransactions.js +4 -0
  11. package/lib/payment/cryptopay/db/CryptopayTransactions.js.map +1 -0
  12. package/lib/payment/cryptopay/index.d.ts +5 -0
  13. package/lib/payment/cryptopay/index.js +6 -0
  14. package/lib/payment/cryptopay/index.js.map +1 -0
  15. package/lib/payment/{cryptopay.d.ts → cryptopay/middleware.d.ts} +0 -0
  16. package/lib/payment/cryptopay/middleware.js +79 -0
  17. package/lib/payment/cryptopay/middleware.js.map +1 -0
  18. package/lib/payment/cryptopay/module/configureCryptopayModule.d.ts +25 -0
  19. package/lib/payment/cryptopay/module/configureCryptopayModule.js +75 -0
  20. package/lib/payment/cryptopay/module/configureCryptopayModule.js.map +1 -0
  21. package/lib/payment/cryptopay/plugin.d.ts +3 -0
  22. package/lib/payment/cryptopay/plugin.js +201 -0
  23. package/lib/payment/cryptopay/plugin.js.map +1 -0
  24. package/lib/payment/datatrans-v2/middleware.js +1 -1
  25. package/lib/payment/datatrans-v2/middleware.js.map +1 -1
  26. package/lib/payment/postfinance-checkout/middleware.js +1 -1
  27. package/lib/payment/postfinance-checkout/middleware.js.map +1 -1
  28. package/lib/pricing/product-catalog-price.js.map +1 -1
  29. package/lib/pricing/product-price-rateconversion.js +23 -17
  30. package/lib/pricing/product-price-rateconversion.js.map +1 -1
  31. package/lib/pricing/product-price-round.js +3 -4
  32. package/lib/pricing/product-price-round.js.map +1 -1
  33. package/lib/pricing/utils/cache.d.ts +2 -1
  34. package/lib/warehousing/google-sheets.js +3 -2
  35. package/lib/warehousing/google-sheets.js.map +1 -1
  36. package/lib/{pricing/product-price-ECB-interbank-exchange.d.ts → warehousing/infinite-minter.d.ts} +0 -0
  37. package/lib/warehousing/infinite-minter.js +50 -0
  38. package/lib/warehousing/infinite-minter.js.map +1 -0
  39. package/lib/warehousing/store.js +2 -1
  40. package/lib/warehousing/store.js.map +1 -1
  41. package/lib/worker/BulkImportWorker.js +2 -3
  42. package/lib/worker/BulkImportWorker.js.map +1 -1
  43. package/lib/worker/ZombieKillerWorker.js +2 -3
  44. package/lib/worker/ZombieKillerWorker.js.map +1 -1
  45. package/lib/worker/email.js.map +1 -1
  46. package/lib/worker/export-token.d.ts +5 -0
  47. package/lib/worker/export-token.js +26 -0
  48. package/lib/worker/export-token.js.map +1 -0
  49. package/lib/worker/external.js +1 -0
  50. package/lib/worker/external.js.map +1 -1
  51. package/lib/{pricing/product-price-coinbase-exchange.d.ts → worker/update-coinbase-rates.d.ts} +0 -0
  52. package/lib/worker/update-coinbase-rates.js +69 -0
  53. package/lib/worker/update-coinbase-rates.js.map +1 -0
  54. package/lib/worker/update-ecb-rates.d.ts +1 -0
  55. package/lib/worker/update-ecb-rates.js +79 -0
  56. package/lib/worker/update-ecb-rates.js.map +1 -0
  57. package/lib/worker/update-token-ownership.d.ts +6 -0
  58. package/lib/worker/update-token-ownership.js +66 -0
  59. package/lib/worker/update-token-ownership.js.map +1 -0
  60. package/package.json +22 -19
  61. package/src/files/gridfs/gridfs-webhook.ts +1 -1
  62. package/src/files/minio/minio-webhook.js +1 -1
  63. package/src/payment/apple-iap/adapter.ts +1 -1
  64. package/src/payment/bity/adapter.ts +2 -2
  65. package/src/payment/cryptopay/db/CryptopayTransactions.ts +17 -0
  66. package/src/payment/cryptopay/index.ts +8 -0
  67. package/src/payment/cryptopay/middleware.ts +95 -0
  68. package/src/payment/cryptopay/module/configureCryptopayModule.ts +133 -0
  69. package/src/payment/cryptopay/plugin.ts +289 -0
  70. package/src/payment/cryptopay/readme.md +1 -0
  71. package/src/payment/datatrans-v2/middleware.ts +1 -1
  72. package/src/payment/postfinance-checkout/middleware.ts +1 -1
  73. package/src/pricing/product-catalog-price.ts +1 -0
  74. package/src/pricing/product-price-rateconversion.ts +25 -28
  75. package/src/pricing/product-price-round.ts +4 -5
  76. package/src/warehousing/google-sheets.ts +4 -2
  77. package/src/warehousing/infinite-minter.ts +66 -0
  78. package/src/warehousing/store.ts +3 -1
  79. package/src/worker/BulkImportWorker.ts +2 -3
  80. package/src/worker/ZombieKillerWorker.ts +2 -3
  81. package/src/worker/email.ts +0 -1
  82. package/src/worker/export-token.ts +33 -0
  83. package/src/worker/external.ts +1 -0
  84. package/src/worker/update-coinbase-rates.ts +91 -0
  85. package/src/worker/update-ecb-rates.ts +97 -0
  86. package/src/worker/update-token-ownership.ts +85 -0
  87. package/lib/payment/cryptopay.js +0 -161
  88. package/lib/payment/cryptopay.js.map +0 -1
  89. package/lib/pricing/product-price-ECB-interbank-exchange.js +0 -90
  90. package/lib/pricing/product-price-ECB-interbank-exchange.js.map +0 -1
  91. package/lib/pricing/product-price-coinbase-exchange.js +0 -60
  92. package/lib/pricing/product-price-coinbase-exchange.js.map +0 -1
  93. package/lib/pricing/product-price-cryptopay.d.ts +0 -2
  94. package/lib/pricing/product-price-cryptopay.js +0 -23
  95. package/lib/pricing/product-price-cryptopay.js.map +0 -1
  96. package/src/payment/cryptopay.ts +0 -197
  97. package/src/pricing/product-price-ECB-interbank-exchange.ts +0 -108
  98. package/src/pricing/product-price-coinbase-exchange.ts +0 -82
  99. package/src/pricing/product-price-cryptopay.ts +0 -27
@@ -0,0 +1,289 @@
1
+ import { IPaymentAdapter } from '@unchainedshop/types/payments';
2
+ import { PaymentAdapter, PaymentDirector, PaymentError } from '@unchainedshop/core-payment';
3
+ import { ethers } from 'ethers';
4
+ import BIP32Factory from 'bip32';
5
+ import * as ecc from 'tiny-secp256k1';
6
+ import * as bitcoin from 'bitcoinjs-lib';
7
+ import { createLogger } from '@unchainedshop/logger';
8
+ import { UnchainedCore } from '@unchainedshop/types/core';
9
+ import { OrderPricingSheet } from '@unchainedshop/core-orders';
10
+ import { CryptopayModule } from './module/configureCryptopayModule';
11
+
12
+ const logger = createLogger('unchained:core-payment:cryptopay');
13
+
14
+ const {
15
+ CRYPTOPAY_SECRET,
16
+ CRYPTOPAY_BTC_XPUB,
17
+ CRYPTOPAY_ETH_XPUB,
18
+ CRYPTOPAY_BTC_TESTNET = false,
19
+ CRYPTOPAY_DERIVATION_START = '0',
20
+ } = process.env;
21
+
22
+ enum CryptopayCurrencies { // eslint-disable-line
23
+ BTC = 'BTC',
24
+ ETH = 'ETH',
25
+ }
26
+
27
+ type CryptopayAddress = {
28
+ currency: CryptopayCurrencies;
29
+ address: string;
30
+ currencyConversionRate?: number;
31
+ currencyConversionExpiryDate?: Date;
32
+ };
33
+
34
+ const getDerivationPath = (currency: CryptopayCurrencies, index: number): string => {
35
+ const address = `${(parseInt(CRYPTOPAY_DERIVATION_START, 10) || 0) + index}`;
36
+ if (currency === CryptopayCurrencies.ETH) {
37
+ const pathComponents = ethers.utils.defaultPath.split('/');
38
+ pathComponents[pathComponents.length - 1] = address;
39
+ return pathComponents.join('/');
40
+ }
41
+ if (currency === CryptopayCurrencies.BTC) {
42
+ return `m/44'/0'/0'/0/${address}`;
43
+ }
44
+ return `0/${address}`;
45
+ };
46
+
47
+ const Cryptopay: IPaymentAdapter = {
48
+ ...PaymentAdapter,
49
+
50
+ key: 'shop.unchained.payment.cryptopay',
51
+ label: 'Cryptopay',
52
+ version: '1.1',
53
+
54
+ typeSupported(type) {
55
+ return type === 'GENERIC';
56
+ },
57
+
58
+ actions: (params) => {
59
+ const modules = params.context.modules as UnchainedCore['modules'] & { cryptopay: CryptopayModule };
60
+
61
+ const setConversionRates = async (currencyCode: string, existingAddresses: any[]) => {
62
+ const originCurrencyObj = await modules.currencies.findCurrency({ isoCode: currencyCode });
63
+ const updatedAddresses = await Promise.all(
64
+ existingAddresses.map(async (addressData) => {
65
+ const targetCurrencyObj = await modules.currencies.findCurrency({
66
+ isoCode: addressData.currency,
67
+ });
68
+ if (!targetCurrencyObj.isActive) return null;
69
+ const rateData = await modules.products.prices.rates.getRate(
70
+ originCurrencyObj,
71
+ targetCurrencyObj,
72
+ );
73
+ return {
74
+ ...addressData,
75
+ currencyConversionRate: rateData?.rate,
76
+ currencyConversionExpiryDate: rateData?.expiresAt,
77
+ };
78
+ }),
79
+ );
80
+ return updatedAddresses.filter(Boolean);
81
+ };
82
+
83
+ const updateTransactionsWithOrderPaymentId = async (
84
+ orderPaymentId: string,
85
+ cryptoAddresses: CryptopayAddress[],
86
+ ) => {
87
+ return Promise.all(
88
+ cryptoAddresses.map(async ({ address, currency }) =>
89
+ modules.cryptopay.mapOrderPaymentToWalletAddress({
90
+ addressId: address,
91
+ contract: null,
92
+ currency,
93
+ orderPaymentId,
94
+ }),
95
+ ),
96
+ );
97
+ };
98
+
99
+ const adapterActions = {
100
+ ...PaymentAdapter.actions(params),
101
+
102
+ // eslint-disable-next-line
103
+ configurationError() {
104
+ // eslint-disable-line
105
+ if (!CRYPTOPAY_SECRET) {
106
+ return PaymentError.INCOMPLETE_CONFIGURATION;
107
+ }
108
+ if (!CRYPTOPAY_BTC_XPUB && !CRYPTOPAY_ETH_XPUB) {
109
+ return PaymentError.INCOMPLETE_CONFIGURATION;
110
+ }
111
+ return null;
112
+ },
113
+
114
+ isActive() {
115
+ // Only support orders that have prices in BTC or ETH for the moment
116
+ if (adapterActions.configurationError() !== null) return false;
117
+ if (!params.paymentContext.order) return true;
118
+ // if (
119
+ // !Object.values(CryptopayCurrencies).includes(
120
+ // params.paymentContext.order.currency as CryptopayCurrencies,
121
+ // )
122
+ // )
123
+ // return false;
124
+ return true;
125
+ },
126
+
127
+ isPayLaterAllowed() {
128
+ return false;
129
+ },
130
+
131
+ sign: async () => {
132
+ const { orderPayment, order } = params.paymentContext;
133
+
134
+ const existingAddresses = await modules.cryptopay.getWalletAddressesByOrderPaymentId(
135
+ orderPayment._id,
136
+ );
137
+ if (existingAddresses.length) {
138
+ // Do not derive addresses a second time for order payment, return existing addresses
139
+ const existingAddressesWithNewExpiration = await setConversionRates(
140
+ order.currency,
141
+ existingAddresses.map(
142
+ ({ _id, currency }) =>
143
+ ({
144
+ address: _id,
145
+ currency,
146
+ } as CryptopayAddress),
147
+ ),
148
+ );
149
+ return JSON.stringify(existingAddressesWithNewExpiration);
150
+ }
151
+
152
+ const cryptoAddresses: CryptopayAddress[] = [];
153
+ if (CRYPTOPAY_BTC_XPUB) {
154
+ const network = CRYPTOPAY_BTC_TESTNET ? bitcoin.networks.testnet : bitcoin.networks.bitcoin;
155
+ const bip32 = BIP32Factory(ecc);
156
+ const hardenedMaster = bip32.fromBase58(CRYPTOPAY_BTC_XPUB, network);
157
+ const btcDerivationNumber = await modules.cryptopay.getNextDerivationNumber(
158
+ CryptopayCurrencies.BTC,
159
+ );
160
+ const derivationPath = getDerivationPath(CryptopayCurrencies.BTC, btcDerivationNumber);
161
+ const child = hardenedMaster.derivePath(derivationPath);
162
+ cryptoAddresses.push({
163
+ currency: CryptopayCurrencies.BTC,
164
+ address: bitcoin.payments.p2pkh({
165
+ pubkey: child.publicKey,
166
+ network,
167
+ }).address,
168
+ });
169
+ }
170
+ if (CRYPTOPAY_ETH_XPUB) {
171
+ // we neuter for security reasons, it's still quite complicated for most ethereum clients to show an appropriate
172
+ // xpub, that's why
173
+ const hardenedMaster = ethers.utils.HDNode.fromExtendedKey(CRYPTOPAY_ETH_XPUB).neuter();
174
+ const ethDerivationNumber = await modules.cryptopay.getNextDerivationNumber(
175
+ CryptopayCurrencies.ETH,
176
+ );
177
+ const derivationPath = getDerivationPath(CryptopayCurrencies.ETH, ethDerivationNumber);
178
+ const cleanedForHD = derivationPath.split('/').slice(-2).join('/');
179
+ cryptoAddresses.push({
180
+ currency: CryptopayCurrencies.ETH,
181
+ address: hardenedMaster.derivePath(cleanedForHD).address,
182
+ });
183
+ }
184
+
185
+ const cryptoAddressesWithExpiration = await setConversionRates(order.currency, cryptoAddresses);
186
+ await updateTransactionsWithOrderPaymentId(orderPayment._id, cryptoAddresses);
187
+ return JSON.stringify(cryptoAddressesWithExpiration);
188
+ },
189
+
190
+ charge: async () => {
191
+ const { order, orderPayment } = params.paymentContext;
192
+
193
+ const foundWalletsWithBalances = await modules.cryptopay.getWalletAddressesByOrderPaymentId(
194
+ orderPayment._id,
195
+ );
196
+
197
+ const walletForOrderPayment = foundWalletsWithBalances.find(
198
+ (wallet) => BigInt(wallet.amount.toString()) > 0n,
199
+ );
200
+
201
+ const pricing = OrderPricingSheet({
202
+ calculation: order.calculation,
203
+ currency: order.currency,
204
+ });
205
+ const totalAmount = BigInt(pricing?.total({ useNetPrice: false }).amount);
206
+
207
+ if (walletForOrderPayment.currency !== order.currency) {
208
+ const baseCurrency = await modules.currencies.findCurrency({
209
+ isoCode: walletForOrderPayment.currency,
210
+ });
211
+ const quoteCurrency = await modules.currencies.findCurrency({ isoCode: order.currency });
212
+
213
+ const { /* min, */ max } = await modules.products.prices.rates.getRateRange(
214
+ baseCurrency,
215
+ quoteCurrency,
216
+ order.confirmed || new Date(),
217
+ );
218
+
219
+ const convertedAmount =
220
+ BigInt(walletForOrderPayment.amount.toString()) /
221
+ 10n ** (BigInt(walletForOrderPayment.decimals) - 9n);
222
+
223
+ // Add a Promille 🍻
224
+ // const minAmount = parseFloat(convertedAmount.toString()) * min * 0.999;
225
+ const maxAmount = parseFloat(convertedAmount.toString()) * max * 1.001;
226
+
227
+ if (maxAmount && maxAmount >= totalAmount) {
228
+ // Enough sent
229
+ return {
230
+ transactionId: walletForOrderPayment._id,
231
+ };
232
+ }
233
+ logger.info(
234
+ `Cryptopay Plugin: Wallet ${walletForOrderPayment._id} balance too low (yet): ${maxAmount} < ${totalAmount}`,
235
+ );
236
+ }
237
+
238
+ if (walletForOrderPayment) {
239
+ // Hack: Downgrade to 8 decimals
240
+ const convertedAmount =
241
+ BigInt(walletForOrderPayment.amount.toString()) /
242
+ 10n ** (BigInt(walletForOrderPayment.decimals) - 9n); // All crypto native prices denoted with 8 decimals
243
+
244
+ if (convertedAmount && convertedAmount >= totalAmount) {
245
+ return {
246
+ transactionId: walletForOrderPayment._id,
247
+ };
248
+ }
249
+ logger.info(
250
+ `Cryptopay Plugin: Wallet ${walletForOrderPayment._id} balance too low (yet): ${convertedAmount} < ${totalAmount}`,
251
+ );
252
+ }
253
+
254
+ logger.info(
255
+ `Cryptopay Plugin: No confirmed payments found for currency ${
256
+ order.currency
257
+ } and addresses ${JSON.stringify(foundWalletsWithBalances)}`,
258
+ );
259
+
260
+ /*
261
+ TODO:
262
+
263
+ 1. Check that generated crypto addresses are unique and no order payments share the same crypto address, only repeat when derivations completely exhausted.
264
+ 2. When derivation path get's close to "exhaustedness" (95%), send a special e-mail alert to tell the user should generate a new xpub.
265
+ 3. Extend the middleware to report the highest seen block number so we can calculate confirmations,
266
+ 4. Order rejection should trigger an e-mail advising the vendor to actively send back funds
267
+
268
+ To allow paying in ETH or BTC or Tokens for CHF orders, it should work like this:
269
+ - When signing, we convert with what we have, if we can convert, fine, if we can't we throw an error at the client. The found rate id will be stored together with a signature of the order total + currency + an expiration date in a special collection
270
+ - We also return that signed payload to the client so the client can show something like Total: CHF 50.- "Pay 0.0024 BTC in the next 30 minutes"
271
+ - When a payment occured, the charge function of the plugin picks up. If it can find a valid signed converted amount in the special collection, it will use that information instead of the order total to compare against.
272
+ - When a payment occured to late, the order payment will not be marked as paid and a report will be sent to the vendor that with information about how much to send back.
273
+
274
+ Refunds have to be 2nd priority:
275
+ For every case where we should "send back" money to someone, it get's difficult because we don't own the private key. On ETH, we could use permit/approve with web3 to get the permission to send those funds back. On BTC, we could use lightning invoices for the refunds but that will be fuckin compley anyway.
276
+
277
+ */
278
+
279
+ return false;
280
+ },
281
+ };
282
+
283
+ return adapterActions;
284
+ },
285
+ };
286
+
287
+ PaymentDirector.registerAdapter(Cryptopay);
288
+
289
+ export default Cryptopay;
@@ -0,0 +1 @@
1
+ curl -X POST http://localhost:4010/payment/cryptopay -d '{ "secret": "secret", "address": "0xF5F72AE7fa1fa990ebaF163208Ed7aD6a3f42DEA", "blockHeight": 7469853, "amount": "50000000000000000", "decimals": 18, "currency": "ETH"}' -H 'Content-Type: application/json'
@@ -101,7 +101,7 @@ export default (app: any) => {
101
101
  } catch (e) {
102
102
  logger.error(`Datatrans Webhook: Unchained rejected to checkout with message`, e);
103
103
  res.writeHead(500);
104
- res.end(JSON.stringify(e));
104
+ res.end(JSON.stringify({ name: e.name, code: e.code, message: e.message }));
105
105
  return;
106
106
  }
107
107
  }
@@ -43,7 +43,7 @@ export default (app) => {
43
43
  } catch (e) {
44
44
  logger.error(`PostFinance Checkout Webhook: Unchained rejected to checkout with message`, e);
45
45
  res.writeHead(500);
46
- res.end(JSON.stringify(e));
46
+ res.end(JSON.stringify({ name: e.name, code: e.code, message: e.message }));
47
47
  }
48
48
  } else {
49
49
  logger.error(
@@ -36,6 +36,7 @@ const ProductPrice: IProductPricingAdapter = {
36
36
  },
37
37
  params.context,
38
38
  ));
39
+
39
40
  const price = await modules.products.prices.price(product, { country, currency, quantity });
40
41
  if (price) {
41
42
  const itemTotal = price.amount * quantity;
@@ -1,10 +1,6 @@
1
1
  import { IProductPricingAdapter } from '@unchainedshop/types/products.pricing';
2
2
  import { ProductPricingDirector, ProductPricingAdapter } from '@unchainedshop/core-products';
3
3
 
4
- const { RATECONVERSION_MAX_AGE = '360' } = process.env;
5
-
6
- const MAX_RATE_AGE = parseInt(RATECONVERSION_MAX_AGE, 10);
7
-
8
4
  const ProductPriceRateConversion: IProductPricingAdapter = {
9
5
  ...ProductPricingAdapter,
10
6
 
@@ -19,48 +15,49 @@ const ProductPriceRateConversion: IProductPricingAdapter = {
19
15
 
20
16
  actions: (params) => {
21
17
  const pricingAdapter = ProductPricingAdapter.actions(params);
22
- const { services, modules } = params.context;
18
+ const { modules } = params.context;
23
19
  return {
24
20
  ...pricingAdapter,
25
21
 
26
22
  calculate: async () => {
27
- const { product, country, quantity, currency } = params.context;
28
- const defaultCurrency = await services.countries.resolveDefaultCurrencyCode(
29
- {
30
- isoCode: country,
31
- },
32
- params.context,
33
- );
23
+ const { product, country, quantity, currency: targetCurrency } = params.context;
24
+
25
+ const { calculation = [] } = params.calculationSheet;
26
+ if (calculation?.length) {
27
+ // If we already have a price for the product,
28
+ // we skip this plugin because we don't need to convert
29
+ return pricingAdapter.calculate();
30
+ }
34
31
 
35
32
  const productPrice = await modules.products.prices.price(product, {
36
33
  country,
37
- currency: defaultCurrency,
38
34
  quantity,
39
35
  });
40
36
 
41
- const { calculation = [] } = params.calculationSheet;
42
- if (
43
- !productPrice ||
44
- !productPrice?.amount ||
45
- calculation?.length ||
46
- defaultCurrency === currency
47
- )
37
+ if (!productPrice) {
38
+ // We were not able to find a price for quantity & country
48
39
  return pricingAdapter.calculate();
40
+ }
41
+
42
+ const fromCurrencyObj = await modules.currencies.findCurrency({
43
+ isoCode: productPrice.currencyCode,
44
+ });
45
+ const targetCurrencyObj = await modules.currencies.findCurrency({
46
+ isoCode: targetCurrency,
47
+ });
48
+
49
+ if (!targetCurrencyObj?.isActive) return pricingAdapter.calculate();
49
50
 
50
- const rate = await modules.products.prices.rates.getRate(
51
- defaultCurrency,
52
- currency,
53
- MAX_RATE_AGE,
54
- );
51
+ const rateData = await modules.products.prices.rates.getRate(fromCurrencyObj, targetCurrencyObj);
55
52
 
56
- if (rate > 0) {
57
- const convertedAmount = productPrice.amount * rate;
53
+ if (rateData?.rate > 0) {
54
+ const convertedAmount = Math.round(productPrice.amount * rateData.rate);
58
55
  pricingAdapter.resultSheet().resetCalculation(params.calculationSheet);
59
56
  pricingAdapter.resultSheet().addItem({
60
57
  amount: convertedAmount * quantity,
61
58
  isTaxable: productPrice?.isTaxable,
62
59
  isNetPrice: productPrice?.isNetPrice,
63
- meta: { adapter: ProductPriceRateConversion.key },
60
+ meta: { adapter: ProductPriceRateConversion.key, rate: rateData.rate },
64
61
  });
65
62
  }
66
63
 
@@ -1,10 +1,7 @@
1
1
  import { IProductPricingAdapter } from '@unchainedshop/types/products.pricing';
2
- import { ProductPricingAdapter } from '@unchainedshop/core-products';
2
+ import { ProductPricingDirector, ProductPricingAdapter } from '@unchainedshop/core-products';
3
3
 
4
- const roundToNext = (value, precision) =>
5
- value % precision === precision / 2
6
- ? value + precision / 2
7
- : value + (precision - (value % precision));
4
+ const roundToNext = (value, precision) => Math.ceil(value / precision) * precision;
8
5
 
9
6
  // String is referencing to a currency
10
7
  interface PriceRoundSettings {
@@ -76,3 +73,5 @@ export const ProductPriceRound: IProductPricingAdapter & {
76
73
  };
77
74
  },
78
75
  };
76
+
77
+ ProductPricingDirector.registerAdapter(ProductPriceRound);
@@ -11,7 +11,7 @@ import { IWarehousingAdapter } from '@unchainedshop/types/warehousing';
11
11
 
12
12
  const { NODE_ENV, GOOGLE_SHEETS_ID, GOOGLE_SHEETS_PRIVATE_KEY_DATA } = process.env;
13
13
 
14
- const ttl = NODE_ENV === 'production' ? 1000 * 60 * 60 : 0; // 1 hour or 1 second
14
+ const ttl = NODE_ENV === 'production' ? 1000 * 60 : 0; // 1 minute or 1 second
15
15
 
16
16
  const downloadSpreadsheet = async () => {
17
17
  if (!GOOGLE_SHEETS_PRIVATE_KEY_DATA || !GOOGLE_SHEETS_ID) return null;
@@ -124,8 +124,10 @@ const GoogleSheets: IWarehousingAdapter = {
124
124
  },
125
125
  ],
126
126
 
127
- actions: (_, context) => {
127
+ actions: (config, context) => {
128
128
  return {
129
+ ...WarehousingAdapter.actions(config, context),
130
+
129
131
  isActive: () => {
130
132
  return true;
131
133
  },
@@ -0,0 +1,66 @@
1
+ import {
2
+ WarehousingDirector,
3
+ WarehousingAdapter,
4
+ WarehousingProviderType,
5
+ } from '@unchainedshop/core-warehousing';
6
+ import { ProductType } from '@unchainedshop/types/products';
7
+ import { IWarehousingAdapter, TokenSurrogate } from '@unchainedshop/types/warehousing';
8
+ import { generateDbObjectId } from '@unchainedshop/utils';
9
+
10
+ const ETHMinter: IWarehousingAdapter = {
11
+ ...WarehousingAdapter,
12
+
13
+ key: 'shop.unchained.warehousing.infinite-minter',
14
+ version: '1.0',
15
+ label: 'Infinite Minter',
16
+ orderIndex: 0,
17
+
18
+ initialConfiguration: [{ key: 'chainId', value: '0' }],
19
+
20
+ typeSupported: (type) => {
21
+ return type === WarehousingProviderType.VIRTUAL;
22
+ },
23
+
24
+ actions: (configuration, context) => {
25
+ const { product, orderPosition } = context;
26
+ return {
27
+ ...WarehousingAdapter.actions(configuration, context),
28
+
29
+ isActive() {
30
+ return product?.type === ProductType.TokenizedProduct;
31
+ },
32
+
33
+ configurationError() {
34
+ return null;
35
+ },
36
+
37
+ stock: async () => {
38
+ return product.tokenization.supply;
39
+ },
40
+
41
+ tokenize: async () => {
42
+ // Upload Image to IPFS
43
+ // Upload Metadata to IPFS
44
+ // Store Metadata in Collection
45
+
46
+ const chainId = configuration.find(({ key }) => key === 'chainId')?.value || '0';
47
+ const { contractAddress, tokenId: chainTokenId } = product.tokenization;
48
+ const _id = generateDbObjectId();
49
+ const meta = {};
50
+
51
+ return [
52
+ {
53
+ _id,
54
+ chainTokenId,
55
+ contractAddress,
56
+ quantity: orderPosition.quantity,
57
+ chainId,
58
+ meta,
59
+ } as TokenSurrogate,
60
+ ];
61
+ },
62
+ };
63
+ },
64
+ };
65
+
66
+ WarehousingDirector.registerAdapter(ETHMinter);
@@ -19,8 +19,10 @@ const Store: IWarehousingAdapter = {
19
19
  return type === WarehousingProviderType.PHYSICAL;
20
20
  },
21
21
 
22
- actions: () => {
22
+ actions: (configuration, context) => {
23
23
  return {
24
+ ...WarehousingAdapter.actions(configuration, context),
25
+
24
26
  isActive() {
25
27
  return true;
26
28
  },
@@ -1,7 +1,6 @@
1
1
  import { IWorkerAdapter } from '@unchainedshop/types/worker';
2
- import { WorkerDirector } from '@unchainedshop/core-worker';
2
+ import { WorkerDirector, WorkerAdapter } from '@unchainedshop/core-worker';
3
3
  import { createLogger, LogLevel } from '@unchainedshop/logger';
4
- import { BaseAdapter } from '@unchainedshop/utils';
5
4
  import fs from 'fs';
6
5
  import JSONStream from 'JSONStream';
7
6
  import { EventIterator } from 'event-iterator';
@@ -48,7 +47,7 @@ const streamPayloadToBulkImporter = async (
48
47
  };
49
48
 
50
49
  export const BulkImportWorker: IWorkerAdapter<any, Record<string, unknown>> = {
51
- ...BaseAdapter,
50
+ ...WorkerAdapter,
52
51
 
53
52
  key: 'shop.unchained.worker-plugin.bulk-import',
54
53
  label: 'Bulk Import',
@@ -1,7 +1,6 @@
1
1
  import { IWorkerAdapter } from '@unchainedshop/types/worker';
2
- import { WorkerDirector } from '@unchainedshop/core-worker';
2
+ import { WorkerDirector, WorkerAdapter } from '@unchainedshop/core-worker';
3
3
  import { createLogger } from '@unchainedshop/logger';
4
- import { BaseAdapter } from '@unchainedshop/utils';
5
4
 
6
5
  const logger = createLogger('unchained:platform:zombie-killer');
7
6
 
@@ -17,7 +16,7 @@ export const ZombieKillerWorker: IWorkerAdapter<
17
16
  deletedFilesCount: number;
18
17
  }
19
18
  > = {
20
- ...BaseAdapter,
19
+ ...WorkerAdapter,
21
20
 
22
21
  key: 'shop.unchained.worker-plugin.zombie-killer',
23
22
  label: 'Zombie Killer',
@@ -67,7 +67,6 @@ const EmailWorkerPlugin: IWorkerAdapter<
67
67
  key: 'shop.unchained.worker-plugin.email',
68
68
  label: 'Send a Mail through Nodemailer',
69
69
  version: '1.0',
70
-
71
70
  type: 'EMAIL',
72
71
 
73
72
  doWork: async ({ from, to, subject, ...rest }) => {
@@ -0,0 +1,33 @@
1
+ import { IWorkerAdapter } from '@unchainedshop/types/worker';
2
+ import { WorkerDirector, WorkerAdapter, WorkerEventTypes } from '@unchainedshop/core-worker';
3
+ import { UnchainedCore } from '@unchainedshop/types/core';
4
+
5
+ export const ExportTokenWorker: IWorkerAdapter<void, void> = {
6
+ ...WorkerAdapter,
7
+
8
+ key: 'shop.unchained.worker-plugin.export-token',
9
+ label: 'External worker to hold the state of the minting/export process of tokens',
10
+ version: '1.0',
11
+ type: 'EXPORT_TOKEN',
12
+ external: true,
13
+
14
+ async doWork() {
15
+ throw new Error('Cannot do work for external workers');
16
+ },
17
+ };
18
+
19
+ export const configureExportToken = (unchainedApi: UnchainedCore) => {
20
+ WorkerDirector.events.on(WorkerEventTypes.FINISHED, async ({ work }) => {
21
+ if (work.type === 'EXPORT_TOKEN' && work.success) {
22
+ await unchainedApi.modules.warehousing.updateTokenOwnership({
23
+ tokenId: work.input.token._id,
24
+ userId: null,
25
+ walletAddress: work.input.recipientWalletAddress,
26
+ });
27
+ }
28
+ });
29
+ };
30
+
31
+ export default configureExportToken;
32
+
33
+ WorkerDirector.registerAdapter(ExportTokenWorker);
@@ -8,6 +8,7 @@ export const ExternalWorkerPlugin: IWorkerAdapter<void, void> = {
8
8
  label: 'External plugin as a placeholder for workers who interact with the system only via GraphQL',
9
9
  version: '1.0',
10
10
  type: 'EXTERNAL',
11
+ external: true,
11
12
 
12
13
  async doWork() {
13
14
  throw new Error('Cannot do work for external workers');