mopybird 2 years ago
parent
commit
486a98ee56
84 changed files with 2162 additions and 11150 deletions
  1. 0 6
      apps/provision/bank.py
  2. 57 86
      apps/web/agent/models.py
  3. 0 7
      apps/web/agent/urls.py
  4. 12 103
      apps/web/agent/views.py
  5. 403 17
      apps/web/common/models.py
  6. 13 4
      apps/web/common/transaction/__init__.py
  7. 1 1
      apps/web/common/transaction/pay/__init__.py
  8. 0 226
      apps/web/common/transaction/pay/dlb.py
  9. 0 337
      apps/web/common/transaction/pay/jdaggre.py
  10. 0 290
      apps/web/common/transaction/pay/jdopen.py
  11. 0 231
      apps/web/common/transaction/pay/saobei.py
  12. 0 222
      apps/web/common/transaction/pay/ys.py
  13. 0 104
      apps/web/common/transaction/refund/jdaggre.py
  14. 0 110
      apps/web/common/transaction/refund/jdopen.py
  15. 359 119
      apps/web/common/transaction/withdraw.py
  16. 18 5
      apps/web/common/urls.py
  17. 173 52
      apps/web/common/views.py
  18. 5 27
      apps/web/core/__init__.py
  19. 16 15
      apps/web/core/accounting.py
  20. 8 0
      apps/web/core/bridge/wechat/v3api.py
  21. 1 1
      apps/web/core/db.py
  22. 3 2
      apps/web/core/messages/__init__.py
  23. 8 25
      apps/web/core/messages/sms.py
  24. 16 534
      apps/web/core/models.py
  25. 6 10
      apps/web/core/payment/__init__.py
  26. 138 61
      apps/web/core/payment/ali.py
  27. 10 11
      apps/web/core/payment/base.py
  28. 0 62
      apps/web/core/payment/dlb.py
  29. 0 92
      apps/web/core/payment/jdaggre.py
  30. 0 120
      apps/web/core/payment/jdopen.py
  31. 0 91
      apps/web/core/payment/saobei.py
  32. 0 23
      apps/web/core/payment/swap.py
  33. 61 44
      apps/web/core/payment/wechat.py
  34. 0 61
      apps/web/core/payment/ys.py
  35. 4 0
      apps/web/core/sysparas.py
  36. 46 261
      apps/web/dealer/models.py
  37. 33 19
      apps/web/dealer/tasks.py
  38. 5 10
      apps/web/dealer/urls.py
  39. 19 12
      apps/web/dealer/utils.py
  40. 47 241
      apps/web/dealer/views.py
  41. 3 0
      apps/web/exceptions.py
  42. 6 18
      apps/web/helpers.py
  43. 0 21
      apps/web/merchant/__init__.py
  44. 0 291
      apps/web/merchant/constant.py
  45. 0 4
      apps/web/merchant/exceptions.py
  46. 0 3127
      apps/web/merchant/models.py
  47. 0 28
      apps/web/merchant/signal.py
  48. 0 73
      apps/web/merchant/tasks.py
  49. 0 2
      apps/web/merchant/testCase.py
  50. 0 42
      apps/web/merchant/urls.py
  51. 0 1750
      apps/web/merchant/utils.py
  52. 0 219
      apps/web/merchant/validation.py
  53. 0 687
      apps/web/merchant/views.py
  54. 1 1
      apps/web/miniuser/views.py
  55. 5 2
      apps/web/superadmin/urls.py
  56. 107 49
      apps/web/superadmin/views.py
  57. 0 16
      apps/web/user/models.py
  58. 2 7
      apps/web/user/utils.py
  59. 45 1
      apps/web/validation.py
  60. 3 1
      library/alipay/__init__.py
  61. 2 1
      library/jdopen/pay.py
  62. 2 1
      library/misc.py
  63. 16 6
      library/sms/aliyun.py
  64. 16 6
      library/sms/ucpaas.py
  65. 6 0
      library/wechatpayv3/client/api/transfer.py
  66. 1 1
      library/wechatpayv3/core.py
  67. 202 95
      static/administrator/js/controllers/dealerManage.js
  68. 102 97
      static/administrator/tpl/dealerManage.html
  69. 17 11
      static/agents/wallet/new-info.html
  70. 0 38
      static/agents/wallet/wallet-bank-edit.html
  71. 0 134
      static/agents/wallet/wallet-bank.html
  72. 3 3
      static/agents/wallet/wallet-bind-alipay.html
  73. 40 18
      static/agents/wallet/wallet-withdraw.html
  74. 7 15
      static/agents/wallet/wallet.html
  75. 0 498
      static/app/js/wallet.js
  76. 17 11
      static/app/wallet/new-info.html
  77. 1 1
      static/app/wallet/wallet-approval.html
  78. 28 18
      static/app/wallet/wallet-auto.html
  79. 0 38
      static/app/wallet/wallet-bank-edit.html
  80. 0 135
      static/app/wallet/wallet-bank.html
  81. 3 3
      static/app/wallet/wallet-bind-alipay.html
  82. 1 1
      static/app/wallet/wallet-config.html
  83. 35 22
      static/app/wallet/wallet-withdraw.html
  84. 29 16
      static/app/wallet/wallet.html

+ 0 - 6
apps/provision/bank.py

@@ -204,12 +204,6 @@ BANK_LIST = [
         'wechatBankCode': '4166'
     },
 
-    {
-        'bankCode': 'GSRCU',
-        'bankName': u'甘肃省农村信用社',
-        'wechatBankCode': '4157'
-    },
-
     {
         'bankCode': 'TRCB',
         'bankName': u'天津农商银行',

+ 57 - 86
apps/web/agent/models.py

@@ -25,14 +25,14 @@ from collections import namedtuple
 from apilib.monetary import RMB, sum_rmb, Permillage, Percent
 from apps.web.agent.define import AgentConst, AGENT_INCOME_SOURCE, AGENT_INCOME_TYPE
 from apps.web.agent.errors import PrimaryAgentDoesNotExist
-from apps.web.common.models import WithdrawRecord, CapitalUser, Balance
+from apps.web.common.models import WithdrawRecord, CapitalUser, Balance, WithdrawBankCard
 
 from apps.web.constant import Const, AppPlatformType, DEALER_CONSUMPTION_AGG_KIND, MoniAppStatus
 from apps.web.core.db import Searchable, MonetaryField, StrictDictField, PermillageField, PercentField
 from apps.web.core.exceptions import InvalidParameter, NoAgentFound, NoManagerFound, MerchantError
 from apps.web.core.messages.sms import agentWithdrawSMSProvider
 from apps.web.core.models import WechatManagerApp, WechatAuthApp, BoundOpenInfo, AliApp, WechatPayApp, \
-    WechatMiniApp, WechatUserManagerApp, JDAggrePayApp, JDAuthApp, BankCard, \
+    WechatMiniApp, WechatUserManagerApp, BankCard, \
     WithdrawEntity, WechatUserSubscribeManagerApp, WechatDealerSubscribeManagerApp
 from apps.web.core import PayAppType, APP_KEY_DELIMITER, ROLE
 from apps.web.core.payment import WithdrawGateway
@@ -80,17 +80,14 @@ class Agent(CapitalUser):
         ROLE.agent: {
             AppPlatformType.ALIPAY: PayAppType.ALIPAY,
             AppPlatformType.WECHAT: PayAppType.WECHAT,
-            AppPlatformType.JD: PayAppType.JD_AGGR
         },
         ROLE.dealer: {
             AppPlatformType.ALIPAY: PayAppType.ALIPAY,
             AppPlatformType.WECHAT: PayAppType.WECHAT,
-            AppPlatformType.JD: PayAppType.JD_AGGR
         },
         ROLE.myuser: {
             AppPlatformType.ALIPAY: PayAppType.ALIPAY,
             AppPlatformType.WECHAT: PayAppType.WECHAT,
-            AppPlatformType.JD: PayAppType.JD_AGGR,
             AppPlatformType.WECHAT_MINI: PayAppType.WECHAT_MINI
         }
     }
@@ -99,8 +96,6 @@ class Agent(CapitalUser):
         'gerenzhongxin': False, 'yue': False, 'baogaolaoban': False, 'fukuan': False
     }
 
-    # CAPITAL_PAY_APP_LIST = [WechatPayApp, JDAggrePayApp, JDOpenPayApp]
-
     #: 收入相关
     deviceBalance = MapField(field = EmbeddedDocumentField(Balance))
     trafficBalance = MapField(field = EmbeddedDocumentField(Balance))
@@ -157,9 +152,10 @@ class Agent(CapitalUser):
     mySign = StringField(verbose_name = '唯一签名用于API调用,我们的签名,用于请求对方URl,对方鉴权')
     domain = StringField(verbose_name = '域名调用URL')
 
-    wechatLoginAuthApp = EmbeddedDocumentField(verbose_name='授权APP', document_type = WechatAuthApp)
+    wechatLoginAuthApp = EmbeddedDocumentField(verbose_name = '授权APP', document_type = WechatAuthApp)
 
-    oldWechatLoginAuthApp = EmbeddedDocumentField(verbose_name = '老的授权APP', document_type = WechatAuthApp, default = None)
+    oldWechatLoginAuthApp = EmbeddedDocumentField(verbose_name = '老的授权APP',
+                                                  document_type = WechatAuthApp, default = None)
 
     wechatUserManagerialApp = EmbeddedDocumentField(verbose_name = '用户管理APP', document_type = WechatUserManagerApp)
     wechatDealerManagerialApp = EmbeddedDocumentField(verbose_name = '经销商管理APP', document_type = WechatManagerApp)
@@ -169,16 +165,6 @@ class Agent(CapitalUser):
     wechatDealerSubscribeManagerApp = EmbeddedDocumentField(verbose_name = '经销商订阅通知APP',
                                                             document_type = WechatDealerSubscribeManagerApp)
 
-    jdAuthApp = EmbeddedDocumentField(verbose_name = '管理APP', document_type = JDAuthApp, default = None)
-
-    # 小程序实际上由前台绑定了, 设计需要改变
-    # 小程序只能在主AGENT配置, 其他代理商不能配置任何资金池
-    customizedWechatMiniAllowable = BooleanField(verbose_name = '自定义MINI', default = False)
-    wechatMiniApp = EmbeddedDocumentField(verbose_name = u'微信小程序APP', document_type = WechatMiniApp, default = None)
-    payAppWechatMini = LazyReferenceField(document_type = JDAggrePayApp, default = None)
-
-    # payAppMini = GenericLazyReferenceField(choices = CAPITAL_PAY_APP_LIST, verbose_name = u'资金池APP', default = None)
-
     # 支付APP配置
     customizedAlipayCashflowAllowable = BooleanField(verbose_name = '是否开启自主支付宝收款权限', default = False)
     payAppAli = LazyReferenceField(document_type = AliApp, default = None)
@@ -186,11 +172,6 @@ class Agent(CapitalUser):
     customizedWechatCashflowAllowable = BooleanField(verbose_name = '是否开启微信自主收款权限', default = False)
     payAppWechat = LazyReferenceField(document_type = WechatPayApp, verbose_name = '微信支付APP(用于提现和支付)', default = None)
 
-    customizedJDAggreAllowable = BooleanField(verbose_name = '是否使用京东聚合支付', default = False)
-    payAppJDAggre = LazyReferenceField(document_type = JDAggrePayApp, verbose_name = '京东聚合支付(仅用于支付)', default = None)
-
-    bankcards = ListField(ReferenceField(BankCard), default = [])
-
     featureToggles = DictField(verbose_name = '特性开关', default = {})
 
     # 厂商给代理商的流量卡成本价. 提交经销商的时候,经销商的默认价格从这个继承,代理商也可以更改
@@ -239,9 +220,8 @@ class Agent(CapitalUser):
 
     dealerBankWithdrawFee = BooleanField(verbose_name = u'经销商提现到银行卡的手续费,计算方式', default = False)
 
-    bankWithdrawFee = BooleanField(verbose_name = u"银行卡提现手续开关(资金池代理商开关和这个开关为双开关)", default = True)
-
-    ledgerAppJDAggre = LazyReferenceField(document_type=JDAggrePayApp, verbose_name=u'京东聚合支付(仅用于设备收益分账)', default=None)
+    # 以下字段不在支持
+    bankcards = ListField(ReferenceField(BankCard), default = [])
 
     meta = {
         'indexes': [
@@ -317,29 +297,6 @@ class Agent(CapitalUser):
         my_app.occupant = self
         return my_app
 
-    @property
-    def my_jd_aggre_pay_app(self):
-        # type: ()->JDAggrePayApp
-        if self.customizedJDAggreAllowable:
-            if not self.payAppJDAggre:
-                raise Exception(u'第三方支付配置错误(1001)')
-            else:
-                my_app = self.payAppJDAggre.fetch()  # type: JDAggrePayApp
-                if not my_app.valid:
-                    raise Exception(u'第三方支付配置错误(1002)')
-                else:
-                    if (str(self.id) == settings.MY_PRIMARY_AGENT_ID) and (not my_app.inhouse):
-                        raise Exception(u'第三方支付配置错误(1003)')
-
-                    my_app.occupantId = str(self.id)
-                    my_app.occupant = self
-                    return my_app
-
-        my_app = JDAggrePayApp.get_null_app()
-        my_app.occupantId = str(self.id)
-        my_app.occupant = self
-        return my_app
-
     def to_dict(self, shadow = False):
         rv = super(Agent, self).to_dict()
 
@@ -629,12 +586,13 @@ class Agent(CapitalUser):
         return getattr(self, '__manager__')
 
     @property
-    def primary_agent(self):    # type:() -> Agent
-        primeAgent = Agent.objects(id=self.primary_agent_id).first()
-        if not primeAgent:
-            raise NoAgentFound(agent_id=self.primary_agent_id)
+    def primary_agent(self):  # type:() -> Agent
+        if not hasattr(self, '__my_prime_agent__'):
+            primeAgent = Agent.objects(id = self.primary_agent_id).first()
+            if not primeAgent:
+                raise NoAgentFound(agent_id = self.primary_agent_id)
 
-        setattr(self, '__my_prime_agent__', primeAgent)
+            setattr(self, '__my_prime_agent__', primeAgent)
 
         return getattr(self, '__my_prime_agent__')
 
@@ -878,6 +836,37 @@ class Agent(CapitalUser):
         if not my_app.enable:
             raise Exception(u'系统配置错误(三方支付)')
 
+    @property
+    def my_wechat_withdraw_app(self):
+        my_app = None
+
+        if self.withdrawApps:
+            source_key = self.withdraw_source_key()
+
+            if source_key in self.withdrawApps:
+                my_app = self.withdrawApps[source_key].wechat_app
+
+        if not my_app:
+            my_app = self.my_wechat_pay_app
+
+        if (not my_app.valid) or (not my_app.enable):
+            raise Exception(u'配置错误(第三方支付)')
+
+        my_app.occupant = self
+        my_app.occupantId = str(self.id)
+        return my_app
+
+    @property
+    def wechat_withdraw_app(self):
+        if not self.customizedCashflowAllowable:
+            primary_agent = self.primary_agent
+            if self.is_equal(primary_agent):
+                return self.inhouse_prime_agent.wechat_withdraw_app
+            else:
+                return primary_agent.wechat_withdraw_app
+
+        return self.my_wechat_withdraw_app
+
     def withdraw_source_key(self, pay_app = None):
         # type:(PayAppBase)->str
 
@@ -984,9 +973,10 @@ class Agent(CapitalUser):
     def withdraw_gateway_list(cls, source_key):
         is_ledger, pay_app_type, occupant_id, tokens = cls._parse_source_key(source_key)
         if not is_ledger:
-            return {
+            return is_ledger, {
                 'alipay': WithdrawGateway(AliApp.get_null_app(), False),
-                'wechat': WithdrawGateway(WechatPayApp.get_null_app(), False)
+                'wechat': WithdrawGateway(WechatPayApp.get_null_app(), False),
+                'wechatV3': WithdrawGateway(WechatPayApp.get_null_app(), False)
             }
 
         if pay_app_type != PayAppType.WECHAT:
@@ -997,9 +987,11 @@ class Agent(CapitalUser):
             raise UserServerException(u'系统配置错误(第三方支付),请联系平台客服(1002)')
 
         if not agent.withdrawApps:
-            return {
+            return is_ledger, {
                 'alipay': None,
-                'wechat': agent.my_wechat_pay_app.new_withdraw_gateway(is_ledger = is_ledger)}
+                'wechat': agent.my_wechat_pay_app.new_withdraw_gateway(is_ledger = is_ledger, gateway_version = 'v1'),
+                'wechatV3': agent.my_wechat_pay_app.new_withdraw_gateway(is_ledger = is_ledger, gateway_version = 'v3')
+            }
         else:
             if source_key not in agent.withdrawApps:
                 raise UserServerException(u'系统配置错误(第三方支付),请联系平台客服(1003)')
@@ -1012,9 +1004,12 @@ class Agent(CapitalUser):
             withdraw_entity.wechat_app.occupantId = str(agent.id)
             withdraw_entity.wechat_app.occupant = agent
 
-            return {
+            return is_ledger, {
                 'alipay': withdraw_entity.alipay_app.new_withdraw_gateway(is_ledger = is_ledger),
-                'wechat': withdraw_entity.wechat_app.new_withdraw_gateway(is_ledger = is_ledger)
+                'wechat': withdraw_entity.wechat_app.new_withdraw_gateway(
+                    is_ledger = is_ledger, gateway_version = 'v1'),
+                'wechatV3': withdraw_entity.wechat_app.new_withdraw_gateway(
+                    is_ledger = is_ledger, gateway_version = 'v3')
             }
 
     @staticmethod
@@ -1414,7 +1409,7 @@ class Agent(CapitalUser):
 
     def new_withdraw_record(self, withdraw_gateway, pay_entity, source_key, income_type, amount, pay_type, manual,
                             recurrent):
-        # type: (WithdrawGateway, BankCard, str, str, RMB, str, bool, bool) -> WithdrawRecord
+        # type: (WithdrawGateway, WithdrawBankCard, str, str, RMB, str, bool, bool) -> WithdrawRecord
 
         if income_type == AGENT_INCOME_TYPE.DEALER_WITHDRAW_FEE:
             withdraw_fee_ratio = Permillage('0.00')  # type: Permillage
@@ -1452,19 +1447,6 @@ class Agent(CapitalUser):
                                      manual = manual,
                                      recurrent = recurrent)
 
-    def withdraw_bank_card(self, bank_card_no = None):
-        # type: (str)->BankCard
-
-        bank_card = None
-
-        if not bank_card_no:
-            if len(self.bankcards) > 0:
-                bank_card = self.bankcards[0]
-        else:
-            bank_card = BankCard.objects(cardNo = bank_card_no).first()
-
-        return bank_card
-
     def new_withdraw_handler(self, record):
         # type: (WithdrawRecord) -> WithdrawHandler
         from apps.web.agent.withdraw import AgentWithdrawHandler
@@ -1563,17 +1545,6 @@ class Agent(CapitalUser):
 
         return self.ledgerAppJDAggre.fetch()
 
-    def get_merchant_split_id(self):
-        """
-        获取商户号绑定的email
-        :return:
-        """
-        app = self.myLedgerMerchantApp  # type: PayAppBase
-        if not app:
-            return None
-        else:
-            return app.split_id
-
     @property
     def deviceIncomeShow(self):
         """

+ 0 - 7
apps/web/agent/urls.py

@@ -104,14 +104,7 @@ urlpatterns = patterns(
     # 查询钱包
     url(r'^walletData$', walletData, name = 'walletData'),
 
-    # 查询银行卡信息
-    url(r'^getWalletBank$', getWalletBank, name = 'getWalletBank'),
 
-    # 保存银行卡信息
-    url(r'^saveWalletBank$', saveWalletBank, name = 'saveWalletBank'),
-
-    # 解绑银行卡
-    url(r'^bankCardUnbind$', bankCardUnbind, name = 'bankCardUnbind'),
 
     # 代理商获取提现短信验证码
     url(r'^getWithdrawCode$', getWithdrawCode, name = 'getWithdrawCode'),

+ 12 - 103
apps/web/agent/views.py

@@ -17,6 +17,7 @@ from django.core.handlers.wsgi import WSGIRequest
 from mongoengine.errors import DoesNotExist, NotUniqueError
 from operator import itemgetter
 from typing import Optional, cast
+from voluptuous import MultipleInvalid
 
 from apilib.monetary import RMB, Percent, Ratio
 from apilib.utils_datetime import to_datetime
@@ -27,7 +28,7 @@ from apps.thirdparties.aliyun import AliyunSlider
 from apps.web.agent.define import AGENT_INCOME_SOURCE, AGENT_INCOME_TYPE
 from apps.web.agent.models import Agent, AgentIncomeReport
 from apps.web.agent.withdraw import AgentWithdrawService, check_record_when_revoke_or_approve
-from apps.web.common.models import WithdrawRecord, Banks
+from apps.web.common.models import WithdrawRecord
 from apps.web.common.transaction import WithdrawStatus, WITHDRAW_PAY_TYPE, translate_withdraw_state
 from apps.web.common.validation import NAME_RE, check_phone_number
 from apps.web.constant import Const, DeviceCmdCode, APP_TYPE, ErrorCode
@@ -38,7 +39,6 @@ from apps.web.core.exceptions import ServiceException, InvalidFileSize, InvalidF
 from apps.web.core.file import AliOssFileUploader
 from apps.web.core.helpers import ActionDeviceBuilder
 from apps.web.core.messages.sms import agentRegisterSMSProvider, agentWithdrawSMSProvider
-from apps.web.core.models import BankCard
 from apps.web.core.networking import MessageSender
 from apps.web.core.payment import WithdrawGateway
 from apps.web.core.sysparas import SysParas
@@ -1231,7 +1231,6 @@ def walletData(request):
     inhouse_source_key = Agent.get_inhouse_prime_agent().current_wallet_withdraw_source_key
 
     payload = {
-        'balance': agent.total_balance
     }
 
     withdraw_income_list = []
@@ -1276,8 +1275,6 @@ def walletData(request):
             AGENT_INCOME_TYPE.DEALER_DEVICE_FEE: device_income_list
         })
 
-
-
     insurance_income_list = list()
     for source_key, balance in agent.insuranceBalance.iteritems():
         if WithdrawGateway.is_ledger(source_key) and balance.balance != RMB(0):
@@ -1295,81 +1292,6 @@ def walletData(request):
     return JsonResponse({'result': 1, 'description': None, 'payload': payload})
 
 
-@error_tolerate(nil = DefaultJsonErrorResponse)
-@permission_required(ROLE.agent)
-def getWalletBank(request):
-    # type: (WSGIRequest)->JsonResponse
-
-    agentId = str(request.user.id)
-    agent = Agent.objects(id = agentId).first()
-
-    if not agent:
-        return JsonResponse({"result": 0, "description": u"没有找到代理商", 'payload': {}})
-    if len(agent.bankcards) == 0:
-        return JsonResponse({"result": 0, "description": u"没有找到银行卡,请您先添加银行卡", 'payload': {}})
-
-    # 暂定只支持一张银行卡
-    curCard = agent.bankcards[0]
-    data = {
-        'id': str(curCard.id),
-        'endNum': curCard.cardNo[-4::],
-        'bankName': curCard.bankName,
-        'bankType': curCard.cardType
-    }
-
-    return JsonResponse({"result": 1, "description": None, 'payload': {'dataList': [data]}})
-
-
-@error_tolerate(nil = DefaultJsonErrorResponse)
-@permission_required(ROLE.agent)
-def saveWalletBank(request):
-    # type: (WSGIRequest)->JsonResponse
-
-    agentId = str(request.user.id)
-    agent = Agent.objects(id = agentId).first()
-    if not agent:
-        return JsonResponse({"result": 0, "description": u"没有找到代理商", 'payload': {}})
-
-    bankcard = BankCard()
-    bankcard.cardType = request.POST.get('cardType', '')
-    bankcard.cardNo = request.POST.get('bankId', '')
-    bankcard.holderName = request.POST.get('name', '')
-    bankcard.branchName = request.POST.get('subBankName', '')
-    bankcard.bankName = request.POST.get('bankName', '')
-
-    wechat_bank_code = Banks.get_wechat_bank_code(bankcard.bankName)
-    if not wechat_bank_code:
-        return JsonResponse({"result": 0, "description": u"不支持该银行绑定,请查看支持的银行", 'payload': {}})
-
-    bankcard.code = wechat_bank_code
-    bankcard.save()
-
-    agent.bankcards.append(bankcard)
-    agent.save()
-    return JsonResponse({"result": 1, "description": None, 'payload': {}})
-
-
-@permission_required(ROLE.agent)
-def bankCardUnbind(request):
-    # type: (WSGIRequest)->JsonResponse
-
-    agentId = str(request.user.id)
-    agent = Agent.objects(id = agentId).first()
-    if not agent:
-        return JsonResponse({"result": 0, "description": u"没有找到代理商", 'payload': {}})
-    targetCardId = request.POST.get('id', '')
-
-    try:
-        BankCard.get_collection().remove({'_id': ObjectId(targetCardId)})
-        agent.bankcards = []
-        agent.save()
-    except Exception, e:
-        logger.exception(e)
-        return JsonResponse({"result": 0, "description": u"网络错误", 'payload': {}})
-
-    return JsonResponse({"result": 1})
-
-
 @permission_required(ROLE.agent)
 def getWithdrawCode(request):
     # type: (WSGIRequest)->JsonResponse
@@ -1409,7 +1331,8 @@ def agentWithdraw(request):
     amount = RMB(payload.get('amount', 0.0))
     pay_type = payload.get('payType')
 
-    assert pay_type in (WITHDRAW_PAY_TYPE.WECHAT, WITHDRAW_PAY_TYPE.BANK), 'not support this pay type'
+    assert pay_type in (
+        WITHDRAW_PAY_TYPE.WECHAT, WITHDRAW_PAY_TYPE.BANK, WITHDRAW_PAY_TYPE.ALIPAY), 'not support this pay type'
     assert amount > RMB(0), 'amount must be bigger than zero'
 
     status, msg = request.user.withdraw_sms_provider.verify(phoneNumber = request.user.withdraw_sms_phone_number,
@@ -1498,17 +1421,6 @@ def getWalletWithdrawInfo(request):
 
     agent = Agent.objects(id = str(request.user.id)).first()
 
-    bankcards = {'accountCode': '', 'subBankName': '', 'merchantName': '', 'parentBankName': ''}
-    if len(agent.bankcards) > 0:
-        bankcard = BankCard.objects(id = str(agent.bankcards[0].id)).first()
-        if bankcard:
-            bankcards = {
-                'accountCode': bankcard.cardNo,
-                'subBankName': bankcard.branchName,
-                'merchantName': bankcard.holderName,
-                'parentBankName': bankcard.bankName
-            }
-
     phone = str(request.user.username)
 
     result = {
@@ -1516,19 +1428,13 @@ def getWalletWithdrawInfo(request):
         "description": None,
         'payload': {
             'payOpenId': 'placeholder',
-            'bankAccount': bankcards['accountCode'],
-            'subBank': bankcards['subBankName'],
             'phone': phone,
             'balance': agent.sub_balance(income_type, source_key),
-            'cardHolder': bankcards['merchantName'],
-            'bankName': bankcards['parentBankName'],
-            'withdrawFeeRatio': Const.PLATFORM_DEFAULT_WITHDRAW_FEE_RATIO
+            'withdrawFeeRatio': Const.PLATFORM_DEFAULT_WITHDRAW_FEE_RATIO,
+            'support': agent.withdraw_support(source_key)
         }
     }
 
-    dealerBankWithdrawFee = Agent.withdraw_gateway_list(source_key)['wechat'].occupant.dealerBankWithdrawFee
-    result['payload']['payBankTransFee'] = dealerBankWithdrawFee and agent.bankWithdrawFee
-
     return JsonResponse(result)
 
 
@@ -1839,10 +1745,13 @@ def withdrawEntry(request):
     if source_key not in user.balance_dict(source_type):
         return ErrorResponseRedirect(error = u'提现参数错误,请刷新后重试')
 
-    withdraw_gateway_list = Agent.withdraw_gateway_list(source_key)
+    is_ledger, withdraw_gateway_list = Agent.withdraw_gateway_list(source_key)
+    if not is_ledger:
+        return ErrorResponseRedirect(error = u'系统配置错误,请联系平台客服(10005)')
+
     wechat_withdraw_gateway = withdraw_gateway_list['wechat']
     if not wechat_withdraw_gateway:
-        return ErrorResponseRedirect(error = u'系统配置错误,请联系平台客服(10005)')
+        return ErrorResponseRedirect(error = u'系统配置错误,请联系平台客服(10006)')
 
     code = request.GET.get('code', None)
     if not code:
@@ -2244,7 +2153,7 @@ def setAgentProxyServicePhone(request):
 @permission_required(ROLE.agent)
 def setBankWithdrawFee(request):
     """
-    设置经销商是否启用临时套餐
+    设置经销商提现银行卡转账手续费
     :param request:
     :return:
     """

+ 403 - 17
apps/web/common/models.py

@@ -19,6 +19,7 @@ from apilib.utils_json import json_dumps, json_loads
 from apilib.utils_sys import memcache_lock, ThreadLock
 from apps.web import district
 from apps.web.agent.define import AGENT_INCOME_TYPE
+
 from apps.web.common.transaction import WithdrawStatus, WITHDRAW_PAY_TYPE, WithdrawHandler, OrderNoMaker, OrderMainType
 from apps.web.constant import Const
 from apps.web.core import APP_KEY_DELIMITER, PayAppType, ROLE
@@ -246,9 +247,10 @@ class Banks(DynamicDocument):
         'PC': "预付费卡"
     }
 
-    bankName = StringField(verbose_name = u'银行名称', unique = True, null = False)
-    bankName2 = StringField(verbose_name = u'银行名称2')
     bankCode = StringField(verbose_name = u'银行code', unique = True, null = False)
+
+    bankName = StringField(verbose_name = u'银行名称', unique = True, null = False)
+
     sn = IntField(verbose_name = u'排序序号', default = 0)
     wechatBankCode = StringField(verbose_name = u'微信银行ID', default = '')
     patterns = ListField(verbose_name = u'匹配正则', default = [])
@@ -262,7 +264,6 @@ class Banks(DynamicDocument):
         return {
             'bankCode': self.bankCode,
             'bankName': self.bankName,
-            'bankName2': self.bankName2,
             'wechatBankCode': self.wechatBankCode,
             'patterns': self.patterns
         }
@@ -674,7 +675,15 @@ class CapitalUser(UserSearchable):
         'autoWithdrawType': "wechat",
         'autoWithdrawMin': RMB(settings.WITHDRAW_MINIMUM).mongo_amount,
         'autoWithdrawStrategy': {'type': 'asWeek', 'value': 3},
-        'autoWithdrawBankFee': True
+        'autoWithdrawBankFee': True,
+
+        'alipay': {
+            'realName': '',
+            'loginId': ''
+        },
+        'wechat': {
+            'realName': ''
+        }
     }
 
     ongoingWithdrawList = ListField(field = StringField())
@@ -683,6 +692,8 @@ class CapitalUser(UserSearchable):
 
     withdrawOptions = DictField(verbose_name = u"提现相关选项", default = DEFAULT_WITHDRAW_OPTIONS)
 
+    bankWithdrawFee = BooleanField(verbose_name = u"银行卡提现手续开关(资金池代理商开关和这个开关为双开关)", default = True)
+
     @classmethod
     def income_field_name(cls, income_type):
         # type: (str)->str
@@ -824,10 +835,10 @@ class CapitalUser(UserSearchable):
         result = cls.get_collection().update_one(query, update, upsert=False)  # type: UpdateResult
         return bool(result.modified_count == 1)
 
-    def sub_balance(self, income_type, source_key=None, only_ledger=True):
+    def sub_balance(self, income_type, source_key = None, only_ledger = True):
         # type: (str, str, bool)->RMB
 
-        balance_dict = self.balance_dict(income_type=income_type)  # type: dict
+        balance_dict = self.balance_dict(income_type = income_type)  # type: dict
         if not source_key:
             balance_list = []
             for key, value in balance_dict.iteritems():
@@ -998,8 +1009,9 @@ class CapitalUser(UserSearchable):
         # type: () -> basestring
         raise NotImplementedError()
 
-    def new_withdraw_record(self, withdraw_gateway, pay_entity, source_key, income_type, amount, pay_type, manual, recurrent):
-        # type: (WithdrawGateway, BankCard, str, str, RMB, str, bool, bool) -> WithdrawRecord
+    def new_withdraw_record(self, withdraw_gateway, pay_entity, source_key, income_type, amount, pay_type, manual,
+                            recurrent):
+        # type: (WithdrawGateway, WithdrawBankCard, str, str, RMB, str, bool, bool) -> WithdrawRecord
         raise NotImplementedError()
 
     def new_withdraw_handler(self, record):
@@ -1009,9 +1021,17 @@ class CapitalUser(UserSearchable):
     def get_bound_pay_openid(self, key):
         raise NotImplementedError()
 
-    def withdraw_bank_card(self, bank_card_no = None):
-        # type: (str)->BankCard
-        raise NotImplementedError()
+    def withdraw_bank_card(self, accountCode):
+        # type: (str)->WithdrawBankCard
+        return WithdrawBankCard.objects(ownerId = str(self.id), role = self.role, accountCode = accountCode).first()
+
+    @property
+    def withdraw_bank_cards(self):
+        banks = WithdrawBankCard.objects(ownerId = str(self.id), role = self.role).all()
+        return [{
+            'accountCode': bank.accountCode,
+            'bankName': bank.bankName
+        } for bank in banks]
 
     @property
     def auto_withdraw_min(self):
@@ -1027,7 +1047,7 @@ class CapitalUser(UserSearchable):
 
     @property
     def auto_withdraw_strategy(self):
-        return self.withdrawOptions.get('autoWithdrawStrategy')
+        return self.withdrawOptions.get('autoWithdrawStrategy', {})
 
     @property
     def auto_withdraw_bank_fee(self):
@@ -1041,6 +1061,26 @@ class CapitalUser(UserSearchable):
     def withdraw_open_id(self, open_id):
         setattr(self, '__withdraw_openid__', open_id)
 
+    @property
+    def withdraw_alipay_config(self):
+        return self.withdrawOptions.get('alipay', {})
+
+    @property
+    def withdraw_alipay_login_id(self):
+        return self.withdraw_alipay_config.get('loginId', '')
+
+    @property
+    def withdraw_alipay_real_name(self):
+        return self.withdraw_alipay_config.get('realName', '')
+
+    @property
+    def withdraw_wechat_config(self):
+        return self.withdrawOptions.get('wechat', {})
+
+    @property
+    def withdraw_wechat_real_name(self):
+        return self.withdraw_wechat_config.get('realName', '') or self.nickname
+
     @property
     def auto_withdraw_bound_open_id(self):
         raise NotImplementedError()
@@ -1055,6 +1095,52 @@ class CapitalUser(UserSearchable):
         from apps.web.helpers import AgentCustomizedBrandVisitor
         return AgentCustomizedBrandVisitor(factory_type = 'withdraw_source_key', pay_app = None).visit(self)
 
+    def withdraw_support(self, source_key):
+        from apps.web.agent.models import Agent
+
+        is_ledger, withdraw_gateway_list = Agent.withdraw_gateway_list(source_key)
+
+        if not is_ledger:
+            return {
+                'wechat': {
+                    'support': False,
+
+                },
+                'alipay': {
+                    'support': False,
+
+                },
+                'bank': {
+                    'support': False,
+                }
+            }
+
+        dealerBankWithdrawFee = withdraw_gateway_list['wechat'].occupant.dealerBankWithdrawFee
+
+        rv = {
+            'wechat': {
+                'support': True if withdraw_gateway_list['wechat'] else False,
+                'realName': self.withdraw_wechat_real_name
+            },
+            'alipay': {
+                'support': True if withdraw_gateway_list['alipay'] else False,
+                'realName': self.withdraw_alipay_real_name,
+                'loginId': self.withdraw_alipay_login_id
+            },
+            'bank': {
+                'support': True,
+                'cards': self.withdraw_bank_cards,
+                'transFee': dealerBankWithdrawFee and self.bankWithdrawFee
+            }
+        }
+
+        return rv
+
+    def set_withdraw_alipay(self, loginId, realName):
+        self.update(withdrawOptions__alipay = {'loginId': loginId, 'realName': realName})
+
+    def set_withdraw_wechat(self, realName):
+        self.update(withdrawOptions__wechat = {'realName': realName})
 
 class WithdrawRefundRecord(Searchable):
     withdraw_record_id = ObjectIdField(unique = True)
@@ -1161,9 +1247,10 @@ class WithdrawRecord(Searchable):
     @classmethod
     def create(cls, withdraw_user, withdraw_gateway, pay_entity, source_key, income_type, pay_type, fund_map,
                manual, recurrent, **kwargs):
-        # type: (CapitalUser, WithdrawGateway, BankCard, str, str, str, dict, bool, bool, dict) -> WithdrawRecord
+        # type: (CapitalUser, WithdrawGateway, WithdrawBankCard, str, str, str, dict, bool, bool, dict) -> WithdrawRecord
 
-        assert pay_type in (WITHDRAW_PAY_TYPE.WECHAT, WITHDRAW_PAY_TYPE.BANK), 'not support this pay type'
+        assert pay_type in (
+            WITHDRAW_PAY_TYPE.WECHAT, WITHDRAW_PAY_TYPE.BANK, WITHDRAW_PAY_TYPE.ALIPAY), 'not support this pay type'
 
         if manual:
             assert pay_type == WITHDRAW_PAY_TYPE.BANK, 'pay type must be bank in manual'
@@ -1190,10 +1277,10 @@ class WithdrawRecord(Searchable):
             'actualPay': fund_map.get('actualPay'),
             'bankTransFee': fund_map.get('bankTransFee'),
 
-            'accountCode': pay_entity.cardNo,
-            'cardUserName': pay_entity.holderName,
+            'accountCode': pay_entity.accountCode,
+            'cardUserName': pay_entity.accountName,
             'parentBankName': pay_entity.bankName,
-            'subBankName': pay_entity.branchName,
+            'subBankName': pay_entity.branchBankName,
 
             'partition': fund_map.get('partition'),
 
@@ -1232,6 +1319,13 @@ class WithdrawRecord(Searchable):
             'status': WithdrawStatus.SUCCEEDED
         }
 
+        if kwargs and 'extra' in kwargs:
+            extra = kwargs.pop('extra')
+            for key, value in extra.iteritems():
+                payload.update({
+                    'extra.{}'.format(key): value
+                })
+
         if kwargs:
             payload.update(kwargs)
 
@@ -1836,3 +1930,295 @@ class OrderRecordBase(Searchable):
             'address': self.address,
             'groupId': self.groupId
         }
+
+
+class WithdrawBanks(BaseDocument):
+    CARD_TYPE_MAP = {
+        'DC': "借记卡",
+        'CC': "信用卡",
+        'SCC': "准贷记卡",
+        'PC': "预付费卡"
+    }
+
+    code = StringField(verbose_name = u'编号', unique = True, null = False)
+    name = StringField(verbose_name = u'名称', null = False)
+
+    bankAbbrevCode = StringField(verbose_name = u'银行编码')
+
+    wechatBankCode = StringField(verbose_name = u'微信银行ID', default = '')
+
+    meta = {'collection': 'withdraw_banks', 'db_alias': 'default'}
+
+    buffer = None
+
+    buffer_lock = ThreadLock()
+
+    def to_dict(self):
+        return {
+            'code': self.code,
+            'name': self.name,
+            'wechatBankCode': self.wechatBankCode,
+            'bankAbbrevCode': self.bankAbbrevCode
+        }
+
+    @classmethod
+    def get_wechat_bank_code(cls, bankName):
+        cls.__load_in_mem()
+
+        if bankName in cls.buffer['wechat']:
+            return cls.buffer['wechat'][bankName].get('wechatBankCode', '')
+        else:
+            return ''
+
+    @classmethod
+    def get_wechat_support_banks(cls, keyWord):
+        cls.__load_in_mem()
+
+        rv = []
+
+        for item in cls.buffer['wechat'].values():
+            if keyWord:
+                if keyWord in item['name']:
+                    rv.append({
+                        'code': item['code'], 'name': item['name']
+                    })
+            else:
+                rv.append({
+                    'code': item['code'], 'name': item['name']
+                })
+
+        return rv
+
+    @classmethod
+    def get_by_bank_abbrev_code(cls, bankAbbrevCode):
+        cls.__load_in_mem()
+
+        if bankAbbrevCode in cls.buffer['bankAbbrevCode']:
+            bank_info = cls.buffer['bankAbbrevCode'][bankAbbrevCode]
+            return {
+                'bankName': bank_info['name']
+            }
+        else:
+            return None
+
+    @classmethod
+    def get_all_banks(cls, keyWord):
+        cls.__load_in_mem()
+
+        rv = []
+
+        for item in cls.buffer['all'].values():
+            if keyWord:
+                if keyWord in item['name']:
+                    rv.append({
+                        'code': item['code'], 'name': item['name']
+                    })
+            else:
+                rv.append({
+                    'code': item['code'], 'name': item['name']
+                })
+
+        return rv
+
+    @classmethod
+    def support(cls, bankName):
+        cls.__load_in_mem()
+        return bankName in cls.buffer['all']
+
+    @classmethod
+    def get_collection(cls):
+        return cls._get_collection()
+
+    @classmethod
+    def __acquire_lock(cls):
+        cls.buffer_lock.acquire_lock()
+
+    @classmethod
+    def __release_lock(cls):
+        cls.buffer_lock.release_lock()
+
+    def reset_buffer(self):
+        try:
+            self.__acquire_lock()
+            self.buffer = None
+        finally:
+            self.__release_lock()
+
+    @classmethod
+    def __load_in_mem(cls):
+        if cls.buffer:
+            return
+
+        try:
+            cls.__acquire_lock()
+
+            if cls.buffer:
+                return
+
+            cls.buffer = {
+                'all': {},
+                'wechat': {},
+                'bankAbbrevCode': {}
+            }
+
+            banks = cls.objects().all()
+            for bank in banks:
+                payload = bank.to_dict()
+
+                cls.buffer['all'][bank.name] = payload
+
+                if bank.wechatBankCode:
+                    cls.buffer['wechat'][bank.name] = payload
+
+                if bank.bankAbbrevCode:
+                    cls.buffer['bankAbbrevCode'][bank.bankAbbrevCode] = payload
+        finally:
+            cls.__release_lock()
+
+
+class WithdrawDistrict(BaseDocument):
+    code = StringField(verbose_name = u'编号', unique = True, null = False)
+    name = StringField(verbose_name = u'名称', null = False)
+    parent = StringField(verbose_name = u'父级编号', null = False)
+
+    meta = {
+        "collection": "withdraw_district",
+        "db_alias": "default"
+    }
+
+    @classmethod
+    def get_area(cls, province = None):
+        parent = '0000' if not province else province
+        return [
+            {
+                'code': item.code,
+                'name': item.name
+
+            } for item in cls.objects(parent = parent).order_by('-code')
+        ]
+
+
+class WithdrawBranchBanks(Searchable):
+    code = StringField(verbose_name = u'编号', unique = True, null = False)
+    name = StringField(verbose_name = u'名称', null = False)
+    provinceCode = StringField(verbose_name = u'省编号', null = False)
+    cityCode = StringField(verbose_name = u'城市编号', null = False)
+    bankCode = StringField(verbose_name = u'银行编号', null = False)
+
+    cnapsCode = StringField(verbose_name = u'联行号')
+
+    search_fields = ('name',)
+
+    meta = {
+        "collection": "withdraw_branch_banks",
+        "db_alias": "default"
+    }
+
+
+class WithdrawBankCard(DynamicDocument):
+    """
+    提现银行卡信息
+    """
+
+    class AccountType(object):
+        PERSONAL = 'personal'
+        PUBLIC = 'public'
+
+    ownerId = StringField(verbose_name = u"提现者ID", null = False)
+    role = StringField(verbose_name = u'提现角色', null = False)
+
+    accountCode = StringField(verbose_name = u"银行卡账号", null = False)
+    accountName = StringField(verbose_name = u'持卡人姓名')
+
+    accountType = StringField(verbose_name = u'卡类型(个人账号,对公账号)', default = AccountType.PERSONAL)
+
+    bankCode = StringField(verbose_name = u'银行ID', default = '')
+    bankName = StringField(verbose_name = u"银行名称", default = "")
+
+    phone = StringField(verbose_name = u'银行预留电话', default = "")
+
+    # 对公提现需要提供省市支行名称或者联行号
+    province = StringField(verbose_name = u"省名称", default = None)
+    provinceCode = StringField(verbose_name = u"省编码", default = None)
+
+    city = StringField(verbose_name = u"市名称", default = None)
+    cityCode = StringField(verbose_name = u"市编码", default = None)
+
+    branchBankName = StringField(verbose_name = u"银行支行名称", default = None)
+    branchBankCode = StringField(verbose_name = u"银行支行编码", default = None)
+
+    cnapsCode = StringField(verbose_name = u"联行号", default = None)
+
+    dateTimeAdded = DateTimeField(default = datetime.datetime.now, verbose_name = u'添加进来的时间')
+
+    manual = BooleanField(verbose_name = u"该卡是否仅手动提现", default = False)
+
+    meta = {"collection": "withdraw_bank_card", "db_alias": "default"}
+
+    @classmethod
+    def get_collection(cls):
+        return cls._get_collection()
+
+    def to_dict(self):
+        if self.accountType == self.AccountType.PERSONAL:
+            return {
+                'id': str(self.id),
+                'accountCode': self.accountCode,
+                'accountName': self.accountName,
+                'accountType': self.accountType,
+                'bankName': self.bankName,
+                'bankCode': self.bankCode,
+                'phone': self.phone,
+                'isPublic': False
+            }
+        else:
+            return {
+                'id': str(self.id),
+                'accountCode': self.accountCode,
+                'accountName': self.accountName,
+                'accountType': self.accountType,
+                'bankName': self.bankName,
+                'bankCode': self.bankCode,
+                'phone': self.phone,
+                'province': self.province,
+                'provinceCode': self.provinceCode,
+                'city': self.city,
+                'cityCode': self.cityCode,
+                'branchBankName': self.branchBankName,
+                'branchBankCode': self.branchBankCode,
+                'cnapsCode': self.cnapsCode,
+                'isPublic': True
+            }
+
+    @classmethod
+    def new_personal_withdraw_bank_card(
+            cls, ownerId, role, accountName, accountCode, bankName, phone, id = None):
+        if id:
+            return cls.objects(id = id).upsert_one(
+                ownerId = ownerId, role = role, bankName = bankName,
+                accountType = cls.AccountType.PERSONAL, accountName = accountName,
+                accountCode = accountCode, phone = phone)
+        else:
+            return cls.objects(ownerId = ownerId, role = role, accountCode = accountCode).upsert_one(
+                ownerId = ownerId, role = role, bankName = bankName,
+                accountType = cls.AccountType.PERSONAL, accountName = accountName,
+                accountCode = accountCode, phone = phone)
+
+    @classmethod
+    def new_public_withdraw_bank_card(cls, ownerId, role, accountName, accountCode, bankCode, bankName,
+                                      provinceCode, province, cityCode, city, branchBankCode, branchBankName,
+                                      cnapsCode, phone, id = None):
+        if id:
+            return cls.objects(id = id).upsert_one(
+                ownerId = ownerId, role = role, bankCode = bankCode, bankName = bankName,
+                accountType = cls.AccountType.PUBLIC, accountName = accountName, accountCode = accountCode,
+                phone = phone, provinceCode = provinceCode, province = province,
+                cityCode = cityCode, city = city, branchBankCode = branchBankCode,
+                branchBankName = branchBankName, cnapsCode = cnapsCode)
+        else:
+            return cls.objects(ownerId = ownerId, role = role, accountCode = accountCode).upsert_one(
+                ownerId = ownerId, role = role, bankCode = bankCode, bankName = bankName,
+                accountType = cls.AccountType.PUBLIC, accountName = accountName, accountCode = accountCode,
+                phone = phone, provinceCode = provinceCode, province = province,
+                cityCode = cityCode, city = city, branchBankCode = branchBankCode,
+                branchBankName = branchBankName, cnapsCode = cnapsCode)

+ 13 - 4
apps/web/common/transaction/__init__.py

@@ -32,6 +32,7 @@ class WithdrawStatus(IntEnum):
 class WITHDRAW_PAY_TYPE(IterConstant):
     BANK = 'bank'
     WECHAT = 'wechat'
+    ALIPAY = 'alipay'
 
 
 WITHDRAW_STATE_TRANSLATION = {
@@ -46,15 +47,17 @@ WITHDRAW_STATE_TRANSLATION = {
 
 
 class WithdrawResult(object):
-    def __init__(self, result, err_code, refund, message, show_message):
+    def __init__(self, result, err_code, refund, message, show_message, callResult = None):
         self.result = result
         self.err_code = err_code
         self.refund = refund
         self.message = message
         self.show_message = show_message
+        self.callResult = callResult
 
     def __repr__(self):
-        return 'WithdrawResult<errCode=%s, message=%s>' % (self.err_code, self.message)
+        return 'WithdrawResult<errCode=%s, message=%s, callResult=%s>' % (
+            self.err_code, self.message, str(self.callResult))
 
 
 class WithdrawHandler(object):
@@ -88,7 +91,8 @@ class WithdrawHandler(object):
                 self.record.incomeType,
                 self.record.amount,
                 self.record.source_key,
-                self.record.order)
+                self.record.order,
+                self.record.is_new_version)
 
             self.on_revoke()
         else:
@@ -106,7 +110,12 @@ class WithdrawHandler(object):
 
         success = self.record.succeed(**kwargs)
         if success:
-            self.payee.clear_frozen_balance(self.record.order)
+            self.payee.clear_frozen_balance(
+                self.record.incomeType,
+                self.record.amount,
+                self.record.source_key,
+                self.record.order,
+                self.record.is_new_version)
 
             self.on_approve()
         else:

+ 1 - 1
apps/web/common/transaction/pay/__init__.py

@@ -93,7 +93,7 @@ class PayRecordPoller(object):
         raise NotImplementedError('sub class must implement update_record')
 
     def action_of_pay(self, payment_gateway, record):
-        # type: (PaymentGateway, RechargeRecordT)->(PayNofifyAction, str)
+        # type: (PaymentGateway, RechargeRecordT)->(PayNotifyAction, str)
         raise NotImplementedError('sub class must implement action_of_pay')
 
     def start(self):

+ 0 - 226
apps/web/common/transaction/pay/dlb.py

@@ -1,226 +0,0 @@
-# -*- coding: utf-8 -*-
-# !/usr/bin/env python
-import datetime
-import logging
-import traceback
-
-from django.conf import settings
-from django.http import QueryDict, HttpResponse
-from typing import TYPE_CHECKING, Dict, Optional
-
-from apilib.monetary import RMB
-from apilib.utils_json import JsonResponse
-from apps.web.common.transaction.pay import PayNotifyAction, PayRecordPoller, OrderCacheMgr, PayNotifier, PayPullUp
-from apps.web.constant import PollRecordDefine
-from apps.web.core.utils import async_operation
-from apps.web.exceptions import UserServerException
-from library.dlb import DlbPayException, DlbErrorCode, DlbValidationError
-from apps.web.core.payment import PaymentGateway
-from taskmanager.mediator import task_caller
-
-if TYPE_CHECKING:
-    from apps.web.common.transaction.pay import RechargeRecordT
-    from typing import Dict
-    from apps.web.core.payment import PaymentGatewayT
-    from apps.web.core.payment.dlb import DlbPaymentGateway
-
-logger = logging.getLogger(__name__)
-
-
-def update_record(record, **payload):
-    # type: (RechargeRecordT, Dict)->bool
-
-    if 'completeTime' in payload:
-        import arrow
-        finished_time = arrow.get(payload['completeTime'], 'YYYY-MM-DD HH:mm:ss', tzinfo = settings.TIME_ZONE).naive
-    else:
-        finished_time = datetime.datetime.now()
-
-    return record.succeed(wxOrderNo = payload['orderNum'],
-                          transactionId = payload['bankOutTradeNum'],
-                          finishedTime = finished_time,
-                          **{
-                              'extraInfo.bankTradeNum': payload['bankTradeNum'],
-                              'extraInfo.notify_time': datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
-                          })
-
-
-class DlbPayReordPoller(PayRecordPoller):
-    def update_record(self, record, **payload):
-        # type: (RechargeRecordT, Dict)->bool
-        return update_record(record, **payload)
-
-    def action_of_pay(self, payment_gateway, record):
-        # type: (DlbPaymentGateway, RechargeRecordT)->(PayNotifyAction, str)
-        try:
-            result = payment_gateway.api_trade_query(out_trade_no = record.orderNo)
-
-            if 'status' not in result:
-                return PayNotifyAction.Unknown, result
-
-            if result['status'] == 'SUCCESS':
-                return PayNotifyAction.NeedHandle, {
-                    'orderNum': result['orderNum'],
-                    'bankTradeNum': result['payRecordList'][0]['bankTradeNum'],
-                    'bankOutTradeNum': result['payRecordList'][0]['bankOutTradeNum']
-                }
-
-            if result['status'] == 'FAIL':
-                return PayNotifyAction.NoHandle, result
-
-            if result['status'] == 'INIT':
-                if len(result['payRecordList']) > 0 and result['payRecordList'][0]['payStatus'] == 'FAIL':
-                    return PayNotifyAction.NoHandle, result
-                else:
-                    return PayNotifyAction.Unknown, result
-
-            if result['status'] in ['REFUND', 'CLOSE', 'CANCLE']:
-                return PayNotifyAction.NoHandle, result
-
-            return PayNotifyAction.Unknown, result
-        except DlbPayException as e:
-            logger.error(str(e))
-            return PayNotifyAction.Unknown, {}
-        except Exception, e:
-            logger.exception(e)
-            return PayNotifyAction.Unknown, {}
-
-class DlbPayNotifier(PayNotifier):
-    def __init__(self, request, record_cls_factory):
-        self.timestamp = request.META.get('HTTP_TIMESTAMP')
-        self.token = request.META.get('HTTP_TOKEN')
-        super(DlbPayNotifier, self).__init__(request = request, record_cls_factory = record_cls_factory)
-
-    def parse_request(self, request):
-        return request.GET  # type: QueryDict
-
-    @property
-    def out_trade_no(self):
-        return self.payload['requestNum']
-
-    def verify(self, payment_gateway, record, payload):
-        # type: (PaymentGatewayT, RechargeRecordT, Dict)->None
-        notifier_fen = int(RMB(payload['orderAmount']) * 100)
-        if record.fen_total_fee != notifier_fen:
-            raise DlbValidationError(
-                errorMsg = u'invalid orderAmount',
-                lvalue = record.fen_total_fee,
-                rvalue = notifier_fen,
-                client = payment_gateway._app)
-
-    def update_record(self, record, **payload):
-        # type: (RechargeRecordT, Dict)->bool
-        return update_record(record, **payload)
-
-    def reply(self):
-        return JsonResponse(data = {})
-
-    def verify_sign(self, gateway, data, token):
-        # type: (DlbPaymentGateway, dict, str)->bool
-        return gateway.check(data, token)
-
-    def do(self, post_pay):
-        # type: (callable)->JsonResponse
-
-        try:
-            payload = self.payload
-
-            logger.debug('received dlb pay notify: %s' % str(payload))
-
-            if payload['status'] != 'SUCCESS':
-                return self.reply()
-
-            order_no = payload['requestNum']
-
-            record = self.record_cls.get_record(order_no = order_no)  # type: Optional[RechargeRecordT]
-            if not record:
-                logger.error('no such record<orderNo={}>'.format(order_no))
-                return self.reply()
-
-            if record.is_success():
-                logger.error(
-                    'record<id={},orderNo={}> has been finished.'.format(str(record.id), order_no))
-                return self.reply()
-
-            payment_gateway = PaymentGateway.from_gateway_key(
-                record.my_gateway,
-                record.pay_gateway_key,
-                record.pay_app_type)  # type: DlbPaymentGateway
-
-            if not self.verify_sign(gateway = payment_gateway,
-                                    data = {
-                                        'timestamp': self.timestamp
-                                    },
-                                    token = self.token):
-                raise DlbPayException(errorCode = DlbErrorCode.MY_ERROR_SIGNATURE,
-                                      errorMsg = u'TOKEN校验错误',
-                                      client = payment_gateway._app)
-
-            self.verify(payment_gateway, record, payload)
-
-            order_cache_mgr = OrderCacheMgr(record)
-            try:
-                if order_cache_mgr.check_and_set_done():
-                    logger.debug('{} has been done because cache in notify.'.format(repr(record)))
-                    return self.reply()
-            except Exception as e:
-                logger.error(
-                    'cache key is not exist or exception. exception = {}, key = {}'.format(str(e), order_cache_mgr.key))
-
-            modified = self.update_record(record, **payload)
-            if not modified:
-                # 如果没有任何记录被更新则说明已经处理了
-                logger.debug('{} has been done because db in notify'.format(repr(record)))
-                return self.reply()
-
-            logger.info('{} successfully confirmed'.format(repr(record)))
-
-            async_operation(post_pay, record = record)
-
-            return self.reply()
-        except DlbPayException as e:
-            logger.error(str(e))
-            return self.reply()
-        except Exception as e:
-            logger.exception(e)
-            return self.reply()
-
-
-class DlbPullUp(PayPullUp):
-    """
-    哆啦宝支付获取支付参数。目前只作为商户支付
-    """
-    def do(self):  # type: ()->HttpResponse
-        OrderCacheMgr(self.record).initial()
-
-        try:
-            pay_url = self.payment_gateway.create_pay_url(
-                order_no = self.record.orderNo,
-                money = self.record.money,
-                attach = {'dealerId': self.record.ownerId},
-                notify_url = self.payload.get('notifyUrl'),
-                front_url = self.payload.get('front_url'))
-        except Exception as e:
-            logger.exception(e)
-            self.record.fail(description = u'拉起支付发生异常', extraInfo__traceback = traceback.format_exc())
-            raise UserServerException(u'拉起支付发生异常')
-        else:
-            if not pay_url:
-                self.record.fail(description = u'调起支付失败,请刷新后重试')
-                raise UserServerException(u'调起支付失败,请刷新后重试')
-            else:
-                task_caller('poll_user_recharge_record',
-                            delay = PollRecordDefine.DELAY_BEFORE,
-                            expires = PollRecordDefine.TASK_EXPIRES,
-                            pay_app_type = self.payment_gateway.pay_app_type,
-                            record_id = str(self.record.id),
-                            interval = PollRecordDefine.WAIT_EACH_ROUND,
-                            total_count = PollRecordDefine.TOTAL_ROUNDS)
-
-                return JsonResponse({
-                    'result': 1,
-                    'description': 'SUCCESS',
-                    'payload': {
-                        'payType': self.payment_gateway.pay_app_type,
-                        'payUrl': pay_url
-                    }})

+ 0 - 337
apps/web/common/transaction/pay/jdaggre.py

@@ -1,337 +0,0 @@
-# -*- coding: utf-8 -*-
-# !/usr/bin/env python
-import datetime
-import logging
-import traceback
-
-import simplejson as json
-from django.conf import settings
-from django.http import HttpResponse
-from typing import TYPE_CHECKING, Dict, Optional
-
-from apilib.utils_json import JsonResponse
-from apilib.utils_url import add_query
-from apps.web.common.transaction.pay import PayNotifier, OrderCacheMgr, PayRecordPoller, PayNotifyAction, PayPullUp
-from apps.web.constant import PollRecordDefine
-
-from apps.web.core.models import JDAggrePayApp
-from apps.web.core.payment import PaymentGateway, WithdrawGateway
-from apps.web.core.utils import async_operation, JsonOkResponse
-from apps.web.exceptions import UserServerException
-
-from library.jd import JDAggrePay
-from library.jd.exceptions import JDPayException, JDCommuException, JDValidationError
-from library.jd.pay import PiType, JDJosPay
-from taskmanager.mediator import task_caller
-
-if TYPE_CHECKING:
-    from apps.web.common.transaction.pay import RechargeRecordT
-    from apps.web.core.payment.jdaggre import JDAggrePaymentGateway
-
-logger = logging.getLogger(__name__)
-
-
-def update_record(record, **payload):
-    # type: (RechargeRecordT, Dict)->bool
-
-    if 'payFinishTime' in payload:
-        import arrow
-        finished_time = arrow.get(payload['payFinishTime'], 'YYYYMMDDHHmmss', tzinfo = settings.TIME_ZONE).naive
-    else:
-        finished_time = datetime.datetime.now()
-
-    wxOrderNo = ''
-    if 'channelNoSeq' in payload:
-        wxOrderNo = payload['channelNoSeq']
-    elif 'transactionId' in payload:
-        wxOrderNo = payload['transactionId']
-    elif 'tradeNo' in payload:
-        wxOrderNo = payload['tradeNo']
-    else:
-        logger.error('no any trade no info. orderNo = {}'.format(record.orderNo))
-
-    return record.succeed(wxOrderNo = wxOrderNo,
-                          transactionId = payload.get('transactionId', ''),
-                          finishedTime = finished_time,
-                          **{
-                              'extraInfo.tradeNo': payload.get('tradeNo', ''),
-                              'extraInfo.notify_time': datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
-                          })
-
-
-class JDAggrePayReordPoller(PayRecordPoller):
-    def update_record(self, record, **payload):
-        # type: (RechargeRecordT, Dict)->bool
-        return update_record(record, **payload)
-
-    def action_of_pay(self, payment_gateway, record):
-        # type: (JDAggrePaymentGateway, RechargeRecordT)->(PayNotifyAction, str)
-        try:
-            result = payment_gateway.api_trade_query(out_trade_no = record.orderNo)
-
-            if 'payStatus' not in result:
-                return PayNotifyAction.Unknown, result
-
-            if result['payStatus'] == 'FINISH':
-                return PayNotifyAction.NeedHandle, result
-
-            if result['payStatus'] in ['CLOSE', 'REFUND']:
-                return PayNotifyAction.NoHandle, result
-
-            return PayNotifyAction.Unknown, result
-        except (JDCommuException, JDPayException) as e:
-            logger.error(str(e))
-            return PayNotifyAction.Unknown, {}
-        except Exception, e:
-            logger.exception(e)
-            return PayNotifyAction.Unknown, {}
-
-
-class JDAggrePayNotifier(PayNotifier):
-    def __init__(self, request, record_cls_factory):
-        self.orginal = json.loads(request.body)  # type: Dict
-        logger.debug('received jdaggre pay notify(encrypt): {}'.format(str(self.orginal)))
-        super(JDAggrePayNotifier, self).__init__(request = request, record_cls_factory = record_cls_factory)
-
-    def parse_request(self, request):
-        app = JDAggrePayApp.objects(merchant_no = self.orginal['merchantNo']).first()  # type: JDAggrePayApp
-        client = JDAggrePay(app.merchant_no, app.desKey, app.saltMd5Key, app.debug)  # type: JDAggrePay
-        return client.decrypt_response(self.orginal)
-
-    @property
-    def out_trade_no(self):
-        return self.payload['outTradeNo']
-
-    def verify(self, payment_gateway, record, payload):
-        # type: (JDAggrePaymentGateway, RechargeRecordT, Dict)->None
-
-        notifier_fen = int(payload['amount'])
-
-        if record.fen_total_fee != notifier_fen:
-            raise JDPayException(
-                errmsg = u'无效的total_fee',
-                lvalue = record.fen_total_fee,
-                rvalue = notifier_fen,
-                client = payment_gateway.client)
-
-    def update_record(self, record, **payload):
-        # type: (RechargeRecordT, Dict)->bool
-        return update_record(record, **payload)
-
-    def reply(self):
-        return HttpResponse('ok')
-
-    def do(self, post_pay):
-        # type: (callable)->JsonResponse
-
-        try:
-            decrypt_data = self.payload
-
-            logger.debug('received jdaggre pay notify(decrypt): {}'.format(str(decrypt_data)))
-
-            order_no = self.out_trade_no
-
-            record = self.record_cls.get_record(order_no = order_no)  # type: Optional[RechargeRecordT]
-            if not record:
-                logger.error('no such record<orderNo={}>'.format(order_no))
-                return self.reply()
-
-            if decrypt_data['resultCode'] != 'SUCCESS':
-                logger.debug('record<id={},orderNo={}> do failure'.format(str(record.id), order_no))
-                return self.reply()
-
-            if record.is_success():
-                logger.error(
-                    'record<id={},orderNo={}> has been finished.'.format(str(record.id), order_no))
-                return self.reply()
-
-            pay_gateway = PaymentGateway.from_gateway_key(
-                record.my_gateway,
-                record.pay_gateway_key,
-                record.pay_app_type)  # type: JDAggrePaymentGateway
-            if pay_gateway.pi_type != decrypt_data['piType']:
-                raise JDValidationError(tips = 'invalid pi type',
-                                        lvalue = pay_gateway.pi_type,
-                                        rvalue = decrypt_data['piType'])
-
-            # 校验参数
-            self.verify(pay_gateway, record, decrypt_data)
-
-            if decrypt_data['payStatus'] != 'FINISH':
-                return self.reply()
-
-            order_cache_mgr = OrderCacheMgr(record)
-            try:
-                if order_cache_mgr.check_and_set_done():
-                    logger.debug('{} has been done because cache in notify.'.format(repr(record)))
-                    return self.reply()
-            except Exception as e:
-                logger.error(
-                    'cache key is not exist or exception. exception = {}, key = {}'.format(str(e), order_cache_mgr.key))
-
-            modified = self.update_record(record, **decrypt_data)
-            if not modified:
-                # 如果没有任何记录被更新则说明已经处理了
-                logger.debug('{} has been done because db in notify'.format(repr(record)))
-                return self.reply()
-
-            logger.info('{} successfully confirmed'.format(repr(record)))
-
-            async_operation(post_pay, record = record)
-
-            return self.reply()
-        except JDCommuException as e:
-            logger.error(str(e))
-        except JDPayException as e:
-            logger.error(str(e))
-            return self.reply()
-        except Exception as e:
-            logger.exception(e)
-
-
-class JDJosPayNotifier(JDAggrePayNotifier):
-    """
-    京东引流支付的 回调解析器 拆出来的原因是 回调的时候字段有明显区别 而且解密报文的方式不一样
-    主要用于解决字段不一致的问题以及 加密app获取方式不一致的问题
-
-    和拉取不通的地方在于 回调回来的merchantNo 直接 就是需要的app 不需要再次转换了
-    """
-    def do(self, post_pay):
-        """
-        解析出来的参数和 京东聚合支付的参数有些不一致 这个地方 引用京东聚合解析之前 优先改变参数的字段名称
-        :return:
-        """
-        self.payload['piType'] = PiType.JDPAY
-        self.payload["channelNoSeq"] = self.payload['tradeNo']
-
-        return super(JDJosPayNotifier, self).do(post_pay)
-
-
-class JDJosPayRecordPoller(JDAggrePayReordPoller):
-
-    def action_of_pay(self, payment_gateway, record):  # type: (JDAggrePaymentGateway, RechargeRecordT)->(PayNotifyAction, str)
-        """
-        查询订单
-        :param payment_gateway: 支付的网关
-        :param record: 消费记录
-        :return:
-        """
-        payApp = payment_gateway.app
-        josPayApp = payApp.josPayApp.fetch()          # type: JDAggrePayApp
-        # 构建出 jOS 云支付的 app 用于加密解密
-        # 这个商户号是 品牌注册的时候的商户号,一般来说是固定值
-
-        # 解析 通知的报文
-        client = JDJosPay(
-            josPayApp.merchant_no,
-            josPayApp.desKey,
-            josPayApp.saltMd5Key,
-            josPayApp.systemId,
-            josPayApp.debug
-        )
-
-        # 将门店的相关信息 加载到payApp 里面 需要对门店信息参数进行组装
-        setattr(client, "shopInfo", josPayApp.shopInfo)
-
-        # 调用新构建的 这个 client 进行查询
-        try:
-            result = client.api_trade_query(record.orderNo, record.wxOrderNo)
-
-            # 对result进行字段转换 保证一致
-            result["channelNoSeq"] = result["tradeNo"]
-
-            if 'payStatus' not in result:
-                return PayNotifyAction.Unknown, result
-
-            if result['payStatus'] == 'FINISH':
-                return PayNotifyAction.NeedHandle, result
-
-            if result['payStatus'] in ['CLOSE', 'REFUND']:
-                return PayNotifyAction.NoHandle, result
-
-            return PayNotifyAction.Unknown, result
-        except (JDCommuException, JDPayException) as e:
-            logger.error(str(e))
-            return PayNotifyAction.Unknown, {}
-        except Exception, e:
-            logger.exception(e)
-            return PayNotifyAction.Unknown, {}
-
-
-class JDAggrePayPullUp(PayPullUp):
-    """
-    京东聚合支付获取支付参数。目前只作为商户支付
-    """
-    def do(self):  # type: ()->HttpResponse
-        OrderCacheMgr(self.record).initial()
-
-        try:
-            if WithdrawGateway.is_ledger(self.record.withdraw_source_key):  # 资金池分账的方式
-                ledger_type, ledger_fee_assume, ledger_list = None, None, None
-            else:  # 商户分账的方式
-                ledger_type, ledger_fee_assume, ledger_list = self.payment_gateway.bill_split_rule(
-                    self.record.partition_map)
-
-            pay_info = self.payment_gateway.unified_order(
-                out_trade_no = self.record.orderNo,
-                money = self.record.money,
-                notify_url = self.payload.get('notifyUrl'),
-                subject = self.record.subject,
-                expire = 300,
-                attach = {'dealerId': self.record.ownerId},
-                openId = self.openId,
-                billSplitList = ledger_list,
-                **{'imei': self.payload.get('imei')})
-        except Exception as e:
-            logger.exception(e)
-            self.record.fail(description = u'拉起支付发生异常', extraInfo__traceback = traceback.format_exc())
-            raise UserServerException(u'拉起支付发生异常')
-        else:
-            logger.debug('jd unified order pay info = {}'.format(str(pay_info)))
-
-            pi_type = self.payment_gateway.pi_type
-            if pi_type == PiType.ALIPAY:
-                response = JsonOkResponse(payload = {
-                    'tradeNO': pay_info['tradeNO'],
-                    'outTradeNo': self.record.orderNo,
-                    'adShow': self.payload['showAd']
-                })
-            elif pi_type == PiType.WX:
-                pay_info.update({
-                    'outTradeNo': self.record.orderNo,
-                    'golden': True,
-                    'adShow': self.payload['showAd']
-                })
-                response = JsonOkResponse(payload = pay_info)
-            elif pi_type == PiType.JDPAY:
-                front_url = self.payload.get('front_url', None)
-                if front_url:
-                    pay_url = add_query(pay_info, {
-                        'callbackUrl': front_url
-                    })
-                else:
-                    pay_url = pay_info
-
-                if not pay_url:
-                    self.record.fail(description = u'调起支付失败,请刷新后重试(1001)')
-                    raise UserServerException(u'调起支付失败,请刷新后重试(1001)')
-                else:
-                    response = JsonResponse({
-                        'result': 1,
-                        'description': 'SUCCESS',
-                        'payload': {
-                            'payUrl': pay_url
-                        }})
-            else:
-                self.record.fail(description = u'调起支付失败,请刷新后重试(1002)')
-                raise UserServerException(u'调起支付失败,请刷新后重试(1002)')
-
-            task_caller('poll_user_recharge_record',
-                        delay = PollRecordDefine.DELAY_BEFORE,
-                        expires = PollRecordDefine.TASK_EXPIRES,
-                        pay_app_type = self.payment_gateway.pay_app_type,
-                        record_id = str(self.record.id),
-                        interval = PollRecordDefine.WAIT_EACH_ROUND,
-                        total_count = PollRecordDefine.TOTAL_ROUNDS)
-
-            return response

+ 0 - 290
apps/web/common/transaction/pay/jdopen.py

@@ -1,290 +0,0 @@
-# -*- coding: utf-8 -*-
-# !/usr/bin/env python
-
-import datetime
-import logging
-import traceback
-
-import simplejson as json
-from django.conf import settings
-from django.http import HttpResponse
-from typing import TYPE_CHECKING, Dict, Optional
-
-from apilib.monetary import RMB
-from apilib.utils_json import JsonResponse
-from apps.web.common.transaction.pay import PayNotifier, OrderCacheMgr, PayRecordPoller, PayNotifyAction, PayPullUp
-from apps.web.constant import AppPlatformType, PollRecordDefine
-from apps.web.core import PayAppType
-from apps.web.core.payment import PaymentGateway
-from apps.web.core.utils import async_operation, JsonOkResponse
-from apps.web.exceptions import UserServerException
-
-from library.jdopen.exceptions import JdOpenException, JDOpenValidationError
-from taskmanager.mediator import task_caller
-
-if TYPE_CHECKING:
-    from apps.web.common.transaction.pay import RechargeRecordT
-    from apps.web.core.payment.jdopen import JDOpenPaymentGateway
-
-logger = logging.getLogger(__name__)
-
-
-def update_record(record, **payload):
-    # type: (RechargeRecordT, Dict)->bool
-
-    import arrow
-    finished_time = arrow.get(payload['completeTime'], 'YYYY-MM-DD HH:mm:ss', tzinfo=settings.TIME_ZONE).naive
-
-    jdOrderNo = payload['orderNum']  # 京东系统订单号. 京东系统查单
-
-    if 'bankOutTradeNum' in payload and payload['bankOutTradeNum']:
-        transactionId = payload['bankOutTradeNum']
-    elif 'bankRequestNum' in payload and payload['bankRequestNum']:
-        transactionId = payload['bankRequestNum']
-    else:
-        transactionId = jdOrderNo
-
-    _payload = {
-        'extraInfo.bankRequestNum': payload.get('bankRequestNum', ''),
-        'extraInfo.bankOutTradeNum': payload.get('bankOutTradeNum', ''),
-        'extraInfo.payload': payload,
-        'extraInfo.notify_time': datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
-    }
-
-    if 'ledgerStatus' in payload and payload['ledgerStatus']:
-        _payload.update({
-            'extraInfo.ledgerStatus': payload['ledgerStatus']
-        })
-
-    return record.succeed(wxOrderNo=jdOrderNo,
-                          transactionId=transactionId,
-                          finishedTime=finished_time,
-                          **_payload)
-
-class JDOpenPayReordPoller(PayRecordPoller):
-    def update_record(self, record, **payload):
-        # type: (RechargeRecordT, Dict)->bool
-        return update_record(record, **payload)
-
-    def action_of_pay(self, payment_gateway, record):
-        # type: (JDOpenPaymentGateway, RechargeRecordT)->(PayNotifyAction, str)
-        try:
-            result = payment_gateway.api_trade_query(out_trade_no=record.orderNo)
-
-            if 'status' not in result:
-                return PayNotifyAction.Unknown, result
-
-            if result['status'] == 'SUCCESS':
-                return PayNotifyAction.NeedHandle, {
-                    'completeTime': result['completeTime'],
-                    'orderNum': result['orderNum'],
-                    'bankRequestNum': result['payRecordList'][0]['bankRequestNum'],
-                    'bankOutTradeNum': result['payRecordList'][0]['bankOutTradeNum']
-                }
-
-            if result['payStatus'] in ['FAIL', 'REFUND', 'CANCEL', 'CLOSE']:
-                return PayNotifyAction.NoHandle, result
-
-            return PayNotifyAction.Unknown, result
-        except JdOpenException as e:
-            logger.error(str(e))
-            return PayNotifyAction.Unknown, {}
-        except Exception, e:
-            logger.exception(e)
-            return PayNotifyAction.Unknown, {}
-
-
-class JDOpenPayNotifier(PayNotifier):
-    def __init__(self, request, record_cls_factory):
-        super(JDOpenPayNotifier, self).__init__(request=request, record_cls_factory=record_cls_factory)
-        self.body = request.body
-
-    def parse_request(self, request):
-        _head = {
-            'accessKey': request.META['HTTP_ACCESSKEY'],
-            'version': request.META['HTTP_VERSION'],
-            'timestamp': request.META['HTTP_TIMESTAMP'],
-            'token': request.META['HTTP_TOKEN']
-        }
-
-        _body = json.loads(request.body)  # type: Dict
-
-        logger.debug('received jdopen pay notify: body = {}; heads = {}'.format(_body, _head))
-
-        return {
-            'body': _body,
-            'head': _head
-        }
-
-    @property
-    def out_trade_no(self):
-        return self.payload['body']['requestNum']
-
-    def verify(self, payment_gateway, record, payload):
-        # type: (JDOpenPaymentGateway, RechargeRecordT, Dict)->None
-
-        notifier_yuan = RMB(payload['body']['orderAmount'])
-
-        if record.money != notifier_yuan:
-            raise JDOpenValidationError(
-                tips=u'无效的total_fee',
-                lvalue=str(record.money),
-                rvalue=str(notifier_yuan),
-                client=payment_gateway.client)
-
-    def update_record(self, record, **payload):
-        # type: (RechargeRecordT, Dict)->bool
-
-        return update_record(record, **payload)
-
-    def reply(self):
-        return HttpResponse('ok')
-
-    def check_and_update_ledger_status(self, record):
-        if 'ledgerStatus' in self.payload['body'] and self.payload['body']['ledgerStatus']:
-            old_ledger_status = record.extraInfo.get('ledgerStatus', '')
-
-            if old_ledger_status != self.payload['body']['ledgerStatus']:
-                record.update(extraInfo__ledgerStatus = self.payload['body']['ledgerStatus'])
-
-    def do(self, post_pay):
-        # type: (callable)->HttpResponse
-
-        try:
-            order_no = self.out_trade_no
-
-            record = self.record_cls.get_record(order_no=order_no)  # type: Optional[RechargeRecordT]
-            if not record:
-                logger.error('no such record<orderNo={}>'.format(order_no))
-                return self.reply()
-
-            if self.payload['body']['status'] != 'SUCCESS':
-                logger.error('status of record<orderNo={}> is not success.'.format(order_no))
-                return self.reply()
-
-            if record.is_success():
-                logger.error(
-                    'record<id={},orderNo={}> has been finished.'.format(str(record.id), order_no))
-                self.check_and_update_ledger_status(record)
-                return self.reply()
-
-            pay_gateway = PaymentGateway.from_gateway_key(
-                record.my_gateway,
-                record.pay_gateway_key,
-                record.pay_app_type)  # type: JDOpenPaymentGateway
-
-            pay_gateway.check_token(self.body, self.payload['head']['timestamp'], self.payload['head']['token'])
-
-            self.verify(pay_gateway, record, self.payload)
-
-            order_cache_mgr = OrderCacheMgr(record)
-            try:
-                if order_cache_mgr.check_and_set_done():
-                    logger.debug('{} has been done because cache in notify.'.format(repr(record)))
-                    self.check_and_update_ledger_status(record)
-                    return self.reply()
-            except Exception as e:
-                logger.error(
-                    'cache key is not exist or exception. exception = {}, key = {}'.format(str(e), order_cache_mgr.key))
-
-            modified = self.update_record(record, **(self.payload['body']))
-            if not modified:
-                # 如果没有任何记录被更新则说明已经处理了
-                logger.debug('{} has been done because db in notify'.format(repr(record)))
-                self.check_and_update_ledger_status(record)
-                return self.reply()
-
-            logger.info('{} successfully confirmed'.format(repr(record)))
-
-            async_operation(post_pay, record=record)
-
-            return self.reply()
-        except JdOpenException as e:
-            logger.exception(e)
-            return self.reply()
-        except Exception as e:
-            logger.exception(e)
-
-
-class JDOpenPullUp(PayPullUp):
-    def do(self):  # type: ()->HttpResponse
-        gateway_type = self.payment_gateway.gateway_type
-
-        if gateway_type not in [AppPlatformType.ALIPAY, AppPlatformType.WECHAT, AppPlatformType.JD]:
-            raise UserServerException(u'不支持该支付类型')
-
-        OrderCacheMgr(self.record).initial()
-
-        try:
-            ledger_type, ledger_fee_assume, ledger_list = self.payment_gateway.bill_split_rule(
-                self.record.partition_map)
-
-            _payload = {
-                'out_trade_no': self.record.orderNo,
-                'money': self.record.money,
-                'notify_url': self.payload['notifyUrl'],
-                'subject': self.record.subject,
-                'attach': {'dealerId': self.record.ownerId},
-                'openId': self.openId
-            }
-
-            if ledger_list:
-                _payload.update({
-                    'ledgerRule': {
-                        'ledgerType': ledger_type,
-                        'ledgerFeeAssume': ledger_fee_assume,
-                        'list': ledger_list
-                    }})
-
-            pay_info = self.payment_gateway.unified_order(**_payload)
-        except JdOpenException as e:
-            logger.exception(e)
-            self.record.fail(description = e.errMsg)
-            raise UserServerException(e.errMsg)
-        except Exception as e:
-            logger.exception(e)
-            self.record.fail(description = u'拉起支付发生异常', extraInfo__traceback = traceback.format_exc())
-            raise UserServerException(u'拉起支付发生异常')
-        else:
-            logger.debug('jdopen unified order pay info = {}'.format(str(pay_info)))
-
-            self.record.update(
-                wxOrderNo = pay_info['data']['bankRequestNum'], extraInfo__jdOrderNo = pay_info['data']['orderNum'])
-
-            gateway_type = self.payment_gateway.gateway_type
-
-            if gateway_type == AppPlatformType.ALIPAY:
-                response = JsonOkResponse(payload = {
-                    'tradeNO': pay_info['data']['bankRequest']['TRADENO'],
-                    'outTradeNo': self.record.orderNo,
-                    'adShow': self.payload.get('showAd')
-                })
-            elif gateway_type == AppPlatformType.WECHAT:
-                response = JsonOkResponse(payload = {
-                    'outTradeNo': self.record.orderNo,
-                    'golden': True,
-                    'adShow': self.payload.get('showAd'),
-                    'appId': pay_info['data']['bankRequest']['APPID'],
-                    'timeStamp': pay_info['data']['bankRequest']['TIMESTAMP'],
-                    'nonceStr': pay_info['data']['bankRequest']['NONCESTR'],
-                    'signType': pay_info['data']['bankRequest']['SIBGTYPE'],  # 小程序和公众号返回的值不是一个
-                    'package': pay_info['data']['bankRequest']['PACKAGE'],
-                    'paySign': pay_info['data']['bankRequest']['PAYSIGN']
-                })
-            else:  # gateway_type == AppPlatformType.JD
-                response = JsonResponse({
-                    'result': 1,
-                    'description': 'SUCCESS',
-                    'payload': {
-                        'payUrl': pay_info['data']['bankRequest']['PAY_URL']
-                    }})
-
-            task_caller('poll_user_recharge_record',
-                        delay = PollRecordDefine.DELAY_BEFORE,
-                        expires = PollRecordDefine.TASK_EXPIRES,
-                        pay_app_type = self.payment_gateway.pay_app_type,
-                        record_id = str(self.record.id),
-                        interval = PollRecordDefine.WAIT_EACH_ROUND,
-                        total_count = PollRecordDefine.TOTAL_ROUNDS)
-
-            return response

+ 0 - 231
apps/web/common/transaction/pay/saobei.py

@@ -1,231 +0,0 @@
-# -*- coding: utf-8 -*-
-# !/usr/bin/env python
-import datetime
-import logging
-import traceback
-
-import simplejson as json
-from django.conf import settings
-from django.http import HttpResponse
-from typing import TYPE_CHECKING, Dict, Optional
-
-from apilib.utils_json import JsonResponse
-from apps.web.common.transaction.pay import PayNotifyAction, PayRecordPoller, OrderCacheMgr, PayNotifier, PayPullUp
-from apps.web.constant import PollRecordDefine
-from apps.web.core.payment import PaymentGateway
-from apps.web.core.utils import async_operation
-from apps.web.exceptions import UserServerException
-from library.saobei import SaobeiException, SaobeiValidationError
-from taskmanager.mediator import task_caller
-
-if TYPE_CHECKING:
-    from apps.web.common.transaction.pay import RechargeRecordT
-    from apps.web.core.payment.saobei import SaobeiPaymentGateway
-
-logger = logging.getLogger(__name__)
-
-
-def update_record(record, **payload):
-    # type: (RechargeRecordT, Dict)->bool
-
-    if 'pay_time' in payload:
-        import arrow
-        finished_time = arrow.get(payload['pay_time'], 'YYYYMMDDHHmmss', tzinfo = settings.TIME_ZONE).naive
-    else:
-        finished_time = datetime.datetime.now()
-
-    return record.succeed(wxOrderNo = payload['out_trade_no'],
-                          transactionId = payload['channel_trade_no'],
-                          finishedTime = finished_time,
-                          **{
-                              'extraInfo.notify_time': datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
-                          })
-
-
-class SaobeiPayRecordPoller(PayRecordPoller):
-    def update_record(self, record, **payload):
-        # type: (RechargeRecordT, Dict)->bool
-        return update_record(record, **payload)
-
-    def action_of_pay(self, payment_gateway, record):
-        # type: (SaobeiPaymentGateway, RechargeRecordT)->(PayNotifyAction, str)
-
-        try:
-            pay_time = record.extraInfo.get('payTime') or record.attachParas.get('payTime')
-
-            result = payment_gateway.api_trade_query(pay_trace = record.orderNo,
-                                                     pay_time = pay_time)
-
-            if result['result_code'] == '01':  # 成功
-                return PayNotifyAction.NeedHandle, result
-
-            if result['result_code'] == '02':  # 失败
-                return PayNotifyAction.NoHandle, {}
-
-            return PayNotifyAction.Unknown, result
-        except SaobeiException as e:
-            logger.error(str(e))
-            return PayNotifyAction.Unknown, {}
-        except Exception as e:
-            logger.exception(e)
-            return PayNotifyAction.Unknown, {}
-
-
-class SaobeiPayNotifier(PayNotifier):
-    def __init__(self, request, record_cls_factory):
-        super(SaobeiPayNotifier, self).__init__(request = request, record_cls_factory = record_cls_factory)
-
-    def parse_request(self, request):
-        return json.loads(request.body)  # type: Dict
-
-    @property
-    def out_trade_no(self):
-        return self.payload['terminal_trace']
-
-    def verify(self, payment_gateway, record, payload):
-        # type: (SaobeiPaymentGateway, RechargeRecordT, Dict)->None
-
-        if record.fen_total_fee != int(payload['total_fee']):
-            raise SaobeiValidationError(errmsg = u'无效的total_fee',
-                                        lvalue = record.fen_total_fee,
-                                        rvalue = payload['total_fee'],
-                                        client = payment_gateway.client)
-
-    def update_record(self, record, **payload):
-        # type: (RechargeRecordT, Dict)->bool
-        return update_record(record, **payload)
-
-    @classmethod
-    def reply(cls, ok, msg = None):
-        return_code = '01' if ok else '02'
-
-        if ok:
-            return_msg = 'success'
-        else:
-            return_msg = msg if msg else 'unknown'
-
-        return JsonResponse(data = {'return_code': return_code, 'return_msg': return_msg})
-
-    def verify_sign(self, gateway, data):
-        # type: (SaobeiPaymentGateway, dict)->bool
-
-        from collections import OrderedDict
-        sign_data = OrderedDict()
-        sign_data['return_code'] = data['return_code']
-        sign_data['return_msg'] = data['return_msg']
-        sign_data['result_code'] = data['result_code']
-        sign_data['pay_type'] = data['pay_type']
-        sign_data['user_id'] = data['user_id']
-        sign_data['merchant_name'] = data['merchant_name']
-        sign_data['merchant_no'] = data['merchant_no']
-        sign_data['terminal_id'] = data['terminal_id']
-        sign_data['terminal_trace'] = data['terminal_trace']
-        sign_data['terminal_time'] = data['terminal_time']
-        sign_data['total_fee'] = data['total_fee']
-        sign_data['end_time'] = data['end_time']
-        sign_data['out_trade_no'] = data['out_trade_no']
-        sign_data['channel_trade_no'] = data['channel_trade_no']
-        sign_data['attach'] = data['attach']
-        sign_data['key_sign'] = data['key_sign']
-
-        return gateway.check(sign_data, order = False)
-
-    def do(self, post_pay):
-        # type: (callable)->JsonResponse
-        try:
-            payload = self.payload
-
-            logger.debug('received saobei pay notify: {}'.format(str(payload)))
-
-            if payload['return_code'] != '01':
-                logger.error('return_code = {}, return_msg = {}'.format(payload['return_code'], payload['return_msg']))
-                return self.reply(False, 'return_code != 01')
-
-            order_no = payload['terminal_trace']
-
-            record = self.record_cls.get_record(order_no = order_no)  # type: Optional[RechargeRecordT]
-            if not record:
-                logger.error(
-                    'no such record. orderNo = {}'.format(order_no))
-                return self.reply(True)
-
-            if record.is_success():
-                logger.error('record has been finished. orderNo = {}'.format(order_no))
-                return self.reply(True)
-
-            payment_gateway = PaymentGateway.from_gateway_key(
-                record.my_gateway,
-                record.pay_gateway_key,
-                record.pay_app_type)  # type: SaobeiPaymentGateway
-
-            # 校验签名
-            self.verify_sign(payment_gateway, payload)
-
-            # 校验参数
-            self.verify(payment_gateway, record, payload)
-
-            if payload['result_code'] != '01':
-                record.fail(wxOrderNo = payload['channel_trade_no'], finishedTime = datetime.datetime.now(),
-                            description = payload['return_msg'])
-                return self.reply(True)
-
-            order_cache_mgr = OrderCacheMgr(record)
-            try:
-                if order_cache_mgr.check_and_set_done():
-                    logger.debug('{} has been done because cache in notify.'.format(repr(record)))
-                    return self.reply(True)
-            except Exception as e:
-                logger.error(
-                    'cache key is not exist or exception. exception = {}, key = {}'.format(str(e), order_cache_mgr.key))
-
-            modified = self.update_record(record = record, **payload)
-            if not modified:
-                logger.debug('{} has been done because db in notify'.format(repr(record)))
-                return self.reply(True)
-
-            logger.info('{} successfully confirmed'.format(repr(record)))
-
-            async_operation(post_pay, record = record)
-
-            return self.reply(True)
-        except SaobeiException as e:
-            logger.error(str(e))
-            return self.reply(True)
-        except Exception as e:
-            logger.exception(e)
-            return self.reply(False, 'EXCEPTION')
-
-
-class SaobeiPullUp(PayPullUp):
-    """
-    扫呗支付获取支付参数。目前只作为商户支付
-    """
-    def do(self):  # type: ()->HttpResponse
-        OrderCacheMgr(self.record).initial()
-
-        try:
-            pay_url = self.payment_gateway.create_pay_url(
-                pay_trace = self.record.orderNo,
-                pay_time = self.record.extraInfo['payTime'],
-                money = self.record.money,
-                attach = {'dealerId': self.record.ownerId},
-                body = self.record.subject,
-                notify_url = self.payload['notifyUrl'],
-                front_url = self.payload['front_url'])
-        except Exception as e:
-            logger.exception(e)
-            self.record.fail(description = u'拉起支付发生异常', extraInfo__traceback = traceback.format_exc())
-            raise UserServerException(u'拉起支付发生异常')
-        else:
-            if not pay_url:
-                self.record.fail(description = u'调起支付失败,请刷新后重试')
-                raise UserServerException(u'调起支付失败,请刷新后重试')
-            else:
-                task_caller('poll_user_recharge_record',
-                            delay = PollRecordDefine.DELAY_BEFORE,
-                            expires = PollRecordDefine.TASK_EXPIRES,
-                            pay_app_type = self.payment_gateway.pay_app_type,
-                            record_id = str(self.record.id),
-                            interval = PollRecordDefine.WAIT_EACH_ROUND,
-                            total_count = PollRecordDefine.TOTAL_ROUNDS)
-                return JsonResponse({'result': 1, 'description': 'SUCCESS', 'payload': {'payUrl': pay_url}})

+ 0 - 222
apps/web/common/transaction/pay/ys.py

@@ -1,222 +0,0 @@
-# -*- coding: utf-8 -*-
-# !/usr/bin/env python
-import datetime
-import logging
-import traceback
-
-import simplejson as json
-from django.conf import settings
-from django.http import HttpResponse
-from typing import TYPE_CHECKING, Dict, Optional
-
-from apilib.utils_json import JsonResponse
-from apps.web.common.transaction.pay import PayNotifier, OrderCacheMgr, PayRecordPoller, PayNotifyAction, PayPullUp
-from apps.web.constant import AppPlatformType, PollRecordDefine
-from apps.web.core import PayAppType
-from apps.web.core.payment import PaymentGateway
-from apps.web.core.utils import async_operation, JsonOkResponse
-from apps.web.exceptions import UserServerException
-from library.ys import YsException
-from taskmanager.mediator import task_caller
-
-if TYPE_CHECKING:
-    from apps.web.common.transaction.pay import RechargeRecordT
-    from apps.web.core.payment.ys import YsPaymentGateway
-
-logger = logging.getLogger(__name__)
-
-
-def update_record(record, **payload):
-    # type: (RechargeRecordT, Dict)->bool
-
-    if 'wxtimeend' in payload:
-        import arrow
-        finished_time = arrow.get(payload['wxtimeend'], 'YYYYMMDDHHmmss', tzinfo = settings.TIME_ZONE).naive
-    else:
-        finished_time = datetime.datetime.now()
-
-    return record.succeed(wxOrderNo = payload['wtorderid'],
-                          transactionId = payload['wxtransactionid'],
-                          finishedTime = finished_time,
-                          **{
-                              'extraInfo.wxopenid': payload['wxopenid'],
-                              'extraInfo.acctype': payload['acctype']
-                          })
-
-class YsReordPoller(PayRecordPoller):
-    def update_record(self, record, **payload):
-        # type: (RechargeRecordT, Dict)->bool
-        return update_record(record, **payload)
-
-    def action_of_pay(self, payment_gateway, record):
-        # type: (YsPaymentGateway, RechargeRecordT)->(PayNotifyAction, str)
-
-        '''
-        00 - 成功;98 - 未确定;25 - 找不到原笔交易;AA-待支付;其他 - 失败
-        :param payment_gateway:
-        :param record:
-        :return:
-        '''
-        try:
-            result = payment_gateway.api_trade_query(out_trade_no = record.orderNo)
-
-            if 'resultcode' not in result:
-                return PayNotifyAction.Unknown, result
-
-            if result['resultcode'] == '00':
-                return PayNotifyAction.NeedHandle, result
-
-            if result['resultcode'] in ['98', 'AA']:
-                return PayNotifyAction.Unknown, result
-
-            if result['resultcode'] == '25':
-                return PayNotifyAction.NoHandle, result
-
-            # 其他说明交易失败, 不处理
-            return PayNotifyAction.NoHandle, result
-        except YsException as e:
-            logger.error(str(e))
-            return PayNotifyAction.Unknown, {}
-        except Exception, e:
-            logger.exception(e)
-            return PayNotifyAction.Unknown, {}
-
-
-class YsNotifier(PayNotifier):
-    def __init__(self, request, record_cls_factory):
-        super(YsNotifier, self).__init__(request = request, record_cls_factory = record_cls_factory)
-
-    def parse_request(self, request):
-        return json.loads(request.body)  # type: Dict
-
-    @property
-    def out_trade_no(self):
-        return self.payload['tradetrace']
-
-    def verify(self, payment_gateway, record, payload):
-        # type: (YsPaymentGateway, RechargeRecordT, Dict)->None
-        pass
-
-    def update_record(self, record, **payload):
-        # type: (RechargeRecordT, Dict)->bool
-        return update_record(record, **payload)
-
-    def reply(self, retry = False):
-        if retry:
-            return JsonResponse({'resultcode': ''})
-        else:
-            return JsonResponse({'resultcode': '00'})
-
-    def verify_sign(self, gateway, data):
-        # type: (YsPaymentGateway, dict)->bool
-        sign = data.pop('sign')
-        return gateway.client.sign(data) == sign
-
-    def do(self, post_pay):
-        # type: (callable)->JsonResponse
-
-        try:
-            payload = self.payload
-
-            logger.debug('received ys pay notify: {}'.format(str(payload)))
-
-            order_no = payload['tradetrace']
-
-            record = self.record_cls.get_record(order_no = order_no)  # type: Optional[RechargeRecordT]
-            if not record:
-                logger.error(
-                    'no such record. orderNo = {}'.format(order_no))
-                return self.reply(True)
-
-            if record.is_success():
-                logger.error('record has been finished. orderNo = {}'.format(order_no))
-                return self.reply(True)
-
-            payment_gateway = PaymentGateway.from_gateway_key(
-                record.my_gateway,
-                record.pay_gateway_key,
-                record.pay_app_type)  # type: YsPaymentGateway
-
-            # 校验签名
-            self.verify_sign(payment_gateway, payload)
-
-            # 校验参数
-            self.verify(payment_gateway, record, payload)
-
-            order_cache_mgr = OrderCacheMgr(record)
-            try:
-                if order_cache_mgr.check_and_set_done():
-                    logger.debug('{} has been done because cache in notify.'.format(repr(record)))
-                    return self.reply()
-            except Exception as e:
-                logger.error(
-                    'cache key is not exist or exception. exception = {}, key = {}'.format(str(e), order_cache_mgr.key))
-
-            modified = self.update_record(record = record, **payload)
-            if not modified:
-                logger.debug('{} has been done because db in notify'.format(repr(record)))
-                return self.reply()
-
-            logger.info('{} successfully confirmed'.format(repr(record)))
-
-            async_operation(post_pay, record = record)
-
-            return self.reply(True)
-        except YsException as e:
-            logger.error(str(e))
-            return self.reply()
-        except Exception as e:
-            logger.exception(e)
-            return self.reply(retry = True)
-
-
-class YsPullUp(PayPullUp):
-    """
-    易生获取支付参数。只用作商户,所以GOLDEN为True
-    """
-    def do(self):  # type: ()->HttpResponse
-        OrderCacheMgr(self.record).initial()
-
-        try:
-            wtorderid, pay_info = self.payment_gateway.unified_order(
-                out_trade_no = self.record.orderNo,
-                money = self.record.money,
-                notify_url = self.payload['notifyUrl'],
-                subject = self.record.subject,
-                openId = self.openId)
-        except Exception as e:
-            logger.exception(e)
-            self.record.fail(description = u'拉起支付发生异常', extraInfo__traceback = traceback.format_exc())
-            raise UserServerException(u'拉起支付发生异常')
-        else:
-            logger.debug("wt order id is {}. pay info = {}".format(wtorderid, pay_info))
-
-            response = None
-
-            if self.payment_gateway.gateway_type == AppPlatformType.ALIPAY:
-                response = JsonOkResponse(payload = {
-                    'tradeNO': pay_info['tradeNO'],
-                    'outTradeNo': self.record.orderNo,
-                    'adShow': self.payload.get('showAd', False)
-                })
-            elif self.payment_gateway.gateway_type == AppPlatformType.WECHAT:
-                pay_info.update({
-                    'outTradeNo': self.record.orderNo,
-                    'golden': True,
-                    'adShow': self.payload.get('showAd', False)
-                })
-                response = JsonOkResponse(payload = pay_info)
-
-            if not response:
-                self.record.fail(description = u'不支持该支付类型')
-                raise UserServerException(u'不支持该支付类型')
-
-            task_caller('poll_user_recharge_record',
-                        delay = PollRecordDefine.DELAY_BEFORE,
-                        expires = PollRecordDefine.TASK_EXPIRES,
-                        pay_app_type = self.payment_gateway.pay_app_type,
-                        record_id = str(self.record.id),
-                        interval = PollRecordDefine.WAIT_EACH_ROUND,
-                        total_count = PollRecordDefine.TOTAL_ROUNDS)
-
-            return response

+ 0 - 104
apps/web/common/transaction/refund/jdaggre.py

@@ -1,104 +0,0 @@
-# -*- coding: utf-8 -*-
-# !/usr/bin/env python
-
-import datetime
-import logging
-
-import simplejson as json
-from typing import TYPE_CHECKING, Dict
-
-from apps.web.common.transaction.refund import RefundNotifier, RefundPuller
-from apps.web.core.models import JDAggrePayApp
-from library.jd import JDAggrePay
-from library.jd.exceptions import JDPayException
-
-if TYPE_CHECKING:
-    from django.core.handlers.wsgi import WSGIRequest
-    from apps.web.core.payment import PaymentGatewayT
-    from apps.web.common.transaction.pay import RefundRecordT, RechargeRecordT
-
-logger = logging.getLogger(__name__)
-
-
-class JDAggreRefundNotifier(RefundNotifier):
-    def __init__(self, request, record_cls_factory):
-        self.orginal = json.loads(request.body)  # type: Dict
-        logger.debug('received jdaggre refund notify(encrypt): {}'.format(str(self.orginal)))
-        super(JDAggreRefundNotifier, self).__init__(request=request, refund_order_getter=record_cls_factory)
-
-    def parse_request(self, request):  # type:(WSGIRequest) -> dict
-        app = JDAggrePayApp.objects(merchant_no = self.orginal['merchantNo']).first()  # type: JDAggrePayApp
-        client = JDAggrePay(app.merchant_no, app.desKey, app.saltMd5Key, app.debug)  # type: JDAggrePay
-        return client.decrypt_response(self.orginal)
-
-    def verify_payload(self, payload):  # type:(dict) -> bool
-        """ 京东聚合的退款返回参数中,没有明确要求对于参数做篡改校验 """
-        return True
-
-    @property
-    def refund_order_no(self):  # type:() -> str
-        return self.payload['outRefundNo']
-
-    def handle_refund_order(self, refundOrder, post_pay):  # type:(RefundRecordT, callable) -> None
-        if self.payload["resultCode"] != "SUCCESS":  # 接口失败
-            refundOrder.fail(errorCode = self.payload["errCode"], errorDesc = self.payload["errCodeDes"])
-            return
-
-        if self.payload["payStatus"] == "FINISH":  # 业务状态成功
-            payFinishTime = self.payload["payFinishTime"]
-            datetimeRefund = datetime.datetime.strptime(payFinishTime, "%Y%m%d%H%M%S")
-
-            matched = refundOrder.succeed(tradeRefundNo = self.payload["tradeRefundNo"], finishedTime = datetimeRefund)
-            if not matched:
-                return
-
-            return post_pay(refundOrder, datetimeRefund)
-
-        if self.payload["payStatus"] == "CLOSE":  # 退款单被关闭
-            refundOrder.closed(
-                tradeRefundNo = self.payload["tradeRefundNo"],
-                errorCode = self.payload["errCode"],
-                errorDesc = self.payload["errCodeDes"])
-            return
-
-        # 剩下的情况就是resultCode == SUCCESS 但是 payStatus != [FINISH, CLOSE] 在文档中没有描述 做个防止机制
-        refundOrder.fail(errorCode = self.payload["errCode"], errorDesc = self.payload["errCodeDes"])
-
-    @property
-    def errorResponse(self):  # type:() -> str
-        return "error"
-
-    @property
-    def successResponse(self):  # type:() -> str
-        return "ok"
-
-
-class JDAggreRefundPuller(RefundPuller):
-    def pull(self, payGateWay, payOrder, post_pay):  # type:(PaymentGatewayT, RechargeRecordT, callable) -> None
-        try:
-            result = payGateWay.api_refund_query(out_refund_no = self._refundOrder.orderNo)
-        except JDPayException:
-            return
-
-        if result["resultCode"] != "SUCCESS":
-            self._refundOrder.fail(errorCode = result["errCode"], errorDesc = result["errCodeDes"])
-
-        elif result["payStatus"] == "FINISH":
-            payFinishTime = result["payFinishTime"]
-            datetimeRefund = datetime.datetime.strptime(payFinishTime, "%Y%m%d%H%M%S")
-            matched = self._refundOrder.succeed(result["payRequestPiNo"], finishedTime = datetimeRefund)
-
-            if not matched:
-                return
-
-            post_pay(self._refundOrder, datetimeRefund)
-
-        elif result["payStatus"] == "CLOSE":
-            self._refundOrder.closed(result["payRequestPiNo"], errorCode = result["errCode"],
-                                     errorDesc = result["errCodeDes"])
-
-        elif result["payStatus"] == "PROCESSING":
-            pass
-
-        else:
-            pass

+ 0 - 110
apps/web/common/transaction/refund/jdopen.py

@@ -1,110 +0,0 @@
-# -*- coding: utf-8 -*-
-# !/usr/bin/env python
-
-import datetime
-import logging
-
-import simplejson as json
-from django.conf import settings
-from typing import TYPE_CHECKING, Dict
-
-from apps.web.common.transaction.refund import RefundNotifier, RefundPuller
-
-if TYPE_CHECKING:
-    from django.core.handlers.wsgi import WSGIRequest
-    from apps.web.core.payment import PaymentGatewayT
-    from apps.web.common.transaction.pay import RefundRecordT, RechargeRecordT
-
-logger = logging.getLogger(__name__)
-
-
-class JDOpenRefundNotifier(RefundNotifier):
-    def parse_request(self, request):  # type:(WSGIRequest) -> dict
-        _head = {
-            'accessKey': request.META['HTTP_ACCESSKEY'],
-            'version': request.META['HTTP_VERSION'],
-            'timestamp': request.META['HTTP_TIMESTAMP'],
-            'token': request.META['HTTP_TOKEN']
-        }
-
-        _body = json.loads(request.body)  # type: Dict
-
-        logger.debug('received jdopen refund notify: body = {}; heads = {}'.format(_body, _head))
-
-        return {
-            'body': _body,
-            'head': _head
-        }
-
-    def verify_payload(self, payload):  # type:(dict) -> bool
-        return True
-
-    def handle_refund_order(self, refundOrder, post_pay):  # type:(RefundRecordT, callable) -> None
-        if not self.payload:
-            refundOrder.fail(errorDesc = u'退款通知参数错误')
-            return
-
-        if self.payload["body"]["refundStatus"] != "SUCCESS":
-            refundOrder.fail(
-                errorCode = self.payload["body"].get('failCode', ''),
-                errorDesc = self.payload["body"].get('failReason', ''))
-            return
-
-        import arrow
-        refund_finished_time = arrow.get(
-            self.payload["body"]["refundDate"], 'YYYY-MM-DD HH:mm',
-            tzinfo = settings.TIME_ZONE).naive  # type: datetime.datetime
-
-        matched = refundOrder.succeed(
-            tradeRefundNo = str(self.payload["body"].get('bankRequestNum', '')), finishedTime = refund_finished_time)
-
-        if not matched:
-            return
-
-        return post_pay(refundOrder, refund_finished_time)
-
-    @property
-    def refund_order_no(self):  # type:() -> str
-        return self.payload["body"]['refundRequestNum']
-
-    @property
-    def errorResponse(self):  # type:() -> str
-        return "error"
-
-    @property
-    def successResponse(self):  # type:() -> str
-        return "ok"
-
-
-class JDOpenRefundPuller(RefundPuller):
-    def pull(self, payGateWay, payOrder, post_pay):  # type:(PaymentGatewayT, RechargeRecordT, callable) -> None
-        try:
-            result = payGateWay.api_refund_query(requestNum = payOrder.orderNo)
-
-            refund_record_list = result['data']['refundRecordList']
-            for refund_record in refund_record_list:
-                if refund_record['refundRequestNum'] != self._refundOrder.orderNo:
-                    logger.debug('order<orderNo={}> has another refund record<orderNo={}>'.format(
-                        payOrder.orderNo, self._refundOrder.orderNo))
-                    continue
-                else:
-                    if refund_record["status"] == "FAIL":
-                        self._refundOrder.fail(errorDesc = refund_record["failReason"])
-
-                    elif refund_record["status"] == "SUCCESS":
-                        import arrow
-                        completeTime = arrow.get(
-                            refund_record['refundTime'], 'YYYY-MM-DD HH:mm:ss', tzinfo = settings.TIME_ZONE).naive
-
-                        matched = self._refundOrder.succeed(
-                            tradeRefundNo = refund_record['bankRequestNum'], finishedTime = completeTime)
-
-                        if not matched:
-                            return
-
-                        post_pay(self._refundOrder, completeTime)
-
-                    elif result["payStatus"] == "INIT":
-                        pass
-        except Exception as e:
-            logger.exception(e)

+ 359 - 119
apps/web/common/transaction/withdraw.py

@@ -1,32 +1,35 @@
 # -*- coding: utf-8 -*-
 # !/usr/bin/env python
+
 import datetime
 import logging
 import traceback
 
+import arrow
 from django.conf import settings
 from typing import TYPE_CHECKING, Callable, Union, Optional, cast
 
 from apilib.monetary import RMB
 from apilib.utils_sys import memcache_lock
 from apps.web.agent.models import Agent
-from apps.web.common.models import Banks
+from apps.web.common.models import WithdrawBanks, WithdrawBankCard
 from apps.web.common.transaction import WithdrawStatus, WITHDRAW_PAY_TYPE, WithdrawResult
 from apps.web.core import ROLE
 from apps.web.core.exceptions import ServiceException, WithdrawError
-from apps.web.core.models import BankCard
 from apps.web.core.payment import WithdrawGateway
-from apps.web.core.payment.ali import AliPayWithdrawGateway
+from apps.web.core.payment.ali import AliPayWithdrawGateway, AlipayWithdrawQueryResult
 from apps.web.core.payment.wechat import WechatWithdrawGateway
+from apps.web.exceptions import WithdrawOrderNotExist
 from library.alipay import AliPayGatewayException
 from library.alipay import AliPayServiceException
-from library.wechatbase.exceptions import WechatNetworkException, WeChatPayException, WeChatException
+from library.wechatbase.exceptions import WechatNetworkException, WeChatPayException
 
 if TYPE_CHECKING:
     from contextlib import GeneratorContextManager
     from apps.web.common.models import CapitalUser
     from apps.web.common.models import WithdrawRecord
     from apps.web.core.payment.wechat import WechatWithdrawQueryResult
+    from apps.web.core.payment import WithdrawGatewayT
 
 logger = logging.getLogger(__name__)
 
@@ -41,80 +44,137 @@ def withdraw_lock(type_, id_, key_fn = withdraw_lock_key_fn, value = 1, timeout
     return memcache_lock(key_fn(type_, id_), value, timeout)
 
 
-def withdraw_via_bank_in_ali(gateway, record, bankcard):
-    # type: (AliPayWithdrawGateway, WithdrawRecord, BankCard)->WithdrawResult
-
-    def error_handler(err_code, err_code_des):
+class AlipayWithdraw(object):
+    @classmethod
+    def service_error_handler(cls, err_code, err_code_des):
         # type:(str, str)->WithdrawResult
 
         remarks = u'{err_code_des}({err_code})'.format(err_code = err_code, err_code_des = err_code_des)
 
-        if err_code == 'SYSTEM_ERROR' or err_code == 'PROCESS_FAIL`':
+        if err_code == 'SYSTEM_ERROR' or err_code == 'PROCESS_FAIL':
             return WithdrawResult(
                 False, 0, False, remarks,
-                u"商户平台系统错误,无法获取转账状态。台会每日定时重试,如果3个工作日仍然是失败状态,请联系客服确认结果(1001)")
-        elif err_code in ['PAYMENT_MONEY_NOT_ENOUGH', 'PAYER_BALANCE_NOT_ENOUGH', 'BALANCE_IS_NOT_ENOUGH']:
+                u"商户平台系统错误,无法获取转账状态。台会每日定时重试,如果3个工作日仍然是失败状态,请联系客服确认结果(1021)")
+        elif err_code in ['PAYER_BALANCE_NOT_ENOUGH', 'BALANCE_IS_NOT_ENOUGH']:
             from taskmanager.mediator import task_caller
             task_caller('withdraw_error_alert', err_type = 'NOTENOUGH')
             return WithdrawResult(False, 0, True, remarks,
                                   u"商户平台系统繁忙,请过一小时后再试。验证码每天次数有限,频繁提交提现可能消耗完当天验证码次数(1001)")
-        elif err_code in ['INVALID_PARAMETER', 'EXCEED_LIMIT_SM_AMOUNT', 'EXCEED_LIMIT_MM_AMOUNT',
-                          'PAYCARD_UNABLE_PAYMENT', 'PAYER_STATUS_ERROR', 'PAYER_CERTIFY_CHECK_FAIL',
-                          'PAYER_STATUS_ERROR', 'PAYER_USER_INFO_ERROR', 'PAYMENT_INFO_INCONSISTENCY',
-                          'CARD_BIN_ERROR', 'PAYEE_CARD_INFO_ERROR', 'INST_PAY_UNABLE', 'REQUEST_PROCESSING',
-                          'MEMO_REQUIRED_IN_TRANSFER_ERROR', 'PERMIT_CHECK_PERM_IDENTITY_THEFT',
-                          'REMARK_HAS_SENSITIVE_WORD', 'EXCEED_LIMIT_DM_AMOUNT', 'NO_ACCOUNT_RECEIVE_PERMISSION',
-                          'NO_ACCOUNT_PAYMENT_PERMISSION', 'INVALID_PARAMETER', 'PAYER_NOT_EXIST',
-                          'PRODUCT_NOT_SIGN', 'PAYMENT_TIME_EXPIRE', 'PAYEE_NOT_EXIST', 'PAYEE_ACCOUNT_STATUS_ERROR',
-                          'PERMIT_NON_BANK_LIMIT_PAYEE', 'PERMIT_NON_BANK_LIMIT_PAYEE',
-                          'PAYEE_TRUSTEESHIP_ACC_OVER_LIMIT',
-                          'NO_PERMISSION_ACCOUNT', 'TRUSTEESHIP_ACCOUNT_NOT_EXIST', 'PAYEE_ACCOUNT_NOT_EXSIT',
-                          'ORDER_NOT_EXIST', 'PAYEE_USERINFO_STATUS_ERROR', 'TRUSTEESHIP_RECIEVE_QUOTA_LIMIT',
-                          'SECURITY_CHECK_FAILED', 'NO_ORDER_PERMISSION', 'ORDER_STATUS_INVALID',
-                          'PERM_AML_NOT_REALNAME_REV', 'PERM_AML_NOT_REALNAME_REV', 'USER_AGREEMENT_VERIFY_FAIL',
-                          'PAYER_NOT_EQUAL_PAYEE_ERROR', 'EXCEED_LIMIT_DC_RECEIVED', 'PAYER_PERMLIMIT_CHECK_FAILURE',
-                          'PAYEE_ACC_OCUPIED', 'PAYER_PAYEE_CANNOT_SAME', 'PERMIT_CHECK_PERM_LIMITED',
-                          'PERMIT_CHECK_PERM_LIMITED', 'RESOURCE_LIMIT_EXCEED', 'INVALID_PAYER_ACCOUNT',
-                          'EXCEED_LIMIT_DM_MAX_AMOUNT', 'EXCEED_LIMIT_PERSONAL_SM_AMOUNT',
-                          'EXCEED_LIMIT_UNRN_DM_AMOUNT', 'INVALID_CARDNO', 'RELEASE_USER_FORBBIDEN_RECIEVE',
-                          'PAYEE_USER_TYPE_ERROR', 'EXCEED_LIMIT_SM_MIN_AMOUNT', 'PERMIT_CHECK_RECEIVE_LIMIT',
-                          'NOT_IN_WHITE_LIST', 'MONEY_PAY_CLOSED', 'NO_AVAILABLE_PAYMENT_TOOLS',
-                          'PAYEE_NOT_RELNAME_CERTIFY', 'OVERSEA_TRANSFER_CLOSE', 'PAYMENT_FAIL',
-                          'ALREADY_WITHDRAW_STD_RED_PACKET', 'BLOCK_USER_FORBBIDEN_RECIEVE', 'REQUEST_PROCESSING',
-                          'USER_NOT_EXIST', 'PARAM_ILLEGAL', 'PROCESS_FAIL', 'CURRENCY_NOT_SUPPORT',
-                          'PAYER_REQUESTER_RELATION_INVALID']:
+        elif err_code in [
+            'INVALID_PARAMETER', 'EXCEED_LIMIT_SM_AMOUNT', 'EXCEED_LIMIT_MM_AMOUNT', 'PAYCARD_UNABLE_PAYMENT',
+            'PAYER_STATUS_ERROR', 'PAYER_CERTIFY_CHECK_FAIL', 'PAYER_USER_INFO_ERROR', 'PAYMENT_INFO_INCONSISTENCY',
+            'CARD_BIN_ERROR', 'PAYEE_CARD_INFO_ERROR', 'INST_PAY_UNABLE', 'MEMO_REQUIRED_IN_TRANSFER_ERROR',
+            'PERMIT_CHECK_PERM_IDENTITY_THEFT', 'REMARK_HAS_SENSITIVE_WORD', 'EXCEED_LIMIT_DM_AMOUNT',
+            'NO_ACCOUNT_RECEIVE_PERMISSION', 'NO_ACCOUNT_PAYMENT_PERMISSION', 'INVALID_PARAMETER',
+            'PAYER_NOT_EXIST', 'PRODUCT_NOT_SIGN', 'PAYMENT_TIME_EXPIRE', 'PAYEE_NOT_EXIST',
+            'PAYEE_ACCOUNT_STATUS_ERROR', 'PERMIT_NON_BANK_LIMIT_PAYEE', 'PAYEE_TRUSTEESHIP_ACC_OVER_LIMIT',
+            'NO_PERMISSION_ACCOUNT', 'TRUSTEESHIP_ACCOUNT_NOT_EXIST', 'PAYEE_ACCOUNT_NOT_EXSIT',
+            'ORDER_NOT_EXIST', 'PAYEE_USERINFO_STATUS_ERROR', 'TRUSTEESHIP_RECIEVE_QUOTA_LIMIT',
+            'SECURITY_CHECK_FAILED', 'NO_ORDER_PERMISSION', 'ORDER_STATUS_INVALID', 'PERM_AML_NOT_REALNAME_REV',
+            'USER_AGREEMENT_VERIFY_FAIL', 'PAYER_NOT_EQUAL_PAYEE_ERROR', 'EXCEED_LIMIT_DC_RECEIVED',
+            'PAYER_PERMLIMIT_CHECK_FAILURE', 'PAYEE_ACC_OCUPIED', 'PAYER_PAYEE_CANNOT_SAME',
+            'PERMIT_CHECK_PERM_LIMITED', 'RESOURCE_LIMIT_EXCEED', 'INVALID_PAYER_ACCOUNT',
+            'EXCEED_LIMIT_DM_MAX_AMOUNT', 'EXCEED_LIMIT_PERSONAL_SM_AMOUNT', 'EXCEED_LIMIT_UNRN_DM_AMOUNT',
+            'INVALID_CARDNO', 'RELEASE_USER_FORBBIDEN_RECIEVE', 'PAYEE_USER_TYPE_ERROR',
+            'EXCEED_LIMIT_SM_MIN_AMOUNT', 'PERMIT_CHECK_RECEIVE_LIMIT', 'NOT_IN_WHITE_LIST',
+            'MONEY_PAY_CLOSED', 'NO_AVAILABLE_PAYMENT_TOOLS', 'PAYEE_NOT_RELNAME_CERTIFY',
+            'OVERSEA_TRANSFER_CLOSE', 'PAYMENT_FAIL', 'BLOCK_USER_FORBBIDEN_RECIEVE',
+            'REQUEST_PROCESSING', 'USER_NOT_EXIST', 'PARAM_ILLEGAL', 'CURRENCY_NOT_SUPPORT',
+            'PAYER_REQUESTER_RELATION_INVALID', 'AUTHOREE_IS_NOT_MATCH', 'NO_ACCOUNT_USER_FORBBIDEN_RECIEVE',
+            'SIGN_INVALID', 'SIGN_INVOKE_PID_INCONSISTENT', 'SIGN_QUERY_APP_INFO_ERROR',
+            'SIGN_QUERY_AGGREMENT_ERROR', 'SIGN_AGREEMENT_NO_INCONSISTENT', 'SIGN_PARAM_INVALID',
+            'SIGN_NOT_ALLOW_SKIP', 'EXCEED_LIMIT_ENT_SM_AMOUNT', 'ISV_AUTH_ERROR', 'PAYER_USERINFO_NOT_EXSIT',
+            'BLOCK_USER_FORBBIDEN_SEND', 'BIZ_UNIQUE_EXCEPTION', 'NO_ACCOUNTBOOK_PERMISSION',
+            'PERMIT_CHECK_PERM_AML_CERT_EXPIRED', 'MRCHPROD_QUERY_ERROR', 'PERMIT_PAYER_FORBIDDEN',
+            'IDENTITY_FUND_RELATION_NOT_FOUND', 'PERMIT_LIMIT_PAYEE', 'PERM_PAY_USER_DAILY_QUOTA_ORG_BALANCE_LIMIT',
+            'PERM_PAY_USER_MONTH_QUOTA_ORG_BALANCE_LIMIT', 'PERM_PAY_CUSTOMER_DAILY_QUOTA_ORG_BALANCE_LIMIT',
+            'PERM_PAY_CUSTOMER_MONTH_QUOTA_ORG_BALANCE_LIMIT', 'NOT_SUPPORT_PAYER_ACCOUNT_TYPE',
+            'EXCEED_LIMIT_MM_MAX_AMOUNT', 'ILLEGAL_OPERATION',
+        ]:
             return WithdrawResult(False, 0, True, remarks, err_code_des)
         else:
             return WithdrawResult(False, 0, False, remarks,
-                                  u"系统异常,无法获取转账状态。后台会每日定时重试,如果3个工作日仍然是失败状态,请联系客服确认结果(1001)")
+                                  u"系统异常,无法获取转账状态。台会每日定时重试,如果3个工作日仍然是失败状态,请联系客服确认结果(1001)")
 
-    # 调用付款到银行接口
-    try:
-        result = gateway.withdraw_via_bank(total_amount = record.actualPay,
-                                           order_no = record.order,
-                                           bank_card = bankcard)
-        if result['status'] == 'FAIL':
-            return WithdrawResult(False, 0, True, u'支付失败', u'支付失败')
-        elif result['status'] == 'REFUND':
-            return WithdrawResult(False, 0, True, u'银行退单', u'银行退单')
+    @classmethod
+    def common_error_handler(cls, err_code, err_code_des):
+        # type:(str, str)->WithdrawResult
+
+        remarks = u'{err_code_des}({err_code})'.format(err_code = err_code, err_code_des = err_code_des)
+
+        if err_code in ['isp.unknow-error', 'aop.unknow-error', 'isv.app-call-limited', 'isv.method-call-limited']:
+            return WithdrawResult(False, 0, True, remarks, u"商户平台系统繁忙,请过一小时后再试。验证码每天次数有限,频繁提交提现可能消耗完当天验证码次数(1005)")
+        elif 'aop.' in err_code or 'isv.' in err_code or err_code in ['app-cert-expired', 'invalid-auth-relations']:
+            return WithdrawResult(False, 0, True, remarks, err_code_des)
         else:
-            return WithdrawResult(True, 1, False, u'提现申请已经受理', u'提现申请已经受理')
+            return WithdrawResult(False, 0, False, remarks,
+                                  u"系统异常,无法获取转账状态。平台会每日定时重试,如果3个工作日仍然是失败状态,请联系客服确认结果(1002)")
 
-    except AliPayGatewayException as e:
-        logger.error(repr(e))
-        return error_handler(e.errCode, e.errMsg)
+    @classmethod
+    def withdraw_via_bank_in_ali(cls, gateway, record, bankcard):
+        # type: (AliPayWithdrawGateway, WithdrawRecord, WithdrawBankCard)->WithdrawResult
 
-    except AliPayServiceException as e:
-        logger.error(repr(e))
-        return error_handler(e.errCode, e.errMsg)
+        try:
+            result = gateway.withdraw_via_bank(total_amount = record.actualPay,
+                                               order_no = record.order,
+                                               bank_card = bankcard)
+            if result['status'] == 'FAIL':
+                return WithdrawResult(False, 0, True, u'支付失败', u'支付失败')
+            elif result['status'] == 'REFUND':
+                return WithdrawResult(False, 0, True, u'银行退单', u'银行退单')
+            else:
+                return WithdrawResult(True, 1, False, u'提现申请已经受理', u'提现申请已经受理')
 
-    except Exception as e:
-        logger.exception(e)
-        return WithdrawResult(False, 0, False, traceback.format_exc(), u'系统异常,无法确定转账是否成功,请联系客服确认结果(1001)')
+        except AliPayGatewayException as e:
+            logger.error(repr(e))
+            return cls.common_error_handler(e.errCode, e.errMsg)
+
+        except AliPayServiceException as e:
+            logger.error(repr(e))
+            return cls.service_error_handler(e.errCode, e.errMsg)
+
+        except Exception as e:
+            logger.exception(e)
+            return WithdrawResult(False, 0, False, traceback.format_exc(), u'系统异常,无法确定转账是否成功,请联系客服确认结果(1006)')
+
+    @classmethod
+    def withdraw_via_changes_in_ali(cls, gateway, record):
+        # type: (AliPayWithdrawGateway, WithdrawRecord)->WithdrawResult
+        try:
+            result = gateway.withdraw_via_changes(
+                amount = record.actualPay,
+                payOpenId = record.accountCode,
+                order_no = record.order,
+                real_user_name = record.cardUserName)
+
+            if result['status'] == 'SUCCESS':
+                if result['out_biz_no'] != record.order:
+                    logger.warning(
+                        'alipay withdraw order not match. {} != {}'.format(result['out_biz_no'], record.order))
+                    return WithdrawResult(False, 0, False, u'提现失败(订单号错误)', u'提现失败,请联系平台客服解决')
+                else:
+                    return WithdrawResult(True, 1, False, u'提现成功', u'提现成功', result)
+            elif result['status'] == 'FAIL':
+                return WithdrawResult(False, 0, True, u'支付失败', u'支付失败')
+            else:
+                logger.warning('invalid status = {}'.format(result['status']))
+                return WithdrawResult(False, 0, False, u'支付失败(无效状态)', u'支付失败(无效状态)')
+
+        except AliPayGatewayException as e:
+            logger.error(repr(e))
+            return cls.common_error_handler(e.errCode, e.errMsg)
+
+        except AliPayServiceException as e:
+            logger.error(repr(e))
+            return cls.service_error_handler(e.errCode, e.errMsg)
+
+        except Exception as e:
+            logger.exception(e)
+            return WithdrawResult(False, 0, False, traceback.format_exc(), u'系统异常,无法确定转账是否成功,请联系客服确认结果(1001)')
 
 
 def withdraw_via_bank_in_wechat(gateway, record, bankcard):
-    # type: (WechatWithdrawGateway, WithdrawRecord, BankCard)->WithdrawResult
+    # type: (WechatWithdrawGateway, WithdrawRecord, WithdrawBankCard)->WithdrawResult
 
     def error_handler(err_code, err_code_des):
         # type:(str, str)->WithdrawResult
@@ -137,7 +197,7 @@ def withdraw_via_bank_in_wechat(gateway, record, bankcard):
         else:
             return WithdrawResult(
                 False, 0, False, remarks,
-                u"系统异常,无法获取转账状态。台会每日定时重试,如果3个工作日仍然是失败状态,请联系客服确认结果(1002)")
+                u"系统异常,无法获取转账状态。台会每日定时重试,如果3个工作日仍然是失败状态,请联系客服确认结果(1003)")
 
     # 调用付款到银行接口
     try:
@@ -158,16 +218,20 @@ def withdraw_via_bank_in_wechat(gateway, record, bankcard):
         logger.error(repr(e))
         return error_handler(e.errCode, e.errMsg)
 
+    except NotImplementedError as e:
+        logger.error(e)
+        return WithdrawResult(False, 0, True, traceback.format_exc(), u'提现失败')
+
     except Exception as e:
         logger.exception(e)
         return WithdrawResult(False, 0, False, traceback.format_exc(), u'系统异常,无法确定转账是否成功,请联系客服确认结果(1002)')
 
 
 def withdraw_via_bank(gateway, record, bankcard):
-    # type: (WithdrawGateway, WithdrawRecord, BankCard)->(basestring, WithdrawResult)
+    # type: (WithdrawGateway, WithdrawRecord, WithdrawBankCard)->(basestring, WithdrawResult)
 
     if isinstance(gateway, AliPayWithdrawGateway):
-        return 'alipay', withdraw_via_bank_in_ali(gateway, record, bankcard)
+        return 'alipay', AlipayWithdraw.withdraw_via_bank_in_ali(gateway, record, bankcard)
 
     if isinstance(gateway, WechatWithdrawGateway):
         return 'wechat', withdraw_via_bank_in_wechat(gateway, record, bankcard)
@@ -176,7 +240,7 @@ def withdraw_via_bank(gateway, record, bankcard):
 
 
 def withdraw_via_wechat(gateway, record, payOpenId, real_user_name):
-    # type:(cast[WithdrawGateway], WithdrawRecord, str, str)->WithdrawResult
+    # type:(WechatWithdrawGateway, WithdrawRecord, str, str)->WithdrawResult
 
     """
     微信支付到个人微信
@@ -219,7 +283,7 @@ def withdraw_via_wechat(gateway, record, payOpenId, real_user_name):
                 return WithdrawResult(False, 0, True, remarks, err_code_des)
             else:
                 return WithdrawResult(False, 0, False, remarks,
-                                      u"系统异常,无法获取转账状态。台会每日定时重试,如果3个工作日仍然是失败状态,请联系客服确认结果(1010)")
+                                      u"系统异常,无法获取转账状态。台会每日定时重试,如果3个工作日仍然是失败状态,请联系客服确认结果(1005)")
 
         try:
             gateway.withdraw_via_changes(amount = record.actualPay,
@@ -265,7 +329,7 @@ def withdraw_via_wechat(gateway, record, payOpenId, real_user_name):
                 from taskmanager.mediator import task_caller
                 task_caller('withdraw_error_alert', err_type = 'NOTENOUGH')
                 return WithdrawResult(False, 0, True, remarks,
-                                      u"商户平台系统繁忙,请过一小时后再试。验证码每天次数有限,频繁提交提现可能消耗完当天验证码次数(1003)")
+                                      u"商户平台系统繁忙,请过一小时后再试。验证码每天次数有限,频繁提交提现可能消耗完当天验证码次数(1006)")
             elif err_code == 'NAME_MISMATCH':
                 return WithdrawResult(False, 2, True, remarks, u"实名校验失败,请确保经销商名字与账户登录微信一致后重试。")
             elif err_code == 'V2_ACCOUNT_SIMPLE_BAN':
@@ -277,7 +341,7 @@ def withdraw_via_wechat(gateway, record, payOpenId, real_user_name):
                 return WithdrawResult(False, 0, True, remarks, err_code_des)
             else:
                 return WithdrawResult(False, 0, False, remarks,
-                                      u"系统异常,无法获取转账状态。台会每日定时重试,如果3个工作日仍然是失败状态,请联系客服确认结果(1010)")
+                                      u"系统异常,无法获取转账状态。台会每日定时重试,如果3个工作日仍然是失败状态,请联系客服确认结果(1006)")
 
         try:
             gateway.withdraw_via_changes(amount = record.actualPay,
@@ -301,13 +365,14 @@ def withdraw_via_wechat(gateway, record, payOpenId, real_user_name):
 
         except Exception as e:
             logger.exception(e)
-            return WithdrawResult(False, 0, False, traceback.format_exc(), u'系统异常,无法确定转账是否成功,请联系客服确认结果(1003)')
+            return WithdrawResult(False, 0, False, traceback.format_exc(), u'系统异常,无法确定转账是否成功,请联系客服确认结果(1005)')
 
     if gateway.version == 'v3':
         return withdraw_via_v3(gateway, record, payOpenId, real_user_name)
     else:
         return withdraw_via_v1(gateway, record, payOpenId, real_user_name)
 
+
 class WithdrawService(object):
     def __init__(self, payee, income_type, amount, pay_type, bank_card_no = None):
         # type: (CapitalUser, str, RMB, str, Union[str, Optional[str]]) -> None
@@ -345,6 +410,9 @@ class WithdrawService(object):
                 raise ServiceException(
                     {'result': 0, 'description': u"您的账号权限不足或者异常,暂时不能提现。", 'payload': {}})
 
+            if self.payee.abnormal:
+                raise ServiceException({'result': 0, 'description': u'该帐号资金异常,请联系客服处理', 'payload': {}})
+
             if not self.payee.can_withdraw_today:
                 raise ServiceException(
                     {'result': 0, 'description': u"超过每日最大提现次数,请明天再试。", 'payload': {}})
@@ -354,54 +422,52 @@ class WithdrawService(object):
                     if self.payee.sub_balance(self.income_type, source_key) < self.amount:
                         raise ServiceException({'result': 0, 'description': u'余额不足', 'payload': {}})
 
-                    withdraw_gateway_list = Agent.withdraw_gateway_list(source_key)
+                    is_ledger, withdraw_gateway_list = Agent.withdraw_gateway_list(source_key)
 
-                    default_withdraw_gateway = withdraw_gateway_list['wechat']  # type: WechatWithdrawGateway
-                    if not default_withdraw_gateway.ledger:
+                    if not is_ledger:
                         raise ServiceException({'result': 0, 'description': u'暂时不支持提现(1001)', 'payload': {}})
 
                     if self.pay_type == WITHDRAW_PAY_TYPE.WECHAT:
-                        if default_withdraw_gateway.manual_withdraw:
-                            logger.debug('gateway<{}> is manual withdraw.'.format(repr(default_withdraw_gateway)))
+                        if withdraw_gateway_list['wechat'].manual_withdraw:
+                            logger.debug('gateway<{}> is manual withdraw.'.format(repr(withdraw_gateway_list['wechat'])))
                             raise ServiceException({'result': 0, 'description': u'暂时不支持提现(1002)', 'payload': {}})
 
-                        pay_entity = BankCard(
-                            cardNo = self.payee.withdraw_open_id,
-                            holderName = self.payee.nickname,
+                        pay_entity = WithdrawBankCard(
+                            accountCode = self.payee.withdraw_open_id,
+                            accountName = self.payee.withdraw_wechat_real_name,
                             bankName = u'微信',
-                            branchName = u'微信企业付款'
-                        )
+                            branchBankName = u'微信企业付款')
+
+                        wechat_withdraw_gateway = withdraw_gateway_list['wechatV3'] or withdraw_gateway_list['wechat']
 
                         self.record = self.payee.new_withdraw_record(
-                            withdraw_gateway = default_withdraw_gateway,
+                            withdraw_gateway = wechat_withdraw_gateway,
                             pay_entity = pay_entity,
                             source_key = source_key,
                             income_type = self.income_type,
                             amount = self.amount,
                             pay_type = self.pay_type,
-                            manual = default_withdraw_gateway.manual_withdraw,
+                            manual = False,
                             recurrent = recurrent)
                         if not self.record:
                             raise ServiceException({'result': 0, 'description': u'系统繁忙,请稍后再试', 'payload': {}})
 
-                        if self.payee.abnormal:
-                            raise ServiceException({'result': 0, 'description': u'该帐号资金异常,请联系客服处理', 'payload': {}})
-
                         handler = self.payee.new_withdraw_handler(self.record)
 
                         updated = self.payee.freeze_balance(self.record.incomeType,
                                                             self.record.amount,
                                                             self.record.source_key,
-                                                            self.record.order)
+                                                            self.record.order,
+                                                            self.record.is_new_version)
                         if not updated:
                             handler.revoke(remarks = u'扣款失败', description = u'扣款失败')
                             raise ServiceException({'result': 0, 'description': u'扣款失败', 'payload': {}})
 
                         withdraw_result = withdraw_via_wechat(
-                            gateway = default_withdraw_gateway,
+                            gateway = wechat_withdraw_gateway,
                             record = self.record,
                             payOpenId = self.payee.withdraw_open_id,
-                            real_user_name = self.payee.nickname)  # type: WithdrawResult
+                            real_user_name = self.record.cardUserName)  # type: WithdrawResult
 
                         logger.debug(
                             'withdraw via wechat. record = %s; result = %s' % (
@@ -420,24 +486,30 @@ class WithdrawService(object):
                                              description = withdraw_result.show_message)
 
                             raise ServiceException(
-                                {'result': withdraw_result.err_code, 'description': withdraw_result.show_message,
-                                 'payload': {}})
+                                {
+                                    'result': withdraw_result.err_code,
+                                    'description': withdraw_result.show_message,
+                                    'payload': {}
+                                })
+
+                    elif self.pay_type == WITHDRAW_PAY_TYPE.BANK:
+                        if not self.bank_card_no:
+                            raise ServiceException({'result': 0, 'description': u'银行卡号参数错误', 'payload': {}})
 
-                    if self.pay_type == WITHDRAW_PAY_TYPE.BANK:
                         bank_card = self.payee.withdraw_bank_card(self.bank_card_no)
                         if not bank_card:
                             raise ServiceException({'result': 0, 'description': u'银行卡不存在', 'payload': {}})
 
                         manual = False
-                        withdraw_gateway = default_withdraw_gateway
+                        withdraw_gateway = withdraw_gateway_list['wechat']
 
                         if bank_card.manual:
                             manual = True
 
-                        elif bank_card.accountType == BankCard.AccountType.PUBLIC:
+                        elif bank_card.accountType == WithdrawBankCard.AccountType.PUBLIC:
                             if withdraw_gateway_list['alipay']:
-                                withdraw_gateway = withdraw_gateway_list['alipay']  # type: AliPayWithdrawGateway
-                                if not Banks.support_public(bank_card.bankName):
+                                withdraw_gateway = withdraw_gateway_list['alipay']  # type: WithdrawGateway
+                                if not WithdrawBanks.support(bank_card.bankName):
                                     raise ServiceException({
                                         'result': 0,
                                         'description': u'不支持提现到此银行卡或者银行名称错误, 请联系平台客服(1001)',
@@ -445,15 +517,24 @@ class WithdrawService(object):
                             else:
                                 manual = True
                         else:
-                            if default_withdraw_gateway.manual_withdraw:
-                                manual = True
-                            else:
-                                wechat_bank_code = Banks.get_wechat_bank_code(bank_card.bankName)
-                                if not wechat_bank_code:
+                            # if withdraw_gateway_list['alipay'] and self.payee.supports('withdraw_alipay'):
+                            if withdraw_gateway_list['alipay']:
+                                withdraw_gateway = withdraw_gateway_list['alipay']  # type: WithdrawGateway
+                                if not WithdrawBanks.support(bank_card.bankName):
                                     raise ServiceException({
                                         'result': 0,
                                         'description': u'不支持提现到此银行卡或者银行名称错误, 请联系平台客服(1002)',
                                         'payload': {}})
+                            else:
+                                if withdraw_gateway_list['wechat'].manual_withdraw:
+                                    manual = True
+                                else:
+                                    wechat_bank_code = WithdrawBanks.get_wechat_bank_code(bank_card.bankName)
+                                    if not wechat_bank_code:
+                                        raise ServiceException({
+                                            'result': 0,
+                                            'description': u'不支持提现到此银行卡或者银行名称错误, 请联系平台客服(1002)',
+                                            'payload': {}})
 
                         self.record = self.payee.new_withdraw_record(
                             withdraw_gateway = withdraw_gateway,
@@ -473,12 +554,13 @@ class WithdrawService(object):
                         updated = self.payee.freeze_balance(self.record.incomeType,
                                                             self.record.amount,
                                                             self.record.source_key,
-                                                            self.record.order)
+                                                            self.record.order,
+                                                            self.record.is_new_version)
                         if not updated:
                             handler.revoke(remarks = u'扣款失败', description = u'扣款失败')
                             raise ServiceException({'result': 0, 'description': u'扣款失败', 'payload': {}})
 
-                        # 对公的提现, 或者不支持的银行卡(使用manual标记的BankCard 或者 Merchant)
+                        # 对公的提现, 或者不支持的银行卡
                         if manual:
                             withdraw_result = WithdrawResult(True, 1, False, u'提现申请已经受理', u'提现申请已经受理')
 
@@ -518,6 +600,82 @@ class WithdrawService(object):
                                     {'result': withdraw_result.err_code, 'description': withdraw_result.show_message,
                                      'payload': {}})
 
+                    elif self.pay_type == WITHDRAW_PAY_TYPE.ALIPAY:
+                        pay_entity = WithdrawBankCard(
+                            accountCode = self.payee.withdraw_alipay_login_id,
+                            accountName = self.payee.withdraw_alipay_real_name,
+                            bankName = u'支付宝',
+                            branchBankName = u'支付宝企业付款')
+
+                        if not pay_entity.accountCode:
+                            raise ServiceException({
+                                'result': 0,
+                                'description': u'不支持提现到支付宝账号或者是配置错误(1001)',
+                                'payload': {}})
+
+                        withdraw_gateway = withdraw_gateway_list['alipay']  # type: WithdrawGatewayT
+                        if not withdraw_gateway:
+                            raise ServiceException({
+                                'result': 0,
+                                'description': u'不支持提现到支付宝账号或者是配置错误(1002)',
+                                'payload': {}})
+
+                        self.record = self.payee.new_withdraw_record(
+                            withdraw_gateway = withdraw_gateway,
+                            pay_entity = pay_entity,
+                            source_key = source_key,
+                            income_type = self.income_type,
+                            amount = self.amount,
+                            pay_type = self.pay_type,
+                            manual = False,
+                            recurrent = recurrent)  # type: WithdrawRecord
+
+                        if not self.record:
+                            raise ServiceException({'result': 0, 'description': u'系统繁忙,请稍后再试(1002)', 'payload': {}})
+
+                        handler = self.payee.new_withdraw_handler(self.record)
+
+                        updated = self.payee.freeze_balance(self.record.incomeType,
+                                                            self.record.amount,
+                                                            self.record.source_key,
+                                                            self.record.order,
+                                                            self.record.is_new_version)
+                        if not updated:
+                            handler.revoke(remarks = u'扣款失败', description = u'扣款失败')
+                            raise ServiceException({'result': 0, 'description': u'扣款失败', 'payload': {}})
+
+                        withdraw_result = AlipayWithdraw.withdraw_via_changes_in_ali(withdraw_gateway, self.record)
+
+                        logger.debug(
+                            'withdraw via alipay. record = %s; result = %s' % (
+                                self.record.order, repr(withdraw_result)))
+
+                        if withdraw_result.result is True:
+                            finished_time = arrow.get(withdraw_result.callResult['trans_date'], 'YYYY-MM-DD HH:mm:ss',
+                                                      tzinfo = settings.TIME_ZONE).naive
+                            handler.approve(finishedTime = finished_time, extra = {
+                                'order_id': withdraw_result.callResult.get('order_id'),
+                                'pay_fund_order_id': withdraw_result.callResult.get('pay_fund_order_id'),
+                            })
+                            return {
+                                'result': 1,
+                                'description': u'提现申请已经受理',
+                                'payload': {
+                                    'paymentId': str(self.record.id)
+                                }
+                            }
+                        else:
+                            if withdraw_result.refund:
+                                handler.revoke(remarks = withdraw_result.message,
+                                               description = withdraw_result.show_message)
+                            else:
+                                handler.fail(remarks = withdraw_result.message,
+                                             description = withdraw_result.show_message)
+
+                            raise ServiceException(
+                                {'result': withdraw_result.err_code, 'description': withdraw_result.show_message,
+                                 'payload': {}})
+
                     raise ServiceException({'result': 0, 'description': u'系统错误,请稍后再试', 'payload': {}})
                 else:
                     raise ServiceException({'result': 0, 'description': u'操作频繁,请稍后再试', 'payload': {}})
@@ -554,8 +712,8 @@ class WithdrawRetryService(object):
         end = self.record.postTime + datetime.timedelta(days = 3)  # type: datetime
         now = datetime.datetime.now()
 
-        if end.year > now.year or end.month > now.month or end.day > now.day:
-            handler.revoke(remarks = u'平台退单', description = u'提现失败')
+        if now.year > end.year or now.month > end.month or now.day > end.day:
+            handler.revoke(remarks = u'重试次数超限,平台退单', description = u'提现失败')
             raise ServiceException(
                 {
                     'result': 0,
@@ -611,9 +769,9 @@ class WithdrawRetryService(object):
                 self.record.withdrawGatewayKey,
                 self.record.extras.get('gateway_version', 'v1'))  # type: WithdrawGateway
 
-            if self.record.payType == WITHDRAW_PAY_TYPE.WECHAT:
-                handler = payee.new_withdraw_handler(self.record)
+            handler = payee.new_withdraw_handler(self.record)
 
+            if self.record.payType == WITHDRAW_PAY_TYPE.WECHAT:
                 try:
                     query_result = withdraw_gateway.get_transfer_result_via_changes(
                         self.record.order)  # type: WechatWithdrawQueryResult
@@ -651,17 +809,16 @@ class WithdrawRetryService(object):
                                 'payload': {}
                             })
 
-                except WeChatPayException as e:
-                    # 如果业务状态是查不到订单, 需要继续处理, 其他抛出异常
-                    if e.errCode not in ['ORDERNOTEXIST', 'NOT_FOUND']:
-                        raise e
+                except WithdrawOrderNotExist:
+                    logger.warning('withdraw order<orderNo={}> is not exist.'.format(self.record.order))
 
                 self.check_retry_over(handler)
 
                 payee.freeze_balance(self.record.incomeType,
                                      self.record.amount,
                                      self.record.source_key,
-                                     self.record.order)
+                                     self.record.order,
+                                     self.record.is_new_version)
 
                 withdraw_result = withdraw_via_wechat(withdraw_gateway,
                                                       self.record,
@@ -689,18 +846,15 @@ class WithdrawRetryService(object):
                             'payload': {}
                         })
 
-            if self.record.payType == WITHDRAW_PAY_TYPE.BANK:
-                handler = payee.new_withdraw_handler(self.record)
-
+            elif self.record.payType == WITHDRAW_PAY_TYPE.BANK:
                 try:
                     query_result = withdraw_gateway.get_transfer_result_via_bank(
-                        self.record.order)  # type: WechatWithdrawQueryResult
+                        self.record.order)  # type: Union[WechatWithdrawQueryResult, AlipayWithdrawQueryResult]
 
                     errcode, errmsg = query_result.error_desc
 
-                    if query_result.is_failed:
-                        handler.revoke(remarks = errcode,
-                                       description = errmsg)
+                    if query_result.is_failed or query_result.is_refund:
+                        handler.revoke(remarks = errcode, description = errmsg)
                         return {
                             'result': 1,
                             'description': errmsg,
@@ -710,7 +864,7 @@ class WithdrawRetryService(object):
                         }
 
                     if query_result.is_successful:
-                        handler.approve()
+                        handler.approve(finishedTime = query_result.finished_time, extra = query_result.extra)
                         return {
                             'result': 1,
                             'description': 'SUCCESS',
@@ -729,10 +883,8 @@ class WithdrawRetryService(object):
                                 'payload': {}
                             })
 
-                except (WeChatPayException, AliPayServiceException) as e:
-                    # 如果业务状态是查不到订单, 需要继续处理, 否则继续抛出异常
-                    if e.errCode not in ['ORDERNOTEXIST', 'NOT_FOUND', 'ORDER_NOT_EXIST']:
-                        raise e
+                except WithdrawOrderNotExist:
+                    logger.warning('withdraw order<orderNo={}> is not exist.'.format(self.record.order))
 
                 self.check_retry_over(handler)
 
@@ -748,7 +900,8 @@ class WithdrawRetryService(object):
                 payee.freeze_balance(self.record.incomeType,
                                      self.record.amount,
                                      self.record.source_key,
-                                     self.record.order)
+                                     self.record.order,
+                                     self.record.is_new_version)
 
                 gateway_type, withdraw_result = withdraw_via_bank(withdraw_gateway, self.record, bank_card)
 
@@ -772,6 +925,93 @@ class WithdrawRetryService(object):
                             'description': withdraw_result.show_message,
                             'payload': {}
                         })
+
+            elif self.record.payType == WITHDRAW_PAY_TYPE.ALIPAY:
+                try:
+                    query_result = withdraw_gateway.get_transfer_result_via_changes(
+                        self.record.order)  # type: AlipayWithdrawQueryResult
+
+                    errcode, errmsg = query_result.error_desc
+
+                    if query_result.is_failed:
+                        handler.revoke(remarks = errcode, description = errmsg)
+                        return {
+                            'result': 1,
+                            'description': errmsg,
+                            'payload': {
+                                'paymentId': str(self.record.id)
+                            }
+                        }
+
+                    if query_result.is_successful:
+                        handler.approve(finishedTime = query_result.finished_time, extra = query_result.extra)
+                        return {
+                            'result': 1,
+                            'description': 'SUCCESS',
+                            'payload': {
+                                'paymentId': str(self.record.id)
+                            }
+                        }
+
+                    if query_result.is_processing:
+                        pass
+                    else:
+                        raise ServiceException(
+                            {
+                                'result': 0,
+                                'description': u'未知订单状态{}'.format(query_result.order_status),
+                                'payload': {}
+                            })
+                except WithdrawOrderNotExist:
+                    logger.warning('withdraw order<orderNo={}> is not exist.'.format(self.record.order))
+
+                self.check_retry_over(handler)
+
+                updated = payee.freeze_balance(self.record.incomeType,
+                                               self.record.amount,
+                                               self.record.source_key,
+                                               self.record.order,
+                                               self.record.is_new_version)
+                if not updated:
+                    handler.revoke(remarks = u'扣款失败', description = u'扣款失败')
+                    raise ServiceException({'result': 0, 'description': u'扣款失败', 'payload': {}})
+
+                withdraw_result = AlipayWithdraw.withdraw_via_changes_in_ali(withdraw_gateway, self.record)
+
+                logger.debug(
+                    'withdraw via alipay. record = %s; result = %s' % (
+                        self.record.order, repr(withdraw_result)))
+
+                if withdraw_result.result is True:
+                    finished_time = arrow.get(withdraw_result.callResult['trans_date'], 'YYYY-MM-DD HH:mm:ss',
+                                              tzinfo = settings.TIME_ZONE).naive
+                    handler.approve(finishedTime = finished_time, extra = {
+                        'order_id': withdraw_result.callResult.get('order_id'),
+                        'pay_fund_order_id': withdraw_result.callResult.get('pay_fund_order_id'),
+                    })
+                    return {
+                        'result': 1,
+                        'description': u'提现申请已经受理',
+                        'payload': {
+                            'paymentId': str(self.record.id)
+                        }
+                    }
+                else:
+                    if withdraw_result.refund:
+                        handler.revoke(remarks = withdraw_result.message,
+                                       description = withdraw_result.show_message)
+                    else:
+                        handler.fail(remarks = withdraw_result.message,
+                                     description = withdraw_result.show_message)
+
+                    raise ServiceException(
+                        {
+                            'result': withdraw_result.err_code,
+                            'description': withdraw_result.show_message,
+                            'payload': {}
+                        })
+            else:
+                logger.error('invalid withdraw type: {}'.format(self.record.payType))
         except WechatNetworkException as e:
             logger.exception(e)
             return {'result': 0, 'description': e.errMsg, 'payload': {}}

+ 18 - 5
apps/web/common/urls.py

@@ -24,9 +24,6 @@ urlpatterns = patterns('', *[
     # 获取展示通告
     url(r'^getNotifications$', getNotifications, name='getNotifications'),
 
-    # 根据卡号获取银行名字
-    url(r'^getCardBankNameType$', getCardBankNameType, name='getCardBankNameType'),
-
     url(r'^getVersion$', getVersion, name='getVersion'),
 
     url(r'^generateNewAppKeyPair$', generateNewAppKeyPair, name='generateNewAppKeyPair'),
@@ -45,8 +42,24 @@ urlpatterns = patterns('', *[
 
     url(r'^getReceipt$', getReceipt, name="getReceipt"),
 
-    url(r'^banks/list$', supportedBanks, name="supportedBanks"),
-
     url(r'^getPowerGraph$', getPowerGraphByDevice, name="getPowerGraphByDevice"),
+
+    url(r'^withdraw/bank/list$', supportedWithdrawBanks, name="supportedWithdrawBanks"),
+
+    url(r'^withdraw/bank/byAccount$', getWithdrawBankByAccount, name= "getWithdrawBankByAccount"),
+
+    url(r'^withdraw/area/list', loadWithdrawArea, name='loadWithdrawArea'),
+
+    url(r'^withdraw/branchBank/list', withdrawBranchBankList, name='withdrawBranchBankList'),
+
+    url(r'^withdraw/bankCard/list', getWithdrawBankCards, name='getWithdrawBankCards'),
+
+    url(r'^withdraw/bankCard/save', saveWithdrawBankCard, name='saveWithdrawBankCard'),
+
+    url(r'^withdraw/bankCard/unbind', unbindWithdrawBankCard, name='unbindWithdrawBankCard'),
+
+    url(r'^withdraw/alipay/save', saveAlipayWithdrawInfo, name= 'saveAlipayWithdrawAccount'),
+
+    url(r'^withdraw/wechat/save', saveWechatWithdrawInfo, name= 'saveWechatWithdrawInfo')
 ])
 

+ 173 - 52
apps/web/common/views.py

@@ -17,6 +17,7 @@ from django.views.decorators.gzip import gzip_page
 from django.views.generic.base import View
 from pymongo.results import UpdateResult
 from typing import TYPE_CHECKING, Iterable, Dict
+from voluptuous import MultipleInvalid
 
 from apilib.monetary import RMB
 from apilib.utils import generate_RSA_key_pairs
@@ -26,7 +27,8 @@ from apps import serviceCache
 from apps.web import district
 from apps.web.ad.models import Advertisement, AdStatistics
 from apps.web.agent.models import MoniApp
-from apps.web.common.models import AddressType, WithdrawRecord, Banks
+from apps.web.common.models import AddressType, WithdrawRecord, WithdrawBanks, WithdrawDistrict, \
+    WithdrawBranchBanks, WithdrawBankCard
 from apps.web.common.models import FrontendLog, FAQ
 from apps.web.common.transaction.pay.alipay import AliPayWithdrawNotifier
 from apps.web.common.utils import WechatMessage, WechatText, WechatMenu, WechatSubscribe, WechatUnSubscribe, \
@@ -35,7 +37,7 @@ from apps.web.constant import Const, AppPlatformType, AdSpace, USER_RECHARGE_TYP
 from apps.web.core import ROLE
 from apps.web.core.exceptions import InvalidFileSize, InvalidFileName
 from apps.web.core.file import AliOssFileUploader, WechatSubscriptionAccountVerifyFileUploader
-from apps.web.core.models import WechatPayApp, AliApp, BankCard
+from apps.web.core.models import WechatPayApp, AliApp
 from apps.web.core.payment.ali import AliPayGateway
 from apps.web.core.utils import DefaultJsonErrorResponse, JsonOkResponse, JsonErrorResponse
 from apps.web.dealer.models import Dealer
@@ -46,6 +48,8 @@ from apps.web.user.models import MyUser
 from apps.web.user.utils import get_consume_order
 from apps.web.utils import error_tolerate, permission_required, concat_dealer_main_page_url, \
     concat_front_end_url, concat_user_center_url, concat_user_login_entry_url
+from apps.web.validation import SaveWithdrawBankCardSchema, SaveWithdrawAlipaySchema, SaveWithdrawWechatSchema
+from library.misc import BankAPI
 
 if TYPE_CHECKING:
     from apps.web.core.db import RoleBaseDocument
@@ -56,10 +60,8 @@ if TYPE_CHECKING:
 logger = logging.getLogger(__name__)
 
 
+@permission_required(ROLE.dealer, ROLE.agent, ROLE.supermanager)
 def loadDistrictData(request):
-    if not request.user.is_authenticated():
-        return HttpResponse('Unauthorized', status = 401)
-
     return JsonResponse({'result': 1, 'description': None, 'payload': district.DISTRICT})
 
 
@@ -184,38 +186,6 @@ def uploadFile(request):
         return JsonResponse({'result': 0, 'description': e.message, 'payload': {}})
 
 
-@error_tolerate(nil = DefaultJsonErrorResponse, logger = logger)
-@permission_required(ROLE.dealer, ROLE.agent, ROLE.subaccount)
-def getCardBankNameType(request):
-    cardNo = request.GET.get('cardNo', '')
-    if not cardNo:
-        return JsonResponse({"result": 0, "description": u"银行卡号不能为空", 'payload': {}})
-
-    try:
-        bank = Banks.get_bank_info(cardNo)
-
-        logger.debug(str(bank))
-
-        if not bank:
-            return JsonResponse({"result": -1, "description": "不支持该银行绑定,目前仅支持列表中银行", 'payload': {}})
-        else:
-            bank_name = bank.get('bankName', '')
-            if not bank_name:
-                return JsonResponse({"result": -1, "description": "不支持该银行绑定,目前仅支持列表中银行", 'payload': {}})
-            else:
-                return JsonResponse({
-                    "result": 1,
-                    "description": None,
-                    'payload': {
-                        'bankName': bank_name,
-                        'cardType': bank['cardType']
-                    }
-                })
-
-    except Exception as e:
-        return JsonResponse({"result": 0, "description": u"查询银行卡信息失败,请刷新", 'payload': {}})
-
-
 @error_tolerate(nil = DefaultJsonErrorResponse, logger = logger)
 @permission_required(ROLE.manager, ROLE.advertisement, ROLE.advertiser, ROLE.dealer, ROLE.agent, ROLE.subaccount)
 def getAddressType(request):
@@ -699,19 +669,6 @@ def getReceipt(request): # type: (WSGIRequest)->JsonResponse
     return JsonOkResponse(payload=payload)
 
 
-@error_tolerate(logger = logger, nil = JsonErrorResponse(description = u"获取数据失败,请刷新"))
-# @permission_required(ROLE.dealer, ROLE.agent, ROLE.supermanager)
-@gzip_page
-def supportedBanks(request):
-    # type: (WSGIRequest)->JsonResponse
-
-    account_type = request.GET.get('accountType')
-    if account_type == BankCard.AccountType.PUBLIC:
-        return JsonOkResponse(payload = Banks.get_public_banks())
-    else:
-        return JsonOkResponse(payload = Banks.get_personal_banks())
-
-
 def bolaiEvent(request):
     payload = json.loads(request.body)
 
@@ -761,6 +718,7 @@ def bolaiEvent(request):
 
     return JsonOkResponse()
 
+
 def bolaitenEvent(request):
     payload = json.loads(request.body)
     logger.debug('received bolai event: {}'.format(payload))
@@ -776,6 +734,7 @@ def bolaitenEvent(request):
 
     return JsonOkResponse()
 
+
 @error_tolerate(nil = DefaultJsonErrorResponse)
 @permission_required(ROLE.dealer, ROLE.supermanager, ROLE.myuser)
 def getPowerGraphByDevice(request):
@@ -792,8 +751,6 @@ def getPowerGraphByDevice(request):
 
     items = PowerManager.instence().get(devNo = device.devNo, port = port, sTime = startTime, eTime = endTime,
                                         interval = interval)
-
-
     return JsonOkResponse(payload = {'dataList': items})
 
 
@@ -874,4 +831,168 @@ class WechatFollowView(View):
         return response
 
 
+@permission_required(ROLE.dealer, ROLE.agent, ROLE.supermanager)
+def loadWithdrawArea(request):
+    province = request.GET.get("province", None)
+    return JsonOkResponse(payload = {'dataList': WithdrawDistrict.get_area(province)})
+
+
+@error_tolerate(logger = logger, nil = JsonErrorResponse(description = u"获取数据失败,请刷新"))
+@permission_required(ROLE.dealer, ROLE.agent, ROLE.supermanager)
+def supportedWithdrawBanks(request):
+    # type: (WSGIRequest)->JsonResponse
+
+    keyWord = request.GET.get('keyWord', None)
+    chooseRange = request.GET.get('range', 'wechat')
+    if chooseRange == 'all':
+        return JsonOkResponse(payload = {'dataList': WithdrawBanks.get_all_banks(keyWord)})
+    elif chooseRange == 'wechat':
+        return JsonOkResponse(payload = {'dataList': WithdrawBanks.get_wechat_support_banks(keyWord)})
+    else:
+        return JsonErrorResponse(description = u'参数错误')
+
+
+@error_tolerate(logger = logger, nil = JsonErrorResponse(description = u"获取数据失败,请刷新"))
+@permission_required(ROLE.dealer, ROLE.agent, ROLE.supermanager)
+def getWithdrawBankByAccount(request):
+    # type: (WSGIRequest)->JsonResponse
+
+    accountCode = request.GET.get('accountCode', None)
+    if not accountCode:
+        return JsonResponse({'result': 2, 'description': u'银行账号不能为空', 'payload': {}})
+
+    bank_info = BankAPI().get_bank_card_info(card_no = accountCode)
+    if bank_info:
+        rv = WithdrawBanks.get_by_bank_abbrev_code(bank_info['bankAbbrevCode'])
+        if rv:
+            return JsonOkResponse(payload = rv)
+        else:
+            logger.warning('not find this bank: {}'.format(bank_info))
+            return JsonResponse({'result': 2, 'description': u'该银行卡不支持', 'payload': {}})
+    else:
+        return JsonResponse({'result': 2, 'description': u'没有查到该银行账号信息', 'payload': {}})
+
+
+@error_tolerate(logger = logger, nil = JsonErrorResponse(description = u"获取数据失败,请刷新"))
+@permission_required(ROLE.dealer, ROLE.agent, ROLE.supermanager)
+def withdrawBranchBankList(request):
+    # type: (WSGIRequest)->JsonResponse
+
+    provinceCode = request.GET.get('provinceCode')
+    cityCode = request.GET.get('cityCode')
+    bankCode = request.GET.get('bankCode')
+    keyWord = request.GET.get('keyWord')
+
+    queryset = WithdrawBranchBanks.objects(**{
+        'provinceCode': provinceCode,
+        'cityCode': cityCode,
+        'bankCode': bankCode
+    }).search(keyWord)
+
+    rv = [
+        {
+            'code': item.code,
+            'name': item.name
+        } for item in queryset
+    ]
+    return JsonOkResponse(payload = {'dataList': rv})
+
+
+@error_tolerate(nil = DefaultJsonErrorResponse)
+@permission_required(ROLE.dealer, ROLE.agent)
+def saveWithdrawBankCard(request):
+    # type: (WSGIRequest)->JsonResponse
+    """
+    添加银行卡
+    """
+
+    ownerId = str(request.user.id)
+
+    if WithdrawBankCard.objects.filter(ownerId = ownerId, role = request.user.role).count() > 5:
+        return JsonResponse({"result": 2, "description": u"银行卡数量已添加至上限(5),不允许继续添加"})
+
+    try:
+        payload = SaveWithdrawBankCardSchema(json.loads(request.body))
+    except MultipleInvalid as e:
+        logger.exception(e)
+        return JsonErrorResponse(description = u"信息不完整")
+
+    if WithdrawBankCard.objects(ownerId = ownerId, role = request.user.role,
+                                accountCode = payload['accountCode']).first():
+        return JsonResponse({"result": 2, "description": u"请勿重复添加"})
+
+    accountType = payload.pop('accountType')
+    if accountType == WithdrawBankCard.AccountType.PERSONAL:
+        item = WithdrawBankCard.new_personal_withdraw_bank_card(ownerId = ownerId, role = request.user.role, **payload)
+        return JsonOkResponse(payload = {"id": str(item.id)})
+    else:
+        return JsonErrorResponse(description = u"不支持添加对公提现银行卡,请您开通商户。")
+
+
+@error_tolerate(nil = DefaultJsonErrorResponse)
+@permission_required(ROLE.dealer, ROLE.agent)
+def unbindWithdrawBankCard(request):
+    # type: (WSGIRequest)->JsonResponse
+
+    ownerId = str(request.user.id)
+
+    payload = json.loads(request.body)
+
+    accountCode = payload.get('accountCode')
+
+    if not accountCode:
+        return JsonResponse({"result": 2, "description": u"参数错误,请刷新后重试"})
+
+    try:
+        card = WithdrawBankCard.objects.get(ownerId = ownerId, role = request.user.role, accountCode = accountCode)
+    except Exception as e:
+        logger.exception(e)
+        return JsonResponse({"result": 2, "description": u"解绑定失败,请联系客服解决。"})
+    else:
+        card.delete()
+        return JsonOkResponse()
+
+
+@error_tolerate(nil = DefaultJsonErrorResponse)
+@permission_required(ROLE.dealer, ROLE.agent)
+def getWithdrawBankCards(request):
+    # type: (WSGIRequest)->JsonResponse
+
+    ownerId = str(request.user.id)
+
+    bankCards = WithdrawBankCard.objects.filter(ownerId = ownerId, role = request.user.role)
+    dataList = list()
+    for bankCard in bankCards:
+        dataList.append(bankCard.to_dict())
+
+    return JsonOkResponse(payload = {'dataList': dataList})
+
+
+@error_tolerate(nil = DefaultJsonErrorResponse)
+@permission_required(ROLE.dealer, ROLE.agent)
+def saveAlipayWithdrawInfo(request):
+    # type: (WSGIRequest)->JsonResponse
 
+    try:
+        payload = SaveWithdrawAlipaySchema(json.loads(request.body))
+    except MultipleInvalid as e:
+        logger.exception(e)
+        return JsonErrorResponse(description = u"参数错误")
+    else:
+        request.user.set_withdraw_alipay(**payload)
+        return JsonOkResponse()
+
+
+@error_tolerate(nil = DefaultJsonErrorResponse)
+@permission_required(ROLE.dealer, ROLE.agent)
+def saveWechatWithdrawInfo(request):
+    # type: (WSGIRequest)->JsonResponse
+
+    try:
+        payload = SaveWithdrawWechatSchema(json.loads(request.body))
+    except MultipleInvalid as e:
+        logger.exception(e)
+        return JsonErrorResponse(description = u"参数错误")
+    else:
+        request.user.set_withdraw_wechat(**payload)
+        return JsonOkResponse()

+ 5 - 27
apps/web/core/__init__.py

@@ -10,11 +10,13 @@ from django.utils.module_loading import import_string
 from typing import TYPE_CHECKING, Optional, cast, Union
 from apilib.systypes import IterConstant
 from apps.web.constant import AppPlatformType
+from library.jdopen import JDOpenErrorCode, BankType, JdOpenException
+from library.jd.pay import PiType
 from library.ys.base import PayOpt
 
 if TYPE_CHECKING:
     from apps.web.core.models import WechatPayApp, PayAppBase, EmbeddedApp, WechatMiniApp
-
+    from apps.web.common.models import CapitalUser
 
 logger = logging.getLogger(__name__)
 
@@ -23,22 +25,14 @@ class PayAppType(IterConstant):
     # 资金池模式只使用ALIPAY,WECHAT,JD_AGGR三种支付方式
     ALIPAY = 'alipay'
     WECHAT = 'wechat'
-    JD_AGGR = 'jdaggre'
 
     # 蓝牙特殊处理, 使用小程序绑定的支付方式, 后续可以迁移到京东聚合支付
     WECHAT_MINI = 'wechat_mini'
 
     # 各经销商可使用的支付方式(包括JD_AGGR以及ALIPAY+WECHAT)
-    SAOBEI = 'saobei'
-    JD = 'jd'
-    DLB = 'dlb'
-    YS = 'ys'
-    JOS = "jos"
-    RCU = "rcu"
     PLATFORM_PROMOTION = "platform_promotion"
     PLATFORM_WALLET = 'platform_wallet'
     PLATFORM_RECONCILE ='platform_reconcile'
-    JD_OPEN = 'jdopen'
     MANUAL = 'manual'
 
     SWAP = 'swap'
@@ -49,37 +43,21 @@ PAY_APP_TYPE_TRANSLATION = {
     AppPlatformType.ALIPAY: u'支付宝',
     AppPlatformType.WECHAT: u'微信',
     PayAppType.WECHAT_MINI: u'微信小程序',
-    PayAppType.SAOBEI: u'扫呗聚合支付',
-    PayAppType.JD_AGGR: u'京东聚合支付',
-    PayAppType.DLB: u'哆啦宝聚合支付',
-    PayAppType.YS: u'易生聚合支付',
-    PayAppType.RCU: u'河南农村信用社',
-    PayAppType.JD_OPEN: u'京东聚合支付'
 }
 
-
 PAY_APP_MAP = {
     PayAppType.ALIPAY: 'apps.web.core.models.AliApp',
     PayAppType.WECHAT: 'apps.web.core.models.WechatPayApp',
-    PayAppType.WECHAT_MINI: 'apps.web.core.models.WechatMiniApp',
-    PayAppType.SAOBEI: 'apps.web.core.models.SaobeiPayApp',
-    PayAppType.JD_AGGR: 'apps.web.core.models.JDAggrePayApp',
-    PayAppType.DLB: 'apps.web.core.models.DlbPayApp',
-    PayAppType.YS: 'apps.web.core.models.YsPayApp',
-    PayAppType.RCU: 'apps.web.core.models.RuralCreditUnionApp',
+    PayAppType.WECHAT_MINI: 'apps.web.core.models.WechatMiniApp'
     PayAppType.PLATFORM_PROMOTION: 'apps.web.core.models.PlatformPromotionApp',
     PayAppType.PLATFORM_WALLET: 'app.web.core.models.PlatformWalletApp',
     PayAppType.PLATFORM_RECONCILE: 'app.web.core.models.PlatformReconcileApp',
-    PayAppType.JD_OPEN: 'apps.web.core.models.JDOpenPayApp',
     PayAppType.MANUAL: 'apps.web.core.models.ManualPayApp'
 }
 
-
 APP_KEY_DELIMITER = '-'
 
-
-def wechat_bound_openid_key(appid):
-    return APP_KEY_DELIMITER.join([AppPlatformType.WECHAT, appid])
+wechat_bound_openid_key = lambda appid: APP_KEY_DELIMITER.join([AppPlatformType.WECHAT, appid])
 
 
 class BaseAppProxy(object):

+ 16 - 15
apps/web/core/accounting.py

@@ -17,9 +17,14 @@ if TYPE_CHECKING:
 
 logger = logging.getLogger(__name__)
 
-#: 生成模板函数,预置日期选择
+
 def dated_key_tmpl(template):
-    # type: (str)->Callable[str]
+    # type: (str)->Callable
+    """
+    生成模板函数,预置日期选择
+    :param template:
+    :return:
+    """
     def inner(**kwargs):
         date = datetime.datetime.now().strftime(Const.DATE_FMT)  # type: str
         kwargs.update({'nowDate': date})
@@ -29,7 +34,7 @@ def dated_key_tmpl(template):
 
 
 def build_tmpl(tmpl):
-    # type: (Enum)->Callable[str]
+    # type: (Enum)->Callable
     return dated_key_tmpl(tmpl.value)
 
 
@@ -103,23 +108,21 @@ class Accounting(object):
                                           port = port)
 
         try:
-            set_or_incr_cache(reportCache, device_key, coins)
+            set_or_incr_cache(reportCache, device_key, coins, 48 * 60 * 60)
 
             groupId = device.get('groupId', None)
             if not groupId:
                 return
 
-            set_or_incr_cache(reportCache, groupCoinTmpl(groupId, report_day), coins, 60 * 60 * 72)
+            set_or_incr_cache(reportCache, groupCoinTmpl(groupId, report_day), coins, 48 * 60 * 60)
 
-            set_or_incr_cache(reportCache, ownerCoinTmpl(device['ownerId'], report_day), coins,
-                              60 * 60 * 72)
+            set_or_incr_cache(reportCache, ownerCoinTmpl(device['ownerId'], report_day), coins, 48 * 60 * 60)
 
             from apps.web.device.models import Group
             group = Group.get_group(groupId)
             if group and group['partnerDict']:
                 for partnerId, partner in group['partnerDict'].items():
-                    set_or_incr_cache(reportCache, ownerCoinTmpl(partnerId, report_day), coins,
-                                      60 * 60 * 72)
+                    set_or_incr_cache(reportCache, ownerCoinTmpl(partnerId, report_day), coins, 48 * 60 * 60)
 
         finally:
             from apps.web.report.models import DevReport
@@ -153,7 +156,7 @@ class Accounting(object):
             else:
                 old = int(float(old))
 
-            if reportCache.cas(device_key, str(today_coins), version_token, timeout = 60 * 60 * 72):
+            if reportCache.cas(device_key, str(today_coins), version_token, timeout = 48 * 60 * 60):
                 logger.debug('set coins succeed. diff = {}'.format((today_coins - old)))
                 break
 
@@ -175,17 +178,15 @@ class Accounting(object):
             if not groupId:
                 return
 
-            set_or_incr_cache(reportCache, groupCoinTmpl(groupId, report_day), difference_coins, 60 * 60 * 72)
+            set_or_incr_cache(reportCache, groupCoinTmpl(groupId, report_day), difference_coins, 48 * 60 * 60)
 
-            set_or_incr_cache(reportCache, ownerCoinTmpl(device['ownerId'], report_day), difference_coins,
-                              60 * 60 * 72)
+            set_or_incr_cache(reportCache, ownerCoinTmpl(device['ownerId'], report_day), difference_coins, 48 * 60 * 60)
 
             from apps.web.device.models import Group
             group = Group.get_group(groupId)
             if group and group['partnerDict']:
                 for partnerId, partner in group['partnerDict'].items():
-                    set_or_incr_cache(reportCache, ownerCoinTmpl(partnerId, report_day), difference_coins,
-                                      60 * 60 * 72)
+                    set_or_incr_cache(reportCache, ownerCoinTmpl(partnerId, report_day), difference_coins, 48 * 60 * 60)
 
         finally:
             # 有数据变更的时候, 把离线投币数更新到数据库. 始终以设备上报的为准

+ 8 - 0
apps/web/core/bridge/wechat/v3api.py

@@ -274,6 +274,14 @@ class WechatApiProxy(object):
     def get_merchant_audit(self, applyId=None, busCode=None):
         """
         当服务商提交申请单后,需要定期调用此接口查询申请单的审核状态
+        APPLYMENT_STATE_WAITTING_FOR_AUDIT:【审核中】,请耐心等待3~7个工作日,微信支付将会完成审核。
+        APPLYMENT_STATE_EDITTING:【编辑中】,可能提交申请发生了错误导致,可用同一个业务申请编号重新提交。
+        APPLYMENT_STATE_WAITTING_FOR_CONFIRM_CONTACT:【待确认联系信息】,请扫描微信支付返回的二维码确认联系信息(此过程可修改超级管理员手机号)。
+        APPLYMENT_STATE_WAITTING_FOR_CONFIRM_LEGALPERSON:【待账户验证】,请扫描微信支付返回的二维码在小程序端完成账户验证。
+        APPLYMENT_STATE_PASSED:【审核通过】,请扫描微信支付返回的二维码在小程序端完成授权流程。
+        APPLYMENT_STATE_REJECTED:【审核驳回】,请按照驳回原因修改申请资料,并更换业务申请编码,重新提交申请。
+        APPLYMENT_STATE_FREEZED:【已冻结】,可能是该主体已完成过入驻,请查看驳回原因,并通知驳回原因中指定的联系人扫描微信支付返回的二维码在小程序端完成授权流程。
+        8、APPLYMENT_STATE_CANCELED:【已作废】,表示申请单已被撤销,无需再对其进行操作。
         """
         path = "/v3/apply4subject/applyment"
         data= {}

+ 1 - 1
apps/web/core/db.py

@@ -420,7 +420,7 @@ class CustomQuerySet(QuerySet):
         if fields is None:
             fields = self._document.search_fields
             try:
-                assert len(fields) > 1
+                assert len(fields) >= 1
             except (AssertionError, TypeError):
                 raise ImproperlyConfigured(
                     'in order to search, search_fields has to be defined(length > 1) or supplied, model = %r' % self._document

+ 3 - 2
apps/web/core/messages/__init__.py

@@ -1,6 +1,7 @@
 # -*- coding: utf-8 -*-
-#!/usr/bin/env python
+# !/usr/bin/env python
 
 class SmsVendorCode(object):
     UCPAAS = 'ucpaas'
-    ALIYUN = 'aliyun'
+    ALIYUN = 'aliyun'
+    ZTHY = 'zthy'

+ 8 - 25
apps/web/core/messages/sms.py

@@ -20,21 +20,7 @@ logger = logging.getLogger(__name__)
 
 
 class SMSSender(object):
-    TEMPLATE_MAP = {
-        SmsVendorCode.UCPAAS: {
-            'CAPTCHA_TEMPLATE_ID': '544642',
-            'SMS_NOTIFY_EXPIRED_DEVICE_TEMPLATEID': '384328',
-            'EDIT_MONITOR_ID': '584806',
-            'DEALER_MONITOR_WITHDRAW_ID': '584807',
-            'MERCHANT_NOTIFY': '611184'
-        },
-        SmsVendorCode.ALIYUN: {
-            'CAPTCHA_TEMPLATE_ID': 'SMS_190790529',
-            'SMS_NOTIFY_EXPIRED_DEVICE_TEMPLATEID': 'SMS_190784668',
-            'EDIT_MONITOR_ID': 'SMS_211215112',
-            'DEALER_MONITOR_WITHDRAW_ID': 'SMS_211230116',
-        }
-    }
+    TEMPLATE = None
 
     """
     `ref` http://docs.ucpaas.com/doku.php?id=%E7%9F%AD%E4%BF%A1%E9%AA%8C%E8%AF%81:sms_demo
@@ -55,6 +41,7 @@ class SMSSender(object):
     def send(self, phoneNumber, templateName, msg, productName = u'管理后台'.encode('utf-8'), verifyCode = False):
         """
 
+        :param verifyCode:
         :param phoneNumber:
         :param templateName:
         :param msg:
@@ -67,22 +54,19 @@ class SMSSender(object):
 
         if self.vendor == SmsVendorCode.UCPAAS:
             from library.sms.ucpaas import Sender as UcpassSmsSender
-            sender = UcpassSmsSender(appid = '06268c58ffcf412e875c38663b819495',
-                                     sid = '0dcff57b45a868c31a74ae5bf1488aa8',
-                                     token = '52917c39382fbebda57e0a23be265357')  # type: UcpassSmsSender
+            sender = UcpassSmsSender()  # type: UcpassSmsSender
         elif self.vendor == SmsVendorCode.ALIYUN:
             from library.sms.aliyun import Sender as AliyunSmsSender
-            sender = AliyunSmsSender(ak = 'LTAI4GEc1j8pvs4EjFrtL5K9',
-                                     secret = 'KuCEo8YWRn7tjQaJsXJCcG7P4leBMr',
-                                     region_id = 'cn-hangzhou')  # type: AliyunSmsSender
+            sender = AliyunSmsSender()  # type: AliyunSmsSender
+        elif self.vendor == SmsVendorCode.ZTHY:
+            from library.sms.zthy import Sender as ZTHYSmsSender
+            sender = ZTHYSmsSender()
         else:
             logger.error('not support the sms platform.')
             return {'result': False, 'msg': '系统错误,请联系平台客服'}
 
-        templateId = self.TEMPLATE_MAP[self.vendor][templateName]
-
         return sender.send(phoneNumber = phoneNumber,
-                           templateId = templateId,
+                           templateName = templateName,
                            msg = msg,
                            productName = productName,
                            verifyCode = verifyCode)
@@ -257,7 +241,6 @@ class JDMerchantSMSProvider(GenericSMSProvider):
         return super(JDMerchantSMSProvider, self).verify(phoneNumber, smsCode)
 
 
-
 dealerRegisterSMSProvider = RegisterSMSProvider('dealer-reg')
 dealerForgotPwdSMSProvider = ForgotPwdSMSProvider('dealer-forgot-pwd')
 dealerWithdrawSMSProvider = WithdrawSMSProvider('dealer-withdraw')

+ 16 - 534
apps/web/core/models.py

@@ -1177,427 +1177,6 @@ class JosShopInfo(EmbeddedDocument):
     brandName = StringField(verbose_name = u"品牌名称, 需要和品牌ID对应", default = u"微付乐")
     tradeName = StringField(verbose_name = u"商品名称,需要和商品ID对应", default = u"测试商品")
 
-
-class JDAggrePayApp(PayAppBase):
-    merchant_no = StringField(verbose_name = u'商户号', unique = True, required = True, null = False)
-    desKey = StringField(verbose_name = u'加密秘钥', null = False)
-    saltMd5Key = StringField(verbose_name = u'签名盐', null = False)
-    systemId = StringField(verbose_name = u'商户代理商ID', null = False)
-    boundAppId = StringField(verbose_name = u'绑定的APPID', null = None)
-    boundSecret = StringField(verbose_name = u'绑定的SECRET', default = None)
-    wxSubMchId = StringField(verbose_name = u'京东服务商下微信子商户ID', default = None)
-    regEmail = StringField(verbose_name = u'京东登录用户名', required = True)
-
-    # jos支付所需要的app
-    josPayApp = LazyReferenceField(verbose_name = u"JOS支付的app", document_type = "self")
-    shopInfo = EmbeddedDocumentField(verbose_name = u"商户所绑定的门店信息 目前仅用于JOS京东引流", document_type = JosShopInfo)
-
-    meta = {
-        'indexes': [
-            {
-                'fields': ['merchant_no'], 'unique': True
-            },
-            {
-                'fields': ['wxSubMchId']
-            }
-        ],
-        'collection': 'jdaggre_pay_app',
-        'db_alias': 'default'
-    }
-
-    def __repr__(self):
-        return '<JDAggrePayApp merchant_no=%s>' % (self.merchant_no,)
-
-    @property
-    def __valid_check__(self):
-        return bool(self.merchant_no) and bool(self.regEmail)
-
-    @property
-    def pay_app_type(self):
-        return PayAppType.JD_AGGR
-
-    @property
-    def __gateway_key__(self):
-        _ = [
-            self.occupantId,
-            self.merchant_no,
-            self.occupant.role
-        ]
-        return APP_KEY_DELIMITER.join(_)
-
-    @classmethod
-    def get_null_app(cls):
-        app = cls(merchant_no = '')
-        app.enable = False
-        app.valid = True
-        return app
-
-    @classmethod
-    def __from_gateway_key__(cls, occupant_id, tokens):
-        # type: (str, List)->cast(PayAppBase)
-        merchant_no = tokens[0]
-        app = cls.objects(merchant_no = merchant_no).first()  # type: JDAggrePayApp
-        if not app:
-            raise UserServerException(u'系统配置错误,请联系平台客服')
-
-        app.occupantId = occupant_id
-
-        try:
-            app.role = tokens[1]
-        except:
-            app.role = ROLE.agent
-
-        return app
-
-    def new_gateway(self, app_platform_type):
-        from apps.web.core.payment.jdaggre import JDAggrePaymentGateway
-        return JDAggrePaymentGateway(self, app_platform_type)
-
-    def to_dict(self, shadow = True):
-        return {
-            'id': str(self.id),
-            'merchant_no': self.merchant_no,
-            'companyName': self.companyName,
-            'appName': self.appName,
-            'remarks': self.remarks,
-            'type': self.pay_app_type,
-            'regEmail': self.regEmail
-        }
-
-    @classmethod
-    def get_or_create_by_merchant(cls, source):
-        # type: (MerchantSourceInfo)->JDAggrePayApp
-
-        merchantAgent = source.merchantAgent.fetch()
-
-        app = cls.objects.filter(merchant_no = source.merchantNo).first()  # type: JDAggrePayApp
-
-        if not app:
-            app = cls(merchant_no = source.merchantNo,
-                      desKey = source.desKey,
-                      saltMd5Key = source.mdKey,
-                      systemId = merchantAgent.agentSystemId,
-                      regEmail = source.regEmail,
-                      companyName = source.blicCompanyName)
-        else:
-            app.desKey = source.desKey
-            app.saltMd5Key = source.mdKey
-            app.systemId = merchantAgent.agentSystemId
-            app.companyName = source.blicCompanyName
-            app.regEmail = source.regEmail
-
-        return app.save()
-
-    @property
-    def split_id(self):
-        return self.regEmail
-
-    def bill_split_rule(self, partition_map):
-        """
-        京东支付分账规则:不带拉起支付商户的商户注册邮箱和金额
-        :return:
-        """
-        partitions = list(flatten(partition_map.values()))
-
-        rv = {}
-
-        owner_merchant_id = None
-
-        for partition in partitions:
-            if partition['role'] == PARTITION_ROLE.OWNER:
-                owner_merchant_id = partition['merchantId']
-            else:
-                if RMB(partition['money']) > RMB(0):
-                    if partition['merchantId'] in rv:
-                        item = rv[partition['merchantId']]
-                        item['splitBillAmount'] = str(RMB(item['splitBillAmount']) + RMB(partition['money']))
-                    else:
-                        rv[partition['merchantId']] = {
-                            'splitBillMerchantEmail': partition['merchantId'],
-                            'splitBillAmount': str(RMB(partition['money']))
-                        }
-
-        rv.pop(owner_merchant_id, None)
-
-        return 'FIXED', 'RECEIVER', rv.values()
-
-    def refund_bill_split_list(self, partition_map):
-        """
-        京东退款分账规则:
-        1、即使退款为0, 也必须带上拉起支付的商户号
-        2、所有参与支付分账的商户, 即使金额为0, 也必须带上
-        :return:
-        """
-
-        ownerPartition = partition_map.get(PARTITION_ROLE.OWNER)[0]
-
-        if 'merchantId' not in ownerPartition:
-            return None
-
-        owner_merchant_id = ownerPartition['merchantId']
-
-        partitions = list(flatten(partition_map.values()))
-
-        items = {}
-        for partition in partitions:
-            if 'merchantId' not in partition:
-                continue
-
-            if partition['merchantId'] in items:
-                item = items[partition['merchantId']]
-                item['splitBillAmount'] = str(RMB(item['splitBillAmount']) + abs(RMB(partition['money'])))
-            else:
-                items[partition['merchantId']] = {
-                    'splitBillMerchantEmail': partition['merchantId'],
-                    'splitBillAmount': str(abs(RMB(partition['money'])))
-                }
-
-        ownerItem = items.pop(owner_merchant_id)
-
-        if len(items.values()) == 0:
-            return None
-        else:
-            rv = items.values()
-            rv.append(ownerItem)
-
-            return rv
-
-
-class SaobeiPayApp(PayAppBase):
-    merchant_no = StringField(verbose_name = u'merchant no', required = True, null = False, max_length = 15)
-    terminal_id = StringField(verbose_name = u'terminal id', required = True, null = False, max_length = 8)
-    access_token = StringField(verbose_name = u'access token', required = True, null = False, max_length = 32)
-
-    meta = {
-        'indexes': [
-            {
-                'fields': ['merchant_no', 'terminal_id'], 'unique': True
-            },
-        ],
-        'collection': 'saobei_pay_app',
-        'db_alias': 'default'
-    }
-
-    def __repr__(self):
-        return '<SaobeiPayApp merchant_no=%s,terminal_id=%s>' % (self.merchant_no, self.terminal_id)
-
-    @property
-    def __valid_check__(self):
-        return bool(self.merchant_no and self.terminal_id and self.access_token)
-
-    @property
-    def pay_app_type(self):
-        return PayAppType.SAOBEI
-
-    @property
-    def __gateway_key__(self):
-        _ = [
-            self.occupantId,
-            self.merchant_no,
-            self.terminal_id,
-            self.occupant.role
-        ]
-        return APP_KEY_DELIMITER.join(_)
-
-    def to_dict(self, shadow = True):
-        return {
-            'id': str(self.id),
-            'merchant_no': self.merchant_no,
-            'terminal_id': self.terminal_id,
-            'access_token': encrypt_display(self.access_token) if shadow else self.access_token,
-            'companyName': self.companyName,
-            'appName': self.appName,
-            'remarks': self.remarks,
-            'type': self.pay_app_type
-        }
-
-    @classmethod
-    def get_null_app(cls):
-        app = cls(merchant_no = '', terminal_id = '')
-        app.enable = False
-        app.valid = True
-        return app
-
-    @classmethod
-    def __from_gateway_key__(cls, occupant_id, tokens):
-        # type: (str, List)->cast(PayAppBase)
-        merchant_no, terminal_id = tokens[0], tokens[1]
-        app = cls.objects(merchant_no = merchant_no, terminal_id = terminal_id).first()  # type: SaobeiPayApp
-        if not app:
-            raise UserServerException(u'系统配置错误,请联系平台客服')
-
-        app.occupantId = occupant_id
-
-        try:
-            app.role = tokens[2]
-        except:
-            app.role = ROLE.agent
-
-        return app
-
-    def new_gateway(self, app_platform_type):
-        from apps.web.core.payment.saobei import SaobeiPaymentGateway
-        return SaobeiPaymentGateway(self, app_platform_type)
-
-
-class DlbPayApp(PayAppBase):
-    merchant_no = StringField(verbose_name = u'merchant_no', required = True, null = False, max_length = 32)
-    shop_no = StringField(verbose_name = u'shop_no', required = True, null = False, max_length = 32)
-    machine_no = StringField(verbose_name = u'machine_no', required = True, null = False, max_length = 32)
-
-    access_key = StringField(verbose_name = u'machine_no', required = True, null = False, max_length = 64)
-    secret_key = StringField(verbose_name = u'machine_no', required = True, null = False, max_length = 64)
-
-    meta = {
-        'indexes': [
-            {
-                'fields': ['merchant_no', 'shop_no'], 'unique': True
-            },
-        ],
-        'collection': 'dlb_pay_app',
-        'db_alias': 'default'
-    }
-
-    def __repr__(self):
-        return '<DlbPayApp merchant_no=%s, shop_no=%s>' % (self.merchant_no, self.shop_no,)
-
-    @property
-    def __valid_check__(self):
-        return bool(self.merchant_no and self.shop_no)
-
-    @property
-    def pay_app_type(self):
-        return PayAppType.DLB
-
-    @property
-    def __gateway_key__(self):
-        _ = [
-            self.occupantId,
-            self.merchant_no,
-            self.shop_no,
-            self.occupant.role
-        ]
-        return APP_KEY_DELIMITER.join(_)
-
-    def to_dict(self, shadow = True):
-        return {
-            'id': str(self.id),
-            'merchant_no': self.merchant_no,
-            'shop_no': self.shop_no,
-            'machine_no': self.machine_no,
-            'companyName': self.companyName,
-            'appName': self.appName,
-            'remarks': self.remarks,
-            'type': self.pay_app_type
-        }
-
-    @classmethod
-    def get_null_app(cls):
-        app = cls(merchant_no = '', shop_no = '')
-        app.enable = False
-        app.valid = True
-        return app
-
-    @classmethod
-    def __from_gateway_key__(cls, occupant_id, tokens):
-        # type: (str, List)->cast(PayAppBase)
-        merchant_no, shop_no = tokens[0], tokens[1]
-        app = cls.objects(merchant_no = merchant_no, shop_no = shop_no).first()  # type: DlbPayApp
-        if not app:
-            raise UserServerException(u'系统配置错误,请联系平台客服')
-        app.occupantId = occupant_id
-
-        try:
-            app.role = tokens[2]
-        except:
-            app.role = ROLE.agent
-
-        return app
-
-    def new_gateway(self, app_platform_type):
-        from apps.web.core.payment.dlb import DlbPaymentGateway
-        return DlbPaymentGateway(self, app_platform_type)
-
-
-class YsPayApp(PayAppBase):
-    channel_id = StringField(verbose_name = u'channel_id', required = True, null = False, max_length = 32)
-    mer_id = StringField(verbose_name = u'mer_id', required = True, null = False, max_length = 32)
-    term_id = StringField(verbose_name = u'term_id', required = True, null = False, max_length = 32)
-
-    work_key = StringField(verbose_name = u'work_key', required = True, null = False, max_length = 64)
-
-    meta = {
-        'indexes': [
-            {
-                'fields': ['channel_id', 'mer_id', 'term_id'], 'unique': True
-            },
-        ],
-        'collection': 'ys_pay_app',
-        'db_alias': 'default'
-    }
-
-    def __repr__(self):
-        return '<YsPayApp channel_id=%s, mer_id=%s>' % (self.channel_id, self.mer_id)
-
-    @property
-    def __valid_check__(self):
-        return bool(self.channel_id and self.mer_id and self.term_id)
-
-    @property
-    def pay_app_type(self):
-        return PayAppType.YS
-
-    @property
-    def __gateway_key__(self):
-        _ = [
-            self.occupantId,
-            self.channel_id,
-            self.mer_id,
-            self.term_id,
-            self.occupant.role
-        ]
-        return APP_KEY_DELIMITER.join(_)
-
-    def to_dict(self, shadow = True):
-        return {
-            'id': str(self.id),
-            'channel_id': self.channel_id,
-            'mer_id': self.mer_id,
-            'term_id': self.term_id,
-            'companyName': self.companyName,
-            'appName': self.appName,
-            'remarks': self.remarks,
-            'type': self.pay_app_type
-        }
-
-    @classmethod
-    def get_null_app(cls):
-        app = cls(channel_id = '', mer_id = '')
-        app.enable = False
-        app.valid = True
-        return app
-
-    @classmethod
-    def __from_gateway_key__(cls, occupant_id, tokens):
-        channel_id, mer_id, term_id = tokens[0], tokens[1], tokens[2]
-        app = cls.objects(channel_id = channel_id, mer_id = mer_id, term_id = term_id).first()  # type: YsPayApp
-        if not app:
-            raise UserServerException(u'系统配置错误,请联系平台客服')
-
-        app.occupantId = occupant_id
-
-        try:
-            app.role = tokens[3]
-        except:
-            app.role = ROLE.agent
-
-        return app
-
-    def new_gateway(self, app_platform_type):
-        from apps.web.core.payment.ys import YsPaymentGateway
-        return YsPaymentGateway(self, app_platform_type)
-
-
 class WechatPayApp(PayAppBase):
     """
     对于V1接口, 需要sslKey(证书私钥)和sslCert(证书字符串)来调用接口, 使用apikey来加解密信息
@@ -1733,16 +1312,15 @@ class WechatPayApp(PayAppBase):
         from apps.web.core.payment.wechat import WechatPaymentGateway
         return WechatPaymentGateway(self)
 
-    def new_withdraw_gateway(self, is_ledger = True, gateway_version = None):
+    def new_withdraw_gateway(self, is_ledger = True, gateway_version = 'v1'):
         from apps.web.core.payment.wechat import WechatWithdrawGateway
 
         if not self.enable:
             is_ledger = False
 
-        if self.withdrawV3 and self.apikey_v3:
-            gateway_version = 'v3'
-        else:
-            gateway_version = 'v1'
+        if gateway_version == 'v3':
+            if not self.withdrawV3 or not self.apikey_v3:
+                return None
 
         return WechatWithdrawGateway(self, gateway_version = gateway_version, is_ledger = is_ledger)
 
@@ -2031,111 +1609,6 @@ class AliApp(PayAppBase):
         return getattr(self, '__client__')
 
 
-class RuralCreditUnionApp(PayAppBase):
-    appCode = StringField(verbose_name = u'平台号')
-    MerNbr = StringField(verbose_name = u'一级商户号')
-    EncryptSubMerchantId = StringField(verbose_name = u'加密二级商户号')
-    SubMerchantId = StringField(verbose_name = u'二级商户号', unique = True, required = True, null = False)
-    SubMerchantName = StringField(verbose_name = u'二级商户号名称')
-    branchId = StringField(verbose_name = u'交易机构号')
-    entoperId = StringField(verbose_name = u'操作员ID')
-    channelId = StringField(verbose_name = u'交易渠道号', default = '45')
-    pfx_path = StringField(verbose_name = u'私钥pfx路径', default = '')
-    pub_path = StringField(verbose_name = u'公钥cer路径', default = '')
-    pfx_passwd = StringField(verbose_name = u'证书密码', default = '')
-    authCodeUrl = StringField(verbose_name = u'获取autoCode的url', default = '')
-    SericeCenterUrl = StringField(verbose_name = u'公网UAT接入,交易上送', default = '')
-    privateKey = StringField(verbose_name = u'pfx私钥', default = '')
-    publicKey = StringField(verbose_name = u'验签公钥', default = '')
-
-    meta = {
-        'indexes': [
-            {
-                'fields': ['SubMerchantId'], 'unique': True
-            },
-            {
-                'fields': ['wxSubMchId']
-            }
-        ],
-        'collection': 'rural_credit_union_pay_app',
-        'db_alias': 'default'
-    }
-
-    @property
-    def __valid_check__(self):
-
-        if not self.privateKey:
-            import OpenSSL
-            pfx = open(self.pfx_path, 'rb').read()
-            p12 = OpenSSL.crypto.load_pkcs12(pfx, self.pfx_passwd)
-            self.privateKey = OpenSSL.crypto.dump_privatekey(OpenSSL.crypto.FILETYPE_PEM, p12.get_privatekey())
-            self.save()
-        if not self.publicKey:
-            self.publicKey = open(self.pub_path, 'rb').read()
-            self.save()
-
-        return bool(
-            self.appCode and self.SubMerchantId and self.branchId and self.channelId and self.privateKey and self.publicKey)
-
-    def new_gateway(self, app_platform_type):
-        from apps.web.core.payment.rcu import RuralCreditUnionPaymentGateway
-        return RuralCreditUnionPaymentGateway(self, app_platform_type)
-
-    @property
-    def pay_app_type(self):
-        return PayAppType.RCU
-
-    @property
-    def __gateway_key__(self):
-        _ = [
-            self.occupantId,
-            self.SubMerchantId,
-            self.occupant.role
-        ]
-        return APP_KEY_DELIMITER.join(_)
-
-    @classmethod
-    def get_null_app(cls):
-        app = cls(merchant_no = '')
-        app.enable = False
-        app.valid = True
-        return app
-
-    def to_dict(self, shadow = True):
-        return {
-            'id': str(self.id),
-            'appCode': self.appCode,
-            'MerNbr': self.MerNbr,
-            'EncryptSubMerchantId': self.EncryptSubMerchantId,
-            'SubMerchantId': self.SubMerchantId,
-            'SubMerchantName': self.SubMerchantName,
-            'branchId': self.branchId,
-            'entoperId': self.entoperId,
-            'channelId': self.channelId,
-            'companyName': self.companyName,
-            'appName': self.appName,
-            'remarks': self.remarks,
-            'type': self.pay_app_type,
-        }
-
-    @classmethod
-    def __from_gateway_key__(cls, occupant_id, tokens):
-        # type: (str, List)->cast(PayAppBase)
-        SubMerchantId = tokens[0]
-        app = cls.objects(SubMerchantId = SubMerchantId).first()  # type: JDAggrePayApp
-        if not app:
-            raise UserServerException(u'系统配置错误,请联系平台客服')
-
-        app.occupantId = occupant_id
-
-        try:
-            app.role = tokens[1]
-        except:
-            app.role = ROLE.agent
-
-        return app
-
-
 class PlatformAppBase(PayAppBase):
     """
     平台记账APP
@@ -2720,6 +2193,9 @@ class OfflineTask(Searchable):
 
 
 class BankCard(Searchable):
+    """
+    TODO: 已经废弃.升级脚本暂时需要, 下个版本后删除
+    """
     class AccountType(object):
         PERSONAL = 'personal'
         PUBLIC = 'public'
@@ -2819,6 +2295,15 @@ class SystemSettings(Searchable):
             vaule = item.value
         return vaule.get('LAXIN', False)
 
+    @classmethod
+    def get_system_setting_direct(cls, setting_name, default = None):
+        obj = cls.objects.filter(key = setting_name).first()
+
+        if obj:
+            return obj.value
+        else:
+            return default
+
 
 class DriverCode(Searchable):
     code = StringField(verbose_name = u'驱动编码', unique = True)
@@ -3184,6 +2669,3 @@ class CustomerPayRelation(Searchable):
         app = self.deactive_app()
         app.isDelete = True
         return app.save()
-
-
-MERCHANT_APP_LIST = [JDAggrePayApp, SaobeiPayApp, YsPayApp, RuralCreditUnionApp, JDOpenPayApp, DlbPayApp]

+ 6 - 10
apps/web/core/payment/__init__.py

@@ -7,15 +7,11 @@ from apps.web.core.payment.base import PaymentGateway
 from apps.web.core.payment.base import WithdrawGateway
 
 if TYPE_CHECKING:
-    from apps.web.core.payment.ali import AliPayGateway
-    from apps.web.core.payment.dlb import DlbPaymentGateway
-    from apps.web.core.payment.jdaggre import JDAggrePaymentGateway
-    from apps.web.core.payment.saobei import SaobeiPaymentGateway
-    from apps.web.core.payment.wechat import WechatPaymentGateway
-    from apps.web.core.payment.jdopen import JDOpenPaymentGateway
+    from apps.web.core.payment.ali import AliPayGateway, AliPayWithdrawGateway
+    from apps.web.core.payment.wechat import WechatPaymentGateway, WechatWithdrawGateway
 
-    PaymentGatewayT = Union[
-        AliPayGateway, WechatPaymentGateway, SaobeiPaymentGateway,
-        DlbPaymentGateway, JDAggrePaymentGateway, JDOpenPaymentGateway]
+    PaymentGatewayT = Union[AliPayGateway, WechatPaymentGateway]
 
-    WechatMiniPaymentGatewayT = Union[WechatPaymentGateway, JDOpenPaymentGateway, JDAggrePaymentGateway]
+    WechatMiniPaymentGatewayT = Union[WechatPaymentGateway]
+
+    WithdrawGatewayT = Union[AliPayWithdrawGateway, WechatWithdrawGateway]

+ 138 - 61
apps/web/core/payment/ali.py

@@ -1,20 +1,23 @@
 # -*- coding: utf-8 -*-
 # !/usr/bin/env python
-
+import datetime
 import logging
 
+import arrow
+import simplejson as json
+from django.conf import settings
 from typing import TYPE_CHECKING
 
+from apilib.monetary import RMB
+from apilib.monetary import quantize
 from apilib.utils_string import cn
-from apps.web.user.conf import PAY_NOTIFY_URL
-from apps.web.utils import testcase_point
-from library.alipay import AliPayServiceException
+from apps.web.exceptions import WithdrawOrderNotExist
+from apps.web.common.models import WithdrawBankCard
 from apps.web.core import AlipayMixin
-from library.alipay import AliPayGatewayException, AliErrorCode, AliException
-from apilib.monetary import quantize
-from apilib.monetary import RMB
 from apps.web.core.payment.base import PaymentGateway, WithdrawGateway
-from apps.web.core.models import BankCard
+from apps.web.utils import testcase_point
+from library.alipay import AliPayGatewayException, AliErrorCode, AliException
+from library.alipay import AliPayServiceException
 
 logger = logging.getLogger(__name__)
 
@@ -22,13 +25,70 @@ if TYPE_CHECKING:
     from apps.web.core.models import AliApp
 
 
+class AlipayWithdrawQueryResult(dict):
+    @property
+    def order_status(self):
+        return self.get('status')
+
+    @property
+    def finished_time(self):
+        if self.get('pay_date', None):
+            return arrow.get(self['pay_date'], 'YYYY-MM-DD HH:mm:ss', tzinfo = settings.TIME_ZONE).naive
+        else:
+            return datetime.datetime.now()
+
+    @property
+    def extra(self):
+        return {
+            'order_id': self.get('order_id'),
+            'pay_fund_order_id': self.get('pay_fund_order_id')
+        }
+
+    @property
+    def is_successful(self):
+        return self.get('status') == 'SUCCESS'
+
+    @property
+    def is_failed(self):
+        return self.get('status') == 'FAIL'
+
+    @property
+    def is_processing(self):
+        return self.get('status') == 'DEALING'
+
+    @property
+    def is_refund(self):
+        """
+        成功状态可能会转换为退票状态
+        :return:
+        """
+        return self.get('status') == 'REFUND'
+
+    @property
+    def error_desc(self):
+        if self.is_successful:
+            return self.get('status'), u'成功'
+
+        if self.is_processing:
+            return self.get('status'), u'正在处理'
+
+        if self.is_refund:
+            return self.get('status'), u'银行退票'
+
+        error_code = self.get('error_code', u'1001')
+        fail_reason = self.get('fail_reason', u'转账失败,请登录支付宝商户号查询具体订单信息')
+
+        return self.get('status'), u'{}({})'.format(fail_reason, error_code)
+
+    def __repr__(self):
+        return '<AlipayResultDict successful?(%s), failed?(%s), processing?(%s) \n content=%s' \
+               % (self.is_successful, self.is_failed, self.is_processing, json.dumps(self, indent = 2),)
+
+
 class AliPayGateway(PaymentGateway, AlipayMixin):
     """
         Alipay 支付网关,扩展原库没有的接口 ``alipay.trade.create``
     """
-    @property
-    def notifyUrl(self):
-        return PAY_NOTIFY_URL.ALI_PAY_BACK
 
     def __init__(self, app):
         # type: (AliApp)->None
@@ -48,48 +108,6 @@ class AliPayGateway(PaymentGateway, AlipayMixin):
         else:
             raise AliPayGatewayException(errCode = result['sub_code'], errMsg = result['sub_msg'], client = self)
 
-    def unified_order(self, out_trade_no, openId, money, subject, notifyUrl, **kwargs):
-
-        total_amount = quantize(money.amount)
-        if total_amount != money.amount:
-            raise AliException(
-                errCode=AliErrorCode.MY_INVALID_PARAMETER,
-                errMsg=u'无效的交易金额',
-                client=self.client
-            )
-
-        extras = {
-            "buyer_id": openId,
-            "timeout_express": kwargs.pop("timeout_express", '2m'),
-            "body": kwargs.pop('body', '')
-        }
-        kwargs.update(extras)
-
-        return self.client.api_alipay_trade_create(
-            out_trade_no=out_trade_no, total_amount=str(total_amount), subject=subject, notify_url=notifyUrl, **kwargs
-        )
-
-    def alipay_trade_precreate(self, out_trade_no, money, subject, buyer_id, timeout_express = '2m', body = '',
-                               **kwargs):
-        """
-        当面付的预下单
-        :param out_trade_no:
-        :param money:
-        :param subject:
-        :param buyer_id:
-        :param timeout_express:
-        :param body:
-        :param kwargs:
-        :return:
-        """
-        total_amount = str(quantize(money.amount, places = '0.01'))
-        return self.client.api_alipay_trade_precreate(out_trade_no = out_trade_no,
-                                                      total_amount = total_amount,
-                                                      subject = subject,
-                                                      **{"buyer_id": buyer_id,
-                                                         "timeout_express": timeout_express,
-                                                         "body": body})
-
     def api_alipay_trade_create(self, out_trade_no,
                                 money,
                                 subject,
@@ -133,6 +151,27 @@ class AliPayGateway(PaymentGateway, AlipayMixin):
                                                    notify_url = notify_url,
                                                    **extras)
 
+    def alipay_trade_precreate(self, out_trade_no, money, subject, buyer_id, timeout_express = '2m', body = '',
+                               **kwargs):
+        """
+        当面付的预下单
+        :param out_trade_no:
+        :param money:
+        :param subject:
+        :param buyer_id:
+        :param timeout_express:
+        :param body:
+        :param kwargs:
+        :return:
+        """
+        total_amount = str(quantize(money.amount, places = '0.01'))
+        return self.client.api_alipay_trade_precreate(out_trade_no = out_trade_no,
+                                                      total_amount = total_amount,
+                                                      subject = subject,
+                                                      **{"buyer_id": buyer_id,
+                                                         "timeout_express": timeout_express,
+                                                         "body": body})
+
     @testcase_point()
     def refund_to_user(self, out_trade_no, out_refund_no, refund_fee, total_fee, refund_reason, **kwargs):
         """
@@ -151,8 +190,14 @@ class AliPayGateway(PaymentGateway, AlipayMixin):
     def download_bill(self, bill_type = 'trade', bill_date = None):
         """
         下载支付宝订单用于对账
+        :param bill_type:  (trade|signcustomer)
+                           trade指商户基于支付宝交易收单的业务账单
+                           signcustomer是指基于商户支付宝余额收入及支出等资金变动的帐务账单
+        :param bill_date: 日账单格式为yyyy-MM-dd,月账单格式为yyyy-MM。
+        :return:
         """
-        return self.client.api_alipay_data_dataservice_bill_downloadurl_query(bill_type=bill_type, bill_date=bill_date)
+        return self.client.api_alipay_data_dataservice_bill_downloadurl_query(bill_type = bill_type,
+                                                                              bill_date = bill_date)
 
     def api_refund_query(self, trade_no, out_trade_no, out_request_no):
         return self.client.api_alipay_trade_refund_order_query(trade_no, out_trade_no, out_request_no, ["gmt_refund_pay"])
@@ -166,8 +211,8 @@ class AliPayWithdrawGateway(WithdrawGateway, AlipayMixin):
     def __repr__(self):
         return '<AliPayWithdrawGateway(appid=%s, debug=%s)>' % (self.appid, self.debug)
 
-    def withdraw_via_bank(self, order_no, total_amount, bank_card, order_title = u'提现到银行卡'):
-        # type:(str, RMB, BankCard, str)->dict
+    def withdraw_via_bank(self, order_no, total_amount, bank_card, order_title = u'服务款项'):
+        # type:(str, RMB, WithdrawBankCard, str)->dict
         """
         经销商提现通过银行卡提现
         .. 参考文档 https://opendocs.alipay.com/open/common/transfertocard
@@ -177,12 +222,12 @@ class AliPayWithdrawGateway(WithdrawGateway, AlipayMixin):
         """
 
         payee_info = {
-            'identity': bank_card.cardNo,
             'identity_type': 'BANKCARD_ACCOUNT',
-            'name': cn(bank_card.holderName)
+            'identity': bank_card.accountCode,
+            'name': cn(bank_card.accountName)
         }
 
-        if bank_card.accountType == BankCard.AccountType.PUBLIC:
+        if bank_card.accountType == WithdrawBankCard.AccountType.PUBLIC:
             payee_info['bankcard_ext_info'] = {
                 'inst_name': cn(bank_card.bankName),
                 'account_type': 1
@@ -196,7 +241,7 @@ class AliPayWithdrawGateway(WithdrawGateway, AlipayMixin):
                 payee_info['bankcard_ext_info'].update({
                     'inst_province': cn(bank_card.province),
                     'inst_city': cn(bank_card.city),
-                    'inst_branch_name': cn(bank_card.branchName)
+                    'inst_branch_name': cn(bank_card.branchBankName)
                 })
         else:
             payee_info['bankcard_ext_info'] = {
@@ -220,11 +265,43 @@ class AliPayWithdrawGateway(WithdrawGateway, AlipayMixin):
         查询银行卡提现的返回结果
         :return:
         """
-
         result = self.client.api_alipay_fund_trans_common_query(out_biz_no = order_no)
+        if result['code'] == u'10000':
+            return AlipayWithdrawQueryResult(result)
+        elif result['code'] == u'40004':
+            if result['sub_code'] in ['ORDER_NOT_EXIST']:
+                raise WithdrawOrderNotExist()
+            else:
+                raise AliPayServiceException(errCode = result['sub_code'], errMsg = result['sub_msg'], client = self)
+        else:
+            raise AliPayGatewayException(errCode = result['sub_code'], errMsg = result['sub_msg'], client = self)
+
+    def withdraw_via_changes(self, amount, payOpenId, order_no, real_user_name, subject = u'服务款项'):
+        """
+        提现到支付宝账户
+        :return:
+        """
+
+        payee_info = {
+            'identity': payOpenId,
+            'identity_type': 'ALIPAY_LOGON_ID',
+            'name': cn(real_user_name)
+        }
+
+        result = self.client.api_alipay_fund_trans_uni_transfer(
+            out_biz_no = order_no, trans_amount = str(amount),
+            payee_info = payee_info, order_title = subject, product_code = 'TRANS_ACCOUNT_NO_PWD')
+
         if result['code'] == u'10000':
             return result
         elif result['code'] == u'40004':
             raise AliPayServiceException(errCode = result['sub_code'], errMsg = result['sub_msg'], client = self)
         else:
             raise AliPayGatewayException(errCode = result['sub_code'], errMsg = result['sub_msg'], client = self)
+
+    def get_transfer_result_via_changes(self, order_no):
+        """
+        查询现金提现的返回结果
+        :return:
+        """
+        return self.get_transfer_result_via_bank(order_no = order_no)

+ 10 - 11
apps/web/core/payment/base.py

@@ -85,10 +85,6 @@ class PaymentGateway(_PaymentGateway):
         RefundFail = 'refundFail'
         Error = 'error'
 
-    @property
-    def notifyUrl(self):
-        return ""
-
     @classmethod
     def from_gateway_key(cls, gateway_type, gateway_key, default_pay_app_type):
         pay_app = cls.get_app_from_gateway_key(gateway_key = gateway_key,
@@ -109,6 +105,15 @@ class PaymentGateway(_PaymentGateway):
             pay_app.occupant = source_cls.objects.get(id = pay_app.occupantId)
         return pay_app.new_gateway(gateway_type)
 
+    def api_trade_query(self, out_trade_no = None, trade_no = None):
+        raise NotImplementedError('sub class must implement api_trade_query')
+
+    def refund_to_user(self, out_trade_no, out_refund_no, refund_fee, total_fee, refund_reason, **kwargs):
+        raise NotImplementedError('sub class must implement refund_to_user')
+
+    # service 2位 18, 28 ,38
+    # product 4位 0001, 0002, 0003
+    # WF180001201712281209393393822345
     @staticmethod
     def generate_order_no(service, product):
         return '%s%s%s%s%s' % (
@@ -116,12 +121,6 @@ class PaymentGateway(_PaymentGateway):
             (str(abs(hash(uuid.uuid1())))[0:6]).rjust(6, '0'),
             random.randint(1000, 9999))
 
-    def api_trade_query(self, out_trade_no=None, trade_no=None):
-        raise NotImplementedError('sub class must implement api_trade_query')
-
-    def refund_to_user(self, out_trade_no, out_refund_no, refund_fee, total_fee, refund_reason, **kwargs):
-        raise NotImplementedError('sub class must implement refund_to_user')
-
     def withdraw_source_key(self):
         return self.occupant.withdraw_source_key(self.app)
 
@@ -186,7 +185,7 @@ class WithdrawGateway(_PaymentGateway):
 
         raise NotImplementedError()
 
-    def withdraw_via_bank(self, order_no, total_amount, bank_card, order_title = u'提现到银行卡'):
+    def withdraw_via_bank(self, order_no, total_amount, bank_card, order_title = u'服务款项'):
         """
         提现到银行卡
         :return:

+ 0 - 62
apps/web/core/payment/dlb.py

@@ -1,62 +0,0 @@
-# -*- coding: utf-8 -*-
-# !/usr/bin/env python
-import logging
-from collections import OrderedDict
-
-from typing import TYPE_CHECKING, Optional
-
-import simplejson as json
-
-from apps.web.constant import AppPlatformType
-from apps.web.core import DlbMixin
-from apps.web.core.payment.base import PaymentGateway
-from library.dlb import DlbPay
-
-if TYPE_CHECKING:
-    from apps.web.core.models import DlbPayApp
-
-logger = logging.getLogger(__name__)
-
-
-class DlbPaymentGateway(PaymentGateway, DlbMixin):
-    def __init__(self, app, gateway_type):  # type: (DlbPayApp, AppPlatformType)->None
-        super(DlbPaymentGateway, self).__init__(app)
-        self.__gateway_type__ = gateway_type
-
-    def __repr__(self):
-        return \
-            '<DlbPaymentGateway(occupantId = %s, merchant_no = %s, shop_no = %s, debug = %s)>' % (
-                str(self.occupantId), self.merchant_no, self.shop_no, self.debug)
-
-    @property
-    def __client__(self):
-        # type: ()->DlbPay
-        return DlbPay(self.merchant_no, self.shop_no, self.access_key, self.secret_key, self.machine_no, self.debug)
-
-    def create_pay_url(self, order_no, money, notify_url, front_url = '', attach = None):
-        return self.client.create_pay_url(
-            requestNum = order_no,
-            amount = str(money),
-            notify_url = notify_url,
-            front_url = front_url,
-            extraInfo = json.dumps(attach, separators = (',', ':')) if attach else '')
-
-    def api_trade_query(self, out_trade_no = None, trade_no = None):
-        # type:(Optional[str], Optional[str])->dict
-        """
-        查询订单
-        :param out_trade_no: 商户订单号
-        :param trade_no: 微信订单号
-        :return:
-        """
-        assert out_trade_no or trade_no, 'must input out_trade_no or trade_no'
-        return self.client.query_trade_result(trade_no = trade_no, out_trade_no = out_trade_no)
-
-    def check(self, data, token):
-        # type: (dict, str)->bool
-        sign_data = OrderedDict()
-        sign_data['secretKey'] = self.secret_key
-        sign_data['timestamp'] = data['timestamp']
-
-        re_token = self.client.token(sign_data)
-        return re_token == token

+ 0 - 92
apps/web/core/payment/jdaggre.py

@@ -1,92 +0,0 @@
-# -*- coding: utf-8 -*-
-# !/usr/bin/env python
-
-import logging
-
-from typing import TYPE_CHECKING, Optional, Dict, Union
-
-import simplejson as json
-
-from apilib.monetary import RMB, Percent
-from apps.web.constant import AppPlatformType, PARTITION_ROLE
-from apps.web.core.payment.base import PaymentGateway
-from apps.web.utils import testcase_point
-from library.jd import JDAggrePay
-from library.jd.pay import GatewayMethod
-
-if TYPE_CHECKING:
-    from apps.web.core.models import JDAggrePayApp
-
-logger = logging.getLogger(__name__)
-
-
-class JDAggrePaymentGateway(PaymentGateway, JDAggrePayMixin):
-    def __init__(self, app, gateway_type):  # type: (JDAggrePayApp, AppPlatformType)->None
-        super(JDAggrePaymentGateway, self).__init__(app)
-        self.__gateway_type__ = gateway_type
-
-    def __repr__(self):
-        return '<JDAggrePaymentGateway(occupantId = %s, merchant_no = %s, debug = %s)>' % (
-            str(self.occupantId), self.merchant_no, self.debug)
-
-    @property
-    def __client__(self):
-        # type: ()->JDAggrePay
-        return JDAggrePay(self.merchant_no, self.desKey, self.saltMd5Key, self.systemId, self.debug)
-
-    def _to_fen(self, rmb):
-        # type: (RMB)->long
-        return int(rmb.amount * 100)
-
-    def create_pay_url(self, order_no, money, notify_url, pi_type, subject, attach = None):
-        # type: (str, RMB, str, str, str, Optional[dict])->str
-        return self.client.create_pay_url(
-            out_trade_no = order_no,
-            total_fee = self._to_fen(money),
-            notify_url = notify_url,
-            piType = pi_type,
-            tradeName = subject,
-            returnParams = json.dumps(attach, separators = (',', ':')) if attach else '')
-
-    def decrypt_response(self, payload):
-        return self.client.decrypt_response(payload)
-
-    def unified_order(self, out_trade_no, money, notify_url, subject = u'充值', expire = 300, attach = None,
-                      openId = None, gatewayMethod = GatewayMethod.SUBSCRIPTION, **kwargs):
-        # type: (str, RMB, str, basestring, int, Optional[Dict], Optional[str], str, Dict)->Union[str, Dict]
-        return self.client.unified_order(piType = self.pi_type, out_trade_no = out_trade_no,
-                                         total_fee = str(self._to_fen(money)),
-                                         notify_url = notify_url, productName = subject, expire = expire,
-                                         returnParams = json.dumps(attach, separators = (',', ':')) if attach else '',
-                                         client_ip = '127.0.0.1', openId = openId, gatewayMethod = gatewayMethod,
-                                         **kwargs)
-
-    def api_trade_query(self, out_trade_no = None, trade_no = None):
-        # type:(Optional[str], Optional[str])->dict
-        """
-        查询订单
-        :param out_trade_no: 商户订单号
-        :param trade_no: 微信订单号
-        :return:
-        """
-        return self.client.api_trade_query(out_trade_no = out_trade_no, trade_no = trade_no)
-
-    @testcase_point()
-    def refund_to_user(self, out_trade_no, out_refund_no, refund_fee, total_fee, refund_reason, **kwargs):
-        # type:(str, str, RMB, RMB, str, dict)->dict
-
-        """
-        :param out_trade_no:
-        :param out_refund_no:
-        :param refund_fee:
-        :param total_fee:
-        :param refund_reason:
-        :return:
-        """
-        return self.client.api_trade_refund(outTradeNo = out_trade_no,
-                                            outRefundNo = out_refund_no,
-                                            amount = self._to_fen(refund_fee),
-                                            **kwargs)
-
-    def api_refund_query(self, out_refund_no = None, refund_no = None):
-        return self.client.api_refund_query(out_refund_no=out_refund_no)

+ 0 - 120
apps/web/core/payment/jdopen.py

@@ -1,120 +0,0 @@
-# -*- coding: utf-8 -*-
-# !/usr/bin/env python
-
-import logging
-
-import simplejson as json
-from typing import TYPE_CHECKING, Optional
-
-from apilib.monetary import RMB, Percent
-from apps.web.constant import AppPlatformType, PARTITION_ROLE
-from apps.web.core import JDOpenPayMixin
-from apps.web.core.payment.base import PaymentGateway
-from apps.web.exceptions import UserServerException
-from apps.web.user.conf import REFUND_NOTIFY_URL
-from library.jdopen.pay import JDOpenPay
-
-if TYPE_CHECKING:
-    from apps.web.core.models import JDOpenPayApp
-
-logger = logging.getLogger(__name__)
-
-
-class JDOpenPaymentGateway(PaymentGateway, JDOpenPayMixin):
-    def __init__(self, app, gateway_type):  # type: (JDOpenPayApp, AppPlatformType)->None
-        super(JDOpenPaymentGateway, self).__init__(app)
-        self.__gateway_type__ = gateway_type
-
-    def __repr__(self):
-        return '<JDOpenPaymentGateway(occupantId = %s, customerNum = %s, debug = %s)>' % (
-            str(self.occupantId), self.customerNum, self.debug)
-
-    @property
-    def __client__(self):  # type: ()->JDOpenPay
-        agent = self.app.agent.fetch()
-        return JDOpenPay(agent.agentNo, agent.accessKey, agent.secretKey, self.app.customerNum, self.app.shopNum)
-
-    def create_pay_url(self, order_no, money, notify_url, attach=None, **kwargs):
-        # type: (str, RMB, str, str, dict)->str
-
-        return self.client.create_pay_url(
-            out_trade_no=order_no,
-            total_fee=str(money),
-            notify_url=notify_url,
-            extraInfo=json.dumps(attach, separators=(',', ':')) if attach else '',
-            **kwargs)
-
-    def unified_order(self, out_trade_no, money, notify_url, subject=u'充值', attach=None,
-                      openId=None, **kwargs):
-        return self.client.unified_order(authId=openId, bankType=self.bank_type, requestNum=out_trade_no,
-                                         amount=str(money), callbackUrl=notify_url, subject=subject,
-                                         extraInfo=json.dumps(attach, separators=(',', ':')) if attach else '',
-                                         **kwargs)
-
-    def api_trade_query(self, out_trade_no=None, trade_no=None):
-        # type:(Optional[str], Optional[str])->dict
-        """
-        查询订单
-        :param out_trade_no: 商户订单号
-        :param trade_no: 微信订单号
-        :return:
-        """
-        return self.client.api_trade_query(out_trade_no=out_trade_no, trade_no=trade_no)
-
-    def refund_to_user(self, out_trade_no, out_refund_no, refund_fee, total_fee, refund_reason, **kwargs):
-        # type:(str, str, RMB, RMB, str, dict)->dict
-
-        """
-        :param out_trade_no:
-        :param out_refund_no:
-        :param refund_fee:
-        :param total_fee:
-        :param refund_reason:
-        :param kwargs callbackUrl,ledgerInfoList
-        :return:
-        """
-        return self.client.api_trade_refund(outTradeNo=out_trade_no,
-                                            outRefundNo=out_refund_no,
-                                            amount=str(refund_fee),
-                                            **kwargs)
-
-    def api_refund_query(self, requestNum=None, orderNum=None):
-        """
-        退款查询
-        :param requestNum:
-        :param orderNum:
-        :return:
-        """
-
-        return self.client.api_refund_query(requestNum=requestNum, orderNum=orderNum)
-
-    def add_wechat_auth_pay_dir(self, auth_pay_dir):
-        return self.client.add_wechat_auth_pay_dir(auth_pay_dir)
-
-    def query_wechat_auth_pay_dir(self, batch_num):
-        return self.client.query_wechat_auth_pay_dir(batch_num)
-
-    def api_close_order(self, requestNum):
-        """
-        关闭订单接口提供给商户向支付服务发送关闭订单请求数据集合,支付服务会根据请求数据验证商户身份,以及验证支付信息是否被篡改。
-        验证通过后,支付服务会返回订单关闭信息。
-        商户订单支付失败需要生成新单号重新发起支付,要对原订单号调用关单,避免重复支付;
-        系统下单后,用户支付超时,系统退出不再受理,避免用户继续,请调用关单接口。
-        调用关单的时候,如果订单已经支付成功,会给用户退款
-        :param requestNum:
-        :return:
-        """
-        return self.client.api_close_order(requestNum)
-
-    def api_cancel_order(self, requestNum):
-        """
-        支付交易返回失败或支付系统超时,调用该接口撤销交易。
-        如果此订单用户支付失败,支付系统会将此订单关闭;
-        如果用户支付成功,支付系统会将此订单资金退还给用户。
-        :param requestNum:
-        :return:
-        """
-        return self.client.api_cancel_order(requestNum)
-
-    def download_bill(self, bill_date):
-        return self.client.download_bill(bill_date = bill_date)

+ 0 - 91
apps/web/core/payment/saobei.py

@@ -1,91 +0,0 @@
-# -*- coding: utf-8 -*-
-# !/usr/bin/env python
-
-import logging
-
-from typing import TYPE_CHECKING
-
-import simplejson as json
-
-from apps.web.core import SaobeiMixin
-from library.saobei import SaobeiSignatureError, SaobeiValidationError
-from library.saobei import SaobeiPay
-from apilib.monetary import RMB
-from apps.web.constant import AppPlatformType
-from apps.web.core.payment.base import PaymentGateway
-
-if TYPE_CHECKING:
-    from apps.web.core.models import SaobeiPayApp
-
-logger = logging.getLogger(__name__)
-
-
-class SaobeiPaymentGateway(PaymentGateway, SaobeiMixin):
-    PayTypeMap = {
-        AppPlatformType.WECHAT: '010',
-        AppPlatformType.ALIPAY: '020',
-        AppPlatformType.JD: '080'
-    }
-
-    def __init__(self, app, gateway_type): # type: (SaobeiPayApp, AppPlatformType)->None
-        super(SaobeiPaymentGateway, self).__init__(app)
-        self.__gateway_type__ = gateway_type
-
-    @property
-    def __client__(self):
-        # type: ()->SaobeiPay
-        return SaobeiPay(self.merchant_no, self.terminal_id, self.app.access_token, self.debug)
-
-    def _to_fen(self, rmb):
-        # type: (RMB)->int
-        return str(int(rmb.amount * 100))
-
-    def create_pay_url(self, pay_trace, pay_time, money, attach, body, notify_url, front_url = ''):
-        data = {
-            'attach': json.dumps(attach, separators = (',', ':')) if attach else '',
-            'order_body': body,
-            'auto_pay': '0',
-            'notify_url': notify_url
-        }
-
-        if front_url:
-            data.update({'front_url': front_url})
-
-        url = self.client.generate_wap_pay_url(
-            pay_trace = pay_trace,
-            pay_time = pay_time,
-            total_fee = str(self._to_fen(money)),
-            **data)
-        return url
-
-    def api_trade_query(self, pay_trace, pay_time):
-        result = self.client.api_trade_query(pay_type = self.PayTypeMap[self.gateway_type],
-                                             pay_trace = pay_trace,
-                                             pay_time = pay_time)
-        return result
-
-    def check(self, data, order = True, token = True):
-        key_sign = data.pop('key_sign')
-
-        if self.merchant_no != data['merchant_no']:
-            raise SaobeiValidationError(
-                errmsg = u'商户号不一致',
-                lvalue = self.merchant_no,
-                rvalue = data['merchant_no'],
-                client = self)
-
-        if self.terminal_id != data['terminal_id']:
-            raise SaobeiValidationError(
-                errmsg = u'终端ID不一致',
-                lvalue = self.terminal_id,
-                rvalue = data['terminal_id'],
-                client = self)
-
-        re_key_sign = self.client.sign(data, order, token)
-        if key_sign != re_key_sign:
-            raise SaobeiSignatureError(lvalue = re_key_sign, rvalue = key_sign, client = self)
-
-    def __repr__(self):
-        return \
-            '<SaobeiPaymentGateway(agentId = %s, merchant_no = %s, terminal_id = %s, debug = %s)>' % (
-                str(self.occupantId), self.merchant_no, self.terminal_id, self.debug)

+ 0 - 23
apps/web/core/payment/swap.py

@@ -1,23 +0,0 @@
-# -*- coding: utf-8 -*-
-# !/usr/bin/env python
-
-import logging
-
-from typing import TYPE_CHECKING
-
-from apps.web.core.payment import PaymentGateway
-
-if TYPE_CHECKING:
-    pass
-
-logger = logging.getLogger(__name__)
-
-
-class SwapPaymentGateway(PaymentGateway):
-    def __init__(self, app, gateway_type):
-        super(SwapPaymentGateway, self).__init__(app)
-        self.__gateway_type__ = gateway_type
-
-    def __repr__(self):
-        return '<SwapPaymentGateway(occupantId = %s)>' % (
-            str(self.occupantId))

+ 61 - 44
apps/web/core/payment/wechat.py

@@ -10,29 +10,29 @@
 """
 
 import base64
+import datetime
 import json
 import logging
-import datetime
 
 from Crypto.Cipher import PKCS1_OAEP
 from Crypto.PublicKey import RSA
 from dateutil.relativedelta import relativedelta
 from typing import Optional, Union
-
 from typing import TYPE_CHECKING
 from werkzeug.utils import cached_property
 
-from apilib.utils_string import cn
 from apilib.monetary import RMB
-from apps.web.common.models import Banks
+from apilib.utils_string import cn
+from apps.web.exceptions import WithdrawOrderNotExist
+from apps.web.common.models import WithdrawBankCard, WithdrawBanks
+from apps.web.constant import WECHAT_WITHDRAW_STATUS, AppPlatformType
 from apps.web.core import WechatMixin
+from apps.web.core.models import WechatPayApp
 from apps.web.core.payment import PaymentGateway, WithdrawGateway
-from apps.web.constant import WECHAT_WITHDRAW_STATUS, AppPlatformType
-from apps.web.user.conf import PAY_NOTIFY_URL
 from apps.web.utils import testcase_point
-from library.wechatpy.pay import WeChatPay
-from apps.web.core.models import WechatPayApp, BankCard
 from library.wechatpayv3 import WechatClientV3
+from library.wechatpy.pay import WeChatPay
+from library.wechatbase.exceptions import WeChatPayException
 
 if TYPE_CHECKING:
     from apps.web.core.models import WechatMiniApp
@@ -46,6 +46,14 @@ class WechatWithdrawQueryResult(dict):
     def order_status(self):
         return self.get('status')
 
+    @property
+    def finished_time(self):
+        return datetime.datetime.now()
+
+    @property
+    def extra(self):
+        return {}
+
     @property
     def is_successful(self):
         assert self.get('return_code') == 'SUCCESS' and self.get('result_code') == 'SUCCESS'
@@ -61,6 +69,10 @@ class WechatWithdrawQueryResult(dict):
         assert self.get('return_code') == 'SUCCESS' and self.get('result_code') == 'SUCCESS'
         return self.get('status') == WECHAT_WITHDRAW_STATUS.PROCESSING
 
+    @property
+    def is_refund(self):
+        return False
+
     @property
     def error_desc(self):
         if self.is_successful:
@@ -139,12 +151,7 @@ class WechatPayMixin(WechatMixin):
 
 
 class WechatPaymentGateway(PaymentGateway, WechatPayMixin):
-
-    @property
-    def notifyUrl(self):
-        return PAY_NOTIFY_URL.WECHAT_PAY_BACK
-
-    def __init__(self, app, gateway_type=AppPlatformType.WECHAT):
+    def __init__(self, app, gateway_type = AppPlatformType.WECHAT):
         # type: (Union[WechatPayApp, WechatMiniApp], AppPlatformType)->None
         """
         :param app: WechatPayApp
@@ -175,8 +182,8 @@ class WechatPaymentGateway(PaymentGateway, WechatPayMixin):
                          mch_cert = self.ssl_cert,
                          mch_key = self.ssl_key)
 
-    def generate_js_payment_params(self, payOpenId, out_trade_no, notify_url, money, body=cn(u"购买金币"),
-                                   spbill_create_ip='127.0.0.1', attach=None):
+    def generate_js_payment_params(self, payOpenId, out_trade_no, notify_url, money, body = cn(u"购买金币"),
+                                   spbill_create_ip = '127.0.0.1', attach = None):
         # type: (basestring, str, basestring, RMB, basestring, str, Optional[dict])->dict
         """
         注意: money必须以元为单位
@@ -184,15 +191,15 @@ class WechatPaymentGateway(PaymentGateway, WechatPayMixin):
         注意这里有个微信的坑,就是attach参数不能包含空格,否则在某种情况下被转换成+号,造成解析失败
         :return: dict
         """
-        raw = self.client.order.create(trade_type='JSAPI',
-                                       body=body,
-                                       total_fee=str(self._to_fen(money)),
-                                       notify_url=notify_url,
-                                       client_ip=spbill_create_ip,
-                                       user_id=payOpenId,
-                                       out_trade_no=out_trade_no,
-                                       attach=json.dumps(attach, separators=(',', ':')) if attach else '')
-        return self.client.jsapi.get_jsapi_params(prepay_id=raw["prepay_id"])
+        raw = self.client.order.create(trade_type = 'JSAPI',
+                                       body = body,
+                                       total_fee = str(self._to_fen(money)),
+                                       notify_url = notify_url,
+                                       client_ip = spbill_create_ip,
+                                       user_id = payOpenId,
+                                       out_trade_no = out_trade_no,
+                                       attach = json.dumps(attach, separators = (',', ':')) if attach else '')
+        return self.client.jsapi.get_jsapi_params(prepay_id = raw["prepay_id"])
 
     def api_trade_query(self, out_trade_no = None, trade_no = None):
         # type:(Optional[str], Optional[str])->dict
@@ -292,8 +299,8 @@ class WechatWithdrawGateway(WithdrawGateway, WechatPayMixin):
                                              real_name = real_user_name,
                                              out_trade_no = order_no)
 
-    def withdraw_via_bank(self, order_no, total_amount, bank_card, order_title = u'提现到银行卡'):
-        # type:(str, RMB, BankCard, str)->dict
+    def withdraw_via_bank(self, order_no, total_amount, bank_card, order_title = u'服务款项'):
+        # type:(str, RMB, WithdrawBankCard, str)->dict
 
         """
         经销商提现通过银行卡支付
@@ -305,9 +312,9 @@ class WechatWithdrawGateway(WithdrawGateway, WechatPayMixin):
         :return:
         """
 
-        wechat_bank_code = Banks.get_wechat_bank_code(bank_card.bankName)
-        return self.client.transfer.transfer_bankcard(true_name = bank_card.holderName.encode('utf-8'),
-                                                      bank_card_no = bank_card.cardNo.encode('utf-8'),
+        wechat_bank_code = WithdrawBanks.get_wechat_bank_code(bank_card.bankName)
+        return self.client.transfer.transfer_bankcard(true_name = bank_card.accountName.encode('utf-8'),
+                                                      bank_card_no = bank_card.accountCode.encode('utf-8'),
                                                       bank_code = wechat_bank_code,
                                                       amount = self._to_fen(total_amount),
                                                       desc = order_title,
@@ -319,23 +326,33 @@ class WechatWithdrawGateway(WithdrawGateway, WechatPayMixin):
         查询银行卡提现的返回结果
         :return:
         """
-        return WechatWithdrawQueryResult(self.client.transfer.query_bankcard(out_trade_no = order_no))
+        try:
+            return WechatWithdrawQueryResult(self.client.transfer.query_bankcard(out_trade_no = order_no))
+        except WeChatPayException as e:
+            if e.errCode in ['NOT_FOUND', 'ORDERNOTEXIST']:
+                raise WithdrawOrderNotExist()
+            else:
+                raise e
 
     def get_transfer_result_via_changes(self, order_no):
         """
-
         :param order_no:
         :return:
         """
-
-        if self.version == 'v3':
-            result = self.client.transfer.query(out_trade_no = order_no)
-            rv = {
-                'return_code': 'SUCCESS',
-                'result_code': 'SUCCESS',
-                'status': result['detail_status'],
-                'reason': result.get('fail_reason', None)
-            }
-            return WechatWithdrawQueryResult(rv)
-        else:
-            return WechatWithdrawQueryResult(self.client.transfer.query(out_trade_no = order_no))
+        try:
+            if self.version == 'v3':
+                result = self.client.transfer.query(out_trade_no = order_no)
+                rv = {
+                    'return_code': 'SUCCESS',
+                    'result_code': 'SUCCESS',
+                    'status': result['detail_status'],
+                    'reason': result.get('fail_reason', None)
+                }
+                return WechatWithdrawQueryResult(rv)
+            else:
+                return WechatWithdrawQueryResult(self.client.transfer.query(out_trade_no = order_no))
+        except WeChatPayException as e:
+            if e.errCode in ['NOT_FOUND', 'ORDERNOTEXIST']:
+                raise WithdrawOrderNotExist()
+            else:
+                raise e

+ 0 - 61
apps/web/core/payment/ys.py

@@ -1,61 +0,0 @@
-# -*- coding: utf-8 -*-
-# !/usr/bin/env python
-
-import logging
-
-from typing import TYPE_CHECKING, Optional
-
-from apilib.monetary import RMB
-from apps.web.constant import AppPlatformType
-from apps.web.core import YsMixin
-from apps.web.core.payment.base import PaymentGateway
-from library.ys import YsPayException, YsErrorCode
-from library.ys.yspay import YsPay
-
-if TYPE_CHECKING:
-    from apps.web.core.models import YsPayApp
-
-logger = logging.getLogger(__name__)
-
-
-class YsPaymentGateway(PaymentGateway, YsMixin):
-    def __init__(self, app, gateway_type): # type: (YsPayApp, AppPlatformType)->None
-        super(YsPaymentGateway, self).__init__(app)
-        self.__gateway_type__ = gateway_type
-
-    def __repr__(self):
-        return '<YsPaymentGateway(channelid = {}, merid = {}, termid = {}, debug = {})>'.format(
-            self.channel_id, self.mer_id, self.term_id, self.debug)
-
-    @property
-    def __client__(self):
-        # type: ()->YsPayApp
-        return YsPay(self.channel_id, self.mer_id, self.term_id, self.work_key, self.debug)
-
-    def _to_fen(self, rmb):
-        # type: (RMB)->long
-        return int(rmb.amount * 100)
-
-    def unified_order(self, out_trade_no, money, openId, notify_url, subject, **data):
-        pay_opt = self.GATEWAY_TYPE_TO_OPT.get(self.gateway_type)
-        if not pay_opt:
-            raise YsPayException(errCode = YsErrorCode.MY_INVALID_PARAMETER, errMsg = u'不支持的支付类型')
-
-        return self.client.unified_order(
-            pay_opt = pay_opt,
-            openId = openId,
-            out_trade_no = out_trade_no,
-            total_fee = self._to_fen(money),
-            subject = subject,
-            notify_url = notify_url,
-            **data)
-
-    def api_trade_query(self, out_trade_no = None, trade_no = None):
-        # type:(Optional[str], Optional[str])->dict
-        """
-        查询订单
-        :param out_trade_no: 商户订单号
-        :param trade_no: 微信订单号
-        :return:
-        """
-        return self.client.api_trade_query(out_trade_no = out_trade_no, trade_no = trade_no)

+ 4 - 0
apps/web/core/sysparas.py

@@ -15,6 +15,10 @@ class SysParas(object):
 
         return SystemSettings.get_system_setting('smsVendor', default)
 
+    @classmethod
+    def get_sim_expire_sms_vendor(cls, default = SmsVendorCode.UCPAAS):
+        return SystemSettings.get_system_setting_direct('simExpireSmsVendor', default)
+
     @classmethod
     def get_system_alarmer(cls):
         value = SystemSettings.get_system_setting('systemAlarmer')

+ 46 - 261
apps/web/dealer/models.py

@@ -34,8 +34,7 @@ from apps.web.core.db import Searchable, MonetaryField, PercentField, Permillage
 from apps.web.core.exceptions import UpdateError, ServiceException, InvalidParameter, MerchantError
 from apps.web.dealer.constant import TodoDone, TodoTypeEnum, LinkageSwitchEnum
 from apps.web.core.messages.sms import dealerWithdrawSMSProvider, dealerMonitorWithdrawSMSProvider
-from apps.web.core.models import BoundOpenInfo, BankCard, SaobeiPayApp, JDAggrePayApp, DlbPayApp, \
-    YsPayApp, RuralCreditUnionApp, SwapPayApp, JDOpenPayApp, MERCHANT_APP_LIST
+from apps.web.core.models import BoundOpenInfo, BankCard, SwapPayApp
 from apps.web.core.payment import WithdrawGateway
 from apps.web.dealer.define import DEALER_INCOME_SOURCE, DEALER_INCOME_TYPE, DealerConst, \
     DEALER_INCOME_SOURCE_TRANSLATION
@@ -48,9 +47,7 @@ if TYPE_CHECKING:
     from apps.web.common.transaction import WithdrawHandler
     from apps.web.core.payment import PaymentGateway
     from apps.web.core import PayAppBase
-    from apps.web.merchant.models import JDOpenApplyInfo, MerchantSourceInfo
 
-    PayAppT = Optional[JDAggrePayApp, SaobeiPayApp, YsPayApp, RuralCreditUnionApp, DlbPayApp, JDOpenPayApp]
 
 logger = logging.getLogger(__name__)
 
@@ -259,12 +256,9 @@ class Dealer(CapitalUser):
     domain = StringField(verbose_name = u'api推送地址url', default = '')
 
     payAppType = StringField(verbose_name = u'商户类型', default = None)
-    merchantApp = GenericLazyReferenceField(choices=MERCHANT_APP_LIST, verbose_name= u'商户', default=None)
 
     monitorPhone = StringField(verbose_name = u"提现审批员的手机号", default = "", max_length = 32)
 
-    bankWithdrawFee = BooleanField(verbose_name = u"银行卡提现手续开关(资金池代理商开关和这个开关为双开关)", default = True)
-
     currencyCoins = BooleanField(verbose_name = u'金币是否通用(deprecated)', default = True)
     currencyMode = StringField(verbose_name = u'金币通用模式(allYes, allNo, asGroup)', default = None)
 
@@ -280,10 +274,6 @@ class Dealer(CapitalUser):
     # 配置模板集合
     templateSet = EmbeddedDocumentField(verbose_name=u"开关集合", document_type=ConfigTemplate, default=ConfigTemplate)
 
-    # 兼容老版本
-    payAppJDAggre = LazyReferenceField(document_type=JDAggrePayApp, verbose_name=u'支付APP', default=None)
-    payAppRCU = LazyReferenceField(document_type=RuralCreditUnionApp, verbose_name=u'河南农村信用社(仅用于支付)', default=None)
-
     meta = {
         'indexes': [
             {
@@ -399,13 +389,14 @@ class Dealer(CapitalUser):
         resultFeatures = {}
 
         agent = Agent.objects.get(id=self.agentId)  # type: Agent
+        agent_features = agent.feature_boolean_map
 
         for query in queryList:
             if query in dealerFeatures:
                 resultFeatures[query] = dealerFeatures[query]
             else:
-                if query in agent.feature_boolean_map:
-                    resultFeatures[query] = agent.feature_boolean_map[query]
+                if query in agent_features:
+                    resultFeatures[query] = agent_features[query]
                 else:
                     resultFeatures[query] = False
 
@@ -464,7 +455,6 @@ class Dealer(CapitalUser):
             'offlineNotifyTime': self.offlineNotifyTime,
 
             'currencyMode': self.currency_mode,
-            'payAppType': self.payAppType,
 
             'supportedConsumptionShow': self.supportedConsumptionShow,
         })
@@ -655,28 +645,7 @@ class Dealer(CapitalUser):
         else:
             return str(self.username)
 
-    def withdraw_bank_card(self, bank_card_no = None):
-        if not bank_card_no:
-            merchant = Merchant.objects(ownerId = str(self.id)).first()
-        else:
-            merchant = Merchant.objects(accountCode = bank_card_no).first()
 
-        if not merchant or not merchant.accountCode:
-            return None
-        else:
-            return BankCard(
-                id = merchant.id,
-                bankName = merchant.parentBankName,
-                branchName = merchant.subBankName,
-                holderName = merchant.merchantName,
-                cardNo = merchant.accountCode,
-                cardType = merchant.accountType,
-                province = merchant.bankProvinceName,
-                city = merchant.bankCityName,
-                accountType = merchant.accountType,
-                cnapsCode = merchant.cnapsCode,
-                manual = merchant.manual
-            )
 
     @property
     def auto_withdraw_bound_open_id(self):
@@ -684,6 +653,9 @@ class Dealer(CapitalUser):
 
         auth_bridge = get_wechat_auth_bridge(source = self, app_type = APP_TYPE.WECHAT_WITHDRAW)
         return self.get_bound_pay_openid(auth_bridge.bound_openid_key)
+    @property
+    def auto_withdraw_bank_account(self):
+        return self.withdrawOptions.get('bankAccount', None)
 
     def record_income(self, source, source_key, money):
         # type: (str, str, RMB)->bool
@@ -705,12 +677,12 @@ class Dealer(CapitalUser):
         """
         创建自动提现的单子
         :param withdraw_gateway:  提现网关 WechatPaymentGateway
-        :param pay_entity: 银行卡 BankCard
+        :param pay_entity: 银行卡 WithdrawBankCard
         :param income_type: 收益类型  str
         :param amount: 提现金额(非实际到账) RMB
         :param pay_type: 提现的方式 str
         :param manual: 手动提现 Bool
-        :param recurrent: 是否首次 Bool
+        :param recurrent: 是否自动提现 Bool
         :return: WithdrawRecord
         """
 
@@ -882,114 +854,6 @@ class Dealer(CapitalUser):
         else:
             return attr_or_func
 
-    def _check_merchant_valid(self, app):
-        if isinstance(app, JDAggrePayApp):
-            if self.payAppType != PayAppType.JD_AGGR:
-                return False
-            else:
-                return True
-
-        elif isinstance(app, JDOpenPayApp):
-            if self.payAppType != PayAppType.JD_OPEN:
-                return False
-            else:
-                return True
-
-        elif isinstance(app, RuralCreditUnionApp):
-            if self.payAppType != PayAppType.RCU:
-                return False
-            else:
-                return True
-
-        else:
-            return False
-
-    @property
-    def active_pay_app_field(self):
-        if not self.payAppType:
-            return None
-
-        if self.merchantApp:
-            return 'merchantApp'
-        else:
-            if self.payAppType == PayAppType.JD_AGGR:
-                return 'payAppJDAggre'
-            elif self.payAppType == PayAppType.RCU:
-                return 'payAppRCU'
-            else:
-                return None
-
-    @property
-    def customizedMerchantApp(self):  # type: ()->Optional[PayAppT]
-        if not self.payByMerchant:
-            return None
-
-        app_ref = getattr(self, self.active_pay_app_field, None)
-
-        if not app_ref:
-            raise Exception(u'商户配置错误(1001)')
-
-        my_app = app_ref.fetch()
-
-        if not my_app.valid:
-            raise Exception(u'商户配置错误(1002)')
-
-        if not self._check_merchant_valid(my_app):
-            raise Exception(u'商户配置错误(1003)')
-
-        my_app.occupantId = str(self.id)
-        my_app.occupant = self
-        return my_app
-
-    @property
-    def partnerMerchantApp(self):
-        # TODO: 先简单修改,后面整改
-        if self.merchantApp:
-            app = self.merchantApp
-        elif self.payAppJDAggre:
-            app = self.payAppJDAggre
-        else:
-            return None
-
-        my_app = app.fetch()
-
-        if not my_app.valid:
-            raise Exception(u'商户配置错误(1002)')
-
-        my_app.occupantId = str(self.id)
-        my_app.occupant = self
-
-        return my_app
-
-    @property
-    def payByMerchant(self):
-        return self.payAppType
-
-    def wechat_env_pay_app(self, role = None, pay_app_type = None):
-        """
-        对于经销商而言 不存在一级经销商和二级经销商的区别 不需要再次递归寻找
-        :param role:
-        :param pay_app_type:
-        :return:
-        """
-        return self.customizedMerchantApp
-
-    def alipay_env_pay_app(self, role = None, pay_app_type = None):
-        return self.customizedMerchantApp
-
-    def jd_env_pay_app(self, role = None, pay_app_type = None):
-        app = self.customizedMerchantApp
-        if isinstance(app, JDAggrePayApp) or isinstance(app, JDOpenPayApp):
-            return app
-        else:
-            raise Exception(u'系统配置错误(第三方支付)')
-
-    def swap_env_pay_app(self):
-        my_app = SwapPayApp()
-        my_app.occupantId = str(self.id)
-        my_app.occupant = self
-        return my_app
-
     @property
     def my_avatar(self):
         if self.avatar:
@@ -1334,80 +1198,6 @@ class Dealer(CapitalUser):
         result = self.get_collection().update_one(query, update, upsert = False)
         return bool(result.modified_count == 1)
 
-    def get_merchant_split_id(self):
-        """
-        获取商户号绑定的email
-        :return:
-        """
-        app = self.partnerMerchantApp  # type: PayAppBase
-        if not app:
-            return None
-        else:
-            return app.split_id
-
-    def check_merchant_conditions(self):     # type:() -> None
-        """
-        检查商户是否准备就绪,商户开通前的最后一步检查
-        经销商需要检查 所有的合伙人是否完成开通
-        """
-        # 先检查自己
-        if not self.merchantApp and not self.payAppJDAggre:
-            raise MerchantError(u"商户尚未绑定,开通失败")
-
-        # 然后检查合伙人
-        partnerIds = Group.get_lawful_partner_ids(str(self.id))
-        no_merchant_list = []
-        for _id in partnerIds:
-
-            _dealer = Dealer.objects(id = _id).first()  # type: Dealer
-
-            # 查询是否 已经绑定 京东商户app
-            try:
-                app = _dealer.customizedMerchantApp
-            except Exception as e:
-                logger.exception(e)
-                app = None
-
-            if not isinstance(app, JDAggrePayApp):
-                no_merchant_list.append("{} {}".format(_dealer.nickname, _dealer.username))
-
-        # 检查是否有状态不成功的
-        if no_merchant_list:
-            raise MerchantError(u"当前账户尚有未绑定银行卡的合伙人,请联系相应人员进行银行卡绑定,否则无法对账户进行分账\n{}".format("\n".join(no_merchant_list)))
-
-    def confirm_merchant(self, source):     # type: (Optional[MerchantSourceInfo, JDOpenApplyInfo]) -> Dealer
-        """ 商户已经是确认状态所需要处理的事情 """
-        if not source.support_jdaggre():
-            return
-
-        if not source.owner == self:
-            return
-
-        # 首先获取自己的商户
-        app = source.create_app()
-        self.merchantApp = app
-
-        return self.save()
-
-    def success_merchant(self):
-        """
-        确认使用商户的动作
-        """
-        self.payAppType = PayAppType.JD_AGGR
-        return self.save()
-
-    @property
-    def merchantRegister(self):
-        """
-        使用的京东商户 实际注册的人
-        一个经销商有两个账号
-        """
-        if not self.bindMerSrc:
-            logger.error("[merchantRegister] self = {}".format(self.id))
-            return self
-
-        return self.__class__.objects.get(id=self.bindMerSrc)
-
     def get_linkage_switch(self):
         """
         获取经销商的两级联动开关设置
@@ -1733,6 +1523,9 @@ class DealerDict(dict):
 
 # 经销商商户相关信息
 class Merchant(DynamicDocument):
+    """
+    TODO 经销商商户相关信息. 升级需要, 下个版本后删除
+    """
     ownerId = StringField(verbose_name = "经销商ID", default = "")
     merchantType = StringField(verbose_name = "商户类型", default = "")
     merchantName = StringField(verbose_name = "持卡人姓名", default = "")
@@ -1778,46 +1571,6 @@ class Merchant(DynamicDocument):
     def get_collection(cls):
         return cls._get_collection()
 
-    # 超管页面获取对公账号信息
-    @property
-    def get_bankAccountInfo(self):
-        dic = dict()
-        # 卡号
-        dic["accountCode"] = self.accountCode
-        # 银行名称
-        dic["parentBankName"] = self.parentBankName
-        # 支行名称
-        dic["subBankName"] = self.subBankName
-        # 账户名称
-        dic["merchantName"] = self.merchantName
-        # 卡号类型
-        dic["cardType"] = self.cardType
-        # 是否对公
-        dic["isPublic"] = True if self.accountType == BankCard.AccountType.PUBLIC else False
-
-        dic['bankCityID'] = self.bankCityID
-
-        dic['bankProvinceID'] = self.bankProvinceID
-
-        return dic
-
-    # 插入银行信息 使用该函数前要做数据校验({字段:值})
-    def set_bankAccountInfo(self, kwargs):
-        for i in kwargs.keys():
-            if hasattr(self, i):
-                setattr(self, i, kwargs[i])
-            else:
-                continue
-        self.save()
-
-    # def setWithdrawCard(self):
-    #     """
-    #     设置此卡为提现卡
-    #     """
-    #     self.__class__.objects.filter(ownerId=self.ownerId, id__ne=self.id).update(allowed=False)
-    #     self.allowed = True
-    #     self.save()
-
 
 class UpscoreRecord(Document):
     logicalCode = StringField(verbose_name = "设备逻辑编号", default = '')
@@ -2525,6 +2278,9 @@ class SubAccount(UserSearchable):
     def query_feature_by_list(self, queryList):
         return self.myBoss.query_feature_by_list(queryList)
 
+    def get_feature(self, feature_name):
+        return self.myBoss.get_feature(feature_name)
+
     def query_home_page_layout(self):
         return self.myBoss.query_home_page_layout()
 
@@ -3093,11 +2849,40 @@ class TodoMessage(Searchable):
                 return _._load_todoCls()
 
     @classmethod
-    def get_todo_message(cls, user):
+    def get_todo_message(cls, user, typeList = TodoTypeEnum.choices()):
         """
         获取所有未完成的任务
         """
-        return cls.objects.filter(ownerId=str(user.id), done__in=[TodoDone.INIT, TodoDone.ING], expiredTime__gt=datetime.datetime.now())
+
+        items = cls.objects.filter(
+            ownerId = str(user.id),
+            done__in = [TodoDone.INIT, TodoDone.ING],
+            type__in = typeList,
+            expiredTime__gt = datetime.datetime.now())
+
+        dataList = list()
+        for _m in items:  # type: TodoMessage
+            checkModel = _m.checkModel
+            # 进行一次任务检查 查看任务是否完成以及是否需要强制执行
+            hasDone, force = checkModel.check_has_done(_m)
+            if hasDone:
+                _m.has_done()
+                continue
+
+            data = _m.to_dict()
+            data["force"] = force
+            dataList.append(data)
+
+        return dataList
+
+    @classmethod
+    def merchant_message(cls, user):
+        # type: (RoleBaseDocument)->dict
+        if user.role == ROLE.dealer:
+            messages = cls.get_todo_message(user, [TodoTypeEnum.MER_TODO.code])
+            return messages[0] if len(messages) > 0 else {}
+        else:
+            return {}
 
     @classmethod
     def sim_expire_message(cls, ownerId, expireCount):

+ 33 - 19
apps/web/dealer/tasks.py

@@ -401,12 +401,9 @@ def send_SIM_expired_messages(send_type):
                   {'username': 1, 'agentId': 1, 'smsVendor': 1})
     }  # type: Dict[ObjectId, dict]
 
-    agent_product_map = {
-        agent['_id']: agent.get('productName', '').encode('utf-8')
-        for agent in Agent.get_collection()
-            .find({'_id': {'$in': [ObjectId(_['agentId']) for _ in dealer_map.values()]}},
-                  {'productName': 1})
-    }  # type: Dict[ObjectId, str]
+    agent_product_map = {}
+    for agent in Agent.objects(id__in = [_['agentId'] for _ in dealer_map.values()]).all():  # type: Agent
+        agent_product_map[str(agent.id)] = agent.product_name
 
     if not len(dealer_map):
         logger.info('there are no devices are expired at the moment')
@@ -417,15 +414,20 @@ def send_SIM_expired_messages(send_type):
     if not (settings.DEBUG_CELERY_TASK or settings.CHECK_DEVICE_SMS_EXPIRE):
         logger.info('CHECK_DEVICE_SMS_EXPIRE is turned off, you are probably in dev/testing environment')
     else:
+        smsVendor = SysParas.get_sim_expire_sms_vendor(default = SmsVendorCode.UCPAAS)
+
         for id_, dealer in dealer_map.iteritems():
             logger.debug('sending SIM expired message to Dealer(phone=%s)' % (dealer['username'],))
 
             if send_type == 'sms':
-                sms_sender = SMSSender()
+                logger.debug('dealerId = {}, mobile = {}, productName = {}'.format(
+                    id_, dealer['username'], agent_product_map[dealer['agentId']]))
+
+                sms_sender = SMSSender(vendor = smsVendor)
                 response = sms_sender.send(phoneNumber = dealer['username'],
                                            templateName = "SMS_NOTIFY_EXPIRED_DEVICE_TEMPLATEID",
                                            msg = u'您有设备即将在近期过期'.encode('utf-8'),
-                                           productName = agent_product_map[ObjectId(dealer['agentId'])])
+                                           productName = agent_product_map[dealer['agentId']])
                 if not response['result']:
                     logger.error(
                         'send sim expired sms to dealer failed(phone={}), error={}'.format(
@@ -441,8 +443,8 @@ def send_SIM_expired_messages(send_type):
 
                 wechat_mp_proxy = get_wechat_manager_mp_proxy(dealer)
                 wechat_mp_proxy.notify(dealer.managerialOpenId, 'sim_expire_notify', **{
-                    'title': u'亲爱的{}用户,您有设备本月流量卡过期,请及时充值。已经充值请忽略本消息。'.format(
-                        agent_product_map[ObjectId(dealer['agentId'])]),
+                    'title': u'亲爱的用户,您有设备本月流量卡过期,请及时充值。已经充值请忽略本消息。'.format(
+                        agent_product_map[dealer['agentId']]),
                     'num': u'{}台(本月)'.format(len(dealer_device_map.get(str(id_)))),
                     'type': u'流量卡到期',
                     'time': arrow.now().replace(day = Const.SIM_CARD_FORBIDDEN_DAY).format('YYYY-MM-DD')
@@ -1804,21 +1806,33 @@ def dealer_auto_withdraw():
         try:
             for incomeType in DEALER_INCOME_TYPE.choices():
                 withdrawInfoList = Dealer.get_income_balance_list(dealer, incomeType, settings.WITHDRAW_MINIMUM)
-                autoWithdrawType = dealer.withdrawOptions.get("autoWithdrawType")
 
+                bank_card_no = None
+
+                autoWithdrawType = dealer.withdrawOptions.get("autoWithdrawType")
                 if autoWithdrawType == 'wechat':
-                    dealer.withdraw_open_id = dealer.auto_withdraw_bound_open_id
+                    if not dealer.auto_withdraw_bound_open_id:
+                        logger.warning(
+                            'dealer<id={}> of auto withdraw<type=wechat> has no bound open id.'.format(str(dealer.id)))
+                        continue
+                    else:
+                        dealer.withdraw_open_id = dealer.auto_withdraw_bound_open_id
+                else:
+                    bank_card_no = dealer.auto_withdraw_bank_account
+                    if not bank_card_no:
+                        logger.warning(
+                            'dealer<id={}> of auto withdraw<type=wechat> has no bound bank account.'.format(str(dealer.id)))
+                        continue
 
-                # 对每一种收益分开提现建单
                 for sourceKey, balance in withdrawInfoList:
                     try:
-                        result = DealerWithdrawService(payee = dealer,
-                                                       income_type = incomeType,
-                                                       amount = balance,
-                                                       pay_type = autoWithdrawType).execute(sourceKey, True)
-
+                        result = DealerWithdrawService(
+                            payee = dealer,
+                            income_type = incomeType,
+                            amount = balance,
+                            pay_type = autoWithdrawType,
+                            bank_card_no = bank_card_no).execute(sourceKey, True)
                         logger.info("auto withdraw success, result is <{}>".format(result))
-
                     except Exception as e:
                         logger.exception(e)
 

+ 5 - 10
apps/web/dealer/urls.py

@@ -27,7 +27,8 @@ urlpatterns = patterns('', *[
     url(r'^verifyForgetCode$', verifyForgetCode, name = 'verifyForgetCode'),
 
     # 验证短信验证码
-    url(r'^subAccount/verifyForgetCode$', verifySubAccountForgetCode, name = 'verifySubAccountForgetCode'),
+    url(r'^subAccount/verifyForgetCode$', verifySubAccountForgetCode,
+        name = 'verifySubAccountForgetCode'),
 
     url(r'^wechat/entry$', wechatEntry, name = 'wechatEntry'),
 
@@ -59,7 +60,8 @@ urlpatterns = patterns('', *[
     url(r'^groupIncomeData$', groupIncomeData, name = 'groupIncomeData'),
 
     # 查询组内设备报表
-    url(r'^groupEquipmentIncomeByGroupId$', groupEquipmentIncomeByGroupId, name = 'groupEquipmentIncomeByGroupId'),
+    url(r'^groupEquipmentIncomeByGroupId$', groupEquipmentIncomeByGroupId,
+        name = 'groupEquipmentIncomeByGroupId'),
 
     # 根据groupID查询统计数据
     url(r'^groupIncomeByGroupId$', groupIncomeByGroupId, name = 'groupIncomeByGroupId'),
@@ -121,14 +123,6 @@ urlpatterns = patterns('', *[
     # 查询钱包
     url(r'^walletData$', walletData, name = 'walletData'),
 
-    # 查询银行卡信息
-    url(r'^getWalletBank$', getWalletBank, name = 'getWalletBank'),
-    # 保存银行卡信息
-    url(r'^saveWalletBank$', saveWalletBank, name = 'saveWalletBank'),
-    # 解绑银行卡
-    url(r'^bankCardUnbind$', bankCardUnbind, name = 'bankCardUnbind'),
-    # 查询商户信息
-    url(r'^getMerchantByOrg$', getMerchantByOrg, name = 'getMerchantByOrg'),
     # 查询提现记录
     url(r'^withdrawalsHistoryList$', withdrawalsHistoryList,
         name = 'withdrawalsHistoryList'),
@@ -704,6 +698,7 @@ urlpatterns = patterns('', *[
     url(r'^getConsumeTemplate', getConsumeTemplate, name = 'getConsumeTemplate'),
     url(r'^setConsumeTemplate', setConsumeTemplate, name = 'setConsumeTemplate'),
     url(r'^refundOrder', refundOrder, name = 'refundOrder'),
+
     url(r'^todo/list', getDealerTodoMessage, name = 'getDealerMessage'),
 
     url(r'^users$', userList, name = 'userList'),

+ 19 - 12
apps/web/dealer/utils.py

@@ -886,33 +886,40 @@ class TodoProcessor(object):
 
 
 class MerchantTodo(TodoProcessor):
-
     @classmethod
     def insert_todo(cls, user):
+        # type: (Dealer)->Optional[TodoMessage]
+
         # 已经申请过商户的 就不再处理了
         if MerchantSourceInfo.get_source_record(user).status == MerchantStatus.SUCCESS:
             logger.info("dealer <{}> has apply merchant!".format(user.id))
-            return
+            return None
 
-        # 插入一条消息
         try:
-            todo = TodoMessage.objects.get(ownerId=str(user.id), type=TodoTypeEnum.MER_TODO.code)    # type: TodoMessage
+            todo = TodoMessage.objects.get(ownerId = str(user.id),
+                                           type = TodoTypeEnum.MER_TODO.code)  # type: TodoMessage
         except DoesNotExist:
-            TodoMessage(
-                title=u"商户开通提醒",
-                content=u"根据国家相关政策,商户收款需提交身份证、银行卡等相关信息,请尽快完成商户开通,否则可能会影响您的正常收款",
-                type=TodoTypeEnum.MER_TODO.code,
-                link= concat_front_end_url(uri = '/dealer/index.html#/merchant'),
-                ownerId=str(user.id),
-                expiredTime=datetime.datetime(2099, 1, 1)
+            if user.supports('forceMerchant'):
+                msg = u'根据国家相关政策,商户收款需提交身份证、银行卡等相关信息,请尽快完成商户开通。'
+            else:
+                msg = u'根据国家相关政策,商户收款需提交身份证、银行卡等相关信息,请尽快完成商户开通,否则可能会影响您的正常收款和提现。'
+            todo = TodoMessage(
+                title = u"商户开通提醒",
+                content = msg,
+                type = TodoTypeEnum.MER_TODO.code,
+                link = concat_front_end_url(uri = '/dealer/index.html#/merchant'),
+                ownerId = str(user.id),
+                expiredTime = datetime.datetime(2099, 1, 1)
             ).save()
         else:
             todo.done = TodoDone.INIT
             todo.expiredTime = datetime.datetime(2099, 1, 1)
             todo.save()
 
+        return todo
+
     @classmethod
-    def check_has_done(cls, todo): # type:(TodoMessage) -> (bool, bool)
+    def check_has_done(cls, todo):  # type:(TodoMessage) -> (bool, bool)
         """
         返回参数是两个 是否完成 和是否需要强制显示
         """

+ 47 - 241
apps/web/dealer/views.py

@@ -49,8 +49,8 @@ from apps.thirdparties.aliyun import AliyunSlider
 from apps.web.agent.models import Agent
 
 from apps.web.api.models import APIStartDeviceRecord
-from apps.web.common.models import District, DEFAULT_DEALER_FEATURES, WithdrawRecord, OperatorLog, Banks, \
-    MonthlyPackageTemp
+from apps.web.common.models import District, DEFAULT_DEALER_FEATURES, WithdrawRecord, OperatorLog, \
+    MonthlyPackageTemp, WithdrawBankCard
 from apps.web.common.proxy import ClientRechargeModelProxy, ClientConsumeModelProxy, DealerDailyStatsModelProxy, \
     GroupDailyStatsModelProxy, DeviceDailyStatsModelProxy, ClientDealerIncomeModelProxy, DealerReportModelProxy
 from apps.web.common.transaction import WithdrawStatus, WITHDRAW_PAY_TYPE, translate_withdraw_state
@@ -84,7 +84,7 @@ from apps.web.core.messages.sms import (
     dealerBindWalletWechatSMSProvider,
     dealerEditMonitorSMSProvider,
     dealerMonitorWithdrawSMSProvider)
-from apps.web.core.models import OfflineTask, SaobeiPayApp, WechatPayApp, JDAggrePayApp, WechatAuthApp
+from apps.web.core.models import OfflineTask, WechatPayApp, WechatAuthApp
 from apps.web.core.networking import MessageSender
 from apps.web.core.payment import WithdrawGateway, PaymentGateway
 from apps.web.core.payment.wechat import WechatPaymentGateway
@@ -1959,6 +1959,9 @@ def getIncomeList(request):
 
     groupId = request.GET.get('groupId')
 
+    if groupId and groupId == 'undefined':
+        return JsonErrorResponse(description = u'地址过滤参数错误,请刷新后重试')
+
     if not groupId:
         groupIds = [ObjectId(groupId) for groupId in Group.get_group_ids_of_dealer_and_partner(current_dealer_id)]
     else:
@@ -1968,6 +1971,7 @@ def getIncomeList(request):
 
     filters = {
         "groupId__in": groupIds,
+        "hint": [('groupId', 1), ('dateTimeAdded', -1)],
     }
 
     if logicalCode:
@@ -3207,7 +3211,6 @@ def walletData(request):
             })
 
     payload = {
-        'balance': dealer.total_balance
     }
 
     if len(device_income_list) > 0:
@@ -3254,130 +3257,6 @@ def getWithdrawCode(request):
         return JsonOkResponse()
 
 
-@error_tolerate(nil=DefaultJsonErrorResponse)
-@permission_required(ROLE.dealer)
-def getWalletBank(request):
-    # type: (WSGIRequest)->JsonResponse
-
-    ownerId = str(request.user.bossId)
-
-    merchants = Merchant.objects.filter(ownerId=ownerId)
-    dataList = list()
-    for mer in merchants:
-        dataList.append({
-            "id": str(mer.id),
-            "cardNo": mer.accountCode,
-            "bankName": mer.parentBankName,
-            "bankType": mer.cardType,
-            "cardType": mer.accountType
-        })
-
-    return JsonOkResponse(payload={'dataList': dataList})
-
-
-@error_tolerate(nil=DefaultJsonErrorResponse)
-@permission_required(ROLE.dealer)
-def saveWalletBank(request):
-    # type: (WSGIRequest)->JsonResponse
-    """
-    添加银行卡 经销商只能添加私人银行卡
-    """
-    ownerId = str(request.user.bossId)
-    payload = json.loads(request.body)
-
-    merchantName = payload.get("merchantName")
-    accountCode = payload.get("accountCode")
-    parentBankName = payload.get("parentBankName")
-    cardType = payload.get("cardType")
-    accountType = payload.get("accountType")
-    subBankName = payload.get('subBankName', '')
-
-    if not merchantName:
-        return JsonResponse({"result": 2, "description": "缺少持卡人姓名"})
-    if not accountCode:
-        return JsonResponse({"result": 2, "description": "缺少银行卡号"})
-    if not parentBankName:
-        return JsonResponse({"result": 2, "description": "缺少银行名称"})
-    if not subBankName:
-        return JsonResponse({"result": 2, "description": "缺少支行名称"})
-
-    if Merchant.objects.filter(ownerId=ownerId).count() > 5:
-        return JsonResponse({"result": 2, "description": "银行卡数量已添加至上限(5),暂不允许继续添加"})
-
-    if Merchant.objects.filter(ownerId=ownerId, accountCode=accountCode):
-        return JsonResponse({"result": 2, "description": "请勿重复添加"})
-
-    if not Banks.support_personal(parentBankName):
-        return JsonResponse({"result": 2, "description": "不支持该银行卡的绑定"})
-
-    mer = Merchant(
-        ownerId=ownerId,
-        merchantName=merchantName,
-        accountCode=accountCode,
-        parentBankName=parentBankName,
-        cardType=cardType,
-        accountType=accountType,
-        subBankName=subBankName
-    ).save()
-    return JsonOkResponse(payload={"id": str(mer.id)})
-
-
-@error_tolerate(nil=DefaultJsonErrorResponse)
-@permission_required(ROLE.dealer)
-def bankCardUnbind(request):
-    # type: (WSGIRequest)->JsonResponse
-
-    ownerId = str(request.user.bossId)
-    payload = json.loads(request.body)
-    bankId = payload.get('id', '')
-
-    if not bankId:
-        return JsonResponse({"result": 2, "description": "读取银行卡信息失败,请重新获取"})
-    card = Merchant.objects.filter(ownerId=ownerId, id=bankId)
-    if not card:
-        return JsonResponse({"result": 2, "description": "读取银行卡信息失败,请重新获取"})
-
-    card.delete()
-
-    return JsonOkResponse()
-
-
-# @error_tolerate(nil=DefaultJsonErrorResponse)
-# @permission_required(ROLE.dealer)
-# def setWithdrawCard(request):
-#     ownerId = str(request.user.bossId)
-#     bankId = request.POST.get('id', '')
-#
-#     if not bankId:
-#         return JsonResponse({"result": 2, "description": "读取银行卡信息失败,请重新获取"})
-#     card = Merchant.objects.filter(ownerId=ownerId, id=bankId)
-#     if not card:
-#         return JsonResponse({"result": 2, "description": "读取银行卡信息失败,请重新获取"})
-#
-#     card.setWithdrawCard()
-#
-#     return JsonOkResponse()
-
-
-@permission_required(ROLE.dealer)
-def getMerchantByOrg(request):
-    # type: (WSGIRequest)->JsonResponse
-
-    ownerId = str(request.user.bossId)
-    try:
-        merchant = Merchant.get_collection().find({'ownerId': ownerId})
-    except Exception, e:
-        logger.exception('find Merchant error=%s,ownerId=%s' % (e, ownerId))
-        return JsonErrorResponse(description=u"没有找到经销商的商户号")
-
-    if merchant.count() == 0:
-        return JsonErrorResponse(description=u"没有找到商户账号")
-
-    result = merchant[0]
-
-    return JsonOkResponse(payload=result)
-
-
 @permission_required(ROLE.dealer)
 def withdrawalsHistoryList(request):
     # type: (WSGIRequest)->JsonResponse
@@ -3450,7 +3329,8 @@ def dealerWithdraw(request):
     amount = RMB(payload.get('amount', 0.0))
     pay_type = payload.get('payType')
 
-    assert pay_type in (WITHDRAW_PAY_TYPE.WECHAT, WITHDRAW_PAY_TYPE.BANK), 'not support this pay type'
+    assert pay_type in (
+        WITHDRAW_PAY_TYPE.WECHAT, WITHDRAW_PAY_TYPE.BANK, WITHDRAW_PAY_TYPE.ALIPAY), 'not support this pay type'
     assert amount > RMB(0), 'amount must be bigger than zero'
 
     status, msg = request.user.withdraw_sms_provider.verify(phoneNumber=request.user.withdraw_sms_phone_number,
@@ -4062,8 +3942,6 @@ def getCustomerListUnderDealer(request):
             user["bestowBalance"] = RMB(user.get("bestowBalance", 0))
             user['balance'] = user['chargeBalance'] + user['bestowBalance']
 
-    from pprint import pprint
-    pprint(tempUsers)
     return JsonResponse({
         "result": 1,
         "description": None,
@@ -4503,7 +4381,7 @@ def payGateway(request):
         money = RMB(record.totalFee) * Decimal('0.01')
 
         if payment_gateway.pay_app_type == PayAppType.WECHAT:
-            data = payment_gateway.unified_order(
+            data = payment_gateway.generate_js_payment_params(
                 payOpenId=payload.get('openId'),
                 out_trade_no=order_no,
                 notify_url=PAY_NOTIFY_URL.WECHAT_PAY_BACK,
@@ -4516,62 +4394,6 @@ def payGateway(request):
             })
             response = JsonResponse({'result': 1, 'description': '', 'payload': data})
 
-        elif payment_gateway.pay_app_type == PayAppType.SAOBEI:
-            front_url = payload.get('front_url')
-            pay_url = payment_gateway.create_pay_url(pay_trace=record.orderNo,
-                                                     pay_time=record.extraInfo['payTime'],
-                                                     money=money,
-                                                     attach=attach,
-                                                     body=body,
-                                                     notify_url=PAY_NOTIFY_URL.SAOBEI_PAY_BACK,
-                                                     front_url=front_url)
-            if not pay_url:
-                return JsonErrorResponse(u'调起支付失败,请刷新后再试')
-
-            response = JsonResponse({
-                'result': 1,
-                'description': 'SUCCESS',
-                'payload': {
-                    'payUrl': pay_url
-                }})
-        elif payment_gateway.pay_app_type == PayAppType.JD_AGGR:
-            pay_info = payment_gateway.unified_order(
-                out_trade_no=record.orderNo,
-                money=money,
-                notify_url=PAY_NOTIFY_URL.JD_AGGRE_PAY_BACK,
-                subject=body,
-                expire=300,
-                attach={'dealerId': record.dealerId},
-                openId=payload.get('openId'),
-                **{'imei': str(current_user.id)[0:30]})
-
-            pi_type = payment_gateway.pi_type
-            if pi_type == PiType.ALIPAY:
-                response = JsonOkResponse(payload={'tradeNO': pay_info['tradeNO'],
-                                                     'outTradeNo': record.orderNo,
-                                                     'adShow': current_user.ad_show})
-            elif pi_type == PiType.WX:
-                pay_info.update({'golden': True, 'adShow': current_user.ad_show})
-                response = JsonOkResponse(payload=pay_info)
-            elif pi_type == PiType.JDPAY:
-                front_url = payload.get('front_url', None)
-                if front_url:
-                    pay_url = add_query(pay_info, {
-                        'callbackUrl': front_url
-                    })
-                else:
-                    pay_url = pay_info
-
-                if not pay_url:
-                    return JsonErrorResponse(u'调起支付失败,请刷新后再试')
-
-                response = JsonResponse(
-                    {
-                        'result': 1,
-                        'description': 'SUCCESS',
-                        'payload': {
-                            'payUrl': pay_url
-                        }})
         else:
             raise Exception(cn(u'不支持该支付类型'))
 
@@ -4641,7 +4463,7 @@ def getDeviceCardList(request):
                 'remarks'] or searchKey in dev.logicalCode:
                 group = groupDict.get(dev.groupId)
 
-                if (settings.DEBUG and dev.logicalCode.startswith('DUMMY')) or dev.sim_expire_notify:
+                if (settings.DEBUG and dev.logicalCode.startswith('DUMMY')) or dev.is_expired or dev.sim_expire_notify:
                     item = {
                         'logicalCode': dev.logicalCode,
                         'devNo': dev.devNo,
@@ -7171,25 +6993,14 @@ def freezeCard(request):
 @error_tolerate(logger=logger, nil=JsonErrorResponse(u'系统错误'))
 @permission_required(ROLE.dealer, ROLE.subaccount)
 def getWalletWithdrawInfo(request):
-
     dealer = request.user  # type: Dealer
 
     if not is_dealer(dealer):
-        return ErrorResponseRedirect(error=u'子账号无提现权限')
+        return ErrorResponseRedirect(error = u'子账号无提现权限')
 
     source_key = request.GET.get('sourceId')
     income_type = request.GET.get('sourceType')
 
-    query = Merchant.objects(ownerId=str(dealer.id))
-    merchants = list()
-    for mer in query:
-        merchants.append({
-            'bankAccount': mer.accountCode,
-            'subBank': mer.subBankName,
-            'cardHolder': mer.merchantName,
-            'bankName': mer.parentBankName
-        })
-
     phone = str(dealer.monitorPhone) if dealer.monitorPhone else str(dealer.username)
 
     result = {
@@ -7197,20 +7008,13 @@ def getWalletWithdrawInfo(request):
         "description": None,
         'payload': {
             'payOpenId': 'placeholder',
-            # 'bankAccount': merchant['accountCode'],
-            # 'subBank': merchant['subBankName'],
             'phone': phone,
             'balance': dealer.sub_balance(income_type, source_key),
-            # 'cardHolder': merchant['merchantName'],
-            # 'bankName': merchant['parentBankName'],
             'withdrawFeeRatio': dealer.withdrawFeeRatio,
-            'merchants': merchants
+            'support': dealer.withdraw_support(source_key)
         }
     }
 
-    dealerBankWithdrawFee = Agent.withdraw_gateway_list(source_key)['wechat'].occupant.dealerBankWithdrawFee
-    result['payload']['payBankTransFee'] = dealerBankWithdrawFee and dealer.bankWithdrawFee
-
     return JsonResponse(result)
 
 
@@ -8910,11 +8714,13 @@ def withdrawEntry(request):
     if source_key not in user.balance_dict(source_type):
         return ErrorResponseRedirect(error=u'提现参数错误,请刷新后重试')
 
-    withdraw_gateway_list = Agent.withdraw_gateway_list(source_key)
-    wechat_withdraw_gateway = withdraw_gateway_list['wechat']
+    is_ledger, withdraw_gateway_list = Agent.withdraw_gateway_list(source_key)
+    if not is_ledger:
+        return ErrorResponseRedirect(error = u'系统配置错误,请联系平台客服(10005)')
 
+    wechat_withdraw_gateway = withdraw_gateway_list['wechat']
     if not wechat_withdraw_gateway:
-        return ErrorResponseRedirect(error=u'系统配置错误,请联系平台客服(10005)')
+        return ErrorResponseRedirect(error = u'系统配置错误,请联系平台客服(10006)')
 
     code = request.GET.get('code', None)
     if not code:
@@ -11797,24 +11603,27 @@ def getAutoWithdrawConfig(request):
         "autoWithdrawBankFee": dealer.auto_withdraw_bank_fee,
         "withdrawFeeRatio": dealer.withdrawFeeRatio,
         "autoWithdrawType": dealer.auto_withdraw_type,
-        "weekDay": dealer.auto_withdraw_strategy['value'],
+        "weekDay": dealer.auto_withdraw_strategy.get('value'),
         "autoWithdrawMin": dealer.auto_withdraw_min
     }
 
     if dealer.monitorPhone:
         payload['phone'] = dealer.monitorPhone
 
-    # 添加银行卡信息
-    merchants = Merchant.get_collection().find({'ownerId': str(dealer.id)})
-    if merchants.count() > 0:
-        curMerchant = merchants[0]
-        bankCardData = {
-            'bankAccount': curMerchant['accountCode'],
-            'bankName': curMerchant['parentBankName'],
-        }
-        payload.update(bankCardData)
+    cards = []
 
-    return JsonOkResponse(payload=payload)
+    for bankCard in WithdrawBankCard.objects(ownerId = str(dealer.id), role = dealer.role):
+        cards.append({
+            'accountCode': bankCard.accountCode,
+            'accountName': bankCard.accountName,
+            'bankName': bankCard.bankName
+        })
+
+    payload.update({
+        'cards': cards
+    })
+
+    return JsonOkResponse(payload = payload)
 
 
 @error_tolerate(logger=logger, nil=JsonErrorResponse(u"保存自动提现信息失败"))
@@ -11822,19 +11631,19 @@ def getAutoWithdrawConfig(request):
 def saveAutoWithdrawConfig(request):
     dealer = request.user
 
-    autoWithdrawSwitch = json.loads(request.POST.get("autoWithdrawSwitch"))
+    payload = json.loads(request.body)
+    autoWithdrawSwitch = payload.get("autoWithdrawSwitch")
 
-    autoWithdrawMin = RMB(request.POST.get("autoWithdrawMin"))
+    autoWithdrawMin = RMB(payload.get("autoWithdrawMin"))
     if autoWithdrawMin < RMB(settings.WITHDRAW_MINIMUM):
         return JsonErrorResponse(description=u"最小提现金额不能少于提现接口设置最小转账金额({}元)".format(settings.WITHDRAW_MINIMUM))
 
-    autoWithdrawType = request.POST.get("autoWithdrawType")
+    autoWithdrawType = payload.get("autoWithdrawType")
 
-    weekDay = int(request.POST.get("weekDay"))
+    weekDay = int(payload.get("weekDay"))
 
-    smsCode = request.POST.get("code", "")
+    smsCode = payload.get("code", "")
 
-    bankAccount = request.POST.get("bankAccount", "")
 
     status, msg = dealerWithdrawSMSProvider.verify(dealer.username, smsCode)
     if not status:
@@ -11852,8 +11661,12 @@ def saveAutoWithdrawConfig(request):
         'autoWithdrawSwitch': autoWithdrawSwitch,
         'autoWithdrawType': autoWithdrawType,
         'autoWithdrawStrategy': {'type': 'asWeek', 'value': weekDay},
-        'autoWithdrawMin': autoWithdrawMin.mongo_amount
+        'autoWithdrawMin': autoWithdrawMin.mongo_amount,
     })
+    if 'bankAccount' in payload:
+        dealer.withdrawOptions.update({
+            'bankAccount': payload['bankAccount']
+        })
     dealer.save()
 
     return JsonOkResponse(description=u'自动提现配置成功')
@@ -13622,6 +13435,9 @@ def refundOrder(request):
     if rechargeOrder.coins < deductCoins:
         return JsonErrorResponse(description=u"扣除金币数大于用户实际购买数目")
 
+    currency = rechargeOrder.myuser.calc_currency_balance(rechargeOrder.owner, rechargeOrder.group)
+    if currency < deductCoins:
+        return JsonErrorResponse(description = u"扣除金币数大于用户目前余额")
     if not rechargeOrder.is_success():
         return JsonErrorResponse(description = u"退款失败,订单状态非成功,请联系平台客服")
 
@@ -13648,20 +13464,10 @@ def getDealerTodoMessage(request):
     获取经代销商的代办事项
     """
     messages = TodoMessage.get_todo_message(request.user)
-    dataList = list()
-    for _m in messages:  # type: TodoMessage
-        checkModel = _m.checkModel
         # 进行一次任务检查 查看任务是否完成以及是否需要强制执行
-        hasDone, force = checkModel.check_has_done(_m)
-        if hasDone:
-            _m.has_done()
-            continue
 
-        data = _m.to_dict()
-        data["force"] = force
-        dataList.append(data)
 
-    return JsonOkResponse(payload={"count": len(dataList), "dataList": dataList})
+    return JsonOkResponse(payload={"count": len(messages), "dataList": messages})
 
 
 @permission_required(ROLE.dealer)

+ 3 - 0
apps/web/exceptions.py

@@ -53,3 +53,6 @@ class NoSupportPayment(Exception):
 class DuplicatedOperationError(Exception):
     pass
 
+
+class WithdrawOrderNotExist(UserServerException):
+    pass

+ 6 - 18
apps/web/helpers.py

@@ -18,16 +18,14 @@ from apps.web.core.auth.wechat import WechatAuthBridge
 from apps.web.core.bridge import WechatClientProxy
 from apps.web.core.datastructures import BaseVisitor
 
-
 from apps.web.utils import is_user, is_dealer, is_agent, is_anonymous
-from apps.web.core.models import WechatPayApp, AliApp, SaobeiPayApp, JDAggrePayApp, DlbPayApp, SystemSettings, \
-    ManualPayApp, SwapPayApp
+from apps.web.core.models import WechatPayApp, AliApp, SystemSettings, ManualPayApp
 
 logger = logging.getLogger(__name__)
 
 if TYPE_CHECKING:
     from apps.web.device.models import DeviceDict
-    from apps.web.core.models import WechatManagerApp, WechatAuthApp, RuralCreditUnionApp
+    from apps.web.core.models import WechatManagerApp, WechatAuthApp
     from apps.web.ad.models import Advertiser, Advertisement
     from apps.web.user.models import MyUser
     from apps.web.common.models import WithdrawRecord
@@ -42,7 +40,7 @@ if TYPE_CHECKING:
 
     from apps.web.core.payment import WechatMiniPaymentGatewayT
 
-    WechatAPP_T = Union[WechatPayApp, WechatManagerApp, WechatAuthApp, JDAggrePayApp, SwapPayApp]
+    WechatAPP_T = Union[WechatPayApp, WechatManagerApp, WechatAuthApp]
 
 
 class DeviceTypeVisitor(BaseVisitor):
@@ -265,7 +263,7 @@ def get_wechat_env_pay_gateway(source, role = None, pay_app_type = None, vistor
                   app_type = APP_TYPE.WECHAT_ENV_PAY,
                   role = role,
                   vistor = vistor,
-                  pay_app_type = pay_app_type)  # type: Optional[WechatPayApp, JDAggrePayApp, SaobeiPayApp, DlbPayApp, RuralCreditUnionApp]
+                  pay_app_type = pay_app_type)  # type: Optional[WechatPayApp]
     return app.new_gateway(AppPlatformType.WECHAT)
 
 
@@ -274,19 +272,9 @@ def get_alipay_env_pay_gateway(source, role = None, pay_app_type = None, vistor
                   app_type = APP_TYPE.ALIPAY_ENV_PAY,
                   role = role,
                   vistor = vistor,
-                  pay_app_type = pay_app_type)  # type: Optional[AliApp, JDAggrePayApp, SaobeiPayApp, DlbPayApp]
+                  pay_app_type = pay_app_type)  # type: Optional[AliApp]
     return app.new_gateway(AppPlatformType.ALIPAY)
 
-
-def get_jd_env_pay_gateway(source, role = None, pay_app_type = None, vistor = AgentCustomizedBrandVisitor):
-    app = get_app(source = source,
-                  app_type = APP_TYPE.JD_ENV_PAY,
-                  role = role,
-                  vistor = vistor,
-                  pay_app_type = pay_app_type)  # type: Optional[JDAggrePayApp, DlbPayApp]
-    return app.new_gateway(AppPlatformType.JD)
-
-
 def get_inhourse_wechat_env_pay_gateway(role = None, pay_app_type = None):
     """
     使用平台商户进行支付. 例如SIM卡充值, API充值以及清静计划充值等
@@ -302,7 +290,7 @@ def get_inhourse_wechat_env_pay_gateway(role = None, pay_app_type = None):
     app = get_app(source = inhouse_agent,
                   app_type = APP_TYPE.WECHAT_ENV_PAY,
                   role = role,
-                  pay_app_type = pay_app_type)  # type: Optional[WechatPayApp, JDAggrePayApp, SaobeiPayApp, DlbPayApp]
+                  pay_app_type = pay_app_type)  # type: Optional[WechatPayApp]
     return app.new_gateway(AppPlatformType.WECHAT)
 
 

+ 0 - 21
apps/web/merchant/__init__.py

@@ -1,21 +0,0 @@
-# coding=utf-8
-from apps.web.merchant.signal import merchant_user_submit, merchant_jd_access, merchant_enter, create_customer_success, create_settle_success, create_shop_success, \
-    upload_attach_success, complete_success
-from apps.web.merchant.utils import upload_merchant_to_auth, upload_merchant_to_jd, create_settle, create_shop, upload_attaches, complete_customer, confirm_customer, \
-    create_psi_product
-
-merchant_user_submit.connect(upload_merchant_to_jd)
-
-merchant_jd_access.connect(upload_merchant_to_auth)
-
-merchant_enter.connect(create_psi_product)
-
-create_customer_success.connect(create_settle)
-
-create_settle_success.connect(create_shop)
-
-create_shop_success.connect(upload_attaches)
-
-upload_attach_success.connect(complete_customer)
-
-complete_success.connect(confirm_customer)

File diff suppressed because it is too large
+ 0 - 291
apps/web/merchant/constant.py


+ 0 - 4
apps/web/merchant/exceptions.py

@@ -1,4 +0,0 @@
-# -*- coding: utf-8 -*-
-# !/usr/bin/env python
-class JdOpenParamsError(Exception):
-    pass

File diff suppressed because it is too large
+ 0 - 3127
apps/web/merchant/models.py


+ 0 - 28
apps/web/merchant/signal.py

@@ -1,28 +0,0 @@
-# coding=utf-8
-from django.dispatch import Signal
-
-# 用户提交完毕资料的信号     通用
-merchant_user_submit = Signal(providing_args=["merchantId"])
-
-# 商户审核完毕的信号         通用
-merchant_jd_access = Signal(providing_args=["merchantId"])
-
-# 商户资料提交后申请开通支付产品  京东自研
-merchant_enter = Signal(providing_args=["merchantId"])
-
-# 商户创建完成   京东open
-create_customer_success = Signal(providing_args=["merchantId"])
-
-# 结算创建成功   京东open
-create_settle_success = Signal(providing_args=["merchantId"])
-
-# 商店创建成功   京东open
-create_shop_success = Signal(providing_args=["merchantId"])
-
-# 附件上传完毕   京东open
-upload_attach_success = Signal(providing_args=["merchantId"])
-
-# 报单完成       京东open
-complete_success = Signal(providing_args=["merchantId"])
-
-

+ 0 - 73
apps/web/merchant/tasks.py

@@ -1,73 +0,0 @@
-# -*- coding: utf-8 -*-
-# !/usr/bin/env python
-
-import logging
-
-
-from apps.web.merchant.models import MerchantSourceInfo
-from apps.web.merchant.utils import MerchantApplyProxy
-from apps.web.merchant.constant import MerchantStatus
-
-logger = logging.getLogger(__name__)
-
-
-def query_merchant_status():
-    """
-    申请开通京东的产品
-    """
-
-    # 查询出所有 审核中的商户
-    merchantIds = MerchantSourceInfo.objects.filter(status__in=[
-        int(MerchantStatus.WAITING),
-    ]
-    ).only("status", "id")
-
-    for _m in merchantIds:
-        _mer = MerchantSourceInfo.objects.get(id=_m.id)
-        try:
-            MerchantApplyProxy(_mer).query_merchant_audit()
-        except Exception as e:
-            logger.exception("[tasks_query_merchant_status_query_merchant_audit], merchantId = _{}, error = {}".format(_m, e))
-
-    # 查询出状态为 confirm(有可能程序中断的)
-    merchantIds = MerchantSourceInfo.objects.filter(status__in=[
-        int(MerchantStatus.CONFIRM),
-    ]
-    ).only("status", "id")
-
-    for _m in merchantIds:
-        _mer = MerchantSourceInfo.objects.get(id=_m.id)
-        proxy = MerchantApplyProxy(_mer)
-        try:
-            proxy.submit_auth()
-        except Exception as e:
-            logger.exception("[tasks_query_merchant_status_submit_auth], merchantId = _{}, error = {}".format(_m, e))
-
-    # 查询出说有提交了资料的 进行查询
-    merchantIds = MerchantSourceInfo.objects.filter(status__in=[
-        int(MerchantStatus.AUTH_WAITING),
-    ]
-    ).only("status", "id")
-
-    for _m in merchantIds:
-        _mer = MerchantSourceInfo.objects.get(id=_m.id)
-        proxy = MerchantApplyProxy(_mer)
-        try:
-            proxy.query_auth_audit()
-        except Exception as e:
-            logger.exception("[tasks_query_merchant_status_submit_auth], merchantId = _{}, error = {}".format(_m, e))
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-

+ 0 - 2
apps/web/merchant/testCase.py

@@ -1,2 +0,0 @@
-# coding=utf-8
-

+ 0 - 42
apps/web/merchant/urls.py

@@ -1,42 +0,0 @@
-# -*- coding: utf-8 -*-
-#!/usr/bin/env python
-
-from django.conf.urls import patterns, url
-
-from apps.web.merchant.views import *
-
-urlpatterns = patterns(
-    "",
-    url(regex = r"^getMerchantOptions$", view = getMerchantOptions, name = "getMerchantOptions"),
-    url(regex = r"^uploadImage$", view = uploadImage, name = "uploadImage"),
-    url(regex = r"^sendMSMCodeToOpenMerchant$", view = sendMerchantSmsCode, name = "sendMerchantSmsCode"),
-    url(regex=r"^getPartnerMerchantInfo$", view=getPartnerMerchantInfo, name="getPartnerMerchantInfo"),
-    url(regex=r"^savePartnerInfo$", view=savePartnerMerchantInfo, name="savePartnerMerchantInfo"),
-    url(regex=r"^address$", view=address, name="address"),
-    url(regex=r"^getSettle$", view=getSettle, name="getSettle"),
-    url(regex=r"^getAccount$", view=getAccount, name="getAccount"),
-
-    url(regex=r"^getBankName$", view=getBankName, name="getBankName"),
-    url(regex=r"^getBankList$", view=getBankList, name="getBankList"),
-    url(regex=r"^getSubBankCodeList$", view=getSubBankCodeList, name="getSubBankCodeList"),
-    url(regex=r"^getSubBankCode/([^/]+)$", view=getSubBankCode, name="getSubBankCode"),
-
-    url(regex=r"^([^/]*)[/]?getMerchantInfo$", view=getMerchantInfo, name="getMerchantInfo"),
-    url(regex=r"^([^/]*)[/]?getAuthQrCode", view=getAuthQrCode, name="getAuthQrCode"),
-    url(regex=r"^([^/]*)[/]?getAuths", view=getAuths, name="getAuths"),
-    url(regex=r"^([^/]*)[/]?confirm$", view=confirmMerchant, name="confirm"),
-
-    url(regex=r"^certificateInfo", view=CertificateInfo.as_view(), name="certificateInfo"),
-    url(regex=r"^settlementInfo", view=SettlementInfo.as_view(), name="settlementInfo"),
-    url(regex=r"^businessInfo", view=BusinessInfo.as_view(), name="businessInfo"),
-
-
-    url(regex=r"^getIndustry", view=getIndustry, name="getIndustry"),
-    url(regex=r"^getArea", view=getArea, name="getArea"),
-    url(regex=r"^getPayType", view=getPayType, name="getPayType"),
-    url(regex=r"^getBanks", view=getBanks, name="getBanks"),
-    url(regex=r"^getSubBanks", view=getSubBanks, name="getSubBanks"),
-    url(regex=r"^auditNotify", view=auditNotify, name="auditNotify"),
-    url(regex=r"^v2/customer", view=JdOpenMerchant.as_view(), name="JdOpenMerchant"),
-
-)

File diff suppressed because it is too large
+ 0 - 1750
apps/web/merchant/utils.py


+ 0 - 219
apps/web/merchant/validation.py

@@ -1,219 +0,0 @@
-# -*- coding: utf-8 -*-
-# !/usr/bin/env python
-
-import re
-
-from library.validator import Validator, FieldValidationError, UnicodeValidationError
-from library.validator.fields import StringField, DictField
-from apps.web.merchant.constant import WechatSubjectType, ContactType
-
-
-class CodeNameValidator(Validator):
-    name = StringField(required=True, unicodeName=u"地址名称", requiredError=UnicodeValidationError(detail=u"缺少地址名称"))
-    code = StringField(required=True, unicodeName=u"地址编号", requiredError=UnicodeValidationError(detail=u"缺少地址编号"))
-
-    @staticmethod
-    def validate_name(value):
-        if not value:
-            raise UnicodeValidationError(detail=u"无效的地址名称")
-        return value
-
-    @staticmethod
-    def validate(value):
-        if not value:
-            raise UnicodeValidationError(detail=u"无效的地址编号")
-        return value
-
-
-class LegalValidator(Validator):
-
-    # 身份图片信息
-    urlA = StringField(required=True, unicodeName=u"法人身份证照片【人像面】", requiredError=UnicodeValidationError(detail=u"缺少身份证照片"))
-    urlB = StringField(required=True, unicodeName=u"法人身份证照片【国徽面】", requiredError=UnicodeValidationError(detail=u"缺少身份证照片"))
-
-    # 证件信息内容
-    name = StringField(required=True, unicodeName=u"姓名", requiredError=UnicodeValidationError(detail=u"缺少法人姓名"))
-    cardNo = StringField(required=True, unicodeName=u"证件号码", requiredError=UnicodeValidationError(detail=u"缺少法人证件号码"))
-    startTime = StringField(required=True, unicodeName=u"签发日期", requiredError=UnicodeValidationError(detail=u"缺少法人证件签发日期"))
-    endTime = StringField(required=True, unicodeName=u"失效日期", requiredError=UnicodeValidationError(detail=u"缺少法人证件失效日期"))
-
-    # 省市区的地址回调
-    province = DictField(validator=CodeNameValidator, required=True, unicodeName="法人身份证地区【省】", requiredError=UnicodeValidationError(detail=u"请选择法人身份证地区【省】"))
-    city = DictField(validator=CodeNameValidator, required=True, unicodeName="法人身份证地区【市】", requiredError=UnicodeValidationError(detail=u"请选择法人身份证地区【市】"))
-    area = DictField(validator=CodeNameValidator, required=True, unicodeName="法人身份证地区【区】", requiredError=UnicodeValidationError(detail=u"请选择法人身份证地区【区】"))
-    # 详细地址的输入 街道等等
-    addr = StringField(required=True, unicodeName="法人身份证【详细地址】", requiredError=UnicodeValidationError(detail=u"请填写法人身份证【详细地址】"))
-
-    @staticmethod
-    def validate(data):
-        if not data:
-            raise FieldValidationError(u"法人信息为空,请重新填写")
-
-        return data
-
-
-class ContactValidator(Validator):
-    # 身份图片信息
-    urlA = StringField(required=True, unicodeName=u"联系人身份证照片【人像面】", requiredError=UnicodeValidationError(detail=u"缺少身份证照片"))
-    urlB = StringField(required=True, unicodeName=u"联系人身份证照片【国徽面】", requiredError=UnicodeValidationError(detail=u"缺少身份证照片"))
-
-    # 证件信息内容
-    name = StringField(required=True, unicodeName=u"联系人姓名", requiredError=UnicodeValidationError(detail=u"缺少联系人姓名"))
-    cardNo = StringField(required=True, unicodeName=u"联系人证件号码", requiredError=UnicodeValidationError(detail=u"缺少联系人证件号码"))
-    startTime = StringField(required=True, unicodeName=u"联系人签发日期", requiredError=UnicodeValidationError(detail=u"缺少联系人证件签发日期"))
-    endTime = StringField(required=True, unicodeName=u"联系人失效日期", requiredError=UnicodeValidationError(detail=u"缺少联系人证件失效日期"))
-
-    province = DictField(validator=CodeNameValidator, required=True, unicodeName="联系人身份证地区【省】", requiredError=UnicodeValidationError(detail=u"请选择联系人身份证地区【省】"))
-    city = DictField(validator=CodeNameValidator, required=True, unicodeName="联系人身份证地区【市】", requiredError=UnicodeValidationError(detail=u"请选择联系人身份证地区【市】"))
-    area = DictField(validator=CodeNameValidator, required=True, unicodeName="联系人身份证地区【区】", requiredError=UnicodeValidationError(detail=u"请选择联系人身份证地区【区】"))
-    # 详细地址的输入 街道等等
-    addr = StringField(required=True, unicodeName="联系人身份证【详细地址】", requiredError=UnicodeValidationError(detail=u"请填写联系人身份证【详细地址】"))
-
-
-class BusinessLicenceValidator(Validator):
-    busLicenseUrl = StringField(required=True, unicodeName=u"营业执照照片", requiredError=UnicodeValidationError(detail=u"缺少营业执照照片"))
-    busName = StringField(required=True, unicodeName=u"公司名称", requiredError=UnicodeValidationError(detail=u"缺少公司名称"))
-    busCode = StringField(required=True, unicodeName=u"统一社会信用码", requiredError=UnicodeValidationError(detail=u"缺少统一社会信用码"))
-    startTime = StringField(required=True, unicodeName=u"营业执照起始时间", requiredError=UnicodeValidationError(detail=u"缺少营业执照起始时间"))
-    endTime = StringField(required=True, unicodeName=u"营业执照结束事件", requiredError=UnicodeValidationError(detail=u"缺少营业执照结束事件"))
-
-    province = DictField(validator=CodeNameValidator, required=True, unicodeName="营业执照地区【省】", requiredError=UnicodeValidationError(detail=u"请选择营业执照地区【省】"))
-    city = DictField(validator=CodeNameValidator, required=True, unicodeName="营业执照地区【市】", requiredError=UnicodeValidationError(detail=u"请选择营业执照地区【市】"))
-    area = DictField(validator=CodeNameValidator, required=True, unicodeName="营业执照地区【区】", requiredError=UnicodeValidationError(detail=u"请选择营业执照地区【区】"))
-    addr = StringField(required=True, unicodeName=u"公司地址详细地址", requiredError=UnicodeValidationError(detail=u"缺少公司详细地址"))
-
-
-class SubjectValidator(Validator):
-    businessLicenceInfo = DictField(validator=BusinessLicenceValidator, unicodeName=u"营业执照信息", requiredError=UnicodeValidationError(detail=u"缺少营业执照信息"))
-
-    def validate(self, data):
-        return data
-
-
-class CertificateValidator(Validator):
-    merchantType = StringField(required=True, unicodeName=u"商户类型", requiredError=UnicodeValidationError(detail=u"缺少商户类型"))
-    contactType = StringField(required=True, unicodeName="联系人类型", requiredError=UnicodeValidationError(detail=u"缺少联系人类型"))
-    businessAuthorizationLetter = StringField(unicodeName="业务办理授权函")
-    legalInfo = DictField(required=True, unicodeName=u"负责人/法人信息", requiredError=UnicodeValidationError(detail=u"缺少法人信息"), validator=LegalValidator)
-    contactInfo = DictField(required=False, unicodeName=u"联系人信息", requiredError=UnicodeValidationError(detail=u"缺少联系人信息"), validator=ContactValidator)
-    subjectInfo = DictField(required=False, unicodeName=u"主体信息【营业执照】", requiredError=UnicodeValidationError(detail=u"缺少营业执照信息"), Validator=SubjectValidator)
-
-    @staticmethod
-    def validate_merchantType(value):
-        """
-        验证商户类型
-        """
-        if value not in WechatSubjectType.choices():
-            raise FieldValidationError(u"错误的商户类型,请重新选择")
-        return value
-
-    def validate(self, data):
-
-        if not data.get("contactInfo") and data["merchantType"] != WechatSubjectType.SUBJECT_TYPE_MICRO:
-            raise UnicodeValidationError(detail=u"缺少联系人信息")
-        if not data.get("subjectInfo") and data["merchantType"] != WechatSubjectType.SUBJECT_TYPE_MICRO:
-            raise UnicodeValidationError(detail=u"缺少营业执照信息")
-
-        # 处理联系人类型
-        contactType = data.pop("contactType", str(ContactType.LEGAL.code))
-        if data["merchantType"] != WechatSubjectType.SUBJECT_TYPE_MICRO:
-            # 商户类型为企业且联系人类型为 经办人的情况下 需要检查授权书
-            if contactType == ContactType.SUPER:
-                businessAuthorizationLetter = data.pop("businessAuthorizationLetter", None)
-                if not businessAuthorizationLetter:
-                    raise UnicodeValidationError(detail=u"缺少业务办理授权函")
-            else:
-                businessAuthorizationLetter = ""
-
-        else:
-            contactType = str(ContactType.LEGAL.code)
-            businessAuthorizationLetter = ""
-
-        # 小微商户的情况下 ,联系人即为自身
-        if data["merchantType"] == WechatSubjectType.SUBJECT_TYPE_MICRO:
-            data["contactInfo"] = data["legalInfo"].copy()
-
-        data["contactInfo"]["contactType"] = contactType
-        data["contactInfo"]["businessAuthorizationLetter"] = businessAuthorizationLetter
-
-        return data
-
-
-class SettleValidator(Validator):
-    """
-    结算信息的校验
-    结算信息无论是企业商户还是个人商户都 需要的信息 并且字段一致
-    """
-    bankCardImg = StringField(required=True, unicodeName="银行卡照片", requiredError=UnicodeValidationError(detail=u"银行照片缺失,请确认照片信息上传"))
-    bankName = StringField(required=True, unicodeName="银行名称", requiredError=UnicodeValidationError(detail=u"银行名称缺失,请确认银行名称选择正确"))
-    bankCardCode = StringField(required=True, unicodeName="银行卡号", requiredError=UnicodeValidationError(detail=u"银行卡号缺失,请确认卡号填写无误"))
-    bankCardSubCode = StringField(required=True, unicodeName="支行联行号", requiredError=UnicodeValidationError(detail=u"支行联行号缺失,请确认支行选择无误"))
-    bankCardSubName = StringField(required=True, unicodeName="支行名称", requiredError=UnicodeValidationError(detail=u"支行联行号缺失,请确认支行选择无误"))
-
-    @staticmethod
-    def validate_backCardImg(value):
-        if not value:
-            raise UnicodeValidationError(detail=u"无效的银行卡照片,请重新上传")
-        return value
-
-    @staticmethod
-    def validate_bankName(value):
-        if not value:
-            raise UnicodeValidationError(detail=u"无效的银行名称,请重新上传")
-        return value
-
-    @staticmethod
-    def validate_bankCardCode(value):
-        if not value:
-            raise UnicodeValidationError(detail=u"无效的银行卡号,请重新上传")
-        return value
-
-    @staticmethod
-    def validate_bankCardSubCode(value):
-        if not value:
-            raise UnicodeValidationError(detail=u"无效的开户行支行(10001),请重新上传")
-        return value
-
-    @staticmethod
-    def validate_bankCardSubName(value):
-        if not value:
-            raise UnicodeValidationError(detail=u"无效的开户行支行(10002),请重新上传")
-        return value
-
-
-class BusinessValidator(Validator):
-    storeAUrl = StringField(required=True, unicodeName=u"门头照片", requiredError=UnicodeValidationError(detail=u"门头照片缺失,请确认照片上传信息"))
-    storeBUrl = StringField(required=True, unicodeName=u"门店照片", requiredError=UnicodeValidationError(detail=u"门店照片缺失,请确认照片上传信息"))
-    storeCUrl = StringField(required=True, unicodeName=u"店内照片", requiredError=UnicodeValidationError(detail=u"店内照片缺失,请确认照片上传信息"))
-    storeShotName = StringField(required=True, unicodeName=u"商户简称", requiredError=UnicodeValidationError(detail=u"商户简称缺失,请填写商户简称"))
-
-    province = DictField(validator=CodeNameValidator, required=True, unicodeName="营业地区【省】", requiredError=UnicodeValidationError(detail=u"请选择营业地区【省】"))
-    city = DictField(validator=CodeNameValidator, required=True, unicodeName="营业地区【市】", requiredError=UnicodeValidationError(detail=u"请选择营业地区【市】"))
-    area = DictField(validator=CodeNameValidator, required=True, unicodeName="营业地区【区】", requiredError=UnicodeValidationError(detail=u"请选择营业地区【区】"))
-    addr = StringField(required=True, unicodeName="营业地区【详细地址】", requiredError=UnicodeValidationError(detail=u"请填写营业地区【详细地址】"))
-
-    @staticmethod
-    def validate_storeAUrl(value):
-        if not value:
-            raise UnicodeValidationError(detail=u"无效的门头照,请重新上传")
-        return value
-
-    @staticmethod
-    def validate_storeBUrl(value):
-        if not value:
-            raise UnicodeValidationError(detail=u"无效的门店照,请重新上传")
-        return value
-
-
-    @staticmethod
-    def validate_storeCUrl(value):
-        if not value:
-            raise UnicodeValidationError(detail=u"无效的店内照,请重新上传")
-        return value
-
-    @staticmethod
-    def validate_addr(value):
-        if not value:
-            raise UnicodeValidationError(u"无效的营业地区【详细地址】,请重新填写")
-        return value
-

+ 0 - 687
apps/web/merchant/views.py

@@ -1,687 +0,0 @@
-# -*- coding: utf-8 -*-
-import datetime
-import logging
-
-import simplejson as json
-from django.http import HttpResponse
-from django.views.generic import View
-from typing import Optional
-
-from apilib.bank_card_utils import BankRecognizer
-from apilib.img_utils import ImageRecognizer, parse_identify_image, parse_bank_image, parse_business_image
-from apilib.utils_sys import memcache_lock
-from apps.web.agent.models import Agent
-from apps.web.core import ROLE
-from apps.web.core.exceptions import InvalidFileSize, InvalidFileName, MerchantError
-from apps.web.core.file import AliOssFileUploader
-from apps.web.core.messages.sms import jdMerchantSMSProvider
-from apps.web.core.utils import JsonErrorResponse, JsonOkResponse
-from apps.web.dealer.models import Dealer
-from apps.web.merchant.exceptions import JdOpenParamsError
-from apps.web.merchant.constant import MerchantStatus, WechatSubjectType, IdentificationType, CertType, MicroBizType, ContactType, ALI_PAY_QR_CODE
-from apps.web.merchant.models import MerchantSourceInfo, MerchantAddress, JDOpenApplyInfo
-from apps.web.merchant.utils import query_merchant_settle, get_wechat_proxy, JdOpenParamsChecker, get_open_client, query_audit_result, get_merchant_by_version, \
-    is_wechat_authorized, is_alipay_authorized, get_wechat_auth_info, get_alipay_auth_info
-from apps.web.merchant.validation import SettleValidator, BusinessValidator, CertificateValidator
-from apps.web.utils import error_tolerate, permission_required
-from apilib.exceptions import BaiDuApiImageError, BaiDuApiSysError, AliBankCardParamError, AliBankCardSysError
-from library.jdpsi.exceptions import JdPsiException
-
-
-logger = logging.getLogger(__name__)
-
-
-@error_tolerate(logger=logger, nil=JsonErrorResponse(u"获取失败"))
-@permission_required(ROLE.dealer, ROLE.agent)
-def getMerchantOptions(request):
-    """
-    获取商户的选项信息 在用户选择商户的时候进行加载
-    """
-    merchantOptions = WechatSubjectType.to_list()
-    cardOptions = IdentificationType.to_list()
-    certOptions = CertType.to_list()
-    microOptions = MicroBizType.to_list()
-    contactOptions = ContactType.to_list()
-
-    return JsonOkResponse(payload={
-        "merchantOptions": merchantOptions,
-        "cardOptions": cardOptions,
-        "certOptions": certOptions,
-        "microOptions": microOptions,
-        "contactOptions": contactOptions
-    })
-
-
-@error_tolerate(logger=logger, nil=JsonErrorResponse(u"获取地址失败"))
-@permission_required(ROLE.dealer, ROLE.agent)
-def address(request):
-    addresses = MerchantAddress.get_all()
-
-    return JsonOkResponse(payload=addresses)
-
-
-@error_tolerate(logger=logger, nil=JsonErrorResponse(u"验证码获取失败"))
-@permission_required(ROLE.dealer, ROLE.agent)
-def sendMerchantSmsCode(request):
-    if request.user.role == ROLE.dealer:
-        dealerId = str(request.user.id)
-        dealer = Dealer.objects.get(id=dealerId)
-        agent = Agent.objects.get(id=dealer.agentId)
-    else:
-        agent = request.user
-
-    payload = json.loads(request.body)
-    mobile = payload["mobile"]
-
-    # 防止接口被滥用 加上每日限制
-    status, msg = jdMerchantSMSProvider.get(
-        phoneNumber=mobile, productName=agent.productName,
-        identify='{}_{}'.format(request.user.role, str(request.user.id)))
-
-    if not status:
-        return JsonErrorResponse(description=msg)
-
-    return JsonOkResponse()
-
-
-@error_tolerate(logger = logger, nil = JsonErrorResponse(u"请重试"))
-@permission_required(ROLE.dealer, ROLE.agent)
-def confirmMerchant(request, version):
-    """
-    确认商户的开通界面
-    确认开通前 需要检查该商户是否状态一切就绪
-    需要注意的是 返回正确的操作提示
-    """
-    owner = request.user
-
-    # 获取微信的实名状态
-    try:
-        merchant = get_merchant_by_version(owner, version=version)   # type: MerchantSourceInfo
-        wechatAuth = is_wechat_authorized(merchant)
-        aliPayAuth = is_alipay_authorized(merchant)
-    except MerchantError as me:
-        return JsonErrorResponse(description=me.message)
-    except Exception as e:
-        logger.error("get wx merchant intention status error = {}".format(e))
-        return JsonErrorResponse(u"获取实名状态失败,请刷新页面重试")
-
-    # 两个实名确认状态是否满足
-    if not wechatAuth:
-        return JsonErrorResponse(u"微信实名尚未确认,请前往查看实名确认状态")
-    if not aliPayAuth:
-        return JsonErrorResponse(u"支付宝实名尚未确认,请前往查看实名确认状态")
-
-    # 检查商户的角色是否已经 满足切换为京东支付
-    try:
-        owner.check_merchant_conditions()
-    except MerchantError as me:
-        return JsonErrorResponse(description=me.message)
-
-    sourceRecord = MerchantSourceInfo.get_source_record(owner)
-    sourceRecord.to_success()
-
-    return JsonOkResponse()
-
-
-@error_tolerate(logger=logger, nil=JsonErrorResponse(u"获取信息失败,请重试"))
-@permission_required(ROLE.dealer)
-def getPartnerMerchantInfo(request):
-    """
-    获取 合伙人的 商户(银行卡)的绑定的状态
-    :param request:
-    :return:
-    """
-    return JsonErrorResponse(u"请登录合伙人账户进行商户开通")
-
-
-@error_tolerate(logger=logger, nil=JsonErrorResponse(u"保存信息失败"))
-@permission_required(ROLE.dealer)
-def savePartnerMerchantInfo(request):
-    return JsonErrorResponse(u"请登录合伙人账户完成商户开通")
-
-
-@error_tolerate(logger=logger, nil=JsonErrorResponse(u"信息获取失败"))
-@permission_required(ROLE.dealer, ROLE.agent)
-def getAccount(request):
-    user = request.user # type: Dealer
-
-    try:
-        merchant = MerchantSourceInfo.get_source_record(user)
-        # 没有商户号 说明要么没有开通商户 要么商户是手动切换的 时间就换为大规模切换商户的开始时间
-        if not merchant.status != MerchantStatus.SUCCESS:
-            payload = {"merchantNo": "", "startTime": "2022-02-08"}
-        else:
-            payload = {"merchantNo": merchant.merchantNo, "startTime": merchant.dateTimeAdded.strftime("%Y-%m-%d")}
-    except Exception:
-        return JsonErrorResponse(u"查询商户失败")
-
-    return JsonOkResponse(payload=payload)
-
-
-@error_tolerate(logger=logger, nil=JsonErrorResponse(u"信息获取失败"))
-@permission_required(ROLE.dealer, ROLE.agent, ROLE.subaccount)
-def getSettle(request):
-    """
-    获取商户的结算 转换为获取一段时间的
-    """
-    st = request.GET.get("startTime")
-    et = request.GET.get("endTime")
-    if not all([st, et]):
-        return JsonErrorResponse(description=u"未查询到结算信息(10001)")
-
-    # 转换日期格式区间
-    try:
-        st = datetime.datetime.strptime(st, "%Y-%m-%d")
-        et = datetime.datetime.strptime(et, "%Y-%m-%d")
-    except ValueError:
-        return JsonErrorResponse(u"未查询到结算信息(10002)")
-
-    if st > et:
-        return JsonErrorResponse(u"未查询到结算信息(10003)")
-
-    user = request.user
-    try:
-        result = query_merchant_settle(user, st, et)
-    except JdPsiException as e:
-        return JsonErrorResponse(description=u"查询失败,{}".format(e.errMsg))
-
-    if not result:
-        return JsonErrorResponse(u"未查询到结算信息(10004)")
-
-    # 结构化数据
-    settleMap = {_['settleDate']: _ for _ in result["dataList"]}
-
-    dataList = list()
-    for i in range((et-st).days + 1):
-        _timeKey = (et-datetime.timedelta(days=i)).strftime("%Y-%m-%d")
-        if _timeKey in settleMap:
-            dataList.append(settleMap[_timeKey])
-        else:
-            dataList.append({
-                u'orderStatus': u'UNKNOWN',
-                u'remark': u'上日未产生收益',
-                u'settleAccountNo': u'',
-                u'settleDate': _timeKey,
-                u'settlementAmount': u''
-            })
-
-    payload = {
-        "dataList": dataList
-    }
-    return JsonOkResponse(payload=payload)
-
-
-@error_tolerate(logger=logger, nil=JsonOkResponse())
-@permission_required(ROLE.dealer)
-def getAuths(request, version):
-    """
-    获取实名信息的列表
-    """
-    user = request.user
-    mer = get_merchant_by_version(user, version)    # type: Optional[JDOpenApplyInfo, MerchantSourceInfo]
-    # 目前来说只有微信和支付宝的实名确认
-
-    # 微信
-    wechat = get_wechat_auth_info(merchant=mer)
-
-    # 支付宝
-    alipay = get_alipay_auth_info(merchant=mer)
-
-    return JsonOkResponse(payload={"dataList": [wechat, alipay]})
-
-
-# ------------------------------- 新的接口 -----------------------------------------
-@error_tolerate(logger=logger, nil=JsonErrorResponse(u"图片上传失败"))
-@permission_required(ROLE.dealer)
-def uploadImage(request):
-    image = request.FILES.get("file")
-    _type = request.GET.get("imageType")
-    ocr = request.GET.get("ocr", False)
-    cardSide = request.GET.get("cardSide")
-
-    logger.info("[uploadImage] type = {}, ocr = {}, cardSide = {}, user = {}".format(_type, ocr, cardSide, request.user.id))
-
-    # 防止文件类型上传错误
-    fileSuffix = image.name.split(".")[1]
-    if fileSuffix not in ["jpg", "jpeg", "png"]:
-        return JsonErrorResponse(description = u"图片类型错误,请上传指的的图片类型(jpg,jpeg,png)。")
-
-    # 根据类型以及其他参数 进行ocr识别 反馈有效的信息
-    if int(ocr):
-        try:
-            recognizer = ImageRecognizer(request.user.id, inMemFile=image)
-            if _type == "idCard":
-                ocrData = recognizer.recognize_identify_card(cardSide, callback=parse_identify_image)
-            elif _type == "bankCard":
-                ocrData = recognizer.recognize_bank_card(callback=parse_bank_image)
-            elif _type == "businessCard":
-                ocrData = recognizer.recognize_business_license(callback=parse_business_image)
-            else:
-                ocrData = dict()
-        except (BaiDuApiImageError, BaiDuApiSysError) as be:
-            return JsonErrorResponse(description=be.message)
-        except BaiDuApiSysError:
-            return JsonErrorResponse(description=u"图像上传失败,请重试")
-    else:
-        logger.debug("[uploadImage] not need ocr!")
-        ocrData = dict()
-
-    # 上传保存图片
-    uploader = AliOssFileUploader(inputFile=image, uploadType= 'merchant')
-    try:
-        outputUrl = uploader.upload()
-    except InvalidFileSize as e:
-        return JsonErrorResponse(description=e.message)
-    except InvalidFileName as e:
-        return JsonErrorResponse(description=e.message)
-
-    payload = {
-        "ocrData": ocrData,
-        "url": outputUrl
-    }
-    return JsonOkResponse(payload=payload)
-
-
-@error_tolerate(logger=logger, nil=JsonErrorResponse(u"查找银行失败"))
-@permission_required(ROLE.dealer)
-def getBankName(request):
-    card = request.GET.get("card")
-    try:
-        result = BankRecognizer(request.user.id).query_bank(card or "")
-    except AliBankCardParamError as pe:
-        return JsonErrorResponse(description=pe.message)
-    except AliBankCardSysError as se:
-        logger.error("[getBankName] error = {}".format(se))
-        return JsonErrorResponse(u"查找银行失败")
-
-    return JsonOkResponse(payload=result["data"][0])
-
-
-@error_tolerate(logger=logger, nil=JsonErrorResponse(u"查找银行失败"))
-@permission_required(ROLE.dealer)
-def getBankList(request):
-    """
-    查询支持的银行
-    """
-    search = request.GET.get("search")
-    if not search:
-        return JsonErrorResponse(description=u"请输入关键词查询")
-
-    try:
-        result = BankRecognizer(request.user.id).query_all_bank(search)
-    except AliBankCardParamError as pe:
-        return JsonErrorResponse(description=pe.message)
-    except AliBankCardSysError as se:
-        logger.error("[getBankList] error = {}".format(se))
-        return JsonErrorResponse(u"查找银行失败")
-
-    return JsonOkResponse(payload=result["data"])
-
-
-@error_tolerate(logger=logger, nil=JsonErrorResponse(u"查找支行失败"))
-@permission_required(ROLE.dealer)
-def getSubBankCodeList(request):
-    """
-    查询支行列表
-    """
-    pageIndex = int(request.GET.get("pageIndex", 1))
-    queryKey = request.GET.get("queryKey", "")
-    province = request.GET.get("bankProvince")
-    city = request.GET.get("bankCity")
-    bank = request.GET.get("bankName")
-    card = request.GET.get("bankCardCode")
-
-    if not all(["province", "city", "bank", "card"]):
-        return JsonErrorResponse(description=u"参数错误")
-
-    try:
-        result = BankRecognizer(request.user.id).query_sub_code(
-            bank=bank, card=card,
-            province=province, city=city, queryKey=queryKey, page=pageIndex
-        )
-    except AliBankCardParamError as pe:
-        return JsonOkResponse(description=pe.message)
-    except AliBankCardSysError as se:
-        logger.error("[getBankList] error = {}".format(se))
-        return JsonErrorResponse(u"查找银行失败")
-
-    return JsonOkResponse(payload=result["data"])
-
-
-@error_tolerate(logger=logger, nil=JsonErrorResponse(u"查找支行失败"))
-@permission_required(ROLE.dealer)
-def getSubBankCode(request, subCode):
-    """
-    通过联行号 反查银行的信息
-    """
-    try:
-        result = BankRecognizer(request.user.id).query_sub_bank(subCode)
-    except AliBankCardParamError as pe:
-        return JsonErrorResponse(description=pe.message)
-    except AliBankCardSysError as se:
-        logger.error("[getBankList] error = {}".format(se))
-        return JsonErrorResponse(u"查找银行失败")
-
-    return JsonOkResponse(payload=result["data"][0])
-
-
-@error_tolerate(logger=logger, nil=JsonErrorResponse(u"获取商户信息失败"))
-@permission_required(ROLE.dealer)
-def getMerchantInfo(request, version):
-    """
-    获取商户的基本信息
-    """
-    user = request.user
-    mer = get_merchant_by_version(user, version)
-
-    merInfo = mer.get_merchant_info()
-    helpInfo = mer.get_help_info()
-
-    payload = {
-        "merchantInfo": merInfo,
-        "helpInfo": helpInfo
-    }
-
-    return JsonOkResponse(payload=payload)
-
-
-@error_tolerate(logger=logger, nil=JsonErrorResponse(u"获取二维码失败"))
-@permission_required(ROLE.dealer)
-def getAuthQrCode(request, version):
-    """
-    获取微信实名的二维码
-    """
-    user = request.user
-    mer = get_merchant_by_version(user, version)
-
-    wechatProxy = get_wechat_proxy()
-
-    try:
-        result = wechatProxy.get_merchant_audit(mer.wxSource.applymentId)
-    except MerchantError:
-        logger.error("[getAuthQrCode] ownerId = {} query error!".format(mer.ownerId))
-        return
-
-    if "qrcode_data" not in result:
-        return JsonOkResponse(u"获取实名二维码失败,请联系平台客服处理")
-
-    return JsonOkResponse(payload={"qrCode": "data:image/png;base64,{}".format(result["qrcode_data"])})
-
-
-class CertificateInfo(View):
-    """
-    """
-    def __init__(self, **kwargs):
-        super(CertificateInfo, self).__init__(**kwargs)
-        self._owner = None
-        self._merchant = None
-
-    def dispatch(self, request, *args, **kwargs):
-        self._owner = request.user
-        self._merchant = MerchantSourceInfo.get_source_record(self._owner)  # type: MerchantSourceInfo
-
-        return super(CertificateInfo, self).dispatch(request, *args, **kwargs)
-
-    def get(self, request):
-        """
-        获取商户的证件信息
-        """
-        data = self._merchant.get_certificate_info()
-        data["merchantNo"] = self._merchant.merchantNo
-        data["merchantType"] = self._merchant.wxSource.subjectInfo.mainType
-        return JsonOkResponse(payload=data)
-
-    def post(self, request):
-        """
-        存储商户的证件信息
-        """
-        data = json.loads(request.body)
-        logger.info(
-            "[CertificateInfo _ post] save merchant certificate info, user = {}, data = {}".format(self._owner.id, data)
-        )
-        validator = CertificateValidator(data)
-        if not validator.is_valid():
-            return JsonErrorResponse(description=json.dumps(validator.str_errors).decode("unicode-escape"))
-
-        if not self._merchant.support_modify():
-            return JsonErrorResponse(description=u"当前商户状态已锁定,无法进行修改")
-
-        with memcache_lock(key="merchant_{}".format(self._owner.id), value="1", expire=10) as acquired:
-            if not acquired:
-                return JsonErrorResponse(description=u"当前商户正在修改中,请勿重复提交")
-
-            self._merchant = self._merchant.save_certificate_info(validator.validated_data)
-
-        data = self._merchant.get_certificate_info()
-        data["merchantNo"] = self._merchant.merchantNo
-        data["merchantType"] = self._merchant.wxSource.subjectInfo.mainType
-        return JsonOkResponse(payload=data)
-
-
-class SettlementInfo(View):
-    def __init__(self, **kwargs):
-        super(SettlementInfo, self).__init__(**kwargs)
-        self._owner = None
-        self._merchant = None
-
-    def dispatch(self, request, *args, **kwargs):
-        self._owner = request.user
-        self._merchant = MerchantSourceInfo.get_source_record(self._owner)  # type: MerchantSourceInfo
-
-        return super(SettlementInfo, self).dispatch(request, *args, **kwargs)
-
-    def get(self, request):
-        data = self._merchant.get_settlement_info()
-        data["merchantNo"] = self._merchant.merchantNo
-        data["merchantType"] = self._merchant.wxSource.subjectInfo.mainType
-        return JsonOkResponse(payload=data)
-
-    def post(self, request):
-        """
-        存储商户的结算信息
-        """
-        data = json.loads(request.body)
-        logger.info(
-            "[SettlementInfo _ post] save merchant settle info, user = {}, data = {}".format(self._owner.id, data)
-        )
-        validator = SettleValidator(data)
-        if not validator.is_valid():
-            return JsonErrorResponse(description=json.dumps(validator.str_errors).decode("unicode-escape"))
-
-        if not self._merchant.support_modify():
-            return JsonErrorResponse(description=u"当前商户状态已锁定,无法进行修改结算信息")
-
-        with memcache_lock(key="merchant_{}".format(self._owner.id), value="1", expire=10) as acquired:
-            if not acquired:
-                return JsonErrorResponse(description=u"当前商户正在修改中,请勿重复提交")
-
-            self._merchant = self._merchant.save_settlement_info(validator.validated_data)
-
-        data = self._merchant.get_settlement_info()
-        data["merchantNo"] = self._merchant.merchantNo
-        data["merchantType"] = self._merchant.wxSource.subjectInfo.mainType
-
-        return JsonOkResponse(payload=data)
-
-
-class BusinessInfo(View):
-
-    def __init__(self, **kwargs):
-        super(BusinessInfo, self).__init__(**kwargs)
-        self._owner = None
-        self._merchant = None
-
-    def dispatch(self, request, *args, **kwargs):
-        self._owner = request.user
-        self._merchant = MerchantSourceInfo.get_source_record(self._owner)  # type: MerchantSourceInfo
-
-        return super(BusinessInfo, self).dispatch(request, *args, **kwargs)
-
-    def get(self, request):
-        data = self._merchant.get_business_info()
-        data["merchantNo"] = self._merchant.merchantNo
-        data["merchantType"] = self._merchant.wxSource.subjectInfo.mainType
-        return JsonOkResponse(payload=data)
-
-    def post(self, request):
-        """
-        存储商户的 营业信息
-        """
-        data = json.loads(request.body)
-        logger.info(
-            "[BusinessInfo _ post] save merchant settle info, user = {}, data = {}".format(self._owner.id, data)
-        )
-        businessInfo = data.get("businessInfo", dict())
-        mobile = data.get("mobile", "")
-        smsCode = data.get("SMSCode")
-        result, msg = jdMerchantSMSProvider.verify(mobile, smsCode)
-        if not result:
-            return JsonErrorResponse(description=msg)
-
-        validator = BusinessValidator(businessInfo)
-        if not validator.is_valid():
-            return JsonErrorResponse(description=json.dumps(validator.str_errors).decode("unicode-escape"))
-
-        if not self._merchant.support_modify():
-            return JsonErrorResponse(description=u"当前商户状态已锁定,无法进行修改")
-
-        with memcache_lock(key=str(self._merchant.id), value="1") as acquired:
-            if not acquired:
-                return JsonErrorResponse(description=u"当前商户正在修改中,请勿重复提交")
-
-            self._merchant = self._merchant.save_business_info(validator.validated_data)
-            self._merchant = self._merchant.save_mobile_info(mobile)
-
-        data = self._merchant.get_business_info()
-        data["merchantNo"] = self._merchant.merchantNo
-        data["merchantType"] = self._merchant.wxSource.subjectInfo.mainType
-
-        self._merchant.to_waiting()
-
-        return JsonOkResponse(payload=data)
-
-
-@error_tolerate(logger=logger, nil=JsonErrorResponse(u"获取行业列表失败"))
-def getIndustry(request):
-    num = request.GET.get("parent")
-    openClient = get_open_client()
-    if not num:
-        result = openClient.support.query_industry()
-    else:
-        result = openClient.support.query_sub_industry(num)
-
-    return JsonOkResponse(payload=result["data"])
-
-
-@error_tolerate(logger=logger, nil=JsonErrorResponse(u"获取地址信息失败"))
-def getArea(request):
-    province = request.GET.get("province")
-    city = request.GET.get("city")
-
-    openClient = get_open_client()
-    if not province and not city:
-        result = openClient.support.query_province()
-    elif province:
-        result = openClient.support.query_city(province)
-    else:
-        result = openClient.support.query_district(city)
-
-    return JsonOkResponse(payload=result["data"])
-
-
-@error_tolerate(logger=logger, nil=JsonErrorResponse(u"获取支付产品失败"))
-def getPayType(request):
-    openClient = get_open_client()
-    result = openClient.support.query_pay_type()
-    return JsonOkResponse(payload=result["data"])
-
-
-@error_tolerate(logger=logger, nil=JsonErrorResponse(u"获取银行列表失败"))
-def getBanks(request):
-    keyWork = request.GET.get("keyWord")
-    openClient = get_open_client()
-    result = openClient.support.query_bank(keyWork)
-    return JsonOkResponse(payload=result["data"])
-
-
-@error_tolerate(logger=logger, nil=JsonErrorResponse(u"获取支行列表"))
-def getSubBanks(request):
-    bankCode = request.GET.get("bankCode")
-    keyWork = request.GET.get("keyWord")
-    if not bankCode or not keyWork:
-        return JsonOkResponse()
-
-    openClient = get_open_client()
-    result = openClient.support.query_sub_bank(bankCode, keyWork)
-    return JsonOkResponse(payload=result["data"])
-
-
-@error_tolerate(logger=logger, nil=HttpResponse(status=500))
-def auditNotify(request):
-    customerNum = request.GET.get("customerNum")
-    auditStatus = request.GET.get("status")
-    auditOpinion = request.GET.get("auditOpinion")
-
-    return HttpResponse(status=200)
-
-
-class JdOpenMerchant(View):
-    """
-    哆啦宝商户的获取和保存
-    """
-    def __init__(self, **kwargs):
-        super(JdOpenMerchant, self).__init__(**kwargs)
-        self._owner = None
-        self._merchant = None
-
-    def dispatch(self, request, *args, **kwargs):
-        self._owner = request.user
-        self._merchant = JDOpenApplyInfo.get_merchant_by_owner(self._owner)   # type: Optional[None, JDOpenApplyInfo]
-
-        return super(JdOpenMerchant, self).dispatch(request, *args, **kwargs)
-
-    def get(self, request):
-        if self._merchant is None:
-            mer = JDOpenApplyInfo()
-        else:
-            mer = self._merchant
-
-        return JsonOkResponse(payload=mer.get_info())
-
-    def post(self, request):
-        """
-        创建商户
-        """
-        payload = json.loads(request.body)
-        logger.info("[{} post] user = {}, request body = {}, ".format(self.__class__.__name__, self._owner, payload))
-        try:
-            mer = JdOpenParamsChecker(**payload).create(self._owner)
-        except JdOpenParamsError as jpe:
-            return JsonErrorResponse(description=jpe.message)
-        # 切换商户的资料到提交的状态
-        mer = mer.to_submit()
-        return JsonOkResponse()
-
-
-@error_tolerate(logger=logger, nil=JsonErrorResponse(u"获取商户状态失败"))
-def getMerchantAudit(request):
-    """
-    获取商户的审核状态
-    """
-    merchantId = request.GET.get("merchantId")
-    if not merchantId:
-        return JsonErrorResponse(description=u"商户编号错误,请刷新界面重试(10001)")
-
-    mer = JDOpenApplyInfo.get_merchant_by_id(merchantId)
-    if not mer:
-        return JsonErrorResponse(description=u"商户编号错误,请刷新界面重试(10002)")
-
-    openClient = get_open_client()
-    result = query_audit_result(mer, openClient)
-
-    return JsonOkResponse()
-
-
-
-
-

+ 1 - 1
apps/web/miniuser/views.py

@@ -215,7 +215,7 @@ def wxpayGateway(request):
                 pay_user, device, ruleId, attachParas, payment_gateway)
 
         if payment_gateway.pay_app_type == PayAppType.WECHAT:
-            data = payment_gateway.unified_order(
+            data = payment_gateway.generate_js_payment_params(
                 payOpenId = pay_user.openId,
                 out_trade_no = record.orderNo,
                 notify_url = PAY_NOTIFY_URL.WECHAT_PAY_BACK,

+ 5 - 2
apps/web/superadmin/urls.py

@@ -52,7 +52,6 @@ urlpatterns = patterns('',
                        url(r'^getDealerDetailList$', getDealerDetailList, name = 'getDealerDetailList'),
 
                        # 编辑经销商对公账号信息
-                       url(r'^editDealerBankAccount$', editDealerBankAccount, name='editDealerBankAccount'),
 
                        url(r'^toggleDealerStatus$', toggleDealerStatus, name = 'toggleDealerStatus'),
                        url(r'^toggleDealerWithdrawStatus$', toggleDealerWithdrawStatus, name = 'toggleDealerWithdrawStatus'),
@@ -212,4 +211,8 @@ urlpatterns = patterns('',
                        url(r'^setServiceButtonStatus$', setServiceButtonStatus, name='setServiceButtonStatus'),
                        url(r'^getMerchantLog$', getMerchantLog, name='getMerchantLog'),
 
-                       )
+                       url(r'^getDealerBanks$', getDealerBanks, name='getDealerBanks'),
+
+                       url(r'^saveDealerBankAccount$', saveDealerBankAccount, name='saveDealerBankAccount'),
+                       url(r'^deleteDealerBankAccount$', deleteDealerBankAccount, name='deleteDealerBankAccount'),
+)

+ 107 - 49
apps/web/superadmin/views.py

@@ -12,6 +12,7 @@ from django.contrib import auth
 from django.views.decorators.http import require_POST, require_GET
 from mongoengine.errors import DoesNotExist, NotUniqueError
 from typing import TYPE_CHECKING
+from voluptuous import MultipleInvalid
 
 from apilib import utils_datetime
 from apilib.monetary import RMB, Ratio, VirtualCoin
@@ -23,9 +24,8 @@ from apilib.utils_sys import memcache_lock
 from apps import serviceCache
 from apps.web.agent import api
 from apps.web.agent.models import Agent, MoniApp, MoniAppPoint
-from apps.web.common.models import AddressType, FAQ
+from apps.web.common.models import AddressType, FAQ, WithdrawBankCard, WithdrawBranchBanks
 from apps.web.common.models import Feature
-from apps.web.common.transaction import DealerPaySubType
 from apps.web.constant import Const, SimStatus
 from apps.web.core import ROLE
 from apps.web.core.bridge import WechatClientProxy
@@ -33,13 +33,13 @@ from apps.web.core.bridge.wechat.v3api import WechatComplaint
 from apps.web.core.exceptions import InvalidFileSize, InvalidFileName, \
     ManagerServiceTimeOutException, ManagerServiceSerialException, ServiceException, RentDeviceError
 from apps.web.core.file import AliOssFileUploader
-from apps.web.core.models import AliApp, WechatPayApp, BankCard
+from apps.web.core.models import AliApp, WechatPayApp
 from apps.web.core.models import OfflineTask
 from apps.web.core.models import WechatAuthApp
 from apps.web.core.models import WechatServiceProvider
 from apps.web.core.networking import MessageSender
 from apps.web.core.utils import DefaultJsonErrorResponse, JsonErrorResponse, JsonOkResponse
-from apps.web.dealer.models import Dealer, ExchangeOrder, Merchant, DealerRechargeRecord, ApiAppInfo
+from apps.web.dealer.models import Dealer, ExchangeOrder, DealerRechargeRecord, ApiAppInfo
 from apps.web.dealer.transaction_deprecated import refund_cash_to_dealer
 from apps.web.dealer.utils import create_dealer_sim_charge_order
 from apps.web.device.models import Device, DeviceType, MajorDeviceType, DeviceCommandParam, DeviceCommand, SIMCard, \
@@ -57,6 +57,8 @@ from apps.web.superadmin.models import SuperManager, WebLog
 from apps.web.user.models import RechargeRecord, ConsumeRecord
 from apps.web.user.transaction_deprecated import refund_cash
 from apps.web.utils import permission_required, error_tolerate, super_manager_login, trace_call
+from apps.web.validation import SaveWithdrawBankCardSchema, SavePublicWithdrawBankCardExtraSchema, \
+    SaveWithdrawBankCardSchemaForSuper
 from library.qiben import SimManager
 from taskmanager.mediator import task_caller
 
@@ -987,7 +989,7 @@ def getDealerDetailList(request):
     for dealer in dealerList:
         agent = agentMap.get(dealer.agentId)
         data = dealer.to_dict(shadow = (not request.user.universal_password_login))
-        moniAppName = MoniApp.get_app_by_agent(str(agent.id))   # type: moniAppName
+        moniAppName = MoniApp.get_app_by_agent(str(agent.id))  # type: moniAppName
         data.update(
             {
                 'productName': agent.productName,
@@ -999,10 +1001,6 @@ def getDealerDetailList(request):
             }
         )
 
-        #  获取对公账号信息,没有返回空
-        mm = Merchant.objects.filter(ownerId = str(dealer.id)).first() or Merchant()
-        data['bankAccountInfo'] = mm.get_bankAccountInfo
-
         dataList.append(data)
 
     return JsonOkResponse(
@@ -1014,46 +1012,6 @@ def getDealerDetailList(request):
     )
 
 
-@require_POST
-@error_tolerate(nil = JsonErrorResponse(u'编辑对公账户失败'), logger = logger)
-@permission_required(ROLE.supermanager)
-def editDealerBankAccount(request):
-    """
-        编辑经销商银行对公账号
-        接收数据为:{
-                    'accountCode': '4682037551196297',
-                    'merchantName': u'\u90b5\u9e4f11',
-                    'parentBankName': u'\u62db\u5546\u94f6\u884c11',
-                    'isPublic': False,
-                    'subBankName': u'\u5357\u6d77\u652f\u884c11',
-                    'cardType': u'\u501f\u8bb0\u5361',
-                    'id': '5b9ae99ad89a177846459999'
-                    }
-    """
-    payload = json.loads(request.body) if request.body else {}
-
-    if not payload:
-        return JsonErrorResponse(description = u'获取数据失败')
-
-    # 数据校验
-    # 银行卡号校验,全为数字
-    if not payload['accountCode'].isdigit():
-        return JsonErrorResponse(description = u'银行卡号输入有误')
-
-    # 是否对公账号(根据传过来的bool值转换成对应的常量)
-    payload['accountType'] = BankCard.AccountType.PUBLIC if payload['isPublic'] else BankCard.AccountType.PERSONAL
-    payload.pop('isPublic')
-    # 弹出id(在表中该id对应的为ownerId)
-    id = payload['ownerId'] = payload.pop('id')
-
-    try:
-        mm = Merchant.objects.filter(ownerId = str(id)).first() or Merchant(ownerId = str(id))
-        mm.set_bankAccountInfo(payload)
-    except Exception:
-        return JsonErrorResponse(description = u'保存错误')
-    return JsonOkResponse()
-
-
 @require_POST
 @error_tolerate(nil = JsonErrorResponse(u'封禁/解封失败'), logger = logger)
 def toggleDealerStatus(request):
@@ -3257,3 +3215,103 @@ def setServiceButtonStatus(request):
         deviceType.save()
 
     return JsonOkResponse()
+
+
+@error_tolerate(logger = logger, nil = JsonErrorResponse(u"获取经销商银行卡失败"))
+@permission_required(ROLE.supermanager)
+def getDealerBanks(request):
+    dealerId = request.GET.get('dealerId')
+
+    if not dealerId:
+        return JsonErrorResponse(description = u'参数错误')
+
+    dealer = Dealer.objects(id = dealerId).first()
+    if not dealer:
+        return JsonErrorResponse(description = u'经销商不存在,请刷新后再试')
+
+    return JsonOkResponse(payload = {
+        'dataList': [
+            card.to_dict() for card in
+            WithdrawBankCard.objects(ownerId = str(dealer.id), role = dealer.role).all()
+        ]
+    })
+
+
+@error_tolerate(logger = logger, nil = JsonErrorResponse(u"保存经销商银行卡失败"))
+@permission_required(ROLE.supermanager)
+def saveDealerBankAccount(request):
+    payload = json.loads(request.body)
+
+    dealerId = payload.pop('dealerId', None)
+
+    if not dealerId:
+        return JsonErrorResponse(description = u'参数错误')
+
+    dealer = Dealer.objects(id = dealerId).first()
+    if not dealer:
+        return JsonErrorResponse(description = u'经销商不存在,请刷新后再试')
+
+    bank_card_id = payload.pop('id', None)
+
+    try:
+        payload = SaveWithdrawBankCardSchemaForSuper(payload)
+    except MultipleInvalid as e:
+        logger.exception(e)
+        return JsonErrorResponse(description = u"信息不完整(1001)")
+
+    isPublic = payload.pop('isPublic')
+
+    if isPublic:
+        try:
+            payload = SavePublicWithdrawBankCardExtraSchema(payload)
+        except MultipleInvalid as e:
+            logger.exception(e)
+            return JsonErrorResponse(description = u"信息不完整(1002)")
+
+        branchBank = WithdrawBranchBanks.objects(
+            code = payload['branchBankCode']).first()  # type: WithdrawBranchBanks
+        if not branchBank:
+            return JsonErrorResponse(description = u"查询不到支行信息")
+
+        WithdrawBankCard.new_public_withdraw_bank_card(
+            id = bank_card_id,
+            ownerId = dealerId,
+            role = ROLE.dealer,
+            bankCode = payload['bankCode'],
+            bankName = payload['bankName'],
+            accountName = payload['accountName'],
+            accountCode = payload['accountCode'],
+            phone = payload.get('phone', ''),
+            provinceCode = payload['provinceCode'],
+            province = payload['province'],
+            cityCode = payload['cityCode'],
+            city = payload['city'],
+            branchBankCode = branchBank.code,
+            branchBankName = branchBank.name,
+            cnapsCode = branchBank.cnapsCode)
+    else:
+        WithdrawBankCard.new_personal_withdraw_bank_card(
+            id = bank_card_id,
+            ownerId = dealerId,
+            role = ROLE.dealer,
+            phone = payload.get('phone', ''),
+            bankName = payload['bankName'],
+            accountName = payload['accountName'],
+            accountCode = payload['accountCode'])
+
+    return JsonOkResponse()
+
+
+@error_tolerate(logger = logger, nil = JsonErrorResponse(u"删除经销商银行卡失败"))
+@permission_required(ROLE.supermanager)
+def deleteDealerBankAccount(request):
+    payload = json.loads(request.body)
+
+    bank_card_id = payload.get('id', None)
+
+    if not bank_card_id:
+        return JsonErrorResponse(description = u'参数错误')
+
+    WithdrawBankCard.objects(id = bank_card_id).delete()
+
+    return JsonOkResponse()

+ 0 - 16
apps/web/user/models.py

@@ -1352,19 +1352,6 @@ class RechargeRecordDict(dict):
     def via(self):
         return self.get('via')
 
-    def fetch_account_split_map(self, dealer, group, payment_gateway=None):   # type:(Dealer, GroupDict, PaymentGateway) -> dict
-        """
-        分账的模式以厂家设置为准 不再以代理商设置为准(之前都是默认模式,无需考虑兼容)
-        """
-        if dealer.my_agent.manager.deviceIncomeType == DEVICE_INCOME_STRATEGY.AGENT_FIRST_LEDGER:
-            return AgentLedgerFirst(
-                dealer, group, self, payment_gateway=payment_gateway
-            ).calc_account_split_map()
-        else:
-            return PartnerLedgerFirst(
-                dealer, group, self, payment_gateway = payment_gateway
-            ).calc_account_split_map()
-
     @property
     def partition_map(self):
         """
@@ -2073,9 +2060,6 @@ class RechargeRecord(OrderRecordBase):
     def to_json_dict(self):
         return self.to_dict(json_safe = True)
 
-    def fetch_account_split_map(self, dealer, group, payment_gateway = None):
-        return self.to_dict_obj.fetch_account_split_map(dealer, group, payment_gateway)
-
     def succeed(self, wxOrderNo, **kwargs):
         # 这里和数据库强相关, 通过判断是否正确更新记录
         payload = {

+ 2 - 7
apps/web/user/utils.py

@@ -39,9 +39,9 @@ from apps.web.core import PayAppType, ROLE
 from apps.web.core.accounting import Accounting
 from apps.web.core.auth.wechat import WechatAuthBridge
 from apps.web.core.bridge import WechatClientProxy
-from apps.web.core.exceptions import ServiceException, JOSError, InvalidParameter, InterceptException
+from apps.web.core.exceptions import ServiceException, InvalidParameter, InterceptException
 from apps.web.core.helpers import ActionDeviceBuilder
-from apps.web.core.models import DlbPayApp, SaobeiPayApp, WechatPayApp, JDAggrePayApp, WechatAuthApp
+from apps.web.core.models import WechatPayApp, WechatAuthApp
 from apps.web.dealer.models import Dealer, VirtualCard
 from apps.web.device.define import DeviceChannelType
 from apps.web.device.models import Device, Group
@@ -217,11 +217,6 @@ def auth_wechat_pay_app(user, dealer, product_agent, state, redirect_uri):
                       app_type = APP_TYPE.WECHAT_ENV_PAY,
                       role = ROLE.myuser)  # type: PayAppBase
 
-    # 哆啦宝和扫呗不需要获取OPENID
-    if isinstance(app, DlbPayApp) or isinstance(app, SaobeiPayApp):
-        logger.debug('auth app<{}> need not to fetch openid.'.format(repr(app)))
-        return None
-
     if isinstance(app, WechatPayApp):
         auth_bridge = WechatAuthBridge(app)  # type: WechatAuthBridge
 

+ 45 - 1
apps/web/validation.py

@@ -1,6 +1,50 @@
 # -*- coding: utf-8 -*-
 # !/usr/bin/env python
+from voluptuous import PREVENT_EXTRA
 
 from apps.web.core.validation import Schema, Required, ALLOW_EXTRA
 
-commandSchema = Schema({Required('cmd'): int, Required('IMEI'): basestring}, extra = ALLOW_EXTRA)
+commandSchema = Schema(
+    {
+        Required('cmd'): int,
+        Required('IMEI'): basestring
+    }, extra = ALLOW_EXTRA)
+
+SaveWithdrawBankCardSchema = Schema(
+    {
+        Required('accountName'): basestring,
+        Required('accountCode'): basestring,
+        Required('accountType'): basestring,
+        Required('bankName'): basestring,
+        Required('phone'): basestring
+    }, extra = ALLOW_EXTRA)
+
+SaveWithdrawBankCardSchemaForSuper = Schema(
+    {
+        Required('accountName'): basestring,
+        Required('accountCode'): basestring,
+        Required('isPublic'): bool,
+        Required('bankName'): basestring
+    }, extra = ALLOW_EXTRA)
+
+SavePublicWithdrawBankCardExtraSchema = Schema(
+    {
+        Required('province'): basestring,
+        Required('provinceCode'): basestring,
+        Required('city'): basestring,
+        Required('cityCode'): basestring,
+        Required('branchBankCode'): basestring,
+        Required('branchBankName'): basestring
+    }, extra = ALLOW_EXTRA)
+
+SaveWithdrawAlipaySchema = Schema(
+    {
+        Required('realName'): basestring,
+        Required('loginId'): basestring
+    }, extra = PREVENT_EXTRA)
+
+
+SaveWithdrawWechatSchema = Schema(
+    {
+        Required('realName'): basestring
+    }, extra = PREVENT_EXTRA)

+ 3 - 1
library/alipay/__init__.py

@@ -270,6 +270,8 @@ class BaseAliPay(object):
         raise AttributeError("Unknown attribute" + api_name)
 
     def _request(self, method, url, headers = None, **kwargs):
+        logger.debug("Calculate Signature URL: %s", url)
+
         if headers:
             headers.update({'Content-Type': 'application/json'})
         else:
@@ -623,7 +625,7 @@ class BaseAliPay(object):
             raw_string, "alipay_fund_trans_common_query_response"
         )
 
-    def api_alipay_trade_create(self, out_trade_no, total_amount, subject, notify_url=None, **kwargs):
+    def api_alipay_trade_create(self, out_trade_no, total_amount, subject, notify_url = None, **kwargs):
         biz_content = {
             "out_trade_no": out_trade_no,
             "total_amount": total_amount,

+ 2 - 1
library/jdopen/pay.py

@@ -276,4 +276,5 @@ class JDOpenPay(JdOpenBaseClient):
             'date': bill_date
         }
 
-        return self.post(url = url, data = data)
+        result = self.post(url = url, data = data)
+        return result['data']['downloadUrl']

+ 2 - 1
library/misc.py

@@ -1,5 +1,6 @@
 # -*- coding: utf-8 -*-
 # !/usr/bin/env python
+
 import json
 
 import requests
@@ -63,7 +64,7 @@ class BankAPI(object):
 
         if result['validated']:
             return {
-                'bankCode': result['bank'],
+                'bankAbbrevCode': result['bank'],
                 'cardType': result['cardType']
             }
         else:

+ 16 - 6
library/sms/aliyun.py

@@ -15,19 +15,29 @@ from library.sms import SmsSender
 
 
 class Sender(SmsSender):
-    def __init__(self, ak, secret, region_id):
-        self.ak = ak
-        self.secret = secret
-        self.region_id = region_id
+    TEMPLATE = {
+        'CAPTCHA_TEMPLATE_ID': 'SMS_190790529',
+        'SMS_NOTIFY_EXPIRED_DEVICE_TEMPLATEID': 'SMS_190784668',
+        'EDIT_MONITOR_ID': 'SMS_211215112',
+        'DEALER_MONITOR_WITHDRAW_ID': 'SMS_211230116',
+    }
 
-    def send(self, phoneNumber, templateId, msg, productName, verifyCode = False):
+    def __init__(self, ak = None, secret = None, region_id = None):
+        self.ak = ak or 'LTAI4GEc1j8pvs4EjFrtL5K9'
+        self.secret = secret or 'KuCEo8YWRn7tjQaJsXJCcG7P4leBMr'
+        self.region_id = region_id or 'cn-hangzhou'
+
+    def send(self, phoneNumber, templateName, msg, productName, verifyCode = False):
         """
         :param phoneNumber:
-        :param templateId:
+        :param templateName:
         :param msg:
         :param productName:
         :return: dict
         """
+
+        templateId = self.TEMPLATE[templateName]
+
         try:
             client = AcsClient(ak = self.ak, secret = self.secret, region_id = self.region_id)
 

+ 16 - 6
library/sms/ucpaas.py

@@ -16,20 +16,30 @@ from library.sms import SmsSender
 class Sender(SmsSender):
     API_URL = 'https://open.ucpaas.com/ol/sms/sendsms'
 
-    def __init__(self, appid, sid, token):
-        self.appid = appid
-        self.sid = sid
-        self.token = token
+    TEMPLATE = {
+                   'CAPTCHA_TEMPLATE_ID': '544642',
+                   'SMS_NOTIFY_EXPIRED_DEVICE_TEMPLATEID': '384328',
+                   'EDIT_MONITOR_ID': '584806',
+                   'DEALER_MONITOR_WITHDRAW_ID': '584807',
+                   'MERCHANT_NOTIFY': '611184'
+               }
 
-    def send(self, phoneNumber, templateId, msg, productName, verifyCode = False):
+    def __init__(self, appid = None, sid = None, token = None):
+        self.appid = appid or '06268c58ffcf412e875c38663b819495'
+        self.sid = sid or '0dcff57b45a868c31a74ae5bf1488aa8'
+        self.token = token or '52917c39382fbebda57e0a23be265357'
+
+    def send(self, phoneNumber, templateName, msg, productName, verifyCode = False):
         """
         :param phoneNumber:
-        :param templateId:
+        :param templateName:
         :param msg:
         :param productName:
         :return: dict
         """
 
+        templateId = self.TEMPLATE[templateName]
+
         logger.debug('productName = %s; templateId = %s; msg = %s' % (productName, templateId, msg))
 
         headers = {

+ 6 - 0
library/wechatpayv3/client/api/transfer.py

@@ -44,6 +44,12 @@ class Transfer(BaseWeChatAPI):
         """
         return self.transfer_query_out_detail_no(out_trade_no, out_trade_no)
 
+    def transfer_bankcard(self, true_name, bank_card_no, bank_code, amount, desc = None, out_trade_no = None):
+        raise NotImplementedError('not support transfer bank card')
+
+    def query_bankcard(self, out_trade_no):
+        raise NotImplementedError('not support query bank card transfer')
+
     def transfer_batch(self, out_batch_no, batch_name, batch_remark, total_amount, total_num, transfer_detail_list):
         """发起商家转账
         :param out_batch_no: 商户系统内部的商家批次单号,要求此参数只能由数字、大小写字母组成,在商户系统内部唯一,示例值:'plfk2020042013'

+ 1 - 1
library/wechatpayv3/core.py

@@ -8,7 +8,7 @@ from datetime import datetime
 
 import requests
 
-from library.wechatbase.exceptions import WeChatException, InvalidSignatureException, APILimitedException, \
+from library.wechatbase.exceptions import InvalidSignatureException, APILimitedException, \
     WeChatPayException, WechatNetworkException
 from . import update_certificates
 from .type import RequestType, SignType

+ 202 - 95
static/administrator/js/controllers/dealerManage.js

@@ -33,21 +33,17 @@ app.controller('dealerManageCtrl', ['$scope', "$state", '$stateParams', '$http',
     };
 
     $scope.ngEvent = {
-        changeProvince: function () {
+        changeProvince: function (bankInfo) {
             console.log("changeProvince")
             //无论是否选择了数据,都清空子级下拉框选中的项
-            $scope.dialogData.bankCity = null;
-
-            var bankProvinceID = $scope.dialogData.bankProvince.value;
-            for (var index in $scope.provinceList) {
-                var item = $scope.provinceList[index];
-                var children = item.children
-                if (item.value == bankProvinceID) {
-                    // 初始化二级 市
-                    $scope.cityList = children
-                    break
-                }
-            }
+            bankInfo.bankCity = null;
+
+            var bankProvinceID = bankInfo.bankProvince.value;
+
+            if (bankProvinceID)
+                $scope.searchArea(bankProvinceID,bankInfo)
+            else
+                bankInfo.cityList = []
         }
     }
 
@@ -386,25 +382,71 @@ app.controller('dealerManageCtrl', ['$scope', "$state", '$stateParams', '$http',
         return ids
     }
 
-    $scope.parentBankNameList = []
-
     $scope.provinceList = []
-    $scope.cityList = []
 
-    //加载地址列表
-    $http.get('/common/loadDistrictData', {}).then(function (data) {
-        data = data.data
-        var payload = data.payload
-        $scope.provinceList = payload
-    });
+    $scope.searchSubBank = function (bankInfo) {
+        let form = bankInfo
+        $http.get('/common/withdraw/branchBank/list', {
+            params: {
+                bankCode: form?.bankItem?.value,
+                provinceCode: form?.bankProvince?.value,
+                cityCode: form?.bankCity?.value,
+            }
+        }).then(function (data) {
+            data = data.data
+            let {payload} = data
+            let list = payload?.dataList || []
+            list = list.map(item => ({
+                text: item.name,
+                value: item.code,
+            }))
+            bankInfo.branchBankList = list;
+        }).catch(function (data) {
+            toaster.pop("error", "提示", "支行获取失败");
+        });
+    }
+
+    $scope.searchBank = function (bankInfo) {
+        bankInfo.bankList = []
+        $http.get('/common/withdraw/bank/list', {
+            params: {
+                keyWord: bankInfo.keyWord,
+                range: 'all'
+            }
+        }).then(function (data) {
+            var bankList = data.data.payload.dataList
+            bankList.forEach(function (item, index) {
+                bankInfo.bankList.push({value: item.code, text: item.name})
+            })
+        });
+    }
+
+    $scope.searchArea = function (province, bankInfo) {
+        return new Promise((resolve => {
+            //加载地址列表
+            $http.get('/common/withdraw/area/list', {params: {province: province || ""}}).then(function (data) {
+                data = data.data
+                var payload = data.payload
+
+                let dataRe = (payload.dataList || []).map(item => {
+                    return {
+                        ...item,
+                        text: item.name,
+                        value: item.code,
+                    }
+                })
+                if (province) {
+                    bankInfo.cityList = dataRe
+                } else {
+                    $scope.provinceList = dataRe
+                }
+                resolve()
+            });
+        }))
+
+    }
+    $scope.searchArea();// 加载省
 
-    //加载对公提现支持银行列表
-    $http.get('/common/banks/list', {params: {accountType: 'public'}}).then(function (data) {
-        var bankList = data.data.payload
-        bankList.forEach(function (item, index) {
-            $scope.parentBankNameList.push({value: item, text: item})
-        })
-    });
 
     //封号|解封
     $scope.ban = function (entity, isNormal) {
@@ -941,7 +983,74 @@ app.controller('dealerManageCtrl', ['$scope', "$state", '$stateParams', '$http',
         });
     };
 
-    $scope.editBankAccount = function () {
+    // 获取此经销商的银行卡配置
+    function getBankConfigs(id) {
+        return new Promise(((resolve, reject) => {
+            $http.get('/superadmin/getDealerBanks', {
+                params: {
+                    dealerId: id,
+                }
+            }).then(function (res) {
+                resolve(res.data?.payload?.dataList)
+            }).catch(function (data) {
+                toaster.pop("error", "提示", "getDealerBanks失败。");
+                reject()
+            });
+        }))
+    }
+
+    var lastRowId = ""
+    async function renderBanksDialog(id){
+        let list = await getBankConfigs(id) || [];
+
+        list.forEach(bankInfo => {
+            findName(bankInfo)
+        })
+
+        function findName(bankInfo) {
+            if (bankInfo) {
+                // 必须用provinceList ,确保引用地址一致,ui-select才能双绑。
+                for (var index in $scope.provinceList) {
+                    var item = $scope.provinceList[index];
+                    if (item.value == bankInfo.provinceCode) {
+                        // 设置省份对应关系
+                        bankInfo.bankProvince = item
+
+                        // 初始化二级 市(暂时不查接口)
+                        bankInfo.bankCity = {
+                            "text": bankInfo.city,
+                            "value": bankInfo.cityCode,
+                        }
+                        bankInfo.cityList = [
+                            {
+                                "text": bankInfo.city,
+                                "value": bankInfo.cityCode,
+                            }
+                        ]
+                        break
+                    }
+                }
+
+                bankInfo.bankItem = {
+                    text: bankInfo.bankName,
+                    value: bankInfo.bankCode
+                }
+
+                bankInfo.branchBankItem = {
+                    text: bankInfo.branchBankName,
+                    value: bankInfo.branchBankCode
+                }
+            }
+        }
+
+        $scope.dialogData = {
+            id: id,
+            bankCards: list
+        }
+        $scope.$apply();
+    }
+
+    $scope.editBankAccount = async function () {
         var rows = $scope.gridApi.selection.getSelectedRows();
         if (rows.length === 0) {
             toaster.pop("info", "提示", "请选择数据!");
@@ -953,85 +1062,83 @@ app.controller('dealerManageCtrl', ['$scope', "$state", '$stateParams', '$http',
         }
 
         var row = rows[0]
-        $scope.dialogData = $.extend({id: row.id}, row.bankAccountInfo)
-
-        var bankInfo = row.bankAccountInfo || {}
-        if (bankInfo) {
-            // 必须用provinceList ,确保引用地址一致,ui-select才能双绑。
-            for (var index in $scope.provinceList) {
-                var item = $scope.provinceList[index];
-                var children = item.children
-                if (item.value == bankInfo.bankProvinceID) {
-                    // 设置省份对应关系
-                    $scope.dialogData.bankProvince = item
-
-                    // 初始化二级 市
-                    $scope.cityList = children
-
-                    for (var index2 in children) {
-                        var item2 = children[index2];
-                        if (item2.value == bankInfo.bankCityID) {
-                            // 设置省份对应关系
-                            $scope.dialogData.bankCity = item2
-                            break
-                        }
-                    }
-
-                    break
-                }
-            }
-        }
-
-        // 找对应关系
-        $scope.parentBankNameList.forEach(function (item, index) {
-            if (item.text === bankInfo.parentBankName) {
-                $scope.dialogData.parentBankItem = item
-                return
-            }
-        })
+        lastRowId = row.id;
+        await renderBanksDialog(lastRowId)
 
         $("#bankAccountForm").modal();
     }
 
-    $scope.saveBankAccount = function () {
-        var row = getOneRow();
-        if (!row) {
-            return
-        }
-
-        if ($scope.bankAccountForm.$invalid) {
-            toaster.pop("warning", "提示", "请填写正确的数据");
-            return
+    $scope.addBank = function () {
+        $scope.dialogData.bankCards.push({})
+    }
+    $scope.deleteBank = function (index, bankInfo) {
+        if(bankInfo.id) {
+            $.confirm({
+                content: '确定删除?',
+                buttons: {
+                    ok: {
+                        btnClass: 'btn-red',
+                        action: function () {
+                            $http({
+                                method: 'POST',
+                                url: '/superadmin/deleteDealerBankAccount',
+                                data: {id: bankInfo.id}
+                            }).then(function () {
+                                renderBanksDialog(lastRowId)
+                            }, function () {
+                                toaster.pop("error", "提示", "删除失败!");
+                            });
+                        }
+                    },
+                }
+            });
+        } else {
+            $scope.dialogData.bankCards.splice(index, 1)
         }
+    }
+    $scope.saveBankAccount = function (bankInfo) {
+        bankInfo = $.extend(true, {dealerId: lastRowId}, bankInfo);
+        if (bankInfo.isPublic) {
+            if ($.isEmptyObject(bankInfo.bankProvince) || $.isEmptyObject(bankInfo.bankCity)) {
+                toaster.pop("warning", "提示", "请填写省市");
+                return
+            }
 
+            if ($.isEmptyObject(bankInfo.branchBankItem)) {
+                toaster.pop("warning", "提示", "请填写支行名称");
+                return
+            }
 
-        // ui-select ng-required不生效。。。
-        var saveData = $.extend(true, {}, $scope.dialogData)
-        if ($.isEmptyObject(saveData.bankProvince) || $.isEmptyObject(saveData.bankCity)) {
-            toaster.pop("error", "提示", "请填写省市");
-            return
+            bankInfo.cityCode = bankInfo?.bankCity?.value
+            bankInfo.city = bankInfo?.bankCity?.text
+            bankInfo.provinceCode = bankInfo?.bankProvince?.value
+            bankInfo.province = bankInfo?.bankProvince?.text
+            bankInfo.bankCode = bankInfo?.bankItem?.value
+            bankInfo.bankName = bankInfo?.bankItem?.text
+            bankInfo.branchBankCode = bankInfo?.branchBankItem?.value
+            bankInfo.branchBankName = bankInfo?.branchBankItem?.text
+        } else {
+            bankInfo.bankName = bankInfo?.bankItem?.text
         }
 
-        saveData.bankCityID = saveData.bankCity.value
-        saveData.bankCityName = saveData.bankCity.text
-        saveData.bankProvinceID = saveData.bankProvince.value
-        saveData.bankProvinceName = saveData.bankProvince.text
-        saveData.parentBankName = saveData.parentBankNameOther || saveData.parentBankItem.text
+        delete bankInfo.bankCity
+        delete bankInfo.bankProvince
+        delete bankInfo.bankItem
+        delete bankInfo.branchBankItem
 
-        delete saveData.bankCity
-        delete saveData.bankProvince
-        delete saveData.parentBankItem
-        delete saveData.parentBankNameOther
+        delete bankInfo.keyWord
+        delete bankInfo.cityList
+        delete bankInfo.branchBankList
+        delete bankInfo.bankList
 
         $http({
             method: 'POST',
-            url: '/superadmin/editDealerBankAccount',
-            data: saveData
-        }).then(function (response) {
-            initDataGrid();
-            $('#bankAccountForm').modal('hide');
+            url: '/superadmin/saveDealerBankAccount',
+            data: bankInfo
+        }).then(function () {
+            renderBanksDialog(lastRowId)
             toaster.pop("success", "提示", "保存成功!");
-        }, function (response) {
+        }, function () {
             toaster.pop("error", "提示", "保存失败!");
         });
     }

+ 102 - 97
static/administrator/tpl/dealerManage.html

@@ -28,7 +28,7 @@
 
                             <li class="divider"></li>
                             <li ng-click="editBankAccount()">
-                                <a href><i class="fa fa-credit-card text-danger-dk"> </i> 对公账户</a></li>
+                                <a href><i class="fa fa-credit-card text-danger-dk"> </i> 提现银行账户</a></li>
                             <li ng-click="editCardFee()">
                                 <a href><i class="iconfont icon-sim-real text-danger-dk"> </i> 经销商流量费</a></li>
                             <li ng-click="setDealerMaxPackagePrice()">
@@ -691,128 +691,133 @@
 
 
     <form name="bankAccountForm" id="bankAccountForm" class="form-horizontal form-validation modal fade" role="dialog"
-          style=" width: 800px;min-height: 600px;height: 600px;">
+          data-backdrop="static"
+          style=" width: 1000px;min-height: 760px;height: 760px;">
         <div class="panel panel-default">
             <div class="panel-heading">
                 <strong>银行账户</strong>
+                <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
             </div>
 
             <div class="panel-body">
-                <div class="form-group">
-                    <label class="col-sm-3 control-label">银行卡号</label>
-                    <div class="col-sm-9">
-                        <input type="text" ng-model="dialogData.accountCode"
-                               class="form-control"
-                               placeholder="银行卡号"
-                               required>
-                    </div>
-                </div>
+                <div ng-repeat="(index,bankInfo) in dialogData.bankCards"
+                     style="background: #f3f4f5;padding: 10px;border-radius: 10px;margin-bottom: 10px;">
 
-                <div class="form-group">
-                    <label class="col-sm-3 control-label">银行名称</label>
-                    <div class="col-sm-9">
-                        <ui-select ng-model="dialogData.parentBankItem" theme="bootstrap" class="w-md inline v-middle">
-                            <ui-select-match placeholder="选择银行">{{$select.selected.text}}
-                            </ui-select-match>
-                            <ui-select-choices repeat="item in parentBankNameList| filter: $select.search">
-                                <div ng-bind-html="item.text | highlight: $select.search"></div>
-                            </ui-select-choices>
-                        </ui-select>
-                    </div>
-                </div>
+                    <div class="form-group">
+                        <label class="col-sm-3 control-label">是否对公</label>
+                        <div class="col-sm-9">
+                            <div class="radio pull-left">
+                                <label class="i-checks">
+                                    <input type="radio" name="isPublic{{index}}" ng-value="false" required
+                                           ng-model="bankInfo.isPublic">
+                                    <i></i>
+                                    否
+                                </label>
+                            </div>
+                            <div class="radio pull-left m-l">
+                                <label class="i-checks">
+                                    <input type="radio" name="isPublic{{index}}" ng-value="true" required
+                                           ng-model="bankInfo.isPublic">
+                                    <i></i>
+                                    是
+                                </label>
+                            </div>
 
-                <div class="form-group">
-                    <label class="col-sm-3 control-label">其他银行</label>
-                    <div class="col-sm-6">
-                        <input type="text" ng-model="dialogData.parentBankNameOther" class="form-control"
-                               placeholder="其他银行">
+                        </div>
                     </div>
-                </div>
 
-                <div class="form-group">
-                    <label class="col-sm-3 control-label">支行所在地区</label>
-                    <div class="col-sm-9">
-                        <ui-select ng-model="dialogData.bankProvince" theme="bootstrap" class="w-sm inline v-middle"
-                                   ng-change="ngEvent.changeProvince()">
-                            <ui-select-match placeholder="选择省份">{{$select.selected.text}}
-                            </ui-select-match>
-                            <ui-select-choices repeat="item in provinceList| filter: $select.search">
-                                <div ng-bind-html="item.text | highlight: $select.search"></div>
-                            </ui-select-choices>
-                        </ui-select>
-
-                        <ui-select ng-model="dialogData.bankCity" theme="bootstrap" class="w-sm inline v-middle"
-                        >
-                            <ui-select-match placeholder="选择市">{{$select.selected.text}}
-                            </ui-select-match>
-                            <ui-select-choices repeat="item in cityList| filter: $select.search">
-                                <div ng-bind-html="item.text | highlight: $select.search"></div>
-                            </ui-select-choices>
-                        </ui-select>
+                    <div class="form-group">
+                        <label class="col-sm-3 control-label">银行卡号</label>
+                        <div class="col-sm-6">
+                            <input type="text" ng-model="bankInfo.accountCode"
+                                   class="form-control"
+                                   placeholder="银行卡号"
+                                   required>
+                        </div>
+                    </div>
 
+                    <div class="form-group">
+                        <label class="col-sm-3 control-label">银行名称</label>
+                        <div class="col-sm-4">
+                            <ui-select ng-model="bankInfo.bankItem" theme="bootstrap" class="w-md inline v-middle">
+                                <ui-select-match placeholder="选择银行">{{$select.selected.text}}
+                                </ui-select-match>
+                                <ui-select-choices repeat="item in bankInfo.bankList| filter: $select.search">
+                                    <div ng-bind-html="item.text | highlight: $select.search"></div>
+                                </ui-select-choices>
+                            </ui-select>
+                        </div>
+                        <div class="col-sm-5">
+                            <input type="text" ng-model="bankInfo.keyWord"
+                                   class="form-control inline" ng-maxlength="50" maxlength="50"
+                                   style="width: 150px"
+                                   placeholder="关键字搜索银行"
+                                   >
+                            <button type="button" class="btn btn-info inline" ng-click="searchBank(bankInfo)">搜索
+                            </button>
+                        </div>
                     </div>
-                </div>
 
+                    <div class="form-group" ng-if="bankInfo.isPublic">
+                        <label class="col-sm-3 control-label">支行所在地区</label>
+                        <div class="col-sm-9">
+                            <ui-select ng-model="bankInfo.bankProvince" theme="bootstrap" class="w-sm inline v-middle"
+                                       ng-change="ngEvent.changeProvince(bankInfo)">
+                                <ui-select-match placeholder="选择省份">{{$select.selected.text}}
+                                </ui-select-match>
+                                <ui-select-choices repeat="item in provinceList| filter: $select.search">
+                                    <div ng-bind-html="item.text | highlight: $select.search"></div>
+                                </ui-select-choices>
+                            </ui-select>
 
-                <div class="form-group">
-                    <label class="col-sm-3 control-label">支行名称</label>
-                    <div class="col-sm-9">
-                        <input type="text" ng-model="dialogData.subBankName"
-                               class="form-control" ng-maxlength="50" maxlength="50"
-                               placeholder="支行名称"
-                               required>
-                    </div>
-                </div>
+                            <ui-select ng-model="bankInfo.bankCity" theme="bootstrap" class="w-sm inline v-middle"
+                            >
+                                <ui-select-match placeholder="选择市">{{$select.selected.text}}
+                                </ui-select-match>
+                                <ui-select-choices repeat="item in bankInfo.cityList| filter: $select.search">
+                                    <div ng-bind-html="item.text | highlight: $select.search"></div>
+                                </ui-select-choices>
+                            </ui-select>
 
-                <div class="form-group">
-                    <label class="col-sm-3 control-label">账户名称</label>
-                    <div class="col-sm-9">
-                        <input type="text" ng-model="dialogData.merchantName"
-                               class="form-control" ng-maxlength="50" maxlength="50"
-                               placeholder="账户名称"
-                               required>
+                        </div>
                     </div>
-                </div>
 
-
-                <div class="form-group">
-                    <label class="col-sm-3 control-label">是否对公</label>
-                    <div class="col-sm-9">
-                        <div class="radio pull-left">
-                            <label class="i-checks">
-                                <input type="radio" name="isPublic" ng-value="false" required
-                                       ng-model="dialogData.isPublic">
-                                <i></i>
-                                否
-                            </label>
+                    <div class="form-group" ng-if="bankInfo.isPublic">
+                        <label class="col-sm-3 control-label">支行名称</label>
+                        <div class="col-sm-9">
+                            <ui-select ng-model="bankInfo.branchBankItem" theme="bootstrap" class=" inline v-middle" style="width: 360px;">
+                                <ui-select-match placeholder="选择银行">{{$select.selected.text}}
+                                </ui-select-match>
+                                <ui-select-choices repeat="item in bankInfo.branchBankList| filter: $select.search">
+                                    <div ng-bind-html="item.text | highlight: $select.search"></div>
+                                </ui-select-choices>
+                            </ui-select>
+                            <button type="button" class="btn btn-info" ng-click="searchSubBank(bankInfo)">搜索
+                            </button>
                         </div>
-                        <div class="radio pull-left m-l">
-                            <label class="i-checks">
-                                <input type="radio" name="isPublic" ng-value="true" required
-                                       ng-model="dialogData.isPublic">
-                                <i></i>
-                                是
-                            </label>
+                    </div>
+
+                    <div class="form-group">
+                        <label class="col-sm-3 control-label">账户名称</label>
+                        <div class="col-sm-6">
+                            <input type="text" ng-model="bankInfo.accountName"
+                                   class="form-control" ng-maxlength="50" maxlength="50"
+                                   placeholder="账户名称"
+                                   required>
                         </div>
+                    </div>
 
+                    <div class="info-inline text-center">
+                        <span class="btn btn-danger" type="button" ng-click="deleteBank(index,bankInfo)">删除</span>
+                        <span class="btn btn-success" type="button" ng-click="saveBankAccount(bankInfo)">保存修改</span>
                     </div>
                 </div>
 
-                <div class="form-group">
-                    <label class="col-sm-3 control-label">卡号类型</label>
-                    <div class="col-sm-9">
-                        <input type="text" ng-model="dialogData.cardType"
-                               class="form-control" ng-maxlength="20" maxlength="20"
-                               placeholder="卡号类型"
-                               required>
-                    </div>
+                <div class="info-inline text-center" style="padding: 5px 10px;">
+                    <span class="btn btn-info" type="button" ng-click="addBank()">添加</span>
                 </div>
 
             </div>
-            <footer class="panel-footer text-right bg-light lter">
-                <button type="submit" class="btn btn-success" ng-click="saveBankAccount()">保存
-                </button>
-            </footer>
         </div>
     </form>
 

+ 17 - 11
static/agents/wallet/new-info.html

@@ -40,19 +40,25 @@
             mui.toast("请输入真实姓名,不小于2位,不大于20位");
             return;
         }
-        var url = "/agent/updateInfo";
+
         var data = {
-            "name": name
+            "realName": name
         };
-        sendRequest(url, "POST", data, function (res) {
-            if (res.result == 1) {
-                mui.confirm('修改成功', '提示', ['返回'], function (e) {
-                    if (e) {
-                        history.back()
-                    }
-                });
-            } else {
-                mui.alert(res.description, '温馨提示', '我知道了');
+        sendRequest({
+            url: "/common/withdraw/wechat/save",
+            type: "POST",
+            data: data,
+            contentType: "json",
+            success: function (res) {
+                if (res.result === 1) {
+                    mui.confirm('修改成功', '提示', ['返回'], function (e) {
+                        if (e) {
+                            history.back()
+                        }
+                    });
+                } else {
+                    mui.alert(res.description, '温馨提示', '我知道了');
+                }
             }
         });
     }

+ 0 - 38
static/agents/wallet/wallet-bank-edit.html

@@ -1,38 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-    <meta charset="utf-8">
-    <meta name="author" content="">
-    <meta name="description" content=""/>
-    <meta name="keywords" content="扫码支付,线上投币,运营数据,物联网"/>
-    <meta name="format-detection" content="telephone=no,email=no">
-    <meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no">
-    <meta http-equiv="pragma" content="no-cache">
-    <meta http-equiv="cache-control" content="no-cache">
-    <meta http-equiv="expires" content="0">
-    <title>银行卡信息</title>
-    <link rel="stylesheet" href="/components/lib/mui.min.css">
-    <link rel="stylesheet" href="/components/lib/mui.picker.min.css"/>
-    <link rel="stylesheet" href="/components/custom/css/common.css">
-    <link rel="stylesheet" href="/app/css/xyf.common.min.css">
-</head>
-<style>
-    .mui-input-group .mui-input-row label ~ input {
-        color: #000;
-    }
-</style>
-
-<body>
-
-<script src="/components/lib/jquery.min.js"></script>
-<script src="/components/lib/mui.min.js"></script>
-<script src="/components/lib/mui.picker.min.js"></script>
-<script src="/components/custom/js/common.js"></script>
-<script src="/components/custom/js/iconfont.js"></script>
-<script src="/app/js/xyf.common.js"></script>
-<script src="/app/js/wallet.js"></script>
-<script>
-    WalletTool.initBankEdit('agent')
-</script>
-</body>
-</html>

+ 0 - 134
static/agents/wallet/wallet-bank.html

@@ -1,134 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-    <meta charset="utf-8">
-    <meta name="author" content="">
-    <meta name="description" content=""/>
-    <meta name="keywords" content="扫码支付,线上投币,运营数据,物联网"/>
-    <meta name="format-detection" content="telephone=no,email=no">
-    <meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no">
-    <meta http-equiv="pragma" content="no-cache">
-    <meta http-equiv="cache-control" content="no-cache">
-    <meta http-equiv="expires" content="0">
-    <title>银行卡</title>
-    <link rel="stylesheet" href="/components/lib/mui.min.css">
-    <link rel="stylesheet" href="/components/custom/css/common.css">
-    <link rel="stylesheet" href="/app/css/xyf.common.min.css">
-</head>
-<style>
-    .main {
-        background: #2E3132;
-        min-height: 100%;
-    }
-
-    html, body {
-        height: 100%;
-    }
-</style>
-<body>
-
-<div class="main">
-    <ul class="card-ul">
-    </ul>
-
-    <div class="mui-navigate-right iconfont icon-add-more add-card-btn" onclick="goSet()"> 添加银行卡</div>
-</div>
-
-<div id="bankCardMenu" class="mui-popover mui-popover-action mui-popover-bottom">
-    <ul class="mui-table-view">
-        <li class="mui-table-view-cell">
-            <a href="javascript:;" class="cardUnbind">解绑银行卡</a>
-        </li>
-    </ul>
-    <ul class="mui-table-view">
-        <li class="mui-table-view-cell">
-            <a class="font-bold">取消</a>
-        </li>
-    </ul>
-</div>
-
-<script type="text/html" id="model">
-    <li data-id=''>
-        <i class="card-logo-back bank-logo-back"> </i>
-        <a class="card-text-info" href="#bankCardMenu">
-            <div class="card-logo">
-            </div>
-            <div class="name"></div>
-            <div class="type"></div>
-            <div class="card-id"><span>**** **** **** </span><em class="end-num"></em></div>
-        </a>
-    </li>
-</script>
-<script src="/components/lib/jquery.min.js"></script>
-<script src="/components/lib/mui.min.js"></script>
-<script src="/components/custom/js/common.js"></script>
-<script src="/components/custom/js/iconfont.js"></script>
-<script src="/app/js/xyf.common.js"></script>
-<script>
-    var unBindCardId;
-    $(function () {
-        var url = "/agent/getWalletBank";
-        sendRequest(url, 'GET', null, function (res) {
-            if (res.result == 1) {
-                if (res.para.dataList && res.para.dataList.length > 0) {
-                    $(".add-card-btn").hide();
-                }
-
-                for (var i = 0; i < res.para.dataList.length; i++) {
-                    var item = res.para.dataList[i];
-                    var dom = $($('#model').html());
-                    dom.attr("data-id", item.id);
-                    dom.find(".name").text(item.bankName);
-                    dom.find(".type").text(item.bankType);
-                    dom.find(".end-num").text(item.endNum);
-
-                    var bankStyle = findBankStyle(item.bankName);
-                    var iconName = bankStyle.icon;
-                    var color = bankStyle.color;
-                    var cardStyle = bankStyle.cardStyle;
-
-                    dom.find(".card-logo-back").html('<svg class="icon" aria-hidden="true"><use xlink:href="#' + iconName + '"></use></svg>');
-                    dom.find(".card-text-info").css(cardStyle||{background:color});
-                    dom.find(".card-logo").html('<svg class="icon" aria-hidden="true"><use xlink:href="#' + iconName + '"></use></svg>');
-                    $(".card-ul").append(dom);
-                }
-
-            } else {
-                mui.toast(res.description);
-            }
-        });
-
-        $(".card-ul").on("tap", "li", function (event) {
-            unBindCardId = $(this).closest("li").attr("data-id");
-        });
-
-        //底部菜单事件
-        mui('.mui-popover-action').on('tap', 'a', function () {
-            mui('#bankCardMenu').popover('toggle');
-            if ($(this).hasClass("cardUnbind")) {
-
-                var btnArray = ['取消', '确定'];
-                mui.confirm('确定解绑银行卡?', '温馨提示', btnArray, function (e) {
-                    if (e.index == 1) {
-                        var data = {"id": unBindCardId};
-                        sendRequest("/agent/bankCardUnbind", "POST", data, function (res) {
-                            if (res.result == 1) {
-                                mui.toast("银行卡解绑成功");
-                                window.location = "wallet-bank.html";
-                            } else {
-                                mui.toast(res.description);
-                            }
-                        });
-                    }
-                });
-            }
-        });
-    });
-
-    function goSet() {
-        var url = "wallet-bank-edit.html";
-        goPage(url);
-    }
-</script>
-</body>
-</html>

+ 3 - 3
static/agents/wallet/wallet-bind-alipay.html

@@ -59,11 +59,11 @@
         }
 
         var data = {
-            realName: $('#loginId').val(),
-            loginId: $('#realName').val(),
+            realName: $('#realName').val(),
+            loginId: $('#loginId').val(),
         };
         sendRequest({
-            url: "/agent/saveAlipayAccount",
+            url: "/common/withdraw/alipay/save",
             type: "POST",
             data: data,
             contentType: "json",

+ 40 - 18
static/agents/wallet/wallet-withdraw.html

@@ -86,6 +86,19 @@
 <script src="/components/custom/js/iconfont.js"></script>
 <script src="/app/js/xyf.common.js"></script>
 <script>
+    (function () {
+        //ios返回刷新
+        var isPageHide = false;
+        window.addEventListener('pageshow', function () {
+            if (isPageHide) {
+                window.location.reload();
+            }
+        });
+        window.addEventListener('pagehide', function () {
+            isPageHide = true;
+        });
+    })();
+
     var sourceType = getQueryString("sourceType");
     var sourceId = getQueryString("sourceId");
     var openId = getQueryString("openId")
@@ -165,7 +178,7 @@
 
     // 没有银行卡的情况去添加银行卡
     function goAddCard() {
-        var url = "wallet-bank.html";
+        var url = "/agent/index.html?#/wallet/banks";
         goPage(url);
     }
 
@@ -232,7 +245,7 @@
                 $("#amount").attr('placeholder', "余额" + payload.balance);
                 balance = payload.balance
 
-                payBankTransFee = payload.payBankTransFee;
+
 
                 withdrawFeeRatio = payload.withdrawFeeRatio;
                 if (withdrawFeeRatio > 6) {
@@ -269,26 +282,35 @@
                     payTypeMenu.append(dom);
                 }
 
+                if (support.bank && support.bank.support) {
+                    payBankTransFee = support.bank.transFee;
 
-                var cardDom = '';
-                if (payload.bankAccount) {
-                    var bankStyle = findBankStyle(payload.bankName);
-                    var iconName = bankStyle.icon;
+                    var cardDom = '';
 
-                    cardDom = $('<li class="mui-table-view-cell" onclick="changePayType(\'bank\',\'' + payload.bankName + '\',\'' + payload.bankAccount + '\')">' +
-                        '<svg class="" aria-hidden="true"><use xlink:href="#' + iconName + '"></use></svg> <span>' + payload.bankName + "(" + payload.bankAccount.substr(-4) + ")</span>" +
-                        '</li>')
-                } else {
-                    cardDom = $('<li class="mui-table-view-cell " onclick="goAddCard()">' +
-                        "<div class='mui-navigate-right '><i class='iconfont icon-bank-card c-blue'></i> <span>添加银行卡</span></div>" +
-                        '</li>')
-                }
+                    var len = support.bank.cards.length;
 
-                if (payBankTransFee) {
-                    $('.bankFeeText').text('转账到银行卡,微信额外收取0.1%手续费,最低0.1元,最高25元。')
-                }
+                    if (len === 0) {
+                        cardDom = ('<li class="mui-table-view-cell " onclick="goAddCard()">' +
+                            "<div class='mui-navigate-right '><i class='iconfont icon-bank-card c-blue'></i> <span>添加银行卡</span></div>" +
+                            '</li>')
+                    } else {
+                        for (var i = 0; i < len; i++) {
+                            var item = support.bank.cards[i]
+                            var bankStyle = findBankStyle(item.bankName);
+                            var iconName = bankStyle.icon;
+
+                            cardDom += ('<li class="mui-table-view-cell" onclick="changePayType(\'bank\',\'' + item.bankName + '\',\'' + item.accountCode + '\')">' +
+                                '<svg class="" aria-hidden="true"><use xlink:href="#' + iconName + '"></use></svg> <span>' + item.bankName + "(" + item.accountCode.substr(-4) + ")</span>" +
+                                '</li>')
+                        }
+                    }
 
-                payTypeMenu.append(cardDom);
+                    if (payBankTransFee) {
+                        $('.bankFeeText').text('转账到银行卡,微信额外收取0.1%手续费,最低0.1元,最高25元。')
+                    }
+
+                    payTypeMenu.append(cardDom);
+                }
             } else {
                 mui.toast(res.description);
             }

+ 7 - 15
static/agents/wallet/wallet.html

@@ -34,7 +34,6 @@
     <p class="wallet-hd-content"><i id="balance">0.00</i>
         <small>元</small>
     </p>
-    <span class="wallet-hd-right">提现</span>
 </header>
 
 <div id="walletMod">
@@ -48,7 +47,7 @@
             </a>
         </li>
         <li class="mui-table-view-cell">
-            <a class="mui-navigate-right" href="wallet-bank.html">
+            <a class="mui-navigate-right" href="/agent/index.html?#/wallet/banks">
                 <i class="iconfont icon-bank-card c-orange"></i>我的银行卡
             </a>
         </li>
@@ -125,14 +124,12 @@
                     renderBalance("ad", "bg-orange", "广告收入余额")
                 }
                 if (payload.insurance) {
-                    renderBalance("ad", "bg-red", "保险收入余额")
+                    renderBalance("insurance", "bg-red", "保险收入余额")
                 }
 
                 // 如果没有明细,则直接渲染总余额
-                if (payload.withdraw || payload.device || payload.traffic || payload.ad) {
+                if (payload.withdraw || payload.device || payload.traffic || payload.ad || payload.insurance) {
                 } else {
-                    // 为了兼容老的提现
-                    $("#balance").html(payload.balance);
                     $("#balanceMod").removeClass('mui-hidden')
                 }
             } else {
@@ -146,16 +143,11 @@
         var sourceType = $(this).attr('sourceType')
         var sourceId = $(this).attr('sourceId')
 
-        var url;
-        var redirect = '/agents/wallet/wallet-withdraw.html';
-        // 如果有多个资金池的,则需要获取openId
-        if (sourceId) {
-            url = '/agent/withdraw/entry?sourceType=' + sourceType + '&sourceId=' + sourceId + '&redirect=' + encodeURIComponent(redirect)
-        } else {
-            url = redirect
+        if (sourceId && sourceType) {
+            var redirect = '/agents/wallet/wallet-withdraw.html';
+            var url = '/agent/withdraw/entry?sourceType=' + sourceType + '&sourceId=' + sourceId + '&redirect=' + encodeURIComponent(redirect)
+            goPage(url);
         }
-
-        goPage(url);
     });
 </script>
 </body>

+ 0 - 498
static/app/js/wallet.js

@@ -1,498 +0,0 @@
-var WalletTool = {
-    initBankEdit: function (role) {
-        $('body').append(`
-<div class="main">
-    <h5 class="padding-10 margin-0 top-tip-style">注意:提现仅支持个人银行卡,不支持对公账户</h5>
-    <div class="mui-content  ">
-        <div class="mui-input-group">
-            <div class="mui-input-row">
-                <label>持卡人</label>
-                <input type="text" id="name" maxlength="20" placeholder="请填写" class="mui-text-right">
-            </div>
-            <div class="mui-input-row">
-                <label>卡号</label>
-                <input type="text" id="bankId" placeholder="请填写" class="mui-text-right"
-                       maxlength="24">
-            </div>
-            <div class="mui-input-row">
-                <label>银行</label>
-                <input type="text" id="bankName" maxlength="32" class="mui-text-right">
-            </div>
-            <div class="mui-input-row">
-                <label>类型</label>
-                <input type="text" id="cardType" value="借记卡" class="mui-text-right noClick" readonly>
-            </div>
-            <div class="mui-input-row">
-                <label>支行</label>
-                <input type="text" id="subBankName" maxlength="32" class="mui-text-right">
-            </div>
-        </div>
-
-        <div class="custom-subt mui-text-center">
-            <a class="c-primary" id="showBankList">
-                支持哪些银行?
-            </a>
-        </div>
-    </div>
-</div>
-<div class="subt">
-    <input class="mui-btn-block" type="button" value="保存" id="save">
-</div>
-<div class="help-tip mui-hidden">
-    <div class="help-card ">
-        <div class="help-bd">
-            <h4>支持的银行</h4>
-
-            <div class="bank-info-list clearfix">
-                <div class="bank-info">
-                    <svg class="icon" aria-hidden="true">
-                        <use xlink:href="#bicon-gongshangyinhang"></use>
-                    </svg>
-                    <span class="bank-name">中国工商银行</span>
-                </div>
-
-                <div class="bank-info">
-                    <svg class="icon" aria-hidden="true">
-                        <use xlink:href="#bicon-nongyeyinhang"></use>
-                    </svg>
-                    <span class="bank-name">中国农业银行</span>
-                </div>
-                <div class="bank-info">
-                    <svg class="icon" aria-hidden="true">
-                        <use xlink:href="#bicon-zhongguoyinhang"></use>
-                    </svg>
-                    <span class="bank-name">中国银行</span>
-                </div>
-                <div class="bank-info">
-                    <svg class="icon" aria-hidden="true">
-                        <use xlink:href="#bicon-jiansheyinhang"></use>
-                    </svg>
-                    <span class="bank-name">中国建设银行</span>
-                </div>
-
-                <div class="bank-info">
-                    <svg class="icon" aria-hidden="true">
-                        <use xlink:href="#bicon-zhaoshangyinhang"></use>
-                    </svg>
-                    <span class="bank-name">招商银行</span>
-                </div>
-
-                <div class="bank-info">
-                    <svg class="icon" aria-hidden="true">
-                        <use xlink:href="#bicon-youchuyinhang"></use>
-                    </svg>
-                    <span class="bank-name">中国邮政储蓄银行</span>
-                </div>
-                <div class="bank-info">
-                    <svg class="icon" aria-hidden="true">
-                        <use xlink:href="#bicon-jiaotongyinhang"></use>
-                    </svg>
-                    <span class="bank-name">交通银行</span>
-                </div>
-
-                <div class="bank-info">
-                    <svg class="icon" aria-hidden="true">
-                        <use xlink:href="#bicon-pufayinhang"></use>
-                    </svg>
-                    <span class="bank-name">浦发银行</span>
-                </div>
-                <div class="bank-info">
-                    <svg class="icon" aria-hidden="true">
-                        <use xlink:href="#bicon-minshengyinhang"></use>
-                    </svg>
-                    <span class="bank-name">中国民生银行</span>
-                </div>
-                <div class="bank-info">
-                    <svg class="icon" aria-hidden="true">
-                        <use xlink:href="#bicon-xingyeyinhang"></use>
-                    </svg>
-                    <span class="bank-name">兴业银行</span>
-                </div>
-                <div class="bank-info">
-                    <svg class="icon" aria-hidden="true">
-                        <use xlink:href="#bicon-pinganyinhang"></use>
-                    </svg>
-                    <span class="bank-name">平安银行</span>
-                </div>
-                <div class="bank-info">
-                    <svg class="icon" aria-hidden="true">
-                        <use xlink:href="#bicon-zhongxinyinhang"></use>
-                    </svg>
-                    <span class="bank-name">中信银行</span>
-                </div>
-                <div class="bank-info">
-                    <svg class="icon" aria-hidden="true">
-                        <use xlink:href="#bicon-huaxiayinhang"></use>
-                    </svg>
-                    <span class="bank-name">华夏银行</span>
-                </div>
-                <div class="bank-info">
-                    <svg class="icon" aria-hidden="true">
-                        <use xlink:href="#bicon-guangfayinhang"></use>
-                    </svg>
-                    <span class="bank-name">广发银行</span>
-                </div>
-                <div class="bank-info">
-                    <svg class="icon" aria-hidden="true">
-                        <use xlink:href="#bicon-guangdayinhang"></use>
-                    </svg>
-                    <span class="bank-name">中国光大银行</span>
-                </div>
-                <div class="bank-info">
-                    <svg class="icon" aria-hidden="true">
-                        <use xlink:href="#bicon-beijingyinhang"></use>
-                    </svg>
-                    <span class="bank-name">北京银行</span>
-                </div>
-                <div class="bank-info">
-                    <svg class="icon" aria-hidden="true">
-                        <use xlink:href="#bicon-ningboyinhang"></use>
-                    </svg>
-                    <span class="bank-name">宁波银行</span>
-                </div>
-                
-                <div class="bank-info">
-                    <svg class="icon" aria-hidden="true">
-                        <use xlink:href="#bicon-shanghaiyinhang"></use>
-                    </svg>
-                    <span class="bank-name">上海银行</span>
-                </div>
-                
-                <div class="bank-info">
-                    <svg class="icon" aria-hidden="true">
-                        <use xlink:href="#bicon-nanjingyinhang"></use>
-                    </svg>
-                    <span class="bank-name">南京银行</span>
-                </div>
-                
-                <div class="bank-info">
-                    <svg class="icon" aria-hidden="true">
-                        <use xlink:href="#bicon-yinhang"></use>
-                    </svg>
-                    <span class="bank-name">长子县融汇村镇银行</span>
-                </div>
-                
-                <div class="bank-info">
-                    <svg class="icon" aria-hidden="true">
-                        <use xlink:href="#bicon-changsha"></use>
-                    </svg>
-                    <span class="bank-name">长沙银行</span>
-                </div>
-                
-                <div class="bank-info">
-                    <svg class="icon" aria-hidden="true">
-                        <use xlink:href="#bicon-zhejiangtailongshangyeyinhang"></use>
-                    </svg>
-                    <span class="bank-name">浙江泰隆商业银行</span>
-                </div>
-                
-                <div class="bank-info">
-                    <svg class="icon" aria-hidden="true">
-                        <use xlink:href="#bicon-zhongyuanyinhang"></use>
-                    </svg>
-                    <span class="bank-name">中原银行</span>
-                </div>
-                
-                <div class="bank-info">
-                    <svg class="icon" aria-hidden="true">
-                        <use xlink:href="#bicon-qiyeyinhanglogo"></use>
-                    </svg>
-                    <span class="bank-name">企业银行</span>
-                </div>
-                
-                <div class="bank-info">
-                    <svg class="icon" aria-hidden="true">
-                        <use xlink:href="#bicon-shundenongshangyinhang"></use>
-                    </svg>
-                    <span class="bank-name">顺德农商银行</span>
-                </div>
-                
-                <div class="bank-info">
-                    <svg class="icon" aria-hidden="true">
-                        <use xlink:href="#bicon-hengshuiyinhang"></use>
-                    </svg>
-                    <span class="bank-name">衡水市商业银行</span>
-                </div>
-                
-                <div class="bank-info">
-                    <svg class="icon" aria-hidden="true">
-                        <use xlink:href="#bicon-changzhiyinhanglogo"></use>
-                    </svg>
-                    <span class="bank-name">长治市商业银行</span>
-                </div>
-                
-                <div class="bank-info">
-                    <svg class="icon" aria-hidden="true">
-                        <use xlink:href="#bicon-datongyinhanglogo"></use>
-                    </svg>
-                    <span class="bank-name">大同市商业银行</span>
-                </div>
-                
-                <div class="bank-info">
-                    <svg class="icon" aria-hidden="true">
-                        <use xlink:href="#bicon-henanshengnongcunxinyongshelianheshe"></use>
-                    </svg>
-                    <span class="bank-name">河南省农村信用社</span>
-                </div>
-                
-                <div class="bank-info">
-                    <svg class="icon" aria-hidden="true">
-                        <use xlink:href="#bicon-ningxiahuanghenongcunshangyeyinhang"></use>
-                    </svg>
-                    <span class="bank-name">宁夏黄河农村商业银行</span>
-                </div>
-                
-                <div class="bank-info">
-                    <svg class="icon" aria-hidden="true">
-                        <use xlink:href="#bicon-shanxishengnongcunxinyongshe"></use>
-                    </svg>
-                    <span class="bank-name">山西省农村信用社</span>
-                </div>
-                
-                <div class="bank-info">
-                    <svg class="icon" aria-hidden="true">
-                        <use xlink:href="#bicon-anhuishengnongcunxinyongshe"></use>
-                    </svg>
-                    <span class="bank-name">安徽省农村信用社</span>
-                </div>
-                
-                <div class="bank-info">
-                    <svg class="icon" aria-hidden="true">
-                        <use xlink:href="#bicon-gansushengnongcunxinyongshelianheshe"></use>
-                    </svg>
-                    <span class="bank-name">甘肃省农村信用社</span>
-                </div>
-                
-                <div class="bank-info">
-                    <svg class="icon" aria-hidden="true">
-                        <use xlink:href="#bicon-tianjinnongshangyinhang"></use>
-                    </svg>
-                    <span class="bank-name">天津农商银行</span>
-                </div>
-                
-                <div class="bank-info">
-                    <svg class="icon" aria-hidden="true">
-                        <use xlink:href="#bicon-guangxizhuangzuzizhiqunongcunxinyongshelianheshe"></use>
-                    </svg>
-                    <span class="bank-name">广西壮族自治区农村信用社联合社</span>
-                </div>
-                
-                <div class="bank-info">
-                    <svg class="icon" aria-hidden="true">
-                        <use xlink:href="#bicon-shanxishengnongcunxinyongshelianheshe"></use>
-                    </svg>
-                    <span class="bank-name">陕西省农信社</span>
-                </div>
-                
-                <div class="bank-info">
-                    <svg class="icon" aria-hidden="true">
-                        <use xlink:href="#bicon-shenzhennongcunshangyeyinhang"></use>
-                    </svg>
-                    <span class="bank-name">深圳农村商业银行</span>
-                </div>
-                
-                <div class="bank-info">
-                    <svg class="icon" aria-hidden="true">
-                        <use xlink:href="#bicon-yinzhouyinhang"></use>
-                    </svg>
-                    <span class="bank-name">宁波鄞州农商行</span>
-                </div>
-                
-                <div class="bank-info">
-                    <svg class="icon" aria-hidden="true">
-                        <use xlink:href="#bicon-zhejiangshengnongcunxinyongshelianheshe"></use>
-                    </svg>
-                    <span class="bank-name">浙江省农村信用社联合社</span>
-                </div>
-                
-                <div class="bank-info">
-                    <svg class="icon" aria-hidden="true">
-                        <use xlink:href="#bicon-jiangsushengnongcunxinyongshelianheshe"></use>
-                    </svg>
-                    <span class="bank-name">江苏省农村信用社联合社</span>
-                </div>
-                
-                <div class="bank-info">
-                    <svg class="icon" aria-hidden="true">
-                        <use xlink:href="#bicon-jiangsuzijinnongcunshangyeyinhang"></use>
-                    </svg>
-                    <span class="bank-name">江苏紫金农村商业银行</span>
-                </div>
-                
-                <div class="bank-info">
-                    <svg class="icon" aria-hidden="true">
-                        <use xlink:href="#bicon-yinhang"></use>
-                    </svg>
-                    <span class="bank-name">北京中关村银行</span>
-                </div>
-                
-                <div class="bank-info">
-                    <svg class="icon" aria-hidden="true">
-                        <use xlink:href="#bicon-xingzhanyinhang"></use>
-                    </svg>
-                    <span class="bank-name">星展银行(中国)</span>
-                </div>
-                
-                <div class="bank-info">
-                    <svg class="icon" aria-hidden="true">
-                        <use xlink:href="#bicon-zaozhuangyinhang"></use>
-                    </svg>
-                    <span class="bank-name">枣庄银行</span>
-                </div>
-                
-                <div class="bank-info">
-                    <svg class="icon" aria-hidden="true">
-                        <use xlink:href="#bicon-haikoulianhenongshangyinhang"></use>
-                    </svg>
-                    <span class="bank-name">海口联合农商银行</span>
-                </div>
-                
-                <div class="bank-info">
-                    <svg class="icon" aria-hidden="true">
-                        <use xlink:href="#bicon-nanyangshangyeyinhang"></use>
-                    </svg>
-                    <span class="bank-name">南洋商业银行</span>
-                </div>
-            </div>
-        </div>
-        <div class="help-close">我知道了</div>
-    </div>
-</div>`)
-
-        $('#bankId')[0].addEventListener('input', function () {
-            var value = $("#bankId").val().replace(/\s/g, '').replace(/(\d{4})(?=\d)/g, "$1 ");
-            $("#bankId").val(value);
-        });
-        $('#bankId').change(function () {
-            var value = $("#bankId").val().replace(/ /g, "");
-            if (value.length >= 14) {
-                var url = "/common/getCardBankNameType";
-                var data = {
-                    "cardNo": value
-                };
-                sendRequest(url, "GET", data, function (res) {
-                    if (res.result == 1) {
-                        if (res.para) {
-                            $("#bankName").val(res.para.bankName);
-                            $("#cardType").val(res.para.cardType);
-                        }
-                        $("#cardType").addClass("noClick");
-                    } else if (res.result == -1) {
-                        mui.toast(res.description);
-                        $("#cardType").removeClass("noClick");
-                    } else {
-                        mui.toast(res.description);
-                        $("#cardType").removeClass("noClick");
-                    }
-
-                });
-            }
-        });
-
-        $(".help-close").click(function () {
-            $(".help-tip").addClass("mui-hidden");
-        })
-
-        $("#showBankList").click(function () {
-            showBankList()
-        })
-        $("#save").click(function () {
-            save()
-        })
-
-        var cardTypePicker;
-        mui.ready(function () {
-            cardTypePicker = new mui.PopPicker();
-            //地址类型
-            cardTypePicker.setData([{
-                value: '储蓄卡',
-                text: '储蓄卡'
-            }, {
-                value: '信用卡',
-                text: '信用卡'
-            }]);
-
-            var bankType = document.getElementById('cardType');
-
-            function handler(event) {
-                cardTypePicker.show(function (items) {
-                    bankType.value = (items[0] || {}).text;
-                });
-            }
-
-            bankType.addEventListener('tap', handler, false);
-        });
-
-        var saveFlag = false;
-
-        function save() {
-            var name = $("#name").val();
-            var bankId = $("#bankId").val().replace(/ /g, "");
-            var bankName = $("#bankName").val();
-            var cardType = $("#cardType").val();
-            var subBankName = $("#subBankName").val();
-            if (!name) {
-                mui.toast("请填写持卡人名称");
-                return;
-            }
-            if (!bankId) {
-                mui.toast("请填写卡号");
-                return;
-            }
-
-            if (!bankName) {
-                mui.toast("请填写银行名称");
-                return;
-            }
-
-            var bankNameCheck = false;
-            for (var key in  window.BANK_STYLE_MAP) {
-                if (key === bankName) {
-                    bankNameCheck = true;
-                }
-            }
-
-            if (!bankNameCheck) {
-                mui.toast("仅支持下列银行");
-                showBankList()
-                return;
-            }
-
-            if (!cardType) {
-                mui.toast("请选择银行卡类型");
-                return;
-            }
-            if (!subBankName) {
-                mui.toast("请填写支行名称,填写错误将可能导致提现失败");
-                return;
-            }
-
-            if (!saveFlag) {
-                saveFlag = true;
-                var url = "/" + role + "/saveWalletBank";
-                var data = {
-                    "name": name,
-                    "bankId": bankId,
-                    "bankName": bankName,
-                    "cardType": cardType,
-                    "subBankName": subBankName,
-                };
-                sendRequest(url, "POST", data, function (res) {
-                    if (res.result == 1) {
-                        var url = "wallet-bank.html";
-                        goPage(url);
-                    } else {
-                        saveFlag = false;
-                        mui.toast(res.description);
-                    }
-                });
-            }
-        }
-
-        //展示银行列表
-        function showBankList() {
-            $(".help-tip").removeClass("mui-hidden");
-        }
-
-    }
-}

+ 17 - 11
static/app/wallet/new-info.html

@@ -40,19 +40,25 @@
             mui.toast("请输入真实姓名,不小于2位,不大于20位");
             return;
         }
-        var url = "/dealer/updateInfo";
+        
         var data = {
-            "name": name
+            "realName": name
         };
-        sendRequest(url, "POST", data, function (res) {
-            if (res.result == 1) {
-                mui.confirm('修改成功', '提示', ['返回'], function (e) {
-                    if (e) {
-                        history.back()
-                    }
-                });
-            } else {
-                mui.alert(res.description, '温馨提示', '我知道了');
+        sendRequest({
+            url: "/common/withdraw/wechat/save",
+            type: "POST",
+            data: data,
+            contentType: "json",
+            success: function (res) {
+                if (res.result === 1) {
+                    mui.confirm('修改成功', '提示', ['返回'], function (e) {
+                        if (e) {
+                            history.back()
+                        }
+                    });
+                } else {
+                    mui.alert(res.description, '温馨提示', '我知道了');
+                }
             }
         });
     }

+ 1 - 1
static/app/wallet/wallet-approval.html

@@ -11,7 +11,7 @@
     <meta http-equiv="pragma" content="no-cache">
     <meta http-equiv="cache-control" content="no-cache">
     <meta http-equiv="expires" content="0">
-    <title>提现</title>
+    <title>提现审批配置</title>
     <link rel="stylesheet" href="https://cdn.washpayer.com/components/lib/mui.min.css"/>
     <link rel="stylesheet" href="/components/custom/css/common.css"/>
     <link rel="stylesheet" href="/app/css/xyf.common.min.css">

+ 28 - 18
static/app/wallet/wallet-auto.html

@@ -11,7 +11,7 @@
     <meta http-equiv="pragma" content="no-cache">
     <meta http-equiv="cache-control" content="no-cache">
     <meta http-equiv="expires" content="0">
-    <title>提现</title>
+    <title>自动提现配置</title>
     <link rel="stylesheet" href="https://cdn.washpayer.com/components/lib/mui.min.css"/>
     <link rel="stylesheet" href="/components/custom/css/common.css"/>
     <link rel="stylesheet" href="/app/css/xyf.common.min.css">
@@ -171,7 +171,7 @@
 
     // 没有银行卡的情况去添加银行卡
     function goAddCard() {
-        var url = "wallet-bank.html";
+        var url = "/dealer/index.html?#/wallet/banks";
         goPage(url);
     }
 
@@ -225,10 +225,11 @@
         sendRequest(url, "GET", {}, function (res) {
             if (res.result == 1) {
                 var payload = res.payload
+                var cards = payload.cards
 
                 // 初始化参数
                 nowWeekDay = payload.weekDay || 1 // 默认星期1
-                nowBankAccount = payload.bankAccount
+                nowBankAccount = payload.accountCode
                 nowPayType = payload.autoWithdrawType || 'wechat' // 默认微信
 
                 autoWithdrawBankFee = payload.autoWithdrawBankFee // 是否收取银行转账手续费
@@ -257,29 +258,38 @@
 
                 payTypeMenu.append(wechatDom);
                 var cardDom = '';
-                if (nowBankAccount) {
-                    var bankStyle = findBankStyle(payload.bankName);
-                    var iconName = bankStyle.icon;
-
-                    cardDom = $('<li class="mui-table-view-cell" onclick="changePayType(\'bank\',\'' + payload.bankName + '\',\'' + nowBankAccount + '\')">' +
-                        '<svg class="" aria-hidden="true"><use xlink:href="#' + iconName + '"></use></svg> <span>' + payload.bankName + "(" + nowBankAccount.substr(-4) + ")</span>" +
-                        '</li>')
-                    // 如果是银行卡自动提现的方式,则展示银行
-                    if (nowPayType === 'bank') {
-                        $("#payType").html(cardDom.html())
+                if (cards && cards.length) {
+                    for(var index in cards) {
+                        var item = cards[index]
+                        var bankStyle = findBankStyle(item.bankName);
+                        var iconName = bankStyle.icon;
+                        cardDom = $('<li class="mui-table-view-cell" onclick="changePayType(\'bank\',\'' + item.bankName + '\',\'' + item.accountCode + '\')">' +
+                            '<svg class="" aria-hidden="true"><use xlink:href="#' + iconName + '"></use></svg> <span>' + item.bankName + "(" + item.accountCode.substr(-4) + ")</span>" +
+                            '</li>')
+                        payTypeMenu.append(cardDom);
                     }
 
                 } else {
                     cardDom = $('<li class="mui-table-view-cell " onclick="goAddCard()">' +
                         "<div class='mui-navigate-right '><i class='iconfont icon-bank-card c-blue'></i> <span>添加银行卡</span></div>" +
                         '</li>')
+                    payTypeMenu.append(cardDom);
+                }
+
+                // $("#payType").html(cardDom.html())
+                // 如果是银行卡自动提现的方式,则展示银行
+                if (nowPayType === 'bank' && nowBankAccount) {
+                    var bankStyle = findBankStyle(payload.bankName);
+                    var iconName = bankStyle.icon;
+                    cardDom = $('<li class="mui-table-view-cell" onclick="changePayType(\'bank\',\'' + payload.bankName + '\',\'' + nowBankAccount + '\')">' +
+                        '<svg class="" aria-hidden="true"><use xlink:href="#' + iconName + '"></use></svg> <span>' + payload.bankName + "(" + nowBankAccount.substr(-4) + ")</span>" +
+                        '</li>')
+                    $("#payType").html(cardDom.html())
                 }
 
                 if (autoWithdrawBankFee) {
                     $('.bankFeeText').text('微信额外收取0.1%转账到银行卡手续费,最低0.1元,最高25元;')
                 }
-
-                payTypeMenu.append(cardDom);
             } else {
                 mui.toast(res.description);
             }
@@ -320,11 +330,11 @@
                 };
                 var url = "/dealer/saveAutoWithDrawConfig";
 
-                myAjax({
-                    type: "POST",
+                sendRequest({
                     url: url,
+                    type: "POST",
                     data: data,
-                    dataType: "json",
+                    contentType: "json",
                     success: function (res) {
                         if (res.result == 0) {
                             mui.toast(res.description);

+ 0 - 38
static/app/wallet/wallet-bank-edit.html

@@ -1,38 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-    <meta charset="utf-8">
-    <meta name="author" content="">
-    <meta name="description" content=""/>
-    <meta name="keywords" content="扫码支付,线上投币,运营数据,物联网"/>
-    <meta name="format-detection" content="telephone=no,email=no">
-    <meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no">
-    <meta http-equiv="pragma" content="no-cache">
-    <meta http-equiv="cache-control" content="no-cache">
-    <meta http-equiv="expires" content="0">
-    <title>银行卡信息</title>
-    <link rel="stylesheet" href="https://cdn.washpayer.com/components/lib/mui.min.css">
-    <link rel="stylesheet" href="https://cdn.washpayer.com/components/lib/mui.picker.min.css"/>
-    <link rel="stylesheet" href="/components/custom/css/common.css">
-    <link rel="stylesheet" href="/app/css/xyf.common.min.css">
-</head>
-<style>
-    .mui-input-group .mui-input-row label ~ input {
-        color: #000;
-    }
-</style>
-
-<body>
-
-<script src="https://cdn.washpayer.com/components/lib/jquery.min.js"></script>
-<script src="https://cdn.washpayer.com/components/lib/mui.min.js"></script>
-<script src="https://cdn.washpayer.com/components/lib/mui.picker.min.js"></script>
-<script src="/components/custom/js/common.js"></script>
-<script src="/components/custom/js/iconfont.js"></script>
-<script src="/app/js/xyf.common.js"></script>
-<script src="/app/js/wallet.js"></script>
-<script>
-    WalletTool.initBankEdit('dealer')
-</script>
-</body>
-</html>

+ 0 - 135
static/app/wallet/wallet-bank.html

@@ -1,135 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-    <meta charset="utf-8">
-    <meta name="author" content="">
-    <meta name="description" content=""/>
-    <meta name="keywords" content="扫码支付,线上投币,运营数据,物联网"/>
-    <meta name="format-detection" content="telephone=no,email=no">
-    <meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no">
-    <meta http-equiv="pragma" content="no-cache">
-    <meta http-equiv="cache-control" content="no-cache">
-    <meta http-equiv="expires" content="0">
-    <title>银行卡</title>
-    <link rel="stylesheet" href="https://cdn.washpayer.com/components/lib/mui.min.css">
-    <link rel="stylesheet" href="/components/custom/css/common.css">
-    <link rel="stylesheet" href="/app/css/xyf.common.min.css">
-</head>
-<style>
-    .main {
-        background: #2E3132;
-        min-height: 100%;
-        padding-bottom: 30px;
-    }
-
-    html, body {
-        height: 100%;
-    }
-</style>
-<body>
-
-<div class="main">
-    <ul class="card-ul">
-    </ul>
-
-    <div class="mui-navigate-right iconfont icon-add-more add-card-btn" onclick="goSet()"> 添加银行卡</div>
-</div>
-
-<div id="bankCardMenu" class="mui-popover mui-popover-action mui-popover-bottom">
-    <ul class="mui-table-view">
-        <li class="mui-table-view-cell">
-            <a href="javascript:;" class="cardUnbind">解绑银行卡</a>
-        </li>
-    </ul>
-    <ul class="mui-table-view">
-        <li class="mui-table-view-cell">
-            <a class="font-bold">取消</a>
-        </li>
-    </ul>
-</div>
-
-<script type="text/html" id="model">
-    <li data-id=''>
-        <i class="card-logo-back bank-logo-back"> </i>
-        <a class="card-text-info" href="#bankCardMenu">
-            <div class="card-logo">
-            </div>
-            <div class="name"></div>
-            <div class="type"></div>
-            <div class="card-id"><span>**** **** **** </span><em class="end-num"></em></div>
-        </a>
-    </li>
-</script>
-<script src="https://cdn.washpayer.com/components/lib/jquery.min.js"></script>
-<script src="https://cdn.washpayer.com/components/lib/mui.min.js"></script>
-<script src="/components/custom/js/common.js"></script>
-<script src="/components/custom/js/iconfont.js"></script>
-<script src="/app/js/xyf.common.js"></script>
-<script>
-    var unBindCardId;
-    $(function () {
-        var url = "/dealer/getWalletBank";
-        sendRequest(url, 'GET', null, function (res) {
-            if (res.result == 1) {
-                if (res.para.dataList && res.para.dataList.length > 0) {
-                    $(".add-card-btn").hide();
-                }
-
-                for (var i = 0; i < res.para.dataList.length; i++) {
-                    var item = res.para.dataList[i];
-                    var dom = $($('#model').html());
-                    dom.attr("data-id", item.id);
-                    dom.find(".name").text(item.bankName);
-                    dom.find(".type").text(item.bankType);
-                    dom.find(".end-num").text(item.endNum);
-
-                    var bankStyle = findBankStyle(item.bankName);
-                    var iconName = bankStyle.icon;
-                    var color = bankStyle.color;
-                    var cardStyle = bankStyle.cardStyle;
-
-                    dom.find(".card-logo-back").html('<svg class="icon" aria-hidden="true"><use xlink:href="#' + iconName + '"></use></svg>');
-                    dom.find(".card-text-info").css(cardStyle||{background:color});
-                    dom.find(".card-logo").html('<svg class="icon" aria-hidden="true"><use xlink:href="#' + iconName + '"></use></svg>');
-                    $(".card-ul").append(dom);
-                }
-
-            } else {
-                mui.toast(res.description);
-            }
-        });
-
-        $(".card-ul").on("tap", "li", function (event) {
-            unBindCardId = $(this).closest("li").attr("data-id");
-        });
-
-        //底部菜单事件
-        mui('.mui-popover-action').on('tap', 'a', function () {
-            mui('#bankCardMenu').popover('toggle');
-            if ($(this).hasClass("cardUnbind")) {
-
-                var btnArray = ['取消', '确定'];
-                mui.confirm('确定解绑银行卡?', '温馨提示', btnArray, function (e) {
-                    if (e.index == 1) {
-                        var data = {"id": unBindCardId};
-                        sendRequest("/dealer/bankCardUnbind", "POST", data, function (res) {
-                            if (res.result == 1) {
-                                mui.toast("银行卡解绑成功");
-                                window.location = "wallet-bank.html";
-                            } else {
-                                mui.toast(res.description);
-                            }
-                        });
-                    }
-                });
-            }
-        });
-    });
-
-    function goSet() {
-        var url = "wallet-bank-edit.html";
-        goPage(url);
-    }
-</script>
-</body>
-</html>

+ 3 - 3
static/app/wallet/wallet-bind-alipay.html

@@ -59,11 +59,11 @@
         }
 
         var data = {
-            realName: $('#loginId').val(),
-            loginId: $('#realName').val(),
+            realName: $('#realName').val(),
+            loginId: $('#loginId').val(),
         };
         sendRequest({
-            url: "/dealer/saveAlipayAccount",
+            url: "/common/withdraw/alipay/save",
             type: "POST",
             data: data,
             contentType: "json",

+ 1 - 1
static/app/wallet/wallet-config.html

@@ -10,7 +10,7 @@
     <meta http-equiv="pragma" content="no-cache">
     <meta http-equiv="cache-control" content="no-cache">
     <meta http-equiv="expires" content="0">
-    <title>钱包</title>
+    <title>提现配置</title>
     <link rel="stylesheet" href="https://cdn.washpayer.com/components/lib/mui.min.css">
     <link rel="stylesheet" href="/components/custom/css/common.css">
     <link rel="stylesheet" href="/app/css/xyf.common.min.css">

+ 35 - 22
static/app/wallet/wallet-withdraw.html

@@ -86,6 +86,19 @@
 <script src="/components/custom/js/iconfont.js"></script>
 <script src="/app/js/xyf.common.js"></script>
 <script>
+    (function () {
+        //ios返回刷新
+        var isPageHide = false;
+        window.addEventListener('pageshow', function () {
+            if (isPageHide) {
+                window.location.reload();
+            }
+        });
+        window.addEventListener('pagehide', function () {
+            isPageHide = true;
+        });
+    })();
+
     var sourceType = getQueryString("sourceType");
     var sourceId = getQueryString("sourceId");
     var openId = getQueryString("openId")
@@ -228,8 +241,6 @@
                 $("#amount").attr('placeholder', "余额" + payload.balance);
                 balance = payload.balance
 
-                payBankTransFee = payload.payBankTransFee;
-
                 withdrawFeeRatio = payload.withdrawFeeRatio;
 
                 if (withdrawFeeRatio > 6) {
@@ -254,7 +265,6 @@
                 var wechatDom = '<li class="mui-table-view-cell" onclick="changePayType(\'wechat\')">' + wechatText + weInfo + '</li>';
                 payTypeMenu.append(wechatDom);
 
-
                 // ---------如果支持支付宝,则显示支付宝--------------
                 if (support.alipay && support.alipay.support) {
                     var ali = support.alipay
@@ -266,31 +276,34 @@
                     payTypeMenu.append(dom);
                 }
 
+                if (support.bank && support.bank.support) {
+                    payBankTransFee = support.bank.transFee;
 
-                var cardDom = '';
+                    var cardDom = '';
 
-                var len = payload.merchants.length;
-                if (len === 0) {
-                    cardDom = ('<li class="mui-table-view-cell " onclick="goAddCard()">' +
-                        "<div class='mui-navigate-right '><i class='iconfont icon-bank-card c-blue'></i> <span>添加银行卡</span></div>" +
-                        '</li>')
-                } else {
-                    for (var i=0; i<len; i++) {
-                        var item = payload.merchants[i]
-                        var bankStyle = findBankStyle(item.bankName);
-                        var iconName = bankStyle.icon;
-
-                        cardDom += ('<li class="mui-table-view-cell" onclick="changePayType(\'bank\',\'' + item.bankName + '\',\'' + item.bankAccount + '\')">' +
-                            '<svg class="" aria-hidden="true"><use xlink:href="#' + iconName + '"></use></svg> <span>' + item.bankName + "(" + item.bankAccount.substr(-4) + ")</span>" +
+                    var len = support.bank.cards.length;
+                    if (len === 0) {
+                        cardDom = ('<li class="mui-table-view-cell " onclick="goAddCard()">' +
+                            "<div class='mui-navigate-right '><i class='iconfont icon-bank-card c-blue'></i> <span>添加银行卡</span></div>" +
                             '</li>')
+                    } else {
+                        for (var i = 0; i < len; i++) {
+                            var item = support.bank.cards[i]
+                            var bankStyle = findBankStyle(item.bankName);
+                            var iconName = bankStyle.icon;
+
+                            cardDom += ('<li class="mui-table-view-cell" onclick="changePayType(\'bank\',\'' + item.bankName + '\',\'' + item.accountCode + '\')">' +
+                                '<svg class="" aria-hidden="true"><use xlink:href="#' + iconName + '"></use></svg> <span>' + item.bankName + "(" + item.accountCode.substr(-4) + ")</span>" +
+                                '</li>')
+                        }
                     }
-                }
 
-                if (payBankTransFee) {
-                    $('.bankFeeText').text('微信额外收取0.1%转账银行卡手续费,最低0.1元,最高25元。')
-                }
+                    if (payBankTransFee) {
+                        $('.bankFeeText').text('微信额外收取0.1%转账银行卡手续费,最低0.1元,最高25元。')
+                    }
 
-                payTypeMenu.append($(cardDom));
+                    payTypeMenu.append($(cardDom));
+                }
             } else {
                 mui.toast(res.description);
             }

+ 29 - 16
static/app/wallet/wallet.html

@@ -36,7 +36,6 @@
     <p class="wallet-hd-content"><i id="balance">0.00</i>
         <small>元</small>
     </p>
-    <span class="wallet-hd-right">提现</span>
 </header>
 
 <div id="walletMod">
@@ -44,14 +43,22 @@
 
 <main class="margin-t-b-10">
     <ul class="mui-table-view navigate-after account-bd">
+        <li class="mui-table-view-cell">
+            <a class="mui-navigate-right" href="/dealer/index.html#/merchant/settle">
+                <i class="iconfont icon-formfill c-green"></i>结算记录
+            </a>
+        </li>
         <li class="mui-table-view-cell">
             <a class="mui-navigate-right" href="wallet-transactions.html">
                 <i class="iconfont icon-formfill c-blue"></i>提现记录
             </a>
         </li>
+    </ul>
+
+    <ul class="mui-table-view navigate-after account-bd custom-top">
         <li class="mui-table-view-cell">
             <a class="mui-navigate-right" href="wallet-config.html">
-                <i class="iconfont icon-bank-card c-green"></i>提现配置
+                <i class="iconfont icon-bank-card c-orange"></i>提现配置
             </a>
         </li>
         <li class="mui-table-view-cell">
@@ -94,6 +101,7 @@
     }
 
     var walletData = null
+    var merchantTip = false
     //获取账号余额相关信息
     var urlBalance = "/dealer/walletData?random=" + Math.random() * 1000;
     sendRequest({
@@ -103,21 +111,17 @@
         success: function (res) {
             if (res.result == 1) {
                 var payload = walletData = res.payload;
+                merchantTip = payload.merchantTip;
                 if (payload.device) {
                     renderBalance("device", "bg-primary", "设备营收余额")
                 }
                 if (payload.ad) {
                     renderBalance("ad", "bg-orange", "广告收入余额")
                 }
-                if (payload.ledger) {
-                    renderBalance("ledger", "bg-green", "分润余额")
-                }
 
                 // 如果没有明细,则直接渲染总余额
-                if (payload.device || payload.ad || payload.ledger) {
+                if (payload.device || payload.ad) {
                 } else {
-                    // 为了兼容老的提现
-                    $("#balance").html(payload.balance);
                     $("#balanceMod").removeClass('mui-hidden')
                 }
             } else {
@@ -128,19 +132,28 @@
     })
 
     $("body").on('tap', '.wallet-hd', function (e) {
+        var item = merchantTip
+        if (merchantTip && merchantTip.force) {
+            mui.confirm(item.content, item.title, ["去处理", "我知道了"], function (e) {
+                if (e.index === 0) {
+                    // 避免ios的弹窗不关闭
+                    setTimeout(function () {
+                        location.href = item.link
+                    }, 1)
+                } else {
+                }
+            });
+            return
+        }
+
         var sourceType = $(this).attr('sourceType')
         var sourceId = $(this).attr('sourceId')
 
-        var url;
-        var redirect = '/app/wallet/wallet-withdraw.html';
-        // 如果有多个资金池的,则需要获取openId
         if (sourceId) {
-            url = '/dealer/withdraw/entry?sourceType=' + sourceType + '&sourceId=' + sourceId + '&redirect=' + encodeURIComponent(redirect)
-        } else {
-            url = redirect
+            var redirect = '/app/wallet/wallet-withdraw.html';
+            var url = '/dealer/withdraw/entry?sourceType=' + sourceType + '&sourceId=' + sourceId + '&redirect=' + encodeURIComponent(redirect)
+            goPage(url);
         }
-
-        goPage(url);
     });
 </script>
 </body>