Shopier API ile ödeme formu oluşturma
Bu yazı 2020 yılından. Anlatılan sürümler, fiyatlar ve adresler o günden bu yana değişmiş olabilir.
Birçok kişinin online ödeme almak için tercih ettiği Shopier'in API'si sayesinde PHP ile kendi ödeme formunuzu oluşturabilirsiniz. Bu yazıda bunu adım adım göstereceğim.
Güncelleme (Kasım 2026): Bu yazıyı 2020'de yazmıştım. O günden beri iki şey değişti. Birincisi, buradaki eski koddaki imza (signature) kısmında hatalar vardı; kodu olduğu gibi kullanan biri "imza geçersiz" hatası alıyordu, onları düzelttim. İkincisi ve daha önemlisi, o zamanki kod bir ödeme formunda asla yapılmaması gereken bir hata içeriyordu: ödeme tutarını kullanıcıdan (URL'den) alıyordu. Bunu ve güvenlikle ilgili diğer noktaları ayrı bir bölümde anlattım. Ayrıca Shopier artık resmi bir API sunuyor, ondan da bahsedeceğim.
Önce şunu söyleyeyim: Yeni bir proje yapıyorsanız bu yazıdaki elle yazılmış sınıfı kopyalamak yerine, Shopier'in güncel yöntemlerinden birini kullanmanızı öneririm:
- Shopier bugün resmi bir REST API (v1) sunuyor. Panelden bir kişisel erişim anahtarı (Personal Access Token) alıp ödeme bağlantısı oluşturuyor, ödemeleri de
Shopier-Signaturebaşlıklı webhook ile doğruluyorsunuz. Güncel ve bakımlı yol budur.- Hazır bir çözüm isterseniz, topluluk tarafından bakımı yapılan açık kaynak PHP SDK'ları var (örneğin Composer ile kurulan
erkineren/shopierpaketi). Kendi imza kodunuzu yazmaktan kurtarır.Aşağıdaki anlatım, eski usul form gönderme yöntemini merak edenler ya da hâlâ onu kullananlar için; mantığı anlamak açısından da faydalı.
Mağaza ve API bilgileri
Önce Shopier'e kayıt olup mağazanızı oluşturun. Mağaza onaylandıktan sonra panelde Entegrasyonlar > Modül Yönetimi kısmında API kullanıcı adınız (key) ve API şifreniz (secret) yazar. İkisi de bu işlem için lazım; şifrenizi kimseyle paylaşmayın, koda gömüp herkese açık bir yere (GitHub gibi) koymayın.
1. Shopier sınıfı
Shopier.php adında bir dosya oluşturup aşağıdaki sınıfı içine koyuyoruz. Eski yazıdaki koda göre düzelttiğim yerleri kodun altında açıkladım.
<?php
class Shopier
{
private string $payment_url = 'https://www.shopier.com/ShowProduct/api_pay4.php';
private array $buyer = [];
private string $currency = 'TRY';
public function __construct(
private string $api_key,
private string $api_secret,
private string $module_version = '1.0.4'
) {}
public function setBuyer(array $f): void { $this->load(['id','first_name','last_name','email','phone'], $f); }
public function setOrderBilling(array $f): void { $this->load(['billing_address','billing_city','billing_country','billing_postcode'], $f); }
public function setOrderShipping(array $f): void { $this->load(['shipping_address','shipping_city','shipping_country','shipping_postcode'], $f); }
private function load(array $required, array $f): void
{
foreach ($required as $key) {
if (!array_key_exists($key, $f)) {
throw new Exception("$key alanı zorunlu");
}
$this->buyer[$key] = $f[$key];
}
}
/** Ödeme formunu üretir. $product_name: satılan ürün/hizmetin adı. */
public function generateForm(string $order_id, float $order_total, string $product_name, string $callback_url): string
{
$args = [
'API_key' => $this->api_key, // DÜZELTME: eskiden sabit metin yazıyordu
'website_index' => 1,
'platform_order_id' => $order_id,
'product_name' => $product_name, // DÜZELTME: eskiden 'Instagram Takipci' sabitti
'product_type' => 0,
'buyer_name' => $this->buyer['first_name'],
'buyer_surname' => $this->buyer['last_name'],
'buyer_email' => $this->buyer['email'],
'buyer_account_age' => 0,
'buyer_id_nr' => $this->buyer['id'],
'buyer_phone' => $this->buyer['phone'],
'billing_address' => $this->buyer['billing_address'],
'billing_city' => $this->buyer['billing_city'],
'billing_country' => $this->buyer['billing_country'],
'billing_postcode' => $this->buyer['billing_postcode'],
'shipping_address' => $this->buyer['shipping_address'],
'shipping_city' => $this->buyer['shipping_city'],
'shipping_country' => $this->buyer['shipping_country'],
'shipping_postcode' => $this->buyer['shipping_postcode'],
'total_order_value' => number_format($order_total, 2, '.', ''),
'currency' => $this->currencyCode(),
'platform' => 0,
'is_in_frame' => 0,
'current_language' => 0,
'modul_version' => $this->module_version,
'random_nr' => random_int(100000, 999999),
'callback' => $callback_url,
];
// imza: random_nr + order_id + tutar + para birimi, API SECRET ile imzalanır
$data = $args['random_nr'] . $args['platform_order_id'] . $args['total_order_value'] . $args['currency'];
$args['signature'] = base64_encode(hash_hmac('sha256', $data, $this->api_secret, true)); // DÜZELTME: eskiden sabit 'apisfire' yazıyordu
$inputs = '';
foreach ($args as $name => $value) {
$inputs .= '<input type="hidden" name="' . htmlspecialchars($name) . '" value="' . htmlspecialchars($value) . '">';
}
return '<!doctype html><html><head><meta charset="utf-8"></head><body>'
. '<form id="shopier" method="post" action="' . $this->payment_url . '">' . $inputs . '</form>'
. '<script>document.getElementById("shopier").submit();</script>'
. '</body></html>';
}
/** Shopier geri dönüşünü (callback) doğrular. Siparişi "ödendi" saymadan önce MUTLAKA çağırın. */
public function verifyCallback(array $post): bool
{
if (empty($post['platform_order_id']) || empty($post['random_nr']) || empty($post['signature'])) {
return false;
}
$expected = base64_encode(hash_hmac('sha256', $post['random_nr'] . $post['platform_order_id'], $this->api_secret, true));
return hash_equals($expected, $post['signature']); // DÜZELTME: eskiden sabit bir anahtar ve güvensiz "==" vardı
}
private function currencyCode(): int
{
return ['TRY' => 0, 'USD' => 1, 'EUR' => 2][strtoupper($this->currency)] ?? 0;
}
}Eski koda göre düzelttiğim dört şey:
API_keyartık gerçek anahtarı gönderiyor. Eskiden buraya'apikullaniciadi'diye bir metin yazılıydı, yani imza hiç çalışmıyordu.- İmza, gerçek API şifresiyle üretiliyor. Eskiden şifre yerine
'apisfire'yazıyordu; bu yüzden Shopier imzayı reddediyordu. - Geri dönüş doğrulamasındaki sabit anahtar kaldırıldı. Eski kodda
verifyShopierSignatureiçinde sabit bir gizli anahtar gömülüydü (b0345d...). Bu hem yanlıştı hem de o değer gerçek bir API şifresiyse herkese açık demekti. Artık doğrulama da sizin kendi API şifrenizi kullanıyor ve karşılaştırmahash_equalsile zaman-güvenli yapılıyor. - Ürün adı ve tutar artık dışarıdan parametre. Eskiden ürün adı
'Instagram Takipci'olarak sabitti.
2. Sınıfı kullanmak
İkinci bir dosya (odeme.php) oluşturup sınıfı şöyle kullanıyoruz. Buradaki en kritik nokta, tutarı kullanıcıdan değil sunucudan belirlemek (bir alttaki güvenlik bölümünde anlatıyorum):
<?php
require 'Shopier.php';
// --- tutarı SUNUCUDA belirleyin, kullanıcıdan almayın ---
$urunler = ['paket-10' => 50.00, 'paket-25' => 100.00]; // fiyat listesi sizde dursun
$urun = $_POST['urun'] ?? '';
if (!isset($urunler[$urun])) {
die('Geçersiz ürün');
}
$tutar = $urunler[$urun];
$siparis_no = uniqid('ord_', true); // kendi sipariş numaranız (veritabanına kaydedin)
$shopier = new Shopier('API_KEY', 'API_SECRET'); // bilgileri koda gömmeyin, .env'den okuyun
$shopier->setBuyer([
'id' => 1,
'first_name' => trim($_POST['ad'] ?? ''),
'last_name' => trim($_POST['soyad'] ?? ''),
'email' => filter_var($_POST['email'] ?? '', FILTER_VALIDATE_EMAIL) ?: 'musteri@example.com',
'phone' => preg_replace('/\D/', '', $_POST['telefon'] ?? ''),
]);
$shopier->setOrderBilling([
'billing_address' => 'Dijital Teslimat', 'billing_city' => 'Istanbul',
'billing_country' => 'Turkey', 'billing_postcode' => '34000',
]);
$shopier->setOrderShipping([
'shipping_address' => 'Dijital Teslimat', 'shipping_city' => 'Istanbul',
'shipping_country' => 'Turkey', 'shipping_postcode' => '34000',
]);
echo $shopier->generateForm($siparis_no, $tutar, 'Dijital Ürün', 'https://siteniz.com/odeme-sonrasi.php');Bu sayfa çalışınca kullanıcı otomatik olarak Shopier'in kredi kartı sayfasına yönlenir.
3. Formu bağlamak
Kullanıcının ürünü seçtiği formu odeme.php'ye bağlıyoruz. Dikkat edin: form post ile gönderiliyor ve tutar değil, ürün seçimi gidiyor:
<form method="post" action="odeme.php">
<input type="text" name="ad" placeholder="İsim" required>
<input type="text" name="soyad" placeholder="Soyisim" required>
<input type="email" name="email" placeholder="E-posta" required>
<select name="urun" required>
<option value="paket-10">10'luk paket - 50 TL</option>
<option value="paket-25">25'lik paket - 100 TL</option>
</select>
<button type="submit">Ödemeye geç</button>
</form>Güvenlik: en önemli kısım
Ödeme işlerinde bir hata para kaybettirir, o yüzden bunları atlamayın.
1. Tutarı asla kullanıcıdan almayın. Eski yazıdaki en büyük hata buydu: form method="get" ile çalışıyor ve tutar $_GET["tutar"] olarak alınıyordu. Bu şu demek: kullanıcı tarayıcının adres çubuğundaki tutarı değiştirip 100 TL'lik ürünü 1 TL'ye ödeyebilir. Fiyatı her zaman sunucuda, kendi listenizden belirleyin. Kullanıcının kendi tutarını girdiği bir bağış formu yapıyorsanız bile gelen değeri sayıya çevirip pozitif olduğunu kontrol edin.
2. Ödemeyi geri dönüşte (callback) sunucuda doğrulayın. Kullanıcının ödeme sonrası sitenize geri dönmesi, ödemenin gerçekten yapıldığı anlamına gelmez; o adrese ödeme yapmadan da gelinebilir. Shopier geri dönüşte size imzalı bir veri gönderir; siparişi "ödendi" işaretlemeden önce bunu verifyCallback ile doğrulayın:
<?php
require 'Shopier.php';
$shopier = new Shopier('API_KEY', 'API_SECRET');
if ($shopier->verifyCallback($_POST) && ($_POST['status'] ?? '') === 'success') {
$siparis_no = $_POST['platform_order_id'];
// sipariş numarasını veritabanında bulun, tutarını kendi kaydınızla karşılaştırın,
// sonra "ödendi" olarak işaretleyin ve ürünü/hizmeti teslim edin
} else {
// ödeme doğrulanamadı, teslim etmeyin
}3. API bilgilerinizi koda gömmeyin. API_KEY ve API_SECRET'i kodun içine yazıp paylaşmayın; bir .env dosyasından okuyun ve o dosyayı sürüm kontrolüne (git) eklemeyin.
4. Kullanıcıdan gelen her şeyi temizleyin. Yukarıdaki örnekte e-postayı filter_var ile, telefonu preg_replace ile süzdüm. Siz de kendi alanlarınıza bunu uygulayın.
Not: Eğer eski kodu daha önce canlıda kullandıysanız ve içindeki o sabit anahtar (
b0345d...) sizin gerçek Shopier API şifrenizse, Shopier panelinden şifrenizi hemen yenileyin.