DefaultAlipayClient.py 10.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246
  1. #!/usr/bin/env python
  2. # -*- coding: utf-8 -*-
  3. """
  4. Created on 2017-12-20
  5. @author: liuqun
  6. """
  7. import datetime
  8. import uuid
  9. from alipay.aop.api.constant.ParamConstants import *
  10. from alipay.aop.api.util.WebUtils import *
  11. from alipay.aop.api.util.SignatureUtils import *
  12. from alipay.aop.api.util.CommonUtils import *
  13. from alipay.aop.api.util.EncryptUtils import *
  14. """
  15. 蚂蚁金服开放平台接入客户端
  16. """
  17. class DefaultAlipayClient(object):
  18. """
  19. alipay_client_config:客户端配置,包含app_id、应用私钥、支付宝公钥等
  20. logger:日志对象,客户端执行信息会通过此日志对象输出
  21. """
  22. def __init__(self, alipay_client_config, logger=None):
  23. self.__config = alipay_client_config
  24. self.__logger = logger
  25. """
  26. 内部方法,从params中抽取公共参数
  27. """
  28. def __get_common_params(self, params):
  29. common_params = dict()
  30. common_params[P_TIMESTAMP] = params[P_TIMESTAMP]
  31. common_params[P_APP_ID] = self.__config.app_id
  32. common_params[P_METHOD] = params[P_METHOD]
  33. common_params[P_CHARSET] = self.__config.charset
  34. common_params[P_FORMAT] = self.__config.format
  35. common_params[P_VERSION] = params[P_VERSION]
  36. common_params[P_SIGN_TYPE] = self.__config.sign_type
  37. if self.__config.encrypt_type:
  38. common_params[P_ENCRYPT_TYPE] = self.__config.encrypt_type
  39. if has_value(params, P_APP_AUTH_TOKEN):
  40. common_params[P_APP_AUTH_TOKEN] = params[P_APP_AUTH_TOKEN]
  41. if has_value(params, P_AUTH_TOKEN):
  42. common_params[P_AUTH_TOKEN] = params[P_AUTH_TOKEN]
  43. if has_value(params, P_NOTIFY_URL):
  44. common_params[P_NOTIFY_URL] = params[P_NOTIFY_URL]
  45. if has_value(params, P_RETURN_URL):
  46. common_params[P_RETURN_URL] = params[P_RETURN_URL]
  47. return common_params
  48. """
  49. 内部方法,从params中移除公共参数
  50. """
  51. def __remove_common_params(self, params):
  52. if not params:
  53. return
  54. for k in COMMON_PARAM_KEYS:
  55. if k in params:
  56. params.pop(k)
  57. """
  58. 内部方法,构造form表单输出结果
  59. """
  60. def __build_form(self, url, params):
  61. form = "<form name=\"punchout_form\" method=\"post\" action=\""
  62. form += url
  63. form += "\">\n"
  64. if params:
  65. for k, v in params.items():
  66. if not v:
  67. continue
  68. form += "<input type=\"hidden\" name=\""
  69. form += k
  70. form += "\" value=\""
  71. form += v.replace("\"", "&quot;")
  72. form += "\">\n"
  73. form += "<input type=\"submit\" value=\"立即支付\" style=\"display:none\" >\n"
  74. form += "</form>\n"
  75. form += "<script>document.forms[0].submit();</script>"
  76. return form
  77. """
  78. 内部方法,通过请求request对象构造请求查询字符串和业务参数
  79. """
  80. def __prepare_request(self, request):
  81. common_params, params = self.__prepare_request_params(request)
  82. query_string = url_encode(common_params, self.__config.charset)
  83. return query_string, params
  84. """
  85. 内部方法,通过请求request对象构造SDK请求查询字符串
  86. """
  87. def __prepare_sdk_request(self, request):
  88. common_params, params = self.__prepare_request_params(request)
  89. allParams = dict()
  90. allParams.update(common_params)
  91. allParams.update(params)
  92. query_string = url_encode(allParams, self.__config.charset)
  93. return query_string
  94. """
  95. 内部方法,通过请求request对象构造请求参数
  96. """
  97. def __prepare_request_params(self, request):
  98. THREAD_LOCAL.logger = self.__logger
  99. params = request.get_params()
  100. if P_BIZ_CONTENT in params:
  101. if self.__config.encrypt_type and self.__config.encrypt_key:
  102. params[P_BIZ_CONTENT] = encrypt_content(params[P_BIZ_CONTENT], self.__config.encrypt_type,
  103. self.__config.encrypt_key, self.__config.charset)
  104. else:
  105. if request.need_encrypt:
  106. raise RequestException("接口" + params[P_METHOD] + "必须使用encrypt_type、encrypt_key加密")
  107. params[P_TIMESTAMP] = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
  108. common_params = self.__get_common_params(params)
  109. all_params = dict()
  110. all_params.update(params)
  111. all_params.update(common_params)
  112. sign_content = get_sign_content(all_params)
  113. try:
  114. if self.__config.sign_type and self.__config.sign_type == 'RSA2':
  115. sign = sign_with_rsa2(self.__config.app_private_key, sign_content, self.__config.charset)
  116. else:
  117. sign = sign_with_rsa(self.__config.app_private_key, sign_content, self.__config.charset)
  118. except Exception as e:
  119. raise RequestException('[' + THREAD_LOCAL.uuid + ']request sign failed. ' + str(e))
  120. common_params[P_SIGN] = sign
  121. self.__remove_common_params(params)
  122. log_url = self.__config.server_url + '?' + sign_content + "&sign=" + sign
  123. if THREAD_LOCAL.logger:
  124. THREAD_LOCAL.logger.info('[' + THREAD_LOCAL.uuid + ']request:' + log_url)
  125. return common_params, params
  126. """
  127. 内部方法,解析请求返回结果并做验签
  128. """
  129. def __parse_response(self, response_str):
  130. if PYTHON_VERSION_3:
  131. response_str = response_str.decode(self.__config.charset)
  132. if THREAD_LOCAL.logger:
  133. THREAD_LOCAL.logger.info('[' + THREAD_LOCAL.uuid + ']response:' + response_str)
  134. response_content = None
  135. sign = None
  136. em1 = None
  137. em2 = None
  138. has_encrypted = False
  139. if self.__config.encrypt_type and self.__config.encrypt_key:
  140. em1 = PATTERN_RESPONSE_ENCRYPT_BEGIN.search(response_str)
  141. em2 = PATTERN_RESPONSE_SIGN_ENCRYPT_BEGIN.search(response_str)
  142. if em1 and em2:
  143. has_encrypted = True
  144. sign_start_index = em2.start()
  145. sign_end_index = em2.end()
  146. while em2:
  147. em2 = PATTERN_RESPONSE_SIGN_BEGIN.search(response_str, pos=em2.end())
  148. if em2:
  149. sign_start_index = em2.start()
  150. sign_end_index = em2.end()
  151. response_content = response_str[em1.end() - 1:sign_start_index + 1]
  152. if PYTHON_VERSION_3:
  153. response_content = response_content.encode(self.__config.charset)
  154. sign = response_str[sign_end_index:response_str.find("\"", sign_end_index)]
  155. if not response_content:
  156. m1 = PATTERN_RESPONSE_BEGIN.search(response_str)
  157. m2 = PATTERN_RESPONSE_SIGN_BEGIN.search(response_str)
  158. if not m1 or not m2:
  159. raise ResponseException('[' + THREAD_LOCAL.uuid + ']response shape maybe illegal. ' + response_str)
  160. sign_start_index = m2.start()
  161. sign_end_index = m2.end()
  162. while m2:
  163. m2 = PATTERN_RESPONSE_SIGN_BEGIN.search(response_str, pos=m2.end())
  164. if m2:
  165. sign_start_index = m2.start()
  166. sign_end_index = m2.end()
  167. response_content = response_str[m1.end() - 1:sign_start_index + 1]
  168. if PYTHON_VERSION_3:
  169. response_content = response_content.encode(self.__config.charset)
  170. sign = response_str[sign_end_index:response_str.find("\"", sign_end_index)]
  171. try:
  172. verify_res = verify_with_rsa(self.__config.alipay_public_key, response_content, sign)
  173. except Exception as e:
  174. raise ResponseException('[' + THREAD_LOCAL.uuid + ']response sign verify failed. ' + str(e) + \
  175. ' ' + response_str)
  176. if not verify_res:
  177. raise ResponseException('[' + THREAD_LOCAL.uuid + ']response sign verify failed. ' + response_str)
  178. response_content = response_content.decode(self.__config.charset)
  179. if has_encrypted:
  180. response_content = decrypt_content(response_content[1:-1], self.__config.encrypt_type,
  181. self.__config.encrypt_key, self.__config.charset)
  182. return response_content
  183. """
  184. 执行接口请求
  185. """
  186. def execute(self, request):
  187. THREAD_LOCAL.uuid = str(uuid.uuid1())
  188. headers = {
  189. 'Content-type': 'application/x-www-form-urlencoded;charset=' + self.__config.charset,
  190. "Cache-Control": "no-cache",
  191. "Connection": "Keep-Alive",
  192. "User-Agent": ALIPAY_SDK_PYTHON_VERSION,
  193. "log-uuid": THREAD_LOCAL.uuid,
  194. }
  195. query_string, params = self.__prepare_request(request)
  196. multipart_params = request.get_multipart_params()
  197. if multipart_params and len(multipart_params) > 0:
  198. response = do_multipart_post(self.__config.server_url, query_string, headers, params, multipart_params,
  199. self.__config.charset, self.__config.timeout)
  200. else:
  201. response = do_post(self.__config.server_url, query_string, headers, params, self.__config.charset,
  202. self.__config.timeout)
  203. return self.__parse_response(response)
  204. '''
  205. 得到页面跳转接口的url或表单html
  206. '''
  207. def page_execute(self, request, http_method="POST"):
  208. THREAD_LOCAL.uuid = str(uuid.uuid1())
  209. url = self.__config.server_url
  210. pos = url.find("?")
  211. if pos >= 0:
  212. url = url[0:pos]
  213. query_string, params = self.__prepare_request(request)
  214. if http_method == "GET":
  215. return url + "?" + query_string + "&" + url_encode(params, self.__config.charset)
  216. else:
  217. return self.__build_form(url + "?" + query_string, params)
  218. '''
  219. 得到sdk调用的参数
  220. '''
  221. def sdk_execute(self, request):
  222. THREAD_LOCAL.uuid = str(uuid.uuid1())
  223. return self.__prepare_sdk_request(request)