From cce421c418e03ec08abd3a42437a2bd6e4de8a7d Mon Sep 17 00:00:00 2001 From: Victor Gerbrands Date: Fri, 5 May 2023 12:28:12 +0200 Subject: [PATCH] feat: price formatting --- lib/medusa/helpers.ts | 43 ++++++++++++++++++++++++++++++++ lib/medusa/index.ts | 57 ++++++++++++++++++++++++++++++------------- lib/medusa/types.ts | 12 +++++++++ lib/utils.ts | 13 ++++++++++ 4 files changed, 108 insertions(+), 17 deletions(-) create mode 100644 lib/medusa/helpers.ts diff --git a/lib/medusa/helpers.ts b/lib/medusa/helpers.ts new file mode 100644 index 000000000..203d4f927 --- /dev/null +++ b/lib/medusa/helpers.ts @@ -0,0 +1,43 @@ +import { isEmpty } from '../utils'; +import { MedusaProductVariant, RegionInfo } from './types'; + +type ComputeAmountParams = { + amount: number; + region: RegionInfo; + includeTaxes?: boolean; +}; + +/** + * Takes an amount, a region, and returns the amount as a decimal including or excluding taxes + */ +export const computeAmount = ({ amount, region, includeTaxes = true }: ComputeAmountParams) => { + const toDecimal = convertToDecimal(amount, region.currency_code); + + const taxRate = includeTaxes ? getTaxRate(region) : 0; + + const amountWithTaxes = toDecimal * (1 + taxRate); + + return amountWithTaxes; +}; + +export const calculateVariantAmount = (variant: MedusaProductVariant) => { + const currencyCode = variant.prices?.[0]?.currency_code ?? 'USD'; + const amount = convertToDecimal(variant.prices?.[0]?.amount || 0, currencyCode).toString(); + return { + amount, + currencyCode + }; +}; + +// we should probably add a more extensive list +const noDivisionCurrencies = ['krw', 'jpy', 'vnd']; + +export const convertToDecimal = (amount: number, currencyCode = 'USD') => { + const divisor = noDivisionCurrencies.includes(currencyCode.toLowerCase()) ? 1 : 100; + + return Math.floor(amount) / divisor; +}; + +const getTaxRate = (region?: RegionInfo) => { + return region && !isEmpty(region) ? region?.tax_rate / 100 : 0; +}; diff --git a/lib/medusa/index.ts b/lib/medusa/index.ts index 007898710..11407b50a 100644 --- a/lib/medusa/index.ts +++ b/lib/medusa/index.ts @@ -1,6 +1,7 @@ import { isMedusaError } from 'lib/type-guards'; import { mapOptionIds } from 'lib/utils'; +import { calculateVariantAmount, computeAmount, convertToDecimal } from './helpers'; import { Cart, CartItem, @@ -67,18 +68,34 @@ const reshapeCart = (cart: MedusaCart): Cart => { const lines = cart?.items?.map((item) => reshapeLineItem(item)) || []; const totalQuantity = lines.length; const checkoutUrl = '/'; - const currencyCode = 'EUR'; + const currencyCode = cart.region?.currency_code || 'USD'; + + let subtotalAmount = '0'; + if (cart.subtotal && cart.region) { + subtotalAmount = computeAmount({ amount: cart.subtotal, region: cart.region }).toString(); + } + + let totalAmount = '0'; + if (cart.total && cart.region) { + totalAmount = computeAmount({ amount: cart.total, region: cart.region }).toString(); + } + + let totalTaxAmount = '0'; + if (cart.tax_total && cart.region) { + totalTaxAmount = computeAmount({ amount: cart.tax_total, region: cart.region }).toString(); + } + const cost = { subtotalAmount: { - amount: cart?.subtotal?.toString() || '0', + amount: subtotalAmount, currencyCode: currencyCode }, totalAmount: { - amount: (cart?.total && cart?.total.toString()) || '0', + amount: totalAmount, currencyCode: currencyCode }, totalTaxAmount: { - amount: (cart?.tax_total && cart?.tax_total.toString()) || '0', + amount: totalTaxAmount, currencyCode: currencyCode } }; @@ -96,10 +113,7 @@ const reshapeLineItem = (lineItem: MedusaLineItem): CartItem => { const product = { title: lineItem.title, priceRange: { - maxVariantPrice: { - amount: lineItem.variant?.prices?.[0]?.amount.toString() ?? '0', - currencyCode: lineItem.variant?.prices?.[0]?.currency_code ?? 'EUR' - } + maxVariantPrice: calculateVariantAmount(lineItem.variant) }, updatedAt: lineItem.updated_at, tags: [], @@ -128,7 +142,10 @@ const reshapeLineItem = (lineItem: MedusaLineItem): CartItem => { const cost = { totalAmount: { - amount: lineItem.total.toString() ?? '0', + amount: convertToDecimal( + lineItem.total, + lineItem.variant.prices?.[0]?.currency_code + ).toString(), currencyCode: 'EUR' } }; @@ -143,9 +160,18 @@ const reshapeLineItem = (lineItem: MedusaLineItem): CartItem => { }; const reshapeProduct = (product: MedusaProduct): Product => { + const variant = product.variants?.[0]; + + let amount = '0'; + let currencyCode = 'USD'; + if (variant && variant.prices?.[0]?.amount) { + currencyCode = variant.prices?.[0]?.currency_code ?? 'USD'; + amount = convertToDecimal(variant.prices[0].amount, currencyCode).toString(); + } + const priceRange = { maxVariantPrice: { - amount: product.variants?.[0]?.prices?.[0]?.amount.toString() ?? '0', + amount, currencyCode: product.variants?.[0]?.prices?.[0]?.currency_code ?? '' } }; @@ -156,7 +182,7 @@ const reshapeProduct = (product: MedusaProduct): Product => { url: product.images?.[0]?.url ?? '', altText: product.images?.[0]?.id ?? '' }; - const availableForSale = true; + const availableForSale = product.variants?.[0]?.purchasable || true; const variants = product.variants.map((variant) => reshapeProductVariant(variant, product.options) ); @@ -178,7 +204,7 @@ const reshapeProduct = (product: MedusaProduct): Product => { }; const reshapeProductOption = (productOption: MedusaProductOption): ProductOption => { - const availableForSale = true; + const availableForSale = productOption.product?.variants?.[0]?.purchasable || true; const name = productOption.title; let values = productOption.values?.map((option) => option.value) || []; values = [...new Set(values)]; @@ -203,12 +229,9 @@ const reshapeProductVariant = ( value: option.value })); } - const availableForSale = !!productVariant.inventory_quantity; + const availableForSale = productVariant.purchasable || true; + const price = calculateVariantAmount(productVariant); - const price = { - amount: productVariant.prices?.[0]?.amount.toString() ?? 'ß', - currencyCode: productVariant.prices?.[0]?.currency_code ?? '' - }; return { ...productVariant, availableForSale, diff --git a/lib/medusa/types.ts b/lib/medusa/types.ts index 697fc6a37..67f2fcce8 100644 --- a/lib/medusa/types.ts +++ b/lib/medusa/types.ts @@ -153,6 +153,7 @@ export type MedusaProductVariant = { created_at: string; updated_at: string; deleted_at: string | null; + purchasable?: boolean; }; export type ProductVariant = MedusaProductVariant & { @@ -279,6 +280,9 @@ export type Region = { updated_at: string; deleted_at?: string | null; metadata?: Record | null; + currency_code: string; + tax_code: string; + tax_rate: number; }; export type FulfillmentProvider = { @@ -417,3 +421,11 @@ export type CartItem = MedusaLineItem & { }; quantity: number; }; + +export type RegionInfo = Pick; +export type ProductVariantEntity = ConvertDateToString>; +export type ProductVariantInfo = Pick; + +type ConvertDateToString = { + [P in keyof T]: T[P] extends Date ? Date | string : T[P]; +}; diff --git a/lib/utils.ts b/lib/utils.ts index 9c72e43a4..21f627c58 100644 --- a/lib/utils.ts +++ b/lib/utils.ts @@ -15,3 +15,16 @@ export const mapOptionIds = (productOptions: MedusaProductOption[]) => { }); return map; }; + +export const isObject = (input: any) => input instanceof Object; +export const isArray = (input: any) => Array.isArray(input); + +export const isEmpty = (input: any) => { + return ( + input === null || + input === undefined || + (isObject(input) && Object.keys(input).length === 0) || + (isArray(input) && (input as any[]).length === 0) || + (typeof input === 'string' && input.trim().length === 0) + ); +};