Looking to hire Laravel developers? Try LaraJobs

tami-laravel maintained by klc

Description
Tami sanal POS için Laravel-native paket — tip-güvenli, test edilebilir, güvenli
Last update
2026/05/19 16:51 (dev-main)
License
Links
Downloads
1

Comments
comments powered by Disqus

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