Skip to content

Commit 59bd180

Browse files
authored
Merge pull request #41 from HANA-IEUM/refactor/transaction-transfer-40
refactor: TransferService 이체 시스템 전반적 리팩토링 (#40)
2 parents b91a43e + bfab5e9 commit 59bd180

8 files changed

Lines changed: 102 additions & 114 deletions

File tree

src/main/java/com/hanaieum/server/domain/account/service/AccountService.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ Account createMoneyBoxForBucketList(BucketList bucketList, Member member, String
2626

2727
// === 계좌 조회 메서드 ===
2828
MainAccountResponse getMainAccount(Member member);
29-
Account getMainAccountByMemberId(Long memberId);
29+
Long getMainAccountIdByMemberId(Long memberId);
3030
Account findMainAccountByMember(Member member); // 추가: Member 객체로 주계좌 조회
3131
Account findById(Long accountId);
3232
Account findByIdWithLock(Long accountId);

src/main/java/com/hanaieum/server/domain/account/service/AccountServiceImpl.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -197,16 +197,16 @@ public MainAccountResponse getMainAccount(Member member) {
197197

198198
@Override
199199
@Transactional(readOnly = true)
200-
public Account getMainAccountByMemberId(Long memberId) {
200+
public Long getMainAccountIdByMemberId(Long memberId) {
201201
Member member = memberRepository.findById(memberId)
202202
.orElseThrow(() -> new CustomException(ErrorCode.MEMBER_NOT_FOUND));
203203

204204
Account mainAccount = accountRepository.findByMemberAndAccountTypeAndDeletedFalse(member, AccountType.MAIN)
205205
.orElseThrow(() -> new CustomException(ErrorCode.ACCOUNT_NOT_FOUND));
206206

207-
log.info("주계좌 조회 완료 - 회원 ID: {}, 계좌 ID: {}", memberId, mainAccount.getId());
207+
log.info("주계좌 ID 조회 완료 - 회원 ID: {}, 계좌 ID: {}", memberId, mainAccount.getId());
208208

209-
return mainAccount;
209+
return mainAccount.getId();
210210
}
211211

212212
@Override

src/main/java/com/hanaieum/server/domain/bucketList/service/BucketListServiceImpl.java

Lines changed: 22 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -530,23 +530,16 @@ public void deleteBucketList(Long bucketListId) {
530530

531531
// 진행중인 버킷리스트인 경우 머니박스의 잔액을 주계좌로 반환
532532
if (bucketList.getStatus() == BucketListStatus.IN_PROGRESS) {
533-
BigDecimal moneyBoxBalance = moneyBoxAccount.getBalance();
533+
// 머니박스 → 주계좌 전액 인출
534+
BigDecimal withdrawnAmount = transferService.withdrawAllFromMoneyBox(
535+
member.getId(),
536+
moneyBoxAccount.getId(),
537+
ReferenceType.MONEY_BOX_WITHDRAW,
538+
bucketListId
539+
);
534540

535-
if (moneyBoxBalance.compareTo(BigDecimal.ZERO) > 0) {
536-
// 주계좌 조회
537-
Account mainAccount = accountService.findMainAccountByMember(member);
538-
539-
// 머니박스 → 주계좌 이체
540-
transferService.transferBetweenAccounts(
541-
moneyBoxAccount.getId(),
542-
mainAccount.getId(),
543-
moneyBoxBalance,
544-
ReferenceType.MONEY_BOX_WITHDRAW,
545-
bucketListId
546-
);
547-
548-
log.info("버킷리스트 삭제로 인한 머니박스 잔액 이체 완료: {} → 주계좌, 금액: {}", moneyBoxAccount.getId(), moneyBoxBalance);
549-
}
541+
log.info("버킷리스트 삭제로 인한 머니박스 잔액 인출 완료: {} → 주계좌, 인출금액: {}",
542+
moneyBoxAccount.getId(), withdrawnAmount);
550543
}
551544

552545
// 머니박스 계좌 삭제 (Soft Delete)
@@ -619,51 +612,30 @@ public BucketListResponse completeBucketList(Long bucketListId) {
619612
Account moneyBoxAccount = bucketList.getMoneyBoxAccount();
620613

621614
if (moneyBoxAccount != null) {
622-
BigDecimal moneyBoxBalance = moneyBoxAccount.getBalance();
623-
624615
// 1. 머니박스 → 주계좌로 원금 인출
625-
if (moneyBoxBalance.compareTo(BigDecimal.ZERO) > 0) {
626-
transferService.transferBetweenAccounts(
627-
moneyBoxAccount.getId(),
628-
mainAccount.getId(),
629-
moneyBoxBalance,
630-
ReferenceType.MONEY_BOX_WITHDRAW,
631-
bucketListId
632-
);
633-
log.info("목표 달성 원금 인출 완료: 머니박스 {} → 주계좌, 금액: {}",
634-
moneyBoxAccount.getId(), moneyBoxBalance);
635-
}
616+
BigDecimal withdrawnAmount = transferService.withdrawAllFromMoneyBox(
617+
member.getId(),
618+
moneyBoxAccount.getId(),
619+
ReferenceType.MONEY_BOX_WITHDRAW,
620+
bucketListId
621+
);
622+
log.info("목표 달성 원금 인출 완료: 머니박스 {} → 주계좌, 인출금액: {}",
623+
moneyBoxAccount.getId(), withdrawnAmount);
636624

637625
// 2. 이자 계산 및 지급 (목표금액 한도 내에서만)
638626
BigDecimal interestRate = calculateInterestRate(bucketList.getTargetMonth(), mainAccount);
639627

640-
// 이자 계산 기준 금액은 목표금액과 실제 적립금 중 작은 금액
628+
// 이자 계산 기준 금액은 목표금액과 실제 인출금액 중 작은 금액
641629
BigDecimal targetAmount = bucketList.getTargetAmount();
642-
BigDecimal interestBaseAmount = moneyBoxBalance.min(targetAmount);
630+
BigDecimal interestBaseAmount = withdrawnAmount.min(targetAmount);
643631
BigDecimal interest = interestBaseAmount.multiply(interestRate).setScale(0, RoundingMode.DOWN); // 원단위 절사 처리
644632

645-
log.info("이자 계산: 적립금 = {}, 목표금액 = {}, 이자 기준금액 = {}, 이자율 = {}%, 계산된 이자 = {}",
646-
moneyBoxBalance, targetAmount, interestBaseAmount,
633+
log.info("이자 계산: 인출금액 = {}, 목표금액 = {}, 이자 기준금액 = {}, 이자율 = {}%, 계산된 이자 = {}",
634+
withdrawnAmount, targetAmount, interestBaseAmount,
647635
interestRate.multiply(BigDecimal.valueOf(100)), interest);
648636

649637
if (interest.compareTo(BigDecimal.ZERO) > 0) {
650-
// 이자 입금 거래 기록 (상대방: 시스템/은행)
651-
transactionService.recordDeposit(
652-
mainAccount,
653-
interest,
654-
null, // 상대방 계좌 없음 (은행에서 지급)
655-
"하나이음", // 상대방 이름
656-
ReferenceType.MONEY_BOX_INTEREST,
657-
bucketListId
658-
);
659-
660-
// 실제 주계좌 잔액에 이자 추가
661-
accountService.creditBalance(mainAccount.getId(), interest);
662-
663-
log.info("목표 달성 이자 지급 완료: 주계좌 {}, 이자: {} (기준금액: {}, 이자율: {}%)",
664-
mainAccount.getId(), interest, interestBaseAmount, interestRate.multiply(BigDecimal.valueOf(100)));
665-
} else {
666-
log.info("지급할 이자 없음: 기준금액 = {}", interestBaseAmount);
638+
transferService.payInterest(member.getId(), interest, bucketListId);
667639
}
668640

669641
// 3. 머니박스 계좌 삭제 (Soft Delete)

src/main/java/com/hanaieum/server/domain/support/service/SupportServiceImpl.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,6 @@ private void processSponsorshipTransfer(Member supporter, BucketList bucketList,
9292
}
9393

9494
// 이체 실행 (후원자 주계좌 → 버킷리스트 머니박스)
95-
// TransferService.sponsorBucket(sponsorMemberId, bucketId, amount, password)
9695
transferService.sponsorBucket(
9796
supporter.getId(),
9897
bucketList.getId(),

src/main/java/com/hanaieum/server/domain/transaction/service/TransactionService.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ void recordTransfer(Account fromAccount, Account toAccount, BigDecimal amount,
1515

1616
// 이자 입금을 위한 메소드
1717
void recordDeposit(Account toAccount, BigDecimal amount, Long counterpartyAccountId,
18-
String counterpartyName, ReferenceType referenceType, Long referenceId);
18+
String counterpartyName, ReferenceType referenceType, String description, Long referenceId);
1919

2020
Page<TransactionResponse> getTransactionsByAccountId(Long memberId, Long accountId, Pageable pageable);
2121
}

src/main/java/com/hanaieum/server/domain/transaction/service/TransactionServiceImpl.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ public void recordTransfer(Account fromAccount, Account toAccount, BigDecimal am
6565

6666
@Override
6767
public void recordDeposit(Account toAccount, BigDecimal amount, Long counterpartyAccountId,
68-
String counterpartyName, ReferenceType referenceType, Long referenceId) {
68+
String counterpartyName, ReferenceType referenceType, String description, Long referenceId) {
6969

7070
// 입금 레코드 생성
7171
Transaction depositTx = Transaction.builder()
@@ -75,7 +75,7 @@ public void recordDeposit(Account toAccount, BigDecimal amount, Long counterpart
7575
.balanceAfter(toAccount.getBalance()) // credit 이후 값
7676
.counterpartyAccountId(counterpartyAccountId)
7777
.counterpartyName(counterpartyName)
78-
.description(referenceType.getDescription())
78+
.description(description)
7979
.referenceType(referenceType)
8080
.referenceId(referenceId)
8181
.build();

src/main/java/com/hanaieum/server/domain/transfer/service/TransferService.java

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,8 @@ public interface TransferService {
99

1010
void sponsorBucket(Long sponsorMemberId, Long bucketId, BigDecimal amount, String password);
1111

12-
/**
13-
* 계좌 간 직접 이체 (내부 시스템용)
14-
* @param fromAccountId 출금 계좌 ID
15-
* @param toAccountId 입금 계좌 ID
16-
* @param amount 이체 금액
17-
* @param referenceType 거래 참조 유형 (description은 내부에서 getDescription() 사용)
18-
* @param referenceId 참조 ID (버킷리스트 ID 등)
19-
*/
20-
void transferBetweenAccounts(Long fromAccountId, Long toAccountId, BigDecimal amount,
21-
ReferenceType referenceType, Long referenceId);
22-
12+
void payInterest(Long memberId, BigDecimal interestAmount, Long bucketListId);
13+
14+
BigDecimal withdrawAllFromMoneyBox(Long memberId, Long moneyBoxAccountId, ReferenceType referenceType, Long referenceId);
15+
2316
}

src/main/java/com/hanaieum/server/domain/transfer/service/TransferServiceImpl.java

Lines changed: 69 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -29,20 +29,22 @@ public class TransferServiceImpl implements TransferService {
2929
public void fillMoneyBox(Long memberId, Long moneyBoxAccountId, BigDecimal amount, String password) {
3030
log.info("머니박스 채우기 시작 - 회원 ID: {}, 머니박스: {}, 금액: {}", memberId, moneyBoxAccountId, amount);
3131

32-
// 1. 회원의 주계좌 조회
33-
Account fromAccount = accountService.getMainAccountByMemberId(memberId);
32+
// 1. 회원의 주계좌 ID 조회
33+
Long fromAccountId = accountService.getMainAccountIdByMemberId(memberId);
3434

3535
// 2. 동일한 계좌로 이체하는지 체크
36-
if (fromAccount.getId().equals(moneyBoxAccountId)) {
36+
if (fromAccountId.equals(moneyBoxAccountId)) {
3737
throw new CustomException(ErrorCode.INVALID_TRANSFER_SAME_ACCOUNT);
3838
}
3939

4040
// 3. 머니박스 계좌 소유권 검증
4141
accountService.validateAccountOwnership(moneyBoxAccountId, memberId);
4242

43-
// 4. 이체 실행
44-
executeTransfer(fromAccount, moneyBoxAccountId, amount, password,
45-
ReferenceType.MONEY_BOX_DEPOSIT, null);
43+
// 4. 비밀번호 검증
44+
accountService.validateAccountPassword(fromAccountId, password);
45+
46+
// 5. 이체 실행
47+
executeTransfer(fromAccountId, moneyBoxAccountId, amount, ReferenceType.MONEY_BOX_DEPOSIT, null);
4648

4749
log.info("머니박스 채우기 완료 - 회원 ID: {}, 머니박스: {}, 금액: {}", memberId, moneyBoxAccountId, amount);
4850
}
@@ -51,64 +53,60 @@ public void fillMoneyBox(Long memberId, Long moneyBoxAccountId, BigDecimal amoun
5153
public void sponsorBucket(Long sponsorMemberId, Long bucketId, BigDecimal amount, String password) {
5254
log.info("버킷 후원 시작 - 후원자 ID: {}, 버킷 ID: {}, 금액: {}", sponsorMemberId, bucketId, amount);
5355

54-
// 1. 후원자의 주계좌 조회
55-
Account fromAccount = accountService.getMainAccountByMemberId(sponsorMemberId);
56+
// 1. 후원자의 주계좌 ID 조회
57+
Long fromAccountId = accountService.getMainAccountIdByMemberId(sponsorMemberId);
5658

5759
// 2. 버킷리스트 ID로 머니박스 계좌 조회
5860
Long moneyBoxAccountId = getMoneyBoxAccountIdByBucketId(bucketId);
5961

60-
// 3. 이체 실행 (후원은 소유권 검증 없음)
61-
executeTransfer(fromAccount, moneyBoxAccountId, amount, password,
62-
ReferenceType.BUCKET_FUNDING, bucketId);
62+
// 3. 비밀번호 검증
63+
accountService.validateAccountPassword(fromAccountId, password);
64+
65+
// 4. 이체 실행
66+
executeTransfer(fromAccountId, moneyBoxAccountId, amount, ReferenceType.BUCKET_FUNDING, bucketId);
6367

6468
log.info("버킷 후원 완료 - 후원자 ID: {}, 버킷 ID: {}, 금액: {}", sponsorMemberId, bucketId, amount);
6569
}
6670

6771
@Override
68-
public void transferBetweenAccounts(Long fromAccountId, Long toAccountId, BigDecimal amount,
69-
ReferenceType referenceType, Long referenceId) {
70-
log.info("내부 시스템 이체 시작 - 출금: {}, 입금: {}, 금액: {}, 참조: {}", fromAccountId, toAccountId, amount, referenceType);
71-
72-
// 1. 계좌 조회 (잠금 적용)
73-
Account fromAccount = accountService.findByIdWithLock(fromAccountId);
74-
Account toAccount = accountService.findByIdWithLock(toAccountId);
72+
public BigDecimal withdrawAllFromMoneyBox(Long memberId, Long moneyBoxAccountId, ReferenceType referenceType, Long referenceId) {
73+
log.info("머니박스 전액 인출 시작 - 회원 ID: {}, 머니박스: {}, 참조: {}", memberId, moneyBoxAccountId, referenceType);
7574

76-
// 2. 잔액 검증
77-
if (fromAccount.getBalance().compareTo(amount) < 0) {
78-
throw new CustomException(ErrorCode.INSUFFICIENT_BALANCE);
75+
// 1. 회원의 주계좌 ID 조회
76+
Long mainAccountId = accountService.getMainAccountIdByMemberId(memberId);
77+
78+
// 2. 머니박스 계좌 조회 및 잔액 확인
79+
Account moneyBoxAccount = accountService.findByIdWithLock(moneyBoxAccountId);
80+
BigDecimal balance = moneyBoxAccount.getBalance();
81+
82+
// 3. 잔액이 0보다 클 때만 이체 실행
83+
if (balance.compareTo(BigDecimal.ZERO) > 0) {
84+
executeTransfer(moneyBoxAccountId, mainAccountId, balance, referenceType, referenceId);
85+
log.info("머니박스 전액 인출 완료 - 회원 ID: {}, 머니박스: {} → 주계좌: {}, 인출금액: {}",
86+
memberId, moneyBoxAccountId, mainAccountId, balance);
87+
return balance;
88+
} else {
89+
log.info("머니박스 전액 인출 완료 - 회원 ID: {}, 머니박스: {}, 잔액이 0이므로 이체하지 않음",
90+
memberId, moneyBoxAccountId);
91+
return BigDecimal.ZERO;
7992
}
80-
81-
// 3. 계좌 잔액 업데이트
82-
accountService.debitBalance(fromAccountId, amount);
83-
accountService.creditBalance(toAccountId, amount);
84-
85-
// 4. 거래 내역 기록 (이중 기입 방식)
86-
transactionService.recordTransfer(fromAccount, toAccount, amount, referenceType, referenceType.getDescription(), referenceId);
87-
88-
log.info("내부 시스템 이체 완료 - 출금: {}, 입금: {}, 금액: {}", fromAccountId, toAccountId, amount);
8993
}
9094

91-
private void executeTransfer(Account fromAccount, Long toAccountId, BigDecimal amount,
92-
String password, ReferenceType referenceType, Long referenceId) {
93-
// 1. 비밀번호 검증
94-
accountService.validateAccountPassword(fromAccount.getId(), password);
95-
96-
// 2. 잔액 검증
97-
accountService.validateSufficientBalance(fromAccount.getId(), amount);
98-
99-
// 3. 계좌 조회 (락 걸기)
100-
Account lockedFromAccount = accountService.findByIdWithLock(fromAccount.getId());
95+
private void executeTransfer(Long fromAccountId, Long toAccountId, BigDecimal amount,
96+
ReferenceType referenceType, Long referenceId) {
97+
// 1. 계좌 조회 (락 걸기)
98+
Account fromAccount = accountService.findByIdWithLock(fromAccountId);
10199
Account toAccount = accountService.findByIdWithLock(toAccountId);
102100

103-
// 4. 출금
104-
accountService.debitBalance(lockedFromAccount.getId(), amount);
101+
// 2. 출금 (잔액 검증도 처리)
102+
accountService.debitBalance(fromAccount.getId(), amount);
105103

106-
// 5. 입금
104+
// 3. 입금
107105
accountService.creditBalance(toAccount.getId(), amount);
108106

109-
// 6. 거래내역 2건 생성
107+
// 4. 거래내역 2건 생성
110108
transactionService.recordTransfer(
111-
lockedFromAccount,
109+
fromAccount,
112110
toAccount,
113111
amount,
114112
referenceType,
@@ -117,6 +115,31 @@ private void executeTransfer(Account fromAccount, Long toAccountId, BigDecimal a
117115
);
118116
}
119117

118+
@Override
119+
public void payInterest(Long memberId, BigDecimal interestAmount, Long bucketListId) {
120+
log.info("이자 지급 시작 - 회원 ID: {}, 이자: {}, 버킷리스트 ID: {}", memberId, interestAmount, bucketListId);
121+
122+
// 1. 회원의 주계좌 조회
123+
Long mainAccountId = accountService.getMainAccountIdByMemberId(memberId);
124+
Account mainAccount = accountService.findByIdWithLock(mainAccountId);
125+
126+
// 2. 실제 주계좌 잔액에 이자 추가
127+
accountService.creditBalance(mainAccount.getId(), interestAmount);
128+
129+
// 3. 이자 거래 기록 생성 (상대방: 하나이음)
130+
transactionService.recordDeposit(
131+
mainAccount,
132+
interestAmount,
133+
null, // 상대방 계좌 없음 (은행에서 지급)
134+
"하나이음", // 상대방 이름
135+
ReferenceType.MONEY_BOX_INTEREST,
136+
ReferenceType.MONEY_BOX_INTEREST.getDescription(),
137+
bucketListId
138+
);
139+
140+
log.info("목표 달성 이자 지급 완료: 주계좌 {}, 이자: {}", mainAccount.getId(), interestAmount);
141+
}
142+
120143
private Long getMoneyBoxAccountIdByBucketId(Long bucketId) {
121144
// 1. 버킷리스트 조회 -> 머니박스 계좌 ID 조회
122145
BucketList bucketList = bucketListRepository.findByIdAndDeletedFalse(bucketId)
@@ -132,4 +155,5 @@ private Long getMoneyBoxAccountIdByBucketId(Long bucketId) {
132155

133156
return accountId;
134157
}
158+
135159
}

0 commit comments

Comments
 (0)