MercanPay Blog

Node.js ile Kripto Ödeme API Entegrasyonu: Express Örneği

Node.js ile kripto ödeme API entegrasyonu: fetch ile fatura oluşturun, Express'te imzalı webhook'u doğrulayın ve USDT/TRX siparişlerini güvenle onaylayın.

Yayınlanma: 1 Kasım 202610 dk okumaRead in English
Node.js ile Kripto Ödeme API Entegrasyonu: Express Örneği

Node.js ile kripto ödeme API entegrasyonu için ek bir paket kurmanız gerekmez. MercanPay'in REST API'si JSON gönderip JSON döndürür ve Node 18 ile gelen yerleşik fetch bunun için yeterlidir. Bu rehberde bir Express uygulamasında fatura oluşturmayı, müşteriyi ödeme sayfasına yönlendirmeyi ve imzalı webhook ile siparişi onaylamayı adım adım anlatıyoruz.

Node.js için hazır bir SDK var mı?

Hayır, şu an resmi bir Node.js kütüphanesi yok. Resmi istemciler PHP (composer require mercanpay/mercanpay-php) ve Python (pip install mercanpay) için sunuluyor. Node.js tarafında ise doğrudan REST uç noktalarını çağırırsınız. API küçük ve düzenli olduğu için bu pratikte birkaç satırlık bir yardımcı fonksiyondan ibarettir.

Başlamadan önce ihtiyacınız olanlar:

  • Node.js 18 veya üzeri (yerleşik fetch için)
  • onaylı bir MercanPay satıcı hesabı ve invoices yetkili bir API anahtarı
  • panelde Ayarlar sayfasındaki webhook imza sırrı (whsec_…)

Hesabınız yoksa önce satıcı başvurusu yapın. Başvuru sürecini MercanPay satıcı başvurusu yazımızda anlattık.

Akışın genel resmi

Koda geçmeden önce hangi işin nerede yapıldığını netleştirmek, ileride karşılaşacağınız hataların çoğunu baştan önler. Bir Node.js kripto ödeme entegrasyonunda üç ayrı parça vardır:

  • Ödeme başlatma: Müşteri sepeti onayladığında sunucunuz fatura oluşturur ve müşteriyi ödeme sayfasına gönderir. Bu adımda para henüz hareket etmemiştir; yalnızca bir ödeme talebi açılmıştır.
  • Ödeme bildirimi: Müşteri ödemeyi gönderdikten ve ödeme kesinleştikten sonra MercanPay sunucunuza webhook gönderir. Siparişi onaylayacağınız tek güvenilir an budur.
  • Müşteriye geri bildirim: Müşterinin tarayıcısı teşekkür sayfasına döner. Bu sayfa yalnızca bilgi verir; sipariş durumunu değiştirmez.

Bu ayrımı korumak, özellikle sipariş onayını tarayıcı yönlendirmesine bağlama hatasını engeller. Müşterinin tarayıcısı kapanabilir, internet bağlantısı kopabilir ya da biri adres çubuğundaki parametreleri elle değiştirebilir. Webhook ise sunucudan sunucuya, imzalı ve tekrar denenen bir bildirimdir.

API anahtarının yalnızca sunucu tarafında kullanılması da bu yapının doğal sonucudur. Tarayıcıdaki JavaScript hiçbir zaman doğrudan MercanPay API'sini çağırmamalıdır. Tarayıcı sizin sunucunuza istek atar, sunucunuz da anahtarla MercanPay'e bağlanır.

1. Küçük bir API yardımcısı yazın

Anahtarları kodda değil, ortam değişkenlerinde tutun:

MERCANPAY_API_KEY=mp_...
MERCANPAY_WEBHOOK_SECRET=whsec_...

Tüm istekler Authorization: Bearer başlığıyla gönderilir. Hatalı yanıtlarda gövde her zaman { "error": { "code", "message" } } biçimindedir:

// mercanpay.js
const BASE = 'https://mercanpay.com';

export async function mp(method, path, body) {
  const res = await fetch(BASE + path, {
    method,
    headers: {
      Authorization: `Bearer ${process.env.MERCANPAY_API_KEY}`,
      'Content-Type': 'application/json',
      Accept: 'application/json',
    },
    body: body ? JSON.stringify(body) : undefined,
  });
  const data = await res.json().catch(() => null);
  if (!res.ok) {
    const err = new Error(data?.error?.message || `HTTP ${res.status}`);
    err.status = res.status;
    err.code = data?.error?.code || 'http_error';
    throw err;
  }
  return data;
}

2. Node.js ile kripto ödeme faturası oluşturun

Müşteri "Öde" butonuna bastığında sunucunuz POST /v1/invoices çağırır ve dönen payment_url adresine yönlendirir. Fiyatı USD olarak verip coin seçimini müşteriye bırakmak en esnek yoldur:

import express from 'express';
import { mp } from './mercanpay.js';

const app = express();

app.post('/checkout/:orderId', async (req, res) => {
  const order = await findOrder(req.params.orderId); // kendi veritabanınız
  try {
    const invoice = await mp('POST', '/v1/invoices', {
      price_usd: order.total,            // "49.90" gibi metin
      order_id: `order-${order.id}`,
      description: order.title,
      success_url: `https://magazam.com/siparis/${order.id}/tesekkurler`,
      return_url: 'https://magazam.com/sepet',
      metadata: { customer_id: order.customerId },
    });
    res.redirect(303, invoice.payment_url);
  } catch (e) {
    console.error('MercanPay', e.code, e.message);
    res.status(502).send('Ödeme sayfası açılamadı, lütfen tekrar deneyin.');
  }
});

Birkaç önemli ayrıntı:

  • Tutarlar her zaman metindir. 49.9 gibi bir sayı yerine "49.90" gönderin. Kayan nokta yuvarlamaları ödemeyi bozabilir.
  • Sabit coin istiyorsanız price_usd yerine token ve amount verin: { token: 'USDT', amount: '49.90' }.
  • Coin seçeneklerini daraltmak için tokens: ['USDT'] ekleyin.
  • expires_in_minutes 5 ile 1440 dakika (24 saat) arasında olabilir.
  • Minimum fatura 10

USD fiyatlandırmanın arka planını kripto ödemede kur riski yazımızda bulabilirsiniz.

3. order_id ile güvenli tekrar deneme

Ağ zaman aşımı yaşadığınızda faturanın açılıp açılmadığını bilemeyebilirsiniz. Aynı order_id ile yapılan ikinci istek yeni fatura açmaz, mevcut faturayı 200 ile döndürür. Bu yüzden 429 ya da 5xx aldığınızda aynı isteği güvenle tekrarlayabilirsiniz:

async function createInvoiceWithRetry(body, tries = 3) {
  for (let i = 0; ; i++) {
    try {
      return await mp('POST', '/v1/invoices', body);
    } catch (e) {
      const retriable = e.status === 429 || e.status >= 500 || !e.status;
      if (!retriable || i >= tries - 1) throw e;
      await new Promise((r) => setTimeout(r, 1000 * 2 ** i));
    }
  }
}

Varsayılan hız sınırı IP başına dakikada 120 istektir. Konunun ayrıntıları order_id ile fatura sipariş eşleştirme yazımızda.

4. Express'te webhook'u doğrulayın

Ödeme 20 blok onayıyla kesinleştiğinde MercanPay sunucunuza imzalı bir POST gönderir. İmza X-MercanPay-Signature: t=…,v1=… başlığındadır ve ham gövde üzerinden hesaplanır. Bu yüzden bu rotada express.json() kullanmayın, express.raw() kullanın:

import crypto from 'node:crypto';

function verify(secret, rawBody, header, tolerance = 300) {
  const sig = Object.fromEntries(header.split(',').map((p) => p.split('=')));
  const t = Number(sig.t);
  if (!sig.v1 || !Number.isFinite(t) || Math.abs(Date.now() / 1000 - t) > tolerance) return false;
  const mac = crypto.createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex');
  return mac.length === sig.v1.length && crypto.timingSafeEqual(Buffer.from(mac), Buffer.from(sig.v1));
}

app.post('/mercanpay/webhook', express.raw({ type: 'application/json' }), async (req, res) => {
  const ok = verify(process.env.MERCANPAY_WEBHOOK_SECRET, req.body.toString(), req.get('X-MercanPay-Signature') || '');
  if (!ok) return res.sendStatus(401);

  const event = JSON.parse(req.body);
  res.sendStatus(200); // hızlı yanıt verin, işi sonra yapın

  if (event.type === 'invoice.paid' || event.type === 'invoice.overpaid') {
    const inv = event.data.invoice;
    await fulfilOnce(event.id, inv.order_id, inv.token, inv.paid_amount);
  }
});

İmzanın teorisini webhook imzası doğrulama rehberinde, tüm olay türlerini ise webhook olay türleri yazımızda anlattık.

Hızlı yanıt ve tekrarlar

10 saniye içinde 2xx dönmezseniz istek 30 sn, 2 dk, 10 dk, 30 dk, 1 sa, 3 sa, 6 sa, 12 sa ve 24 sa aralıklarla yeniden denenir. Aynı olay birden fazla gelebilir. fulfilOnce içinde olay kimliğini (event.id) benzersiz bir sütuna yazarak aynı siparişi iki kez teslim etmeyi önleyin. Ayrıntılar: webhook yeniden deneme ve idempotent işleme.

5. Başarılı sayfasında API'yi sorgulayın

Müşterinin tarayıcısı ödeme sonrası success_url adresine invoice_id, order_id ve status parametreleriyle gelir. Bu parametreler taklit edilebilir. Teşekkür sayfasında gerçek durumu API'den okuyun:

app.get('/siparis/:id/tesekkurler', async (req, res) => {
  const inv = await mp('GET', `/v1/invoices/by-order/order-${encodeURIComponent(req.params.id)}`);
  const paid = inv.status === 'paid' || inv.status === 'overpaid';
  res.send(paid ? 'Ödemeniz onaylandı, teşekkürler!' : 'Ödemeniz onaylanıyor, kısa süre içinde e-posta alacaksınız.');
});

Ödeme henüz kesinleşmediyse müşteriye "onaylanıyor" mesajı göstermek yeterlidir. Webhook geldiğinde sipariş zaten onaylanacağı için müşteriyi sayfayı yenilemeye zorlamanıza gerek yoktur; bir onay e-postası göndermek çoğu zaman daha iyi bir deneyimdir.

Ödenmemiş bir faturayı iptal etmeniz gerekirse POST /v1/invoices/{id}/cancel uç noktasını kullanabilirsiniz. Yalnızca hiç ödeme almamış bekleyen faturalar iptal edilebilir.

Hata kodları kısa özet

HTTP Kod Anlamı
400 validation_error Bir alan hatalı; mesaj hangisi olduğunu söyler
401 unauthorized Anahtar eksik, hatalı ya da iptal edilmiş
403 forbidden Anahtarın bu işlem için yetkisi yok
404 not_found Kayıt yok ya da başka satıcıya ait
429 — Hız sınırı; kısa süre bekleyin
5xx — Geçici hata; aynı isteği tekrarlayın

Canlıya geçmeden önce

  • API anahtarı yalnızca sunucuda, ortam değişkeninde duruyor; tarayıcıya hiç gönderilmiyor.
  • Webhook rotası express.raw() kullanıyor ve imzayı doğruluyor.
  • Her siparişin benzersiz bir order_id değeri var.
  • invoice.partial ve invoice.overpaid ele alınıyor.
  • Panelde Webhooklar sayfasındaki "Test gönder" ile test.ping olayını aldınız.
  • Akışı küçük tutarlı gerçek bir ödemeyle uçtan uca denediniz.

Sık sorulan sorular

TypeScript ile kullanabilir miyim?

Evet. Yukarıdaki kod doğrudan TypeScript'e taşınabilir. Yanıt alanları için kendi tiplerinizi dokümantasyondaki örnek yanıtlara göre tanımlayabilirsiniz.

Next.js veya Fastify gibi başka çatılarda da çalışır mı?

Çalışır. Tek kritik nokta, webhook imzasını doğrularken gövdeyi JSON'a çevrilmeden önceki ham haliyle kullanmaktır. Kullandığınız çatının ham gövdeye erişim yöntemini kontrol edin.

Node.js'ten çekim yapabilir miyim?

withdrawals yetkili bir anahtarla POST /v1/withdrawals çağrısı yapabilirsiniz. Güvenlik gereği API çekimleri yalnızca panelde güvenilir olarak işaretlenmiş kayıtlı adreslere gider. Süreç için kripto bakiye çekim süreci yazımıza bakın.

Python veya PHP kullanıyorsam?

O zaman resmi kütüphaneler işinizi kolaylaştırır: Python ile kripto ödeme entegrasyonu ve PHP ile kripto ödeme entegrasyonu.

MercanPay ile başlayın

Node.js ile kripto ödeme API entegrasyonu için ihtiyacınız olan her şey birkaç HTTP çağrısı. Satıcı hesabınızı açın, bir API anahtarı oluşturun ve ilk USDT faturanızı bugün kesin. dır. Ağ ücretleri müşterinin ödediği tutara dahildir.

USD fiyatlandırmanın arka planını kripto ödemede kur riski yazımızda bulabilirsiniz.

3. order_id ile güvenli tekrar deneme

Ağ zaman aşımı yaşadığınızda faturanın açılıp açılmadığını bilemeyebilirsiniz. Aynı order_id ile yapılan ikinci istek yeni fatura açmaz, mevcut faturayı 200 ile döndürür. Bu yüzden 429 ya da 5xx aldığınızda aynı isteği güvenle tekrarlayabilirsiniz:

async function createInvoiceWithRetry(body, tries = 3) {
  for (let i = 0; ; i++) {
    try {
      return await mp('POST', '/v1/invoices', body);
    } catch (e) {
      const retriable = e.status === 429 || e.status >= 500 || !e.status;
      if (!retriable || i >= tries - 1) throw e;
      await new Promise((r) => setTimeout(r, 1000 * 2 ** i));
    }
  }
}

Varsayılan hız sınırı IP başına dakikada 120 istektir. Konunun ayrıntıları order_id ile fatura sipariş eşleştirme yazımızda.

4. Express'te webhook'u doğrulayın

Ödeme 20 blok onayıyla kesinleştiğinde MercanPay sunucunuza imzalı bir POST gönderir. İmza X-MercanPay-Signature: t=…,v1=… başlığındadır ve ham gövde üzerinden hesaplanır. Bu yüzden bu rotada express.json() kullanmayın, express.raw() kullanın:

import crypto from 'node:crypto';

function verify(secret, rawBody, header, tolerance = 300) {
  const sig = Object.fromEntries(header.split(',').map((p) => p.split('=')));
  const t = Number(sig.t);
  if (!sig.v1 || !Number.isFinite(t) || Math.abs(Date.now() / 1000 - t) > tolerance) return false;
  const mac = crypto.createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex');
  return mac.length === sig.v1.length && crypto.timingSafeEqual(Buffer.from(mac), Buffer.from(sig.v1));
}

app.post('/mercanpay/webhook', express.raw({ type: 'application/json' }), async (req, res) => {
  const ok = verify(process.env.MERCANPAY_WEBHOOK_SECRET, req.body.toString(), req.get('X-MercanPay-Signature') || '');
  if (!ok) return res.sendStatus(401);

  const event = JSON.parse(req.body);
  res.sendStatus(200); // hızlı yanıt verin, işi sonra yapın

  if (event.type === 'invoice.paid' || event.type === 'invoice.overpaid') {
    const inv = event.data.invoice;
    await fulfilOnce(event.id, inv.order_id, inv.token, inv.paid_amount);
  }
});

İmzanın teorisini webhook imzası doğrulama rehberinde, tüm olay türlerini ise webhook olay türleri yazımızda anlattık.

Hızlı yanıt ve tekrarlar

10 saniye içinde 2xx dönmezseniz istek 30 sn, 2 dk, 10 dk, 30 dk, 1 sa, 3 sa, 6 sa, 12 sa ve 24 sa aralıklarla yeniden denenir. Aynı olay birden fazla gelebilir. fulfilOnce içinde olay kimliğini (event.id) benzersiz bir sütuna yazarak aynı siparişi iki kez teslim etmeyi önleyin. Ayrıntılar: webhook yeniden deneme ve idempotent işleme.

5. Başarılı sayfasında API'yi sorgulayın

Müşterinin tarayıcısı ödeme sonrası success_url adresine invoice_id, order_id ve status parametreleriyle gelir. Bu parametreler taklit edilebilir. Teşekkür sayfasında gerçek durumu API'den okuyun:

app.get('/siparis/:id/tesekkurler', async (req, res) => {
  const inv = await mp('GET', `/v1/invoices/by-order/order-${encodeURIComponent(req.params.id)}`);
  const paid = inv.status === 'paid' || inv.status === 'overpaid';
  res.send(paid ? 'Ödemeniz onaylandı, teşekkürler!' : 'Ödemeniz onaylanıyor, kısa süre içinde e-posta alacaksınız.');
});

Ödeme henüz kesinleşmediyse müşteriye "onaylanıyor" mesajı göstermek yeterlidir. Webhook geldiğinde sipariş zaten onaylanacağı için müşteriyi sayfayı yenilemeye zorlamanıza gerek yoktur; bir onay e-postası göndermek çoğu zaman daha iyi bir deneyimdir.

Ödenmemiş bir faturayı iptal etmeniz gerekirse POST /v1/invoices/{id}/cancel uç noktasını kullanabilirsiniz. Yalnızca hiç ödeme almamış bekleyen faturalar iptal edilebilir.

Hata kodları kısa özet

HTTP Kod Anlamı
400 validation_error Bir alan hatalı; mesaj hangisi olduğunu söyler
401 unauthorized Anahtar eksik, hatalı ya da iptal edilmiş
403 forbidden Anahtarın bu işlem için yetkisi yok
404 not_found Kayıt yok ya da başka satıcıya ait
429 — Hız sınırı; kısa süre bekleyin
5xx — Geçici hata; aynı isteği tekrarlayın

Canlıya geçmeden önce

  • API anahtarı yalnızca sunucuda, ortam değişkeninde duruyor; tarayıcıya hiç gönderilmiyor.
  • Webhook rotası express.raw() kullanıyor ve imzayı doğruluyor.
  • Her siparişin benzersiz bir order_id değeri var.
  • invoice.partial ve invoice.overpaid ele alınıyor.
  • Panelde Webhooklar sayfasındaki "Test gönder" ile test.ping olayını aldınız.
  • Akışı küçük tutarlı gerçek bir ödemeyle uçtan uca denediniz.

Sık sorulan sorular

TypeScript ile kullanabilir miyim?

Evet. Yukarıdaki kod doğrudan TypeScript'e taşınabilir. Yanıt alanları için kendi tiplerinizi dokümantasyondaki örnek yanıtlara göre tanımlayabilirsiniz.

Next.js veya Fastify gibi başka çatılarda da çalışır mı?

Çalışır. Tek kritik nokta, webhook imzasını doğrularken gövdeyi JSON'a çevrilmeden önceki ham haliyle kullanmaktır. Kullandığınız çatının ham gövdeye erişim yöntemini kontrol edin.

Node.js'ten çekim yapabilir miyim?

withdrawals yetkili bir anahtarla POST /v1/withdrawals çağrısı yapabilirsiniz. Güvenlik gereği API çekimleri yalnızca panelde güvenilir olarak işaretlenmiş kayıtlı adreslere gider. Süreç için kripto bakiye çekim süreci yazımıza bakın.

Python veya PHP kullanıyorsam?

O zaman resmi kütüphaneler işinizi kolaylaştırır: Python ile kripto ödeme entegrasyonu ve PHP ile kripto ödeme entegrasyonu.

MercanPay ile başlayın

Node.js ile kripto ödeme API entegrasyonu için ihtiyacınız olan her şey birkaç HTTP çağrısı. Satıcı hesabınızı açın, bir API anahtarı oluşturun ve ilk USDT faturanızı bugün kesin.

#Node.js#API#Webhook#USDT

İlgili yazılar

Kripto ödemeleri dakikalar içinde almaya başlayın

USDT ve TRX (TRC20) ile ödeme alın. %0,4'ten başlayan komisyon, hazır ödeme sayfası, API ve webhook.