klc / tami-laravel
Tami sanal POS için Laravel-native paket — tip-güvenli, test edilebilir, güvenli
Requires
- php: ^8.2
- illuminate/contracts: >=10.0
- illuminate/events: >=10.0
- illuminate/http: >=10.0
- illuminate/queue: >=10.0
- illuminate/routing: >=10.0
- illuminate/support: >=10.0
- illuminate/validation: >=10.0
Requires (Dev)
- orchestra/testbench: >=8.0
- phpunit/phpunit: >=10.0
README
Tami sanal POS için Laravel-native paket. Tip-güvenli, test edilebilir, güvenli.
Kurulum
composer require klc/tami-laravel
Laravel 10.x+ ve PHP 8.1+ gerektirir. Paket auto-discovery ile otomatik yüklenir.
Konfigürasyon
php artisan vendor:publish --tag=tami-config
.env dosyasına aşağıdaki değişkenleri ekleyin:
TAMI_ENVIRONMENT=sandbox TAMI_MERCHANT_ID=77006950 TAMI_TERMINAL_ID=84006953 TAMI_SECRET_KEY=your-secret-key TAMI_JWK_KID=your-jwk-kid TAMI_JWK_K=your-jwk-k # Normal API response securityHash doğrulaması, Tami algoritması netleşene kadar kapalı kalmalıdır. TAMI_RESPONSE_HASH_ENABLED=false
Canlı ortam için:
TAMI_ENVIRONMENT=production TAMI_MERCHANT_ID=your-production-merchant TAMI_TERMINAL_ID=your-production-terminal TAMI_SECRET_KEY=your-production-secret TAMI_JWK_KID=your-production-kid TAMI_JWK_K=your-production-k
Kullanım
Ödeme (Payment)
use Klc\Tami\Facades\Tami; use Klc\Tami\DTOs\CardDto; use Klc\Tami\DTOs\AddressDto; use Klc\Tami\DTOs\BuyerDto; use Klc\Tami\DTOs\BasketItemDto; use Klc\Tami\DTOs\BasketDto; use Klc\Tami\DTOs\Payment\AuthRequestDto; use Klc\Tami\Enums\ItemType; use Klc\Tami\Enums\PaymentChannel; // Kart $card = new CardDto( number: '4824910501747014', holderName: 'Örnek Kullanıcı', cvv: '123', expireMonth: 12, expireYear: 2026, ); // Adres $address = new AddressDto( address: 'Örnek Mah. Test Sk. No:1', city: 'İstanbul', companyName: 'Örnek Firma', country: 'Türkiye', district: 'Kadıköy', contactName: 'Örnek İletişim', phoneNumber: '05555555555', zipCode: '34700', ); // Alıcı $buyer = new BuyerDto( ipAddress: '127.0.0.1', buyerId: 'buyer-001', name: 'Örnek', surName: 'Kullanıcı', emailAddress: 'ornek@eposta.com', phoneNumber: '05555555555', // Aşağıdaki alanlar Tami dokümanında opsiyonel/senaryoya bağlıdır. identityNumber: '11111111111', city: 'İstanbul', country: 'Türkiye', zipCode: '34700', registrationAddress: 'Örnek Mah. Test Sk. No:1', registrationDate: '2023-01-01T10:00:00', lastLoginDate: '2023-06-01T10:00:00', ); // Sepet $item = new BasketItemDto( itemId: 'item-001', name: 'Örnek Ürün', itemType: ItemType::Physical, numberOfProducts: 1, totalPrice: '100.00', unitPrice: '100.00', category: 'Elektronik', subCategory: 'Bilgisayar', ); $basket = new BasketDto('basket-001', [$item]); // Ödeme isteği $dto = new AuthRequestDto( orderId: 'order-' . uniqid(), amount: '100.00', currency: 'TRY', installmentCount: 1, paymentGroup: 'PRODUCT', paymentChannel: PaymentChannel::Web, card: $card, billingAddress: $address, shippingAddress: $address, buyer: $buyer, basket: $basket, ); // Ödemeyi gerçekleştir $response = Tami::payment()->auth($dto); if ($response->isSuccess()) { echo "Ödeme başarılı! Sipariş: " . $response->getOrderId(); } else { echo "Hata: " . $response->getErrorCode() . " - " . $response->getErrorMessage(); }
3D Secure Ödeme
// Geri dönüş URL'si ile 3DS başlatma $authDto = new AuthRequestDto( orderId: 'order-' . uniqid(), amount: '100.00', currency: 'TRY', installmentCount: 1, paymentGroup: 'PRODUCT', paymentChannel: PaymentChannel::Web, card: $card, billingAddress: $address, shippingAddress: $address, buyer: $buyer, basket: $basket, callbackUrl: 'https://example.com/tami/callback', ); $response = Tami::payment()->auth($authDto); if ($response->is3dsRequired()) { echo $response->getHtmlContent(); // Banka 3DS sayfasına yönlendir }
3DS Callback Yönetimi
Paket otomatik olarak POST /tami/callback ve GET /tami/callback route'larını kaydeder. Callback payload'ında hashedData varsa paket Tami'nin 3DS HMAC-SHA256 formülüne göre doğrular; doğrulama başarısızsa /payment/complete-3ds çağrısı yapılmadan hata event'i yayınlanır. Başarılı/başarısız durumlar için event dinleyicileri:
// EventServiceProvider.php use Klc\Tami\Events\TamiPaymentSucceeded; use Klc\Tami\Events\TamiPaymentFailed; protected $listen = [ TamiPaymentSucceeded::class => [ // Siparişi tamamla, stok düş, vs. ], TamiPaymentFailed::class => [ // Hata logla, kullanıcıyı bilgilendir ], ];
Ön Otorizasyon (Pre-Auth)
$preAuthResponse = Tami::payment()->preAuth($authDto); // Daha sonra kapama (capture) use Klc\Tami\DTOs\Payment\PostAuthRequestDto; $postAuthDto = new PostAuthRequestDto( orderId: $preAuthResponse->getOrderId(), amount: '100.00', // Kısmi kapama için daha düşük tutar ); $captureResponse = Tami::payment()->postAuth($postAuthDto);
İptal / İade
use Klc\Tami\DTOs\Payment\ReverseRequestDto; // Tam iade $reverseDto = new ReverseRequestDto(orderId: 'order-123'); $reverseResponse = Tami::payment()->reverse($reverseDto); // Kısmi iade $reverseDto = new ReverseRequestDto( orderId: 'order-123', amount: '50.00', reason: 'Kısmi iade', ); $reverseResponse = Tami::payment()->reverse($reverseDto);
Sipariş Sorgulama
use Klc\Tami\DTOs\Payment\QueryRequestDto; $queryDto = new QueryRequestDto( orderId: 'order-123', isTransactionDetail: true, ); $queryResponse = Tami::payment()->query($queryDto); echo "Sipariş Durumu: " . $queryResponse->getOrderStatus(); echo "Ödeme Durumu: " . $queryResponse->getPaymentStatus(); echo "Taksit Sayısı: " . $queryResponse->getInstallmentCount();
Taksit ve Kart Bilgisi
use Klc\Tami\DTOs\Installment\BinInfoRequestDto; use Klc\Tami\DTOs\Installment\InstallmentInfoRequestDto; // BIN sorgulama $binDto = new BinInfoRequestDto('48249105'); $binResponse = Tami::installment()->binInfo($binDto); echo "Banka: " . $binResponse->getBankName(); echo "Kart Tipi: " . $binResponse->getCardType(); // CREDIT/DEBIT echo "Kart Ağı: " . $binResponse->getCardOrg(); // VISA/MASTERCARD/TROY/AMEX // Taksit seçenekleri $installmentDto = new InstallmentInfoRequestDto('48249105'); $installmentResponse = Tami::installment()->installmentInfo($installmentDto); $installments = $installmentResponse->getInstallments(); // [1, 3, 5] $requires3ds = $installmentResponse->isForce3ds(); $requiresCvv = $installmentResponse->isForceCvc();
Bonus / Puan Sorgulama
use Klc\Tami\DTOs\Vas\BonusInquiryRequestDto; $bonusDto = new BonusInquiryRequestDto( ipAddress: '127.0.0.1', emailAddress: 'ornek@eposta.com', cardNumber: '4824910501747014', expireMonth: 12, expireYear: 2026, currencyCode: 949, // TRY için 949 amount: '100.00', ); $bonusResponse = Tami::vas()->bonusInquiry($bonusDto); if ($bonusResponse->isApproved()) { foreach ($bonusResponse->getRewardList() as $reward) { echo $reward['type'] . ': ' . $reward['amount']; } }
Puanlı Satış
use Klc\Tami\DTOs\RewardDto; use Klc\Tami\DTOs\RewardToBeUsedDto; $reward = new RewardDto(type: 'BNS', amount: '20.00'); $rewardToBeUsed = new RewardToBeUsedDto([$reward]); $dto = new AuthRequestDto( // ... diğer alanlar orderId: 'order-' . uniqid(), amount: '100.00', currency: 'TRY', installmentCount: 1, paymentGroup: 'PRODUCT', paymentChannel: PaymentChannel::Web, card: $card, billingAddress: $address, shippingAddress: $address, buyer: $buyer, basket: $basket, isRewardToBeUsed: true, rewardToBeUsed: $rewardToBeUsed, ); $response = Tami::payment()->auth($dto);
Hata Yönetimi
use Klc\Tami\Exceptions\TamiConnectionException; use Klc\Tami\Exceptions\TamiAuthException; use Klc\Tami\Exceptions\TamiApiException; use Klc\Tami\Exceptions\TamiValidationException; use Klc\Tami\Exceptions\TamiSecurityException; try { $response = Tami::payment()->auth($dto); } catch (TamiValidationException $e) { // DTO validasyon hatası $errors = $e->errors; // MessageBag } catch (TamiConnectionException $e) { // Bağlantı hatası } catch (TamiAuthException $e) { // Kimlik doğrulama hatası (HTTP 401) } catch (TamiApiException $e) { // API hatası echo $e->errorCode; echo $e->errorMessage; echo $e->errorGroup; } catch (TamiSecurityException $e) { // Security hash doğrulama hatası }
Route Konfigürasyonu
# Route'ları kapat TAMI_ROUTES_ENABLED=false # Route ön eki TAMI_ROUTES_PREFIX=payment # Başarı/başarısızlık URL'leri TAMI_SUCCESS_URL=/odeme/basarili TAMI_FAILURE_URL=/odeme/basarisiz ``` ## Güvenlik Notları Paket, Tami'ye gönderilen request body için `securityHash` değerini Tami PHP örneğiyle uyumlu JWK/HS512 JWT algoritmasıyla üretir. `securityHash`, request body'ye eklenmeden önceki JSON üzerinden hesaplanır. Normal API response'larında dönen `securityHash` için Tami PHP örneklerinde doğrulama algoritması bulunmadığı için bu paket o alanı yanlış algoritmayla doğrulamaya çalışmaz. `TAMI_RESPONSE_HASH_ENABLED=true` yapılırsa paket fail-closed davranır ve `TamiSecurityException` fırlatır. Bu ayarı ancak normal API response hash algoritması netleşip pakette ayrıca implemente edildikten sonra açın. 3DS callback payload'ında `hashedData` varsa paket bu değeri `secretKey` ile HMAC-SHA256 kullanarak doğrular. Callback payload'ına güvenip siparişi doğrudan tamamlamayın; paket callback sonrası server-to-server `/payment/complete-3ds` çağrısı yapar. ## Test ```bash # Docker ile (önerilen) make test # veya doğrudan docker compose run --rm php vendor/bin/phpunit
3DS Callback ve CSRF
3DS callback route'u varsayılan olarak api middleware ile kaydedilir — banka callback'i
CSRF token taşımadığı için web middleware altında 419 hatası alabilir.
Eğer web middleware kullanmak isterseniz VerifyCsrfToken middleware'ine
tami/callback yolunu ekleyin:
// app/Http/Middleware/VerifyCsrfToken.php protected $except = [ 'tami/callback', ];
Lisans
MIT