Skip to content

Commit 6305753

Browse files
authored
Merge pull request #12 from Kimjipang/round06
Round06
2 parents 3b8f531 + a8bfb4f commit 6305753

45 files changed

Lines changed: 1271 additions & 1 deletion

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

apps/commerce-api/build.gradle.kts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,13 @@ dependencies {
1616
annotationProcessor("jakarta.persistence:jakarta.persistence-api")
1717
annotationProcessor("jakarta.annotation:jakarta.annotation-api")
1818

19+
// FeignClient
20+
implementation("org.springframework.cloud:spring-cloud-starter-openfeign")
21+
22+
// Resilience4j
23+
implementation("io.github.resilience4j:resilience4j-spring-boot3")
24+
implementation("org.springframework.boot:spring-boot-starter-aop")
25+
1926
// test-fixtures
2027
testImplementation(testFixtures(project(":modules:jpa")))
2128
testImplementation(testFixtures(project(":modules:redis")))

apps/commerce-api/src/main/java/com/loopers/CommerceApiApplication.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,14 @@
55
import org.springframework.boot.autoconfigure.SpringBootApplication;
66
import org.springframework.boot.context.properties.ConfigurationPropertiesScan;
77
import org.springframework.cache.annotation.EnableCaching;
8+
import org.springframework.cloud.openfeign.EnableFeignClients;
89

910
import java.util.TimeZone;
1011

1112
@ConfigurationPropertiesScan
1213
@SpringBootApplication
1314
@EnableCaching
15+
@EnableFeignClients
1416
public class CommerceApiApplication {
1517

1618
@PostConstruct
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package com.loopers.application.payment;
2+
3+
import com.loopers.infrastructure.payment.client.PgClient;
4+
import com.loopers.interfaces.api.ApiResponse;
5+
import com.loopers.interfaces.api.payment.PaymentV1Dto;
6+
import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker;
7+
import lombok.RequiredArgsConstructor;
8+
import org.springframework.stereotype.Component;
9+
10+
@Component
11+
@RequiredArgsConstructor
12+
public class PaymentFacade {
13+
private final PgClient pgClient;
14+
15+
@CircuitBreaker(name = "pgCircuit", fallbackMethod = "fallback")
16+
public PaymentV1Dto.TransactionResponse requestPayment(String userId, PaymentV1Dto.PaymentRequest request) {
17+
ApiResponse<PaymentV1Dto.TransactionResponse> result = pgClient.requestPayment(userId, request);
18+
19+
PaymentV1Dto.TransactionResponse response = result.data();
20+
21+
return response;
22+
}
23+
24+
public PaymentV1Dto.TransactionResponse fallback(String userId, PaymentV1Dto.PaymentRequest request, Throwable throwable) {
25+
return new PaymentV1Dto.TransactionResponse(
26+
null,
27+
PaymentV1Dto.TransactionStatusResponse.FAILED,
28+
"현재 결제 서비스가 불안정합니다. 잠시 후 다시 시도해주세요."
29+
);
30+
}
31+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package com.loopers.domain.payment;
2+
3+
import com.loopers.domain.BaseEntity;
4+
import jakarta.persistence.Column;
5+
import jakarta.persistence.Entity;
6+
import jakarta.persistence.Table;
7+
import lombok.AccessLevel;
8+
import lombok.Getter;
9+
import lombok.NoArgsConstructor;
10+
11+
@Entity
12+
@Getter
13+
@NoArgsConstructor(access = AccessLevel.PROTECTED)
14+
@Table(name = "payment")
15+
public class Payment extends BaseEntity {
16+
@Column(name = "ref_order_id", nullable = false)
17+
private Long orderId;
18+
19+
@Column(name = "ref_user_id", nullable = false)
20+
private Long userId;
21+
22+
@Column(name = "transaction_key")
23+
private String transactionKey;
24+
25+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package com.loopers.domain.payment;
2+
3+
public enum PaymentTransactionStatus {
4+
PENDING,
5+
SUCCESS,
6+
FAILED,
7+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package com.loopers.infrastructure.payment.client;
2+
3+
import feign.Request;
4+
import org.springframework.context.annotation.Bean;
5+
import org.springframework.context.annotation.Configuration;
6+
7+
@Configuration
8+
public class FeignClientTimeoutConfig {
9+
@Bean
10+
public Request.Options feignOptions() {
11+
return new Request.Options(1000, 3000); // 연결/응답 타임아웃 (ms)
12+
}
13+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package com.loopers.infrastructure.payment.client;
2+
3+
import com.loopers.interfaces.api.ApiResponse;
4+
import com.loopers.interfaces.api.payment.PaymentV1Dto;
5+
import org.springframework.cloud.openfeign.FeignClient;
6+
import org.springframework.web.bind.annotation.PostMapping;
7+
import org.springframework.web.bind.annotation.RequestBody;
8+
import org.springframework.web.bind.annotation.RequestHeader;
9+
10+
11+
@FeignClient(
12+
name = "pgClient",
13+
url = "http://localhost:8082",
14+
configuration = FeignClientTimeoutConfig.class
15+
)
16+
public interface PgClient {
17+
@PostMapping("/api/v1/payments")
18+
ApiResponse<PaymentV1Dto.TransactionResponse> requestPayment(
19+
@RequestHeader("X-USER-ID") String userId,
20+
@RequestBody PaymentV1Dto.PaymentRequest request
21+
);
22+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package com.loopers.interfaces.api.payment;
2+
3+
import com.loopers.application.payment.PaymentFacade;
4+
import com.loopers.interfaces.api.ApiResponse;
5+
import lombok.RequiredArgsConstructor;
6+
import org.springframework.web.bind.annotation.PostMapping;
7+
import org.springframework.web.bind.annotation.RequestBody;
8+
import org.springframework.web.bind.annotation.RequestHeader;
9+
import org.springframework.web.bind.annotation.RequestMapping;
10+
import org.springframework.web.bind.annotation.RestController;
11+
12+
@RestController
13+
@RequestMapping("/api/v1/payments")
14+
@RequiredArgsConstructor
15+
public class PaymentV1Controller implements PaymentV1Spec {
16+
private final PaymentFacade paymentFacade;
17+
18+
@PostMapping
19+
@Override
20+
public ApiResponse<PaymentV1Dto.TransactionResponse> requestPayment(
21+
@RequestHeader("X-USER-ID") String userId,
22+
@RequestBody PaymentV1Dto.PaymentRequest request
23+
) {
24+
PaymentV1Dto.TransactionResponse response = paymentFacade.requestPayment(userId, request);
25+
26+
return ApiResponse.success(response);
27+
}
28+
29+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package com.loopers.interfaces.api.payment;
2+
3+
4+
public class PaymentV1Dto {
5+
public record TransactionResponse(
6+
String transactionKey,
7+
TransactionStatusResponse status,
8+
String reason
9+
) { }
10+
11+
public enum TransactionStatusResponse {
12+
PENDING,
13+
SUCCESS,
14+
FAILED,
15+
}
16+
17+
public record PaymentRequest(
18+
String orderId,
19+
CardTypeDto cardType,
20+
String cardNo,
21+
Long amount,
22+
String callbackUrl
23+
) { }
24+
25+
public enum CardTypeDto {
26+
SAMSUNG,
27+
KB,
28+
HYUNDAI
29+
}
30+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package com.loopers.interfaces.api.payment;
2+
3+
import com.loopers.interfaces.api.ApiResponse;
4+
import io.swagger.v3.oas.annotations.Operation;
5+
import io.swagger.v3.oas.annotations.tags.Tag;
6+
7+
@Tag(name = "Payment V1 API", description = "Payment V1 API Spec")
8+
public interface PaymentV1Spec {
9+
10+
@Operation(summary = "PG사 결제 요청")
11+
ApiResponse<PaymentV1Dto.TransactionResponse> requestPayment(String userId, PaymentV1Dto.PaymentRequest request);
12+
13+
}

0 commit comments

Comments
 (0)