0%

tosspayment 토스페이 연동하기

PG사란

Payment Gateway의 약자로 결제를 위한 인터넷 서비스를 제공하는 결제대행사를 말한다. 국내에서는 인터넷 가맹점이 소비자의 카드정보를 보유하면 안되기 때문에 PG사를 통해 결제를 진행해야만 한다. PG사마다 수수료율이 다르고, 정산주기도 다르고, 편의성도 다르다. 따라서 자신의 상황에 맞는 PG사를 선택해야 한다.

Toss Payment

당시에 토스페이를 선택한 이유는 다음과 같다.

  1. 짧은 정산주기(5일)
  2. 낮은 프론트 구현 난이도
  3. 브랜드페이(자체 결제)를 제공
  4. 좋은 UI/UX (API문서 포함)
  5. discord 채널을 통한 문의 가능

이외에도 사용을 하면서 지속적인 업데이트가 있었고, 빠른 문제 해결이 가능했다.

결제 흐름 정리

아마 다른 PG사들도 비슷할 것이다. 결제 흐름을 정리하면 다음과 같다.
image

파란색은 필자가 추가한 부분이다. 이 부분을 통해서 결제를 시도한 유저가 중간에 구매를 포기했는지 파악할 수 있다.

결제 흐름에서 중요한 용어를 정리하면 다음과 같다.
orderId는 주문 번호이다. orderId는 주문을 구분하기 위한 고유한 값이다. 따라서 중복이 되면 안된다. 6자~64자 사이의 문자열로 구성되고, 서버나 클라이언트에서 생성할 수 있다.

amount는 결제 금액이다. 사용자가 결제할 금액을 의미한다. 단위는 원이다.

paymentKey는 결제 키이다. 결제데이터 관리를 위해서 필수로 필요한 값이다. paymentKey를 통해서 결제를 진행하게 된다. PG사에서 발급해주는 값이다.

  1. 먼저 사용자가 결제를 시도하면, 클라이언트는 서버로 부터 해당 결제의 orderId, amount와 결제에 필요한 정보들을 받는다. 그리고 서버에서 받은 정보를 바탕으로 PG사에 결제를 요청한다.
  2. 결제 요청을 받은 PG사는 사용자에게 결제창을 띄운다.
  3. 사용자가 결제를 완료하면, 결제창은 사용자를 success url로 리다이렉트 시킨다.
  4. success url에 amount, orderId, paymentKey를 포함해서 도달한다.
  5. 서버는 amount, orderId에 이상이 없는지 확인하고, PG사에 paymentKey를 추가해 결제 승인 요청을 보낸다. (추가로 헤더에 SecretKey를 추가해야 한다.)
  6. PG사는 결제 승인 요청을 받아서 결제를 승인하고, 서버에 결제 승인 정보를 보낸다.
  7. 서버는 결제 승인 정보를 받아서 결제가 완료되었다는 것을 사용자에게 알린다.

이렇게 지속적으로 서버와 PG사가 통신하면서 정상 결제인지 확인하고 결제를 진행한다.

결제 흐름 구현

  • 결제 요청

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    public Order makeOrder(UserInfo user, Store store, Long amount, Cart cart, Coupon coupon, Long point) {
    List<CartItem> cartItems = cart.getCartItems().stream()
    .filter(cartItem -> !cartItem.getIsDeleted())
    .toList();

    CartItem firstItem = cartItems.get(0);
    String orderName =
    firstItem.getCount() == 1 ? firstItem.getFoodie().getName() :
    firstItem.getFoodie().getName() + " * " + firstItem.getCount();
    if (cartItems.size() > 1) {
    orderName += " 외 "
    + (cartItems.stream().filter(cartItem -> !cartItem.getIsDeleted()).count() - 1) + "개";
    }

    return Order.builder()
    .userInfo(user)
    .store(store)
    .amount(amount)
    .orderId(UUID.randomUUID().toString())
    .cart(cart)
    .paymentKey(null)
    .orderName(orderName)
    .orderNumber(null)
    .progress(Progress.REQUEST)
    .build();
    }

    카트에 담긴 음식들을 주문으로 만들어주는 메소드이고, 결제 금액인 amount에 대한 검증은 다른 매서드에서 통과한 상태이다. 주문에 필요한 정보들을 담아서 반환한다. orderId는 UUID를 사용해서 중복을 방지한다.

  • 결제 승인

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    public 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);

    return restTemplate.postForObject(TossPaymentConfig.CONFIRM_URL,
    new HttpEntity<>(params, headers),
    TosspaymentDto.class);
    }

    public 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;
    }

successUrl로부터 받은 paymentKey, orderId, amount를 통해서 결제 승인을 요청한다. TosspaymentDto는 결제 승인에 대한 응답을 담는 DTO이다. makeToosHeader는 tosspaymentConfig에서 받은 secretKey를 헤더에 추가해주는 메소드이다.

이렇게 결제 승인을 요청하면, PG사는 결제 승인을 진행하고, 결제 승인에 대한 응답을 반환한다. 승인될 때 사용자의 계좌에서 결제 금액이 차감된다.

추가 고려 사항

이렇게 하면 결제 연동은 끝이다. 하지만, 결제 연동을 하면서 추가로 고려해야 할 사항들이 있다.

실결제 환경에서는 실패와 취소에 대한 처리가 필요하다. 특히 결제 실패가 생각보다 많이 발생한다. 주로 사용자의 계좌에 돈이 부족할 때 발생한다. 그래서 결제가 실패했을 때 적절한 메시지를 사용자에게 전달해서 다시 시도하게 유도해야 한다.

여담

문제 없이 결제 연동을 하고 서비스를 운영하던중 토스페이먼츠 디코에 다음과 같은 공지가 떴다.
image
토스페이를 사용하면서 사용성이 좋았기 때문에 신청을 했다. 운이 좋게도 선정되어서 결제 연동 경험에 대해서 다양한 이야기를 할 수 있었다.

필자가 주로 얘기한 것은 예제 코드가 node로 되어있어서 java도 추가 했으면 좋겠다고 했다. 최근에 확인해보니 api문서에 java 예제 코드가 추가되었다. 이렇게 사용자의 의견을 적극적으로 수용하는 모습이 좋았다.

당시 받은 굳즈
image