@uplab/vendure-plugin-exchange-rates 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -0
- package/README.md +369 -0
- package/dist/api/admin.resolver.d.ts +16 -0
- package/dist/api/admin.resolver.d.ts.map +1 -0
- package/dist/api/admin.resolver.js +66 -0
- package/dist/api/admin.resolver.js.map +1 -0
- package/dist/api/api-extensions.d.ts +6 -0
- package/dist/api/api-extensions.d.ts.map +1 -0
- package/dist/api/api-extensions.js +57 -0
- package/dist/api/api-extensions.js.map +1 -0
- package/dist/api/shop.resolver.d.ts +15 -0
- package/dist/api/shop.resolver.d.ts.map +1 -0
- package/dist/api/shop.resolver.js +63 -0
- package/dist/api/shop.resolver.js.map +1 -0
- package/dist/constants.d.ts +10 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +13 -0
- package/dist/constants.js.map +1 -0
- package/dist/dashboard/format-rate.ts +8 -0
- package/dist/dashboard/graphql.ts +74 -0
- package/dist/dashboard/i18n/en.po +109 -0
- package/dist/dashboard/i18n/uk.po +109 -0
- package/dist/dashboard/index.tsx +7 -0
- package/dist/dashboard/routes/currency-exchange-rate-detail.tsx +194 -0
- package/dist/dashboard/routes/currency-exchange-rate-list.tsx +105 -0
- package/dist/derive-rates.d.ts +13 -0
- package/dist/derive-rates.d.ts.map +1 -0
- package/dist/derive-rates.js +40 -0
- package/dist/derive-rates.js.map +1 -0
- package/dist/effective-rate.d.ts +8 -0
- package/dist/effective-rate.d.ts.map +1 -0
- package/dist/effective-rate.js +13 -0
- package/dist/effective-rate.js.map +1 -0
- package/dist/entities/currency-exchange-rate.entity.d.ts +13 -0
- package/dist/entities/currency-exchange-rate.entity.d.ts.map +1 -0
- package/dist/entities/currency-exchange-rate.entity.js +55 -0
- package/dist/entities/currency-exchange-rate.entity.js.map +1 -0
- package/dist/events/currency-exchange-rate.event.d.ts +18 -0
- package/dist/events/currency-exchange-rate.event.d.ts.map +1 -0
- package/dist/events/currency-exchange-rate.event.js +18 -0
- package/dist/events/currency-exchange-rate.event.js.map +1 -0
- package/dist/exchange-rate-source-error.d.ts +16 -0
- package/dist/exchange-rate-source-error.d.ts.map +1 -0
- package/dist/exchange-rate-source-error.js +18 -0
- package/dist/exchange-rate-source-error.js.map +1 -0
- package/dist/exchange-rate-sync.task.d.ts +3 -0
- package/dist/exchange-rate-sync.task.d.ts.map +1 -0
- package/dist/exchange-rate-sync.task.js +18 -0
- package/dist/exchange-rate-sync.task.js.map +1 -0
- package/dist/exchange-rates.plugin.d.ts +24 -0
- package/dist/exchange-rates.plugin.d.ts.map +1 -0
- package/dist/exchange-rates.plugin.js +101 -0
- package/dist/exchange-rates.plugin.js.map +1 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +31 -0
- package/dist/index.js.map +1 -0
- package/dist/services/currency-exchange-rate-sync.service.d.ts +30 -0
- package/dist/services/currency-exchange-rate-sync.service.d.ts.map +1 -0
- package/dist/services/currency-exchange-rate-sync.service.js +145 -0
- package/dist/services/currency-exchange-rate-sync.service.js.map +1 -0
- package/dist/services/currency-exchange-rate.service.d.ts +30 -0
- package/dist/services/currency-exchange-rate.service.d.ts.map +1 -0
- package/dist/services/currency-exchange-rate.service.js +93 -0
- package/dist/services/currency-exchange-rate.service.js.map +1 -0
- package/dist/sources/ecb-exchange-rate-source.d.ts +20 -0
- package/dist/sources/ecb-exchange-rate-source.d.ts.map +1 -0
- package/dist/sources/ecb-exchange-rate-source.js +33 -0
- package/dist/sources/ecb-exchange-rate-source.js.map +1 -0
- package/dist/sources/fetch-body.d.ts +9 -0
- package/dist/sources/fetch-body.d.ts.map +1 -0
- package/dist/sources/fetch-body.js +59 -0
- package/dist/sources/fetch-body.js.map +1 -0
- package/dist/sources/frankfurter-exchange-rate-source.d.ts +23 -0
- package/dist/sources/frankfurter-exchange-rate-source.d.ts.map +1 -0
- package/dist/sources/frankfurter-exchange-rate-source.js +35 -0
- package/dist/sources/frankfurter-exchange-rate-source.js.map +1 -0
- package/dist/sources/monobank-exchange-rate-source.d.ts +29 -0
- package/dist/sources/monobank-exchange-rate-source.d.ts.map +1 -0
- package/dist/sources/monobank-exchange-rate-source.js +46 -0
- package/dist/sources/monobank-exchange-rate-source.js.map +1 -0
- package/dist/sources/nbu-exchange-rate-source.d.ts +20 -0
- package/dist/sources/nbu-exchange-rate-source.d.ts.map +1 -0
- package/dist/sources/nbu-exchange-rate-source.js +25 -0
- package/dist/sources/nbu-exchange-rate-source.js.map +1 -0
- package/dist/sources/static-exchange-rate-source.d.ts +20 -0
- package/dist/sources/static-exchange-rate-source.d.ts.map +1 -0
- package/dist/sources/static-exchange-rate-source.js +23 -0
- package/dist/sources/static-exchange-rate-source.js.map +1 -0
- package/dist/types.d.ts +64 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/package.json +96 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# @uplab/vendure-plugin-exchange-rates
|
|
2
|
+
|
|
3
|
+
## 0.1.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#30](https://github.com/Uplab/uplab-vendure-plugins/pull/30) [`d132726`](https://github.com/Uplab/uplab-vendure-plugins/commit/d132726b2fecf429684ae6e558db6d9b754c03b2) Thanks [@brmk](https://github.com/brmk)! - Initial release: exchange rates in your shop's base currency from a pluggable source (the ECB, Frankfurter, Monobank, the NBU, fixed rates or your own), cross rates derived automatically, refreshed on a schedule, served on the Shop API and overridable per currency in the dashboard.
|
package/README.md
ADDED
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="https://raw.githubusercontent.com/Uplab/uplab-vendure-plugins/main/packages/vendure-plugin-exchange-rates/assets/icon.svg" alt="" width="96" height="96">
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
# @uplab/vendure-plugin-exchange-rates
|
|
6
|
+
|
|
7
|
+
[](https://www.npmjs.com/package/@uplab/vendure-plugin-exchange-rates)
|
|
8
|
+
[](https://www.vendure.io/)
|
|
9
|
+
[](https://github.com/Uplab/uplab-vendure-plugins/blob/main/LICENSE)
|
|
10
|
+
|
|
11
|
+
Exchange rates in your shop's base currency, for a Vendure shop that prices in one currency and shows or
|
|
12
|
+
charges in others. Rates come from a **source you choose** — the European Central Bank, Frankfurter,
|
|
13
|
+
Monobank, the National Bank of Ukraine, fixed rates or your own — are re-based onto your currency, refreshed
|
|
14
|
+
on a schedule, served on the Shop API, and can be overridden per currency in the dashboard.
|
|
15
|
+
|
|
16
|
+
The plugin stores and serves rates; it does not touch Vendure prices or orders — your storefront, feed or
|
|
17
|
+
payment code converts with them. Vendure's own per-currency variant prices are unaffected.
|
|
18
|
+
|
|
19
|
+

|
|
20
|
+
|
|
21
|
+
## Contents
|
|
22
|
+
|
|
23
|
+
[Install](#install) · [Use cases](#use-cases) · [Sources](#sources) · [Base currency](#base-currency) ·
|
|
24
|
+
[Options](#options) · [Dashboard](#dashboard) · [GraphQL API](#graphql-api) ·
|
|
25
|
+
[In your own code](#in-your-own-code) · [Event](#event) · [Database](#database) · [Limitations](#limitations)
|
|
26
|
+
|
|
27
|
+
## Install
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
npm install @uplab/vendure-plugin-exchange-rates
|
|
31
|
+
# or
|
|
32
|
+
pnpm add @uplab/vendure-plugin-exchange-rates
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
import { DefaultSchedulerPlugin, VendureConfig } from '@vendure/core';
|
|
37
|
+
import { EcbExchangeRateSource, ExchangeRatesPlugin } from '@uplab/vendure-plugin-exchange-rates';
|
|
38
|
+
|
|
39
|
+
export const config: VendureConfig = {
|
|
40
|
+
// ...
|
|
41
|
+
plugins: [
|
|
42
|
+
DefaultSchedulerPlugin.init(), // runs the scheduled refresh
|
|
43
|
+
ExchangeRatesPlugin.init({ source: new EcbExchangeRateSource() }),
|
|
44
|
+
],
|
|
45
|
+
};
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Rates are stored in the **default channel's currency**; see [Base currency](#base-currency) to pin another.
|
|
49
|
+
|
|
50
|
+
`@vendure/core`, `@nestjs/*` and `typeorm` are peers. The dashboard pages need `@vendure/dashboard` (an
|
|
51
|
+
optional peer): its Vite plugin discovers the extension — restart the dashboard dev server after
|
|
52
|
+
installing.
|
|
53
|
+
|
|
54
|
+
**First deploy.** Generate and run a migration for the new `currency_exchange_rate` table. On the API
|
|
55
|
+
server's first boot the plugin fills the empty table from the source (startup waits for it, up to the
|
|
56
|
+
source's `timeout`); if the source is unreachable then, the table stays empty until the next scheduled
|
|
57
|
+
refresh. Every currency arrives **disabled** — enable the ones you sell in under _Settings → Currency
|
|
58
|
+
exchange rates_. Until you do, the storefront sees no rates and stays in your base currency.
|
|
59
|
+
|
|
60
|
+
## Use cases
|
|
61
|
+
|
|
62
|
+
**Show prices in the visitor's currency.** The storefront reads the enabled rates once:
|
|
63
|
+
|
|
64
|
+
```graphql
|
|
65
|
+
query {
|
|
66
|
+
currencyExchangeRates {
|
|
67
|
+
items {
|
|
68
|
+
code
|
|
69
|
+
rate # base-currency units per one unit, e.g. a USD shop: EUR → 1.12
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
```ts
|
|
76
|
+
type Rate = { code: string; rate: number };
|
|
77
|
+
|
|
78
|
+
/** `rates` = the query's items, [] until an admin enables a currency. */
|
|
79
|
+
export function convertPrice(priceInMinorUnits: number, rates: Rate[], code: string): number | undefined {
|
|
80
|
+
const rate = rates.find((r) => r.code === code)?.rate;
|
|
81
|
+
// Vendure prices are minor units: base minor units ÷ rate = target minor units. undefined → show the base.
|
|
82
|
+
return rate ? Math.round(priceInMinorUnits / rate) : undefined;
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
This assumes both currencies have 1/100 minor units. When they differ (JPY has none, KWD and BHD have
|
|
87
|
+
1/1000), multiply the result by 10^(target digits − base digits).
|
|
88
|
+
|
|
89
|
+
**Charge in a foreign currency.** A `PaymentMethodHandler` gets the service in `init` and converts the
|
|
90
|
+
amount it is asked to collect:
|
|
91
|
+
|
|
92
|
+
```ts
|
|
93
|
+
import { CurrencyCode, LanguageCode, PaymentMethodHandler } from '@vendure/core';
|
|
94
|
+
import { CurrencyExchangeRateService } from '@uplab/vendure-plugin-exchange-rates';
|
|
95
|
+
|
|
96
|
+
let rates: CurrencyExchangeRateService;
|
|
97
|
+
|
|
98
|
+
export const eurCardHandler = new PaymentMethodHandler({
|
|
99
|
+
code: 'eur-card',
|
|
100
|
+
description: [{ languageCode: LanguageCode.en, value: 'Card, billed in EUR' }],
|
|
101
|
+
args: {},
|
|
102
|
+
init: (injector) => {
|
|
103
|
+
rates = injector.get(CurrencyExchangeRateService);
|
|
104
|
+
},
|
|
105
|
+
createPayment: async (ctx, _order, amount) => {
|
|
106
|
+
const rate = await rates.getRate(ctx, CurrencyCode.EUR, { requireEnabled: true });
|
|
107
|
+
const amountInCents = rate ? Math.round(amount / rate) : undefined; // undefined → bill in the base
|
|
108
|
+
// …call your payment provider with amountInCents
|
|
109
|
+
return { amount, state: 'Authorized', metadata: { amountInCents } };
|
|
110
|
+
},
|
|
111
|
+
settlePayment: () => ({ success: true }),
|
|
112
|
+
});
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
**Price a product feed in another currency** (Google Merchant, Meta, …) even if the storefront does not
|
|
116
|
+
offer it: `getRate(ctx, CurrencyCode.EUR, { requireEnabled: false })`.
|
|
117
|
+
|
|
118
|
+
**Pin a rate.** Turn on _Use custom rate_ for a currency: the storefront and your code use your number
|
|
119
|
+
until you turn it off, while the fetched rate keeps updating underneath.
|
|
120
|
+
|
|
121
|
+
**Fixed rates only.** `source: new StaticExchangeRateSource({ base: CurrencyCode.USD, rates: { EUR: 1.12 } })`.
|
|
122
|
+
Keep the sync on, so a number you change in config is picked up on the next refresh; pin any currency in
|
|
123
|
+
the meantime with _Use custom rate_.
|
|
124
|
+
|
|
125
|
+
## Sources
|
|
126
|
+
|
|
127
|
+
| Source | Base | Rates |
|
|
128
|
+
| ------------------------------- | -------------- | ---------------------------------------------------------------------------------------------------------------- |
|
|
129
|
+
| `EcbExchangeRateSource` | EUR | The ECB's euro reference rates: ~30 major currencies, every working day around 16:00 CET. Free, no key. |
|
|
130
|
+
| `FrankfurterExchangeRateSource` | EUR, or `base` | [Frankfurter](https://frankfurter.dev): the same ECB rates as JSON. Free, no key, can be self-hosted (`apiUrl`). |
|
|
131
|
+
| `StaticExchangeRateSource` | yours | The numbers you pass: `{ base, rates }`. In e2e tests, set `source.config` before a sync — no network. |
|
|
132
|
+
| `MonobankExchangeRateSource` | UAH | [Ukrainian](#ukrainian-sources) bank rates, buy/sell/mid. |
|
|
133
|
+
| `NbuExchangeRateSource` | UAH | [Ukrainian](#ukrainian-sources) official rates, ~40 currencies. |
|
|
134
|
+
|
|
135
|
+
The network sources take `apiUrl` and `timeout` (default 10 s). Whatever a source returns, only Vendure
|
|
136
|
+
`CurrencyCode`s with a positive finite rate are stored — metals, the SDR and garbage are dropped. If the
|
|
137
|
+
source fails or returns nothing usable, the stored rates stay as they are.
|
|
138
|
+
|
|
139
|
+
**Cross rates.** A source that quotes against another base is re-based onto yours: with the ECB and a USD
|
|
140
|
+
shop, `GBP = (EUR per GBP) / (EUR per USD)`, and EUR itself is added. This needs the source to quote your
|
|
141
|
+
base currency — the ECB does not quote UAH, for instance, so a UAH shop uses a Ukrainian source. Rates are
|
|
142
|
+
stored per unit with 8 decimals, so a weak currency against a strong base keeps few significant digits
|
|
143
|
+
(LBP in USD: 0.00001117); a rate that does not fit decimal(19, 8) is dropped.
|
|
144
|
+
|
|
145
|
+
### Ukrainian sources
|
|
146
|
+
|
|
147
|
+
- **`MonobankExchangeRateSource`** — Monobank's public rates. `side: 'buy'` (default) is what the bank pays
|
|
148
|
+
you for the currency — the lower rate, so a UAH price converted with it never comes up short; or
|
|
149
|
+
`'sell'`, `'mid'`. Rate-limited; cached 5 minutes.
|
|
150
|
+
- **`NbuExchangeRateSource`** — the National Bank of Ukraine's official rate, set once per business day.
|
|
151
|
+
|
|
152
|
+
Both quote against UAH, so they suit a UAH shop. They also work for a shop in a currency they quote (USD,
|
|
153
|
+
EUR, …), but the ECB is the more natural choice there.
|
|
154
|
+
|
|
155
|
+
### Your own source
|
|
156
|
+
|
|
157
|
+
```ts
|
|
158
|
+
import { CurrencyCode } from '@vendure/core';
|
|
159
|
+
import {
|
|
160
|
+
ExchangeRateSource,
|
|
161
|
+
ExchangeRateSourceError,
|
|
162
|
+
ExchangeRateSourceResult,
|
|
163
|
+
} from '@uplab/vendure-plugin-exchange-rates';
|
|
164
|
+
|
|
165
|
+
type Observation = { d: string } & Record<string, { v: string } | string>;
|
|
166
|
+
|
|
167
|
+
/** Rates of the Bank of Canada: CAD per one unit. */
|
|
168
|
+
export class BankOfCanadaExchangeRateSource implements ExchangeRateSource {
|
|
169
|
+
readonly name = 'bank-of-canada';
|
|
170
|
+
|
|
171
|
+
async fetchRates(): Promise<ExchangeRateSourceResult> {
|
|
172
|
+
const res = await fetch('https://www.bankofcanada.ca/valet/observations/group/FX_RATES_DAILY/json?recent=1', {
|
|
173
|
+
signal: AbortSignal.timeout(10_000), // the API server waits for the first fetch on boot
|
|
174
|
+
});
|
|
175
|
+
if (!res.ok) throw new ExchangeRateSourceError(`HTTP ${res.status}`, { source: this.name, status: res.status });
|
|
176
|
+
const body = (await res.json()) as { observations?: Observation[] };
|
|
177
|
+
if (!body.observations?.length) throw new ExchangeRateSourceError('No observations', { source: this.name });
|
|
178
|
+
// `recent=1` is the last value of every series, discontinued ones included: keep the newest day.
|
|
179
|
+
const latest = body.observations.reduce((a, b) => (a.d > b.d ? a : b));
|
|
180
|
+
const quotes = Object.entries(latest)
|
|
181
|
+
.filter(([series]) => /^FX[A-Z]{3}CAD$/.test(series))
|
|
182
|
+
.map(([series, value]) => ({
|
|
183
|
+
currencyCode: series.slice(2, 5) as CurrencyCode,
|
|
184
|
+
rate: Number((value as { v: string }).v),
|
|
185
|
+
}));
|
|
186
|
+
return { base: CurrencyCode.CAD, quotes };
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
A source returns its `base` and quotes `{ currencyCode, rate }`, `rate` being **base units per one unit**;
|
|
192
|
+
the plugin re-bases them onto the shop's currency. Throw on failure — never return an empty list to mean
|
|
193
|
+
"failed" — and give the request a timeout, since the API server waits for the first fetch on boot. If the
|
|
194
|
+
source needs services, implement `init(injector)` (and `destroy()`); the plugin calls `init` before the
|
|
195
|
+
first fetch.
|
|
196
|
+
|
|
197
|
+
### Combining sources
|
|
198
|
+
|
|
199
|
+
When a source rate-limits or is down, fall back to another — the first one that answers wins:
|
|
200
|
+
|
|
201
|
+
```ts
|
|
202
|
+
import { RequestContext } from '@vendure/core';
|
|
203
|
+
import {
|
|
204
|
+
EcbExchangeRateSource,
|
|
205
|
+
ExchangeRateSource,
|
|
206
|
+
ExchangeRateSourceResult,
|
|
207
|
+
FrankfurterExchangeRateSource,
|
|
208
|
+
} from '@uplab/vendure-plugin-exchange-rates';
|
|
209
|
+
|
|
210
|
+
export class FallbackExchangeRateSource implements ExchangeRateSource {
|
|
211
|
+
readonly name = 'fallback';
|
|
212
|
+
constructor(private readonly sources: ExchangeRateSource[]) {}
|
|
213
|
+
|
|
214
|
+
async fetchRates(ctx: RequestContext): Promise<ExchangeRateSourceResult> {
|
|
215
|
+
let lastError: unknown;
|
|
216
|
+
for (const source of this.sources) {
|
|
217
|
+
try {
|
|
218
|
+
return await source.fetchRates(ctx);
|
|
219
|
+
} catch (e) {
|
|
220
|
+
lastError = e;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
throw lastError;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
export const source = new FallbackExchangeRateSource([
|
|
228
|
+
new EcbExchangeRateSource(),
|
|
229
|
+
new FrankfurterExchangeRateSource(),
|
|
230
|
+
]);
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
To add a currency a source does not quote, merge the quotes of several sources **in the same base** (a
|
|
234
|
+
`StaticExchangeRateSource` last); when two quotes share a code, the last one wins. Forward `init`/`destroy`
|
|
235
|
+
if the inner sources need them.
|
|
236
|
+
|
|
237
|
+
## Base currency
|
|
238
|
+
|
|
239
|
+
Rates are stored and served in one base currency: `baseCurrency` if you set it, otherwise the default
|
|
240
|
+
channel's `defaultCurrencyCode`, read on every sync and lookup.
|
|
241
|
+
|
|
242
|
+
```ts
|
|
243
|
+
ExchangeRatesPlugin.init({ source: new MonobankExchangeRateSource(), baseCurrency: CurrencyCode.UAH });
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
When the base changes, the Shop API and `getRate` return nothing until a sync re-bases the table. The API
|
|
247
|
+
server does that on boot, so changing the option needs nothing more; after changing the default channel's
|
|
248
|
+
currency, run the task (see [Options](#options)) or restart. The sync moves every currency to the new
|
|
249
|
+
base, keeps whether it is enabled, **drops its custom rate** (it was in the old base) and deletes the
|
|
250
|
+
currencies the new base does not cover.
|
|
251
|
+
|
|
252
|
+
## Options
|
|
253
|
+
|
|
254
|
+
| Option | Type | Default |
|
|
255
|
+
| -------------- | --------------------------------------------------------- | --------------------------------- |
|
|
256
|
+
| `source` | `ExchangeRateSource` — required | — |
|
|
257
|
+
| `baseCurrency` | `CurrencyCode` | the default channel's currency |
|
|
258
|
+
| `sync` | `{ schedule?: ScheduledTaskConfig['schedule'] } \| false` | `{ schedule: '40 2-23/3 * * *' }` |
|
|
259
|
+
|
|
260
|
+
`sync` registers the scheduled task `currency-exchange-rate-updater` (`SYNC_TASK_ID`, every 3 hours). It
|
|
261
|
+
needs a scheduler plugin such as `DefaultSchedulerPlugin`, and runs in the worker — so the worker needs the
|
|
262
|
+
plugin in its config too. `sync: false` leaves it out; you can still call `CurrencyExchangeRateSyncService.syncRates(ctx)`.
|
|
263
|
+
|
|
264
|
+
To refresh now, run it under _System → Scheduled tasks_ (or
|
|
265
|
+
`runScheduledTask(id: "currency-exchange-rate-updater")`); its last run there tells you the sync is alive.
|
|
266
|
+
A sync moves a row's `updatedAt` only when its rate changes. A currency the source stops quoting keeps its
|
|
267
|
+
last rate; if it is enabled, every sync logs a warning.
|
|
268
|
+
|
|
269
|
+
## Dashboard
|
|
270
|
+
|
|
271
|
+
_Settings → Currency exchange rates_ lists every stored currency with its rate and base, whether it is
|
|
272
|
+
enabled and whether a custom rate is in use. A currency's page shows the fetched rate and lets you enable
|
|
273
|
+
it and set a custom rate.
|
|
274
|
+
|
|
275
|
+

|
|
276
|
+
|
|
277
|
+
Rates are shop settings: listing needs `ReadSettings`, editing `UpdateSettings` — no extra role setup.
|
|
278
|
+
|
|
279
|
+
## GraphQL API
|
|
280
|
+
|
|
281
|
+
**Shop** (public)
|
|
282
|
+
|
|
283
|
+
```graphql
|
|
284
|
+
currencyExchangeRates(options: CurrencyExchangeRateListOptions): CurrencyExchangeRateList!
|
|
285
|
+
# items: { id, code, baseCurrency, rate, enabled, createdAt, updatedAt }
|
|
286
|
+
# enabled currencies in the current base only, whatever the filter
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
`rate` is the custom rate while one is in use, otherwise the fetched one.
|
|
290
|
+
|
|
291
|
+
**Admin**
|
|
292
|
+
|
|
293
|
+
```graphql
|
|
294
|
+
currencyExchangeRates(options: CurrencyExchangeRateListOptions): CurrencyExchangeRateList! # ReadSettings
|
|
295
|
+
currencyExchangeRate(id: ID!): CurrencyExchangeRate # ReadSettings
|
|
296
|
+
updateCurrencyExchangeRate(input: { id, enabled, useCustomRate, customRate }): CurrencyExchangeRate! # UpdateSettings
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
In the Admin API `rate` is always the fetched rate; `useCustomRate` and `customRate` are separate fields.
|
|
300
|
+
Turning on `useCustomRate` without a positive `customRate` is rejected.
|
|
301
|
+
|
|
302
|
+
## In your own code
|
|
303
|
+
|
|
304
|
+
| Export | Use |
|
|
305
|
+
| --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
|
|
306
|
+
| `CurrencyExchangeRateService` | `getRate(ctx, code, { requireEnabled })` → base units per unit or `undefined`; `getBaseCurrency(ctx)`; `findAll`, `findOne`, `update` |
|
|
307
|
+
| `CurrencyExchangeRateSyncService` | `syncRates(ctx)` — refresh now |
|
|
308
|
+
| `effectiveRate(row)` | The rate to convert with for a row you loaded yourself, or `undefined` if it is not usable |
|
|
309
|
+
|
|
310
|
+
`requireEnabled` is your policy: `true` for anything the customer is billed in, `false` for internal uses.
|
|
311
|
+
|
|
312
|
+
## Event
|
|
313
|
+
|
|
314
|
+
`CurrencyExchangeRateEvent` is published once the transaction has committed: `type: 'synced'` when a sync
|
|
315
|
+
changed anything, with every rate now stored in the base currency, or `'updated'` with the one rate an
|
|
316
|
+
admin saved. Use it to drop caches
|
|
317
|
+
that embed the rates:
|
|
318
|
+
|
|
319
|
+
```ts
|
|
320
|
+
import { OnApplicationBootstrap } from '@nestjs/common';
|
|
321
|
+
import { EventBus, PluginCommonModule, VendurePlugin } from '@vendure/core';
|
|
322
|
+
import { CurrencyExchangeRateEvent } from '@uplab/vendure-plugin-exchange-rates';
|
|
323
|
+
|
|
324
|
+
@VendurePlugin({ imports: [PluginCommonModule] })
|
|
325
|
+
export class RatesCachePlugin implements OnApplicationBootstrap {
|
|
326
|
+
constructor(private readonly eventBus: EventBus) {}
|
|
327
|
+
|
|
328
|
+
onApplicationBootstrap() {
|
|
329
|
+
this.eventBus.ofType(CurrencyExchangeRateEvent).subscribe(() => {
|
|
330
|
+
// drop whatever caches the rates, e.g. a cached Shop API response
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
## Database
|
|
337
|
+
|
|
338
|
+
One table, `currency_exchange_rate`:
|
|
339
|
+
|
|
340
|
+
| Column | Type |
|
|
341
|
+
| --------------- | ------------------------------------------- |
|
|
342
|
+
| `code` | varchar, unique |
|
|
343
|
+
| `baseCurrency` | varchar — the currency `rate` is counted in |
|
|
344
|
+
| `rate` | decimal(19, 8) — the fetched one |
|
|
345
|
+
| `enabled` | boolean, default `false` |
|
|
346
|
+
| `useCustomRate` | boolean, default `false` |
|
|
347
|
+
| `customRate` | decimal(19, 8), nullable |
|
|
348
|
+
|
|
349
|
+
A sync updates `rate` and inserts new currencies, in one transaction; on a [base change](#base-currency) it
|
|
350
|
+
also rewrites `baseCurrency`, clears custom rates and deletes the currencies the new base does not cover.
|
|
351
|
+
|
|
352
|
+
## Limitations
|
|
353
|
+
|
|
354
|
+
- **One base currency** — channels that sell in different currencies share the rates of the default
|
|
355
|
+
channel's (or `baseCurrency`).
|
|
356
|
+
- **Rates are global** — one table, not per channel: anyone with `UpdateSettings` in any channel can
|
|
357
|
+
change them.
|
|
358
|
+
- **Filtering and sorting on `rate`** use the fetched rate, while the returned `rate` is the effective one.
|
|
359
|
+
- **Dashboard 3.7.0–3.7.3 in dev mode** can render a blank page with two or more dashboard extensions
|
|
360
|
+
(for example together with `@uplab/vendure-plugin-unified-slug`). It is a Vendure bug, fixed in 3.7.4
|
|
361
|
+
([vendure#5459](https://github.com/vendure-ecommerce/vendure/pull/5459)); production builds are fine.
|
|
362
|
+
|
|
363
|
+
## Changelog
|
|
364
|
+
|
|
365
|
+
See [CHANGELOG.md](./CHANGELOG.md).
|
|
366
|
+
|
|
367
|
+
## License
|
|
368
|
+
|
|
369
|
+
MIT
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { ID, ListQueryOptions, PaginatedList, RequestContext } from '@vendure/core';
|
|
2
|
+
import { CurrencyExchangeRate } from '../entities/currency-exchange-rate.entity';
|
|
3
|
+
import { CurrencyExchangeRateService } from '../services/currency-exchange-rate.service';
|
|
4
|
+
import { MutationUpdateCurrencyExchangeRateArgs } from '../types';
|
|
5
|
+
export declare class CurrencyExchangeRateAdminResolver {
|
|
6
|
+
private currencyExchangeRateService;
|
|
7
|
+
constructor(currencyExchangeRateService: CurrencyExchangeRateService);
|
|
8
|
+
currencyExchangeRates(ctx: RequestContext, args: {
|
|
9
|
+
options: ListQueryOptions<CurrencyExchangeRate>;
|
|
10
|
+
}): Promise<PaginatedList<CurrencyExchangeRate>>;
|
|
11
|
+
currencyExchangeRate(ctx: RequestContext, args: {
|
|
12
|
+
id: ID;
|
|
13
|
+
}): Promise<CurrencyExchangeRate | null>;
|
|
14
|
+
updateCurrencyExchangeRate(ctx: RequestContext, args: MutationUpdateCurrencyExchangeRateArgs): Promise<CurrencyExchangeRate>;
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=admin.resolver.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"admin.resolver.d.ts","sourceRoot":"","sources":["../../src/api/admin.resolver.ts"],"names":[],"mappings":"AACA,OAAO,EAGL,EAAE,EACF,gBAAgB,EAChB,aAAa,EAEb,cAAc,EAEf,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,2BAA2B,EAAE,MAAM,4CAA4C,CAAC;AACzF,OAAO,EAAE,sCAAsC,EAAE,MAAM,UAAU,CAAC;AAElE,qBACa,iCAAiC;IAChC,OAAO,CAAC,2BAA2B;gBAA3B,2BAA2B,EAAE,2BAA2B;IAI5E,qBAAqB,CACZ,GAAG,EAAE,cAAc,EAClB,IAAI,EAAE;QAAE,OAAO,EAAE,gBAAgB,CAAC,oBAAoB,CAAC,CAAA;KAAE,GAChE,OAAO,CAAC,aAAa,CAAC,oBAAoB,CAAC,CAAC;IAM/C,oBAAoB,CAAQ,GAAG,EAAE,cAAc,EAAU,IAAI,EAAE;QAAE,EAAE,EAAE,EAAE,CAAA;KAAE,GAAG,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC;IAOhH,0BAA0B,CACjB,GAAG,EAAE,cAAc,EAClB,IAAI,EAAE,sCAAsC,GACnD,OAAO,CAAC,oBAAoB,CAAC;CAGjC"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
12
|
+
return function (target, key) { decorator(target, key, paramIndex); }
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.CurrencyExchangeRateAdminResolver = void 0;
|
|
16
|
+
const graphql_1 = require("@nestjs/graphql");
|
|
17
|
+
const core_1 = require("@vendure/core");
|
|
18
|
+
const currency_exchange_rate_service_1 = require("../services/currency-exchange-rate.service");
|
|
19
|
+
let CurrencyExchangeRateAdminResolver = class CurrencyExchangeRateAdminResolver {
|
|
20
|
+
constructor(currencyExchangeRateService) {
|
|
21
|
+
this.currencyExchangeRateService = currencyExchangeRateService;
|
|
22
|
+
}
|
|
23
|
+
currencyExchangeRates(ctx, args) {
|
|
24
|
+
return this.currencyExchangeRateService.findAll(ctx, args.options);
|
|
25
|
+
}
|
|
26
|
+
currencyExchangeRate(ctx, args) {
|
|
27
|
+
return this.currencyExchangeRateService.findOne(ctx, args.id);
|
|
28
|
+
}
|
|
29
|
+
updateCurrencyExchangeRate(ctx, args) {
|
|
30
|
+
return this.currencyExchangeRateService.update(ctx, args.input);
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
exports.CurrencyExchangeRateAdminResolver = CurrencyExchangeRateAdminResolver;
|
|
34
|
+
__decorate([
|
|
35
|
+
(0, graphql_1.Query)(),
|
|
36
|
+
(0, core_1.Allow)(core_1.Permission.ReadSettings),
|
|
37
|
+
__param(0, (0, core_1.Ctx)()),
|
|
38
|
+
__param(1, (0, graphql_1.Args)()),
|
|
39
|
+
__metadata("design:type", Function),
|
|
40
|
+
__metadata("design:paramtypes", [core_1.RequestContext, Object]),
|
|
41
|
+
__metadata("design:returntype", Promise)
|
|
42
|
+
], CurrencyExchangeRateAdminResolver.prototype, "currencyExchangeRates", null);
|
|
43
|
+
__decorate([
|
|
44
|
+
(0, graphql_1.Query)(),
|
|
45
|
+
(0, core_1.Allow)(core_1.Permission.ReadSettings),
|
|
46
|
+
__param(0, (0, core_1.Ctx)()),
|
|
47
|
+
__param(1, (0, graphql_1.Args)()),
|
|
48
|
+
__metadata("design:type", Function),
|
|
49
|
+
__metadata("design:paramtypes", [core_1.RequestContext, Object]),
|
|
50
|
+
__metadata("design:returntype", Promise)
|
|
51
|
+
], CurrencyExchangeRateAdminResolver.prototype, "currencyExchangeRate", null);
|
|
52
|
+
__decorate([
|
|
53
|
+
(0, core_1.Transaction)(),
|
|
54
|
+
(0, graphql_1.Mutation)(),
|
|
55
|
+
(0, core_1.Allow)(core_1.Permission.UpdateSettings),
|
|
56
|
+
__param(0, (0, core_1.Ctx)()),
|
|
57
|
+
__param(1, (0, graphql_1.Args)()),
|
|
58
|
+
__metadata("design:type", Function),
|
|
59
|
+
__metadata("design:paramtypes", [core_1.RequestContext, Object]),
|
|
60
|
+
__metadata("design:returntype", Promise)
|
|
61
|
+
], CurrencyExchangeRateAdminResolver.prototype, "updateCurrencyExchangeRate", null);
|
|
62
|
+
exports.CurrencyExchangeRateAdminResolver = CurrencyExchangeRateAdminResolver = __decorate([
|
|
63
|
+
(0, graphql_1.Resolver)(),
|
|
64
|
+
__metadata("design:paramtypes", [currency_exchange_rate_service_1.CurrencyExchangeRateService])
|
|
65
|
+
], CurrencyExchangeRateAdminResolver);
|
|
66
|
+
//# sourceMappingURL=admin.resolver.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"admin.resolver.js","sourceRoot":"","sources":["../../src/api/admin.resolver.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAA,6CAAkE;AAClE,wCASuB;AAEvB,+FAAyF;AAIlF,IAAM,iCAAiC,GAAvC,MAAM,iCAAiC;IAC5C,YAAoB,2BAAwD;QAAxD,gCAA2B,GAA3B,2BAA2B,CAA6B;IAAG,CAAC;IAIhF,qBAAqB,CACZ,GAAmB,EAClB,IAAyD;QAEjE,OAAO,IAAI,CAAC,2BAA2B,CAAC,OAAO,CAAC,GAAG,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;IACrE,CAAC;IAID,oBAAoB,CAAQ,GAAmB,EAAU,IAAgB;QACvE,OAAO,IAAI,CAAC,2BAA2B,CAAC,OAAO,CAAC,GAAG,EAAE,IAAI,CAAC,EAAE,CAAC,CAAC;IAChE,CAAC;IAKD,0BAA0B,CACjB,GAAmB,EAClB,IAA4C;QAEpD,OAAO,IAAI,CAAC,2BAA2B,CAAC,MAAM,CAAC,GAAG,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;IAClE,CAAC;CACF,CAAA;AA3BY,8EAAiC;AAK5C;IAFC,IAAA,eAAK,GAAE;IACP,IAAA,YAAK,EAAC,iBAAU,CAAC,YAAY,CAAC;IAE5B,WAAA,IAAA,UAAG,GAAE,CAAA;IACL,WAAA,IAAA,cAAI,GAAE,CAAA;;qCADK,qBAAc;;8EAI3B;AAID;IAFC,IAAA,eAAK,GAAE;IACP,IAAA,YAAK,EAAC,iBAAU,CAAC,YAAY,CAAC;IACT,WAAA,IAAA,UAAG,GAAE,CAAA;IAAuB,WAAA,IAAA,cAAI,GAAE,CAAA;;qCAAvB,qBAAc;;6EAE9C;AAKD;IAHC,IAAA,kBAAW,GAAE;IACb,IAAA,kBAAQ,GAAE;IACV,IAAA,YAAK,EAAC,iBAAU,CAAC,cAAc,CAAC;IAE9B,WAAA,IAAA,UAAG,GAAE,CAAA;IACL,WAAA,IAAA,cAAI,GAAE,CAAA;;qCADK,qBAAc;;mFAI3B;4CA1BU,iCAAiC;IAD7C,IAAA,kBAAQ,GAAE;qCAEwC,4DAA2B;GADjE,iCAAiC,CA2B7C"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api-extensions.d.ts","sourceRoot":"","sources":["../../src/api/api-extensions.ts"],"names":[],"mappings":"AAAA,OAAO,GAAG,MAAM,aAAa,CAAC;AAE9B,KAAK,QAAQ,GAAG,UAAU,CAAC,OAAO,GAAG,CAAC,CAAC;AA0BvC,eAAO,MAAM,iBAAiB,EAAE,QAE/B,CAAC;AAEF,eAAO,MAAM,kBAAkB,EAAE,QAsBhC,CAAC"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.adminApiExtensions = exports.shopApiExtensions = void 0;
|
|
7
|
+
const graphql_tag_1 = __importDefault(require("graphql-tag"));
|
|
8
|
+
const commonApiExtensions = (0, graphql_tag_1.default) `
|
|
9
|
+
type CurrencyExchangeRate implements Node {
|
|
10
|
+
id: ID!
|
|
11
|
+
createdAt: DateTime!
|
|
12
|
+
updatedAt: DateTime!
|
|
13
|
+
code: String!
|
|
14
|
+
"The currency rate is expressed in: rate = units of it per one unit of code."
|
|
15
|
+
baseCurrency: String!
|
|
16
|
+
rate: Float!
|
|
17
|
+
enabled: Boolean!
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
type CurrencyExchangeRateList implements PaginatedList {
|
|
21
|
+
items: [CurrencyExchangeRate!]!
|
|
22
|
+
totalItems: Int!
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
extend type Query {
|
|
26
|
+
currencyExchangeRates(options: CurrencyExchangeRateListOptions): CurrencyExchangeRateList!
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
input CurrencyExchangeRateListOptions
|
|
30
|
+
`;
|
|
31
|
+
exports.shopApiExtensions = (0, graphql_tag_1.default) `
|
|
32
|
+
${commonApiExtensions}
|
|
33
|
+
`;
|
|
34
|
+
exports.adminApiExtensions = (0, graphql_tag_1.default) `
|
|
35
|
+
${commonApiExtensions}
|
|
36
|
+
|
|
37
|
+
extend type CurrencyExchangeRate {
|
|
38
|
+
useCustomRate: Boolean!
|
|
39
|
+
customRate: Float
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
extend type Query {
|
|
43
|
+
currencyExchangeRate(id: ID!): CurrencyExchangeRate
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
extend type Mutation {
|
|
47
|
+
updateCurrencyExchangeRate(input: UpdateCurrencyExchangeRateInput!): CurrencyExchangeRate!
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
input UpdateCurrencyExchangeRateInput {
|
|
51
|
+
id: ID!
|
|
52
|
+
enabled: Boolean!
|
|
53
|
+
useCustomRate: Boolean!
|
|
54
|
+
customRate: Float
|
|
55
|
+
}
|
|
56
|
+
`;
|
|
57
|
+
//# sourceMappingURL=api-extensions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api-extensions.js","sourceRoot":"","sources":["../../src/api/api-extensions.ts"],"names":[],"mappings":";;;;;;AAAA,8DAA8B;AAI9B,MAAM,mBAAmB,GAAa,IAAA,qBAAG,EAAA;;;;;;;;;;;;;;;;;;;;;;CAsBxC,CAAC;AAEW,QAAA,iBAAiB,GAAa,IAAA,qBAAG,EAAA;IAC1C,mBAAmB;CACtB,CAAC;AAEW,QAAA,kBAAkB,GAAa,IAAA,qBAAG,EAAA;IAC3C,mBAAmB;;;;;;;;;;;;;;;;;;;;;CAqBtB,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { ListQueryOptions, PaginatedList, RequestContext } from '@vendure/core';
|
|
2
|
+
import { CurrencyExchangeRate } from '../entities/currency-exchange-rate.entity';
|
|
3
|
+
import { CurrencyExchangeRateService } from '../services/currency-exchange-rate.service';
|
|
4
|
+
export declare class CurrencyExchangeRateShopResolver {
|
|
5
|
+
private currencyExchangeRateService;
|
|
6
|
+
constructor(currencyExchangeRateService: CurrencyExchangeRateService);
|
|
7
|
+
/** Only the currencies an admin enabled, in the current base: the storefront offers nothing else. */
|
|
8
|
+
currencyExchangeRates(ctx: RequestContext, args: {
|
|
9
|
+
options?: ListQueryOptions<CurrencyExchangeRate>;
|
|
10
|
+
}): Promise<PaginatedList<CurrencyExchangeRate>>;
|
|
11
|
+
}
|
|
12
|
+
export declare class CurrencyExchangeRateShopFieldResolver {
|
|
13
|
+
rate(currencyExchangeRate: CurrencyExchangeRate): number;
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=shop.resolver.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shop.resolver.d.ts","sourceRoot":"","sources":["../../src/api/shop.resolver.ts"],"names":[],"mappings":"AACA,OAAO,EAAc,gBAAgB,EAAE,aAAa,EAAc,cAAc,EAAE,MAAM,eAAe,CAAC;AACxG,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AAEjF,OAAO,EAAE,2BAA2B,EAAE,MAAM,4CAA4C,CAAC;AAEzF,qBACa,gCAAgC;IAC/B,OAAO,CAAC,2BAA2B;gBAA3B,2BAA2B,EAAE,2BAA2B;IAE5E,qGAAqG;IAG/F,qBAAqB,CAClB,GAAG,EAAE,cAAc,EAClB,IAAI,EAAE;QAAE,OAAO,CAAC,EAAE,gBAAgB,CAAC,oBAAoB,CAAC,CAAA;KAAE,GACjE,OAAO,CAAC,aAAa,CAAC,oBAAoB,CAAC,CAAC;CAMhD;AAED,qBACa,qCAAqC;IAEhD,IAAI,CAAC,oBAAoB,EAAE,oBAAoB;CAIhD"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
12
|
+
return function (target, key) { decorator(target, key, paramIndex); }
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.CurrencyExchangeRateShopFieldResolver = exports.CurrencyExchangeRateShopResolver = void 0;
|
|
16
|
+
const graphql_1 = require("@nestjs/graphql");
|
|
17
|
+
const core_1 = require("@vendure/core");
|
|
18
|
+
const currency_exchange_rate_entity_1 = require("../entities/currency-exchange-rate.entity");
|
|
19
|
+
const effective_rate_1 = require("../effective-rate");
|
|
20
|
+
const currency_exchange_rate_service_1 = require("../services/currency-exchange-rate.service");
|
|
21
|
+
let CurrencyExchangeRateShopResolver = class CurrencyExchangeRateShopResolver {
|
|
22
|
+
constructor(currencyExchangeRateService) {
|
|
23
|
+
this.currencyExchangeRateService = currencyExchangeRateService;
|
|
24
|
+
}
|
|
25
|
+
/** Only the currencies an admin enabled, in the current base: the storefront offers nothing else. */
|
|
26
|
+
async currencyExchangeRates(ctx, args) {
|
|
27
|
+
return this.currencyExchangeRateService.findAll(ctx, args.options, {
|
|
28
|
+
enabled: true,
|
|
29
|
+
baseCurrency: await this.currencyExchangeRateService.getBaseCurrency(ctx),
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
exports.CurrencyExchangeRateShopResolver = CurrencyExchangeRateShopResolver;
|
|
34
|
+
__decorate([
|
|
35
|
+
(0, graphql_1.Query)(),
|
|
36
|
+
(0, core_1.Allow)(core_1.Permission.Public),
|
|
37
|
+
__param(0, (0, core_1.Ctx)()),
|
|
38
|
+
__param(1, (0, graphql_1.Args)()),
|
|
39
|
+
__metadata("design:type", Function),
|
|
40
|
+
__metadata("design:paramtypes", [core_1.RequestContext, Object]),
|
|
41
|
+
__metadata("design:returntype", Promise)
|
|
42
|
+
], CurrencyExchangeRateShopResolver.prototype, "currencyExchangeRates", null);
|
|
43
|
+
exports.CurrencyExchangeRateShopResolver = CurrencyExchangeRateShopResolver = __decorate([
|
|
44
|
+
(0, graphql_1.Resolver)(),
|
|
45
|
+
__metadata("design:paramtypes", [currency_exchange_rate_service_1.CurrencyExchangeRateService])
|
|
46
|
+
], CurrencyExchangeRateShopResolver);
|
|
47
|
+
let CurrencyExchangeRateShopFieldResolver = class CurrencyExchangeRateShopFieldResolver {
|
|
48
|
+
rate(currencyExchangeRate) {
|
|
49
|
+
// The Admin API forbids a custom rate that is on but empty, so this falls back only for old rows.
|
|
50
|
+
return (0, effective_rate_1.effectiveRate)(currencyExchangeRate) ?? currencyExchangeRate.rate;
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
exports.CurrencyExchangeRateShopFieldResolver = CurrencyExchangeRateShopFieldResolver;
|
|
54
|
+
__decorate([
|
|
55
|
+
(0, graphql_1.ResolveField)(),
|
|
56
|
+
__metadata("design:type", Function),
|
|
57
|
+
__metadata("design:paramtypes", [currency_exchange_rate_entity_1.CurrencyExchangeRate]),
|
|
58
|
+
__metadata("design:returntype", void 0)
|
|
59
|
+
], CurrencyExchangeRateShopFieldResolver.prototype, "rate", null);
|
|
60
|
+
exports.CurrencyExchangeRateShopFieldResolver = CurrencyExchangeRateShopFieldResolver = __decorate([
|
|
61
|
+
(0, graphql_1.Resolver)('CurrencyExchangeRate')
|
|
62
|
+
], CurrencyExchangeRateShopFieldResolver);
|
|
63
|
+
//# sourceMappingURL=shop.resolver.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shop.resolver.js","sourceRoot":"","sources":["../../src/api/shop.resolver.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAA,6CAAsE;AACtE,wCAAwG;AACxG,6FAAiF;AACjF,sDAAkD;AAClD,+FAAyF;AAGlF,IAAM,gCAAgC,GAAtC,MAAM,gCAAgC;IAC3C,YAAoB,2BAAwD;QAAxD,gCAA2B,GAA3B,2BAA2B,CAA6B;IAAG,CAAC;IAEhF,qGAAqG;IAG/F,AAAN,KAAK,CAAC,qBAAqB,CAClB,GAAmB,EAClB,IAA0D;QAElE,OAAO,IAAI,CAAC,2BAA2B,CAAC,OAAO,CAAC,GAAG,EAAE,IAAI,CAAC,OAAO,EAAE;YACjE,OAAO,EAAE,IAAI;YACb,YAAY,EAAE,MAAM,IAAI,CAAC,2BAA2B,CAAC,eAAe,CAAC,GAAG,CAAC;SAC1E,CAAC,CAAC;IACL,CAAC;CACF,CAAA;AAfY,4EAAgC;AAMrC;IAFL,IAAA,eAAK,GAAE;IACP,IAAA,YAAK,EAAC,iBAAU,CAAC,MAAM,CAAC;IAEtB,WAAA,IAAA,UAAG,GAAE,CAAA;IACL,WAAA,IAAA,cAAI,GAAE,CAAA;;qCADK,qBAAc;;6EAO3B;2CAdU,gCAAgC;IAD5C,IAAA,kBAAQ,GAAE;qCAEwC,4DAA2B;GADjE,gCAAgC,CAe5C;AAGM,IAAM,qCAAqC,GAA3C,MAAM,qCAAqC;IAEhD,IAAI,CAAC,oBAA0C;QAC7C,kGAAkG;QAClG,OAAO,IAAA,8BAAa,EAAC,oBAAoB,CAAC,IAAI,oBAAoB,CAAC,IAAI,CAAC;IAC1E,CAAC;CACF,CAAA;AANY,sFAAqC;AAEhD;IADC,IAAA,sBAAY,GAAE;;qCACY,oDAAoB;;iEAG9C;gDALU,qCAAqC;IADjD,IAAA,kBAAQ,EAAC,sBAAsB,CAAC;GACpB,qCAAqC,CAMjD"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare const EXCHANGE_RATES_PLUGIN_OPTIONS: unique symbol;
|
|
2
|
+
export declare const loggerCtx = "ExchangeRatesPlugin";
|
|
3
|
+
/** Every 3 hours at minute 40. */
|
|
4
|
+
export declare const DEFAULT_SYNC_SCHEDULE = "40 2-23/3 * * *";
|
|
5
|
+
export declare const SYNC_TASK_ID = "currency-exchange-rate-updater";
|
|
6
|
+
/** How long a source request may take before it is aborted, in milliseconds. */
|
|
7
|
+
export declare const DEFAULT_SOURCE_TIMEOUT = 10000;
|
|
8
|
+
/** decimal(19, 8) holds 11 integer digits. */
|
|
9
|
+
export declare const MAX_RATE = 100000000000;
|
|
10
|
+
//# sourceMappingURL=constants.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,6BAA6B,eAA0C,CAAC;AACrF,eAAO,MAAM,SAAS,wBAAwB,CAAC;AAE/C,kCAAkC;AAClC,eAAO,MAAM,qBAAqB,oBAAoB,CAAC;AAEvD,eAAO,MAAM,YAAY,mCAAmC,CAAC;AAE7D,gFAAgF;AAChF,eAAO,MAAM,sBAAsB,QAAS,CAAC;AAE7C,8CAA8C;AAC9C,eAAO,MAAM,QAAQ,eAAO,CAAC"}
|