Skip to content
Last updated

Kustom Checkout for Magento / Adobe Commerce - Comprehensive Developer Guide

Table of Contents

  1. Introduction
  2. Installation & Setup
  3. Configuration Reference
  4. Architecture Overview
  5. Module Reference
  6. Database Schema
  7. Data Flow & Integration
  8. Extension Points
  9. API Reference
  10. Troubleshooting
  11. Support
  12. Quick Reference

Introduction

Kustom Checkout is a modular, embedded checkout solution for Adobe Commerce and Magento Open Source 2.4 that supports multiple payment methods, B2B purchases, and visual customization to match the store theme.

With a single integration, merchants can enable all Kustom payment methods in supported markets and keep shoppers on-site in a fast checkout flow without redirects.

Important Note: Naming Convention

While the composer vendor name is kustom, the internal PHP namespaces use Klarna due to the module's origin. This guide uses both names as appropriate:

  • Composer packages: kustom/module-*
  • PHP namespaces: Klarna\*
  • Database tables: klarna_*
  • Configuration paths: klarna/*

This naming reflects the transition from Klarna to Kustom branding while maintaining backward compatibility.

Key Highlights

  • Broad payment method coverage in all Kustom-supported countries
  • Pre-filled customer addresses for a near one-click checkout
  • Custom marketing and account-creation checkboxes
  • Business-to-business (B2B) purchase support
  • Optional shipping assistant integration
  • Extra Merchant Data to improve risk decisions
  • Merchant reference fields for settlements and reconciliation
  • Flexible UI options to align with the store's design

Requirements

  • Adobe Commerce (cloud or on-premise) 2.4 or Magento Open Source 2.4
  • PHP 8.1, 8.2, or 8.4 aligned with Adobe's certified versions
  • HTTPS with a valid SSL certificate
  • An active Kustom merchant account with API credentials

Compatibility

  • Adobe Commerce Cloud 2.4
  • Adobe Commerce on-premise 2.4
  • Magento Open Source 2.4

Supported Markets

Kustom Checkout is offered in many countries, with a core set including Nordic, major European, and selected other markets such as:

  • Nordic: Sweden, Norway, Finland, Denmark
  • Europe: United Kingdom, Germany, the Netherlands, Austria, Switzerland, Spain, France, Belgium, Poland
  • Americas: United States
  • Other: Additional markets available (contact Kustom for full list)

Installation & Setup

Installing Kustom Checkout is straightforward using Composer, which automatically manages all module dependencies. The process involves installing the package, running Magento's setup commands, and configuring your API credentials. For local development, you'll need to set up a tunnel service to make callbacks publicly accessible. This section guides you through the complete installation and initial setup process.

Installation via Composer

Kustom Checkout is installed using Composer. The main package automatically pulls in all required dependencies.

Install the Kustom Checkout module:

# Install the main checkout module (includes all dependencies)
composer require kustom/module-checkout

# Run Magento setup commands
php bin/magento setup:upgrade
php bin/magento setup:di:compile
php bin/magento setup:static-content:deploy
php bin/magento cache:flush

Verify installation:

# Check that all Kustom modules are enabled
php bin/magento module:status | grep Klarna

# You should see modules like:
# Klarna_Base
# Klarna_Kco
# Klarna_Orderlines
# Klarna_Kss
# ... etc.

Initial Configuration

After installation, you need to configure your Kustom API credentials and enable the checkout. The configuration is region-specific (EU, US, AU, etc.), so make sure to select the region that matches your merchant account and store country. The Terms & Conditions URL is mandatory - checkout will not work without it. Remember to flush caches after any configuration changes for settings to take effect.

  1. Navigate to Stores → Configuration → Sales → Payment Methods → Kustom

  2. API Credentials (per region):

    • Select your region (EU, US, AU, etc.)
    • Enter API username (production)
    • Enter API password (production)
    • Set API mode (Test/Live)
  3. Enable Kustom Checkout:

    • Set "Enabled" to Yes
    • Configure allowed countries
    • Set order status for new orders
  4. Configure Terms URL (Required):

    • Navigate to Stores → Configuration → Sales → Checkout → Kustom Checkout
    • Set "Terms & Conditions URL" - checkout will fail without this
  5. Flush Caches:

    Option A - Via Admin Panel (easiest for non-developers):

    • Navigate to System → Cache Management
    • Click Flush Magento Cache button

    Option B - Via Command Line:

    php bin/magento cache:flush

QA Verification Steps

Once installation and configuration are complete, test the integration to ensure everything works correctly. Use your test/playground API credentials for these verification steps to avoid creating real transactions. This checklist confirms that the module is properly installed, configured, and communicating with Kustom's API.

  1. Verify module is active:

    php bin/magento module:status Klarna_Kco
    # Should show: Module is enabled
  2. Check configuration is saved:

    • Navigate to Stores → Configuration → Sales → Payment Methods → Kustom
    • Verify your API credentials are present
    • Confirm "Enabled" is set to Yes
    • Ensure API mode is set to "Playground" or "Test"
  3. Test the checkout flow:

    • Add a product to your cart
    • Proceed to checkout
    • Expected result: Kustom checkout iframe loads successfully
    • Verify payment methods are displayed in the iframe
    • Enter a test address to trigger the address update callback
    • Select a shipping method to trigger the shipping callback
  4. Check for errors:

    • Open browser developer console (F12)
    • Look for JavaScript errors (there should be none)
    • Check Network tab for failed API calls
  5. Complete a test order:

    • Use Kustom test credentials to complete the payment
    • Expected result: Order is created in Magento
    • You should be redirected to the confirmation page
  6. Verify order in Magento Admin:

    • Navigate to Sales → Orders
    • Find your test order
    • Verify order status is correct (typically "Pending Payment" or "Processing")
    • Check that order totals match the checkout
  7. Check logs for issues (optional but recommended):

    SELECT * FROM klarna_logs
    ORDER BY created_at DESC
    LIMIT 10;
    • Verify API calls show status 200/201
    • Check that callbacks (address_update, shipping_update, push) completed successfully

Local Development Setup

When developing locally (e.g., on localhost or a private network), Kustom's servers cannot reach your callbacks since they're not publicly accessible. To test the full checkout flow including callbacks, you need to expose your local environment to the internet using a tunneling service. This allows Kustom to send address updates, shipping requests, and the final push callback to your development machine.

Option 1: ngrok (Recommended)

ngrok creates a secure tunnel to your local server and provides a public HTTPS URL:

# Start ngrok tunnel to your local Magento
ngrok http 80

# ngrok will display a URL like: https://abc123-456.ngrok-free.app

After starting ngrok:

  1. Copy the HTTPS URL provided by ngrok
  2. Update your Magento base URL:
    • Admin Panel: Stores → Configuration → General → Web → Base URLs
    • Set "Base URL" and "Base URL (Secure)" to your ngrok URL
  3. Clear Magento cache
  4. Test checkout - Kustom callbacks will now reach your local environment

Option 2: localtunnel

An alternative open-source tunneling solution:

# Install localtunnel (one-time)
npm install -g localtunnel

# Create tunnel
lt --port 80

# Use the provided URL (e.g., https://random-word-123.loca.lt)

Follow the same steps as ngrok to update your Magento base URL.

Important Notes:

  • No whitelisting required: Kustom accepts callbacks from any URL, so your tunnel URL will work immediately
  • HTTPS is required: Both ngrok and localtunnel provide HTTPS by default
  • Remember to revert base URL: After development, change your base URLs back to your actual domain
  • Tunnel URLs change: Free ngrok/localtunnel URLs change each restart, so you'll need to update Magento's base URL each time

Configuration Reference

Kustom Checkout is configured through Magento's admin panel, with settings organized by region and functionality. Configuration includes API credentials for different regions (EU, US, AU, etc.), payment method settings, checkout behavior, and visual design options. Most settings are store-scoped, allowing different configurations per store view. Sensitive values like API passwords are automatically encrypted by Magento. This section provides a complete reference of all configuration paths and their purposes.

API Configuration Paths

Core API Settings

klarna/api/
  - debug (boolean, default: 0)
      Enable debug logging

      ⚠️ WARNING: Only enable debug mode when actively troubleshooting issues.
      Debug mode generates extensive logs that can quickly consume disk space
      and fill the klarna_logs database table. Always disable debug mode after
      completing your investigation to prevent performance issues.

  - region (checkbox: au, ca, eu, mx, nz, us)
      Active regions for API connections

Region-Specific API Credentials

Replace {region} with: au, ca, eu, mx, nz, or us

klarna/api_{region}/
  - api_mode (select: 0=production, 1=playground)
      Environment selection

  - username_production (string, required)
      Production API username

  - password_production (encrypted, required)
      Production API password (stored encrypted)

  - client_identifier_production (string)
      Optional client identifier

  - username_playground (string)
      Playground/test username

  - password_playground (encrypted)
      Playground/test password (stored encrypted)

  - client_identifier_playground (string)
      Optional test client identifier

Payment Method Configuration

Klarna Payments (KP):

payment/klarna_kp/
  - active (boolean)
      Enable/disable Klarna Payments

  - enable_b2b (boolean)
      Enable B2B purchase support

  - sort_order (integer)
      Display order in payment methods list

  - title (string)
      Payment method title shown to customers

Kustom Checkout (KCO):

payment/klarna_kco/
  - active (boolean)
      Enable/disable Kustom Checkout

  - order_status (select)
      Initial order status after placement

  - shipping_countries (multiselect)
      Allowed shipping countries

  - billing_countries (multiselect)
      Allowed billing countries

  - disable_customer_group (multiselect)
      Customer groups excluded from KCO

Checkout Configuration

checkout/klarna_kco/
  - guest_checkout (boolean)
      Allow guest checkout in KCO

  - enable_b2b (boolean)
      Enable business purchases

  - auto_focus (boolean)
      Auto-focus first field in iframe

  - merchant_prefill (boolean)
      Pre-fill customer data

  - terms_url (text, REQUIRED)
      Terms & conditions URL (must be set)

  - shipping_in_iframe (boolean)
      Show shipping methods in iframe

  - phone_mandatory (boolean)
      Make phone number required

  - dob_mandatory (boolean)
      Make date of birth required

Design Configuration

checkout/klarna_kco_design/
  - color_button (color)
      Button color (hex value)

  - color_button_text (color)
      Button text color

  - color_checkbox (color)
      Checkbox color

  - color_header (color)
      Header color

  - color_link (color)
      Link color

  - radius_border (text)
      Border radius in pixels

Shipping Configuration

klarna/shipping/
  - product_unit (select: cm/inch)
      Unit for package dimensions

  - length (attribute)
      Product attribute for length

  - width (attribute)
      Product attribute for width

  - height (attribute)
      Product attribute for height

Kustom Shipping Service

payment/klarna_kss/
  - enabled (boolean)
      Enable dynamic shipping in KCO

Programmatic Configuration Access

<?php
use Magento\Framework\App\Config\ScopeConfigInterface;
use Magento\Store\Model\ScopeInterface;

class ConfigExample
{
    private $scopeConfig;

    public function __construct(ScopeConfigInterface $scopeConfig)
    {
        $this->scopeConfig = $scopeConfig;
    }

    public function getApiUsername($storeId = null)
    {
        return $this->scopeConfig->getValue(
            'klarna/api_eu/username_production',
            ScopeInterface::SCOPE_STORE,
            $storeId
        );
    }

    public function isDebugEnabled($storeId = null)
    {
        return $this->scopeConfig->isSetFlag(
            'klarna/api/debug',
            ScopeInterface::SCOPE_STORE,
            $storeId
        );
    }
}

Sensitive Configuration

The following configuration values are encrypted:

  • All password_production fields
  • All password_playground fields
  • klarna/api/merchant_id
  • klarna/api/shared_secret

Environment-Specific Configuration

These settings typically differ between environments:

  • klarna/api_{region}/api_mode - Test mode in dev/staging
  • klarna/api/debug - Enabled in dev, disabled in production
  • checkout/klarna_kco/terms_url - Environment-specific URLs

Architecture Overview

The Kustom integration is built on a modular architecture where each module has a specific responsibility. At the core is the Base module, which handles all API communication, while specialized modules manage payments (KP), checkout (KCO), shipping (KSS), and order management. All modules share common infrastructure for logging, configuration, and data transformation. Understanding this architecture helps developers know where to find functionality and how to extend the system safely.

System Components

The Kustom integration consists of several interconnected modules, each with specific responsibilities:

┌─────────────────────────────────────────────────────────────┐
│                     Magento Storefront                      │
│  ┌─────────────┐  ┌──────────────┐  ┌──────────────────┐    │
│  │ KP Payment  │  │  KCO Iframe  │  │  PWA/GraphQL     │    │
│  └─────────────┘  └──────────────┘  └──────────────────┘    │
└─────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────┐
│              Kustom API Layer (Base Module)                 │
│  ┌────────┐ ┌────────┐ ┌────────┐ ┌───────────┐ ┌──────┐    │
│  │   KP   │ │  KCO   │ │  KSS   │ │Orderlines │ │  OM  │    │
│  └────────┘ └────────┘ └────────┘ └───────────┘ └──────┘    │
│  ┌──────────────────────┐  ┌──────────────────────────┐     │
│  │  Logger / Settings   │  │   Supporting Modules     │     │
│  └──────────────────────┘  └──────────────────────────┘     │
└─────────────────────────────────────────────────────────────┘

Core Architectural Principles

  1. Unified API Client: All Kustom communication flows through the Base module
  2. Orderlines as Foundation: Required by both KP and KCO, recalculated whenever totals change
  3. Public Callbacks: KCO requires publicly accessible callback endpoints
  4. Centralized Logging: All requests, responses, and callbacks captured by Logger module
  5. Order State Synchronization: Order Management ensures Magento state matches Kustom

Important Deprecation Notice

The following Klarna capabilities are being phased out and should not be relied upon for new development:

  • Klarna Payments (KP) - legacy payment method support
  • Klarna Sign-In (SIWK) - customer identity/prefill
  • Klarna On-Site Messaging (OSM) - promotional widgets

These features continue to work but are scheduled for removal as Kustom transitions to a fully vendor-independent checkout architecture.


Module Reference

This section provides detailed information about each Kustom module, including its purpose, key files, capabilities, and developer notes. The modules are organized into core foundation modules (Base, Logger, Orderlines), payment and checkout modules (KP, KCO, KSS), backend operations (Order Management), and optional enhancement modules (Express Checkout, Sign-In-With-Klarna, On-Site Messaging). Understanding each module's role helps developers navigate the codebase and troubleshoot issues effectively.

Module Dependencies

The modules have specific dependencies that are automatically handled by Composer. When you install kustom/module-checkout, Composer will pull in all required dependencies in the correct order.

Dependency Chain (for reference):

### Base Module (foundation)
  ├─ Logger
  ├─ AdminSettings
  └─ Orderlines
      ├─ KCO (Kustom Checkout)
      │   └─ KSS (Shipping Service)
      ├─ KP (Klarna Payments)
      │   └─ KpGraphQl
      └─ Backend (Order Management)

Optional Modules:
  ├─ KEC (Express Checkout)
  ├─ SIWK (Sign-In-With-Klarna)
  ├─ OSM (On-Site Messaging)
  └─ Support, Interoperability, etc.

Core Foundation Modules

Base Module (kustom/module-base)

The Base module is the foundation of the entire Kustom integration, providing shared infrastructure that all other modules depend on. It handles all HTTP communication with Kustom's API, manages authentication and environment selection (test vs production), and provides utilities for configuration access. Think of it as the core engine that powers everything else.

Key Capabilities:

  • HTTP client wrapper for all API communication
  • Authentication and environment selection (test/live)
  • Store-scoped configuration retrieval
  • Shared utilities and constants
  • Error wrapping and transport handling

Key Files:

  • Api/ServiceInterface.php - HTTP client interface
  • Api/OrderRepositoryInterface.php - Order data access
  • Model/Api/Service.php - Main API client implementation
  • Helper/ConfigHelper.php - Configuration access

Developer Notes:

  • Used by every other Kustom module
  • Provides a connection factory for different Kustom services (KCO, KP, OM)
  • Handles region-specific API endpoints (EU, US, AU, etc.)

Admin Settings Module (kustom/module-admin-settings)

This module manages all configuration storage and retrieval for Kustom. It defines the admin panel structure where merchants configure API credentials, enable/disable modules, set merchant references, and control debug mode. All configuration values are store-scoped and cached by Magento's config system for performance.

Key Capabilities:

  • API credentials management (test/live environments)
  • Activation toggles per module
  • Merchant references for ERP integration
  • Debug mode configuration

Key Files:

  • etc/adminhtml/system.xml - Admin configuration structure
  • etc/config.xml - Default configuration values

Developer Notes:

  • Configuration values are store-scoped
  • Cached by Magento's config cache
  • Sensitive data (passwords, API keys) is encrypted

Logger Module (kustom/module-logger)

The Logger module captures all API interactions and callback activity for debugging and auditing purposes. It stores logs both in the database (klarna_logs table) and files (var/log/klarna.log), making it easy to track down issues during development and production. This is your first stop when troubleshooting integration problems.

Key Capabilities:

  • Logs all API requests and responses
  • Captures callback payloads (address, shipping, validate, push)
  • Optional database logging for debugging
  • File-based logging via Monolog

Storage Locations:

  • Database: klarna_logs table
  • File: var/log/klarna.log

Cron Jobs:

  • klarna_core_clean_logs - Daily at midnight (cleanup old entries)
  • klarna_core_update_api_log - Every minute (update log entries)

Developer Notes:

  • Enable debug mode in Admin Settings to see detailed logs
  • Essential for troubleshooting API issues
  • Check klarna_logs table for order-specific debugging

Orderlines Module (kustom/module-orderlines)

This critical module translates Magento's cart totals into the orderline format required by Kustom's API. It handles all the complexity of converting products, shipping, discounts, taxes, gift cards, and other totals into properly formatted line items with correct amounts in minor units (cents). Most integration issues stem from orderlines mismatches, making this module essential to understand.

Key Capabilities:

  • Translates Magento totals into Kustom line items
  • Handles tax, discounts, bundles, shipping
  • Regenerates orderlines when cart totals change
  • Ensures numeric values follow Kustom formatting rules

Orderline Types Handled:

  • Product items
  • Shipping costs
  • Discounts (cart/coupon)
  • Tax line items
  • Gift cards
  • Store credit
  • Reward points
  • Gift wrapping
  • Custom surcharges

Key Files:

  • Model/Container/Parameter.php - Orderline container
  • Model/Container/AbstractParameter.php - Base for custom handlers
  • Helper/DataHelper.php - Amount formatting utilities

Developer Notes:

  • CRITICAL: Most Kustom integration issues stem from incorrect orderlines
  • Always verify amounts are in minor units (cents)
  • Ensure rounding precision matches Kustom expectations
  • Line item totals must match grand total
  • VAT/tax alignment is crucial

Payment & Checkout Modules

Kustom Payments (KP) (kustom/module-payments)

Klarna Payments integrates as a standard Magento payment method, allowing customers to select Klarna as their payment option during checkout while staying within Magento's native checkout flow. It manages payment sessions, handles authorization callbacks, and supports B2B purchases with multiple payment categories (pay now, pay later, pay over time). Note that this module is being phased out in favor of Kustom Checkout (KCO) for new implementations.

Payment Method Code: klarna_kp

Key Capabilities:

  • Session management
  • Authorization callback support
  • B2B support
  • Multiple payment categories (pay_now, pay_later, pay_over_time)

Key Files:

  • Model/Payment/Kp.php - Payment method implementation
  • Model/Api/Builder/Kasper.php - Session builder
  • Gateway/Command/ - Payment commands

Database Tables:

  • klarna_payments_quote - Stores KP sessions per quote

Cron Jobs:

  • klarna_payments_quote_clean_old_entries - Weekly on Saturday (cleanup expired sessions)

Payments GraphQL (kustom/module-payments-graph-ql)

This module extends Klarna Payments with GraphQL support for headless and PWA storefronts. It provides mutations to create payment sessions and set payment methods, allowing frontend applications to integrate Klarna Payments without relying on traditional Magento checkout templates. Essential for modern, decoupled commerce architectures.

Key Capabilities:

  • Create KP session via GraphQL mutation
  • Return client token for frontend widget
  • Handle authorization in headless architecture

Key Files:

  • Model/Resolver/CreateSession.php - GraphQL resolver
  • etc/schema.graphqls - GraphQL schema definition

Developer Notes:

  • Required for PWA storefronts
  • Frontend must load Klarna widget with client token
  • See API Reference for GraphQL schema

Kustom Checkout (KCO) (kustom/module-kco)

Kustom Checkout replaces Magento's entire checkout experience with an embedded iframe that handles address collection, shipping selection, and payment in one unified flow. Kustom manages the customer experience while Magento responds to callbacks to update orderlines and ultimately create the order. This is the recommended integration method for new implementations, offering the smoothest customer experience.

Payment Method Code: klarna_kco

Key Capabilities:

  • Loads Kustom iframe on checkout page
  • Kustom handles address collection, shipping selection, and payment
  • Magento responds to Kustom callbacks
  • Push callback creates/updates Magento order

Callback Endpoints:

CallbackRouteTriggerPurpose
address_update/kco/api/addressupdateAddress changeUpdate orderlines with new address/tax
shipping_option_update/kco/api/shippingmethodupdateShipping selectionUpdate orderlines with shipping cost
validate/kco/api/validateBefore purchaseFinal validation before Kustom authorizes
push/kco/api/pushPost-authorizationCreate/update Magento order
confirmation/kco/klarna/confirmationSuccess redirectDisplay confirmation page

Key Files:

  • Controller/Api/ - Callback controllers
  • Model/Api/Builder/ - Session builders
  • Model/Checkout/ - Order creation logic

Database Tables:

  • klarna_kco_quote - Links quotes to KCO orders

Required Configuration:

  • checkout/klarna_kco/terms_url - REQUIRED (checkout will fail without this)
  • checkout/klarna_kco_design/* - Iframe styling options

Developer Requirements:

  • ⚠️ Callbacks must be publicly accessible
  • Use ngrok/localtunnel for local development
  • Maintenance mode breaks KCO (callbacks return 503)

Kustom Shipping Service (KSS) (kustom/module-kss)

The Shipping Service acts as a bridge between Magento's shipping methods and the Kustom Checkout iframe. When a customer enters their address in KCO, Kustom requests available shipping options from Magento via KSS, which returns the appropriate carriers and costs. This ensures shipping calculations stay synchronized between Magento and the checkout experience.

Key Capabilities:

  • Supplies Magento shipping methods to KCO
  • Supports multiple carriers and delivery modes
  • Recalculates totals when shipping method changes

REST API Endpoint:

GET /V1/getDeliveryDataByKlarnaSessionId/:sessionId
Authentication: Required (Magento token)
Returns: JSON delivery data

Key Files:

  • Api/DeliveryDetailsInterface.php - REST service interface
  • Model/ShippingProvider.php - Shipping method provider

Database Tables:

  • klarna_shipping_method_gateway - Stores shipping methods for KSS

Developer Notes:

  • Used exclusively by KCO callbacks, not by external systems
  • Session ID must be a valid Klarna session
  • Called during shipping_option_update callback

Backend Operations (Order Management)

Backend Module (kustom/module-backend)

Order Management keeps Magento and Kustom synchronized throughout the order lifecycle. When you create an invoice in Magento, it triggers a capture in Kustom. Credit memos trigger refunds, and cancellations are synchronized automatically. This ensures that order states remain consistent across both systems without manual intervention.

Key Capabilities:

  • Invoice → Capture in Kustom
  • Credit Memo → Refund in Kustom
  • Cancel Order → Cancel in Kustom
  • Transaction info retrieval

Gateway Commands:

  • CaptureCommand - Triggered on invoice creation
  • RefundCommand - Triggered on credit memo
  • CancelCommand - Triggered on order cancellation
  • FetchTransactionInfoCommand - Retrieve transaction details

Order Lifecycle & State Mapping:

The order flow differs between KCO and KP, but both follow similar state transitions:

  1. Order Placement (Push Callback for KCO, Authorization for KP):

    • Kustom State: AUTHORIZED
    • Magento State: new or payment_review (initially)
    • What happens: Order is created in Magento, payment is authorized in Kustom
    • Note: Order is NOT yet captured/invoiced at this stage
  2. Acknowledgement (automatic after order placement):

    • Action: acknowledgeOrder() API call
    • Purpose: Notifies Kustom that Magento received the order
    • When: Called automatically during push callback (KCO) or after authorization (KP)
    • Magento State: Transitions from payment_review to configured status (typically processing)
    • Database: Sets is_acknowledged = 1 in klarna_core_order table
  3. Manual Invoice Creation (merchant action):

    • Magento Action: Create invoice in admin
    • Triggers: CaptureCommand
    • Kustom API Call: POST /ordermanagement/v1/orders/{id}/captures
    • Kustom State: AUTHORIZEDCAPTURED (full capture) or PART_CAPTURED (partial)
    • Magento State: processing (remains in processing)
  4. Refund (merchant action):

    • Magento Action: Create a credit memo
    • Triggers: RefundCommand
    • Kustom API Call: POST /ordermanagement/v1/orders/{id}/refunds
    • Kustom State: CAPTUREDREFUNDED or PART_REFUNDED
    • Magento State: closed (if fully refunded)
  5. Cancellation (merchant action):

    • Magento Action: Cancel order
    • Triggers: CancelCommand
    • Kustom API Call: POST /ordermanagement/v1/orders/{id}/cancel
    • Kustom State: AUTHORIZEDCANCELLED
    • Magento State: canceled

Important Notes:

  • No automatic capture: Creating the order does NOT automatically capture payment. Capture only happens when you manually create an invoice.
  • Acknowledgement is automatic: The order is acknowledged to Kustom immediately after placement, transitioning from payment_review to your configured order status.
  • No cron for capture: There is no cron job that automatically captures orders. Merchants must manually invoice orders to trigger capture.

Key Files:

  • Gateway/Command/Capture.php - Capture command (invoice creation)
  • Gateway/Command/Refund.php - Refund command (credit memo)
  • Gateway/Command/Cancel.php - Cancel command
  • Model/Api/OrderManagement.php - Order Management API client

Developer Notes:

  • Uses Magento's Payment Gateway pattern
  • Commands extend both KlarnaKpCommandPool and KcoPaymentCommandPool
  • Capture can include shipping information if "Create Shipment" is selected during invoice creation
  • Extensible via dependency injection

Optional / Enhancement Modules

Express Checkout (KEC) (kustom/module-kec)

Express Checkout adds a one-click checkout button to product and cart pages, allowing returning customers to skip multiple checkout steps by using their pre-filled Kustom data. The button can be placed on product pages, cart, or during checkout, and its appearance is fully configurable to match your store's design.

Key Capabilities:

  • Pre-populated customer data
  • Skips Magento checkout steps
  • Configurable button placement and styling

Configuration:

  • payment/kec/enabled
  • payment/kec/position - Placement (product, cart, checkout)
  • payment/kec/theme + shape - Button styling

Sign-In-With-Klarna (SIWK) (kustom/module-siwk)

This module implements OAuth-based authentication allowing customers to sign in with their Klarna account and automatically populate their profile, email, phone, and address information. The integration uses JWT tokens validated with the Firebase PHP-JWT library. Note that this module is being phased out as part of Kustom's transition away from Klarna-specific features.

OAuth Scopes:

  • openid - Required
  • profile - Name, DOB
  • email, phone, address - Optional

Database Tables:

  • klarna_siwk_token - OAuth tokens
  • klarna_siwk_customer - Maps Magento customers to Klarna customers

Security:

  • Uses Firebase PHP-JWT library for token validation
  • Access tokens, refresh tokens, and ID tokens stored securely

On-Site Messaging (OSM) (kustom/module-osm)

On-Site Messaging displays promotional payment messaging widgets throughout your store (product pages, cart, category pages, checkout) to inform customers about available payment options like "Pay in 3 installments" or "Buy now, pay later". The widgets are customizable and designed to increase conversion by highlighting flexible payment options. This module is being phased out.

Placement Options:

  • Product pages
  • Cart page
  • Category pages
  • Checkout

Configuration:

  • klarna/osm/enabled
  • klarna/osm/theme - Styling options
  • klarna/osm/position - Widget placement

Database Schema

Kustom modules use several database tables to store session data, order mappings, shipping information, and logs. The primary tables link Magento orders to Kustom orders, track payment sessions, and log all API interactions for debugging. Understanding the database schema is essential for troubleshooting, reporting, and building custom integrations. All tables use the klarna_ prefix for easy identification, and most are linked to Magento's core tables via foreign keys with cascade delete behavior.

Core Tables

klarna_core_order

The central table that links Magento orders to Kustom orders, storing the order ID mappings, acknowledgement status, and business purchase flags. This table is created by the Base Module and is used by all other modules to look up Kustom order information.

Key Columns:

  • klarna_order_id - Kustom's order identifier
  • session_id - Checkout session ID
  • reservation_id - Payment reservation ID (used for Order Management API calls)
  • order_id - Foreign key to Magento sales_order.entity_id (CASCADE DELETE)
  • is_acknowledged - Whether order has been acknowledged to Kustom (0 or 1)
  • is_b2b - Business purchase flag

Indexes: klarna_order_id, order_id, is_acknowledged

Schema Location: vendor/kustom/module-base/etc/db_schema.xml

klarna_logs

Stores all API requests and responses for debugging and auditing, including callback payloads. This table is your primary debugging resource when troubleshooting integration issues. Created by the Logger Module, it can grow large if debug mode is left enabled, so regular cleanup via cron is essential.

Key Columns:

  • status - HTTP response status (200, 201, 400, etc.)
  • action - Action type (create_order, update_order, push, capture, etc.)
  • klarna_id - Kustom order ID
  • increment_id - Magento order increment ID
  • url - Full API endpoint URL
  • method - HTTP method (GET, POST, etc.)
  • service - Service type (KCO, KP, OM)
  • request - Full request payload (JSON)
  • response - Full response payload (JSON)

Indexes: action, klarna_id, increment_id, status, composite index

Schema Location: vendor/kustom/module-logger/etc/db_schema.xml

Common Queries:

-- Find all failed API calls
SELECT * FROM klarna_logs
WHERE status NOT IN ('200', '201')
ORDER BY created_at DESC;

-- Find logs for specific order
SELECT * FROM klarna_logs
WHERE increment_id = '000000123'
ORDER BY created_at ASC;

-- Find KCO callback logs
SELECT * FROM klarna_logs
WHERE url LIKE '%/api/%'
  AND method = 'POST'
ORDER BY created_at DESC;

-- Find logs by action type
SELECT * FROM klarna_logs
WHERE action IN ('create_order', 'update_order', 'push')
ORDER BY created_at DESC
LIMIT 100;

klarna_payments_quote

Manages Klarna Payments (KP) session data per quote, storing the client token for frontend widgets and authorization tokens for payment processing. Created by the Kp Module, entries are automatically cleaned up weekly via cron for expired sessions.

Key Columns:

  • session_id - KP session identifier from Kustom API
  • client_token - Token for loading Klarna widget in frontend
  • authorization_token - Token received after customer authorizes payment
  • quote_id - Foreign key to Magento quote.entity_id (CASCADE DELETE)
  • is_active - Whether session is still valid
  • is_kec_session - Express checkout flag
  • payment_methods - Available payment categories (JSON)

Indexes: session_id, authorization_token, quote_id, order_id

Schema Location: vendor/kustom/module-payments/etc/db_schema.xml

klarna_kco_quote

Links Magento quotes to Kustom Checkout order IDs, tracking whether the checkout session is active and whether cart contents have changed. Created by the Kco Module, this table is essential for maintaining the connection between Magento's cart and Kustom's checkout iframe.

Key Columns:

  • klarna_checkout_id - Kustom Checkout order ID
  • quote_id - Foreign key to Magento quote.entity_id (CASCADE DELETE)
  • is_active - Whether checkout session is still active
  • is_changed - Flag indicating cart contents changed since last KCO update

Indexes: klarna_checkout_id, quote_id

Schema Location: vendor/kustom/module-kco/etc/db_schema.xml

klarna_shipping_method_gateway

Stores shipping method information for Kustom Shipping Service (KSS) integration, associating shipping options with Kustom checkout sessions. Created by the Kss Module, this table enables dynamic shipping method delivery to the KCO iframe based on customer address.

Key Columns:

  • klarna_session_id - Kustom Checkout session ID
  • shipping_method_id - Magento shipping method code (e.g., flatrate_flatrate)
  • is_pick_up_point - Whether this is a pickup point delivery
  • shipping_amount - Shipping cost
  • tax_amount - Tax amount for shipping
  • delivery_details - Additional delivery information (JSON)

Indexes: klarna_session_id

Schema Location: vendor/kustom/module-kss/etc/db_schema.xml

Database Relationships

sales_order (Magento Core)
    ↓ (one-to-one)
klarna_core_order
    ↓ (references)
klarna_logs (many-to-one via klarna_order_id)
quote (Magento Core)
    ↓ (one-to-one)
klarna_kco_quote
    ↓ (references)
klarna_shipping_method_gateway (via klarna_checkout_id)
quote (Magento Core)
    ↓ (one-to-one)
klarna_payments_quote
customer_entity (Magento Core)
    ↓ (one-to-one)
klarna_siwk_token

klarna_siwk_customer

Maintenance & Cleanup

Automated Cleanup:

  • klarna_logs - Cleaned daily by klarna_core_clean_logs cron
  • klarna_payments_quote - Cleaned weekly by klarna_payments_quote_clean_old_entries cron

Manual Cleanup:

-- Clean old logs (older than 30 days)
DELETE FROM klarna_logs
WHERE created_at < DATE_SUB(NOW(), INTERVAL 30 DAY);

-- Clean expired KP sessions (older than 7 days)
DELETE FROM klarna_payments_quote
WHERE created_at < DATE_SUB(NOW(), INTERVAL 7 DAY)
  AND is_active = 0;

Data Flow & Integration

Understanding how data flows through the Kustom integration is crucial for debugging and customization. This section maps out the complete journey from customer checkout through order creation, including all API calls, callbacks, and state transitions. For Kustom Payments (KP), the flow is relatively simple with session creation and authorization. For Kustom Checkout (KCO), the flow involves multiple callbacks as the customer enters their address, selects shipping, and completes payment. The Orderlines data flow shows how Magento cart totals are transformed into the format required by Kustom's API.

Kustom Payments (KP) Flow

Klarna Payments integrates into Magento's native checkout flow as a payment method option. The process involves session creation, widget rendering, authorization, and optional capture.

1. Customer Reaches Payment Step
   └─> Magento creates KP session
       ├─> API: POST /payments/v1/sessions
       ├─> Payload: Cart totals, customer data, orderlines
       ├─> Response: client_token + available payment_categories
       └─> Database: Insert into klarna_payments_quote

2. Frontend Widget Loading
   └─> Inject Klarna JavaScript SDK
   └─> Initialize widget with client_token
   └─> Widget displays payment method options (pay now/later/over time)
   └─> Customer selects payment method and authorizes
   └─> Widget returns authorization_token to Magento

3. Order Placement (Customer clicks "Place Order")
   └─> Magento receives authorization_token
   ├─> Stores token in klarna_payments_quote
   ├─> Creates Magento order
   │   ├─> Initial state: new or payment_review
   │   └─> Database: Insert into sales_order
   ├─> Links order to Kustom
   │   └─> Database: Insert into klarna_core_order
   ├─> Acknowledges order to Kustom (automatic)
   │   ├─> API: POST /ordermanagement/v1/orders/{id}/acknowledge
   │   ├─> Database: Set is_acknowledged = 1
   │   └─> Magento state: payment_review → configured status (typically processing)
   └─> Redirect to success page

4. Post-Order Operations (Merchant Actions)
   └─> Create Invoice (manual action)
       ├─> Triggers: CaptureCommand
       ├─> API: POST /ordermanagement/v1/orders/{id}/captures
       ├─> Kustom state: AUTHORIZED → CAPTURED
       └─> Payment is now settled

   └─> Create Credit Memo (if needed)
       ├─> Triggers: RefundCommand
       ├─> API: POST /ordermanagement/v1/orders/{id}/refunds
       └─> Kustom state: CAPTURED → REFUNDED

Key Points:

  • Authorization happens during checkout, NOT at order placement
  • Order is created with payment authorized but NOT captured
  • Capture requires manual invoice creation (no auto-capture)
  • Session expires after a period of inactivity

Kustom Checkout (KCO) Flow

Kustom Checkout replaces Magento's entire checkout experience with an iframe. The flow involves multiple callbacks as Kustom requests updated information based on customer actions.

1. Checkout Page Load
   └─> Magento creates KCO session
       ├─> API: POST /checkout/v3/orders
       ├─> Payload: Initial cart totals, merchant URLs, orderlines
       ├─> Response: order_id (Kustom session ID) + html_snippet (iframe code)
       └─> Database: Insert into klarna_kco_quote
   └─> Render html_snippet in page
   └─> Kustom iframe loads and displays checkout form

2. Address Update Callback (⚠️ Publicly Accessible Required)
   └─> Customer enters/changes address in iframe
   └─> Kustom → POST /kco/api/addressupdate
       ├─> Payload: Customer address, Kustom order_id
       ├─> Magento loads quote by klarna_kco_quote.klarna_checkout_id
       ├─> Updates shipping address on quote
       ├─> Recalculates tax based on new address
       ├─> Regenerates orderlines with updated tax
       ├─> Response: Updated orderlines JSON
       └─> Database: Updates quote shipping address
   └─> Iframe updates totals display

3. Shipping Method Selection Callback (⚠️ Publicly Accessible Required)
   └─> Iframe requests available shipping methods
   └─> Kustom → POST /kco/api/shippingmethodupdate
       ├─> Payload: Customer address, Kustom order_id
       ├─> Magento calls KSS to get shipping options
       ├─> Returns: Available carriers and costs
       └─> Database: Insert into klarna_shipping_method_gateway
   └─> Customer selects shipping method in iframe
   └─> Kustom → POST /kco/api/shippingmethodupdate (again)
       ├─> Payload: Selected shipping_method_id
       ├─> Magento applies shipping to quote
       ├─> Regenerates orderlines with shipping cost
       ├─> Response: Updated orderlines JSON
       └─> Database: Updates quote with shipping method
   └─> Iframe updates totals with shipping

4. Validation Callback (⚠️ Publicly Accessible Required)
   └─> Customer clicks "Complete Purchase" button
   └─> Kustom → POST /kco/api/validate
       ├─> Payload: Final order details
       ├─> Magento performs validation:
       │   ├─ Inventory check
       │   ├─ Quote still valid
       │   ├─ Totals match
       │   └─ Custom business rules
       ├─> Response: Success (200) or Failure (400 with error message)
       └─> If validation fails, iframe shows error to customer

5. Authorization & Push Callback (⚠️ Publicly Accessible Required)
   └─> Kustom authorizes payment with payment provider
   └─> Kustom → POST /kco/api/push
       ├─> Payload: order_id, Kustom order details
       ├─> Magento creates order from quote:
       │   ├─ Database: Insert into sales_order
       │   ├─ Initial state: new or payment_review
       │   ├─ Database: Insert into klarna_core_order
       │   └─ Link: order_id → klarna_order_id
       ├─> Acknowledges order (automatic):
       │   ├─ API: POST /ordermanagement/v1/orders/{id}/acknowledge
       │   ├─ Database: Set is_acknowledged = 1
       │   └─ State: payment_review → configured status (typically processing)
       ├─> Updates merchant references:
       │   └─ API: POST /ordermanagement/v1/orders/{id}/merchant-references
       ├─> Response: Success (200)
       └─> Sends order confirmation email

6. Confirmation Page
   └─> Customer redirected to confirmation page
   └─> GET /kco/klarna/confirmation?sid={session_id}
       ├─> Magento loads order by klarna_kco_quote.klarna_checkout_id
       ├─> Displays order details and success message
       └─> Quote is converted, session expires

Key Points:

  • All callbacks must be publicly accessible (use ngrok for local dev)
  • Callbacks can be called multiple times (address changes, shipping changes)
  • Order is only created during push callback (step 5)
  • Magento must respond quickly (<2s) to keep checkout smooth
  • If push callback fails, customer sees error but payment IS authorized (must handle manually)

Orderlines Data Flow

Quote/Order Totals (Magento)

Orderlines Container (collects all line items)
    ├─> Product Items Handler
    ├─> Shipping Handler
    ├─> Discount Handler
    ├─> Tax Handler
    ├─> Gift Card Handler
    ├─> Custom Handlers (extensible)

Format to Kustom API Structure
    ├─> Convert to minor units (cents)
    ├─> Calculate tax rates
    ├─> Validate totals match

Send to Kustom API
    ├─> Create Order API
    ├─> Update Order API
    ├─> Callbacks (address/shipping updates)

State Synchronization

Magento → Kustom (Merchant Actions)

These operations are triggered by merchant actions in Magento Admin and synchronized to Kustom via Order Management API:

Magento ActionGateway CommandKustom API CallKustom State ChangeWhen to Use
Create InvoiceCaptureCommandPOST /capturesAUTHORIZED → CAPTUREDAfter fulfilling order, to settle payment
Create Credit MemoRefundCommandPOST /refundsCAPTURED → REFUNDEDTo refund customer after capture
Cancel OrderCancelCommandPOST /cancelAUTHORIZED → CANCELLEDTo cancel before fulfillment/capture

Important: These are manual merchant actions, not automatic. There are no cron jobs that auto-capture orders.

Kustom → Magento (Payment Events)

These events originate from Kustom and trigger actions in Magento:

Kustom EventCallback EndpointMagento ActionMagento StateNotes
Authorization CompletePOST /kco/api/pushCreate ordernew → payment_review → configured statusAutomatic during KCO checkout
Authorization Complete(none for KP)Create ordernew → payment_review → configured statusAutomatic during KP checkout
Manual Capture (Portal)(none)No actionNo change⚠️ Manual sync required - update order in Magento manually

Warning: If a merchant captures an order in the Kustom merchant portal (outside Magento), Magento is NOT notified. You must manually create the invoice in Magento to keep systems synchronized.


Extension Points

Kustom modules are designed to be extended without modifying core code. This section shows how to customize the integration using Magento's standard extension patterns: plugins (interceptors), events and observers, and dependency injection. Common use cases include adding custom data to payment sessions, inserting additional orderlines (fees, surcharges), integrating with ERP systems during order placement, and adding custom validation logic to callbacks. All examples follow Magento best practices and are safe for production use.

Session Builder Plugins

Use Magento's plugin system (interceptors) to modify session data before it's sent to Kustom's API. This is useful for adding merchant references, custom data fields, or ERP integration identifiers that you want Kustom to store with the order.

Use Cases:

  • Add ERP order references for reconciliation
  • Include internal tracking IDs
  • Pass custom metadata for analytics
  • Add customer segment information

Target Classes:

  • Klarna\Kco\Model\Api\Builder\Kasper - KCO session builder
  • Klarna\Kp\Model\Api\Builder\Kasper - KP session builder

Example: Add ERP Reference to KCO Session

<!-- app/code/Vendor/Module/etc/di.xml -->
<config>
    <type name="Klarna\Kco\Model\Api\Builder\Kasper">
        <plugin name="vendor_add_custom_field"
                type="Vendor\Module\Plugin\KcoSessionBuilder"/>
    </type>
</config>
<?php
namespace Vendor\Module\Plugin;

use Klarna\Kco\Model\Api\Builder\Kasper;
use Magento\Quote\Api\Data\CartInterface;

class KcoSessionBuilder
{
    /**
     * Add custom field to KCO session request
     */
    public function afterGenerateRequest(
        Kasper $subject,
        array $result,
        CartInterface $quote
    ): array {
        // Add ERP reference
        $result['merchant_reference2'] = $this->getErpReference($quote);

        // Add custom data
        $result['merchant_data'] = json_encode([
            'internal_id' => $quote->getId(),
            'customer_segment' => $this->getCustomerSegment($quote)
        ]);

        return $result;
    }

    private function getErpReference(CartInterface $quote): string
    {
        // Custom logic to generate ERP reference
        return 'ERP-' . $quote->getId();
    }

    private function getCustomerSegment(CartInterface $quote): string
    {
        // Example: Determine customer segment
        $customer = $quote->getCustomer();
        if ($customer && $customer->getId()) {
            return $customer->getGroupId() == 1 ? 'VIP' : 'Standard';
        }
        return 'Guest';
    }
}

Important Notes:

  • Use afterGenerateRequest to add data without breaking existing logic
  • Use aroundGenerateRequest if you need to modify existing fields
  • Always return the modified $result array
  • Test thoroughly - incorrect data can cause Kustom API errors

Orderlines Customization

Add custom line items to the orderlines sent to Kustom. This is essential when you have fees, surcharges, or discounts that aren't part of Magento's standard totals but need to be included in the payment amount.

Use Cases:

  • Payment processing fees
  • Handling fees or packaging charges
  • Custom discounts not in Magento's discount system
  • Environmental fees or taxes
  • Membership discounts

Important: Custom orderlines must be added to the total correctly, otherwise Kustom will reject the order due to amount mismatch.

Example: Add Payment Processing Fee

<!-- app/code/Vendor/Module/etc/di.xml -->
<config>
    <type name="Klarna\Orderlines\Model\Container\Parameter">
        <arguments>
            <argument name="entities" xsi:type="array">
                <item name="processing_fee"
                      xsi:type="object">Vendor\Module\Model\Orderlines\ProcessingFee</item>
            </argument>
        </arguments>
    </type>
</config>
<?php
namespace Vendor\Module\Model\Orderlines;

use Klarna\Orderlines\Model\Container\AbstractParameter;
use Magento\Quote\Model\Quote;

class ProcessingFee extends AbstractParameter
{
    /**
     * Collect processing fee orderline
     */
    public function collect(Quote $quote): void
    {
        $fee = $this->calculateProcessingFee($quote);

        if ($fee > 0) {
            $this->addItem([
                'type' => 'surcharge',
                'reference' => 'processing_fee',
                'name' => 'Processing Fee',
                'quantity' => 1,
                'unit_price' => $this->helper->toApiFloat($fee), // Convert to cents
                'tax_rate' => 0, // Tax rate in basis points (2500 = 25%)
                'total_amount' => $this->helper->toApiFloat($fee),
                'total_tax_amount' => 0,
            ]);
        }
    }

    private function calculateProcessingFee(Quote $quote): float
    {
        // Custom logic - e.g., percentage of subtotal
        $subtotal = $quote->getSubtotal();
        return $subtotal * 0.02; // 2% processing fee
    }
}

Critical Points:

  • Always convert to cents using toApiFloat() - Kustom API expects minor units
  • Tax rates in basis points: 2500 = 25%, 1000 = 10%, 0 = no tax
  • Total must match: total_amount = (unit_price × quantity) + total_tax_amount
  • Test with various cart totals to ensure rounding is correct
  • Use type: 'surcharge' for fees, type: 'discount' for reductions

Event Observers

Kustom modules dispatch events at key points in the checkout and order management flow. Use observers to react to these events for integrations, logging, or custom business logic.

Common Use Cases:

  • ERP integration (send order data when placed)
  • Custom analytics tracking
  • Fraud prevention checks
  • Inventory synchronization
  • Email notifications to internal teams

Available Events:

// Before creating KCO order
Event: klarna_kco_create_order_before
Payload: ['api' => $api, 'request' => &$createData]

// After creating KCO order
Event: klarna_kco_create_order_after
Payload: ['api' => $api, 'response' => $response, 'request' => $createData]

// Before updating KCO order
Event: klarna_kco_update_order_before
Payload: ['api' => $api, 'request' => &$updateData, 'order_id' => $orderId]

// After updating KCO order
Event: klarna_kco_update_order_after
Payload: ['api' => $api, 'response' => $response, 'request' => $updateData]

// Before placing Magento order (during push callback)
Event: klarna_kco_place_order_before
Payload: ['quote' => $quote, 'klarna_order' => $klarnaOrder]

// After placing Magento order (during push callback)
Event: klarna_kco_place_order_after
Payload: ['order' => $order, 'klarna_order' => $klarnaOrder]

Example: ERP Integration on Order Placement

<!-- app/code/Vendor/Module/etc/events.xml -->
<config>
    <event name="klarna_kco_place_order_after">
        <observer name="vendor_erp_integration"
                  instance="Vendor\Module\Observer\ErpIntegration"/>
    </event>
</config>
<?php
namespace Vendor\Module\Observer;

use Magento\Framework\Event\Observer;
use Magento\Framework\Event\ObserverInterface;
use Magento\Sales\Api\Data\OrderInterface;

class ErpIntegration implements ObserverInterface
{
    private $erpClient;

    public function __construct(\Vendor\Module\Model\ErpClient $erpClient)
    {
        $this->erpClient = $erpClient;
    }

    /**
     * Send order to ERP system after KCO order placement
     */
    public function execute(Observer $observer): void
    {
        /** @var OrderInterface $order */
        $order = $observer->getData('order');
        $klarnaOrder = $observer->getData('klarna_order');

        try {
            // Send to ERP
            $erpOrderId = $this->erpClient->createOrder([
                'magento_order_id' => $order->getIncrementId(),
                'klarna_order_id' => $klarnaOrder->getKlarnaOrderId(),
                'customer_data' => $this->extractCustomerData($order),
                'items' => $this->extractItems($order),
            ]);

            // Store ERP reference on order
            $order->setData('erp_order_id', $erpOrderId);
            $order->addCommentToStatusHistory('Sent to ERP: ' . $erpOrderId);
            $order->save();

        } catch (\Exception $e) {
            // CRITICAL: Always catch exceptions in observers
            // Don't let external system failures break customer checkout
            $this->logger->error('ERP integration failed: ' . $e->getMessage());
        }
    }

    private function extractCustomerData(OrderInterface $order): array
    {
        return [
            'email' => $order->getCustomerEmail(),
            'name' => $order->getCustomerName(),
            'phone' => $order->getBillingAddress()->getTelephone(),
        ];
    }

    private function extractItems(OrderInterface $order): array
    {
        $items = [];
        foreach ($order->getAllVisibleItems() as $item) {
            $items[] = [
                'sku' => $item->getSku(),
                'name' => $item->getName(),
                'qty' => $item->getQtyOrdered(),
                'price' => $item->getPrice(),
            ];
        }
        return $items;
    }
}

Best Practices for Observers:

  • Always wrap in try/catch - External system failures shouldn't break checkout
  • Log failures - But don't throw exceptions in place_order_after events
  • Keep execution fast - Long-running observers slow down checkout
  • Consider async processing - Use message queues for time-consuming tasks
  • Test edge cases - What if ERP is down? Network timeout? Invalid response?

Payment Gateway Command Extension

Extend Magento's payment gateway pattern to add custom commands to the Kustom payment flow. This is an advanced extension point for custom payment operations.

Use Cases:

  • Custom pre-authorization checks
  • Fraud detection integration
  • Split payment handling
  • Custom settlement logic

Available Command Pools:

  • KlarnaKpCommandPool - For Klarna Payments (KP)
  • KcoPaymentCommandPool - For Kustom Checkout (KCO)

Example: Add Custom Pre-Authorization Validation

<!-- app/code/Vendor/Module/etc/di.xml -->
<config>
    <virtualType name="KlarnaKpCommandPool" type="Magento\Payment\Gateway\Command\CommandPool">
        <arguments>
            <argument name="commands" xsi:type="array">
                <item name="custom_validate"
                      xsi:type="string">Vendor\Module\Gateway\Command\ValidateCommand</item>
            </argument>
        </arguments>
    </virtualType>
</config>
<?php
namespace Vendor\Module\Gateway\Command;

use Magento\Payment\Gateway\CommandInterface;

class ValidateCommand implements CommandInterface
{
    public function execute(array $commandSubject)
    {
        $payment = $commandSubject['payment']->getPayment();
        $order = $payment->getOrder();

        // Custom validation logic
        if (!$this->fraudCheck($order)) {
            throw new \Magento\Framework\Exception\LocalizedException(
                __('Order failed fraud validation')
            );
        }

        return null;
    }

    private function fraudCheck($order): bool
    {
        // Your fraud detection logic
        return true;
    }
}

Note: This is an advanced extension point. Most customizations can be done with simpler observers.

Callback Customization

Hook into KCO callbacks to add custom validation, modify behavior, or integrate with external systems during the checkout flow.

Use Cases:

  • Custom inventory validation during checkout
  • Credit limit checks before authorization
  • Fraud scoring during validation callback
  • Custom shipping restrictions
  • Business hours validation

Available Callback Events:

  • klarna_kco_validate - Before final validation (can reject checkout)
  • klarna_kco_push - During order creation (can modify order)
  • klarna_kco_address_update - When address changes
  • klarna_kco_shipping_update - When shipping method changes

Example: Custom Inventory Validation

<!-- app/code/Vendor/Module/etc/events.xml -->
<config>
    <event name="klarna_kco_validate">
        <observer name="vendor_custom_validation"
                  instance="Vendor\Module\Observer\CustomValidation"/>
    </event>
</config>
<?php
namespace Vendor\Module\Observer;

use Magento\Framework\Event\Observer;
use Magento\Framework\Event\ObserverInterface;

class CustomValidation implements ObserverInterface
{
    private $inventoryCheck;
    private $customerCreditValidator;
    private $logger;

    public function __construct(
        \Vendor\Module\Model\InventoryCheck $inventoryCheck,
        \Vendor\Module\Model\CustomerCreditValidator $customerCreditValidator,
        \Psr\Log\LoggerInterface $logger
    ) {
        $this->inventoryCheck = $inventoryCheck;
        $this->customerCreditValidator = $customerCreditValidator;
        $this->logger = $logger;
    }

    /**
     * Perform custom validation before order authorization
     *
     * IMPORTANT: Exceptions thrown here will be shown to the customer
     * in the Kustom iframe, so messages must be customer-friendly.
     */
    public function execute(Observer $observer): void
    {
        $quote = $observer->getData('quote');

        // Log validation attempt for debugging
        $this->logger->info('Custom validation for quote: ' . $quote->getId());

        // Check real-time inventory from warehouse system
        if (!$this->inventoryCheck->hasStock($quote)) {
            throw new \Magento\Framework\Exception\LocalizedException(
                __('Sorry, some items are no longer in stock. Please update your cart.')
            );
        }

        // Validate customer credit limit for B2B
        if ($quote->getCustomerId() && !$this->customerCreditValidator->validate($quote)) {
            throw new \Magento\Framework\Exception\LocalizedException(
                __('This order exceeds your available credit limit. Please contact customer service.')
            );
        }

        // Example: Block orders during maintenance window
        if ($this->isMaintenanceWindow()) {
            throw new \Magento\Framework\Exception\LocalizedException(
                __('Orders are temporarily unavailable during system maintenance. Please try again later.')
            );
        }
    }

    private function isMaintenanceWindow(): bool
    {
        // Example: Block orders between 2 AM and 3 AM
        $hour = (int)date('G');
        return $hour >= 2 && $hour < 3;
    }
}

Critical Considerations:

  • Exception messages are shown to customers - Keep them friendly and actionable
  • Keep validation fast (<1 second) - Customers are waiting in the checkout iframe
  • Don't fail on external system errors - If your inventory API is down, allow the order or have a fallback
  • Log all validation attempts - Essential for debugging failed checkouts
  • Test thoroughly - Failed validation prevents order completion

API Reference

Kustom provides both REST and GraphQL APIs for different integration scenarios. The REST API is primarily used internally by the modules for server-to-server communication, with the Shipping Service (KSS) endpoint being the main developer-facing API for customizing shipping options during checkout. The GraphQL API is essential for headless, PWA, and mobile implementations, enabling complete checkout flows without relying on Magento's default frontend. This section provides complete authentication examples, detailed field descriptions, error handling patterns, and full workflow demonstrations for common integration scenarios.

REST API

The REST API uses Magento's standard authentication mechanism and follows REST conventions. All endpoints require proper authentication tokens and return standard HTTP status codes.

Authentication

REST API calls require a valid Magento integration or admin token. Create an integration in System → Extensions → Integrations to obtain an access token.

Example Token Request:

# Get admin token (for testing only - use integrations in production)
curl -X POST "https://example.com/rest/V1/integration/admin/token" \
  -H "Content-Type: application/json" \
  -d '{"username": "admin", "password": "password123"}'

# Response
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."

Using Token in Requests:

curl -X GET "https://example.com/rest/V1/endpoint" \
  -H "Authorization: Bearer YOUR_TOKEN_HERE" \
  -H "Content-Type: application/json"

Important Notes:

  • Admin tokens expire based on Magento configuration (default: 4 hours)
  • Integration tokens do not expire but can be revoked
  • Always use HTTPS in production
  • Store tokens securely, never expose in frontend code

Get Shipping Options (KSS)

This endpoint retrieves available shipping methods for a Kustom Checkout session, typically used when implementing the Kustom Shipping Service integration or building custom shipping selection interfaces.

Endpoint: GET /rest/V1/getDeliveryDataByKlarnaSessionId/:sessionId

Authentication: Required (Magento integration token)

When to Use:

  • Building custom checkout flows that need to display shipping options outside the standard KCO iframe
  • Implementing third-party shipping calculators that integrate with Kustom
  • Creating mobile apps that need to fetch shipping data programmatically

Parameters:

  • sessionId (path, required) - The Klarna session ID from the checkout session

Success Response (200 OK):

{
  "shipping_options": [
    {
      "id": "flatrate_flatrate",
      "name": "Flat Rate",
      "description": "Fixed shipping cost",
      "price": 500,
      "tax_amount": 125,
      "tax_rate": 2500,
      "preselected": true
    },
    {
      "id": "tablerate_bestway",
      "name": "Best Way",
      "description": "Table Rate shipping method",
      "price": 350,
      "tax_amount": 87,
      "tax_rate": 2500,
      "preselected": false
    }
  ]
}

Response Fields:

  • id - Unique identifier combining carrier and method codes (e.g., flatrate_flatrate)
  • name - Display name shown to customers
  • description - Additional details about the shipping method
  • price - Shipping cost in minor units (500 = 5.00 in currency)
  • tax_amount - Tax on shipping in minor units
  • tax_rate - Tax rate in basis points (2500 = 25%)
  • preselected - Whether this option is selected by default

Error Responses:

// 401 Unauthorized - Invalid or expired token
{
  "message": "The consumer isn't authorized to access %resources.",
  "parameters": {
    "resources": "Magento_Backend::all"
  }
}

// 404 Not Found - Invalid session ID
{
  "message": "No such entity with sessionId = invalid_id"
}

// 500 Internal Server Error - Quote or session issue
{
  "message": "Unable to retrieve shipping options for session"
}

Complete Example:

# 1. Obtain integration token (one-time setup in Admin)
# System → Extensions → Integrations → Create new integration
# Save and activate, copy the access token

# 2. Make API request
curl -X GET \
  "https://example.com/rest/V1/getDeliveryDataByKlarnaSessionId/abc123def456" \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
  -H "Content-Type: application/json"

# 3. Handle response
# Success: Parse shipping_options array and display to user
# Error: Check message field and handle appropriately

Common Issues:

  • 401 error: Token expired or lacks permissions - regenerate integration token
  • 404 error: Session ID is invalid or expired - create new checkout session
  • Empty shipping_options array: No shipping methods configured for customer's address or all methods filtered out

GraphQL API

The GraphQL API enables headless implementations by providing flexible queries and mutations for the complete checkout flow. Unlike the REST API, GraphQL allows you to request exactly the data you need in a single request, making it ideal for mobile apps, PWA storefronts, and custom frontend frameworks.

Authentication for GraphQL

GraphQL endpoints support both guest cart tokens and customer tokens depending on the checkout scenario.

Guest Checkout Flow:

# 1. Create empty cart (no authentication needed)
mutation {
  createEmptyCart
}

# Response
{
  "data": {
    "createEmptyCart": "xyz123abc456"
  }
}

# 2. Use cart_id in all subsequent mutations
# No additional authentication required for guest checkout

Customer Checkout Flow:

# 1. Generate customer token via REST
curl -X POST "https://example.com/rest/V1/integration/customer/token" \
  -H "Content-Type: application/json" \
  -d '{"username": "customer@example.com", "password": "password"}'

# Response
"customer_token_here"

# 2. Include token in GraphQL headers
curl -X POST "https://example.com/graphql" \
  -H "Authorization: Bearer customer_token_here" \
  -H "Content-Type: application/json" \
  -d '{"query": "mutation { createCustomerCart }"}'

Important Notes:

  • Guest cart tokens are embedded in the cart_id itself
  • Customer tokens are required for accessing saved addresses and order history
  • Cart IDs expire based on Magento quote lifetime configuration (default: 30 days for guests, persistent for customers)
  • Always validate token expiration and handle re-authentication gracefully

Create Klarna Payments Session

This mutation initializes a Klarna Payments session and returns the client token needed to render the Klarna payment widget on your frontend. Call this after setting shipping address and method, as Klarna needs the complete order total to create a valid session.

When to Use:

  • Building PWA or headless storefronts with Klarna Payments
  • Creating mobile checkout experiences
  • Implementing custom payment flows that need Klarna as an option

Prerequisites:

  • Cart must have items
  • Shipping address must be set
  • Shipping method must be selected
  • Cart totals must be calculated

Mutation:

mutation CreateKlarnaSession($cartId: String!) {
  createKlarnaPaymentsSession(input: {
    cart_id: $cartId
  }) {
    client_token
    payment_method_categories {
      identifier
      name
      asset_urls {
        descriptive
        standard
      }
    }
  }
}

Variables:

{
  "cartId": "xyz123abc456"
}

Success Response:

{
  "data": {
    "createKlarnaPaymentsSession": {
      "client_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzZXNzaW9uX2lkIjoiYWJjMTIzIiwiaWF0IjoxNjMwMDAwMDAwfQ...",
      "payment_method_categories": [
        {
          "identifier": "pay_later",
          "name": "Pay later in 30 days",
          "asset_urls": {
            "descriptive": "https://x.klarnacdn.net/payment-method/assets/badges/generic/klarna.svg",
            "standard": "https://x.klarnacdn.net/payment-method/assets/badges/generic/klarna.png"
          }
        },
        {
          "identifier": "pay_over_time",
          "name": "Pay over time",
          "asset_urls": {
            "descriptive": "https://x.klarnacdn.net/payment-method/assets/badges/generic/klarna.svg",
            "standard": "https://x.klarnacdn.net/payment-method/assets/badges/generic/klarna.png"
          }
        },
        {
          "identifier": "pay_now",
          "name": "Pay now",
          "asset_urls": {
            "descriptive": "https://x.klarnacdn.net/payment-method/assets/badges/generic/klarna.svg",
            "standard": "https://x.klarnacdn.net/payment-method/assets/badges/generic/klarna.png"
          }
        }
      ]
    }
  }
}

Response Fields:

  • client_token - JWT token required to initialize Klarna's payment widget (expires in ~1 hour)
  • payment_method_categories - Available payment options for the customer's region and order total
    • identifier - Unique ID for the payment category (pay_later, pay_over_time, pay_now)
    • name - Customer-facing display name
    • asset_urls - Logo images for rendering payment options
      • descriptive - SVG version (recommended for web)
      • standard - PNG version (fallback)

Error Responses:

// Missing shipping address
{
  "errors": [
    {
      "message": "The shipping address is missing. Set the address and try again.",
      "extensions": {
        "category": "graphql-input"
      }
    }
  ],
  "data": {
    "createKlarnaPaymentsSession": null
  }
}

// Invalid cart ID
{
  "errors": [
    {
      "message": "Could not find a cart with ID \"invalid_cart\"",
      "extensions": {
        "category": "graphql-no-such-entity"
      }
    }
  ],
  "data": {
    "createKlarnaPaymentsSession": null
  }
}

// Cart total too low
{
  "errors": [
    {
      "message": "The order total is below Klarna's minimum amount",
      "extensions": {
        "category": "graphql-input"
      }
    }
  ],
  "data": {
    "createKlarnaPaymentsSession": null
  }
}

Frontend Implementation Example:

// 1. Call GraphQL mutation to get client_token
const response = await fetch('/graphql', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    query: `mutation { createKlarnaPaymentsSession(input: { cart_id: "${cartId}" }) { client_token } }`
  })
});

const { data } = await response.json();
const clientToken = data.createKlarnaPaymentsSession.client_token;

// 2. Load Klarna SDK
const script = document.createElement('script');
script.src = 'https://x.klarna.com/kp/lib/v1/api.js';
script.onload = () => initKlarna(clientToken);
document.head.appendChild(script);

// 3. Initialize Klarna widget
function initKlarna(clientToken) {
  Klarna.Payments.init({ client_token: clientToken });

  Klarna.Payments.load({
    container: '#klarna-payments-container',
    payment_method_category: 'pay_later'
  }, (res) => {
    if (res.show_form) {
      // Widget loaded successfully
    }
  });
}

// 4. Authorize payment when customer clicks "Place Order"
Klarna.Payments.authorize({
  payment_method_category: 'pay_later'
}, (res) => {
  if (res.approved) {
    // Use res.authorization_token in setPaymentMethodOnCart mutation
    setPaymentMethod(res.authorization_token);
  }
});

Common Issues:

  • Token expires before customer completes payment: Refresh session by calling mutation again
  • No payment categories returned: Check customer's country and cart total against Klarna's requirements
  • Widget fails to load: Verify CSP headers allow Klarna domains (x.klarna.com, x.klarnacdn.net)

Set Klarna Payment Method

This mutation sets Klarna Payments as the selected payment method for the cart and attaches the authorization token obtained from the Klarna widget. This must be called after the customer authorizes payment through the widget and before placing the order.

When to Use:

  • After customer successfully authorizes payment in Klarna widget
  • Before calling placeOrder mutation
  • As part of the final checkout step in headless flows

Prerequisites:

  • Klarna session created (createKlarnaPaymentsSession called)
  • Customer authorized payment through Klarna widget
  • Valid authorization_token received from widget callback

Mutation:

mutation SetKlarnaPayment($cartId: String!, $authToken: String!) {
  setPaymentMethodOnCart(input: {
    cart_id: $cartId
    payment_method: {
      code: "klarna_kp"
      klarna: {
        authorization_token: $authToken
      }
    }
  }) {
    cart {
      selected_payment_method {
        code
        title
      }
    }
  }
}

Variables:

{
  "cartId": "xyz123abc456",
  "authToken": "b4c5d6e7-f8g9-h0i1-j2k3-l4m5n6o7p8q9"
}

Success Response:

{
  "data": {
    "setPaymentMethodOnCart": {
      "cart": {
        "selected_payment_method": {
          "code": "klarna_kp",
          "title": "Klarna Payments"
        }
      }
    }
  }
}

Error Responses:

// Invalid authorization token
{
  "errors": [
    {
      "message": "Klarna authorization token is invalid or expired",
      "extensions": {
        "category": "graphql-input"
      }
    }
  ],
  "data": {
    "setPaymentMethodOnCart": null
  }
}

// Payment method not available
{
  "errors": [
    {
      "message": "The payment method klarna_kp is not available",
      "extensions": {
        "category": "graphql-input"
      }
    }
  ],
  "data": {
    "setPaymentMethodOnCart": null
  }
}

// Cart modified after authorization
{
  "errors": [
    {
      "message": "Cart totals have changed since authorization. Please authorize payment again.",
      "extensions": {
        "category": "graphql-input"
      }
    }
  ],
  "data": {
    "setPaymentMethodOnCart": null
  }
}

Important Notes:

  • Authorization tokens are single-use and expire quickly (typically 5-10 minutes)
  • If cart totals change after authorization, you must create a new session and re-authorize
  • Always call this mutation immediately before placeOrder to minimize token expiration risk
  • For KCO (Kustom Checkout), use the embedded iframe instead - this mutation is only for KP

Common Issues:

  • Token expired: Customer took too long to complete checkout - reinitialize Klarna widget and re-authorize
  • Cart changed: Items added/removed or prices updated - refresh session and re-authorize
  • Payment method unavailable: Check configuration in Admin and verify customer's region is supported

Complete GraphQL Checkout Flow

This example demonstrates a complete headless checkout flow from empty cart to placed order, including all required steps, error handling, and best practices for production implementations.

Flow Overview:

  1. Create cart
  2. Add products
  3. Set shipping address
  4. Set shipping method
  5. Create Klarna session
  6. Load Klarna widget and authorize (frontend)
  7. Set payment method with auth token
  8. Place order

Step 1: Create Empty Cart

mutation CreateCart {
  createEmptyCart
}

Response:

{
  "data": {
    "createEmptyCart": "xyz123abc456"
  }
}

Step 2: Add Products to Cart

mutation AddProducts($cartId: String!) {
  addSimpleProductsToCart(input: {
    cart_id: $cartId
    cart_items: [
      {
        data: {
          sku: "PRODUCT-SKU-001"
          quantity: 2
        }
      },
      {
        data: {
          sku: "PRODUCT-SKU-002"
          quantity: 1
        }
      }
    ]
  }) {
    cart {
      items {
        id
        quantity
        product {
          name
          sku
        }
        prices {
          row_total {
            value
            currency
          }
        }
      }
      prices {
        grand_total {
          value
          currency
        }
      }
    }
  }
}

Variables:

{
  "cartId": "xyz123abc456"
}

Response:

{
  "data": {
    "addSimpleProductsToCart": {
      "cart": {
        "items": [
          {
            "id": "123",
            "quantity": 2,
            "product": {
              "name": "Example Product 1",
              "sku": "PRODUCT-SKU-001"
            },
            "prices": {
              "row_total": {
                "value": 39.98,
                "currency": "USD"
              }
            }
          },
          {
            "id": "124",
            "quantity": 1,
            "product": {
              "name": "Example Product 2",
              "sku": "PRODUCT-SKU-002"
            },
            "prices": {
              "row_total": {
                "value": 24.99,
                "currency": "USD"
              }
            }
          }
        ],
        "prices": {
          "grand_total": {
            "value": 64.97,
            "currency": "USD"
          }
        }
      }
    }
  }
}

Step 3: Set Shipping Address

mutation SetShippingAddress($cartId: String!) {
  setShippingAddressesOnCart(input: {
    cart_id: $cartId
    shipping_addresses: [{
      address: {
        firstname: "John"
        lastname: "Doe"
        company: "Example Corp"
        street: ["123 Main Street", "Apartment 4B"]
        city: "New York"
        region: "NY"
        postcode: "10001"
        country_code: "US"
        telephone: "+1-555-123-4567"
        save_in_address_book: false
      }
    }]
  }) {
    cart {
      shipping_addresses {
        firstname
        lastname
        street
        city
        region { code label }
        postcode
        country { code label }
        telephone
        available_shipping_methods {
          carrier_code
          method_code
          carrier_title
          method_title
          amount {
            value
            currency
          }
        }
      }
    }
  }
}

Response:

{
  "data": {
    "setShippingAddressesOnCart": {
      "cart": {
        "shipping_addresses": [
          {
            "firstname": "John",
            "lastname": "Doe",
            "street": ["123 Main Street", "Apartment 4B"],
            "city": "New York",
            "region": {
              "code": "NY",
              "label": "New York"
            },
            "postcode": "10001",
            "country": {
              "code": "US",
              "label": "United States"
            },
            "telephone": "+1-555-123-4567",
            "available_shipping_methods": [
              {
                "carrier_code": "flatrate",
                "method_code": "flatrate",
                "carrier_title": "Flat Rate",
                "method_title": "Fixed",
                "amount": {
                  "value": 5.00,
                  "currency": "USD"
                }
              },
              {
                "carrier_code": "tablerate",
                "method_code": "bestway",
                "carrier_title": "Best Way",
                "method_title": "Table Rate",
                "amount": {
                  "value": 3.50,
                  "currency": "USD"
                }
              }
            ]
          }
        ]
      }
    }
  }
}

Step 4: Set Shipping Method

mutation SetShippingMethod($cartId: String!) {
  setShippingMethodsOnCart(input: {
    cart_id: $cartId
    shipping_methods: [{
      carrier_code: "flatrate"
      method_code: "flatrate"
    }]
  }) {
    cart {
      shipping_addresses {
        selected_shipping_method {
          carrier_code
          method_code
          carrier_title
          method_title
          amount {
            value
            currency
          }
        }
      }
      prices {
        grand_total {
          value
          currency
        }
      }
    }
  }
}

Response:

{
  "data": {
    "setShippingMethodsOnCart": {
      "cart": {
        "shipping_addresses": [
          {
            "selected_shipping_method": {
              "carrier_code": "flatrate",
              "method_code": "flatrate",
              "carrier_title": "Flat Rate",
              "method_title": "Fixed",
              "amount": {
                "value": 5.00,
                "currency": "USD"
              }
            }
          }
        ],
        "prices": {
          "grand_total": {
            "value": 69.97,
            "currency": "USD"
          }
        }
      }
    }
  }
}

Step 5: Create Klarna Session

mutation CreateKlarnaSession($cartId: String!) {
  createKlarnaPaymentsSession(input: {
    cart_id: $cartId
  }) {
    client_token
    payment_method_categories {
      identifier
      name
    }
  }
}

Response:

{
  "data": {
    "createKlarnaPaymentsSession": {
      "client_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
      "payment_method_categories": [
        {
          "identifier": "pay_later",
          "name": "Pay later in 30 days"
        },
        {
          "identifier": "pay_over_time",
          "name": "Pay over time"
        }
      ]
    }
  }
}

Step 6: Load Klarna Widget (Frontend JavaScript)

// Initialize Klarna SDK with client_token from Step 5
Klarna.Payments.init({
  client_token: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...'
});

// Load payment widget
Klarna.Payments.load({
  container: '#klarna-payments-container',
  payment_method_category: 'pay_later'
}, (res) => {
  console.log('Widget loaded:', res.show_form);
});

// Authorize when customer clicks "Place Order"
document.getElementById('place-order-btn').addEventListener('click', () => {
  Klarna.Payments.authorize({
    payment_method_category: 'pay_later'
  }, (res) => {
    if (res.approved) {
      // Proceed to Step 7 with res.authorization_token
      setPaymentMethodAndPlaceOrder(res.authorization_token);
    } else {
      alert('Payment not approved. Please try again.');
    }
  });
});

Step 7: Set Payment Method

mutation SetPayment($cartId: String!, $authToken: String!) {
  setPaymentMethodOnCart(input: {
    cart_id: $cartId
    payment_method: {
      code: "klarna_kp"
      klarna: {
        authorization_token: $authToken
      }
    }
  }) {
    cart {
      selected_payment_method {
        code
        title
      }
    }
  }
}

Variables:

{
  "cartId": "xyz123abc456",
  "authToken": "b4c5d6e7-f8g9-h0i1-j2k3-l4m5n6o7p8q9"
}

Response:

{
  "data": {
    "setPaymentMethodOnCart": {
      "cart": {
        "selected_payment_method": {
          "code": "klarna_kp",
          "title": "Klarna Payments"
        }
      }
    }
  }
}

Step 8: Place Order

mutation PlaceOrder($cartId: String!) {
  placeOrder(input: {
    cart_id: $cartId
  }) {
    order {
      order_number
    }
  }
}

Response:

{
  "data": {
    "placeOrder": {
      "order": {
        "order_number": "000000123"
      }
    }
  }
}

Error Handling Best Practices:

async function executeGraphQLMutation(query, variables) {
  try {
    const response = await fetch('/graphql', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Store': 'default' // Include store code for multi-store setups
      },
      body: JSON.stringify({ query, variables })
    });

    const result = await response.json();

    // Check for GraphQL errors
    if (result.errors) {
      console.error('GraphQL errors:', result.errors);

      // Handle specific error types
      const error = result.errors[0];
      if (error.extensions?.category === 'graphql-no-such-entity') {
        // Cart expired or invalid
        alert('Your cart has expired. Please start over.');
        createNewCart();
      } else if (error.extensions?.category === 'graphql-input') {
        // Validation error
        alert(`Validation error: ${error.message}`);
      } else {
        // Generic error
        alert('An error occurred. Please try again.');
      }

      return null;
    }

    return result.data;
  } catch (error) {
    console.error('Network error:', error);
    alert('Network error. Please check your connection.');
    return null;
  }
}

Complete Integration Example:

// Complete checkout flow with error handling
async function completeCheckout() {
  try {
    // 1. Create cart
    const cartData = await executeGraphQLMutation(
      'mutation { createEmptyCart }',
      {}
    );
    const cartId = cartData.createEmptyCart;

    // 2. Add products
    await executeGraphQLMutation(addProductsMutation, {
      cartId,
      items: [{ sku: 'PROD-001', quantity: 1 }]
    });

    // 3. Set shipping address
    await executeGraphQLMutation(setShippingAddressMutation, {
      cartId,
      address: customerAddress
    });

    // 4. Set shipping method
    await executeGraphQLMutation(setShippingMethodMutation, {
      cartId,
      carrier: 'flatrate',
      method: 'flatrate'
    });

    // 5. Create Klarna session
    const klarnaData = await executeGraphQLMutation(
      createKlarnaSessionMutation,
      { cartId }
    );
    const clientToken = klarnaData.createKlarnaPaymentsSession.client_token;

    // 6. Initialize Klarna widget
    await initializeKlarnaWidget(clientToken);

    // 7. Authorize payment (triggered by user clicking button)
    const authToken = await authorizeKlarnaPayment();

    // 8. Set payment method
    await executeGraphQLMutation(setPaymentMethodMutation, {
      cartId,
      authToken
    });

    // 9. Place order
    const orderData = await executeGraphQLMutation(placeOrderMutation, {
      cartId
    });

    // Success!
    window.location.href = `/checkout/success?order=${orderData.placeOrder.order.order_number}`;

  } catch (error) {
    console.error('Checkout failed:', error);
    alert('Checkout failed. Please try again or contact support.');
  }
}

Performance Optimization Tips:

  • Batch independent mutations when possible (e.g., set email and billing address together)
  • Cache cart ID in localStorage to resume abandoned carts
  • Implement retry logic for network failures
  • Show loading states between each step
  • Validate input client-side before sending mutations to reduce round trips

Troubleshooting

Most Kustom integration issues fall into a few common categories: checkout not loading, region mismatches, callback failures, orderlines mismatches, and payment authorization problems. This section provides step-by-step solutions for each, along with diagnostic commands and SQL queries to identify root causes. The debug checklist walks through systematic troubleshooting, and the performance section addresses checkout speed and database growth issues. Always enable debug mode and check the klarna_logs table when investigating problems.

Common Issues

1. Checkout Fails to Load

Symptoms:

  • Kustom iframe doesn't appear
  • Blank checkout page
  • JavaScript console errors

Solutions:

Check module is enabled:

php bin/magento module:status | grep Klarna

Verify configuration:

  • Admin → Stores → Configuration → Sales → Payment Methods → Kustom Checkout
  • Ensure "Enabled" is set to "Yes"
  • Verify API credentials are entered
  • Check "Terms URL" is configured

Clear caches and redeploy:

php bin/magento cache:flush
php bin/magento cache:clean config
# If issue persists, redeploy static content
php bin/magento setup:static-content:deploy

Check browser console for CSP errors:

  • Add Kustom domains to Content Security Policy if using CSP headers
  • Common CSP issue: iframe not loading due to frame-src restrictions

Check logs:

SELECT * FROM klarna_logs
WHERE action = 'create_order'
  AND status != '200'
ORDER BY created_at DESC
LIMIT 10;

2. Region Not Supported

Symptoms:

  • Error: "Region not supported"
  • Checkout fails to initialize

Solutions:

Verify region alignment:

  • Store country: Admin → Stores → Configuration → General → Country
  • Base currency matches region (EUR for EU, USD for US, etc.)
  • Merchant region matches store region
  • API credentials match region (EU credentials for EU stores, US credentials for US stores)

Ensure alignment across all three:

  • Base currency + Store country + Merchant account region must match
  • Example: EUR + Germany + EU merchant account ✓
  • Example: USD + Germany + EU merchant account ✗ (currency mismatch)

Check API region configuration:

  • Admin → Stores → Configuration → Sales → Payment Methods → Kustom API
  • Ensure correct region is selected
  • Verify region-specific credentials

3. Callbacks Not Working (Webhook Issues)

Symptoms:

  • Checkout gets stuck at address entry
  • Shipping options don't load
  • Order doesn't complete after payment
  • Push callback doesn't trigger

Solutions:

Verify webhook/callback endpoint is reachable:

  • Callbacks must be publicly accessible (not behind firewall)
  • Must return HTTP 200 response
  • Must not be blocked by maintenance mode or IP restrictions

Verify callbacks are publicly accessible:

# Test from external server (not localhost)
curl -X POST https://yourdomain.com/kco/api/addressupdate \
  -H "Content-Type: application/json" \
  -d '{"test": true}'

# Should return HTTP 200, not 403/404/503

Check maintenance mode:

# Maintenance mode blocks callbacks
php bin/magento maintenance:status
# If enabled: php bin/magento maintenance:disable

For local development, use ngrok:

ngrok http 80
# Update Magento base URL to ngrok URL

Check callback logs:

SELECT * FROM klarna_logs
WHERE url LIKE '%/api/%'
ORDER BY created_at DESC
LIMIT 20;

4. Orderlines Mismatch

Symptoms:

  • Error: "Order total mismatch"
  • API returns 400 error
  • Logs show "orderlines validation failed"

Solutions:

Enable debug mode:

  • Admin → Stores → Configuration → Sales → Payment Methods → Kustom API
  • Set "Debug Mode" to "Yes"
  • Flush caches

Check orderlines in logs:

SELECT request, response FROM klarna_logs
WHERE action IN ('create_order', 'update_order')
  AND status = '400'
ORDER BY created_at DESC
LIMIT 1;

Common orderlines issues:

  • Rounding errors: Ensure amounts are in minor units (cents) with proper rounding
  • Tax calculation: Verify tax rates match (2500 = 25%)
  • Missing line items: Check all totals are represented (shipping, discounts, etc.)
  • Negative amounts: Discounts should have positive unit_price but reduce total

Validate orderlines total:

// In custom observer/plugin
$orderlines = $klarnaRequest['order_lines'];
$calculatedTotal = array_sum(array_column($orderlines, 'total_amount'));
$quoteTotal = $quote->getGrandTotal() * 100; // Convert to cents

if ($calculatedTotal !== $quoteTotal) {
    $this->logger->error('Orderlines mismatch', [
        'calculated' => $calculatedTotal,
        'quote' => $quoteTotal,
        'difference' => $calculatedTotal - $quoteTotal
    ]);
}

5. Payment Authorization Fails

Symptoms:

  • Customer completes checkout but order doesn't create
  • Error: "Authorization failed"
  • Push callback doesn't trigger

Solutions:

Check Kustom merchant portal:

  • Log in to merchant portal
  • Check if order appears as "AUTHORIZED"
  • If yes: push callback failed (see callback troubleshooting above)
  • If no: payment was declined by Kustom

Verify push callback endpoint:

curl -X POST https://yourdomain.com/kco/api/push \
  -H "Content-Type: application/json" \
  -d '{"order_id": "test"}'

Check for duplicate orders:

SELECT * FROM klarna_core_order
WHERE klarna_order_id = 'KLARNA_ORDER_ID';

-- If multiple rows exist, indicates duplicate push callbacks

6. Orders Stuck in Pending Payment

Symptoms:

  • Orders created but remain in "pending_payment" status
  • Invoice not automatically created

Solutions:

Check capture configuration:

  • Admin → Stores → Configuration → Sales → Payment Methods → Kustom
  • Verify "Payment Action" setting

Manual capture:

# Check if order is authorized in Kustom
SELECT * FROM klarna_core_order WHERE order_id = {magento_order_id};

# If authorized, manually create invoice in Admin
# This will trigger capture via Order Management module

Check Order Management logs:

SELECT * FROM klarna_logs
WHERE action = 'capture'
  AND klarna_id = 'KLARNA_ORDER_ID'
ORDER BY created_at DESC;

Debug Checklist

When troubleshooting any issue:

1. Enable debug mode

  • Admin → Sales → Payment Methods → Kustom API → Debug Mode: Yes

2. Check module status

php bin/magento module:status | grep Klarna

3. Check logs

-- Last 50 log entries
SELECT * FROM klarna_logs
ORDER BY created_at DESC
LIMIT 50;

-- Failed requests only
SELECT * FROM klarna_logs
WHERE status NOT IN ('200', '201')
ORDER BY created_at DESC;

4. Check file logs

tail -f var/log/klarna.log
tail -f var/log/system.log
tail -f var/log/exception.log

5. Verify configuration

SELECT path, value FROM core_config_data
WHERE path LIKE 'klarna/%' OR path LIKE 'payment/klarna_%'
ORDER BY path;

6. Test API connectivity

// In custom script or controller
$service = $this->serviceFactory->create();
$service->connect(\Klarna\Base\Api\ServiceInterface::SERVICE_KCO, $storeId);
try {
    $response = $service->makeRequest('/checkout/v3/orders', [], 'OPTIONS');
    echo "API connection successful";
} catch (\Exception $e) {
    echo "API connection failed: " . $e->getMessage();
}

7. Check quote/order data

-- Check if quote has KCO session
SELECT * FROM klarna_kco_quote WHERE quote_id = {quote_id};

-- Check if order is linked
SELECT * FROM klarna_core_order WHERE order_id = {order_id};

Performance Issues

Slow Checkout

Symptoms:

  • Callbacks take > 2 seconds
  • Checkout feels sluggish

Solutions:

Enable caching:

  • Ensure Redis/Varnish is configured
  • Check cache hit rate

Optimize orderlines collection:

  • Profile Klarna\Orderlines\Model\Container\Parameter::collect()
  • Remove unnecessary custom orderline handlers

Check database indexes:

SHOW INDEX FROM klarna_logs;
SHOW INDEX FROM klarna_core_order;
-- Ensure indexes exist on frequently queried columns

Database Table Growth

Symptoms:

  • klarna_logs table grows large (hundreds of MB or GB)
  • Slow queries on logs
  • Disk space warnings

Common Causes:

  • Debug mode left enabled in production
  • High transaction volume with long retention period
  • Cron job not running to clean old logs

Solutions:

1. Disable debug mode if enabled:

# Check current debug mode status
php bin/magento config:show klarna/api/debug

# Disable debug mode
php bin/magento config:set klarna/api/debug 0
php bin/magento cache:flush

⚠️ IMPORTANT: Debug mode should ONLY be enabled temporarily when troubleshooting specific issues. It generates extensive logs that can quickly fill disk space and impact performance. Always disable it after completing your investigation.

2. Verify cron is running:

php bin/magento cron:run
# The klarna_core_clean_logs job runs daily at midnight

3. Manually clean old logs:

-- Clean logs older than 30 days
DELETE FROM klarna_logs
WHERE created_at < DATE_SUB(NOW(), INTERVAL 30 DAY);

-- For immediate space recovery, clean logs older than 7 days
DELETE FROM klarna_logs
WHERE created_at < DATE_SUB(NOW(), INTERVAL 7 DAY);

4. Check table size:

SELECT
    table_name AS 'Table',
    ROUND(((data_length + index_length) / 1024 / 1024), 2) AS 'Size (MB)'
FROM information_schema.TABLES
WHERE table_schema = DATABASE()
    AND table_name = 'klarna_logs';

Support

When you need help with the Kustom integration, providing detailed information upfront speeds up resolution time significantly. Effective support requests include environment details (Magento version, PHP version, module versions), reproduction steps, relevant log entries from the klarna_logs table, and screenshots of errors. Never share sensitive information like full API credentials or customer payment details. This section guides you through creating comprehensive support requests and gathering the right diagnostic information.

Technical Support

Technical support is available via the Kustom support email address.

Creating Effective Support Requests

A well-structured support request accelerates resolution time. Include the following information in your support ticket to help the team diagnose and fix issues quickly.

Required Information

1. Issue Summary

  • Brief, clear description of the problem (1-2 sentences)
  • When the issue started occurring (date and time)
  • Frequency (always, intermittent, specific conditions)

Example:

"KCO checkout iframe fails to load for all customers since Dec 10 at 3pm EST. Error appears consistently on all product checkouts."

2. Environment Details

Gather technical environment information:

# Magento version
php bin/magento --version

# PHP version
php -v

# Module versions
composer show kustom/* --format=json | jq '.installed[] | {name, version}'

# Or simpler output
composer show kustom/*

# Server information (if relevant)
uname -a

Include:

  • Magento version (e.g., Adobe Commerce 2.4.6-p3)
  • PHP version (e.g., PHP 8.2.12)
  • All Kustom module versions
  • Operating system (e.g., Ubuntu 22.04, CentOS 8)
  • Web server (Nginx/Apache version)
  • Database (MySQL/MariaDB version)

3. Store Details

  • Store URL (e.g., https://example.com)
  • Merchant ID (first 4 characters only, e.g., "AB12...")
  • Affected store view(s) (if multi-store)
  • Region/Market (EU, US, UK, etc.)
  • Test or Production environment

4. Detailed Reproduction Steps

Provide step-by-step instructions:

  1. Exact actions taken (e.g., "Add product SKU ABC123 to cart")
  2. Expected result (e.g., "Checkout page should load with KCO iframe")
  3. Actual result (e.g., "Blank page, console error: 'kco-v1.js failed to load'")
  4. Frequency (happens every time, 50% of attempts, etc.)

5. Timestamps

  • When did the issue occur? (Include multiple occurrences if intermittent)
  • Timezone (critical for log correlation)
  • Duration (if applicable)

Example:

  • First occurrence: 2025-12-10 15:23:45 EST
  • Last occurrence: 2025-12-10 16:45:12 EST
  • Approximately 15 failed checkout attempts during this window

6. Order/Transaction IDs

Provide specific identifiers for investigation:

  • Magento order number (e.g., "000000123")
  • Klarna order ID (if available, find in klarna_core_order table)
  • Quote ID (for checkout issues)
  • Session ID (for KCO issues, find in klarna_kco_quote table)

Query to find IDs:

-- Get all relevant IDs for an order
SELECT
    so.increment_id AS order_number,
    so.entity_id AS order_id,
    kco.klarna_order_id,
    kco.reservation_id,
    so.quote_id
FROM sales_order so
LEFT JOIN klarna_core_order kco ON so.entity_id = kco.order_id
WHERE so.increment_id = 'ORDER_NUMBER';

7. Logs & Screenshots

Include relevant diagnostic data:

  • Database logs: Export from klarna_logs table (see below)
  • File logs: Excerpts from var/log/klarna.log
  • Browser console: Full console output for frontend issues (F12 → Console)
  • Network tab: Failed requests (F12 → Network)
  • Screenshots: Visual evidence of the issue
  • Stack traces: Full exception traces if available

How to Export Logs for Support

Method 1: Direct SQL Query (Recommended)

-- Export logs for specific order (readable format)
SELECT
    log_id,
    DATE_FORMAT(created_at, '%Y-%m-%d %H:%i:%s') AS timestamp,
    action,
    method,
    url,
    status,
    increment_id,
    klarna_id,
    SUBSTRING(request, 1, 500) AS request_preview,
    SUBSTRING(response, 1, 500) AS response_preview
FROM klarna_logs
WHERE increment_id = 'ORDER_NUMBER'
ORDER BY created_at ASC;

-- Export to CSV file (if you have file write permissions)
SELECT * FROM klarna_logs
WHERE increment_id = 'ORDER_NUMBER'
ORDER BY created_at ASC
INTO OUTFILE '/tmp/klarna_logs_ORDER_NUMBER.csv'
FIELDS TERMINATED BY ',' ENCLOSED BY '"'
LINES TERMINATED BY '\n';

Method 2: Using mysqldump

# Export specific order logs
mysqldump -u USER -p DATABASE klarna_logs \
  --where="increment_id='ORDER_NUMBER'" \
  --no-create-info \
  > klarna_logs_ORDER_NUMBER.sql

# Export logs from last 24 hours
mysqldump -u USER -p DATABASE klarna_logs \
  --where="created_at >= DATE_SUB(NOW(), INTERVAL 24 HOUR)" \
  --no-create-info \
  > klarna_logs_last_24h.sql

Method 3: Export from Magento Admin

If you have database access restrictions, use the Klarna Logger module's export feature (if available), or run this PHP script:

<?php
// Save as export_klarna_logs.php in Magento root

use Magento\Framework\App\Bootstrap;
require __DIR__ . '/app/bootstrap.php';

$bootstrap = Bootstrap::create(BP, $_SERVER);
$objectManager = $bootstrap->getObjectManager();
$resource = $objectManager->get(\Magento\Framework\App\ResourceConnection::class);
$connection = $resource->getConnection();

$incrementId = 'ORDER_NUMBER'; // Change this
$select = $connection->select()
    ->from('klarna_logs')
    ->where('increment_id = ?', $incrementId)
    ->order('created_at ASC');

$logs = $connection->fetchAll($select);

file_put_contents(
    "klarna_logs_{$incrementId}.json",
    json_encode($logs, JSON_PRETTY_PRINT)
);

echo "Exported " . count($logs) . " log entries to klarna_logs_{$incrementId}.json\n";

Method 4: Export Recent Errors Only

-- Get last 50 failed API calls with details
SELECT
    log_id,
    created_at,
    action,
    status,
    increment_id,
    klarna_id,
    method,
    url,
    request,
    response
FROM klarna_logs
WHERE status NOT IN ('200', '201')
ORDER BY created_at DESC
LIMIT 50;

Security & Privacy Guidelines

When sharing logs with support, protect sensitive information by following these guidelines:

Never Share:

  • Full API credentials (username/password/shared secret)
  • Customer personal data (unless absolutely necessary and with explicit consent)
  • Full credit card numbers (Kustom doesn't store these, but check request/response fields)
  • Database credentials or connection strings
  • Production API keys in clear text

Always Mask Sensitive Data:

-- Create a sanitized copy of logs before exporting
CREATE TEMPORARY TABLE klarna_logs_sanitized AS
SELECT
    log_id,
    created_at,
    action,
    method,
    url,
    status,
    increment_id,
    klarna_id,
    -- Mask email addresses
    REGEXP_REPLACE(request, '"email":"[^"]*"', '"email":"***@***.com"') AS request,
    -- Mask phone numbers
    REGEXP_REPLACE(
        REGEXP_REPLACE(request, '"phone":"[^"]*"', '"phone":"***"'),
        '"telephone":"[^"]*"', '"telephone":"***"'
    ) AS response
FROM klarna_logs
WHERE increment_id = 'ORDER_NUMBER';

-- Export sanitized data
SELECT * FROM klarna_logs_sanitized;

Best Practices:

  • Share only the minimum necessary log entries (specific order or time range)
  • Use secure file transfer methods (encrypted email, secure file sharing)
  • Delete exported log files after support issue is resolved
  • Inform customers if sharing their order data with support
  • Redact customer names if not essential for troubleshooting

Preparing Diagnostic Information Package

Create a comprehensive support package with all relevant information:

#!/bin/bash
# save as generate_support_package.sh

ORDER_NUMBER="000000123"
PACKAGE_DIR="kustom_support_${ORDER_NUMBER}_$(date +%Y%m%d_%H%M%S)"

mkdir -p "$PACKAGE_DIR"

# 1. System information
echo "=== Magento Version ===" > "$PACKAGE_DIR/system_info.txt"
php bin/magento --version >> "$PACKAGE_DIR/system_info.txt"

echo -e "\n=== PHP Version ===" >> "$PACKAGE_DIR/system_info.txt"
php -v >> "$PACKAGE_DIR/system_info.txt"

echo -e "\n=== Kustom Modules ===" >> "$PACKAGE_DIR/system_info.txt"
composer show kustom/* >> "$PACKAGE_DIR/system_info.txt"

# 2. Module status
php bin/magento module:status | grep Klarna > "$PACKAGE_DIR/module_status.txt"

# 3. Recent Klarna log file entries
tail -n 500 var/log/klarna.log > "$PACKAGE_DIR/klarna_file_log.txt" 2>/dev/null

# 4. System log errors (Klarna-related only)
grep -i klarna var/log/system.log | tail -n 100 > "$PACKAGE_DIR/system_log_klarna.txt" 2>/dev/null

# 5. Exception log errors (Klarna-related only)
grep -i klarna var/log/exception.log | tail -n 100 > "$PACKAGE_DIR/exception_log_klarna.txt" 2>/dev/null

# 6. Configuration (non-sensitive)
echo "Klarna configuration paths (values hidden for security):" > "$PACKAGE_DIR/config_paths.txt"
mysql -u USER -p -e "SELECT path FROM core_config_data WHERE path LIKE 'klarna/%' OR path LIKE 'payment/klarna_%' ORDER BY path;" DATABASE >> "$PACKAGE_DIR/config_paths.txt"

# 7. Create README
cat > "$PACKAGE_DIR/README.txt" << EOF
Kustom Support Package
Generated: $(date)
Order Number: $ORDER_NUMBER

Contents:
- system_info.txt: Magento, PHP, and module versions
- module_status.txt: Klarna module status
- klarna_file_log.txt: Recent Klarna log file entries
- system_log_klarna.txt: Klarna-related system log entries
- exception_log_klarna.txt: Klarna-related exception log entries
- config_paths.txt: Klarna configuration paths (values hidden)
- database_logs.sql: Export from klarna_logs table (run separately)

Next steps:
1. Export database logs using the SQL queries in the documentation
2. Add screenshots if applicable
3. Add browser console output if frontend issue
4. Compress this folder and send to support

IMPORTANT: Review all files and remove any sensitive information
before sharing with support.
EOF

echo "Support package created in: $PACKAGE_DIR"
echo "Review files and export database logs separately"

# Create archive
tar -czf "${PACKAGE_DIR}.tar.gz" "$PACKAGE_DIR"
echo "Archive created: ${PACKAGE_DIR}.tar.gz"

What Support Needs Most

To prioritize your diagnostic information, here's what support finds most valuable:

Critical (Always Include):

  1. Exact error message or symptom description
  2. Magento and PHP versions
  3. Kustom module versions
  4. Logs from klarna_logs table for the affected order/timeframe
  5. Reproduction steps

Very Helpful: 6. Browser console errors (for frontend issues) 7. Network tab showing failed requests 8. Screenshots showing the issue 9. Recent configuration changes

Nice to Have: 10. Full environment details 11. Server logs 12. Notes on what you've already tried

Common Support Scenarios

Scenario 1: Checkout Not Loading

# Provide:
- Browser console errors (screenshot)
- Network tab showing blocked requests
- Last 20 entries from klarna_logs for that session
- CSP headers from browser DevTools

Scenario 2: Order Not Creating After Payment

# Provide:
- Klarna order ID from merchant portal
- Quote ID from session
- All klarna_logs entries for that quote_id
- Push callback logs from web server access logs

Scenario 3: Orderlines Mismatch

# Provide:
- Full request/response from klarna_logs showing the error
- Cart contents and totals
- Tax configuration
- Any custom totals or fees applied

Scenario 4: Performance Issues

# Provide:
- Callback response times from klarna_logs
- Database table sizes (especially klarna_logs)
- Debug mode status
- Number of orders per day

Quick Reference

Quick lookup reference for common development tasks, frequently used commands, file locations, and database queries. Bookmark this section for fast access during development and troubleshooting.

Module Directory Structure

vendor/kustom/
├── module-base/                 # Core API client, service factory, configuration helpers
├── module-admin-settings/       # Admin panel configuration UI
├── module-logger/               # Database and file logging infrastructure
├── module-orderlines/           # Cart → Klarna orderlines conversion engine
├── module-kp/                   # Klarna Payments (native checkout integration)
├── module-payments-graph-ql/    # GraphQL API for headless KP implementation
├── module-kco/                  # Kustom Checkout (iframe-based checkout)
├── module-kss/                  # Kustom Shipping Service integration
├── module-backend/              # Order Management (capture, refund, cancel)
├── module-kec/                  # Express Checkout buttons
├── module-siwk/                 # Sign-In-With-Klarna authentication
├── module-osm/                  # On-Site Messaging promotional widgets
├── module-support/              # Support tools and diagnostics
├── module-interoperability/     # Compatibility with external payment methods
├── module-klarna-api/           # Low-level API client (internal)
└── module-plugins-api/          # Installation and version tracking

Critical File Paths

Configuration Files

vendor/kustom/module-*/etc/adminhtml/system.xml    # Admin UI configuration
vendor/kustom/module-*/etc/config.xml              # Default configuration values
vendor/kustom/module-*/etc/di.xml                  # Dependency injection setup

API Clients

vendor/kustom/module-base/Model/Api/Service.php           # Base API service factory
vendor/kustom/module-kco/Model/Api/Rest/Service.php       # KCO API client
vendor/kustom/module-kp/Model/Api/Rest/Service.php        # KP API client
vendor/kustom/module-backend/Model/Api/Rest/Service.php   # Order Management API

Controllers (Callback Endpoints)

vendor/kustom/module-kco/Controller/Api/Push.php              # Order creation callback
vendor/kustom/module-kco/Controller/Api/AddressUpdate.php     # Address validation
vendor/kustom/module-kco/Controller/Api/ShippingOptionUpdate.php  # Shipping updates
vendor/kustom/module-kco/Controller/Api/OrderValidation.php   # Pre-order validation
vendor/kustom/module-kco/Controller/Klarna/Success.php        # Confirmation page

Orderlines Engine

vendor/kustom/module-orderlines/Model/Container/Parameter.php  # Main container
vendor/kustom/module-orderlines/Model/Container/Products.php   # Product line items
vendor/kustom/module-orderlines/Model/Container/Shipping.php   # Shipping costs
vendor/kustom/module-orderlines/Model/Container/Discount.php   # Discounts
vendor/kustom/module-orderlines/Model/Container/Tax.php        # Tax handling

Database Setup

vendor/kustom/module-*/etc/db_schema.xml           # Table definitions
vendor/kustom/module-*/Setup/Patch/Data/*.php      # Data patches

Key Models

vendor/kustom/module-kco/Model/Checkout/Order.php          # KCO order placement
vendor/kustom/module-backend/Gateway/Command/Capture.php   # Invoice capture
vendor/kustom/module-base/Model/OrderRepository.php        # Order data access

Essential Commands

Installation & Upgrades

# Initial installation
composer require kustom/module-checkout
php bin/magento setup:upgrade
php bin/magento setup:di:compile
php bin/magento cache:flush

# Update to latest version
composer update kustom/*
php bin/magento setup:upgrade
php bin/magento cache:flush

# Production mode deployment
php bin/magento setup:static-content:deploy -f
php bin/magento setup:di:compile

Module Management

# Check module status
php bin/magento module:status | grep Klarna

# Enable/disable modules
php bin/magento module:enable Klarna_Kco Klarna_Base
php bin/magento module:disable Klarna_Kp

# List all Kustom modules with versions
composer show kustom/* | grep -E '^name|^versions'

Cache Management

# Flush all caches
php bin/magento cache:flush

# Clean specific cache types
php bin/magento cache:clean config
php bin/magento cache:clean layout
php bin/magento cache:clean full_page

# Disable cache for development
php bin/magento cache:disable

# Enable cache for production
php bin/magento cache:enable

Debugging

# Monitor Klarna logs in real-time
tail -f var/log/klarna.log

# Monitor system logs
tail -f var/log/system.log | grep -i klarna

# Monitor exception logs
tail -f var/log/exception.log

# Check last 100 Klarna log entries
tail -n 100 var/log/klarna.log

# Search for specific error
grep "orderlines" var/log/klarna.log

Configuration Management

# Export configuration to file
php bin/magento app:config:dump

# Show specific config value
php bin/magento config:show klarna/api/debug
php bin/magento config:show payment/klarna_kco/active

# Set config value
php bin/magento config:set klarna/api/debug 0
php bin/magento config:set payment/klarna_kco/active 1

# Clear config cache after changes
php bin/magento cache:clean config

Cron Management

# Run cron manually (includes log cleanup)
php bin/magento cron:run

# List cron jobs
php bin/magento cron:list

# Install crontab entries
php bin/magento cron:install

Troubleshooting Commands

# Reindex if orders not appearing
php bin/magento indexer:reindex

# Check for compilation errors
php bin/magento setup:di:compile

# Verify database schema
php bin/magento setup:db:status

# Check for module conflicts
php bin/magento info:dependencies:show-modules-circular

Admin Panel Configuration Paths

Quick navigation to common configuration areas:

Stores → Configuration → Sales → Payment Methods → Kustom API
    - API credentials (username, password)
    - Test/Live mode
    - Debug logging toggle
    - Region selection

Stores → Configuration → Sales → Payment Methods → Kustom Checkout
    - Enable/disable KCO
    - Available payment methods
    - Terms & conditions URL
    - Shipping options

Stores → Configuration → Sales → Checkout → Kustom Checkout → Design
    - Color scheme customization
    - Button styles
    - Widget placement

Stores → Configuration → Sales → Payment Methods → Klarna Payments
    - KP-specific settings (legacy)
    - Payment method categories

System → Cache Management
    - Clear Kustom-related caches

System → Tools → Index Management
    - Reindex after database changes

Database Query Reference

Order Lookups

-- Find order by Klarna order ID
SELECT
    so.increment_id AS order_number,
    so.entity_id AS order_id,
    so.status,
    so.state,
    kco.klarna_order_id,
    kco.reservation_id,
    kco.is_acknowledged
FROM sales_order so
JOIN klarna_core_order kco ON so.entity_id = kco.order_id
WHERE kco.klarna_order_id = 'KLARNA_ORDER_ID';

-- Find order by increment ID with all Klarna data
SELECT
    so.increment_id,
    so.customer_email,
    so.grand_total,
    so.status,
    kco.*
FROM sales_order so
LEFT JOIN klarna_core_order kco ON so.entity_id = kco.order_id
WHERE so.increment_id = 'ORDER_NUMBER';

-- Find orders in specific state
SELECT
    so.increment_id,
    so.created_at,
    so.status,
    kco.klarna_order_id
FROM sales_order so
JOIN klarna_core_order kco ON so.entity_id = kco.order_id
WHERE so.status = 'pending_payment'
ORDER BY so.created_at DESC
LIMIT 20;

Log Queries

-- Get all logs for specific order
SELECT
    log_id,
    created_at,
    action,
    method,
    status,
    url,
    SUBSTRING(request, 1, 200) AS request_preview,
    SUBSTRING(response, 1, 200) AS response_preview
FROM klarna_logs
WHERE increment_id = 'ORDER_NUMBER'
ORDER BY created_at ASC;

-- Find failed API calls today
SELECT
    created_at,
    action,
    status,
    increment_id,
    klarna_id,
    response
FROM klarna_logs
WHERE DATE(created_at) = CURDATE()
  AND status NOT IN ('200', '201')
ORDER BY created_at DESC;

-- Find logs by action type (last 24 hours)
SELECT * FROM klarna_logs
WHERE action = 'capture'
  AND created_at >= DATE_SUB(NOW(), INTERVAL 24 HOUR)
ORDER BY created_at DESC;

-- Count API calls by action (performance analysis)
SELECT
    action,
    COUNT(*) AS call_count,
    AVG(CASE WHEN status IN ('200', '201') THEN 1 ELSE 0 END) * 100 AS success_rate
FROM klarna_logs
WHERE created_at >= DATE_SUB(NOW(), INTERVAL 7 DAY)
GROUP BY action
ORDER BY call_count DESC;

Quote/Session Queries

-- Find active KCO sessions
SELECT
    q.entity_id AS quote_id,
    q.customer_email,
    q.created_at,
    q.updated_at,
    kq.klarna_checkout_id,
    kq.is_active
FROM quote q
JOIN klarna_kco_quote kq ON q.entity_id = kq.quote_id
WHERE kq.is_active = 1
ORDER BY q.updated_at DESC;

-- Find abandoned KCO checkouts (sessions created but no order)
SELECT
    q.entity_id AS quote_id,
    q.customer_email,
    q.grand_total,
    q.created_at,
    kq.klarna_checkout_id
FROM quote q
JOIN klarna_kco_quote kq ON q.entity_id = kq.quote_id
LEFT JOIN sales_order so ON q.entity_id = so.quote_id
WHERE kq.is_active = 1
  AND so.entity_id IS NULL
  AND q.created_at >= DATE_SUB(NOW(), INTERVAL 7 DAY)
ORDER BY q.created_at DESC;

Configuration Queries

-- Show all Klarna configuration (paths only for security)
SELECT path, scope, scope_id
FROM core_config_data
WHERE path LIKE 'klarna/%' OR path LIKE 'payment/klarna_%'
ORDER BY path;

-- Check if debug mode is enabled
SELECT path, value, scope
FROM core_config_data
WHERE path = 'klarna/api/debug';

-- Find stores with KCO enabled
SELECT
    ccd.scope_id,
    ccd.value,
    s.name AS store_name
FROM core_config_data ccd
JOIN store s ON ccd.scope_id = s.store_id
WHERE ccd.path = 'payment/klarna_kco/active'
  AND ccd.scope = 'stores';

Maintenance Queries

-- Check klarna_logs table size
SELECT
    table_name,
    ROUND(((data_length + index_length) / 1024 / 1024), 2) AS size_mb,
    table_rows
FROM information_schema.TABLES
WHERE table_schema = DATABASE()
  AND table_name LIKE 'klarna%'
ORDER BY (data_length + index_length) DESC;

-- Clean old logs (older than 30 days)
DELETE FROM klarna_logs
WHERE created_at < DATE_SUB(NOW(), INTERVAL 30 DAY);

-- Find oldest and newest log entries
SELECT
    MIN(created_at) AS oldest_log,
    MAX(created_at) AS newest_log,
    COUNT(*) AS total_logs,
    COUNT(DISTINCT increment_id) AS unique_orders
FROM klarna_logs;

Callback URLs Reference

Production URLs that Klarna will call:

https://yourdomain.com/kco/api/push                    # Order creation (POST)
https://yourdomain.com/kco/api/addressupdate           # Address validation (POST)
https://yourdomain.com/kco/api/shippingoptionupdate    # Shipping options (POST)
https://yourdomain.com/kco/api/ordervalidation         # Pre-order validation (POST)
https://yourdomain.com/kco/klarna/success              # Customer redirect (GET)

Important: All callback URLs must be publicly accessible (no firewall, no maintenance mode, no IP restrictions).

Common Error Codes

Quick reference for troubleshooting:

HTTP StatusMeaningCommon Cause
200SuccessRequest completed successfully
400Bad RequestOrderlines mismatch, validation error
401UnauthorizedInvalid API credentials
403ForbiddenAPI credentials lack permissions
404Not FoundOrder/resource doesn't exist
409ConflictOrder already captured/refunded
500Server ErrorMagento exception, check logs
503Service UnavailableKlarna API down (rare)

Quick Diagnostic Checklist

When something isn't working, check these in order:

  1. ✓ Modules enabled: php bin/magento module:status | grep Klarna
  2. ✓ Configuration saved: Check Admin → Kustom API settings
  3. ✓ Cache cleared: php bin/magento cache:flush
  4. ✓ API credentials correct: Test vs Live mode matches credentials
  5. ✓ Logs show error: tail -f var/log/klarna.log
  6. ✓ Database logs: SELECT * FROM klarna_logs ORDER BY created_at DESC LIMIT 10
  7. ✓ Callbacks accessible: Test URLs from external source
  8. ✓ Browser console: Check for JavaScript errors (F12)

Document Version: 1.0 Last Updated: December 2025 Maintained by: Kustom Development Team