__init__.py 40 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179
  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, notify_url = None, **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. if notify_url:
  164. data['notify_url'] = notify_url
  165. data.update(query)
  166. if self.aes_encrypt_key and 'biz_content' in data:
  167. biz_content = data.pop('biz_content')
  168. data['biz_content'] = encrypt_content(
  169. content = json.dumps(biz_content,
  170. ensure_ascii = False,
  171. sort_keys = True,
  172. separators = (',', ':')),
  173. encrypt_type = 'AES',
  174. encrypt_key = self.aes_encrypt_key,
  175. charset = 'utf-8')
  176. return data
  177. def _verify(self, raw_content, signature):
  178. # 开始计算签名
  179. key = self.alipay_public_key
  180. signer = PKCS1_v1_5.new(key)
  181. if self._sign_type == "RSA":
  182. digest = SHA.new()
  183. else:
  184. digest = SHA256.new()
  185. digest.update(raw_content.encode("utf-8"))
  186. signature = decodebytes(signature.encode("utf-8"))
  187. if signer.verify(digest, signature):
  188. return True
  189. return False
  190. def sign_data(self, data):
  191. data.pop("sign", None)
  192. ordered_items = self._ordered_data(data)
  193. raw_string = "&".join("{}={}".format(k, v) for k, v in ordered_items)
  194. sign = self._sign(raw_string)
  195. unquoted_items = ordered_items + [('sign', sign)]
  196. return "&".join("{}={}".format(k, quote_plus(v)) for k, v in unquoted_items)
  197. def verify(self, data, signature, pop_sign_type = True):
  198. if "sign_type" in data:
  199. if pop_sign_type:
  200. sign_type = data.pop("sign_type")
  201. else:
  202. sign_type = data.get("sign_type")
  203. if sign_type != self._sign_type:
  204. raise AliValidationError(
  205. errmsg = 'invalid sign type',
  206. lvalue = self._sign_type,
  207. rvalue = sign_type,
  208. client = self)
  209. # 排序后的字符串
  210. unsigned_items = self._ordered_data(data)
  211. message = "&".join(u"{}={}".format(k, v) for k, v in unsigned_items)
  212. return self._verify(message, signature)
  213. def api(self, api_name, **kwargs):
  214. """
  215. alipay.api("alipay.trade.page.pay", **kwargs) ==> alipay.api_alipay_trade_page_pay(**kwargs)
  216. """
  217. api_name = api_name.replace(".", "_")
  218. key = "api_" + api_name
  219. if hasattr(self, key):
  220. return getattr(self, key)
  221. raise AttributeError("Unknown attribute" + api_name)
  222. def _request(self, method, url, headers = None, **kwargs):
  223. logger.debug("Calculate Signature URL: %s", url)
  224. if headers:
  225. headers.update({'Content-Type': 'application/json'})
  226. else:
  227. headers = {'Content-Type': 'application/json'}
  228. kwargs['timeout'] = kwargs.get('timeout', 15)
  229. logger.debug('Request to Alipay API: %s %s\n%s', method, url, kwargs)
  230. with requests.sessions.Session() as session:
  231. res = session.request(
  232. url = url,
  233. method = method,
  234. headers = headers,
  235. **kwargs)
  236. try:
  237. res.raise_for_status()
  238. return res.text.decode('utf-8')
  239. except requests.RequestException as reqe:
  240. raise AliPayNetworkException(
  241. errCode = res.status_code,
  242. errMsg = reqe.message,
  243. client = self,
  244. request = reqe.request,
  245. response = reqe.response
  246. )
  247. def api_alipay_trade_wap_pay(
  248. self, subject, out_trade_no, total_amount,
  249. return_url = None, notify_url = None, **kwargs
  250. ):
  251. biz_content = {
  252. "subject": subject,
  253. "out_trade_no": out_trade_no,
  254. "total_amount": total_amount,
  255. "product_code": "QUICK_WAP_PAY"
  256. }
  257. biz_content.update(kwargs)
  258. query = {
  259. 'biz_content': biz_content
  260. }
  261. if return_url:
  262. query.update({'return_url': return_url})
  263. if notify_url:
  264. query.update({'notify_url': notify_url})
  265. data = self.build_body("alipay.trade.wap.pay", **query)
  266. return self.sign_data(data)
  267. def api_alipay_trade_app_pay(
  268. self, subject, out_trade_no, total_amount, notify_url = None, **kwargs
  269. ):
  270. biz_content = {
  271. "subject": subject,
  272. "out_trade_no": out_trade_no,
  273. "total_amount": total_amount,
  274. "product_code": "QUICK_MSECURITY_PAY"
  275. }
  276. biz_content.update(kwargs)
  277. query = {
  278. 'biz_content': biz_content
  279. }
  280. if notify_url:
  281. query.update({'notify_url': notify_url})
  282. data = self.build_body("alipay.trade.app.pay", **query)
  283. return self.sign_data(data)
  284. def api_alipay_trade_page_pay(self, subject, out_trade_no, total_amount,
  285. return_url = None, notify_url = None, **kwargs):
  286. biz_content = {
  287. "subject": subject,
  288. "out_trade_no": out_trade_no,
  289. "total_amount": total_amount,
  290. "product_code": "FAST_INSTANT_TRADE_PAY"
  291. }
  292. biz_content.update(kwargs)
  293. query = {
  294. 'biz_content': biz_content
  295. }
  296. if return_url:
  297. query.update({'return_url': return_url})
  298. if notify_url:
  299. query.update({'notify_url': notify_url})
  300. data = self.build_body("alipay.trade.page.pay", **query)
  301. return self.sign_data(data)
  302. def api_alipay_trade_query(self, out_trade_no = None, trade_no = None):
  303. """
  304. response = {
  305. "alipay_trade_query_response": {
  306. "trade_no": "2017032121001004070200176844",
  307. "code": "10000",
  308. "invoice_amount": "20.00",
  309. "open_id": "20880072506750308812798160715407",
  310. "fund_bill_list": [
  311. {
  312. "amount": "20.00",
  313. "fund_channel": "ALIPAYACCOUNT"
  314. }
  315. ],
  316. "buyer_logon_id": "csq***@sandbox.com",
  317. "send_pay_date": "2017-03-21 13:29:17",
  318. "receipt_amount": "20.00",
  319. "out_trade_no": "out_trade_no15",
  320. "buyer_pay_amount": "20.00",
  321. "buyer_user_id": "2088102169481075",
  322. "msg": "Success",
  323. "point_amount": "0.00",
  324. "trade_status": "TRADE_SUCCESS",
  325. "total_amount": "20.00"
  326. },
  327. "sign": ""
  328. }
  329. """
  330. assert (out_trade_no is not None) or (trade_no is not None), \
  331. "Both trade_no and out_trade_no are None"
  332. biz_content = {}
  333. if out_trade_no:
  334. biz_content["out_trade_no"] = out_trade_no
  335. if trade_no:
  336. biz_content["trade_no"] = trade_no
  337. data = self.build_body("alipay.trade.query", biz_content = biz_content)
  338. url = self._gateway + "?" + self.sign_data(data)
  339. raw_string = self._request(method = 'get', url = url, timeout = self.timeout)
  340. return self._verify_and_return_sync_response(raw_string, "alipay_trade_query_response")
  341. def api_alipay_trade_pay(
  342. self, out_trade_no, scene, auth_code, subject, notify_url = None, **kwargs
  343. ):
  344. """
  345. eg:
  346. self.api_alipay_trade_pay(
  347. out_trade_no,
  348. "bar_code/wave_code",
  349. auth_code,
  350. subject,
  351. total_amount=12,
  352. discountable_amount=10
  353. )
  354. failed response = {
  355. "alipay_trade_pay_response": {
  356. "code": "40004",
  357. "msg": "Business Failed",
  358. "sub_code": "ACQ.INVALID_PARAMETER",
  359. "sub_msg": "",
  360. "buyer_pay_amount": "0.00",
  361. "invoice_amount": "0.00",
  362. "point_amount": "0.00",
  363. "receipt_amount": "0.00"
  364. },
  365. "sign": ""
  366. }
  367. succeeded response =
  368. {
  369. "alipay_trade_pay_response": {
  370. "trade_no": "2017032121001004070200176846",
  371. "code": "10000",
  372. "invoice_amount": "20.00",
  373. "open_id": "20880072506750308812798160715407",
  374. "fund_bill_list": [
  375. {
  376. "amount": "20.00",
  377. "fund_channel": "ALIPAYACCOUNT"
  378. }
  379. ],
  380. "buyer_logon_id": "csq***@sandbox.com",
  381. "receipt_amount": "20.00",
  382. "out_trade_no": "out_trade_no18",
  383. "buyer_pay_amount": "20.00",
  384. "buyer_user_id": "2088102169481075",
  385. "msg": "Success",
  386. "point_amount": "0.00",
  387. "gmt_payment": "2017-03-21 15:07:29",
  388. "total_amount": "20.00"
  389. },
  390. "sign": ""
  391. }
  392. """
  393. assert scene in ("bar_code", "wave_code"), 'scene not in ("bar_code", "wave_code")'
  394. biz_content = {
  395. "out_trade_no": out_trade_no,
  396. "scene": scene,
  397. "auth_code": auth_code,
  398. "subject": subject
  399. }
  400. biz_content.update(**kwargs)
  401. query = {
  402. 'biz_content': biz_content
  403. }
  404. if notify_url:
  405. query.update({
  406. 'notify_url': notify_url
  407. })
  408. data = self.build_body("alipay.trade.pay", **query)
  409. url = self._gateway + "?" + self.sign_data(data)
  410. raw_string = self._request(method = 'get', url = url, timeout = self.timeout)
  411. return self._verify_and_return_sync_response(raw_string, "alipay_trade_pay_response")
  412. def api_alipay_trade_refund(self, out_trade_no, out_request_no, refund_amount, refund_reason):
  413. biz_content = {
  414. "out_trade_no": out_trade_no,
  415. "refund_amount": refund_amount,
  416. "out_request_no": out_request_no,
  417. 'refund_reason': refund_reason
  418. }
  419. data = self.build_body("alipay.trade.refund", biz_content = biz_content)
  420. url = self._gateway + "?" + self.sign_data(data)
  421. raw_string = self._request(method = 'get', url = url, timeout = self.timeout)
  422. return self._verify_and_return_sync_response(raw_string, "alipay_trade_refund_response")
  423. def api_alipay_trade_cancel(self, out_trade_no = None, trade_no = None):
  424. """
  425. response = {
  426. "alipay_trade_cancel_response": {
  427. "msg": "Success",
  428. "out_trade_no": "out_trade_no15",
  429. "code": "10000",
  430. "retry_flag": "N"
  431. }
  432. }
  433. """
  434. assert (out_trade_no is not None) or (trade_no is not None), \
  435. "Both trade_no and out_trade_no are None"
  436. biz_content = {}
  437. if out_trade_no:
  438. biz_content["out_trade_no"] = out_trade_no
  439. if trade_no:
  440. biz_content["trade_no"] = trade_no
  441. data = self.build_body("alipay.trade.cancel", biz_content = biz_content)
  442. url = self._gateway + "?" + self.sign_data(data)
  443. raw_string = self._request(method = 'get', url = url, timeout = self.timeout)
  444. return self._verify_and_return_sync_response(raw_string, "alipay_trade_cancel_response")
  445. def api_alipay_trade_precreate(self, out_trade_no, total_amount, subject, **kwargs):
  446. """
  447. success response = {
  448. "alipay_trade_precreate_response": {
  449. "msg": "Success",
  450. "out_trade_no": "out_trade_no17",
  451. "code": "10000",
  452. "qr_code": "https://qr.alipay.com/bax03431ljhokirwl38f00a7"
  453. },
  454. "sign": ""
  455. }
  456. failed response = {
  457. "alipay_trade_precreate_response": {
  458. "msg": "Business Failed",
  459. "sub_code": "ACQ.TOTAL_FEE_EXCEED",
  460. "code": "40004",
  461. "sub_msg": "订单金额超过限额"
  462. },
  463. "sign": ""
  464. }
  465. """
  466. biz_content = {
  467. "out_trade_no": out_trade_no,
  468. "total_amount": total_amount,
  469. "subject": subject
  470. }
  471. biz_content.update(**kwargs)
  472. data = self.build_body("alipay.trade.precreate", biz_content = biz_content)
  473. url = self._gateway + "?" + self.sign_data(data)
  474. raw_string = self._request(method = 'get', url = url, timeout = self.timeout)
  475. return self._verify_and_return_sync_response(raw_string, "alipay_trade_precreate_response")
  476. def api_alipay_fund_trans_toaccount_transfer(
  477. self, out_biz_no, payee_type, payee_account, amount, **kwargs
  478. ):
  479. assert payee_type in ("ALIPAY_USERID", "ALIPAY_LOGONID"), "unknown payee type"
  480. biz_content = {
  481. "out_biz_no": out_biz_no,
  482. "payee_type": payee_type,
  483. "payee_account": payee_account,
  484. "amount": amount
  485. }
  486. biz_content.update(kwargs)
  487. data = self.build_body("alipay.fund.trans.toaccount.transfer", biz_content = biz_content)
  488. url = self._gateway + "?" + self.sign_data(data)
  489. raw_string = self._request(method = 'get', url = url, timeout = self.timeout)
  490. return self._verify_and_return_sync_response(
  491. raw_string, "alipay_fund_trans_toaccount_transfer_response"
  492. )
  493. def api_alipay_fund_trans_uni_transfer(
  494. self, out_biz_no, trans_amount, payee_info, order_title, product_code = 'TRANS_BANKCARD_NO_PWD',
  495. biz_scene = 'DIRECT_TRANSFER', **kwargs):
  496. biz_content = {
  497. "out_biz_no": out_biz_no,
  498. "trans_amount": trans_amount,
  499. "product_code": product_code,
  500. "biz_scene": biz_scene,
  501. "payee_info": payee_info,
  502. "order_title": order_title
  503. }
  504. biz_content.update(kwargs)
  505. data = self.build_body("alipay.fund.trans.uni.transfer", biz_content = biz_content)
  506. url = self._gateway + "?" + self.sign_data(data)
  507. raw_string = self._request(method = 'get', url = url, timeout = self.timeout)
  508. return self._verify_and_return_sync_response(
  509. raw_string, "alipay_fund_trans_uni_transfer_response"
  510. )
  511. def api_alipay_fund_trans_common_query(self, out_biz_no, product_code = 'TRANS_BANKCARD_NO_PWD',
  512. biz_scene = 'DIRECT_TRANSFER', **kwargs):
  513. biz_content = {
  514. "out_biz_no": out_biz_no,
  515. "product_code": product_code,
  516. "biz_scene": biz_scene
  517. }
  518. biz_content.update(kwargs)
  519. data = self.build_body("alipay.fund.trans.common.query", biz_content = biz_content)
  520. url = self._gateway + "?" + self.sign_data(data)
  521. raw_string = self._request(method = 'get', url = url, timeout = self.timeout)
  522. return self._verify_and_return_sync_response(
  523. raw_string, "alipay_fund_trans_common_query_response"
  524. )
  525. def api_alipay_trade_create(self, out_trade_no, total_amount, subject, notify_url = None, **kwargs):
  526. biz_content = {
  527. "out_trade_no": out_trade_no,
  528. "total_amount": total_amount,
  529. "subject": subject
  530. }
  531. biz_content.update(**kwargs)
  532. query = {
  533. 'biz_content': biz_content
  534. }
  535. if notify_url:
  536. query.update({
  537. 'notify_url': notify_url
  538. })
  539. data = self.build_body("alipay.trade.create", **query)
  540. url = self._gateway + "?" + self.sign_data(data)
  541. raw_string = self._request(method = 'get', url = url, timeout = self.timeout)
  542. return self._verify_and_return_sync_response(
  543. raw_string, "alipay_trade_create_response"
  544. )
  545. def api_alipay_fund_trans_order_query(self, out_biz_no = None, order_id = None):
  546. if out_biz_no is None and order_id is None:
  547. raise Exception("Both out_biz_no and order_id are None!")
  548. biz_content = {}
  549. if out_biz_no:
  550. biz_content["out_biz_no"] = out_biz_no
  551. if order_id:
  552. biz_content["order_id"] = order_id
  553. data = self.build_body("alipay.fund.trans.order.query", biz_content = biz_content)
  554. url = self._gateway + "?" + self.sign_data(data)
  555. raw_string = self._request(method = 'get', url = url, timeout = self.timeout)
  556. return self._verify_and_return_sync_response(
  557. raw_string, "alipay_fund_trans_order_query_response"
  558. )
  559. def api_alipay_system_oauth_token(self, auth_code, refresh_token = None):
  560. """
  561. :param auth_code:
  562. :param refresh_token:
  563. :return: dict
  564. """
  565. if refresh_token:
  566. query = {
  567. "grant_type": "refresh_token",
  568. "refresh_token": refresh_token
  569. }
  570. else:
  571. query = {
  572. "grant_type": "authorization_code",
  573. "code": auth_code
  574. }
  575. data = self.build_body("alipay.system.oauth.token", **query)
  576. url = self._gateway + "?" + self.sign_data(data)
  577. raw_string = self._request(method = 'get', url = url, timeout = self.timeout)
  578. return self._verify_and_return_sync_response(
  579. raw_string, "alipay_system_oauth_token_response"
  580. )
  581. def api_alipay_trade_refund_order_query(self, out_trade_no, out_request_no, query_options=None):
  582. # type:(str, str, dict) -> dict
  583. biz_content = {
  584. "out_trade_no": out_trade_no,
  585. "out_request_no": out_request_no,
  586. }
  587. if query_options:
  588. biz_content["query_options"] = query_options
  589. data = self.build_body("alipay.trade.fastpay.refund.query", biz_content=biz_content)
  590. url = self._gateway + "?" + self.sign_data(data)
  591. raw_string = self._request(method = 'get', url = url, timeout = self.timeout)
  592. return self._verify_and_return_sync_response(
  593. raw_string, "alipay_trade_fastpay_refund_query_response"
  594. )
  595. def query_auth(self, sub_merchant_id):
  596. biz_content = {
  597. "sub_merchant_id": sub_merchant_id
  598. }
  599. data = self.build_body("alipay.merchant.indirect.smidbind.query", biz_content=biz_content)
  600. url = self._gateway + "?" + self.sign_data(data)
  601. raw_string = self._request(method = 'get', url = url, timeout = self.timeout)
  602. return
  603. def api_alipay_data_dataservice_bill_downloadurl_query(self, bill_type = 'trade', bill_date = None):
  604. """
  605. :return: dict
  606. """
  607. #: 注意,最多只能取昨天的数据,支付宝没有说明这一点
  608. if bill_date is None:
  609. bill_date = (datetime.datetime.now() - datetime.timedelta(days = 1)).strftime('%Y-%m-%d')
  610. biz_content = {
  611. 'bill_type': bill_type,
  612. 'bill_date': bill_date
  613. }
  614. data = self.build_body("alipay.data.dataservice.bill.downloadurl.query", biz_content = biz_content)
  615. url = self._gateway + "?" + self.sign_data(data)
  616. raw_string = self._request(method = 'get', url = url, timeout = self.timeout)
  617. return self._verify_and_return_sync_response(
  618. raw_string, "alipay_data_dataservice_bill_downloadurl_query_response"
  619. )
  620. def api_alipay_user_info_share(self, auth_token):
  621. """
  622. :param auth_token:
  623. :return: dict
  624. """
  625. data = self.build_body("alipay.user.info.share", auth_token = auth_token)
  626. url = self._gateway + "?" + self.sign_data(data)
  627. raw_string = self._request(method = 'get', url = url, timeout = self.timeout)
  628. return self._verify_and_return_sync_response(
  629. raw_string, "alipay_user_info_share_response"
  630. )
  631. def _verify_and_return_sync_response(self, raw_string, response_type):
  632. """
  633. return data if verification succeeded, else raise exception
  634. """
  635. logger.debug('Response from Alipay API: %s', raw_string)
  636. response = json.loads(raw_string)
  637. result = response[response_type]
  638. sign = response["sign"]
  639. # locate string to be signed
  640. raw_string = self._get_string_to_be_signed(
  641. raw_string, response_type
  642. )
  643. if not self._verify(raw_string, sign):
  644. raise AliException(
  645. errCode = AliErrorCode.MY_ERROR_SIGNATURE,
  646. errMsg = u'签名错误',
  647. client = self)
  648. return result
  649. def _get_string_to_be_signed(self, raw_string, response_type):
  650. """
  651. https://doc.open.alipay.com/docs/doc.htm?docType=1&articleId=106120
  652. 从同步返回的接口里面找到待签名的字符串
  653. """
  654. left_index = 0
  655. right_index = 0
  656. index = raw_string.find(response_type)
  657. left_index = raw_string.find("{", index)
  658. index = left_index + 1
  659. balance = -1
  660. while balance < 0 and index < len(raw_string) - 1:
  661. index_a = raw_string.find("{", index)
  662. index_b = raw_string.find("}", index)
  663. # 右括号没找到, 退出
  664. if index_b == -1:
  665. break
  666. right_index = index_b
  667. # 左括号没找到,移动到右括号的位置
  668. if index_a == -1:
  669. index = index_b + 1
  670. balance += 1
  671. # 左括号出现在有括号之前,移动到左括号的位置
  672. elif index_a > index_b:
  673. balance += 1
  674. index = index_b + 1
  675. # 左括号出现在右括号之后, 移动到右括号的位置
  676. else:
  677. balance -= 1
  678. index = index_a + 1
  679. return raw_string[left_index: right_index + 1]
  680. def upload_image(self, imageType, imageContent):
  681. """
  682. 上传图片
  683. """
  684. path = "ant.merchant.expand.indirect.image.upload"
  685. query = {
  686. "image_type": imageType,
  687. "image_content": imageContent
  688. }
  689. data = self.build_body(path)
  690. url = self._gateway + "?" + self.sign_data(data)
  691. raw_string = self._request(method="get", url=url, data=query)
  692. return self._verify_and_return_sync_response(
  693. raw_string, "ant_merchant_expand_indirect_image_upload_response"
  694. )
  695. def api_alipay_user_agreement_page_sign(
  696. self, personal_product_code, sign_scene, access_params,
  697. product_code, external_agreement_no, third_party_type, external_logon_id,
  698. sign_validity_period = None, notify_url = None):
  699. biz_content = {
  700. "personal_product_code": personal_product_code,
  701. "sign_scene": sign_scene,
  702. "access_params": access_params,
  703. "product_code": product_code,
  704. "external_agreement_no": external_agreement_no,
  705. "third_party_type": third_party_type
  706. }
  707. if external_logon_id:
  708. biz_content.update({
  709. "external_logon_id": external_logon_id
  710. })
  711. if sign_validity_period:
  712. biz_content.update({
  713. 'sign_validity_period': sign_validity_period
  714. })
  715. data = self.build_body("alipay.user.agreement.page.sign", notify_url = notify_url, biz_content = biz_content)
  716. return self._gateway + "?" + self.sign_data(data)
  717. def api_alipay_user_agreement_query(
  718. self, personal_product_code, sign_scene, product_code, external_agreement_no):
  719. biz_content = {
  720. "personal_product_code": personal_product_code,
  721. "sign_scene": sign_scene,
  722. "product_code": product_code,
  723. "external_agreement_no": external_agreement_no
  724. }
  725. data = self.build_body("alipay.user.agreement.query", biz_content = biz_content)
  726. url = self._gateway + "?" + self.sign_data(data)
  727. raw_string = self._request(method = "get", url = url)
  728. return self._verify_and_return_sync_response(
  729. raw_string, "alipay_user_agreement_query_response"
  730. )
  731. def api_alipay_user_agreement_unsign(
  732. self, personal_product_code, sign_scene, external_agreement_no):
  733. biz_content = {
  734. "personal_product_code": personal_product_code,
  735. "sign_scene": sign_scene,
  736. "external_agreement_no": external_agreement_no
  737. }
  738. data = self.build_body("alipay.user.agreement.unsign", biz_content = biz_content)
  739. url = self._gateway + "?" + self.sign_data(data)
  740. raw_string = self._request(method = "get", url = url)
  741. return self._verify_and_return_sync_response(
  742. raw_string, "alipay_user_agreement_unsign_response"
  743. )
  744. def api_alipay_fund_accountbook_create(self, merchant_user_id, merchant_user_type, scene_code, ext_info):
  745. biz_content = {
  746. "merchant_user_id": merchant_user_id,
  747. "merchant_user_type": merchant_user_type,
  748. "scene_code": scene_code,
  749. "ext_info": ext_info
  750. }
  751. data = self.build_body("alipay.fund.accountbook.create", biz_content = biz_content)
  752. url = self._gateway + "?" + self.sign_data(data)
  753. raw_string = self._request(method = "get", url = url)
  754. return self._verify_and_return_sync_response(
  755. raw_string, "alipay_fund_accountbook_create_response"
  756. )
  757. def api_alipay_fund_accountbook_query(self, account_book_id, scene_code, ext_info):
  758. biz_content = {
  759. "account_book_id": account_book_id,
  760. "scene_code": scene_code,
  761. "ext_info": ext_info
  762. }
  763. data = self.build_body("alipay.fund.accountbook.query", biz_content = biz_content)
  764. url = self._gateway + "?" + self.sign_data(data)
  765. raw_string = self._request(method = "get", url = url)
  766. return self._verify_and_return_sync_response(
  767. raw_string, "alipay_fund_accountbook_query_response"
  768. )
  769. def api_alipay_fund_trans_page_pay(self, agreement_no, account_book_id, out_biz_no, trans_amount, time_expire, remark = u'转账', order_title = u'代发专项充值'):
  770. biz_content = {
  771. 'out_biz_no': out_biz_no,
  772. 'trans_amount': trans_amount,
  773. 'product_code': 'FUND_ACCOUNT_BOOK',
  774. 'biz_scene': 'SATF_DEPOSIT',
  775. 'remark': remark,
  776. 'order_title': order_title,
  777. 'time_expire':time_expire,
  778. 'payee_info': {
  779. 'identity_type': 'ACCOUNT_BOOK_ID',
  780. 'identity': account_book_id,
  781. 'ext_info': json.dumps({
  782. 'agreement_no':agreement_no
  783. })
  784. }
  785. }
  786. data = self.build_body("alipay.fund.trans.page.pay", biz_content = biz_content)
  787. url = self._gateway + "?" + self.sign_data(data)
  788. return url
  789. # raw_string = self._request(method = "get", url = url)
  790. # return raw_string
  791. # return self._verify_and_return_sync_response(
  792. # raw_string, "alipay_fund_accountbook_query_response"
  793. # )
  794. class AliPay(BaseAliPay):
  795. def __init__(self, appid,
  796. app_private_key_string,
  797. public_key_string,
  798. aes_encrypt_key = None,
  799. sign_type = "RSA2",
  800. debug = False,
  801. timeout = 15):
  802. super(AliPay, self).__init__(
  803. appid = appid,
  804. app_private_key_string = app_private_key_string,
  805. public_key_string = public_key_string,
  806. aes_encrypt_key = aes_encrypt_key,
  807. sign_type = sign_type,
  808. debug = debug,
  809. timeout = timeout)
  810. class DCAliPay(AliPay):
  811. """
  812. 数字证书 (digital certificate) 版本
  813. """
  814. def __init__(
  815. self,
  816. appid,
  817. app_public_key_cert_string,
  818. public_key_cert_string,
  819. root_cert_string,
  820. app_private_key_string = None,
  821. aes_encrypt_key = None,
  822. sign_type = "RSA2",
  823. debug = False,
  824. timeout = 15):
  825. """
  826. 初始化
  827. DCAlipay(
  828. appid='',
  829. app_private_key_string='',
  830. app_public_key_cert_string='',
  831. alipay_public_key_cert_sring='',
  832. aplipay_root_cert_string='',
  833. )
  834. """
  835. self._app_public_key_cert_string = app_public_key_cert_string
  836. self._alipay_public_key_cert_string = public_key_cert_string
  837. self._alipay_root_cert_string = root_cert_string
  838. public_key_string = self.load_alipay_public_key_string()
  839. super(DCAliPay, self).__init__(
  840. appid = appid,
  841. app_private_key_string = app_private_key_string,
  842. public_key_string = public_key_string,
  843. aes_encrypt_key = aes_encrypt_key,
  844. sign_type = sign_type,
  845. debug = debug,
  846. timeout = timeout)
  847. def api_alipay_open_app_alipaycert_download(self, alipay_cert_sn):
  848. """
  849. 下载支付宝证书
  850. 验签使用,支付宝公钥证书无感知升级机制
  851. """
  852. biz_content = {
  853. "alipay_cert_sn": alipay_cert_sn
  854. }
  855. data = self.build_body("alipay.open.app.alipaycert.download", biz_content = biz_content)
  856. return self.sign_data(data)
  857. def build_body(self, method, append_auth_token = False, **query):
  858. data = super(DCAliPay, self).build_body(method, append_auth_token, **query)
  859. data["app_cert_sn"] = self.app_cert_sn
  860. data["alipay_root_cert_sn"] = self.alipay_root_cert_sn
  861. return data
  862. def load_alipay_public_key_string(self):
  863. cert = OpenSSL.crypto.load_certificate(
  864. OpenSSL.crypto.FILETYPE_PEM, self._alipay_public_key_cert_string
  865. )
  866. return OpenSSL.crypto.dump_publickey(
  867. OpenSSL.crypto.FILETYPE_PEM, cert.get_pubkey()
  868. ).decode("utf-8")
  869. @staticmethod
  870. def get_cert_sn(cert):
  871. """
  872. 获取证书 SN 算法
  873. """
  874. cert = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, cert)
  875. certIssue = cert.get_issuer()
  876. name = 'CN={},OU={},O={},C={}'.format(certIssue.CN, certIssue.OU, certIssue.O, certIssue.C)
  877. string = name + str(cert.get_serial_number())
  878. return hashlib.md5(string.encode()).hexdigest()
  879. @staticmethod
  880. def read_pem_cert_chain(certContent):
  881. """解析根证书"""
  882. # 根证书中,每个 cert 中间有两个回车间隔
  883. items = [i for i in certContent.split('\n\n') if i]
  884. load_cert = partial(OpenSSL.crypto.load_certificate, OpenSSL.crypto.FILETYPE_PEM)
  885. return [load_cert(c) for c in items]
  886. @staticmethod
  887. def get_root_cert_sn(rootCert):
  888. """ 根证书 SN 算法"""
  889. certs = DCAliPay.read_pem_cert_chain(rootCert)
  890. rootCertSN = None
  891. for cert in certs:
  892. try:
  893. sigAlg = cert.get_signature_algorithm()
  894. except ValueError:
  895. continue
  896. if sigAlg in CryptoAlgSet:
  897. certIssue = cert.get_issuer()
  898. name = 'CN={},OU={},O={},C={}'.format(
  899. certIssue.CN, certIssue.OU, certIssue.O, certIssue.C
  900. )
  901. string = name + str(cert.get_serial_number())
  902. certSN = hashlib.md5(string.encode()).hexdigest()
  903. if not rootCertSN:
  904. rootCertSN = certSN
  905. else:
  906. rootCertSN = rootCertSN + '_' + certSN
  907. return rootCertSN
  908. @property
  909. def app_cert_sn(self):
  910. if not hasattr(self, "_app_cert_sn"):
  911. self._app_cert_sn = self.get_cert_sn(self._app_public_key_cert_string)
  912. return getattr(self, "_app_cert_sn")
  913. @property
  914. def alipay_root_cert_sn(self):
  915. if not hasattr(self, "_alipay_root_cert_sn"):
  916. self._alipay_root_cert_sn = self.get_root_cert_sn(self._alipay_root_cert_string)
  917. return getattr(self, "_alipay_root_cert_sn")
  918. class ISVAliPay(BaseAliPay):
  919. def __init__(self, appid, app_private_key_string, public_key_string, sign_type = "RSA2", app_auth_token = None,
  920. app_auth_code = None, debug = False):
  921. if not app_auth_token and not app_auth_code:
  922. raise Exception("Both app_auth_code and app_auth_token are None !!!")
  923. self._app_auth_token = app_auth_token
  924. self._app_auth_code = app_auth_code
  925. super(ISVAliPay, self).__init__(appid, app_private_key_string, public_key_string, sign_type, debug)
  926. @property
  927. def app_auth_token(self):
  928. # 没有则换取token
  929. if not self._app_auth_token:
  930. result = self.api_alipay_open_auth_token_app(self._app_auth_code)
  931. self._app_auth_token = result.get("app_auth_token", None)
  932. if not self._app_auth_token:
  933. raise Exception("Get auth token by auth code failed: {}".format(self._app_auth_code))
  934. return self._app_auth_token
  935. def build_body(self, method, biz_content, return_url = None, append_auth_token = True):
  936. return super(ISVAliPay, self).build_body(method, biz_content, return_url, append_auth_token)
  937. def api_alipay_open_auth_token_app(self, refresh_token = None):
  938. """
  939. response = {
  940. "code": "10000",
  941. "msg": "Success",
  942. "app_auth_token": "201708BB28623ce3d10f4f62875e9ef5cbeebX07",
  943. "app_refresh_token": "201708BB108a270d8bb6409890d16175a04a7X07",
  944. "auth_app_id": "appid",
  945. "expires_in": 31536000,
  946. "re_expires_in": 32140800,
  947. "user_id": "2088xxxxx
  948. }
  949. """
  950. if refresh_token:
  951. biz_content = {
  952. "grant_type": "refresh_token",
  953. "refresh_token": refresh_token
  954. }
  955. else:
  956. biz_content = {
  957. "grant_type": "authorization_code",
  958. "code": self._app_auth_code
  959. }
  960. data = self.build_body(
  961. "alipay.open.auth.token.app",
  962. biz_content,
  963. append_auth_token = False
  964. )
  965. url = self._gateway + "?" + self.sign_data(data)
  966. raw_string = self._request(method = 'get', url = url, timeout = self.timeout)
  967. return self._verify_and_return_sync_response(
  968. raw_string, "alipay_open_auth_token_app_response"
  969. )
  970. def api_alipay_open_auth_token_app_query(self):
  971. biz_content = {
  972. "app_auth_token": self.app_auth_token
  973. }
  974. data = self.build_body(
  975. "alipay.open.auth.token.app.query",
  976. biz_content,
  977. append_auth_token = False
  978. )
  979. url = self._gateway + "?" + self.sign_data(data)
  980. raw_string = self._request(method = 'get', url = url, timeout = self.timeout)
  981. return self._verify_and_return_sync_response(
  982. raw_string, "alipay_open_auth_token_app_query_response"
  983. )