Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

결제승인 API 및 Test code #4

Merged
merged 2 commits into from
Nov 24, 2023
Merged
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
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
@@ -40,6 +40,7 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation group: 'io.github.lotteon-maven', name: 'blooming-blooms-utils', version: '0.1.0-alpha2'
runtimeOnly 'com.h2database:h2'
testImplementation 'org.mock-server:mockserver-netty:5.11.2' // 사용 중인 MockServer 버전
}

dependencyManagement {
13 changes: 13 additions & 0 deletions src/main/java/kr/bb/payment/config/RestTemplateConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package kr.bb.payment.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

@Configuration
public class RestTemplateConfig {
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
Original file line number Diff line number Diff line change
@@ -1,29 +1,31 @@
package kr.bb.payment.controller.clientcontroller;

import bloomingblooms.response.SuccessResponse;
import kr.bb.payment.dto.request.KakaopayApproveRequestDto;
import kr.bb.payment.dto.request.KakaopayReadyRequestDto;
import kr.bb.payment.dto.response.KakaopayReadyResponseDto;
import kr.bb.payment.service.KakaopayReadyService;
import kr.bb.payment.service.KakaopayService;
import kr.bb.payment.service.PaymentService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

@RestController
@RequiredArgsConstructor
public class OrderClientController {

private final PaymentService paymentService;
private final KakaopayReadyService kakaopayReadyService;
private final KakaopayService kakaopayService;

@PostMapping("/ready")
public ResponseEntity<SuccessResponse<KakaopayReadyResponseDto>> payReady(
@RequestBody KakaopayReadyRequestDto readyRequestDto) {

KakaopayReadyResponseDto responseDto = kakaopayReadyService.kakaoPayReady(readyRequestDto);
KakaopayReadyResponseDto responseDto = kakaopayService.kakaoPayReady(readyRequestDto);

return ResponseEntity.ok()
.body(
@@ -33,4 +35,15 @@ public ResponseEntity<SuccessResponse<KakaopayReadyResponseDto>> payReady(
.data(responseDto)
.build());
}

@PostMapping("/approve")
public ResponseEntity<SuccessResponse<Void>> payApprove(@RequestBody KakaopayApproveRequestDto approveRequestDto){

kakaopayService.kakaoPayApprove(approveRequestDto);

return ResponseEntity.ok().body(SuccessResponse.<Void>builder()
.code(String.valueOf(HttpStatus.OK.value()))
.message(HttpStatus.OK.name())
.build());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package kr.bb.payment.dto.request;

import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@Builder
@AllArgsConstructor
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class KakaopayApproveRequestDto {
private Long orderId;
private Long userId;
private String tid;
private String pgToken;
}
30 changes: 30 additions & 0 deletions src/main/java/kr/bb/payment/dto/response/Amount.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package kr.bb.payment.dto.response;

import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;

@Getter
@Setter
@ToString
@NoArgsConstructor
public class Amount {

private Integer total;
private Integer tax_free;
private Integer vat;
private Integer point;
private Integer discount;

public Amount(Integer total, Integer tax_free, Integer vat, Integer point, Integer discount) {
this.total = total;
this.tax_free = tax_free;
this.vat = vat;
this.point = point;
this.discount = discount;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package kr.bb.payment.dto.response;

import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;

@Getter
@Setter
@ToString
@Builder
@AllArgsConstructor
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class KakaoPayApproveResponseDto {
private String aid; // 요청 고유번호
private String tid; // 결제 고유번호
private String cid; // 가맹점 코드
private String sid; // 정기 결제용 id
@JsonProperty("partner_order_id")
private String partnerOrderId; // 가맹점 주문번호
@JsonProperty("partner_user_id")
private String partnerUserId; // 가맹점 회원
@JsonProperty("payment_method_type")
private String paymentMethodType; // 결제수단
@JsonProperty("item_name")
private String itemName;
private Integer quantity;
@JsonProperty("created_at")
private String createdAt;
@JsonProperty("approved_at")
private String approvedAt;
private Amount amount;
}
4 changes: 4 additions & 0 deletions src/main/java/kr/bb/payment/entity/Payment.java
Original file line number Diff line number Diff line change
@@ -12,9 +12,13 @@
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@Entity
@Getter
@Setter
@Table(name = "payment")
@AllArgsConstructor(access = AccessLevel.PROTECTED)
@NoArgsConstructor(access = AccessLevel.PROTECTED)
Original file line number Diff line number Diff line change
@@ -3,4 +3,6 @@
import kr.bb.payment.entity.Payment;
import org.springframework.data.jpa.repository.JpaRepository;

public interface PaymentRepository extends JpaRepository<Payment, Long> {}
public interface PaymentRepository extends JpaRepository<Payment, Long> {
Payment findByOrderId(Long orderId);
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package kr.bb.payment.service;

import kr.bb.payment.dto.request.KakaopayApproveRequestDto;
import kr.bb.payment.dto.request.KakaopayReadyRequestDto;
import kr.bb.payment.dto.response.KakaoPayApproveResponseDto;
import kr.bb.payment.dto.response.KakaopayReadyResponseDto;
import kr.bb.payment.entity.Payment;
import lombok.RequiredArgsConstructor;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Value;
@@ -14,8 +17,9 @@

@RequiredArgsConstructor
@Component
public class KakaopayReadyService {
public class KakaopayService {
private final PaymentService paymentService;
private final RestTemplate restTemplate;

@Value("${kakao.admin}")
private String ADMIN_KEY;
@@ -45,16 +49,37 @@ public KakaopayReadyResponseDto kakaoPayReady(KakaopayReadyRequestDto requestDto
HttpEntity<MultiValueMap<String, String>> requestEntity =
new HttpEntity<>(parameters, this.getHeaders());

RestTemplate template = new RestTemplate();
String url = "https://kapi.kakao.com/v1/payment/ready";
KakaopayReadyResponseDto responseDto =
template.postForObject(url, requestEntity, KakaopayReadyResponseDto.class);
restTemplate.postForObject(url, requestEntity, KakaopayReadyResponseDto.class);

paymentService.savePayReadyInfo(requestDto, responseDto, cid);

return responseDto;
}

public void kakaoPayApprove(KakaopayApproveRequestDto requestDto) {
Payment paymentEntity = paymentService.getPaymentEntity(requestDto.getOrderId());

MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>();

parameters.add("cid", paymentEntity.getPaymentCid());
parameters.add("tid", requestDto.getTid());
parameters.add("partner_order_id", String.valueOf(requestDto.getOrderId()));
parameters.add("partner_user_id", String.valueOf(requestDto.getUserId()));
parameters.add("pg_token", requestDto.getPgToken());

HttpEntity<MultiValueMap<String, String>> requestEntity =
new HttpEntity<>(parameters, this.getHeaders());

String url = "https://kapi.kakao.com/v1/payment/approve";

KakaoPayApproveResponseDto approveResponse =
restTemplate.postForObject(url, requestEntity, KakaoPayApproveResponseDto.class);

paymentService.updatePayInfo(paymentEntity, approveResponse);
}

@NotNull
private HttpHeaders getHeaders() {
HttpHeaders headers = new HttpHeaders();
26 changes: 26 additions & 0 deletions src/main/java/kr/bb/payment/service/PaymentService.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package kr.bb.payment.service;

import kr.bb.payment.dto.request.KakaopayReadyRequestDto;
import kr.bb.payment.dto.response.KakaoPayApproveResponseDto;
import kr.bb.payment.dto.response.KakaopayReadyResponseDto;
import kr.bb.payment.entity.OrderType;
import kr.bb.payment.entity.Payment;
@@ -47,4 +48,29 @@ public KakaopayReadyResponseDto savePayReadyInfo(

return responseDto;
}

/**
* orderId로 Payment Entity 찾기
*
* @param orderId
* @return
*/
@Transactional
public Payment getPaymentEntity(Long orderId) {
return paymentRepository.findByOrderId(orderId);
}

/**
* 결제수단, 결제상태 업데이트(PENDING -> COMPLETED)
*
* @param paymentEntity
* @param approveResponse
*/
@Transactional
public void updatePayInfo(Payment paymentEntity, KakaoPayApproveResponseDto approveResponse) {
paymentEntity.setPaymentType(approveResponse.getPaymentMethodType());
paymentEntity.setPaymentStatus(PaymentStatus.COMPLETED);

paymentRepository.save(paymentEntity);
}
}
100 changes: 100 additions & 0 deletions src/test/java/kr/bb/payment/service/KakaopayApproveTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package kr.bb.payment.service;


import com.fasterxml.jackson.databind.ObjectMapper;
import kr.bb.payment.dto.request.KakaopayApproveRequestDto;
import kr.bb.payment.dto.response.Amount;
import kr.bb.payment.dto.response.KakaoPayApproveResponseDto;
import kr.bb.payment.entity.OrderType;
import kr.bb.payment.entity.Payment;
import kr.bb.payment.entity.PaymentStatus;
import kr.bb.payment.repository.PaymentRepository;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.web.client.MockRestServiceServer;
import org.springframework.test.web.client.match.MockRestRequestMatchers;
import org.springframework.test.web.client.response.MockRestResponseCreators;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.client.RestTemplate;

@SpringBootTest
@Transactional
public class KakaopayApproveTest {
@Autowired
private RestTemplate restTemplate;
private MockRestServiceServer mockServer;
@Autowired
private KakaopayService kakaopayService;
@Autowired
private PaymentRepository paymentRepository;

@BeforeEach
void setUp() throws Exception{
mockServer = MockRestServiceServer.createServer(restTemplate);

// Payment Entity 저장
Payment payment =
Payment.builder()
.userId(1L)
.orderId(1L)
.orderType(OrderType.ORDER_DELIVERY)
.paymentCid("TC0ONETIME")
.paymentTid("FAKE_TID_FOR_TEST")
.paymentActualAmount(52900L)
.paymentStatus(PaymentStatus.PENDING)
.build();
paymentRepository.save(payment);

KakaoPayApproveResponseDto responseDto =
KakaoPayApproveResponseDto.builder()
.aid("A5678901234567890123")
.tid("T1234567890123456789")
.cid("TC0ONETIME")
.partnerOrderId("partner_order_id")
.partnerUserId("partner_user_id")
.paymentMethodType("MONEY")
.itemName("초코파이")
.quantity(1)
.amount(new Amount(2200, 0, 200, 0, 0))
.createdAt("2016-11-15T21:18:22")
.approvedAt("2016-11-15T21:20:47")
.build();

ObjectMapper objectMapper = new ObjectMapper();
String responseJson = objectMapper.writeValueAsString(responseDto);

mockServer
.expect(MockRestRequestMatchers.requestTo("https://kapi.kakao.com/v1/payment/approve"))
.andExpect(MockRestRequestMatchers.method(HttpMethod.POST))
.andRespond(MockRestResponseCreators.withSuccess(responseJson, MediaType.APPLICATION_JSON));
}

@AfterEach
void shutDown() {
mockServer.reset();
}

@DisplayName("결제 승인 테스트")
@DirtiesContext
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

v2때 Mock을 이용한 테스트로 수정하면 좋을듯
이유 : https://newwisdom.tistory.com/95
참고 : https://velog.io/@qwerty1434/TestCode-치팅시트

@Test
void kakaoPayApproveTest() {
KakaopayApproveRequestDto requestDto =
KakaopayApproveRequestDto.builder()
.userId(1L)
.orderId(1L)
.tid("T1234567890123456789")
.pgToken("pg_token=xxxxxxxxxxxxxxxxxxxx")
.build();

kakaopayService.kakaoPayApprove(requestDto);

mockServer.verify();
}
}
Original file line number Diff line number Diff line change
@@ -7,17 +7,21 @@
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.transaction.annotation.Transactional;

@SpringBootTest
public class PaymentServiceTest {
@Autowired
private KakaopayReadyService kakaopayReadyService;
@Transactional
public class KakaopayReadyTest {
@Autowired private KakaopayService kakaopayService;

@DisplayName("단건결제 준비 - 픽업")
@DirtiesContext
@Test
void kakaopayReadyForDeliveryAndPickupTest() {
// given
KakaopayReadyRequestDto DTO_1 = KakaopayReadyRequestDto.builder()
KakaopayReadyRequestDto DTO_1 =
KakaopayReadyRequestDto.builder()
.userId("1")
.orderId("1")
.orderType("ORDER_PICKUP")
@@ -29,16 +33,18 @@ void kakaopayReadyForDeliveryAndPickupTest() {
.build();

// then
KakaopayReadyResponseDto responseDto1 = kakaopayReadyService.kakaoPayReady(DTO_1);
Assertions.assertEquals(20, responseDto1.getTid().length());
Assertions.assertTrue(responseDto1.getNextRedirectPcUrl().startsWith("https://"));
KakaopayReadyResponseDto responseDto = kakaopayService.kakaoPayReady(DTO_1);
Assertions.assertEquals(20, responseDto.getTid().length());
Assertions.assertTrue(responseDto.getNextRedirectPcUrl().startsWith("https://"));
}

@DisplayName("단건결제 준비 - 배송&구독")
@DirtiesContext
@Test
void kakaopayReadyForSubscriptionTest(){
void kakaopayReadyForSubscriptionTest() {
// given
KakaopayReadyRequestDto DTO_2 = KakaopayReadyRequestDto.builder()
KakaopayReadyRequestDto DTO_2 =
KakaopayReadyRequestDto.builder()
.userId("1")
.orderId("2")
.orderType("ORDER_DELIVERY")
@@ -50,7 +56,7 @@ void kakaopayReadyForSubscriptionTest(){
.build();

// then
KakaopayReadyResponseDto responseDto2 = kakaopayReadyService.kakaoPayReady(DTO_2);
KakaopayReadyResponseDto responseDto2 = kakaopayService.kakaoPayReady(DTO_2);
Assertions.assertEquals(20, responseDto2.getTid().length());
Assertions.assertTrue(responseDto2.getNextRedirectPcUrl().startsWith("https://"));
}