- Minimum Gereksinimler
- Kurulum
- Kullanım (3D Secure Ödeme)
- Troubelshoots
- Konfigurasyon Yapısı ve Örnekler
- API ve 3D Form verisini degiştirme
- PHP >= 7.4
- mews/pos ^1.3
- laravel 8, 9, 10, 11
-
$ composer require mews/laravel-pos $ php artisan vendor:publish --tag=laravel-pos
-
/config/laravel-pos.php
ayarınızı elinizde gateway bilgileri göre güncelleyiniz. Örnek konfigurasyon:<?php # /config/laravel-pos.php return [ 'banks' => [ # array keyleri unique olmalıdır, bu keylerle Controller'larda su sekilde erisilebilir: # $this->container->get('laravel-pos:gateway:kuveytpos'); 'kuveytpos' => [ # ilk sıradaki banka injection için default olur. 'gateway_class' => \Mews\Pos\Gateways\KuveytPos::class, 'test_mode' => true, 'lang' => \Mews\Pos\PosInterface::LANG_TR, 'credentials' => [ 'payment_model' => \Mews\Pos\PosInterface::MODEL_3D_SECURE, 'merchant_id' => 'xxx', 'terminal_id' => 'yyyyyyy', 'user_name' => 'zzzzzzz', 'enc_key' => 'www123', ], 'gateway_endpoints' => [ 'payment_api' => 'https://boatest.kuveytturk.com.tr/boa.virtualpos.services/Home', 'gateway_3d' => 'https://boatest.kuveytturk.com.tr/boa.virtualpos.services/Home/ThreeDModelPayGate', 'query_api' => 'https://boatest.kuveytturk.com.tr/BOA.Integration.WCFService/BOA.Integration.VirtualPos/VirtualPosService.svc?wsdl', ], ], 'estpos_payten' => [ 'gateway_class' => \Mews\Pos\Gateways\EstV3Pos::class, 'test_mode' => true, 'lang' => \Mews\Pos\PosInterface::LANG_TR, 'credentials' => [ 'payment_model' => \Mews\Pos\PosInterface::MODEL_3D_SECURE, 'merchant_id' => '7001132146464', 'user_name' => 'ISBXXXXX', 'user_password' => 'ISBYYYYY', 'enc_key' => 'TRPZZZZZ', ], 'gateway_endpoints' => [ 'payment_api' => 'https://entegrasyon.asseco-see.com.tr/fim/api', 'gateway_3d' => 'https://entegrasyon.asseco-see.com.tr/fim/est3Dgate', 'gateway_3d_host' => 'https://sanalpos.sanalakpos.com.tr/fim/est3Dgate', ], ], ], ];
-
PHP Session kullanıyorsanız 3D ödemeler için session'i alttaki şekilde ayarlamanız gerekir.
Laravel 11 için environment değişkenleri şu şekilde olacak:
SESSION_SECURE_COOKIE=true SESSION_SAME_SITE=None
Laravel 10, 9, 8 için ise
- Environment'da
SESSION_SECURE_COOKIE=true
yapılacak - Ve
/config/session.php
'desame_site
değeri güncellenecek:# /config/session.php: return [ // ... 'same_site' => 'none', ]
Değişikliklerden sonra var olan session'i silip yeni session oluşturunuz.
- Environment'da
-
3D ödemelerde bankadan websiteye geri redirect edilecek URL'larda (success/fail URL'lar) CSRF kapatılması gerekir.
Laravel 11
withMiddleware()
method'la ayarı yapabilirsiniz.<?php # /bootstrap/app.php use Illuminate\Foundation\Application; use Illuminate\Foundation\Configuration\Exceptions; use Illuminate\Foundation\Configuration\Middleware; return Application::configure(basePath: dirname(__DIR__)) // ... ->withMiddleware(function (Middleware $middleware) { $middleware->validateCsrfTokens(except: [ '/payment/3d/response' ]); });
Laravel 10, 9, 8 ise
/app/Http/Middleware/VerifyCsrfToken.php
'de ayarlayabilirsiniz.<?php # /app/Http/Middleware/VerifyCsrfToken.php namespace App\Http\Middleware; use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as Middleware; class VerifyCsrfToken extends Middleware { protected $except = [ // success ve fail URL'lar buraya eklenecek: '/payment/3d/response', ]; }
-
KuveytPos için API isteklere ekstra alanlar eklemeniz gerekiyor, bunun için Event Listener'ları kullanabilirsiniz. Örnek:
<?php # /app/Listeners/KuveytPosV2RequestDataPreparedEventListener.php: namespace App\Listeners; use Mews\Pos\Event\RequestDataPreparedEvent; /** * KuveytPos TDV2.0.0 odemenin calismasi icin zorunlu eklenmesi gereken alan var. */ class KuveytPosV2RequestDataPreparedEventListener { public function __invoke(RequestDataPreparedEvent $event): void { if ($event->getGatewayClass() !== \Mews\Pos\Gateways\KuveytPos::class) { return; } /** * ekstra eklenmesi gereken verileri isteseniz $order icine ekleyip sonra o verilere * $event->getOrder() ile erisebilirsiniz. */ $additionalRequestDataForKuveyt = [ 'DeviceData' => [ /** * DeviceChannel : DeviceData alanı içerisinde gönderilmesi beklenen işlemin yapıldığı cihaz bilgisi. * 2 karakter olmalıdır. 01-Mobil, 02-Web Browser için kullanılmalıdır. */ 'DeviceChannel' => '02', ], 'CardHolderData' => [ /** * BillAddrCity: Kullanılan kart ile ilişkili kart hamilinin fatura adres şehri. * Maksimum 50 karakter uzunluğunda olmalıdır. */ 'BillAddrCity' => 'İstanbul', /** * BillAddrCountry Kullanılan kart ile ilişkili kart hamilinin fatura adresindeki ülke kodu. * Maksimum 3 karakter uzunluğunda olmalıdır. * ISO 3166-1 sayısal üç haneli ülke kodu standardı kullanılmalıdır. */ 'BillAddrCountry' => '792', /** * BillAddrLine1: Kullanılan kart ile ilişkili kart hamilinin teslimat adresinde yer alan sokak vb. bilgileri içeren açık adresi. * Maksimum 150 karakter uzunluğunda olmalıdır. */ 'BillAddrLine1' => 'XXX Mahallesi XXX Caddesi No 55 Daire 1', /** * BillAddrPostCode: Kullanılan kart ile ilişkili kart hamilinin fatura adresindeki posta kodu. */ 'BillAddrPostCode' => '34000', /** * BillAddrState: CardHolderData alanı içerisinde gönderilmesi beklenen ödemede kullanılan kart ile ilişkili kart hamilinin fatura adresindeki il veya eyalet bilgisi kodu. * ISO 3166-2'de tanımlı olan il/eyalet kodu olmalıdır. */ 'BillAddrState' => '40', /** * Email: Kullanılan kart ile ilişkili kart hamilinin iş yerinde oluşturduğu hesapta kullandığı email adresi. * Maksimum 254 karakter uzunluğunda olmalıdır. */ 'Email' => 'xxxxx@gmail.com', 'MobilePhone' => [ /** * Cc: Kullanılan kart ile ilişkili kart hamilinin cep telefonuna ait ülke kodu. 1-3 karakter uzunluğunda olmalıdır. */ 'Cc' => '90', /** * Subscriber: Kullanılan kart ile ilişkili kart hamilinin cep telefonuna ait abone numarası. * Maksimum 15 karakter uzunluğunda olmalıdır. */ 'Subscriber' => '1234567899', ], ], ]; $requestData = $event->getRequestData(); $requestData = \array_merge_recursive($requestData, $additionalRequestDataForKuveyt); $event->setRequestData($requestData); } }
Sonra bu yeni Listener'i
AppServiceProvider
'da register etmeniz gerekiyor.# /app/Providers/AppServiceProvider.php namespace App\Providers; class AppServiceProvider extends ServiceProvider { public function boot(): void { // ... \Illuminate\Support\Facades\Event::listen( \Mews\Pos\Event\RequestDataPreparedEvent::class, \App\Listeners\KuveytPosV2RequestDataPreparedEventListener::class ); } }
<?php
namespace App\Http\Controllers;
use Illuminate\Container\Container;
use Illuminate\Http\Request;
use Mews\Pos\Entity\Card\CreditCardInterface;
use Mews\Pos\Exceptions\CardTypeNotSupportedException;
use Mews\Pos\Exceptions\CardTypeRequiredException;
use Mews\Pos\Exceptions\HashMismatchException;
use Mews\Pos\Factory\CreditCardFactory;
use Mews\Pos\Gateways\PayFlexV4Pos;
use Mews\Pos\PosInterface;
class ThreeDSecurePaymentController extends Controller
{
private string $paymentModel = PosInterface::MODEL_3D_SECURE;
// Tek banka örnegi:
// Tek banka anlamı /config/laravel-pos.php'de sadece bir banka tanımlanmış olmasıdır.
public function __construct(
private PosInterface $pos,
) {
}
// START: birden fazla banka ile örnek:
// public function __construct(
// private Container $container,
// ) {}
//
// private function getPosService(string $bank): PosInterface
// {
// return $this->container->get('laravel-pos:gateway:'.$bank);
// }
// END: birden fazla banka ile örnek
/**
* route: /payment/3d/form
* Kullanicidan kredi kart bilgileri alip buraya POST ediyoruz
*/
public function form(Request $request)
{
$session = $request->getSession();
// START: birden fazla banka ile örnek
// $secilenBanka = $request->get('installment') > 1 ? 'kuveytpos' : 'estpos_payten';
// $this->pos = $this->posService($secilenBanka);
// $session->set('secilen_banka', $secilenBanka);
// END: birden fazla banka ile örnek
$transaction = $request->get('tx', PosInterface::TX_TYPE_PAY_AUTH);
$callbackUrl = url("/payment/3d/response");
$order = $this->createNewOrder(
$this->paymentModel,
$callbackUrl,
$request->getClientIp(),
$request->get('currency', PosInterface::CURRENCY_TRY),
$request->get('installment'),
);
$session->set('order', $order);
$card = $this->createCard($this->pos, $request->request->all());
/**
* PayFlex'te provizyonu (odemeyi) tamamlamak icin tekrar kredi kart bilgileri isteniyor,
* bu yuzden kart bilgileri kaydediyoruz
*/
if ($this->pos::class === PayFlexV4Pos::class) {
// Laravel 8'de set() yerine put() metodu kullanmanız gerekiyor.
$session->set('card', $request->request->all());
}
$session->set('tx', $transaction);
try {
$formData = $this->pos->get3DFormData($order, $this->paymentModel, $transaction, $card);
} catch (\Throwable $e) {
dd($e);
}
return view('redirect-form', [
'formData' => $formData,
]);
}
/**
* route: /payment/3d/response
* Kullanici bankadan geri buraya redirect edilir.
* Bu route icin CSRF disable edilmesi gerekiyor.
*/
public function response(Request $request)
{
$session = $request->getSession();
// START: birden fazla banka ile örnek
// $this->pos = $this->posService($session->get('secilen_banka'));
// END: birden fazla banka ile örnek
$transaction = $session->get('tx', PosInterface::TX_TYPE_PAY_AUTH);
// bankadan POST veya GET ile veri gelmesi gerekiyor
if (($request->getMethod() !== 'POST')
// PayFlex-CP GET request ile cevapliyor
&& ($request->getMethod() === 'GET' && ($this->pos::class !== \Mews\Pos\Gateways\PayFlexCPV4Pos::class || [] === $request->query->all()))
) {
return redirect('/');
}
$card = null;
if ($this->pos::class === \Mews\Pos\Gateways\PayFlexV4Pos::class) {
// bu gateway için ödemeyi tamamlarken tekrar kart bilgisi lazım.
$savedCard = $session->get('card');
$card = $this->createCard($this->pos, $savedCard);
}
$order = $session->get('order');
if (!$order) {
throw new \Exception('Sipariş bulunamadı, session sıfırlanmış olabilir.');
}
try {
$this->pos->payment($this->paymentModel, $order, $transaction, $card);
} catch (HashMismatchException $e) {
dd($request->request->all(), $request->query->all(), $e);
} catch (\Exception|\Error $e) {
dd($request->request->all(), $request->query->all(), $e);
}
$response = $this->pos->getResponse();
// iptal, iade, siparis durum sorgulama islemleri yapabilmek icin $response'u kaydediyoruz
$session->set('last_response', $response);
if ($this->pos->isSuccess()) {
echo 'success';
}
dd($response);
}
private function createNewOrder(
string $paymentModel,
string $callbackUrl,
string $ip,
string $currency,
?int $installment = 0,
string $lang = PosInterface::LANG_TR
): array
{
$orderId = date('Ymd').strtoupper(substr(uniqid(sha1(time())), 0, 4));
$order = [
'id' => $orderId,
'amount' => 10.01,
'currency' => $currency,
'installment' => $installment,
'ip' => filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) ? $ip : '127.0.0.1',
];
if (in_array($paymentModel, [
PosInterface::MODEL_3D_SECURE,
PosInterface::MODEL_3D_PAY,
PosInterface::MODEL_3D_HOST,
PosInterface::MODEL_3D_PAY_HOSTING,
], true)) {
$order['success_url'] = $callbackUrl;
$order['fail_url'] = $callbackUrl;
}
if ($lang) {
//lang degeri verilmezse account (EstPosAccount) dili kullanilacak
$order['lang'] = $lang;
}
return $order;
}
private function createCard(PosInterface $pos, array $card): CreditCardInterface
{
try {
return CreditCardFactory::createForGateway(
$pos,
$card['number'],
$card['year'],
$card['month'],
$card['cvv'],
$card['name'],
$card['type'] ?? null
);
} catch (CardTypeRequiredException|CardTypeNotSupportedException $e) {
dd($e);
} catch (\LogicException $e) {
dd($e);
}
}
}
# /routes/web.php
Route::match(['POST'], '/payment/3d/form', [\App\Http\Controllers\ThreeDSecurePaymentController::class, 'form']);
Route::match(['GET','POST'], '/payment/3d/response', [\App\Http\Controllers\ThreeDSecurePaymentController::class, 'response']);
<!--/resources/views/redirect-form.blade.php-->
<form method="{{ $formData['method'] }}" action="{{ $formData['gateway'] }}" class="redirect-form" role="form">
@foreach($formData['inputs'] as $key => $value)
<input type="hidden" name="{{ $key }}" value="{{ $value }}">
@endforeach
<div class="text-center">Redirecting...</div>
<hr>
<div class="form-group text-center">
<button type="submit" class="btn btn-lg btn-block btn-success">Submit</button>
</div>
</form>
<script>
// Formu JS ile otomatik submit ederek kullaniciyi banka gatewayine yonlendiriyoruz.
let redirectForm = document.querySelector('form.redirect-form');
if (redirectForm) {
redirectForm.submit();
}
</script>
- Error: "cURL error 60: SSL certificate problem: unable to get local issuer certificate (see https://curl.haxx.se/libcurl/c/libcurl-errors.html) for https://..." Genellikle lokal ortamda bu sorunla karşılaşabilirsiniz. Lokal ortamınızda CA certificate bulunmadığında oluşur. Bu durumda sunucuda çalıştırmayı deneyiniz.
MIT