123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311 |
- # -*- 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 '<AlipayResultDict successful?(%s), failed?(%s), processing?(%s) \n content=%s' \
- % (self.is_successful, self.is_failed, self.is_processing, json.dumps(self, indent = 2),)
- class AliPayGateway(PaymentGateway, AlipayMixin):
- """
- Alipay 支付网关,扩展原库没有的接口 ``alipay.trade.create``
- """
- def __init__(self, app, gateway_type = AppPlatformType.ALIPAY):
- # type: (AliApp, AppPlatformType)->None
- super(AliPayGateway, self).__init__(app)
- self.__gateway_type__ = gateway_type
- def __repr__(self):
- return '<AliPayPaymentGateway(appid=%s, debug=%s)>' % (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 '<AliPayWithdrawGateway(appid=%s, debug=%s)>' % (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)
|