mirror of
https://github.com/vercel/commerce.git
synced 2025-05-15 22:16:58 +00:00
feat: price formatting
This commit is contained in:
parent
575c3a3d0b
commit
cce421c418
43
lib/medusa/helpers.ts
Normal file
43
lib/medusa/helpers.ts
Normal file
@ -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;
|
||||
};
|
@ -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,
|
||||
|
@ -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<string, unknown> | 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<Region, 'currency_code' | 'tax_code' | 'tax_rate'>;
|
||||
export type ProductVariantEntity = ConvertDateToString<Omit<ProductVariant, 'beforeInsert'>>;
|
||||
export type ProductVariantInfo = Pick<ProductVariantEntity, 'prices'>;
|
||||
|
||||
type ConvertDateToString<T extends {}> = {
|
||||
[P in keyof T]: T[P] extends Date ? Date | string : T[P];
|
||||
};
|
||||
|
13
lib/utils.ts
13
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)
|
||||
);
|
||||
};
|
||||
|
Loading…
x
Reference in New Issue
Block a user