diff --git a/src/main/java/com/readyvery/readyverydemo/domain/Order.java b/src/main/java/com/readyvery/readyverydemo/domain/Order.java index 31fd894..ef4916f 100644 --- a/src/main/java/com/readyvery/readyverydemo/domain/Order.java +++ b/src/main/java/com/readyvery/readyverydemo/domain/Order.java @@ -11,6 +11,7 @@ import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; +import jakarta.persistence.Index; import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; import jakarta.persistence.OneToMany; @@ -20,14 +21,16 @@ import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; +import lombok.Setter; import lombok.extern.slf4j.Slf4j; @Getter @Entity @Builder -@Table(name = "ORDERS") +@Table(name = "ORDERS", indexes = {@Index(name = "idx_order_id", columnList = "orderId", unique = true)}) @AllArgsConstructor @NoArgsConstructor +@Setter @Slf4j public class Order { @Id @@ -70,6 +73,7 @@ public class Order { // 가게 아이템 연관 관계 @OneToMany(mappedBy = "order") + @Builder.Default private List orderItems = new ArrayList(); // 가게 연관 관계 diff --git a/src/main/java/com/readyvery/readyverydemo/domain/OrderItem.java b/src/main/java/com/readyvery/readyverydemo/domain/OrderItem.java index 2491b35..705b351 100644 --- a/src/main/java/com/readyvery/readyverydemo/domain/OrderItem.java +++ b/src/main/java/com/readyvery/readyverydemo/domain/OrderItem.java @@ -44,6 +44,7 @@ public class OrderItem { private Foodie foodie; @OneToMany(mappedBy = "orderItem") + @Builder.Default private List orderItemOptions = new ArrayList(); } diff --git a/src/main/java/com/readyvery/readyverydemo/domain/Progress.java b/src/main/java/com/readyvery/readyverydemo/domain/Progress.java index c773e4e..be32da7 100644 --- a/src/main/java/com/readyvery/readyverydemo/domain/Progress.java +++ b/src/main/java/com/readyvery/readyverydemo/domain/Progress.java @@ -10,7 +10,7 @@ public enum Progress { REQUEST("REQUEST", "토스 결제 요청"), ORDER("ORDER", "주문 접수"), MAKE("MAKE", "음식 제조 중"), - COMPLETE("COMPLETE", "배달 완료"), + COMPLETE("COMPLETE", "제조 완료"), PICKUP("PICKUP", "픽업 완료"); private final String key; diff --git a/src/main/java/com/readyvery/readyverydemo/domain/Receipt.java b/src/main/java/com/readyvery/readyverydemo/domain/Receipt.java index a3ea477..2c1ce4a 100644 --- a/src/main/java/com/readyvery/readyverydemo/domain/Receipt.java +++ b/src/main/java/com/readyvery/readyverydemo/domain/Receipt.java @@ -1,9 +1,11 @@ package com.readyvery.readyverydemo.domain; +import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.FetchType; import jakarta.persistence.Id; import jakarta.persistence.JoinColumn; +import jakarta.persistence.MapsId; import jakarta.persistence.OneToOne; import jakarta.persistence.Table; import lombok.AllArgsConstructor; @@ -21,7 +23,93 @@ @Slf4j public class Receipt { @Id + private Long id; + @OneToOne(fetch = FetchType.LAZY) + @MapsId @JoinColumn(name = "order_idx") private Order order; + + @Column + private String type; + + @Column + private String mid; + + @Column + private String currency; + + @Column + private Long balanceAmount; + + @Column + private Long suppliedAmount; + + @Column + private String status; + + @Column + private String requestedAt; + + @Column + private String approvedAt; + + @Column + private String lastTransactionKey; + + @Column + private Long vat; + + @Column + private Long taxFreeAmount; + + @Column + private Long taxExemptionAmount; + + // @Column(columnDefinition = "json") + @Column + private String cancels; + + // @Column(columnDefinition = "json") + @Column + private String card; + + // @Column(columnDefinition = "json") + @Column + private String receipt; + + // @Column(columnDefinition = "json") + @Column + private String checkout; + + // @Column(columnDefinition = "json") + @Column + private String easyPay; + + @Column + private String country; + + // @Column(columnDefinition = "json") + @Column + private String failure; + + // @Column(columnDefinition = "json") + @Column + private String discount; + + // @Column(columnDefinition = "json") + @Column + private String virtualAccount; + + // @Column(columnDefinition = "json") + @Column + private String transfer; + + // @Column(columnDefinition = "json") + @Column + private String cashReceipt; + + // @Column(columnDefinition = "json") + @Column + private String cashReceipts; } diff --git a/src/main/java/com/readyvery/readyverydemo/domain/UserInfo.java b/src/main/java/com/readyvery/readyverydemo/domain/UserInfo.java index 994749e..5e37501 100644 --- a/src/main/java/com/readyvery/readyverydemo/domain/UserInfo.java +++ b/src/main/java/com/readyvery/readyverydemo/domain/UserInfo.java @@ -96,10 +96,12 @@ public class UserInfo extends BaseTimeEntity { // 유저 주문 연관관계 매핑 @OneToMany(mappedBy = "userInfo", cascade = CascadeType.ALL) + @Builder.Default private List orders = new ArrayList(); // 유저 쿠폰 연관관계 매핑 @OneToMany(mappedBy = "userInfo", cascade = CascadeType.ALL) + @Builder.Default private List coupons = new ArrayList(); // 리프레시토큰 업데이트 diff --git a/src/main/java/com/readyvery/readyverydemo/domain/repository/OrdersRepository.java b/src/main/java/com/readyvery/readyverydemo/domain/repository/OrdersRepository.java new file mode 100644 index 0000000..ce0f46e --- /dev/null +++ b/src/main/java/com/readyvery/readyverydemo/domain/repository/OrdersRepository.java @@ -0,0 +1,11 @@ +package com.readyvery.readyverydemo.domain.repository; + +import java.util.Optional; + +import org.springframework.data.jpa.repository.JpaRepository; + +import com.readyvery.readyverydemo.domain.Order; + +public interface OrdersRepository extends JpaRepository { + Optional findByOrderId(String orderId); +} diff --git a/src/main/java/com/readyvery/readyverydemo/domain/repository/ReceiptRepository.java b/src/main/java/com/readyvery/readyverydemo/domain/repository/ReceiptRepository.java new file mode 100644 index 0000000..27bb672 --- /dev/null +++ b/src/main/java/com/readyvery/readyverydemo/domain/repository/ReceiptRepository.java @@ -0,0 +1,8 @@ +package com.readyvery.readyverydemo.domain.repository; + +import org.springframework.data.jpa.repository.JpaRepository; + +import com.readyvery.readyverydemo.domain.Receipt; + +public interface ReceiptRepository extends JpaRepository { +} diff --git a/src/main/java/com/readyvery/readyverydemo/global/exception/ExceptionCode.java b/src/main/java/com/readyvery/readyverydemo/global/exception/ExceptionCode.java index 68926f1..d4947fd 100644 --- a/src/main/java/com/readyvery/readyverydemo/global/exception/ExceptionCode.java +++ b/src/main/java/com/readyvery/readyverydemo/global/exception/ExceptionCode.java @@ -13,7 +13,10 @@ public enum ExceptionCode { OPTION_NOT_FOUND(404, "Option does not exists."), CART_ITEM_NOT_FOUND(404, "Cart item does not exists."), CART_NOT_FOUND(404, "Cart does not exists."), - ITEM_NOT_SAME_STORE(400, "Item is not same store."); + ITEM_NOT_SAME_STORE(400, "Item is not same store."), + TOSS_PAYMENT_SUCCESS_FAIL(400, "Toss payment success fail."), + ORDER_NOT_FOUND(400, "Order does not exists."), + TOSS_PAYMENT_AMOUNT_NOT_MATCH(400, "Toss payment amount not match."); private int status; private String message; diff --git a/src/main/java/com/readyvery/readyverydemo/src/order/OrderController.java b/src/main/java/com/readyvery/readyverydemo/src/order/OrderController.java index 8f7754f..583a68d 100644 --- a/src/main/java/com/readyvery/readyverydemo/src/order/OrderController.java +++ b/src/main/java/com/readyvery/readyverydemo/src/order/OrderController.java @@ -50,6 +50,15 @@ public ResponseEntity getCart(@AuthenticationPrincipal CustomUserDet return new ResponseEntity<>(cartGetRes, HttpStatus.OK); } + @GetMapping("/toss/success") + public ResponseEntity tossPaymentSuccess( + @RequestParam("paymentKey") String paymentKey, + @RequestParam("orderId") String orderId, + @RequestParam("amount") Long amount) { + String result = orderService.tossPaymentSuccess(paymentKey, orderId, amount); + return new ResponseEntity<>(result, HttpStatus.OK); + } + @PostMapping("/cart") public ResponseEntity addCart(@AuthenticationPrincipal CustomUserDetails userDetails, @RequestBody CartAddReq cartAddReq) { diff --git a/src/main/java/com/readyvery/readyverydemo/src/order/OrderService.java b/src/main/java/com/readyvery/readyverydemo/src/order/OrderService.java index ed248c3..e930703 100644 --- a/src/main/java/com/readyvery/readyverydemo/src/order/OrderService.java +++ b/src/main/java/com/readyvery/readyverydemo/src/order/OrderService.java @@ -27,4 +27,6 @@ public interface OrderService { CartGetRes getCart(CustomUserDetails userDetails, Long inout); TosspaymentMakeRes requestTossPayment(CustomUserDetails userDetails, PaymentReq paymentReq); + + String tossPaymentSuccess(String paymentKey, String orderId, Long amount); } diff --git a/src/main/java/com/readyvery/readyverydemo/src/order/OrderServiceImpl.java b/src/main/java/com/readyvery/readyverydemo/src/order/OrderServiceImpl.java index 8e36ccf..fd1df17 100644 --- a/src/main/java/com/readyvery/readyverydemo/src/order/OrderServiceImpl.java +++ b/src/main/java/com/readyvery/readyverydemo/src/order/OrderServiceImpl.java @@ -1,11 +1,20 @@ package com.readyvery.readyverydemo.src.order; +import java.nio.charset.StandardCharsets; +import java.util.Base64; +import java.util.Collections; import java.util.List; import java.util.Set; import java.util.UUID; import java.util.stream.Collectors; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; import org.springframework.stereotype.Service; +import org.springframework.web.client.RestTemplate; + +import net.minidev.json.JSONObject; import com.readyvery.readyverydemo.domain.Cart; import com.readyvery.readyverydemo.domain.CartItem; @@ -15,6 +24,7 @@ import com.readyvery.readyverydemo.domain.FoodieOptionCategory; import com.readyvery.readyverydemo.domain.Order; import com.readyvery.readyverydemo.domain.Progress; +import com.readyvery.readyverydemo.domain.Receipt; import com.readyvery.readyverydemo.domain.Store; import com.readyvery.readyverydemo.domain.UserInfo; import com.readyvery.readyverydemo.domain.repository.CartItemRepository; @@ -23,11 +33,14 @@ import com.readyvery.readyverydemo.domain.repository.FoodieOptionRepository; import com.readyvery.readyverydemo.domain.repository.FoodieRepository; import com.readyvery.readyverydemo.domain.repository.OrderRepository; +import com.readyvery.readyverydemo.domain.repository.OrdersRepository; +import com.readyvery.readyverydemo.domain.repository.ReceiptRepository; import com.readyvery.readyverydemo.domain.repository.StoreRepository; import com.readyvery.readyverydemo.domain.repository.UserRepository; import com.readyvery.readyverydemo.global.exception.BusinessLogicException; import com.readyvery.readyverydemo.global.exception.ExceptionCode; import com.readyvery.readyverydemo.security.jwt.dto.CustomUserDetails; +import com.readyvery.readyverydemo.src.order.config.TossPaymentConfig; import com.readyvery.readyverydemo.src.order.dto.CartAddReq; import com.readyvery.readyverydemo.src.order.dto.CartAddRes; import com.readyvery.readyverydemo.src.order.dto.CartEditReq; @@ -40,6 +53,7 @@ import com.readyvery.readyverydemo.src.order.dto.FoodyDto; import com.readyvery.readyverydemo.src.order.dto.OrderMapper; import com.readyvery.readyverydemo.src.order.dto.PaymentReq; +import com.readyvery.readyverydemo.src.order.dto.TosspaymentDto; import com.readyvery.readyverydemo.src.order.dto.TosspaymentMakeRes; import lombok.RequiredArgsConstructor; @@ -56,6 +70,9 @@ public class OrderServiceImpl implements OrderService { private final StoreRepository storeRepository; private final OrderRepository orderRepository; private final OrderMapper orderMapper; + private final TossPaymentConfig tosspaymentConfig; + private final OrdersRepository ordersRepository; + private final ReceiptRepository receiptRepository; @Override public FoodyDetailRes getFoody(Long storeId, Long foodyId, Long inout) { @@ -146,6 +163,67 @@ public TosspaymentMakeRes requestTossPayment(CustomUserDetails userDetails, Paym return orderMapper.orderToTosspaymentMakeRes(order); } + @Override + public String tossPaymentSuccess(String paymentKey, String orderId, Long amount) { + Order order = getOrder(orderId); + verifyOrder(order, amount); + TosspaymentDto tosspaymentDto = requestTossPaymentAccept(paymentKey, orderId, amount); + applyTosspaymentDto(order, tosspaymentDto); + orderRepository.save(order); + //TODO: 영수증 처리 + Receipt receipt = orderMapper.tosspaymentDtoToReceipt(tosspaymentDto, order); + receiptRepository.save(receipt); + return "hi"; + } + + private void applyTosspaymentDto(Order order, TosspaymentDto tosspaymentDto) { + //TODO: orderNumber 적용 + order.setPaymentKey(tosspaymentDto.getPaymentKey()); + order.setMethod(tosspaymentDto.getMethod()); + order.setProgress(Progress.ORDER); + } + + private void verifyOrder(Order order, Long amount) { + if (!order.getTotalAmount().equals(amount)) { + throw new BusinessLogicException(ExceptionCode.TOSS_PAYMENT_AMOUNT_NOT_MATCH); + } + } + + private Order getOrder(String orderId) { + return ordersRepository.findByOrderId(orderId).orElseThrow( + () -> new BusinessLogicException(ExceptionCode.ORDER_NOT_FOUND) + ); + } + + private TosspaymentDto requestTossPaymentAccept(String paymentKey, String orderId, Long amount) { + RestTemplate restTemplate = new RestTemplate(); + HttpHeaders headers = makeTossHeader(); + JSONObject params = new JSONObject(); + + params.put("amount", amount); + params.put("orderId", orderId); + params.put("paymentKey", paymentKey); + + try { + return restTemplate.postForObject(TossPaymentConfig.CONFIRM_URL, + new HttpEntity<>(params, headers), + TosspaymentDto.class); + } catch (Exception e) { + System.out.println("e.getMessage() = " + e.getMessage()); + throw new BusinessLogicException(ExceptionCode.TOSS_PAYMENT_SUCCESS_FAIL); + } + } + + private HttpHeaders makeTossHeader() { + HttpHeaders headers = new HttpHeaders(); + String encodedAuthKey = new String( + Base64.getEncoder().encode((tosspaymentConfig.getTossSecretKey() + ":").getBytes(StandardCharsets.UTF_8))); + headers.setBasicAuth(encodedAuthKey); + headers.setContentType(MediaType.APPLICATION_JSON); + headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON)); + return headers; + } + private Order makeOrder(UserInfo user, Store store, Long amount, List carts) { if (carts.isEmpty()) { throw new BusinessLogicException(ExceptionCode.CART_NOT_FOUND); diff --git a/src/main/java/com/readyvery/readyverydemo/src/order/dto/OrderMapper.java b/src/main/java/com/readyvery/readyverydemo/src/order/dto/OrderMapper.java index e561a7b..3381ca9 100644 --- a/src/main/java/com/readyvery/readyverydemo/src/order/dto/OrderMapper.java +++ b/src/main/java/com/readyvery/readyverydemo/src/order/dto/OrderMapper.java @@ -13,6 +13,7 @@ import com.readyvery.readyverydemo.domain.FoodieOption; import com.readyvery.readyverydemo.domain.FoodieOptionCategory; import com.readyvery.readyverydemo.domain.Order; +import com.readyvery.readyverydemo.domain.Receipt; import com.readyvery.readyverydemo.src.order.config.TossPaymentConfig; import lombok.RequiredArgsConstructor; @@ -152,4 +153,35 @@ public TosspaymentMakeRes orderToTosspaymentMakeRes(Order order) { .amount(order.getAmount()) .build(); } + + public Receipt tosspaymentDtoToReceipt(TosspaymentDto tosspaymentDto, Order order) { + return Receipt.builder() + .order(order) + .type(tosspaymentDto.getType()) + .mid(tosspaymentDto.getMid()) + .currency(tosspaymentDto.getCurrency()) + .balanceAmount(tosspaymentDto.getBalanceAmount()) + .suppliedAmount(tosspaymentDto.getSuppliedAmount()) + .status(tosspaymentDto.getStatus()) + .requestedAt(tosspaymentDto.getRequestedAt()) + .approvedAt(tosspaymentDto.getApprovedAt()) + .lastTransactionKey(tosspaymentDto.getLastTransactionKey()) + .vat(tosspaymentDto.getVat()) + .taxFreeAmount(tosspaymentDto.getTaxFreeAmount()) + .taxExemptionAmount(tosspaymentDto.getTaxExemptionAmount()) + .cancels(tosspaymentDto.getCancels() != null ? tosspaymentDto.getCancels().toString() : null) + .card(tosspaymentDto.getCard() != null ? tosspaymentDto.getCard().toString() : null) + .receipt(tosspaymentDto.getReceipt() != null ? tosspaymentDto.getReceipt().toString() : null) + .checkout(tosspaymentDto.getCheckout() != null ? tosspaymentDto.getCheckout().toString() : null) + .easyPay(tosspaymentDto.getEasyPay() != null ? tosspaymentDto.getEasyPay().toString() : null) + .country(tosspaymentDto.getCountry()) + .failure(tosspaymentDto.getFailure() != null ? tosspaymentDto.getFailure().toString() : null) + .discount(tosspaymentDto.getDiscount() != null ? tosspaymentDto.getDiscount().toString() : null) + .virtualAccount( + tosspaymentDto.getVirtualAccount() != null ? tosspaymentDto.getVirtualAccount().toString() : null) + .transfer(tosspaymentDto.getTransfer() != null ? tosspaymentDto.getTransfer().toString() : null) + .cashReceipt(tosspaymentDto.getCashReceipt() != null ? tosspaymentDto.getCashReceipt().toString() : null) + .cashReceipts(tosspaymentDto.getCashReceipts() != null ? tosspaymentDto.getCashReceipts().toString() : null) + .build(); + } } diff --git a/src/main/java/com/readyvery/readyverydemo/src/order/dto/TosspaymentDto.java b/src/main/java/com/readyvery/readyverydemo/src/order/dto/TosspaymentDto.java new file mode 100644 index 0000000..3a4564c --- /dev/null +++ b/src/main/java/com/readyvery/readyverydemo/src/order/dto/TosspaymentDto.java @@ -0,0 +1,36 @@ +package com.readyvery.readyverydemo.src.order.dto; + +import lombok.Getter; + +@Getter +public class TosspaymentDto { + private String paymentKey; + private String type; + private String orderId; + private String orderName; + private String mid; + private String method; + private String currency; + private Long totalAmount; + private Long balanceAmount; + private Long suppliedAmount; + private String status; + private String requestedAt; + private String approvedAt; + private String lastTransactionKey; + private Long vat; + private Long taxFreeAmount; + private Long taxExemptionAmount; + private Object cancels; // JSON 문자열로 변경 + private Object card; // JSON 문자열로 변경 + private Object receipt; // JSON 문자열로 변경 + private Object checkout; // JSON 문자열로 변경 + private Object easyPay; // JSON 문자열로 변경 + private String country; + private Object failure; // JSON 문자열로 변경 + private Object discount; // JSON 문자열로 변경 + private Object virtualAccount; // JSON 문자열로 변경 + private Object transfer; // JSON 문자열로 변경 + private Object cashReceipt; // JSON 문자열로 변경 + private Object cashReceipts; // JSON 문자열로 변경 +}