Order
Fetch order details for confirmation pages and order tracking
Overview
The order resource provides access to order details for:
- Order confirmation pages - Display order summary after successful payment
- Order tracking - Show shipment status and tracking information
- Order detail views - Display full order information to customers
This resource returns the raw order data as stored in the database, including line items, customer delivery info, and shipment tracking.
Note: For customer order history in account pages, use customer.getOrders() instead, which returns orders with enhanced product information.
Methods
order.get(orderId, options?)
Fetch complete order details by order ID.
const order = await storefront.order.get(orderId);
console.log(`Order #${order.orderNumber}: ${order.status}`);
console.log(`Total: ${order.totalAmount / 100} EUR`);Parameters:
| Param | Type | Required | Description |
|---|---|---|---|
orderId | string | Yes | Order ID |
options | FetchOptions | No | Fetch options (caching, etc.) |
Returns: Promise<Order>
Throws:
NotFoundError- Order not found or belongs to different store
order.listDownloads(orderId, auth?, options?)
List downloadable files and delivery instructions for a paid order. Returns one entry per order line item that has digital content or attached files.
Authentication is required via one of two paths:
- Email token — the
?token=query parameter from the order confirmation email link. Use this for guest checkouts or first-click access from email. - Customer session — pass
sessionIdfor logged-in customers browsing their order history.
// Guest via email link
const token = new URLSearchParams(location.search).get("token") ?? undefined;
const { items } = await storefront.order.listDownloads(orderId, { token });
// Logged-in customer
const { items } = await storefront.order.listDownloads(orderId, { sessionId });Parameters:
| Param | Type | Required | Description |
|---|---|---|---|
orderId | string | Yes | Order ID |
auth | DownloadsAuthOptions | No | { token } (email link) or { sessionId } (logged-in customer) |
options | FetchOptions | No | Fetch options |
Returns: Promise<OrderDownloadsResponse>
Throws:
AuthError- Neither token nor session was validNotFoundError- Order not found or belongs to different storeStorefrontError(403) - Order is not paid (statusPENDINGorFAILED)
order.getDownloadUrl(orderId, downloadId, auth?, options?)
Issue a short-lived presigned Cloudflare R2 URL for a specific file. The server atomically increments the file's download counter before returning the URL, and logs an audit event with IP / user-agent.
const { url, expiresIn } = await storefront.order.getDownloadUrl(
orderId,
downloadId,
{ token }
);
// Redirect the browser so the file streams directly from R2
window.location.href = url;Parameters:
| Param | Type | Required | Description |
|---|---|---|---|
orderId | string | Yes | Order ID |
downloadId | string | Yes | OrderDownload.id from listDownloads |
auth | DownloadsAuthOptions | No | Same as listDownloads |
options | FetchOptions | No | Fetch options |
Returns: Promise<DownloadUrlResponse> — { url, expiresIn }. The URL is
valid for ~1 hour; long enough to survive a slow download on mobile, short
enough that a forwarded link isn't a persistent credential.
Throws:
AuthError- Neither token nor session was validNotFoundError- Order or download not foundStorefrontError(403) - Order is not paidStorefrontError(429) - Per-file download limit already reached (counter was not incremented)
Counter semantics: only successful 200 responses consume a slot against
maxDownloads. A 429 leaves the counter untouched. The presigned URL
itself, once issued, lives for its full TTL — forwarding it lets a third
party download within that window, but does not consume further increments.
Usage Examples
Order Confirmation Page
// app/payment/success/[orderId]/page.tsx
import { storefront } from '@/lib/storefront';
import type { Order } from '@putiikkipalvelu/storefront-sdk';
export default async function OrderConfirmationPage({
params,
}: {
params: Promise<{ orderId: string }>;
}) {
const { orderId } = await params;
let order: Order | null = null;
try {
order = await storefront.order.get(orderId);
} catch {
order = null;
}
if (!order) {
return <div>Order not found</div>;
}
return (
<div>
<h1>Thank you for your order!</h1>
<p>Order #{order.orderNumber}</p>
<p>Status: {order.status}</p>
<p>Total: {order.totalAmount / 100} EUR</p>
<h2>Order Items</h2>
{order.OrderLineItems
.filter(item => item.itemType !== 'SHIPPING')
.map(item => (
<div key={item.id}>
<p>{item.name} x{item.quantity}</p>
<p>{item.totalAmount / 100} EUR</p>
</div>
))}
</div>
);
}Display Tracking Information
const order = await storefront.order.get(orderId);
if (order.orderShipmentMethod?.trackingNumber) {
console.log(`Tracking: ${order.orderShipmentMethod.trackingNumber}`);
order.orderShipmentMethod.trackingUrls?.forEach(url => {
console.log(`Track at: ${url}`);
});
}Display Customer Delivery Info
const order = await storefront.order.get(orderId);
if (order.orderCustomerData) {
const customer = order.orderCustomerData;
console.log(`Delivering to: ${customer.firstName} ${customer.lastName}`);
console.log(`Address: ${customer.address}`);
console.log(`${customer.postalCode} ${customer.city}`);
}Digital Downloads Page
Route pattern: /orders/:orderId/downloads?token=... — the link format used
in order confirmation emails.
import { storefront } from '@/lib/storefront';
export default async function DownloadsPage({
params,
searchParams,
}: {
params: Promise<{ orderId: string }>;
searchParams: Promise<{ token?: string }>;
}) {
const { orderId } = await params;
const { token } = await searchParams;
const { items } = await storefront.order.listDownloads(orderId, { token });
return (
<div>
{items.map((item) => (
<section key={item.id}>
<h2>{item.name}</h2>
{item.digitalContent && (
<div dangerouslySetInnerHTML={{ __html: item.digitalContent }} />
)}
{item.downloads.map((file) => (
<DownloadButton
key={file.id}
orderId={orderId}
downloadId={file.id}
token={token}
displayName={file.displayName}
remaining={file.remaining}
/>
))}
</section>
))}
</div>
);
}Note: keep the download button as a server action or an API route on your storefront so the API key never leaks to the browser. The SDK is configured server-side.
Trigger a Download
'use server';
import { storefront } from '@/lib/storefront';
export async function issueDownload(
orderId: string,
downloadId: string,
token?: string
) {
const { url } = await storefront.order.getDownloadUrl(
orderId,
downloadId,
{ token }
);
return url;
}Client side, redirect to the returned URL. The file streams directly from R2.
Next.js Caching
// Cache for 1 minute
const order = await storefront.order.get(orderId, {
next: { revalidate: 60, tags: ['order', orderId] }
});
// No caching (always fresh)
const order = await storefront.order.get(orderId, {
cache: 'no-store'
});Error Handling
import { NotFoundError } from '@putiikkipalvelu/storefront-sdk';
import { notFound } from 'next/navigation';
try {
const order = await storefront.order.get(orderId);
// Display order...
} catch (error) {
if (error instanceof NotFoundError) {
notFound();
}
throw error;
}TypeScript Types
// Order status values
type ConfirmationOrderStatus =
| 'PENDING'
| 'PAID'
| 'SHIPPED'
| 'DELIVERED'
| 'CANCELLED'
| 'REFUNDED';
// Line item type
type ConfirmationItemType = 'PRODUCT' | 'VARIATION' | 'SHIPPING';
// Order line item
interface ConfirmationOrderLineItem {
id: string;
orderId: string;
itemType: ConfirmationItemType;
quantity: number;
price: number; // Price per unit in cents
totalAmount: number; // Total in cents
productCode: string;
name: string;
vatRate: number;
images: string[];
}
// Customer delivery info
interface ConfirmationOrderCustomerData {
id: string;
firstName: string;
lastName: string;
email: string;
phone: string | null;
address: string;
city: string;
postalCode: string;
}
// Shipment method with tracking
interface ConfirmationOrderShipmentMethod {
id: string;
serviceId: string | null;
name: string;
description: string | null;
logo: string | null;
price: number; // In cents
orderId: string;
vatRate: number | null;
trackingNumber: string | null;
trackingUrls: string[];
shipmentNumber: string | null;
freightDoc: string[];
}
// Complete order
interface Order {
id: string;
storeId: string;
createdAt: string;
totalAmount: number; // Total in cents
status: ConfirmationOrderStatus;
orderNumber: number;
OrderLineItems: ConfirmationOrderLineItem[];
orderCustomerData: ConfirmationOrderCustomerData | null;
orderShipmentMethod: ConfirmationOrderShipmentMethod | null;
}
// ============================================================================
// Digital downloads
// ============================================================================
// Auth input for listDownloads / getDownloadUrl
interface DownloadsAuthOptions {
token?: string; // From order confirmation email link
sessionId?: string; // Logged-in customer session
}
// One downloadable file on an order line item
interface OrderDownload {
id: string; // Pass to getDownloadUrl
displayName: string; // e.g. "guide.pdf"
sizeBytes: number;
mimeType: string;
downloadCount: number; // Times downloaded so far
maxDownloads: number | null; // Cap, or null = unlimited
remaining: number | null; // Convenience: max - count, or null
}
// Line item with digital content and/or downloadable files
interface OrderDownloadLineItem {
id: string;
name: string;
quantity: number;
digitalContent: string | null; // Sanitized HTML instructions
downloads: OrderDownload[];
}
// listDownloads response
interface OrderDownloadsResponse {
orderId: string;
items: OrderDownloadLineItem[]; // Only items with digital payload
}
// getDownloadUrl response
interface DownloadUrlResponse {
url: string; // Presigned R2 URL
expiresIn: number; // Seconds (currently 3600 = 1h)
}Order vs CustomerOrder
The SDK has two different order types for different use cases:
| Type | Use Case | Source |
|---|---|---|
Order | Order confirmation, order detail pages | order.get(orderId) |
CustomerOrder | Customer order history in account pages | customer.getOrders() |
Key differences:
Orderhas raw line items withimagesarray directlyCustomerOrderhas line items with nestedproductobject containing enhanced info (slug, variation options)Order.orderNumberis a number,CustomerOrder.orderNumberis a stringOrderincludes more shipment details (trackingNumber, trackingUrls)