Skip to content

Latest commit

ย 

History

3 Commits

Folders and files

NameName
Last commit message
Last commit date
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 

Repository files navigation

๐Ÿ›๏ธ NestJS Klarna Payments Integration

A premium, interactive integration of a NestJS backend with the Klarna Payments API (v1), serving a beautiful, fully functional checkout frontend using the Klarna JavaScript SDK.


๐Ÿš€ Overview (Code Mein Kya Hai?)

Yeh project ek payment integration flow ko demonstrate karta hai jisme Klarna Payments ke main steps implement kiye gaye hain:

  1. Local Order Creation: Backend local database (in-memory) mein order detail save karta hai.
  2. Klarna Session Generation: Backend Klarna's API to call karke payment session create karta hai aur client_token request karta hai.
  3. Klarna Widget Loading: Frontend client_token use karke official Klarna SDK loading container mein payment options display karta hai.
  4. Payment Authorization: Payment authorize hone par Klarna SDK frontend par authorization_token provide karta hai.
  5. Place Order: Backend is token ko use karke Klarna system mein order finalize/place karta hai.
  6. Capture Payment (Paisa Kaatna): Order placed hone ke baad backend amount ko capture karta hai, jisse customer ke account se funds deduct ho sakein.

๐Ÿ“‚ Project Structure

klarna/
โ”œโ”€โ”€ public/
โ”‚   โ””โ”€โ”€ index.html               # ๐ŸŽจ Beautiful HTML/JS Frontend (UI) utilizing Klarna SDK
โ”œโ”€โ”€ src/
โ”‚   โ”œโ”€โ”€ klarna/
โ”‚   โ”‚   โ”œโ”€โ”€ klarna.controller.ts # ๐Ÿ•น๏ธ Controller for Session, Order Placement & Capture APIs
โ”‚   โ”‚   โ”œโ”€โ”€ klarna.module.ts     # ๐Ÿ“ฆ Module integrating Orders service and Config
โ”‚   โ”‚   โ””โ”€โ”€ klarna.service.ts    # โš™๏ธ Core Klarna REST API Integrations (fetch requests to Klarna)
โ”‚   โ”œโ”€โ”€ orders/
โ”‚   โ”‚   โ”œโ”€โ”€ dto/
โ”‚   โ”‚   โ”‚   โ””โ”€โ”€ create-order.dto.ts # ๐Ÿ“ Data Transfer Object for creating an Order
โ”‚   โ”‚   โ”œโ”€โ”€ orders.controller.ts # ๐Ÿ•น๏ธ REST Controller for managing orders
โ”‚   โ”‚   โ”œโ”€โ”€ orders.module.ts     # ๐Ÿ“ฆ Module for Orders Service
โ”‚   โ”‚   โ””โ”€โ”€ orders.service.ts    # โš™๏ธ In-memory Order Database & updates
โ”‚   โ”œโ”€โ”€ app.module.ts            # ๐Ÿ  Main NestJS Root Module (serves static files and loads modules)
โ”‚   โ””โ”€โ”€ main.ts                  # ๐Ÿš€ Application entry point (running on port 3000)
โ”œโ”€โ”€ .env                         # ๐Ÿ”‘ Private environment file containing credentials (do not commit!)
โ”œโ”€โ”€ .env.example                 # ๐Ÿ“„ Example environment template
โ””โ”€โ”€ .gitignore                   # ๐Ÿšซ Specifies files to ignore by git (node_modules, .env, dist)

๐Ÿ› ๏ธ Step-by-Step Payment Flow (Paisa Katne Tak Ka Safar)

sequenceDiagram
    autonumber
    actor User as Customer
    participant FE as Frontend (index.html)
    participant BE as NestJS Backend
    participant Klarna as Klarna Payments API

    User->>FE: Fill details & click "Create Order"
    FE->>BE: POST /orders (Create order)
    BE-->>FE: Return local order (id)
    FE->>BE: POST /klarna/create-session/:orderId
    BE->>Klarna: POST /payments/v1/sessions (using Basic Auth credentials)
    Klarna-->>BE: Return session_id & client_token
    BE-->>FE: Return client_token
    FE->>FE: Initialize Klarna.Payments.init() & load()
    FE->>User: Display Klarna Payment Widget (Pay Over Time, etc.)
    User->>FE: Select Klarna option & click "Complete Payment"
    FE->>FE: Klarna.Payments.authorize() (via Klarna JS SDK)
    FE-->>User: Approved by Klarna
    FE->>BE: POST /klarna/place-order/:orderId (with authorization_token)
    BE->>Klarna: POST /payments/v1/authorizations/{authorization_token}/order
    Klarna-->>BE: Return klarna_order_id & fraud_status
    BE-->>FE: Return Order Placed details
    FE->>User: Show "Order Placed & Authorized" screen
    User->>FE: Click "Capture Payment" (Paisa Kato)
    FE->>BE: POST /klarna/capture-order/:orderId
    BE->>Klarna: POST /ordermanagement/v1/orders/{klarna_order_id}/captures
    Klarna-->>BE: Return capture_id
    BE-->>FE: Return Payment Captured Successfully!
    FE->>User: Show Payment Captured (Success Screen)
Loading

โš™๏ธ Setup & Installation

1. Clone & Install Dependencies

First, install the npm modules:

npm install

2. Configure Environment Variables

Copy .env.example to .env:

cp .env.example .env

Now open .env and configure your credentials. Example:

KLARNA_USERNAME=
KLARNA_PASSWORD=
KLARNA_BASE_URL=https://api.playground.klarna.com

3. Run the NestJS Server

Launch the server in development mode (it will serve the frontend at http://localhost:3000):

npm run start:dev

4. Test the Integration

Open your browser and navigate to: ๐Ÿ‘‰ http://localhost:3000

Fill in the dummy details, click through the checkout steps, choose Klarna payment, and experience the complete flow!


๐Ÿ“ก API Reference

1. Orders Module

  • Create Order: POST /orders
    • Body: CreateOrderDto
    {
      "name": "Rahul Sharma",
      "address": "123, MG Road, Delhi",
      "amount": 12000,
      "currency": "USD",
      "order_lines": [
        {
          "name": "Premium Leather Boots",
          "quantity": 1,
          "unit_price": 12000,
          "total_amount": 12000
        }
      ]
    }

2. Klarna Integration Module

  • Create Klarna Session: POST /klarna/create-session/:orderId
    • Initializes the Klarna session based on the specified order. Returns the client_token.
  • Place Order: POST /klarna/place-order/:orderId
    • Body: { "authorization_token": "..." }
    • Finalizes the order inside Klarna's system.
  • Capture Order: POST /klarna/capture-order/:orderId
    • Captures the funds from the client's authorized order. Marks payment as finished/completed.
  • Cancel Order: POST /klarna/cancel-order/:orderId
    • Cancels the placed/authorized order and releases the hold on customer's funds.
  • Refund Order: POST /klarna/refund-order/:orderId
    • Refunds the captured funds of the completed order back to the customer's account.

๐Ÿš‚ Adapting to Express.js (Express.js Setup)

Since the Klarna integration logic is built using standard Node.js fetch requests, you can easily port it from NestJS to standard Express.js. Here is how you can write the equivalent Express routes:

const express = require('express');
const { v4: uuidv4 } = require('uuid');
const app = express();

app.use(express.json());
app.use(express.static('public')); // Serves your index.html

const KLARNA_USERNAME = process.env.KLARNA_USERNAME;
const KLARNA_PASSWORD = process.env.KLARNA_PASSWORD;
const KLARNA_BASE_URL = process.env.KLARNA_BASE_URL || 'https://api.playground.klarna.com';
const authHeader = 'Basic ' + Buffer.from(`${KLARNA_USERNAME}:${KLARNA_PASSWORD}`).toString('base64');

// Mock database
let orders = [];

// 1. Create Local Order
app.post('/orders', (req, res) => {
  const order = { id: uuidv4(), ...req.body, status: 'pending' };
  orders.push(order);
  res.json(order);
});

// 2. Create Klarna Session
app.post('/klarna/create-session/:orderId', async (req, res) => {
  try {
    const order = orders.find(o => o.id === req.params.orderId);
    if (!order) return res.status(404).json({ error: 'Order not found' });

    const body = {
      purchase_country: 'US',
      purchase_currency: order.currency,
      locale: 'en-US',
      order_amount: order.amount,
      order_tax_amount: 0,
      order_lines: order.order_lines.map(item => ({ ...item, tax_rate: 0, total_tax_amount: 0 })),
      intent: 'buy'
    };

    const response = await fetch(`${KLARNA_BASE_URL}/payments/v1/sessions`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json', 'Authorization': authHeader },
      body: JSON.stringify(body)
    });

    const klarnaResponse = await response.json();
    res.json({
      order_id: order.id,
      session_id: klarnaResponse.session_id,
      client_token: klarnaResponse.client_token,
      payment_method_categories: klarnaResponse.payment_method_categories
    });
  } catch (err) {
    res.status(500).json({ error: err.message });
  }
});

// 3. Place Klarna Order
app.post('/klarna/place-order/:orderId', async (req, res) => {
  try {
    const order = orders.find(o => o.id === req.params.orderId);
    const { authorization_token } = req.body;

    const body = {
      purchase_country: 'US',
      purchase_currency: order.currency,
      locale: 'en-US',
      order_amount: order.amount,
      order_tax_amount: 0,
      order_lines: order.order_lines.map(item => ({ ...item, tax_rate: 0, total_tax_amount: 0 }))
    };

    const response = await fetch(`${KLARNA_BASE_URL}/payments/v1/authorizations/${authorization_token}/order`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json', 'Authorization': authHeader },
      body: JSON.stringify(body)
    });

    const result = await response.json();
    order.status = 'placed';
    order.klarnaOrderId = result.order_id;
    res.json(result);
  } catch (err) {
    res.status(500).json({ error: err.message });
  }
});

// 4. Capture Payment (Paisa Kato)
app.post('/klarna/capture-order/:orderId', async (req, res) => {
  try {
    const order = orders.find(o => o.id === req.params.orderId);

    const body = {
      captured_amount: order.amount,
      description: 'Capturing payment',
      order_lines: order.order_lines.map(item => ({ ...item, tax_rate: 0, total_tax_amount: 0 }))
    };

    const response = await fetch(`${KLARNA_BASE_URL}/ordermanagement/v1/orders/${order.klarnaOrderId}/captures`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': authHeader,
        'Klarna-Idempotency-Key': uuidv4()
      },
      body: JSON.stringify(body)
    });

    const captureId = response.headers.get('capture-id') || '';
    order.status = 'captured';
    res.json({ message: 'Order captured', capture_id: captureId, klarna_order_id: order.klarnaOrderId });
  } catch (err) {
    res.status(500).json({ error: err.message });
  }
});

// 5. Cancel Order (Before Capture)
app.post('/klarna/cancel-order/:orderId', async (req, res) => {
  try {
    const order = orders.find(o => o.id === req.params.orderId);
    if (!order) return res.status(404).json({ error: 'Order not found' });

    const response = await fetch(`${KLARNA_BASE_URL}/ordermanagement/v1/orders/${order.klarnaOrderId}/cancel`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': authHeader,
        'Klarna-Idempotency-Key': uuidv4()
      },
      body: JSON.stringify({})
    });

    if (!response.ok) {
      const error = await response.json();
      return res.status(response.status).json(error);
    }

    order.status = 'cancelled';
    res.json({ message: 'Order cancelled successfully', klarna_order_id: order.klarnaOrderId });
  } catch (err) {
    res.status(500).json({ error: err.message });
  }
});

// 6. Refund Order (After Capture)
app.post('/klarna/refund-order/:orderId', async (req, res) => {
  try {
    const order = orders.find(o => o.id === req.params.orderId);
    if (!order) return res.status(404).json({ error: 'Order not found' });

    const body = {
      refunded_amount: order.amount,
      description: 'Refunding payment',
      order_lines: order.order_lines.map(item => ({ ...item, tax_rate: 0, total_tax_amount: 0 }))
    };

    const response = await fetch(`${KLARNA_BASE_URL}/ordermanagement/v1/orders/${order.klarnaOrderId}/refunds`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': authHeader,
        'Klarna-Idempotency-Key': uuidv4()
      },
      body: JSON.stringify(body)
    });

    if (!response.ok) {
      const error = await response.json();
      return res.status(response.status).json(error);
    }

    const refundId = response.headers.get('refund-id') || '';
    order.status = 'refunded';
    res.json({ message: 'Order refunded successfully', refund_id: refundId, klarna_order_id: order.klarnaOrderId });
  } catch (err) {
    res.status(500).json({ error: err.message });
  }
});

app.listen(3000, () => console.log('Server running on port 3000'));

๐Ÿ“ License

This project is licensed under the MIT License.

About

A developer-ready NestJS & Express.js integration with the Klarna Payments API (v1). Includes a NestJS backend, an Express.js conversion guide, and a clean HTML/JS frontend playground using Klarna JS SDK. Features a complete flow: session initialization, widget load, order authorization, backend order placement, and capture.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages