diff --git a/app/components/BookListItem.vue b/app/components/BookListItem.vue index 16e367f52..2d80de846 100644 --- a/app/components/BookListItem.vue +++ b/app/components/BookListItem.vue @@ -122,10 +122,11 @@ const { formatPrice, formatDiscountedPrice } = useCurrency() const pricingItem = computed(() => bookInfo.pricingItems.value[props.priceIndex]) const originalPrice = computed(() => pricingItem.value?.price || 0) -const formattedOriginalPrice = computed(() => formatPrice(originalPrice.value)) +const priceCurrencyOverride = computed(() => pricingItem.value?.priceInDecimalByCurrency) +const formattedOriginalPrice = computed(() => formatPrice(originalPrice.value, priceCurrencyOverride.value)) const formattedDiscountedPrice = computed(() => { if (isLikerPlus.value && originalPrice.value > 0) { - return formatDiscountedPrice(originalPrice.value, PLUS_BOOK_PURCHASE_DISCOUNT) + return formatDiscountedPrice(originalPrice.value, PLUS_BOOK_PURCHASE_DISCOUNT, priceCurrencyOverride.value) } return null }) diff --git a/app/components/BookstoreItem.vue b/app/components/BookstoreItem.vue index ac825bfcd..19d5ca5e5 100644 --- a/app/components/BookstoreItem.vue +++ b/app/components/BookstoreItem.vue @@ -133,11 +133,16 @@ const bookName = computed(() => bookInfo.name.value || props.bookName) const authorName = computed(() => bookInfo.authorName.value) const price = computed(() => props.price || bookInfo.minPrice.value) -const formattedPrice = computed(() => formatPrice(price.value)) +// Only the catalog's own min-tier carries an override; a price passed in via +// props has no currency override context, so fall back to ladder conversion. +const priceCurrencyOverride = computed(() => ( + props.price ? undefined : bookInfo.minPricingItem.value?.priceInDecimalByCurrency +)) +const formattedPrice = computed(() => formatPrice(price.value, priceCurrencyOverride.value)) const formattedDiscountPrice = computed(() => { if (isLikerPlus.value && price.value > 0) { - return formatDiscountedPrice(price.value, PLUS_BOOK_PURCHASE_DISCOUNT) + return formatDiscountedPrice(price.value, PLUS_BOOK_PURCHASE_DISCOUNT, priceCurrencyOverride.value) } return null }) diff --git a/app/composables/use-book-info.ts b/app/composables/use-book-info.ts index 86ae1100c..0562c6e4f 100644 --- a/app/composables/use-book-info.ts +++ b/app/composables/use-book-info.ts @@ -255,6 +255,7 @@ export default function ( name: localeString(item.name), description: localeString(item.description), price: item.price, + priceInDecimalByCurrency: item.priceInDecimalByCurrency, currency: item.price > 0 ? 'USD' : '', isSoldOut: item.isSoldOut, canTip: item.isAllowCustomPrice && item.isTippingEnabled, @@ -263,11 +264,13 @@ export default function ( }) }) - const minPrice = computed(() => { - if (!pricingItems.value.length) return 0 - return Math.min(...pricingItems.value.map(item => item.price)) + const minPricingItem = computed(() => { + if (!pricingItems.value.length) return undefined + return pricingItems.value.reduce((min, item) => (item.price < min.price ? item : min)) }) + const minPrice = computed(() => minPricingItem.value?.price ?? 0) + const userOwnedNFTIds = computed(() => { return bookshelfStore.getTokenIdsByNFTClassId(toValue(nftClassId)) }) @@ -364,6 +367,7 @@ export default function ( promotionalVideos, pricingItems, + minPricingItem, minPrice, userOwnedNFTIds, diff --git a/app/composables/use-currency.ts b/app/composables/use-currency.ts index 52581d7c9..005ef8441 100644 --- a/app/composables/use-currency.ts +++ b/app/composables/use-currency.ts @@ -32,24 +32,52 @@ export default function () { }).format(amount)}` } - function formatPrice(price: number) { - const convertedPrice = convertUSDPriceToCurrency(price, displayCurrency.value) - return formatCurrencyAmount(convertedPrice, displayCurrency.value) + // A per-book override (minor units, e.g. cents) takes precedence over the + // index-based ladder conversion. USD always uses the ladder/stored price. + function resolvePrice( + usdPrice: number, + priceInDecimalByCurrency?: BookPriceInDecimalByCurrency, + ): number { + const currency = displayCurrency.value + if (currency !== 'usd') { + const override = priceInDecimalByCurrency?.[currency] + if (typeof override === 'number' && override > 0) { + return override / 100 + } + } + return convertUSDPriceToCurrency(usdPrice, currency) + } + + function formatPrice(price: number, priceInDecimalByCurrency?: BookPriceInDecimalByCurrency) { + return formatCurrencyAmount(resolvePrice(price, priceInDecimalByCurrency), displayCurrency.value) } - function formatDiscountedPrice(usdPrice: number, discountRate: number): string { - const convertedPrice = convertUSDPriceToCurrency(usdPrice, displayCurrency.value) - const discountedPrice = convertedPrice * (1 - discountRate) + function formatDiscountedPrice( + usdPrice: number, + discountRate: number, + priceInDecimalByCurrency?: BookPriceInDecimalByCurrency, + ): string { + const discountedPrice = resolvePrice(usdPrice, priceInDecimalByCurrency) * (1 - discountRate) return formatCurrencyAmount(discountedPrice, displayCurrency.value) } - function convertPrice(usdPrice: number): number { - return convertUSDPriceToCurrency(usdPrice, displayCurrency.value) + function convertPrice( + usdPrice: number, + priceInDecimalByCurrency?: BookPriceInDecimalByCurrency, + ): number { + return resolvePrice(usdPrice, priceInDecimalByCurrency) + } + + // Formats an amount already expressed in the display currency (e.g. a sum of + // per-line-item resolved prices), without re-applying ladder conversion. + function formatConvertedPrice(amount: number): string { + return formatCurrencyAmount(amount, displayCurrency.value) } return { formatPrice, formatDiscountedPrice, convertPrice, + formatConvertedPrice, } } diff --git a/app/pages/checkout.vue b/app/pages/checkout.vue index a6e06b61d..02ddb1731 100644 --- a/app/pages/checkout.vue +++ b/app/pages/checkout.vue @@ -45,7 +45,7 @@