프로젝트에서 결제를 구현하는 게 이번 프로젝트에서 내가 원했던 거였다.

알아본 결과 Iamport라는 게 현재는 PortOne으로 바뀌었고 이걸로 결제를 구현했다.

트레이닝을 예약하는 걸로, 날짜와 시간을 선택하고 예약하기를 누르면 결제가 진행되어야 한다.

포트원을 통한 결제는 사전 검증 → 결제 요청 → 사후 검증으로 나뉜다.

우리 프로젝트에서 결제 시 주의해야 할 점은 날짜, 시간을 예약하기 때문에 먼저 예약하기로 한 시간이 있다면 그 시간을 다음에 누른 사람은 예약이 돼서는 안 된다는 거다. 이를 위해 적용했던 거는 다음 포스트에서 다루기로 하고, 이걸 위해 여기서는 미리 예약 정보를 만드는 작업을 진행했다.

결제 과정

  1. /payment/order 로 결제 전 예약 시간이 예약 가능한지 확인, 저장
  2. 결제 진행
  3. /payment/validation으로 결제가 진행된 후에 결제한 금액과 트레이닝에 등록된 금액 검증

1. /payment/order

@Override
@Transactional
public Long saveOrder(ReserveReqDto dto, User user) {
    Training training = trainingRepository.findById(dto.getTrainingId()).orElseThrow(() -> new CustomException(ErrorCode.NOT_FOUND, "존재하지 않는 트레이닝입니다."));
    if (training.isClosed()) {
        throw new CustomException(ErrorCode.BAD_REQUEST, "마감된 트레이닝은 예약할 수 없습니다.");
    }

    AvailableDate availableDate = availableDateRepository.findById(dto.getReservationDateId()).orElseThrow(() -> new CustomException(ErrorCode.NOT_FOUND, "해당 예약 날짜는 존재하지 않습니다."));
    if (!availableDate.isEnabled()) {
        throw new CustomException(ErrorCode.DATE_OR_TIME_ERROR, "해당 날짜에 예약 가능한 시간대가 없습니다.");
    }

    AvailableTime availableTime = availableTimeRepository.findById(dto.getReservationTimeId()).orElseThrow(() -> new CustomException(ErrorCode.NOT_FOUND, "해당 예약 시간은 존재하지 않습니다."));
    if (!availableTime.isEnabled()) {
        throw new CustomException(ErrorCode.DATE_OR_TIME_ERROR, "해당 시간은 이미 예약되었습니다.");
    }

    if (!availableTime.getAvailableDate().equals(availableDate)) {
        throw new CustomException(ErrorCode.DATE_OR_TIME_ERROR, "예약 하려는 날짜에 해당하는 시간이 아닙니다.");
    }

    closeReservationDateTime(availableTime, availableDate);
    availableDateRepository.saveAndFlush(availableDate);
    availableTimeRepository.saveAndFlush(availableTime);

    updateTrainingStatus(training, availableDate.getId());

    ReserveInfo reserveInfo = reserveInfoRepository.save(createReserveInfo(user, training, availableDate, availableTime));

    eventPublisher.publishEvent(createReservationNotifyRequest(training));
    return reserveInfo.getId();
}

날짜와 시간에 대한 여러 검증을 거치고 난 후에 미리 예약 정보를 만둘어둔다.

다음 글에서 동시성을 다루기도 하는데 여기서 하나의 시간에 동시성 문제가 있기때문에 시간 객체 수정 작업이 끝난 후, saveAndFlush로 db에 바로 적용되도록 ㅐ줬다.

2. 프론트에서 결제 요청

3. /payment/validation