feat: client-side currency conversion with admin-configurable rates
Some checks failed
Architecture Governance / architecture (push) Has been cancelled

- CurrencyRatesService: RUB-based rates, persisted via localStorage
- CurrencyConvertPipe: impure pipe converting item price to selected currency
- Admin settings page: editable currency rates form
- Applied conversion to product-card, product-information, quick-view-dialog,
  delivery-information, compare-table, cart totals + payment payload

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
sdarbinyan
2026-08-15 01:45:12 +04:00
parent d8c078ad5a
commit 4510eb769a
20 changed files with 235 additions and 30 deletions

View File

@@ -22,12 +22,14 @@ import { TenantResolverService } from '../../core/config/tenant-resolver.service
import { UserNotificationService } from '../../features/website/user-experience/services/user-notification.service';
import { ConfirmDialogComponent } from '../../shared/ui/confirm-dialog/confirm-dialog.component';
import { DialogComponent } from '../../shared/ui/dialog/dialog.component';
import { CurrencyConvertPipe } from '../../pipes/currency-convert.pipe';
import { CurrencyRatesService } from '../../services/currency-rates.service';
type PaymentMethod = 'qr' | 'card';
@Component({
selector: 'app-cart',
imports: [DecimalPipe, RouterLink, FormsModule, DeliverySelectorComponent, TelegramLoginComponent, LangRoutePipe, TranslatePipe, IconComponent, EmptyStateComponent, ButtonComponent, ConfirmDialogComponent, DialogComponent],
imports: [DecimalPipe, RouterLink, FormsModule, DeliverySelectorComponent, TelegramLoginComponent, LangRoutePipe, TranslatePipe, IconComponent, EmptyStateComponent, ButtonComponent, ConfirmDialogComponent, DialogComponent, CurrencyConvertPipe],
templateUrl: './cart.component.html',
styleUrls: ['./cart.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
@@ -80,6 +82,7 @@ export class CartComponent implements OnDestroy {
private configService = inject(ConfigService);
private tenantResolver = inject(TenantResolverService);
private currencyRates = inject(CurrencyRatesService);
constructor(
private cartService: CartService,
@@ -183,6 +186,10 @@ export class CartComponent implements OnDestroy {
itemName(item: Item): string { return getTranslatedField(item, 'name', this.langService.currentLanguage()); }
itemDesc(item: Item): string { return getTranslatedField(item, 'simpleDescription', this.langService.currentLanguage()); }
get currentCurrency(): string { return this.langService.currentCurrency(); }
/** Cart totals are summed in the base currency (RUB); convert to whatever the shopper has selected. */
convertTotal(amountInBaseCurrency: number): number {
return this.currencyRates.convert(amountInBaseCurrency, this.currencyRates.baseCurrency, this.currentCurrency);
}
get isCheckoutDisabled(): boolean { return !this.termsAccepted || !this.isAuthenticated() || !this.allRequiredDeliveriesSelected(); }
selectDelivery(itemID: number, selectedDelivery: DeliveryOption | null): void {
@@ -255,7 +262,7 @@ export class CartComponent implements OnDestroy {
createPayment(paymentMethod: PaymentMethod): void {
const orderId = this.generateOrderId();
const paymentPayload = {
amount: Number(this.totalWithDelivery()),
amount: Number(this.convertTotal(this.totalWithDelivery())),
currency: this.langService.currentCurrency(),
siteuserID: this.getPaymentUserId(),
siteorderID: orderId,