개발자센터
V1
V2

결제정보 사후 검증하기

결제 정보를 사후 검증하는 과정은 크게 세 단계로 이루어집니다.

  • 응답받은 내용을 바탕으로 실 결제 금액과 결제요청금액(가맹점 자체 데이터베이스)을 비교
  • 결제 상세내역 조회를 위해 포트원 결제 단건 조회 API 요청
  • 포트원 결제고유번호(imp_uid), 가맹점 주문번호(merchant_uid)를 프론트엔드로부터 수신

STEP 01 결제결과 서버 수신

결제정보를 받은 가맹점 endpoint URL 에 대한 POST 요청을 수신하는 예제

app.use(bodyParser.json());
// "{서버의 결제 정보를 받는 가맹점 endpoint}" POST 요청 수신부
app.post("/payments/complete", async (req, res) => {
  try {
    // req의 body에서 imp_uid, merchant_uid 추출
    const { imp_uid, merchant_uid } = req.body;
  } catch (e) {
    res.status(400).send(e);
  }
});

STEP 02 결제내역 단건 조회

수신받은 포트원 결제고유번호(imp_uid)로 결제단건조회 API 를 호출하여 결제정보 획득 예제

app.use(bodyParser.json());
app.post("/payments/complete", async (req, res) => {
  try {
    // req의 body에서 imp_uid, merchant_uid 추출
    const { imp_uid, merchant_uid } = req.body;
    ...
    // 액세스 토큰(access token) 발급 받기
    const getToken = await axios({
      url: "https://api.iamport.kr/users/getToken",
      method: "post", // POST method
      headers: { "Content-Type": "application/json" },
      data: {
        imp_key: "imp_apikey", // REST API 키
        imp_secret: "ekKoeW8RyKuT0zgaZsUtXXTLQ4AhPFW3ZGseDA6bkA5lamv9OqDMnxyeB9wqOsuO9W3Mx9YSJ4dTqJ3f" // REST API Secret
      }
    });
    const { access_token } = getToken.data; // 인증 토큰
    ...
    // imp_uid로 포트원 서버에서 결제 정보 조회
    const getPaymentData = await axios({
      // imp_uid 전달
      url: `https://api.iamport.kr/payments/${imp_uid}`,
      // GET method
      method: "get",
      // 인증 토큰 Authorization header에 추가
      headers: { "Authorization": access_token }
    });
    const paymentData = getPaymentData.data.response; // 조회한 결제 정보
    ...
  } catch (e) {
    res.status(400).send(e);
  }
});

STEP 03 결제정보 검증

결제된 실 금액과 요청 금액을 비교하여 결제금액 위변조여부 검증 및 DB저장 예시

app.use(bodyParser.json());
app.post("/payments/complete", async (req, res) => {
  try {
    // req의 body에서 imp_uid, merchant_uid 추출
    const { imp_uid, merchant_uid } = req.body;

    // 액세스 토큰(access token) 발급 받기
    // 코드 생략

    // imp_uid로 포트원 서버에서 결제 정보 조회
    // 코드 생략

    const paymentData = getPaymentData.data.response; // 조회한 결제 정보
    // ...
    // DB에서 결제되어야 하는 금액 조회
    const order = await Orders.findById(paymentData.merchant_uid);
    const amountToBePaid = order.amount; // 결제 되어야 하는 금액
    // ...
    // 결제 검증하기
    const { amount, status } = paymentData;
    // 결제금액 일치. 결제 된 금액 === 결제 되어야 하는 금액
    if (amount === amountToBePaid) {
      await Orders.findByIdAndUpdate(merchant_uid, { $set: paymentData }); // DB에 결제 정보 저장
      // ...
      switch (status) {
        case "ready": // 가상계좌 발급
          // DB에 가상계좌 발급 정보 저장
          const { vbank_num, vbank_date, vbank_name } = paymentData;
          await Users.findByIdAndUpdate("/* 고객 id */", {
            $set: { vbank_num, vbank_date, vbank_name },
          });
          // 가상계좌 발급 안내 문자메시지 발송
          SMS.send({
            text: `가상계좌 발급이 성공되었습니다. 계좌 정보 ${vbank_num} ${vbank_date} ${vbank_name}`,
          });
          res.send({ status: "vbankIssued", message: "가상계좌 발급 성공" });
          break;
        case "paid": // 결제 완료
          res.send({ status: "success", message: "일반 결제 성공" });
          break;
      }
    } else {
      // 결제금액 불일치. 위/변조 된 결제
      throw { status: "forgery", message: "위조된 결제시도" };
    }
  } catch (e) {
    res.status(400).send(e);
  }
});

처음 요청한 금액은 merchant_uid 로 데이터베이스에서 조회하고 실제 결제금액은 imp_uid로 포트원 서버에서 조회하여 두 값을 비교합니다. 검증이 성공하면 결제 정보를 데이터베이스에 저장한 뒤 결제 상태(status)에 따라 알맞은 응답을 반환하고 실패 시 에러 메세지를 출력합니다.

결제결과 DB 처리는 웹훅(Webhook)을 연동하여 수신되는 데이터를 기준으로 처리하셔야 결제결과 누락없이 안정적인 결과처리를 완료하실 수 있습니다.

Confirm Process 기능을 사용하여서도 결제금액 검증을 할 수 있습니다.