-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #15 from woowa-techcamp-2024/feature/3_Hyeon-Uk_구매…
…자_페이먼트_충전 구매자 페이먼트 충전 메인 머지
- Loading branch information
Showing
26 changed files
with
940 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
5 changes: 5 additions & 0 deletions
5
src/main/java/camp/woowak/lab/payaccount/domain/AccountTransactionType.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
package camp.woowak.lab.payaccount.domain; | ||
|
||
public enum AccountTransactionType { | ||
DEPOSIT, WITHDRAW, CHARGE; | ||
} |
94 changes: 94 additions & 0 deletions
94
src/main/java/camp/woowak/lab/payaccount/domain/PayAccount.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,13 +1,107 @@ | ||
package camp.woowak.lab.payaccount.domain; | ||
|
||
import java.time.LocalDateTime; | ||
import java.util.ArrayList; | ||
import java.util.List; | ||
|
||
import org.hibernate.annotations.ColumnDefault; | ||
|
||
import camp.woowak.lab.payaccount.exception.DailyLimitExceededException; | ||
import camp.woowak.lab.payaccount.exception.InsufficientBalanceException; | ||
import camp.woowak.lab.payaccount.exception.InvalidTransactionAmountException; | ||
import jakarta.persistence.CascadeType; | ||
import jakarta.persistence.Column; | ||
import jakarta.persistence.Entity; | ||
import jakarta.persistence.FetchType; | ||
import jakarta.persistence.GeneratedValue; | ||
import jakarta.persistence.GenerationType; | ||
import jakarta.persistence.Id; | ||
import jakarta.persistence.OneToMany; | ||
import lombok.extern.slf4j.Slf4j; | ||
|
||
@Entity | ||
@Slf4j | ||
public class PayAccount { | ||
@Id | ||
@GeneratedValue(strategy = GenerationType.IDENTITY) | ||
private Long id; | ||
|
||
@Column(name = "balance", nullable = false) | ||
@ColumnDefault("0") | ||
private long balance; | ||
|
||
@OneToMany(mappedBy = "payAccount", fetch = FetchType.LAZY, cascade = CascadeType.PERSIST) | ||
private List<PayAccountHistory> history = new ArrayList<>(); | ||
|
||
public PayAccount() { | ||
this.balance = 0; | ||
} | ||
|
||
public Long getId() { | ||
return id; | ||
} | ||
|
||
public long getBalance() { | ||
return this.balance; | ||
} | ||
|
||
public PayAccountHistory withdraw(long amount) { | ||
validateTransactionAmount(amount); | ||
validateInsufficientBalance(amount); | ||
this.balance -= amount; | ||
|
||
return issueAndSavePayAccountHistory(amount, AccountTransactionType.WITHDRAW); | ||
} | ||
|
||
public PayAccountHistory deposit(long amount) { | ||
validateTransactionAmount(amount); | ||
this.balance += amount; | ||
|
||
return issueAndSavePayAccountHistory(amount, AccountTransactionType.DEPOSIT); | ||
} | ||
|
||
public PayAccountHistory charge(long amount) { | ||
validateTransactionAmount(amount); | ||
validateDailyChargeLimit(amount); | ||
this.balance += amount; | ||
|
||
return issueAndSavePayAccountHistory(amount, AccountTransactionType.CHARGE); | ||
} | ||
|
||
private PayAccountHistory issueAndSavePayAccountHistory(long amount, AccountTransactionType type) { | ||
PayAccountHistory payAccountHistory = new PayAccountHistory(this, amount, type); | ||
this.history.add(payAccountHistory); | ||
|
||
return payAccountHistory; | ||
} | ||
|
||
private void validateDailyChargeLimit(long amount) { | ||
LocalDateTime startOfDay = LocalDateTime.now().toLocalDate().atStartOfDay(); | ||
LocalDateTime endOfDay = startOfDay.plusDays(1); | ||
|
||
long todayTotalCharge = history.stream() | ||
.filter(h -> h.getType() == AccountTransactionType.CHARGE) | ||
.filter(h -> h.getCreatedAt().isAfter(startOfDay) && h.getCreatedAt().isBefore(endOfDay)) | ||
.mapToLong(PayAccountHistory::getAmount) | ||
.sum(); | ||
|
||
if (todayTotalCharge + amount > 1_000_000) { | ||
log.warn("Daily charge limit of {} exceeded.", 1_000_000); | ||
throw new DailyLimitExceededException(); | ||
} | ||
} | ||
|
||
private void validateTransactionAmount(long amount) { | ||
if (amount <= 0) { | ||
log.warn("Transaction amount must be greater than zero."); | ||
throw new InvalidTransactionAmountException(); | ||
} | ||
} | ||
|
||
private void validateInsufficientBalance(long amount) { | ||
if (this.balance - amount < 0) { | ||
log.warn("Insufficient balance for this transaction."); | ||
throw new InsufficientBalanceException(); | ||
} | ||
} | ||
} |
64 changes: 64 additions & 0 deletions
64
src/main/java/camp/woowak/lab/payaccount/domain/PayAccountHistory.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
package camp.woowak.lab.payaccount.domain; | ||
|
||
import java.time.LocalDateTime; | ||
|
||
import org.springframework.data.annotation.CreatedDate; | ||
import org.springframework.data.jpa.domain.support.AuditingEntityListener; | ||
|
||
import jakarta.persistence.Column; | ||
import jakarta.persistence.Entity; | ||
import jakarta.persistence.EntityListeners; | ||
import jakarta.persistence.EnumType; | ||
import jakarta.persistence.Enumerated; | ||
import jakarta.persistence.GeneratedValue; | ||
import jakarta.persistence.GenerationType; | ||
import jakarta.persistence.Id; | ||
import jakarta.persistence.JoinColumn; | ||
import jakarta.persistence.ManyToOne; | ||
|
||
@Entity | ||
@EntityListeners(value = AuditingEntityListener.class) | ||
public class PayAccountHistory { | ||
@Id | ||
@GeneratedValue(strategy = GenerationType.IDENTITY) | ||
private Long id; | ||
|
||
@ManyToOne | ||
@JoinColumn(name = "account_id", nullable = false) | ||
private PayAccount payAccount; | ||
|
||
@Column | ||
private long amount; | ||
|
||
@Enumerated(value = EnumType.STRING) | ||
private AccountTransactionType type; | ||
|
||
@CreatedDate | ||
@Column(name = "created_at", updatable = false) | ||
private LocalDateTime createdAt = LocalDateTime.now(); | ||
|
||
public PayAccountHistory() { | ||
} | ||
|
||
public PayAccountHistory(PayAccount payAccount, long amount, AccountTransactionType type) { | ||
this.payAccount = payAccount; | ||
this.amount = amount; | ||
this.type = type; | ||
} | ||
|
||
public Long getId() { | ||
return id; | ||
} | ||
|
||
public long getAmount() { | ||
return amount; | ||
} | ||
|
||
public AccountTransactionType getType() { | ||
return type; | ||
} | ||
|
||
public LocalDateTime getCreatedAt() { | ||
return createdAt; | ||
} | ||
} |
9 changes: 9 additions & 0 deletions
9
src/main/java/camp/woowak/lab/payaccount/exception/DailyLimitExceededException.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
package camp.woowak.lab.payaccount.exception; | ||
|
||
import camp.woowak.lab.common.exception.BadRequestException; | ||
|
||
public class DailyLimitExceededException extends BadRequestException { | ||
public DailyLimitExceededException() { | ||
super(PayAccountErrorCode.DAILY_LIMIT_EXCEED); | ||
} | ||
} |
9 changes: 9 additions & 0 deletions
9
src/main/java/camp/woowak/lab/payaccount/exception/InsufficientBalanceException.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
package camp.woowak.lab.payaccount.exception; | ||
|
||
import camp.woowak.lab.common.exception.BadRequestException; | ||
|
||
public class InsufficientBalanceException extends BadRequestException { | ||
public InsufficientBalanceException() { | ||
super(PayAccountErrorCode.INSUFFICIENT_BALANCE); | ||
} | ||
} |
9 changes: 9 additions & 0 deletions
9
src/main/java/camp/woowak/lab/payaccount/exception/InvalidTransactionAmountException.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
package camp.woowak.lab.payaccount.exception; | ||
|
||
import camp.woowak.lab.common.exception.BadRequestException; | ||
|
||
public class InvalidTransactionAmountException extends BadRequestException { | ||
public InvalidTransactionAmountException() { | ||
super(PayAccountErrorCode.INVALID_TRANSACTION_AMOUNT); | ||
} | ||
} |
9 changes: 9 additions & 0 deletions
9
src/main/java/camp/woowak/lab/payaccount/exception/NotFoundAccountException.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
package camp.woowak.lab.payaccount.exception; | ||
|
||
import camp.woowak.lab.common.exception.NotFoundException; | ||
|
||
public class NotFoundAccountException extends NotFoundException { | ||
public NotFoundAccountException() { | ||
super(PayAccountErrorCode.ACCOUNT_NOT_FOUND); | ||
} | ||
} |
37 changes: 37 additions & 0 deletions
37
src/main/java/camp/woowak/lab/payaccount/exception/PayAccountErrorCode.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
package camp.woowak.lab.payaccount.exception; | ||
|
||
import org.springframework.http.HttpStatus; | ||
|
||
import camp.woowak.lab.common.exception.ErrorCode; | ||
|
||
public enum PayAccountErrorCode implements ErrorCode { | ||
INVALID_TRANSACTION_AMOUNT(HttpStatus.BAD_REQUEST, "a_1_1", "금액은 0보다 커야합니다."), | ||
DAILY_LIMIT_EXCEED(HttpStatus.BAD_REQUEST, "a_1_2", "일일 충전 한도 금액을 초과했습니다."), | ||
INSUFFICIENT_BALANCE(HttpStatus.BAD_REQUEST,"a_1_3","금액이 부족합니다."), | ||
ACCOUNT_NOT_FOUND(HttpStatus.NOT_FOUND, "a_1_4", "계좌를 찾을 수 없습니다."); | ||
|
||
private final int status; | ||
private final String errorCode; | ||
private final String message; | ||
|
||
PayAccountErrorCode(HttpStatus status, String errorCode, String message) { | ||
this.status = status.value(); | ||
this.errorCode = errorCode; | ||
this.message = message; | ||
} | ||
|
||
@Override | ||
public int getStatus() { | ||
return status; | ||
} | ||
|
||
@Override | ||
public String getErrorCode() { | ||
return errorCode; | ||
} | ||
|
||
@Override | ||
public String getMessage() { | ||
return message; | ||
} | ||
} |
Empty file.
8 changes: 8 additions & 0 deletions
8
src/main/java/camp/woowak/lab/payaccount/repository/PayAccountHistoryRepository.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
package camp.woowak.lab.payaccount.repository; | ||
|
||
import org.springframework.data.jpa.repository.JpaRepository; | ||
|
||
import camp.woowak.lab.payaccount.domain.PayAccountHistory; | ||
|
||
public interface PayAccountHistoryRepository extends JpaRepository<PayAccountHistory, Long> { | ||
} |
9 changes: 9 additions & 0 deletions
9
src/main/java/camp/woowak/lab/payaccount/repository/PayAccountRepository.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,17 @@ | ||
package camp.woowak.lab.payaccount.repository; | ||
|
||
import java.util.Optional; | ||
|
||
import org.springframework.data.jpa.repository.JpaRepository; | ||
import org.springframework.data.jpa.repository.Lock; | ||
import org.springframework.data.jpa.repository.Query; | ||
import org.springframework.data.repository.query.Param; | ||
|
||
import camp.woowak.lab.payaccount.domain.PayAccount; | ||
import jakarta.persistence.LockModeType; | ||
|
||
public interface PayAccountRepository extends JpaRepository<PayAccount, Long> { | ||
@Lock(LockModeType.PESSIMISTIC_WRITE) | ||
@Query("SELECT pa FROM PayAccount pa LEFT JOIN Customer c on c.payAccount = pa where c.id = :customerId") | ||
Optional<PayAccount> findByCustomerIdForUpdate(@Param("customerId") Long customerId); | ||
} |
Empty file.
39 changes: 39 additions & 0 deletions
39
src/main/java/camp/woowak/lab/payaccount/service/PayAccountChargeService.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
package camp.woowak.lab.payaccount.service; | ||
|
||
import org.springframework.stereotype.Service; | ||
import org.springframework.transaction.annotation.Transactional; | ||
|
||
import camp.woowak.lab.payaccount.domain.PayAccount; | ||
import camp.woowak.lab.payaccount.domain.PayAccountHistory; | ||
import camp.woowak.lab.payaccount.exception.NotFoundAccountException; | ||
import camp.woowak.lab.payaccount.repository.PayAccountRepository; | ||
import camp.woowak.lab.payaccount.service.command.PayAccountChargeCommand; | ||
import lombok.extern.slf4j.Slf4j; | ||
|
||
@Service | ||
@Slf4j | ||
public class PayAccountChargeService { | ||
private final PayAccountRepository payAccountRepository; | ||
|
||
public PayAccountChargeService(PayAccountRepository payAccountRepository) { | ||
this.payAccountRepository = payAccountRepository; | ||
} | ||
|
||
/** | ||
* @throws camp.woowak.lab.payaccount.exception.NotFoundAccountException 계좌를 찾지 못함. 존재하지 않는 계좌 | ||
* @throws camp.woowak.lab.payaccount.exception.DailyLimitExceededException 일일 충전 한도를 초과함 | ||
*/ | ||
@Transactional | ||
public long chargeAccount(PayAccountChargeCommand command) { | ||
PayAccount payAccount = payAccountRepository.findByCustomerIdForUpdate(command.customerId()) | ||
.orElseThrow(() -> { | ||
log.warn("Invalid account id with {}", command.customerId()); | ||
throw new NotFoundAccountException(); | ||
}); | ||
|
||
PayAccountHistory chargeHistory = payAccount.charge(command.amount()); | ||
log.info("A Charge of {} has been completed from Account ID {}", command.amount(), payAccount.getId()); | ||
|
||
return payAccount.getBalance(); | ||
} | ||
} |
6 changes: 6 additions & 0 deletions
6
src/main/java/camp/woowak/lab/payaccount/service/command/PayAccountChargeCommand.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
package camp.woowak.lab.payaccount.service.command; | ||
|
||
public record PayAccountChargeCommand( | ||
Long customerId, | ||
long amount) { | ||
} |
47 changes: 47 additions & 0 deletions
47
src/main/java/camp/woowak/lab/web/api/payaccount/PayAccountApiController.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
package camp.woowak.lab.web.api.payaccount; | ||
|
||
import org.springframework.http.HttpStatus; | ||
import org.springframework.http.ResponseEntity; | ||
import org.springframework.validation.annotation.Validated; | ||
import org.springframework.web.bind.annotation.PostMapping; | ||
import org.springframework.web.bind.annotation.RequestBody; | ||
import org.springframework.web.bind.annotation.RequestMapping; | ||
import org.springframework.web.bind.annotation.RestController; | ||
|
||
import camp.woowak.lab.payaccount.service.PayAccountChargeService; | ||
import camp.woowak.lab.payaccount.service.command.PayAccountChargeCommand; | ||
import camp.woowak.lab.web.api.utils.APIResponse; | ||
import camp.woowak.lab.web.api.utils.APIUtils; | ||
import camp.woowak.lab.web.authentication.LoginCustomer; | ||
import camp.woowak.lab.web.authentication.annotation.AuthenticationPrincipal; | ||
import camp.woowak.lab.web.dto.request.payaccount.PayAccountChargeRequest; | ||
import camp.woowak.lab.web.dto.response.payaccount.PayAccountChargeResponse; | ||
import lombok.extern.slf4j.Slf4j; | ||
|
||
@RestController | ||
@RequestMapping("/account") | ||
@Slf4j | ||
public class PayAccountApiController { | ||
private final PayAccountChargeService payAccountChargeService; | ||
|
||
public PayAccountApiController(PayAccountChargeService payAccountChargeService) { | ||
this.payAccountChargeService = payAccountChargeService; | ||
} | ||
|
||
/** | ||
* TODO 1. api end-point 설계 논의 | ||
*/ | ||
@PostMapping("/charge") | ||
public ResponseEntity<APIResponse<PayAccountChargeResponse>> payAccountCharge( | ||
@AuthenticationPrincipal LoginCustomer loginCustomer, | ||
@Validated @RequestBody PayAccountChargeRequest request) { | ||
PayAccountChargeCommand command = new PayAccountChargeCommand(loginCustomer.getId(), request.amount()); | ||
log.info("Pay account charge request received. Account Owner ID: {}, Charge Amount: {}", loginCustomer.getId(), | ||
request.amount()); | ||
|
||
long remainBalance = payAccountChargeService.chargeAccount(command); | ||
log.info("Charge successful. Account Owner ID: {}, New Balance: {}", loginCustomer.getId(), remainBalance); | ||
|
||
return APIUtils.of(HttpStatus.OK, new PayAccountChargeResponse(remainBalance)); | ||
} | ||
} |
Oops, something went wrong.