diff --git a/.gradle/7.4.1/checksums/checksums.lock b/.gradle/7.4.1/checksums/checksums.lock deleted file mode 100644 index 7a4d7e3..0000000 Binary files a/.gradle/7.4.1/checksums/checksums.lock and /dev/null differ diff --git a/.gradle/7.4.1/dependencies-accessors/dependencies-accessors.lock b/.gradle/7.4.1/dependencies-accessors/dependencies-accessors.lock deleted file mode 100644 index d0c96a3..0000000 Binary files a/.gradle/7.4.1/dependencies-accessors/dependencies-accessors.lock and /dev/null differ diff --git a/.gradle/7.4.1/dependencies-accessors/gc.properties b/.gradle/7.4.1/dependencies-accessors/gc.properties deleted file mode 100644 index e69de29..0000000 diff --git a/.gradle/7.4.1/fileChanges/last-build.bin b/.gradle/7.4.1/fileChanges/last-build.bin deleted file mode 100644 index f76dd23..0000000 Binary files a/.gradle/7.4.1/fileChanges/last-build.bin and /dev/null differ diff --git a/.gradle/7.4.1/fileHashes/fileHashes.lock b/.gradle/7.4.1/fileHashes/fileHashes.lock deleted file mode 100644 index e2e7780..0000000 Binary files a/.gradle/7.4.1/fileHashes/fileHashes.lock and /dev/null differ diff --git a/.gradle/7.4.1/gc.properties b/.gradle/7.4.1/gc.properties deleted file mode 100644 index e69de29..0000000 diff --git a/.gradle/8.5/executionHistory/executionHistory.lock b/.gradle/8.5/executionHistory/executionHistory.lock index 42d7636..89ee103 100644 Binary files a/.gradle/8.5/executionHistory/executionHistory.lock and b/.gradle/8.5/executionHistory/executionHistory.lock differ diff --git a/.gradle/8.5/fileHashes/fileHashes.lock b/.gradle/8.5/fileHashes/fileHashes.lock index 163111f..cafaa92 100644 Binary files a/.gradle/8.5/fileHashes/fileHashes.lock and b/.gradle/8.5/fileHashes/fileHashes.lock differ diff --git a/.gradle/buildOutputCleanup/buildOutputCleanup.lock b/.gradle/buildOutputCleanup/buildOutputCleanup.lock index ec2cf8b..965ad9c 100644 Binary files a/.gradle/buildOutputCleanup/buildOutputCleanup.lock and b/.gradle/buildOutputCleanup/buildOutputCleanup.lock differ diff --git a/src/main/java/com/track/fin/controller/AccountController.java b/src/main/java/com/track/fin/controller/AccountController.java index ff282b3..1a85d34 100644 --- a/src/main/java/com/track/fin/controller/AccountController.java +++ b/src/main/java/com/track/fin/controller/AccountController.java @@ -2,6 +2,7 @@ import com.track.fin.domain.Account; import com.track.fin.record.*; +import com.track.fin.service.AccountFacadeService; import com.track.fin.service.AccountService; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; @@ -18,6 +19,7 @@ public class AccountController { private final AccountService accountService; + private final AccountFacadeService accountFacadeService; @PostMapping public Account createAccount( @@ -42,31 +44,27 @@ public Account getAccount( return accountService.getAccount(id); } - @DeleteMapping - public AccountRecord deleteAccount( - @RequestBody @Valid DeleteAccountRecord deleteAccountRecord + @PostMapping("/restore") + public AccountRecord restoreAccount( + @RequestParam Long userId, + @RequestParam String accountNumber ) { - return accountService.deleteAccount(deleteAccountRecord); + return accountFacadeService.restoreAccount(userId, accountNumber); } @GetMapping("/{accountNumber}/transactions") public TransferResponseRecord getTransferTransactions( @PathVariable String accountNumber, - @RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime startDate, - @RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime endDate, - @RequestParam(defaultValue = "true") boolean sortDesc, + @ModelAttribute TransferSearchRequestRecord searchRequest, @RequestBody TransferRequestRecord transferRequestRecord ) { return TransferResponseRecord.from(null); } + @GetMapping("/active") - public List getActiveAccounts( - @RequestParam("userId") Long userId - ) { - return accountService.getActiveAccounts(userId).stream() - .map(AccountRecord::from) - .toList(); + public List getActiveAccounts(@RequestParam("userId") Long userId) { + return accountService.getActiveAccounts(userId); } @GetMapping("/{accountId}/collateral") @@ -99,4 +97,15 @@ public boolean validateAutoTransferNotRegistered( return accountService.validateAutoTransferNotRegistered(accountNumber); } + @DeleteMapping + public AccountRecord deleteAccount(@Valid @RequestBody DeleteAccountRecord request) { + return accountFacadeService.deleteAccount(request); + } + + @DeleteMapping("/expired/{accountNumber}") + public void deleteExpiredAccount(@PathVariable String accountNumber) { + Account account = accountService.getAccountByNumber(accountNumber); + accountFacadeService.deleteIfExpired(account); + } + } diff --git a/src/main/java/com/track/fin/controller/TransactionController.java b/src/main/java/com/track/fin/controller/TransactionController.java index 32fb2bf..889fac8 100644 --- a/src/main/java/com/track/fin/controller/TransactionController.java +++ b/src/main/java/com/track/fin/controller/TransactionController.java @@ -5,38 +5,47 @@ import com.track.fin.exception.AccountException; import com.track.fin.record.*; import com.track.fin.service.TransactionService; +import com.track.fin.type.TransactionType; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.format.annotation.DateTimeFormat; import org.springframework.web.bind.annotation.*; +import java.time.LocalDateTime; +import java.util.List; + @Slf4j @RequiredArgsConstructor @RestController +@RequestMapping("/transactions") public class TransactionController { private final TransactionService transactionService; - @PostMapping("transaction/use") - public UseBalance.Response useBalance( - @Valid @RequestBody UseBalance.Request requset + @PostMapping("/use") + public TransferResponseRecord useBalance( + @Valid @RequestBody UseBalance.Request request ) { try { - return UseBalance.Response.from(transactionService.useBalance(requset.getUserId(), - requset.getAccountNumber(), requset.getAmount()) + return transactionService.useBalance( + request.getUserId(), + request.getAccountNumber(), + request.getAmount(), + request.getTransactionMethodType() ); } catch (AccountException e) { - log.error("Failed to use balance. "); - + log.error("잔액 사용 실패: {}", e.getMessage()); transactionService.saveFailedUseTransaction( - requset.getAccountNumber(), - requset.getAmount() + request.getAccountNumber(), + request.getAmount(), + request.getTransactionMethodType() ); throw e; } } - @PostMapping("/transaction/deposit") + @PostMapping("/deposit") public DepositRecord deposit( @Valid @RequestBody DepositTransactionRecord request ) { @@ -45,21 +54,23 @@ public DepositRecord deposit( transactionService.deposit( request.userId(), request.accountNumber(), - request.amount() + request.amount(), + request.transactionMethodType() ) ); } catch (AccountException e) { - log.error("Failed to deposit."); + log.error("입금 실패: {}", e.getMessage()); transactionService.saveFailedDepositTransaction( request.accountNumber(), - request.amount() + request.amount(), + request.transactionMethodType() ); throw e; } } - @PostMapping("/transaction/transfer") + @PostMapping("/transfer") public TransferResponseRecord transfer( @Valid @RequestBody TransferRequestRecord request ) { @@ -68,20 +79,35 @@ public TransferResponseRecord transfer( request.userId(), request.fromAccountNumber(), request.toAccountNumber(), - request.amount() + request.amount(), + request.transactionMethodType() ); } catch (AccountException e) { - log.error("Failed to transfer."); + log.error("이체 실패: {}", e.getMessage()); transactionService.saveFailedTransferTransaction( request.fromAccountNumber(), request.toAccountNumber(), - request.amount() + request.amount(), + request.transactionMethodType() ); throw e; } } - @PostMapping("/transaction/withdraw") + @GetMapping("/transfers") + public List getTransfers( + @RequestParam String accountNumber, + @RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime startDate, + @RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime endDate, + @RequestParam(required = false) TransactionType transactionType, + @RequestParam(defaultValue = "false") boolean sortDesc + ) { + return transactionService.getTransferTransactionsByAccount( + accountNumber, startDate, endDate, transactionType, sortDesc + ); + } + + @PostMapping("/withdraw") public WithdrawalRecord withdraw( @Valid @RequestBody WithdrawalRequestRecord request ) { @@ -90,17 +116,22 @@ public WithdrawalRecord withdraw( transactionService.withdraw( request.userId(), request.accountNumber(), - request.amount() + request.amount(), + request.transactionMethodType() ) ); } catch (AccountException e) { - log.error("Failed to withdraw."); - transactionService.saveFailedWithdrawTransaction(request.accountNumber(), request.amount()); + log.error("출금 실패: {}", e.getMessage()); + transactionService.saveFailedWithdrawTransaction( + request.accountNumber(), + request.amount(), + request.transactionMethodType() + ); throw e; } } - @PostMapping("/transaction/cancel") + @PostMapping("/cancel") public TransferResponseRecord cancelBalance( @Valid @RequestBody CancelBalance.Request request ) { @@ -108,19 +139,21 @@ public TransferResponseRecord cancelBalance( return transactionService.cancelBalance( String.valueOf(request.getTransactionId()), request.getAccountNumber(), - request.getAmount() + request.getAmount(), + request.getTransactionMethodType() ); } catch (AccountException e) { log.error("Cancel failed", e); transactionService.saveFailedCancelTransaction( request.getAccountNumber(), - request.getAmount() + request.getAmount(), + request.getTransactionMethodType() ); throw e; } } - @GetMapping("/transaction/{transactionId}") + @GetMapping("/{transactionId}") public TransferResponseRecord queryTransaction( @PathVariable String transactionId) { return transactionService.queryTransactionId(transactionId); diff --git a/src/main/java/com/track/fin/domain/Loan.java b/src/main/java/com/track/fin/domain/Loan.java index ca83eb0..3a13fbd 100644 --- a/src/main/java/com/track/fin/domain/Loan.java +++ b/src/main/java/com/track/fin/domain/Loan.java @@ -71,7 +71,7 @@ public void update(User user, Account account, CreateLoanRecord dto) { this.user = user; this.account = account; this.balance = dto.balance(); - // this.loanDate = LocalDateTime.now(); + this.loanDate = LocalDateTime.now(); this.delinquencyDate = dto.delinquencyDate(); this.loanStatus = dto.loanStatus(); this.loanType = dto.loanType(); diff --git a/src/main/java/com/track/fin/dto/CancelBalance.java b/src/main/java/com/track/fin/dto/CancelBalance.java index 6c9743b..3e6fbd1 100644 --- a/src/main/java/com/track/fin/dto/CancelBalance.java +++ b/src/main/java/com/track/fin/dto/CancelBalance.java @@ -1,5 +1,6 @@ package com.track.fin.dto; +import com.track.fin.type.TransactionMethodType; import com.track.fin.type.TransactionResultType; import jakarta.validation.constraints.*; import lombok.AllArgsConstructor; @@ -24,6 +25,10 @@ public static class Request { @Min(10) @Max(1000_000_000) private Long amount; + + @NotNull + private TransactionMethodType transactionMethodType; + } @Getter diff --git a/src/main/java/com/track/fin/dto/UseBalance.java b/src/main/java/com/track/fin/dto/UseBalance.java index 571bcbb..451c053 100644 --- a/src/main/java/com/track/fin/dto/UseBalance.java +++ b/src/main/java/com/track/fin/dto/UseBalance.java @@ -1,5 +1,6 @@ package com.track.fin.dto; +import com.track.fin.type.TransactionMethodType; import com.track.fin.type.TransactionResultType; import jakarta.validation.constraints.*; import lombok.AllArgsConstructor; @@ -26,6 +27,9 @@ public static class Request { @Min(10) @Max(1000_000_000) private Long amount; + + @NotNull + private TransactionMethodType transactionMethodType; } @Getter diff --git a/src/main/java/com/track/fin/record/DepositRecord.java b/src/main/java/com/track/fin/record/DepositRecord.java index 0a7c6fa..c3c19d6 100644 --- a/src/main/java/com/track/fin/record/DepositRecord.java +++ b/src/main/java/com/track/fin/record/DepositRecord.java @@ -2,13 +2,13 @@ import com.track.fin.domain.Transaction; + public record DepositRecord( String transactionId, String accountNumber, Long amount, Long balanceSnapshot - ) { public static DepositRecord from(Transaction transaction) { diff --git a/src/main/java/com/track/fin/record/DepositTransactionRecord.java b/src/main/java/com/track/fin/record/DepositTransactionRecord.java index c5ffae4..d9fe769 100644 --- a/src/main/java/com/track/fin/record/DepositTransactionRecord.java +++ b/src/main/java/com/track/fin/record/DepositTransactionRecord.java @@ -1,10 +1,25 @@ package com.track.fin.record; +import com.track.fin.type.TransactionMethodType; +import jakarta.validation.constraints.*; + public record DepositTransactionRecord( + @NotNull + @Min(1) Long userId, + + @NotBlank + @Size(min = 10, max = 10) String accountNumber, - Long amount + + @NotNull + @Min(10) + @Max(1_000_000_000) + Long amount, + + @NotNull + TransactionMethodType transactionMethodType ) { } diff --git a/src/main/java/com/track/fin/record/TransferRequestRecord.java b/src/main/java/com/track/fin/record/TransferRequestRecord.java index 00fdfb3..5d29438 100644 --- a/src/main/java/com/track/fin/record/TransferRequestRecord.java +++ b/src/main/java/com/track/fin/record/TransferRequestRecord.java @@ -1,12 +1,29 @@ package com.track.fin.record; +import com.track.fin.type.TransactionMethodType; +import jakarta.validation.constraints.*; + public record TransferRequestRecord( + @NotNull + @Min(1) Long userId, + + @NotBlank + @Size(min = 10, max = 10) String fromAccountNumber, + + @NotBlank + @Size(min = 10, max = 10) String toAccountNumber, - Long amount + @NotNull + @Min(10) + @Max(1_000_000_000) + Long amount, + + @NotNull + TransactionMethodType transactionMethodType ) { } diff --git a/src/main/java/com/track/fin/record/TransferResponseRecord.java b/src/main/java/com/track/fin/record/TransferResponseRecord.java index 8cc05fc..af0eaa1 100644 --- a/src/main/java/com/track/fin/record/TransferResponseRecord.java +++ b/src/main/java/com/track/fin/record/TransferResponseRecord.java @@ -1,6 +1,7 @@ package com.track.fin.record; import com.track.fin.domain.Transaction; +import com.track.fin.type.TransactionType; import java.time.LocalDateTime; @@ -12,7 +13,8 @@ public record TransferResponseRecord( Long amount, Long fromBalanceSnapshot, Long toBalanceSnapshot, - LocalDateTime transactionDate + LocalDateTime transactionDate, + TransactionType transactionType ) { @@ -24,7 +26,8 @@ public static TransferResponseRecord from(Transaction fromTransaction, Transacti fromTransaction.getAmount(), fromTransaction.getBalanceSnapshot(), toTransaction.getBalanceSnapshot(), - fromTransaction.getTransactionDate() + fromTransaction.getTransactionDate(), + fromTransaction.getTransactionType() ); } @@ -36,7 +39,8 @@ public static TransferResponseRecord from(Transaction transaction) { transaction.getAmount(), transaction.getBalanceSnapshot(), null, - transaction.getTransactionDate() + transaction.getTransactionDate(), + transaction.getTransactionType() ); } diff --git a/src/main/java/com/track/fin/record/TransferSearchRequestRecord.java b/src/main/java/com/track/fin/record/TransferSearchRequestRecord.java new file mode 100644 index 0000000..302adec --- /dev/null +++ b/src/main/java/com/track/fin/record/TransferSearchRequestRecord.java @@ -0,0 +1,17 @@ +package com.track.fin.record; + +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDateTime; + +public record TransferSearchRequestRecord( + @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) + LocalDateTime startDate, + + @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) + LocalDateTime endDate, + + Boolean sortDesc, + + String transactionType +) {} diff --git a/src/main/java/com/track/fin/record/WithdrawalRequestRecord.java b/src/main/java/com/track/fin/record/WithdrawalRequestRecord.java index 8bc2c43..d00d553 100644 --- a/src/main/java/com/track/fin/record/WithdrawalRequestRecord.java +++ b/src/main/java/com/track/fin/record/WithdrawalRequestRecord.java @@ -1,11 +1,27 @@ package com.track.fin.record; +import com.track.fin.type.TransactionMethodType; +import jakarta.validation.constraints.Max; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; + public record WithdrawalRequestRecord( + @NotNull + @Min(1) Long userId, + + @NotBlank String accountNumber, - Long amount + + @NotNull + @Min(10) + @Max(1000000000) + Long amount, + + @NotNull + TransactionMethodType transactionMethodType ) { } - diff --git a/src/main/java/com/track/fin/service/AccountFacadeService.java b/src/main/java/com/track/fin/service/AccountFacadeService.java new file mode 100644 index 0000000..52505bb --- /dev/null +++ b/src/main/java/com/track/fin/service/AccountFacadeService.java @@ -0,0 +1,87 @@ +package com.track.fin.service; + +import com.track.fin.domain.Account; +import com.track.fin.domain.User; +import com.track.fin.exception.AccountException; +import com.track.fin.record.AccountRecord; +import com.track.fin.record.DeleteAccountRecord; +import com.track.fin.type.TransactionMethodType; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; + +import static com.track.fin.design.singleton.AccountValidates.validateDeleteAccount; +import static com.track.fin.design.singleton.AccountValidates.validateRestoreAccount; +import static com.track.fin.type.ErrorCode.*; + +@Service +@RequiredArgsConstructor +public class AccountFacadeService { + + private final UserService userService; + private final AccountService accountService; + private final LoanService loanService; + private final TransactionService transactionService; + + + @Transactional + public AccountRecord deleteAccount(DeleteAccountRecord deleteAccountRecord) { + User user = userService.get(deleteAccountRecord.userId()); + Account closingAccount = accountService.getAccountByNumber(deleteAccountRecord.accountNumber()); + Account withdrawAccount = accountService.getAccountByNumber(deleteAccountRecord.withdrawAccountNumber()); + + validateDeleteAccount(user, closingAccount, withdrawAccount); + + transferRemainingBalanceIfExists(deleteAccountRecord, closingAccount); + + closingAccount.close(); + return AccountRecord.from(accountService.createAccount(closingAccount)); + } + + @Transactional + public AccountRecord restoreAccount(Long userId, String accountNumber) { + User user = userService.get(userId); + Account account = accountService.getAccountByNumber(accountNumber); + + validateRestoreAccount(user, account); + account.restore(); + + return AccountRecord.from(accountService.createAccount(account)); + } + + @Transactional + public void deleteIfExpired(Account account) { + if (account.getUnregisteredAt() != null && + account.getUnregisteredAt().isBefore(LocalDateTime.now().minusMonths(3))) { + accountService.deleteAccount(account); + throw new AccountException(ACCOUNT_RESTORE_EXPIRED); + } + } + + private void transferRemainingBalanceIfExists(DeleteAccountRecord record, Account closingAccount) { + if (closingAccount.getBalance() > 0) { + transactionService.transfer( + record.userId(), + record.accountNumber(), + record.withdrawAccountNumber(), + closingAccount.getBalance(), + TransactionMethodType.AUTO + ); + } + } + // TODO : 대출 로직 구현 후 사용 예정 + private void validateUnpaidLoan(Account account) { + if (loanService.existsUnpaidLoanByAccount(account)) { + throw new AccountException(LOAN_EXISTS); + } + } + + private void validateAutoTransfer(Account account) { + if (transactionService.existsByAccountAndTransactionMethodType(account, TransactionMethodType.AUTO)) { + throw new AccountException(AUTO_TRANSFER_EXISTS); + } + } + +} diff --git a/src/main/java/com/track/fin/service/AccountService.java b/src/main/java/com/track/fin/service/AccountService.java index 994d35a..70fb19e 100644 --- a/src/main/java/com/track/fin/service/AccountService.java +++ b/src/main/java/com/track/fin/service/AccountService.java @@ -5,9 +5,7 @@ import com.track.fin.exception.AccountException; import com.track.fin.record.AccountRecord; import com.track.fin.record.CreateAccount; -import com.track.fin.record.DeleteAccountRecord; import com.track.fin.repository.AccountRepository; -import com.track.fin.repository.UserRepository; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -17,7 +15,8 @@ import java.util.List; import static com.track.fin.design.singleton.AccountUtils.generateUniqueAccountNumber; -import static com.track.fin.design.singleton.AccountValidates.*; +import static com.track.fin.design.singleton.AccountValidates.validateInitialBalance; +import static com.track.fin.design.singleton.AccountValidates.validateRestoreAccount; import static com.track.fin.type.AccountStatus.ACTIVE; import static com.track.fin.type.AccountStatus.CLOSED; import static com.track.fin.type.ErrorCode.*; @@ -29,9 +28,10 @@ public class AccountService { private final AccountRepository accountRepository; private final UserService userService; - private final LoanService loanService; - private final UserRepository userRepository; - private final TransactionService transactionService; + + public Account createAccount(Account account) { + return accountRepository.save(account); + } @Transactional public Account createAccount(CreateAccount createAccount) { @@ -68,25 +68,10 @@ public List getAccounts(Long userId) { return accountRepository.findByUserId(userId); } - @Transactional - public AccountRecord deleteAccount(DeleteAccountRecord deleteAccountRecord) { - User user = userService.get(deleteAccountRecord.userId()); - Account closingAccount = getAccountByNumber(deleteAccountRecord.accountNumber()); - Account withdrawAccount = getAccountByNumber(deleteAccountRecord.withdrawAccountNumber()); - - validateDeleteAccount(user, closingAccount, withdrawAccount); - - transactionService.transfer(deleteAccountRecord.userId(), deleteAccountRecord.accountNumber(), deleteAccountRecord.withdrawAccountNumber(), closingAccount.getBalance()); - - closingAccount.close(); - return AccountRecord.from(accountRepository.save(closingAccount)); - } - private void validateCreateAccount(User user) { if (accountRepository.countByUser(user) == 10) { throw new AccountException(MAX_ACCOUNT_PER_USER_10); } - if (hasClosedAccountWithinLastMonth(user)) { throw new AccountException(CANNOT_CREATE_ACCOUNT_DUE_TO_RECENT_CLOSURE); } @@ -103,22 +88,12 @@ private boolean hasClosedAccountWithinLastMonth(User user) { }); } -// private void validatePendingLoanOrAutoTransfer(Account account) { -// boolean hasLoan = loanService.existsUnpaidLoanByAccount(account); -//// boolean hasAutoTransfer = this.existsByAccount(account); -// -// if (hasLoan) { -// throw new AccountException(LOAN_EXISTS); -// } -// if (hasAutoTransfer) { -// throw new AccountException(AUTO_TRANSFER_EXISTS); -// } -// } - @Transactional(readOnly = true) - public List getActiveAccounts(Long userId) { + public List getActiveAccounts(Long userId) { User user = userService.get(userId); - return accountRepository.findByUserAndAccountStatus(user, ACTIVE); + return accountRepository.findByUserAndAccountStatus(user, ACTIVE).stream() + .map(AccountRecord::from) + .toList(); } @Transactional @@ -132,14 +107,6 @@ public AccountRecord restoreAccount(Long userId, String accountNumber) { return AccountRecord.from(accountRepository.save(account)); } - // TODO: 3개월 지나면 삭제 - private void delete(Account account){ - if (account.getUnregisteredAt().isBefore(LocalDateTime.now().minusMonths(3))) { - accountRepository.delete(account); - throw new AccountException(ACCOUNT_RESTORE_EXPIRED); - } - } - public boolean validateAutoTransferNotRegistered(String accountNumber) { Account account = getAccountByNumber(accountNumber); @@ -159,4 +126,9 @@ public Account findByAccountNumber(String accountNumber) { return accountRepository.findByAccountNumber(accountNumber).orElseThrow(() -> new AccountException(ACCOUNT_NOT_FOUND)); } + public void deleteAccount(Account account) { + accountRepository.findById(account.getId()); + return; + } + } diff --git a/src/main/java/com/track/fin/service/LoanService.java b/src/main/java/com/track/fin/service/LoanService.java index d021052..d9328fc 100644 --- a/src/main/java/com/track/fin/service/LoanService.java +++ b/src/main/java/com/track/fin/service/LoanService.java @@ -5,32 +5,29 @@ import com.track.fin.domain.User; import com.track.fin.exception.AccountException; import com.track.fin.record.CreateLoanRecord; -import com.track.fin.repository.AccountRepository; import com.track.fin.repository.LoanRepository; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import static com.track.fin.type.ErrorCode.ACCOUNT_NOT_FOUND; import static com.track.fin.type.ErrorCode.LOAN_NOT_FOUND; import static com.track.fin.type.LoanStatus.REPAID; @Service @RequiredArgsConstructor public class LoanService { - private final UserService userService; - private final AccountRepository accountRepository; private final LoanRepository loanRepository; + private final AccountService accountService; + private final UserService userService; + @Transactional public Loan createLoan(CreateLoanRecord createLoan) { User user = userService.get(createLoan.userId()); - Account account = accountRepository.findById(createLoan.accountId()) - .orElseThrow(() -> new AccountException(ACCOUNT_NOT_FOUND)); + Account account = accountService.getAccount(createLoan.accountId()); account.afterLoan(); - Loan loan = Loan.from(user, account, createLoan); return loanRepository.save(loan); } @@ -45,8 +42,7 @@ public Loan getLoan(Long loanId) { public Loan updateLoan(Long loanId, CreateLoanRecord updateLoan) { Loan loan = getLoan(loanId); - Account account = accountRepository.findById(updateLoan.accountId()) - .orElseThrow(() -> new AccountException(ACCOUNT_NOT_FOUND)); + Account account = accountService.getAccount(updateLoan.accountId()); User user = userService.get(updateLoan.userId()); loan.update(user, account, updateLoan); diff --git a/src/main/java/com/track/fin/service/TransactionService.java b/src/main/java/com/track/fin/service/TransactionService.java index aee617c..e6adb06 100644 --- a/src/main/java/com/track/fin/service/TransactionService.java +++ b/src/main/java/com/track/fin/service/TransactionService.java @@ -3,12 +3,9 @@ import com.track.fin.domain.Account; import com.track.fin.domain.Transaction; import com.track.fin.domain.User; -import com.track.fin.dto.TransactionDto; import com.track.fin.exception.AccountException; import com.track.fin.record.TransferResponseRecord; -import com.track.fin.repository.AccountRepository; import com.track.fin.repository.TransactionRepository; -import com.track.fin.repository.UserRepository; import com.track.fin.type.AccountStatus; import com.track.fin.type.TransactionMethodType; import com.track.fin.type.TransactionResultType; @@ -34,31 +31,40 @@ @RequiredArgsConstructor public class TransactionService { + private static final long MAX_DEPOSIT_AMOUNT = 1_000_000L; + private final TransactionRepository transactionRepository; - private final AccountRepository accountRepository; - private final UserRepository userRepository; + + private final AccountService accountService; + private final UserService userService; @Transactional - public TransactionDto useBalance(Long userId, String accountNumber, Long amount) { - User user = userRepository.findById(userId) - .orElseThrow(() -> new AccountException(USER_NOT_FOUND)); - Account account = accountRepository.findByAccountNumber(accountNumber) - .orElseThrow(() -> new AccountException(ACCOUNT_NOT_FOUND)); + public TransferResponseRecord useBalance(Long userId, String accountNumber, Long amount, TransactionMethodType methodType) { + User user = userService.get(userId); + + Account account = accountService.getAccountByNumber(accountNumber); validateUserBalance(user, account, amount); account.useBalance(amount); - return TransactionDto.fromEntity( - saveAndGetTransaction(DEPOSIT, SUCCESS, account, amount) - ); + Transaction transaction = saveAndGetTransaction(WITHDRAWAL, SUCCESS, account, amount, methodType); + + return TransferResponseRecord.from(transaction); } - private Transaction saveAndGetTransaction(TransactionType type, TransactionResultType result, Account account, Long amount) { + private Transaction saveAndGetTransaction( + TransactionType type, + TransactionResultType result, + Account account, + Long amount, + TransactionMethodType methodType + ) { return transactionRepository.save( Transaction.builder() .id(UUID.randomUUID().toString().replace("-", "")) .transactionType(type) .transactionResultType(result) + .transactionMethodType(methodType) .account(account) .amount(amount) .balanceSnapshot(account.getBalance()) @@ -71,24 +77,25 @@ public List getTransferTransactionsByAccount( String accountNumber, LocalDateTime startDate, LocalDateTime endDate, + TransactionType transactionType, boolean sortDesc ) { Account account = getAccountByNumber(accountNumber); - List transfers = getTransferTransactions(account, startDate, endDate); + List transfers = getTransferTransactions(account, startDate, endDate, transactionType); List results = convertToTransferRecords(transfers, accountNumber); sortTransferRecords(results, sortDesc); return results; } private Account getAccountByNumber(String accountNumber) { - return accountRepository.findByAccountNumber(accountNumber) - .orElseThrow(() -> new AccountException(ACCOUNT_NOT_FOUND)); + return accountService.getAccountByNumber(accountNumber); } - private List getTransferTransactions(Account account, LocalDateTime start, LocalDateTime end) { - return transactionRepository.findByAccountAndTransactionTypeAndTransactionDateBetween( - account, TransactionType.TRANSFER, start, end - ); + private List getTransferTransactions(Account account, LocalDateTime start, LocalDateTime end, TransactionType type) { + if (type == null) { + return transactionRepository.findByAccountAndTransactionDateBetween(account, start, end); + } + return transactionRepository.findByAccountAndTransactionTypeAndTransactionDateBetween(account, type, start, end); } private List convertToTransferRecords(List transactions, String accountNumber) { @@ -121,30 +128,33 @@ private void sortTransferRecords(List list, boolean sort : a.transactionDate().compareTo(b.transactionDate())); } - public void saveFailedUseTransaction(String accountNumber, Long amount) { - Account account = accountRepository.findByAccountNumber(accountNumber) - .orElseThrow(() -> new AccountException(ACCOUNT_NOT_FOUND)); - saveAndGetTransaction(DEPOSIT, FAIL, account, amount); + public void saveFailedUseTransaction(String accountNumber, Long amount, TransactionMethodType transactionMethodType) { + Account account = accountService.getAccountByNumber(accountNumber); + saveAndGetTransaction(DEPOSIT, FAIL, account, amount, transactionMethodType); } @Transactional - public TransferResponseRecord cancelBalance(String transactionId, String accountNumber, Long amount) { + public TransferResponseRecord cancelBalance( + String transactionId, + String accountNumber, + Long amount, + TransactionMethodType methodType + ) { Transaction transaction = transactionRepository.findById(transactionId) .orElseThrow(() -> new AccountException(TRANSACTION_NOT_FOUND)); - Account account = accountRepository.findByAccountNumber(accountNumber) - .orElseThrow(() -> new AccountException(ACCOUNT_NOT_FOUND)); + + Account account = accountService.getAccountByNumber(accountNumber); validateCancelBalance(transaction, account, amount); account.useBalance(amount); - Transaction newTransaction = saveAndGetTransaction(DEPOSIT, SUCCESS, account, amount); + Transaction newTransaction = saveAndGetTransaction(DEPOSIT, SUCCESS, account, amount, methodType); return TransferResponseRecord.from(newTransaction); } - public void saveFailedCancelTransaction(String accountNumber, Long amount) { - Account account = accountRepository.findByAccountNumber(accountNumber) - .orElseThrow(() -> new AccountException(ACCOUNT_NOT_FOUND)); - saveAndGetTransaction(DEPOSIT, FAIL, account, amount); + public void saveFailedCancelTransaction(String accountNumber, Long amount, TransactionMethodType methodType) { + Account account = accountService.getAccountByNumber(accountNumber); + saveAndGetTransaction(DEPOSIT, FAIL, account, amount, methodType); } public TransferResponseRecord queryTransactionId(String transactionId) { @@ -153,7 +163,6 @@ public TransferResponseRecord queryTransactionId(String transactionId) { return TransferResponseRecord.from(transaction); } - private void validateCancelBalance(Transaction transaction, Account account, Long amount) { if (!Objects.equals(transaction.getAccount().getId(), account.getId())) { throw new AccountException(TRANSACTION_ACCOUNT_UN_MATCH); @@ -176,22 +185,21 @@ private void validateUserBalance(User user, Account account, Long amount) { } @Transactional - public Transaction deposit(Long userId, String accountNumber, Long amount) { - User user = userRepository.findById(userId) - .orElseThrow(() -> new AccountException(USER_NOT_FOUND)); - Account account = accountRepository.findByAccountNumber(accountNumber) - .orElseThrow(() -> new AccountException(ACCOUNT_NOT_FOUND)); + public Transaction deposit(Long userId, String accountNumber, Long amount, TransactionMethodType methodType) { + User user = userService.get(userId); + + Account account = accountService.getAccountByNumber(accountNumber); + + validateDeposit(user, account, amount); - validateDeposit(user, account); account.deposit(amount); - return saveAndGetTransaction(DEPOSIT, SUCCESS, account, amount); + return saveAndGetTransaction(DEPOSIT, SUCCESS, account, amount, methodType); } - public void saveFailedDepositTransaction(String accountNumber, Long amount) { - Account account = accountRepository.findByAccountNumber(accountNumber) - .orElseThrow(() -> new AccountException(ACCOUNT_NOT_FOUND)); - saveAndGetTransaction(DEPOSIT, FAIL, account, amount); + public void saveFailedDepositTransaction(String accountNumber, Long amount, TransactionMethodType methodType) { + Account account = accountService.getAccountByNumber(accountNumber); + saveAndGetTransaction(DEPOSIT, FAIL, account, amount, methodType); } private void validateDeposit(User user, Account account) { @@ -204,22 +212,25 @@ private void validateDeposit(User user, Account account) { } @Transactional - public Transaction withdraw(Long userId, String accountNumber, Long amount) { - User user = userRepository.findById(userId) - .orElseThrow(() -> new AccountException(USER_NOT_FOUND)); - Account account = accountRepository.findByAccountNumber(accountNumber) - .orElseThrow(() -> new AccountException(ACCOUNT_NOT_FOUND)); + public Transaction withdraw(Long userId, String accountNumber, Long amount, TransactionMethodType methodType) { + User user = userService.get(userId); + + Account account = accountService.getAccountByNumber(accountNumber); validateWithdraw(user, account, amount); account.withdraw(amount); - return saveAndGetTransaction(WITHDRAWAL, SUCCESS, account, amount); + return saveAndGetTransaction(WITHDRAWAL, SUCCESS, account, amount, methodType); } - public void saveFailedWithdrawTransaction(String accountNumber, Long amount) { - Account account = accountRepository.findByAccountNumber(accountNumber) - .orElseThrow(() -> new AccountException(ACCOUNT_NOT_FOUND)); - saveAndGetTransaction(WITHDRAWAL, FAIL, account, amount); + public void saveFailedWithdrawTransaction(String accountNumber, Long amount, TransactionMethodType methodType) { + try { + Account account = accountService.getAccountByNumber(accountNumber); + saveAndGetTransaction(WITHDRAWAL, FAIL, account, amount, methodType); + } catch (AccountException e) { + log.error("출금 실패 거래 내역 저장 중 오류 발생 - 계좌번호: {}", accountNumber, e); + throw e; + } } private void validateWithdraw(User user, Account account, Long amount) { @@ -235,32 +246,28 @@ private void validateWithdraw(User user, Account account, Long amount) { } @Transactional - public TransferResponseRecord transfer(Long userId, String fromAccountNumber, String toAccountNumber, Long amount) { - User user = userRepository.findById(userId) - .orElseThrow(() -> new AccountException(USER_NOT_FOUND)); - Account fromAccount = accountRepository.findByAccountNumber(fromAccountNumber) - .orElseThrow(() -> new AccountException(ACCOUNT_NOT_FOUND)); - Account toAccount = accountRepository.findByAccountNumber(toAccountNumber) - .orElseThrow(() -> new AccountException(ACCOUNT_NOT_FOUND)); + public TransferResponseRecord transfer(Long userId, String fromAccountNumber, String toAccountNumber, Long amount, TransactionMethodType methodType) { + User user = userService.get(userId); + Account fromAccount = accountService.getAccountByNumber(fromAccountNumber); + Account toAccount = accountService.getAccountByNumber(toAccountNumber); validateTransfer(user, fromAccount, toAccount, amount); fromAccount.withdraw(amount); toAccount.deposit(amount); - Transaction fromTransaction = saveAndGetTransaction(TRANSFER, SUCCESS, fromAccount, amount); - Transaction toTransaction = saveAndGetTransaction(TRANSFER, SUCCESS, toAccount, amount); + Transaction fromTransaction = saveAndGetTransaction(TRANSFER, SUCCESS, fromAccount, amount, methodType); + Transaction toTransaction = saveAndGetTransaction(TRANSFER, SUCCESS, toAccount, amount, methodType); return TransferResponseRecord.from(fromTransaction, toTransaction); } - public void saveFailedTransferTransaction(String fromAccountNumber, String toAccountNumber, Long amount) { - Account fromAccount = accountRepository.findByAccountNumber(fromAccountNumber) - .orElseThrow(() -> new AccountException(ACCOUNT_NOT_FOUND)); - Account toAccount = accountRepository.findByAccountNumber(toAccountNumber) - .orElseThrow(() -> new AccountException(ACCOUNT_NOT_FOUND)); - saveAndGetTransaction(TRANSFER, FAIL, fromAccount, amount); - saveAndGetTransaction(TRANSFER, FAIL, toAccount, amount); + public void saveFailedTransferTransaction(String fromAccountNumber, String toAccountNumber, Long amount, TransactionMethodType methodType) { + Account fromAccount = accountService.getAccountByNumber(fromAccountNumber); + Account toAccount = accountService.getAccountByNumber(toAccountNumber); + + saveAndGetTransaction(TRANSFER, FAIL, fromAccount, amount, methodType); + saveAndGetTransaction(TRANSFER, FAIL, toAccount, amount, methodType); } private void validateTransfer(User user, Account from, Account to, Long amount) { @@ -278,9 +285,20 @@ private void validateTransfer(User user, Account from, Account to, Long amount) } } - @Transactional(readOnly = true) - public boolean existsByAccountAndTransactionMethodType(Account account) { - return transactionRepository.existsByAccountAndTransactionMethodType(account, TransactionMethodType.AUTO); + private void validateDeposit(User user, Account account, Long amount) { + if (!Objects.equals(user.getId(), account.getUser().getId())) { + throw new AccountException(USER_ACCOUNT_UNMATCH); + } + if (account.getAccountStatus() != AccountStatus.ACTIVE) { + throw new AccountException(ACCOUNT_ALREADY_UNREGISTERED); + } + if (amount > MAX_DEPOSIT_AMOUNT) { + throw new AccountException(AMOUNT_EXCEED_DEPOSIT_LIMIT); + } + } + + public boolean existsByAccountAndTransactionMethodType(Account account, TransactionMethodType auto) { + return transactionRepository.existsByAccountAndTransactionMethodType(account, auto); } } diff --git a/src/main/java/com/track/fin/type/ErrorCode.java b/src/main/java/com/track/fin/type/ErrorCode.java index 667358d..dd98b70 100644 --- a/src/main/java/com/track/fin/type/ErrorCode.java +++ b/src/main/java/com/track/fin/type/ErrorCode.java @@ -32,6 +32,7 @@ public enum ErrorCode { WITHDRAW_ACCOUNT_INACTIVE("출금 계좌가 해지 상태입니다."), CANNOT_CREATE_ACCOUNT_DUE_TO_RECENT_CLOSURE("해지된 계좌가 있어 1개월 내 신규 계좌 개설이 제한됩니다."), ACCOUNT_RESTORE_EXPIRED("계좌 복구 가능 기간이 지났습니다."), + AMOUNT_EXCEED_DEPOSIT_LIMIT("입금 금액이 허용된 최대 한도를 초과했습니다") ; private final String description; diff --git a/src/main/java/com/track/fin/type/TransactionType.java b/src/main/java/com/track/fin/type/TransactionType.java index 0442ef1..adf7c9c 100644 --- a/src/main/java/com/track/fin/type/TransactionType.java +++ b/src/main/java/com/track/fin/type/TransactionType.java @@ -5,5 +5,4 @@ public enum TransactionType { DEPOSIT, WITHDRAWAL, TRANSFER - }