아임포트 결제 시스템을 이용하여 결제 서비스 구현(node.js)
1.아임포트(https://portone.io/korea/ko) 사이트에 접속해서 회원가입
2.PG정보 설정하기(관리자 페이지->결제연동->테스트 연동 관리->PG사 선택)
1) 각 PG사별 포트원 관리자 페이지 설정 (https://portone.gitbook.io/docs/ready/2.-pg/payment-gateway)
3.연동정보 확인
1)포트원 관리자콘술 로그인->상점 계정관리->내 식별코드 API Keys선택
2)가맹점 식별코드(/REST API Key/REST API Secre)확인
4.백엔드의 사전검증과 프론트 엔트의 결제요청 코드
// 사용자에게 결제 화면을 보여주기 전에 서버 코드에 가맹점 주문번호와
// 결제 예정금액을 서버에 저장한다
<!DOCTYPE html>
<html lang="en">
<head>
<!-- jQuery -->
<script type="text/javascript" src="https://code.jquery.com/jquery-1.12.4.min.js" ></script>
<!-- iamport.payment.js -->
<script type="text/javascript" src="https://cdn.iamport.kr/js/iamport.payment-1.2.0.js"></script>
//포트원 라이브러리 추가
<script src="https://cdn.iamport.kr/v1/iamport.js"></script>
<script>
//포트원 객체 초기화 하기
var IMP = window.IMP;
IMP.init("본인가맹점고유번호");
await axios({
url: "정보를 저장하는 백엔드 서비스 url",//(ex: https://api.iamport.kr/payments/prepare)
method: "post",
headers: { "Content-Type": "application/json" },
data: {
merchant_uid: "...", // 가맹점 주문번호
amount: 420000, // 결제 예정금액
//그외 order collection에 저장할 정보도 같이 전송
}
}).then((res)=>{
//DB에 상품 재고가 있는지 파악하고 구매자 아이디, 가맹점 주문번호, 결제 예정금액등
//필요한 정보를 저장하고 코드 작성하고... 결제 요청을 보낸다
if(res.date === "ok"){
function requestPay() {
IMP.request_pay({
pg : 'kcp.{상점ID}',//각 PG사마다 pay_method 파라미터 확인
pay_method : 'card',//실시간 계좌이체 'trans'
merchant_uid: "57008833-33004",
name : '당근 10kg',
amount : 1004,
buyer_email : 'Iamport@chai.finance',
buyer_name : '포트원 기술지원팀',
buyer_tel : '010-1234-5678',
buyer_addr : '서울특별시 강남구 삼성동',
buyer_postcode : '123-456',
notice_url : 'https://웹훅수신 URL',//웹훅수신 URL 설정(예: /portone-webhook)
}, function (rsp) { // callback
if (rsp.success) {
//DB로 저장될 정보 전송
// axios로 HTTP 요청
axios({
url: "/payment/complete",
method: "post",
headers: { "Content-Type": "application/json" },
data: {
imp_uid: rsp.imp_uid,
merchant_uid: rsp.merchant_uid
}
}).then((data) => {
// 서버 결제 API 성공시 로직
})
console.log(rsp);
} else {
console.log(rsp);
}
});
}
}
}).catch(error=>{
});
</script>
<meta charset="UTF-8">
<title>Sample Payment</title>
</head>
<body>
<button onclick="requestPay()">결제하기</button> <!-- 결제하기 버튼 생성 -->
</body>
</html>
5.서버 코드
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; // 조회한 결제 정보
// 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);
}
});
6.백엔드 - 사후 검증(보안을 위해 webhook이벤트를 사용)
app.use(bodyParser.json());
app.post("/portone-webhook", 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; // 조회한 결제 정보
// DB에서 결제되어야 하는 금액 조회
const order = await Orders.findById(paymentData.merchant_uid);
const amountToBePaid = order.amount; // 결제 되어야 하는 금액
// 결제 검증하기
const { amount, status } = paymentData;
// 결제금액 일치. 결제 된 금액 === 결제 되어야 하는 금액
if (amount === amountToBePaid) {
// DB에 결제 정보 저장
await Orders.findByIdAndUpdate(merchant_uid, { $set: paymentData });
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);
}
});