Skip to content

Commit

Permalink
feature: 결제 취소(배송주문) API 및 Test Code
Browse files Browse the repository at this point in the history
  • Loading branch information
binarywoo27 committed Jan 12, 2024
1 parent 841c79c commit 6973c99
Show file tree
Hide file tree
Showing 10 changed files with 257 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import bloomingblooms.response.CommonResponse;
import java.time.LocalDateTime;
import java.util.List;
import kr.bb.payment.dto.request.KakaopayCancelRequestDto;
import kr.bb.payment.service.KakaopayService;
import kr.bb.payment.service.PaymentService;
import lombok.RequiredArgsConstructor;
Expand Down Expand Up @@ -47,4 +48,10 @@ CommonResponse<List<PaymentInfoDto>> getPaymentInfo(@RequestBody List<String> or
CommonResponse<String> getPaymentDate(@RequestParam String orderGroupId){
return CommonResponse.success(paymentService.getPaymentDate(orderGroupId));
}

@PostMapping(value = "/cancel")
CommonResponse<Void> cancel(@RequestBody KakaopayCancelRequestDto cancelRequestDto){
kakaopayService.cancelPayment(cancelRequestDto);
return CommonResponse.success(null);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package kr.bb.payment.dto.request;

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

@Getter
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class KakaopayCancelRequestDto {
private String orderId;
private Long cancelAmount;
}
17 changes: 17 additions & 0 deletions src/main/java/kr/bb/payment/dto/response/ApprovedCancelAmount.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package kr.bb.payment.dto.response;

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

@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ApprovedCancelAmount { // 이번 요청으로 취소된 금액
private Integer total;
private Integer tax_free;
private Integer vat;
private Integer point;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package kr.bb.payment.dto.response;

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

@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class CancelAvailableAmount { // 남은 취소 가능 금액
private Integer total;
}
14 changes: 14 additions & 0 deletions src/main/java/kr/bb/payment/dto/response/CanceledAmount.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package kr.bb.payment.dto.response;

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

@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class CanceledAmount { // 누계 취소된 금액
private Integer total;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package kr.bb.payment.dto.response;

import java.time.LocalDateTime;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@Getter
@Setter
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class KakaopayCancelResponseDto {
private String cid;
private String status;
private String partner_order_id;
private String partner_user_id;
private ApprovedCancelAmount approved_cancel_amount; // 금번 취소 금액
private CanceledAmount canceled_amount; // 누적 취소 금액
private CancelAvailableAmount cancel_available_amount;
private LocalDateTime created_at;
private LocalDateTime canceled_at;
}
20 changes: 20 additions & 0 deletions src/main/java/kr/bb/payment/service/KakaopayService.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@
import java.util.List;
import java.util.Map;
import javax.validation.constraints.NotNull;
import kr.bb.payment.dto.request.KakaopayCancelRequestDto;
import kr.bb.payment.dto.response.KakaopayApproveResponseDto;
import kr.bb.payment.dto.response.KakaopayCancelResponseDto;
import kr.bb.payment.entity.Payment;
import kr.bb.payment.feign.DeliveryServiceClient;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
Expand Down Expand Up @@ -119,6 +122,23 @@ public void renewSubscription(SubscriptionBatchDtoList subscriptionBatchDtoList)

}

public void cancelPayment(KakaopayCancelRequestDto cancelRequestDto){
Payment paymentEntity = paymentService.getPaymentEntity(cancelRequestDto.getOrderId());

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

parameters.add("cid", paymentEntity.getPaymentCid());
parameters.add("tid", paymentEntity.getPaymentTid());
parameters.add("cancel_amount", String.valueOf(cancelRequestDto.getCancelAmount()));
parameters.add("cancel_tax_free_amount", String.valueOf(0L));

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

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

restTemplate.postForObject(url, requestEntity, KakaopayCancelResponseDto.class);
}

@NotNull
private HttpHeaders getHeaders() {
HttpHeaders headers = new HttpHeaders();
Expand Down
5 changes: 5 additions & 0 deletions src/main/java/kr/bb/payment/service/PaymentService.java
Original file line number Diff line number Diff line change
Expand Up @@ -104,4 +104,9 @@ public String getPaymentDate(String orderGroupId){
}
return "";
}

@Transactional(readOnly = true)
public Payment getPaymentEntity(String orderGroupId){
return paymentRepository.findByOrderId(orderGroupId);
}
}
94 changes: 94 additions & 0 deletions src/test/java/kr/bb/payment/service/KakaopayCancelTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package kr.bb.payment.service;

import bloomingblooms.domain.notification.order.OrderType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import java.time.LocalDateTime;
import kr.bb.payment.dto.request.KakaopayCancelRequestDto;
import kr.bb.payment.dto.response.ApprovedCancelAmount;
import kr.bb.payment.dto.response.CancelAvailableAmount;
import kr.bb.payment.dto.response.CanceledAmount;
import kr.bb.payment.dto.response.KakaopayCancelResponseDto;
import kr.bb.payment.entity.Payment;
import kr.bb.payment.entity.PaymentStatus;
import kr.bb.payment.repository.PaymentRepository;
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.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 KakaopayCancelTest {
@Autowired private RestTemplate restTemplate;
@Autowired private KakaopayService kakaopayService;
@Autowired private PaymentRepository paymentRepository;
private MockRestServiceServer mockServer;

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

ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new JavaTimeModule());
String responseJson = objectMapper.writeValueAsString(kakaopayCancelResponseDto());

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

@Test
@DisplayName("카카오 결제 취소 테스트")
void cancelPay() {
// given
KakaopayCancelRequestDto cancelRequestDto =
KakaopayCancelRequestDto.builder().cancelAmount(2000L).orderId("orderGroupId").build();

Payment payment = Payment.builder()
.orderId("orderGroupId")
.orderType(OrderType.DELIVERY)
.paymentActualAmount(10000L)
.paymentCid("TC0ONETIME")
.paymentStatus(PaymentStatus.PENDING)
.paymentTid("T59eb9072dff7a6a6515")
.paymentType("MONEY")
.userId(1L)
.build();
paymentRepository.save(payment);

// when
kakaopayService.cancelPayment(cancelRequestDto);

mockServer.verify();
}

private KakaopayCancelResponseDto kakaopayCancelResponseDto() {
ApprovedCancelAmount approvedCancelAmount =
ApprovedCancelAmount.builder().total(10000).tax_free(0).vat(0).point(0).build();
CanceledAmount canceledAmount = CanceledAmount.builder().total(10000).build();
CancelAvailableAmount cancelAvailableAmount =
CancelAvailableAmount.builder().total(40000).build();

return KakaopayCancelResponseDto.builder()
.cid("cid 번호")
.status("주문취소")
.partner_order_id("orderGroupId")
.partner_user_id("userId")
.approved_cancel_amount(approvedCancelAmount)
.canceled_amount(canceledAmount)
.cancel_available_amount(cancelAvailableAmount)
.created_at(LocalDateTime.now().minusDays(10))
.canceled_at(LocalDateTime.now())
.build();
}
}
51 changes: 46 additions & 5 deletions src/test/java/kr/bb/payment/service/KakaopayReadyTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,45 @@

import bloomingblooms.domain.payment.KakaopayReadyRequestDto;
import bloomingblooms.domain.payment.KakaopayReadyResponseDto;
import org.junit.jupiter.api.Assertions;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import java.time.LocalDateTime;
import kr.bb.payment.dto.response.Amount;
import kr.bb.payment.dto.response.KakaopayApproveResponseDto;
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 KakaopayReadyTest {
@Autowired private RestTemplate restTemplate;
@Autowired private KakaopayService kakaopayService;
private MockRestServiceServer mockServer;

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

ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new JavaTimeModule());
String responseJson = objectMapper.writeValueAsString(kakaopayReadyResponseDto());

mockServer
.expect(MockRestRequestMatchers.requestTo("https://kapi.kakao.com/v1/payment/ready"))
.andExpect(MockRestRequestMatchers.method(HttpMethod.POST))
.andRespond(MockRestResponseCreators.withSuccess(responseJson, MediaType.APPLICATION_JSON));
}
@DisplayName("단건결제 준비 - 픽업")
@DirtiesContext
@Test
Expand All @@ -34,8 +60,7 @@ void kakaopayReadyForDeliveryAndPickupTest() {

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

@DisplayName("단건결제 준비 - 배송&구독")
Expand All @@ -57,7 +82,23 @@ void kakaopayReadyForSubscriptionTest() {

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

KakaopayApproveResponseDto kakaopayReadyResponseDto() {
return KakaopayApproveResponseDto.builder()
.aid("고유번호")
.tid("tid고유번호")
.cid("cid번호")
.sid("sid")
.partnerOrderId("1")
.partnerUserId("1")
.paymentMethodType("MONEY")
.itemName("상품명")
.quantity(1)
.createdAt(LocalDateTime.now())
.approvedAt(LocalDateTime.now())
.amount(new Amount(1000, 0, 0, 0, 0))
.build();
}
}

0 comments on commit 6973c99

Please sign in to comment.