home / blog / Ödeme Geçidi Entegrasyonu: Kalıplar, Tuz...
Yazılım Mühendisliği Jan 22, 2026

Ödeme Geçidi Entegrasyonu: Kalıplar, Tuzaklar ve En İyi Uygulamalar

Ödeme geçitlerini doğru şekilde entegre etmeye derinlemesine bakış — idempotency, webhook işleme, hata kurtarma ve çoklu geçit desteği için strateji kalıbı.

Ödeme Geçidi Entegrasyonu: Kalıplar, Tuzaklar ve En İyi Uygulamalar

Ödeme Entegrasyonu Sadece Bir API Çağrısı Değildir

Her geliştiricinin ilk ödeme entegrasyonu aynı görünür: API'yi çağır, yanıtı kontrol et, işlemi kaydet. Yayınla.

Sonra gerçek dünya devreye girer -- ağ zaman aşımları, mükerrer ödemeler, webhook teslim hataları, para birimi yuvarlama hataları, kısmi iadeler ve "Öde" düğmesine on yedi kez tıklayan o müşteri.

Bu rehber, kırılgan bir ödeme entegrasyonunu üretime hazır bir entegrasyondan ayıran desenleri kapsar.

Strategy Pattern: Birden Fazla Ödeme Sağlayıcısı Desteği

Çoğu uygulama eninde sonunda birden fazla ödeme sağlayıcısını desteklemek zorunda kalır. Strategy pattern bunu temiz bir şekilde çözer:

interface PaymentGateway
{
    public function charge(Money $amount, PaymentMethod $method): PaymentResult;
    public function refund(string $transactionId, ?Money $amount = null): RefundResult;
    public function supportsMethod(string $method): bool;
}

class StripeGateway implements PaymentGateway
{
    public function __construct(private readonly StripeClient $client) {}

    public function charge(Money $amount, PaymentMethod $method): PaymentResult
    {
        try {
            $intent = $this->client->paymentIntents->create([
                'amount' => $amount->inCents(),
                'currency' => strtolower($amount->currency),
                'payment_method' => $method->token,
                'confirm' => true,
                'idempotency_key' => $method->idempotencyKey,
            ]);

            return PaymentResult::success(
                transactionId: $intent->id,
                amount: $amount,
            );
        } catch (CardException $e) {
            return PaymentResult::failed($e->getMessage(), $e->getDeclineCode());
        }
    }

    // ...
}

class PayPalGateway implements PaymentGateway
{
    // Same interface, different implementation
}

// Resolution
class PaymentGatewayFactory
{
    public function make(string $provider): PaymentGateway
    {
        return match($provider) {
            'stripe' => app(StripeGateway::class),
            'paypal' => app(PayPalGateway::class),
            default => throw new UnsupportedGatewayException($provider),
        };
    }
}

Idempotency: Mükerrer Ödemeleri Önleme

Bu, ödeme işlemlerindeki en önemli desendir. Kullanıcı çift tıklarsa, sunucunuz zaman aşımı sonrası yeniden denerse, bir webhook iki kez tetiklenirse -- müşteriden yalnızca bir kez ücret alınmalıdır.

class ProcessPayment
{
    public function execute(PaymentRequest $request): PaymentResult
    {
        // Generate a deterministic idempotency key
        $idempotencyKey = $this->generateKey($request);

        // Check if we've already processed this payment
        $existing = Payment::where('idempotency_key', $idempotencyKey)->first();
        if ($existing) {
            return PaymentResult::fromExisting($existing);
        }

        // Create a pending record BEFORE calling the gateway
        $payment = Payment::create([
            'idempotency_key' => $idempotencyKey,
            'amount' => $request->amount,
            'status' => PaymentStatus::Pending,
            'gateway' => $request->gateway,
        ]);

        try {
            $result = $this->gateway->charge($request->amount, $request->method);

            $payment->update([
                'status' => $result->success ? PaymentStatus::Completed : PaymentStatus::Failed,
                'transaction_id' => $result->transactionId,
                'gateway_response' => $result->rawResponse,
            ]);

            return $result;
        } catch (Throwable $e) {
            $payment->update(['status' => PaymentStatus::Error, 'error' => $e->getMessage()]);
            throw $e;
        }
    }

    private function generateKey(PaymentRequest $request): string
    {
        return hash('sha256', implode('|', [
            $request->orderId,
            $request->amount->inCents(),
            $request->amount->currency,
            $request->userId,
        ]));
    }
}

Webhook Yönetimi: Ödeme Durumunun Omurgası

İstemci tarafındaki ödeme onayına asla tek başına güvenmeyin. Webhook'lar doğruluğun kaynağıdır.

class StripeWebhookController extends Controller
{
    public function handle(Request $request): Response
    {
        // 1. Verify the webhook signature
        try {
            $event = Webhook::constructEvent(
                $request->getContent(),
                $request->header('Stripe-Signature'),
                config('services.stripe.webhook_secret')
            );
        } catch (SignatureVerificationException $e) {
            return response('Invalid signature', 400);
        }

        // 2. Check for duplicate delivery
        if (WebhookLog::where('event_id', $event->id)->exists()) {
            return response('Already processed', 200);
        }

        // 3. Log before processing
        WebhookLog::create([
            'event_id' => $event->id,
            'type' => $event->type,
            'payload' => $event->toArray(),
        ]);

        // 4. Dispatch to handler
        match($event->type) {
            'payment_intent.succeeded' => $this->handlePaymentSuccess($event),
            'payment_intent.payment_failed' => $this->handlePaymentFailure($event),
            'charge.refunded' => $this->handleRefund($event),
            default => null, // Ignore unhandled events
        };

        // 5. Always return 200 to prevent retries
        return response('OK', 200);
    }
}

Hata Kurtarma Desenleri

Ödemeler ilginç şekillerde başarısız olabilir. En yaygın senaryoları nasıl ele alacağınız:

  • Ödeme ağ geçidi zaman aşımı: Körü körüne yeniden denemeyin. Önce ödeme durumunu kontrol edin, ardından yalnızca orijinal ücretlendirme gerçekleşmediyse yeniden deneyin
  • Kısmi başarısızlık: Ücretlendirme başarılı olup veritabanı yazımı başarısız olursa, webhook durumu uzlaştıracaktır
  • Reddedilen kartlar: Red kodlarından eşleştirilmiş kullanıcı dostu mesajlar döndürün
  • Para birimi uyumsuzluğu: Para birimini giriş katmanında doğrulayın, asla ödeme ağ geçidi katmanında değil

Ödeme Entegrasyonlarını Test Etme

class ProcessPaymentTest extends TestCase
{
    public function test_idempotent_payment_does_not_double_charge(): void
    {
        $gateway = Mockery::mock(PaymentGateway::class);
        $gateway->shouldReceive('charge')
            ->once()  // Critical: should only be called ONCE
            ->andReturn(PaymentResult::success('txn_123', new Money(1000)));

        $processor = new ProcessPayment($gateway);
        $request = new PaymentRequest(orderId: 1, amount: new Money(1000));

        $result1 = $processor->execute($request);
        $result2 = $processor->execute($request);

        $this->assertTrue($result1->success);
        $this->assertTrue($result2->success);
        $this->assertEquals(1, Payment::count()); // Only one record
    }
}

Yayına Almadan Önce Kontrol Listesi

  • Webhook imza doğrulaması etkinleştirildi
  • Tüm ücretlendirmeler için idempotency key'ler oluşturuluyor
  • Tüm parasal tutarlar ondalık sayı değil, tam sayı cent olarak kullanılıyor
  • Webhook endpoint'i işlemeden önce 200 döndürüyor (kuyruklanmış job'lar kullanın)
  • Başarısız webhook teslimatları izleniyor ve uyarılar yapılandırılmış
  • PCI uyumluluk gereksinimleri anlaşılmış (barındırılan ödeme formları kullanın)
  • İade akışları uçtan uca test edildi
tum yazilara don