@Transactional
public AccountWithdrawResponseDto 계좌출금(AccountWithdrawRequestDto accountWithdrawRequestDto, Long userId) {
// 1. 0원 체크
if (accountWithdrawRequestDto.getAmount() <= 0L) {
throw new CustomApiException("0원 이하의 금액을 입금할 수 없습니다. ");
}
// 2. 출금계좌 확인
Account withdrawAccountPS = accountRepository.findByNumber(accountWithdrawRequestDto.getNumber()).orElseThrow(
() -> new CustomApiException("계좌를 찾을 수 없습니다. "));
// 3. 출금 소유자 확인(로그인한 사람과 비교)
withdrawAccountPS.checkOwner(userId);
// 4. 출금 비밀번호 확인
withdrawAccountPS.checkSamePassword(accountWithdrawRequestDto.getPassword());
// 5. 출금계좌 잔액 확인
withdrawAccountPS.checkBalance(accountWithdrawRequestDto.getAmount());
// 6. 출금하기
withdrawAccountPS.withdraw(accountWithdrawRequestDto.getAmount());
// 7. 거래내역 남기기
Transaction transaction = Transaction.builder()
.withdrawAccount(withdrawAccountPS)
.depositAccount(null)
.withdrawAccountBalance(withdrawAccountPS.getBalance())
.depositAccountBalance(null)
.amount(accountWithdrawRequestDto.getAmount())
.gubun(TransactionEnum.WITHDRAW)
.sender(accountWithdrawRequestDto.getNumber() + "")
.receiver("ATM")
.build();
Transaction transactionPS = transactionRepository.save(transaction);
// 8. DTO
return new AccountWithdrawResponseDto(withdrawAccountPS, transactionPS);
}
@Getter
@Setter
public static class AccountWithdrawRequestDto {
@NotNull
@Digits(integer = 4, fraction = 4)
private Long number;
@NotNull
@Digits(integer = 4, fraction = 4)
private Long password;
@NotNull
private Long amount;
@NotEmpty
@Pattern(regexp = "WITHDRAW")
private String gubun;
}
.
.
.
@Getter
@Setter
public static class AccountWithdrawResponseDto {
private Long id; // 계좌 ID
private Long number; // 계좌번호
private Long balance; // 잔약
private TransactionDto transaction;
public AccountWithdrawResponseDto(Account account, Transaction transaction) {
this.id = account.getId();
this.number = account.getNumber();
this.balance = account.getBalance();
this.transaction = new TransactionDto(transaction);
}
@Getter
@Setter
public class TransactionDto {
private Long id;
private String gubun;
private String sender;
private String receiver;
private Long amount;
private String tel;
private String createdAt;
public TransactionDto(Transaction transaction) {
this.id = transaction.getId();
this.gubun = transaction.getGubun().getValue();
this.sender = transaction.getSender();
this.receiver = transaction.getReceiver();
this.amount = transaction.getAmount();
this.createdAt = CustomDateUtil.toStringFormat(transaction.getCreateAt());
}
}
}
package shop.mtcoding.bank.domain.account;
import java.time.LocalDateTime;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EntityListeners;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Index;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import shop.mtcoding.bank.domain.user.User;
import shop.mtcoding.bank.handler.ex.CustomApiException;
@NoArgsConstructor
@Getter
@EntityListeners(AuditingEntityListener.class)
@Table(name = "account_tb", indexes = {
@Index(name = "idx_account_number", columnList = "number")
})
@Entity
public class Account {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(unique = true, nullable = false, length = 4)
private Long number; // 계좌변호
@Column(nullable = false, length = 4)
private Long password; // 계좌비번
@Column(nullable = false)
private Long balance; // 잔액(기본 1000원)
@ManyToOne(fetch = FetchType.LAZY)
private User user;
@CreatedDate
@Column(nullable = false)
private LocalDateTime createAt;
@LastModifiedDate
@Column(nullable = false)
private LocalDateTime updateAt;
@Builder
public Account(Long id, Long number, Long password, Long balance, User user, LocalDateTime createAt,
LocalDateTime updateAt) {
this.id = id;
this.number = number;
this.password = password;
this.balance = balance;
this.user = user;
this.createAt = createAt;
this.updateAt = updateAt;
}
public void checkOwner(Long userId) {
if (user.getId() != userId) { // lazy -> id는 조회 가능, SELECT 쿼리 X
throw new CustomApiException("계좌 소유자가 아닙니다. ");
}
}
public void deposit(Long amount) {
balance += amount;
}
public void checkSamePassword(Long password) {
if (this.password.longValue() != password.longValue()) {
throw new CustomApiException("계좌 비밀번호 검증에 실패하였습니다. ");
}
}
public void checkBalance(Long amount) {
if (this.balance < amount) {
throw new CustomApiException("계좌 잔액이 부족합니다. ");
}
}
public void withdraw(Long amount) {
checkBalance(amount); // 여기 주목!! 체크를 1번 더 하는 것!
balance -= amount;
}
}
주목할 점 : long 비교시 longValue()로 비교
@PostMapping("/s/account/withdraw")
public ResponseEntity<?> withdrawAccount(@RequestBody @Valid AccountWithdrawRequestDto accountWithdrawRequestDto,
BindingResult bindingResult,
@AuthenticationPrincipal LoginUser loginUser) {
AccountWithdrawResponseDto accountWithdrawResponseDto = accountService.계좌출금(accountWithdrawRequestDto,
loginUser.getUser().getId());
return new ResponseEntity<>(new ResponseDto<>(1, "계좌출금 성공", accountWithdrawResponseDto), HttpStatus.CREATED);
}