Putiikkipalvelu Docs
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 range

Parameters:

ParamTypeRequiredDescription
startDateDate | string | nullNoSale start date (ISO string or Date)
endDateDate | string | nullNoSale 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:

ParamTypeRequiredDescription
productProductDetailYesThe product to get price info for
variationProductVariationNoSelected 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:

  1. If variation is provided, uses variation's pricing
  2. Checks if sale is active using isSaleActive() with product/variation dates
  3. If sale is active AND sale price exists, uses sale price
  4. 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

  1. Always use getPriceInfo() for display - Don't manually check sale dates
  2. Pass variation when available - Variation pricing takes precedence
  3. Prices are in cents - Divide by 100 for display
  4. Cache appropriately - Price info can change when sales start/end

On this page