Laravel
lunixi/laravel, PHP SDK'sını (lunixi/php-sdk) Laravel uygulamanıza bağlayan pakettir. İstek imzalama, token, yeniden deneme, idempotency ve tüm Lunixi uçları SDK'dadır; paket bunların üzerine şunları ekler:
.envüzerinden yapılandırma, bir ya da birden fazla bağlantı (API anahtarı)Lunixifacade'ı veLunixiClientinjection- isteklerin Laravel HTTP istemcisinden geçmesi —
Http::fake()testlerinizde çalışır - bearer token'ın sizin önbelleğinizde, şifreli tutulması
- imza doğrulamalı, tekrar teslimatı tanıyan webhook rotası ve Laravel olayı; istek içinde ya da kuyrukta işleme
- Checkout Formu için
<x-lunixi::checkout-form>Blade bileşeni - webhook kaybolduğunda bekleyen ödemeleri okuyan
lunixi:reconcile lunixi:install,lunixi:check,lunixi:generate-keykomutları- imzalı webhook gönderen test yardımcısı
Gereksinimler
- PHP 8.2 ve üzeri
- Laravel 12 ya da 13
ext-sodium,ext-curl,ext-json
Kurulum
composer require lunixi/laravel
php artisan lunixi:installPaket: Packagist (opens in a new tab) · Kaynak kod: GitHub (opens in a new tab)
Servis sağlayıcı ve Lunixi facade'ı otomatik kaydedilir. lunixi:install, config/lunixi.php dosyasını yayımlar ve varsayılan bağlantı için .env'de eksik kalan değerleri listeler; .env'ye kendisi bir şey yazmaz. Webhook rotası açıksa, panele kaydedeceğiniz tam webhook adresini de yazdırır.
Anahtar ve yapılandırma
Anahtar çiftini uygulamanızın sunucusunda üretin:
php artisan lunixi:generate-keyKomut özel anahtarı storage/lunixi/private.pem dosyasına yazar (yalnız dosya sahibi okuyup yazabilir) ve public anahtarı ekrana basar. Başka bir yol için --path= kullanın. Var olan bir anahtar dosyasının üzerine yalnız --force ile yazar; eski anahtar o andan sonra Lunixi'de çalışmaz.
Public anahtarı Lunixi paneline kaydedin, panelin gösterdiği anahtar kimliğini (key id) alın ve .env'ye ekleyin:
LUNIXI_ENVIRONMENT=TEST
LUNIXI_KEY_ID=mk_test_sk_...
LUNIXI_PRIVATE_KEY_PATH=/var/www/app/storage/lunixi/private.pem
LUNIXI_WEBHOOK_SECRET=whsec_...LUNIXI_ENVIRONMENT'ın varsayılanı LIVE'dır; test anahtarıyla çalışırken TEST yapın. LUNIXI_PRIVATE_KEY_PATH göreli bir yol ise uygulamanın kök dizinine göre çözülür.
Özel anahtar dosyasını sürüm kontrolüne ve public web köküne koymayın. Anahtarı satır içi veren LUNIXI_PRIVATE_KEY (yeni satırlar \n olarak) yalnız yerel geliştirme içindir.
Bağlantıyı doğrulama
php artisan lunixi:checklunixi:check anahtarınızla yeni bir token alır; bağlantı adını, ortamı, taban adresi ve anahtar kimliğini tablo olarak yazar. Anahtarı ya da token'ı hiçbir zaman yazdırmaz. LUNIXI_WEBHOOK_SECRET boşsa webhook'ların 503 ile yanıtlanacağını uyarır. Başka bir bağlantıyı denemek için --connection= kullanın.
Lunixi'yi çağırma
SDK'nın tüm istemcileri facade üzerinden ya da Lunixi\Sdk\LunixiClient injection ile kullanılabilir:
use Lunixi\Laravel\Facades\Lunixi;
$payment = Lunixi::payments()->get($paymentId);use Lunixi\Sdk\LunixiClient;
final class RefundOrder
{
public function __construct(private readonly LunixiClient $lunixi) {}
public function __invoke(Order $order): void
{
$this->lunixi->payments()->refund(
$order->lunixi_payment_id,
$order->refund_amount_minor,
'Customer request',
"refund-{$order->id}-{$order->refund_attempt}"
);
}
}Facade'da kullanılabilen istemciler: payments(), paymentLinks(), subscriptions(), fraud(), kyc(), sanction(), marketplace(), identity(), wallet(), bankConnect(), reference(), insurance(). Metotların ayrıntısı PHP SDK'sındadır. SDK istisnaları (Lunixi\Sdk\Exception\ApiException, AuthenticationException, ConfigurationException …) kodunuza değişmeden ulaşır.
Birden fazla anahtar
config/lunixi.php içinde bir bağlantı daha tanımlayın ve onu adıyla seçin. Örneğin cüzdan toplu ödemeleri, onu oluşturan anahtardan farklı bir anahtarla onaylanır:
'connections' => [
'default' => [/* ... */],
'approver' => [
'base_url' => env('LUNIXI_BASE_URL', 'https://api-gateway.lunixi.com'),
'environment' => env('LUNIXI_ENVIRONMENT', 'LIVE'),
'key_id' => env('LUNIXI_APPROVER_KEY_ID'),
'private_key_path' => env('LUNIXI_APPROVER_PRIVATE_KEY_PATH'),
],
],$created = Lunixi::wallet()->bulkPayouts->create($payoutRequest, "payroll-{$period}");
$batchId = (string) $created['batch']['id'];
Lunixi::wallet()->bulkPayouts->submit($batchId);
$checker = Lunixi::connection('approver')->wallet()->bulkPayouts;
$checker->approve($batchId);
$checker->process($batchId);Facade ve injection, LUNIXI_CONNECTION ile seçilen varsayılan bağlantıyı (default) kullanır. → Toplu Ödemeler
Ödeme alma
Ödeme niyetini oluşturun
Alıcı siparişi onayladığında niyeti oluşturun. Tutarlar para biriminin en küçük biriminde tam sayıdır (TRY için kuruş). Her deneme için yeni bir idempotency anahtarı kullanın — sipariş numarası tek başına yetmez, çünkü alıcı başka bir kartla yeniden deneyebilir.
use Lunixi\Laravel\Facades\Lunixi;
use Lunixi\Sdk\Payment\CreateIntentRequest;
$attempt = $order->attempts()->create();
$request = (new CreateIntentRequest($order->total_minor, 'TRY', $order->number))
->withCallbackUrl(route('checkout.return', $order));
$intent = Lunixi::payments()->createIntent($request, "order-{$order->id}-attempt-{$attempt->id}");
$order->update([
'lunixi_payment_id' => $intent->paymentId(),
'lunixi_checkout_script' => $intent->checkoutFormContent(),
'status' => 'pending',
]);Formu gösterin
Ödeme sayfanızda:
<x-lunixi::checkout-form :script="$order->lunixi_checkout_script" />ya da doğrudan niyetten: <x-lunixi::checkout-form :intent="$intent" />.
Ağ geçidinin ürettiği script olduğu gibi basılır; Lunixi Checkout'u yükler ve formu yerleştirir. Script boşsa bileşen "Ödeme başlatılamadı" mesajını gösterir; metni lunixi::checkout.unavailable çevirisiyle değiştirebilirsiniz.
İçerik Güvenliği Politikası (CSP) kullanıyorsanız nonce'unuzu verin:
<x-lunixi::checkout-form :script="$order->lunixi_checkout_script" :nonce="Vite::cspNonce()" />Script, Checkout çalışma zamanını Lunixi CDN'inden yükler. script-src içinde 'strict-dynamic' varsa bu yükleme nonce üzerinden güvenilir sayılır; yoksa Lunixi CDN adresine script-src'de izin verin.
Alıcının dönüşünü karşılayın
Alıcı 3D Secure'dan sonra callbackUrl adresinize döner. Dönüşün kendisi ödemenin gerçekleştiğini kanıtlamaz: "ödemeniz onaylanıyor" sayfası gösterin. Durumu göstermek isterseniz Lunixi::payments()->get($order->lunixi_payment_id) ile okuyun. Siparişi burada ödendi olarak işaretlemeyin.
Sipariş durumunu webhook belirler
Siparişi webhook dinleyiciniz günceller. → Webhook'lar
Kaybolan webhook'u uzlaştırma kapatır
Hiç gelmeyen bir webhook'un yerini lunixi:reconcile tutar. → Uzlaştırma
Capture, iptal ve iade
Bunlar SDK çağrılarıdır ve her biri kendi idempotency anahtarını alır. Tutar verilmezse işlem tutarın tamamı için yapılır.
Lunixi::payments()->capture($order->lunixi_payment_id, null, "capture-{$order->id}");
Lunixi::payments()->void($order->lunixi_payment_id, "void-{$order->id}");
Lunixi::payments()->refund($order->lunixi_payment_id, 2500, 'Customer request', "refund-{$order->id}-{$order->refund_attempt}");Webhook'lar
Paket POST /lunixi/webhook rotasını kaydeder (rota adı lunixi.webhook). Bu rotanın tam adresini Lunixi panelinde endpoint olarak kaydedin ve endpoint'in secret'ını LUNIXI_WEBHOOK_SECRET'a yazın. Rotada oturum ve CSRF middleware'i yoktur; kimlik doğrulama imzadır. → Webhook Kurulumu
Dinleyici yazma
İmzası doğrulanan her webhook Lunixi\Laravel\Events\WebhookReceived olayını tetikler:
use Lunixi\Laravel\Events\WebhookReceived;
final class UpdateOrderFromLunixi
{
public function handle(WebhookReceived $event): void
{
$webhook = $event->webhook;
if (!$webhook->matches('payment.*')) {
return;
}
$order = Order::where('lunixi_payment_id', $webhook->data()['intentId'] ?? null)->first();
if ($order === null) {
return;
}
match ($webhook->type()) {
'payment.captured', 'payment.authorized' => $order->markPaid(),
'payment.failed', 'payment.expired', 'payment.voided' => $order->markFailed(),
'payment.refunded', 'payment.partially_refunded' => $order->syncRefunds(),
default => null,
};
}
}Ödeme olayları ödeme kimliğini data() içinde intentId olarak, tutarları en küçük birimde string olarak taşır. → Ödeme webhook'ları. Diğer ürünlerin olay adları SDK'da sabit olarak bulunur (InsuranceEvents, SubscriptionEvents, KycEvents …).
Lunixi her olayı en az bir kez teslim eder. Dinleyiciniz aynı olayı yeniden aldığında aynı sonucu vermelidir. Paket işlenen olay kimliklerini de hatırlar (dedupe_ttl, varsayılan 7 gün) ve tekrarı dinleyicilerinizi çağırmadan duplicate ile yanıtlar.
Yanıt kodları
| Yanıt | Ne zaman | Lunixi ne yapar |
|---|---|---|
200 ok | Dinleyiciler çalıştı | Teslimat tamam |
200 duplicate | Olay kimliği daha önce işlendi | Teslimat tamam |
200 queued | Kuyruk modu: olay kuyruğunuza konuldu | Teslimat tamam |
409 in_progress | Aynı olay başka bir istekte işleniyor | Daha sonra yeniden teslim eder |
400 invalid_signature | İmza, zaman damgası ya da başlıklar hatalı | Daha sonra yeniden teslim eder |
500 processing_failed | Bir dinleyici istisna fırlattı | Daha sonra yeniden teslim eder |
503 not_configured | LUNIXI_WEBHOOK_SECRET boş ya da mode geçersiz | Daha sonra yeniden teslim eder |
İstisna fırlatan dinleyici olayı işlenmemiş bırakır; bir sonraki teslimatta dinleyicileriniz yeniden çalışır.
Kuyrukta işleme
LUNIXI_WEBHOOK_MODE=queue
LUNIXI_WEBHOOK_QUEUE=lunixiİmza istek içinde doğrulanır, yanıt 200 queued olur ve dinleyicilerinizi kuyrukta ProcessWebhookJob çalıştırır. Kuyruk bağlantısını LUNIXI_WEBHOOK_QUEUE_CONNECTION ile seçebilirsiniz. Başarısız bir dinleyici işi başarısız kılar; yeniden denemeler webhooks.queue.tries ve webhooks.queue.backoff ayarlarına göre yapılır, ardından iş failed_jobs'a düşer (php artisan queue:retry ile yeniden çalıştırın).
Kendi rotanız
LUNIXI_WEBHOOKS_ENABLED=false yapın ve rotayı web middleware grubunun dışında kendiniz kaydedin:
Route::lunixiWebhooks('payments/lunixi/hook');Önbellek
İşlenen olay kimlikleri ve aynı olayın iki paralel teslimatını durduran kilit, webhooks.cache_store ayarındaki önbellek deposunu kullanır (null ise varsayılan depo; .env'de LUNIXI_WEBHOOK_CACHE_STORE). Üretimde tüm sunucularınızın paylaştığı bir depo (Redis, veritabanı, Memcached) kullanın.
Uzlaştırma
Bir webhook kaybolduğunda lunixi:reconcile ödemeyi Lunixi'den okur. Siparişleriniz için PendingPaymentRepository arayüzünü uygulayın ve bağlayın:
use Lunixi\Laravel\Payments\PendingPaymentRepository;
final class PendingOrders implements PendingPaymentRepository
{
public function pendingPaymentIds(\DateTimeInterface $createdBefore, int $limit): iterable
{
return Order::where('status', 'pending')
->whereNotNull('lunixi_payment_id')
->where('created_at', '<', $createdBefore)
->oldest()
->limit($limit)
->pluck('lunixi_payment_id');
}
}
// AppServiceProvider::register()
$this->app->bind(PendingPaymentRepository::class, PendingOrders::class);Komutu zamanlayın:
// routes/console.php
Schedule::command('lunixi:reconcile')->everyFiveMinutes()->withoutOverlapping();Komut varsayılan olarak en az 15 dakikalık bekleyen ödemelerden en fazla 100 tanesini okur; --older-than= (dakika), --limit= ve --connection= seçenekleri ya da reconcile.older_than_minutes ve reconcile.limit ayarlarıyla değiştirebilirsiniz. Okunamayan bir ödeme olursa komut başarısız çıkış koduyla biter.
Okunan her ödeme için Lunixi\Laravel\Events\PaymentReconciled olayı tetiklenir ($event->paymentId, $event->payment):
use Lunixi\Laravel\Events\PaymentReconciled;
use Lunixi\Sdk\Payment\PaymentStatus;
final class SettleReconciledOrder
{
public function handle(PaymentReconciled $event): void
{
$order = Order::where('lunixi_payment_id', $event->paymentId)->first();
if ($order === null) {
return;
}
$status = $event->payment->status();
if (in_array($status, [...PaymentStatus::CAPTURED_STATES, PaymentStatus::AUTHORIZED], true)) {
$order->markPaid();
} elseif (in_array($status, PaymentStatus::FINAL_FAILURE_STATES, true)) {
$order->markFailed();
}
}
}Ödeme hâlâ sürüyor olabilir ya da uzlaştırma bekliyor olabilir (PaymentStatus::NEEDS_RECONCILIATION). Siparişi yalnız capture edilmiş ya da onaylanmış (authorized) bir durumda ödendi sayın; diğer durumlarda bekler hâlde bırakın.
Entegrasyonunuzu test etme
İstekler Laravel HTTP istemcisinden geçtiği için Http::fake() çalışır:
Http::fake([
'*/api/v1/auth/token' => Http::response(['data' => ['access_token' => 'tok', 'expires_in' => 900]]),
'*/api/v1/payments/*' => Http::response(['data' => ['id' => 'pay_1', 'status' => 'CAPTURED']]),
]);SignsLunixiWebhooks trait'i, Lunixi'nin imzaladığı biçimde imzalanmış bir webhook'u rotanıza gönderir; dinleyicileriniz gerçek imza doğrulamasından geçerek çalışır:
use Lunixi\Laravel\Testing\SignsLunixiWebhooks;
final class PaymentWebhookTest extends TestCase
{
use SignsLunixiWebhooks;
public function test_captured_payment_marks_order_paid(): void
{
config(['lunixi.webhooks.secret' => 'whsec_test']);
$order = Order::factory()->create(['lunixi_payment_id' => 'pay_1']);
$this->postLunixiWebhook('payment.captured', ['intentId' => 'pay_1', 'amount' => '10000', 'currency' => 'TRY'])
->assertOk();
$this->assertSame('paid', $order->fresh()->status);
}
}postLunixiWebhook($type, $data, $eventId, $secret, $uri): tekrar teslimatı denemek için aynı $eventId'yi yeniden verin; $secret verilmezse lunixi.webhooks.secret kullanılır; $uri verilmezse lunixi.webhook rotasına gönderilir. Trait, Illuminate\Foundation\Testing\TestCase'i genişleten bir test sınıfında kullanılır.
Yapılandırma
php artisan vendor:publish --tag=lunixi-config komutu config/lunixi.php'yi yayımlar; her seçenek dosyada açıklanmıştır. Görünüm ve çeviriler için lunixi-views ve lunixi-lang etiketleri vardır.
| Değişken | Varsayılan | Açıklama |
|---|---|---|
LUNIXI_BASE_URL | https://api-gateway.lunixi.com | |
LUNIXI_ENVIRONMENT | LIVE | LIVE ya da TEST |
LUNIXI_KEY_ID | — | Zorunlu |
LUNIXI_PRIVATE_KEY_PATH | — | Zorunlu (ya da LUNIXI_PRIVATE_KEY) |
LUNIXI_PRIVATE_KEY | — | Satır içi anahtar, yalnız yerel geliştirme |
LUNIXI_CONNECTION | default | Facade ve injection'ın kullandığı bağlantı |
LUNIXI_WEBHOOK_SECRET | — | Webhook'lar için zorunlu |
LUNIXI_WEBHOOKS_ENABLED | true | false: rotayı kendiniz kaydedersiniz |
LUNIXI_WEBHOOK_PATH | lunixi/webhook | |
LUNIXI_WEBHOOK_MODE | sync | sync ya da queue |
LUNIXI_WEBHOOK_QUEUE_CONNECTION | varsayılan bağlantı | Kuyruk modu |
LUNIXI_WEBHOOK_QUEUE | varsayılan kuyruk | Kuyruk modu |
LUNIXI_WEBHOOK_CACHE_STORE | varsayılan depo | Tekrar ve kilit kayıtları |
LUNIXI_TOKEN_CACHE_STORE | varsayılan depo | Bearer token önbelleği |
LUNIXI_HTTP_TRANSPORT | laravel | laravel ya da curl |
LUNIXI_LOG_CHANNEL | varsayılan kanal |
Loglara istek gövdeleri, secret'lar, özel anahtarlar ve token'lar hiçbir zaman yazılmaz.
Octane
İstemciler her bağlantı için bir kez oluşturulur ve isteğe bağlı durum tutmaz; token önbellektedir. Octane istekleri arasında saklanmaları güvenlidir.
İlgili
- PHP SDK — PHP SDK'sının kurulumu ve kapsamı
- Ödeme webhook'ları — olay türleri ve
dataalanları - Webhook Kurulumu — endpoint ve secret yönetimi
- Test Ortamı — TEST anahtarıyla çalışma