Skip to content

Commit

Permalink
feat : 토스 결제 취소 API 연결 #40
Browse files Browse the repository at this point in the history
  • Loading branch information
JiwonKKang committed Aug 28, 2024
1 parent 5ae2294 commit d93613e
Show file tree
Hide file tree
Showing 22 changed files with 182 additions and 15 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
@RestController
@RequestMapping("/api/v1/payments")
@RequiredArgsConstructor
public class PaymentApi {
public class PaymentApi implements PaymentApiDocs {

private final PaymentService paymentService;

Expand All @@ -41,6 +41,6 @@ public Response<Void> fail(PaymentFailReason paymentFailReason) {
public Response<Void> cancel(
@PathVariable Long paymentId, @RequestBody PaymentCancelRequest request) {
paymentService.cancel(PaymentId.from(paymentId), request.cancelReason());
return Response.success();
return Response.success("성공적으로 결제가 취소되었습니다.");
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,23 @@
package core.startup.mealtoktok.api.payment;

import core.startup.mealtoktok.api.payment.dto.PaymentCancelRequest;
import core.startup.mealtoktok.api.payment.dto.PaymentFailReason;
import core.startup.mealtoktok.api.payment.dto.PaymentRequest;
import core.startup.mealtoktok.api.payment.dto.PaymentResponse;
import core.startup.mealtoktok.common.dto.Response;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;

@Tag(name = "결제 API")
public interface PaymentApiDocs {}
public interface PaymentApiDocs {

@Operation(summary = "인증 성공 시")
Response<PaymentResponse> pay(PaymentRequest request);

@Operation(summary = "인증 실패 시")
Response<Void> fail(PaymentFailReason paymentFailReason);

@Operation(summary = "결제 취소")
Response<Void> cancel(Long paymentId, PaymentCancelRequest request);
}
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,16 @@ public Response<UserId> changeEmail(@AuthenticationPrincipal User currentUser, S
return Response.success(userService.changeEmail(currentUser, email));
}

@PatchMapping("/my/addresses/default")
public Response<UserId> addDefaultDeliveryAddress(
@AuthenticationPrincipal User currentUser,
@RequestBody AddressInfoRequest addressInfoRequest) {

return Response.success(
userService.addDefaultDeliveryAddress(
currentUser, addressInfoRequest.toAddressWithCoordinate()));
}

@PatchMapping("/my/addresses")
public Response<UserId> addDeliveryAddress(
@AuthenticationPrincipal User currentUser,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ public interface UserApiDocs {
@Operation(summary = "이메일 변경")
Response<UserId> changeEmail(User currentUser, String email);

@Operation(summary = "기본 배송지 추가")
Response<UserId> addDefaultDeliveryAddress(
User currentUser, AddressInfoRequest addressInfoRequest);

@Operation(summary = "배송지 추가")
Response<UserId> addDeliveryAddress(User currentUser, AddressInfoRequest addressInfoRequest);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package core.startup.mealtoktok.common.domain;

import lombok.NoArgsConstructor;

@NoArgsConstructor(access = lombok.AccessLevel.PROTECTED)
public abstract class BaseId<T> {

private T value;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
public record MealOwner(Long userId) {

public static MealOwner from(User user) {
return new MealOwner(user.getUserId());
return new MealOwner(user.getUserId().getValue());
}

public boolean isSameUser(MealOwner mealOwner) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

import core.startup.mealtoktok.domain.order.Money;
import core.startup.mealtoktok.domain.order.OrderId;
import core.startup.mealtoktok.domain.payment.exception.PaymentDomainException;

@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
Expand All @@ -26,6 +27,7 @@ public class Payment {
private PaymentState paymentState;
private String failedReason;
private String canceledReason;
private LocalDateTime canceledAt;
private LocalDateTime requestedAt;
private LocalDateTime approvedAt;

Expand All @@ -36,4 +38,14 @@ public static Payment fail(String failedReason, OrderId orderId) {
.failedReason(failedReason)
.build();
}

public Payment cancel(String canceledReason) {
if (!this.paymentState.equals(PaymentState.PAYMENT_COMPLETED)) {
throw new PaymentDomainException("결제가 완료된 상태에서만 취소가 가능합니다.");
}
this.paymentState = PaymentState.PAYMENT_CANCELLED;
this.canceledReason = canceledReason;
this.canceledAt = LocalDateTime.now();
return this;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,6 @@
public interface PaymentGateway {

Payment confirm(String paymentKey, OrderId orderId, Money amount);

void cancel(String paymentKey, String cancelReason);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package core.startup.mealtoktok.domain.payment;

import org.springframework.stereotype.Component;

import lombok.RequiredArgsConstructor;

@Component
@RequiredArgsConstructor
public class PaymentManager {

private final PaymentRepository paymentRepository;

public void cancel(Payment payment, String cancelReason) {
Payment cancelled = payment.cancel(cancelReason);
paymentRepository.update(cancelled);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,6 @@ public interface PaymentRepository {
Payment save(Payment payment);

Payment findById(PaymentId paymentId);

void update(Payment payment);
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,13 @@ public class PaymentService {
private final PaymentGateway paymentGateway;
private final PaymentAppender paymentAppender;
private final PaymentReader paymentReader;
private final PaymentManager paymentManager;
private final OrderReader orderReader;

public Payment pay(String paymentKey, OrderId orderId, Money amount) {
public Payment pay(String paymentKey, OrderId orderId, Money payAmount) {
Order order = orderReader.read(orderId);
paymentValidator.validate(order, amount);
Payment confirmedPayment = paymentGateway.confirm(paymentKey, orderId, amount);
paymentValidator.validate(order, payAmount);
Payment confirmedPayment = paymentGateway.confirm(paymentKey, orderId, payAmount);
return paymentAppender.append(confirmedPayment);
}

Expand All @@ -31,7 +32,9 @@ public void fail(String failReason, OrderId orderId) {
paymentAppender.append(failedPayment);
}

public void cancel(PaymentId paymentId, String s) {
paymentReader.read(paymentId);
public void cancel(PaymentId paymentId, String cancelReason) {
Payment payment = paymentReader.read(paymentId);
paymentGateway.cancel(payment.getPaymentKey(), cancelReason);
paymentManager.cancel(payment, cancelReason);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package core.startup.mealtoktok.domain.payment.exception;

import core.startup.mealtoktok.common.exception.DomainException;

public class PaymentDomainException extends DomainException {

public PaymentDomainException(String message) {
super(PaymentErrorCode.PAYMENT_DOMAIN_ERROR.setMessage(message));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

@AllArgsConstructor
public enum PaymentErrorCode implements BaseErrorCode {
PAYMENT_NOT_FOUND(404, "PAYMENT_404_1", "결제 정보를 찾을 수 없습니다"),
PAYMENT_DOMAIN_ERROR(400, "PAYMENT_400_1"),
PAYMENT_AMOUNT_MISMATCH(400, "PAYMENT_400_2", "주문금액과 결제금액이 일치하지 않습니다");

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
package core.startup.mealtoktok.domain.user;

import lombok.AccessLevel;
import lombok.NoArgsConstructor;

import core.startup.mealtoktok.common.domain.BaseId;

@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class UserId extends BaseId<Long> {

public UserId(Long id) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ public class UserService {
private final userRemover userRemover;
private final UserValidator userValidator;

public UserId addDefaultDeliveryAddress(
User currentUser, AddressWithCoordinate addressWithCoordinate) {
return userUpdater.addDeliveryAddress(
currentUser, DeliveryAddress.configure(addressWithCoordinate));
}

public UserId addDeliveryAddress(
User currentUser, AddressWithCoordinate addressWithCoordinate) {
return userUpdater.addDeliveryAddress(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import core.startup.mealtoktok.domain.payment.Payment;
import core.startup.mealtoktok.domain.payment.PaymentGateway;
import core.startup.mealtoktok.infra.toss.client.TossPaymentClient;
import core.startup.mealtoktok.infra.toss.dto.TossCancelRequest;
import core.startup.mealtoktok.infra.toss.dto.TossConfirmRequest;
import core.startup.mealtoktok.infra.toss.dto.TossPayment;

Expand All @@ -24,4 +25,9 @@ public Payment confirm(String paymentKey, OrderId orderId, Money money) {
tossPaymentClient.confirm(TossConfirmRequest.of(paymentKey, orderId, money));
return confirmedPayment.toDomain();
}

@Override
public void cancel(String paymentKey, String cancelReason) {
tossPaymentClient.cancel(paymentKey, TossCancelRequest.from(cancelReason));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;

import lombok.AccessLevel;
import lombok.AllArgsConstructor;
Expand All @@ -30,6 +31,7 @@
@AllArgsConstructor
@Builder
@Getter
@Table(name = "payment")
public class PaymentEntity {

@Id
Expand All @@ -52,19 +54,24 @@ public class PaymentEntity {
@Enumerated(EnumType.STRING)
private PaymentState paymentState;

private String failedReason;

private String canceledReason;

private LocalDateTime requestedAt;

private LocalDateTime approvedAt;

public static PaymentEntity from(Payment payment) {
return PaymentEntity.builder()
.paymentId(payment.getPaymentId().getValue())
.paymentKey(payment.getPaymentKey())
.orderId(payment.getOrderId().toString())
.payAmount(payment.getPayAmount())
.paymentMethod(payment.getPaymentMethod())
.paymentProvider(payment.getPaymentProvider())
.paymentState(payment.getPaymentState())
.failedReason(payment.getFailedReason())
.canceledReason(payment.getCanceledReason())
.requestedAt(payment.getRequestedAt())
.approvedAt(payment.getApprovedAt())
.build();
Expand All @@ -79,8 +86,15 @@ public Payment toDomain() {
.paymentMethod(paymentMethod)
.paymentProvider(paymentProvider)
.paymentState(paymentState)
.failedReason(failedReason)
.canceledReason(canceledReason)
.requestedAt(requestedAt)
.approvedAt(approvedAt)
.build();
}

public void update(Payment payment) {
this.paymentState = payment.getPaymentState();
this.canceledReason = payment.getCanceledReason();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package core.startup.mealtoktok.infra.payment.exception;

import core.startup.mealtoktok.common.exception.InfraException;
import core.startup.mealtoktok.domain.payment.exception.PaymentErrorCode;

public class PaymentNotFoundException extends InfraException {

public static final PaymentNotFoundException EXCEPTION = new PaymentNotFoundException();

private PaymentNotFoundException() {
super(PaymentErrorCode.PAYMENT_NOT_FOUND);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import core.startup.mealtoktok.domain.payment.PaymentId;
import core.startup.mealtoktok.domain.payment.PaymentRepository;
import core.startup.mealtoktok.infra.payment.entity.PaymentEntity;
import core.startup.mealtoktok.infra.payment.exception.PaymentNotFoundException;

@Repository
@RequiredArgsConstructor
Expand All @@ -22,6 +23,18 @@ public Payment save(Payment payment) {

@Override
public Payment findById(PaymentId paymentId) {
return null;
return getPaymentEntity(paymentId).toDomain();
}

@Override
public void update(Payment payment) {
PaymentEntity paymentEntity = getPaymentEntity(payment.getPaymentId());
paymentEntity.update(payment);
}

private PaymentEntity getPaymentEntity(PaymentId paymentId) {
return paymentJpaRepository
.findById(paymentId.getValue())
.orElseThrow(() -> PaymentNotFoundException.EXCEPTION);
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
package core.startup.mealtoktok.infra.toss.client;

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;

import core.startup.mealtoktok.infra.toss.config.TossPaymentClientConfig;
import core.startup.mealtoktok.infra.toss.dto.TossCancelRequest;
import core.startup.mealtoktok.infra.toss.dto.TossConfirmRequest;
import core.startup.mealtoktok.infra.toss.dto.TossPayment;

Expand All @@ -15,5 +17,8 @@
public interface TossPaymentClient {

@PostMapping("/confirm")
TossPayment confirm(@RequestBody TossConfirmRequest tossConfirmRequest);
TossPayment confirm(@RequestBody TossConfirmRequest confirmRequest);

@PostMapping("/{paymentKey}/cancel")
void cancel(@PathVariable String paymentKey, @RequestBody TossCancelRequest cancelRequest);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package core.startup.mealtoktok.infra.toss.dto;

public record TossCancelRequest(String cancelReason) {

public static TossCancelRequest from(String cancelReason) {
return new TossCancelRequest(cancelReason);
}
}
Loading

0 comments on commit d93613e

Please sign in to comment.