123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035 |
- # -*- 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<id={}> has refunded.'.format(str(self.record.id)),
- 'payload': {}
- })
- if self.record.manual:
- raise ServiceException(
- {
- 'result': 0,
- 'description': 'record<id={}> 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<id={}> 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<id={}> 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<orderNo={}> 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<orderNo={}> 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<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.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': {}}
|