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

fix: 배치 정기결제 로직 수정 및 정기결제 취소 API 추가 #21

Merged
merged 3 commits into from
Jan 15, 2024
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
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ dependencies {
testImplementation 'org.springframework.boot:spring-boot-starter-test'
implementation 'org.springframework.kafka:spring-kafka'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'io.github.lotteon-maven:blooming-blooms-utils:202401091420'
implementation 'io.github.lotteon-maven:blooming-blooms-utils:202401140749'
runtimeOnly 'com.h2database:h2'
implementation 'mysql:mysql-connector-java:8.0.33'
testImplementation 'org.mock-server:mockserver-netty:5.11.2' // 사용 중인 MockServer 버전
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
package kr.bb.payment.controller.clientcontroller;

import bloomingblooms.domain.batch.SubscriptionBatchDtoList;
import bloomingblooms.domain.payment.KakaopayApproveRequestDto;
import bloomingblooms.domain.payment.KakaopayReadyRequestDto;
import bloomingblooms.domain.payment.KakaopayReadyResponseDto;
import bloomingblooms.domain.payment.PaymentInfoDto;
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 +49,22 @@ 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);
}

@PostMapping(value = "/subscription")
CommonResponse<Void> subscription(@RequestBody SubscriptionBatchDtoList subscriptionBatchDtolist){
kakaopayService.renewSubscription(subscriptionBatchDtolist);
return CommonResponse.success(null);
}

@PostMapping(value = "/subscription/cancel")
CommonResponse<Void> cancelSubscription(@RequestBody KakaopayCancelRequestDto cancelRequestDto){
kakaopayService.cancelSubscription(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;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
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 KakaopayCancelSubscriptionResponseDto {
private String status;
private LocalDateTime created_at;
private LocalDateTime inactivated_at;
private LocalDateTime last_approved_at;
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package kr.bb.payment.repository;

import java.util.Optional;
import kr.bb.payment.entity.Subscription;
import org.springframework.data.jpa.repository.JpaRepository;

public interface SubscriptionRepository extends JpaRepository<Subscription, Long> {
Subscription findBySubscriptionSid(String subscriptionSid);
Optional<Subscription> findByOrderSubscriptionId(String orderSubscriptionId);
}
56 changes: 49 additions & 7 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,12 @@
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.dto.response.KakaopayCancelSubscriptionResponseDto;
import kr.bb.payment.entity.Payment;
import kr.bb.payment.entity.Subscription;
import kr.bb.payment.feign.DeliveryServiceClient;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
Expand Down Expand Up @@ -91,17 +96,22 @@ public void renewSubscription(SubscriptionBatchDtoList subscriptionBatchDtoList)
Map<Long, Long> oldDeliveryIdsMap = new HashMap<>(); // <결제기록id, old 배송id>

for(SubscriptionBatchDto subscriptionBatchDto : subscriptionBatchDtoList.getSubscriptionBatchDtoList()){
Long userId = subscriptionBatchDto.getUserId();
String orderSubscriptionId = subscriptionBatchDto.getOrderSubscriptionId();

Subscription subscription = paymentService.getSubscriptionEntity(
subscriptionBatchDto.getOrderSubscriptionId());

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

parameters.add("cid", subscriptionBatchDto.getCid());
parameters.add("sid", subscriptionBatchDto.getSid());
parameters.add("partner_order_id", String.valueOf(subscriptionBatchDto.getPartnerOrderId()));
parameters.add("partner_user_id", String.valueOf(subscriptionBatchDto.getPartnerUserId()));
parameters.add("quantity", String.valueOf(subscriptionBatchDto.getQuantity()));
parameters.add("total_amount", String.valueOf(subscriptionBatchDto.getTotalAmount()));
parameters.add("cid", subscription.getSubscriptionCid());
parameters.add("sid", subscription.getSubscriptionSid());
parameters.add("partner_order_id", String.valueOf(orderSubscriptionId));
parameters.add("partner_user_id", String.valueOf(userId));
parameters.add("quantity", String.valueOf(subscription.getSubscriptionQuantity()));
parameters.add("total_amount", String.valueOf(subscription.getSubscriptionTotalAmount()));
parameters.add("tax_free_amount", String.valueOf(0));


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

Expand All @@ -119,6 +129,38 @@ 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);
}

public void cancelSubscription(KakaopayCancelRequestDto cancelRequestDto ){
Subscription subscription = paymentService.getSubscriptionEntity(cancelRequestDto.getOrderId());

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

parameters.add("cid", subscription.getSubscriptionCid());
parameters.add("sid", subscription.getSubscriptionSid());

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

String url = "https://kapi.kakao.com/v1/payment/manage/subscription/inactive";

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

@NotNull
private HttpHeaders getHeaders() {
HttpHeaders headers = new HttpHeaders();
Expand Down
10 changes: 10 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,14 @@ public String getPaymentDate(String orderGroupId){
}
return "";
}

@Transactional(readOnly = true)
public Payment getPaymentEntity(String orderGroupId){
return paymentRepository.findByOrderId(orderGroupId);
}

@Transactional
public Subscription getSubscriptionEntity(String orderSubscriptionId) {
return subscriptionRepository.findByOrderSubscriptionId(orderSubscriptionId).orElseThrow(EntityNotFoundException::new);
}
}
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();
}
}
Loading
Loading