Skip to content

Commit 271636f

Browse files
committed
feat: withdrawal
1 parent 56e5f5c commit 271636f

File tree

10 files changed

+149
-35
lines changed

10 files changed

+149
-35
lines changed
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package com.example.paul.constants;
2+
3+
import java.util.regex.Pattern;
4+
5+
public class constants {
6+
public static final String NO_ACCOUNT_FOUND =
7+
"Unable to find an account matching this sort code and account number";
8+
public static final String INVALID_SEARCH_CRITERIA =
9+
"The provided sort code or account number did not match the expected format";
10+
11+
public static final String INSUFFICIENT_ACCOUNT_BALANCE =
12+
"Your account does not have sufficient balance";
13+
14+
public static final String SORT_CODE_PATTERN_STRING = "[0-9]{2}-[0-9]{2}-[0-9]{2}";
15+
16+
public static final String ACCOUNT_NUMBER_PATTERN_STRING = "[0-9]{8}";
17+
public static final Pattern SORT_CODE_PATTERN = Pattern.compile("^[0-9]{2}-[0-9]{2}-[0-9]{2}$");
18+
public static final Pattern ACCOUNT_NUMBER_PATTERN = Pattern.compile("^[0-9]{8}$");
19+
}

src/main/java/com/example/paul/controllers/AccountRestController.java

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.example.paul.controllers;
22

3+
import com.example.paul.constants.constants;
34
import com.example.paul.models.Account;
45
import com.example.paul.services.AccountService;
56
import com.example.paul.utils.AccountInput;
@@ -25,12 +26,6 @@ public class AccountRestController {
2526

2627
private static final Logger LOGGER = LoggerFactory.getLogger(AccountRestController.class);
2728

28-
private static final String INVALID_SEARCH_CRITERIA =
29-
"The provided sort code or account number did not match the expected format";
30-
31-
private static final String NO_ACCOUNT_FOUND =
32-
"Unable to find an account matching this sort code and account number";
33-
3429
private static final String CREATE_ACCOUNT_FAILED =
3530
"Error happened during creating new account";
3631

@@ -57,12 +52,12 @@ public ResponseEntity<?> checkAccountBalance(
5752

5853
// Return the account details, or warn that no account was found for given input
5954
if (account == null) {
60-
return new ResponseEntity<>(NO_ACCOUNT_FOUND, HttpStatus.NO_CONTENT);
55+
return new ResponseEntity<>(constants.NO_ACCOUNT_FOUND, HttpStatus.NO_CONTENT);
6156
} else {
6257
return new ResponseEntity<>(account, HttpStatus.OK);
6358
}
6459
} else {
65-
return new ResponseEntity<>(INVALID_SEARCH_CRITERIA, HttpStatus.BAD_REQUEST);
60+
return new ResponseEntity<>(constants.INVALID_SEARCH_CRITERIA, HttpStatus.BAD_REQUEST);
6661
}
6762
}
6863

@@ -72,7 +67,7 @@ public ResponseEntity<?> checkAccountBalance(
7267
produces = MediaType.APPLICATION_JSON_VALUE)
7368
public ResponseEntity<?> createAccount(
7469
@Valid @RequestBody CreateAccountInput createAccountInput) {
75-
LOGGER.debug("Triggered AccountRestController.accountInput");
70+
LOGGER.debug("Triggered AccountRestController.createAccountInput");
7671

7772
// Validate input
7873
if (InputValidator.isCreateAccountCriteriaValid(createAccountInput)) {
@@ -87,7 +82,7 @@ public ResponseEntity<?> createAccount(
8782
return new ResponseEntity<>(account, HttpStatus.OK);
8883
}
8984
} else {
90-
return new ResponseEntity<>(INVALID_SEARCH_CRITERIA, HttpStatus.BAD_REQUEST);
85+
return new ResponseEntity<>(constants.INVALID_SEARCH_CRITERIA, HttpStatus.BAD_REQUEST);
9186
}
9287
}
9388

src/main/java/com/example/paul/controllers/TransactionRestController.java

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
package com.example.paul.controllers;
22

3+
import com.example.paul.models.Account;
4+
import com.example.paul.services.AccountService;
35
import com.example.paul.services.TransactionService;
6+
import com.example.paul.utils.AccountInput;
47
import com.example.paul.utils.InputValidator;
58
import com.example.paul.utils.TransactionInput;
9+
import com.example.paul.utils.WithdrawInput;
610
import org.slf4j.Logger;
711
import org.slf4j.LoggerFactory;
812
import org.springframework.beans.factory.annotation.Autowired;
@@ -16,6 +20,9 @@
1620
import javax.validation.Valid;
1721
import java.util.HashMap;
1822
import java.util.Map;
23+
import java.util.Optional;
24+
25+
import static com.example.paul.constants.constants.*;
1926

2027
@RestController
2128
@RequestMapping("api/v1")
@@ -26,10 +33,12 @@ public class TransactionRestController {
2633
private static final String INVALID_TRANSACTION =
2734
"Account information is invalid or transaction has been denied for your protection. Please try again.";
2835

36+
private final AccountService accountService;
2937
private final TransactionService transactionService;
3038

3139
@Autowired
32-
public TransactionRestController(TransactionService transactionService) {
40+
public TransactionRestController(AccountService accountService, TransactionService transactionService) {
41+
this.accountService = accountService;
3342
this.transactionService = transactionService;
3443
}
3544

@@ -47,6 +56,34 @@ public ResponseEntity<?> makeTransfer(
4756
}
4857
}
4958

59+
@PostMapping(value = "/withdraw",
60+
consumes = MediaType.APPLICATION_JSON_VALUE,
61+
produces = MediaType.APPLICATION_JSON_VALUE)
62+
public ResponseEntity<?> withdraw(
63+
@Valid @RequestBody WithdrawInput withdrawInput) {
64+
LOGGER.debug("Triggered AccountRestController.withdrawInput");
65+
66+
// Validate input
67+
if (InputValidator.isSearchCriteriaValid(withdrawInput)) {
68+
// Attempt to retrieve the account information
69+
Account account = accountService.getAccount(
70+
withdrawInput.getSortCode(), withdrawInput.getAccountNumber());
71+
72+
// Return the account details, or warn that no account was found for given input
73+
if (account == null) {
74+
return new ResponseEntity<>(NO_ACCOUNT_FOUND, HttpStatus.NO_CONTENT);
75+
} else {
76+
if (transactionService.isAmountAvailable(withdrawInput.getAmount(), account.getCurrentBalance())) {
77+
transactionService.updateAccountBalance(account, withdrawInput.getAmount());
78+
return new ResponseEntity<>(account, HttpStatus.OK);
79+
}
80+
return new ResponseEntity<>(INSUFFICIENT_ACCOUNT_BALANCE, HttpStatus.NOT_ACCEPTABLE);
81+
}
82+
} else {
83+
return new ResponseEntity<>(INVALID_SEARCH_CRITERIA, HttpStatus.BAD_REQUEST);
84+
}
85+
}
86+
5087
@ResponseStatus(HttpStatus.BAD_REQUEST)
5188
@ExceptionHandler(MethodArgumentNotValidException.class)
5289
public Map<String, String> handleValidationExceptions(

src/main/java/com/example/paul/services/AccountService.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import com.example.paul.models.Account;
44
import com.example.paul.repositories.AccountRepository;
55
import com.example.paul.repositories.TransactionRepository;
6-
import com.example.paul.utils.CreateAccountInput;
6+
import com.example.paul.utils.CodeGenerator;
77
import org.springframework.beans.factory.annotation.Autowired;
88
import org.springframework.stereotype.Service;
99

@@ -30,8 +30,8 @@ public Account getAccount(String sortCode, String accountNumber) {
3030
}
3131

3232
public Account createAccount(String bankName, String ownerName) {
33-
CreateAccountInput createAccountInput = new CreateAccountInput();
34-
Account newAccount = new Account(bankName, ownerName, createAccountInput.generateSortCode(), createAccountInput.generateAccountNumber(), 0.00);
33+
CodeGenerator codeGenerator = new CodeGenerator();
34+
Account newAccount = new Account(bankName, ownerName, codeGenerator.generateSortCode(), codeGenerator.generateAccountNumber(), 0.00);
3535
return accountRepository.save(newAccount);
3636
}
3737
}

src/main/java/com/example/paul/services/TransactionService.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,13 +55,13 @@ public boolean makeTransfer(TransactionInput transactionInput) {
5555
return false;
5656
}
5757

58-
private void updateAccountBalance(Account account, double amount) {
58+
public void updateAccountBalance(Account account, double amount) {
5959
account.setCurrentBalance((account.getCurrentBalance() - amount));
6060
accountRepository.save(account);
6161
}
6262

6363
// TODO support overdrafts or credit account
64-
private boolean isAmountAvailable(double amount, double accountBalance) {
64+
public boolean isAmountAvailable(double amount, double accountBalance) {
6565
return (accountBalance - amount) > 0;
6666
}
6767
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package com.example.paul.utils;
2+
3+
import com.mifmif.common.regex.Generex;
4+
5+
import static com.example.paul.constants.constants.ACCOUNT_NUMBER_PATTERN_STRING;
6+
import static com.example.paul.constants.constants.SORT_CODE_PATTERN_STRING;
7+
8+
public class CodeGenerator {
9+
Generex sortCodeGenerex = new Generex(SORT_CODE_PATTERN_STRING);
10+
Generex accountNumberGenerex = new Generex(ACCOUNT_NUMBER_PATTERN_STRING);
11+
12+
public CodeGenerator(){}
13+
14+
public String generateSortCode() {
15+
return sortCodeGenerex.random();
16+
}
17+
18+
public String generateAccountNumber() {
19+
return accountNumberGenerex.random();
20+
}
21+
}

src/main/java/com/example/paul/utils/CreateAccountInput.java

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
package com.example.paul.utils;
22

3+
import com.example.paul.constants.constants;
34
import com.mifmif.common.regex.Generex;
45

56
import javax.validation.constraints.NotBlank;
67
import java.util.Objects;
78

9+
import static com.example.paul.constants.constants.ACCOUNT_NUMBER_PATTERN_STRING;
10+
import static com.example.paul.constants.constants.SORT_CODE_PATTERN_STRING;
11+
812
public class CreateAccountInput {
913

1014
@NotBlank(message = "Bank name is mandatory")
@@ -32,16 +36,6 @@ public void setOwnerName(String ownerName) {
3236
this.ownerName = ownerName;
3337
}
3438

35-
public String generateSortCode() {
36-
Generex generex = new Generex(InputValidator.sortCodePattern.toString());
37-
return generex.getMatchedString(2);
38-
}
39-
40-
public String generateAccountNumber() {
41-
Generex generex = new Generex(InputValidator.accountNumberPattern.toString());
42-
return generex.getMatchedString(2);
43-
}
44-
4539
@Override
4640
public String toString() {
4741
return "CreateAccountInput{" +

src/main/java/com/example/paul/utils/InputValidator.java

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,12 @@
11
package com.example.paul.utils;
22

3-
import java.util.regex.Pattern;
3+
import com.example.paul.constants.constants;
44

55
public class InputValidator {
66

7-
static final Pattern sortCodePattern = Pattern.compile("^[0-9]{2}-[0-9]{2}-[0-9]{2}$");
8-
9-
static final Pattern accountNumberPattern = Pattern.compile("^[0-9]{8}$");
10-
117
public static boolean isSearchCriteriaValid(AccountInput accountInput) {
12-
return sortCodePattern.matcher(accountInput.getSortCode()).find() &&
13-
accountNumberPattern.matcher(accountInput.getAccountNumber()).find();
8+
return constants.SORT_CODE_PATTERN.matcher(accountInput.getSortCode()).find() &&
9+
constants.ACCOUNT_NUMBER_PATTERN.matcher(accountInput.getAccountNumber()).find();
1410
}
1511

1612
public static boolean isCreateAccountCriteriaValid(CreateAccountInput createAccountInput) {
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package com.example.paul.utils;
2+
3+
import javax.validation.constraints.Min;
4+
import javax.validation.constraints.NotBlank;
5+
import javax.validation.constraints.Positive;
6+
import java.util.Objects;
7+
8+
public class WithdrawInput extends AccountInput{
9+
String sortCode;
10+
String accountNumber;
11+
@Positive(message = "Transfer amount must be positive")
12+
// Prevent fraudulent transfers attempting to abuse currency conversion errors
13+
@Min(value = 0, message = "Amount must be larger than 0")
14+
private double amount;
15+
16+
public WithdrawInput() {
17+
this.sortCode = super.getSortCode();
18+
this.accountNumber = super.getAccountNumber();
19+
}
20+
21+
public double getAmount() {
22+
return amount;
23+
}
24+
25+
public void setAmount(double amount) {
26+
this.amount = amount;
27+
}
28+
29+
@Override
30+
public String toString() {
31+
return "AccountInput{" +
32+
"sortCode='" + sortCode + '\'' +
33+
", accountNumber='" + accountNumber + '\'' +
34+
", amount='" + amount + '\'' +
35+
'}';
36+
}
37+
38+
@Override
39+
public boolean equals(Object o) {
40+
if (this == o) return true;
41+
if (o == null || getClass() != o.getClass()) return false;
42+
WithdrawInput that = (WithdrawInput) o;
43+
return Objects.equals(sortCode, that.sortCode) &&
44+
Objects.equals(accountNumber, that.accountNumber) &&
45+
Objects.equals(amount, that.amount);
46+
}
47+
48+
@Override
49+
public int hashCode() {
50+
return Objects.hash(sortCode, accountNumber, amount);
51+
}
52+
}

src/main/resources/application.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,13 @@ spring:
55
spring:
66
profiles: local
77
datasource:
8-
url: jdbc:h2:mem:testdb
8+
url: jdbc:h2:mem:online_bank
99
h2:
1010
console:
1111
enabled: true
1212
path: /h2-console
1313
jpa:
1414
hibernate:
15-
ddl-auto: create-drop
15+
ddl-auto: create
1616
server:
1717
port: 8080

0 commit comments

Comments
 (0)