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

@moneygatex/merchant-sdk

v1.3.0

Published

SDK for MoneyGateX merchant API: orders, webhook verification, NestJS module

Readme

@moneygatex/merchant-sdk

SDK для интеграции мерчантов с API MoneyGateX: создание заказов, получение статуса, проверка подписи webhook и готовый NestJS-модуль с обработчиком webhook.

Содержание


Установка

# Yarn
yarn add @moneygatex/merchant-sdk

# npm
npm install @moneygatex/merchant-sdk

Для использования только NestJS-модуля в проекте должны быть установлены @nestjs/common и @nestjs/core (peer dependencies).

Подпути @moneygatex/merchant-sdk/client, .../webhook и .../nestjs объявлены в package.json и ведут на тот же артефакт, что и корень пакета — удобно для явного разделения импортов в коде.


Быстрый старт

Только API-клиент (Node.js или браузер)

import {
	ECurrencyCode,
	MerchantApiClient,
} from '@moneygatex/merchant-sdk/client';

const client = new MerchantApiClient({
	baseUrl: 'https://api.example.com',
	projectToken: 'sk_live_xxxx',
});

const order = await client.createOrder({
	projectPublicId: 'ABC12XYZ',
	amount: 1000.5,
	currency: ECurrencyCode.USD,
	merchantOrderId: 'ORDER-12345',
	description: 'Payment for order #12345',
});

console.log(order.paymentUrl); // Ссылка на страницу оплаты (может быть на домене проекта или PayGate X — используйте как есть)

const rebill = await client.rebill(order.publicId, {
	amount: 1000.5,
	options: { recurring: 1 },
});
console.log(rebill.orders?.[0]?.status);

NestJS: модуль + сервис + свой webhook-эндпоинт

// app.module.ts
import { MerchantSdkModule } from '@moneygatex/merchant-sdk/nestjs';
import { Module } from '@nestjs/common';

import { PaymentsController } from './payments.controller';

@Module({
	imports: [
		MerchantSdkModule.forRoot({
			baseUrl: process.env.MONEYGATEX_API_URL!,
			projectToken: process.env.MONEYGATEX_PROJECT_TOKEN!,
			projectPublicId: process.env.MONEYGATEX_PROJECT_PUBLIC_ID,
			webhookSecret: process.env.MONEYGATEX_WEBHOOK_SECRET, // для MerchantWebhookGuard
		}),
	],
	controllers: [PaymentsController],
})
export class AppModule {}
// payments.controller.ts — свой контроллер и эндпоинт для webhook
import {
	MerchantWebhookBody,
	MerchantWebhookGuard,
} from '@moneygatex/merchant-sdk/nestjs';
import type { WebhookEvent } from '@moneygatex/merchant-sdk/webhook';
import { Controller, Post, UseGuards } from '@nestjs/common';

@Controller('payments')
export class PaymentsController {
	@Post('webhook')
	@UseGuards(MerchantWebhookGuard)
	handleWebhook(@MerchantWebhookBody() event: WebhookEvent): string {
		if (event.event === 'order.charged') {
			console.log(
				'Order charged:',
				event.data.publicId,
				event.data.amountCharged,
			);
			// Обновить заказ в БД, отправить уведомление и т.д.
		}
		return 'OK'; // обязательно 200 и тело "OK"
	}
}
// orders.controller.ts — projectPublicId можно не передавать, если задан в опциях модуля
import { ECurrencyCode } from '@moneygatex/merchant-sdk/client';
import { MerchantSdkService } from '@moneygatex/merchant-sdk/nestjs';
import { Controller, Param, Post } from '@nestjs/common';

@Controller('orders')
export class OrdersController {
	constructor(private readonly merchantSdk: MerchantSdkService) {}

	@Post()
	async create() {
		return this.merchantSdk.createOrder({
			amount: 99.99,
			currency: ECurrencyCode.EUR,
		});
	}

	@Post(':publicId/rebill')
	async rebill(@Param('publicId') publicId: string) {
		return this.merchantSdk.rebill(publicId, {
			amount: 99.99,
			options: { recurring: 1 },
		});
	}
}

Конфигурация NestJS

forRoot(options)

Синхронная конфигурация при старте приложения.

| Параметр | Тип | Обязательный | Описание | | ----------------- | -------- | ------------ | -------------------------------------------------------------------------------------------------------------------------------- | | baseUrl | string | да | Базовый URL API (например https://api.example.com) | | projectToken | string | да | Токен проекта (Bearer) | | projectPublicId | string | нет | Публичный ID проекта; подставляется в createOrder, startCardAuth, credit, listCardFingerprints, если не передан в вызове | | webhookSecret | string | нет | Секрет для проверки подписи webhook (нужен для MerchantWebhookGuard) |

forRootAsync(options)

Асинхронная конфигурация (например из ConfigService).

| Параметр | Тип | Описание | | ------------ | ---------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- | | useFactory | (...args) => MerchantSdkModuleOptions \| Promise<MerchantSdkModuleOptions> | Функция, возвращающая опции (можно async) | | inject | (InjectionToken \| OptionalFactoryDependency)[] | Массив токенов для инъекции аргументов в useFactory (например [ConfigService]) |

Пример с кастомным ConfigModule:

// app.module.ts
import { MerchantSdkModule } from '@moneygatex/merchant-sdk/nestjs';
import { Module } from '@nestjs/common';

import { CustomConfigModule } from '@/config/config.module';

import { CustomConfigService } from '@/config/config.service';

@Module({
	imports: [
		MerchantSdkModule.forRootAsync({
			imports: [CustomConfigModule],
			inject: [CustomConfigService],
			useFactory: (configService: CustomConfigService) => ({
				baseUrl: configService.merchantSdkBaseUrl,
				projectToken: configService.merchantSdkProjectToken,
				projectPublicId: configService.merchantSdkProjectPublicId,
				webhookSecret: configService.merchantSdkWebhookSecret,
			}),
		}),
	],
})
export class AppModule {}

В CustomConfigService добавьте геттеры (значения из env или валидированного конфига), например:

get merchantSdkBaseUrl(): string {
  return this.config.MONEYGATEX_API_URL ?? '';
}
get merchantSdkProjectToken(): string {
  return this.config.MONEYGATEX_PROJECT_TOKEN ?? '';
}
get merchantSdkProjectPublicId(): string | undefined {
  return this.config.MONEYGATEX_PROJECT_PUBLIC_ID;
}
get merchantSdkWebhookSecret(): string | undefined {
  return this.config.MONEYGATEX_WEBHOOK_SECRET;
}

API-клиент

Точка входа: @moneygatex/merchant-sdk/client.

Конфигурация

import { MerchantApiClient } from '@moneygatex/merchant-sdk/client';

const client = new MerchantApiClient({
	baseUrl: 'https://api.example.com', // без завершающего /
	projectToken: 'sk_live_xxxx',
});

createOrder(params, options?)

Создаёт заказ. Требуется токен проекта.

Параметры (CreateOrderParams):

| Поле | Тип | Обязательный | Описание | | --------------------- | --------------------------------- | ------------ | ---------------------------------------------------------------------------------------------------- | | projectPublicId | string | да | Публичный ID проекта (до 50 символов) | | amount | number | да | Сумма (0.01–999999.99, до 2 знаков после запятой) | | currency | ECurrencyCode | да | EUR | USD | GBP | AUD | RUB | BYN | | merchantOrderId | string \| null | нет | ID заказа мерчанта (до 255 символов) | | description | string \| null | нет | Описание заказа | | metadata | Record<string, unknown> \| null | нет | Произвольный JSON | | projectUserId | string | нет | Вместе с cardFingerprintUuid — автосписание (rebill в фоне), без обязательного редиректа на оплату | | cardFingerprintUuid | string (UUID) | нет | Вместе с projectUserId — автосписание по сохранённой карте |

Опции: { idempotencyKey?: string } — ключ идемпотентности (см. Идемпотентность).

Возвращает: Promise<CreateOrderResponse>publicId, amount, currency, status, expiresAt, paymentUrl, merchantOrderId, description, metadata, опционально autoPay.

  • Без автоплатежа (поля projectUserId / cardFingerprintUuid не переданы или передано только одно из них): ведите пользователя на paymentUrl для оплаты; итог — webhook и/или getStatus(publicId).
  • С автоплатежом (оба поля заданы): в ответе появляется autoPay: { pending: true, state: 'queued' } — rebill ставится в очередь на бэкенде; итог — webhook и/или getStatus. Поле paymentUrl всё равно присутствует, но обычно редирект на страницу оплаты не нужен.

Примечание по paymentUrl: ссылка на страницу оплаты (order.paymentUrl) может вести на домен проекта (например https://pay.merchant.com/pay/...), если в настройках проекта указан «Базовый URL страницы оплаты», иначе — на домен PayGate X. Всегда используйте возвращённый paymentUrl как есть, не полагайтесь на фиксированный домен.

startCardAuth(params)

Старт привязки карты под пользователем проекта (POST /v1/card-auth/start): создаётся заказ на фиксированную сумму, пользователь переходит по paymentUrl. Требуется токен проекта.

Параметры: projectPublicId, projectUserId (обязательны), опционально currency, pan, card, client, custom_fields, returnUrl — см. тип StartCardAuthParams.

Возвращает: Promise<StartCardAuthResponse>paymentUrl, publicId, опционально sessionId.

listCardFingerprints(projectUserId, projectPublicId)

Список активных отпечатков карт (GET /v1/card-fingerprints/:projectUserId?projectPublicId=...). Требуется токен проекта.

Возвращает: Promise<CardFingerprintListResponse>projectUserId, cards[] с полями uuid, currency, isDefault, cardType?, panMask?, expirationMonth?, expirationYear?.

getOrder(publicId)

Получает заказ по публичному ID. Публичный эндпоинт, токен не используется.

Возвращает: Promise<OrderPublicResponse> — данные заказа и merchantName.

getStatus(publicId)

Получает статус заказа по публичному ID. Публичный эндпоинт.

Возвращает: Promise<OrderStatusResponse>status, publicId, providerOrderId?.

rebill(publicId, params)

Повторное списание по заказу. Требуется токен проекта.

Параметры:

  • publicId: string — публичный ID заказа.
  • params: RebillParams — обязательно:
    • amount: number — сумма списания (требуется провайдером).
    • options?: { recurring?: 0 | 1 }

Возвращает: Promise<ProviderOrderResponse>.

Пример:

const rebill = await client.rebill('ORDER_PUBLIC_ID', {
	amount: 100.5,
	options: { recurring: 1 },
});

credit(params)

Перевод на карту (OCT, POST /v1/orders/credit). Требуется токен проекта.

Параметры — union CreditParams:

  1. Полные реквизиты карты и клиента: projectPublicId, amount, currency, pan, card, client, custom_fields, опционально merchant_order_id.
  2. Сохранённая карта: projectPublicId, amount, currency, projectUserId, cardFingerprintUuid (остальное подставляет бэкенд из отпечатка и данных привязки).

Возвращает: Promise<CreateOrderResponse> — тот же формат, что после createOrder (publicId, status, paymentUrl, expiresAt и т.д.). Поле autoPay в ответе OCT не используется (итог операции смотрите по status и webhook).

Миграция с 1.1.x: раньше тип ответа был ProviderOrderResponse (сырой ответ провайдера). Теперь используйте поля заказа (publicId, status, …), а не result.orders?.[0].

Пример с полной картой:

const result = await client.credit({
	projectPublicId: 'ABC12XYZ',
	amount: 99.99,
	currency: ECurrencyCode.EUR,
	pan: '4111111111111111',
	card: {
		expiration_month: 12,
		expiration_year: 2030,
		holder: 'JOHN SMITH',
	},
	client: {
		name: 'JOHN SMITH',
		country: 'USA',
	},
	custom_fields: {
		recipient_birth_date: '1999-12-16',
		recipient_first_name: 'JOHN',
		recipient_last_name: 'SMITH',
	},
});

Пример по сохранённому отпечатку:

const result = await client.credit({
	projectPublicId: 'ABC12XYZ',
	amount: 99.99,
	currency: ECurrencyCode.EUR,
	projectUserId: 'user-42',
	cardFingerprintUuid: '550e8400-e29b-41d4-a716-446655440000',
});

cancel(publicId, params)

Отмена заказа: при статусе charged у провайдера выполняется refund, при authorizedreverse. Требуется токен проекта.

Параметры (CancelParams):

  • amount: numberобязательно. Сумма возврата/отмены.

Возвращает: Promise<ProviderOrderResponse>.

Пример:

const result = await client.cancel('ORDER_PUBLIC_ID', { amount: 50.0 });
console.log(result.orders?.[0]?.status);

Привязка карты и автоплатёж

Типичный сценарий:

  1. startCardAuth — получить paymentUrl, открыть пользователю оплату фиксированной суммы для привязки.
  2. После успешной оплаты — отпечаток карты доступен; при необходимости listCardFingerprints для выбора карты по uuid.
  3. createOrder с projectUserId и cardFingerprintUuid — автосписание без редиректа: в ответе будет autoPay; дождитесь order.charged / order.status_changed или опросите getStatus(publicId).

Без пары projectUserId + cardFingerprintUuid заказ создаётся как обычно — редирект на paymentUrl.


Webhook

Точка входа: @moneygatex/merchant-sdk/webhook.

MoneyGateX отправляет webhook POST-запросом на URL, указанный в настройках проекта. Обязательно возвращайте статус 200 и тело OK (текст/plain).

Заголовки и подпись

  • X-Webhook-Timestamp — Unix-время (секунды).
  • X-Webhook-Signature — подпись в формате v1,{hex}.

Подпись вычисляется так:

HMAC-SHA256(timestamp + "." + rawBody, webhookSecret)

Строка для подписи: {timestamp}.{rawBody} (сырое тело запроса без изменений). Результат — hex (64 символа).

verifyWebhookSignature

Проверка подписи вручную (Express, Koa, другой фреймворк):

import { verifyWebhookSignature } from '@moneygatex/merchant-sdk/webhook';

// rawBody — строка: сырое тело запроса (не парсенный JSON)
const isValid = verifyWebhookSignature(
	req.headers['x-webhook-timestamp'],
	req.headers['x-webhook-signature'],
	rawBody,
	process.env.WEBHOOK_SECRET!,
);

if (!isValid) {
	return res.status(401).send('Invalid signature');
}

const event = JSON.parse(rawBody);
if (event.event === 'order.charged') {
	// Обработка
}
res.status(200).send('OK');

Типы событий

order.charged — успешное списание по заказу.

import type {
	OrderChargedEvent,
	OrderChargedEventData,
} from '@moneygatex/merchant-sdk/webhook';

// OrderChargedEvent
{
	event: 'order.charged';
	data: {
		orderId: string;
		publicId: string;
		amount: number;
		amountCharged: number;
		currency: string;
		status: 'SUCCEEDED'; // в SDK: EOrderChargeStatus
		chargedAt: string; // ISO 8601
	}
}

order.status_changed — изменение статуса заказа (отмена/refund/reverse по API, перевод на карту — credit, истечение срока и т.д.). В панели управления в настройках Webhook можно отправить имитацию этого события — в data будет передан simulated: true.

import type { OrderStatusChangedEvent, OrderStatusChangedEventData } from '@moneygatex/merchant-sdk/webhook';

// event.event === 'order.status_changed'
{
  event: 'order.status_changed';
  data: {
    orderId: string;
    publicId: string;
    amount: number;
    currency: string;
    status: string;       // например 'CANCELED', 'REFUNDED'
    changedAt: string;    // ISO 8601
    reason: string | null;  // 'expired', 'refund', 'reverse', 'credit' и др.
    metadata?: Record<string, unknown> | null;  // опционально, передаётся при наличии
    simulated?: true;     // присутствует только при имитации из настроек
  };
}

Типы для этого события: OrderStatusChangedEvent, OrderStatusChangedEventData. Экспортируются также: OrderChargedEvent, OrderChargedEventData, WebhookEvent, EWebhookEvent.


Модуль NestJS

Точка входа: @moneygatex/merchant-sdk/nestjs.

Что даёт модуль

  • MerchantSdkService — инжектируемый сервис: createOrder, startCardAuth, listCardFingerprints, getOrder, getStatus, rebill, credit, cancel. Для projectPublicId в теле запросов действует та же подстановка из опций модуля, что и для createOrder.
  • MerchantWebhookGuard — guard для проверки подписи webhook. Вешается на ваш эндпоинт через @UseGuards(MerchantWebhookGuard).
  • MerchantWebhookBody — параметр-декоратор: извлекает и парсит тело webhook в типизированный WebhookEvent. Использовать в обработчике: handleWebhook(@MerchantWebhookBody() event: WebhookEvent).

Контроллер и путь эндпоинта вы создаёте сами; SDK только проверяет подпись и отдаёт распарсенные данные.

Raw body для webhook

Проверка подписи требует сырого тела запроса. В NestJS достаточно создать приложение с опцией rawBody: true и не подключать глобально express.json() — встроенный парсер Nest сохранит сырое тело в req.rawBody.

В main.ts:

import { NestFactory } from '@nestjs/core';
import * as express from 'express';

import { AppModule } from './app.module';

async function bootstrap() {
	// rawBody: true — Nest сохраняет сырое тело в req.rawBody (нужно для проверки подписи webhook)
	const app = await NestFactory.create(AppModule, { rawBody: true });
	// express.json() не подключать — иначе сырое тело будет потреблено и guard вернёт 401
	app.use(express.urlencoded({ extended: true }));
	// ...
	await app.listen(3000);
}

Если не включить rawBody: true или подключить app.use(express.json()), guard вернёт 401 «Missing raw body for webhook verification».

Экспорты из nestjs

  • MerchantSdkModule — модуль.
  • MerchantSdkService — сервис API.
  • MerchantWebhookGuard — guard проверки подписи webhook.
  • MerchantWebhookBody — декоратор параметра для тела webhook.
  • MerchantSdkModuleOptions — тип опций.
  • Типы с опциональным projectPublicId: CreateOrderParamsWithOptionalProjectId, StartCardAuthParamsWithOptionalProjectId, CreditParamsWithOptionalProjectId.

Типы и точки входа

| Подключение | Содержимое | | ---------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | @moneygatex/merchant-sdk | Клиент + webhook (без NestJS) | | @moneygatex/merchant-sdk/client | MerchantApiClient, MerchantApiError (поле body — разобранное тело ошибки API), типы заказов и OCT (CreditParams*, StartCardAuth*, CardFingerprint*, …), ECurrencyCode, EOrderStatus, MerchantApiErrorCode | | @moneygatex/merchant-sdk/webhook | verifyWebhookSignature, WebhookEvent, OrderChargedEvent, OrderChargedEventData, OrderStatusChangedEvent, OrderStatusChangedEventData, EWebhookEvent, EOrderChargeStatus | | @moneygatex/merchant-sdk/nestjs | MerchantSdkModule, MerchantSdkService, опциональные типы параметров с projectPublicId, MerchantWebhookGuard, MerchantWebhookBody, MerchantSdkModuleOptions |

Типы API: CreateOrderParams, CreateOrderResponse, MerchantOrderResponse, StartCardAuthParams, StartCardAuthResponse, CardFingerprintListResponse, OrderPublicResponse, OrderStatusResponse, RebillParams, CreditParams, CancelParams, ProviderOrderResponse (rebill/cancel).


Идемпотентность

Бэкенд поддерживает идемпотентность создания заказа через заголовок Idempotency-Key. При повторной отправке того же ключа в течение срока действия возвращается сохранённый ответ.

await client.createOrder(params, { idempotencyKey: 'unique-key-123' });

В NestJS:

await this.merchantSdk.createOrder(params, {
	idempotencyKey: 'unique-key-123',
});

Используйте уникальный ключ на операцию (например UUID или order-{merchantOrderId}).


Ошибки и отладка

MerchantApiError

При ошибках HTTP API клиент выбрасывает MerchantApiError (из @moneygatex/merchant-sdk/client):

import {
	MerchantApiClient,
	MerchantApiError,
} from '@moneygatex/merchant-sdk/client';

try {
	await client.createOrder(params);
} catch (err) {
	if (err instanceof MerchantApiError) {
		console.error(err.statusCode, err.message);
	}
	throw err;
}
  • 401 — неверный или отсутствующий токен проекта.
  • 4xx/5xx — тело ответа API передаётся в message; при JSON в формате API также доступно err.body (MerchantApiErrorBody).

Webhook

  • Invalid signature (401) — неверный webhookSecret, изменённое тело запроса или не тот raw body (например парсенный JSON вместо строки).
  • Убедитесь, что ответ на webhook: статус 200, тело строка OK.

Рекомендации

  • Храните baseUrl, projectToken и webhookSecret в переменных окружения или секретах.
  • В логах не выводите токен и секрет целиком.
  • Для отладки webhook можно логировать заголовки (без подписи) и длину тела.

Лицензия

UNLICENSED (проприетарный пакет).