Merancang Sistem Pembayaran yang Scalable
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
- Idempotensi: Setiap permintaan pembayaran harus memiliki
idempotency_keyyang unik. Ini memastikan bahwa bahkan jika terjadi timeout jaringan dan klien mencoba lagi, kita tidak akan melakukan penagihan ganda. - Transaksi ACID: Catatan keuangan harus bersifat atomik dan konsisten. Kami menggunakan database relasional (PostgreSQL/MySQL) dengan penguncian ketat untuk pembaruan buku besar.
- State Machine yang Terukur: Pembayaran melewati beberapa status:
PENDING→PROCESSING→SUCCEEDED/FAILED.
Arsitektur Tingkat Tinggi
Berikut adalah arsitektur tingkat tinggi dari sistem pembayaran kita:
Arch Note
Interactive logic enabled. Click components in expanded view for technical service definitions.
Skema Database (ERD)
Sistem pembayaran yang kuat dimulai dengan skema yang dirancang dengan baik untuk auditabilitas.
Arch Note
Interactive logic enabled. Click components in expanded view for technical service definitions.
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):
- Transaksi Lokal: Buat catatan
PENDINGdi DB. - Panggilan Eksternal: Panggil Payment Provider API.
- Callback Asinkron: Terima webhook atau polling status.
- 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
- Klien mengirimkan permintaan pembayaran dengan kunci idempotensi yang unik.
- API Gateway merutekannya ke Payment Service.
- Payment Service memeriksa database apakah kunci idempotensi ini sudah diproses.
- Jika belum, ia membuat catatan pembayaran yang tertunda.
- Ia memanggil penyedia pembayaran eksternal.
- Setelah pembayaran berhasil, ia memperbarui database dan menerbitkan event "Payment Succeeded" ke Kafka.
- Notification Service mengambil event tersebut dan mengirimkan email ke pengguna.
Arsitektur ini memastikan keandalan dan skala.