RMT.GG/وثائق مطور البائع
v1

واجهة برمجة التطبيقات للبائع

قم بأتمتة القوائم، تنفيذ المبيعات، وتدفق أحداث الطلب. تشمل الويب هوكس الخارجة ونقاط النهاية الاحتياطية لتجديد المخزون عند الطلب بعد الدفع.

واجهة برمجة التطبيقات المفتوحة REST

مصرح بها باستخدام Bearer /api/v1 للعروض والطلبات، مع رؤوس اكتشاف وحدود معدل.

الويب هوكس الخارجة

تسليمات HTTPS (أو Discord) الموقعة لأحداث دورة حياة الطلب والعرض.

احجز / أعد التعبئة

اصنع مخزون COMPLEX من خادمك بعد الدفع عندما يكون المخزون المحلي قليل.

ما يمكنك بناؤه

واجهة برمجة التطبيقات المفتوحة للبائعين مخصصة للبائعين الذين يرغبون في تنبيهات Discord، مزامنة المخزون، أتمتة على طراز Zapier، أو مكتب خلفي مخصص فوق RMT.GG.

  • إدارة العروض
    قم بإنشاء مسودات، تحديث الحقول الآمنة، نشر، وأرشفة عبر /api/v1/offers.
  • تنفيذ المبيعات
    قم بإدراج وفحص طلبات البائع، ثم حددها كتم التسليم مع روابط الأدلة الاختيارية.
  • ابق تحت الحد
    كل مفتاح محدود إلى 300 طلبات في الدقيقة. تتضمن الاستجابات رؤوس X-RateLimit-*.
  • تفاعل في الوقت الحقيقي
    اشترك في أحداث الطلب والعرض، أو أعد تعبئة المخزون COMPLEX باستخدام الويب هوكس الاحتياطية.
  • استقبل المدفوعات من متجرك
    يمكن للشركاء المعتمدين إرسال المشترين من متجر خارجي إلى صفحة الدفع المستضافة، ثم تنفيذ الطلب المدفوع.

بدء سريع

قم بإنشاء مفتاح API في إعدادات المطور، ثم استخدم الاستكشاف لطباعة الكتالوج المباشر.

  1. 1افتح الإعدادات → المطور (لا توجد خطوة تمكين منفصلة).
  2. 2أنشئ مفتاح واجهة برمجة التطبيقات وانسخ السر مرة واحدة (rmt_sk_live_…). احفظه في مدير الأسرار الخاص بك.
  3. 3اتصل بـ GET /api/v1 مع Authorization: Bearer لتأكيد النطاقات، الحصص، والعمليات.
GET/api/v1

وثيقة الاكتشاف

تعيد النطاقات، الحصص، أحداث الويب هوكس، وكatalog العمليات الكامل. أي مفتاح واجهة برمجة تطبيقات صالح يعمل.

المصادقة

أرسل مفتاحك السري المباشر في كل طلب /api/v1. يفضل استخدام HTTPS فقط. لا تقم أبدًا بتضمين المفاتيح في العملاء العامة أو حزم المتصفح.

الرأس المفضل

http
Authorization: Bearer rmt_sk_live_<prefix>_<secret>

رأس بديل

http
X-Api-Key: rmt_sk_live_<prefix>_<secret>

عميل TypeScript قابل لإعادة الاستخدام (مصادقة Bearer، أخطاء محددة، إعادة المحاولة 429)

typescript
const API_BASE = "https://rmt.gg/api/v1";
const API_KEY = process.env.RMT_API_KEY!; // rmt_sk_live_…

export class RmtApiError extends Error {
  constructor(
    readonly status: number,
    readonly code: string | undefined,
    message: string,
    readonly retryAfterSec?: number,
  ) {
    super(message);
    this.name = "RmtApiError";
  }
}

type RmtFetchInit = RequestInit & { idempotencyKey?: string };

export async function rmtFetch<T>(path: string, init: RmtFetchInit = {}): Promise<T> {
  const headers = new Headers(init.headers);
  headers.set("Authorization", `Bearer ${API_KEY}`);
  // Alternate: headers.set("X-Api-Key", API_KEY);
  headers.set("Accept", "application/json");
  if (init.body && !headers.has("Content-Type")) {
    headers.set("Content-Type", "application/json");
  }
  if (init.idempotencyKey) headers.set("Idempotency-Key", init.idempotencyKey);

  const res = await fetch(`${API_BASE}${path}`, { ...init, headers });
  const retryAfter = Number(res.headers.get("Retry-After") ?? "");
  const body = (await res.json().catch(() => ({}))) as {
    error?: string;
    code?: string;
    retryAfter?: number;
  };

  if (res.status === 429) {
    throw new RmtApiError(
      429,
      body.code ?? "RATE_LIMITED",
      body.error ?? "Rate limited",
      Number.isFinite(retryAfter) ? retryAfter : body.retryAfter,
    );
  }
  if (!res.ok) {
    throw new RmtApiError(res.status, body.code, body.error ?? res.statusText);
  }
  return body as T;
}

export async function withRetry<T>(fn: () => Promise<T>, maxAttempts = 4): Promise<T> {
  let attempt = 0;
  for (;;) {
    try {
      return await fn();
    } catch (err) {
      attempt += 1;
      if (!(err instanceof RmtApiError) || err.status !== 429 || attempt >= maxAttempts) {
        throw err;
      }
      const waitSec = Math.max(1, err.retryAfterSec ?? 1);
      await new Promise((r) => setTimeout(r, waitSec * 1000));
    }
  }
}

قم بتدوير عند التسرب

إذا تسرب مفتاح، قم بإلغائه في إعدادات المطور وأنشئ واحدًا جديدًا. قم بتحديث الأتمتة الخاصة بك قبل الإلغاء إذا كنت مباشرًا.

نطاقات

كل مفتاح واجهة برمجة التطبيقات يحمل نطاقات تحدد نقاط النهاية. النطاق المفقود يعيد 403 SCOPE_MISSING.

offers:read
offers:write
orders:read
orders:write
webhooks:manage
checkout:write
  • offers:read: قائمة والحصول على عروضك.
  • offers:write: إنشاء، تحديث، نشر، وحذف العروض.
  • orders:read: قائمة والحصول على طلبات البائع.
  • orders:write: تحديد الطلبات كتم التسليم.
  • webhooks:manage: محجوز لإدارة واجهة برمجة التطبيقات المفتوحة المستقبلية. قم بتكوين Discord/Telegram في الإشعارات و JSON webhooks في إعدادات المطور اليوم.
  • checkout:write: إنشاء وقراءة جلسات الدفع المستضاف. يتطلب موافقة الإدارة على شراكة الدفع.

نطاقات المفتاح الافتراضية

تتلقى المفاتيح الجديدة offers:read، offers:write، orders:read، وorders:write. تظل CRUD الويب هوكس الخارجة في واجهة إعدادات (مصادقة الجلسة).

واجهة برمجة التطبيقات للعروض

معرفات العروض تقبل شريحة URL العامة أو المعرف الرقمي. تستبعد الاستجابات المعرف الداخلي وsellerId.

ما لا يمكن تغييره بعد باستخدام PATCH

تُدار صفوف المخزون، أسعار الخيارات، الوسائط، والسمات في محرر البائع (أو نقاط النهاية المستقبلية)، وليس عبر PATCH اليوم.

GET/api/v1/offers
offers:read

قائمة عروضك

قم بتصفية باستخدام archive=active (افتراضي)، archived، أو الكل.

الطلب

  • archive
    في
    query
    النوع
    string
    مطلوب
    اختياري
    الوصف
    One of "active" (default), "archived", or "all".
  • Response: { offers: Offer[], total: number }. Numeric id and sellerId are omitted.
POST/api/v1/offers
offers:write

إنشاء عرض مسودة

ينشئ مسودة فارغة مملوكة للبائع المعتمد. لا يتطلب جسم.

  • No request body required.
  • Response 201: { offer: Offer }.
GET/api/v1/offers/:urlOrId
offers:read

الحصول على عرض واحد

قم بالتحميل بواسطة شريحة URL العامة أو المعرف الرقمي. قد يتم تضمين العلاقات (الخيارات)؛ العناصر المخزنة ليست كذلك.

الطلب

  • urlOrId
    في
    path
    النوع
    string
    مطلوب
    مطلوب
    الوصف
    Offer.url slug or Offer.id.
  • Returns relations (options, etc.) when available; items are not included.
PATCH/api/v1/offers/:urlOrId
offers:write

تحديث حقول العرض

قم بتحديث مجموعة آمنة من حقول الإدراج. يصدر offer.updated عند تكوين الويب هوكس الخارجة.

الطلب

  • urlOrId
    في
    path
    النوع
    string
    مطلوب
    مطلوب
    الوصف
    Offer.url slug or Offer.id.
  • title
    في
    body
    النوع
    string
    مطلوب
    اختياري
    الوصف
    Listing title.
  • description
    في
    body
    النوع
    string
    مطلوب
    اختياري
    الوصف
    Listing description.
  • visibility
    في
    body
    النوع
    string
    مطلوب
    اختياري
    الوصف
    PUBLIC | PRIVATE | UNPUBLISHED.
  • categoryId
    في
    body
    النوع
    number
    مطلوب
    اختياري
    الوصف
    Catalog category id.
  • offeringId
    في
    body
    النوع
    number
    مطلوب
    اختياري
    الوصف
    Catalog offering id.
  • thumbnail
    في
    body
    النوع
    string
    مطلوب
    اختياري
    الوصف
    Thumbnail URL or asset reference.
  • offerType
    في
    body
    النوع
    string
    مطلوب
    اختياري
    الوصف
    Offer type string used by the listing.
  • listingMode
    في
    body
    النوع
    string
    مطلوب
    اختياري
    الوصف
    Listing mode (for example STANDARD, RANK_BOOST, SESSION).
  • At least one allowed field is required.
  • Emits offer.updated webhook when configured.
  • Stock is managed via GET/POST /api/v1/offers/:urlOrId/stock. Option prices, media, and attributes are not editable via this endpoint yet.
DELETE/api/v1/offers/:urlOrId
offers:write

حذف أو أرشفة

نفس قواعد الحذف / الأرشفة مثل واجهة مستخدم البائع.

الطلب

  • urlOrId
    في
    path
    النوع
    string
    مطلوب
    مطلوب
    الوصف
    Offer.url slug or Offer.id.
  • Response: { ok: true }.
POST/api/v1/offers/:urlOrId/publish
offers:write

نشر عرض

ينشر مسودة (أو يغير الرؤية). يفشل مع 400 إذا كانت حقول الإدراج المطلوبة غير مكتملة.

الطلب

  • urlOrId
    في
    path
    النوع
    string
    مطلوب
    مطلوب
    الوصف
    Offer.url slug or Offer.id.
  • visibility
    في
    body
    النوع
    string
    مطلوب
    اختياري
    الوصف
    Optional. PUBLIC (default), PRIVATE, or UNPUBLISHED.
  • Response: { offer: Offer }.
  • Fails if the listing is incomplete for publish.

واجهة برمجة تطبيقات المخزون

شاهد الكميات القابلة للشراء لكل مستوى، ووافق أسماء حقول التسليم مع القائمة الصحيحة، ثم أعد تعبئة الكمية أو المفاتيح والحسابات المحفوظة.

كيف يعمل المطابقة

GET /api/v1/stock?fields=username,password يعثر على القوائم التي تحتوي على تلك الحقول. أعد التعبئة باستخدام أسماء الخيارات (أو optionId) وأسماء الحقول. لا تحتاج إلى معرفات الحقول الداخلية. لا تتضمن الاستجابات أبداً قيم الاعتماد.

GET/api/v1/stock
offers:read

قائمة المخزون عبر قوائمك

يعود بعدد الكميات لكل مستوى وأسماء حقول التسليم حتى تتمكن من مطابقة المفاتيح والحسابات مع العرض الصحيح. قم بالتصفية باستخدام q و fields و stockMode و lowStock. لا تعيد أبداً قيم الاعتماد.

الطلب

  • q
    في
    query
    النوع
    string
    مطلوب
    اختياري
    الحدود
    Max 80
    الوصف
    Filter by listing title or url slug.
  • fields
    في
    query
    النوع
    string
    مطلوب
    اختياري
    الوصف
    Comma-separated delivery field names. The listing must have all of them (Username,Password). Names match case-insensitively.
  • stockMode
    في
    query
    النوع
    string
    مطلوب
    اختياري
    الوصف
    QUANTITY or COMPLEX. Listing must have at least one option in that mode.
  • lowStock
    في
    query
    النوع
    number
    مطلوب
    اختياري
    الوصف
    Keep listings that have a finite tier with available less than or equal to this number.
  • archive
    في
    query
    النوع
    string
    مطلوب
    اختياري
    الوصف
    One of "active" (default), "archived", or "all".
  • Response: { offers: StockOffer[], total: number }. Numeric offer id is omitted. Option id is included so you can restock a specific tier.
  • available is the buyable count. null with unlimited true means unlimited quantity or on-demand COMPLEX inventory.
  • fields[] is the listing delivery schema (empty for quantity-only listings). Use it to map keys and accounts without field ids.
  • This endpoint never returns credential values.
GET/api/v1/offers/:urlOrId/stock
offers:read

احصل على المخزون لقائمة واحدة

نفس شكل StockOffer كما في الفهرس، لعنوان URL واحد أو معرف رقمي. العد فقط.

الطلب

  • urlOrId
    في
    path
    النوع
    string
    مطلوب
    مطلوب
    الوصف
    Offer.url slug or Offer.id.
  • Response: { offer } with the same StockOffer shape as GET /api/v1/stock.
  • Counts only. Use the seller editor to inspect saved key values.
POST/api/v1/offers/:urlOrId/stock
offers:write

إعادة تعبئة قائمة

مستويات الكمية: إضافة، إزالة، أو تعيين. مستويات العناصر المحفوظة: كائنات العناصر حسب اسم الحقل، keys[] عندما يكون هناك حقل واحد، أو نص مفصول. عدة مستويات في مكالمة واحدة عبر options[]. التشغيل التجريبي يعرض المطابقة. onDuplicate الافتراضي هو التخطي.

json
{
  "option": "1 Month",
  "add": 50
}

الطلب

  • urlOrId
    في
    path
    النوع
    string
    مطلوب
    مطلوب
    الوصف
    Offer.url slug or Offer.id.
  • option
    في
    body
    النوع
    string
    مطلوب
    شرط
    الوصف
    Pricing option name (case-insensitive). Omit when the listing has a single tier.
  • optionId
    في
    body
    النوع
    number
    مطلوب
    شرط
    الوصف
    Pricing option id from GET stock. Wins over option when both are sent. Ambiguous names return 409 OPTION_AMBIGUOUS.
  • add
    في
    body
    النوع
    number
    مطلوب
    شرط
    الحدود
    1-1,000,000
    الوصف
    QUANTITY: add this many units. Fails with 400 UNLIMITED_STOCK if the tier is unlimited.
  • remove
    في
    body
    النوع
    number
    مطلوب
    شرط
    الحدود
    1-1,000,000
    الوصف
    QUANTITY: withdraw this many units. Fails with 400 INSUFFICIENT_STOCK when there is not enough.
  • set
    في
    body
    النوع
    number | null
    مطلوب
    شرط
    الوصف
    QUANTITY: set an absolute count. null means unlimited. Cannot go below units held in checkout.
  • items
    في
    body
    النوع
    object[]
    مطلوب
    شرط
    الحدود
    Max 1,000
    الوصف
    COMPLEX: objects keyed by delivery field name, for example { "Username": "a", "Password": "b" }. Names match case-insensitively.
  • keys
    في
    body
    النوع
    string[]
    مطلوب
    شرط
    الحدود
    Max 1,000
    الوصف
    COMPLEX: license keys when the listing has exactly one delivery field. Otherwise 400 FIELD_MAPPING_AMBIGUOUS.
  • text
    في
    body
    النوع
    string
    مطلوب
    شرط
    الحدود
    Max 1,000 rows
    الوصف
    COMPLEX: delimited paste. A header row that matches field names is detected automatically. Otherwise columns map in field sort order when the column count matches.
  • delimiter
    في
    body
    النوع
    string
    مطلوب
    اختياري
    الحدود
    Default :
    الوصف
    Delimiter for text. Ignored unless text is sent.
  • headers
    في
    body
    النوع
    string[]
    مطلوب
    اختياري
    الوصف
    Optional column headers for text when the first line is data, not names.
  • options
    في
    body
    النوع
    object[]
    مطلوب
    شرط
    الوصف
    Restock several tiers in one call. Each element is the same shape as a single-option body (option, add, items, …).
  • dryRun
    في
    body
    النوع
    boolean
    مطلوب
    اختياري
    الوصف
    Preview matching and counts without writing. Default false.
  • onDuplicate
    في
    body
    النوع
    string
    مطلوب
    اختياري
    الحدود
    skip (default) or error
    الوصف
    COMPLEX: skip existing unsold fingerprints, or fail the request with 409 DUPLICATE_ITEMS.
  • Send exactly one action per option: add, remove, set, items, keys, or text.
  • Sending items to a QUANTITY tier (or add to a COMPLEX tier) returns 400 STOCK_MODE_MISMATCH.
  • Responses never echo credential values. COMPLEX results include imported, skippedDuplicates, errors, and matchedFields.
  • Saved items are capped at 5,000 unsold rows per option. A single request may import at most 1,000 rows.

استيراد العديد من الحسابات أو المفاتيح

دليل

استخدم POST /api/v1/offers/:url/stock مع items[] للحسابات أو keys[] لرموز الترخيص ذات الحقل الواحد. قسم إلى 1,000 صف لكل طلب.

  1. 1احصل على القائمة. استخدم fields[] و stockMode لاختيار العناصر، المفاتيح، أو الإضافة.
  2. 2احفظ الحسابات بصيغة JSON أو CSV مع المفاتيح حسب اسم الحقل. احفظ مفاتيح الترخيص سطرًا واحدًا لكل مفتاح.
  3. 3قم بتشغيل تجريبي أولاً. تحقق من wouldImport و skippedDuplicates و matchedFields.
  4. 4أعد إرسال نفس المحتوى مرة أخرى بدون dryRun لكتابة المخزون.

اختر الحمولة التي تتطابق مع العرض

قم باستدعاء GET stock أولاً. إذا كان fields[] يحتوي على أكثر من اسم واحد، أرسل كائنات items مفاتيحها تلك الأسماء (اسم المستخدم، كلمة المرور، البريد الإلكتروني). إذا كان هناك حقل واحد بالضبط، فإن keys[] تكفي. قوائم الكمية تستخدم add، وليس items.

1,000 صف لكل طلب. 5,000 عنصر غير مبيع لكل مستوى. 300 طلب في الدقيقة. يتم تخطي التكرارات بشكل افتراضي.

accounts.json (كائن واحد لكل حساب)

json
[
  { "Username": "player1", "Password": "secret1", "E-Mail": "[email protected]" },
  { "Username": "player2", "Password": "secret2", "E-Mail": "[email protected]" }
]

accounts.csv

csv
Username,Password,E-Mail
player1,secret1,p1@example.com
player2,secret2,p2@example.com

keys.txt (مفتاح ترخيص واحد لكل سطر)

text
AAAA-BBBB-CCCC
DDDD-EEEE-FFFF
GGGG-HHHH-IIII

cURL

bash
# Inspect field names and stockMode
curl -s -H "Authorization: Bearer rmt_sk_live_…" \
  -H "Accept: application/json" \
  https://rmt.gg/api/v1/offers/YOUR_OFFER_URL/stock

# Preview (no write)
curl -s -X POST \
  -H "Authorization: Bearer rmt_sk_live_…" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  https://rmt.gg/api/v1/offers/YOUR_OFFER_URL/stock \
  -d '{"dryRun":true,"onDuplicate":"skip","option":"Premium","items":[{"Username":"player1","Password":"secret1","E-Mail":"[email protected]"}]}'

# Apply accounts
curl -s -X POST \
  -H "Authorization: Bearer rmt_sk_live_…" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  https://rmt.gg/api/v1/offers/YOUR_OFFER_URL/stock \
  -d '{"onDuplicate":"skip","option":"Premium","items":[{"Username":"player1","Password":"secret1","E-Mail":"[email protected]"}]}'

# Apply license keys (listing must have exactly one delivery field)
curl -s -X POST \
  -H "Authorization: Bearer rmt_sk_live_…" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  https://rmt.gg/api/v1/offers/YOUR_OFFER_URL/stock \
  -d '{"option":"Steam","keys":["AAAA-BBBB-CCCC","DDDD-EEEE-FFFF"]}'

# Or paste CSV / colon-separated rows in text
curl -s -X POST \
  -H "Authorization: Bearer rmt_sk_live_…" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  https://rmt.gg/api/v1/offers/YOUR_OFFER_URL/stock \
  -d '{"option":"Premium","delimiter":",","text":"Username,Password,E-Mail\nplayer1,secret1,[email protected]"}'

استيراد دفعة TypeScript (1,000 صف لكل طلب)

typescript
// Paste rmtFetch and withRetry from the Auth section first.
const CHUNK = 1000;

async function importRows(offerUrl: string, option: string, rows: Array<Record<string, string>>) {
  for (let i = 0; i < rows.length; i += CHUNK) {
    const items = rows.slice(i, i + CHUNK);
    const preview = await rmtFetch<{
      results: Array<{ wouldImport: number; skippedDuplicates: number; errors: string[] }>;
    }>(`/offers/${offerUrl}/stock`, {
      method: "POST",
      body: JSON.stringify({ dryRun: true, onDuplicate: "skip", option, items }),
    });
    const row = preview.results[0];
    if ((row?.errors?.length ?? 0) > 0) {
      throw new Error(row.errors.join("; "));
    }
    await withRetry(() =>
      rmtFetch(`/offers/${offerUrl}/stock`, {
        method: "POST",
        body: JSON.stringify({ onDuplicate: "skip", option, items }),
      }),
    );
  }
}

// License keys: only when GET stock.fields has exactly one name
async function importKeys(offerUrl: string, option: string, keys: string[]) {
  for (let i = 0; i < keys.length; i += CHUNK) {
    await withRetry(() =>
      rmtFetch(`/offers/${offerUrl}/stock`, {
        method: "POST",
        body: JSON.stringify({ option, keys: keys.slice(i, i + CHUNK) }),
      }),
    );
  }
}

ما تحميه هذه الواجهة البرمجية

تحتاج المفاتيح إلى offers:write، وهي محدودة بمعدل، ويمكنها فقط إعادة تخزين عروضك الخاصة. GET لا تعيد أبدًا بيانات الاعتماد المحفوظة. استجابات POST لا تعكس قيم اسم المستخدم، كلمة المرور، أو المفاتيح. أرسل الجسم عبر HTTPS في الإنتاج واحتفظ بمفتاح الواجهة البرمجية في متغير بيئي.

على نظام ويندوز، استخدم curl.exe (ليس بديل curl). اقتبس -d JSON حتى لا يقوم PowerShell بتقسيمه.

واجهة برمجة التطبيقات للطلبات

الطلبات محددة لحساب البائع الخاص بك. قد يتم حذف تفاصيل فواتير المشتري بموجب قواعد خصوصية سوق السجلات.

GET/api/v1/orders
orders:read

قائمة طلبات البائع

يدعم limit، offset، status، q، وsort (الأحدث، الأقدم، total_high، total_low).

الطلب

  • limit
    في
    query
    النوع
    number
    مطلوب
    اختياري
    الحدود
    1-100, default 20
    الوصف
    Page size.
  • offset
    في
    query
    النوع
    number
    مطلوب
    اختياري
    الحدود
    >= 0, default 0
    الوصف
    Skip this many rows.
  • status
    في
    query
    النوع
    string
    مطلوب
    اختياري
    الحدود
    Max 32
    الوصف
    Filter by order status (for example PAID, DELIVERED, COMPLETED).
  • q
    في
    query
    النوع
    string
    مطلوب
    اختياري
    الحدود
    Max 80
    الوصف
    Search reference or related text.
  • sort
    في
    query
    النوع
    string
    مطلوب
    اختياري
    الحدود
    newest (default)
    الوصف
    newest | oldest | total_high | total_low.
  • Response: { orders: Order[], total: number }.
GET/api/v1/orders/:uid
orders:read

الحصول على طلب واحد

تعيد الطلب مع عناصر الخط. استخدم uid الطلب العام.

الطلب

  • uid
    في
    path
    النوع
    string
    مطلوب
    مطلوب
    الوصف
    Order.uid.
  • Response: { order } with line items.
  • Buyer billing fields may be redacted under marketplace-of-record privacy rules.
POST/api/v1/orders/:uid/deliver
orders:write

تحديد كتم التسليم

تنفيذ يدوي. يجب أن تكون خطوط COMPLEX مرتبطة بالكامل عند الطلب. يصدر order.delivered.

الطلب

  • uid
    في
    path
    النوع
    string
    مطلوب
    مطلوب
    الوصف
    Order.uid.
  • evidence
    في
    body
    النوع
    string[]
    مطلوب
    اختياري
    الحدود
    HTTPS, max 10
    الوصف
    Optional screenshot or transfer-proof URLs.
  • Response: { success: true, order }.
  • COMPLEX inventory lines must be fully attached before deliver when the product requires it.
  • Emits order.delivered webhook when configured.

الدفع المستضاف

يمكن لأي متجر معتمد أو نظام خلفي إرسال المشترين إلى صفحة الدفع الخاصة بـ RMT.GG. نحن نبقى التاجر المسجل ونأخذ 4% من المبلغ المقفل.

القائمة المسموح بها والتنفيذ

قم بالتطبيق تحت الإعدادات، الدفع المستضاف، ثم أنشئ مفتاح API وويب هوك JSON هناك. بعد الدفع، نقوم بإصدار checkout.completed. تبقى قيم التسليم على تأكيد RMT.GG؛ فهي ليست في GET البائع أو الويب هوكس.

POST/api/v1/checkout/sessions
checkout:write

إنشاء جلسة دفع مستضاف

قم بإرسال المشترين إلى صفحة دفع مؤمنة على RMT.GG. عنصر واحد: المبلغ و itemName. السلة: items[] مع الاسم والمبلغ في كل سطر. العملة الافتراضية هي الدولار الأمريكي. بعد الدفع، يبقى المشتري على RMT.GG عندما تكون هناك حقول تسليم للنسخ. returnUrl يستمر إلى المتجر؛ بدون تسليم، نعيدهم بعد عد تنازلي قصير. المبلغ، الأطوال، والحدود الأخرى موجودة في عمود الحدود.

javascript
{
  amount:   10,           // what the buyer pays
  itemName: "Gold pack",  // pay page heading
}

الطلب

  • amount
    في
    body
    النوع
    number
    مطلوب
    شرط
    الحدود
    > 0, max 1,000,000
    الوصف
    What the buyer pays. Required for a single item. With items[], omit it or send the line sum. Mismatch: 400 AMOUNT_MISMATCH.
  • currency
    في
    body
    النوع
    string
    مطلوب
    اختياري
    الحدود
    Default USD
    الوصف
    ISO 4217 code such as USD or EUR.
  • itemName
    في
    body
    النوع
    string
    مطلوب
    شرط
    الحدود
    Max 120
    الوصف
    Pay page heading. Required for a single item. Alias: title. With items[], defaults to the first line name. Missing: 400 ITEM_NAME_REQUIRED.
  • title
    في
    body
    النوع
    string
    مطلوب
    اختياري
    الوصف
    Alias of itemName. If both are sent, itemName wins.
  • description
    في
    body
    النوع
    string
    مطلوب
    اختياري
    الحدود
    Max 200
    الوصف
    Copy under the heading. If omitted, the heading is reused.
  • imageUrl
    في
    body
    النوع
    string
    مطلوب
    اختياري
    الحدود
    HTTPS, max 2048
    الوصف
    Product image, or fallback for lines without imageUrl. Invalid: 400 INVALID_IMAGE_URL.
  • items
    في
    body
    النوع
    object[]
    مطلوب
    شرط
    الحدود
    1-20 lines, JSON max 48,000
    الوصف
    Locked cart. Required when amount is omitted. Buyers cannot change lines. Empty: 400 INVALID_ITEMS.
  • items[].name
    في
    body
    النوع
    string
    مطلوب
    مطلوب
    الحدود
    Max 120
    الوصف
    Line title. Alias: title.
  • items[].title
    في
    body
    النوع
    string
    مطلوب
    اختياري
    الوصف
    Alias of items[].name. If both are sent, name wins.
  • items[].description
    في
    body
    النوع
    string
    مطلوب
    اختياري
    الحدود
    Max 200
    الوصف
    Line copy under the name.
  • items[].amount
    في
    body
    النوع
    number
    مطلوب
    مطلوب
    الحدود
    > 0, max 1,000,000
    الوصف
    Unit price. Session total is sum(amount * quantity).
  • items[].quantity
    في
    body
    النوع
    number
    مطلوب
    اختياري
    الحدود
    1-99, default 1
    الوصف
    Locked on the pay page.
  • items[].imageUrl
    في
    body
    النوع
    string
    مطلوب
    اختياري
    الحدود
    HTTPS, max 2048
    الوصف
    Line image. Falls back to top-level imageUrl.
  • items[].delivery
    في
    body
    النوع
    object[]
    مطلوب
    اختياري
    الحدود
    Max 16 fields
    الوصف
    Shown after payment on RMT.GG. Seller GET and webhooks omit values.
  • items[].delivery[].name
    في
    body
    النوع
    string
    مطلوب
    مطلوب
    الحدود
    Max 80
    الوصف
    Field label, for example Code or Password.
  • items[].delivery[].type
    في
    body
    النوع
    string
    مطلوب
    اختياري
    الحدود
    text, password, textarea
    الوصف
    password is blurred until the buyer reveals it. Default text.
  • items[].delivery[].value
    في
    body
    النوع
    string
    مطلوب
    مطلوب
    الحدود
    Max 2048
    الوصف
    Field value. Numbers are stored as strings. Empty: 400 INVALID_DELIVERY.
  • email
    في
    body
    النوع
    string
    مطلوب
    اختياري
    الحدود
    Invalid values ignored
    الوصف
    Prefills the pay page. The buyer still confirms email before paying.
  • returnUrl
    في
    body
    النوع
    string
    مطلوب
    اختياري
    الحدود
    HTTPS, max 2048
    الوصف
    Continue-to-shop after payment. Delivery fields keep the buyer on RMT.GG with a button. No delivery: we send them back after a short countdown. If omitted, there is no shop button. http://localhost is allowed for local shops.
  • cancelUrl
    في
    body
    النوع
    string
    مطلوب
    اختياري
    الحدود
    HTTPS, max 2048
    الوصف
    Redirect if the buyer cancels or the session expires. If omitted, they stay on the pay page.
  • invoiceId
    في
    body
    النوع
    string
    مطلوب
    اختياري
    الحدود
    Max 128
    الوصف
    Your shop id. Same payload returns the existing session. A different payload: 409 INVOICE_CONFLICT.
  • categorySlug
    في
    body
    النوع
    string
    مطلوب
    شرط
    الحدود
    With offering, or omit both
    الوصف
    Public root slug such as games. Used for card, PayPal, and crypto labels, not the pay page title. Wrong pair: 400 INVALID_PSP_CATEGORY.
  • offering
    في
    body
    النوع
    string
    مطلوب
    شرط
    الحدود
    With categorySlug, or omit both
    الوصف
    Catalog offering such as Mods. Mapped to labels like Games · Add-ons.
  • metadata
    في
    body
    النوع
    object
    مطلوب
    اختياري
    الحدود
    Object, max 4096 chars
    الوصف
    Stored on the session. Not returned on seller GET.
  • Idempotency-Key
    في
    header
    النوع
    string
    مطلوب
    اختياري
    الحدود
    Max 128
    الوصف
    Replay header. Same key and payload returns the existing session. A different payload: 409 IDEMPOTENCY_CONFLICT.

الاستجابة

  • uid
    في
    response
    النوع
    string
    الوصف
    Session id. Same value as in hostedUrl / hosted_url.
  • status
    في
    response
    النوع
    string
    الوصف
    created | pending_payment | paid | canceled | expired | refunded | error. Unpaid sessions expire after 24 hours.
  • paid
    في
    response
    النوع
    boolean
    الوصف
    true only when status is paid. false for refunded, expired, canceled, and unpaid states.
  • amount
    في
    response
    النوع
    number
    الوصف
    Locked buyer total in major units.
  • currency
    في
    response
    النوع
    string
    الوصف
    ISO currency code stored on the session (for example USD).
  • itemName
    في
    response
    النوع
    string | null
    الوصف
    Pay page heading.
  • description
    في
    response
    النوع
    string
    الوصف
    Longer copy under the heading.
  • email
    في
    response
    النوع
    string | null
    الوصف
    Prefill or confirmed buyer email. Guest checkout placeholders are returned as null.
  • lang
    في
    response
    النوع
    string | null
    الوصف
    Buyer locale when known. Not a create-session field.
  • returnUrl
    في
    response
    النوع
    string | null
    الوصف
    Continue-to-shop URL stored on the session, or null.
  • cancelUrl
    في
    response
    النوع
    string | null
    الوصف
    Cancel/expiry redirect, or null.
  • invoiceId
    في
    response
    النوع
    string | null
    الوصف
    Your invoice id. Same value as externalInvoiceId.
  • externalInvoiceId
    في
    response
    النوع
    string | null
    الوصف
    Same as invoiceId (legacy alias).
  • source
    في
    response
    النوع
    string
    الوصف
    How the session was created. API sessions are "api".
  • expiresAt
    في
    response
    النوع
    string
    الوصف
    ISO timestamp. Unpaid checkouts cannot be completed after this time.
  • hostedUrl
    في
    response
    النوع
    string
    الوصف
    Pay page URL (same target as top-level hosted_url on create).
  • orderUid
    في
    response
    النوع
    string | null
    الوصف
    Marketplace order uid after payment. null until the session is paid.
  • items
    في
    response
    النوع
    object[]
    الوصف
    Locked lines: name, description, amount, quantity, imageUrl. Seller GET never includes delivery values.
  • hosted_url
    في
    response
    النوع
    string
    الوصف
    Pay page URL. Send the buyer here. Same target as hostedUrl.
  • expires_at
    في
    response
    النوع
    string
    الوصف
    ISO timestamp. Same value as expiresAt.
  • Approved partners only. Platform fee is 4% of the locked amount. Payment-method costs are absorbed by the platform.
  • After payment the buyer stays on RMT.GG so they can copy delivery fields. returnUrl is a continue button when delivery is present. With no delivery fields we send them back after a short countdown. If you omit them, they stay on the pay page after payment, cancel, or expiry.
  • Fulfill on checkout.completed. Sessions expire after 24 hours. Buyers cannot change line items. imageUrl must be HTTPS. categorySlug and offering must be sent together (or omit both); a wrong pair returns 400 INVALID_PSP_CATEGORY.
GET/api/v1/checkout/sessions/:uid
checkout:write

الحصول على جلسة دفع مستضاف

يُرجع الجلسة التي أنشأتها. استخدم هذا إذا تم تأخير checkout.completed. paid يكون صحيحًا فقط عندما تكون الحالة مدفوعة. العناصر لا تتضمن أبدًا قيم التسليم.

الطلب

  • uid
    في
    path
    النوع
    string
    مطلوب
    مطلوب
    الوصف
    Session uid returned at create time.

الاستجابة

  • uid
    في
    response
    النوع
    string
    الوصف
    Session id. Same value as in hostedUrl / hosted_url.
  • status
    في
    response
    النوع
    string
    الوصف
    created | pending_payment | paid | canceled | expired | refunded | error. Unpaid sessions expire after 24 hours.
  • paid
    في
    response
    النوع
    boolean
    الوصف
    true only when status is paid. false for refunded, expired, canceled, and unpaid states.
  • amount
    في
    response
    النوع
    number
    الوصف
    Locked buyer total in major units.
  • currency
    في
    response
    النوع
    string
    الوصف
    ISO currency code stored on the session (for example USD).
  • itemName
    في
    response
    النوع
    string | null
    الوصف
    Pay page heading.
  • description
    في
    response
    النوع
    string
    الوصف
    Longer copy under the heading.
  • email
    في
    response
    النوع
    string | null
    الوصف
    Prefill or confirmed buyer email. Guest checkout placeholders are returned as null.
  • lang
    في
    response
    النوع
    string | null
    الوصف
    Buyer locale when known. Not a create-session field.
  • returnUrl
    في
    response
    النوع
    string | null
    الوصف
    Continue-to-shop URL stored on the session, or null.
  • cancelUrl
    في
    response
    النوع
    string | null
    الوصف
    Cancel/expiry redirect, or null.
  • invoiceId
    في
    response
    النوع
    string | null
    الوصف
    Your invoice id. Same value as externalInvoiceId.
  • externalInvoiceId
    في
    response
    النوع
    string | null
    الوصف
    Same as invoiceId (legacy alias).
  • source
    في
    response
    النوع
    string
    الوصف
    How the session was created. API sessions are "api".
  • expiresAt
    في
    response
    النوع
    string
    الوصف
    ISO timestamp. Unpaid checkouts cannot be completed after this time.
  • hostedUrl
    في
    response
    النوع
    string
    الوصف
    Pay page URL (same target as top-level hosted_url on create).
  • orderUid
    في
    response
    النوع
    string | null
    الوصف
    Marketplace order uid after payment. null until the session is paid.
  • items
    في
    response
    النوع
    object[]
    الوصف
    Locked lines: name, description, amount, quantity, imageUrl. Seller GET never includes delivery values.
  • Response: { session }. Stale unpaid sessions are marked expired before they are returned.
  • Use this as a backup to checkout.completed. paid is true only when status is paid.
  • 404 NOT_FOUND if the uid is unknown or belongs to another seller.
GET/api/v1/checkout/sessions
checkout:write

ابحث عن جلسة دفع مستضافة بواسطة رقم الفاتورة

نفس كائن الجلسة كما في GET بواسطة uid. مرر invoiceId الذي أرسلته عند الإنشاء. مفقود: 400 INVOICE_ID_REQUIRED. غير معروف: 404 NOT_FOUND.

الطلب

  • invoiceId
    في
    query
    النوع
    string
    مطلوب
    مطلوب
    الحدود
    Max 128
    الوصف
    invoiceId from create. Missing: 400 INVOICE_ID_REQUIRED. Too long: 400 INVALID_INVOICE_ID.

الاستجابة

  • uid
    في
    response
    النوع
    string
    الوصف
    Session id. Same value as in hostedUrl / hosted_url.
  • status
    في
    response
    النوع
    string
    الوصف
    created | pending_payment | paid | canceled | expired | refunded | error. Unpaid sessions expire after 24 hours.
  • paid
    في
    response
    النوع
    boolean
    الوصف
    true only when status is paid. false for refunded, expired, canceled, and unpaid states.
  • amount
    في
    response
    النوع
    number
    الوصف
    Locked buyer total in major units.
  • currency
    في
    response
    النوع
    string
    الوصف
    ISO currency code stored on the session (for example USD).
  • itemName
    في
    response
    النوع
    string | null
    الوصف
    Pay page heading.
  • description
    في
    response
    النوع
    string
    الوصف
    Longer copy under the heading.
  • email
    في
    response
    النوع
    string | null
    الوصف
    Prefill or confirmed buyer email. Guest checkout placeholders are returned as null.
  • lang
    في
    response
    النوع
    string | null
    الوصف
    Buyer locale when known. Not a create-session field.
  • returnUrl
    في
    response
    النوع
    string | null
    الوصف
    Continue-to-shop URL stored on the session, or null.
  • cancelUrl
    في
    response
    النوع
    string | null
    الوصف
    Cancel/expiry redirect, or null.
  • invoiceId
    في
    response
    النوع
    string | null
    الوصف
    Your invoice id. Same value as externalInvoiceId.
  • externalInvoiceId
    في
    response
    النوع
    string | null
    الوصف
    Same as invoiceId (legacy alias).
  • source
    في
    response
    النوع
    string
    الوصف
    How the session was created. API sessions are "api".
  • expiresAt
    في
    response
    النوع
    string
    الوصف
    ISO timestamp. Unpaid checkouts cannot be completed after this time.
  • hostedUrl
    في
    response
    النوع
    string
    الوصف
    Pay page URL (same target as top-level hosted_url on create).
  • orderUid
    في
    response
    النوع
    string | null
    الوصف
    Marketplace order uid after payment. null until the session is paid.
  • items
    في
    response
    النوع
    object[]
    الوصف
    Locked lines: name, description, amount, quantity, imageUrl. Seller GET never includes delivery values.
  • Same { session } body as GET /api/v1/checkout/sessions/:uid, including the fields above.
  • Prefer this when you stored your own invoice id and not the session uid.
  • 404 NOT_FOUND if no session exists for that invoice id.

الويب هوكس الخارجة

قم بتكوين نقاط النهاية HTTPS (أو الويب هوكس Discord) في الإعدادات → المطور. يقوم RMT بإرسال POST عند تشغيل الأحداث المشترك فيها.

order.paid
order.delivered
order.completed
order.refunded
order.disputed
offer.published
offer.updated
checkout.completed
checkout.canceled
checkout.refunded
  • تنسيق JSON ينشر ظرفًا منظمًا مع id، النوع، created، وdata.
  • تنسيق Discord ينشر تضمينات غنية مع روابط الطلب أو العرض.
  • التوقيع الاختياري يستخدم X-RMT-Timestamp وX-RMT-Signature (نفس المخطط مثل الاحتياطي).
  • تظهر تاريخ التسليم تحت كل نقطة نهاية حتى تتمكن من إعادة محاولة الفشل. تتوقف نقاط النهاية تلقائيًا بعد الفشل المتكرر.
  • الدفع المستضاف يرسل checkout.completed وcheckout.canceled وcheckout.refunded مع data.checkout. يتم حذف قيم التسليم. مبيعات السوق تحتفظ بـ order.paid وأحداث order.* الأخرى.

ظرف تسليم JSON

json
{
  "id": "whd_…",
  "type": "order.paid",
  "created": "2026-07-23T12:00:00.000Z",
  "data": {
    "order": {
      "uid": "ord_…",
      "reference": "RMT-…",
      "status": "PAID",
      "url": "https://rmt.gg/orders/ord_…",
      "items": [ /* line items with offer names */ ]
    }
  }
}

رؤوس تسليم موقعة

json
{
  "X-RMT-Event": "order.paid",
  "X-RMT-Delivery": "whd_…",
  "X-RMT-Timestamp": "1710000000",
  "X-RMT-Signature": "v1=abc123…"
}

حمولة إتمام الدفع

json
{
  "id": "whd_…",
  "type": "checkout.completed",
  "created": "2026-08-14T12:00:00.000Z",
  "data": {
    "checkout": {
      "uid": "pcs_…",
      "status": "paid",
      "amount": 10,
      "currency": "USD",
      "itemName": "Gold pack",
      "description": "1000 gold for account example",
      "invoiceId": "inv-12345",
      "source": "api",
      "orderUid": "ord_…",
      "hostedUrl": "https://rmt.gg/pay/pcs_…",
      "email": "[email protected]",
      "paidAt": "2026-08-14T12:01:00.000Z",
      "expiresAt": "2026-08-15T12:00:00.000Z",
      "createdAt": "2026-08-14T12:00:00.000Z",
      "reason": null,
      "items": [
        {
          "name": "Gold pack",
          "description": "1000 gold for account example",
          "amount": 10,
          "quantity": 1,
          "imageUrl": "https://cdn.shop.example/gold.png"
        }
      ]
    }
  }
}

حمولة checkout.canceled

json
{
  "id": "whd_…",
  "type": "checkout.canceled",
  "created": "2026-08-14T12:20:00.000Z",
  "data": {
    "checkout": {
      "uid": "pcs_…",
      "status": "canceled",
      "amount": 10,
      "currency": "USD",
      "itemName": "Gold pack",
      "description": "1000 gold for account example",
      "invoiceId": "inv-12345",
      "source": "api",
      "orderUid": null,
      "hostedUrl": "https://rmt.gg/pay/pcs_…",
      "email": "[email protected]",
      "paidAt": null,
      "expiresAt": "2026-08-15T12:00:00.000Z",
      "createdAt": "2026-08-14T12:00:00.000Z",
      "reason": "buyer_canceled",
      "items": [
        {
          "name": "Gold pack",
          "description": "1000 gold for account example",
          "amount": 10,
          "quantity": 1,
          "imageUrl": "https://cdn.shop.example/gold.png"
        }
      ]
    }
  }
}

تحقق من توقيعات الويب هوكس

عند تعيين سر التوقيع، احسب HMAC-SHA256 على timestamp + '.' + rawBody وقارنه بالهيكس بعد v1=.

تظل السرية على RMT. يتضمن كل POST موقع X-RMT-Timestamp (ثواني Unix) و X-RMT-Signature (v1= بالإضافة إلى hex). احسب HMAC-SHA256 على السلسلة timestamp + '.' + rawBody باستخدام سرك، ثم قارن مع hex بعد v1=. ارفض الطوابع الزمنية التي تزيد عن 5 دقائق.

  • اقرأ بيانات الجسم الخام تمامًا كما تم استلامها. لا تقم بتحليل JSON وإعادة تسلسلها قبل التجزئة.
  • استخدم قيمة رأس X-RMT-Timestamp كبادئة للزمن (نفس السلسلة، بدون إعادة تنسيق).
  • قارن باستخدام فحص مساواة آمن من حيث التوقيت. ارفض الطلبات التي تحتوي على توقيعات مفقودة أو غير متطابقة عند تكوين سر.
  • ارفض الطوابع الزمنية التي تزيد عن 5 دقائق للحد من إعادة التشغيل. نفس المخطط ينطبق على reserve.item وأحداث الطلب الخارجي أو الخروج.

تحقق TypeScript (مقارنة آمنة زمنياً ونافذة إعادة تشغيل مدتها 5 دقائق)

typescript
import { createHmac, timingSafeEqual } from "node:crypto";

const MAX_AGE_SEC = 5 * 60; // reject replays older than 5 minutes

export function verifyRmtSignature(opts: {
  secret: string;
  timestamp: string | null | undefined;
  signatureHeader: string | null | undefined;
  rawBody: string; // exact POST bytes. Do not JSON.parse then re-stringify.
  nowSec?: number;
}): boolean {
  const secret = opts.secret.trim();
  const timestamp = String(opts.timestamp ?? "").trim();
  const provided = String(opts.signatureHeader ?? "").trim().replace(/^v1=/i, "");
  if (!secret || !timestamp || !provided) return false;

  const ts = Number(timestamp);
  if (!Number.isInteger(ts) || ts <= 0) return false;
  const nowSec = opts.nowSec ?? Math.floor(Date.now() / 1000);
  if (Math.abs(nowSec - ts) > MAX_AGE_SEC) return false;

  const expected = createHmac("sha256", secret)
    .update(`${timestamp}.${opts.rawBody}`)
    .digest("hex");

  const a = Buffer.from(expected, "utf8");
  const b = Buffer.from(provided.toLowerCase(), "utf8");
  if (a.length !== b.length) return false;
  return timingSafeEqual(a, b);
}

// Express / Node HTTP example:
// const rawBody = (req as { rawBody?: string }).rawBody
//   ?? JSON.stringify(req.body); // only if you captured the raw string first
// const ok = verifyRmtSignature({
//   secret: process.env.RMT_WEBHOOK_SECRET!,
//   timestamp: req.headers["x-rmt-timestamp"] as string,
//   signatureHeader: req.headers["x-rmt-signature"] as string,
//   rawBody,
// });
// if (!ok) return res.status(401).end();

معالج webhook TypeScript

typescript
type CheckoutCompleted = {
  id: string;
  type: "checkout.completed";
  created: string;
  data: {
    checkout: {
      uid: string;
      status: "paid";
      amount: number;
      currency: string;
      itemName: string | null;
      description: string;
      invoiceId: string | null;
      source: string | null;
      orderUid: string | null;
      hostedUrl: string;
      email: string | null;
      paidAt: string | null;
      expiresAt: string | null;
      createdAt: string | null;
      reason: null;
      items: Array<{
        name: string;
        description: string | null;
        amount: number;
        quantity: number;
        imageUrl: string | null;
      }>;
    };
  };
};

type CheckoutCanceled = {
  type: "checkout.canceled";
  data: {
    checkout: {
      uid: string;
      status: "canceled" | "expired";
      invoiceId: string | null;
      reason: "buyer_canceled" | "expired";
    };
  };
};

export async function handleRmtWebhook(rawBody: string, headers: Headers) {
  const ok = verifyRmtSignature({
    secret: process.env.RMT_WEBHOOK_SECRET!,
    timestamp: headers.get("x-rmt-timestamp"),
    signatureHeader: headers.get("x-rmt-signature"),
    rawBody,
  });
  if (!ok) throw new Response("Unauthorized", { status: 401 });

  const event = JSON.parse(rawBody) as { type: string; data: Record<string, unknown> };
  switch (event.type) {
    case "checkout.completed": {
      const checkout = (event as CheckoutCompleted).data.checkout;
      if (!checkout.invoiceId || !checkout.paidAt) break;
      await fulfillShopOrder(checkout.invoiceId, checkout.orderUid);
      break;
    }
    case "checkout.canceled": {
      const checkout = (event as CheckoutCanceled).data.checkout;
      await markShopOrderCanceled(checkout.invoiceId, checkout.reason);
      break;
    }
    case "checkout.refunded":
    case "order.paid":
    case "order.delivered":
      break;
    default:
      break;
  }
}

احجز الويب هوكس (إعادة تعبئة المخزون)

للعروض COMPLEX (وحدات فريدة)، يمكن لـ RMT إرسال POST إلى نقطة النهاية HTTPS الخاصة بك بعد الدفع لصنع الترخيص التالي، الحساب، أو المفتاح عندما يكون المخزون المحلي قليل.

فشل آمن للدفع

إذا انتهى وقت نقطة النهاية الخاصة بك أو أعادت بيانات غير صالحة، يبقى الطلب مدفوعًا. يتم تحصيل رسوم من المشتري؛ ترى خطأ في الطلب ويمكنك إعادة محاولة الاحتياطي أو إرفاق المفاتيح يدويًا.

كيفية إعدادها

  1. قم بإنشاء عرض معقد مع حقول العناصر (على سبيل المثال، الترخيص).
  2. في خطوة العناصر، قم بتمكين نقطة نهاية المخزون عند الطلب والصق عنوان URL العام الخاص بك HTTPS.
  3. يمكنك اختيار تعيين سر توقيع حتى ترسل RMT X-RMT-Timestamp وX-RMT-Signature في كل مكالمة.
  4. قم بتشغيل الاختبار (أو الصق JSON عينة)، وقم بتعيين مسارات الاستجابة إلى حقول العناصر، ثم احفظ.
  5. انشر القائمة. يمكن للمشترين الشراء مع مخزون محلي فارغ؛ يتم إنشاء المفاتيح بعد الدفع.
  • يفضل دائمًا المخزون المحلي؛ يملأ الويب هوك فقط النقص.
  • قم بتكوين افتراضي على مستوى العرض، أو تجاوز لكل خيار تسعير، في خطوة العناصر من محرر العرض.
  • HTTPS فقط. التوقيع HMAC الاختياري يتطابق مع الويب هوكس الخارجة (X-RMT-Event: reserve.item).
  • اختبار في المحرر يرسل dryRun: true. في صفحة الطلب، استخدم Retry reserve بعد إصلاح نقطة النهاية الخاصة بك.

جسم POST القياسي (مقصوص)

json
{
  "id": "rsv_…",
  "type": "reserve.item",
  "order": { "uid": "ord_…", "reference": "RMT-…", "url": "https://rmt.gg/orders/ord_…" },
  "offer": { "url": "my-offer", "title": "Game key", "pageUrl": "https://rmt.gg/offers/my-offer" },
  "option": { "id": 1, "name": "Standard" },
  "fields": [{ "id": 10, "name": "License", "type": "text", "required": true }],
  "quantity": 1
}

رؤوس الطلبات (عند تعيين سر التوقيع)

json
{
  "Content-Type": "application/json",
  "X-RMT-Event": "reserve.item",
  "X-RMT-Delivery": "rsv_…",
  "X-RMT-Timestamp": "1710000000",
  "X-RMT-Signature": "v1=abc123…"
}

استجابة ملائمة

json
{
  "entries": [
    { "name": "License", "value": "AAAA-BBBB-CCCC" }
  ]
}

حقول JSON المرسومة (مع مسارات responseMap مثل $.license)

json
{
  "license": "AAAA-BBBB-CCCC",
  "email": "[email protected]",
  "password": "temporary-pass"
}

كيفية التحقق من سر التوقيع

إذا قمت بتعيين سر على العرض، فإن كل POST احتياطي يتم توقيعه. أعد حساب HMAC-SHA256(secret, timestamp + '.' + rawBody) وقارن مع X-RMT-Signature بعد إزالة بادئة v1=. السر نفسه لا يتم تضمينه أبدًا في الطلب.

انظر مثال التحقق الكامل

معالج reserve TypeScript (تحقق، ثم أعد الإدخالات)

typescript
type ReserveRequest = {
  id: string;
  type: "reserve.item";
  dryRun?: boolean;
  quantity: number;
  fields: Array<{ name: string; required?: boolean }>;
};

export async function handleReserve(rawBody: string, headers: Headers) {
  const ok = verifyRmtSignature({
    secret: process.env.RMT_RESERVE_SECRET!,
    timestamp: headers.get("x-rmt-timestamp"),
    signatureHeader: headers.get("x-rmt-signature"),
    rawBody,
  });
  if (!ok) return new Response("Unauthorized", { status: 401 });

  const body = JSON.parse(rawBody) as ReserveRequest;
  if (body.type !== "reserve.item") {
    return Response.json({ error: "Unexpected event" }, { status: 400 });
  }

  const qty = Number(body.quantity);
  if (!Number.isInteger(qty) || qty < 1) {
    return Response.json({ error: "Invalid quantity" }, { status: 400 });
  }

  if (body.dryRun) {
    return Response.json({
      entries: [{ name: "License", value: "TEST-AAAA-BBBB" }],
    });
  }

  const license = await mintLicense(); // your inventory
  return Response.json({
    entries: [{ name: "License", value: license }],
  });
}

لا تتصل بالاحتياطي قبل الدفع

RMT يتصل بنقطة النهاية الخاصة بك فقط بعد نجاح الدفع، لذا فإن عمليات الخروج المهجورة لا تحرق التراخيص.

الأخطاء وحدود المعدل

تعود الأخطاء بصيغة JSON { error, code? }. يتم تحديد حركة مرور واجهة برمجة التطبيقات المفتوحة إلى 300 طلبات في الدقيقة لكل مفتاح واجهة برمجة التطبيقات.

  • API_KEY_REQUIRED
    401

    مفقود Authorization أو X-Api-Key header.

  • API_KEY_INVALID
    401

    المفتاح غير معروف، تم إلغاؤه، انتهت صلاحيته، أو تم تعليق وصول المطور.

  • SCOPE_MISSING
    403

    المفتاح يفتقر إلى النطاق المطلوب من قبل نقطة النهاية.

  • RATE_LIMITED
    429

    عدد كبير جدًا من الطلبات. احترم Retry-After وX-RateLimit-Reset.

  • CHECKOUT_PARTNER_NOT_APPROVED
    403

    هذا البائع غير معتمد للخروج المستضاف.

  • INVALID_JSON
    400

    يجب أن يكون جسم الطلب JSON.

  • INVALID_AMOUNT
    400

    يجب أن تكون القيمة أكبر من 0 وأقصى حد 1,000,000.

  • UNSUPPORTED_CURRENCY
    400

    العملة ليست رمز ISO مدعوم.

  • INVALID_RETURN_URL
    400

    يجب أن يكون returnUrl و cancelUrl https (http://localhost مسموح به للمتاجر المحلية).

  • INVALID_PSP_CATEGORY
    400

    يجب إرسال categorySlug و offering معًا ومطابقة زوج من الكتالوج.

  • INVALID_INVOICE_ID
    400

    invoiceId أطول من 128 حرفًا.

  • INVOICE_ID_REQUIRED
    400

    GET /checkout/sessions يتطلب invoiceId كمعامل استعلام.

  • INVALID_IDEMPOTENCY_KEY
    400

    Idempotency-Key أطول من 128 حرفًا.

  • INVALID_METADATA
    400

    يجب أن تكون metadata كائن JSON، وليس مصفوفة أو قيمة بدائية.

  • METADATA_TOO_LARGE
    400

    البيانات الوصفية المتسلسلة أكبر من 4096 حرفًا.

  • IDEMPOTENCY_CONFLICT
    409

    تم إعادة استخدام Idempotency-Key مع مبلغ مختلف أو عملة أو عنصر.

  • INVOICE_CONFLICT
    409

    تم إعادة استخدام invoiceId مع مبلغ مختلف أو عملة أو عنصر.

  • INVALID_IMAGE_URL
    400

    يجب أن يكون imageUrl رابط https.

  • ITEM_NAME_REQUIRED
    400

    اسم العنصر (أو العنوان) مطلوب عند عدم وجود عناصر.

  • INVALID_ITEMS
    400

    يجب أن تكون items مصفوفة غير فارغة من العناصر المقفلة (بحد أقصى 20). يحتاج كل عنصر إلى اسم ومبلغ.

  • TOO_MANY_ITEMS
    400

    لا يمكن أن تحتوي items على أكثر من 20 عنصر.

  • AMOUNT_MISMATCH
    400

    يجب أن يساوي المبلغ مجموع كل مبلغ عنصر مضروبًا في الكمية.

  • INVALID_DELIVERY
    400

    حقول التسليم غير صالحة. يحتاج كل حقل إلى اسم (بحد أقصى 80) وقيمة (بحد أقصى 2048). يجب أن يكون النوع نصًا، كلمة مرور، أو منطقة نصية (النص الافتراضي). الحد الأقصى 16 حقلًا لكل سطر.

  • ITEMS_TOO_LARGE
    400

    JSON العناصر المتسلسلة أكبر من 48,000 حرف.

  • NOT_FOUND
    404

    لا توجد جلسة دفع مستضافة تتطابق مع uid أو invoiceId لهذا البائع.

  • RESERVE_FAILED
    400

    انتهى وقت الويب هوك الاحتياطي، أعاد بيانات غير صالحة، أو فاته حقول مطلوبة.

  • OPTION_AMBIGUOUS
    409

    هناك أكثر من خيار تسعير واحد يتطابق مع ذلك الاسم. مرر optionId من GET stock.

  • OPTION_NOT_FOUND
    404

    لا يوجد خيار تسعير يتطابق مع ذلك المعرف أو الاسم في هذه القائمة.

  • OPTION_REQUIRED
    400

    تحتوي هذه القائمة على خيارات تسعير متعددة. مرر الخيار أو optionId.

  • UNKNOWN_FIELD
    400

    اسم الحقل لا يتطابق مع مخطط تسليم هذه القائمة.

  • FIELD_MAPPING_AMBIGUOUS
    400

    لم يتمكن من مطابقة الأعمدة أو المفاتيح مع حقول التسليم. أرسل رؤوس، أو استخدم كائنات العناصر المفاتيح حسب اسم الحقل.

  • STOCK_MODE_MISMATCH
    400

    لا يتطابق هذا الحمولة مع وضع مخزون الخيار (الكمية مقابل العناصر المحفوظة).

  • IMPORT_TOO_LARGE
    400

    قد تستورد طلب إعادة التعبئة في أقصى حد 1,000 عنصر محفوظ لكل خيار.

  • OPTION_ITEM_CAPACITY
    400

    هذا الخيار لديه بالفعل الحد الأقصى من 5,000 عنصر محفوظ غير مبيع.

  • DUPLICATE_ITEMS
    409

    onDuplicate=error و يوجد عنصر واحد على الأقل موجود بالفعل في هذا الخيار.

  • UNLIMITED_STOCK
    400

    هذا الخيار لديه كمية غير محدودة. استخدم set للتبديل إلى عدد محدود أولاً.

  • INSUFFICIENT_STOCK
    400

    لا يوجد كمية كافية من المخزون للإزالة.

  • STOCK_HELD_IN_CHECKOUT
    400

    لا يمكن تقليل الكمية تحت الوحدات المحجوزة حاليًا في عملية الدفع.

  • INVALID_RESTOCK
    400

    جسم إعادة التعبئة يفتقر إلى إجراء مطلوب، أو يجمع بين add/items في خيار واحد.

تعامل مع 429

تراجع باستخدام Retry-After seconds. لا تقم بتدوير المفاتيح لتجاوز الحدود؛ الحد هو لكل مفتاح وثابت لجميع البائعين.

تتضمن الاستجابات الناجحة X-RateLimit-Limit، X-RateLimit-Remaining، وX-RateLimit-Reset.

هل أنت مستعد للأتمتة؟

قم بإنشاء مفتاح في إعدادات المطور، ووصّل Discord أو Telegram تحت الإشعارات.