Putiikkipalvelu Docs
SDK

Customer

Customer authentication, profile management, orders, and wishlist

Overview

The customer resource provides complete customer account management including:

  • Authentication - Register, login, logout, email verification, password reset
  • Profile Management - View and update profile, delete account
  • Order History - View past orders with full details
  • Wishlist - Save products for later

Session-Based Authentication

Customer authentication uses session tokens:

  1. Customer logs in with email/password
  2. API returns a sessionId (valid for 7 days)
  3. Store the session ID in an HTTP-only cookie
  4. Pass x-session-id header with authenticated requests
// After login
const { sessionId, expiresAt } = await storefront.customer.login(
  email,
  password
);

// Store in cookie
cookies().set('session-id', sessionId, {
  httpOnly: true,
  secure: true,
  expires: new Date(expiresAt),
});

Authentication Methods

customer.register(data, options?)

Register a new customer account. A verification email is automatically sent by the server.

const { customer, message } = await storefront.customer.register({
  firstName: 'John',
  lastName: 'Doe',
  email: 'john@example.com',
  password: 'securePassword123',
});

// Verification email is sent automatically by the server
console.log(message); // "Account created. Please check your email to verify."

Parameters:

ParamTypeRequiredDescription
data.firstNamestringYesCustomer's first name
data.lastNamestringYesCustomer's last name
data.emailstringYesCustomer's email
data.passwordstringYesPassword (min 8 chars)

Returns: Promise<RegisterResponse>

Throws:

  • ValidationError - Invalid input or email already registered

Security:

  • Verification tokens are never exposed to the client
  • Emails are sent server-side using the store's configured email settings

customer.login(email, password, options?, fetchOptions?)

Log in and receive a session token.

try {
  const { sessionId, customer, expiresAt } = await storefront.customer.login(
    'john@example.com',
    'securePassword123',
    { cartId: guestCartId } // Optional: merge guest cart
  );

  // Store session
  cookies().set('session-id', sessionId, {
    httpOnly: true,
    expires: new Date(expiresAt),
  });
} catch (error) {
  if (error instanceof ValidationError && error.message.includes('not verified')) {
    // Handle unverified email - customerId is in the error details
    console.log('Please verify your email first');
  }
}

Parameters:

ParamTypeRequiredDescription
emailstringYesCustomer's email
passwordstringYesCustomer's password
options.cartIdstringNoGuest cart ID to merge

Returns: Promise<LoginResponse>

Throws:

  • AuthError - Invalid credentials
  • ValidationError - Email not verified (includes customerId for resend)

customer.logout(sessionId, options?)

Log out and invalidate the session.

const { cartId } = await storefront.customer.logout(sessionId);

// Clear session cookie
cookies().delete('session-id');

// If cart was migrated, store new guest cart ID
if (cartId) {
  cookies().set('cart-id', cartId);
}

Parameters:

ParamTypeRequiredDescription
sessionIdstringYesSession token

Returns: Promise<LogoutResponse>


customer.getUser(sessionId, options?)

Get the current authenticated customer.

const { customer } = await storefront.customer.getUser(sessionId);
console.log(`Welcome back, ${customer.firstName}!`);

Parameters:

ParamTypeRequiredDescription
sessionIdstringYesSession token

Returns: Promise<GetUserResponse>

Throws: AuthError - Invalid or expired session


customer.verifyEmail(token, options?)

Verify email address after registration.

// Token comes from verification email link
const token = searchParams.get('token');

const { message } = await storefront.customer.verifyEmail(token);
// message: "Email verified successfully. You can now log in."

Parameters:

ParamTypeRequiredDescription
tokenstringYesVerification token

Returns: Promise<VerifyEmailResponse>

Throws: ValidationError - Invalid or expired token


customer.resendVerification(customerId, options?)

Resend verification email. The email is automatically sent by the server.

// After login fails with requiresVerification: true
const { message } = await storefront.customer.resendVerification(customerId);

// Verification email is sent automatically by the server
console.log(message); // "Verification email sent."

Parameters:

ParamTypeRequiredDescription
customerIdstringYesCustomer ID (from login error)

Returns: Promise<ResendVerificationResponse>

Security:

  • Verification tokens are never exposed to the client
  • Emails are sent server-side using the store's configured email settings

customer.forgotPassword(email, options?)

Request a password reset for a customer account. The API sends the reset email server-side.

const response = await storefront.customer.forgotPassword('john@example.com');

// Always show the same message to user (prevents email enumeration)
console.log(response.message);
// "If an account exists with that email, password reset instructions have been sent."

Parameters:

ParamTypeRequiredDescription
emailstringYesCustomer's email address

Returns: Promise<ForgotPasswordResponse>

Security Features:

  • Email sent server-side (token never exposed to client)
  • Token hashed with SHA-256 before storing in database
  • Always returns same response to prevent email enumeration
  • Rate limited: 5 requests per email per 15 minutes
  • Token expires in 1 hour

customer.resetPassword(token, password, options?)

Reset a customer's password using a valid reset token.

// Token comes from the reset email link
const token = searchParams.get('token');

try {
  const { message } = await storefront.customer.resetPassword(token, newPassword);
  console.log(message); // "Password reset successful..."

  // Redirect to login
  redirect('/login?reset=success');
} catch (error) {
  if (error instanceof ValidationError) {
    // Token is invalid or expired
    console.error('Please request a new password reset');
  }
}

Parameters:

ParamTypeRequiredDescription
tokenstringYesReset token from email link
passwordstringYesNew password (min 8 characters)

Returns: Promise<ResetPasswordResponse>

Throws: ValidationError - Invalid or expired token

After Success:

  • Password is updated
  • All existing sessions are invalidated (security measure)
  • Customer must log in again with new password

Profile Management Methods

customer.updateProfile(sessionId, data, options?)

Update customer profile information.

const { customer } = await storefront.customer.updateProfile(sessionId, {
  firstName: 'Jane',
  lastName: 'Smith',
  email: 'jane.smith@example.com',
});

Parameters:

ParamTypeRequiredDescription
sessionIdstringYesSession token
data.firstNamestringNoUpdated first name
data.lastNamestringNoUpdated last name
data.emailstringNoUpdated email

Returns: Promise<UpdateProfileResponse>

Throws:

  • AuthError - Invalid session
  • ValidationError - Email already taken

customer.deleteAccount(sessionId, options?)

Permanently delete customer account.

// Confirm with user first!
if (confirm('Are you sure you want to delete your account?')) {
  await storefront.customer.deleteAccount(sessionId);
  cookies().delete('session-id');
  redirect('/');
}

Parameters:

ParamTypeRequiredDescription
sessionIdstringYesSession token

Returns: Promise<DeleteAccountResponse>


Order History

customer.getOrders(sessionId, customerId, options?)

Get customer's order history with full details.

const { orders } = await storefront.customer.getOrders(sessionId, customerId);

orders.forEach(order => {
  console.log(`Order #${order.orderNumber}: ${order.status}`);
  console.log(`Total: ${order.totalAmount / 100}€`);

  order.OrderLineItems.forEach(item => {
    console.log(`  - ${item.name} x${item.quantity}`);
    if (item.product.variationId) {
      console.log(`    ${item.product.optionName}: ${item.product.optionValue}`);
    }
  });
});

Parameters:

ParamTypeRequiredDescription
sessionIdstringYesSession token
customerIdstringYesCustomer ID

Returns: Promise<GetOrdersResponse>


Wishlist

The wishlist is a nested resource accessed via customer.wishlist.

customer.wishlist.get(sessionId, options?)

Get all wishlist items.

const { items } = await storefront.customer.wishlist.get(sessionId);

items.forEach(item => {
  console.log(`${item.product.name} - ${item.product.price / 100}€`);

  if (item.variation) {
    const options = item.variation.options
      .map(o => `${o.optionType.name}: ${o.value}`)
      .join(', ');
    console.log(`  Variant: ${options}`);
  }
});

Returns: Promise<WishlistResponse>


customer.wishlist.add(sessionId, productId, variationId?, options?)

Add a product to the wishlist.

// Simple product
await storefront.customer.wishlist.add(sessionId, 'prod_123');

// Product with variation
await storefront.customer.wishlist.add(sessionId, 'prod_123', 'var_456');

Parameters:

ParamTypeRequiredDescription
sessionIdstringYesSession token
productIdstringYesProduct ID
variationIdstringNoVariation ID

Returns: Promise<AddToWishlistResponse>

Throws: ValidationError - Product already in wishlist


customer.wishlist.remove(sessionId, productId, variationId?, options?)

Remove a product from the wishlist.

await storefront.customer.wishlist.remove(sessionId, 'prod_123');

// Or with variation
await storefront.customer.wishlist.remove(sessionId, 'prod_123', 'var_456');

Parameters:

ParamTypeRequiredDescription
sessionIdstringYesSession token
productIdstringYesProduct ID
variationIdstringNoVariation ID

Returns: Promise<RemoveFromWishlistResponse>

Throws: NotFoundError - Item not in wishlist


TypeScript Types

// Customer - single unified type with optional fields
interface Customer {
  id: string;
  firstName: string;
  lastName: string;
  email: string;
  createdAt?: string;           // Included in register, login, edit responses
  emailVerified?: string | null; // Only in login response
}

// Auth types
interface RegisterData {
  firstName: string;
  lastName: string;
  email: string;
  password: string;
}

interface LoginOptions {
  cartId?: string;
}

// Response types (all emails sent automatically by server)
interface RegisterResponse {
  success: true;
  customer: Customer;  // includes createdAt
  message: string;
}

interface LoginResponse {
  success: true;
  customer: Customer;  // includes createdAt and emailVerified
  message: string;
  sessionId: string;
  expiresAt: string;
}

interface LogoutResponse {
  success: true;
  message: string;
  cartId?: string;
}

interface ResendVerificationResponse {
  success: true;
  message: string;
}

interface ForgotPasswordResponse {
  success: true;
  message: string;
}

interface ResetPasswordResponse {
  success: true;
  message: string;
}

// Order types
type OrderStatus =
  | 'PENDING'
  | 'PROCESSING'
  | 'SHIPPED'
  | 'DELIVERED'
  | 'CANCELLED'
  | 'REFUNDED';

interface CustomerOrder {
  id: string;
  orderNumber: string;
  totalAmount: number;
  status: OrderStatus;
  createdAt: string;
  OrderLineItems: OrderLineItem[];
  orderShipmentMethod: OrderShipmentMethod | null;
}

// Wishlist types
interface WishlistItem {
  id: string;
  customerId: string;
  productId: string;
  variationId: string | null;
  createdAt: string;
  product: WishlistProduct;
  variation: WishlistVariation | null;
}

interface WishlistResponse {
  items: WishlistItem[];
}

Error Handling

import {
  AuthError,
  ValidationError,
  NotFoundError,
} from '@putiikkipalvelu/storefront-sdk';

// Login with error handling
try {
  const { sessionId } = await storefront.customer.login(email, password);
} catch (error) {
  if (error instanceof AuthError) {
    // Invalid credentials
    return { error: 'Invalid email or password' };
  }
  if (error instanceof ValidationError) {
    // Email not verified
    return { error: 'Please verify your email first' };
  }
  throw error;
}

// Wishlist with error handling
try {
  await storefront.customer.wishlist.add(sessionId, productId);
} catch (error) {
  if (error instanceof ValidationError) {
    // Already in wishlist
    console.log('Product is already in your wishlist');
  }
  if (error instanceof AuthError) {
    // Session expired
    redirect('/login');
  }
}

Next.js Integration

Server Component Example

// app/account/page.tsx
import { storefront } from '@/lib/storefront';
import { cookies } from 'next/headers';

export default async function AccountPage() {
  const sessionId = cookies().get('session-id')?.value;

  if (!sessionId) {
    redirect('/login');
  }

  const { customer } = await storefront.customer.getUser(sessionId, {
    next: { revalidate: 0 }, // Always fresh
  });

  return (
    <div>
      <h1>Welcome, {customer.firstName}!</h1>
      {/* ... */}
    </div>
  );
}

Server Action Example

// app/actions/auth.ts
'use server';

import { storefront } from '@/lib/storefront';
import { cookies } from 'next/headers';
import { redirect } from 'next/navigation';

export async function login(formData: FormData) {
  const email = formData.get('email') as string;
  const password = formData.get('password') as string;
  const cartId = cookies().get('cart-id')?.value;

  try {
    const { sessionId, expiresAt } = await storefront.customer.login(
      email,
      password,
      { cartId }
    );

    cookies().set('session-id', sessionId, {
      httpOnly: true,
      secure: process.env.NODE_ENV === 'production',
      expires: new Date(expiresAt),
    });

    // Clear guest cart cookie after merge
    cookies().delete('cart-id');
  } catch (error) {
    if (error instanceof AuthError) {
      return { error: 'Invalid email or password' };
    }
    throw error;
  }

  redirect('/account');
}

export async function logout() {
  const sessionId = cookies().get('session-id')?.value;

  if (sessionId) {
    const { cartId } = await storefront.customer.logout(sessionId);

    cookies().delete('session-id');

    if (cartId) {
      cookies().set('cart-id', cartId);
    }
  }

  redirect('/');
}

export async function forgotPassword(formData: FormData) {
  const email = formData.get('email') as string;

  // API sends reset email server-side (token never exposed to client)
  const response = await storefront.customer.forgotPassword(email);

  // Always return success message (same whether email exists or not)
  return { success: true, message: response.message };
}

export async function resetPassword(formData: FormData) {
  const token = formData.get('token') as string;
  const password = formData.get('password') as string;

  try {
    await storefront.customer.resetPassword(token, password);
    redirect('/login?reset=success');
  } catch (error) {
    if (error instanceof ValidationError) {
      return { error: 'Invalid or expired token. Please request a new reset link.' };
    }
    throw error;
  }
}

On this page