npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

mcp-nexi-pos-api

v0.1.4

Published

MCP server for Nexi POS card payments

Readme

mcp-nexi-pos-api

MVP MCP server for Nexi POS payment operations.

It provides tools to create purchases and refunds, poll payment state, confirm transactions, recover unconfirmed transactions, check terminal status, and list terminal events.

Setup

From npm package

npm install -g mcp-nexi-pos-api
mcp-nexi-pos-api

From source

npm install
npm run build
npm start

Minimum working config

You need Nexi POS API credentials before you can use the server:

  • NEXI_POS_API_KEY_ID
  • NEXI_POS_API_KEY_SECRET
  • a Nexi POS terminal ID

The terminal ID is generated together with the API key ID and API key secret. Credential and terminal generation is outside this MCP server for now. Get these values from Nexi API documentation before starting the server.

The smallest useful .env file is:

NEXI_POS_API_KEY_ID=your-api-key-id
NEXI_POS_API_KEY_SECRET=your-api-key-secret
NEXI_POS_TERMINAL_ID=your-terminal-id

NEXI_POS_TERMINAL_ID is optional. It is used as an environment fallback when a tool call does not include terminal_id and no session terminal has been set with set_terminal_id. It has no default value.

A fuller local .env file can include:

NEXI_POS_API_KEY_ID=your-api-key-id
NEXI_POS_API_KEY_SECRET=your-api-key-secret
NEXI_POS_TERMINAL_ID=your-terminal-id
NEXI_POS_BASE_URL=https://api.sandbox.npay.eu/pos/v1
NEXI_POS_DEFAULT_CURRENCY=SEK
NEXI_POS_MAX_AMOUNT_MINOR=100021
NEXI_POS_USER_AGENT=mcp-nexi-pos-api/0.1.4
NEXI_POS_STORAGE_PATH=./data/nexi-pos.sqlite
NEXI_POS_REQUEST_TIMEOUT_SECONDS=30

Environment variables

| Variable | Description | Default | | --- | --- | --- | | NEXI_POS_API_KEY_ID | Nexi Basic Auth username. Generated outside this MCP server. | Required | | NEXI_POS_API_KEY_SECRET | Nexi Basic Auth password. Generated outside this MCP server. | Required | | NEXI_POS_TERMINAL_ID | Optional terminal ID fallback when no tool terminal_id or session terminal ID is available. Generated with the API credentials. | No default | | NEXI_POS_BASE_URL | Nexi POS API base URL. | https://api.sandbox.npay.eu/pos/v1 | | NEXI_POS_DEFAULT_CURRENCY | Currency used when a tool omits currency. | SEK | | NEXI_POS_MAX_AMOUNT_MINOR | Safety limit in minor units. Defaults high enough for Nexi test-case amounts. | 100021 | | NEXI_POS_USER_AGENT | User agent sent to Nexi. | mcp-nexi-pos-api/0.1.4 | | NEXI_POS_STORAGE_PATH | SQLite file for local recovery state. | ./data/nexi-pos.sqlite | | NEXI_POS_REQUEST_TIMEOUT_SECONDS | Hard timeout for each HTTP request to Nexi. | 30 |

Claude Desktop config

Minimum example for a global npm install:

{
  "mcpServers": {
    "nexi-pos": {
      "command": "mcp-nexi-pos-api",
      "env": {
        "NEXI_POS_API_KEY_ID": "your-api-key-id",
        "NEXI_POS_API_KEY_SECRET": "your-api-key-secret",
        "NEXI_POS_TERMINAL_ID": "your-terminal-id"
      }
    }
  }
}

Fuller example for a global npm install:

{
  "mcpServers": {
    "nexi-pos": {
      "command": "mcp-nexi-pos-api",
      "env": {
        "NEXI_POS_API_KEY_ID": "your-api-key-id",
        "NEXI_POS_API_KEY_SECRET": "your-api-key-secret",
        "NEXI_POS_TERMINAL_ID": "your-terminal-id",
        "NEXI_POS_BASE_URL": "https://api.sandbox.npay.eu/pos/v1",
        "NEXI_POS_DEFAULT_CURRENCY": "SEK",
        "NEXI_POS_MAX_AMOUNT_MINOR": "100021",
        "NEXI_POS_STORAGE_PATH": "/absolute/path/to/data/nexi-pos.sqlite"
      }
    }
  }
}

Example from source:

{
  "mcpServers": {
    "nexi-pos": {
      "command": "node",
      "args": ["/absolute/path/to/mcp-nexi-pos-api/dist/index.js"],
      "env": {
        "NEXI_POS_API_KEY_ID": "your-api-key-id",
        "NEXI_POS_API_KEY_SECRET": "your-api-key-secret",
        "NEXI_POS_TERMINAL_ID": "your-terminal-id"
      }
    }
  }
}

Amount format

All amounts are integers in ISO 4217 minor units. Do not send floating point values.

Examples:

  • SEK 1.00 = 100
  • EUR 1.00 = 100
  • JPY 1 = 1

The default max amount is 100021, meaning SEK/EUR 1000.21. This allows the documented Nexi simulator/test-case amounts. Set NEXI_POS_MAX_AMOUNT_MINOR to a lower value if your environment should block larger test amounts.

External IDs

external_id is your unique order or payment ID. Use a value that lets you find the payment later, for example order-1001-payment-1.

Rules:

  • Use one unique external_id for one transaction.
  • Reuse the same external_id only when retrying the same transaction after a timeout, restart, or network problem.
  • Use a new external_id for a new order, a new payment attempt, or a different amount.

Reusing an old external_id for a different amount or order can return the old transaction or fail because Nexi treats it as the same transaction request.

Tools

  • set_terminal_id - store a terminal ID for this MCP server session.
  • get_session_terminal_id - show the current session terminal ID.
  • clear_terminal_id - clear the session terminal ID.
  • list_test_cases - list documented Nexi simulator/test cases and their trigger amounts.
  • create_purchase - low-level purchase call. Can use requested_amount or named test_case.
  • take_payment - recommended normal purchase flow. It creates/polls a purchase until Nexi leaves PROCESSING or the timeout is reached. Can use requested_amount or named test_case.
  • create_refund - low-level refund call.
  • confirm_transaction - confirm a purchase or refund result.
  • get_transaction - fetch a transaction by external_id and terminal.
  • get_unconfirmed_transactions - list unconfirmed transactions for a terminal.
  • get_terminal_status - fetch terminal connection, transaction state, screen, and battery status.
  • list_terminal_events - list terminal event stream entries, optionally filtered by event type.

Response guide

All tool responses use this JSON shape:

{
  "ok": true,
  "operation": "take_payment",
  "terminal_id": "terminal-1",
  "external_id": "order-123",
  "state": "AWAITING_CONFIRM",
  "result_code": "SUCCESS",
  "result_description": "Approved",
  "success": true,
  "message": "Payment flow stopped at terminal/current state",
  "user_message": "The card step succeeded, but the transaction is not complete until it is confirmed.",
  "next_action": "Call confirm_transaction to complete the transaction.",
  "summary": {
    "amount": 500,
    "currency": "SEK",
    "state": "AWAITING_CONFIRM",
    "result_code": "SUCCESS",
    "reference": "reference-from-nexi",
    "masked_card": "************0010"
  },
  "transaction": {},
  "raw": {}
}

Field meanings:

  • ok: the MCP tool call was handled. It does not always mean that the card payment succeeded.
  • success: the payment/refund result was successful. This is true only when result_code is SUCCESS.
  • state: the current Nexi transaction state, for example PROCESSING, AWAITING_CONFIRM, CONFIRMED, or COMMITTED.
  • result_code: the payment/refund result from Nexi, when available.
  • summary: short user-friendly payment details. Use this before inspecting raw.
  • user_message: important human-readable warning or context.
  • next_action: suggested next step, for example poll again, confirm, or no action needed.
  • test_case: included when requested_amount matches a documented Nexi simulator/test case, or when test_case was used as input.
  • raw: full Nexi response for debugging and integrations.

Normal payment flow

For most clients, use take_payment for purchases. It calls Nexi purchase repeatedly while the state is PROCESSING, until the total timeout_seconds is reached or Nexi returns a later state such as AWAITING_CONFIRM, CONFIRMED, or COMMITTED.

You can provide the terminal in three ways:

  1. pass terminal_id in the tool call;
  2. call set_terminal_id once for the session;
  3. set NEXI_POS_TERMINAL_ID as an environment fallback.

Recommended flow:

  1. Start a payment with take_payment.
  2. If the response state is AWAITING_CONFIRM, call confirm_transaction when your business flow is ready.
  3. If the response is still PROCESSING, retry later with the same external_id and terminal ID.

Examples

Take a 5 SEK payment

Tool: take_payment

{
  "external_id": "order-1001-payment-1",
  "requested_amount": 500,
  "currency": "SEK",
  "timeout_seconds": 60
}

Run a named Nexi test case

Tool: take_payment

{
  "external_id": "test-issuer-error-1",
  "test_case": "issuer_error",
  "currency": "SEK",
  "timeout_seconds": 60
}

The tool maps issuer_error to requested_amount: 100006. You can also use descriptions or aliases, for example "bank declined".

To list supported names and descriptions, call list_test_cases:

{}

Confirm payment

Tool: confirm_transaction

{
  "external_id": "order-1001-payment-1",
  "result_code": "SUCCESS"
}

Refund 5 SEK

Tool: create_refund

{
  "external_id": "order-1001-refund-1",
  "requested_amount": 500,
  "currency": "SEK",
  "original_purchase_external_id": "order-1001-payment-1"
}

Purchase with environment terminal

Set NEXI_POS_TERMINAL_ID in the environment, then omit terminal_id in the tool call.

Tool: create_purchase

{
  "external_id": "order-1002-payment-1",
  "requested_amount": 500,
  "currency": "SEK"
}

Purchase with explicit terminal

Tool: create_purchase

{
  "terminal_id": "your-terminal-id",
  "external_id": "order-1003-payment-1",
  "requested_amount": 500,
  "currency": "SEK"
}

Recovery after restart

If the server restarts during a payment, use the same external_id and terminal ID.

Tool: get_transaction

{
  "terminal_id": "your-terminal-id",
  "external_id": "order-1001-payment-1"
}

Then check unconfirmed transactions.

Tool: get_unconfirmed_transactions

{
  "terminal_id": "your-terminal-id"
}

You can also check the terminal status.

Tool: get_terminal_status

{
  "terminal_id": "your-terminal-id"
}

If a transaction is waiting for confirmation, confirm it.

Tool: confirm_transaction

{
  "terminal_id": "your-terminal-id",
  "external_id": "order-1001-payment-1",
  "result_code": "SUCCESS"
}

List terminal events

Tool: list_terminal_events

{
  "terminal_id": "your-terminal-id",
  "event_type": "eu.npay.api.pos.v0.TerminalStatus",
  "limit": 20,
  "wait_seconds": 30
}

If wait_seconds is omitted, the tool uses short polling with 0. Set a positive wait_seconds value for long polling.

Use next_token from a previous response to continue the same event list. When next_token is sent, the server does not send a new filter because Nexi keeps the previous filtering criteria.

{
  "terminal_id": "your-terminal-id",
  "next_token": "token-from-previous-response",
  "limit": 20,
  "wait_seconds": 30
}

Confirmation

This MVP does not confirm by default.

When a transaction reaches AWAITING_CONFIRM, call confirm_transaction:

{
  "external_id": "order-1001-payment-1",
  "result_code": "SUCCESS"
}

You can also let take_payment confirm automatically:

{
  "external_id": "order-1002-payment-1",
  "requested_amount": 500,
  "auto_confirm": true
}

Use auto_confirm only when your business flow is ready to accept the transaction result immediately.

SQLite storage and recovery

The server stores payment-relevant state in SQLite at NEXI_POS_STORAGE_PATH.

Stored data includes:

  • external ID
  • terminal ID
  • transaction type
  • currency
  • requested amount
  • state
  • result code and description
  • confirmation flag
  • timestamps
  • redacted raw transaction JSON

API credentials are never stored. Card-related fields should be redacted by the storage/client layer.

Recovery workflow after a restart:

  1. Call set_terminal_id if the session terminal ID was lost and you do not use NEXI_POS_TERMINAL_ID.
  2. Call get_transaction with the known external_id.
  3. Call get_unconfirmed_transactions for the terminal.
  4. If needed, call confirm_transaction with the correct result_code.

SQLite improves recovery, but it is not a full production operations system. Keep your own order records and reconcile with Nexi when needed.

Troubleshooting

HTTP 401 or 403

Check NEXI_POS_API_KEY_ID and NEXI_POS_API_KEY_SECRET. Also check that the credentials are valid for the selected NEXI_POS_BASE_URL environment, for example sandbox versus production. The credentials and terminal ID are generated outside this MCP server.

Missing terminal ID

Pass terminal_id in the tool call, call set_terminal_id, or set NEXI_POS_TERMINAL_ID. There is no default terminal ID.

Amount exceeds max

The server blocks amounts above NEXI_POS_MAX_AMOUNT_MINOR. Lower requested_amount or raise NEXI_POS_MAX_AMOUNT_MINOR for your environment. Amounts are minor units, so SEK 5.00 is 500. The default 100021 allows Nexi's documented test-case trigger amounts.

Transaction stuck in PROCESSING

Do not create a new transaction immediately. Retry take_payment with the same external_id and terminal ID, or call get_transaction. If it remains in PROCESSING, check the terminal and Nexi status before deciding whether to start a new payment with a new external_id.

Transaction awaiting confirm

If state is AWAITING_CONFIRM, the card step has succeeded but the transaction is not complete. Call confirm_transaction with the same external_id and terminal ID when you are ready to accept the result. You can also use auto_confirm with take_payment for flows where immediate confirmation is correct.

Reused external_id

Use the same external_id only to retry the same transaction. If the order, amount, or payment attempt is different, use a new external_id.

Nexi simulator/test cases

Nexi documents special test-case amounts that can be used in sandbox and production simulator testing. The server detects these amounts and adds test_case information to responses. You can also trigger them by name or description with test_case instead of remembering the amount.

Examples:

  • test_case: "instant_success" -> requested_amount: 100002
  • test_case: "bank declined" -> requested_amount: 100006
  • requested_amount: 100017 -> response includes the 10% tip test-case information

Available cases:

| Name | Amount | Category | Description | | --- | ---: | --- | --- | | successful_2s_delay | 100001 | success | Successful transaction with 2-second delay | | instant_success | 100002 | success | Instant successful transaction (0 seconds) | | rejected_by_processor | 100003 | error | Transaction rejected by payment processor | | customer_cancelled | 100004 | error | Transaction aborted (customer cancelled) | | card_removed | 100005 | error | Card removed during processing | | issuer_error | 100006 | error | Issuer error (bank declined) | | internal_system_error | 100007 | error | Internal system error | | network_connectivity_error | 100008 | error | Network connectivity error | | payment_method_not_accepted | 100009 | error | Card/payment method not accepted | | contactless_pin_verified | 100010 | payment_method | Successful PIN-verified contactless transaction | | contactless_cardholder_verification | 100011 | payment_method | Successful contactless with cardholder verification | | chip_without_pin | 100012 | payment_method | Successful chip card without PIN | | chip_with_pin | 100013 | payment_method | Successful chip card with PIN | | magstripe_signature | 100014 | payment_method | Successful magnetic stripe with signature | | magstripe_pin | 100015 | payment_method | Successful magnetic stripe with PIN | | chip_with_pin_alternative | 100016 | payment_method | Successful chip card with PIN (alternative mode) | | tip_10_percent | 100017 | special_feature | Successful transaction with 10% tip added | | surcharge_1_5_percent | 100018 | special_feature | Successful transaction with 1.5% surcharge | | dcc_enabled | 100019 | special_feature | Successful transaction with Dynamic Currency Conversion enabled | | success_30s_delay | 100020 | success | Successful transaction with 30-second delay | | loyalty_link_identity | 100021 | special_feature | Successful transaction with Nexi Loyalty Link customer identity in response |

Implementation notes for integration

The MCP tool layer expects these core modules to exist:

  • src/config.ts exporting getConfig() with API, currency, terminal fallback, max amount, request timeout, and storage path settings.
  • src/nexi-client.ts exporting NexiClient with purchase, refund, confirm, getTransaction, getUnconfirmedTransactions, getTerminalStatus, and listTerminalEvents methods.
  • src/storage/sqlite-store.ts exporting SQLiteStore with saveIntent, updateTransaction, and markConfirmed methods.

These modules are intentionally separate from the MCP tool code so the API client and storage can be tested independently.