Entegrasyonlar
Laravel

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ı)
  • Lunixi facade'ı ve LunixiClient injection
  • 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-key komutları
  • 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:install

Paket: 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-key

Komut ö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:check

lunixi: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ıtNe zamanLunixi ne yapar
200 okDinleyiciler çalıştıTeslimat tamam
200 duplicateOlay kimliği daha önce işlendiTeslimat tamam
200 queuedKuyruk modu: olay kuyruğunuza konulduTeslimat tamam
409 in_progressAynı olay başka bir istekte işleniyorDaha sonra yeniden teslim eder
400 invalid_signatureİmza, zaman damgası ya da başlıklar hatalıDaha sonra yeniden teslim eder
500 processing_failedBir dinleyici istisna fırlattıDaha sonra yeniden teslim eder
503 not_configuredLUNIXI_WEBHOOK_SECRET boş ya da mode geçersizDaha 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şkenVarsayılanAçıklama
LUNIXI_BASE_URLhttps://api-gateway.lunixi.com
LUNIXI_ENVIRONMENTLIVELIVE 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_CONNECTIONdefaultFacade ve injection'ın kullandığı bağlantı
LUNIXI_WEBHOOK_SECRET—Webhook'lar için zorunlu
LUNIXI_WEBHOOKS_ENABLEDtruefalse: rotayı kendiniz kaydedersiniz
LUNIXI_WEBHOOK_PATHlunixi/webhook
LUNIXI_WEBHOOK_MODEsyncsync ya da queue
LUNIXI_WEBHOOK_QUEUE_CONNECTIONvarsayılan bağlantıKuyruk modu
LUNIXI_WEBHOOK_QUEUEvarsayılan kuyrukKuyruk modu
LUNIXI_WEBHOOK_CACHE_STOREvarsayılan depoTekrar ve kilit kayıtları
LUNIXI_TOKEN_CACHE_STOREvarsayılan depoBearer token önbelleği
LUNIXI_HTTP_TRANSPORTlaravellaravel ya da curl
LUNIXI_LOG_CHANNELvarsayı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