diff --git a/build.gradle b/build.gradle index 61359f0..179eccc 100644 --- a/build.gradle +++ b/build.gradle @@ -31,6 +31,8 @@ dependencies { testImplementation 'org.springframework.boot:spring-boot-starter-test' implementation 'io.springfox:springfox-boot-starter:3.0.0' implementation 'io.springfox:springfox-swagger-ui:3.0.0' + implementation 'org.springframework.security:spring-security-crypto:5.7.1' + } diff --git a/src/main/java/com/eum/bank/common/dto/request/AccountRequestDTO.java b/src/main/java/com/eum/bank/common/dto/request/AccountRequestDTO.java index 2654aef..c1a8e4a 100644 --- a/src/main/java/com/eum/bank/common/dto/request/AccountRequestDTO.java +++ b/src/main/java/com/eum/bank/common/dto/request/AccountRequestDTO.java @@ -10,4 +10,13 @@ public static class CreateAccount { @NotEmpty(message = "비밀번호를 입력해주세요.") private Long password; } + + // 계좌 조회 요청 + @Getter + public static class GetAccount { + @NotEmpty(message = "계좌 번호를 입력해주세요.") + private String accountNumber; + @NotEmpty(message = "비밀번호를 입력해주세요.") + private String password; + } } diff --git a/src/main/java/com/eum/bank/common/dto/request/AccountTransferHistoryRequestDTO.java b/src/main/java/com/eum/bank/common/dto/request/AccountTransferHistoryRequestDTO.java new file mode 100644 index 0000000..54a7ebe --- /dev/null +++ b/src/main/java/com/eum/bank/common/dto/request/AccountTransferHistoryRequestDTO.java @@ -0,0 +1,41 @@ +package com.eum.bank.common.dto.request; + +import com.eum.bank.domain.account.entity.Account; +import com.eum.bank.domain.account.entity.AccountTransferHistory; +import jakarta.validation.constraints.NotEmpty; +import lombok.Builder; +import lombok.Getter; + +public class AccountTransferHistoryRequestDTO { + + // 내역 생성 + @Getter + @Builder + public static class CreateAccountTransferHistory { + @NotEmpty(message = "거래 내역을 생성할 계좌를 입력해주세요.") + private Account ownerAccount; + @NotEmpty(message = "거래 상대 계좌를 입력해주세요.") + private Account oppenentAccount; + @NotEmpty(message = "거래 금액을 입력해주세요.") + private Long transferAmount; + @NotEmpty(message = "거래 유형을 입력해주세요.") + private String transferType; + @NotEmpty(message = "거래 후 잔액을 입력해주세요.") + private Long budgetAfterTransfer; + @NotEmpty(message = "거래 메모를 입력해주세요.") + private String memo; + + // toEntity + public AccountTransferHistory toEntity() { + return AccountTransferHistory.builder() + .ownerAccount(ownerAccount) + .oppenentAccount(oppenentAccount) + .transferAmount(transferAmount) + .transferType(transferType) + .budgetAfterTransfer(budgetAfterTransfer) + .memo(memo) + .build(); + } + } + +} diff --git a/src/main/java/com/eum/bank/common/dto/request/TotalTransferHistoryRequestDTO.java b/src/main/java/com/eum/bank/common/dto/request/TotalTransferHistoryRequestDTO.java new file mode 100644 index 0000000..e08f89d --- /dev/null +++ b/src/main/java/com/eum/bank/common/dto/request/TotalTransferHistoryRequestDTO.java @@ -0,0 +1,33 @@ +package com.eum.bank.common.dto.request; + +import com.eum.bank.domain.account.entity.Account; +import com.eum.bank.domain.account.entity.TotalTransferHistory; +import jakarta.validation.constraints.NotEmpty; +import lombok.Builder; +import lombok.Getter; + +public class TotalTransferHistoryRequestDTO { + + @Getter + @Builder + public static class CreateTotalTransferHistory { + @NotEmpty(message = "송금자 계좌를 입력해주세요.") + private Account senderAccount; + @NotEmpty(message = "수취자 계좌를 입력해주세요.") + private Account receiverAccount; + @NotEmpty(message = "송금 금액을 입력해주세요.") + private Long transferAmount; + @NotEmpty(message = "거래유형을 입력해주세요.") + private String transferType; + + // toEntity + public TotalTransferHistory toEntity() { + return TotalTransferHistory.builder() + .senderAccount(senderAccount) + .receiverAccount(receiverAccount) + .transferAmount(transferAmount) + .transferType(transferType) + .build(); + } + } +} diff --git a/src/main/java/com/eum/bank/common/dto/response/AccountResponseDTO.java b/src/main/java/com/eum/bank/common/dto/response/AccountResponseDTO.java index 6bd1b95..5732d2a 100644 --- a/src/main/java/com/eum/bank/common/dto/response/AccountResponseDTO.java +++ b/src/main/java/com/eum/bank/common/dto/response/AccountResponseDTO.java @@ -1,9 +1,9 @@ package com.eum.bank.common.dto.response; +import com.eum.bank.domain.account.entity.Account; import jakarta.validation.constraints.NotEmpty; import lombok.Builder; import lombok.Getter; -import lombok.Setter; public class AccountResponseDTO { // 계좌 생성 응답 @@ -13,4 +13,22 @@ public static class Create { @NotEmpty(message = "계좌 번호가 생성되어야 합니다.") private String accountNumber; } + + // 계좌 조회 응답 + @Builder + @Getter + public static class AccountInfo { + private String accountNumber; + private Long totalBudget; + private Long availableBudget; + + // fromEntity + public static AccountInfo fromEntity(Account account) { + return AccountInfo.builder() + .accountNumber(account.getAccountNumber()) + .totalBudget(account.getTotalBudget()) + .availableBudget(account.getAvailableBudget()) + .build(); + } + } } diff --git a/src/main/java/com/eum/bank/common/dto/response/TotalTransferHistoryResponseDTO.java b/src/main/java/com/eum/bank/common/dto/response/TotalTransferHistoryResponseDTO.java new file mode 100644 index 0000000..ad88629 --- /dev/null +++ b/src/main/java/com/eum/bank/common/dto/response/TotalTransferHistoryResponseDTO.java @@ -0,0 +1,30 @@ +package com.eum.bank.common.dto.response; + +import com.eum.bank.domain.account.entity.TotalTransferHistory; +import lombok.Builder; +import lombok.Getter; + +public class TotalTransferHistoryResponseDTO { + + // 거래 내역 반환 + @Builder + @Getter + public static class GetTotalTransferHistory { + private Long id; + private AccountResponseDTO.AccountInfo senderAccount; + private AccountResponseDTO.AccountInfo receiverAccount; + private Long transferAmount; + private String transferType; + + // fromEntity + public static GetTotalTransferHistory fromEntity(TotalTransferHistory totalTransferHistory) { + return GetTotalTransferHistory.builder() + .id(totalTransferHistory.getId()) + .senderAccount(AccountResponseDTO.AccountInfo.fromEntity(totalTransferHistory.getSenderAccount())) + .receiverAccount(AccountResponseDTO.AccountInfo.fromEntity(totalTransferHistory.getReceiverAccount())) + .transferAmount(totalTransferHistory.getTransferAmount()) + .transferType(totalTransferHistory.getTransferType()) + .build(); + } + } +} diff --git a/src/main/java/com/eum/bank/config/PasswordEncoderConfig.java b/src/main/java/com/eum/bank/config/PasswordEncoderConfig.java new file mode 100644 index 0000000..26171d0 --- /dev/null +++ b/src/main/java/com/eum/bank/config/PasswordEncoderConfig.java @@ -0,0 +1,14 @@ +package com.eum.bank.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.crypto.factory.PasswordEncoderFactories; +import org.springframework.security.crypto.password.PasswordEncoder; + +@Configuration +public class PasswordEncoderConfig { + @Bean + public PasswordEncoder passwordEncoder() { + return PasswordEncoderFactories.createDelegatingPasswordEncoder(); + } +} diff --git a/src/main/java/com/eum/bank/repository/AccountRepository.java b/src/main/java/com/eum/bank/repository/AccountRepository.java index 4aae0e5..71d02c5 100644 --- a/src/main/java/com/eum/bank/repository/AccountRepository.java +++ b/src/main/java/com/eum/bank/repository/AccountRepository.java @@ -3,7 +3,9 @@ import com.eum.bank.domain.account.entity.Account; import org.springframework.data.jpa.repository.JpaRepository; +import java.util.Optional; + public interface AccountRepository extends JpaRepository { Account save(Account account); - Boolean findByAccountNumber(String accountNumber); + Optional findByAccountNumber(String accountNumber); } \ No newline at end of file diff --git a/src/main/java/com/eum/bank/service/AccountService.java b/src/main/java/com/eum/bank/service/AccountService.java index 865d3aa..a870c3d 100644 --- a/src/main/java/com/eum/bank/service/AccountService.java +++ b/src/main/java/com/eum/bank/service/AccountService.java @@ -1,13 +1,21 @@ package com.eum.bank.service; import com.eum.bank.common.APIResponse; +import com.eum.bank.common.ErrorResponse; +import com.eum.bank.common.dto.request.AccountTransferHistoryRequestDTO; +import com.eum.bank.common.dto.request.TotalTransferHistoryRequestDTO; import com.eum.bank.common.dto.response.AccountResponseDTO; +import com.eum.bank.common.enums.ErrorCode; import com.eum.bank.common.enums.SuccessCode; import com.eum.bank.domain.account.entity.Account; +import com.eum.bank.domain.account.entity.AccountTransferHistory; +import com.eum.bank.domain.account.entity.TotalTransferHistory; import com.eum.bank.repository.AccountRepository; +import jakarta.transaction.Transactional; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; +import org.springframework.security.crypto.password.PasswordEncoder; import java.util.Random; @Service @@ -15,6 +23,11 @@ public class AccountService { private final AccountRepository accountRepository; + private final PasswordEncoder passwordEncoder; + private final AccountTransferHistoryService accountTransferHistoryService; + private final TotalTransferHistoryService totalTransferHistoryService; + + public APIResponse createAccount(Long password) { @@ -27,7 +40,7 @@ public APIResponse createAccount(Long password) { Account account = Account.builder() .accountNumber(accountNumber) - .password(password.toString()) + .password(passwordEncoder.encode(password.toString())) .totalBudget(0L) .availableBudget(0L) .build(); @@ -67,5 +80,84 @@ public Boolean validateAccountNumber(String accountNumber) { return true; } + // 계좌번호와 비밀번호로 계좌 조회 + public APIResponse getAccount(String accountNumber, String password) { + Account account = accountRepository.findByAccountNumber(accountNumber).orElseThrow(() -> new IllegalArgumentException("Invalid account number")); + + // 비밀번호 검증 + if (!passwordEncoder.matches(password, account.getPassword())) { + throw new IllegalArgumentException("Invalid password"); + } + return APIResponse.of(SuccessCode.SELECT_SUCCESS, AccountResponseDTO.AccountInfo.builder() + .accountNumber(account.getAccountNumber()) + .totalBudget(account.getTotalBudget()) + .availableBudget(account.getAvailableBudget()) + .build()); + } + + // 자유송금 + // 1. 송금자 계좌, 수신자 계좌 상태 검증 + // 2. 송금자 잔액 확인 + // 3. 송금자 전체금액, 가용금액 마이너스 + // 4. 수신자 전체금액, 가용금액 플러스 + // 5. 통합 거래내역 생성, 각 계좌 거래내역 생성 + @Transactional + public APIResponse transfer(String senderAccountNumber, String receiverAccountNumber, Long amount, String password, String transferType) { + Account senderAccount = accountRepository.findByAccountNumber(senderAccountNumber).orElseThrow(() -> new IllegalArgumentException("Invalid account number")); + Account receiverAccount = accountRepository.findByAccountNumber(receiverAccountNumber).orElseThrow(() -> new IllegalArgumentException("Invalid account number")); + + // 비밀번호 검증 + if (!passwordEncoder.matches(password, senderAccount.getPassword())) { + throw new IllegalArgumentException("Invalid password"); + } + + // 송금자 잔액 검증 + if (senderAccount.getAvailableBudget() < amount) { + throw new IllegalArgumentException("Insufficient balance"); + } + + // 송금자 잔액 마이너스 + senderAccount.setTotalBudget(senderAccount.getTotalBudget() - amount); + senderAccount.setAvailableBudget(senderAccount.getAvailableBudget() - amount); + + + // 수신자 잔액 플러스 + receiverAccount.setTotalBudget(receiverAccount.getTotalBudget() + amount); + receiverAccount.setAvailableBudget(receiverAccount.getAvailableBudget() + amount); + + // 통합 거래내역 생성 + TotalTransferHistory response = totalTransferHistoryService.save( + TotalTransferHistoryRequestDTO.CreateTotalTransferHistory.builder() + .senderAccount(senderAccount) + .receiverAccount(receiverAccount) + .transferAmount(amount) + .transferType(transferType) + .build() + ); + + // 각 계좌 거래내역 생성 + accountTransferHistoryService.save( + AccountTransferHistoryRequestDTO.CreateAccountTransferHistory.builder() + .ownerAccount(senderAccount) + .oppenentAccount(receiverAccount) + .transferAmount(amount) + .transferType(transferType) + .budgetAfterTransfer(senderAccount.getAvailableBudget()) + .memo("") + .build() + ); + accountTransferHistoryService.save( + AccountTransferHistoryRequestDTO.CreateAccountTransferHistory.builder() + .ownerAccount(receiverAccount) + .oppenentAccount(senderAccount) + .transferAmount(-amount) + .transferType(transferType) + .budgetAfterTransfer(receiverAccount.getAvailableBudget()) + .memo("") + .build() + ); + + return APIResponse.of(SuccessCode.INSERT_SUCCESS, response); + } } diff --git a/src/main/java/com/eum/bank/service/AccountTransferHistoryService.java b/src/main/java/com/eum/bank/service/AccountTransferHistoryService.java new file mode 100644 index 0000000..4d08da5 --- /dev/null +++ b/src/main/java/com/eum/bank/service/AccountTransferHistoryService.java @@ -0,0 +1,19 @@ +package com.eum.bank.service; + +import com.eum.bank.common.dto.request.AccountTransferHistoryRequestDTO; +import com.eum.bank.domain.account.entity.AccountTransferHistory; +import com.eum.bank.repository.AccountTransferHistoryRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class AccountTransferHistoryService { + + private final AccountTransferHistoryRepository accountTransferHistoryRepository; + + // 저장 + public AccountTransferHistory save(AccountTransferHistoryRequestDTO.CreateAccountTransferHistory dto) { + return accountTransferHistoryRepository.save(dto.toEntity()); + } +} diff --git a/src/main/java/com/eum/bank/service/TotalTransferHistoryService.java b/src/main/java/com/eum/bank/service/TotalTransferHistoryService.java new file mode 100644 index 0000000..9dcb728 --- /dev/null +++ b/src/main/java/com/eum/bank/service/TotalTransferHistoryService.java @@ -0,0 +1,17 @@ +package com.eum.bank.service; + +import com.eum.bank.common.dto.request.TotalTransferHistoryRequestDTO; +import com.eum.bank.domain.account.entity.TotalTransferHistory; +import com.eum.bank.repository.TotalTransferHistoryRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class TotalTransferHistoryService { + private final TotalTransferHistoryRepository totalTransferHistoryRepository; + + public TotalTransferHistory save(TotalTransferHistoryRequestDTO.CreateTotalTransferHistory dto) { + return totalTransferHistoryRepository.save(dto.toEntity()); + } +}