shopidi kendi mağazan, kendi adresin
Geliştirici · API v1

API sanal POS

Kendi web sitenizde, Shopidi güvenli ödeme altyapısıyla kartlı ödeme alın. Tek bir API çağrısıyla ödeme oluşturun, alıcıyı ortak ödeme sayfasına yönlendirin, sonucu imzalı webhook ile alın.

Genel bakış

API sanal POS, Shopidi satıcılarının kendi web sitelerinde (WooCommerce, OpenCart, özel yazılım vb.) kartlı ödeme almasını sağlar. Kart bilgileri sizin sitenize hiç uğramaz: alıcı, shopidi.com üzerindeki ortak ödeme sayfasında 3D Secure ile öder. Böylece PCI DSS yükümlülüğünüz en aza iner.

  • Temel adres: https://shopidi.com/api/v1
  • Biçim: JSON istek ve yanıt, UTF-8. Tutarlar iki ondalıklı metin ("1249.90"), tarihler ISO 8601.
  • Para birimi: Türk lirası (TRY).
  • Hız sınırı: anahtar başına dakikada 120 istek.
  • Ücretlendirme: her başarılı ödemeden mağazanıza tanımlı API sanal POS komisyonu kesilir; net tutar bloke süresi sonunda bakiyenize geçer ve hakediş talebiyle IBAN'ınıza ödenir. Güncel oranınızı GET /ping döndürür.

Başlamak için

  1. Satıcı hesabı açın ve mağazanızı onaylatın.
  2. Satıcı panelinde Finans → API sanal POS sayfasından web sitenizi ve iş modelinizi yazıp API Sanal POS Sözleşmesi'ni onaylayarak başvurun.
  3. Başvurunuz onaylanınca aynı sayfada API anahtarınızı, gizli anahtarınızı (yalnızca bir kez gösterilir) ve webhook imza anahtarınızı görürsünüz.

Ödeme akışı

  1. Alıcı sitenizde "Öde" der. Sunucunuz POST /api/v1/payments ile ödeme oluşturur.
  2. Yanıttaki payment_url adresine alıcıyı yönlendirirsiniz.
  3. Alıcı ödeme sayfasında bilgilerini girip kartla öder.
  4. Sonuç callback_url adresinize imzalı webhook olarak gelir. Siparişi burada onaylayın.
  5. Alıcı success_url veya fail_url adresinize döner.
Önemli: Siparişi dönüş adresine gelen parametrelere bakarak onaylamayın; bu adresler alıcının tarayıcısından açılır ve değiştirilebilir. Onay için webhook'u veya GET /payments/{id} sorgusunu kullanın.

Kimlik doğrulama

Her isteğe gizli anahtarınızı Authorization başlığında Bearer olarak ekleyin. Gizli anahtar yalnızca sunucunuzda durmalı; tarayıcıya, mobil uygulamaya veya herkese açık depoya koymayın. Anahtarınız sızdıysa panelden hemen yenileyin, eskisi o anda geçersiz olur.

AnahtarBiçimKullanım
API anahtarıpk_live_…Hesabınızı tanımlar, destek taleplerinde paylaşabilirsiniz.
Gizli anahtarsk_live_…API isteklerini yetkilendirir. Gizli tutun.
Webhook imza anahtarıwhsec_…Gelen webhook'ların imzasını doğrulamak için.
GET/api/v1/ping

Anahtarınızı ve bağlantınızı test eder; komisyon oranı ve bloke süresini döndürür.

İstek
curl https://shopidi.com/api/v1/ping -H "Authorization: Bearer sk_live_XXXXXXXXXXXXXXXX"
Yanıt · 200
{
  "ok": true,
  "merchant": { "name": "Toprak Atölye Ltd", "website": "https://www.siteniz.com" },
  "commission_rate": 5,
  "hold_days": 7,
  "max_amount": 50000
}

Ödeme oluştur

POST/api/v1/payments

Tek kullanımlık bir ödeme oluşturur ve 201 ile ödeme nesnesini döndürür. Yanıttaki id değerini siparişinizle birlikte saklayın.

AlanTürAçıklama
amount zorunlustring / sayıTahsil edilecek tutar, TL. En fazla iki ondalık, nokta ile: 1249.90. En az 1 TL; üst sınır /ping yanıtındaki max_amount.
order_id zorunlustringSizin sipariş numaranız. Harf, rakam, . _ -; en fazla 64 karakter. Hesabınızda benzersiz olmalı (aynı sipariş için iki kez ödeme açılamaz).
description zorunlustringÖdeme sayfasında alıcıya gösterilen açıklama (en fazla 160 karakter).
success_url zorunluurlBaşarılı ödemeden sonra alıcının döneceği adres.
fail_url zorunluurlÖdeme başarısız olduğunda veya alıcı vazgeçtiğinde dönülecek adres.
callback_urlurlBu ödemenin webhook adresi. Gönderilmezse panelde kayıtlı varsayılan webhook adresi kullanılır.
currencystringYalnızca TRY.
expires_intamsayıÖdeme sayfasının geçerlilik süresi, dakika. 5–10080, varsayılan 60.
customer.name / .email / .phonestringÖdeme formunda önceden doldurulur. Alıcı değiştirebilir.
metadatanesneEn fazla 20 anahtar, değerler en fazla 255 karakterlik metin. Size aynen geri döner.
Alan adı kuralı: success_url, fail_url ve callback_url https olmalı ve başvuruda bildirdiğiniz web sitesinin alan adında veya bir alt alan adında bulunmalıdır (ör. siteniz.com için www.siteniz.com, odeme.siteniz.com). Farklı bir alan adı kullanacaksanız destek ekibine yazın.
cURL
curl -X POST https://shopidi.com/api/v1/payments \
  -H "Authorization: Bearer sk_live_XXXXXXXXXXXXXXXX" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": "1249.90",
    "order_id": "SIP-100245",
    "description": "Sipariş #100245",
    "success_url": "https://www.siteniz.com/odeme/basarili",
    "fail_url": "https://www.siteniz.com/odeme/hata",
    "callback_url": "https://www.siteniz.com/shopidi/webhook",
    "expires_in": 60,
    "customer": { "name": "Ayşe Kaya", "email": "ayse@ornek.com", "phone": "05551112233" },
    "metadata": { "cart_id": "88812" }
  }'
PHP
<?php
$payload = [
    'amount'      => '1249.90',
    'order_id'    => 'SIP-100245',
    'description' => 'Sipariş #100245',
    'success_url' => 'https://www.siteniz.com/odeme/basarili',
    'fail_url'    => 'https://www.siteniz.com/odeme/hata',
    'customer'    => ['name' => 'Ayşe Kaya', 'email' => 'ayse@ornek.com'],
];

$ch = curl_init('https://shopidi.com/api/v1/payments');
curl_setopt_array($ch, [
    CURLOPT_POST           => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer ' . getenv('SHOPIDI_SECRET_KEY'),
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS     => json_encode($payload),
]);
$response = json_decode(curl_exec($ch), true);
$status   = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

if ($status === 201) {
    // Ödeme kimliğini siparişinizle eşleştirip saklayın
    saveShopidiPaymentId('SIP-100245', $response['id']);
    header('Location: ' . $response['payment_url']);
    exit;
}
// Hata: $response['error']['code'], $response['error']['message']
Node.js
const res = await fetch('https://shopidi.com/api/v1/payments', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.SHOPIDI_SECRET_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    amount: '1249.90',
    order_id: 'SIP-100245',
    description: 'Sipariş #100245',
    success_url: 'https://www.siteniz.com/odeme/basarili',
    fail_url: 'https://www.siteniz.com/odeme/hata',
  }),
});
const payment = await res.json();
if (res.status === 201) {
  // Express örneği
  return reply.redirect(payment.payment_url);
}
console.error(payment.error.code, payment.error.message);

Ödeme sorgula

GET/api/v1/payments/{id}

GET/api/v1/payments?order_id={order_id}

Ödemenin güncel durumunu döndürür. Webhook gelmediyse veya alıcı sitenize döndüğünde durumu doğrulamak için kullanın.

İstek
# Ödeme kimliğiyle
curl https://shopidi.com/api/v1/payments/K7PQ2XAB9M \
  -H "Authorization: Bearer sk_live_XXXXXXXXXXXXXXXX"

# Kendi sipariş numaranızla
curl "https://shopidi.com/api/v1/payments?order_id=SIP-100245" \
  -H "Authorization: Bearer sk_live_XXXXXXXXXXXXXXXX"

İptal

POST/api/v1/payments/{id}/cancel

Henüz ödenmemiş bir ödemeyi iptal eder; ödeme sayfası artık açılmaz. Örneğin alıcı sepetini değiştirdiğinde eski ödemeyi iptal edip yenisini oluşturun.

İstek
curl -X POST https://shopidi.com/api/v1/payments/K7PQ2XAB9M/cancel \
  -H "Authorization: Bearer sk_live_XXXXXXXXXXXXXXXX"

İade

POST/api/v1/payments/{id}/refunds

Tahsil edilmiş ödemeyi tamamen veya kısmen iade eder. Tutar alıcının kartına bankasına göre birkaç iş günü içinde yansır. İade tutarı mağaza bakiyenizden düşülür; komisyon iade edilen tutar oranında geri verilir.

AlanTürAçıklama
amountstring / sayıİade tutarı. Gönderilmezse iade edilebilir tutarın tamamı.
reasonstringİade nedeni (kayıtlarda tutulur).
İstek
# Tam iade (amount gönderilmezse iade edilebilir tutarın tamamı)
curl -X POST https://shopidi.com/api/v1/payments/K7PQ2XAB9M/refunds \
  -H "Authorization: Bearer sk_live_XXXXXXXXXXXXXXXX" \
  -H "Content-Type: application/json" \
  -d '{ "amount": "250.00", "reason": "Ürün iadesi" }'

Ödeme nesnesi

Tüm uç noktalar ve webhook'lar aynı nesneyi döndürür. transaction son kart denemesini gösterir; commission ve net_amount yalnızca ödenmiş ödemelerde bulunur.

payment
{
  "id": "K7PQ2XAB9M",
  "object": "payment",
  "status": "paid",
  "order_id": "SIP-100245",
  "amount": "1249.90",
  "currency": "TRY",
  "description": "Sipariş #100245",
  "payment_url": "https://shopidi.com/odeme/K7PQ2XAB9M",
  "customer": { "name": "Ayşe Kaya", "email": "ayse@ornek.com" },
  "metadata": { "cart_id": "88812" },
  "created_at": "2026-10-23T14:02:11+03:00",
  "expires_at": "2026-10-23T15:02:11+03:00",
  "paid_at": "2026-10-23T14:04:37+03:00",
  "refunded_amount": "0.00",
  "transaction": {
    "number": "LP-2610-48213",
    "status": "succeeded",
    "card": "VISA **** 4242",
    "commission": "62.50",
    "net_amount": "1187.40"
  }
}

Durumlar

DurumAnlamı
pendingÖdeme bekleniyor (alıcı henüz ödemedi veya banka onayı sürüyor).
paidTahsilat başarılı. Siparişi bu durumda onaylayın.
failedSon deneme başarısız. Süre dolmadıysa alıcı aynı bağlantıdan tekrar deneyebilir.
expiredexpires_at geçti, artık ödenemez.
cancelledAPI ile iptal edildi.
partially_refundedTutarın bir kısmı iade edildi.
refundedTamamı iade edildi.

Dönüş adresleri

Alıcı ödemeden sonra aşağıdaki parametrelerle sitenize döner:

  • payment_id: ödeme kimliği (id)
  • order_id: sizin sipariş numaranız
  • status: paid veya failed

Başarısız ödemede alıcı önce ödeme sayfasında "Tekrar dene" seçeneğini görür; vazgeçerse fail_url adresine döner. Ödeme sayfasının süresi dolana kadar aynı payment_url tekrar kullanılabilir.

success_url · PHP
<?php
// success_url: https://www.siteniz.com/odeme/basarili?payment_id=K7PQ2XAB9M&order_id=SIP-100245&status=paid
// Bu parametrelere güvenmeyin: sonucu sunucunuzdan API ile doğrulayın.
$payment = shopidiGet('/api/v1/payments/' . urlencode($_GET['payment_id']));

if ($payment['status'] === 'paid' && $payment['order_id'] === $_GET['order_id']) {
    showThankYou($payment);
} else {
    showPaymentPending();
}

Webhook

Ödeme durumu değiştiğinde Shopidi, callback_url adresinize (yoksa paneldeki varsayılan adrese) JSON gövdeli bir POST isteği gönderir. Adresiniz 8 saniye içinde 2xx yanıt vermezse bildirim 1, 5, 15, 30 dakika ve 1, 3, 6, 12 saat sonra yeniden gönderilir.

OlayNe zaman
payment.succeededTahsilat başarılı oldu.
payment.failedBir ödeme denemesi başarısız oldu (alıcı tekrar deneyebilir).
payment.refundedKısmi veya tam iade yapıldı (refunded_amount güncel tutarı içerir).
Örnek istek
POST /shopidi/webhook HTTP/1.1
Content-Type: application/json
User-Agent: Shopidi-Webhook/1.0
X-Shopidi-Event: payment.succeeded
X-Shopidi-Signature: t=1792753477,v1=5f2c0e8b9d...e41a

{
  "id": "evt_3n9x0q1v7c8k2m4p6r5t0w1y",
  "type": "payment.succeeded",
  "created_at": "2026-10-23T14:04:38+03:00",
  "data": { "id": "K7PQ2XAB9M", "object": "payment", "status": "paid", "order_id": "SIP-100245", "amount": "1249.90", "...": "..." }
}

İmza doğrulama

X-Shopidi-Signature başlığı t=zaman_damgası,v1=imza biçimindedir. İmza, "{t}.{ham gövde}" metninin webhook imza anahtarınızla HMAC-SHA256 özetidir (hex). İmzayı mutlaka doğrulayın, karşılaştırmayı sabit zamanlı yapın ve 5 dakikadan eski zaman damgalarını reddedin. Aynı olay birden fazla kez gelebilir; işleminizi tekrar gelse de aynı sonucu verecek şekilde yazın.

PHP
<?php
$secret    = getenv('SHOPIDI_WEBHOOK_SECRET'); // whsec_...
$body      = file_get_contents('php://input'); // ham gövde, json_decode etmeden önce
$header    = $_SERVER['HTTP_X_SHOPIDI_SIGNATURE'] ?? '';

parse_str(str_replace(',', '&', $header), $sig); // ['t' => ..., 'v1' => ...]
$expected = hash_hmac('sha256', ($sig['t'] ?? '') . '.' . $body, $secret);

if (empty($sig['v1']) || ! hash_equals($expected, $sig['v1']) || abs(time() - (int) $sig['t']) > 300) {
    http_response_code(400);
    exit('invalid signature');
}

$event = json_decode($body, true);
$payment = $event['data'];

// Aynı olay birden çok kez gelebilir: event id veya ödeme durumuyla tekrarı engelleyin
if ($event['type'] === 'payment.succeeded' && $payment['status'] === 'paid') {
    markOrderPaid($payment['order_id'], $payment['id'], $payment['amount']);
}

http_response_code(200);
echo 'ok';
Node.js
import crypto from 'node:crypto';
import express from 'express';

const app = express();

// Ham gövde gerekir: express.json() yerine express.raw()
app.post('/shopidi/webhook', express.raw({ type: 'application/json' }), (req, res) => {
  const header = req.get('X-Shopidi-Signature') || '';
  const parts = Object.fromEntries(header.split(',').map((p) => p.split('=')));
  const expected = crypto
    .createHmac('sha256', process.env.SHOPIDI_WEBHOOK_SECRET)
    .update(`${parts.t}.${req.body}`)
    .digest('hex');

  const valid = parts.v1 && parts.v1.length === expected.length
    && crypto.timingSafeEqual(Buffer.from(parts.v1), Buffer.from(expected))
    && Math.abs(Date.now() / 1000 - Number(parts.t)) < 300;
  if (!valid) return res.status(400).send('invalid signature');

  const event = JSON.parse(req.body);
  if (event.type === 'payment.succeeded') {
    markOrderPaid(event.data.order_id, event.data.id, event.data.amount);
  }
  res.send('ok');
});

Hatalar

Hatalarda uygun HTTP kodu ve aşağıdaki biçimde gövde döner:

Örnek hata
HTTP/1.1 422 Unprocessable Content
{
  "error": {
    "code": "validation_error",
    "message": "Gönderilen veriler geçersiz.",
    "fields": {
      "success_url": ["Adres https olmalı ve başvurudaki web sitenizin alan adında (siteniz.com) bulunmalı."]
    }
  }
}
HTTPcodeAçıklama
401unauthorizedAuthorization başlığı yok veya gizli anahtar hatalı.
403forbiddenAPI erişiminiz askıda, onaysız veya mağaza hesabınız aktif değil.
404not_foundÖdeme bulunamadı (yalnızca kendi ödemelerinizi görebilirsiniz).
409duplicate_order_idBu order_id ile ödeme zaten var. Yanıttaki payment alanında mevcut ödeme döner.
409invalid_stateİşlem bu durumda yapılamaz (ör. ödenmiş ödemeyi iptal etmek).
422validation_errorAlan hataları fields içinde döner.
422refund_failedİade yapılamadı (ör. tutar iade edilebilir tutarı aşıyor veya bakiye yetersiz).
429—Dakikada 120 istek sınırı aşıldı. Retry-After başlığına göre bekleyin.
503service_unavailableHizmet geçici olarak kapalı.

Canlıya geçiş kontrol listesi

  • Gizli anahtar yalnızca sunucuda, ortam değişkeninde saklanıyor.
  • Siparişler yalnızca imzası doğrulanmış webhook'la veya API sorgusuyla onaylanıyor.
  • Webhook işleyiciniz aynı olayın tekrarına dayanıklı ve tutarı siparişle karşılaştırıyor.
  • Dönüş adresleriniz başvurudaki alan adında ve https.
  • Ödeme sayfasının süresi (expires_in) sepet sürenize uygun.
  • İlk canlı ödemeyi küçük bir tutarla deneyip panelden iade ederek uçtan uca kontrol edin.

Sorunuz mu var? Destek ekibine yazın; isteğinize API anahtarınızı (pk_live_…) ve ödeme kimliğini ekleyin. Gizli anahtarınızı kimseyle paylaşmayın.

🍪 Çerezleri kullanıyoruz

Sitenin çalışması için zorunlu çerezler kullanırız. Ziyaretinizi analiz etmek ve size uygun reklamlar göstermek için ek çerezleri yalnızca onayınızla kullanırız. Çerez politikası