__init__.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396
  1. # -*- coding: utf-8 -*-
  2. # !/usr/bin/env python
  3. import logging
  4. import dicttoxml
  5. import six
  6. from django.utils.module_loading import import_string
  7. from typing import TYPE_CHECKING, Optional, cast, Union
  8. from apilib.systypes import IterConstant
  9. from apps.web.constant import AppPlatformType
  10. from library.ys.base import PayOpt
  11. if TYPE_CHECKING:
  12. from apps.web.core.models import WechatPayApp, PayAppBase, EmbeddedApp, WechatMiniApp
  13. logger = logging.getLogger(__name__)
  14. class PayAppType(IterConstant):
  15. # 资金池模式只使用ALIPAY,WECHAT,JD_AGGR三种支付方式
  16. ALIPAY = 'alipay'
  17. WECHAT = 'wechat'
  18. JD_AGGR = 'jdaggre'
  19. # 蓝牙特殊处理, 使用小程序绑定的支付方式, 后续可以迁移到京东聚合支付
  20. WECHAT_MINI = 'wechat_mini'
  21. # 各经销商可使用的支付方式(包括JD_AGGR以及ALIPAY+WECHAT)
  22. SAOBEI = 'saobei'
  23. JD = 'jd'
  24. DLB = 'dlb'
  25. YS = 'ys'
  26. JOS = "jos"
  27. RCU = "rcu"
  28. PLATFORM_PROMOTION = "platform_promotion"
  29. PLATFORM_WALLET = 'platform_wallet'
  30. PLATFORM_RECONCILE ='platform_reconcile'
  31. JD_OPEN = 'jdopen'
  32. MANUAL = 'manual'
  33. SWAP = 'swap'
  34. LEDGER_CONSUME = "ledgerConsume"
  35. PAY_APP_TYPE_TRANSLATION = {
  36. AppPlatformType.ALIPAY: u'支付宝',
  37. AppPlatformType.WECHAT: u'微信',
  38. PayAppType.WECHAT_MINI: u'微信小程序',
  39. PayAppType.SAOBEI: u'扫呗聚合支付',
  40. PayAppType.JD_AGGR: u'京东聚合支付',
  41. PayAppType.DLB: u'哆啦宝聚合支付',
  42. PayAppType.YS: u'易生聚合支付',
  43. PayAppType.RCU: u'河南农村信用社',
  44. PayAppType.JD_OPEN: u'京东聚合支付'
  45. }
  46. PAY_APP_MAP = {
  47. PayAppType.ALIPAY: 'apps.web.core.models.AliApp',
  48. PayAppType.WECHAT: 'apps.web.core.models.WechatPayApp',
  49. PayAppType.WECHAT_MINI: 'apps.web.core.models.WechatMiniApp',
  50. PayAppType.SAOBEI: 'apps.web.core.models.SaobeiPayApp',
  51. PayAppType.JD_AGGR: 'apps.web.core.models.JDAggrePayApp',
  52. PayAppType.DLB: 'apps.web.core.models.DlbPayApp',
  53. PayAppType.YS: 'apps.web.core.models.YsPayApp',
  54. PayAppType.RCU: 'apps.web.core.models.RuralCreditUnionApp',
  55. PayAppType.PLATFORM_PROMOTION: 'apps.web.core.models.PlatformPromotionApp',
  56. PayAppType.PLATFORM_WALLET: 'app.web.core.models.PlatformWalletApp',
  57. PayAppType.PLATFORM_RECONCILE: 'app.web.core.models.PlatformReconcileApp',
  58. PayAppType.JD_OPEN: 'apps.web.core.models.JDOpenPayApp',
  59. PayAppType.MANUAL: 'apps.web.core.models.ManualPayApp'
  60. }
  61. APP_KEY_DELIMITER = '-'
  62. def wechat_bound_openid_key(appid):
  63. return APP_KEY_DELIMITER.join([AppPlatformType.WECHAT, appid])
  64. class BaseAppProxy(object):
  65. def __init__(self, app):
  66. # type: (Optional[EmbeddedApp, WechatPayApp])->None
  67. self._app = app # type: Optional[EmbeddedApp, WechatPayApp]
  68. @property
  69. def app(self):
  70. return self._app
  71. @property
  72. def client(self):
  73. if hasattr(self, '__client__'):
  74. return getattr(self, '__client__')
  75. else:
  76. raise AttributeError('no __client__ attribute')
  77. @property
  78. def occupantId(self):
  79. return self._app.occupantId
  80. @property
  81. def occupant(self):
  82. return self._app.occupant
  83. @property
  84. def bound_openid_key(self):
  85. if not hasattr(self, '__bound_openid_key__'):
  86. raise AttributeError('no __bound_openid_key__ attribute')
  87. return getattr(self, '__bound_openid_key__')
  88. @property
  89. def gateway_type(self):
  90. if not hasattr(self, '__gateway_type__'):
  91. raise AttributeError('no __gateway_type__ attribute')
  92. return getattr(self, '__gateway_type__')
  93. @property
  94. def debug(self):
  95. return self._app.debug
  96. @property
  97. def enable(self):
  98. return self._app.enable
  99. @property
  100. def valid(self):
  101. return self._app.valid
  102. class _BaseMixin(object):
  103. @property
  104. def __app_for_inner__(self):
  105. # type: ()->Union[cast(PayAppBase), EmbeddedApp]
  106. if not hasattr(self, 'app'):
  107. raise AttributeError('no app attribute')
  108. return getattr(self, 'app')
  109. @property
  110. def __client_for_inner__(self):
  111. if not hasattr(self, 'client'):
  112. raise AttributeError('no client attribute')
  113. return getattr(self, 'client')
  114. @property
  115. def __bound_openid_key__(self):
  116. raise AttributeError('no __bound_openid_key__ attribute')
  117. class WechatMixin(_BaseMixin):
  118. @property
  119. def appid(self):
  120. return self.__app_for_inner__.appid
  121. @property
  122. def secret(self):
  123. return self.__app_for_inner__.secret
  124. @classmethod
  125. def reply(cls, msg, ok):
  126. # type: (basestring, bool)->str
  127. response = {
  128. "return_code": "SUCCESS" if ok else "FAIL",
  129. "return_msg": msg
  130. }
  131. return cls.serialize_wechat_xml(response)
  132. def check(self, data, order = True, token = True):
  133. return self.__client_for_inner__.check_signature(data)
  134. @classmethod
  135. def serialize_wechat_xml(cls, payload):
  136. # type:(dict)->str
  137. return dicttoxml.dicttoxml(payload, attr_type = False, custom_root = 'xml', cdata = True)
  138. @property
  139. def __bound_openid_key__(self):
  140. return wechat_bound_openid_key(self.appid)
  141. # @property
  142. # def __gateway_type__(self):
  143. # return AppPlatformType.WECHAT
  144. class DlbMixin(_BaseMixin):
  145. @property
  146. def merchant_no(self):
  147. return self.__app_for_inner__.merchant_no
  148. @property
  149. def shop_no(self):
  150. return self.__app_for_inner__.shop_no
  151. @property
  152. def machine_no(self):
  153. return self.__app_for_inner__.machine_no
  154. @property
  155. def access_key(self):
  156. return self.__app_for_inner__.access_key
  157. @property
  158. def secret_key(self):
  159. return self.__app_for_inner__.secret_key
  160. @property
  161. def __bound_openid_key__(self):
  162. # 哆啦宝不需要获取openid
  163. raise AttributeError("no __bound_openid_key__ attribute")
  164. class YsMixin(_BaseMixin):
  165. GATEWAY_TYPE_TO_OPT = {
  166. AppPlatformType.WECHAT: PayOpt.wxPreOrder,
  167. AppPlatformType.ALIPAY: PayOpt.apPreOrder
  168. }
  169. @property
  170. def channel_id(self):
  171. return self.__app_for_inner__.channel_id
  172. @property
  173. def mer_id(self):
  174. return self.__app_for_inner__.mer_id
  175. @property
  176. def term_id(self):
  177. return self.__app_for_inner__.term_id
  178. @property
  179. def work_key(self):
  180. return self.__app_for_inner__.work_key
  181. @property
  182. def __bound_openid_key__(self):
  183. # 接口调用的时候获取openid
  184. raise AttributeError("no __bound_openid_key__ attribute")
  185. class AlipayMixin(_BaseMixin):
  186. """
  187. Alipay 支付网关,扩展原库没有的接口 ``alipay.trade.create``
  188. """
  189. @property
  190. def appid(self):
  191. return self.__app_for_inner__.appid
  192. @property
  193. def app_private_key_string(self):
  194. return self.__app_for_inner__.app_private_key_string
  195. @property
  196. def public_key_string(self):
  197. return self.__app_for_inner__.public_key_string
  198. @property
  199. def public_key_cert_string(self):
  200. return self.__app_for_inner__.public_key_cert_string
  201. @property
  202. def root_cert_string(self):
  203. return self.__app_for_inner__.root_cert_string
  204. @property
  205. def app_public_key_cert_string(self):
  206. return self.__app_for_inner__.app_public_key_cert_string
  207. @property
  208. def aes_encrypt_key(self):
  209. return self.__app_for_inner__.aes_encrypt_key
  210. @property
  211. def signKeyType(self):
  212. return self.__app_for_inner__.signKeyType
  213. @property
  214. def __gateway_type__(self):
  215. return AppPlatformType.ALIPAY
  216. @property
  217. def __bound_openid_key__(self):
  218. # 支付宝用户ID是唯一的,直接以平台类型区分
  219. return AppPlatformType.ALIPAY
  220. @property
  221. def __client__(self):
  222. return self.__app_for_inner__.client
  223. def check(self, data, pop_sign_type = True):
  224. # type:(dict, bool)->bool
  225. sign = data.pop('sign')
  226. if not sign:
  227. logger.error('has no sign')
  228. return False
  229. return self.__client__.verify(data, sign, pop_sign_type)
  230. class ROLEMetaClass(type):
  231. def __setattr__(self, name, value):
  232. raise NotImplementedError()
  233. def __getattr__(self, item):
  234. if item in self.choices():
  235. return item
  236. else:
  237. raise KeyError()
  238. class ROLE(six.with_metaclass(ROLEMetaClass)):
  239. __role_map = {
  240. 'myuser': {
  241. 'module': 'apps.web.user.models.MyUser',
  242. 'sub_type': 'U'
  243. },
  244. 'dealer': {
  245. 'module': 'apps.web.dealer.models.Dealer',
  246. 'sub_type': 'D'
  247. },
  248. 'subaccount': {
  249. 'module': 'apps.web.dealer.models.SubAccount',
  250. 'sub_type': 'B'
  251. },
  252. 'agent': {
  253. 'module': 'apps.web.agent.models.Agent',
  254. 'sub_type': 'A'
  255. },
  256. 'manager': {
  257. 'module': 'apps.web.management.models.Manager',
  258. 'sub_type': 'M'
  259. },
  260. 'supermanager': {
  261. 'module': 'apps.web.superamdin.models.SuperManager',
  262. 'sub_type': 'S'
  263. },
  264. 'advertiser': {
  265. 'module': 'apps.web.ad.models.Advertiser',
  266. 'sub_type': 'V'
  267. },
  268. 'tester': {
  269. 'module': 'apps.web.test.models.Tester',
  270. 'sub_type': 'T'
  271. },
  272. 'advertisement': {
  273. 'module': 'apps.web.ad.models.Advertisement',
  274. 'sub_type': 'T'
  275. },
  276. 'anonymoususer': {
  277. 'module': 'django.contrib.auth.models.AnonymousUser',
  278. 'sub_type': 'N'
  279. },
  280. 'manufacturer': {
  281. 'module': 'apps.web.management.models.Manager',
  282. 'sub_type': 'M'
  283. }
  284. }
  285. def __init__(self):
  286. raise NotImplementedError()
  287. @classmethod
  288. def choices(cls):
  289. return cls.__role_map.keys()
  290. @classmethod
  291. def from_role_id(cls, role, id):
  292. if role == cls.anonymoususer:
  293. return import_string(cls.__role_map[role]['module'])()
  294. else:
  295. return import_string(cls.__role_map[role]['module']).objects(id = id).get()
  296. @classmethod
  297. def sub_type(cls, role):
  298. return cls.__role_map[role]['sub_type']
  299. @classmethod
  300. def sub_type_list(cls):
  301. return [item['sub_type'] for item in cls.__role_map.values()]