Skip to content

Commit

Permalink
Merge pull request #113 from autumn-radiata/feat/add-payment-cancel
Browse files Browse the repository at this point in the history
[#112] feat(payment): 결제 취소 보상 트랜잭션 리스너 추가
  • Loading branch information
yunjae62 authored Oct 24, 2024
2 parents 0585625 + 413e347 commit 7cc89ea
Show file tree
Hide file tree
Showing 11 changed files with 177 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import static org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR;
import static org.springframework.http.HttpStatus.PAYMENT_REQUIRED;
import static org.springframework.http.HttpStatus.SERVICE_UNAVAILABLE;

import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
Expand All @@ -31,6 +32,7 @@ public enum ExceptionMessage {
PAY_USER_NOT_FOUND(HttpStatus.NOT_FOUND, "2002", "간편결제 사용자가 존재하지 않습니다."),
INVALID_PASSWORD(BAD_REQUEST, "2003", "비밀번호가 일치하지 않습니다."),
PAY_USER_DUPLICATE(BAD_REQUEST, "2004", "이미 등록된 사용자입니다."),
PAYMENT_NOT_FOUND(HttpStatus.NOT_FOUND, "2005", "결제 정보가 존재하지 않습니다."),

/* 유저 3000번대 */

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public DataSource mainDataSource() {
.password(mainDataSourceProperty.password())
.build();

dataSource.setMaximumPoolSize(30);
dataSource.setMaximumPoolSize(20);

return new LazyConnectionDataSourceProxy(dataSource); // 트랜잭션 진입 하더라도 커넥션 가져오지 않음
}
Expand Down
4 changes: 4 additions & 0 deletions service/payment/payment-api/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,14 @@ dependencies {
// batch
implementation 'org.springframework.boot:spring-boot-starter-batch'

//kafka
implementation 'org.springframework.kafka:spring-kafka'

/* test */

testImplementation platform('org.junit:junit-bom:5.10.0')
testImplementation 'org.junit.jupiter:junit-jupiter'
testImplementation 'org.springframework.kafka:spring-kafka-test'
}

dependencyManagement {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package radiata.service.payment.api.config;


import java.util.HashMap;
import java.util.Map;
import org.apache.kafka.clients.consumer.ConsumerConfig;
import org.apache.kafka.clients.producer.ProducerConfig;
import org.apache.kafka.common.serialization.StringDeserializer;
import org.apache.kafka.common.serialization.StringSerializer;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.kafka.annotation.EnableKafka;
import org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory;
import org.springframework.kafka.core.ConsumerFactory;
import org.springframework.kafka.core.DefaultKafkaConsumerFactory;
import org.springframework.kafka.core.DefaultKafkaProducerFactory;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.kafka.core.ProducerFactory;
import org.springframework.kafka.support.serializer.JsonSerializer;

@EnableKafka
@Configuration
public class KafkaConfig {

@Value("${spring.kafka.bootstrap-servers}")
private String bootstrapServers;

@Bean
public ProducerFactory<String, String> producerFactory() {
Map<String, Object> configProds = new HashMap<>();
configProds.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers);
configProds.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
configProds.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, JsonSerializer.class);
return new DefaultKafkaProducerFactory<>(configProds);
}

@Bean
public ConsumerFactory<String, String> consumerFactory() {
HashMap<String, Object> config = new HashMap<>();
config.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers);
config.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
config.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);

return new DefaultKafkaConsumerFactory<>(config);
}

@Bean
public KafkaTemplate<String, String> kafkaTemplate() {
return new KafkaTemplate<>(producerFactory());
}

@Bean
public ConcurrentKafkaListenerContainerFactory<String, String> KafkaListenerContainerFactory() {
ConcurrentKafkaListenerContainerFactory<String, String> factory = new ConcurrentKafkaListenerContainerFactory<>();
factory.setConsumerFactory(consumerFactory());
return factory;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package radiata.service.payment.api.listener;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.kafka.annotation.KafkaListener;
import org.springframework.stereotype.Component;
import radiata.common.domain.payment.dto.request.PaymentCancelRequestDto;
import radiata.service.payment.core.service.PaymentRollbackService;

@Slf4j
@Component
@RequiredArgsConstructor
public class PaymentRollbackListener {

private final ObjectMapper objectMapper;
private final PaymentRollbackService paymentRollbackService;

@KafkaListener(topics = "payment.cancel")
public void cancelPayment(String message) throws JsonProcessingException {
PaymentCancelRequestDto request = objectMapper.readValue(message, PaymentCancelRequestDto.class);
paymentRollbackService.rollbackPayment(request.paymentId());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ spring:
default: dev
lifecycle:
timeout-per-shutdown-phase: 10s
kafka:
bootstrap-servers: yunjae.click:9092

server:
shutdown: graceful # 애플리케이션 종료 시 정상 종료 (Graceful Shutdown)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
package radiata.service.payment.core.domain.repository;

import java.util.Optional;
import org.springframework.stereotype.Repository;
import radiata.service.payment.core.domain.model.entity.Payment;

@Repository
public interface PaymentRepository {

Payment save(Payment payment);

Optional<Payment> findById(String id);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package radiata.service.payment.core.implementation;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import radiata.common.annotation.Implementation;
import radiata.common.exception.BusinessException;
import radiata.common.message.ExceptionMessage;
import radiata.service.payment.core.domain.model.entity.Payment;
import radiata.service.payment.core.domain.repository.PaymentRepository;

@Slf4j
@Implementation
@RequiredArgsConstructor
public class PaymentReader {

private final PaymentRepository paymentRepository;

public Payment getPaymentById(String paymentId) {
return paymentRepository.findById(paymentId)
.orElseThrow(() -> new BusinessException(ExceptionMessage.PAYMENT_NOT_FOUND));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -92,4 +92,16 @@ public void requestEasyPay(Payment payment, PayUser payUser) {
// 결제 승인
payment.approve();
}

public void cancelTossPayment(Payment payment) {
// 토스 결제 취소 API 호출
log.info("Toss payment cancel request: {}", payment.getId());
payment.fail();
}

public void cancelEasyPay(Payment payment, PayUser payUser) {
// 간편결제 취소 API 호출
payUser.deposit(payment.getAmount());
payment.fail();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,8 @@ public Payment createEasyPay(String userId, Long amount) {

return paymentRepository.save(payment);
}

public Payment save(Payment payment) {
return paymentRepository.save(payment);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package radiata.service.payment.core.service;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import radiata.common.domain.payment.constant.PaymentType;
import radiata.service.payment.core.domain.model.entity.PayUser;
import radiata.service.payment.core.domain.model.entity.Payment;
import radiata.service.payment.core.implementation.PayUserReader;
import radiata.service.payment.core.implementation.PaymentReader;
import radiata.service.payment.core.implementation.PaymentRequester;
import radiata.service.payment.core.implementation.PaymentSaver;

@Slf4j
@Service
@RequiredArgsConstructor
public class PaymentRollbackService {

private final PaymentSaver paymentSaver;
private final PayUserReader payUserReader;
private final PaymentReader paymentReader;
private final PaymentRequester paymentRequester;

@Transactional
public void rollbackPayment(String paymentId) {
// 결제 조회
Payment payment = paymentReader.getPaymentById(paymentId);
// 결제 취소
if (payment.getType().equals(PaymentType.EASY_PAY)) {
// 간편결제 유저 조회
PayUser payUser = payUserReader.getPayUserByUserId(payment.getUserId());
// 간편결제 취소
paymentRequester.cancelEasyPay(payment, payUser);
} else if (payment.getType().equals(PaymentType.TOSS_PAYMENTS)) {
// 토스 결제 취소
paymentRequester.cancelTossPayment(payment);
}

// 결제 저장
paymentSaver.save(payment);
}
}

0 comments on commit 7cc89ea

Please sign in to comment.