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.
Yeh project ek payment integration flow ko demonstrate karta hai jisme Klarna Payments ke main steps implement kiye gaye hain:
- Local Order Creation: Backend local database (in-memory) mein order detail save karta hai.
- Klarna Session Generation: Backend Klarna's API to call karke payment session create karta hai aur
client_tokenrequest karta hai. - Klarna Widget Loading: Frontend
client_tokenuse karke official Klarna SDK loading container mein payment options display karta hai. - Payment Authorization: Payment authorize hone par Klarna SDK frontend par
authorization_tokenprovide karta hai. - Place Order: Backend is token ko use karke Klarna system mein order finalize/place karta hai.
- Capture Payment (Paisa Kaatna): Order placed hone ke baad backend amount ko capture karta hai, jisse customer ke account se funds deduct ho sakein.
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)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)
First, install the npm modules:
npm installCopy .env.example to .env:
cp .env.example .envNow open .env and configure your credentials. Example:
KLARNA_USERNAME=
KLARNA_PASSWORD=
KLARNA_BASE_URL=https://api.playground.klarna.comLaunch the server in development mode (it will serve the frontend at http://localhost:3000):
npm run start:devOpen 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!
- 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 } ] } - Body:
- Create Klarna Session:
POST /klarna/create-session/:orderId- Initializes the Klarna session based on the specified order. Returns the
client_token.
- Initializes the Klarna session based on the specified order. Returns the
- Place Order:
POST /klarna/place-order/:orderId- Body:
{ "authorization_token": "..." } - Finalizes the order inside Klarna's system.
- Body:
- 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.
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'));This project is licensed under the MIT License.