1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030 |
- # -*- 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 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, 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__)
- 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 WeChatPayException 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 WeChatPayException 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 WeChatPayException 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.amount < RMB(settings.WITHDRAW_MINIMUM):
- raise ServiceException(
- {'result': 0, 'description': u"提现金额不能少于%s元" % (settings.WITHDRAW_MINIMUM,), 'payload': {}})
- if self.amount > RMB(settings.WITHDRAW_MAXIMUM):
- raise ServiceException(
- {'result': 0, 'description': u"单次提现金额不得大于%s元" % (settings.WITHDRAW_MAXIMUM,), 'payload': {}})
- #: 大额提现单预警
- if self.amount >= RMB(settings.WHALE_WITHDRAWAL_ORDER_AMOUNT):
- from taskmanager.mediator import task_caller
- task_caller('whale_withdraw_order_alert')
- if self.payee.no_withdraw:
- 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': {}})
- 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, 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'暂时不支持提现(1002)', '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.source_key,
- 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:
- 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:
- manual = True
- elif bank_card.accountType == WithdrawBankCard.AccountType.PUBLIC:
- 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'不支持提现到此银行卡或者银行名称错误, 请联系平台客服(1001)',
- 'payload': {}})
- else:
- manual = True
- else:
- # 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,
- 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.source_key,
- 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:
- 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,
- )
- 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.exception(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.source_key,
- self.record.order,
- self.record.is_new_version)
- 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.source_key,
- 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.source_key,
- 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 WeChatPayException 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': {}}
|