아임포트와 Firebase Cloud Function
지난 7월에 회사(파프리카 데이터랩)에서 아임포트 일반 결제를 React를 이용해 구현한 웹페이지(캐다의 웹페이지)에 연동해보았습니다. Firebase를 활용한 방법은 인터넷에 잘 나와있지 않아서 글을 작성합니다.
연동 가이드 를 참조하여 진행하면 되지만, Step 5
와 Step 7
부분을 이 글을 참조하면 됩니다.
사실 firebase는 node JS로 코드를 작성하면 되어서 큰 틀은 달라지지 않습니다. 하지만, 다른 DB에서 사용하게 되는 Query 방식과는 다르기에 처음에 Firebase의 cloud function에 대해 튜토리얼을 읽어 봐야했습니다. 그 이후 작성한 firebase cloud function을 이용한 아임포트 결제 API 예제를 작성했습니다.
Step 5(서버에서 거래 검증 및 데이터 동기화)
이 과정은 실제 클라이언트에서 온 결제 금액을 아임포트 서버와 통신하여 결제 금액이 위조되지 않고, 올바른지 확인하는 과정입니다. 그리고 실제 결제 상태도 서버에서 비교하게 됩니다. Firebase Cloud Function 에서 https 통신을 구현하기 위해서는 express
, axios
, cors
같은 라이브러리가 필요합니다.
결제 정보를 저장할 collection을 firestore에 생성하기.
이 문서에서는 pay라는 컬렉션에 정보를 저장한다고 가정하였습니다.
파이어 베이스에서 https endpoint를 만들기 위한 준비
const functions = require("firebase-functions");
const admin = require('firebase-admin');
const express = require('express');
const axios = require('axios');
const cors = require('cors');
const app = express();
const bodyParser = require('body-parser');
app.use(bodyParser.json()); // 아임포트 가이드를 참조하였습니다.
app.use(cors({ origin: true })); // cors 정책 때문에 필요합니다.
admin.initializeApp(); // firebase 시작
const db = admin.firestore(); // firestore 불러오기
를 코드 제일 윗부분에 추가해줍니다.
엔드포인트 생성 및 결제정보를 아임포트 서버로부터 조회하기
app.post("/", async (req, res) => {
try {
const newData = req.body; // client에서 보내온 request body
const docRef = await db.collection("pays").add(newData); // firestore에 pays라는 컬렉션에 client에서 보내온 정보 저장.
const { userID, amount: amountFromClient } = newData; // 구조 분해 할당
// 액세스 토큰(access token) 발급 받기(아임포트 서버와 통신하기 위한 엑세스 토큰.)
const getToken = await axios({
url: "https://api.iamport.kr/users/getToken",
method: "POST", // POST method
headers: { "Content-Type": "application/json" }, // "Content-Type": "application/json"
data: {
imp_key: "아임포트 API키를 입력해주세요", // REST API키
imp_secret:
"아임포트 계정에서 발급 받은 API Secret", // REST API Secret
},
});
const { access_token } = getToken.data.response; // 인증 토큰
const getPaymentData = await axios({
url: `https://api.iamport.kr/payments/${userID}`, // imp_uid 전달
method: "GET", // GET method
headers: { Authorization: access_token },
});
const paymentData = getPaymentData.data.response; // 조회한 결제 정보
결제 검증 후 서버에 검증 결과 저장하기.(가상 계좌 관련된 부분은 구현하지 않았습니다)
// 결제 검증하기, paymentData는 아임포트 서버에서 받아온 정보입니다.
const { amount, status } = paymentData;
if (amount === amountFromClient) {
// 결제 금액 일치. 결제 된 금액 === 결제 되어야 하는 금액
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": // 결제 완료
db.collection('pays').doc(docRef.id).update({status: 'paid'});
res.status(200).send(JSON.stringify({ status: "success", message: `결제 금액: ${amount}원`, amount: amount}));
break;
default:
db.collection("pays").doc(docRef.id).update({ status: "fail" });
res.send(JSON.stringify({ status: "fail", message: "결제에 실패하였습니다."}));
}
} else {
db.collection('pays').doc(docRef.id).update({status: 'forgery'})
// 결제 금액 불일치. 위/변조 된 결제
throw JSON.stringify({ status: "forgery", message: "정상적이지 않은 결제 시도입니다. 다시 결제를 시도해주세요."});
}
} catch (e) {
functions.logger.log(e);
res.status(400).send(e);
}
});
구현한 cloud function을 exports.(CloudFunction 이름)과 같이 해주어야 deploy할 수 있습니다.
앞서 작성한 코드 뒷부분에 작성해주세요.
exports.payment = functions.https.onRequest(app);
(추가적인 과정) firebase emulator를 이용하여 로컬에서 테스트하기 (firebase 문서 참조)
터미널에서 배포 명령어 입력하기
firebase deploy --only fucntions:payment
Step 7(Webhook 및 데이터동기화 설정하기)
앞서 작성된 Step 5와 완전히 동일한 방법으로 구현되지만,
const { imp_uid: userID, merchant_uid: merchantID, status: webHookStatus} = req.body;
처럼 정해진 형태(imp_uid, merchant_uid, status)의 데이터가 들어오게 됩니다.
또한 request를 보내는 곳이 클라이언트가 아닌 아임포트라는 점이 다릅니다. 결제 상태가 아임포트 서버에서 변경되면 우리의 /iamport-webhook
와 같은 엔드포인트로 정보를 보내고 우리는 변동되는 정보를 웹훅을 통해 인지할 수 있다는 점입니다.