From 7222d0c9bed14f3270b7e300cc7765d1110f6dc0 Mon Sep 17 00:00:00 2001 From: nobswebdev Date: Sat, 5 Sep 2026 20:50:49 +0200 Subject: [PATCH] show multi-crypto rates in shop nav --- .../StorefrontShopViewService.spec.ts | 25 ++++++++++++----- .../services/StorefrontShopViewService.ts | 27 ++++++++++++++++--- .../storefrontCore/types/ShopRenderLocals.ts | 3 ++- .../types/StorefrontCryptoRate.ts | 4 +++ .../views/partials/shop-nav.hbs | 8 ++++-- 5 files changed, 55 insertions(+), 12 deletions(-) create mode 100644 backend/src/modules/storefrontCore/types/StorefrontCryptoRate.ts diff --git a/backend/src/modules/storefrontCore/services/StorefrontShopViewService.spec.ts b/backend/src/modules/storefrontCore/services/StorefrontShopViewService.spec.ts index 791f598..cc90992 100644 --- a/backend/src/modules/storefrontCore/services/StorefrontShopViewService.spec.ts +++ b/backend/src/modules/storefrontCore/services/StorefrontShopViewService.spec.ts @@ -8,6 +8,7 @@ import { StorefrontFeedbackCookieService } from './StorefrontFeedbackCookieServi import { StorefrontOrderAuthCookieService } from './StorefrontOrderAuthCookieService'; import { StorefrontThemeCookieService } from './StorefrontThemeCookieService'; import type { StorefrontPageMetaInput } from '../types/StorefrontPageMetaInput'; +import { PaymentMethod } from '../../payment/types/PaymentMethod'; describe('StorefrontShopViewService', () => { type ServiceOverrides = { @@ -21,8 +22,9 @@ describe('StorefrontShopViewService', () => { simplexLink: string | null; shippingNote: string | null; }; - shopSettings?: { shopName: string; shopFiatCurrency: string }; + shopSettings?: { shopName: string; shopFiatCurrency: string; enabledPaymentMethods?: PaymentMethod[] }; fiatPerXmr?: number; + fiatPerBtc?: number | null; req?: Partial> & { host?: string; }; @@ -45,13 +47,20 @@ describe('StorefrontShopViewService', () => { simplexLink: null, shippingNote: null }, - shopSettings = { shopName: 'Demo Shop', shopFiatCurrency: 'USD' }, + shopSettings = { + shopName: 'Demo Shop', + shopFiatCurrency: 'USD', + enabledPaymentMethods: [PaymentMethod.Xmr, PaymentMethod.Btc] + }, fiatPerXmr = 150, + fiatPerBtc = 60_000, req: reqOverrides = {} } = overrides; const exchangeRateService = { - getLiveFiatPerCrypto: jest.fn().mockReturnValue(fiatPerXmr) + getLiveFiatPerCrypto: jest.fn((method: PaymentMethod) => + method === PaymentMethod.Btc ? fiatPerBtc : fiatPerXmr + ) } as unknown as ExchangeRateService; const configService = { get: jest.fn().mockReturnValue(shopSettings) @@ -124,7 +133,8 @@ describe('StorefrontShopViewService', () => { simplexLink: 'https://simplex.example', shippingNote: 'Ships in 3 days' }, - fiatPerXmr: 200 + fiatPerXmr: 200, + fiatPerBtc: 80_000 }); const locals = await service.buildShopRenderLocals(req, res, defaultPage); @@ -134,7 +144,10 @@ describe('StorefrontShopViewService', () => { cartTotalQty: 3, feedback: { type: 'success', text: 'Added to cart' }, shopFiatCurrency: 'USD', - fiatPerXmr: 200, + cryptoRates: [ + { cryptoCurrency: 'XMR', fiatPerCrypto: 200 }, + { cryptoCurrency: 'BTC', fiatPerCrypto: 80_000 } + ], logoUrl: '/uploads/logo.png', faviconUrl: '/uploads/favicon.ico', simplexLink: 'https://simplex.example', @@ -264,7 +277,7 @@ describe('StorefrontShopViewService', () => { it('uses shop fiat currency in product json-ld', async () => { const { service, req, res } = createService({ - shopSettings: { shopName: 'Euro Shop', shopFiatCurrency: 'EUR' }, + shopSettings: { shopName: 'Euro Shop', shopFiatCurrency: 'EUR', enabledPaymentMethods: [PaymentMethod.Xmr] }, req: { path: '/shop/products/1/variants/2', originalUrl: '/shop/products/1/variants/2' diff --git a/backend/src/modules/storefrontCore/services/StorefrontShopViewService.ts b/backend/src/modules/storefrontCore/services/StorefrontShopViewService.ts index 99e4fd3..2431836 100644 --- a/backend/src/modules/storefrontCore/services/StorefrontShopViewService.ts +++ b/backend/src/modules/storefrontCore/services/StorefrontShopViewService.ts @@ -8,9 +8,11 @@ import { formatShortOrderId } from '../../../utils/order/formatShortOrderId'; import { toAbsoluteUrl } from '../../../utils/toAbsoluteUrl'; import { ShopSettingsService } from '../../shopSettings/services/ShopSettingsService'; import { ExchangeRateService } from '../../exchangeRate/services/ExchangeRateService'; +import { paymentMethodLabel } from '../../../consts/paymentMethodLabel'; import { PaymentMethod } from '../../payment/types/PaymentMethod'; import type { AuthorizedOrderNavItem } from '../types/AuthorizedOrderNavItem'; import type { ShopRenderLocals } from '../types/ShopRenderLocals'; +import type { StorefrontCryptoRate } from '../types/StorefrontCryptoRate'; import type { StorefrontPageMeta } from '../types/StorefrontPageMeta'; import type { StorefrontPageMetaInput } from '../types/StorefrontPageMetaInput'; import type { StorefrontProductJsonLdInput } from '../types/StorefrontProductJsonLdInput'; @@ -39,9 +41,11 @@ export class StorefrontShopViewService { const cartTotalQty = getTotalCartQtyFromCart(cart); - const { shopName, shopFiatCurrency } = this.configService.get('shopSettings') as Config['shopSettings']; + const { shopName, shopFiatCurrency, enabledPaymentMethods } = this.configService.get( + 'shopSettings' + ) as Config['shopSettings']; - const fiatPerXmr = this.exchangeRateService.getLiveFiatPerCrypto(PaymentMethod.Xmr); + const cryptoRates = this.buildCryptoRates(enabledPaymentMethods); const { logoUrl, faviconUrl, simplexLink, shippingNote } = await this.shopSettingsService.getStorefrontBranding(); @@ -61,7 +65,7 @@ export class StorefrontShopViewService { authorizedOrders, shopNavActive, shopFiatCurrency, - fiatPerXmr, + cryptoRates, logoUrl, faviconUrl, simplexLink, @@ -130,4 +134,21 @@ export class StorefrontShopViewService { return JSON.stringify(data); } + + private buildCryptoRates(enabledPaymentMethods: PaymentMethod[]): StorefrontCryptoRate[] { + return enabledPaymentMethods.flatMap(paymentMethod => { + const fiatPerCrypto = this.exchangeRateService.getLiveFiatPerCrypto(paymentMethod); + + if (fiatPerCrypto === null) { + return []; + } + + return [ + { + cryptoCurrency: paymentMethodLabel[paymentMethod], + fiatPerCrypto + } + ]; + }); + } } diff --git a/backend/src/modules/storefrontCore/types/ShopRenderLocals.ts b/backend/src/modules/storefrontCore/types/ShopRenderLocals.ts index ab1770b..49742f9 100644 --- a/backend/src/modules/storefrontCore/types/ShopRenderLocals.ts +++ b/backend/src/modules/storefrontCore/types/ShopRenderLocals.ts @@ -1,6 +1,7 @@ import type { ShopFiatCurrency } from '../../../types/ShopFiatCurrency'; import type { AuthorizedOrderNavItem } from './AuthorizedOrderNavItem'; import type { ShopNavActive } from './ShopNavActive'; +import type { StorefrontCryptoRate } from './StorefrontCryptoRate'; import type { StorefrontFeedback } from './StorefrontFeedback'; import type { StorefrontPageMeta } from './StorefrontPageMeta'; import type { StorefrontThemePreference } from './StorefrontThemePreference'; @@ -13,7 +14,7 @@ export type ShopRenderLocals = { authorizedOrders: AuthorizedOrderNavItem[]; shopNavActive: ShopNavActive; shopFiatCurrency: ShopFiatCurrency; - fiatPerXmr: number | null; + cryptoRates: StorefrontCryptoRate[]; logoUrl: string | null; faviconUrl: string | null; simplexLink: string | null; diff --git a/backend/src/modules/storefrontCore/types/StorefrontCryptoRate.ts b/backend/src/modules/storefrontCore/types/StorefrontCryptoRate.ts new file mode 100644 index 0000000..769e126 --- /dev/null +++ b/backend/src/modules/storefrontCore/types/StorefrontCryptoRate.ts @@ -0,0 +1,4 @@ +export type StorefrontCryptoRate = { + cryptoCurrency: string; + fiatPerCrypto: number; +}; diff --git a/backend/src/modules/storefrontCore/views/partials/shop-nav.hbs b/backend/src/modules/storefrontCore/views/partials/shop-nav.hbs index 834e1ee..90203c7 100644 --- a/backend/src/modules/storefrontCore/views/partials/shop-nav.hbs +++ b/backend/src/modules/storefrontCore/views/partials/shop-nav.hbs @@ -29,8 +29,12 @@ - {{#if fiatPerXmr}} -
1 XMR = {{fiatPerXmr}} {{shopFiatCurrency}}
+ {{#if cryptoRates.length}} +
+ {{#each cryptoRates}} +
1 {{cryptoCurrency}} = {{fiatPerCrypto}} {{../shopFiatCurrency}}
+ {{/each}} +
{{/if}}