Skip to content
V3.0 // STABLE
LOAD 12%
LAT 24MS
SLA 99.99%

Merancang Sistem Pembayaran yang Scalable

3 min read
51 views
paymentsdistributed systemsarchitecturegolang

Mendesain sistem pembayaran membutuhkan konsistensi, keandalan, dan keamanan yang tinggi. Dalam artikel ini, kita akan mendesain arsitektur tingkat tinggi untuk memproses pembayaran pada skala perusahaan, dengan fokus pada Idempotensi, Integritas Transaksional, dan Pemrosesan Asinkron.

Prinsip Rekayasa Inti

  1. Idempotensi: Setiap permintaan pembayaran harus memiliki idempotency_key yang unik. Ini memastikan bahwa bahkan jika terjadi timeout jaringan dan klien mencoba lagi, kita tidak akan melakukan penagihan ganda.
  2. Transaksi ACID: Catatan keuangan harus bersifat atomik dan konsisten. Kami menggunakan database relasional (PostgreSQL/MySQL) dengan penguncian ketat untuk pembaruan buku besar.
  3. State Machine yang Terukur: Pembayaran melewati beberapa status: PENDINGPROCESSINGSUCCEEDED / FAILED.

Arsitektur Tingkat Tinggi

Berikut adalah arsitektur tingkat tinggi dari sistem pembayaran kita:

Live architecture
Analyzing Schema...

Arch Note

Interactive logic enabled. Click components in expanded view for technical service definitions.

Layer.0 / Distributed_System_Viz

Skema Database (ERD)

Sistem pembayaran yang kuat dimulai dengan skema yang dirancang dengan baik untuk auditabilitas.

Live architecture
Analyzing Schema...

Arch Note

Interactive logic enabled. Click components in expanded view for technical service definitions.

Layer.0 / Distributed_System_Viz

Implementasi: Idempotensi di Golang

Menggunakan Redis untuk menyimpan dan memvalidasi kunci permintaan dengan cepat sebelum mencapai database relasional.

func (s *PaymentService) ProcessPayment(ctx context.Context, req *PaymentRequest) (*PaymentResponse, error) {
    // 1. Check Redis for existing Idempotency Key
    exists, err := s.redis.SetNX(ctx, req.IdempotencyKey, "PROCESSING", 30*time.Minute).Result()
    if err != nil {
        return nil, fmt.Errorf("idempotency check failed: %w", err)
    }
    if !exists {
        return nil, ErrDuplicateRequest
    }
 
    // 2. Wrap in DB Transaction
    err = s.db.WithTransaction(func(tx *sql.Tx) error {
        // ... Core Payment Logic ...
        return nil
    })
 
    if err != nil {
        // Reset idempotency if business error allows retry
        s.redis.Del(ctx, req.IdempotencyKey)
        return nil, err
    }
 
    return &PaymentResponse{Status: "SUCCESS"}, nil
}

Menangani Kegagalan Terdistribusi

Saat berurusan dengan penyedia eksternal (Stripe, bank), kita tidak dapat membungkusnya dalam transaksi DB lokal. Kita menggunakan Saga Pattern (Berbasis Koreografi):

  1. Transaksi Lokal: Buat catatan PENDING di DB.
  2. Panggilan Eksternal: Panggil Payment Provider API.
  3. Callback Asinkron: Terima webhook atau polling status.
  4. Penyelesaian: Perbarui DB dan picu event hilir melalui Kafka.

Arsitektur ini memastikan bahwa bahkan jika server crash setelah langkah 2, kita dapat merekonsiliasi status nanti menggunakan catatan yang tertunda.

Alur Pembayaran

  1. Klien mengirimkan permintaan pembayaran dengan kunci idempotensi yang unik.
  2. API Gateway merutekannya ke Payment Service.
  3. Payment Service memeriksa database apakah kunci idempotensi ini sudah diproses.
  4. Jika belum, ia membuat catatan pembayaran yang tertunda.
  5. Ia memanggil penyedia pembayaran eksternal.
  6. Setelah pembayaran berhasil, ia memperbarui database dan menerbitkan event "Payment Succeeded" ke Kafka.
  7. Notification Service mengambil event tersebut dan mengirimkan email ke pengguna.

Arsitektur ini memastikan keandalan dan skala.