168 lines
3.3 KiB
Go
168 lines
3.3 KiB
Go
package storage
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"math/rand"
|
|
"os"
|
|
"sync"
|
|
)
|
|
|
|
type Card struct {
|
|
Number string `json:"number"`
|
|
HolderName string `json:"holder_name"`
|
|
Expiry string `json:"expiry"`
|
|
CVV string `json:"cvv"`
|
|
Balance float64 `json:"balance"`
|
|
}
|
|
|
|
type Account struct {
|
|
ID int `json:"id"`
|
|
Name string `json:"name"`
|
|
Cards []*Card `json:"cards"`
|
|
}
|
|
|
|
type Store struct {
|
|
mu sync.RWMutex
|
|
Accounts []*Account `json:"accounts"`
|
|
filePath string
|
|
}
|
|
|
|
func New(path string) *Store {
|
|
s := &Store{filePath: path}
|
|
data, err := os.ReadFile(path)
|
|
if err == nil {
|
|
json.Unmarshal(data, &s.Accounts)
|
|
}
|
|
return s
|
|
}
|
|
|
|
func (s *Store) Save() error {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
data, _ := json.MarshalIndent(s.Accounts, "", " ")
|
|
return os.WriteFile(s.filePath, data, 0644)
|
|
}
|
|
|
|
func (s *Store) AddAccount(name string) *Account {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
acc := &Account{ID: len(s.Accounts) + 1, Name: name}
|
|
s.Accounts = append(s.Accounts, acc)
|
|
return acc
|
|
}
|
|
|
|
func (s *Store) reload() {
|
|
data, err := os.ReadFile(s.filePath)
|
|
if err != nil {
|
|
return
|
|
}
|
|
var accounts []*Account
|
|
if json.Unmarshal(data, &accounts) == nil {
|
|
s.Accounts = accounts
|
|
}
|
|
}
|
|
|
|
func (s *Store) FindCardByNumber(number string) (*Account, *Card) {
|
|
s.mu.Lock()
|
|
s.reload()
|
|
s.mu.Unlock()
|
|
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
for _, acc := range s.Accounts {
|
|
for _, c := range acc.Cards {
|
|
if c.Number == number {
|
|
return acc, c
|
|
}
|
|
}
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
func (s *Store) Charge(cardNumber string, amount float64) error {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
s.reload()
|
|
for _, acc := range s.Accounts {
|
|
for _, c := range acc.Cards {
|
|
if c.Number == cardNumber {
|
|
if c.Balance < amount {
|
|
return fmt.Errorf("insufficient funds: %.2f < %.2f", c.Balance, amount)
|
|
}
|
|
c.Balance -= amount
|
|
return nil
|
|
}
|
|
}
|
|
}
|
|
return fmt.Errorf("card not found")
|
|
}
|
|
|
|
func (s *Store) AddCard(accountID int) (*Card, error) {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
for _, acc := range s.Accounts {
|
|
if acc.ID == accountID {
|
|
card := &Card{
|
|
Number: generateCardNumber(),
|
|
HolderName: acc.Name,
|
|
Expiry: fmt.Sprintf("%02d/%02d", rand.Intn(12)+1, 26+rand.Intn(5)),
|
|
CVV: fmt.Sprintf("%03d", rand.Intn(1000)),
|
|
Balance: 0,
|
|
}
|
|
acc.Cards = append(acc.Cards, card)
|
|
return card, nil
|
|
}
|
|
}
|
|
return nil, fmt.Errorf("account %d not found", accountID)
|
|
}
|
|
|
|
func (s *Store) TopUp(cardNumber string, amount float64) error {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
for _, acc := range s.Accounts {
|
|
for _, c := range acc.Cards {
|
|
if c.Number == cardNumber {
|
|
c.Balance += amount
|
|
return nil
|
|
}
|
|
}
|
|
}
|
|
return fmt.Errorf("card not found")
|
|
}
|
|
|
|
func (s *Store) ListAccounts() []*Account {
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
return s.Accounts
|
|
}
|
|
|
|
func generateCardNumber() string {
|
|
prefix := "4228"
|
|
body := make([]byte, 11)
|
|
for i := range body {
|
|
body[i] = byte(rand.Intn(10))
|
|
}
|
|
partial := prefix
|
|
for _, b := range body {
|
|
partial += fmt.Sprintf("%d", b)
|
|
}
|
|
digits := make([]int, 15)
|
|
for i, ch := range partial {
|
|
digits[i] = int(ch - '0')
|
|
}
|
|
sum := 0
|
|
for i := 0; i < 15; i++ {
|
|
d := digits[14-i]
|
|
if i%2 == 0 {
|
|
d *= 2
|
|
if d > 9 {
|
|
d -= 9
|
|
}
|
|
}
|
|
sum += d
|
|
}
|
|
check := (10 - (sum % 10)) % 10
|
|
return partial + fmt.Sprintf("%d", check)
|
|
}
|