Faturalama Davranışını Değiştirme
Neyin, ne kadara ve ne zaman faturalanacağını, faturalama motorunu düzenleyerek değil onun açtığı dikişleri kullanarak ayarlayın.
Genel Bakış
Faturalama bir zincirdir: yenilemenin vakti gelir, fiyat çıkarılır, ödenmemiş fatura yazılır. Hizmeti ancak ödeme ilerletir. Her halkanın kendi genişletme noktası vardır. Yanlış halka, doğru toplamlı ama hiç ilerlemeyen bir vade üretir.
Her şeyi bir arada tutan tek kural: fatura kesmek hiçbir şeyi uzatmaz. Vade ödemede, ayrı bir işleyicide, fatura kalemine damgalanan döneme göre ilerler.
Ön Koşullar
- Kanca dinleyicilerine aşinalık: buradaki neredeyse her dikiş bir dinleyicidir. Referans sözleşmesini de bilin. Bu filtrelerin çoğu size değeri döndürmeniz için değil değiştirmeniz için verir.
- Gerçekten yenileyebileceğiniz bir test hizmeti. Kilitli ve canlı fiyat farkı ancak gerçek bir satırda görünür.
- "Hangi halkayı değiştiriyorum" sorusunun net cevabı. Seçenekler: faturalama kararı, tutar, fatura belgesi ya da ödeme.
Yapı
Tekrarlayan zincir ve dikişleri.
| Adım | Ne olur | Dikişiniz |
|---|---|---|
| Keşif | Zamanlanmış bir görev, yenileme tarihi gelen hizmetleri bulur | Tek bir hedefi gerekçesiyle atlayabilen bir kapı |
| Fiyatlama | Birim fiyat, miktar, vergi muafiyeti ve indirimler çözülür | Fiyat sonucunun tamamı üzerinde bir filtre |
| Fatura | Ödenmemiş fatura ve kalemleri, yenilenen dönem damgasıyla yazılır | Yük, kalem açıklaması ve toplamlar üzerinde filtreler |
| Ödeme | Kalem olayı vadeyi ilerletir ve döngüyü hizmete geri yazar | Uzatmadan sonra ateşlenen bir olay |
Metrik faturalama, birinciyi besleyen ikinci bir zincirdir. Kullanım saatlik toplanır; kapanan ay ya kendi faturası olur ya da bir sonraki yenileme faturasına alt kalem olur.
Adım Adım
Dikişi Seçin
- Değişikliğinizi anlatan cümleyi yazın ve içindeki nesneyi bulun. "Bu müşteriyi faturalama" keşiftir. "Yüzde 10 daha az al" fiyatlamadır. "Bir satır ekle" faturadır. "Sistemimize yenilendiğini söyle" ödemedir.
- Seçtiğiniz noktanın her yol için ateşlenip ateşlenmediğine bakın. Yenileme fiyatı filtresi ateşlenir; bir operatör ekranındaki filtre ateşlenmez.
- Yönü doğrulayın. Referansla çalışan bir filtre yerinde değişiklik bekler ve ne döndürdüğünüzü yok sayar.
- Hiçbiri uymuyorsa yardımcıyı düzenlemeyin. İhtiyacınız olan noktada jenerik bir kanca isteyin ve kendi koşulunuzu dinleyicinize koyun.
Bir Şeyin Fiyatını Değiştirin
- Yenileme tutarı filtresini dinleyin. Çözülmüş sonuç dizisini referansla, ayrıca hedef tipini, hedef satırını ve müşterinin fatura verisini alırsınız.
- Toplamı değil birim tutarı değiştirin. Miktar, vergiler ve indirimler sizin değerinizin etrafında uygulanır, yani yanlış alanı çarpmak iki kez saydırır.
- Sonuçtaki fiyat kaynağına saygı gösterin. Sayının satırdaki donmuş bir fiyattan mı yoksa ürünün canlı fiyatından mı geldiğini söyler. Biri için anlamlı olan indirim çoğu zaman diğeri için yanlıştır.
- Elde hesapla değil gerçek bir yenilemeyle doğrulayın: dahil vergi filtrenizden sonra ayıklanır.
Bir Yenilemeyi Atlayın ya da Yönlendirin
- Tek bir yenilemeyi durdurmak için oluşturma kapısından boş olmayan bir gerekçe dizesi döndürün. Görev kendini iptal edilmiş bildirir, gerekçe olarak dizeniz kaydedilir.
- Yenilemeleri kalıcı durdurmak için hizmete atlama bayrağını basın. Keşif adımı onu okur, işaretli hizmet hiç kuyruklanmaz.
- Hizmetin kendi döngüsünden farklı bir dönemi faturalamak için dönemi yenileme giriş noktasına geçirin. Hizmet satırına dokunmayın. Motor istenen dönemi canlı fiyatlar ve dönemi faturaya yazar. Ödeme o dönem kadar uzatır, saklı döngü yerinde kalır.
- Vadeyi kendiniz asla ilerletmeyin. Hizmet, arkasında ödenmiş bir fatura olmadan uzar ve bir sonraki koşu onu yeniden faturalar.
Paraya Tepki Verin
- "Hizmet uzatıldı" için yenileme olayını dinleyin. Yeni vade yazıldıktan sonra ateşlenir ve uzatmadan önceki anlık görüntüyü iki tarihle birlikte verir.
- "Bir fatura el değiştirdi" için, yeniden yüklenmiş faturayı ve önceki durumu veren durum değişikliği olayını dinleyin.
- Fatura oluşturma olayını ödeme yerine kullanmayın. Ödenmemiş bir fatura hiç ödenmeyebilir.
- Dinleyiciyi idempotent yapın. Operatör bir durumu yeniden yazabilir, bir ödeme farklı yollardan iki kez kaydedilebilir.
Referans
Giriş Noktaları
// Yenilemenin TEK giriş noktası. $target_type 'service' ya da 'addon'.
// ÖDENMEMİŞ bir fatura yazar ve döner; vadeye dokunmaz.
public static function process_renewal(string $target_type, int $target_id, array $opts = []): array;
// Hedef ve müşteri çözüldükten sonra yukarıdaki tarafından çağrılır.
public static function generate_renewal(string $target_type, array $target, array $user_data, array $opts = []): int|false;
// Fiyat. period ve period_time'ı $target ÜZERİNDEN okur; farklı bir dönemi
// fiyatlamanın yeni bir metot değil değiştirilmiş bir hedef vermek olmasının sebebi budur.
public static function calculate_renewal_amount(string $target_type, array $target, array $user_data, array $opts = []): array;
// 'already-invoiced' arkasındaki yinelenme sorgusu. Dönem anahtarın parçasıdır.
public static function has_renewal(string $target_type, int $target_id, string $duedate, string $period = ''): bool;
// Üründeki her faturanın tek INSERT noktası.
public static function create(array $data): int;
public static function add_item(array $data): int;
// Ara toplam, vergi, komisyon ve toplamı yeniden hesaplar. Önizleme için $persist = false.
public static function recalculate_totals(int $invoiceId, bool $persist = true): array|false;
// Durum geçişi ve tüm yan etkileri.
public static function change_status(int $invoiceId, string $status, array $options = []): bool;
$opts, hizmet detayı sayfasının kullandığı ve yalnız gösterime ait display_own_currency anahtarını taşır. Fatura fiyatlarken boş bırakın.
false geçmek yazmadan hesaplar; bir raporun belgeye dokunmadan güncel rakamı almasının yolu budur.
success artı invoice_id; hiçbir şey faturalanmadıysa bu null'dır ve reason nedenini taşır: already-invoiced, skip-renewal-invoice-flag, recurring-cycles-limit-reached, invalid-period ve kardeşleri. Atlama da bir başarıdır, o yüzden fatura id'sine göre dallanın.
Fiyat ve Belge Dikişleri
amount (birim fiyat), quantity, currency, taxexempt, additional_taxes, discounts, pricing_source ve period_time. Bağlam: hedef tipi, hedef satırı ve müşterinin fatura verisi. İndirimler çözüldükten sonra, dahil vergi ayıklanmadan önce ateşlenir. Diziyi yerinde değiştirin; dönüş kullanılmaz.
subtotal, tax, additional_tax, pmethod_commission, total ve discounts, referansla, saklanmadan önce. Fatura satırı ve kalemler değiştirilemez gelir. Her yeniden hesaplamada ateşlenir; buna kalem eklemek ve ödeme kaydetmek de dahildir. Toplamları yerinde değiştirin; dönüş kullanılmaz.
Karar ve Olay Dikişleri
Vade Gerçekte Nasıl İlerler
İki aday tarih üretilir; seçim yalnız operatörün ayarına değil hizmetin durumuna da bağlıdır.
| Hizmet durumu | Yeni vade | Neden |
|---|---|---|
| Aktif, henüz vadesi geçmemiş | Ayar ne derse desin kalemin dönem sonu | Erken ödeyen asla gün kaybetmemeli |
| Aktif ama vadesi geçmiş | Operatörün ayarına göre | Gecikmenin affedilip affedilmeyeceğine operatör karar verir |
| Askıya alınmış | Operatörün ayarına göre | Aynı karar, aynı anahtar |
| Başka her şey | Şimdi artı dönem | Eski döngü artık anlamlı değil |
| Daha eski ödenmemiş bir yenileme var | Zorla kalemin dönem sonu | Birikmiş faturayı ödemek kapsadığı ayları atlamamalı |
| Saatlik döngü | Zorla şimdi artı dönem | Saatlik bir dönem sonu, ödendiğinde çoktan geçmiştir |
Sonuç ardından asla geriye gidemeyecek şekilde sınırlanır. Fatura hizmetin kendi döneminden başkası için üretildiyse ödeme vadeyi o dönem kadar ilerletir. Saklı döngü, tutar ve para birimi olduğu gibi kalır.
Kullanım Bazlı Faturalama
// Bir dönemin aşım fiyatı. $scheme 'per_unit', 'volume' ya da 'graduated';
// $pricing kademe haritası; $ccode ise PARA BİRİMİ KODUDUR, çünkü kademeler
// çevrilmez, para birimi başına fiyatlanır.
public static function calculate(string $scheme, float $billable, array $pricing, string $ccode): float;
// Kullanım eksi dahil hak, sıfırda taban yapılmış.
public static function get_billable(float $usage, float $included): float;
// Saatlik anlık görüntülerden ayın rakamı. Metrik dönemler son okumaya değil
// ortalamaya göre faturalanır.
public static function monthly_average(array $snapshots): float;
Örnek
Yenilemelerde bir sadakat indirimi, bir atlama kuralı ve dış bir sisteme haber veren ödeme tarafı.
// 1. FİYAT. Her şey referansla gelir; BİRİM tutarı değiştirin, çünkü adet,
// vergiler ve indirimler onun etrafında uygulanır.
Hook::add('filter:invoice.renewal_amount', 20, function (&$result, $target_type, $target, $user_data) {
if ($target_type !== 'service') return;
$uid = (int) ($target['owner_id'] ?? 0);
if ($uid <= 0) return;
$years = (int) (User::getInfo($uid, ['acme_loyalty_years'])['acme_loyalty_years'] ?? 0);
if ($years < 3) return;
// Bu müşteriyle dondurulmuş bir fiyat üzerinde anlaşılmış; ona bir kez daha indirim
// uygulamak, operatörün bilerek yaptığı bir anlaşmayı bozar.
if (($result['pricing_source'] ?? '') !== 'live') return;
$result['amount'] = round((float) ($result['amount'] ?? 0) * 0.9, 4);
// İndirim listesi, faturanın müşteriye gösterdiği şeydir. Onu atlamak, üzerinde
// hiçbir açıklama olmayan daha ucuz bir fatura üretir.
$result['discounts']['acme_loyalty'] = [
'label' => Language::gc('admin/invoices/acme-loyalty-label'),
'rate' => 10,
];
});
// 2. KARAR. Boş olmayan bir dize reddeder; iş onu gerekçe olarak kaydeder.
Hook::add('gate:invoice.create', 10, function ($target_type, $target_id, $duedate) {
if ($target_type !== 'service') return '';
$service = Services::get((int) $target_id, 'id,options');
$opts = $service['options'] ?? [];
// Göç hâlindeki bir hizmet, yerine oturana kadar faturalanmamalıdır.
if ((int) ($opts['acme_migrating'] ?? 0) === 1) return 'acme-migration-in-progress';
return '';
});
// 3. TEPKİ. Vade yazıldıktan SONRA ateşlenir, yani uzatma sinyali budur.
// $service, taşınmadan ÖNCEKİ anlık görüntüdür: eski değerleri ondan okuyun.
Hook::add('action:service.renewed', 30, function ($serviceId, $service, $newDuedate, $oldDuedate) {
Utility::HttpRequest([
'url' => 'https://crm.example.com/renewals',
'type' => 'POST',
'data' => [
'service' => (int) $serviceId,
'from' => (string) $oldDuedate,
'to' => (string) $newDuedate,
'cycle' => (string) ($service['period'] ?? ''),
],
]);
});
Bir yenilemeyi kendiniz sürmek; hizmette saklı olan dönem yerine müşterinin seçtiği dönem için.
$result = Invoices::process_renewal('service', 5001, [
// Hizmet aylık olarak saklanıyor olsa da iki yıl faturalayın. Fiyatlama yalnız bu
// fatura için canlıya geçer ve ödeme iki yıl uzatır; saklı aylık döngü ile onun
// dondurulmuş tutarı ise olduğu gibi kalır.
'period' => 'year',
'period_time' => 2,
'source' => 'acme-portal',
]);
// Atlamada invoice_id null olur ve atlama bir başarısızlık DEĞİLDİR: dallanmayı id üzerinden yapın.
if (($result['invoice_id'] ?? null) === null) {
// already-invoiced | skip-renewal-invoice-flag | recurring-cycles-limit-reached
// | invalid-period | service-not-found | user-data-unavailable | ...
throw new Exception('Renewal not issued: ' . (string) ($result['reason'] ?? 'unknown'));
}
// Fatura vardır ve ÖDENMEMİŞTİR. Hizmete dair henüz hiçbir şey değişmemiştir.
$invoiceId = (int) $result['invoice_id'];
Aynı paranın okuma tarafı; bir rapor ya da mutabakat işi için. Kontrol, bir belgenin varlığında değil durum geçişindedir.
Hook::add('action:invoice.status_changed', 10, function ($invoice, $status, $old_status, $options) {
// Yalnız ödenmiş durumuna GEÇİŞ bir satıştır; tekrar eden bir yazma değildir.
if ($status !== 'paid' || $old_status === 'paid') return;
$id = (int) ($invoice['id'] ?? 0);
// Saklı bir rakama güvenmek yerine yeniden hesaplayın: operatör, fatura kesildikten
// sonra kalemleri düzenlemiş olabilir. false = önizleme, hiçbir şey kalıcı olmaz.
$totals = Invoices::recalculate_totals($id, false);
AcmeLedger::record([
'invoice' => $id,
'customer' => (int) ($invoice['user_id'] ?? 0),
'currency' => (string) ($invoice['currency'] ?? ''),
'net' => (float) ($totals['subtotal'] ?? 0),
'tax' => (float) ($totals['tax'] ?? 0),
'gross' => (float) ($totals['total'] ?? 0),
'method' => (string) ($options['pmethod'] ?? ''),
]);
});
Tuzaklar
Yenileme giriş noktası ödenmemiş bir fatura yazar ve durur. Fatura oluşturmayı yenileme olayı sayan bir entegrasyon, hiç ödenmemiş hizmetleri uzatır.
Kilitli fiyat, üzerinde anlaşılan döngüye aittir. Onu farklı bir döneme uygulamak, bir yıl için aylık tutarı tahsil eder. Başka bir dönemi fiyatlamak, o çağrı için hesabı canlıya çevirmek demektir; yenileme giriş noktası bir dönem geçtiğinizde bunu zaten yapar.
Fatura yükü filtresi her faturayı görür; sepet, yükseltme ve kullanım dahil. Toplamlar filtresi her yeniden hesaplamada ateşlenir. Dar bir koşulu olmayan bir dinleyici satırını tekrar tekrar ekler.
Reddedilen bir çağrı yine başarı döndürür; fatura id'si null olur ve bir gerekçe gelir. Gerekçeler: bu dönem için zaten faturalanmış, atlama bayrağı basılı, tekrarlama limiti dolmuş. Başarıya değil fatura id'sine bakın.
Kapanan bir metrik dönem, son okumadan değil saatlik anlık görüntülerin ortalamasından fiyatlanır. Kademe tablosu para birimi koduyla anahtarlanır, yani kademe girdisi olmayan para birimi sıfıra fiyatlanır.
İlgili Makaleler
Geri bildiriminiz için teşekkürler!
Yukarıda bulamadığınız her şey için destek ekibimiz her zaman yanınızda.