영화관 사이트 프로젝트 ( 결제 서비스 구현 ) #아임포트

2019. 8. 3. 16:03프로젝트

아임 포트를 사용해서 결제 기능을 구현해보기

프로젝트를 진행하면서 결제 기능이 필요해서 무료로 결제 기능을 구현할 수 있는 방법이 있나 찾아보다가 아임 포트를 알게 되었습니다. 

 

일반적으로 PG사와 직접 연동하여 사용한다면 다음과 같은 과정이 이루어집니다.

PG사와 직접 연동한 경우

1. 고객이 사이트에 접근하여 물건을 고른 후 결제를 요청

2. 사이트 서버에서는 해당 요청을 받고 주문정보와 카드정보를 PG사 서버에 결제 요청

3. PG 서버에서 해당 요청을 카드사에 전달하고 결제 승인 결과를 다시 사이트 서버에 응답

4. 사이트 서버에서는 최종 결제 결과를 고객에 브라우저에 표시

 

아임 포트를 사용한 경우

아임포트 사용

1. 사이트 서버에 화면에서 고객 브라우저는 아임 포트 서버로 결제 요청 ( 서버로 결제 요청을 보내는 게 아닙니다. )

2. 아임 포트는 해당 결제정보를 PG 서버에 요청 ( 위에 1번 과정을 아임 포트가 대신해줍니다 )

3. 결과를 고객의 브라우저로 전달

4. 고객의 브라우저에서는 결제 정보를 서버로 전달

 

이처럼 아이포트를 사용하면 PG사에서 요구하는 모듈 설치와 보안설정 등의 과정을 거치지 않고 바로 개발에만 집중할 수 있어서 개발 시간이 무척 단축이 됩니다.

 

+ 배운 용어

Webhook(웹 훅)

일반적으로 웹서버는 요청에 대한 응답만 해주는 방식이기 때문에 사용자의 요청을 기억했다가 작업이 완료되었음을 

알려줄 수 없습니다. 때문에 요청을 보낸 쪽에서는 폴링(Polling) 방식을 사용하여 요청의 결과를 수시로 확인하는 불필요한 과정이 필요합니다. 웹 훅이란 서버로 어떤 작업 요청을 보냈을 때 해당 작업의 수행 결과를 Http post 방식으로 알리는 개념을 말합니다. 이런 문제를 Webhook을 사용한다면 요청에 대한 응답 주소(콜백 URL)를 지정하여 작업이 수행될 때 해당 URL로 POST방식의 응답을 받을 수 있게 됩니다.

 

아임 포트 사용 준비물

아임 포트 회원가입 후 가맹점 식별코드를 발급받습니다.

가맹점 식별 코드

<script type="text/javascript" src="https://code.jquery.com/jquery-1.12.4.min.js" ></script>

<script type="text/javascript" src="https://cdn.iamport.kr/js/iamport.payment-1.1.5.js"></script>

아임 포트 라이브러리를 가져옵니다. ( 아임 포트 라이브러리를 사용하려면 Jquery 필요 )

 

 

브라우저에서 고객이 아임포트 서버로 결제 요청을 보냅니다.

var mid = '${mId}'; // 서버에서 발급받은 결제 고유번호
var money = ${pInfo.money}; // 결제금액 ( 서버에서 RestAPI를 통해서 결제번호로 금액변조를 방지함)
var point = $ {userPoint}; // 고객 맴버십 포인트 정보
function paymentDo() {
    $.ajax({
        type: "get",
        url: "/civ/payment/checkseat", // 최종 결제전에 마지막으로 해당 좌석이 예매가능한 상태인지 확인 요청
        data: {
            'scheduleCode': ${pInfo.scheduleCode}, // 구매하려는 스케줄 코드
            'checkSeat': '${pInfo.checkSeat}' // 확인 좌석
        },
        success: function(data) {
            if (data) {
                var IMP = window.IMP; 
                IMP.init('imp93258645');// 아임포트 라이브러리를 통해서 가맹점정보를 불러옴
                IMP.request_pay({
                    pg: 'html5_inicis', // 웹 표준 결제
                    pay_method: $(":input:radio[name=paycard]:checked").val(), //결제수단
                    merchant_uid: mid, // 결제 고유번호
                    name: '테스트 주문', // 결제 상품 이름
                    amount: money, // 결제금액 ( RestAPI를 호출한 상태이기 때문에 금액 변조시 결제창이 뜨지 않고 오류 )
                    buyer_tel: '${sessionScope.loginInfo.userPhone}' // 유저 전화번호 ( 필수 )
                }, function(rsp) { // 콜백 함수
                    if (rsp.success) {
                    	// 결제 정보를 form에 담음
                        $('#checkSeat').val('${pInfo.checkSeat}');
                        $('#merchant_uid').val(rsp.merchant_uid);
                        $('#paid_amount').val(rsp.paid_amount);
                        $('#pay_method').val(rsp.pay_method);
                        var queryString = $("#form").serialize();
                        $.ajax({
                            type: "post",
                            url: "/civ/payment/paid", //최종 결제정보를 서버로 전달
                            data: queryString,
                            success: function(data) {
                                if (data == "failed") {
                                    alert('결제 실패');
                                } else {
                                    // 성공시 웹소켓을 사용해서 해당 스케줄을 보고있는 사람들에게도 좌석 정보를 업데이트
                                    var webSocket = new WebSocket("ws://192.168.219.101/civ/websocket/${pInfo.scheduleCode}");
                                    webSocket.onopen = () => webSocket.send(data);
                                    webSocket.onclose = function(message) {
                                        disconnect();
                                    };
                                    function disconnect() {
                                        webSocket.close();
                                    }
                                    // 결제 확인창으로 이동
                                    location.href = "/civ/payment/payok";
                                }
                            }
                        })
                    } else {
                        alert('결제 실패하셨습니다.');
                    }
                });
            } else {
                alert('결제 오류');
            }
        }
    });
}

 

아임 포트 RestAPI를 호출하기 위한 자바 객체

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;

public class PaymentCheck {

	public static final String IMPORT_TOKEN_URL = "https://api.iamport.kr/users/getToken";
	public static final String IMPORT_PAYMENTINFO_URL = "https://api.iamport.kr/payments/find/";
	public static final String IMPORT_CANCEL_URL = "https://api.iamport.kr/payments/cancel";
	public static final String IMPORT_PREPARE_URL = "https://api.iamport.kr/payments/prepare";
	
	public static final String KEY = "아임포트 Rest Api key";
	public static final String SECRET = "아임포트 Rest Api Secret";
	
    // 아임포트 인증(토큰)을 받아주는 함수
	public String getImportToken() {
		String result = "";
		HttpClient client = HttpClientBuilder.create().build();
		HttpPost post = new HttpPost(IMPORT_TOKEN_URL);
		Map<String,String> m  =new HashMap<String,String>();
		m.put("imp_key", KEY);
		m.put("imp_secret", SECRET);
		try {
			post.setEntity(new UrlEncodedFormEntity(convertParameter(m)));
			HttpResponse res = client.execute(post);
			ObjectMapper mapper = new ObjectMapper();
			String body = EntityUtils.toString(res.getEntity());
			JsonNode rootNode = mapper.readTree(body);
			JsonNode resNode = rootNode.get("response");
			result = resNode.get("access_token").asText();
		} catch (Exception e) {
			e.printStackTrace();
		}
		return result;
	}
	
    // Map을 사용해서 Http요청 파라미터를 만들어 주는 함수
	private List<NameValuePair> convertParameter(Map<String,String> paramMap){
		List<NameValuePair> paramList = new ArrayList<NameValuePair>();
		
		Set<Entry<String,String>> entries = paramMap.entrySet();
		
		for(Entry<String,String> entry : entries) {
			paramList.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
		}
		return paramList;
	}
	
    // 결제취소
	public int cancelPayment(String token, String mid) {
		HttpClient client = HttpClientBuilder.create().build();
		HttpPost post = new HttpPost(IMPORT_CANCEL_URL);
		Map<String, String> map = new HashMap<String, String>();
		post.setHeader("Authorization", token);
		map.put("merchant_uid", mid);
		String asd = "";
		try {
			post.setEntity(new UrlEncodedFormEntity(convertParameter(map)));
			HttpResponse res = client.execute(post);
			ObjectMapper mapper = new ObjectMapper();
			String enty = EntityUtils.toString(res.getEntity());
			JsonNode rootNode = mapper.readTree(enty);
			asd = rootNode.get("response").asText();
		} catch (Exception e) {
			e.printStackTrace();
		}
		if (asd.equals("null")) {
			System.err.println("환불실패");
			return -1;
		} else {
			System.err.println("환불성공");
			return 1;
		}
	}
	
    // 아임포트 결제정보를 조회해서 결제금액을 뽑아주는 함수
	public String getAmount(String token, String mId) {
		String amount = "";
		HttpClient client = HttpClientBuilder.create().build();
		HttpGet get = new HttpGet(IMPORT_PAYMENTINFO_URL + mId + "/paid");
		get.setHeader("Authorization", token);

		try {
			HttpResponse res = client.execute(get);
			ObjectMapper mapper = new ObjectMapper();
			String body = EntityUtils.toString(res.getEntity());
			JsonNode rootNode = mapper.readTree(body);
			JsonNode resNode = rootNode.get("response");
			amount = resNode.get("amount").asText();
		} catch (Exception e) {
			e.printStackTrace();
		}

		return amount;
	}
	
    // 아임포트 결제금액 변조는 방지하는 함수
	public void setHackCheck(String amount,String mId,String token) {
		HttpClient client = HttpClientBuilder.create().build();
		HttpPost post = new HttpPost(IMPORT_PREPARE_URL);
		Map<String,String> m  =new HashMap<String,String>();
		post.setHeader("Authorization", token);
		m.put("amount", amount);
		m.put("merchant_uid", mId);
		try {
			post.setEntity(new UrlEncodedFormEntity(convertParameter(m)));
			HttpResponse res = client.execute(post);
			ObjectMapper mapper = new ObjectMapper();
			String body = EntityUtils.toString(res.getEntity());
			JsonNode rootNode = mapper.readTree(body);
			System.out.println(rootNode);
		} catch (Exception e) {
			e.printStackTrace();
		}	
	}
	
//	public static void main(String...args) {
//		new PaymentCheck().cancelPayment(new PaymentCheck().getImportToken(), "merchant_1563254570837");
//	}
}

public static final String IMPORT_TOKEN_URL = "https://api.iamport.kr/users/getToken";

아임 포트 토큰 요청 REST API URL


public static final String IMPORT_PAYMENTINFO_URL = "https://api.iamport.kr/payments/find/";

아임포트 결제정보 조회 REST API URL

 

public static final String IMPORT_CANCEL_URL = "https://api.iamport.kr/payments/cancel";

아임 포트 결제 취소 요청 REST API URL

 

public static final String IMPORT_PREPARE_URL = "https://api.iamport.kr/payments/prepare";

아임포트 결제금액 변조 방지 요청 REST API URL ( 금액이 일치하지 않다면 결제창을 띄워 주지 않는다.)


public static final String KEY = "아임 포트 Rest API key";
public static final String SECRET = "아임 포트 Rest API Secret";

 

HttpClientBuilder를 상용해서 Post , Get 요청을 만들어 Rest API를 호출해 보았습니다.

아임 포트 Rest api의 반환 값은 json객체이며 요청 파라미터 정보와 반환 값 정보는 

https://api.iamport.kr/

 

API-아임포트

 

api.iamport.kr

주소를 통해서 확인이 가능합니다.