D.S

item4.github.io

이니시스 Inipay 모바일 연동 (카드결제 추가) - item4 Dev Story

이니시스 Inipay 모바일 연동 (카드결제 추가) - item4 Dev Story About Résumé Feed item4 Dev Story 이니시스 Inipay 모바일 연동 (카드결제 추가) Posted on 2016-02-03 01:26 Permalink: https://item4.github.io/2016-02-03/Inicis-Inipay-Mobile/ Keywords: 이니시스 이니페이 Inicis Inipay PG 연동 Share: Twitter Facebook Google+ Tumblr Reddit ※ 카드 결제 관련해서 본문에 중요한 변경사항이 많습니다. (2016년 2월 21일 수정) 이니시스사에서 제공하는 Inipay를 모바일 웹에서 사용할 수 있도록 연동하는 작업을 했는데, 공식 문서라고 할만한게 사실상 아예 없는지라 기록을 위해서라도 삽질 기록을 남겨놓습니다. 중요 상수 & 변수 여기 나열된 변수명은 실제 변수명과 일치한다는 보장은 없습니다. 자주 쓰이는 네이밍 정도로 이해하시면 될 것 같습니다. MID Market ID 입니다. 상점마다 고유값이 발급됩니다. 테스트중일 땐 INIpayTest로 지정하면 됩니다. OID Order ID 입니다. 각 주문마다 고유하게(unique하게) 직접 발급해주셔야 합니다. 보통 timestamp 1 이나 고유성이 보장 안 된 랜덤값을 사용하는데, 방문자 수가 많은 곳이라면 피하는게 좋습니다. TID Transaction ID 입니다. 각 결제 진행마다 고유하게 발급됩니다. 환불 프로세스를 구성해야한다면 DB에 저장해야할 것으로 보입니다. P_AMT 가격을 담는 변수입니다. 당연하지만 정수로 적어야 합니다. P_NEXT_URL 결제창이 끝난 후 이동할 페이지의 URL입니다. 실험해보진 않았으나 전체 경로를 적어야할 것으로 보입니다. P_NOTI_URL 카드 결제 진행시 결과를 저장할 때 쓰이는 URL입니다. 실험해보진 않았으나 전체 경로를 적어야할 것으로 보입니다. P_RETURN_URL 카드 결제 진행 완료시 넘어가는 페이지입니다. 아무런 정보도 받을 수 없는 페이지 입니다. 실험해보진 않았으나 전체 경로를 적어야할 것으로 보입니다. HTML 소스 https://www.inicis.com/smartphone/iphone/INIpayMobile_Sample.html 의 소스를 열어봅시다. 주소만 봐서는 iPhone용인데, 해본 결과 그냥 다 됩니다. 2 이 소스를 가져다가 여러분의 결제 신청 폼에 맞게 개조하시면 됩니다. 3 단, 다른건 다 바꿔도 <form>태그는 있어야하고, <input> 태그들에서 name 속성은 바꾸시면 안됩니다. 결제 창을 폼 전송을 통해 띄우기 때문입니다. 여기서 꼭 설정해야하는 값들은 <input> 태그 형태로 생성되어있는, 위에서 이미 언급한 변수들입니다. 카드 결제 결과 저장 카드결제를 진행하고 난 뒤에 이니시스측에서 상점 홈페이지쪽으로 정말 정상적으로 결제가 진행됬는지 확인 PING을 보냅니다. 해당 부분은 OK 또는 FAIL을 보내줘야 서버가 인식하고 거래를 처리합니다. 그 외의 값이 출력되면 일정 시간 후에 다시 PING이 옵니다. 이 부분의 소스 예제는 이니시스측에서 JSP, ASP, PHP로 제공하고 있습니다. 소스를 받아서 열어보면 WEB 방식엔 필요 없다고 하는데, 카드 결제에는 무조건 필요합니다. 또 하나 주의할 점은 넘어오는 정보는 euc-kr로 인코딩 되어 있습니다. utf-8 환경에서는 변경해서 사용해야합니다. # 예시로 Flask를 사용합니다. from flask import redirect, request, url_for # P_NOTI_URL @app.route('/shop/noti/', methods=['POST']) def noti(): ip = request.remote_addr if ip in ('211.219.96.165', '118.129.210.25'): p_status = request.form['P_STATUS'] if request.form['P_TYPE'] == 'vbank': if p_status != '02': return 'OK' else: # 존재하는 상품이 맞다면 if is_correct_production(request.args['good_id'], requst.form['P_AMT']): result = save_into_db(...) # DB에 성공적으로 저장되었다면 if result: return 'OK' return 'FAIL' # P_RETURN_URL @app.route('/shop/return/') def return_page(): return redirect(url_for('shop.buy_list')) 예시로 사용된 코드에서는 GET으로 good_id란 값을 넘겨서 실제 상품과 가격이 맞는지 대조하고 있습니다. 상품정보 조작을 방지하려면 꼭 해야하는 작업입니다. P_NOTI_URL에서 ?good_id=123 과 같은 식으로 주소 뒤에 덧붙여서 충분히 인자 전송이 가능합니다. 기타 거래 결과 저장 결제창이 끝나면 P_NEXT_URL으로 POST 형식으로 TID를 포함한 몇 가지 값이 넘어옵니다. 문제는 넘어온 값만 봐서는 얼마를 결제했는 지 알 수 없으므로 추가 작업을 해줘야만 합니다. 소스는 Python과 PHP의 2벌의 예제로 준비하였습니다. Python Python은 실제로 돌려보진 않았습니다. 의존성으로 requests를 사용하였습니다. 본 예제에는 Python 3이 메인입니다. Python 2를 위한 소스는 주석을 쳐두었으니 참고 바랍니다. from urllib.parse import parse_qsl, urlencode # from urllib import urlencode # from urlparse import parse_qsl from flask import redirect, request import requests @app.route('/shop/result/', methods=['POST']) def result(): if request.form['P_STATUS'] == '00' and 'P_TID' in request.form: request_body = { 'P_MID': 'INIpayTest', # 여러분 값에 맞게! 'P_TID': request.form['P_TID'], } request_body = urlencode(request_body) raw_data = requests.post(request.form['P_REQ_URL'], request_body).content data = {x[0]: x[1] for x in parse_qsl(raw_data.decode('cp949'))} # 적당한 조건을 충족하면 if data['P_STATUS'] == '00' and data['P_AMT'] == 1000: # DB에 저장합시다. save_into_database(data) return redirect('shop.success') else: # 아니면 저장하면 안되겠죠! kickban() return redirect('shop.fail') return redirect('shop.fail') PHP PHP는 사실 설명할 생각이 없었는데, 아직도 많은 분들이 현업에서 PHP를 쓰고 있고, Python은 몰라도 PHP는 아는 분이 많으실 수 있으니 설명합니다. PHP쪽은 iconv와 curl 의존성이 있습니다. iconv나 curl을 쓸 수 없다면 적당히 대체 소스를 찾아보시는 것을 권합니다. <?php if ($_POST['P_STATUS'] === '00') { $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $_POST['P_REQ_URL']); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_POSTFIELDS, 'P_MID=INIpayTest&P_TID='.$_POST['P_TID']) ; // 여기 수정하세요! $inipay = iconv('euc-kr', 'utf-8', curl_exec($ch)); curl_close($ch); $result = array(); parse_str($inipay, $result); if ($result['P_STATUS'] == '00' && (int)$cashmoney === (int)$result['P_AMT']) { save_into_db(); header('Location: /shop/success/'); } else { kickban(); header('Location: /shop/fail/'); } } 테스트 결제 다 세팅이 되셨다면 테스트 결제를 해보시면 됩니다. 테스트 계정으로 테스트를 하셨다면 결제에 쓰인 돈은 당일 23시 즈음 환불됩니다. 다만 가상계좌(vbank)의 경우는 다르다고 쓰여있더군요. 후기 문서화의 소중함을 새삼 느꼈습니다. 문서가 없으니 두 명이서 이곳저곳에서 문서들을 짜집기하면서 만들었습니다. 이니시스측의 예제 소스는 왜 아직도 HTML 4 시절의 <table> 기준 레이아웃을 쓴다던가 하는 시대 퇴행적 면모를 보여주는가 하는 의문도 생겼습니다. 그 밖에도 왜 상품을 하나 밖에 지정을 못하는가 4 하는 API 구조상의 의문 등이 있으나 의문이 쌓여도 해소가 될 것 같지 않으므로 그냥 잊기로 했습니다. 이번 작업은 모바일이었기에 ActiveX가 필요 없었지만 아직도 한국 PC용 PG 연동은 ActiveX로 점철되어있습니다. 문서화도 그렇고, 좀 더 개발자 친화적인 PG 생태계가 되었으면 좋겠다는 생각입니다. PHP의 time(), JavaScript의 Date().getTime() 같은 것. ↩ 심지어 PC에서도! ↩ 기초적인 HTML 및 JavaScript 내용이므로 생략합니다. ↩ 몇년 전 PayPal의 PHP 연동 작업을 했었는데 쇼핑몰에서 구입하는 상품의 각각의 명칭과 갯수까지 기입할 수 있었습니다. ↩ Twitter에 공유 Facebook에 공유 Google+에 공유 Tumblr에 공유 Reddit에 공유 GitHub