Bu rehber, “Ben HyperPay/Paymob/Stripe webhook’larını entegre edebilirim” seviyesinden “Bir fintech şirketinin ödeme altyapısını tasarlayabilirim” seviyesine sizi taşıyor. Uygulama yığını olarak PHP 8.x + Laravel varsayılarak yazılmıştır; çünkü bu sizin en güçlü temelinizdir.
1. İş Gereksinimleri
1. İş Gereksinimleri
Bir Ödeme Sistemi Gerçekten Hangi Problemi Çözmelidir?
Bir Ödeme Sistemi Gerçekten Hangi Problemi Çözmelidir?
Naif bir bakış: “Müşteriden para al, satıcıya ver.” Gerçek problem, üç asenkron, güvenilir olmayan gerçeklik kaynağını uzlaştırmaktır — uygulama durumu, ödeme kapısı durumu ve banka/kart ağının durumu — para kaybını asla önlemeli, çiftlenmemeli ve her hareket muhasebe ve yasal amaçlar için denetlenebilir olmalıdır.
Diğer her şey (idempotentlik, durum makineleri, webhooks, uzlaştırma) bir gerçeğin var olmasından kaynaklanmaktadır: ödeme onayı asenkron olup sessiz bir şekilde başarısız olabilir, gecikebilir, iki kez gelebilir ya da sıralama bozukluğu yaşayabilir.
Desteklemeniz Gereken Ödeme Akışları
Desteklemeniz Gereken Ödeme Akışları
| Akış | Temel zorluk |
|---|---|
| Tek seferlik ödeme | Basit kimlik doğrulama → yakalama, ancak yine de idempotentlik ve webhook işleme gereklidir |
| Abonelik | Tekrarlayan faturalama, dunning (başarısız yenilemeleri tekrar deneme), prorasyon, plan değişiklikleri |
| Cüzdan ödemeleri | İç muhasebe bakiyesi, ACID olmalıdır — cüzdanlar arası işlemde bir kapı yoktur, ancak fonlama/çekim bir kapı içerir |
| Parçalı ödemeler | Tek bir ödeme, birden fazla alıcı (pazar yeri modeli) — her alıcı için alt defter gerektirir ve Stripe Connect gibi kapılar veya manuel bölme-sonra-ödeme gerektirir |
| Tam iadeler | Yakalanmış bir ödemenin tersine çevrilmesi; muhasebe girişlerini de tersine çevirmelidir |
| Parçalı iadeler | Aynı ödeme birden fazla kez uzatılabilir; refunds tablosu gerektirir, tek bir bayrak değildir |
| Chargeback’ler | Banka tarafından başlatılan ters bir işlem, kontrolünüz dışında, günler/haftalar sonra gelir, itiraz kanıtı sunma süreci gerektirir |
| Ödeme anlaşmazlıkları | Son tarihleri, kanıtları ve muhasebeye hitap edecek kesin kazanma/kayıp sonuçlarını içeren resmi bir süreç |
Yaygın hata: iadeleri payments.status="refunded" olarak modellemek. Bu, kısmi iadeleri bozar ve geçmişi kaybeder. İadeler ödemeye bağlı ayrı birinci dereceden varlıklardır.
2. Sistem Mimarisi
2. Sistem Mimarisi
Bileşenler
Bileşenler
- API Gateway — kimlik doğrulama, hız sınırlama, istek yönlendirme, ödeme mantığı içermez
- Sipariş Servisi — iş sipariş/sepetini sahiplenir; ödeme, siparişin bir sonucudur, tersine değil
- Ödeme Servisi — kapı entegrasyonu, ödeme durum makinesi, idempotentlik içerir
- Cüzdan Servisi — iç bakiye, cüzdan defteri ve transferlerle ilgilenir
- Muhasebe Servisi — çift giriş defteri, muhasebe kayıtları, hesap chartı ile ilgilenir
- Webhook Servisi — kapı olaylarını alır, doğrular, çiftlenmelerini önler ve kuyruklar (genellikle Ödeme Servisi önünde ince bir entegrasyon katmanı)
- Bildirim Servisi — ödeme olaylarında e-posta/SMS/push, tamamen ayrılmış, olayları tüketir
- Uzlaştırma Servisi — dönemsel olarak kapı uzlaşma raporlarını iç kayıtlarla karşılaştırır
Tasarım prensibi: Ödeme Servisi, kapılarla konuşmaya izin verilen tek servistir. Sipariş Servisi asla Stripe’ı doğrudan çağırmaz. Bu, kapı-spesifik mantığı tek bir yerde tutar ve kapıları değiştirebilir/birleştirebiliriz, sipariş mantığını etkilemeden.
graph TD
Client --> APIGateway
APIGateway --> OrderService
APIGateway --> WalletService
OrderService -- "1. request payment" --> PaymentService
PaymentService -- "2. charge" --> Gateway[Stripe/HyperPay/Paymob]
Gateway -- "3. webhook" --> WebhookService
WebhookService -- "4. verified event" --> PaymentService
PaymentService -- "5. PaymentCaptured event" --> EventBus
EventBus --> OrderService
EventBus --> AccountingService
EventBus --> NotificationService
EventBus --> WalletService
ReconciliationService -- "settlement files" --> Gateway
ReconciliationService -- "compare" --> PaymentService
AccountingService --> Ledger[(Double-Entry Ledger)]
Tek seferlik bir ödeme için etkinlik akışı (mutlu yol)
Tek seferlik bir ödeme için etkinlik akışı (mutlu yol)
sequenceDiagram
participant U as User
participant O as Order Service
participant P as Payment Service
participant G as Gateway
participant W as Webhook Service
participant A as Accounting
U->>O: Checkout
O->>P: CreatePayment(order_id, amount, idempotency_key)
P->>P: Insert payment(status=PENDING)
P->>G: Create charge
G-->>P: charge_id (PROCESSING)
P-->>O: payment_id, redirect_url
U->>G: Completes 3DS
G->>W: webhook: charge.succeeded
W->>W: verify signature, dedupe
W->>P: handle event
P->>P: transition PROCESSING -> CAPTURED
P->>A: emit PaymentCaptured
A->>A: write journal entry
P->>O: emit OrderPaid
Önemli bir içgörü: kullanıcının gördüğü yönlendirme/geri çağırma asla gerçeklik kaynağı olarak güvenilmez. Sadece webhook (sunucu-sunucu, imzalı) durumu tamamlar. Geri çağırma yalnızca UX’tur.
3. Veritabanı Tasarımı
3. Veritabanı Tasarımı
payments
payments| Sütun | Tür | Notlar |
|---|---|---|
| id | bigint PK | |
| uuid | uuid, unique | dışa açık ID, id asla serbest bırakma |
| order_id | bigint, FK, indexed | |
| merchant_id | bigint, indexed | çoklu kiracı/pazar yeri için |
| amount | bigint | küçük birimler (cents/piastres) olarak sakla, asla float |
| currency | char(3) | ISO 4217 |
| status | enum/string, indexed | durum makinesi değeri |
| gateway | string | stripe, hyperpay, paymob |
| gateway_payment_id | string, indexed, nullable | |
| idempotency_key | string, unique, indexed | |
| captured_amount | bigint, default 0 | kısmi yakalamalar için |
| refunded_amount | bigint, default 0 | denormalize edilmiş, iade ile senkronize edilir |
| metadata | json | |
| created_at, updated_at | timestamp |
Kısıtlamalar: UNIQUE(idempotency_key), CHECK(refunded_amount, index on (order_id), (status, created_at) for queue scanning.
payment_attempts
payment_attemptsBir ödemenin birden fazla denemesi olabilir (kart reddedildi, başka bir yöntemle tekrar deneme). Yeni başlayanların çoğu bunu payments ile karıştırır.
| Sütun | Tür |
|---|---|
| id | bigint PK |
| payment_id | FK, indexed |
| payment_method_id | FK, nullable |
| attempt_number | int |
| status | succeeded/failed/declined |
| gateway_response_code | string |
| failure_reason | string, nullable |
| created_at | timestamp |
payment_transactions
payment_transactionsDurumu etkileyen her olayın değişmez, yalnızca eklenebilir bir denetim günlüğü (ödemeler için olay kaynaklı katman). Burada satırları asla güncellemeyin veya silmeyin.
| Sütun | Tür |
|---|---|
| id | bigint PK |
| payment_id | FK, indexed |
| type | authorize/capture/refund/void/chargeback |
| amount | bigint |
| gateway_transaction_id | string, indexed |
| raw_payload | json |
| created_at | timestamp |
payment_methods
payment_methods| Sütun | Tür |
|---|---|
| id | bigint PK |
| user_id | FK, indexed |
| gateway | string |
| gateway_token | string, rahatça şifrelenmiş |
| type | card/wallet/bank |
| last4 | string, nullable |
| brand | string, nullable |
| is_default | boolean |
| expires_at | date, nullable |
Asla PAN, CVV saklamayın. Sadece kapı tokenlerini saklayın. (Güvenlik bölümüne bakın.)
webhooks
webhooks| Sütun | Tür |
|---|---|
| id | bigint PK |
| gateway | string |
| event_id | string, unique, indexed (bu sizin dedupe anahtarınız) |
| event_type | string |
| payload | json |
| signature_verified | boolean |
| status | received/processing/processed/failed |
| processed_at | timestamp, nullable |
| received_at | timestamp |
UNIQUE(gateway, event_id) bu tüm şemanın en önemli kısıtlamasıdır — sadece çiftleme yapılaması için gereklidir.
refunds
refunds| Sütun | Tür |
|---|---|
| id | bigint PK |
| payment_id | FK, indexed |
| amount | bigint |
| reason | string |
| status | pending/succeeded/failed |
| gateway_refund_id | string, indexed |
| initiated_by | user_id/system |
| created_at | timestamp |
disputes
disputes| Sütun | Tür |
|---|---|
| id | bigint PK |
| payment_id | FK, indexed |
| gateway_dispute_id | string, indexed |
| reason | string |
| amount | bigint |
| status | needs_response/under_review/won/lost |
| evidence_due_by | timestamp, nullable |
| outcome_at | timestamp, nullable |
settlements ve reconciliation_records
settlements ve reconciliation_recordssettlements: bir kapı ödeme dağıtım partisi için bir satır (gateway_settlement_id, amount, currency, settled_at, fee_amount).
reconciliation_records: payment_transactions ile settlements bağlantıları düzenlenir, match_status (eşleşmiş/içsel eksik/dışsal eksik/miktar uyuşmazlığı). Bu tablo, Uzlaştırma Servisi’nin çıktısı olup, girişi değil.
erDiagram
payments ||--o{ payment_attempts : has
payments ||--o{ payment_transactions : logs
payments ||--o{ refunds : has
payments ||--o{ disputes : has
payments }o--|| payment_methods : uses
webhooks ||--o{ payment_transactions : triggers
settlements ||--o{ reconciliation_records : produces
payment_transactions ||--o{ reconciliation_records : matched_against
4. Ödeme Durum Makinesi
4. Ödeme Durum Makinesi
stateDiagram-v2
[*] --> PENDING
PENDING --> PROCESSING
PROCESSING --> AUTHORIZED
PROCESSING --> FAILED
AUTHORIZED --> CAPTURED
AUTHORIZED --> CANCELLED
CAPTURED --> PARTIALLY_REFUNDED
CAPTURED --> REFUNDED
CAPTURED --> CHARGEBACK
PARTIALLY_REFUNDED --> REFUNDED
PARTIALLY_REFUNDED --> CHARGEBACK
FAILED --> [*]
CANCELLED --> [*]
REFUNDED --> [*]
CHARGEBACK --> [*]
Açıkça korunması gereken geçersiz geçişler: FAILED → CAPTURED (g gecikmeli, sıra dışı bir webhook’un başarısız bir ödemeyi “düzeltmeye” çalışması — bunu reddet, kaydet, uyarı yap), REFUNDED → CAPTURED (imkansız ters çevrilme), PENDING → REFUNDED (asla yakalanmamış bir şeyi iade edemezsiniz).
Uygulama Deseni: Açık Geçiş Tablosu, Dağılmış ifler Değil
Uygulama Deseni: Açık Geçiş Tablosu, Dağılmış
ifler Değil
final class PaymentStateMachine
{
private const TRANSITIONS = [
'PENDING' => ['PROCESSING', 'FAILED'],
'PROCESSING' => ['AUTHORIZED', 'FAILED'],
'AUTHORIZED' => ['CAPTURED', 'CANCELLED'],
'CAPTURED' => ['PARTIALLY_REFUNDED', 'REFUNDED', 'CHARGEBACK'],
'PARTIALLY_REFUNDED' => ['REFUNDED', 'CHARGEBACK'],
'FAILED' => [],
'CANCELLED' => [],
'REFUNDED' => [],
'CHARGEBACK' => [],
];
public function canTransition(string $from, string $to): bool
{
return in_array($to, self::TRANSITIONS[$from] ?? [], true);
}
public function transition(Payment $payment, string $to): void
{
if (!$this->canTransition($payment->status, $to)) {
throw new InvalidPaymentTransitionException($payment->status, $to);
}
DB::transaction(function () use ($payment, $to) {
$payment->lockForUpdate(); // yeniden almak için
if (!$this->canTransition($payment->status, $to)) {
throw new InvalidPaymentTransitionException($payment->status, $to);
}
$payment->update(['status' => $to]);
PaymentTransaction::create([
'payment_id' => $payment->id,
'type' => strtolower($to),
'amount' => $payment->amount,
]);
});
}
}
Yaygın hata: durumu kontrol edip daha sonra güncellemek. Bu durum, bir webhook ile geri çağırmanın aynı anda çarparak yaratabileceği klasik bir TOCTOU yarışı. Her zaman durumu locked işlem içinde yeniden kontrol edin.
5. Ödeme Kapısı Entegrasyonu
5. Ödeme Kapısı Entegrasyonu
Soyutlama Katmanı
Soyutlama Katmanı
Ödeme Servisi mantığının kapı detayları sızmasını önlemek için bir PaymentGatewayInterface tanımlayın:
interface PaymentGatewayContract
{
public function createCharge(ChargeRequest $request): GatewayChargeResponse;
public function capture(string $gatewayPaymentId, int $amount): GatewayChargeResponse;
public function refund(string $gatewayPaymentId, int $amount): GatewayRefundResponse;
public function verifyWebhookSignature(string $payload, string $signature): bool;
public function parseWebhookEvent(string $payload): WebhookEvent;
}
Her kapı (StripeGateway, HyperPayGateway, PaymobGateway) bunu uygular. Ödeme Servisi yalnızca arabirime bağımlıdır ve payment.gateway anahtarı ile çözülür.
Stripe — Yetkilendirme + Yakalama
Stripe — Yetkilendirme + Yakalama
$intent = $this->stripe->paymentIntents->create([
'amount' => $amountInCents,
'currency' => 'usd',
'capture_method' => 'manual', // ayrı yetkilendirme/yakalama
'metadata' => [
'payment_uuid' => $payment->uuid,
],
], [
'idempotency_key' => $payment->idempotency_key,
]);
Ödeme ile ilgili idempotentlik anahtar parametresini Stripe’ın yerel olarak sağlamasına dikkat edin — kendi anahtarınızı geçin, veritabanı kısıtlamanıza güvenmeyin.
HyperPay — checkout id akışı
HyperPay — checkout id akışı
HyperPay, iki aşamalı bir akış gerektirir: bir checkoutId oluşturun, kullanıcıyı yerleşik widget’a yönlendirin, ardından yeniden yönlendirme sonrasında GET /payments/{id} sunucu tarafında çağırarak durumu doğrulayın. Yalnızca yönlendirme sorgu parametrelerine güvenmeyin — her zaman sunucudan sunucuya durum çağrısıyla yeniden doğrulayın, çünkü yönlendirme parametreleri istemci tarafında değiştirilebilir.
Paymob — kimlik doğrulama tokeni → sipariş → ödeme anahtarı → iframe
Paymob — kimlik doğrulama tokeni → sipariş → ödeme anahtarı → iframe
Paymob’un akışı en çok durumlu olandır: (1) kimlik doğrulama tokenini al, (2) siparişi kaydet, (3) sipariş_id + tutar ile ödeme anahtarı iste, (4) ödeme anahtarı ile iframe’i renderla. Her adım ayrı bir API çağrısıdır; kimlik doğrulama tokenini önbelleğe al (kısa ömürlü, ~1 saat), her istekte yeniden çekmek yerine.
Geri çağırma ve Webhook işleme
Geri çağırma ve Webhook işleme
- Geri çağırma (tarayıcı yönlendirmesi): yalnızca UX. “İşleniyor” sayfasını göster. Bir durum kontrolü çağrısı tetikleyin, final durumu için sorgu parametrelerine güvenmeyin.
- Webhook (sunucu-sunucu): gerçeklik kaynağı. İmzayı doğrula →
webhooks.event_ideşsizlik kontrolü yap → kuyruklu bir işe yönlendir → işPaymentStateMachine::transition()çağrısını yapsın.
Yaygın hata: webhook HTTP denetleyicisi içinde doğrudan siparişin “ödenmiş” olarak işaretlenmesi, senkronize olarak ağır bir mantık. Webhook uç noktaları, 200 olarak yanıt vermelidir, milisaniyeler içinde — imzayı doğrula, ham olayı sakla, bir işi yönlendir, geri dön. Tüm işleme kuyruğa alınmış işte gerçekleşir.
6. İdempotentlik
6. İdempotentlik
Neden Kopya Ücretler Oluşuyor
Neden Kopya Ücretler Oluşuyor
Ağ zaman aşımaları kök nedendir: istemci “müşteriyi yükle” çağrısını yapar, istek kapıya ulaşır ve başarılı olur, ancak yanıt kaybolur (zaman aşımı, bağlantı düşer). İstemci tekrar dener. İdempotentlik olmadan, bu bir kullanıcı eylemi için iki ücret oluşturur.
Stripe bunu Nasıl Çözüyor
Stripe bunu Nasıl Çözüyor
Stripe, 24 saat içinde aynı anahtarın yeniden kullanılması durumunda işlemi yeniden çalıştırmadan orijinal yanıtı döndürür. Bu kalıbı içsel olarak, yalnızca kapının değil, tüm kendi tarafınızdaki işlemler için yansıtmalısınız.
Veritabanı Tasarımı
Veritabanı Tasarımı
payments.idempotency_key UNIQUE — checkout denemesi başına istemci tarafında (örneğin, ön yüzde saklanan UUID, create-payment isteğiyle gönderilir) veya sunucu tarafında hash(order_id + user_id + amount) şeklinde oluşturulur.
Laravel Uygulaması
Laravel Uygulaması
public function createPayment(CreatePaymentRequest $request): Payment
{
$key = $request->header('Idempotency-Key') ?? throw new MissingIdempotencyKeyException();
return DB::transaction(function () use ($request, $key) {
$existing = Payment::where('idempotency_key', $key)
->lockForUpdate()
->first();
if ($existing) {
return $existing; // orijinal sonucu döndürün, yeniden ücreti almayın
}
return Payment::create([
'idempotency_key' => $key,
'order_id' => $request->order_id,
'amount' => $request->amount,
'status' => 'PENDING',
]);
});
}
UNIQUE kısıtlaması, gerçekten emniyet ağınızdır — eş zamanlı isteklerde yalnızca bir INSERT başarılı olur; UniqueConstraintViolationException yakalayın ve yeniden edin.
Yarış koşulu önleme
Yarış koşulu önleme
try {
$payment = Payment::create([...]);
} catch (UniqueConstraintViolationException $e) {
$payment = Payment::where('idempotency_key', $key)->firstOrFail();
}
Bu kalıp (insert-sonra-yakala) yüksek eşzamanlılık altında daha dayanıklıdır çünkü veritabanı, uygulamanızın değil, yarış koşulunu yönetir.
7. Webhook Mimarisi
7. Webhook Mimarisi
İmza Doğrulama
İmza Doğrulama
Ham veriye dokunmadan önce her zaman doğrulayın. Stripe: HMAC-SHA256 üzerinden timestamp.payload kullanarak, webhook sırrını kullanarak; timestamp yaklaşık 5 dakikadan eskiyse reddedin (yeniden oynatma koruması).
public function verifyWebhookSignature(string $payload, string $sigHeader): bool
{
[$timestamp, $signature] = $this->parseStripeSigHeader($sigHeader);
if (abs(time() - (int) $timestamp) > 300) {
return false; // eski/yeniden oynatılan olayları reddet
}
$expected = hash_hmac('sha256', "{$timestamp}.{$payload}", $this->webhookSecret);
return hash_equals($expected, $signature);
}
Yeniden Deneme Mekanizmaları
Yeniden Deneme Mekanizmaları
Kapılar, zaman aşımı olmayan 2xx göndermeleri için saatler/günler boyunca yeniden denerler. Uç noktanız hızlı ve idempotent olmalıdır — ham olayı kaydettikten sonra hemen 200 yanıtını verin, asenkron olarak işleyin.
Çift Olaylar
Çift Olaylar
UNIQUE(gateway, event_id) üzerinde webhooks tablosunda. Çift ekleme durumunda, yeniden işleme olmadan 200 döndürün — kapı sadece bir onay ister.
Olay Sıralaması Problemleri
Olay Sıralaması Problemleri
Bir ödeme için webhook’lar farklı sırada (örneğin, charge.failed gecikmiş yeniden deneme charge.succeeded‘dan önce gelir, veya capture authorize‘dan önce gelir) gelebilir. Sıralamaya asla güvenmeyin. Yanlış sıradaki bir olay geçersiz bir geçişe neden olursa, sessizce uygulanmak yerine manuel inceleme için bir ölü mektup tablosuna kaydedin.
Olay Yeniden Oynatma Koruması
Olay Yeniden Oynatma Koruması
Bunları birleştirin: (1) zaman damgası tazelik kontrolü, (2) event_id eşsizlik, (3) durum makinesi geçerlilik kontrolü. Üç katman — her biri tek başına yetersizdir.
flowchart LR
A[Webhook received] --> B{Signature valid?}
B -- No --> X[Reject 400]
B -- Yes --> C{event_id seen before?}
C -- Yes --> D[Return 200, no-op]
C -- No --> E[Persist raw event]
E --> F[Dispatch ProcessWebhookJob]
F --> G[Return 200]
G -.async.-> H{Valid state transition?}
H -- No --> I[Dead-letter + alert]
H -- Yes --> J[Apply transition + emit domain event]
8. Muhasebe Tasarımı
8. Muhasebe Tasarımı
Neden Ödemelerin Çift Giriş Muhasebesine İhtiyacı Vardır
Neden Ödemelerin Çift Giriş Muhasebesine İhtiyacı Vardır
status="captured" bayrağı size ne olduğunu söylese de paranın nerede olduğunu göstermez. Maliye, ödeme kapısının bekleyen bakiyesinde ne kadar, banka hesabınızda ne kadar, satıcılara ne kadar borçlu olduğunuzu bilmek zorundadır ve bu da gerçek bir defter gerektirir.
Hesap Çizelgesi (minimal örnek)
Hesap Çizelgesi (minimal örnek)
1000 Varlıklar
1010 Nakit - Banka Hesabı
1020 Kapı Alacak (Stripe)
1030 Kapı Alacak (HyperPay)
2000 Yükümlülükler
2010 Satıcı Ödemeleri
2020 Müşteri Cüzdan Yükümlülüğü
2030 İadeler Ödenecek
4000 Gelir
4010 Platform Ücreti Geliri
5000 Giderler
5010 Ödeme İşlem Ücretleri
5020 Chargeback Kayıpları
Defter Girişi Yapısı
Defter Girişi Yapısı
| Sütun | Tür |
|---|---|
| id | PK |
| reference_type | payment/refund/chargeback |
| reference_id | FK |
| description | string |
| posted_at | timestamp |
ledger_lines (gerçek borç/alacak)
ledger_lines (gerçek borç/alacak)| Sütun | Tür |
|---|---|
| journal_entry_id | FK |
| account_code | string |
| debit | bigint |
| credit | bigint |
Uygulama katmanında (ve idealde veritabanı tetikleyicisi/kontrolü ile) zorunlu bir koşul: SUM(debit) = SUM(credit) her jurnal girişi için. Bu müzakere edilemez — “çift giriş” ne anlama geliyor.
Örnek: başarılı $100 ödeme, $3 kapı ücreti
Örnek: başarılı $100 ödeme, $3 kapı ücreti
DR 1020 Kapı Alacak 100.00
CR 4010 Platform Ücreti Geliri ... (eğer iradeli satıcıysanız, daha sonra ayırın)
Daha doğru olarak, iki giriş:
Entry 1 (capture):
DR 1020 Kapı Alacak 100.00
CR 2010 Satıcı Ödemeleri 100.00
Entry 2 (kapı uzlaşması, ücret düşüldü):
DR 1010 Nakit - Banka 97.00
DR 5010 Ödeme İşlem Ücreti 3.00
CR 1020 Kapı Alacak 100.00
Örnek: $20 Kısmi İade
Örnek: $20 Kısmi İade
DR 2010 Satıcı Ödemeleri 20.00
CR 1010 Nakit - Banka Hesabı 20.00
Örnek: Chargeback ($100, kaybedilen itiraz)
Örnek: Chargeback ($100, kaybedilen itiraz)
DR 5020 Chargeback Kayıpları 100.00
CR 1010 Nakit - Banka Hesabı 100.00
Yaygın hata: hesap bakiyelerini doğrudan güncellemek yerine bunları SUM(ledger_lines) üzerinden türetmek. Bakiyeler her zaman defterin bir materialized view/cache olmalıdır, asla gerçeklik kaynağı değil — aksi takdirde denetim izini kaybedersiniz ve uzlaştırma imkansız hale gelir.
9. Uzlaştırma Sistemi
9. Uzlaştırma Sistemi
Neden Vardır
Neden Vardır
Veritabanınız “yakalanmış” diyebilirken kapı asla fonları kesin olarak yerleşim yapmamış olabilir (kapı tarafı hatası, döviz çevirme sorunu, uç noktanız kapalı olduğu için almadığınız
Kaynak: Orijinal Makale


