yataproject의 토스 페이먼트API 연동로직 중

결제취소결제 내역 조회를 살펴볼 것이다.

 

 

 

📌결제 취소 

 

먼저 결제를 취소하려면 결제 승인 시 발급받은 paymentKeycancelReason이 필요하다.

또, 성공/실패 시와 같이 헤더에 시크릿 키를 인코딩하여 보내야한다.

 

Controller

    @PostMapping("/toss/cancel/point")
    public ResponseEntity tossPaymentCancelPoint(
            @AuthenticationPrincipal User principal,
            @RequestParam String paymentKey,
            @RequestParam String cancelReason
    ) {
        return ResponseEntity.ok().body(new SingleResponse<>(
                paymentService
                        .cancelPaymentPoint(principal.getUsername(), paymentKey, cancelReason)));
    }

유저 정보 ,PaymentKey, CancelReason을 받아 서비스단에 넘겨줌

 

Service

@Transactional
public Map cancelPaymentPoint(String userEmail, String paymentKey, String cancelReason) {
    Payment payment = paymentRepository.findByPaymentKeyAndCustomer_Email(paymentKey, userEmail).orElseThrow(() -> {
        throw new CustomLogicException(ExceptionCode.PAYMENT_NOT_FOUND);
    });
    // 취소 할려는데 포인트가 그만큼 없으면 환불 몬하지~
    if (payment.getCustomer().getPoint() >= payment.getAmount()) {
        payment.setCancelYN(true);
        payment.setCancelReason(cancelReason);
        payment.getCustomer().setPoint(payment.getCustomer().getPoint() - payment.getAmount());
        return tossPaymentCancel(paymentKey, cancelReason);
    }

    throw new CustomLogicException(ExceptionCode.PAYMENT_NOT_ENOUGH_POINT);
}

 

paymenyKey와 유저 Email로 알맞은 payment객체 찾아옴

payment객체의 충전금액이 환불금액 이상인지 확인 후,

취소 여부와 취소 이유,를 바꿔준 후 환불금액만큼 차감해준 후 tossPaymentCancel()메서드로 넘겨줌

public Map tossPaymentCancel(String paymentKey, String cancelReason) {
    RestTemplate restTemplate = new RestTemplate();
    HttpHeaders headers = getHeaders();
    JSONObject params = new JSONObject();
    params.put("cancelReason", cancelReason);

    return restTemplate.postForObject(TossPaymentConfig.URL + paymentKey + "/cancel",
            new HttpEntity<>(params, headers),
            Map.class);
}

이제 헤더에 시크릿 키를 인코딩하여 POST요청한 후 받은 값을 리턴해줄 것

 

🔎코드설명

HTTP 요청을 보내고 응답을 받기 위한 RestTemplate 인스턴스 생성

getHeaders() 메서드를 호출하여 HTTP 요청에 필요한 헤더를 가져옴

cancelReason을 포함하는 JSON 형식의 요청 파라미터를 생성후 params 객체에 추가

postForObject 메서드를 통해 Toss Payment URL+ 결제 키(paymentKey)+ "/cancel" 경로로 param과 header를 포함한 HTTP POST 요청을 보냄

 

Toss Payment 서비스에서 받은 응답을 Map 형식으로 반환.

private HttpHeaders getHeaders() { //요청 헤더에 Authorization 넣어줘야 함
    HttpHeaders headers = new HttpHeaders();
    String encodedAuthKey = new String(
            Base64.getEncoder().encode((tossPaymentConfig.getTestSecretKey() + ":").getBytes(StandardCharsets.UTF_8)));
    headers.setBasicAuth(encodedAuthKey);
    headers.setContentType(MediaType.APPLICATION_JSON);
    headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
    return headers;
}

헤더에는 토스에서 제공해준 시크릿 키를
Basic Authorization 방식으로 base64를 이용하여 인코딩하여 보내야함

( {시크릿키 + ":"} 조합으로 인코딩 )

 

 

 

참고 ) https://docs.tosspayments.com/common/apis/cancel-payment

 

 

📌결제 내역 조회

Dto

@Setter
@Getter
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class ChargingHistoryDto {
    private Long paymentHistoryId;
    @NonNull
    private Long amount;
    @NonNull
    private String orderName;

    private boolean isPaySuccessYN;
    private LocalDateTime createdAt;
}

1.가격
2.주문명
3.결제성공여부

4.생성시각

을 담아 반환해줄 것

 

Mapper

@Mapper(componentModel = "spring")
public interface PaymentMapper {
    default List<ChargingHistoryDto> chargingHistoryToChargingHistoryResponses(List<Payment> chargingHistories) {
        if (chargingHistories == null) {
            return null;
        }

        return chargingHistories.stream()
                .map(chargingHistory -> {
                    return ChargingHistoryDto.builder()
                            .paymentHistoryId(chargingHistory.getPaymentId())
                            .amount(chargingHistory.getAmount())
                            .orderName(chargingHistory.getOrderName())
                            .createdAt(chargingHistory.getCreatedAt())
                            .isPaySuccessYN(chargingHistory.isPaySuccessYN())
                            .build();
                }).collect(Collectors.toList());
    }
}

stream을 통해 List<ChargeHistoryDto>를 반환할 수 있도록 변환해주었다.

 

 

controller

@GetMapping("/history")
public ResponseEntity getChargingHistory(@AuthenticationPrincipal User authMember,
                                         Pageable pageable) {
    Slice<Payment> chargingHistories = paymentService.findAllChargingHistories(authMember.getUsername(), pageable);
    SliceInfo sliceInfo = new SliceInfo(pageable, chargingHistories.getNumberOfElements(), chargingHistories.hasNext());
    return new ResponseEntity<>(
            new SliceResponseDto<>(mapper.chargingHistoryToChargingHistoryResponses(chargingHistories.getContent()), sliceInfo), HttpStatus.OK);
}

yata 프로젝트에서는 데이터를 슬라이스 형식으로 보여주기 위해 SliceResponseDto를 사용하였다.

 

 

 

 

service

@Override
public Slice<Payment> findAllChargingHistories(String username, Pageable pageable) {
    memberService.verifyMember(username);
    return paymentRepository.findAllByCustomer_Email(username,
            PageRequest.of(pageable.getPageNumber(), pageable.getPageSize(),
                    Sort.Direction.DESC, "paymentId")
    );
}

username을 통해 멤버 확인 후
repository의 findAllByCustomer_Email()메서드를 통해  paymentId를 기준으로
payment내역을 내림차순 정렬하여 가져온다.

 

 

postman으로 결제 내역을 조회해보면, 결제 내역이 잘 가져와지는 것을 확인할 수 있다.

사진 추가

 

 

 

 

📌결과

이렇게 YataProject에서 포인트 충전을 구현할 수 있었고, 실제 서비스 화면에선 다음과 같이 포인트 충전이 가능하며, 충전 내역을 조회할 수 있게 되었다.

복사했습니다!