2012년 6월 21일 목요일

Django에서 온라인 카드결제 구현하기

APSys 2012 학회 웹사이트를 만들면서, 학회 등록비를 받기 위해 온라인 결제를 구현하기로 하였다. 내가 있는 연구실에서 예전에 PAM 2009 학회를 열었을 때는 국내 등록자는 정보과학회에서 제공하는 등록 페이지를 이용하고 해외 등록자는 이메일이나 FAX로 등록 요청을 받아 카드 정보를 기록해두었다가 현장에서 카드 정보를 대조하여 결제를 진행하는 방식을 사용했다. 문제는 아무래도 민감한 카드 정보를 온라인으로 주고받다보니 (요즘이야 대부분의 이메일 서버가 SSL/TLS 연결을 기본으로 쓰기는 하지만) 좀 꺼림칙하다는 점, 또 카드 정보를 받는 쪽도 전달 자체가 안전한지 여부와 상관없이 민감한 개인정보를 직접 들고 있다는 것에 대한 부담감이 있게 마련이다.

이번에는 그래서 결제대행사를 거치기로 하였다. 정보과학회하고 얘기해보니 올앳페이라는 결제대행사와 계약이 되어있고 얘네들이 해외카드 결제를 지원한다고 하여 직접 온라인 등록페이지를 만들기로 하였다. 내가 개발에 조금 시간을 써야 하는 일이긴 하지만, 전체적으로 봤을 때 등록비 정산에 관한 업무량을 확 줄일 수 있을 것이라 예상했기 때문이고 나 또한 실제 온라인 결제가 어떤 방식으로 구현되는지 기술적인 호기심이 있기 때문이기도 했다.

올앳페이에서 제공한 매뉴얼을 읽어보니 인터넷 상에서의 온라인 쇼핑은 크게 다음과 같은 과정으로 이루어진다는 것을 알 수 있었다.
  1. 사용자가 물건을 구매하겠다는 의사를 표시함. 즉 "주문"이라는 형태로 쇼핑몰 서버의 DB 또는 사용자 session에 주문할 물품에 대한 정보가 생긴다.
  2. 구매하는 과정으로 들어가면, 쇼핑몰은 배송 등을 위한 주소 등의 추가 정보를 입력받는다.
  3. 결제 방법을 선택하면 각 결제수단에 따라 적절한 인증절차를 거친다. (client side)
    예) ISP 안전결제 등
  4. 결제하기 버튼을 누르면 쇼핑몰 서버로 결제 내용이 암호화되어 전달된다. 쇼핑몰 서버는 주문 정보를 확인하여 금액이 맞게 들어왔는지 한번 더 검증하고, 이를 결제대행사 서버로 보내어 결과를 리턴받는다. (server side)
  5. 받은 결과를 보고 결제의 성공/실패 여부를 확인하고 적절한 후처리를 한다.
이 중에서 결제대행사가 제공하는 부분은 3단계의 client side 구현체와 4단계에서 결제대행사 서버로 데이터를 주고받는 API이다. 이 두 단계에서는 다음과 같이 보안에 대한 고려가 필요하다.
  • 결제 인증 과정에서 신용카드 번호와 비밀번호 등 결제하는 데 필요한 정보가 쇼핑몰 서버로 노출되지 않아야 한다. 이는 결제대행사 측에서 제공한 client side 플러그인이 구현해준다.
  • 쇼핑몰 서버에서 결제대행사 서버로 가는 정보는 암호화되어야 한다. 이는 결제대행사에서 SSL 연결을 제공함으로써 해결 가능하다.
국내에서 대부분의 온라인 쇼핑을 Microsoft의 Internet Explorer로만 가능하게 만드는 주범은 바로 3단계의 client side 구현체로, 이것이 대부분 ActiveX 플러그인 형태로만 제공된다. (경우에 따라 키보드 보안 프로그램 등이 설치되기도 한다. ㅠㅠ) 쇼핑몰 서버에만 노출시키지 않는 것이 아니라, 사용자 PC의 다른 프로그램들에도 노출시키지 않고자 하는, 즉 사용자 PC의 보안을 쇼핑몰/결제대행사 쪽에서 지켜주어야 하는 국내 인식과 관련 제도 때문이다. 또한 결제대행사 자체 플러그인뿐만 아니라 카드 인증 등에 사용하는 ISP 인증 플러그인 등도 기본적으로 Windows binary로만 배포되는 것 같은데 이니시스에 제공하는 오픈웹 결제는 이 부분을 자체 구현하는 식으로 해결한 것 같다. 나머지 단계는 쇼핑몰 제작자의 의지에 따라 얼마든지 크로스브라우징 환경으로 구축할 수 있다.

쇼핑몰 측에서는 결제대행사와의 계약 내용에 따라, 신용카드만 지원할 수도 있고 휴대폰 결제나 무통장 입금 등의 좀더 다양한 결제 수단을 지원할 수도 있는데, 어쨌든 이러한 결제 수단에 대한 인증 과정은 모두 결제대행사의 플러그인에서 처리되고 여기에 쇼핑몰이 개입할 여지는 없다. (어떤 수단을 선택할지 고르는 화면을 쇼핑몰 쪽에서 제공하게 만들 수는 있다.) 개발자 입장에서는 client side에서 javascript 파일 하나 연결해주고 결제 화면 form submit 핸들러를 결제대행사에서 제공해준 것으로 대체하기만 하면 된다.

내 경우 문제는... 올앳페이에서 제공하는 API 라이브러리 중에 Python 구현이 없었다는 것이다. PHP, JSP, ASP, ASP.NET, C(!)로는 올앳페이 측에서 결제 API 라이브러리를 이미 제공하고 있는데, Python용 코드는 혹시나 했는데 역시나 없더라. ㅠㅠ 아직 Python/Django 플랫폼으로 쇼핑몰을 구축하는 사례가 거의 없다는 뜻일 것이다.

그래서 이걸 PHP를 cli로 실행해서 인자를 넘겨 처리하고 결과값을 받아오는 식으로 짜야 하나 잠시 고민했는데, 막상 API 라이브러리 소스코드를 열어보니 괜히 함수별로 똑같은 내용을 복사붙여넣기 해놔서 길이만 길었지 막상 하는 일은 약간의 고유 오류코드 처리를 포함한 SSL HTTP 요청 날리는 게 전부였다. 그래서 1시간만에 뚝딱 포팅해버리고 그날 중으로 실제로 결제 되는 것까지 확인했다.
(프로그램 반복 테스트 과정에서 거의 백만원 정도를 내 개인카드로 긁었다가 식겁하고 얼른 올앳페이 관리자시스템 들어가서 승인취소를 넣었다...ㅋㅋㅋ 원래 디버깅용 test 플래그가 있어서 이걸 쓰면 실제 결제가 이루어지지 않는데 그걸 깜빡....;;)

국내를 대상으로 하는 보통의 온라인 쇼핑몰이라면 여기까지만 하면 땡인데, 내 경우에는 한 단계가 더 있었다. 국제 워크샵이다보니 해외 참가자들의 등록비도 결제할 수 있게 만들어야 하는 것. 사실 그것이 이 일을 한 주 목적이기도 했고.

여기서 문제는 실제로 해외에서 발급받은 신용카드가 아니면 테스트조차 해볼 수 없다는 것이다. -_-;;; 쇼핑몰에서 어찌할 수 없는 3단계(결제 인증)에서 인증이 안 되기 때문이다. 게다가 test 플래그를 켜도 일단 인증 과정을 진행한 다음 올앳페이 서버를 거칠 때 bypass하는 방식이라서 인증이 안 되면 진행이 불가능한 것은 마찬가지. 국내에서 발급받은 카드들은 카드사에서 발급한 것인지 은행에서 발급한 것인지에 따라 3단계에서 막히는지 4단계에서 막히는지 차이가 좀 있으나 어쨌든 결제가 불가능하다. (국내상점에 국내카드로 해외결제를 한다는 것을 못 받아들이는 듯. 올앳 쪽에 문의하니 국내에서의 해외결제는 모두 외환카드를 통해 처리되는데 여기 서버에서 막고 있는 것 같다.)

결국은 교수님이 해외에 계실 때 발급받았던 '진퉁' 해외카드를 써서야 최종 결제 테스트에 성공할 수 있었고, 그 와중에 있었던 자잘한 프로그래밍 실수나 버그들 때문에 며칠 동안은 수동으로 등록절차를 진행해야 했다.

어쨌든...
온라인 결제 연동도 생각보다 어려운 일은 아니라는 것.

다른 사람들을 위해 github에 올앳페이 결제 라이브러리와 Django 예제 코드들을 공개한다.