Skip to Content
FeaturesPayments

Payments

Hoikka uses a provider pattern for payments. Stripe is integrated with real Stripe Elements for secure card collection.

Payment Flow

Stripe

1. Customer reaches checkout 2. Select Stripe as payment method 3. Create payment → Stripe PaymentIntent created 4. Stripe Elements renders card form on frontend 5. Client-side confirmation via stripe.confirmPayment() 6. Server confirms payment status 7. Order is completed → redirect to thank-you

Mock (development)

1. Customer reaches checkout 2. Select mock payment method 3. Click "Place Order" → payment created and order completed in one step 4. Redirect to thank-you

Stripe Integration

Environment Variables

STRIPE_SECRET_KEY="sk_test_..." PUBLIC_STRIPE_PUBLISHABLE_KEY="pk_test_..."

Server-Side (PaymentIntent)

const { payment, paymentInfo } = await paymentService.createPayment(order, stripeMethodId); // paymentInfo.clientSecret → used by Stripe Elements // paymentInfo.providerTransactionId → Stripe PaymentIntent ID

Frontend (Stripe Elements)

The StripePayment component handles loading Stripe.js, mounting the Payment Element, and exposing refs for confirmation:

<script lang="ts"> import StripePayment from "$lib/components/storefront/StripePayment.svelte"; let stripeRef, elementsRef; </script> {#if paymentInfo?.methodCode === "stripe" && paymentInfo?.clientSecret} <StripePayment clientSecret={paymentInfo.clientSecret} onready={(stripe, elements) => { stripeRef = stripe; elementsRef = elements; }} /> {/if}

On form submission, confirm the payment client-side before the server action:

const { error } = await stripeRef.confirmPayment({ elements: elementsRef, redirect: "if_required" });

Webhook Handler

src/routes/api/webhooks/stripe/+server.ts
export const POST = async ({ request }) => { const sig = request.headers.get("stripe-signature"); const body = await request.text(); const event = stripe.webhooks.constructEvent(body, sig, WEBHOOK_SECRET); if (event.type === "payment_intent.succeeded") { const paymentIntent = event.data.object; await paymentService.confirmPayment(paymentIntent.id); } return new Response("OK"); };

Payment States

StateDescription
pendingPayment created, awaiting completion
authorizedPayment authorized, not captured
settledPayment captured successfully
declinedPayment failed
refundedPayment refunded

Refunds

// Full refund await paymentService.refundPayment(paymentId); // Partial refund (amount in cents) await paymentService.refundPayment(paymentId, 1500);

Adding Payment Providers

Implement the PaymentProvider interface:

import type { PaymentProvider } from "$lib/server/services/payments/types"; class MyProvider implements PaymentProvider { code = "my-provider"; async createPayment(order) { // Create payment with external provider } async confirmPayment(transactionId) { // Check payment status } async refundPayment(transactionId, amount?) { // Process refund } }

Register in src/lib/server/services/payments/index.ts:

const PROVIDERS = new Map([ ["stripe", new StripeProvider()], ["my-provider", new MyProvider()] ]);
Last updated on