3. 결제검증 API 구현하기 (서버)

클라이언트에서 결제를 구현하기 전 결제검증 API를 구현합니다.

Deprecated

이 문서는 더 이상 관리되지 않습니다.

PortOne 개발자센터를 이용해주세요.

예시를 위해 경로가 /payments/complete인 결제 성공 여부 확인 API를 구현합니다.

이 API는 내부적으로 포트원의 결제내역 단건조회 API를 호출하며 크게 3단계에 따라 정상적으로 결제가 이루어졌는지를 파악합니다.

  1. 가맹점 내부 주문 데이터와 지불된 금액 비교

    가맹점의 DB에 저장된 값과 포트원에 저장된 값을 비교합니다. 검증이 성공하면 결제정보를 데이터베이스에 저장한 뒤 결제 상태(status)에 따라 알맞은 응답을 반환하고 실패 시 에러 메세지를 출력합니다.

// bodyParser 등을 통해 body의 JSON 데이터를 파싱할 수 있는지 확인해주세요.
app.use(bodyParser.json());
// POST 요청을 받는 /payments/complete
app.post("/payments/complete", async (req, res) => {
  try {
    // 요청의 body로 SDK의 응답 중 txId와 paymentId가 오기를 기대합니다.
    const { txId, paymentId } = req.body;
    
    // 1. 포트원 API를 사용하기 위해 액세스 토큰을 발급받습니다.
    const signinResponse = await axios({
      url: "https://api.portone.io/v2/signin/api-key",
      method: "post",
      headers: { "Content-Type": "application/json" },
      data: {
        api_key: PORTONE_API_KEY, // 포트원 API Key
      },
    });
    const { access_token } = signinResponse.data;
    
    // 2. 포트원 결제내역 단건조회 API 호출
    const paymentResponse = await axios({
      url: `https://api.portone.io/v2/payments/${paymentId}`,
      method: "get",
      // 1번에서 발급받은 액세스 토큰을 Bearer 형식에 맞게 넣어주세요.
      headers: { "Authorization": "Bearer " + access_token },
    });
    const { payment: { id, transactions } } = paymentResponse.data;
    // 대표 트랜잭션(승인된 트랜잭션)을 선택합니다.
    const transaction = transactions.find(tx => tx.is_primary === true);
    
    // 3. 가맹점 내부 주문 데이터의 가격과 실제 지불된 금액을 비교합니다.
    const order = await OrderService.findById(id);
    if (order.amount === transaction.amount.total) {
      switch (status) {
        case "VIRTUAL_ACCOUNT_ISSUED": {
          const { virtual_account } = transaction.payment_method_detail;
          // 가상 계좌가 발급된 상태입니다.
          // 계좌 정보(virtual_account)를 이용해 원하는 로직을 구성하세요.
          break;
        }
        case "PAID": {
          // 모든 금액을 지불했습니다! 완료 시 원하는 로직을 구성하세요.
          break;
        }
      }
    } else {
      // 결제 금액이 불일치하여 위/변조 시도가 의심됩니다.
    }
  } catch (e) {
    // 결제 검증에 실패했습니다.
    res.status(400).send(e);
  }
});

Last updated