Laravel İyzico Sanal Pos Entegrasyonu

2020 başından beri Laravel ile proje geliştiriyorum. Daha önce Symfony ile tecrübem olduğu için aslında öğrenmesi çok zor olmadı. İçine dalınca da ister istemez öğreniliyor. Hızlı bir giriş yaptım diyebilirim. İrili ufaklı 4-5 proje çıkarttım sanırım. Son Laravel projesinde iyzico entegrasyonu yapıyordum bazı sorunlarla karşılaştım.

2 günümü harcayan, beni deli eden, bir ton Türkçe ve İngilizce forum ve siteyi hatim etmemi sağlayan bu entegrasyon nasıl yapılıyor bundan bahsedeceğim. Sanırım sizde zaten o yüzden bu yazıyı okuyorsunuz duygularınıza tercüman olmaya çalışıyorum 🙂

Öncelikle neredeyse tüm sıkıntılarla karşılaştım diyebilirim. Hepsini tek tek anlatacağım ki benim gibi sorun yaşayan arkadaşlar rahatlıkla durumu atlatabilsinler.

Karşılaştığım sorunların listesi

  • 419 Page Expired Hatasının çözümü
  • .env dosyasındaki eksik düzenlemeler
  • Callback sonrası oturum sonlanması sorunu (Auth)

Evet sırasıyla bunları başlıklar halinde irdeliyor olacağız. Ondan önce bir Laravel projenizin olduğunu varsayıyorum. İyzico entegrasyonuyla başlayalım.

İyzico Entegrasyonu

https://dev.iyzipay.com/tr Öncelikle bu adrese giderek neler yapmamız gerekiyor bir bakalım. Yapılması gereken tüm adımlar tek tek anlatılmış. Normalde bir başvuru yaptığınızda size gerçek bir API Key ve Secret Key veriliyor. O bilgiler canlı ortamda kullanmak için lazım oluyor. Ancak siz başvuru yapmadan da iyzico entegrasyonu yapabilirsiniz. Sandbox sayesinde bu adımı kolaylıkla gerçekleştirebiliyoruz. https://sandbox-merchant.iyzipay.com/ bu adresten hesap oluşturup kullanmaya başlayabilirsiniz. Zaten iyzico’nun kendi sitesinde de yönlendirmeler mevcut. Kısaca hesabı oluşturup giriş yaptıktan sonra ayarlar menüsünden API Key ve Secret Key oluşturup ödeme formuyla çekim yapmak için hazır oluyorsunuz.

Gelelim kodları indirmeye. Ben direk composer yüklemesi gerçekleştirdim. https://github.com/iyzico Github üzerinden PHP veya diğer kütüphaneler için ilgili kodları indirebilirsiniz. Ben PHP versiyonunu kurdum. Laravel içinde bir paket gözüküyor ancak zamanında farklı amaç için yazılmış ve artık desteklenmiyor. O yüzden PHP paketinden devam.

Kurulum

composer require iyzico/iyzipay-php

Yukarıdaki kodu terminalden çalıştırarak paketi direk Laravel’e ekleyebilirsiniz. Proje klasörününün içinde olmayı unutmayın 🙂

Kodların kullanımı için isterseniz iyzico dökümanından isterseniz de github üzerindeki örneklerden devam edebilirsiniz. Bir çok kullanım şekli mevcut. Biz hazır ödeme formunu kullanacağız. Bunuda isterseniz popup şeklinde, isterseniz de direk sayfaya gömülü olarak kullanmanız mümkün.

Kodların Yazımı

Kullanım kodlarını da adım adım anlatmak istiyorum daha açıklayıcı olur.

$options = new \Iyzipay\Options();
$options->setApiKey("your api key");
$options->setSecretKey("your secret key");
$options->setBaseUrl("https://sandbox-api.iyzipay.com");

Yukarıdaki kodlar için sandboxtan aldığınız API ve Secret keyleri yazıyoruz. Canlı ortama geçerseniz bunları size verilen kodlarla değiştirmeniz gerekecek ve https://sandbox-api.iyzipay.com yolunu https://api.iyzipay.com olarak güncellemeniz gerekecek.

$request = new \Iyzipay\Request\CreatePaymentRequest();
$request->setLocale(\Iyzipay\Model\Locale::TR);
$request->setConversationId("123456789");
$request->setPrice("1");
$request->setPaidPrice("1.2");
$request->setCurrency(\Iyzipay\Model\Currency::TL);
$request->setInstallment(1);
$request->setBasketId("B67832");
$request->setPaymentChannel(\Iyzipay\Model\PaymentChannel::WEB);
$request->setPaymentGroup(\Iyzipay\Model\PaymentGroup::PRODUCT);
$request->setCallbackUrl("url");

İyzico içerisinde de request ile başlayan bir obje var. Biz bunu Laravel ile gelen değerleri almak için kullanıyoruz. Karışmasın diye ya Laraveldekine ya da iyzico daki değişkenlerin isimlerini değiştirebilirsiniz. Bu bölüm çekim için ayarlanan detaylar mevcut. Gerekli açıklamaların hepsi iyzico dökümanında mevcut. Hangi alan neresi için kullanılıyor gibi. Tekrar tekrar yazmıyorum.


$buyer = new \Iyzipay\Model\Buyer();
$buyer->setId("BY789");
$buyer->setName("John");
$buyer->setSurname("Doe");
$buyer->setGsmNumber("+905350000000");
$buyer->setEmail("email@email.com");
$buyer->setIdentityNumber("74300864791");
$buyer->setLastLoginDate("2015-10-05 12:43:35");
$buyer->setRegistrationDate("2013-04-21 15:12:09");
$buyer->setRegistrationAddress("Nidakule Göztepe, Merdivenköy Mah. Bora Sok. No:1");
$buyer->setIp("85.34.78.112");
$buyer->setCity("Istanbul");
$buyer->setCountry("Turkey");
$buyer->setZipCode("34732");
$request->setBuyer($buyer);

Buyer bölümü kullanıcı bilgilerinin ayarlandığı bölüm. Kişinin adı, soyadı, adresi ve diğer şahsi bilgileri burada tanımlanıyor.

$shippingAddress = new \Iyzipay\Model\Address();
$shippingAddress->setContactName("Jane Doe");
$shippingAddress->setCity("Istanbul");
$shippingAddress->setCountry("Turkey");
$shippingAddress->setAddress("Nidakule Göztepe, Merdivenköy Mah. Bora Sok. No:1");
$shippingAddress->setZipCode("34742");
$request->setShippingAddress($shippingAddress);

$billingAddress = new \Iyzipay\Model\Address();
$billingAddress->setContactName("Jane Doe");
$billingAddress->setCity("Istanbul");
$billingAddress->setCountry("Turkey");
$billingAddress->setAddress("Nidakule Göztepe, Merdivenköy Mah. Bora Sok. No:1");
$billingAddress->setZipCode("34742");
$request->setBillingAddress($billingAddress);

Teslimat adresi (Shipping) ve fatura adresi (Billing) bölümleride buradan tanımlama yapıyoruz. Çoğu zamana fatura adresi teslim adresiyle aynı olur. Ancak teslim yeri farklı bir adreste olduğu olabiliyor. Bilgileri eksiksiz doldurmanız önemli.

Ürünlerin Önemi

$basketItems = array();
$firstBasketItem = new \Iyzipay\Model\BasketItem();
$firstBasketItem->setId("BI101");
$firstBasketItem->setName("Binocular");
$firstBasketItem->setCategory1("Collectibles");
$firstBasketItem->setCategory2("Accessories");
$firstBasketItem->setItemType(\Iyzipay\Model\BasketItemType::PHYSICAL);
$firstBasketItem->setPrice("0.3");
$basketItems[0] = $firstBasketItem;

Yukarıdaki bölüm önemli noktalardan bir diğeri. Sattığınız ürünlerin bir listesini burada oluşturacaksınız. Tek bir ürün satıyorsanız sorun yok. Ancak birden fazla ürün varsa foreach ile dönerek alınan ürün kadar yukarıdaki bloktan oluşturmanız gerekiyor. $basketItems[0] = $firstBasketItem; yazan bölümde indis sıfırdan (0) başlıyor. Örneğin 3 ürün varsa 0,1,2 indislerini oluşturmanız gerekecek. Burası önemli demiştim çünkü hem kalem kalem bilgi vermemiz gerekiyor hemde satır ve toplam bazında iyzico çekilecek miktar eşleşiyor mu diye kontrol sağlıyor. Kendi kullandığım yapıyı altta paylaşıyorum.

$piece = 0;
            foreach($data['product'] as $product){

                $BasketItem = new BasketItem();
                $BasketItem->setId($product->id);
                $BasketItem->setName($product->product_name);
                $BasketItem->setCategory1($product->category->category_name);
                $BasketItem->setCategory2($product->category->category_name);
                $BasketItem->setItemType(\Iyzipay\Model\BasketItemType::PHYSICAL);
                $BasketItem->setPrice($product->price);
                $basketItems[$piece] = $BasketItem;

                $piece++;
            }

            $request->setBasketItems($basketItems);

piece değişkeni ile kaçıncı indis olduğunu tutuyorum. ve sırayla ürünleri indislere atarak en son basketItems a gönderiyorum. Kullandığım yapı böyle.

Son olarak formu ekrana basmamız için bir satırlık kodumuz kaldı.

$form = \Iyzipay\Model\CheckoutFormInitialize::create($request, $options)

Form değişkenini view ekranına gönderdikten sonra ekrana html olarak basarsanız form popup olarak karşınıza çıkıcaktır.

{!! $form !!}
iyzico entegrasyonu Popup Ödeme Ekranı

Ancak hemen altına birde iyziconun verdiği form div’ini eklerseniz ekrana gömümü olarak gelecektir.

{!! $form !!}
<div id="iyzipay-checkout-form" class="responsive my-5"></div>
iyzico entegrayonu Form Ödeme Ekranı

Bu sayede iyzico entegrasyonu tamamlanmış oluyor. Çekim yapmak için iyzico sitesindeki test kartlarını kullanabilirsiniz. Ay / Yıl ve CVC numarasını rastgele yazabilirsiniz sorun olmaz. yapılan başarılı çekimleri sandbox ekranından görebiliyorsunuz.

Şimdi gelelim Laravel tarafındaki sorunlara. Bu formu biz oluşturmadık. Tıklayınca bilgiler POST edilecek. Ama CSRF token olmayınca Laravel hata veriyor diyeceksiniz. Aynen öyle Şimdi bu sorunun çözümünü görelim.

419 Page Expired Hatasının Çözümü

Ödeme çekilecek ve sonuç dönecek sayfanın bağlantısını request bölümündeki callbackUrl değişkeninde belirliyoruz. Ödeme butonuna tıklayınca bu sayfaya gidecek ve 419 Expired hatası verecek. Niye formda CSRF token yok çünkü.

Bu sorunu aşmanın kolay bir yolu var. Laravel projenizin içindeki app/Http/Middleware/VerifyCsrfToken.php dosyasını açıyoruz.

protected $except = [
    "/siparis/kaydet",
];

Yukarıda bir örnek paylaştım. expect bölümüne callbackUrl de yazan url için csrf korumasını geçersiz kılıyoruz. Eğer değişken kullancaksanız * koyarak değişkenin yerini sağlayabilirsiniz. Birden fazla değişken varsa /*/* yaparak karşılık sağlayabilirsiniz.

protected $except = [
    "/siparis/kaydet/*/*",
];

Bunu yaptıktan sonra bu hatadan kurtuluyor olacaksınız.

.env ve session.php Dosyasındaki Eksik Düzenlemeler

Herkeste aynı sorun çıkmasa da ayarların düzgün yapılması olabilecek başka hatalarıda önlemede yardımcı olacaktır. .env dosyasında bulunmayan şu 2 satırıda ekleyip gerekli karşılıkları yazmanız faydanıza olacaktır.

SESSION_DOMAIN=site.com
SESSION_SECURE_COOKIE=true

Bu ayarların karşılığı config/session.php dosyasında mevcut. Session domainin karşılığı zaten belli. SESSION_SECURE_COOKIE ne işe yarıyor açıklamasına bakalım.

Bu seçeneği doğru olarak ayarladığınızda, oturum tanımlama bilgileri yalnızca tarayıcının HTTPS bağlantısı varsa sunucuya geri gönderilir. Bu, güvenli bir şekilde yapılamazsa çerezin size gönderilmesini önleyecektir.

Güvenli olacağını düşündüğüm için bu ayarı true yaptım. Bundan hariç olarak yine session.php dosyasında bu ayarın hemen altında bir ayar daha mevcut. http_only => true

Bu değerin true olarak ayarlanması, JavaScript'in tanımlama bilgisinin değerine erişmesini engeller ve tanımlama bilgisine yalnızca HTTP protokolü aracılığıyla erişilebilir. Gerekirse bu seçeneği değiştirmekte özgürsünüz.

Ben bu ayarı yanlış anlamdıysam arka plandaki JS tanımlamalarının sadece HTTP bağlantısı ile düzenleme erişimi verdiğini söylüyor. O yüzden false yaptım. HTTPS de bir sorun çıkmasın diye. Yanlışım varsa bilen söyleyebilir seve seve düzeltirim 🙂

Callback Sonrası Oturum Sonlanması Sorunu (Auth)

Burada karşılaştığım 2 tip hata var. İlkini hemen söyleyeyim. Hepimiz bunu yapıyoruz. Eğer dönüş adresindeki sayfada kullanıcı bilgilerinden herhangi birini auth->user->id ya da auth->user->name gibi bir yapı kullanarak almaya çalışmayın hata alırsınız.

Çünkü oturumla alakalı bir durum dönen sayfada mevcut değil. Sonuç dışarıdan geldiği için bu kontrolü yapamayız. Hangi oturumu kontrol edeceğiz ki? İlk başta bende denedim ama sonradan ben ne yapıyorum diyerek düzelttim. Bu sorunu bir önceki başlıkta callbackUrl içerine bir değişken koyarak kullanıcı bilgisinin id değerini göndererek sağlayabilirsiniz.

Diğer bir sorun ve beni en çok yoran sorun diyebilirim oturum sonlanması. Bilgilerin hepsi doğru, çekim yapılıyor, sonuç dönüyor ama mevcut oturum sonlanıyor. Tamam kullanıcı bilgilerini kullanamıyoruz ama giriş bilgilerimin kalması gerekiyor. Yani sağ üstte kullanıcı adım gözükmeli. Başka sayfaya geçtiğimde tekrar giriş yapmadan alışveriş yapabilmeliyim.

Bir çok araştırma yaptım sonunda sorunun çözümünü buldum. config/session.php dosyasında en sonra bulunan

'same_site' => 'none',

same_site => lax ayarını none ile değiştirerek durumu düzelttim. Üstünde bulunan açıklama şu.

Bu seçenek, siteler arası istekler gerçekleştiğinde tanımlama bilgilerinizin nasıl davranacağını belirler ve CSRF saldırılarını azaltmak için kullanılabilir. Varsayılan olarak, bu güvenli bir varsayılan değer olduğu için bu değeri "lax" olarak ayarlayacağız.
Desteklenen: "lax", "strict", "none", null

Özellikle Chrome tarayıcı kullananlar için bu ayarın lax olarak ayarlı olması durumu tam bir ızdırap konumuna taşıyacaktır ki yaşadığım şey buydu. Chrome güvenlik ayarlarındaki bazı ön tanımlı zorlamalar ile lax ayarıda birleşince çekim oluyor ancak oturum sonlanıyordu. Karşısındaki değeri none yapınca oturum sonlanması düzeldi ve derin bir nefes aldım diyebilirim.

Çekim Yapıldıktan Sonra

Her şey yolunda gider ve bir sorun çıkmazsa çekim yapıldıktan sonra birde yapılan çekimle alakalı bilgileri almak isteyeceksiniz. Onuda aşağıdaki şekilde sorgulayabilirsiniz.

$options = new \Iyzipay\Options();
$options->setApiKey("your api key");
$options->setSecretKey("your secret key");
$options->setBaseUrl("https://sandbox-api.iyzipay.com");
$request = new \Iyzipay\Request\RetrieveCheckoutFormRequest();
$request->setLocale(\Iyzipay\Model\Locale::TR);
$request->setConversationId($id);
$request->setToken($token);
$checkoutForm = \Iyzipay\Model\CheckoutForm::retrieve($request, $options);

Yaptığım entegrasyon ve karşılaştığım sorunların özeti bu şekilde diyebilirim. Şu an her şey yolunda ve rahatalıkla çekim yapılabiliyor. Umarım benim gibi sorun yaşayan birine faydalı olur ve gününü kurtarır.

Mutlu kodlamalar 🙂

Laravel İyzico Sanal Pos Entegrasyonu” ile ilgili 4 görüş

  1. Ramazan

    Sanırım şurada bir problem var.

    $request=new \Iyzipay\Request\CreatePaymentRequest();
    $form = \Iyzipay\Model\CheckoutFormInitialize::create($request, $options);

    $request CreatePaymentRequest tipinde ve CheckoutFormInitialize class’ı içerisinde static olan create() fonksiyonu $request’i CreateCheckoutFormInitializeRequest tipinde istiyor.Ama CreatePaymentRequest’e extends olarak tanımlanmamış nasıl böyle kullanılabiliyor?

    Yanıtla

Bir Cevap Yazın