@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.
- package/lib/files/gridfs/gridfs-webhook.js +1 -1
- package/lib/files/gridfs/gridfs-webhook.js.map +1 -1
- package/lib/files/minio/minio-webhook.js +1 -1
- package/lib/files/minio/minio-webhook.js.map +1 -1
- package/lib/payment/apple-iap/adapter.js +1 -1
- package/lib/payment/apple-iap/adapter.js.map +1 -1
- package/lib/payment/bity/adapter.js +2 -2
- package/lib/payment/bity/adapter.js.map +1 -1
- package/lib/payment/cryptopay/db/CryptopayTransactions.d.ts +13 -0
- package/lib/payment/cryptopay/db/CryptopayTransactions.js +4 -0
- package/lib/payment/cryptopay/db/CryptopayTransactions.js.map +1 -0
- package/lib/payment/cryptopay/index.d.ts +5 -0
- package/lib/payment/cryptopay/index.js +6 -0
- package/lib/payment/cryptopay/index.js.map +1 -0
- package/lib/payment/{cryptopay.d.ts → cryptopay/middleware.d.ts} +0 -0
- package/lib/payment/cryptopay/middleware.js +79 -0
- package/lib/payment/cryptopay/middleware.js.map +1 -0
- package/lib/payment/cryptopay/module/configureCryptopayModule.d.ts +25 -0
- package/lib/payment/cryptopay/module/configureCryptopayModule.js +75 -0
- package/lib/payment/cryptopay/module/configureCryptopayModule.js.map +1 -0
- package/lib/payment/cryptopay/plugin.d.ts +3 -0
- package/lib/payment/cryptopay/plugin.js +201 -0
- package/lib/payment/cryptopay/plugin.js.map +1 -0
- package/lib/payment/datatrans-v2/middleware.js +1 -1
- package/lib/payment/datatrans-v2/middleware.js.map +1 -1
- package/lib/payment/postfinance-checkout/middleware.js +1 -1
- package/lib/payment/postfinance-checkout/middleware.js.map +1 -1
- package/lib/pricing/product-catalog-price.js.map +1 -1
- package/lib/pricing/product-price-rateconversion.js +23 -17
- package/lib/pricing/product-price-rateconversion.js.map +1 -1
- package/lib/pricing/product-price-round.js +3 -4
- package/lib/pricing/product-price-round.js.map +1 -1
- package/lib/pricing/utils/cache.d.ts +2 -1
- package/lib/warehousing/google-sheets.js +3 -2
- package/lib/warehousing/google-sheets.js.map +1 -1
- package/lib/{pricing/product-price-ECB-interbank-exchange.d.ts → warehousing/infinite-minter.d.ts} +0 -0
- package/lib/warehousing/infinite-minter.js +50 -0
- package/lib/warehousing/infinite-minter.js.map +1 -0
- package/lib/warehousing/store.js +2 -1
- package/lib/warehousing/store.js.map +1 -1
- package/lib/worker/BulkImportWorker.js +2 -3
- package/lib/worker/BulkImportWorker.js.map +1 -1
- package/lib/worker/ZombieKillerWorker.js +2 -3
- package/lib/worker/ZombieKillerWorker.js.map +1 -1
- package/lib/worker/email.js.map +1 -1
- package/lib/worker/export-token.d.ts +5 -0
- package/lib/worker/export-token.js +26 -0
- package/lib/worker/export-token.js.map +1 -0
- package/lib/worker/external.js +1 -0
- package/lib/worker/external.js.map +1 -1
- package/lib/{pricing/product-price-coinbase-exchange.d.ts → worker/update-coinbase-rates.d.ts} +0 -0
- package/lib/worker/update-coinbase-rates.js +69 -0
- package/lib/worker/update-coinbase-rates.js.map +1 -0
- package/lib/worker/update-ecb-rates.d.ts +1 -0
- package/lib/worker/update-ecb-rates.js +79 -0
- package/lib/worker/update-ecb-rates.js.map +1 -0
- package/lib/worker/update-token-ownership.d.ts +6 -0
- package/lib/worker/update-token-ownership.js +66 -0
- package/lib/worker/update-token-ownership.js.map +1 -0
- package/package.json +22 -19
- package/src/files/gridfs/gridfs-webhook.ts +1 -1
- package/src/files/minio/minio-webhook.js +1 -1
- package/src/payment/apple-iap/adapter.ts +1 -1
- package/src/payment/bity/adapter.ts +2 -2
- package/src/payment/cryptopay/db/CryptopayTransactions.ts +17 -0
- package/src/payment/cryptopay/index.ts +8 -0
- package/src/payment/cryptopay/middleware.ts +95 -0
- package/src/payment/cryptopay/module/configureCryptopayModule.ts +133 -0
- package/src/payment/cryptopay/plugin.ts +289 -0
- package/src/payment/cryptopay/readme.md +1 -0
- package/src/payment/datatrans-v2/middleware.ts +1 -1
- package/src/payment/postfinance-checkout/middleware.ts +1 -1
- package/src/pricing/product-catalog-price.ts +1 -0
- package/src/pricing/product-price-rateconversion.ts +25 -28
- package/src/pricing/product-price-round.ts +4 -5
- package/src/warehousing/google-sheets.ts +4 -2
- package/src/warehousing/infinite-minter.ts +66 -0
- package/src/warehousing/store.ts +3 -1
- package/src/worker/BulkImportWorker.ts +2 -3
- package/src/worker/ZombieKillerWorker.ts +2 -3
- package/src/worker/email.ts +0 -1
- package/src/worker/export-token.ts +33 -0
- package/src/worker/external.ts +1 -0
- package/src/worker/update-coinbase-rates.ts +91 -0
- package/src/worker/update-ecb-rates.ts +97 -0
- package/src/worker/update-token-ownership.ts +85 -0
- package/lib/payment/cryptopay.js +0 -161
- package/lib/payment/cryptopay.js.map +0 -1
- package/lib/pricing/product-price-ECB-interbank-exchange.js +0 -90
- package/lib/pricing/product-price-ECB-interbank-exchange.js.map +0 -1
- package/lib/pricing/product-price-coinbase-exchange.js +0 -60
- package/lib/pricing/product-price-coinbase-exchange.js.map +0 -1
- package/lib/pricing/product-price-cryptopay.d.ts +0 -2
- package/lib/pricing/product-price-cryptopay.js +0 -23
- package/lib/pricing/product-price-cryptopay.js.map +0 -1
- package/src/payment/cryptopay.ts +0 -197
- package/src/pricing/product-price-ECB-interbank-exchange.ts +0 -108
- package/src/pricing/product-price-coinbase-exchange.ts +0 -82
- 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(
|
|
@@ -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 {
|
|
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
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
-
|
|
42
|
-
|
|
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
|
|
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
|
|
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: (
|
|
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);
|
package/src/warehousing/store.ts
CHANGED
|
@@ -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
|
-
...
|
|
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
|
-
...
|
|
19
|
+
...WorkerAdapter,
|
|
21
20
|
|
|
22
21
|
key: 'shop.unchained.worker-plugin.zombie-killer',
|
|
23
22
|
label: 'Zombie Killer',
|
package/src/worker/email.ts
CHANGED
|
@@ -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);
|
package/src/worker/external.ts
CHANGED
|
@@ -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');
|