SDK
Price Utilities
Helper functions for calculating prices with sale logic
Overview
The SDK exports utility functions for handling price calculations with sale logic. These are standalone functions (not methods on resources) that help you determine effective prices and sale status.
Key Features:
- Date-based sale validation
- Automatic sale price vs regular price selection
- Variation price support
- All prices in cents
Functions
isSaleActive(startDate, endDate)
Check if a sale is currently active based on start and end dates.
import { isSaleActive } from '@putiikkipalvelu/storefront-sdk';
// No dates = always active
isSaleActive(null, null); // true
// Only start date = active if past start
isSaleActive('2024-01-01', null); // true if today >= Jan 1
// Only end date = active until end
isSaleActive(null, '2024-12-31'); // true if today <= Dec 31
// Both dates = active if within range
isSaleActive('2024-01-01', '2024-12-31'); // true if within rangeParameters:
| Param | Type | Required | Description |
|---|---|---|---|
startDate | Date | string | null | No | Sale start date (ISO string or Date) |
endDate | Date | string | null | No | Sale end date (ISO string or Date) |
Returns: boolean - true if the sale is currently active
Logic:
- If both dates are
null/undefined, sale is always active - If only start date is set, active if current time is after start
- If only end date is set, active if current time is before end
- If both dates are set, active if within range (inclusive)
getPriceInfo(product, variation?)
Get complete price information for a product or variation. Returns the effective price (sale or regular) and sale status. All prices are in cents.
import { getPriceInfo } from '@putiikkipalvelu/storefront-sdk';
// Product without variation
const priceInfo = getPriceInfo(product);
console.log(priceInfo.effectivePrice); // 1990 (cents)
console.log(priceInfo.originalPrice); // 2990 (cents)
console.log(priceInfo.isOnSale); // true
console.log(priceInfo.salePercent); // "-33%"
// Product with selected variation
const priceInfo = getPriceInfo(product, selectedVariation);Parameters:
| Param | Type | Required | Description |
|---|---|---|---|
product | ProductDetail | Yes | The product to get price info for |
variation | ProductVariation | No | Selected variation (takes precedence) |
Returns: PriceInfo
interface PriceInfo {
effectivePrice: number; // Current price in cents (sale or regular)
originalPrice: number; // Original/regular price in cents
isOnSale: boolean; // Whether sale is currently active
salePercent: string | null; // Sale percentage (e.g., "-20%")
}Price Resolution:
- If variation is provided, uses variation's pricing
- Checks if sale is active using
isSaleActive()with product/variation dates - If sale is active AND sale price exists, uses sale price
- Otherwise, uses regular price
Examples
Display Price Component
'use client';
import { getPriceInfo } from '@putiikkipalvelu/storefront-sdk';
import type { ProductDetail, ProductVariation } from '@putiikkipalvelu/storefront-sdk';
interface DisplayPriceProps {
product: ProductDetail;
variation?: ProductVariation;
className?: string;
}
export function DisplayPrice({ product, variation, className }: DisplayPriceProps) {
const { effectivePrice, originalPrice, isOnSale, salePercent } = getPriceInfo(
product,
variation
);
const formatPrice = (cents: number) => (cents / 100).toFixed(2);
return (
<div className={className}>
<span className="text-xl font-bold">
{formatPrice(effectivePrice)} EUR
</span>
{isOnSale && (
<>
<span className="ml-2 text-gray-500 line-through">
{formatPrice(originalPrice)} EUR
</span>
{salePercent && (
<span className="ml-2 text-red-500 font-medium">
{salePercent}
</span>
)}
</>
)}
</div>
);
}Product Card with Sale Badge
import { getPriceInfo } from '@putiikkipalvelu/storefront-sdk';
import type { Product } from '@putiikkipalvelu/storefront-sdk';
interface ProductCardProps {
product: Product;
}
export function ProductCard({ product }: ProductCardProps) {
// For listing cards, use the first variation's pricing if exists
const firstVariation = product.variations?.[0];
const { effectivePrice, isOnSale, salePercent } = getPriceInfo(
product as ProductDetail,
firstVariation
);
return (
<div className="relative">
{isOnSale && salePercent && (
<div className="absolute top-2 right-2 bg-red-500 text-white px-2 py-1 rounded text-sm font-bold">
{salePercent}
</div>
)}
<img src={product.images[0]} alt={product.name} />
<h3>{product.name}</h3>
<p>{(effectivePrice / 100).toFixed(2)} EUR</p>
</div>
);
}Variation Selector with Prices
'use client';
import { useState } from 'react';
import { getPriceInfo } from '@putiikkipalvelu/storefront-sdk';
import type { ProductDetail, ProductVariation } from '@putiikkipalvelu/storefront-sdk';
interface VariationSelectorProps {
product: ProductDetail;
onSelect: (variation: ProductVariation) => void;
}
export function VariationSelector({ product, onSelect }: VariationSelectorProps) {
const [selected, setSelected] = useState<ProductVariation | undefined>(
product.variations[0]
);
const handleSelect = (variation: ProductVariation) => {
setSelected(variation);
onSelect(variation);
};
return (
<div className="space-y-2">
{product.variations.map((variation) => {
const { effectivePrice, isOnSale, originalPrice } = getPriceInfo(
product,
variation
);
const optionLabel = variation.options.map(o => o.value).join(' / ');
return (
<button
key={variation.id}
onClick={() => handleSelect(variation)}
className={`w-full p-3 border rounded flex justify-between ${
selected?.id === variation.id ? 'border-primary bg-primary/5' : ''
}`}
>
<span>{optionLabel}</span>
<span>
{(effectivePrice / 100).toFixed(2)} EUR
{isOnSale && (
<span className="ml-2 text-gray-400 line-through text-sm">
{(originalPrice / 100).toFixed(2)} EUR
</span>
)}
</span>
</button>
);
})}
</div>
);
}Calculate Cart Line Item Total
import { getPriceInfo } from '@putiikkipalvelu/storefront-sdk';
import type { CartItem } from '@putiikkipalvelu/storefront-sdk';
function calculateLineItemTotal(item: CartItem): number {
const { effectivePrice } = getPriceInfo(item.product, item.variation);
return effectivePrice * item.cartQuantity;
}
function calculateCartSubtotal(items: CartItem[]): number {
return items.reduce((sum, item) => sum + calculateLineItemTotal(item), 0);
}
// Usage
const subtotal = calculateCartSubtotal(cartItems);
console.log(`Subtotal: ${(subtotal / 100).toFixed(2)} EUR`);Check Sale Validity in Server Component
import { isSaleActive } from '@putiikkipalvelu/storefront-sdk';
import { storefront } from '@/lib/storefront';
export default async function ProductPage({ params }: { params: { slug: string } }) {
const product = await storefront.products.getBySlug(params.slug);
// Check if product has an active sale
const hasSale = product.salePrice !== null &&
isSaleActive(product.saleStartDate, product.saleEndDate);
return (
<div>
<h1>{product.name}</h1>
{hasSale ? (
<div>
<span className="text-red-500">{(product.salePrice! / 100).toFixed(2)} EUR</span>
<span className="line-through">{(product.price / 100).toFixed(2)} EUR</span>
</div>
) : (
<span>{(product.price / 100).toFixed(2)} EUR</span>
)}
</div>
);
}Type Definitions
PriceInfo
interface PriceInfo {
/** Current effective price in cents (sale price if on sale, otherwise regular) */
effectivePrice: number;
/** Original/regular price in cents */
originalPrice: number;
/** Whether the item is currently on sale */
isOnSale: boolean;
/** Sale percentage (e.g., "-20%") if applicable */
salePercent: string | null;
}Best Practices
- Always use
getPriceInfo()for display - Don't manually check sale dates - Pass variation when available - Variation pricing takes precedence
- Prices are in cents - Divide by 100 for display
- Cache appropriately - Price info can change when sales start/end