빌링키
를 발급받는 단계입니다.빌링키란?
REST API
를 통해 빌링키를 발급받는 방식(나이스정보통신
, JTNet
)일반결제창
을 통해 빌링키를 발급받는 방식(JTNet
, 이니시스
, 다날-신용카드
, 다날-휴대폰
, 모빌리언스
)REST API
를 통해 빌링키를 발급받는 방식나이스정보통신
을 사용하여 정기결제를 진행하는 경우에는 아임포트에서 제공하는 REST API를 통해 빌링키를 발급받습니다. 가맹점 서버가 아임포트 REST API를 사용하여 아임포트 서버에 카드정보를 전달하면, 아임포트 서버가 PG사의 API를 호출하는 방식으로 구성됩니다. 여기에서 아임포트 서버는 카드정보를 PG사에게 전달하는 역할을 담당합니다. 이 과정에서 카드정보는 기록하지 않습니다.해당 방식은 다음과 같은 특징이 있습니다.카드 번호
, 카드 유효기간
, 생년월일
, 카드 비밀번호 앞 두자리
를 입력할 수 있도록 합니다. 법인카드(단, 개인명의로 발급된 기명카드
는 제외)의 경우 birth
파라메터에 사업자번호 10자리
를 입력하시면 됩니다. <form action="https://www.myservice.com/subscription/issue-billing" method="post">
<div>
<label for="card_number">카드 번호 XXXX-XXXX-XXXX-XXXX</label>
<input id="card_number" type="text" name="card_number">
</div>
<div>
<label for="expiry">카드 유효기간 YYYY-MM</label>
<input id="expiry" type="text" name="expiry">
</div>
<div>
<label for="birth">생년월일 YYMMDD</label>
<input id="birth" type="text" name="birth">
</div>
<div>
<label for="pwd_2digit">카드 비밀번호 앞 두자리 XX</label>
<input id="pwd_2digit" type="text" name="pwd_2digit">
</div>
<input hidden type="text" value="gildong_0001_1234" name="customer_uid">
<input type="submit" value="결제하기">
</form>
카드 번호
, 카드 유효기간
, 생년월일
, 카드 비밀번호 앞 두자리
를 입력할 수 있는 필드를 생성했습니다. 결제하기 버튼을 클릭하면 필드의 내용이 /subscription/issue-billing
에 대해 POST
요청이 생성됩니다. 이때 유저의 카드를 가르키는 값(customer_uid
)을 함께 전달합니다.customer_uid 속성
customer_uid
속성으로 지정한 값을 unique key로 활용하여 아임포트 서버에 저장됩니다. 즉, customer_uid
값은 빌링키(카드 한 장)와 1:1로 대응합니다.
만약 고객으로부터 여러장의 카드를 등록받는 상황이라면, customer_uid
를 카드 한 장에 대응하도록 값을 구성해야 합니다. 예를들어, 고객의 unique key가 gildong_0001이고 카드 뒤 네자리 번호가 1234라면, customer_uid
속성의 값은 gildong_0001_1234와 같이 구성하여 특정 고객의 특정 카드를 가르킬 수 있습니다.
// "/subscription/issue-billing"에 대한 POST 요청을 처리
app.post("/subscriptions/issue-billing", async (req, res) => {
try {
const {
card_number, // 카드 번호
expiry, // 카드 유효기간
birth, // 생년월일
pwd_2digit, // 카드 비밀번호 앞 두자리,
customer_uid, // 카드(빌링키)와 1:1로 대응하는 값
} = req.body; // req의 body에서 카드정보 추출
...
} catch (e) {
res.status(400).send(e);
}
});
/subscription/issue-billing
에 대한 POST
요청을 처리하는 API입니다. HTTP request의 body에 담긴 card_number
, expiry
, birth
, pwd_2digit
, customer_uid
를 추출했습니다.https:\//api.iamport.kr/subscribe/customers/{customer_uid}
에 대해 HTTP 요청을 생성하여 빌링키를 발급받습니다. // "/subscription/issue-billing"에 대한 POST 요청을 처리
app.post("/subscriptions/issue-billing", async (req, res) => {
try {
const {
card_number, // 카드 번호
expiry, // 카드 유효기간
birth, // 생년월일
pwd_2digit, // 카드 비밀번호 앞 두자리
customer_uid, // 카드(빌링키)와 1:1로 대응하는 값
} = req.body; // req의 body에서 카드정보 추출
...
// 인증 토큰 발급 받기
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: "imp_apikey", // REST API키
imp_secret: "ekKoeW8RyKuT0zgaZsUtXXTLQ4AhPFW3ZGseDA6bkA5lamv9OqDMnxyeB9wqOsuO9W3Mx9YSJ4dTqJ3f" // REST API Secret
}
});
const { access_token } = getToken.data.response; // 인증 토큰
...
// 빌링키 발급 요청
const issueBilling = await axios({
url: \`https://api.iamport.kr/subscribe/customers/\${customer_uid}\`,
method: "post",
headers: { "Authorization": access_token }, // 인증 토큰 Authorization header에 추가
data: {
card_number, // 카드 번호
expiry, // 카드 유효기간
birth, // 생년월일
pwd_2digit, // 카드 비밀번호 앞 두자리
}
});
...
const { code, message } = issueBilling.data;
if (code === 0) { // 빌링키 발급 성공
res.send({ status: "success", message: "Billing has successfully issued" });
} else { // 빌링키 발급 실패
res.send({ status: "failed", message });
}
} catch (e) {
res.status(400).send(e);
}
});
REST API키
와 REST API Secret
으로 인증 토큰을 발급받았습니다. 인증토큰 발급은 https:\//api.iamport.kr/users/getToken
에 Content-Type
은 application/json
, 그리고 body에 imp_key: REST API키, imp_secret: REST API Secret
을 포함하여 POST
방식으로 요청을 생성했습니다.
해당 요청에 대한 응답이 오면, 응답 데이터에 담겨있는 인증토큰인 access_token
을 저장합니다. 그 다음 https:\//api.iamport.kr/subscribe/customers/{customer_uid}
에 customer_uid
를 작성하고 Authorization
에 발급받은 인증토큰인 access_token
을 지정한 후, 카드 정보인 card_number
, expiry
, birth
그리고 pwd_2digit
데이터와 함께 POST
방식으로 요청을 생성하여 빌링키 발급 요청을 생성했습니다.
빌링키 발급 요청에 대한 응답의 code
속성의 값이 0
이면 빌링키 발급에 성공한 것입니다. 결과에 따라 적절한 서버응답을 생성합니다.발급과 동시에 결제하기
/subscribe/payments/onetime
를 활용하면 빌링키 발급과 결제 요청을 동시에 처리할 수 있습니다.
일반결제창
을 통해 빌링키를 발급받는 방식JTNet
, 이니시스
, 다날-신용카드
, 다날-휴대폰
, 모빌리언스
을 사용하여 정기결제를 진행하는 경우에는 PG사가 제공하는 일반결제창을 통해 빌링키를 발급받습니다. PG사의 일반 결제창에 고객이 카드정보를 입력하여 결제 프로세스가 진행됩니다.해당 방식은 다음과 같은 특징이 있습니다.iamport.payment.js
의 IMP.request_pay(param, callback)
와 동일한 연동방법을 사용합니다. 코드 설치 방법은 일반결제 연동 페이지에서 확인할 수 있습니다. 해당 방식은 다음의 과정을 통해 구현할 수 있습니다.IMP.request_pay(param, callback)
을 호출합니다. 함수의 첫번째 인자인 param
에 결제 요청에 필요한 속성과 값을 담습니다. 해당 과정을 통해 빌링키가 발급됩니다. // IMP.request_pay(param, callback) 호출
IMP.request_pay({ // param
pg: "html5_inicis",
pay_method: "card", // "card"만 지원됩니다
merchant_uid: "issue_billingkey_monthly_0001", // 빌링키 발급용 주문번호
customer_uid: "gildong_0001_1234", // 카드(빌링키)와 1:1로 대응하는 값
name: "최초인증결제",
amount: 0, // 0 으로 설정하여 빌링키 발급만 진행합니다.
buyer_email: "gildong@gmail.com",
buyer_name: "홍길동",
buyer_tel: "010-4242-4242",
buyer_addr: "서울특별시 강남구 신사동",
buyer_postcode: "01181"
}, function (rsp) { // callback
if (rsp.success) {
// 빌링키 발급 성공
} else {
// 빌링키 발급 실패
}
});
IMP.request_pay(param, callback)
을 호출하면 PC 환경에서는 지정한 pg사의 결제모듈 창이 나타납니다. 반면 모바일 환경에서는 pg사의 웹사이트로 리다이렉트하는 프로세스를 제공하는 pg사도 있습니다.
빌링키 발급을 위해 IMP.request_pay
를 호출 할때에는 다음의 param
속성의 조건을 만족해야합니다.pay_method
속성을 card
로 지정: 정기결제는 신용/직불카드로만 결제가 가능하기 때문에 card
로 지정합니다.amount
속성을 0
으로 지정: 실제로 결제를 진행하는것이 아니고 빌링키를 발급하기 위한 것이므로 0
으로 지정합니다.customer_uid
속성 지정: 빌링키에 1:1로 대응하는 값으로써, 해당 속성에 지정한 값으로 반복 결제를 시도할 것이기 때문에 고유(unique)한 고객 번호를 지정합니다.모바일 웹 환경
KG이니시스
, LG U+
, NHN KCP
, 나이스정보통신
, JTNet
, KICC
는 PC 환경과 달리, 모바일 웹 환경에서 각 pg사의 웹사이트로 리다이렉트되어 결제를 진행하는 프로세스를 제공합니다. 따라서 해당 pg사를 사용하는 경우에는 m_redirect_url
을 지정하는 등의 추가 작업이 필요합니다. 해당 내용은 일반결제 연동하기> Step 7.모바일 웹 환경에 대응하기에서 확인할 수 있습니다.
pg사별 설정 차이
param
의 정책이 다르기 때문에 반드시 pg사별 예제를 참고해주세요. pg사별 예제에서 확인할 수 있습니다.
customer_uid
를 서버에 전달합니다. 서버에서는 해당 customer_uid
를 통해 차후에 결제를 요청할 수 있습니다. // IMP.request_pay(param, callback) 호출
IMP.request_pay({ // param
/* ...중략... */
}, function (rsp) { // callback
if (rsp.success) {
// 빌링키 발급 성공
// jQuery로 HTTP 요청
jQuery.ajax({
url: "https://www.myservice.com/billings/", // 서비스 웹서버
method: "POST",
headers: { "Content-Type": "application/json" },
data: {
customer_uid: "gildong_0001_1234", // 카드(빌링키)와 1:1로 대응하는 값
}
});
} else {
// 빌링키 발급 실패
}
});
customer_uid
를 전달받는 API endpoint를 생성합니다. // "/billings" 에 대한 POST 요청을 처리
app.post("/billings", async (req, res) => {
try {
const { customer_uid } = req.body; // req body에서 customer_uid 추출
...
} catch (e) {
res.status(400).send(e);
}
});
customer_uid
를 전달했습니다. 서버에서는 이 값을 전달받는 API Endpoint를 생성했습니다. 차후에 customer_uid
를 통해 결제를 요청하고, 주문정보를 함께 담아 결제를 요청할 것입니다.https:\//api.iamport.kr/subscribe/payments/again
에 대해 HTTP 요청을 생성하여 결제를 시도합니다. // "/billings" 에 대한 POST 요청을 처리하는 controller
app.post("/billings", async (req, res) => {
try {
const { customer_uid } = req.body; // req의 body에서 customer_uid 추출
// 인증 토큰 발급 받기
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: "imp_apikey", // REST API키
imp_secret: "ekKoeW8RyKuT0zgaZsUtXXTLQ4AhPFW3ZGseDA6bkA5lamv9OqDMnxyeB9wqOsuO9W3Mx9YSJ4dTqJ3f" // REST API Secret
}
});
const { access_token } = getToken.data.response; // 인증 토큰
...
// 결제(재결제) 요청
const paymentResult = await axios({
url: \`https://api.iamport.kr/subscribe/payments/again\`,
method: "post",
headers: { "Authorization": access_token }, // 인증 토큰 Authorization header에 추가
data: {
customer_uid,
merchant_uid: "order_monthly_0001", // 새로 생성한 결제(재결제)용 주문 번호
amount: 8900,
name: "월간 이용권 정기결제"
}
});
...
const { code, message } = paymentResult;
if (code === 0) { // 카드사 통신에 성공(실제 승인 성공 여부는 추가 판단이 필요합니다.)
if ( paymentResult.status === "paid" ) { //카드 정상 승인
res.send({ ... });
} else { //카드 승인 실패 (ex. 고객 카드 한도초과, 거래정지카드, 잔액부족 등)
//paymentResult.status : failed 로 수신됩니다.
res.send({ ... });
}
res.send({ ... });
} else { // 카드사 요청에 실패 (paymentResult is null)
res.send({ ... });
}
} catch (e) {
res.status(400).send(e);
}
});
customer_uid
를 추출합니다.
그 다음, 인증을 위해 아임포트 계정에 발급되는 REST API키와 REST API Secret으로 인증 토큰을 발급받았습니다. 인증토큰 발급은 https:\//api.iamport.kr/users/getToken
에 대해 Content-Type
은 application/json
, 그리고 body에 imp_key: { REST API키 }, imp_secret: { REST API Secret }
을 포함하여 POST
방식으로 요청을 생성했습니다.
해당 요청에 대한 응답이 오면, 응답 데이터에 담겨있는 인증토큰인 access_token
을 저장합니다. 그 다음 https:\//api.iamport.kr/subscribe/payments/again
에 대해 Authorization
에 발급받은 인증토큰인 access_token
을 지정하고 customer_uid
,merchant_uid
, amount
그리고 name
데이터와 함께 POST
방식으로 요청을 생성하여 결제를 요청했습니다./subscribe/payments/schedule
를 활용하여 미래의 특정 시점에 결제를 시도하도록 예약할 수 있습니다./subscribe/payments/schedule
에 대해 요청을 생성하여 결제를 예약합니다. 빌링키에 대응하는 고객 번호를 customer_uid
속성에 지정하고 schedules
속성에 주문 및 예약 시각의 정보를 지정합니다. schedules
속성은 array
타입이므로 한번의 요청에 복수의 주문정보를 입력할 수 있습니다.
schedules
의 각 항목에는 merchant_uid
, schedule_at
, amount
속성을 반드시 작성해야 합니다. // 결제 예약
axios({
url: \`https://api.iamport.kr/subscribe/payments/schedule\`,
method: "post",
headers: { "Authorization": access_token }, // 인증 토큰 Authorization header에 추가
data: {
customer_uid: "gildong_0001_1234", // 카드(빌링키)와 1:1로 대응하는 값
schedules: [
{
merchant_uid: "order_monthly_0001", // 주문 번호
schedule_at: 1519862400, // 결제 시도 시각 in Unix Time Stamp. ex. 다음 달 1일
amount: 8900,
name: "월간 이용권 정기결제",
buyer_name: "홍길동",
buyer_tel: "01012345678",
buyer_email: "gildong@gmail.com"
}
]
}
});
/subscribe/payments/schedule
에 대해 POST
방식으로 요청을 생성했습니다. 요청 body에는 customer_uid
속성에 고객 번호를 지정하고 schedules
속성에 한개의 주문 항목을 작성했습니다.
주문 항목에서는 merchant_uid
속성에 주문 고유 번호를 지정했고 scheduled_at
속성에 결제 시도 시각을 unix timestamp로 지정했습니다. 그리고 amount
속성에 결제 요청 금액을 지정하고 고객정보를 지정했습니다./subscribe/payments/schedule
/subscribe/payments/schedule
에 대한 요청 속성은 아임포트 API 문서에서 자세히 볼 수 있습니다.
imp_uid
와 merchant_uid
를 전달하게 됩니다. 가맹점 서버는 전달받은 imp_uid
값으로 아임포트 서버에서 결제 정보를 조회한 후, 결제 상태에 따라 알맞은 로직을 수행합니다.아임포트 Webhook
POST
요청을 처리할 endpoint를 생성합니다. // "/iamport-callback/schedule"에 대한 POST 요청을 처리
app.post("/iamport-callback/schedule", async (req, res) => {
try {
const { imp_uid, merchant_uid } = req.body;
...
} catch (e) {
res.status(400).send(e);
}
});
/iamport-webhook/iamport-callback
에 대한 POST
요청을 처리하는 endpoint를 생성했습니다. HTTP request의 body에 담긴 imp_uid
와 merchant_uid
를 추출했습니다.
그 다음, imp_uid
를 통해 결제 정보를 조회합니다. 결제가 성공적으로 완료 되었으면, 해당 결제 정보를 데이터베이스에 저장합니다. // "/iamport-callback/schedule"에 대한 POST 요청을 처리
app.post("/iamport-callback/schedule", async (req, res) => {
try {
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" }, // "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({
url: \`https://api.iamport.kr/payments/\${imp_uid}\`, // imp_uid 전달
method: "get", // GET method
headers: { "Authorization": access_token } // 인증 토큰 Authorization header에 추가
});
const paymentData = getPaymentData.data.response; // 조회한 결제 정보
const { status } = paymentData;
if (status === "paid) { // 결제 완료
// DB에 결제 정보 저장
await Orders.findByIdAndUpdate(merchant_uid, { $set: paymentData }); // Mongoose
...
} else {
// 재결제 시도
}
} catch (e) {
res.status(400).send(e);
}
});
REST API키
와 REST API Secret
으로 액세스 토큰을 발급받았습니다. 액세스 토큰 발급은 https:\//api.iamport.kr/users/getToken
에 대해 "Content-Type"
은 "application/json"
, 그리고 body에 imp_key: REST API키, imp_secret: REST API Secret
을 포함하여 POST
방식으로 요청을 생성했습니다.
해당 요청에 대한 응답이 오면, 응답 데이터에 담겨있는 액세스 토큰인 access_token
을 저장합니다. 그 다음 https:\//api.iamport.kr/payments/{imp_uid}
에 imp_uid
를 작성하고 "Authorization"
에 발급받은 액세스 토큰인 access_token
을 지정하고 GET
방식으로 요청을 생성하여 결제정보를 조회했습니다.
그 다음, 조회한 결제 정보의 결제 상태(status
)를 통해 결제 완료 여부를 파악하고, 결제가 완료 되었으면 결제 데이터를 가맹점의 데이터베이스에 동기화합니다./subscribe/payments/schedule
로 원하는 시점에 결제를 시도하도록 예약할 수 있기 때문에, 아임포트 webhook을 활용하여 반복적인 결제를 구현할 수 있습니다. 예약한 결제가 시도될 때마다 webhook이 호출되어 가맹점 서버가 콜백 url을 통해 결제 정보를 수신하고, 결제가 성공했음을 확인하면 다음 결제를 예약하는 방식으로 반복적인 결제를 구현할 수 있습니다.
3단계에 이어, 조회한 결제가 완료되었음을 확인한 후, 새로운 결제를 예약합니다. // "/iamport-callback/schedule"에 대한 POST 요청을 처리
app.post("/iamport-callback/schedule", async (req, res) => {
try {
const { imp_uid, merchant_uid } = req.body;
// 액세스 토큰(access token) 발급 받기
/* ...중략 ... */
// imp_uid로 아임포트 서버에서 결제 정보 조회
/* ...중략 ... */
const paymentData = getPaymentData.data.response; // 조회한 결제 정보
const { status } = paymentData;
if (status === "paid) { // 결제 완료
// DB에 결제 정보 저장
await Orders.findByIdAndUpdate(merchant_uid, { $set: paymentData }); // Mongoose
...
// 새로운 결제 예약
axios({
url: \`https://api.iamport.kr/subscribe/payments/schedule\`,
method: "post",
headers: { "Authorization": access_token }, // 인증 토큰 Authorization header에 추가
data: {
customer_uid: "gildong_0001_1234", // 카드(빌링키)와 1:1로 대응하는 값
schedules: [
{
merchant_uid: "order_monthly_0001", // 주문 번호
schedule_at: 1519516800, // 결제 시도 시각 in Unix Time Stamp. ex. 다음 달 1일
amount: 8900,
name: "월간 이용권 정기결제",
...
}
]
}
});
} else {
// 재결제 시도
}
} catch (e) {
res.status(400).send(e);
}
});
status === "paid"
)을 확인하고 데이터베이스에 결제 내역을 저장한 뒤, scheduled_at
속성에 다음에 결제를 시도할 시각을 unix timestamp로 지정했습니다.