개요
주문 배달 관리 시스템에서 주문/결제 부분을 개발하게 되었고 주문에 대한 결제를 어떤 식으로 구현할 지에 대해 고민해 보았다.
1. 주문이 생성되면 해당 주문의 정보를 토대로 결제 정보를 만들고 저장한다.
- 고객의 주문이 들어오면 해당 주문을 가지고 Payment(결제 정보)를 구현하고 DB에 저장했다.
- Payment에는 주문 식별값(order_id), UUID(외부로 노출할 Unique 컬럼), 결제 비용(amount), 결제 상태(대기, 완료, 취소, 실패), 결제 종류(카드, 외부 결제 등)이 저장된다.
2. 결제 상태를 활용하기.
- 초기 API 명세서를 만들 때 결제 관련 API는 하나로 만들고 주문 부분부터 개발을 시작했다.
- 결제 상태(대기, 완료, 취소, 실패)에는 여러 가지가 있는데 주문 API가 하나라면 결제를 진행만 하는 API 하나만 있을 때 상태가 있을 필요성이 없다고 생각했다.
- 추가적으로 주문을 만들 때 결제 정보를 가지고 결제 대기 상태로 DB에 저장한 후 결제 진행 시 정보를 가져와 상태를 변경하는 것이 로직 상으로 더 맞을 것이라고 판단했기 때문에 팀원에게 의견을 전달해 결제 등록(주문 등록과 같이 되도록)과 결제 진행을 나누기로 했다.
3. 시간 문제
- 프로젝트 진행 중 시간 상의 문제로 결제 취소 처리와 결제 실패 시 재 결제에 대한 부분을 구현하지 못한 점이 아쉽다.
결제 관련 코드
결제를 진행하는데 필요한 클래스 코드들 중 일부
결제 등록 - 주문 등록과 같이 진행
// OrderService 코드 일부
public OrderPayment createOrder(OrderForCreate orderForCreate) {
User user = validateUserIdAndGet(orderForCreate.userId());
checkUserRoleForOrderType(user.getRole(), orderForCreate.orderType());
productService.validateProductsAndGetProductList(orderForCreate.productList());
shopService.validateShopIdAndGetShop(orderForCreate.shopId());
String orderId = orderOutPutPort.saveOrder(orderForCreate);
String paymentId = paymentService.createPayment(orderId);
return new OrderPayment(orderId, paymentId);
}
- 주문이 성공적으로 생성되면 paymentService의 createPayment(orderId)를 호출한다.
//paymentService 코드 일부
public String createPayment(String orderId) {
return paymentOutputPort.savePayment(orderId);
}
- 주문 ID와 함께 결제 정보를 저장하는 savePayment(orderId)를 호출한다.
//Payment Adapter 코드 일부
@Transactional
@Override
public String savePayment(String orderId) {
OrderEntity orderEntity = orderRepository.findByOrderUuid(orderId).get();
PaymentEntity paymentEntity = paymentRepository.save(PaymentEntity.from(orderEntity));
orderEntity.updatePayment(paymentEntity);
return paymentEntity.getPaymentUuid();
}
- OrderId를 기준으로 Order 정보를 가져와 Payment를 저장하고 Order에 Payment 정보를 업데이트 한 후 필요한 값을 반환하도록 했다.
결제 진행(실패 처리 포함)
결제 등록과 다르게 주문과 별개의 상황에서 진행되도록 코드를 설계했다.
// paymentService 코드 일부
public Payment processPayment(PaymentForUpdate paymentForUpdate) {
Order order = OrderUserIdInvalidAndGetOrder(paymentForUpdate.orderUuid(), paymentForUpdate.updatedUserId());
validateOrderCheckCanceled(order);
List<OrderProduct> orderProducts = orderProductService.findOrderProductsByOrderId(paymentForUpdate.orderUuid());
int amount = calculateTotalOrderPrice(orderProducts);
return paymentOutputPort.processPayment(paymentForUpdate, amount);
}
- 결제 진행 시 결제에 필요한 정보를 받아와 결제 금액을 계산하고 주문 상태를 변경한 후 결제 진행에 필요한 정보를 반환한다.
- 실제 결제 과정과 차이가 있는 것 같고, 수정이 필요하다, 추후 결제 시스템에 대해 더 공부하고 리팩토링을 진행해야 겠다.
// payment Adapter 코드 일부
@Transactional
@Override
public Payment processPayment(PaymentForUpdate paymentForUpdate, int amount) {
PaymentEntity paymentEntity = paymentRepository.findByPaymentUuid(paymentForUpdate.paymentUuid()).get();
try {
paymentEntity.processPayment(paymentForUpdate, amount);
} catch (PaymentProcessingException e) {
paymentEntity.failedState();
throw new PaymentFailedException(e);
}
return paymentEntity.toDomain();
}
- 결제 진행 중 문제가 발생하면 실패 처리를 할 수 있도록 try-catch문을 사용해 exception이 발생하면 결제 상태를 실패로 변경하고 예외와 메시지를 클라이언트로 반환하도록 설정했다.
마무리
- 짧은 시간동안 주문 등록, 주문 변경 및 취소, 조회, 결제 등의 기능을 만들어야 되서 많이 미흡한 점도 있었고 충분히 공부할 시간도 부족했다.
- 추후 결제 시스템에 대한 공부를 통해 현재 결제 시스템을 개선하고 싶다.
'자바 심화 > TIL' 카테고리의 다른 글
클린 코드 1 (2) | 2024.11.19 |
---|---|
페이지네이션 Offset vs Cursor (2) | 2024.11.18 |
PostgreSQL 기초 (1) | 2024.11.14 |
아키텍처(Architecture) (0) | 2024.11.12 |
데이터베이스 PK 타입 및 페이지네이션 관련 의사 결정 (1) | 2024.11.11 |