178 lines
3.8 KiB
Go
178 lines
3.8 KiB
Go
package bank
|
|
|
|
import (
|
|
"fmt"
|
|
"math/rand"
|
|
"sync"
|
|
"time"
|
|
|
|
"bank-emulator/internal/storage"
|
|
)
|
|
|
|
type ThreeDSSession struct {
|
|
SessionID string
|
|
CardNumber string
|
|
Code string
|
|
Attempts int
|
|
ExpiresAt time.Time
|
|
Confirmed bool
|
|
}
|
|
|
|
type Bank struct {
|
|
Store *storage.Store
|
|
sessions map[string]*ThreeDSSession
|
|
mu sync.Mutex
|
|
}
|
|
|
|
func New(store *storage.Store) *Bank {
|
|
return &Bank{
|
|
Store: store,
|
|
sessions: make(map[string]*ThreeDSSession),
|
|
}
|
|
}
|
|
|
|
func (b *Bank) ValidateCard(number string) error {
|
|
_, card := b.Store.FindCardByNumber(number)
|
|
if card == nil {
|
|
return fmt.Errorf("card not found")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (b *Bank) InitiateBind(cardNumber, cvv, expiry string) (*ThreeDSSession, error) {
|
|
_, card := b.Store.FindCardByNumber(cardNumber)
|
|
if card == nil {
|
|
return nil, fmt.Errorf("card not found")
|
|
}
|
|
|
|
if card.CVV != cvv {
|
|
return nil, fmt.Errorf("invalid CVV")
|
|
}
|
|
|
|
if expiry != "" && card.Expiry != expiry {
|
|
return nil, fmt.Errorf("invalid expiry date")
|
|
}
|
|
|
|
b.mu.Lock()
|
|
defer b.mu.Unlock()
|
|
|
|
sessionID := fmt.Sprintf("3ds-%d", rand.Int63())
|
|
code := fmt.Sprintf("%06d", rand.Intn(1_000_000))
|
|
|
|
session := &ThreeDSSession{
|
|
SessionID: sessionID,
|
|
CardNumber: cardNumber,
|
|
Code: code,
|
|
Attempts: 0,
|
|
ExpiresAt: time.Now().Add(5 * time.Minute),
|
|
}
|
|
b.sessions[sessionID] = session
|
|
|
|
fmt.Printf("\n========== 3-DS CODE ==========\n")
|
|
fmt.Printf(" Session: %s\n", sessionID)
|
|
fmt.Printf(" Card: %s\n", cardNumber)
|
|
fmt.Printf(" Code: %s\n", code)
|
|
fmt.Printf(" Expires: %s\n", session.ExpiresAt.Format("15:04:05"))
|
|
fmt.Printf("===============================\n\n")
|
|
|
|
return session, nil
|
|
}
|
|
|
|
func (b *Bank) Confirm3DS(sessionID, code string) error {
|
|
b.mu.Lock()
|
|
defer b.mu.Unlock()
|
|
|
|
session, ok := b.sessions[sessionID]
|
|
if !ok {
|
|
return fmt.Errorf("session not found")
|
|
}
|
|
if session.Confirmed {
|
|
return fmt.Errorf("session already confirmed")
|
|
}
|
|
if time.Now().After(session.ExpiresAt) {
|
|
delete(b.sessions, sessionID)
|
|
return fmt.Errorf("session expired")
|
|
}
|
|
|
|
session.Attempts++
|
|
if session.Code != code {
|
|
remaining := 3 - session.Attempts
|
|
if remaining <= 0 {
|
|
delete(b.sessions, sessionID)
|
|
return fmt.Errorf("max attempts reached, session invalidated")
|
|
}
|
|
return fmt.Errorf("invalid code, %d attempts remaining", remaining)
|
|
}
|
|
|
|
session.Confirmed = true
|
|
fmt.Printf("[3DS] Session %s confirmed successfully\n", sessionID)
|
|
return nil
|
|
}
|
|
|
|
func (b *Bank) Charge(cardNumber string, amount float64) (*ThreeDSSession, error) {
|
|
if err := b.ValidateCard(cardNumber); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
_, card := b.Store.FindCardByNumber(cardNumber)
|
|
if card.Balance < amount {
|
|
return nil, fmt.Errorf("insufficient funds: %.2f < %.2f", card.Balance, amount)
|
|
}
|
|
|
|
session, err := b.InitiateBind(cardNumber, card.CVV, "")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return session, nil
|
|
}
|
|
|
|
func (b *Bank) CompleteCharge(sessionID string, amount float64) error {
|
|
b.mu.Lock()
|
|
session, ok := b.sessions[sessionID]
|
|
b.mu.Unlock()
|
|
|
|
if !ok {
|
|
return fmt.Errorf("session not found")
|
|
}
|
|
if !session.Confirmed {
|
|
return fmt.Errorf("3DS not confirmed")
|
|
}
|
|
|
|
err := b.Store.Charge(session.CardNumber, amount)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
b.mu.Lock()
|
|
delete(b.sessions, sessionID)
|
|
b.mu.Unlock()
|
|
|
|
b.Store.Save()
|
|
fmt.Printf("[BANK] Charged %.2f from card %s\n", amount, session.CardNumber)
|
|
return nil
|
|
}
|
|
|
|
func (b *Bank) DirectCharge(cardNumber string, amount float64) error {
|
|
if amount <= 0 {
|
|
return fmt.Errorf("amount must be positive")
|
|
}
|
|
|
|
_, card := b.Store.FindCardByNumber(cardNumber)
|
|
if card == nil {
|
|
return fmt.Errorf("card not found")
|
|
}
|
|
|
|
if card.Balance < amount {
|
|
return fmt.Errorf("insufficient funds: %.2f < %.2f", card.Balance, amount)
|
|
}
|
|
|
|
err := b.Store.Charge(cardNumber, amount)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
b.Store.Save()
|
|
fmt.Printf("[BANK] Direct charged %.2f from card %s\n", amount, cardNumber)
|
|
return nil
|
|
}
|