1package main 2 3import ( 4 "fmt" 5 "time" 6 7 "github.com/golang/protobuf/ptypes" 8 "github.com/golang/protobuf/ptypes/empty" 9 "golang.org/x/net/context" 10 "google.golang.org/grpc/codes" 11 "google.golang.org/grpc/status" 12) 13 14// bankServer implements the Bank gRPC service. 15type bankServer struct { 16 allAccounts *accounts 17} 18 19func (s *bankServer) OpenAccount(ctx context.Context, req *OpenAccountRequest) (*Account, error) { 20 cust := getCustomer(ctx) 21 if cust == "" { 22 return nil, status.Error(codes.Unauthenticated, codes.Unauthenticated.String()) 23 } 24 switch req.Type { 25 case Account_CHECKING, Account_SAVING, Account_MONEY_MARKET: 26 if req.InitialDepositCents < 0 { 27 return nil, status.Errorf(codes.InvalidArgument, "initial deposit amount cannot be negative: %s", dollars(req.InitialDepositCents)) 28 } 29 case Account_LINE_OF_CREDIT, Account_LOAN, Account_EQUITIES: 30 if req.InitialDepositCents != 0 { 31 return nil, status.Errorf(codes.InvalidArgument, "initial deposit amount must be zero for account type %v: %s", req.Type, dollars(req.InitialDepositCents)) 32 } 33 default: 34 return nil, status.Errorf(codes.InvalidArgument, "invalid account type: %v", req.Type) 35 } 36 37 return s.allAccounts.openAccount(cust, req.Type, req.InitialDepositCents), nil 38} 39 40func (s *bankServer) CloseAccount(ctx context.Context, req *CloseAccountRequest) (*empty.Empty, error) { 41 cust := getCustomer(ctx) 42 if cust == "" { 43 return nil, status.Error(codes.Unauthenticated, codes.Unauthenticated.String()) 44 } 45 46 if err := s.allAccounts.closeAccount(cust, req.AccountNumber); err != nil { 47 return nil, err 48 } 49 return &empty.Empty{}, nil 50} 51 52func (s *bankServer) GetAccounts(ctx context.Context, _ *empty.Empty) (*GetAccountsResponse, error) { 53 cust := getCustomer(ctx) 54 if cust == "" { 55 return nil, status.Error(codes.Unauthenticated, codes.Unauthenticated.String()) 56 } 57 58 accounts := s.allAccounts.getAllAccounts(cust) 59 return &GetAccountsResponse{Accounts: accounts}, nil 60} 61 62func (s *bankServer) GetTransactions(req *GetTransactionsRequest, stream Bank_GetTransactionsServer) error { 63 cust := getCustomer(stream.Context()) 64 if cust == "" { 65 return status.Error(codes.Unauthenticated, codes.Unauthenticated.String()) 66 } 67 68 acct, err := s.allAccounts.getAccount(cust, req.AccountNumber) 69 if err != nil { 70 return err 71 } 72 73 var start, end time.Time 74 if req.Start != nil { 75 start, err = ptypes.Timestamp(req.Start) 76 if err != nil { 77 return err 78 } 79 } 80 if req.End != nil { 81 end, err = ptypes.Timestamp(req.End) 82 if err != nil { 83 return err 84 } 85 } else { 86 end = time.Date(9999, 12, 31, 23, 59, 59, 999999999, time.Local) 87 } 88 89 txns := acct.getTransactions() 90 for _, txn := range txns { 91 t, err := ptypes.Timestamp(txn.Date) 92 if err != nil { 93 return err 94 } 95 if (t.After(start) || t.Equal(start)) && 96 (t.Before(end) || t.Equal(end)) { 97 98 if err := stream.Send(txn); err != nil { 99 return err 100 } 101 } 102 } 103 return nil 104} 105 106func (s *bankServer) Deposit(ctx context.Context, req *DepositRequest) (*BalanceResponse, error) { 107 cust := getCustomer(ctx) 108 if cust == "" { 109 return nil, status.Error(codes.Unauthenticated, codes.Unauthenticated.String()) 110 } 111 112 switch req.Source { 113 case DepositRequest_ACH, DepositRequest_CASH, DepositRequest_CHECK, DepositRequest_WIRE: 114 // ok 115 default: 116 return nil, status.Errorf(codes.InvalidArgument, "unknown deposit source: %v", req.Source) 117 } 118 119 if req.AmountCents <= 0 { 120 return nil, status.Errorf(codes.InvalidArgument, "deposit amount cannot be non-positive: %s", dollars(req.AmountCents)) 121 } 122 123 desc := fmt.Sprintf("%v deposit", req.Source) 124 if req.Desc != "" { 125 desc = fmt.Sprintf("%s: %s", desc, req.Desc) 126 } 127 acct, err := s.allAccounts.getAccount(cust, req.AccountNumber) 128 if err != nil { 129 return nil, err 130 } 131 newBalance, err := acct.newTransaction(req.AmountCents, desc) 132 if err != nil { 133 return nil, err 134 } 135 return &BalanceResponse{ 136 AccountNumber: req.AccountNumber, 137 BalanceCents: newBalance, 138 }, nil 139} 140 141func (s *bankServer) Withdraw(ctx context.Context, req *WithdrawRequest) (*BalanceResponse, error) { 142 cust := getCustomer(ctx) 143 if cust == "" { 144 return nil, status.Error(codes.Unauthenticated, codes.Unauthenticated.String()) 145 } 146 147 if req.AmountCents >= 0 { 148 return nil, status.Errorf(codes.InvalidArgument, "withdrawal amount cannot be non-negative: %s", dollars(req.AmountCents)) 149 } 150 151 acct, err := s.allAccounts.getAccount(cust, req.AccountNumber) 152 if err != nil { 153 return nil, err 154 } 155 newBalance, err := acct.newTransaction(req.AmountCents, req.Desc) 156 if err != nil { 157 return nil, err 158 } 159 return &BalanceResponse{ 160 AccountNumber: req.AccountNumber, 161 BalanceCents: newBalance, 162 }, nil 163} 164 165func (s *bankServer) Transfer(ctx context.Context, req *TransferRequest) (*TransferResponse, error) { 166 cust := getCustomer(ctx) 167 if cust == "" { 168 return nil, status.Error(codes.Unauthenticated, codes.Unauthenticated.String()) 169 } 170 171 if req.AmountCents <= 0 { 172 return nil, status.Errorf(codes.InvalidArgument, "transfer amount cannot be non-positive: %s", dollars(req.AmountCents)) 173 } 174 175 var srcAcct *account 176 var srcDesc string 177 switch src := req.Source.(type) { 178 case *TransferRequest_ExternalSource: 179 srcDesc = fmt.Sprintf("ACH %09d:%06d", src.ExternalSource.AchRoutingNumber, src.ExternalSource.AchAccountNumber) 180 if src.ExternalSource.AchAccountNumber == 0 || src.ExternalSource.AchRoutingNumber == 0 { 181 return nil, status.Errorf(codes.InvalidArgument, "external source routing and account numbers cannot be zero: %s", srcDesc) 182 } 183 case *TransferRequest_SourceAccountNumber: 184 srcDesc = fmt.Sprintf("account %06d", src.SourceAccountNumber) 185 var err error 186 if srcAcct, err = s.allAccounts.getAccount(cust, src.SourceAccountNumber); err != nil { 187 return nil, err 188 } 189 } 190 191 var destAcct *account 192 var destDesc string 193 switch dest := req.Dest.(type) { 194 case *TransferRequest_ExternalDest: 195 destDesc = fmt.Sprintf("ACH %09d:%06d", dest.ExternalDest.AchRoutingNumber, dest.ExternalDest.AchAccountNumber) 196 if dest.ExternalDest.AchAccountNumber == 0 || dest.ExternalDest.AchRoutingNumber == 0 { 197 return nil, status.Errorf(codes.InvalidArgument, "external source routing and account numbers cannot be zero: %s", destDesc) 198 } 199 case *TransferRequest_DestAccountNumber: 200 destDesc = fmt.Sprintf("account %06d", dest.DestAccountNumber) 201 var err error 202 if destAcct, err = s.allAccounts.getAccount(cust, dest.DestAccountNumber); err != nil { 203 return nil, err 204 } 205 } 206 207 var srcBalance int32 208 if srcAcct != nil { 209 desc := fmt.Sprintf("transfer to %s", destDesc) 210 if req.Desc != "" { 211 desc = fmt.Sprintf("%s: %s", desc, req.Desc) 212 } 213 var err error 214 if srcBalance, err = srcAcct.newTransaction(-req.AmountCents, desc); err != nil { 215 return nil, err 216 } 217 } 218 219 var destBalance int32 220 if destAcct != nil { 221 desc := fmt.Sprintf("transfer from %s", srcDesc) 222 if req.Desc != "" { 223 desc = fmt.Sprintf("%s: %s", desc, req.Desc) 224 } 225 var err error 226 if destBalance, err = destAcct.newTransaction(req.AmountCents, desc); err != nil { 227 return nil, err 228 } 229 } 230 231 return &TransferResponse{ 232 SrcAccountNumber: req.GetSourceAccountNumber(), 233 SrcBalanceCents: srcBalance, 234 DestAccountNumber: req.GetDestAccountNumber(), 235 DestBalanceCents: destBalance, 236 }, nil 237} 238