__init__.py 36 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057
  1. #!/usr/bin/env python
  2. # coding: utf-8
  3. """
  4. __init__.py
  5. ~~~~~~~~~~
  6. python-alipay-sdk
  7. """
  8. import base64
  9. import hashlib
  10. import datetime
  11. import logging
  12. import sys
  13. from functools import partial
  14. import json
  15. import OpenSSL
  16. import requests
  17. import six
  18. from Crypto.Signature import PKCS1_v1_5
  19. from Crypto.Hash import SHA, SHA256
  20. from Crypto.PublicKey import RSA
  21. from library import to_binary, to_text
  22. from .compat import quote_plus, decodebytes, encodebytes, b
  23. from .exceptions import AliPayGatewayException, AliException, AliValidationError, AliPayServiceException, \
  24. AliPayNetworkException
  25. from .base import AliErrorCode
  26. # 常见加密算法
  27. CryptoAlgSet = (
  28. b'rsaEncryption',
  29. b'md2WithRSAEncryption',
  30. b'md5WithRSAEncryption',
  31. b'sha1WithRSAEncryption',
  32. b'sha256WithRSAEncryption',
  33. b'sha384WithRSAEncryption',
  34. b'sha512WithRSAEncryption'
  35. )
  36. PY3 = sys.version_info[0] == 3
  37. from Crypto.Cipher import AES
  38. BLOCK_SIZE = AES.block_size
  39. pad = lambda s, length: s + (BLOCK_SIZE - length % BLOCK_SIZE) * chr(BLOCK_SIZE - length % BLOCK_SIZE)
  40. unpad = lambda s: s[:-ord(s[len(s) - 1:])]
  41. logger = logging.getLogger(__name__)
  42. def encrypt_content(content, encrypt_type, encrypt_key, charset):
  43. if "AES" == encrypt_type.upper():
  44. return aes_encrypt_content(content, encrypt_key, charset)
  45. raise Exception("当前不支持该算法类型encrypt_type=" + encrypt_type)
  46. def aes_encrypt_content(content, encrypt_key, charset):
  47. length = None
  48. if PY3:
  49. length = len(bytes(content, encoding = charset))
  50. else:
  51. length = len(bytes(content))
  52. padded_content = pad(content, length)
  53. iv = '\0' * BLOCK_SIZE
  54. cryptor = AES.new(base64.b64decode(encrypt_key), AES.MODE_CBC, iv)
  55. encrypted_content = cryptor.encrypt(padded_content)
  56. encrypted_content = base64.b64encode(encrypted_content)
  57. if PY3:
  58. encrypted_content = str(encrypted_content, encoding = charset)
  59. return encrypted_content
  60. def decrypt_content(encrypted_content, encrypt_type, encrypt_key, charset):
  61. if "AES" == encrypt_type.upper():
  62. return aes_decrypt_content(encrypted_content, encrypt_key, charset)
  63. raise Exception("当前不支持该算法类型encrypt_type=" + encrypt_type)
  64. def aes_decrypt_content(encrypted_content, encrypt_key, charset):
  65. encrypted_content = base64.b64decode(encrypted_content)
  66. iv = '\0' * BLOCK_SIZE
  67. cryptor = AES.new(base64.b64decode(encrypt_key), AES.MODE_CBC, iv)
  68. content = unpad(cryptor.decrypt(encrypted_content))
  69. if PY3:
  70. content = content.decode(charset)
  71. return content
  72. class BaseAliPay(object):
  73. def __str__(self):
  74. _repr = '{kclass}(appid: {appid})'.format(kclass = self.__class__.__name__, appid = self.appid)
  75. if six.PY2:
  76. return to_binary(_repr)
  77. else:
  78. return to_text(_repr)
  79. def __repr__(self):
  80. return str(self)
  81. @property
  82. def appid(self):
  83. return self._appid
  84. @property
  85. def sign_type(self):
  86. return self._sign_type
  87. @property
  88. def app_auth_token(self):
  89. return None
  90. def __init__(self, appid,
  91. app_private_key_string,
  92. public_key_string,
  93. aes_encrypt_key = None,
  94. sign_type = "RSA2",
  95. debug = False,
  96. timeout = 15):
  97. """
  98. 初始化:
  99. alipay = AliPay(
  100. appid="",
  101. app_notify_url="",
  102. app_private_key_path="",
  103. alipay_public_key_path="",
  104. sign_type="RSA2"
  105. )
  106. """
  107. self._appid = appid
  108. self.app_private_key = RSA.importKey(app_private_key_string)
  109. self.alipay_public_key = RSA.importKey(public_key_string)
  110. self.aes_encrypt_key = aes_encrypt_key
  111. if sign_type not in ("RSA", "RSA2"):
  112. raise AliException(errCode = AliErrorCode.MY_INVALID_PARAMETER,
  113. errMsg = 'Unsupported sign type {}'.format(sign_type),
  114. client = self)
  115. self._sign_type = sign_type
  116. if debug is True:
  117. self._gateway = "https://openapi.alipaydev.com/gateway.do"
  118. else:
  119. self._gateway = "https://openapi.alipay.com/gateway.do"
  120. self.timeout = timeout
  121. def _sign(self, raw_string):
  122. """
  123. 通过如下方法调试签名
  124. 方法1
  125. key = rsa.PrivateKey.load_pkcs1(open(self._app_private_key_path).read())
  126. sign = rsa.sign(unsigned_string.encode("utf8"), key, "SHA-1")
  127. # base64 编码,转换为unicode表示并移除回车
  128. sign = base64.encodebytes(sign).decode("utf8").replace("\n", "")
  129. 方法2
  130. key = RSA.importKey(open(self._app_private_key_path).read())
  131. signer = PKCS1_v1_5.new(key)
  132. signature = signer.sign(SHA.new(unsigned_string.encode("utf8")))
  133. # base64 编码,转换为unicode表示并移除回车
  134. sign = base64.encodebytes(signature).decode("utf8").replace("\n", "")
  135. 方法3
  136. echo "abc" | openssl sha1 -sign alipay.key | openssl base64
  137. """
  138. key = self.app_private_key
  139. signer = PKCS1_v1_5.new(key)
  140. if self._sign_type == "RSA":
  141. signature = signer.sign(SHA.new(b(raw_string)))
  142. else:
  143. signature = signer.sign(SHA256.new(b(raw_string)))
  144. # base64 编码,转换为unicode表示并移除回车
  145. sign = encodebytes(signature).decode("utf-8").replace("\n", "")
  146. return sign
  147. def _ordered_data(self, data):
  148. for k, v in data.items():
  149. if isinstance(v, dict):
  150. data[k] = json.dumps(v, separators = (',', ':'))
  151. return sorted(data.items())
  152. def build_body(self, method, append_auth_token = False, **query):
  153. data = {
  154. "app_id": self._appid,
  155. "method": method,
  156. "charset": "utf-8",
  157. "sign_type": self._sign_type,
  158. "timestamp": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
  159. "version": "1.0"
  160. }
  161. if append_auth_token:
  162. data["app_auth_token"] = self.app_auth_token
  163. data.update(query)
  164. if self.aes_encrypt_key and 'biz_content' in data:
  165. biz_content = data.pop('biz_content')
  166. data['biz_content'] = encrypt_content(
  167. content = json.dumps(biz_content,
  168. ensure_ascii = False,
  169. sort_keys = True,
  170. separators = (',', ':')),
  171. encrypt_type = 'AES',
  172. encrypt_key = self.aes_encrypt_key,
  173. charset = 'utf-8')
  174. return data
  175. def _verify(self, raw_content, signature):
  176. # 开始计算签名
  177. key = self.alipay_public_key
  178. signer = PKCS1_v1_5.new(key)
  179. if self._sign_type == "RSA":
  180. digest = SHA.new()
  181. else:
  182. digest = SHA256.new()
  183. digest.update(raw_content.encode("utf-8"))
  184. signature = decodebytes(signature.encode("utf-8"))
  185. if signer.verify(digest, signature):
  186. return True
  187. return False
  188. def sign_data(self, data):
  189. data.pop("sign", None)
  190. ordered_items = self._ordered_data(data)
  191. raw_string = "&".join("{}={}".format(k, v) for k, v in ordered_items)
  192. sign = self._sign(raw_string)
  193. unquoted_items = ordered_items + [('sign', sign)]
  194. return "&".join("{}={}".format(k, quote_plus(v)) for k, v in unquoted_items)
  195. def verify(self, data, signature, pop_sign_type = True):
  196. if "sign_type" in data:
  197. if pop_sign_type:
  198. sign_type = data.pop("sign_type")
  199. else:
  200. sign_type = data.get("sign_type")
  201. if sign_type != self._sign_type:
  202. raise AliValidationError(
  203. errmsg = 'invalid sign type',
  204. lvalue = self._sign_type,
  205. rvalue = sign_type,
  206. client = self)
  207. # 排序后的字符串
  208. unsigned_items = self._ordered_data(data)
  209. message = "&".join(u"{}={}".format(k, v) for k, v in unsigned_items)
  210. return self._verify(message, signature)
  211. def api(self, api_name, **kwargs):
  212. """
  213. alipay.api("alipay.trade.page.pay", **kwargs) ==> alipay.api_alipay_trade_page_pay(**kwargs)
  214. """
  215. api_name = api_name.replace(".", "_")
  216. key = "api_" + api_name
  217. if hasattr(self, key):
  218. return getattr(self, key)
  219. raise AttributeError("Unknown attribute" + api_name)
  220. def _request(self, method, url, headers = None, **kwargs):
  221. logger.debug("Calculate Signature URL: %s", url)
  222. if headers:
  223. headers.update({'Content-Type': 'application/json'})
  224. else:
  225. headers = {'Content-Type': 'application/json'}
  226. kwargs['timeout'] = kwargs.get('timeout', 15)
  227. logger.debug('Request to Alipay API: %s %s\n%s', method, url, kwargs)
  228. with requests.sessions.Session() as session:
  229. res = session.request(
  230. url = url,
  231. method = method,
  232. headers = headers,
  233. **kwargs)
  234. try:
  235. res.raise_for_status()
  236. return res.text.decode('utf-8')
  237. except requests.RequestException as reqe:
  238. raise AliPayNetworkException(
  239. errCode = res.status_code,
  240. errMsg = reqe.message,
  241. client = self,
  242. request = reqe.request,
  243. response = reqe.response
  244. )
  245. def api_alipay_trade_wap_pay(
  246. self, subject, out_trade_no, total_amount,
  247. return_url = None, notify_url = None, **kwargs
  248. ):
  249. biz_content = {
  250. "subject": subject,
  251. "out_trade_no": out_trade_no,
  252. "total_amount": total_amount,
  253. "product_code": "QUICK_WAP_PAY"
  254. }
  255. biz_content.update(kwargs)
  256. query = {
  257. 'biz_content': biz_content
  258. }
  259. if return_url:
  260. query.update({'return_url': return_url})
  261. if notify_url:
  262. query.update({'notify_url': notify_url})
  263. data = self.build_body("alipay.trade.wap.pay", **query)
  264. return self.sign_data(data)
  265. def api_alipay_trade_app_pay(
  266. self, subject, out_trade_no, total_amount, notify_url = None, **kwargs
  267. ):
  268. biz_content = {
  269. "subject": subject,
  270. "out_trade_no": out_trade_no,
  271. "total_amount": total_amount,
  272. "product_code": "QUICK_MSECURITY_PAY"
  273. }
  274. biz_content.update(kwargs)
  275. query = {
  276. 'biz_content': biz_content
  277. }
  278. if notify_url:
  279. query.update({'notify_url': notify_url})
  280. data = self.build_body("alipay.trade.app.pay", **query)
  281. return self.sign_data(data)
  282. def api_alipay_trade_page_pay(self, subject, out_trade_no, total_amount,
  283. return_url = None, notify_url = None, **kwargs):
  284. biz_content = {
  285. "subject": subject,
  286. "out_trade_no": out_trade_no,
  287. "total_amount": total_amount,
  288. "product_code": "FAST_INSTANT_TRADE_PAY"
  289. }
  290. biz_content.update(kwargs)
  291. query = {
  292. 'biz_content': biz_content
  293. }
  294. if return_url:
  295. query.update({'return_url': return_url})
  296. if notify_url:
  297. query.update({'notify_url': notify_url})
  298. data = self.build_body("alipay.trade.page.pay", **query)
  299. return self.sign_data(data)
  300. def api_alipay_trade_query(self, out_trade_no = None, trade_no = None):
  301. """
  302. response = {
  303. "alipay_trade_query_response": {
  304. "trade_no": "2017032121001004070200176844",
  305. "code": "10000",
  306. "invoice_amount": "20.00",
  307. "open_id": "20880072506750308812798160715407",
  308. "fund_bill_list": [
  309. {
  310. "amount": "20.00",
  311. "fund_channel": "ALIPAYACCOUNT"
  312. }
  313. ],
  314. "buyer_logon_id": "csq***@sandbox.com",
  315. "send_pay_date": "2017-03-21 13:29:17",
  316. "receipt_amount": "20.00",
  317. "out_trade_no": "out_trade_no15",
  318. "buyer_pay_amount": "20.00",
  319. "buyer_user_id": "2088102169481075",
  320. "msg": "Success",
  321. "point_amount": "0.00",
  322. "trade_status": "TRADE_SUCCESS",
  323. "total_amount": "20.00"
  324. },
  325. "sign": ""
  326. }
  327. """
  328. assert (out_trade_no is not None) or (trade_no is not None), \
  329. "Both trade_no and out_trade_no are None"
  330. biz_content = {}
  331. if out_trade_no:
  332. biz_content["out_trade_no"] = out_trade_no
  333. if trade_no:
  334. biz_content["trade_no"] = trade_no
  335. data = self.build_body("alipay.trade.query", biz_content = biz_content)
  336. url = self._gateway + "?" + self.sign_data(data)
  337. raw_string = self._request(method = 'get', url = url, timeout = self.timeout)
  338. return self._verify_and_return_sync_response(raw_string, "alipay_trade_query_response")
  339. def api_alipay_trade_pay(
  340. self, out_trade_no, scene, auth_code, subject, notify_url = None, **kwargs
  341. ):
  342. """
  343. eg:
  344. self.api_alipay_trade_pay(
  345. out_trade_no,
  346. "bar_code/wave_code",
  347. auth_code,
  348. subject,
  349. total_amount=12,
  350. discountable_amount=10
  351. )
  352. failed response = {
  353. "alipay_trade_pay_response": {
  354. "code": "40004",
  355. "msg": "Business Failed",
  356. "sub_code": "ACQ.INVALID_PARAMETER",
  357. "sub_msg": "",
  358. "buyer_pay_amount": "0.00",
  359. "invoice_amount": "0.00",
  360. "point_amount": "0.00",
  361. "receipt_amount": "0.00"
  362. },
  363. "sign": ""
  364. }
  365. succeeded response =
  366. {
  367. "alipay_trade_pay_response": {
  368. "trade_no": "2017032121001004070200176846",
  369. "code": "10000",
  370. "invoice_amount": "20.00",
  371. "open_id": "20880072506750308812798160715407",
  372. "fund_bill_list": [
  373. {
  374. "amount": "20.00",
  375. "fund_channel": "ALIPAYACCOUNT"
  376. }
  377. ],
  378. "buyer_logon_id": "csq***@sandbox.com",
  379. "receipt_amount": "20.00",
  380. "out_trade_no": "out_trade_no18",
  381. "buyer_pay_amount": "20.00",
  382. "buyer_user_id": "2088102169481075",
  383. "msg": "Success",
  384. "point_amount": "0.00",
  385. "gmt_payment": "2017-03-21 15:07:29",
  386. "total_amount": "20.00"
  387. },
  388. "sign": ""
  389. }
  390. """
  391. assert scene in ("bar_code", "wave_code"), 'scene not in ("bar_code", "wave_code")'
  392. biz_content = {
  393. "out_trade_no": out_trade_no,
  394. "scene": scene,
  395. "auth_code": auth_code,
  396. "subject": subject
  397. }
  398. biz_content.update(**kwargs)
  399. query = {
  400. 'biz_content': biz_content
  401. }
  402. if notify_url:
  403. query.update({
  404. 'notify_url': notify_url
  405. })
  406. data = self.build_body("alipay.trade.pay", **query)
  407. url = self._gateway + "?" + self.sign_data(data)
  408. raw_string = self._request(method = 'get', url = url, timeout = self.timeout)
  409. return self._verify_and_return_sync_response(raw_string, "alipay_trade_pay_response")
  410. def api_alipay_trade_refund(self, out_trade_no, out_request_no, refund_amount, refund_reason):
  411. biz_content = {
  412. "out_trade_no": out_trade_no,
  413. "refund_amount": refund_amount,
  414. "out_request_no": out_request_no,
  415. 'refund_reason': refund_reason
  416. }
  417. data = self.build_body("alipay.trade.refund", biz_content = biz_content)
  418. url = self._gateway + "?" + self.sign_data(data)
  419. raw_string = self._request(method = 'get', url = url, timeout = self.timeout)
  420. return self._verify_and_return_sync_response(raw_string, "alipay_trade_refund_response")
  421. def api_alipay_trade_cancel(self, out_trade_no = None, trade_no = None):
  422. """
  423. response = {
  424. "alipay_trade_cancel_response": {
  425. "msg": "Success",
  426. "out_trade_no": "out_trade_no15",
  427. "code": "10000",
  428. "retry_flag": "N"
  429. }
  430. }
  431. """
  432. assert (out_trade_no is not None) or (trade_no is not None), \
  433. "Both trade_no and out_trade_no are None"
  434. biz_content = {}
  435. if out_trade_no:
  436. biz_content["out_trade_no"] = out_trade_no
  437. if trade_no:
  438. biz_content["trade_no"] = trade_no
  439. data = self.build_body("alipay.trade.cancel", biz_content = biz_content)
  440. url = self._gateway + "?" + self.sign_data(data)
  441. raw_string = self._request(method = 'get', url = url, timeout = self.timeout)
  442. return self._verify_and_return_sync_response(raw_string, "alipay_trade_cancel_response")
  443. def api_alipay_trade_precreate(self, out_trade_no, total_amount, subject, **kwargs):
  444. """
  445. success response = {
  446. "alipay_trade_precreate_response": {
  447. "msg": "Success",
  448. "out_trade_no": "out_trade_no17",
  449. "code": "10000",
  450. "qr_code": "https://qr.alipay.com/bax03431ljhokirwl38f00a7"
  451. },
  452. "sign": ""
  453. }
  454. failed response = {
  455. "alipay_trade_precreate_response": {
  456. "msg": "Business Failed",
  457. "sub_code": "ACQ.TOTAL_FEE_EXCEED",
  458. "code": "40004",
  459. "sub_msg": "订单金额超过限额"
  460. },
  461. "sign": ""
  462. }
  463. """
  464. biz_content = {
  465. "out_trade_no": out_trade_no,
  466. "total_amount": total_amount,
  467. "subject": subject
  468. }
  469. biz_content.update(**kwargs)
  470. data = self.build_body("alipay.trade.precreate", biz_content = biz_content)
  471. url = self._gateway + "?" + self.sign_data(data)
  472. raw_string = self._request(method = 'get', url = url, timeout = self.timeout)
  473. return self._verify_and_return_sync_response(raw_string, "alipay_trade_precreate_response")
  474. def api_alipay_fund_trans_toaccount_transfer(
  475. self, out_biz_no, payee_type, payee_account, amount, **kwargs
  476. ):
  477. assert payee_type in ("ALIPAY_USERID", "ALIPAY_LOGONID"), "unknown payee type"
  478. biz_content = {
  479. "out_biz_no": out_biz_no,
  480. "payee_type": payee_type,
  481. "payee_account": payee_account,
  482. "amount": amount
  483. }
  484. biz_content.update(kwargs)
  485. data = self.build_body("alipay.fund.trans.toaccount.transfer", biz_content = biz_content)
  486. url = self._gateway + "?" + self.sign_data(data)
  487. raw_string = self._request(method = 'get', url = url, timeout = self.timeout)
  488. return self._verify_and_return_sync_response(
  489. raw_string, "alipay_fund_trans_toaccount_transfer_response"
  490. )
  491. def api_alipay_fund_trans_uni_transfer(
  492. self, out_biz_no, trans_amount, payee_info, order_title, product_code = 'TRANS_BANKCARD_NO_PWD',
  493. biz_scene = 'DIRECT_TRANSFER', **kwargs):
  494. biz_content = {
  495. "out_biz_no": out_biz_no,
  496. "trans_amount": trans_amount,
  497. "product_code": product_code,
  498. "biz_scene": biz_scene,
  499. "payee_info": payee_info,
  500. "order_title": order_title
  501. }
  502. biz_content.update(kwargs)
  503. data = self.build_body("alipay.fund.trans.uni.transfer", biz_content = biz_content)
  504. url = self._gateway + "?" + self.sign_data(data)
  505. raw_string = self._request(method = 'get', url = url, timeout = self.timeout)
  506. return self._verify_and_return_sync_response(
  507. raw_string, "alipay_fund_trans_uni_transfer_response"
  508. )
  509. def api_alipay_fund_trans_common_query(self, out_biz_no, product_code = 'TRANS_BANKCARD_NO_PWD',
  510. biz_scene = 'DIRECT_TRANSFER', **kwargs):
  511. biz_content = {
  512. "out_biz_no": out_biz_no,
  513. "product_code": product_code,
  514. "biz_scene": biz_scene
  515. }
  516. biz_content.update(kwargs)
  517. data = self.build_body("alipay.fund.trans.common.query", biz_content = biz_content)
  518. url = self._gateway + "?" + self.sign_data(data)
  519. raw_string = self._request(method = 'get', url = url, timeout = self.timeout)
  520. return self._verify_and_return_sync_response(
  521. raw_string, "alipay_fund_trans_common_query_response"
  522. )
  523. def api_alipay_trade_create(self, out_trade_no, total_amount, subject, notify_url = None, **kwargs):
  524. biz_content = {
  525. "out_trade_no": out_trade_no,
  526. "total_amount": total_amount,
  527. "subject": subject
  528. }
  529. biz_content.update(**kwargs)
  530. query = {
  531. 'biz_content': biz_content
  532. }
  533. if notify_url:
  534. query.update({
  535. 'notify_url': notify_url
  536. })
  537. data = self.build_body("alipay.trade.create", **query)
  538. url = self._gateway + "?" + self.sign_data(data)
  539. raw_string = self._request(method = 'get', url = url, timeout = self.timeout)
  540. return self._verify_and_return_sync_response(
  541. raw_string, "alipay_trade_create_response"
  542. )
  543. def api_alipay_fund_trans_order_query(self, out_biz_no = None, order_id = None):
  544. if out_biz_no is None and order_id is None:
  545. raise Exception("Both out_biz_no and order_id are None!")
  546. biz_content = {}
  547. if out_biz_no:
  548. biz_content["out_biz_no"] = out_biz_no
  549. if order_id:
  550. biz_content["order_id"] = order_id
  551. data = self.build_body("alipay.fund.trans.order.query", biz_content = biz_content)
  552. url = self._gateway + "?" + self.sign_data(data)
  553. raw_string = self._request(method = 'get', url = url, timeout = self.timeout)
  554. return self._verify_and_return_sync_response(
  555. raw_string, "alipay_fund_trans_order_query_response"
  556. )
  557. def api_alipay_system_oauth_token(self, auth_code, refresh_token = None):
  558. """
  559. :param auth_code:
  560. :param refresh_token:
  561. :return: dict
  562. """
  563. if refresh_token:
  564. query = {
  565. "grant_type": "refresh_token",
  566. "refresh_token": refresh_token
  567. }
  568. else:
  569. query = {
  570. "grant_type": "authorization_code",
  571. "code": auth_code
  572. }
  573. data = self.build_body("alipay.system.oauth.token", **query)
  574. url = self._gateway + "?" + self.sign_data(data)
  575. raw_string = self._request(method = 'get', url = url, timeout = self.timeout)
  576. return self._verify_and_return_sync_response(
  577. raw_string, "alipay_system_oauth_token_response"
  578. )
  579. def api_alipay_trade_refund_order_query(self, out_trade_no, out_request_no, query_options=None):
  580. # type:(str, str, dict) -> dict
  581. biz_content = {
  582. "out_trade_no": out_trade_no,
  583. "out_request_no": out_request_no,
  584. }
  585. if query_options:
  586. biz_content["query_options"] = query_options
  587. data = self.build_body("alipay.trade.fastpay.refund.query", biz_content=biz_content)
  588. url = self._gateway + "?" + self.sign_data(data)
  589. raw_string = self._request(method = 'get', url = url, timeout = self.timeout)
  590. return self._verify_and_return_sync_response(
  591. raw_string, "alipay_trade_fastpay_refund_query_response"
  592. )
  593. def query_auth(self, sub_merchant_id):
  594. biz_content = {
  595. "sub_merchant_id": sub_merchant_id
  596. }
  597. data = self.build_body("alipay.merchant.indirect.smidbind.query", biz_content=biz_content)
  598. url = self._gateway + "?" + self.sign_data(data)
  599. raw_string = self._request(method = 'get', url = url, timeout = self.timeout)
  600. return
  601. def api_alipay_data_dataservice_bill_downloadurl_query(self, bill_type = 'trade', bill_date = None):
  602. """
  603. :return: dict
  604. """
  605. #: 注意,最多只能取昨天的数据,支付宝没有说明这一点
  606. if bill_date is None:
  607. bill_date = (datetime.datetime.now() - datetime.timedelta(days = 1)).strftime('%Y-%m-%d')
  608. biz_content = {
  609. 'bill_type': bill_type,
  610. 'bill_date': bill_date
  611. }
  612. data = self.build_body("alipay.data.dataservice.bill.downloadurl.query", biz_content = biz_content)
  613. url = self._gateway + "?" + self.sign_data(data)
  614. raw_string = self._request(method = 'get', url = url, timeout = self.timeout)
  615. return self._verify_and_return_sync_response(
  616. raw_string, "alipay_data_dataservice_bill_downloadurl_query_response"
  617. )
  618. def api_alipay_user_info_share(self, auth_token):
  619. """
  620. :param auth_token:
  621. :return: dict
  622. """
  623. data = self.build_body("alipay.user.info.share", auth_token = auth_token)
  624. url = self._gateway + "?" + self.sign_data(data)
  625. raw_string = self._request(method = 'get', url = url, timeout = self.timeout)
  626. return self._verify_and_return_sync_response(
  627. raw_string, "alipay_user_info_share_response"
  628. )
  629. def _verify_and_return_sync_response(self, raw_string, response_type):
  630. """
  631. return data if verification succeeded, else raise exception
  632. """
  633. logger.debug('Response from Alipay API: %s', raw_string)
  634. response = json.loads(raw_string)
  635. result = response[response_type]
  636. sign = response["sign"]
  637. # locate string to be signed
  638. raw_string = self._get_string_to_be_signed(
  639. raw_string, response_type
  640. )
  641. if not self._verify(raw_string, sign):
  642. raise AliException(
  643. errCode = AliErrorCode.MY_ERROR_SIGNATURE,
  644. errMsg = u'签名错误',
  645. client = self)
  646. return result
  647. def _get_string_to_be_signed(self, raw_string, response_type):
  648. """
  649. https://doc.open.alipay.com/docs/doc.htm?docType=1&articleId=106120
  650. 从同步返回的接口里面找到待签名的字符串
  651. """
  652. left_index = 0
  653. right_index = 0
  654. index = raw_string.find(response_type)
  655. left_index = raw_string.find("{", index)
  656. index = left_index + 1
  657. balance = -1
  658. while balance < 0 and index < len(raw_string) - 1:
  659. index_a = raw_string.find("{", index)
  660. index_b = raw_string.find("}", index)
  661. # 右括号没找到, 退出
  662. if index_b == -1:
  663. break
  664. right_index = index_b
  665. # 左括号没找到,移动到右括号的位置
  666. if index_a == -1:
  667. index = index_b + 1
  668. balance += 1
  669. # 左括号出现在有括号之前,移动到左括号的位置
  670. elif index_a > index_b:
  671. balance += 1
  672. index = index_b + 1
  673. # 左括号出现在右括号之后, 移动到右括号的位置
  674. else:
  675. balance -= 1
  676. index = index_a + 1
  677. return raw_string[left_index: right_index + 1]
  678. def upload_image(self, imageType, imageContent):
  679. """
  680. 上传图片
  681. """
  682. path = "ant.merchant.expand.indirect.image.upload"
  683. query = {
  684. "image_type": imageType,
  685. "image_content": imageContent
  686. }
  687. data = self.build_body(path)
  688. url = self._gateway + "?" + self.sign_data(data)
  689. raw_string = self._request(method="get", url=url, data=query)
  690. return self._verify_and_return_sync_response(
  691. raw_string, "ant_merchant_expand_indirect_image_upload_response"
  692. )
  693. class AliPay(BaseAliPay):
  694. def __init__(self, appid,
  695. app_private_key_string,
  696. public_key_string,
  697. aes_encrypt_key = None,
  698. sign_type = "RSA2",
  699. debug = False,
  700. timeout = 15):
  701. super(AliPay, self).__init__(
  702. appid = appid,
  703. app_private_key_string = app_private_key_string,
  704. public_key_string = public_key_string,
  705. aes_encrypt_key = aes_encrypt_key,
  706. sign_type = sign_type,
  707. debug = debug,
  708. timeout = timeout)
  709. class DCAliPay(AliPay):
  710. """
  711. 数字证书 (digital certificate) 版本
  712. """
  713. def __init__(
  714. self,
  715. appid,
  716. app_public_key_cert_string,
  717. public_key_cert_string,
  718. root_cert_string,
  719. app_private_key_string = None,
  720. aes_encrypt_key = None,
  721. sign_type = "RSA2",
  722. debug = False,
  723. timeout = 15):
  724. """
  725. 初始化
  726. DCAlipay(
  727. appid='',
  728. app_private_key_string='',
  729. app_public_key_cert_string='',
  730. alipay_public_key_cert_sring='',
  731. aplipay_root_cert_string='',
  732. )
  733. """
  734. self._app_public_key_cert_string = app_public_key_cert_string
  735. self._alipay_public_key_cert_string = public_key_cert_string
  736. self._alipay_root_cert_string = root_cert_string
  737. public_key_string = self.load_alipay_public_key_string()
  738. super(DCAliPay, self).__init__(
  739. appid = appid,
  740. app_private_key_string = app_private_key_string,
  741. public_key_string = public_key_string,
  742. aes_encrypt_key = aes_encrypt_key,
  743. sign_type = sign_type,
  744. debug = debug,
  745. timeout = timeout)
  746. def api_alipay_open_app_alipaycert_download(self, alipay_cert_sn):
  747. """
  748. 下载支付宝证书
  749. 验签使用,支付宝公钥证书无感知升级机制
  750. """
  751. biz_content = {
  752. "alipay_cert_sn": alipay_cert_sn
  753. }
  754. data = self.build_body("alipay.open.app.alipaycert.download", biz_content = biz_content)
  755. return self.sign_data(data)
  756. def build_body(self, method, append_auth_token = False, **query):
  757. data = super(DCAliPay, self).build_body(method, append_auth_token, **query)
  758. data["app_cert_sn"] = self.app_cert_sn
  759. data["alipay_root_cert_sn"] = self.alipay_root_cert_sn
  760. return data
  761. def load_alipay_public_key_string(self):
  762. cert = OpenSSL.crypto.load_certificate(
  763. OpenSSL.crypto.FILETYPE_PEM, self._alipay_public_key_cert_string
  764. )
  765. return OpenSSL.crypto.dump_publickey(
  766. OpenSSL.crypto.FILETYPE_PEM, cert.get_pubkey()
  767. ).decode("utf-8")
  768. @staticmethod
  769. def get_cert_sn(cert):
  770. """
  771. 获取证书 SN 算法
  772. """
  773. cert = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, cert)
  774. certIssue = cert.get_issuer()
  775. name = 'CN={},OU={},O={},C={}'.format(certIssue.CN, certIssue.OU, certIssue.O, certIssue.C)
  776. string = name + str(cert.get_serial_number())
  777. return hashlib.md5(string.encode()).hexdigest()
  778. @staticmethod
  779. def read_pem_cert_chain(certContent):
  780. """解析根证书"""
  781. # 根证书中,每个 cert 中间有两个回车间隔
  782. items = [i for i in certContent.split('\n\n') if i]
  783. load_cert = partial(OpenSSL.crypto.load_certificate, OpenSSL.crypto.FILETYPE_PEM)
  784. return [load_cert(c) for c in items]
  785. @staticmethod
  786. def get_root_cert_sn(rootCert):
  787. """ 根证书 SN 算法"""
  788. certs = DCAliPay.read_pem_cert_chain(rootCert)
  789. rootCertSN = None
  790. for cert in certs:
  791. try:
  792. sigAlg = cert.get_signature_algorithm()
  793. except ValueError:
  794. continue
  795. if sigAlg in CryptoAlgSet:
  796. certIssue = cert.get_issuer()
  797. name = 'CN={},OU={},O={},C={}'.format(
  798. certIssue.CN, certIssue.OU, certIssue.O, certIssue.C
  799. )
  800. string = name + str(cert.get_serial_number())
  801. certSN = hashlib.md5(string.encode()).hexdigest()
  802. if not rootCertSN:
  803. rootCertSN = certSN
  804. else:
  805. rootCertSN = rootCertSN + '_' + certSN
  806. return rootCertSN
  807. @property
  808. def app_cert_sn(self):
  809. if not hasattr(self, "_app_cert_sn"):
  810. self._app_cert_sn = self.get_cert_sn(self._app_public_key_cert_string)
  811. return getattr(self, "_app_cert_sn")
  812. @property
  813. def alipay_root_cert_sn(self):
  814. if not hasattr(self, "_alipay_root_cert_sn"):
  815. self._alipay_root_cert_sn = self.get_root_cert_sn(self._alipay_root_cert_string)
  816. return getattr(self, "_alipay_root_cert_sn")
  817. class ISVAliPay(BaseAliPay):
  818. def __init__(self, appid, app_private_key_string, public_key_string, sign_type = "RSA2", app_auth_token = None,
  819. app_auth_code = None, debug = False):
  820. if not app_auth_token and not app_auth_code:
  821. raise Exception("Both app_auth_code and app_auth_token are None !!!")
  822. self._app_auth_token = app_auth_token
  823. self._app_auth_code = app_auth_code
  824. super(ISVAliPay, self).__init__(appid, app_private_key_string, public_key_string, sign_type, debug)
  825. @property
  826. def app_auth_token(self):
  827. # 没有则换取token
  828. if not self._app_auth_token:
  829. result = self.api_alipay_open_auth_token_app(self._app_auth_code)
  830. self._app_auth_token = result.get("app_auth_token", None)
  831. if not self._app_auth_token:
  832. raise Exception("Get auth token by auth code failed: {}".format(self._app_auth_code))
  833. return self._app_auth_token
  834. def build_body(self, method, biz_content, return_url = None, append_auth_token = True):
  835. return super(ISVAliPay, self).build_body(method, biz_content, return_url, append_auth_token)
  836. def api_alipay_open_auth_token_app(self, refresh_token = None):
  837. """
  838. response = {
  839. "code": "10000",
  840. "msg": "Success",
  841. "app_auth_token": "201708BB28623ce3d10f4f62875e9ef5cbeebX07",
  842. "app_refresh_token": "201708BB108a270d8bb6409890d16175a04a7X07",
  843. "auth_app_id": "appid",
  844. "expires_in": 31536000,
  845. "re_expires_in": 32140800,
  846. "user_id": "2088xxxxx
  847. }
  848. """
  849. if refresh_token:
  850. biz_content = {
  851. "grant_type": "refresh_token",
  852. "refresh_token": refresh_token
  853. }
  854. else:
  855. biz_content = {
  856. "grant_type": "authorization_code",
  857. "code": self._app_auth_code
  858. }
  859. data = self.build_body(
  860. "alipay.open.auth.token.app",
  861. biz_content,
  862. append_auth_token = False
  863. )
  864. url = self._gateway + "?" + self.sign_data(data)
  865. raw_string = self._request(method = 'get', url = url, timeout = self.timeout)
  866. return self._verify_and_return_sync_response(
  867. raw_string, "alipay_open_auth_token_app_response"
  868. )
  869. def api_alipay_open_auth_token_app_query(self):
  870. biz_content = {
  871. "app_auth_token": self.app_auth_token
  872. }
  873. data = self.build_body(
  874. "alipay.open.auth.token.app.query",
  875. biz_content,
  876. append_auth_token = False
  877. )
  878. url = self._gateway + "?" + self.sign_data(data)
  879. raw_string = self._request(method = 'get', url = url, timeout = self.timeout)
  880. return self._verify_and_return_sync_response(
  881. raw_string, "alipay_open_auth_token_app_query_response"
  882. )