# -*- coding: utf-8 -*- # !/usr/bin/env python import datetime import logging import arrow import simplejson as json from django.conf import settings from typing import TYPE_CHECKING from apilib.monetary import RMB from apilib.monetary import quantize from apilib.utils_string import cn from apps.web.constant import AppPlatformType from apps.web.exceptions import WithdrawOrderNotExist from apps.web.common.models import WithdrawBankCard from apps.web.core import AlipayMixin from apps.web.core.payment import PaymentGateway, WithdrawGateway from apps.web.utils import testcase_point from library.alipay import AliPayGatewayException, AliErrorCode, AliException from library.alipay import AliPayServiceException logger = logging.getLogger(__name__) if TYPE_CHECKING: from apps.web.core.models import AliApp class AlipayWithdrawQueryResult(dict): @property def order_status(self): return self.get('status') @property def finished_time(self): if self.get('pay_date', None): return arrow.get(self['pay_date'], 'YYYY-MM-DD HH:mm:ss', tzinfo = settings.TIME_ZONE).naive else: return datetime.datetime.now() @property def extra(self): return { 'order_id': self.get('order_id'), 'pay_fund_order_id': self.get('pay_fund_order_id') } @property def is_successful(self): return self.get('status') == 'SUCCESS' @property def is_failed(self): return self.get('status') == 'FAIL' @property def is_processing(self): return self.get('status') == 'DEALING' @property def is_refund(self): """ 成功状态可能会转换为退票状态 :return: """ return self.get('status') == 'REFUND' @property def error_desc(self): if self.is_successful: return self.get('status'), u'成功' if self.is_processing: return self.get('status'), u'正在处理' if self.is_refund: return self.get('status'), u'银行退票' error_code = self.get('error_code', u'1001') fail_reason = self.get('fail_reason', u'转账失败,请登录支付宝商户号查询具体订单信息') return self.get('status'), u'{}({})'.format(fail_reason, error_code) def __repr__(self): return 'None super(AliPayGateway, self).__init__(app) self.__gateway_type__ = gateway_type def __repr__(self): return '' % (self.appid, self.debug) def api_trade_query(self, out_trade_no = None, trade_no = None): assert out_trade_no or trade_no, 'must input out_trade_no or trade_no' result = self.client.api_alipay_trade_query(out_trade_no = out_trade_no, trade_no = trade_no) if result['code'] == u'10000': return result elif result['code'] == u'40004': raise AliPayServiceException(errCode = result['sub_code'], errMsg = result['sub_msg'], client = self) else: raise AliPayGatewayException(errCode = result['sub_code'], errMsg = result['sub_msg'], client = self) def api_alipay_trade_create(self, out_trade_no, money, subject, buyer_id, notify_url, timeout_express = '2m', body = '', **kwargs): # type:(str, RMB, basestring, str, str, str, str, dict)->dict """ 手机扫码创建订单 :param body: :param timeout_express: 订单关闭超时时间 :param out_trade_no: 因需要保存到rechargeRecord里,所以需要在方法外生成 :param money: 交易数额 [0.01,100000000] :param subject: 标题: 支付宝充值1元 :param buyer_id: 正在扫码准备充值的用户 :param kwargs: :return: """ total_amount = quantize(money.amount, places = '0.01') if total_amount != money.amount: raise AliException( errCode = AliErrorCode.MY_INVALID_PARAMETER, errMsg = u'无效的交易金额', client = self.client) extras = { "buyer_id": buyer_id, "timeout_express": timeout_express, "body": body } extras.update(**kwargs) logger.debug('alipay kwargs = {}'.format(extras)) logger.debug('alipay kwargs2,out_trade_no=%s,total_amount=%s,subject=%s,notify_url=%s' % ( out_trade_no, str(total_amount), subject, notify_url)) return self.client.api_alipay_trade_create(out_trade_no = out_trade_no, total_amount = str(total_amount), subject = subject, notify_url = notify_url, **extras) def alipay_trade_precreate(self, out_trade_no, money, subject, buyer_id, timeout_express = '2m', body = '', **kwargs): """ 当面付的预下单 :param out_trade_no: :param money: :param subject: :param buyer_id: :param timeout_express: :param body: :param kwargs: :return: """ total_amount = str(quantize(money.amount, places = '0.01')) return self.client.api_alipay_trade_precreate(out_trade_no = out_trade_no, total_amount = total_amount, subject = subject, **{"buyer_id": buyer_id, "timeout_express": timeout_express, "body": body}) @testcase_point() def refund_to_user(self, out_trade_no, out_refund_no, refund_fee, total_fee, refund_reason, **kwargs): """ :param out_trade_no: :param out_refund_no: :param refund_fee: :param total_fee: :param refund_reason: :return: """ return self.client.api_alipay_trade_refund(out_trade_no = out_trade_no, out_request_no = out_refund_no, refund_amount = str(refund_fee), refund_reason = refund_reason) def download_bill(self, bill_type = 'trade', bill_date = None): """ 下载支付宝订单用于对账 :param bill_type: (trade|signcustomer) trade指商户基于支付宝交易收单的业务账单 signcustomer是指基于商户支付宝余额收入及支出等资金变动的帐务账单 :param bill_date: 日账单格式为yyyy-MM-dd,月账单格式为yyyy-MM。 :return: """ return self.client.api_alipay_data_dataservice_bill_downloadurl_query(bill_type = bill_type, bill_date = bill_date) def api_refund_query(self, out_refund_no, out_trade_no): return self.client.api_alipay_trade_refund_order_query(out_trade_no, out_refund_no, ["gmt_refund_pay"]) class AliPayWithdrawGateway(WithdrawGateway, AlipayMixin): def __init__(self, app): # type: (AliApp)->None super(AliPayWithdrawGateway, self).__init__(app) self.__gateway_type__ = AppPlatformType.WITHDRAW def __repr__(self): return '' % (self.appid, self.debug) def withdraw_via_bank(self, order_no, total_amount, bank_card, order_title = u'服务款项'): # type:(str, RMB, WithdrawBankCard, str)->dict """ 经销商提现通过银行卡提现 .. 参考文档 https://opendocs.alipay.com/open/common/transfertocard :param total_amount: :param order_no: 商户企业付款单号(本平台订单号) len(order_no) (- [8-32] :return: """ payee_info = { 'identity_type': 'BANKCARD_ACCOUNT', 'identity': bank_card.accountCode, 'name': cn(bank_card.accountName) } if bank_card.accountType == WithdrawBankCard.AccountType.PUBLIC: payee_info['bankcard_ext_info'] = { 'inst_name': cn(bank_card.bankName), 'account_type': 1 } if bank_card.cnapsCode: payee_info['bankcard_ext_info'].update({ 'bank_code': bank_card.cnapsCode }) else: payee_info['bankcard_ext_info'].update({ 'inst_province': cn(bank_card.province), 'inst_city': cn(bank_card.city), 'inst_branch_name': cn(bank_card.branchBankName) }) else: payee_info['bankcard_ext_info'] = { 'account_type': 2 } result = self.client.api_alipay_fund_trans_uni_transfer( out_biz_no = order_no, trans_amount = str(total_amount), payee_info = payee_info, order_title = order_title) if result['code'] == u'10000': return result elif result['code'] == u'40004': raise AliPayServiceException(errCode = result['sub_code'], errMsg = result['sub_msg'], client = self) else: raise AliPayGatewayException(errCode = result['sub_code'], errMsg = result['sub_msg'], client = self) def get_transfer_result_via_bank(self, order_no): """ 查询银行卡提现的返回结果 :return: """ result = self.client.api_alipay_fund_trans_common_query(out_biz_no = order_no) if result['code'] == u'10000': return AlipayWithdrawQueryResult(result) elif result['code'] == u'40004': if result['sub_code'] in ['ORDER_NOT_EXIST']: raise WithdrawOrderNotExist() else: raise AliPayServiceException(errCode = result['sub_code'], errMsg = result['sub_msg'], client = self) else: raise AliPayGatewayException(errCode = result['sub_code'], errMsg = result['sub_msg'], client = self) def withdraw_via_changes(self, amount, payOpenId, order_no, real_user_name, subject = u'服务款项'): """ 提现到支付宝账户 :return: """ payee_info = { 'identity': payOpenId, 'identity_type': 'ALIPAY_LOGON_ID', 'name': cn(real_user_name) } result = self.client.api_alipay_fund_trans_uni_transfer( out_biz_no = order_no, trans_amount = str(amount), payee_info = payee_info, order_title = subject, product_code = 'TRANS_ACCOUNT_NO_PWD') if result['code'] == u'10000': return result elif result['code'] == u'40004': raise AliPayServiceException(errCode = result['sub_code'], errMsg = result['sub_msg'], client = self) else: raise AliPayGatewayException(errCode = result['sub_code'], errMsg = result['sub_msg'], client = self) def get_transfer_result_via_changes(self, order_no): """ 查询现金提现的返回结果 :return: """ return self.get_transfer_result_via_bank(order_no = order_no)