diff --git a/src/main/java/com/eum/bank/common/dto/request/DealRequestDTO.java b/src/main/java/com/eum/bank/common/dto/request/DealRequestDTO.java index b3fb934..9950148 100644 --- a/src/main/java/com/eum/bank/common/dto/request/DealRequestDTO.java +++ b/src/main/java/com/eum/bank/common/dto/request/DealRequestDTO.java @@ -1,7 +1,8 @@ package com.eum.bank.common.dto.request; -import lombok.Builder; +import jakarta.validation.constraints.NotEmpty; import lombok.Getter; +import lombok.Builder; public class DealRequestDTO { @Builder @@ -19,4 +20,51 @@ public static class Create{ private Long postId; } + + @Getter + public static class completeDeal{ + // 거래ID + @NotEmpty(message = "거래ID를 입력해주세요.") + private Long dealId; + // 예치금 + @NotEmpty(message = "예치금을 입력해주세요.") + private Long deposit; + // 수신 계좌번호 리스트 + @NotEmpty(message = "수신 계좌번호를 입력해주세요.") + private String[] receiverAccountNumbers; + } + + + @Getter + public static class updateDeal{ + // 거래ID + @NotEmpty(message = "거래ID를 입력해주세요.") + private Long dealId; + // 송금자 계좌번호 + @NotEmpty(message = "송금자 계좌번호를 입력해주세요.") + private String senderAccountNumber; + // 비밀번호 + @NotEmpty(message = "비밀번호를 입력해주세요.") + private String password; + // 예치금 + @NotEmpty(message = "예치금을 입력해주세요.") + private Long deposit; + // 최대인원수 + @NotEmpty(message = "최대인원수를 입력해주세요.") + private Long numberOfPeople; + } + + @Getter + public static class cancelDeal{ + // 거래ID + @NotEmpty(message = "거래ID를 입력해주세요.") + private Long dealId; + // 송금자 계좌번호 + @NotEmpty(message = "송금자 계좌번호를 입력해주세요.") + private String senderAccountNumber; + // 비밀번호 + @NotEmpty(message = "비밀번호를 입력해주세요.") + private String password; + } + } 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 5732d2a..ad2b83e 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 @@ -21,6 +21,7 @@ public static class AccountInfo { private String accountNumber; private Long totalBudget; private Long availableBudget; + private Boolean isBlocked; // fromEntity public static AccountInfo fromEntity(Account account) { @@ -28,6 +29,7 @@ public static AccountInfo fromEntity(Account account) { .accountNumber(account.getAccountNumber()) .totalBudget(account.getTotalBudget()) .availableBudget(account.getAvailableBudget()) + .isBlocked(account.getIsBlocked()) .build(); } } diff --git a/src/main/java/com/eum/bank/domain/account/entity/Account.java b/src/main/java/com/eum/bank/domain/account/entity/Account.java index ecbf1ef..bf8e7f9 100644 --- a/src/main/java/com/eum/bank/domain/account/entity/Account.java +++ b/src/main/java/com/eum/bank/domain/account/entity/Account.java @@ -30,4 +30,8 @@ public class Account extends BaseEntity { // 계좌 사용 가능 잔액 @Column(name = "available_budget", nullable = false) private Long availableBudget; + + // 블락 여부 + @Column(name = "is_blocked", nullable = false) + private Boolean isBlocked; } diff --git a/src/main/java/com/eum/bank/domain/deal/entity/Deal.java b/src/main/java/com/eum/bank/domain/deal/entity/Deal.java index 262a467..0ac9a26 100644 --- a/src/main/java/com/eum/bank/domain/deal/entity/Deal.java +++ b/src/main/java/com/eum/bank/domain/deal/entity/Deal.java @@ -23,6 +23,10 @@ public class Deal extends BaseEntity { private Account senderAccount; // 상태 + // a: 거래 생성 후 거래 성사 전 (수신계좌가 안엮인 상태) + // b: 거래 성사 후 (수신계좌가 엮인 상태) + // c: 거래 취소 됨 + // d: 거래 수행 됨 @Column(name = "status", nullable = false) private String status; diff --git a/src/main/java/com/eum/bank/repository/DealReceiverRepository.java b/src/main/java/com/eum/bank/repository/DealReceiverRepository.java index 62d8f16..4c44dc2 100644 --- a/src/main/java/com/eum/bank/repository/DealReceiverRepository.java +++ b/src/main/java/com/eum/bank/repository/DealReceiverRepository.java @@ -1,7 +1,9 @@ package com.eum.bank.repository; +import com.eum.bank.domain.deal.entity.Deal; import com.eum.bank.domain.deal.entity.DealReceiver; import org.springframework.data.jpa.repository.JpaRepository; public interface DealReceiverRepository extends JpaRepository { + void deleteByDeal(Deal deal); } \ 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 1be2026..fe8d18a 100644 --- a/src/main/java/com/eum/bank/service/AccountService.java +++ b/src/main/java/com/eum/bank/service/AccountService.java @@ -42,6 +42,7 @@ public APIResponse createAccount(String password) { .password(passwordEncoder.encode(password)) .totalBudget(0L) .availableBudget(0L) + .isBlocked(false) .build(); accountRepository.save(account); @@ -77,9 +78,18 @@ public Boolean validateAccountNumber(String accountNumber) { return true; } + // 계좌 검증 (계좌 존재여부 + 블락 여부) + public Account validateAccount(String accountNumber) { + Account account = accountRepository.findByAccountNumber(accountNumber).orElseThrow(() -> new IllegalArgumentException("Invalid account number : " + accountNumber)); + if(account.getIsBlocked()){ + throw new IllegalArgumentException("Blocked account : " + accountNumber); + } + return account; + } + // 계좌번호와 비밀번호로 계좌 조회 public APIResponse getAccount(String accountNumber, String password) { - Account account = accountRepository.findByAccountNumber(accountNumber).orElseThrow(() -> new IllegalArgumentException("Invalid account number")); + Account account = this.validateAccount(accountNumber); // 비밀번호 검증 if (!passwordEncoder.matches(password, account.getPassword())) { @@ -104,8 +114,8 @@ public Account getAccount(String accountNumber) { // 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")); + Account senderAccount = this.validateAccount(senderAccountNumber); + Account receiverAccount = this.validateAccount(receiverAccountNumber); // 비밀번호 검증 if (!passwordEncoder.matches(password, senderAccount.getPassword())) { diff --git a/src/main/java/com/eum/bank/service/DealService.java b/src/main/java/com/eum/bank/service/DealService.java index 72539d0..62e25b1 100644 --- a/src/main/java/com/eum/bank/service/DealService.java +++ b/src/main/java/com/eum/bank/service/DealService.java @@ -1,17 +1,30 @@ package com.eum.bank.service; import com.eum.bank.common.APIResponse; + +import com.eum.bank.common.dto.request.DealRequestDTO; +import com.eum.bank.common.enums.SuccessCode; import com.eum.bank.domain.account.entity.Account; import com.eum.bank.domain.deal.entity.Deal; +import com.eum.bank.domain.deal.entity.DealReceiver; +import com.eum.bank.repository.DealReceiverRepository; import com.eum.bank.repository.DealRepository; import lombok.RequiredArgsConstructor; +import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; @Service @RequiredArgsConstructor public class DealService { private final DealRepository dealRepository; + private final AccountService accountService; + private final DealReceiverRepository dealReceiverRepository; + private final PasswordEncoder passwordEncoder; + // 거래 생성 public void createDeal(Account accountNumber, Long deposit, Long maxPeople, Long postId){ @@ -27,4 +40,140 @@ public void createDeal(Account accountNumber, Long deposit, Long maxPeople, Long dealRepository.save(deal); } + + // 거래 성사 + // 1. 거래상태 a인지 확인 + // 2. 수신계좌들 검증 + // 3. 최종 예치금 확인해서 차액만큼 가용금액 플러스 + // 4. 수신자 계좌번호 거래에 묶기 + // 5. 거래상태 b로 변경 + // 6. 거래ID 반환 + @Transactional + public APIResponse completeDeal(DealRequestDTO.completeDeal dto) { + // 거래 검증 및 거래 상태 a 인지 검증 + Deal deal = this.validateDeal(dto.getDealId(), List.of("a")); + + List receiverAccountNumbers = List.of(dto.getReceiverAccountNumbers()); + + // 송신계좌 검증 및 잔액 확인 + // 최종 예치금 - 기존 거래의 예치금 만큼 송신자 계좌의 가용금액을 마이너스 + Account senderAccount = accountService.validateAccount(deal.getSenderAccount().getAccountNumber()); + Long finalDeposit = dto.getDeposit() - deal.getDeposit(); + if (senderAccount.getAvailableBudget() < finalDeposit) { + throw new IllegalArgumentException("잔액이 부족합니다."); + } + senderAccount.setAvailableBudget(senderAccount.getAvailableBudget() - finalDeposit); + deal.setDeposit(dto.getDeposit()); + + // 수신자 계좌번호 검증하면서 DealReceiver로 만들어서 저장 + for (String receiverAccountNumber : receiverAccountNumbers) { + dealReceiverRepository.save( + DealReceiver.builder() + .deal(deal) + .receiverAccount(accountService.validateAccount(receiverAccountNumber)) + .build() + ); + } + + // 거래상태 b로 변경 + deal.setStatus("b"); + + return APIResponse.of(SuccessCode.INSERT_SUCCESS, dealRepository.save(deal).getId()); + } + + // 거래ID로 존재여부 + 거래상태 검증 + private Deal validateDeal(Long dealId, List status) { + Deal deal = dealRepository.findById(dealId) + .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 거래입니다.")); + + if (!status.contains(deal.getStatus())) { + throw new IllegalArgumentException("거래 상태가 올바르지 않습니다."); + } + + return deal; + } + + // 거래 수정 + // 1. 거래 ID 확인 + // 2. 송금자 계좌 검증 + // 3. 비밀번호 검증 + // 4. 거래 상태 확인 + // 5. 예치금 수정 및 송신자 계좌에 가용금액 플러스 + // 6. 거래 인원수 수정 + // 7. b일 경우 dealReceiver 삭제 + // 8. 거래 상태 a로 변경 + // 9. 거래ID 반환 + @Transactional + public APIResponse updateDeal(DealRequestDTO.updateDeal dto) { + // 거래ID로 존재여부 + 거래상태 검증 + Deal deal = this.validateDeal(dto.getDealId(), List.of("a", "b")); + + // 송금자 계좌 검증 + Account senderAccount = accountService.validateAccount(dto.getSenderAccountNumber()); + + // 비밀번호 검증 + if (!passwordEncoder.matches(dto.getPassword(), senderAccount.getPassword())) { + throw new IllegalArgumentException("비밀번호가 올바르지 않습니다."); + } + + // 거래 상태 확인 + if (deal.getStatus().equals("b")) { + // 거래 상태가 b일 경우 dealReceiver 삭제 + dealReceiverRepository.deleteByDeal(deal); + } + + // 예치금 수정 및 송신자 계좌에 가용금액 마이너스 + Long finalDeposit = dto.getDeposit() - deal.getDeposit(); + if (senderAccount.getAvailableBudget() < finalDeposit) { + throw new IllegalArgumentException("잔액이 부족합니다."); + } + senderAccount.setAvailableBudget(senderAccount.getAvailableBudget() - finalDeposit); + deal.setDeposit(dto.getDeposit()); + + // 거래 인원수 수정 + deal.setNumberOfPeople(dto.getNumberOfPeople()); + + // 거래 상태 a로 변경 + deal.setStatus("a"); + + return APIResponse.of(SuccessCode.UPDATE_SUCCESS, dealRepository.save(deal).getId()); + } + + // 거래 취소 + // 1. 거래 ID 확인 + // 2. 송금자 계좌 검증 + // 3. 비밀번호 검증 + // 4. 거래 상태 확인 + // 5. 거래 상태 b일 경우 dealReceiver 삭제 + // 6. 송신자 계좌에 가용금액 플러스 + // 7. 거래 상태 c로 변경 + // 8. 거래ID 반환 + @Transactional + public APIResponse cancelDeal(DealRequestDTO.cancelDeal dto) { + // 거래ID로 존재여부 + 거래상태 검증 + Deal deal = this.validateDeal(dto.getDealId(), List.of("a", "b")); + + // 송금자 계좌 검증 + Account senderAccount = accountService.validateAccount(dto.getSenderAccountNumber()); + + // 비밀번호 검증 + if (!passwordEncoder.matches(dto.getPassword(), senderAccount.getPassword())) { + throw new IllegalArgumentException("비밀번호가 올바르지 않습니다."); + } + + // 거래 상태 확인 + if (deal.getStatus().equals("b")) { + // 거래 상태가 b일 경우 dealReceiver 삭제 + // 근데 삭제해야 하나? + dealReceiverRepository.deleteByDeal(deal); + } + + // 송신자 계좌에 가용금액 플러스 + senderAccount.setAvailableBudget(senderAccount.getAvailableBudget() + deal.getDeposit()); + + // 거래 상태 c로 변경 + deal.setStatus("c"); + + return APIResponse.of(SuccessCode.DELETE_SUCCESS, dealRepository.save(deal).getId()); + } }