Skip to content

Instantly share code, notes, and snippets.

@ssut
Created May 17, 2016 12:53
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ssut/2c9d1ae97dff769822a11e2e193b7d12 to your computer and use it in GitHub Desktop.
Save ssut/2c9d1ae97dff769822a11e2e193b7d12 to your computer and use it in GitHub Desktop.
A Simple PayApp Implementation
# coding: utf-8
try:
import requests
except ImportError:
raise ImportError(u'requests 패키지가 설치되어있지 않습니다.\n>> pip install requests')
from urllib.parse import parse_qsl
from collections import namedtuple
from datetime import datetime
import json
__all__ = ['PayApp', ]
CMD_LIST = ('payrequest', 'paycancel', 'paycancelreq', )
REST_URL = 'http://api.payapp.kr/oapi/apiLoad.html'
class Struct(object):
def __init__(self, **entries):
self.__dict__.update(entries)
def __repr__(self):
return str(self.__dict__)
class PayAppInternalResult(namedtuple('PayAppInternalResult',
['success', 'error', 'content'])):
pass
class PayType(object):
TYPES = (u'신용카드', u'휴대전화', u'해외결제', u'대면결제',
u'계좌이체', u'가상계좌', u'문화상품권')
def __init__(self, type_id):
if type(type_id) is not int:
type_id = int(type_id)
self.type_id = type_id
def __int__(self):
return self.type_id
def __str__(self):
return PayType.TYPES[self.type_id - 1]
def __repr__(self):
return str(self.type_id)
class PayState(object):
STATES = (
([1], u'요청', 'req'), ([4], u'결제완료', 'approved'), ([8, 16, 32], u'요청취소', 'cancel'),
([9, 64], u'승인취소', 'cancel-approval'), ([10], u'결제대기', 'waiting')
)
def __init__(self, state_id):
if type(state_id) is not int:
state_id = int(state_id)
self.state_id = state_id
self.state_name = next(state[1] for state in PayState.STATES if state_id in state[0])
self.stat = next(state[2] for state in PayState.STATES if state_id in state[0])
@property
def code(self):
return self.stat
def __int__(self):
return self.state_id
def __str__(self):
return self.state_name
def __repr__(self):
return str(self.state_id)
class PayApp(object):
"""Payapp API"""
TEST_ID = 'payapptest'
TEST_KEY = 'ALSL4bhw2kPlZ+NeonwmJ+1DPJnCCRVaOgT+oqg6zaM='
TEST_VALUE = 'ALSL4bhw2kPlZ+NeonwmJ7lHBlJsugxqHSmEy6b48Xk='
def __init__(self, user_id, link_key, link_value):
self.user_id = user_id
self.link_key = link_key
self.link_value = link_value
def __post(self, cmd, data={}):
assert cmd in CMD_LIST
assert self.user_id
data.update({
'cmd': cmd,
'userid': self.user_id,
})
print(data)
resp = requests.post(REST_URL, data=data)
success = False
error = None
content = {}
if resp.status_code == 200:
data = dict(parse_qsl(resp.text))
if data['state'] == '1':
success = True
elif 'errorMessage' in data:
error = data['errorMessage']
content = Struct(**{k: v for k, v in data.items() if k not in ('state', 'errorMessage')})
result = PayAppInternalResult(success=success, error=error, content=content)
return result
def verify_callback(self, params):
"""올바른 콜백 데이터인지 확인합니다.
:param params: `dict` 또는 쿼리 스트링 `str`, POST 데이터
"""
if type(params) is str:
params = Struct(**dict(parse_qsl(params)))
elif type(params) is dict:
params = Struct(**params)
valid = True
try:
assert self.user_id == params.userid, u'판매자 회원 아이디가 일치하지 않습니다.'
assert self.link_key == params.linkkey, u'연동 KEY가 일치하지 않습니다.'
assert self.link_value == params.linkval, u''
except (AssertionError, AttributeError) as e:
valid = False
print(e)
return valid
def parse_callback(self, params):
"""콜백 데이터를 사용히기 쉬운 형태로 파싱합니다.
파싱된 정보는 반드시 검증을 거쳐야 합니다. (name, price, contact)
:param params: `dict` 또는 쿼리 스트링 `str`, POST 데이터
"""
if type(params) is str:
params = Struct(**dict(parse_qsl(params)))
elif type(params) is dict:
params = Struct(**params)
assert self.user_id == params.userid, u'판매자 회원 아이디가 일치하지 않습니다.'
assert self.link_key == params.linkkey, u'연동 KEY가 일치하지 않습니다.'
assert self.link_value == params.linkval, u''
reqdate = datetime.strptime(params.reqdate, '%Y-%m-%d %H:%M:%S')
paydate = None
if params.pay_date != '':
paydate = datetime.strptime(params.pay_date, '%Y-%m-%d %H:%M:%S')
paystate = PayState(params.pay_state)
paytype = None
if paystate.code not in ('cancel'):
paytype = PayType(params.pay_type)
restructed = {
'name': params.goodname,
'price': params.price,
'contact': params.recvphone,
'memo': params.memo,
'request_date': reqdate,
'user_memo': params.pay_memo,
'user_addr': params.pay_addr,
'pay_date': paydate,
'pay_type': paytype,
'pay_state': paystate,
'var1': params.var1,
'var2': params.var2,
'identifier': params.mul_no,
'url': params.payurl,
'currency': params.currency,
}
# 신용카드 거래일 경우
if paytype is not None and int(paytype) == 1:
restructed.update({
'pay_check_url': params.csturl,
'pay_card_name': params.card_name,
})
# 가상계좌 거래일 경우
elif paytype is not None and int(paytype) == 6:
restructed.update({
'pay_vbank': params.vbank,
'pay_vbank_account': params.vbankno,
})
return Struct(**restructed)
def pay_request(self, name, price, contact, callback, returns, paytype='card,rbank',
memo='', var1='', var2=''):
"""결제를 요청합니다.
:param name: 상품명
:param price: 결제요청 금액
:param contact: (recvphone) 수신 휴대폰번호
:param callback: (feedbackurl) 결제 완료 피드백 URL
:param returns: (returnurl) 결제 완료 후 이동할 URL
:param paytype: (openpaytype) 결제수단
:param memo: 메모
:param var1: 임의 사용 변수 1
:param var2: 임의 사용 변수 2
"""
data = {
'goodname': name,
'price': price,
'recvphone': contact,
'memo': memo,
'feedbackurl': callback,
'var1': var1,
'var2': var2,
'returnurl': returns,
'openpaytype': paytype,
}
result = self.__post('payrequest', data=data)
data = {
'success': result.success,
'error': result.error,
}
if result.success:
data['identifier'] = result.content.mul_no
data['url'] = result.content.payurl
return Struct(**data)
def pay_cancel(self, identifier, memo='', ready=False):
"""결제승인 후 5일이 지나지 않았거나, 정산이 완료되지 않은 경우 결제를 즉시 취소합니다.
:param identifier: 결제요청번호
:param memo: 결제요청취소 메모
:param mode: 취소 모드, 'ready'인 경우 실제 취소는 이루어지지 않고 결제요청 상태만 취소됩니다.
"""
data = {
'linkkey': self.link_key,
'mul_no': identifier,
}
if len(memo) > 0:
data['cancelmemo'] = memo
if ready:
data['cancelmode'] = True
result = self.__post('paycancel', data=data)
data = {
'success': result.success,
'error': result.error,
}
return Struct(**data)
def pay_cancel_request(self, identifier, memo):
"""결제승인 후 5일이 지났거나, 정산이 이미 완료된 경우 결제 취소 요청을 보냅니다.
:param identifier: 결제요청번호
:param memo: 결제요청취소 메모
"""
data = {
'linkkey': self.link_key,
'mul_no': identifier,
'cancelmemo': memo,
}
result = self.__post('paycancelreq', data=data)
data = {
'success': result.success,
'error': result.error,
}
return Struct(**data)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment