Skip to content

Commit ddc420c

Browse files
authored
Merge pull request #50 from UruruLab/feat/45-paymentPointEntity
결제/포인트 관련 도메인 설계
2 parents d4b3b5f + 5ae59a8 commit ddc420c

File tree

11 files changed

+548
-0
lines changed

11 files changed

+548
-0
lines changed
Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
package com.ururulab.ururu.payment.domain.entity;
2+
3+
import com.ururulab.ururu.global.common.entity.BaseEntity;
4+
import com.ururulab.ururu.member.domain.entity.Member;
5+
import com.ururulab.ururu.order.domain.entity.Order;
6+
import com.ururulab.ururu.payment.domain.entity.enumerated.PayMethod;
7+
import com.ururulab.ururu.payment.domain.entity.enumerated.PaymentStatus;
8+
import com.ururulab.ururu.payment.domain.policy.PaymentPolicy;
9+
import jakarta.persistence.*;
10+
import lombok.AccessLevel;
11+
import lombok.Getter;
12+
import lombok.NoArgsConstructor;
13+
14+
import java.time.ZoneId;
15+
import java.time.ZonedDateTime;
16+
17+
@Entity
18+
@Getter
19+
@Table(name = "payment")
20+
@NoArgsConstructor(access = AccessLevel.PROTECTED)
21+
public class Payment extends BaseEntity {
22+
23+
@Id
24+
@GeneratedValue(strategy = GenerationType.IDENTITY)
25+
private Long id;
26+
27+
@ManyToOne(fetch = FetchType.LAZY)
28+
@JoinColumn(name = "member_id", nullable = false)
29+
private Member member;
30+
31+
@ManyToOne(fetch = FetchType.LAZY)
32+
@JoinColumn(name = "order_id", nullable = false)
33+
private Order order;
34+
35+
@Column(name = "payment_key", length = PaymentPolicy.PAYMENT_KEY_MAX_LENGTH)
36+
private String paymentKey;
37+
38+
@Column(name = "total_amount", nullable = false)
39+
private Integer totalAmount;
40+
41+
@Column(nullable = false)
42+
private Integer amount;
43+
44+
@Column(nullable = false)
45+
private Integer point;
46+
47+
@Enumerated(EnumType.STRING)
48+
@Column(name = "pay_method")
49+
private PayMethod payMethod;
50+
51+
@Enumerated(EnumType.STRING)
52+
@Column(nullable = false)
53+
private PaymentStatus status;
54+
55+
@Column(name = "request_at")
56+
private ZonedDateTime requestAt;
57+
58+
@Column(name = "paid_at")
59+
private ZonedDateTime paidAt;
60+
61+
@Column(name = "cancelled_at")
62+
private ZonedDateTime cancelledAt;
63+
64+
public static Payment create(
65+
Member member,
66+
Order order,
67+
Integer totalAmount,
68+
Integer amount,
69+
Integer point
70+
) {
71+
if (member == null) {
72+
throw new IllegalArgumentException(PaymentPolicy.MEMBER_REQUIRED);
73+
}
74+
if (order == null) {
75+
throw new IllegalArgumentException(PaymentPolicy.ORDER_REQUIRED);
76+
}
77+
if (totalAmount == null) {
78+
throw new IllegalArgumentException(PaymentPolicy.TOTAL_AMOUNT_REQUIRED);
79+
}
80+
if (totalAmount < PaymentPolicy.MIN_AMOUNT) {
81+
throw new IllegalArgumentException(PaymentPolicy.TOTAL_AMOUNT_MIN);
82+
}
83+
if (totalAmount > PaymentPolicy.MAX_AMOUNT) {
84+
throw new IllegalArgumentException(PaymentPolicy.TOTAL_AMOUNT_MAX);
85+
}
86+
87+
if (amount == null) {
88+
throw new IllegalArgumentException(PaymentPolicy.AMOUNT_REQUIRED);
89+
}
90+
if (amount < PaymentPolicy.MIN_AMOUNT) {
91+
throw new IllegalArgumentException(PaymentPolicy.AMOUNT_MIN);
92+
}
93+
if (amount > PaymentPolicy.MAX_AMOUNT) {
94+
throw new IllegalArgumentException(PaymentPolicy.AMOUNT_MAX);
95+
}
96+
if (point == null) {
97+
throw new IllegalArgumentException(PaymentPolicy.POINT_REQUIRED);
98+
}
99+
if (point < PaymentPolicy.MIN_POINT) {
100+
throw new IllegalArgumentException(PaymentPolicy.POINT_MIN);
101+
}
102+
if (!totalAmount.equals(amount + point)) {
103+
throw new IllegalArgumentException(PaymentPolicy.AMOUNT_MISMATCH);
104+
}
105+
106+
Payment payment = new Payment();
107+
payment.member = member;
108+
payment.order = order;
109+
payment.totalAmount = totalAmount;
110+
payment.amount = amount;
111+
payment.point = point;
112+
payment.status = PaymentStatus.PENDING;
113+
payment.requestAt = ZonedDateTime.now(ZoneId.of("Asia/Seoul"));
114+
115+
return payment;
116+
}
117+
118+
public void updatePaymentInfo(String paymentKey, PayMethod payMethod, Integer paidAmount) {
119+
if (this.status == PaymentStatus.PAID) {
120+
throw new IllegalStateException(PaymentPolicy.CANNOT_UPDATE_PAID);
121+
}
122+
if (this.status == PaymentStatus.REFUNDED) {
123+
throw new IllegalStateException(PaymentPolicy.CANNOT_UPDATE_REFUNDED);
124+
}
125+
if (paymentKey == null || paymentKey.trim().isEmpty()) {
126+
throw new IllegalArgumentException(PaymentPolicy.PAYMENT_KEY_REQUIRED);
127+
}
128+
if (payMethod == null) {
129+
throw new IllegalArgumentException(PaymentPolicy.PAY_METHOD_REQUIRED);
130+
}
131+
if (!this.amount.equals(paidAmount)) {
132+
throw new IllegalArgumentException(PaymentPolicy.PAYMENT_AMOUNT_MISMATCH);
133+
}
134+
135+
this.paymentKey = paymentKey;
136+
this.payMethod = payMethod;
137+
}
138+
139+
public void markAsPaid(ZonedDateTime approvedAt) {
140+
if (approvedAt == null) {
141+
throw new IllegalArgumentException(PaymentPolicy.APPROVED_AT_REQUIRED);
142+
}
143+
if (this.status == PaymentStatus.PAID) {
144+
throw new IllegalStateException(PaymentPolicy.ALREADY_PAID);
145+
}
146+
if (this.status == PaymentStatus.REFUNDED) {
147+
throw new IllegalStateException(PaymentPolicy.ALREADY_REFUNDED);
148+
}
149+
150+
this.status = PaymentStatus.PAID;
151+
this.paidAt = approvedAt;
152+
}
153+
154+
public void markAsRefunded(ZonedDateTime cancelledAt) {
155+
if (cancelledAt == null) {
156+
throw new IllegalArgumentException(PaymentPolicy.CANCELLED_AT_REQUIRED);
157+
}
158+
if (this.status != PaymentStatus.PAID) {
159+
throw new IllegalStateException(PaymentPolicy.NOT_PAID);
160+
}
161+
162+
this.status = PaymentStatus.REFUNDED;
163+
this.cancelledAt = cancelledAt;
164+
}
165+
166+
public void markAsFailed() {
167+
if (this.status == PaymentStatus.PAID) {
168+
throw new IllegalStateException(PaymentPolicy.CANNOT_FAIL_PAID);
169+
}
170+
171+
this.status = PaymentStatus.FAILED;
172+
}
173+
174+
public boolean isPaid() {
175+
return this.status == PaymentStatus.PAID;
176+
}
177+
178+
public boolean isRefundable() {
179+
return this.status == PaymentStatus.PAID;
180+
}
181+
182+
public boolean isCancellable() {
183+
return this.status == PaymentStatus.PENDING;
184+
}
185+
}
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
package com.ururulab.ururu.payment.domain.entity;
2+
3+
import com.ururulab.ururu.global.common.entity.BaseEntity;
4+
import com.ururulab.ururu.member.domain.entity.Member;
5+
import com.ururulab.ururu.payment.domain.entity.enumerated.PointSource;
6+
import com.ururulab.ururu.payment.domain.entity.enumerated.PointType;
7+
import com.ururulab.ururu.payment.domain.policy.PointTransactionPolicy;
8+
import jakarta.persistence.*;
9+
import lombok.AccessLevel;
10+
import lombok.Getter;
11+
import lombok.NoArgsConstructor;
12+
13+
@Entity
14+
@Getter
15+
@Table(name = "point_transaction")
16+
@NoArgsConstructor(access = AccessLevel.PROTECTED)
17+
public class PointTransaction extends BaseEntity {
18+
19+
@Id
20+
@GeneratedValue(strategy = GenerationType.IDENTITY)
21+
private Long id;
22+
23+
@ManyToOne(fetch = FetchType.LAZY)
24+
@JoinColumn(name = "member_id", nullable = false)
25+
private Member member;
26+
27+
@Enumerated(EnumType.STRING)
28+
@Column(nullable = false)
29+
private PointType type;
30+
31+
@Enumerated(EnumType.STRING)
32+
@Column(nullable = false)
33+
private PointSource source;
34+
35+
@Column(nullable = false)
36+
private Integer amount;
37+
38+
@Column(length = PointTransactionPolicy.REASON_MAX_LENGTH)
39+
private String reason;
40+
41+
public static PointTransaction createEarned(
42+
Member member,
43+
PointSource source,
44+
Integer amount,
45+
String reason
46+
) {
47+
return create(member, PointType.EARNED, source, amount, reason);
48+
}
49+
50+
public static PointTransaction createUsed(
51+
Member member,
52+
PointSource source,
53+
Integer amount,
54+
String reason
55+
) {
56+
return create(member, PointType.USED, source, amount, reason);
57+
}
58+
59+
private static PointTransaction create(
60+
Member member,
61+
PointType type,
62+
PointSource source,
63+
Integer amount,
64+
String reason
65+
) {
66+
validateCommonParameters(member, type, source, amount);
67+
validateTypeSpecific(type, amount);
68+
69+
PointTransaction transaction = new PointTransaction();
70+
transaction.member = member;
71+
transaction.type = type;
72+
transaction.source = source;
73+
transaction.amount = amount;
74+
transaction.reason = reason != null ? reason.trim() : null;
75+
76+
return transaction;
77+
}
78+
79+
private static void validateCommonParameters(Member member, PointType type, PointSource source, Integer amount) {
80+
if (member == null) {
81+
throw new IllegalArgumentException(PointTransactionPolicy.MEMBER_REQUIRED);
82+
}
83+
if (type == null) {
84+
throw new IllegalArgumentException(PointTransactionPolicy.TYPE_REQUIRED);
85+
}
86+
if (source == null) {
87+
throw new IllegalArgumentException(PointTransactionPolicy.SOURCE_REQUIRED);
88+
}
89+
if (amount == null) {
90+
throw new IllegalArgumentException(PointTransactionPolicy.AMOUNT_REQUIRED);
91+
}
92+
}
93+
94+
private static void validateTypeSpecific(PointType type, Integer amount) {
95+
if (type == PointType.EARNED) {
96+
if (amount < PointTransactionPolicy.MIN_EARNED_AMOUNT) {
97+
throw new IllegalArgumentException(PointTransactionPolicy.EARNED_AMOUNT_MIN);
98+
}
99+
} else if (type == PointType.USED) {
100+
if (amount < PointTransactionPolicy.MIN_USED_AMOUNT) {
101+
throw new IllegalArgumentException(PointTransactionPolicy.USED_AMOUNT_MIN);
102+
}
103+
}
104+
}
105+
106+
public boolean isEarned() {
107+
return this.type == PointType.EARNED;
108+
}
109+
110+
public boolean isUsed() {
111+
return this.type == PointType.USED;
112+
}
113+
}
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
package com.ururulab.ururu.payment.domain.entity;
2+
3+
import com.ururulab.ururu.global.common.entity.BaseEntity;
4+
import com.ururulab.ururu.payment.domain.entity.enumerated.RefundStatus;
5+
import com.ururulab.ururu.payment.domain.policy.RefundPolicy;
6+
import jakarta.persistence.*;
7+
import lombok.AccessLevel;
8+
import lombok.Getter;
9+
import lombok.NoArgsConstructor;
10+
11+
import java.time.LocalDateTime;
12+
13+
@Entity
14+
@Getter
15+
@Table(name = "refund")
16+
@NoArgsConstructor(access = AccessLevel.PROTECTED)
17+
public class Refund extends BaseEntity {
18+
19+
@Id
20+
@GeneratedValue(strategy = GenerationType.IDENTITY)
21+
private Long id;
22+
23+
@ManyToOne(fetch = FetchType.LAZY)
24+
@JoinColumn(name = "payment_id", nullable = false)
25+
private Payment payment;
26+
27+
@Column(length = RefundPolicy.REASON_MAX_LENGTH, nullable = false)
28+
private String reason;
29+
30+
@Column(nullable = false)
31+
private Integer amount;
32+
33+
@Column(name = "refunded_at")
34+
private LocalDateTime refundedAt;
35+
36+
@Enumerated(EnumType.STRING)
37+
@Column(nullable = false)
38+
private RefundStatus status;
39+
40+
public static Refund create(Payment payment, String reason, Integer amount) {
41+
if (payment == null) {
42+
throw new IllegalArgumentException(RefundPolicy.PAYMENT_REQUIRED);
43+
}
44+
if (!payment.isRefundable()) {
45+
throw new IllegalStateException(RefundPolicy.NOT_REFUNDABLE);
46+
}
47+
if (reason == null || reason.trim().isEmpty()) {
48+
throw new IllegalArgumentException(RefundPolicy.REASON_REQUIRED);
49+
}
50+
if (amount == null) {
51+
throw new IllegalArgumentException(RefundPolicy.AMOUNT_REQUIRED);
52+
}
53+
if (amount < RefundPolicy.MIN_AMOUNT) {
54+
throw new IllegalArgumentException(RefundPolicy.AMOUNT_MIN);
55+
}
56+
if (amount > payment.getAmount()) {
57+
throw new IllegalArgumentException(RefundPolicy.AMOUNT_EXCEEDS_PAYMENT);
58+
}
59+
60+
Refund refund = new Refund();
61+
refund.payment = payment;
62+
refund.reason = reason.trim();
63+
refund.amount = amount;
64+
refund.status = RefundStatus.INITIATED;
65+
66+
return refund;
67+
}
68+
69+
public void markAsCompleted(LocalDateTime refundedAt) {
70+
if (refundedAt == null) {
71+
throw new IllegalArgumentException(RefundPolicy.REFUNDED_AT_REQUIRED);
72+
}
73+
if (this.status == RefundStatus.COMPLETED) {
74+
throw new IllegalStateException(RefundPolicy.ALREADY_COMPLETED);
75+
}
76+
77+
this.status = RefundStatus.COMPLETED;
78+
this.refundedAt = refundedAt;
79+
}
80+
81+
public void markAsFailed() {
82+
if (this.status == RefundStatus.COMPLETED) {
83+
throw new IllegalStateException(RefundPolicy.CANNOT_FAIL_COMPLETED);
84+
}
85+
86+
this.status = RefundStatus.FAILED;
87+
}
88+
89+
public boolean isCompleted() {
90+
return this.status == RefundStatus.COMPLETED;
91+
}
92+
93+
public boolean isFailed() {
94+
return this.status == RefundStatus.FAILED;
95+
}
96+
}

0 commit comments

Comments
 (0)