@revenexx/cover 0.1.19 → 0.1.20

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.
@@ -11,7 +11,7 @@ const { icon } = useIcons();
11
11
  const { t, locale, locales } = useI18n();
12
12
  const switchLocalePath = useSwitchLocalePath();
13
13
  const { markets } = useMarkets();
14
- const { market, setMarket, region, currencySymbol } = useLocalePreferences(markets);
14
+ const { market, setMarket, region, currencies, currency, currencySymbol, setCurrency } = useLocalePreferences(markets);
15
15
 
16
16
  const open = ref(false);
17
17
 
@@ -44,6 +44,9 @@ async function onLanguageChange(code: string): Promise<void> {
44
44
  }
45
45
  }
46
46
 
47
+ const currencyItems = computed(() =>
48
+ currencies.value.map(code => ({ code, name: code, active: code === currency.value })));
49
+
47
50
  async function onMarketChange(code: string): Promise<void> {
48
51
  setMarket(code);
49
52
  // The market may not offer the current language — fall back to its first.
@@ -51,6 +54,18 @@ async function onMarketChange(code: string): Promise<void> {
51
54
  if (fallback && !languageItems.value.some(item => item.active)) {
52
55
  await navigateTo(fallback.to);
53
56
  }
57
+ // The market may not trade in the current currency — re-resolve prices.
58
+ reloadNuxtApp({ ttl: 0 });
59
+ }
60
+
61
+ function onCurrencyChange(code: string): void {
62
+ if (code === currency.value) {
63
+ return;
64
+ }
65
+ setCurrency(code);
66
+ // Re-SSR so the catalog, cart and checkout all re-price in the new
67
+ // currency (prices come from the API, keyed by the currency cookie).
68
+ reloadNuxtApp({ ttl: 0 });
54
69
  }
55
70
 
56
71
  /** The trigger states the full current selection, e.g. "Deutschland (Deutsch) in €". */
@@ -108,8 +123,24 @@ const triggerLabel = computed(() =>
108
123
  />
109
124
  </UFormField>
110
125
 
111
- <!-- Currency: defined by the market -->
112
- <div class="flex items-center justify-between text-sm">
126
+ <!-- Currency: chosen within the currencies the market trades in -->
127
+ <UFormField
128
+ v-if="currencyItems.length > 1"
129
+ :label="t('topbar.localePanel.currency')"
130
+ >
131
+ <USelect
132
+ :model-value="currency"
133
+ value-key="code"
134
+ label-key="name"
135
+ :items="currencyItems"
136
+ class="w-full"
137
+ @update:model-value="(code: string) => onCurrencyChange(code)"
138
+ />
139
+ </UFormField>
140
+ <div
141
+ v-else
142
+ class="flex items-center justify-between text-sm"
143
+ >
113
144
  <span class="text-muted">{{ t('topbar.localePanel.currency') }}</span>
114
145
  <span class="font-medium">{{ currencySymbol }}</span>
115
146
  </div>
@@ -4,10 +4,11 @@ import type { ShopMarket } from "../interfaces/market";
4
4
 
5
5
  /**
6
6
  * Market-driven locale preferences: the shop's markets (markets app) carry
7
- * region, currency and the offered locales the selector picks a market,
8
- * language stays an independent choice within the market's locales
9
- * (Smashing Magazine, "Designing A Better Language Selector"). Persisted
10
- * as a cookie so SSR renders the same selection.
7
+ * region, the offered locales and the currencies the market trades in — the
8
+ * selector picks a market, while language and currency stay independent
9
+ * choices within the market's options (Smashing Magazine, "Designing A Better
10
+ * Language Selector"). All three persist as cookies so SSR renders the same
11
+ * selection and the BFF resolves prices in the chosen currency.
11
12
  */
12
13
 
13
14
  const CURRENCY_SYMBOLS: Record<string, string> = {
@@ -22,6 +23,12 @@ export function useLocalePreferences(markets: Ref<ShopMarket[]>) {
22
23
  default: () => "",
23
24
  watch: true,
24
25
  });
26
+ // Read server-side by the BFF (resolveSelectedCurrency) to price the
27
+ // catalog, cart and order in the buyer's currency.
28
+ const currencyCode = useCookie<string>("cover-currency", {
29
+ default: () => "",
30
+ watch: true,
31
+ });
25
32
 
26
33
  const market = computed<ShopMarket | null>(() => {
27
34
  if (!markets.value.length) {
@@ -38,8 +45,33 @@ export function useLocalePreferences(markets: Ref<ShopMarket[]>) {
38
45
  };
39
46
 
40
47
  const region = computed(() => market.value?.name ?? "");
41
- const currency = computed(() => market.value?.currency ?? "EUR");
48
+
49
+ /** The currencies the active market offers (falls back to its base currency). */
50
+ const currencies = computed<string[]>(() => {
51
+ const offered = market.value?.currencies?.map(c => c.code) ?? [];
52
+ if (offered.length) {
53
+ return offered;
54
+ }
55
+ return market.value?.currency ? [market.value.currency] : ["EUR"];
56
+ });
57
+
58
+ /** The market's default currency — initial choice and fallback. */
59
+ const defaultCurrency = computed(() => {
60
+ const flagged = market.value?.currencies?.find(c => c.isDefault)?.code;
61
+ return flagged ?? market.value?.currency ?? currencies.value[0] ?? "EUR";
62
+ });
63
+
64
+ /** The active currency: the cookie choice if the market offers it, else the default. */
65
+ const currency = computed(() => {
66
+ const chosen = currencyCode.value;
67
+ return chosen && currencies.value.includes(chosen) ? chosen : defaultCurrency.value;
68
+ });
69
+
42
70
  const currencySymbol = computed(() => CURRENCY_SYMBOLS[currency.value] ?? currency.value);
43
71
 
44
- return { market, setMarket, region, currency, currencySymbol };
72
+ const setCurrency = (code: string): void => {
73
+ currencyCode.value = code;
74
+ };
75
+
76
+ return { market, setMarket, region, currencies, currency, defaultCurrency, currencySymbol, setCurrency };
45
77
  }
@@ -1,6 +1,7 @@
1
1
  /**
2
- * Storefront view of the markets app: a market bundles currency and
3
- * locales (language + country); the locale selector is driven by this.
2
+ * Storefront view of the markets app: a market bundles its base currency,
3
+ * locales (language + country) and the currencies it trades in; the locale
4
+ * selector is driven by this.
4
5
  */
5
6
  export interface ShopMarketLocale {
6
7
  /** BCP-47 code, e.g. "de-DE". */
@@ -12,13 +13,25 @@ export interface ShopMarketLocale {
12
13
  isDefault: boolean;
13
14
  }
14
15
 
16
+ export interface ShopMarketCurrency {
17
+ /** ISO 4217 code, e.g. "EUR". */
18
+ code: string;
19
+ isDefault: boolean;
20
+ }
21
+
15
22
  export interface ShopMarket {
16
23
  id: string;
17
24
  /** Stable market code, e.g. "de". */
18
25
  code: string;
19
26
  name: string;
20
- /** ISO 4217, e.g. "EUR". */
27
+ /** ISO 4217 base currency, e.g. "EUR" — the fallback when none is chosen. */
21
28
  currency: string;
22
29
  isDefault: boolean;
23
30
  locales: ShopMarketLocale[];
31
+ /**
32
+ * The currencies the market trades in (loosely configured in the markets
33
+ * app). Empty falls back to the single base `currency`; the selector lets
34
+ * the buyer pick one and prices resolve in it.
35
+ */
36
+ currencies: ShopMarketCurrency[];
24
37
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@revenexx/cover",
3
- "version": "0.1.19",
3
+ "version": "0.1.20",
4
4
  "description": "Cover \u2014 revenexx design system for Nuxt. Distributed as a Nuxt layer: generic UI components, theming tokens and stores shared by the demo shop, custom storefronts and the Blokkli theme.",
5
5
  "type": "module",
6
6
  "main": "./nuxt.config.ts",
@@ -16,6 +16,10 @@ async function resolveLiveOverrides(
16
16
  subtotal: number,
17
17
  ): Promise<CartCalculationOverrides> {
18
18
  const overrides: CartCalculationOverrides = {};
19
+ const currency = resolveSelectedCurrency(event);
20
+ if (currency) {
21
+ overrides.currency = currency;
22
+ }
19
23
 
20
24
  if (resolvePriceServiceKey(event) === "api" && body.items.length) {
21
25
  try {
@@ -25,19 +29,31 @@ async function resolveLiveOverrides(
25
29
  sku: it.sku ? String(it.sku) : undefined,
26
30
  quantity: Number(it.quantity) || 1,
27
31
  })) as Models.PriceResolveItem[],
28
- }) as unknown as { prices: Array<{ product_id: string | null; sku: string | null; tax_rate: number | null }> };
32
+ ...(currency ? { currency } : {}),
33
+ }) as unknown as { prices: Array<{ product_id: string | null; sku: string | null; tax_rate: number | null; on_request: boolean; unit_price: number | null }> };
29
34
  const itemTaxRates: Record<string, number> = {};
35
+ const itemUnitPrices: Record<string, number> = {};
30
36
  for (const p of prices) {
31
- if (p.tax_rate == null) continue;
32
- if (p.product_id) itemTaxRates[p.product_id] = p.tax_rate;
33
- if (p.sku) itemTaxRates[p.sku] = p.tax_rate;
37
+ if (p.tax_rate != null) {
38
+ if (p.product_id) itemTaxRates[p.product_id] = p.tax_rate;
39
+ if (p.sku) itemTaxRates[p.sku] = p.tax_rate;
40
+ }
41
+ // Re-price in the resolved currency; on-request items keep the
42
+ // client price (the cart shows them as quote positions).
43
+ if (!p.on_request && p.unit_price != null) {
44
+ if (p.product_id) itemUnitPrices[p.product_id] = p.unit_price;
45
+ if (p.sku) itemUnitPrices[p.sku] = p.unit_price;
46
+ }
34
47
  }
35
48
  if (Object.keys(itemTaxRates).length) {
36
49
  overrides.itemTaxRates = itemTaxRates;
37
50
  }
51
+ if (Object.keys(itemUnitPrices).length) {
52
+ overrides.itemUnitPrices = itemUnitPrices;
53
+ }
38
54
  }
39
55
  catch (err) {
40
- getLogService().error("Cart item tax-rate resolution failed", apiErrorContext(err));
56
+ getLogService().error("Cart item price/tax resolution failed", apiErrorContext(err));
41
57
  }
42
58
  }
43
59
 
@@ -47,6 +63,7 @@ async function resolveLiveOverrides(
47
63
  const { rates } = await useRevenexxSdk().shipping.shippingRates({
48
64
  orderValue: subtotal,
49
65
  country: "",
66
+ ...(currency ? { currency } : {}),
50
67
  }) as unknown as { rates: Array<{ code: string; price: number; tax_rate: number | null }> };
51
68
  const rate = rates.find(r => r.code === method) ?? rates[0];
52
69
  if (rate) {
@@ -91,7 +108,7 @@ export default defineEventHandler(async (event) => {
91
108
  }
92
109
  const subtotal = base.totals.find(row => row.key === "subtotal")?.amount ?? 0;
93
110
  const overrides = await resolveLiveOverrides(event, body, subtotal);
94
- if (!overrides.shipping && !overrides.itemTaxRates) {
111
+ if (!overrides.shipping && !overrides.itemTaxRates && !overrides.itemUnitPrices && !overrides.currency) {
95
112
  return base;
96
113
  }
97
114
  return await service.calculate(body, context, locale, overrides);
@@ -128,6 +128,11 @@ export default defineEventHandler(async (event) => {
128
128
  throw createError({ status: 422, message: addressError });
129
129
  }
130
130
 
131
+ // The buyer's chosen currency (locale selector cookie) — forwarded to
132
+ // every resolve so the order is priced, shipped and charged in one
133
+ // currency. Undefined → the apps fall back to their EUR price lists.
134
+ const selectedCurrency = resolveSelectedCurrency(event);
135
+
131
136
  const liveShipping = resolveShippingServiceKey(event) === "api";
132
137
  if (
133
138
  !liveShipping
@@ -174,6 +179,7 @@ export default defineEventHandler(async (event) => {
174
179
  orderValue: calculation.totals.find(row => row.key === "subtotal")?.amount
175
180
  ?? calculation.totals.find(row => row.key === "total")?.amount ?? 0,
176
181
  country: String(address?.country ?? ""),
182
+ ...(selectedCurrency ? { currency: selectedCurrency } : {}),
177
183
  }) as unknown as { rates: Array<{ code: string; price: number; tax_rate: number | null }> };
178
184
  const rate = rates.find(r => r.code === body.deliveryMethod);
179
185
  if (!rate) {
@@ -224,26 +230,35 @@ export default defineEventHandler(async (event) => {
224
230
  // app then taxes items + shipping and computes grand_total itself.
225
231
  const orderItems = body.items as Array<Record<string, unknown>>;
226
232
  const itemRate = new Map<string, number>();
233
+ const itemPrice = new Map<string, number>();
227
234
  try {
228
235
  const { prices: resolved } = await useRevenexxSdk().prices.pricesResolve({
229
236
  items: orderItems.map(it => ({ product_id: String(it.id), sku: it.sku ? String(it.sku) : undefined, quantity: Number(it.quantity) || 1 })) as Models.PriceResolveItem[],
230
237
  ...(contactId ? { contactId } : {}),
231
238
  ...(organizationId ? { organizationId } : {}),
232
- }) as unknown as { prices: Array<{ product_id: string | null; sku: string | null; tax_rate: number | null }> };
239
+ ...(selectedCurrency ? { currency: selectedCurrency } : {}),
240
+ }) as unknown as { prices: Array<{ product_id: string | null; sku: string | null; tax_rate: number | null; on_request: boolean; unit_price: number | null }> };
233
241
  for (const p of resolved) {
234
- if (p.tax_rate == null) continue;
235
- if (p.product_id) itemRate.set(p.product_id, p.tax_rate);
236
- if (p.sku) itemRate.set(p.sku, p.tax_rate);
242
+ if (p.tax_rate != null) {
243
+ if (p.product_id) itemRate.set(p.product_id, p.tax_rate);
244
+ if (p.sku) itemRate.set(p.sku, p.tax_rate);
245
+ }
246
+ // Re-price in the resolved currency (the orders app is the SoR
247
+ // for totals, but it must be fed prices in the buyer's currency).
248
+ if (!p.on_request && p.unit_price != null) {
249
+ if (p.product_id) itemPrice.set(p.product_id, p.unit_price);
250
+ if (p.sku) itemPrice.set(p.sku, p.unit_price);
251
+ }
237
252
  }
238
253
  }
239
254
  catch (rateErr) {
240
- getLogService().error("Item tax-rate resolution failed", apiErrorContext(rateErr));
255
+ getLogService().error("Item price/tax resolution failed", apiErrorContext(rateErr));
241
256
  }
242
257
 
243
258
  const placed = await useRevenexxSdk().orders.ordersPlace({
244
259
  ...(contactId ? { contactId } : {}),
245
260
  ...(organizationId ? { organizationId } : {}),
246
- currency: calculation.currency,
261
+ currency: selectedCurrency ?? calculation.currency,
247
262
  ...(body.orderNumber ? { customerOrderNumber: body.orderNumber } : {}),
248
263
  buyer: user ? { name: user.name, email: user.email } : { email: body.contactEmail ?? null },
249
264
  billingAddress: (billing ?? null) as object,
@@ -262,7 +277,7 @@ export default defineEventHandler(async (event) => {
262
277
  sku: String(item.sku ?? "") || undefined,
263
278
  name: String(item.name ?? item.sku ?? item.id),
264
279
  quantity: calculation.lines[index]?.adjustedQuantity ?? Number(item.quantity),
265
- unit_price: calculation.lines[index]?.unitPrice ?? Number(item.price ?? 0),
280
+ unit_price: itemPrice.get(String(item.id)) ?? itemPrice.get(String(item.sku ?? "")) ?? calculation.lines[index]?.unitPrice ?? Number(item.price ?? 0),
266
281
  tax_rate: itemRate.get(String(item.id)) ?? itemRate.get(String(item.sku ?? "")) ?? 0,
267
282
  product: {
268
283
  ...(item.image ? { image: String(item.image) } : {}),
@@ -307,7 +322,7 @@ export default defineEventHandler(async (event) => {
307
322
  methodCode: payment.method,
308
323
  // Charge exactly what the orders app computed (incl. shipping tax).
309
324
  amount: Math.max(0, Math.round((liveOrderTotal ?? totalRow?.amount ?? 0) * 100) / 100),
310
- currency: calculation.currency,
325
+ currency: selectedCurrency ?? calculation.currency,
311
326
  country,
312
327
  orderRef: orderId,
313
328
  idempotencyKey: body.checkoutSessionToken,
@@ -8,6 +8,10 @@
8
8
  "locales": [
9
9
  { "code": "de-DE", "language": "de", "country": "DE", "isDefault": true },
10
10
  { "code": "en-GB", "language": "en", "country": "GB", "isDefault": false }
11
+ ],
12
+ "currencies": [
13
+ { "code": "EUR", "isDefault": true },
14
+ { "code": "USD", "isDefault": false }
11
15
  ]
12
16
  },
13
17
  {
@@ -19,6 +23,10 @@
19
23
  "locales": [
20
24
  { "code": "de-CH", "language": "de", "country": "CH", "isDefault": true },
21
25
  { "code": "en-GB", "language": "en", "country": "GB", "isDefault": false }
26
+ ],
27
+ "currencies": [
28
+ { "code": "CHF", "isDefault": true },
29
+ { "code": "EUR", "isDefault": false }
22
30
  ]
23
31
  }
24
32
  ]
@@ -8,10 +8,18 @@ import type { CartCalculation, CartCalculationRequest } from "../../app/interfac
8
8
  * endpoint; absent in mock mode (then the demo profile fills in).
9
9
  */
10
10
  export interface CartCalculationOverrides {
11
+ /** The currency prices/shipping resolved in — becomes the cart currency. */
12
+ currency?: string;
11
13
  /** Live shipping fee + its tax rate for the chosen method. */
12
14
  shipping?: { price: number; taxRate: number };
13
15
  /** Per-item tax rate (percent) keyed by product id or sku, from prices.resolve. */
14
16
  itemTaxRates?: Record<string, number>;
17
+ /**
18
+ * Per-item unit price (in the resolved currency) keyed by product id or
19
+ * sku. Re-prices the cart in the chosen currency instead of trusting the
20
+ * client-supplied price — the dumb BFF reads prices from the prices app.
21
+ */
22
+ itemUnitPrices?: Record<string, number>;
15
23
  }
16
24
 
17
25
  /**
@@ -1,4 +1,4 @@
1
- import type { ShopMarket, ShopMarketLocale } from "../../app/interfaces/market";
1
+ import type { ShopMarket, ShopMarketCurrency, ShopMarketLocale } from "../../app/interfaces/market";
2
2
  import type { IMarketService } from "../interfaces/market";
3
3
 
4
4
  interface ApiMarketRow {
@@ -19,6 +19,19 @@ interface ApiLocaleRow {
19
19
  position: number;
20
20
  }
21
21
 
22
+ interface ApiCurrencyRow {
23
+ code: string;
24
+ is_default: boolean;
25
+ position: number;
26
+ }
27
+
28
+ // The markets app's /context response. Typed locally because the pinned SDK
29
+ // (0.0.5) predates the `currencies` field — at runtime the app returns it.
30
+ interface ApiMarketContext {
31
+ locales?: ApiLocaleRow[];
32
+ currencies?: ApiCurrencyRow[];
33
+ }
34
+
22
35
  interface ApiListPage<T> {
23
36
  items: T[];
24
37
  }
@@ -31,8 +44,9 @@ const cache = new Map<string, { markets: ShopMarket[]; loadedAt: number }>();
31
44
 
32
45
  /**
33
46
  * Live markets via the public revenexx API (markets app):
34
- * GET /v1/markets + each market's locales. Cached briefly the selector
35
- * renders on every page and markets change rarely.
47
+ * GET /v1/markets + each market's /context (locales + currencies in one
48
+ * call). Cached briefly — the selector renders on every page and markets
49
+ * change rarely.
36
50
  */
37
51
  export class ApiMarketService implements IMarketService {
38
52
  async listMarkets(): Promise<ShopMarket[]> {
@@ -49,21 +63,25 @@ export class ApiMarketService implements IMarketService {
49
63
  .sort((a, b) => a.position - b.position || a.code.localeCompare(b.code));
50
64
 
51
65
  const markets = await Promise.all(active.map(async (market): Promise<ShopMarket> => {
52
- const { items: locales } = await sdk.markets.marketsLocalesList({ marketId: market.id }) as unknown as ApiListPage<ApiLocaleRow>;
66
+ const context = await sdk.markets.marketsContext({ id: market.id }) as unknown as ApiMarketContext;
67
+ const locales = (context.locales ?? []).slice().sort((a, b) => a.position - b.position);
68
+ const currencies = (context.currencies ?? []).slice().sort((a, b) => a.position - b.position);
53
69
  return {
54
70
  id: market.id,
55
71
  code: market.code,
56
72
  name: market.name,
57
73
  currency: market.currency,
58
74
  isDefault: market.is_default,
59
- locales: locales
60
- .sort((a, b) => a.position - b.position)
61
- .map((locale): ShopMarketLocale => ({
62
- code: locale.code,
63
- language: locale.language,
64
- country: locale.country,
65
- isDefault: locale.is_default,
66
- })),
75
+ locales: locales.map((locale): ShopMarketLocale => ({
76
+ code: locale.code,
77
+ language: locale.language,
78
+ country: locale.country,
79
+ isDefault: locale.is_default,
80
+ })),
81
+ currencies: currencies.map((currency): ShopMarketCurrency => ({
82
+ code: currency.code,
83
+ isDefault: currency.is_default,
84
+ })),
67
85
  };
68
86
  }));
69
87
 
@@ -122,7 +122,10 @@ export class MockCartCalculationService implements ICartCalculationService {
122
122
  }
123
123
 
124
124
  const labelCode = PRICE_LABELS[item.id];
125
- const unitPrice = labelCode ? 0 : tieredUnitPrice(item, quantity);
125
+ // Live mode re-prices in the resolved currency (prices app); mock
126
+ // mode uses the cart item's tier price.
127
+ const liveUnitPrice = overrides?.itemUnitPrices?.[item.id] ?? overrides?.itemUnitPrices?.[item.sku ?? ""];
128
+ const unitPrice = labelCode ? 0 : (liveUnitPrice ?? tieredUnitPrice(item, quantity));
126
129
  const lineTotal = r2(unitPrice * quantity);
127
130
 
128
131
  const surcharges = !labelCode && SURCHARGE_SKU_PREFIXES.some(p => (item.sku ?? "").startsWith(p))
@@ -305,7 +308,7 @@ export class MockCartCalculationService implements ICartCalculationService {
305
308
  ];
306
309
 
307
310
  return {
308
- currency: "EUR",
311
+ currency: overrides?.currency ?? "EUR",
309
312
  lines,
310
313
  totals,
311
314
  orderable,
@@ -0,0 +1,20 @@
1
+ import type { H3Event } from "h3";
2
+
3
+ /** Cookie the storefront's locale selector persists the chosen currency in. */
4
+ export const CURRENCY_COOKIE_NAME = "cover-currency";
5
+
6
+ /**
7
+ * The buyer's chosen currency for server-side price resolution. Read from the
8
+ * cookie the locale selector sets — the BFF forwards it to the prices and
9
+ * shipping apps so the catalog, cart and order all resolve in one currency.
10
+ * Undefined → the prices app falls back to the price list's currency (EUR),
11
+ * so the shop works before any choice is made.
12
+ */
13
+ export function resolveSelectedCurrency(event: H3Event): string | undefined {
14
+ const raw = getCookie(event, CURRENCY_COOKIE_NAME);
15
+ if (!raw) {
16
+ return undefined;
17
+ }
18
+ const code = raw.trim().toUpperCase();
19
+ return /^[A-Z]{3}$/.test(code) ? code : undefined;
20
+ }
@@ -62,9 +62,11 @@ export async function enrichProductsWithLivePrices(event: H3Event, products: Pro
62
62
  if (products.length === 0) {
63
63
  return products;
64
64
  }
65
+ const currency = resolveSelectedCurrency(event);
65
66
  const { prices } = await useRevenexxSdk().prices.pricesResolve({
66
67
  items: products.map(p => ({ product_id: p.id, sku: p.sku, quantity: 1 })) as Models.PriceResolveItem[],
67
68
  ...priceContext(event),
69
+ ...(currency ? { currency } : {}),
68
70
  }) as unknown as { prices: ResolvedPrice[] };
69
71
 
70
72
  const byKey = new Map<string, ResolvedPrice>();
@@ -85,9 +87,11 @@ export async function enrichDetailWithLivePrices(event: H3Event, detail: Product
85
87
  if (!product) {
86
88
  return detail;
87
89
  }
90
+ const currency = resolveSelectedCurrency(event);
88
91
  const { prices } = await useRevenexxSdk().prices.pricesResolve({
89
92
  items: [{ product_id: product.id, sku: product.sku, quantity: 1 }] as Models.PriceResolveItem[],
90
93
  ...priceContext(event),
94
+ ...(currency ? { currency } : {}),
91
95
  }) as unknown as { prices: ResolvedPrice[] };
92
96
  const resolved = prices[0];
93
97
  return { ...detail, prices: resolved ? toPriceMap(resolved) : [] };