Teknomers | Dünyadan Güncel Teknoloji | Oyun | Müzik | Film | Spor HaberleriTeknomers | Dünyadan Güncel Teknoloji | Oyun | Müzik | Film | Spor HaberleriTeknomers | Dünyadan Güncel Teknoloji | Oyun | Müzik | Film | Spor Haberleri
Yazı Tipi BoyutlandırıcıAa
  • Anasayfa
  • Teknoloji
    • Siber Güvenlik
    • Yapay Zeka
    • Donanım
    • Bilim
  • Yazılım
  • Savunma & İstihbarat
  • Oyun
  • Yaşam
    • Finans
    • Sinema
    • Dünyadan Haberler
  • İş Birliği
Okuma: Laravel Faturalama: Tek Paket, Her Ödeme Geçidi, İlk Günden İtibaren Çalışır
Paylaş
Yazı Tipi BoyutlandırıcıAa
Teknomers | Dünyadan Güncel Teknoloji | Oyun | Müzik | Film | Spor HaberleriTeknomers | Dünyadan Güncel Teknoloji | Oyun | Müzik | Film | Spor Haberleri
Ara
Bizi Takip Et
  • Hakkımızda
  • Gizlilik politikası
  • Tanıtım Yazısı ve Backlink Hizmeti
© 2026 Teknomers. All Rights Reserved.

Anasayfa » Laravel Faturalama: Tek Paket, Her Ödeme Geçidi, İlk Günden İtibaren Çalışır

Yazılım

Laravel Faturalama: Tek Paket, Her Ödeme Geçidi, İlk Günden İtibaren Çalışır

teknomers
Son güncelleme: 1 Haziran 2026 10:58
teknomers
Paylaş
Paylaş

Her SaaS faturalama entegrasyonu şu şekilde başlar: bir sağlayıcı seçersiniz, paketi ekler, bağlarsınız — ve üç ay sonra iş, ikinci bir ödeme geçidi eklemek (veya Stripe’ın yerel destek sunmadığı bir Malasyalça ödemesini BayarCash veya ToyyibPay gibi başka bir sağlayıcıya geçmek) istediğinde, tüm abonelik katmanınızın ilk sağlayıcının paketiyle kenetli olduğunu keşfedersiniz. Farklı webhook yapıları, farklı durum kelimeleri, farklı model varsayımları. Bir geçit eklemiyorsunuz; yeniden yapılandırıyorsunuz.

<p>Bunun sıkça yaşandığını görmekten rahatsız oldum — özellikle Malasya pazarında, "açık" olan global paketlerin birçok müşterimin aslında kullanamadığı bir geçidi varsaydığını gözlemledim. Bu yüzden <a href="https://github.com/cleaniquecoders/laravel-billing" target="_blank" rel="noopener noreferrer"><code>cleaniquecoders/laravel-billing</code></a> paketini oluşturduk. Burada bir ters dönüşüm yapıyoruz: <strong>geçit eklentidir, paket değildir.</strong> Motor, abonelik ve fatura durumunu yönetir. Bir geçit, uygulamanızın uyguladığı tek bir kontrattır. Bu makale, API yüzeyine odaklanmaktan ziyade <em>neden böyle şekillendiği</em> ile ilgilidir — çünkü şekil tüm konunun kendisidir.</p>

<h2>
    <a name="the-core-decision-one-package-gateways-as-a-contract" href="#the-core-decision-one-package-gateways-as-a-contract"></a>
    Temel karar: bir paket, geçitler sözleşme olarak
</h2>

<p>Bir faturalama kütüphanesi oluşturma konusunda en büyük cazibe, <code>laravel-billing-stripe</code>, <code>laravel-billing-bayarcash</code>, <code>laravel-billing-toyyibpay</code> gibi birçok paket göndermektir. Modüler olduğu hissini verir. Ama aslında bu bir bakım tuzağıdır – her geçit alt paketi, aynı abonelik yaşam döngüsünü biraz farklı bir şekilde yeniden uygular ve çekirdek hiçbir zaman kararlı bir şekil varsayamaz çünkü her adaptör onu bükebilir.</p>

<p>Bu paket diğer şekilde hareket eder. <strong>Bir paket, bir repo</strong> vardır ve gerçek bir sağlayıcı adını asla referans almaz. Bunun yerine tek bir genişletme noktası vardır:<br/></p>

<div class="highlight js-code-highlight">
    <pre class="highlight php"><code>
        namespace CleaniqueCoders\LaravelBilling\Contracts;

        interface PaymentGateway
        {
            public function createCheckout(
                Billable $billable,
                Plan $plan,
                PlanInterval $interval,
                string $returnUrl,
            ): CheckoutIntent;

            public function cancel(Subscription $subscription): void;

            public function parseWebhook(Request $request): ?WebhookEvent;
        }
        </code></pre>
</div>

<p>Üç metot. BayarCash, ToyyibPay, Chip, senangPay, Stripe veya başka bir şeyi eklemek için uygulamanızın uyguladığı yüzey budur. Bunu tutarlı hale getiren, sınırdaki iki DTO’dur — <code>CheckoutIntent</code> çıkışta, <code>WebhookEvent</code> geri dönüşte:<br/></p>

<div class="highlight js-code-highlight">
    <pre class="highlight php"><code>
        final class CheckoutIntent
        {
            public function __construct(
                public string $redirectUrl,   // müşteriyi nereye göndereceğimiz
                public string $externalId,     // webhook tarafından geri döndürülecek
            ) {}
        }
        </code></pre>
</div>

<p>Geçidin görevi, sağlayıcının alışılmadık dünyasını bu iki tarafsız forma dönüştürmektir. Bir kez bunu yaptığında, motor — abonelik geçişleri, fatura tahsisi, olaylar — hangi sağlayıcıyla konuştuğunu bilmeden devam edebilir. <strong>Sağlayıcıya özel karmaşa, bir sınıf içinde karantinaya alınmıştır</strong>, böylece tüm faturalama katmanınıza yayılmaz. Buradaki paket değerli bir ders: N dış hizmetlerle entegrasyon yaptığınızda, aynı şeyleri yapan, sınırda kendi DTO'nuzu tanımlayın ve her bir adaptörü çeviri ile sorumlu tutun. Sağlayıcı şekillerinin içeriye yayılmasına izin vermeyin.</p>

<h2>
    <a name="batteries-included-a-gateway-that-needs-no-merchant-account" href="#batteries-included-a-gateway-that-needs-no-merchant-account"></a>
    Pil dahil: bir ticaret hesabı gerektirmeyen bir geçit
</h2>

<p>En memnun kaldığım kısım burası. Yeni bir kurulum <code>BILLING_GATEWAY=local</code> varsayılanı ile başlar ve paketlenmiş <code>LocalGateway</code> gerçek para ve ticaret hesabı olmadan <strong>tam</strong> bir abonelik → aktif etme → fatura → makbuz akışını çalıştırır. <code>composer require</code> edersiniz, geçişleri çalıştırırsınız ve faturalama akışı hemen çalışır — demo, geliştirme, UAT, CI’de.</p>

<p>Ama bu bir stub değil. Önemli olan detay şu:<br/></p>

<div class="highlight js-code-highlight">
    <pre class="highlight php"><code>
        // LocalGateway::createCheckout — onay akışları TAMAMEN aynı
        // WebhookEvent yolu
        return new WebhookEvent(
            type: WebhookEventType::SubscriptionActivated,
            externalId: $payload['external_id'],
            amountCents: $payload['amount_cents'] ?? null,
            providerEventId: 'local-' . $payload['external_id'],
            rawPayload: $payload,
        );
        </code></pre>
</div>

<p>Yerel geliştirme kontrol sayfasında "Onayla" düğmesine tıkladığınızda, <code>WebhookEvent</code> üretir ve bunu <code>Billing::handle()</code> işleme geçirir — gerçek bir BayarCash webhook’un geçmesi gereken <em>tam aynı kod yolu</em>. Hatta kontrol tokenını <code>app.key</code> ile HMAC imzalar ve geri gelirken imzayı doğrular, böylece imza doğrulama mantığı da işlenir:<br/></p>

<div class="highlight js-code-highlight">
    <pre class="highlight php"><code>
        public static function verify(string $token): ?array
        {
            [$data, $signature] = explode('.', $token, 2);
            $expected = hash_hmac('sha256', $data, static::key());

            if (!hash_equals($expected, $signature)) {
                return null; // değiştirilmiş veya geçersiz
            }
            // ...
        }
        </code></pre>
</div>

<p>Bunun için "geliştirici" geçidi olarak bu kadar zahmete girmeye neden var? Çünkü farklı bir yoldan geçen bir sahte, gerçek akıştan daha kötü — sahte bir güven sunar. Yerel geçit akışını gerçek doğrulama hattından geçirerek, <code>local</code> üzerinden yaptığınız testler, gerçek bir ödeyenin deneyimleyeceği akışı doğrular. <code>BILLING_LOCAL_AUTO=true</code> ayarlarsanız, tüm şey belirli bir istekte senkron olarak çalışır, bu da CI ve özellik testleri için mükemmeldir. Yerel yollar, üretimde kaydedilmez, bu nedenle yanlış bir adım olamaz.</p>

<h2>
    <a name="headless-core-optional-ui" href="#headless-core-optional-ui"></a>
    Başsız çekirdek, isteğe bağlı UI
</h2>

<p>Motor — modeller, servisler, sözleşmeler, olaylar, yönetici — herhangi bir UI olmadan çalışır. Eğer hızlı bir şekilde faturalama sayfaları istiyorsanız, hızlıca bütün döngüyü tamamlamak için gönüllü bir Livewire + Flux UI (plan seçici, faturalama portalı, makbuz kartı) mevcuttur. Koruma temizdir:<br/></p>

<div class="highlight js-code-highlight">
    <pre class="highlight php"><code>
        if (config('billing.routes.enabled') && class_exists(Livewire::class)) {
            // kayıt et /billing yolları
        }
        </code></pre>
</div>

<p>Livewire yüklü değilse veya <code>BILLING_UI_ENABLED=false</code> ayarlarsanız, paket tamamen başsız kalır ve aynı modellerle ve yüzeyle kendi sayfalarınızı oluşturursunuz. Çekirdek üzerine UI yığınından zorunlu bir bağımlılık yoktur. Bu, bir kütüphane için doğru varsayılandır: Fikrini savunan bir rahatlık katmanı varsa, ancak bu bir <code>class_exists</code> kontrolü ve bir yapılandırma bayrağı ile gerçekleştirilir, asla zorunlu değildir.</p>

<h2>
    <a name="the-webhook-flow-and-a-replay-guard-worth-stealing" href="#the-webhook-flow-and-a-replay-guard-worth-stealing"></a>
    Webhook akışı ve çalınmaya değer bir tekrar koruma
</h2>

<p>Uygulamanız yolu yönetir; paket iş yapar:<br/></p>

<div class="highlight js-code-highlight">
    <pre class="highlight php"><code>
        Route::post('/webhooks/{gateway}', function (Request $request, string $gateway) {
            $event = Billing::gateway($gateway)->parseWebhook($request);
            abort_if($event === null, 401);

            Billing::handle($event); // deduplar, durum geçişleri, faturalara çıkarır, olayları başlatır
            return response()->noContent();
        });
        </code></pre>
</div>

<p><code>parseWebhook()</code> (geçidinizin kodu) imzayı doğrular ve yükü normalleştirir veya bunu reddetmek için <code>null</code> döndürür. Sonra <code>Billing::handle()</code> bir <code>WebhookProcessor</code>'e devreder; tekrar koruma uygular, aboneliği bulur, durumu değiştirir, aktif/yenileme sırasında bir fatura çıkarır ve eşleşen alan olayını ateşler.</p>

<p>Tekrar koruma küçük ama hoş bir anlayış:<br/></p>

<div class="highlight js-code-highlight">
    <pre class="highlight php"><code>
        protected function isReplay(WebhookEvent $event): bool
        {
            if ($event->providerEventId === null) {
                return false;
            }

            $key = 'billing:webhook:' . $event->providerEventId;
            $ttl = (int) config('billing.webhook.replay_ttl', 60 * 60 * 24 * 30);

            // Cache::add, anahtar zaten mevcut olduğunda false döner → tekrar.
            return Cache::add($key, true, $ttl) === false;
        }
        </code></pre>
</div>

<p>Geçitler tekrar deneyebilir. Aynı olayı iki, üç kez gönderirler çünkü yeterince hızlı <code>200</code> alamadılar. Eğer deduplar yapmazsanız, iki kat fatura çıkarabilirsiniz. Güzel bir kısım ise <code>Cache::add</code>'ın atomikliğine dayanmak — bu yalnızca anahtar yoksa yazar ve yarışmada kazananını belirtir, tek operasyonda. Paralel bir çoğaltmanın kayma olasılığının olması için okuma-sonrası-yazma penceresi yok. Bu, yalnızca faturalama için değil, her idempotent etkinlik işleme için yeniden kullanılabilir bir modeldir.</p>

<h2>
    <a name="state-transitions-live-in-one-place" href="#state-transitions-live-in-one-place"></a>
    Durum geçişleri tek bir yerde yaşar
</h2>

<p><code>WebhookProcessor</code>, sağlayıcı olaylarının abonelik durumuna dönüşüm noktasıdır ve bir durum makinasi gibi okunur:<br/></p>

<div class="highlight js-code-highlight">
    <pre class="highlight php"><code>
        match ($event->type) {
            WebhookEventType::SubscriptionActivated => $this->activate($subscription),
            WebhookEventType::SubscriptionRenewed => $this->renew($subscription),
            WebhookEventType::PaymentSucceeded => $this->paymentSucceeded($subscription, $event),
            WebhookEventType::PaymentFailed => $this->paymentFailed($subscription, $event),
            WebhookEventType::SubscriptionCanceled => $this->cancel($subscription),
        };
        </code></pre>
</div>

<p>Geçidin tek sorumluluğu, kendi sağlayıcısının sözlüğünü bu beş <code>WebhookEventType</code> durumuna eşlemektir. Aşağıya bakan her şey — "aktifleştirme"nin dönem tarihleri için ne ifade ettiği, bir faturanın ne zaman çıkarılacağı, hangi olayın ateşlenir olduğu — bir defada belirlenir, motorda, sağlayıcıdan bağımsız olarak. Bir <code>SubscriptionStatus</code> enum, kendi erişim mantığını taşır bu nedenle kural yerlerde dağılmaz:<br/></p>

<div class="highlight js-code-highlight">
    <pre class="highlight php"><code>
        public function grantsAccess(): bool
        {
            return match ($this) {
                self::Trialing, self::Active, self::PastDue => true,
                self::Canceled, self::Incomplete => false,
            };
        }
        </code></pre>
</div>

<p>Dikkatinizi çekin, <code>PastDue</code> hala erişim izni verir — başarısız bir yenilemenin, bir dönemin ortasında birini hemen kilitlememesi gerektiği. Bu kasıtlı bir cümle dostu seçimidir ve çünkü enum'da yaşar, erişim kontrol edilen her yerde tutarlıdır.</p>

<h2>
    <a name="polymorphic-billing-tenancy-is-optional" href="#polymorphic-billing-tenancy-is-optional"></a>
    Polimorfik faturalama: Tenellik isteğe bağlı
</h2>

<p>Faturalama hedefi polimorfiktir, bu yüzden aynı motor tekli kiracı (<code>User</code>) ve çok kiracı (<code>Team</code>/<code>Workspace</code>/<code>Organization</code>) hizmet eder, hangisi olursa olsun:<br/></p>

<div class="highlight js-code-highlight">
    <pre class="highlight php"><code>
        class User extends Authenticatable implements Billable
        {
            use HasSubscriptions;
        }
        </code></pre>
</div>

<p><code>HasSubscriptions</code>, <code>Billable</code> sözleşmesini tamamen karşılar ve motor ve UI'nın bağımlı olduğu erişim sağlayıcıları sunar — <code>subscription()</code>, <code>subscribedTo('pro')</code>, <code>onTrial()</code>, <code>onGracePeriod()</code>, <code>plan()</code>, <code>invoices()</code>, ayrıca metered kullanım konusunda <code>canConsume('seats', 1)</code> / <code>recordUsage('seats', 1)</code> ile arama yapar. Faturalama işleminin bir takıma yönlendirilmesi için, bir yapılandırma kapanışını buna yönlendirebilirsiniz:<br/></p>

<div class="highlight js-code-highlight">
    <pre class="highlight php"><code>
        'billable_resolver' => fn($request) => $request->user()->currentTeam,
        </code></pre>
</div>

<p>Her UI sorgusu ve her fatura indirme, çözülen faturalama ile sınırlıdır ve indirme yolları yabancı bir fatura üzerinde <code>403</code> döner — bu nedenle bir kiracı asla diğerinin faturalarını göremez. Kiracılık, bir kiracılık <em>özelliği</em> gerektirmeden ortaya çıktı; hedefin polimorfik hale getirilip tüm erişimlerin bir çözümleyici üzerinden aktarılması sayesinde.</p>

<h2>
    <a name="a-few-more-details-worth-noting" href="#a-few-more-details-worth-noting"></a>
    Dikkate değer birkaç detay
</h2>

<p><strong>Anlık görüntü vs canlı.</strong> Bir abonelik, <code>plan_tier</code> değerini bir <em>anlık görüntü</em> dizesi olarak saklar, ancak canlı <code>Plan</code> okuma zamanında deposundan çözülür. Böylece plan tanımları yapılandırmada veya bir veritabanı tablosunda yaşayabilir (her ikisi de aynı <code>PlanRepository</code> arayüzü), bir abone'nin katman referansı, plan modellerinizi yeniden yapılandırsanız bile kalır.</p>

<p><strong>Atomik fatura numaraları.</strong> Ardışık numaralandırma (<code>INV-2026-000001</code>) bir satır kilitli işlemde tahsis edilir, bu nedenle eş zamanlı tahsis hiçbir numara çakışmasına neden olmaz:<br/></p>

<div class="highlight js-code-highlight">
    <pre class="highlight php"><code>
        $sequence = $sequenceModel::query()->where('year', $year)->lockForUpdate()->first();
        $current = (int)$sequence->next_number;
        $sequence->next_number = $current + 1;
        $sequence->save();
        </code></pre>
</div>

<p><strong>Malezya dostu, nötr.</strong> MYR varsayılı, SST/SSM farkında bir vergi-fatura şablonu, yapılandırılabilir satıcı detayları — ama varsayılan olarak hepsi nötr olduğundan, yalnızca Malezya'ya özgü değildir. Vergi matematiği sadece <code>round(subtotal * rate)</code> olup, fatura üzerinde doğru bir şekilde render edilmesi için bir döküm olarak saklanır.</p>

<p><strong>Etkinlikler, uzatma dikişiniz.</strong> Motor yalnızca durumu güncelleyip fatura oluşturur. Erişim verme, dunning e-postaları, Slack bildirimleri — bunlar <code>SubscriptionActivated</code>, <code>SubscriptionRenewed</code>, <code>SubscriptionCanceled</code>, <code>PaymentSucceeded</code>, <code>PaymentFailed</code>, <code>InvoiceIssued</code> etkinlikleriniz dolayısıyla okuğuna bağlanacak dinleyicilerdir. Paket yan etkilerinizi tahmin etmez.</p>

<h2>
    <a name="when-youd-reach-for-this" href="#when-youd-reach-for-this"></a>
    Ne zaman bu paketi kullanmalısınız
</h2>

<p>Bu, Laravel'de abonelik + faturalandırma istiyorsanız uygun bir yapıdadır ve:</p>

<ul>
    <li>Başka bir <strong>geçit gerekli olduğunda</strong>, veya <strong>Malezyalı bir geçit</strong> veya daha sonra yeniden yapılandırma özgürlüğü aradığınızda;</li>
    <li>İlk günden <strong>tam akışın çalışmasını</strong> istiyorsanız — demo, UAT, CI — herhangi bir ticaret hesabı olmadan;</li>
    <li>Kendi UI'nizden kontrol edebileceğiniz <strong>başsız bir motor</strong> istiyorsanız, hızlıca hareket ederken isteğe bağlı bir kullanıcı arayüzü ile;</li>
    <li>Sadece kullanıcıları değil, <strong>takımları veya iş alanlarını faturalıyorsanız;</li>
    <li>Bir <strong>SST/SSM</strong> bağlamında iseniz ve sağlayıcıya bir kilitlenme olmadan makul bir yerel faturalandırma istiyorsanız.</li>
</ul>

<p>Tek bir global geçit için her şey dahil iseniz ve ilk taraf Laravel paketi sizi kapsıyorsa, o zaman onu kullanın. Buradaki değer, "hangi geçit" haline geldiği anda kendini gösterir; bunun için birden fazla yanıt olduğunda — Malezya pazarında, bu her zaman geçerlidir.</p>

<p>MIT lisanslıdır ve Packagist’te mevcuttur:<br/></p>

<div class="highlight js-code-highlight">
    <pre class="highlight shell"><code>composer require cleaniquecoders/laravel-billing
        </code></pre>
</div>

<p>Repo ve tam belgeler (mimari, geçitler, tam faturalama döngüsü, kendi sürücünüzü yazmak): <a href="https://github.com/cleaniquecoders/laravel-billing" target="_blank" rel="noopener noreferrer">github.com/cleaniquecoders/laravel-billing</a>.</p>

<p>Bir geçit uygulamak bir sınıf ve üç metot gerektirir — bir BayarCash veya ToyyibPay sürücüsü yazarsanız, görmekten çok mutlu olurum.</p>

Kaynak: Orijinal Makale

Laravel Ekiplerinde Kod İncelemelerinin Neden Başarısız Olduğu
Yumuşak Silme Uygulamalarının SaaS Veritabanınızı Nasıl Bozduğu ve Çözüm Yöntemleri
Hacklenmiş bir WordPress Sitesinin Kurtarılması ve Laravel ile Yeniden İnşası
Tahmin etmeyi bırak, denetlemeye başla: Laravel geliştiricileri için özel bir Web Performans aracı neden geliştirdim
Laravel Core CRUD Paketi: Laravel Core CRUD ile Temiz Mimari
Bu Makaleyi Paylaş
Facebook Bağlantıyı Kopyala Yazdır
Paylaş
Önceki Makale Nvidia, Computex 2026’da RTX Spark yol haritasını açıkladı
Sonraki Makale AMD, AM5 desteğini 2029’a kadar sürdürecek: İki nesil daha bekleniyor

Sanal Medya

FacebookBeğen
452Takip Et
PinterestSabitle
237Takip Et

Son Eklenenler

Google’dan rakiplerine giden AI araştırmacıları artıyor
Yapay Zeka
Mühendislik Meslekleri Yok Olacak Diye Bekleniyordu, Ama Veriler Farklı Söylüyor
Genel
Prime Günü’nde Yüzde 36’ya Varan İndirimle En İyi Bilgisayar Hoparlörleri
Donanım
Riot, League of Legends’taki tartışmalı güncellemeyi erteledi
Oyun
Microsoft’un Geçen Yıl Kuantum Taleplerini Abarttığı İddia Ediliyor
Liste
Kritik: Kötü Niyetli Edge Eklentisi, Zararlı Yazılımlara Geçit Sağlıyor
Siber Güvenlik
//

Siber güvenlik, yapay zeka ve savunma sanayiinden; finans ve sinema dünyasına uzanan geniş bir yelpaze. Teknomers; teknoloji, strateji ve yazılım dünyasını sade bir dille sizlerle buluşturuyor.

Kurumsal

  • Hakkımızda
  • Gizlilik politikası
  • Tanıtım Yazısı ve Backlink Hizmeti

Kategoriler

  • Teknoloji
  • Oyun
  • Sinema
  • Siber Güvenlik
  • Bilim
  • Finans
  • Dünyadan Güncel Haberler

Populer

  • TV'de Ücretsiz İzlenebilen Şifresiz Erotik Kanallar (2025 Güncel Frekans Listesi)

  • The Last of Us PC Kontrolleri: Hızlı Silah Değiştirme ve Tüm Tuşlar (2025)

  • Hogwarts Legacy'de Odaklanma İksiri Nasıl Yapılır?

Teknomers | Dünyadan Güncel Teknoloji | Oyun | Müzik | Film | Spor HaberleriTeknomers | Dünyadan Güncel Teknoloji | Oyun | Müzik | Film | Spor Haberleri
Bizi Takip Et
© 2026 Teknomers. All Rights Reserved.
Welcome Back!

Sign in to your account

Kullanıcı Adı veya E-posta Adresi
Şifre

Şifrenizi mi unuttunuz?