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 { isMedusaError } from 'lib/type-guards';
|
||||||
|
|
||||||
import { mapOptionIds } from 'lib/utils';
|
import { mapOptionIds } from 'lib/utils';
|
||||||
|
import { calculateVariantAmount, computeAmount, convertToDecimal } from './helpers';
|
||||||
import {
|
import {
|
||||||
Cart,
|
Cart,
|
||||||
CartItem,
|
CartItem,
|
||||||
@ -67,18 +68,34 @@ const reshapeCart = (cart: MedusaCart): Cart => {
|
|||||||
const lines = cart?.items?.map((item) => reshapeLineItem(item)) || [];
|
const lines = cart?.items?.map((item) => reshapeLineItem(item)) || [];
|
||||||
const totalQuantity = lines.length;
|
const totalQuantity = lines.length;
|
||||||
const checkoutUrl = '/';
|
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 = {
|
const cost = {
|
||||||
subtotalAmount: {
|
subtotalAmount: {
|
||||||
amount: cart?.subtotal?.toString() || '0',
|
amount: subtotalAmount,
|
||||||
currencyCode: currencyCode
|
currencyCode: currencyCode
|
||||||
},
|
},
|
||||||
totalAmount: {
|
totalAmount: {
|
||||||
amount: (cart?.total && cart?.total.toString()) || '0',
|
amount: totalAmount,
|
||||||
currencyCode: currencyCode
|
currencyCode: currencyCode
|
||||||
},
|
},
|
||||||
totalTaxAmount: {
|
totalTaxAmount: {
|
||||||
amount: (cart?.tax_total && cart?.tax_total.toString()) || '0',
|
amount: totalTaxAmount,
|
||||||
currencyCode: currencyCode
|
currencyCode: currencyCode
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -96,10 +113,7 @@ const reshapeLineItem = (lineItem: MedusaLineItem): CartItem => {
|
|||||||
const product = {
|
const product = {
|
||||||
title: lineItem.title,
|
title: lineItem.title,
|
||||||
priceRange: {
|
priceRange: {
|
||||||
maxVariantPrice: {
|
maxVariantPrice: calculateVariantAmount(lineItem.variant)
|
||||||
amount: lineItem.variant?.prices?.[0]?.amount.toString() ?? '0',
|
|
||||||
currencyCode: lineItem.variant?.prices?.[0]?.currency_code ?? 'EUR'
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
updatedAt: lineItem.updated_at,
|
updatedAt: lineItem.updated_at,
|
||||||
tags: [],
|
tags: [],
|
||||||
@ -128,7 +142,10 @@ const reshapeLineItem = (lineItem: MedusaLineItem): CartItem => {
|
|||||||
|
|
||||||
const cost = {
|
const cost = {
|
||||||
totalAmount: {
|
totalAmount: {
|
||||||
amount: lineItem.total.toString() ?? '0',
|
amount: convertToDecimal(
|
||||||
|
lineItem.total,
|
||||||
|
lineItem.variant.prices?.[0]?.currency_code
|
||||||
|
).toString(),
|
||||||
currencyCode: 'EUR'
|
currencyCode: 'EUR'
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -143,9 +160,18 @@ const reshapeLineItem = (lineItem: MedusaLineItem): CartItem => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const reshapeProduct = (product: MedusaProduct): Product => {
|
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 = {
|
const priceRange = {
|
||||||
maxVariantPrice: {
|
maxVariantPrice: {
|
||||||
amount: product.variants?.[0]?.prices?.[0]?.amount.toString() ?? '0',
|
amount,
|
||||||
currencyCode: product.variants?.[0]?.prices?.[0]?.currency_code ?? ''
|
currencyCode: product.variants?.[0]?.prices?.[0]?.currency_code ?? ''
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -156,7 +182,7 @@ const reshapeProduct = (product: MedusaProduct): Product => {
|
|||||||
url: product.images?.[0]?.url ?? '',
|
url: product.images?.[0]?.url ?? '',
|
||||||
altText: product.images?.[0]?.id ?? ''
|
altText: product.images?.[0]?.id ?? ''
|
||||||
};
|
};
|
||||||
const availableForSale = true;
|
const availableForSale = product.variants?.[0]?.purchasable || true;
|
||||||
const variants = product.variants.map((variant) =>
|
const variants = product.variants.map((variant) =>
|
||||||
reshapeProductVariant(variant, product.options)
|
reshapeProductVariant(variant, product.options)
|
||||||
);
|
);
|
||||||
@ -178,7 +204,7 @@ const reshapeProduct = (product: MedusaProduct): Product => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const reshapeProductOption = (productOption: MedusaProductOption): ProductOption => {
|
const reshapeProductOption = (productOption: MedusaProductOption): ProductOption => {
|
||||||
const availableForSale = true;
|
const availableForSale = productOption.product?.variants?.[0]?.purchasable || true;
|
||||||
const name = productOption.title;
|
const name = productOption.title;
|
||||||
let values = productOption.values?.map((option) => option.value) || [];
|
let values = productOption.values?.map((option) => option.value) || [];
|
||||||
values = [...new Set(values)];
|
values = [...new Set(values)];
|
||||||
@ -203,12 +229,9 @@ const reshapeProductVariant = (
|
|||||||
value: option.value
|
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 {
|
return {
|
||||||
...productVariant,
|
...productVariant,
|
||||||
availableForSale,
|
availableForSale,
|
||||||
|
@ -153,6 +153,7 @@ export type MedusaProductVariant = {
|
|||||||
created_at: string;
|
created_at: string;
|
||||||
updated_at: string;
|
updated_at: string;
|
||||||
deleted_at: string | null;
|
deleted_at: string | null;
|
||||||
|
purchasable?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ProductVariant = MedusaProductVariant & {
|
export type ProductVariant = MedusaProductVariant & {
|
||||||
@ -279,6 +280,9 @@ export type Region = {
|
|||||||
updated_at: string;
|
updated_at: string;
|
||||||
deleted_at?: string | null;
|
deleted_at?: string | null;
|
||||||
metadata?: Record<string, unknown> | null;
|
metadata?: Record<string, unknown> | null;
|
||||||
|
currency_code: string;
|
||||||
|
tax_code: string;
|
||||||
|
tax_rate: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type FulfillmentProvider = {
|
export type FulfillmentProvider = {
|
||||||
@ -417,3 +421,11 @@ export type CartItem = MedusaLineItem & {
|
|||||||
};
|
};
|
||||||
quantity: number;
|
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;
|
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