Checkout
Process payments with Stripe and Paytrail payment providers
Overview
The checkout resource handles payment processing with two supported providers:
- Stripe - International card payments and digital wallets
- Paytrail - Finnish payment methods (banks, mobile pay, cards)
Both methods validate cart contents, reserve inventory, create an order, and return payment URLs/providers.
Checkout Flow:
- Cart is validated (stock, prices)
- Order is created with PENDING status
- Inventory is reserved
- Payment session is created
- User completes payment
- Webhook updates order to PAID
Methods
checkout.stripe(params, options?)
Create a Stripe checkout session and get a redirect URL.
const { url } = await storefront.checkout.stripe({
customerData: {
first_name: "Matti",
last_name: "Meikäläinen",
email: "matti@example.fi",
address: "Mannerheimintie 1",
postal_code: "00100",
city: "Helsinki",
phone: "+358401234567"
},
shipmentMethod: {
shipmentMethodId: "ship_123",
pickupId: null
},
orderId: "order_abc123",
successUrl: "https://mystore.com/payment/success/order_abc123",
cancelUrl: "https://mystore.com/payment/cancel/order_abc123"
}, {
cartId: "cart_xyz", // For guest users
sessionId: "sess_123" // For logged-in users
});
// Redirect to Stripe
window.location.href = url;Parameters:
| Param | Type | Required | Description |
|---|---|---|---|
params.customerData | CheckoutCustomerData | Yes | Customer information |
params.shipmentMethod | CheckoutShipmentMethod | null | Yes | Selected shipping method |
params.orderId | string | Yes | Unique order ID (generate with randomUUID()) |
params.successUrl | string | Yes | Redirect URL on success |
params.cancelUrl | string | Yes | Redirect URL on cancel |
options.cartId | string | No | Cart ID for guest users |
options.sessionId | string | No | Session ID for logged-in users |
Returns: Promise<StripeCheckoutResponse>
interface StripeCheckoutResponse {
url: string; // Stripe checkout URL
}Throws:
ValidationError- Empty cart, product not found, insufficient inventory
checkout.paytrail(params, options?)
Create a Paytrail checkout session and get payment providers.
const response = await storefront.checkout.paytrail({
customerData: {
first_name: "Matti",
last_name: "Meikäläinen",
email: "matti@example.fi",
address: "Mannerheimintie 1",
postal_code: "00100",
city: "Helsinki",
phone: "+358401234567"
},
shipmentMethod: {
shipmentMethodId: "ship_123",
pickupId: "loc_456" // For pickup points
},
orderId: "order_abc123",
successUrl: "https://mystore.com/payment/success/order_abc123",
cancelUrl: "https://mystore.com/payment/cancel/order_abc123"
}, {
cartId: "cart_xyz"
});
// Display payment providers grouped by type
const banks = response.providers.filter(p => p.group === "bank");
const mobile = response.providers.filter(p => p.group === "mobile");Parameters:
Same as checkout.stripe().
Returns: Promise<PaytrailCheckoutResponse>
interface PaytrailCheckoutResponse {
transactionId: string; // Paytrail transaction ID
href: string; // Direct payment URL
reference: string; // Payment reference
terms: string; // Terms URL
groups: PaytrailGroup[]; // Payment method groups
providers: PaytrailProvider[]; // Available providers
customProviders: Record<string, unknown>;
}
interface PaytrailGroup {
id: string; // "bank", "mobile", "creditcard"
name: string; // Display name
icon: string; // Icon URL
svg: string; // SVG icon
}
interface PaytrailProvider {
id: string; // Provider ID (e.g., "nordea")
name: string; // Display name
group: string; // Group ID
icon: string; // Icon URL
svg: string; // SVG icon
url: string; // Form submission URL
parameters: Array<{ // Hidden form fields
name: string;
value: string;
}>;
}Throws:
ValidationError- Empty cart, product not found, insufficient inventory
Usage Examples
Stripe Checkout (Server Action)
// lib/actions/checkout.ts
"use server";
import { storefront } from "@/lib/storefront";
import { cookies } from "next/headers";
import { randomUUID } from "crypto";
import type { CheckoutCustomerData, CheckoutShipmentMethod } from "@putiikkipalvelu/storefront-sdk";
export async function createStripeCheckout(
customerData: CheckoutCustomerData,
shipmentMethod: CheckoutShipmentMethod | null
) {
const cookieStore = await cookies();
const cartId = cookieStore.get("cart-id")?.value;
const sessionId = cookieStore.get("session-id")?.value;
const orderId = randomUUID();
const { url } = await storefront.checkout.stripe({
customerData,
shipmentMethod,
orderId,
successUrl: `${process.env.NEXT_PUBLIC_BASE_URL}/payment/success/${orderId}`,
cancelUrl: `${process.env.NEXT_PUBLIC_BASE_URL}/payment/cancel/${orderId}`,
}, {
cartId,
sessionId,
});
return { url };
}Paytrail Provider Selection
// components/PaytrailProviders.tsx
"use client";
import type { PaytrailCheckoutResponse } from "@putiikkipalvelu/storefront-sdk";
export function PaytrailProviders({ data }: { data: PaytrailCheckoutResponse }) {
return (
<div>
{data.groups.map(group => (
<div key={group.id}>
<h3>{group.name}</h3>
<div className="grid grid-cols-4 gap-4">
{data.providers
.filter(p => p.group === group.id)
.map(provider => (
<form
key={provider.id}
method="POST"
action={provider.url}
>
{provider.parameters.map(param => (
<input
key={param.name}
type="hidden"
name={param.name}
value={param.value}
/>
))}
<button type="submit">
<img src={provider.svg} alt={provider.name} />
</button>
</form>
))}
</div>
</div>
))}
</div>
);
}Complete Checkout Flow
// app/checkout/page.tsx
import { storefront } from "@/lib/storefront";
import { cookies } from "next/headers";
import { randomUUID } from "crypto";
async function handleCheckout(formData: FormData) {
"use server";
const cookieStore = await cookies();
const cartId = cookieStore.get("cart-id")?.value;
const orderId = randomUUID();
const customerData = {
first_name: formData.get("firstName") as string,
last_name: formData.get("lastName") as string,
email: formData.get("email") as string,
address: formData.get("address") as string,
postal_code: formData.get("postalCode") as string,
city: formData.get("city") as string,
phone: formData.get("phone") as string,
};
const shipmentMethod = {
shipmentMethodId: formData.get("shipmentMethodId") as string,
pickupId: formData.get("pickupId") as string | null,
};
const provider = formData.get("provider") as string;
if (provider === "stripe") {
const { url } = await storefront.checkout.stripe({
customerData,
shipmentMethod,
orderId,
successUrl: `${process.env.NEXT_PUBLIC_BASE_URL}/payment/success/${orderId}`,
cancelUrl: `${process.env.NEXT_PUBLIC_BASE_URL}/payment/cancel/${orderId}`,
}, { cartId });
redirect(url);
} else {
const response = await storefront.checkout.paytrail({
customerData,
shipmentMethod,
orderId,
successUrl: `${process.env.NEXT_PUBLIC_BASE_URL}/payment/success/${orderId}`,
cancelUrl: `${process.env.NEXT_PUBLIC_BASE_URL}/payment/cancel/${orderId}`,
}, { cartId });
// Return Paytrail providers for selection
return response;
}
}Error Handling
import { ValidationError, StorefrontError } from "@putiikkipalvelu/storefront-sdk";
try {
const { url } = await storefront.checkout.stripe(params, options);
redirect(url);
} catch (error) {
if (error instanceof ValidationError) {
// Cart/inventory issues
if (error.message.includes("EMPTY_CART")) {
return { error: "Your cart is empty" };
}
if (error.message.includes("INSUFFICIENT_INVENTORY")) {
return { error: "Some items are out of stock" };
}
return { error: "Checkout validation failed" };
}
if (error instanceof StorefrontError) {
console.error("Checkout failed:", error.message);
return { error: "Payment processing failed" };
}
throw error;
}TypeScript Types
// Customer data for checkout
interface CheckoutCustomerData {
first_name: string;
last_name: string;
email: string;
address: string;
postal_code: string; // 5-digit Finnish postal code
city: string;
phone: string;
}
// Shipment method selection
interface CheckoutShipmentMethod {
shipmentMethodId: string;
pickupId: string | null; // For pickup points
}
// Checkout parameters
interface CheckoutParams {
customerData: CheckoutCustomerData;
shipmentMethod: CheckoutShipmentMethod | null;
orderId: string;
successUrl: string;
cancelUrl: string;
}
// Checkout options (session context)
interface CheckoutOptions {
cartId?: string; // Guest cart ID
sessionId?: string; // Logged-in user session
}Important Notes
Order ID Generation
Always generate a unique order ID on the client before calling checkout:
import { randomUUID } from "crypto";
const orderId = randomUUID();Success/Cancel URL Pattern
Include the order ID in your URLs for easy order lookup:
const baseUrl = process.env.NEXT_PUBLIC_BASE_URL;
const successUrl = `${baseUrl}/payment/success/${orderId}`;
const cancelUrl = `${baseUrl}/payment/cancel/${orderId}`;Cart Context Headers
The SDK automatically handles cart context via options:
cartId- For guest users (from cookie)sessionId- For logged-in users (from session cookie)
Pass at least one for cart identification.
Webhook Handling
After successful payment:
- Stripe/Paytrail sends webhook to your backend
- Order status updates from PENDING to PAID
- Cart is cleared
- User is redirected to successUrl
The order will be visible at /order/{orderId} after payment webhook processes.