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:
- Customer logs in with email/password
- API returns a
sessionId(valid for 7 days) - Store the session ID in an HTTP-only cookie
- Pass
x-session-idheader 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:
| Param | Type | Required | Description |
|---|---|---|---|
data.firstName | string | Yes | Customer's first name |
data.lastName | string | Yes | Customer's last name |
data.email | string | Yes | Customer's email |
data.password | string | Yes | Password (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:
| Param | Type | Required | Description |
|---|---|---|---|
email | string | Yes | Customer's email |
password | string | Yes | Customer's password |
options.cartId | string | No | Guest cart ID to merge |
Returns: Promise<LoginResponse>
Throws:
AuthError- Invalid credentialsValidationError- Email not verified (includescustomerIdfor 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:
| Param | Type | Required | Description |
|---|---|---|---|
sessionId | string | Yes | Session 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:
| Param | Type | Required | Description |
|---|---|---|---|
sessionId | string | Yes | Session 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:
| Param | Type | Required | Description |
|---|---|---|---|
token | string | Yes | Verification 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:
| Param | Type | Required | Description |
|---|---|---|---|
customerId | string | Yes | Customer 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:
| Param | Type | Required | Description |
|---|---|---|---|
email | string | Yes | Customer'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:
| Param | Type | Required | Description |
|---|---|---|---|
token | string | Yes | Reset token from email link |
password | string | Yes | New 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:
| Param | Type | Required | Description |
|---|---|---|---|
sessionId | string | Yes | Session token |
data.firstName | string | No | Updated first name |
data.lastName | string | No | Updated last name |
data.email | string | No | Updated email |
Returns: Promise<UpdateProfileResponse>
Throws:
AuthError- Invalid sessionValidationError- 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:
| Param | Type | Required | Description |
|---|---|---|---|
sessionId | string | Yes | Session 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:
| Param | Type | Required | Description |
|---|---|---|---|
sessionId | string | Yes | Session token |
customerId | string | Yes | Customer 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:
| Param | Type | Required | Description |
|---|---|---|---|
sessionId | string | Yes | Session token |
productId | string | Yes | Product ID |
variationId | string | No | Variation 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:
| Param | Type | Required | Description |
|---|---|---|---|
sessionId | string | Yes | Session token |
productId | string | Yes | Product ID |
variationId | string | No | Variation 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;
}
}