Skip to content
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
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package in.koreatech.payment.model.entity;
package in.koreatech.koin.domain.order.model;

import static jakarta.persistence.GenerationType.IDENTITY;
import static lombok.AccessLevel.PROTECTED;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
package in.koreatech.payment.repository;
package in.koreatech.koin.domain.order.repository;

import java.util.Optional;

import org.springframework.data.repository.Repository;

import in.koreatech.payment.model.entity.PaymentIdempotencyKey;
import in.koreatech.koin.domain.order.model.PaymentIdempotencyKey;

public interface PaymentIdempotencyKeyRepository extends Repository<PaymentIdempotencyKey, Integer> {

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package in.koreatech.payment.event;

import in.koreatech.payment.client.dto.response.TossPaymentConfirmResponse;
import in.koreatech.payment.model.redis.TemporaryPayment;

public record TossPaymentRollBackEvent(
String paymentKey,
TemporaryPayment temporaryPayment,
TossPaymentConfirmResponse tossPaymentConfirmResponse
) {
public static TossPaymentRollBackEvent from(String paymentKey, TemporaryPayment temporaryPayment, TossPaymentConfirmResponse tossPaymentConfirmResponse) {
return new TossPaymentRollBackEvent(paymentKey, temporaryPayment, tossPaymentConfirmResponse);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public class TemporaryPayment {

private String phoneNumber;

private in.koreatech.koin.domain.order.model.OrderType orderType;
private OrderType orderType;

private String address;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package in.koreatech.payment.service;

import in.koreatech.payment.event.TossPaymentRollBackEvent;

public interface PaymentRollBackService {
void paymentRollback(TossPaymentRollBackEvent event);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package in.koreatech.payment.service;

import static org.springframework.transaction.annotation.Propagation.REQUIRES_NEW;
import static org.springframework.transaction.event.TransactionPhase.AFTER_ROLLBACK;

import java.util.List;
import java.util.UUID;

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.event.TransactionalEventListener;

import in.koreatech.koin.domain.order.cart.repository.CartRepository;
import in.koreatech.koin.domain.order.model.Order;
import in.koreatech.koin.domain.order.model.Payment;
import in.koreatech.koin.domain.order.model.PaymentCancel;
import in.koreatech.koin.domain.order.model.PaymentIdempotencyKey;
import in.koreatech.koin.domain.order.repository.OrderRepository;
import in.koreatech.koin.domain.order.repository.PaymentCancelRepository;
import in.koreatech.koin.domain.order.repository.PaymentIdempotencyKeyRepository;
import in.koreatech.koin.domain.order.repository.PaymentRepository;
import in.koreatech.koin.domain.order.shop.model.entity.shop.OrderableShop;
import in.koreatech.koin.domain.order.shop.repository.OrderableShopRepository;
import in.koreatech.koin.domain.user.model.User;
import in.koreatech.koin.domain.user.repository.UserRepository;
import in.koreatech.payment.client.TossPaymentClient;
import in.koreatech.payment.client.dto.response.PaymentCancelResponse;
import in.koreatech.payment.event.TossPaymentRollBackEvent;
import in.koreatech.payment.model.redis.TemporaryPayment;
import in.koreatech.payment.repository.redis.TemporaryPaymentRedisRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@Service
@RequiredArgsConstructor
public class TossPaymentRollBackService implements PaymentRollBackService {

private final TossPaymentClient tossPaymentClient;
private final PaymentIdempotencyKeyRepository paymentIdempotencyKeyRepository;
private final UserRepository userRepository;
private final OrderableShopRepository orderableShopRepository;
private final OrderRepository orderRepository;
private final PaymentRepository paymentRepository;
private final TemporaryPaymentRedisRepository temporaryPaymentRedisRepository;
private final CartRepository cartRepository;
private final PaymentCancelRepository paymentCancelRepository;

private static final String PAYMENT_CANCEL_REASON = "코인 서버 오류로 인한 결제 취소";

@TransactionalEventListener(phase = AFTER_ROLLBACK)
@Transactional(propagation = REQUIRES_NEW, transactionManager = "koinTransactionManager")
public void paymentRollback(TossPaymentRollBackEvent event) {
TemporaryPayment temporaryPayment = event.temporaryPayment();
User user = userRepository.getById(temporaryPayment.getUserId());
PaymentIdempotencyKey paymentIdempotencyKey = paymentIdempotencyKeyRepository
.findByUserId(user.getId())
.map(idempotencyKey -> {
if (idempotencyKey.isOlderThanExpireDays()) {
idempotencyKey.updateIdempotencyKey(UUID.randomUUID().toString());
}
return idempotencyKey;
})
.orElseGet(() -> paymentIdempotencyKeyRepository.save(
PaymentIdempotencyKey.builder()
.userId(user.getId())
.idempotencyKey(UUID.randomUUID().toString())
.build()
));

PaymentCancelResponse response = tossPaymentClient.requestCancel(event.paymentKey(),
PAYMENT_CANCEL_REASON, paymentIdempotencyKey.getIdempotencyKey());

try {
OrderableShop orderableShop = orderableShopRepository.getById(temporaryPayment.getOrderableShopId());
Order order = temporaryPayment.toOrder(user, orderableShop);
orderRepository.save(order);

Payment payment = event.tossPaymentConfirmResponse().toEntity(order);
payment.cancel();
paymentRepository.save(payment);

List<PaymentCancel> paymentCancels = response.getPaymentCancels(payment);
paymentCancelRepository.saveAll(paymentCancels);

temporaryPaymentRedisRepository.deleteById(order.getId());
cartRepository.deleteByUserId(user.getId());
} catch (Exception e) {
log.error("결제 취소 과정에서 오류 발생 - paymentId: {}, userId: {}, orderId: {}", event.paymentKey(),
temporaryPayment.getUserId(), temporaryPayment.getOrderId());
}
}
}
12 changes: 9 additions & 3 deletions src/main/java/in/koreatech/payment/service/TossService.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import java.util.List;
import java.util.UUID;

import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

Expand All @@ -28,14 +29,15 @@
import in.koreatech.payment.dto.request.TemporaryDeliveryPaymentSaveRequest;
import in.koreatech.payment.dto.request.TemporaryTakeoutPaymentSaveRequest;
import in.koreatech.payment.dto.response.PaymentConfirmResponse;
import in.koreatech.payment.event.TossPaymentRollBackEvent;
import in.koreatech.payment.exception.OrderPriceMismatchException;
import in.koreatech.payment.exception.PaymentAlreadyCanceledException;
import in.koreatech.payment.exception.PaymentCancelException;
import in.koreatech.payment.exception.PaymentConfirmException;
import in.koreatech.payment.model.domain.TemporaryMenuItems;
import in.koreatech.payment.model.entity.PaymentIdempotencyKey;
import in.koreatech.koin.domain.order.model.PaymentIdempotencyKey;
import in.koreatech.payment.model.redis.TemporaryPayment;
import in.koreatech.payment.repository.PaymentIdempotencyKeyRepository;
import in.koreatech.koin.domain.order.repository.PaymentIdempotencyKeyRepository;
import in.koreatech.payment.repository.redis.TemporaryPaymentRedisRepository;
import in.koreatech.payment.util.OrderIdGenerator;
import in.koreatech.payment.util.TemporaryMenuItemConverter;
Expand All @@ -58,6 +60,7 @@ public class TossService implements PaymentService {
private final OrderableShopRepository orderableShopRepository;
private final OrderRepository orderRepository;
private final OrderMenuRepository orderMenuRepository;
private final ApplicationEventPublisher applicationEventPublisher;

@Transactional
public String createTemporaryDeliveryPayment(String accessToken, TemporaryDeliveryPaymentSaveRequest request) {
Expand Down Expand Up @@ -147,6 +150,9 @@ public PaymentConfirmResponse confirmPayment(String accessToken, String paymentK
throw PaymentConfirmException.withDetail("paymentStatus : " + tossPaymentResponse.status());
}

applicationEventPublisher.publishEvent(
TossPaymentRollBackEvent.from(paymentKey, temporaryPayment, tossPaymentResponse));

OrderableShop orderableShop = orderableShopRepository.getById(temporaryPayment.getOrderableShopId());
Order order = temporaryPayment.toOrder(user, orderableShop);
orderRepository.save(order);
Expand All @@ -164,7 +170,7 @@ public PaymentConfirmResponse confirmPayment(String accessToken, String paymentK
return response;
}

@Transactional
@Transactional(transactionManager = "koinTransactionManager")
public List<PaymentCancel> cancelPayment(String accessToken, String paymentKey, String cancelReason) {
Integer userId = jwtTokenResolver.getUserId(accessToken);
User user = userRepository.getById(userId);
Expand Down
Loading