Cost+Docs

Node.js / TypeScript

Official Node.js SDK for the Cost+ payment gateway

Official Node.js/TypeScript SDK for the Cost+ payment gateway. Simplifies the HPP (Hosted Payment Page) redirect flow, HMAC payload signing, and webhook verification.

Features

  • Zero dependencies — uses only built-in Node.js crypto and fetch
  • Full TypeScript types with declaration maps
  • HMAC-SHA256 signature generation and constant-time verification
  • Automatic snake_case/camelCase mapping between the API and SDK
  • Webhook parsing + API-based order verification
  • Tested across Node.js 18, 20, and 22

Requirements

Installation

npm install nopayn-node-sdk

Quick Start

1. Initialise the Client

import { NoPaynClient } from 'nopayn-node-sdk';

const nopayn = new NoPaynClient({
  apiKey: 'your-api-key',      // From the NoPayn merchant portal
  merchantId: 'your-project',  // Your project/merchant ID
});

2. Create a Payment and Redirect to the HPP

const result = await nopayn.generatePaymentUrl({
  amount: 1295,            // €12.95 in cents
  currency: 'EUR',
  merchantOrderId: 'ORDER-001',
  description: 'Premium Widget',
  returnUrl: 'https://shop.example.com/success',
  failureUrl: 'https://shop.example.com/failure',
  webhookUrl: 'https://shop.example.com/webhook',
  locale: 'en-GB',
  expirationPeriod: 'PT30M',
});

// Redirect the customer
// result.orderUrl   → HPP (customer picks payment method)
// result.paymentUrl → direct link to the first transaction's payment method
// result.signature  → HMAC-SHA256 for verification
// result.orderId    → NoPayn order UUID

3. Handle the Webhook

app.post('/webhook', async (req, res) => {
  const verified = await nopayn.verifyWebhook(JSON.stringify(req.body));

  console.log(verified.order.status); // 'completed', 'cancelled', etc.
  console.log(verified.isFinal);      // true when the order won't change

  if (verified.order.status === 'completed') {
    // Fulfil the order
  }

  res.sendStatus(200);
});

API Reference

new NoPaynClient(config)

ParameterTypeRequiredDefault
apiKeystringYes
merchantIdstringYes
baseUrlstringNohttps://api.nopayn.co.uk

client.createOrder(params)

Creates an order via POST /v1/orders/.

ParameterTypeRequiredDescription
amountnumberYesAmount in smallest currency unit (cents)
currencystringYesISO 4217 code (EUR, GBP, USD, NOK, SEK)
merchantOrderIdstringNoYour internal order reference
descriptionstringNoOrder description
returnUrlstringNoRedirect after successful payment
failureUrlstringNoRedirect on cancel/expiry/error
webhookUrlstringNoAsync status-change notifications
localestringNoHPP language (en-GB, de-DE, nl-NL, etc.)
paymentMethodsPaymentMethod[]NoFilter HPP methods
expirationPeriodstringNoISO 8601 duration (PT30M)

Available payment methods: credit-card, apple-pay, google-pay, vipps-mobilepay

client.getOrder(orderId)

Fetches order details via GET /v1/orders/{id}/.

client.createRefund(orderId, amount, description?)

Issues a full or partial refund via POST /v1/orders/{id}/refunds/.

client.generatePaymentUrl(params)

Convenience method that creates an order and returns:

{
  orderId: string;     // NoPayn order UUID
  orderUrl: string;    // HPP URL
  paymentUrl?: string; // Direct payment URL (first transaction)
  signature: string;   // HMAC-SHA256 of amount:currency:orderId
  order: Order;        // Full order object
}

client.generateSignature(amount, currency, orderId)

Generates an HMAC-SHA256 hex signature. The canonical message is ${amount}:${currency}:${orderId}, signed with the API key.

client.verifySignature(amount, currency, orderId, signature)

Constant-time verification of an HMAC-SHA256 signature. Returns true if valid.

client.verifyWebhook(rawBody)

Parses the webhook body, then calls GET /v1/orders/{id}/ to verify the actual status. Returns:

{
  orderId: string;
  order: Order;     // Verified via API
  isFinal: boolean; // true for completed/cancelled/expired/error
}

client.parseWebhookBody(rawBody)

Parses and validates a webhook body without calling the API.

Standalone HMAC Utilities

import { generateSignature, verifySignature } from 'nopayn-node-sdk';

const sig = generateSignature('your-api-key', 1295, 'EUR', 'order-uuid');
const ok  = verifySignature('your-api-key', 1295, 'EUR', 'order-uuid', sig);

Error Handling

import { NoPaynApiError, NoPaynError, NoPaynWebhookError } from 'nopayn-node-sdk';

try {
  await nopayn.createOrder({ amount: 100, currency: 'EUR' });
} catch (err) {
  if (err instanceof NoPaynApiError) {
    console.error(err.statusCode); // 401, 400, etc.
    console.error(err.errorBody);  // Raw API error response
  } else if (err instanceof NoPaynError) {
    console.error(err.message);    // Network or parsing error
  }
}

Order Statuses

StatusFinal?Description
newNoOrder created
processingNoPayment in progress
completedYesPayment successful — deliver the goods
cancelledYesPayment cancelled by customer
expiredYesPayment link timed out
errorYesTechnical failure

Webhook Best Practices

  1. Always verify via the API — the webhook payload only contains the order ID, never the status. The SDK's verifyWebhook() does this automatically.
  2. Return HTTP 200 to acknowledge receipt. Any other code triggers up to 10 retries (2 minutes apart).
  3. Implement a backup poller — for orders older than 10 minutes that haven't reached a final status, poll getOrder() as a safety net.
  4. Be idempotent — you may receive the same webhook multiple times.

Test Cards

Use these cards in Cost+ test mode (sandbox website):

CardNumberNotes
Visa (success)4111 1111 1111 1111Any CVV
Mastercard (success)5544 3300 0003 7Any CVV
Visa (declined)4111 1111 1111 1105Do Not Honor
Visa (insufficient funds)4111 1111 1111 1151Insufficient Funds

Use any future expiry date and any 3-digit CVC.

Demo App

A Docker-based demo app is included in the GitHub repository for testing the full payment flow:

cd demo

cat > .env << EOF
NOPAYN_API_KEY=your-api-key
NOPAYN_MERCHANT_ID=your-merchant-id
PUBLIC_URL=http://localhost:3000
EOF

docker compose up --build

Open http://localhost:3000 to see the demo checkout page.

Support

Need help? Reach out to our support team at support@costplus.io.

On this page