tami-laravel maintained by klc
Tami Laravel
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