12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028 |
- # -*- 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,
- self.record.is_new_version)
- if not updated:
- handler.revoke(remarks = u'扣款失败', description = u'扣款失败')
- raise ServiceException({'result': 0, 'description': u'扣款失败', 'payload': {}})
- withdraw_result = withdraw_via_wechat(
- gateway = 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,
- self.record.is_new_version)
- 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,
- self.record.is_new_version)
- if not updated:
- handler.revoke(remarks = u'扣款失败', description = u'扣款失败')
- raise ServiceException({'result': 0, 'description': u'扣款失败', 'payload': {}})
- withdraw_result = AlipayWithdraw.withdraw_via_changes_in_ali(withdraw_gateway, self.record)
- logger.debug(
- 'withdraw via alipay. record = %s; result = %s' % (
- self.record.order, repr(withdraw_result)))
- if withdraw_result.result is True:
- finished_time = arrow.get(withdraw_result.callResult['trans_date'], 'YYYY-MM-DD HH:mm:ss',
- tzinfo = settings.TIME_ZONE).naive
- handler.approve(finishedTime = finished_time, extra = {
- 'order_id': withdraw_result.callResult.get('order_id'),
- 'pay_fund_order_id': withdraw_result.callResult.get('pay_fund_order_id'),
- })
- return {
- 'result': 1,
- 'description': u'提现申请已经受理',
- 'payload': {
- 'paymentId': str(self.record.id)
- }
- }
- else:
- if withdraw_result.refund:
- handler.revoke(remarks = withdraw_result.message,
- description = withdraw_result.show_message)
- else:
- handler.fail(remarks = withdraw_result.message,
- description = withdraw_result.show_message)
- raise ServiceException(
- {'result': withdraw_result.err_code, 'description': withdraw_result.show_message,
- 'payload': {}})
- raise ServiceException({'result': 0, 'description': u'系统错误,请稍后再试', 'payload': {}})
- else:
- raise ServiceException({'result': 0, 'description': u'操作频繁,请稍后再试', 'payload': {}})
- 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,
- self.record.is_new_version)
- 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,
- self.record.is_new_version)
- if not updated:
- handler.revoke(remarks = u'扣款失败', description = u'扣款失败')
- raise ServiceException({'result': 0, 'description': u'扣款失败', 'payload': {}})
- withdraw_result = AlipayWithdraw.withdraw_via_changes_in_ali(withdraw_gateway, self.record)
- logger.debug(
- 'withdraw via alipay. record = %s; result = %s' % (
- self.record.order, repr(withdraw_result)))
- if withdraw_result.result is True:
- finished_time = arrow.get(withdraw_result.callResult['trans_date'], 'YYYY-MM-DD HH:mm:ss',
- tzinfo = settings.TIME_ZONE).naive
- handler.approve(finishedTime = finished_time, extra = {
- 'order_id': withdraw_result.callResult.get('order_id'),
- 'pay_fund_order_id': withdraw_result.callResult.get('pay_fund_order_id'),
- })
- return {
- 'result': 1,
- 'description': u'提现申请已经受理',
- 'payload': {
- 'paymentId': str(self.record.id)
- }
- }
- else:
- if withdraw_result.refund:
- handler.revoke(remarks = withdraw_result.message,
- description = withdraw_result.show_message)
- else:
- handler.fail(remarks = withdraw_result.message,
- description = withdraw_result.show_message)
- raise ServiceException(
- {
- 'result': withdraw_result.err_code,
- 'description': withdraw_result.show_message,
- 'payload': {}
- })
- else:
- logger.error('invalid withdraw type: {}'.format(self.record.payType))
- except WechatNetworkException as e:
- logger.exception(e)
- return {'result': 0, 'description': e.errMsg, 'payload': {}}
- 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': {}}
|