Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ public enum ErrorCode {
COIN_NOT_ENOUGH_EXCEPTION(HttpStatus.BAD_REQUEST, "ACC-002", "코인이 부족합니다."),
COIN_TRANSFER_SAME_USER_EXCEPTION(HttpStatus.BAD_REQUEST, "ACC-003", "자신에게 코인을 송금할 수 업습니다."),
COIN_TRANSFER_FAIL_EXCEPTION(HttpStatus.INTERNAL_SERVER_ERROR, "ACC-004", "코인 송금에 실패하였습니다."),
EFFECTIVE_AT_CANT_NOT_BE_NULL(HttpStatus.BAD_REQUEST, "ACC-005", "유효 시간이 null일 수 없습니다.")
;

private final HttpStatus status;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import jakarta.persistence.Version;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
Expand All @@ -31,6 +32,10 @@ public class Account {
@Column(name = "coin", nullable = false)
private Long coin = 0L;

@Version
@Column(name = "version", nullable = false)
private Long version = 0L;

/**
* 사용자 아이디로 계좌 생성
* 코인은 기본값 0으로 설정
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package hanium.modic.backend.domain.transaction.entity;

import static jakarta.persistence.EnumType.*;

import java.time.LocalDateTime;

import hanium.modic.backend.common.entity.BaseEntity;
import hanium.modic.backend.domain.transaction.enums.TransactionStatus;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Enumerated;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

/**
* Transaction 거래의 단위 거래(debit, credit)
*/
@Table(name = "coin_transactions")
@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class CoinTransaction extends BaseEntity {

@Id
@Column(name = "id", updatable = false)
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@Column(name = "status", nullable = false, updatable = false)
@Enumerated(STRING)
private TransactionStatus status;

@Column(name = "effective_at", nullable = true)
private LocalDateTime effectiveAt; // 거래가 실제로 반영된 시간

@Builder
private CoinTransaction(
TransactionStatus status,
LocalDateTime effectiveAt
) {
this.status = status;
this.effectiveAt = effectiveAt;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
package hanium.modic.backend.domain.transaction.entity;

import static hanium.modic.backend.common.error.ErrorCode.*;
import static jakarta.persistence.EnumType.*;

import java.time.LocalDateTime;

import hanium.modic.backend.common.entity.BaseEntity;
import hanium.modic.backend.common.error.exception.AppException;
import hanium.modic.backend.domain.transaction.enums.TransactionDirection;
import hanium.modic.backend.domain.transaction.enums.TransactionStatus;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Enumerated;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;

/**
* Transaction 거래의 단위 거래(debit, credit)
*/
@Table(name = "coin_tranaction_entities")
@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class CoinTransactionEntity extends BaseEntity {

@Id
@Column(name = "id", updatable = false)
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@Column(name = "account_id", nullable = false, updatable = false)
private Long accountId;

@Column(name = "account_version", nullable = false, updatable = false)
private Long accountVersion;

@Column(name = "coin_transaction_id", nullable = false, updatable = false)
private Long coinTransactionId;

@Column(name = "status", nullable = false, updatable = false)
@Enumerated(STRING)
private TransactionStatus status;

@Column(name = "direction", nullable = false, updatable = false)
@Enumerated(STRING)
private TransactionDirection direction;

@Column(name = "amount", nullable = false, updatable = false)
private Long amount;

@Column(name = "discarded_at", nullable = true)
private LocalDateTime discardedAt; // 취소되지 않았으면 null

@Column(name = "effective_at", nullable = true)
private LocalDateTime effectiveAt; // 거래가 실제로 반영된 시간

private CoinTransactionEntity(
Long accountId,
Long accountVersion,
Long coinTransactionId,
TransactionStatus status,
TransactionDirection direction,
Long amount,
LocalDateTime discardedAt,
LocalDateTime effectiveAt
) {
this.accountId = accountId;
this.accountVersion = accountVersion;
this.coinTransactionId = coinTransactionId;
this.status = status;
this.direction = direction;
this.amount = amount;
this.discardedAt = discardedAt;
this.effectiveAt = effectiveAt;
}

public static CoinTransactionEntity createPendingTransaction(
Long accountId,
Long accountVersion,
Long coinTransactionId,
TransactionDirection direction,
Long amount
) {
return new CoinTransactionEntity(
accountId,
accountVersion,
coinTransactionId,
TransactionStatus.PENDING,
direction,
amount,
null,
null
);
}

// 거래가 실제로 반영된 시간도 함께 설정
// Posted를 생성 시에는 기존 Pending 거래의 discardedAt 설정해야 함
public static CoinTransactionEntity createPostedTransaction(
Long accountId,
Long accountVersion,
Long coinTransactionId,
TransactionDirection direction,
Long amount,
LocalDateTime effectiveAt
) {
// effectiveAt null 체크
if (effectiveAt == null) {
throw new AppException(EFFECTIVE_AT_CANT_NOT_BE_NULL);
}

return new CoinTransactionEntity(
accountId,
accountVersion,
coinTransactionId,
TransactionStatus.POSTED,
direction,
amount,
null,
effectiveAt
);
}

// 거래 취소하기
// 별도로 취소 상태의 CoinTransactionEntity 생성해야 함
public void discardTransaction(LocalDateTime discardedAt) {
// 이미 취소된 거래는 다시 취소할 수 없음
if (this.discardedAt != null) {
throw new AppException(COIN_TRANSFER_FAIL_EXCEPTION);
}
// discardedAt은 null일 수 없음
if (discardedAt == null) {
throw new AppException(EFFECTIVE_AT_CANT_NOT_BE_NULL);
}

this.discardedAt = discardedAt;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package hanium.modic.backend.domain.transaction.enums;

public enum TransactionDirection {
DEBIT, // 출금
CREDIT // 입금
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package hanium.modic.backend.domain.transaction.enums;

public enum TransactionStatus {
PENDING, // 대기중
POSTED, // 성공
ARCHIVED // 취소 및 보관
}