name: simplepay-js-sdk description: Best practices and integration guide for SimplePay payment processing in Node.js/TypeScript applications. Use this skill when implementing SimplePay payments, handling IPN callbacks, setting up recurring payments, or integrating Hungary's SimplePay gateway. license: MIT metadata: author: rrd108 version: "0.12.3" organization: SimplePay JS SDK date: February 2026 abstract: Complete integration guide for the simplepay-js-sdk npm package. Covers one-time payments, recurring payments, token-based payments, IPN handling, card cancellation, and security best practices for Hungary's SimplePay payment gateway.
SimplePay JS SDK - Integration Guide
A lightweight TypeScript utility for integrating Hungary's SimplePay payments in Node.js applications. This skill provides procedural knowledge for AI agents to correctly implement SimplePay payment flows.
When to Use This Skill
- Integrating SimplePay payment gateway in a Node.js or TypeScript project
- Implementing one-time card payments with SimplePay
- Setting up recurring/subscription payments
- Handling IPN (Instant Payment Notification) callbacks
- Processing token-based merchant-initiated payments
- Managing card registration and cancellation
- Working with Hungarian payment systems
Installation
npm install simplepay-js-sdk
Required Environment Variables
Set these in your .env file. Never hardcode secrets in source code.
# Required
SIMPLEPAY_MERCHANT_KEY_HUF=your-secret-merchant-key
SIMPLEPAY_MERCHANT_ID_HUF=your-merchant-id
SIMPLEPAY_REDIRECT_URL=https://yoursite.com/payment/callback
# Optional - additional currencies
SIMPLEPAY_MERCHANT_KEY_EUR=your-eur-key
SIMPLEPAY_MERCHANT_ID_EUR=your-eur-id
SIMPLEPAY_MERCHANT_KEY_USD=your-usd-key
SIMPLEPAY_MERCHANT_ID_USD=your-usd-id
# Optional - SZEP card payments
SIMPLEPAY_MERCHANT_KEY_HUF_SZEP=your-szep-key
SIMPLEPAY_MERCHANT_ID_HUF_SZEP=your-szep-id
# Optional - environment
SIMPLEPAY_PRODUCTION=true # omit or set to false for sandbox
SIMPLEPAY_LOGGER=true # enable debug logging (development only)
Architecture Overview
A SimplePay integration requires three endpoints:
- Start Payment Endpoint - Initiates payment and redirects user to SimplePay
- Payment Response Endpoint - Handles redirect back from SimplePay (at
SIMPLEPAY_REDIRECT_URL) - IPN Endpoint - Receives server-to-server payment confirmations from SimplePay
One-Time Payment Flow
Step 1: Start a Payment
import { startPayment } from 'simplepay-js-sdk'
const initiatePayment = async () => {
try {
const response = await startPayment({
orderRef: 'order-12',
total: 1212,
currency: 'HUF',
customerEmail: 'customer@example.com',
language: 'HU',
method: 'CARD',
invoice: {
name: 'Customer Name',
country: 'HU',
state: 'Budapest',
city: 'Budapest',
zip: '1234',
address: 'Street 1',
},
}, {
redirectUrl: 'https://yoursite.com/payment/callback',
})
// Redirect the user to response.paymentUrl
return response
} catch (error) {
console.error('Payment initiation failed:', error)
throw error
}
}
Parameters:
orderRef(required): Unique order reference stringtotal(required): Payment amount (number or string)customerEmail(required): Customer's email addresscurrency(optional):'HUF'|'HUF_SZEP'|'EUR'|'USD'(defaults to'HUF')language(optional):'HU'|'EN'|'DE'|'RO'| etc. (defaults to'HU')method(optional):'CARD'|'WIRE'(defaults to'CARD')invoice(optional): Billing address objectredirectUrl(optional): Override theSIMPLEPAY_REDIRECT_URLenv variable
Response: Contains paymentUrl to redirect the customer to.
Step 2: Handle Payment Response
When the customer returns from SimplePay, your redirect URL receives r and s query parameters.
import { getPaymentResponse } from 'simplepay-js-sdk'
const handlePaymentReturn = (r: string, s: string) => {
const response = getPaymentResponse(r, s)
// response.event: 'SUCCESS' | 'FAIL' | 'TIMEOUT' | 'CANCEL'
// response.responseCode: 0 on success
// response.transactionId: SimplePay transaction ID
// response.orderRef: your original order reference
return response
}
Step 3: Handle IPN (Instant Payment Notification)
SimplePay sends a POST request to your IPN endpoint. This is the authoritative payment confirmation.
import { handleIpnRequest, getSimplePayConfig } from 'simplepay-js-sdk'
const handleIpn = async (request: Request) => {
const ipnBody = await request.text() // IMPORTANT: use .text(), NOT .json()
const incomingSignature = request.headers.get('Signature')
const { MERCHANT_KEY } = getSimplePayConfig('HUF')
const { responseBody, signature } = handleIpnRequest(
ipnBody,
incomingSignature,
MERCHANT_KEY
)
// CRITICAL: Send responseBody exactly as returned - do NOT modify it
return new Response(responseBody, {
status: 200,
headers: {
'Content-Type': 'application/json',
'Signature': signature,
},
})
}
Critical IPN rules:
- Always read the request body as raw text with
.text(), never.json() - Never re-format, pretty-print, or re-stringify the response body
- Any modification to
responseBodyinvalidates the signature - Always return HTTP 200 with the exact
responseBodyandSignatureheader
Recurring Payment Flow
Step 1: Register a Card with Recurring Payment
import { startRecurringPayment } from 'simplepay-js-sdk'
const initiateRecurring = async () => {
try {
const response = await startRecurringPayment({
orderRef: 'order-recurring-1',
total: 1212,
currency: 'HUF',
customerEmail: 'customer@example.com',
language: 'HU',
customer: 'Customer Name',
recurring: {
times: 3,
until: '2025-12-31T18:00:00+02:00',
maxAmount: 100000,
},
invoice: {
name: 'Customer Name',
country: 'HU',
state: 'Budapest',
city: 'Budapest',
zip: '1234',
address: 'Street 1',
},
})
// response.tokens contains the card tokens - save these to your database
return response
} catch (error) {
console.error('Recurring payment initiation failed:', error)
throw error
}
}
Additional parameters:
customer(required): Customer name stringrecurring.times(required): Number of tokens/payment occurrencesrecurring.until(required): End date in ISO 8601 formatrecurring.maxAmount(required): Maximum recurring payment amount
Important: Save the returned tokens array to your database for later token payments.
Step 2: Make Token Payments
After card registration, use tokens for merchant-initiated payments (e.g., via cron job):
import { startTokenPayment } from 'simplepay-js-sdk'
import type { Currency } from 'simplepay-js-sdk'
const processTokenPayment = async () => {
try {
const response = await startTokenPayment({
orderRef: Date.now().toString(),
token: '1234567890123456', // from your database
total: 1212,
currency: 'HUF' as Currency,
customer: 'Customer Name',
customerEmail: 'customer@example.com',
language: 'HU',
method: 'CARD',
invoice: {
name: 'Customer Name',
country: 'HU',
state: 'Budapest',
city: 'Budapest',
zip: '1234',
address: 'Street 1',
},
})
return response
} catch (error) {
console.error('Token payment failed:', error)
throw error
}
}
Step 3: Cancel a Registered Card
Allow customers to remove their registered card:
import { cancelCard } from 'simplepay-js-sdk'
const handleCardCancellation = async (cardId: string) => {
try {
const response = await cancelCard(cardId)
if (response.status === 'DISABLED') {
// Card successfully disabled - delete tokens from your database
}
return response
} catch (error) {
console.error('Card cancellation failed:', error)
throw error
}
}
Use the SimplePay transaction ID from the card registration as cardId.
Recurring IPN Response
The IPN response for recurring payments includes two additional fields:
cardMask: Masked card number (e.g.,xxxx-xxxx-xxxx-1234)expiry: Card expiry date in ISO 8601 format
Available Types
import type {
Currency, // 'HUF' | 'HUF_SZEP' | 'EUR' | 'USD'
Language, // 'HU' | 'EN' | 'DE' | ... (16 languages)
PaymentMethod, // 'CARD' | 'WIRE'
SimplePayResponse,
SimplePayRecurringResponse,
SimplePayTokenResponse,
SimplePayCancelCardResponse,
SimplePayResult,
SimplePayIPNResponse,
} from 'simplepay-js-sdk'
Utility Functions
import {
checkSignature, // Verify HMAC-SHA384 signature
generateSignature, // Generate HMAC-SHA384 signature
getPaymentResponse, // Decode payment redirect response
handleIpnRequest, // Handle full IPN flow
toISO8601DateString, // Convert Date to SimplePay-compatible ISO 8601 string
} from 'simplepay-js-sdk'
Manual Signature Operations
For advanced use cases:
// Verify a signature
const isValid = checkSignature(bodyString, signatureHeader, merchantKey)
// Generate a signature
const signature = generateSignature(bodyString, merchantKey)
// Convert date to ISO 8601 format compatible with SimplePay
const dateStr = toISO8601DateString(new Date())
// Output: "2025-10-06T07:00:34+00:00"
Security Best Practices
- Never expose merchant keys - Keep
SIMPLEPAY_MERCHANT_KEY_*in environment variables only - Always validate signatures - The SDK validates all response signatures automatically
- Use HTTPS - All redirect and IPN URLs must use HTTPS in production
- Validate IPN server-side - Never trust client-side redirect responses alone; always confirm via IPN
- Set
SIMPLEPAY_PRODUCTION=trueonly in production - Use sandbox for development and testing - Disable logging in production - Never set
SIMPLEPAY_LOGGER=truein production environments - Use unique
orderRefvalues - Prevent duplicate payment processing
Common Mistakes to Avoid
- Parsing IPN body as JSON before passing to
handleIpnRequest- Always use.text()to get the raw string - Modifying
responseBodyfromhandleIpnRequest- Any change invalidates the HMAC signature - Forgetting the
Signatureheader in IPN response - SimplePay requires it - Using
CARDmethod for WIRE payments - Ensuremethodmatches the payment type - Not saving recurring tokens - Tokens from
startRecurringPaymentmust be persisted - Using sandbox credentials in production - Double-check
SIMPLEPAY_PRODUCTIONflag
Framework Integration Examples
Express.js
import express from 'express'
import { startPayment, getPaymentResponse, handleIpnRequest, getSimplePayConfig } from 'simplepay-js-sdk'
const app = express()
app.post('/api/payment/start', async (req, res) => {
try {
const response = await startPayment({
orderRef: req.body.orderRef,
total: req.body.total,
customerEmail: req.body.email,
invoice: req.body.invoice,
})
res.json({ paymentUrl: response.paymentUrl })
} catch (error) {
res.status(500).json({ error: 'Payment failed' })
}
})
app.get('/api/payment/callback', (req, res) => {
const { r, s } = req.query
const result = getPaymentResponse(r as string, s as string)
// Handle result based on result.event
res.redirect(`/payment/${result.event.toLowerCase()}`)
})
app.post('/api/payment/ipn', express.text({ type: '*/*' }), (req, res) => {
const signature = req.headers['signature'] as string
const { MERCHANT_KEY } = getSimplePayConfig('HUF')
const { responseBody, signature: respSignature } = handleIpnRequest(req.body, signature, MERCHANT_KEY)
res.set('Signature', respSignature).type('json').send(responseBody)
})
Next.js App Router
import { startPayment, handleIpnRequest, getSimplePayConfig } from 'simplepay-js-sdk'
import { NextRequest, NextResponse } from 'next/server'
// app/api/payment/start/route.ts
export const POST = async (request: NextRequest) => {
const body = await request.json()
try {
const response = await startPayment({
orderRef: body.orderRef,
total: body.total,
customerEmail: body.email,
invoice: body.invoice,
})
return NextResponse.json({ paymentUrl: response.paymentUrl })
} catch (error) {
return NextResponse.json({ error: 'Payment failed' }, { status: 500 })
}
}
// app/api/payment/ipn/route.ts
export const POST = async (request: NextRequest) => {
const ipnBody = await request.text()
const signature = request.headers.get('Signature')
const { MERCHANT_KEY } = getSimplePayConfig('HUF')
const { responseBody, signature: respSignature } = handleIpnRequest(ipnBody, signature!, MERCHANT_KEY!)
return new Response(responseBody, {
status: 200,
headers: { 'Content-Type': 'application/json', 'Signature': respSignature },
})
}