Node.js SDK for the Rublex Payment Gateway — accept crypto and fiat payments through a single, terminal-scoped API.
Wraps every endpoint of the Rublex Merchant API with a thin client, plus a fluent invoice builder. Zero runtime dependencies (uses the built-in global fetch).
- Node.js 18+
npm install @rublex/payments
# or
pnpm add @rublex/payments
# or
yarn add @rublex/paymentsSkip reading the rest of this doc. Paste this whole README.md plus the prompt below into Claude / Cursor / Copilot — the assistant interviews you, then wires the SDK into your Node app end to end.
ROLE
You are a senior Node.js engineer. Your task is to integrate the Rublex Payment
Gateway into my Node app using the official `@rublex/payments` SDK (README
provided above) and take me from zero to a production-ready integration.
SOURCE OF TRUTH
The `@rublex/payments` README provided above is your single source of truth.
- USE the SDK. Do not hand-roll a fetch/axios client or call the REST API directly.
- Only call methods that exist in the README's endpoint reference table
(`crypto()`, `fiat()`, `getSupportedCurrencies()`, `getCryptoInvoice()`,
`getFiatInvoice()`, `listFiatInvoiceGateways()`, `selectFiatGateway()`, …).
- Method signatures are listed in the README — respect them exactly. Crypto
takes ONE argument now (`createCryptoInvoice(data)`), no payer-choice flag.
- Every SDK call resolves with the `{ status, message, data }` envelope —
handle that centrally (a small wrapper module is fine).
- Hosted invoice pages live on https://p.rublex.io and are baked into the
`data.invoice_url` you receive. Redirect customers there as-is.
- If something I ask for is not in the SDK or the README is silent on it,
STOP and tell me. Never invent endpoints or response fields.
CRYPTO INVOICE — NON-NEGOTIABLE PRE-FLIGHT
Before you call `client.crypto().…createInvoice()` (or `client.createCryptoInvoice(data)`)
you MUST:
1. Call `client.getSupportedCurrencies()` first.
2. Pick a `currency_id` from THAT response (terminal-approved list).
3. Pass it via `.pick(currencyId)` or as `currency_id` in the data object.
Never hard-code numeric currency IDs. The terminal rejects unapproved IDs with
HTTP 422 — surface a clear error and refetch the list on retry.
RETURN URLS — success_url AND failed_url
Every invoice flow accepts optional `success_url` / `failed_url` (builder
methods `.success(url)`, `.failed(url)`, or `.returnTo(url)` for the same URL).
Pass them from the checkout. The redirect is UX only — NOT proof of payment.
Order state must come from the webhook + a server-side status lookup.
WORK IN TWO PHASES.
──────────────────────────────────────────────
PHASE 1 — INTERVIEW ME (no code yet)
Ask the questions below in ONE grouped message, recommend where you can, then
STOP and wait.
1. Payment types: crypto, fiat, or both?
2. Flow: explain the trade-offs and recommend one:
- Crypto · Pay Request (`client.crypto().pick(id).createInvoice()`)
- Fiat · Direct gateway (`client.fiat().pick(gatewayId).createInvoice()`)
- Fiat · Gateway selection (`client.fiat().byPayer().createInvoice()` + payer endpoints)
3. Runtime & framework: Node version, Express / Fastify / Koa / Hono / Next.js
(App Router or Pages), NestJS, etc.
4. ORM / DB layer if any: Prisma, Drizzle, TypeORM, Mongoose, raw SQL …
5. TypeScript or plain JS?
6. Where should the terminal token live? (`.env`, AWS Secrets Manager, Vault, …)
7. Which URL should be my `callback_url`, and which should `success_url` /
`failed_url` point to? (Same URL for both is fine — recommended.)
8. Scope: which pieces do I need — checkout endpoint that creates an invoice
and returns / redirects to `invoice_url`, webhook endpoint, Order/Payment
model + migration, status reconciliation job (cron / BullMQ / Agenda),
admin/status view, tests?
9. Greenfield or fitting into code I'll paste?
10. Separate staging/production terminals?
──────────────────────────────────────────────
PHASE 2 — BUILD IT (after I confirm)
Deliverables, idiomatic for the stack chosen in Phase 1:
a. SDK wrapper module — a thin `rublexClient.{js,ts}` that constructs
`RublexPayments.fromEnv()` once and exports it. Plus a `paymentsService`
that centralises envelope handling and logging. NO direct HTTP calls.
b. Invoice creation
- Crypto: call `getSupportedCurrencies()` (cached briefly with TTL), pick
the right `currency_id`, create the invoice via the SDK with `amount`,
`callback`, `success`, `failed`, persist `invoice_number` on the Order.
- Fiat: same pattern with `pick(gatewayId)` or `byPayer()`.
- Return / redirect the caller to `data.invoice_url` as-is.
c. Webhook endpoint
- Responds `200 OK` within 10s (defer heavy work to a queue).
- Treats the body as UNTRUSTED: re-fetch via `getCryptoInvoice()` /
`getFiatInvoice()` before marking paid.
- Idempotent — guard by Order status before transitioning.
- Maps PENDING / PARTIAL / PAID / EXPIRED / CANCELLED → order state.
d. Return-URL endpoint
- Reads `invoice_number` from the query string, looks up MY order, renders
status from the local record. Never trust the redirect alone.
e. Config + errors
- Use `RublexPayments.fromEnv()` so env vars match the SDK defaults.
- Centralised error handling: 400, 401, 403, 404, 422, 429, 5xx. Retry
with exponential backoff on 429 / 5xx (a queued job, not in-process).
- On 422 from crypto, refetch the supported list and bubble a clear error.
f. Security
- Token only in env / secrets manager, never in client code or VCS.
- Log `invoice_number` next to my internal order id.
- HTTPS on every URL (callback / success / failed).
g. Optional but recommended: a small cron job that pulls open invoices and
reconciles their status — defends against missed webhooks.
OUTPUT FORMAT
- Full file tree first, then each file in its own code block.
- Setup steps + env vars + how to run.
- Finally, ask whether I want automated tests (Vitest / Jest / node:test)
or any adjustments.
Begin with PHASE 1 now.
Tip: If you use TypeScript, also tell the assistant to import the SDK's
index.d.tstypes so your service layer is fully typed.
The SDK needs your terminal token (a 60-char secret from Stores → Terminals in the merchant panel). Pass it explicitly or via environment variables.
RUBLEX_PAYMENTS_API_KEY=<your-60-char-terminal-token>
RUBLEX_PAYMENTS_CALLBACK_URL=https://your-site.com/rublex/callback
# Override only if Rublex tells you to:
# RUBLEX_PAYMENTS_URL=https://api.pay.rublex.io/terminals/v1/const { RublexPayments } = require('@rublex/payments');
// Explicit
const rublex = new RublexPayments({
apiKey: process.env.RUBLEX_PAYMENTS_API_KEY,
callbackUrl: 'https://your-site.com/rublex/callback',
});
// Or pick up everything from the environment
const rublex = RublexPayments.fromEnv();Treat the terminal token like a password. Keep it server-side only — never ship it to a browser or mobile app.
Crypto pre-flight is mandatory. Before creating a crypto invoice you MUST call
getSupportedCurrencies()and use one of the returnedidvalues ascurrency_id. The terminal rejects IDs it has not approved with HTTP422. Do not hard-code IDs.
Two equivalent styles — pick whichever fits your code.
// 1) Look up which currencies this terminal supports.
const supported = await rublex.getSupportedCurrencies();
const currencyId = supported.data[0].id;
// 2) Crypto · merchant-fixed coin
const invoice = await rublex.crypto()
.amount(0.5)
.pick(currencyId) // from /currencies/supported
.callback('https://your-site.com/rublex/callback')
.returnTo('https://your-site.com/checkout/return') // success + failure
.createInvoice();
// Fiat · direct gateway
const fiat = await rublex.fiat()
.amount(19.99)
.pick(4) // gateway_id from /fiat/gateways
.lockRate() // fixed FX rate
.success('https://your-site.com/checkout/success')
.failed('https://your-site.com/checkout/cancelled')
.customer({ email: 'buyer@example.com', firstName: 'Ada' })
.createInvoice();
// Fiat · gateway selection (payer picks the gateway on the hosted page)
const fiatPick = await rublex.fiat()
.amount(19.99)
.byPayer()
.lockRate(false)
.returnTo('https://your-site.com/checkout/return')
.createInvoice();const { data: supported } = await rublex.getSupportedCurrencies();
await rublex.createCryptoInvoice({
amount: 0.5,
currency_id: supported[0].id,
success_url: 'https://your-site.com/checkout/return',
failed_url: 'https://your-site.com/checkout/return',
});
await rublex.createFiatInvoice({
amount: 19.99,
gateway_id: 4,
success_url: 'https://your-site.com/checkout/return',
failed_url: 'https://your-site.com/checkout/return',
});
await rublex.createFiatInvoice({
amount: 19.99,
success_url: 'https://your-site.com/checkout/return',
failed_url: 'https://your-site.com/checkout/return',
}, true); // gateway selectionRedirect the customer to response.data.invoice_url to complete payment.
success_url/failed_urlare UX, not proof of payment. Always reconcile against the webhook orgetCryptoInvoice()/getFiatInvoice().
| Group | Method | Endpoint |
|---|---|---|
| Terminal | getInformation() |
GET /info |
| Catalog | getCurrencies(page?, perPage?) |
GET /currencies |
| Catalog | getSupportedCurrencies(page?, perPage?) |
GET /currencies/supported |
| Catalog | getFiatGateways() |
GET /fiat/gateways |
| Catalog | getFiatCurrencies() |
GET /fiat/currencies |
| Crypto | createCryptoInvoice(data) |
POST /pay-request |
| Crypto | getCryptoInvoice(invoiceNumber) |
GET /invoices |
| Crypto | listCryptoInvoices(params?) |
GET /invoices |
| Crypto | listPayRequests(params?) |
GET /pay-requests |
| Fiat | createFiatInvoice(data, payerChoice?) |
POST /fiat/pay-request-direct or /fiat/pay-request-selection |
| Fiat | getFiatInvoice(invoiceNumber) |
GET /fiat/invoices |
| Fiat | listFiatInvoices(params?) |
GET /fiat/invoices |
| Payer | listFiatInvoiceGateways(invoiceNumber) |
GET /fiat/invoices/{n}/gateways |
| Payer | selectFiatGateway(invoiceNumber, data) |
POST /fiat/invoices/{n}/select-gateway |
Every method resolves with the gateway's shared envelope:
{ status: 'SUCCESS' | 'ERROR', message: string, data: { /* payload */ } }Set a callback_url (per-invoice or globally via callbackUrl). On status change Rublex POSTs JSON to it:
{ "invoice_number": "BpXo8T60vIN9D7NCcs66rOnZVipBLUah", "status": "PAID", "amount": "0.50000000", "paid_amount": "0.50000000", "currency": "USDT (TRC20)" }Required behaviour:
- Respond
200 OKwithin 10 seconds. - Treat the callback as untrusted — re-fetch the invoice via
getCryptoInvoice/getFiatInvoicebefore marking the order paid. - Be idempotent — the same callback may be retried.
RublexConfigError— missing API key / bad configuration.RublexRequestError— network failure or non-JSON response (has.status,.body).
HTTP-level errors (4xx/5xx) are not thrown — the parsed envelope is returned so you can inspect status === 'ERROR' and the message.
MIT © Rublex Team. See LICENSE.md.