# -*- 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 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 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.payment import WithdrawGateway 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, WeChatException 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.type_checking import WithdrawGatewayT logger = logging.getLogger(__name__) def withdraw_lock_key_fn(role_type, role_id): # type: (str, str)->str return '{role_type}-{role_id}-withdraw-lock'.format(role_type = role_type, role_id = role_id) def withdraw_lock(type_, id_, key_fn = withdraw_lock_key_fn, value = 1, timeout = 100): # type: (str, str, Callable, int, int)->GeneratorContextManager return memcache_lock(key_fn(type_, id_), value, timeout) 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': return WithdrawResult( False, 0, False, remarks, 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_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)") @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(False, 0, False, remarks, u"系统异常,无法获取转账状态。平台会每日定时重试,如果3个工作日仍然是失败状态,请联系客服确认结果(1002)") @classmethod def withdraw_via_bank_in_ali(cls, gateway, record, bankcard): # type: (AliPayWithdrawGateway, WithdrawRecord, WithdrawBankCard)->WithdrawResult 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 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, WithdrawBankCard)->WithdrawResult def error_handler(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 ['SYSTEMERROR', 'SEND_FAILED']: return WithdrawResult( False, 0, False, remarks, u"商户平台系统错误,无法获取转账状态。后台会每日定时重试,如果3个工作日仍然是失败状态,请联系客服确认结果(1002)") elif err_code == 'NOTENOUGH': from taskmanager.mediator import task_caller task_caller('withdraw_error_alert', err_type = 'NOTENOUGH') return WithdrawResult(False, 0, True, remarks, u"商户平台系统繁忙,请过一小时后再试。验证码每天次数有限,频繁提交提现可能消耗完当天验证码次数(1002)") elif err_code in ['AMOUNT_LIMIT', 'SENDNUM_LIMIT', 'INVALID_REQUEST', 'PARAM_ERROR', 'SIGNERROR', 'ORDERPAID', 'FATAL_ERROR', 'FREQUENCY_LIMITED', 'RECV_ACCOUNT_NOT_ALLOWED', 'PAY_CHANNEL_NOT_ALLOWED', 'NO_AUTH', 'FREQ_LIMIT', 'OPENID_ERROR', 'NOTENOUGH']: return WithdrawResult(False, 0, True, remarks, err_code_des) else: return WithdrawResult( False, 0, False, remarks, u"系统异常,无法获取转账状态。平台会每日定时重试,如果3个工作日仍然是失败状态,请联系客服确认结果(1003)") # 调用付款到银行接口 try: gateway.withdraw_via_bank(order_no = record.order, total_amount = record.actualPay, bank_card = bankcard) return WithdrawResult(True, 1, False, u'提现申请微信已经受理', u'提现申请微信已经受理') except WechatNetworkException as e: logger.error(repr(e)) if e.errMsg: return WithdrawResult(False, 0, False, e.errMsg, e.errMsg) else: return WithdrawResult(False, 0, False, u'微信通讯错误', u'微信通讯错误,请联系客服确认提现是否成功。') except WeChatException as e: # 接口性质的失败 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, WithdrawBankCard)->(basestring, WithdrawResult) if isinstance(gateway, AliPayWithdrawGateway): 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) raise Exception(u'不支持的提现网关类型') def withdraw_via_wechat(gateway, record, payOpenId, real_user_name): # type:(WechatWithdrawGateway, WithdrawRecord, str, str)->WithdrawResult """ 微信支付到个人微信 :param gateway: :param record: :param payOpenId: :param real_user_name: :return: """ def withdraw_via_v1(gateway, record, payOpenId, real_user_name): def error_handler(err_code, err_code_des): # type:(str, str)->(bool, bool, str, str) """ SYSTEMERROR, SEND_FAILED: 不确定是否成功,这个必须必须设置为错误,联系微信确认订单是否成功 目前已经在微信中明确的错误码可以自动退款,没有明确的其他错误码必须联系微信确认 """ remarks = u'{err_code_des}({err_code})'.format(err_code = err_code, err_code_des = err_code_des) if err_code in ['SYSTEMERROR', 'SEND_FAILED']: # 该情况不能确认是否成功,需要平台侧查找订单确认 return WithdrawResult( False, 0, False, remarks, u"商户平台系统错误,无法获取转账状态。后台会每日定时重试,如果3个工作日仍然是失败状态,请联系客服确认结果(1003)") elif err_code == 'NOTENOUGH': from taskmanager.mediator import task_caller task_caller('withdraw_error_alert', err_type = 'NOTENOUGH') return WithdrawResult(False, 0, True, remarks, u"商户平台系统繁忙,请过一小时后再试。验证码每天次数有限,频繁提交提现可能消耗完当天验证码次数(1003)") elif err_code == 'NAME_MISMATCH': return WithdrawResult(False, 2, True, remarks, u"实名校验失败,请确保经销商名字与账户登录微信一致后重试。") elif err_code == 'V2_ACCOUNT_SIMPLE_BAN': return WithdrawResult(False, 4, True, remarks, u'您的微信尚未实名认证,请去微信绑定银行卡或身份证完成实名认证。') elif err_code in ['AMOUNT_LIMIT', 'MONEY_LIMIT', 'SENDNUM_LIMIT', 'NO_AUTH', 'PARAM_ERROR', 'OPENID_ERROR', 'SIGN_ERROR', 'XML_ERROR', 'FATAL_ERROR', 'FREQUENCY_LIMITED', 'FREQ_LIMIT', 'CA_ERROR', 'PARAM_IS_NOT_UTF8', 'RECV_ACCOUNT_NOT_ALLOWED', 'PAY_CHANNEL_NOT_ALLOWED']: return WithdrawResult(False, 0, True, remarks, err_code_des) else: return WithdrawResult(False, 0, False, remarks, u"系统异常,无法获取转账状态。平台会每日定时重试,如果3个工作日仍然是失败状态,请联系客服确认结果(1005)") try: gateway.withdraw_via_changes(amount = record.actualPay, payOpenId = payOpenId, order_no = record.order, real_user_name = real_user_name, subject = u'服务款项') return WithdrawResult(True, 1, False, u'提现成功', u'提现成功') except WechatNetworkException as e: logger.error(repr(e)) if e.errMsg: return WithdrawResult(False, 0, False, e.errMsg, e.errMsg) else: return WithdrawResult(False, 0, False, u'微信通讯错误', u'微信通讯错误,请联系客服确认提现是否成功。') except WeChatException as e: logger.error(repr(e)) return error_handler(e.errCode, e.errMsg) except Exception as e: logger.exception(e) return WithdrawResult(False, 0, False, traceback.format_exc(), u'系统异常,无法确定转账是否成功,请联系客服确认结果(1003)') def withdraw_via_v3(gateway, record, payOpenId, real_user_name): def error_handler(err_code, err_code_des): # type:(str, str)->(bool, bool, str, str) """ SYSTEM_ERROR, SEND_FAILED: 不确定是否成功,这个必须必须设置为错误,联系微信确认订单是否成功 目前已经在微信中明确的错误码可以自动退款,没有明确的其他错误码必须联系微信确认 """ remarks = u'{err_code_des}({err_code})'.format(err_code = err_code, err_code_des = err_code_des) if err_code in ['SYSTEM_ERROR', 'SEND_FAILED']: # 该情况不能确认是否成功,需要平台侧查找订单确认 return WithdrawResult( False, 0, False, remarks, u"商户平台系统错误,无法获取转账状态。后台会每日定时重试,如果3个工作日仍然是失败状态,请联系客服确认结果(1003)") elif err_code == 'NOT_ENOUGH': from taskmanager.mediator import task_caller task_caller('withdraw_error_alert', err_type = 'NOTENOUGH') return WithdrawResult(False, 0, True, remarks, u"商户平台系统繁忙,请过一小时后再试。验证码每天次数有限,频繁提交提现可能消耗完当天验证码次数(1006)") elif err_code == 'NAME_MISMATCH': return WithdrawResult(False, 2, True, remarks, u"实名校验失败,请确保经销商名字与账户登录微信一致后重试。") elif err_code == 'V2_ACCOUNT_SIMPLE_BAN': return WithdrawResult(False, 4, True, remarks, u'您的微信尚未实名认证,请去微信绑定银行卡或身份证完成实名认证。') elif err_code in ['AMOUNT_LIMIT', 'MONEY_LIMIT', 'SENDNUM_LIMIT', 'NO_AUTH', 'PARAM_ERROR', 'OPENID_ERROR', 'SIGN_ERROR', 'XML_ERROR', 'FATAL_ERROR', 'FREQUENCY_LIMITED', 'FREQ_LIMIT', 'CA_ERROR', 'PARAM_IS_NOT_UTF8', 'RECV_ACCOUNT_NOT_ALLOWED', 'PAY_CHANNEL_NOT_ALLOWED']: return WithdrawResult(False, 0, True, remarks, err_code_des) else: return WithdrawResult(False, 0, False, remarks, u"系统异常,无法获取转账状态。平台会每日定时重试,如果3个工作日仍然是失败状态,请联系客服确认结果(1006)") try: gateway.withdraw_via_changes(amount = record.actualPay, payOpenId = payOpenId, order_no = record.order, real_user_name = real_user_name, subject = u'服务款项') return WithdrawResult(True, 1, False, u'提现成功', u'提现成功') except WechatNetworkException as e: logger.error(repr(e)) if e.errMsg: return WithdrawResult(False, 0, False, e.errMsg, e.errMsg) else: return WithdrawResult(False, 0, False, u'微信通讯错误', u'微信通讯错误,请联系客服确认提现是否成功。') except WeChatException as e: logger.error(repr(e)) return error_handler(e.errCode, e.errMsg) except Exception as e: logger.exception(e) 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 self.payee = payee self.income_type = income_type self.amount = amount self.pay_type = pay_type self.bank_card_no = bank_card_no self.record = None def execute(self, source_key, recurrent = False): try: logger.debug('now to withdraw for {}'.format(source_key)) if not settings.CAN_WITHDRAW_FUND: logger.info('test environment do not support withdraw.') raise ServiceException({'result': 0, 'description': u'测试环境不允许提现', 'payload': {}}) if self.payee.no_withdraw: raise ServiceException( {'result': 0, 'description': u"您的账号权限不足或者异常,暂时不能提现。", 'payload': {}}) if self.payee.abnormal: raise ServiceException({'result': 0, 'description': u'该帐号资金异常,请联系客服处理', 'payload': {}}) self.payee.check_withdraw_min_fee( income_type = self.income_type, amount = self.amount, pay_type = self.pay_type) self.payee.can_withdraw_today(self.amount) #: 大额提现单预警 if self.amount >= RMB(settings.WHALE_WITHDRAWAL_ORDER_AMOUNT): from taskmanager.mediator import task_caller task_caller('whale_withdraw_order_alert') with withdraw_lock(self.payee.role, str(self.payee.id)) as acquired: if acquired: if self.payee.sub_balance(self.income_type, source_key) < self.amount: raise ServiceException({'result': 0, 'description': u'余额不足', 'payload': {}}) is_ledger, agent, withdraw_gateway_list = Agent.withdraw_gateway_list(source_key) if not is_ledger: raise ServiceException({'result': 0, 'description': u'暂时不支持提现(1001)', 'payload': {}}) if self.pay_type == WITHDRAW_PAY_TYPE.WECHAT: 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'不支持手动提现到微信,请选择提现到银行卡。', 'payload': {}}) if not withdraw_gateway_list['wechat'].support_withdraw: raise ServiceException( {'result': 0, 'description': u'由于微信接口变更,提现到微信功能已经下线。请您选择提现到支付宝或者银行卡', 'payload': {}}) pay_entity = WithdrawBankCard( accountCode = self.payee.withdraw_open_id, accountName = self.payee.withdraw_wechat_real_name, bankName = u'微信', branchBankName = u'微信企业付款') wechat_withdraw_gateway = withdraw_gateway_list['wechatV3'] or withdraw_gateway_list['wechat'] self.record = self.payee.new_withdraw_record( 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 = False, recurrent = recurrent) if not self.record: 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.withdrawSourceKey, self.record.order, ) if not updated: handler.revoke(remarks = u'扣款失败', description = u'扣款失败') raise ServiceException({'result': 0, 'description': u'扣款失败', 'payload': {}}) withdraw_result = withdraw_via_wechat( gateway = wechat_withdraw_gateway, record = self.record, payOpenId = self.payee.withdraw_open_id, real_user_name = self.record.cardUserName) # type: WithdrawResult logger.debug( 'withdraw via wechat. record = %s; result = %s' % ( self.record.order, repr(withdraw_result))) if withdraw_result.result is True: if wechat_withdraw_gateway.version == 'v3': handler.processing(remarks = u'提现申请已经受理', description = u'提现申请已经受理') else: handler.approve(finishedTime = datetime.datetime.now()) return {'result': 1, 'description': withdraw_result.show_message, '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': {} }) elif self.pay_type == WITHDRAW_PAY_TYPE.BANK: if not self.bank_card_no: raise ServiceException({'result': 0, 'description': u'银行卡号参数错误', 'payload': {}}) 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 = withdraw_gateway_list['wechat'] if bank_card.manual or withdraw_gateway_list['wechat'].manual_withdraw: manual = True elif bank_card.accountType == WithdrawBankCard.AccountType.PUBLIC: if withdraw_gateway_list['alipay'].support_withdraw_bank: withdraw_gateway = withdraw_gateway_list['alipay'] # type: WithdrawGateway if not WithdrawBanks.support(bank_card.bankName): raise ServiceException({ 'result': 0, 'description': u'不支持提现到此银行卡或者银行名称错误, 请联系平台客服(1001)', 'payload': {}}) else: raise ServiceException({ 'result': 0, 'description': u'不支持提现到对公银行卡, 请联系平台客服(1001)', 'payload': {}}) else: while True: if withdraw_gateway_list['alipay'].support_withdraw_bank: if WithdrawBanks.support(bank_card.bankName): withdraw_gateway = withdraw_gateway_list['alipay'] # type: WithdrawGateway break if withdraw_gateway_list['wechat'].support_withdraw_bank: wechat_bank_code = WithdrawBanks.get_wechat_bank_code(bank_card.bankName) if wechat_bank_code: break raise ServiceException({ 'result': 0, 'description': u'不支持提现到银行卡, 请联系平台客服(1002)', 'payload': {}}) self.record = self.payee.new_withdraw_record( withdraw_gateway = withdraw_gateway, pay_entity = bank_card, source_key = source_key, income_type = self.income_type, amount = self.amount, pay_type = self.pay_type, manual = manual, 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.withdrawSourceKey, self.record.order ) if not updated: handler.revoke(remarks = u'扣款失败', description = u'扣款失败') raise ServiceException({'result': 0, 'description': u'扣款失败', 'payload': {}}) # 对公的提现, 或者不支持的银行卡 if manual: withdraw_result = WithdrawResult(True, 1, False, u'提现申请已经受理', u'提现申请已经受理') logger.debug( 'withdraw via bank(manual). record = %s; result = %s' % ( self.record.order, repr(withdraw_result))) handler.processing(remarks = u'提现申请已经受理', description = u'提现申请已经受理') return { 'result': withdraw_result.err_code, 'description': withdraw_result.show_message, 'payload': {'paymentId': str(self.record.id)}} else: gateway_type, withdraw_result = withdraw_via_bank(withdraw_gateway, self.record, bank_card) logger.debug( 'withdraw via bank({}). record = {}; result = {}'.format( gateway_type, self.record.order, repr(withdraw_result))) if withdraw_result.result is True: handler.processing(remarks = u'提现申请已经受理', description = u'提现申请已经受理') 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': {}}) elif self.pay_type == WITHDRAW_PAY_TYPE.ALIPAY: withdraw_gateway = withdraw_gateway_list['alipay'] # type: WithdrawGatewayT if not withdraw_gateway.support_withdraw: raise ServiceException({ 'result': 0, 'description': u'不支持提现到支付宝,请联系平台客服(1002)', 'payload': {}}) 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'您还没有配置提现支付宝账号', '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.withdrawSourceKey, self.record.order, ) 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': {}}) except ServiceException as e: logger.warning(str(e)) if self.record: if 'payload' in e.result: e.result['payload'].update({'paymentId': str(self.record.id)}) else: e.result['payload'] = {'paymentId': str(self.record.id)} return e.result except WithdrawError as e: logger.exception(e) return { 'result': 0, 'description': e.message, 'payload': {'paymentId': str(self.record.id)} if str(self.record.id) else {} } class WithdrawRetryService(object): def __init__(self, record, only_fail = True): # type: (WithdrawRecord, bool)->None self.record = record # type : WithdrawRecord self.only_fail = only_fail def get_payee(self): return ROLE.from_role_id(self.record.role, self.record.ownerId) def check_retry_over(self, handler): end = self.record.postTime + datetime.timedelta(days = 3) # type: datetime now = datetime.datetime.now() 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, 'description': u'重试次数超限,平台退单', 'payload': {} }) def execute(self): try: if self.record.refunded: raise ServiceException( { 'result': 0, 'description': 'record has refunded.'.format(str(self.record.id)), 'payload': {} }) if self.record.manual: raise ServiceException( { 'result': 0, 'description': 'record can not be manual.'.format(str(self.record.id)), 'payload': {} }) if self.only_fail: if self.record.status != WithdrawStatus.FAILED: raise ServiceException( { 'result': 0, 'description': 'record must be fail.'.format(str(self.record.id)), 'payload': {} }) else: if self.record.status not in [WithdrawStatus.SUBMITTED, WithdrawStatus.FAILED]: raise ServiceException( { 'result': 0, 'description': 'record must be fail.'.format(str(self.record.id)), 'payload': {} }) payee = self.get_payee() # type: CapitalUser if payee.role != self.record.role: raise ServiceException( { 'result': 0, 'description': 'role is not same', 'payload': {} }) withdraw_gateway = WithdrawGateway.from_withdraw_gateway_key( self.record.withdrawGatewayKey, self.record.extras.get('gateway_version', 'v1')) # type: WithdrawGateway 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 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() 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 is not exist.'.format(self.record.order)) self.check_retry_over(handler) payee.freeze_balance(self.record.incomeType, self.record.amount, self.record.withdrawSourceKey, self.record.order) withdraw_result = withdraw_via_wechat(withdraw_gateway, self.record, self.record.accountCode, self.record.cardUserName) # type: WithdrawResult logger.debug( 'withdraw via wechat. record = %s; result = %s' % ( self.record.order, repr(withdraw_result))) if withdraw_result.result is True: handler.approve() return { 'result': 1, 'description': withdraw_result.show_message, 'payload': { 'paymentId': str(self.record.id) } } else: raise ServiceException( { 'result': withdraw_result.err_code, 'description': withdraw_result.show_message, 'payload': {} }) elif self.record.payType == WITHDRAW_PAY_TYPE.BANK: try: query_result = withdraw_gateway.get_transfer_result_via_bank( self.record.order) # type: Union[WechatWithdrawQueryResult, AlipayWithdrawQueryResult] errcode, errmsg = query_result.error_desc if query_result.is_failed or query_result.is_refund: 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 is not exist.'.format(self.record.order)) self.check_retry_over(handler) bank_card = payee.withdraw_bank_card(self.record.accountCode) if not bank_card: raise ServiceException( { 'result': 0, 'description': '银行卡不存在', 'payload': {} }) payee.freeze_balance(self.record.incomeType, self.record.amount, self.record.withdrawSourceKey, self.record.order, ) gateway_type, withdraw_result = withdraw_via_bank(withdraw_gateway, self.record, bank_card) logger.debug( 'withdraw via bank({}). record = {}; result = {}'.format( gateway_type, self.record.order, repr(withdraw_result))) if withdraw_result.result is True: handler.processing(remarks=withdraw_result.message, description=withdraw_result.show_message) return { 'result': withdraw_result.err_code, 'description': withdraw_result.show_message, 'payload': { 'paymentId': str(self.record.id) } } else: raise ServiceException( { 'result': withdraw_result.err_code, '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 is not exist.'.format(self.record.order)) self.check_retry_over(handler) updated = payee.freeze_balance(self.record.incomeType, self.record.amount, self.record.withdrawSourceKey, self.record.order, ) 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': {}} except WeChatException as e: logger.exception(e) return {'result': 0, 'description': e.errMsg, 'payload': {}} except ServiceException as e: return e.result except WithdrawError as e: logger.exception(e) return {'result': 0, 'description': e.message, 'payload': {}} except Exception as e: logger.exception(e) return {'result': 0, 'description': e.message, 'payload': {}}