🔦5. 결제정보 검증하기

결제결과를 검증하여 안정적인 결제서비스 구축에 관한 가이드 입니다.

운영중인 서버에서 클라이언트로 부터 전달 받은 결제 결과 데이터를 바탕으로 결제금액 위변조 여부를 검증하고 필요시 데이터베이스에 저장합니다. 결제 정보를 검증하는 과정은 크게 아래와 같은 단계로 진행합니다.

  • 아임포트 결제고유번호(imp_uid), 주문번호(merchant_uid)를 서버단에서 수신

  • 결제 상세내역 조회를 위해 아임포트 결제 단건 조회 API 요청

  • 응답받은 내용을 바탕으로 실 결제 금액과 결제요청금액(가맹점 자체 데이터베이스)을 비교

STEP 01 결제결과 서버 수신

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

server-side
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 를 호출하여 결제정보 획득 예제

server-side
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.response; // 인증 토큰
        ...
        // 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 결제정보 검증

결제금액의 위변조 검증 이유

결제 요청은 클라이언트 환경에서 이루어지기 때문에 클라이언트 스크립트를 조작해 금액을 위 변조하여 결제를 요청할 수 있습니다. 따라서 결제완료 후 처음 요청했던 금액과 실제로 결제된 금액을 반드시 비교해야 합니다.

예를 들어 100,000원짜리 상품을 결제할 때에는 amount: 100000으로 결제요청을 하게 되는데, 공격자가 스크립트를 조작하여 해당 속성을 실제 금액보다 낮은 값으로 변조할 수 있습니다.

클라이언트에서의 스크립트 조작은 원천적으로 막을 수 없는 기술적 특징이 있기 때문에 결제 후 서버에서 결제금액의 위변조 여부를 반드시 검증해야 합니다.

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

server-side
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)을 연동하여 수신되는 데이터를 기준으로 처리하셔야 결제결과 누락없이 안정적인 결과처리를 완료하실 수 있습니다.

Last updated