Providers
Hoikka uses a provider pattern for payments and shipping, making it easy to add new integrations.
Payment Providers
Interface
Defined in src/lib/server/services/payments/types.ts:
interface PaymentProvider {
code: string; // unique code like 'stripe', 'paypal'
createPayment: (order: OrderWithRelations) => Promise<PaymentInfo>;
confirmPayment?: (paymentId: string) => Promise<PaymentStatus>;
refundPayment?: (paymentId: string, amount?: number) => Promise<RefundInfo>;
}Adding a Provider
export class KlarnaProvider implements PaymentProvider {
code = "klarna";
async createPayment(order: OrderWithRelations): Promise<PaymentInfo> {
// Create Klarna payment session
const session = await klarnaClient.createSession({
amount: order.total,
currency: "EUR"
// ...
});
return {
providerTransactionId: session.id,
redirectUrl: session.redirect_url
};
}
async confirmPayment(paymentId: string): Promise<PaymentStatus> {
const payment = await klarnaClient.getPayment(paymentId);
return payment.status === "CAPTURED" ? "settled" : "pending";
}
}Register it:
import { KlarnaProvider } from "./providers/klarna";
PROVIDERS.set("klarna", new KlarnaProvider());Then add a matching row to the payment_methods table (the built-in methods are seeded by drizzle/0002_seed_defaults.sql).
Enabling Stripe
Stripe is the built-in real payment provider. To enable it:
- Get API keys from Stripe Dashboard
- Set environment variables:
STRIPE_SECRET_KEY="sk_test_..."
PUBLIC_STRIPE_PUBLISHABLE_KEY="pk_test_..."Stripe is already registered in the provider map and seeded in the database. Once the env vars are set, customers will see a card form powered by Stripe Elements at checkout.
Without Stripe keys, use the built-in mock payment method instead; Stripe payments fail when STRIPE_SECRET_KEY is unset. Mock payments complete the order immediately without a separate confirmation step.
Shipping Providers
Interface
Defined in src/lib/server/services/shipping/types.ts:
interface ShippingProvider {
code: string; // unique code like 'flat_rate'
getRates: (order: OrderWithRelations) => Promise<ShippingRate[]>;
createShipment: (order: OrderWithRelations) => Promise<ShipmentInfo>;
trackShipment?: (trackingNumber: string) => Promise<ShipmentStatus>;
}Adding a Provider
export class DhlProvider implements ShippingProvider {
code = "dhl";
async getRates(order: OrderWithRelations): Promise<ShippingRate[]> {
const rates = await dhlClient.getRates({
from: WAREHOUSE_ADDRESS,
to: order.shippingCity,
weight: calculateWeight(order)
});
return rates.map((rate) => ({
id: rate.serviceCode,
name: rate.serviceName,
price: rate.totalPrice, // in cents
estimatedDeliveryDays: rate.transitDays
}));
}
// ... other methods
}Register it:
import { DhlProvider } from "./providers/dhl";
PROVIDERS.set("dhl", new DhlProvider());Then add a matching row to the shipping_methods table (the built-in flat_rate method is seeded by drizzle/0002_seed_defaults.sql).
Built-in Providers
Payments
- Stripe - Full integration with Payment Intents and Stripe Elements
- Mock - For development/testing (auto-completes orders immediately)
Shipping
- Flat Rate - Simple flat-rate shipping for development and demos