Skip to content

feat: klarna integration #56

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ To learn how to use an example, open its `README.md` file. You'll find the detai
| [Custom Item Price](./custom-item-price/README.md) | Add items with custom prices from third-party service to the cart. |
| [Digital Products](./digital-product/README.md) | Sell digital products in your Medusa application. |
| [Express Checkout Storefront](./express-checkout-storefront/README.md) | Use a storefront with an express checkout experience. |
| [Klarna Integration](./klarna-integration/README.md) | Integrate Klarna Payment to your Medusa application. |
| [Localization with Contentful Integration](./localization-contentful/README.md) | Integrate Contentful to implement localization in Medusa. |
| [Loyalty Points System](./loyalty-points/README.md) | Allow customers to earn and redeem loyalty points. |
| [Marketplace](./marketplace/README.md) | Allow vendors to register and sell products. |
Expand Down
15 changes: 15 additions & 0 deletions klarna-integration/.env.template
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
STORE_CORS=http://localhost:8000,https://docs.medusajs.com
ADMIN_CORS=http://localhost:5173,http://localhost:9000,https://docs.medusajs.com
AUTH_CORS=http://localhost:5173,http://localhost:9000,https://docs.medusajs.com
REDIS_URL=redis://localhost:6379
JWT_SECRET=supersecret
COOKIE_SECRET=supersecret
DATABASE_URL=postgres://postgres@localhost/$DB_NAME # change user and password if necessary
DB_NAME=medusa-algolia-integration
POSTGRES_URL=

KLARNA_USERNAME=
KLARNA_PASSWORD=
KLARNA_BASE_URL=
KLARNA_AUTHORIZATION_CALLBACK= # URL pattern: {public_url}/hooks/payment/klarna_klarna you can use ngrok to get a public URL in development
# KLARNA_AUTO_CAPTURE=true
26 changes: 26 additions & 0 deletions klarna-integration/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/dist
.env
.DS_Store
/uploads
/node_modules
yarn-error.log

.idea

coverage

!src/**

./tsconfig.tsbuildinfo
medusa-db.sql
build
.cache

.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/sdks
!.yarn/versions

.medusa
1 change: 1 addition & 0 deletions klarna-integration/.yarnrc.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
nodeLinker: node-modules
135 changes: 135 additions & 0 deletions klarna-integration/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
# Medusa v2 Example: Klarna Payment Integration

This directory holds the code for the [Klarna Payment Integration Tutorial](https://docs.medusajs.com/resources/integrations/guides/klarna).

You can either:

- [install and use it as a Medusa application](#installation);
- or [copy its source files into an existing Medusa application](#copy-into-existing-medusa-application).

## Prerequisites

- [Node.js v20+](https://nodejs.org/en/download)
- [Git CLI](https://git-scm.com/downaloads)
- [PostgreSQL](https://www.postgresql.org/download/)
- [Klarna](https://www.klarna.com/) merchant or [playground](https://portal.playground.klarna.com/) account.

## Installation

1. Clone the repository and change to the `klarna-integration` directory:

```bash
git clone https://github.com/medusajs/examples.git
cd examples/klarna-integration
```

2\. Rename the `.env.template` file to `.env`.

3\. If necessary, change the PostgreSQL username, password, and host in the `DATABASE_URL` environment variable.

4\. Set the Klarna environment variables:

```bash
KLARNA_USERNAME=
KLARNA_PASSWORD=
KLARNA_BASE_URL=
KLARNA_AUTHORIZATION_CALLBACK=
```

Where:

- `KLARNA_USERNAME` and `KLARNA_PASSWORD` are the Klarna API credentials
- `KLARNA_BASE_URL` is the [Klarna API URL](https://docs.klarna.com/api/api-urls/)
- `KLARNA_AUTHORIZATION_CALLBACK` is the webhook endpoint that Klarna will call on successful authentication. It's of the format `{public_url}/hooks/payment/klarna_klarna`.
- In development, you can use tools like [ngrok](https://ngrok.com/) to get a public URL.
- In production, replace `{public_url}` with the URL of the deployed Medusa server.
- You can optionally enable the `KLARNA_AUTO_CAPTURE` to automatically capture payments when the order is placed.

Learn more about retrieving these variables in the [tutorial](https://docs.medusajs.com/resources/integrations/guides/klarna#p-add-environment-variables)

5\. Install dependencies:

```bash
yarn # or npm install
```

6\. Setup and seed the database:

```bash
npx medusa db:setup
yarn seed # or npm run seed
```

7\. Start the Medusa application:

```bash
yarn dev # or npm run dev
```

Refer to the [Next Steps](#next-steps) on what you should do after integrating Klarna.

## Copy into Existing Medusa Application

If you have an existing Medusa application, copy the `src/modules/klarna` directory to your application.

Then, add the Klarna Module to `medusa-config.ts`:

```ts
module.exports = defineConfig({
// ...
modules: [
{
resolve: "@medusajs/medusa/payment",
options: {
providers: [
{
resolve: "./src/modules/klarna",
id: "klarna",
options: {
baseUrl: process.env.KLARNA_BASE_URL,
username: process.env.KLARNA_USERNAME,
password: process.env.KLARNA_PASSWORD,
auto_capture: process.env.KLARNA_AUTO_CAPTURE === "true",
merchant_urls: {
authorization: process.env.KLARNA_AUTHORIZATION_CALLBACK,
push: process.env.KLARNA_AUTHORIZATION_CALLBACK,
notification: process.env.KLARNA_AUTHORIZATION_CALLBACK,
},
}
}
]
}
}
]
})
```

Next, add the following environment variables:

```bash
KLARNA_USERNAME=
KLARNA_PASSWORD=
KLARNA_BASE_URL=
KLARNA_AUTHORIZATION_CALLBACK=
```

Where:

- `KLARNA_USERNAME` and `KLARNA_PASSWORD` are the Klarna API credentials
- `KLARNA_BASE_URL` is the [Klarna API URL](https://docs.klarna.com/api/api-urls/)
- `KLARNA_AUTHORIZATION_CALLBACK` is the webhook endpoint that Klarna will call on successful authentication. It's of the format `{public_url}/hooks/payment/klarna_klarna`.
- In development, you can use tools like [ngrok](https://ngrok.com/) to get a public URL.
- In production, replace `{public_url}` with the URL of the deployed Medusa server.
- You can optionally enable the `KLARNA_AUTO_CAPTURE` to automatically capture payments when the order is placed.

Learn more about retrieving these variables in the [tutorial](https://docs.medusajs.com/resources/integrations/guides/klarna#p-add-environment-variables)

## Next Steps

- Make sure to enable Klarna as a payment provider in a region using the [Medusa Admin dashboard](https://docs.medusajs.com/user-guide/settings/regions#edit-region-details).
- Customize the [Next.js Starter Storefront](https://docs.medusajs.com/resources/nextjs-starter) as explained in the [tutorial](https://docs.medusajs.com/resources/integrations/guides/klarna#step-4-customize-nextjs-storefront-for-klarna) to support Klarna payments

## More Resources

- [Medusa Documentatin](https://docs.medusajs.com)
- [Klarna Documentation](https://docs.klarna.com)
24 changes: 24 additions & 0 deletions klarna-integration/instrumentation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// Uncomment this file to enable instrumentation and observability using OpenTelemetry
// Refer to the docs for installation instructions: https://docs.medusajs.com/learn/debugging-and-testing/instrumentation

// import { registerOtel } from "@medusajs/medusa"
// // If using an exporter other than Zipkin, require it here.
// import { ZipkinExporter } from "@opentelemetry/exporter-zipkin"

// // If using an exporter other than Zipkin, initialize it here.
// const exporter = new ZipkinExporter({
// serviceName: 'my-medusa-project',
// })

// export function register() {
// registerOtel({
// serviceName: 'medusajs',
// // pass exporter
// exporter,
// instrument: {
// http: true,
// workflows: true,
// query: true
// },
// })
// }
29 changes: 29 additions & 0 deletions klarna-integration/integration-tests/http/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Integration Tests

The `medusa-test-utils` package provides utility functions to create integration tests for your API routes and workflows.

For example:

```ts
import { medusaIntegrationTestRunner } from "medusa-test-utils"

medusaIntegrationTestRunner({
testSuite: ({ api, getContainer }) => {
describe("Custom endpoints", () => {
describe("GET /store/custom", () => {
it("returns correct message", async () => {
const response = await api.get(
`/store/custom`
)

expect(response.status).toEqual(200)
expect(response.data).toHaveProperty("message")
expect(response.data.message).toEqual("Hello, World!")
})
})
})
}
})
```

Learn more in [this documentation](https://docs.medusajs.com/learn/debugging-and-testing/testing-tools/integration-tests).
15 changes: 15 additions & 0 deletions klarna-integration/integration-tests/http/health.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { medusaIntegrationTestRunner } from "@medusajs/test-utils"
jest.setTimeout(60 * 1000)

medusaIntegrationTestRunner({
inApp: true,
env: {},
testSuite: ({ api }) => {
describe("Ping", () => {
it("ping the server health endpoint", async () => {
const response = await api.get('/health')
expect(response.status).toEqual(200)
})
})
},
})
3 changes: 3 additions & 0 deletions klarna-integration/integration-tests/setup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
const { MetadataStorage } = require("@mikro-orm/core")

MetadataStorage.clear()
27 changes: 27 additions & 0 deletions klarna-integration/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
const { loadEnv } = require("@medusajs/utils");
loadEnv("test", process.cwd());

module.exports = {
transform: {
"^.+\\.[jt]s$": [
"@swc/jest",
{
jsc: {
parser: { syntax: "typescript", decorators: true },
},
},
],
},
testEnvironment: "node",
moduleFileExtensions: ["js", "ts", "json"],
modulePathIgnorePatterns: ["dist/", "<rootDir>/.medusa/"],
setupFiles: ["./integration-tests/setup.js"],
};

if (process.env.TEST_TYPE === "integration:http") {
module.exports.testMatch = ["**/integration-tests/http/*.spec.[jt]s"];
} else if (process.env.TEST_TYPE === "integration:modules") {
module.exports.testMatch = ["**/src/modules/*/__tests__/**/*.[jt]s"];
} else if (process.env.TEST_TYPE === "unit") {
module.exports.testMatch = ["**/src/**/__tests__/**/*.unit.spec.[jt]s"];
}
40 changes: 40 additions & 0 deletions klarna-integration/medusa-config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { loadEnv, defineConfig } from '@medusajs/framework/utils'

loadEnv(process.env.NODE_ENV || 'development', process.cwd())

module.exports = defineConfig({
projectConfig: {
databaseUrl: process.env.DATABASE_URL,
http: {
storeCors: process.env.STORE_CORS!,
adminCors: process.env.ADMIN_CORS!,
authCors: process.env.AUTH_CORS!,
jwtSecret: process.env.JWT_SECRET || "supersecret",
cookieSecret: process.env.COOKIE_SECRET || "supersecret",
}
},
modules: [
{
resolve: "@medusajs/medusa/payment",
options: {
providers: [
{
resolve: "./src/modules/klarna",
id: "klarna",
options: {
baseUrl: process.env.KLARNA_BASE_URL,
username: process.env.KLARNA_USERNAME,
password: process.env.KLARNA_PASSWORD,
auto_capture: process.env.KLARNA_AUTO_CAPTURE === "true",
merchant_urls: {
authorization: process.env.KLARNA_AUTHORIZATION_CALLBACK,
push: process.env.KLARNA_AUTHORIZATION_CALLBACK,
notification: process.env.KLARNA_AUTHORIZATION_CALLBACK,
},
}
}
]
}
}
]
})
59 changes: 59 additions & 0 deletions klarna-integration/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
{
"name": "klarna-integration",
"version": "0.0.1",
"description": "A starter for Medusa projects.",
"author": "Medusa (https://medusajs.com)",
"license": "MIT",
"keywords": [
"sqlite",
"postgres",
"typescript",
"ecommerce",
"headless",
"medusa"
],
"scripts": {
"build": "medusa build",
"seed": "medusa exec ./src/scripts/seed.ts",
"start": "medusa start",
"dev": "medusa develop",
"test:integration:http": "TEST_TYPE=integration:http NODE_OPTIONS=--experimental-vm-modules jest --silent=false --runInBand --forceExit",
"test:integration:modules": "TEST_TYPE=integration:modules NODE_OPTIONS=--experimental-vm-modules jest --silent --runInBand --forceExit",
"test:unit": "TEST_TYPE=unit NODE_OPTIONS=--experimental-vm-modules jest --silent --runInBand --forceExit"
},
"dependencies": {
"@medusajs/admin-sdk": "2.8.0",
"@medusajs/cli": "2.8.0",
"@medusajs/framework": "2.8.0",
"@medusajs/medusa": "2.8.0",
"@mikro-orm/core": "6.4.3",
"@mikro-orm/knex": "6.4.3",
"@mikro-orm/migrations": "6.4.3",
"@mikro-orm/postgresql": "6.4.3",
"awilix": "^8.0.1",
"jsonwebtoken": "^9.0.2",
"pg": "^8.13.0"
},
"devDependencies": {
"@medusajs/test-utils": "2.8.0",
"@mikro-orm/cli": "6.4.3",
"@swc/core": "1.5.7",
"@swc/jest": "^0.2.36",
"@types/jest": "^29.5.13",
"@types/jsonwebtoken": "^9.0.9",
"@types/node": "^20.0.0",
"@types/react": "^18.3.2",
"@types/react-dom": "^18.2.25",
"jest": "^29.7.0",
"prop-types": "^15.8.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"ts-node": "^10.9.2",
"typescript": "^5.6.2",
"vite": "^5.2.11",
"yalc": "^1.0.0-pre.53"
},
"engines": {
"node": ">=20"
}
}
Loading