parent
cae0715e7a
commit
e2d8f60640
@ -0,0 +1,102 @@ |
|||||||
|
#!/usr/bin/env python |
||||||
|
# -*- coding:utf-8 -*- |
||||||
|
# project: 4月 |
||||||
|
# author: NinEveN |
||||||
|
# date: 2021/4/13 |
||||||
|
|
||||||
|
from rest_framework.views import APIView |
||||||
|
from api.utils.response import BaseResponse |
||||||
|
from api.utils.auth import ExpiringTokenAuthentication |
||||||
|
from rest_framework.response import Response |
||||||
|
from django.db.models import Sum |
||||||
|
from api.utils.app.supersignutils import IosUtils, resign_by_app_obj |
||||||
|
from api.utils.storage.storage import Storage |
||||||
|
from api.utils.storage.caches import del_cache_response_by_short, get_app_today_download_times, del_cache_by_delete_app |
||||||
|
from api.models import Apps, AppReleaseInfo, APPToDeveloper, AppIOSDeveloperInfo, UserInfo, AppScreenShot |
||||||
|
from api.utils.serializer import AppsSerializer, AppReleaseSerializer |
||||||
|
from rest_framework.pagination import PageNumberPagination |
||||||
|
import logging |
||||||
|
from fir_ser.settings import SERVER_DOMAIN |
||||||
|
from api.utils.utils import is_valid_domain, delete_local_files, delete_app_screenshots_files |
||||||
|
|
||||||
|
logger = logging.getLogger(__name__) |
||||||
|
|
||||||
|
|
||||||
|
def app_delete(app_obj): |
||||||
|
res = BaseResponse() |
||||||
|
if not app_obj: |
||||||
|
res.code = 1001 |
||||||
|
res.msg = "应用不存在" |
||||||
|
return res |
||||||
|
user_obj = app_obj.user_id |
||||||
|
count = APPToDeveloper.objects.filter(app_id=app_obj).count() |
||||||
|
if app_obj.issupersign or count > 0: |
||||||
|
logger.info("app_id:%s is supersign ,delete this app need clean IOS developer" % (app_obj.app_id)) |
||||||
|
IosUtils.clean_app_by_user_obj(app_obj, user_obj) |
||||||
|
|
||||||
|
storage = Storage(user_obj) |
||||||
|
has_combo = app_obj.has_combo |
||||||
|
if has_combo: |
||||||
|
logger.info( |
||||||
|
"app_id:%s has_combo ,delete this app need uncombo and clean del_cache_response_by_short" % ( |
||||||
|
app_obj.app_id)) |
||||||
|
has_combo.has_combo = None |
||||||
|
del_cache_response_by_short(app_obj.app_id) |
||||||
|
del_cache_by_delete_app(app_obj.app_id) |
||||||
|
for app_release_obj in AppReleaseInfo.objects.filter(app_id=app_obj).all(): |
||||||
|
logger.info("delete app_id:%s need clean all release,release_id:%s" % ( |
||||||
|
app_obj.app_id, app_release_obj.release_id)) |
||||||
|
storage.delete_file(app_release_obj.release_id, app_release_obj.release_type) |
||||||
|
delete_local_files(app_release_obj.release_id, app_release_obj.release_type) |
||||||
|
storage.delete_file(app_release_obj.icon_url) |
||||||
|
app_release_obj.delete() |
||||||
|
delete_app_screenshots_files(storage, app_obj) |
||||||
|
app_obj.delete() |
||||||
|
|
||||||
|
return res |
||||||
|
|
||||||
|
|
||||||
|
def app_screen_delete(screen_id, apps_obj, storage): |
||||||
|
screen_obj = AppScreenShot.objects.filter(pk=screen_id, app_id=apps_obj).first() |
||||||
|
if screen_obj: |
||||||
|
storage.delete_file(screen_obj.screenshot_url) |
||||||
|
screen_obj.delete() |
||||||
|
del_cache_response_by_short(apps_obj.app_id) |
||||||
|
|
||||||
|
|
||||||
|
def app_release_delete(app_obj, release_id, storage): |
||||||
|
res = BaseResponse() |
||||||
|
user_obj = app_obj.user_id |
||||||
|
if app_obj: |
||||||
|
apprelease_count = AppReleaseInfo.objects.filter(app_id=app_obj).values("release_id").count() |
||||||
|
appreleaseobj = AppReleaseInfo.objects.filter(app_id=app_obj, release_id=release_id).first() |
||||||
|
if not appreleaseobj.is_master: |
||||||
|
logger.info("delete app release %s" % (appreleaseobj)) |
||||||
|
storage.delete_file(appreleaseobj.release_id, appreleaseobj.release_type) |
||||||
|
delete_local_files(appreleaseobj.release_id, appreleaseobj.release_type) |
||||||
|
storage.delete_file(appreleaseobj.icon_url) |
||||||
|
|
||||||
|
appreleaseobj.delete() |
||||||
|
elif appreleaseobj.is_master and apprelease_count < 2: |
||||||
|
logger.info("delete app master release %s and clean app %s " % (appreleaseobj, app_obj)) |
||||||
|
count = APPToDeveloper.objects.filter(app_id=app_obj).count() |
||||||
|
if app_obj.issupersign or count > 0: |
||||||
|
logger.info("app_id:%s is supersign ,delete this app need clean IOS developer" % (app_obj.app_id)) |
||||||
|
IosUtils.clean_app_by_user_obj(app_obj, user_obj) |
||||||
|
|
||||||
|
storage.delete_file(appreleaseobj.release_id, appreleaseobj.release_type) |
||||||
|
delete_local_files(appreleaseobj.release_id, appreleaseobj.release_type) |
||||||
|
storage.delete_file(appreleaseobj.icon_url) |
||||||
|
del_cache_by_delete_app(app_obj.app_id) |
||||||
|
|
||||||
|
appreleaseobj.delete() |
||||||
|
delete_app_screenshots_files(storage, app_obj) |
||||||
|
has_combo = app_obj.has_combo |
||||||
|
if has_combo: |
||||||
|
app_obj.has_combo.has_combo = None |
||||||
|
app_obj.delete() |
||||||
|
else: |
||||||
|
pass |
||||||
|
del_cache_response_by_short(app_obj.app_id) |
||||||
|
|
||||||
|
return res |
@ -0,0 +1,18 @@ |
|||||||
|
# Generated by Django 3.0.3 on 2021-04-13 10:10 |
||||||
|
|
||||||
|
from django.db import migrations, models |
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration): |
||||||
|
dependencies = [ |
||||||
|
('api', '0039_auto_20210412_1759'), |
||||||
|
] |
||||||
|
|
||||||
|
operations = [ |
||||||
|
migrations.AlterField( |
||||||
|
model_name='usercertificationinfo', |
||||||
|
name='status', |
||||||
|
field=models.SmallIntegerField(choices=[(-1, '待认证'), (0, '认证中'), (1, '认证成功'), (2, '认证失败')], default=0, |
||||||
|
verbose_name='审核状态'), |
||||||
|
), |
||||||
|
] |
@ -0,0 +1,861 @@ |
|||||||
|
#!/usr/bin/env python |
||||||
|
# coding: utf-8 |
||||||
|
""" |
||||||
|
__init__.py |
||||||
|
~~~~~~~~~~ |
||||||
|
|
||||||
|
""" |
||||||
|
import json |
||||||
|
from datetime import datetime |
||||||
|
from functools import partial |
||||||
|
|
||||||
|
import hashlib |
||||||
|
import OpenSSL |
||||||
|
from Cryptodome.Hash import SHA, SHA256 |
||||||
|
from Cryptodome.PublicKey import RSA |
||||||
|
from Cryptodome.Signature import PKCS1_v1_5 |
||||||
|
|
||||||
|
from .compat import decodebytes, encodebytes, quote_plus, urlopen |
||||||
|
from .exceptions import AliPayException, AliPayValidationError |
||||||
|
from .utils import AliPayConfig |
||||||
|
from .loggers import logger |
||||||
|
|
||||||
|
# 常见加密算法 |
||||||
|
CryptoAlgSet = ( |
||||||
|
b'rsaEncryption', |
||||||
|
b'md2WithRSAEncryption', |
||||||
|
b'md5WithRSAEncryption', |
||||||
|
b'sha1WithRSAEncryption', |
||||||
|
b'sha256WithRSAEncryption', |
||||||
|
b'sha384WithRSAEncryption', |
||||||
|
b'sha512WithRSAEncryption' |
||||||
|
) |
||||||
|
|
||||||
|
|
||||||
|
class BaseAliPay: |
||||||
|
@property |
||||||
|
def appid(self): |
||||||
|
return self._appid |
||||||
|
|
||||||
|
@property |
||||||
|
def sign_type(self): |
||||||
|
return self._sign_type |
||||||
|
|
||||||
|
@property |
||||||
|
def app_private_key(self): |
||||||
|
"""签名用""" |
||||||
|
return self._app_private_key |
||||||
|
|
||||||
|
@property |
||||||
|
def alipay_public_key(self): |
||||||
|
"""验证签名用""" |
||||||
|
return self._alipay_public_key |
||||||
|
|
||||||
|
def __init__( |
||||||
|
self, |
||||||
|
appid, |
||||||
|
app_notify_url, |
||||||
|
app_private_key_string=None, |
||||||
|
alipay_public_key_string=None, |
||||||
|
sign_type="RSA2", |
||||||
|
debug=False, |
||||||
|
verbose=False, |
||||||
|
config=None |
||||||
|
): |
||||||
|
""" |
||||||
|
初始化: |
||||||
|
alipay = AliPay( |
||||||
|
appid="", |
||||||
|
app_notify_url="http://example.com", |
||||||
|
sign_type="RSA2" |
||||||
|
) |
||||||
|
""" |
||||||
|
self._appid = str(appid) |
||||||
|
self._app_notify_url = app_notify_url |
||||||
|
self._app_private_key_string = app_private_key_string |
||||||
|
self._alipay_public_key_string = alipay_public_key_string |
||||||
|
self._verbose = verbose |
||||||
|
self._config = config or AliPayConfig() |
||||||
|
|
||||||
|
self._app_private_key = None |
||||||
|
self._alipay_public_key = None |
||||||
|
if sign_type not in ("RSA", "RSA2"): |
||||||
|
message = "Unsupported sign type {}".format(sign_type) |
||||||
|
raise AliPayException(None, message) |
||||||
|
self._sign_type = sign_type |
||||||
|
|
||||||
|
if debug: |
||||||
|
self._gateway = "https://openapi.alipaydev.com/gateway.do" |
||||||
|
else: |
||||||
|
self._gateway = "https://openapi.alipay.com/gateway.do" |
||||||
|
|
||||||
|
# load key file immediately |
||||||
|
self._load_key() |
||||||
|
|
||||||
|
def _load_key(self): |
||||||
|
# load private key |
||||||
|
content = self._app_private_key_string |
||||||
|
self._app_private_key = RSA.importKey(content) |
||||||
|
|
||||||
|
# load public key |
||||||
|
content = self._alipay_public_key_string |
||||||
|
self._alipay_public_key = RSA.importKey(content) |
||||||
|
|
||||||
|
def _sign(self, unsigned_string): |
||||||
|
""" |
||||||
|
通过如下方法调试签名 |
||||||
|
方法1 |
||||||
|
key = rsa.PrivateKey.load_pkcs1(open(self._app_private_key_string).read()) |
||||||
|
sign = rsa.sign(unsigned_string.encode(), key, "SHA-1") |
||||||
|
# base64 编码,转换为unicode表示并移除回车 |
||||||
|
sign = base64.encodebytes(sign).decode().replace("\n", "") |
||||||
|
方法2 |
||||||
|
key = RSA.importKey(open(self._app_private_key_string).read()) |
||||||
|
signer = PKCS1_v1_5.new(key) |
||||||
|
signature = signer.sign(SHA.new(unsigned_string.encode())) |
||||||
|
# base64 编码,转换为unicode表示并移除回车 |
||||||
|
sign = base64.encodebytes(signature).decode().replace("\n", "") |
||||||
|
方法3 |
||||||
|
echo "abc" | openssl sha1 -sign alipay.key | openssl base64 |
||||||
|
|
||||||
|
""" |
||||||
|
# 开始计算签名 |
||||||
|
key = self.app_private_key |
||||||
|
signer = PKCS1_v1_5.new(key) |
||||||
|
if self._sign_type == "RSA": |
||||||
|
signature = signer.sign(SHA.new(unsigned_string.encode())) |
||||||
|
else: |
||||||
|
signature = signer.sign(SHA256.new(unsigned_string.encode())) |
||||||
|
# base64 编码,转换为unicode表示并移除回车 |
||||||
|
sign = encodebytes(signature).decode().replace("\n", "") |
||||||
|
return sign |
||||||
|
|
||||||
|
def _ordered_data(self, data): |
||||||
|
for k, v in data.items(): |
||||||
|
if isinstance(v, dict): |
||||||
|
# 将字典类型的数据dump出来 |
||||||
|
data[k] = json.dumps(v, separators=(',', ':')) |
||||||
|
return sorted(data.items()) |
||||||
|
|
||||||
|
def build_body(self, method, biz_content, **kwargs): |
||||||
|
data = { |
||||||
|
"app_id": self._appid, |
||||||
|
"method": method, |
||||||
|
"charset": "utf-8", |
||||||
|
"sign_type": self._sign_type, |
||||||
|
"timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"), |
||||||
|
"version": "1.0", |
||||||
|
"biz_content": biz_content |
||||||
|
} |
||||||
|
data.update(kwargs) |
||||||
|
|
||||||
|
if method in ( |
||||||
|
"alipay.trade.app.pay", "alipay.trade.wap.pay", |
||||||
|
"alipay.trade.page.pay", "alipay.trade.pay", |
||||||
|
"alipay.trade.precreate", "alipay.trade.create" |
||||||
|
) and not data.get("notify_url") and self._app_notify_url: |
||||||
|
data["notify_url"] = self._app_notify_url |
||||||
|
|
||||||
|
if self._verbose: |
||||||
|
logger.debug("data to be signed") |
||||||
|
logger.debug(data) |
||||||
|
return data |
||||||
|
|
||||||
|
def sign_data(self, data): |
||||||
|
# 排序后的字符串 |
||||||
|
ordered_items = self._ordered_data(data) |
||||||
|
raw_string = "&".join("{}={}".format(k, v) for k, v in ordered_items) |
||||||
|
sign = self._sign(raw_string) |
||||||
|
unquoted_items = ordered_items + [('sign', sign)] |
||||||
|
|
||||||
|
# 获得最终的订单信息字符串 |
||||||
|
signed_string = "&".join("{}={}".format(k, quote_plus(v)) for k, v in unquoted_items) |
||||||
|
if self._verbose: |
||||||
|
logger.debug("signed srtring") |
||||||
|
logger.debug(signed_string) |
||||||
|
return signed_string |
||||||
|
|
||||||
|
def _verify(self, raw_content, signature): |
||||||
|
# 开始计算签名 |
||||||
|
key = self.alipay_public_key |
||||||
|
signer = PKCS1_v1_5.new(key) |
||||||
|
if self._sign_type == "RSA": |
||||||
|
digest = SHA.new() |
||||||
|
else: |
||||||
|
digest = SHA256.new() |
||||||
|
digest.update(raw_content.encode()) |
||||||
|
return bool(signer.verify(digest, decodebytes(signature.encode()))) |
||||||
|
|
||||||
|
def verify(self, data, signature): |
||||||
|
if "sign_type" in data: |
||||||
|
sign_type = data.pop("sign_type") |
||||||
|
if sign_type != self._sign_type: |
||||||
|
raise AliPayException(None, "Unknown sign type: {}".format(sign_type)) |
||||||
|
# 排序后的字符串 |
||||||
|
unsigned_items = self._ordered_data(data) |
||||||
|
message = "&".join(u"{}={}".format(k, v) for k, v in unsigned_items) |
||||||
|
return self._verify(message, signature) |
||||||
|
|
||||||
|
def client_api(self, api_name, biz_content, **kwargs): |
||||||
|
""" |
||||||
|
alipay api without http request |
||||||
|
""" |
||||||
|
data = self.build_body(api_name, biz_content, **kwargs) |
||||||
|
return self.sign_data(data) |
||||||
|
|
||||||
|
def server_api(self, api_name, biz_content, **kwargs): |
||||||
|
""" |
||||||
|
alipay api with http request |
||||||
|
""" |
||||||
|
data = self.build_body(api_name, biz_content, **kwargs) |
||||||
|
# alipay.trade.query => alipay_trade_query_response |
||||||
|
response_type = api_name.replace(".", "_") + "_response" |
||||||
|
print(data) |
||||||
|
return self.verified_sync_response(data, response_type) |
||||||
|
|
||||||
|
def api_alipay_trade_wap_pay( |
||||||
|
self, subject, out_trade_no, total_amount, |
||||||
|
return_url=None, notify_url=None, **kwargs |
||||||
|
): |
||||||
|
biz_content = { |
||||||
|
"subject": subject, |
||||||
|
"out_trade_no": out_trade_no, |
||||||
|
"total_amount": total_amount, |
||||||
|
"product_code": "QUICK_WAP_PAY" |
||||||
|
} |
||||||
|
biz_content.update(kwargs) |
||||||
|
data = self.build_body( |
||||||
|
"alipay.trade.wap.pay", |
||||||
|
biz_content, |
||||||
|
return_url=return_url, |
||||||
|
notify_url=notify_url |
||||||
|
) |
||||||
|
return self.sign_data(data) |
||||||
|
|
||||||
|
def api_alipay_trade_app_pay( |
||||||
|
self, subject, out_trade_no, total_amount, notify_url=None, **kwargs |
||||||
|
): |
||||||
|
biz_content = { |
||||||
|
"subject": subject, |
||||||
|
"out_trade_no": out_trade_no, |
||||||
|
"total_amount": total_amount, |
||||||
|
"product_code": "QUICK_MSECURITY_PAY" |
||||||
|
} |
||||||
|
biz_content.update(kwargs) |
||||||
|
data = self.build_body("alipay.trade.app.pay", biz_content, notify_url=notify_url) |
||||||
|
return self.sign_data(data) |
||||||
|
|
||||||
|
def api_alipay_trade_page_pay(self, subject, out_trade_no, total_amount, |
||||||
|
return_url=None, notify_url=None, **kwargs): |
||||||
|
biz_content = { |
||||||
|
"subject": subject, |
||||||
|
"out_trade_no": out_trade_no, |
||||||
|
"total_amount": total_amount, |
||||||
|
"product_code": "FAST_INSTANT_TRADE_PAY" |
||||||
|
} |
||||||
|
|
||||||
|
biz_content.update(kwargs) |
||||||
|
data = self.build_body( |
||||||
|
"alipay.trade.page.pay", |
||||||
|
biz_content, |
||||||
|
return_url=return_url, |
||||||
|
notify_url=notify_url |
||||||
|
) |
||||||
|
return self.sign_data(data) |
||||||
|
|
||||||
|
def api_alipay_trade_query(self, out_trade_no=None, trade_no=None): |
||||||
|
""" |
||||||
|
response = { |
||||||
|
"alipay_trade_query_response": { |
||||||
|
"trade_no": "2017032121001004070200176844", |
||||||
|
"code": "10000", |
||||||
|
"invoice_amount": "20.00", |
||||||
|
"open_id": "20880072506750308812798160715407", |
||||||
|
"fund_bill_list": [ |
||||||
|
{ |
||||||
|
"amount": "20.00", |
||||||
|
"fund_channel": "ALIPAYACCOUNT" |
||||||
|
} |
||||||
|
], |
||||||
|
"buyer_logon_id": "csq***@sandbox.com", |
||||||
|
"send_pay_date": "2017-03-21 13:29:17", |
||||||
|
"receipt_amount": "20.00", |
||||||
|
"out_trade_no": "out_trade_no15", |
||||||
|
"buyer_pay_amount": "20.00", |
||||||
|
"buyer_user_id": "2088102169481075", |
||||||
|
"msg": "Success", |
||||||
|
"point_amount": "0.00", |
||||||
|
"trade_status": "TRADE_SUCCESS", |
||||||
|
"total_amount": "20.00" |
||||||
|
}, |
||||||
|
"sign": "" |
||||||
|
} |
||||||
|
""" |
||||||
|
assert (out_trade_no is not None) or (trade_no is not None), \ |
||||||
|
"Both trade_no and out_trade_no are None" |
||||||
|
|
||||||
|
biz_content = {} |
||||||
|
if out_trade_no: |
||||||
|
biz_content["out_trade_no"] = out_trade_no |
||||||
|
if trade_no: |
||||||
|
biz_content["trade_no"] = trade_no |
||||||
|
data = self.build_body("alipay.trade.query", biz_content) |
||||||
|
response_type = "alipay_trade_query_response" |
||||||
|
return self.verified_sync_response(data, response_type) |
||||||
|
|
||||||
|
def api_alipay_trade_pay( |
||||||
|
self, out_trade_no, scene, auth_code, subject, notify_url=None, **kwargs |
||||||
|
): |
||||||
|
""" |
||||||
|
eg: |
||||||
|
self.api_alipay_trade_pay( |
||||||
|
out_trade_no, |
||||||
|
"bar_code/wave_code", |
||||||
|
auth_code, |
||||||
|
subject, |
||||||
|
total_amount=12, |
||||||
|
discountable_amount=10 |
||||||
|
) |
||||||
|
|
||||||
|
failed response = { |
||||||
|
"alipay_trade_pay_response": { |
||||||
|
"code": "40004", |
||||||
|
"msg": "Business Failed", |
||||||
|
"sub_code": "ACQ.INVALID_PARAMETER", |
||||||
|
"sub_msg": "", |
||||||
|
"buyer_pay_amount": "0.00", |
||||||
|
"invoice_amount": "0.00", |
||||||
|
"point_amount": "0.00", |
||||||
|
"receipt_amount": "0.00" |
||||||
|
}, |
||||||
|
"sign": "" |
||||||
|
} |
||||||
|
succeeded response = |
||||||
|
{ |
||||||
|
"alipay_trade_pay_response": { |
||||||
|
"trade_no": "2017032121001004070200176846", |
||||||
|
"code": "10000", |
||||||
|
"invoice_amount": "20.00", |
||||||
|
"open_id": "20880072506750308812798160715407", |
||||||
|
"fund_bill_list": [ |
||||||
|
{ |
||||||
|
"amount": "20.00", |
||||||
|
"fund_channel": "ALIPAYACCOUNT" |
||||||
|
} |
||||||
|
], |
||||||
|
"buyer_logon_id": "csq***@sandbox.com", |
||||||
|
"receipt_amount": "20.00", |
||||||
|
"out_trade_no": "out_trade_no18", |
||||||
|
"buyer_pay_amount": "20.00", |
||||||
|
"buyer_user_id": "2088102169481075", |
||||||
|
"msg": "Success", |
||||||
|
"point_amount": "0.00", |
||||||
|
"gmt_payment": "2017-03-21 15:07:29", |
||||||
|
"total_amount": "20.00" |
||||||
|
}, |
||||||
|
"sign": "" |
||||||
|
} |
||||||
|
""" |
||||||
|
assert scene in ("bar_code", "wave_code"), 'scene not in ("bar_code", "wave_code")' |
||||||
|
|
||||||
|
biz_content = { |
||||||
|
"out_trade_no": out_trade_no, |
||||||
|
"scene": scene, |
||||||
|
"auth_code": auth_code, |
||||||
|
"subject": subject |
||||||
|
} |
||||||
|
biz_content.update(**kwargs) |
||||||
|
data = self.build_body("alipay.trade.pay", biz_content, notify_url=notify_url) |
||||||
|
response_type = "alipay_trade_pay_response" |
||||||
|
return self.verified_sync_response(data, response_type) |
||||||
|
|
||||||
|
def api_alipay_trade_refund(self, refund_amount, out_trade_no=None, trade_no=None, **kwargs): |
||||||
|
biz_content = { |
||||||
|
"refund_amount": refund_amount |
||||||
|
} |
||||||
|
biz_content.update(**kwargs) |
||||||
|
if out_trade_no: |
||||||
|
biz_content["out_trade_no"] = out_trade_no |
||||||
|
if trade_no: |
||||||
|
biz_content["trade_no"] = trade_no |
||||||
|
|
||||||
|
data = self.build_body("alipay.trade.refund", biz_content) |
||||||
|
response_type = "alipay_trade_refund_response" |
||||||
|
return self.verified_sync_response(data, response_type) |
||||||
|
|
||||||
|
def api_alipay_trade_cancel(self, out_trade_no=None, trade_no=None): |
||||||
|
""" |
||||||
|
response = { |
||||||
|
"alipay_trade_cancel_response": { |
||||||
|
"msg": "Success", |
||||||
|
"out_trade_no": "out_trade_no15", |
||||||
|
"code": "10000", |
||||||
|
"retry_flag": "N" |
||||||
|
} |
||||||
|
} |
||||||
|
""" |
||||||
|
|
||||||
|
assert (out_trade_no is not None) or (trade_no is not None), \ |
||||||
|
"Both trade_no and out_trade_no are None" |
||||||
|
|
||||||
|
biz_content = {} |
||||||
|
if out_trade_no: |
||||||
|
biz_content["out_trade_no"] = out_trade_no |
||||||
|
if trade_no: |
||||||
|
biz_content["trade_no"] = trade_no |
||||||
|
|
||||||
|
data = self.build_body("alipay.trade.cancel", biz_content) |
||||||
|
response_type = "alipay_trade_cancel_response" |
||||||
|
return self.verified_sync_response(data, response_type) |
||||||
|
|
||||||
|
def api_alipay_trade_close(self, out_trade_no=None, trade_no=None, operator_id=None): |
||||||
|
""" |
||||||
|
response = { |
||||||
|
"alipay_trade_close_response": { |
||||||
|
"code": "10000", |
||||||
|
"msg": "Success", |
||||||
|
"trade_no": "2013112111001004500000675971", |
||||||
|
"out_trade_no": "YX_001"a |
||||||
|
} |
||||||
|
} |
||||||
|
""" |
||||||
|
|
||||||
|
assert (out_trade_no is not None) or (trade_no is not None), \ |
||||||
|
"Both trade_no and out_trade_no are None" |
||||||
|
|
||||||
|
biz_content = {} |
||||||
|
if out_trade_no: |
||||||
|
biz_content["out_trade_no"] = out_trade_no |
||||||
|
if trade_no: |
||||||
|
biz_content["trade_no"] = trade_no |
||||||
|
if operator_id: |
||||||
|
biz_content["operator_id"] = operator_id |
||||||
|
|
||||||
|
data = self.build_body("alipay.trade.close", biz_content) |
||||||
|
response_type = "alipay_trade_close_response" |
||||||
|
return self.verified_sync_response(data, response_type) |
||||||
|
|
||||||
|
def api_alipay_trade_create( |
||||||
|
self, subject, out_trade_no, total_amount, notify_url=None, **kwargs |
||||||
|
): |
||||||
|
biz_content = { |
||||||
|
"subject": subject, |
||||||
|
"out_trade_no": out_trade_no, |
||||||
|
"total_amount": total_amount |
||||||
|
} |
||||||
|
biz_content.update(kwargs) |
||||||
|
data = self.build_body("alipay.trade.create", biz_content, notify_url=notify_url) |
||||||
|
response_type = "alipay_trade_create" |
||||||
|
return self.verified_sync_response(data, response_type) |
||||||
|
|
||||||
|
def api_alipay_trade_precreate( |
||||||
|
self, subject, out_trade_no, total_amount, notify_url=None, **kwargs |
||||||
|
): |
||||||
|
""" |
||||||
|
success response = { |
||||||
|
"alipay_trade_precreate_response": { |
||||||
|
"msg": "Success", |
||||||
|
"out_trade_no": "out_trade_no17", |
||||||
|
"code": "10000", |
||||||
|
"qr_code": "https://qr.alipay.com/bax03431ljhokirwl38f00a7" |
||||||
|
}, |
||||||
|
"sign": "" |
||||||
|
} |
||||||
|
|
||||||
|
failed response = { |
||||||
|
"alipay_trade_precreate_response": { |
||||||
|
"msg": "Business Failed", |
||||||
|
"sub_code": "ACQ.TOTAL_FEE_EXCEED", |
||||||
|
"code": "40004", |
||||||
|
"sub_msg": "订单金额超过限额" |
||||||
|
}, |
||||||
|
"sign": "" |
||||||
|
} |
||||||
|
|
||||||
|
""" |
||||||
|
biz_content = { |
||||||
|
"out_trade_no": out_trade_no, |
||||||
|
"total_amount": total_amount, |
||||||
|
"subject": subject |
||||||
|
} |
||||||
|
biz_content.update(**kwargs) |
||||||
|
data = self.build_body("alipay.trade.precreate", biz_content, notify_url=notify_url) |
||||||
|
response_type = "alipay_trade_precreate_response" |
||||||
|
return self.verified_sync_response(data, response_type) |
||||||
|
|
||||||
|
def api_alipay_trade_fastpay_refund_query( |
||||||
|
self, out_request_no, trade_no=None, out_trade_no=None |
||||||
|
): |
||||||
|
assert (out_trade_no is not None) or (trade_no is not None), \ |
||||||
|
"Both trade_no and out_trade_no are None" |
||||||
|
|
||||||
|
biz_content = {"out_request_no": out_request_no} |
||||||
|
if trade_no: |
||||||
|
biz_content["trade_no"] = trade_no |
||||||
|
else: |
||||||
|
biz_content["out_trade_no"] = out_trade_no |
||||||
|
|
||||||
|
data = self.build_body("alipay.trade.fastpay.refund.query", biz_content) |
||||||
|
response_type = "alipay_trade_fastpay_refund_query_response" |
||||||
|
return self.verified_sync_response(data, response_type) |
||||||
|
|
||||||
|
def api_alipay_fund_trans_toaccount_transfer( |
||||||
|
self, out_biz_no, payee_type, payee_account, amount, **kwargs |
||||||
|
): |
||||||
|
assert payee_type in ("ALIPAY_USERID", "ALIPAY_LOGONID"), "unknown payee type" |
||||||
|
biz_content = { |
||||||
|
"out_biz_no": out_biz_no, |
||||||
|
"payee_type": payee_type, |
||||||
|
"payee_account": payee_account, |
||||||
|
"amount": amount |
||||||
|
} |
||||||
|
biz_content.update(kwargs) |
||||||
|
data = self.build_body("alipay.fund.trans.toaccount.transfer", biz_content) |
||||||
|
response_type = "alipay_fund_trans_toaccount_transfer_response" |
||||||
|
return self.verified_sync_response(data, response_type) |
||||||
|
|
||||||
|
def api_alipay_fund_trans_order_query(self, out_biz_no=None, order_id=None): |
||||||
|
if out_biz_no is None and order_id is None: |
||||||
|
raise Exception("Both out_biz_no and order_id are None!") |
||||||
|
|
||||||
|
biz_content = {} |
||||||
|
if out_biz_no: |
||||||
|
biz_content["out_biz_no"] = out_biz_no |
||||||
|
if order_id: |
||||||
|
biz_content["order_id"] = order_id |
||||||
|
|
||||||
|
data = self.build_body("alipay.fund.trans.order.query", biz_content) |
||||||
|
response_type = "alipay_fund_trans_order_query_response" |
||||||
|
return self.verified_sync_response(data, response_type) |
||||||
|
|
||||||
|
def api_alipay_trade_order_settle( |
||||||
|
self, |
||||||
|
out_request_no, |
||||||
|
trade_no, |
||||||
|
royalty_parameters, |
||||||
|
**kwargs |
||||||
|
): |
||||||
|
biz_content = { |
||||||
|
"out_request_no": out_request_no, |
||||||
|
"trade_no": trade_no, |
||||||
|
"royalty_parameters": royalty_parameters, |
||||||
|
} |
||||||
|
biz_content.update(kwargs) |
||||||
|
data = self.build_body("alipay.trade.order.settle", biz_content) |
||||||
|
response_type = "alipay_trade_order_settle_response" |
||||||
|
return self.verified_sync_response(data, response_type) |
||||||
|
|
||||||
|
def api_alipay_ebpp_invoice_token_batchquery(self, invoice_token=None, scene=None): |
||||||
|
if scene is None: |
||||||
|
scene = "INVOICE_EXPENSE" |
||||||
|
if invoice_token is None: |
||||||
|
raise Exception("invoice_token is None!") |
||||||
|
|
||||||
|
biz_content = { |
||||||
|
"invoice_token": invoice_token, |
||||||
|
"scene": scene |
||||||
|
} |
||||||
|
data = self.build_body("alipay.ebpp.invoice.token.batchquery", biz_content) |
||||||
|
response_type = "alipay_ebpp_invoice_token_batchquery_response" |
||||||
|
return self.verified_sync_response(data, response_type) |
||||||
|
|
||||||
|
def _verify_and_return_sync_response(self, raw_string, response_type): |
||||||
|
""" |
||||||
|
return response if verification succeeded, raise exception if not |
||||||
|
|
||||||
|
As to issue #69, json.loads(raw_string)[response_type] should not be returned directly, |
||||||
|
use json.loads(plain_content) instead |
||||||
|
|
||||||
|
failed response is like this |
||||||
|
{ |
||||||
|
"alipay_trade_query_response": { |
||||||
|
"sub_code": "isv.invalid-app-id", |
||||||
|
"code": "40002", |
||||||
|
"sub_msg": "无效的AppID参数", |
||||||
|
"msg": "Invalid Arguments" |
||||||
|
} |
||||||
|
} |
||||||
|
""" |
||||||
|
response = json.loads(raw_string) |
||||||
|
# raise exceptions |
||||||
|
if "sign" not in response.keys(): |
||||||
|
result = response[response_type] |
||||||
|
raise AliPayException( |
||||||
|
code=result.get("code", "0"), |
||||||
|
message=raw_string |
||||||
|
) |
||||||
|
|
||||||
|
sign = response["sign"] |
||||||
|
|
||||||
|
# locate string to be signed |
||||||
|
plain_content = self._get_string_to_be_signed(raw_string, response_type) |
||||||
|
|
||||||
|
if not self._verify(plain_content, sign): |
||||||
|
raise AliPayValidationError |
||||||
|
return json.loads(plain_content) |
||||||
|
|
||||||
|
def verified_sync_response(self, data, response_type): |
||||||
|
url = self._gateway + "?" + self.sign_data(data) |
||||||
|
raw_string = urlopen(url, timeout=self._config.timeout).read().decode() |
||||||
|
return self._verify_and_return_sync_response(raw_string, response_type) |
||||||
|
|
||||||
|
def _get_string_to_be_signed(self, raw_string, response_type): |
||||||
|
""" |
||||||
|
https://docs.open.alipay.com/200/106120 |
||||||
|
从同步返回的接口里面找到待签名的字符串 |
||||||
|
""" |
||||||
|
balance = 0 |
||||||
|
start = end = raw_string.find("{", raw_string.find(response_type)) |
||||||
|
# 从response_type之后的第一个{的下一位开始匹配, |
||||||
|
# 如果是{则balance加1; 如果是}而且balance=0,就是待验签字符串的终点 |
||||||
|
for i, c in enumerate(raw_string[start + 1:], start + 1): |
||||||
|
if c == "{": |
||||||
|
balance += 1 |
||||||
|
elif c == "}": |
||||||
|
if balance == 0: |
||||||
|
end = i + 1 |
||||||
|
break |
||||||
|
balance -= 1 |
||||||
|
return raw_string[start:end] |
||||||
|
|
||||||
|
|
||||||
|
class AliPay(BaseAliPay): |
||||||
|
pass |
||||||
|
|
||||||
|
|
||||||
|
class DCAliPay(BaseAliPay): |
||||||
|
""" |
||||||
|
数字证书 (digital certificate) 版本 |
||||||
|
""" |
||||||
|
|
||||||
|
def __init__( |
||||||
|
self, |
||||||
|
appid, |
||||||
|
app_notify_url, |
||||||
|
app_private_key_string, |
||||||
|
app_public_key_cert_string, |
||||||
|
alipay_public_key_cert_string, |
||||||
|
alipay_root_cert_string, |
||||||
|
sign_type="RSA2", |
||||||
|
debug=False |
||||||
|
): |
||||||
|
""" |
||||||
|
初始化 |
||||||
|
DCAlipay( |
||||||
|
appid='', |
||||||
|
app_notify_url='http://example.com', |
||||||
|
app_private_key_string='', |
||||||
|
app_public_key_cert_string='', |
||||||
|
alipay_public_key_cert_sring='', |
||||||
|
aplipay_root_cert_string='', |
||||||
|
) |
||||||
|
""" |
||||||
|
self._app_public_key_cert_string = app_public_key_cert_string |
||||||
|
self._alipay_public_key_cert_string = alipay_public_key_cert_string |
||||||
|
self._alipay_root_cert_string = alipay_root_cert_string |
||||||
|
alipay_public_key_string = self.load_alipay_public_key_string() |
||||||
|
super().__init__( |
||||||
|
appid=appid, |
||||||
|
app_notify_url=app_notify_url, |
||||||
|
app_private_key_string=app_private_key_string, |
||||||
|
alipay_public_key_string=alipay_public_key_string, |
||||||
|
sign_type=sign_type, |
||||||
|
debug=debug |
||||||
|
) |
||||||
|
|
||||||
|
def api_alipay_open_app_alipaycert_download(self, alipay_cert_sn): |
||||||
|
""" |
||||||
|
下载支付宝证书 |
||||||
|
验签使用,支付宝公钥证书无感知升级机制 |
||||||
|
""" |
||||||
|
biz_content = { |
||||||
|
"alipay_cert_sn": alipay_cert_sn |
||||||
|
} |
||||||
|
data = self.build_body("alipay.open.app.alipaycert.download", biz_content) |
||||||
|
return self.sign_data(data) |
||||||
|
|
||||||
|
def build_body(self, *args, **kwargs): |
||||||
|
data = super().build_body(*args, **kwargs) |
||||||
|
data["app_cert_sn"] = self.app_cert_sn |
||||||
|
data["alipay_root_cert_sn"] = self.alipay_root_cert_sn |
||||||
|
if self._verbose: |
||||||
|
logger.debug("data to be signed") |
||||||
|
logger.debug(data) |
||||||
|
return data |
||||||
|
|
||||||
|
def load_alipay_public_key_string(self): |
||||||
|
cert = OpenSSL.crypto.load_certificate( |
||||||
|
OpenSSL.crypto.FILETYPE_PEM, self._alipay_public_key_cert_string |
||||||
|
) |
||||||
|
return OpenSSL.crypto.dump_publickey( |
||||||
|
OpenSSL.crypto.FILETYPE_PEM, cert.get_pubkey() |
||||||
|
).decode("utf-8") |
||||||
|
|
||||||
|
@staticmethod |
||||||
|
def get_cert_sn(cert): |
||||||
|
""" |
||||||
|
获取证书 SN 算法 |
||||||
|
""" |
||||||
|
cert = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, cert) |
||||||
|
certIssue = cert.get_issuer() |
||||||
|
name = 'CN={},OU={},O={},C={}'.format(certIssue.CN, certIssue.OU, certIssue.O, certIssue.C) |
||||||
|
string = name + str(cert.get_serial_number()) |
||||||
|
return hashlib.md5(string.encode()).hexdigest() |
||||||
|
|
||||||
|
@staticmethod |
||||||
|
def read_pem_cert_chain(certContent): |
||||||
|
"""解析根证书""" |
||||||
|
# 根证书中,每个 cert 中间有两个回车间隔 |
||||||
|
items = [i for i in certContent.split('\n\n') if i] |
||||||
|
load_cert = partial(OpenSSL.crypto.load_certificate, OpenSSL.crypto.FILETYPE_PEM) |
||||||
|
return [load_cert(c) for c in items] |
||||||
|
|
||||||
|
@staticmethod |
||||||
|
def get_root_cert_sn(rootCert): |
||||||
|
""" 根证书 SN 算法""" |
||||||
|
certs = DCAliPay.read_pem_cert_chain(rootCert) |
||||||
|
rootCertSN = None |
||||||
|
for cert in certs: |
||||||
|
try: |
||||||
|
sigAlg = cert.get_signature_algorithm() |
||||||
|
except ValueError: |
||||||
|
continue |
||||||
|
if sigAlg in CryptoAlgSet: |
||||||
|
certIssue = cert.get_issuer() |
||||||
|
name = 'CN={},OU={},O={},C={}'.format( |
||||||
|
certIssue.CN, certIssue.OU, certIssue.O, certIssue.C |
||||||
|
) |
||||||
|
string = name + str(cert.get_serial_number()) |
||||||
|
certSN = hashlib.md5(string.encode()).hexdigest() |
||||||
|
if not rootCertSN: |
||||||
|
rootCertSN = certSN |
||||||
|
else: |
||||||
|
rootCertSN = rootCertSN + '_' + certSN |
||||||
|
return rootCertSN |
||||||
|
|
||||||
|
@property |
||||||
|
def app_cert_sn(self): |
||||||
|
if not hasattr(self, "_app_cert_sn"): |
||||||
|
self._app_cert_sn = self.get_cert_sn(self._app_public_key_cert_string) |
||||||
|
return getattr(self, "_app_cert_sn") |
||||||
|
|
||||||
|
@property |
||||||
|
def alipay_root_cert_sn(self): |
||||||
|
if not hasattr(self, "_alipay_root_cert_sn"): |
||||||
|
self._alipay_root_cert_sn = self.get_root_cert_sn(self._alipay_root_cert_string) |
||||||
|
return getattr(self, "_alipay_root_cert_sn") |
||||||
|
|
||||||
|
def api_alipay_fund_trans_uni_transfer( |
||||||
|
self, out_biz_no, identity_type, identity, trans_amount, name=None, **kwargs |
||||||
|
): |
||||||
|
""" |
||||||
|
单笔转账接口, 只支持公钥证书模式 |
||||||
|
文档地址: https://opendocs.alipay.com/apis/api_28/alipay.fund.trans.uni.transfer |
||||||
|
""" |
||||||
|
assert identity_type in ("ALIPAY_USER_ID", "ALIPAY_LOGON_ID"), "unknown identity type" |
||||||
|
|
||||||
|
biz_content = { |
||||||
|
"payee_info": { |
||||||
|
"identity": identity, |
||||||
|
"identity_type": identity_type, |
||||||
|
}, |
||||||
|
"out_biz_no": out_biz_no, |
||||||
|
"trans_amount": trans_amount, |
||||||
|
"product_code": "TRANS_ACCOUNT_NO_PWD", |
||||||
|
"biz_scene": "DIRECT_TRANSFER", |
||||||
|
} |
||||||
|
biz_content["payee_info"]["name"] = name if name else None |
||||||
|
biz_content.update(kwargs) |
||||||
|
|
||||||
|
response_type = "alipay_fund_trans_uni_transfer_response" |
||||||
|
data = self.build_body("alipay.fund.trans.uni.transfer", biz_content) |
||||||
|
return self.verified_sync_response(data, response_type) |
||||||
|
|
||||||
|
|
||||||
|
class ISVAliPay(BaseAliPay): |
||||||
|
|
||||||
|
def __init__( |
||||||
|
self, |
||||||
|
appid, |
||||||
|
app_notify_url, |
||||||
|
app_private_key_string=None, |
||||||
|
alipay_public_key_string=None, |
||||||
|
sign_type="RSA2", |
||||||
|
debug=False, |
||||||
|
app_auth_token=None, |
||||||
|
app_auth_code=None |
||||||
|
): |
||||||
|
if not app_auth_token and not app_auth_code: |
||||||
|
raise Exception("Both app_auth_code and app_auth_token are None !!!") |
||||||
|
|
||||||
|
self._app_auth_token = app_auth_token |
||||||
|
self._app_auth_code = app_auth_code |
||||||
|
super().__init__( |
||||||
|
appid, |
||||||
|
app_notify_url, |
||||||
|
app_private_key_string=app_private_key_string, |
||||||
|
alipay_public_key_string=alipay_public_key_string, |
||||||
|
sign_type=sign_type, |
||||||
|
debug=debug |
||||||
|
) |
||||||
|
|
||||||
|
@property |
||||||
|
def app_auth_token(self): |
||||||
|
# 没有则换取token |
||||||
|
if not self._app_auth_token: |
||||||
|
result = self.api_alipay_open_auth_token_app(self._app_auth_code) |
||||||
|
self._app_auth_token = result.get("app_auth_token") |
||||||
|
if not self._app_auth_token: |
||||||
|
msg = "Get auth token by auth code failed: {}" |
||||||
|
raise Exception(msg.format(self._app_auth_code)) |
||||||
|
return self._app_auth_token |
||||||
|
|
||||||
|
def build_body(self, *args, **kwargs): |
||||||
|
data = super().build_body(*args, **kwargs) |
||||||
|
if self._app_auth_token: |
||||||
|
data["app_auth_token"] = self._app_auth_token |
||||||
|
if self._verbose: |
||||||
|
logger.debug("data to be signed") |
||||||
|
logger.debug(data) |
||||||
|
|
||||||
|
return data |
||||||
|
|
||||||
|
def api_alipay_open_auth_token_app(self, refresh_token=None): |
||||||
|
""" |
||||||
|
response = { |
||||||
|
"code": "10000", |
||||||
|
"msg": "Success", |
||||||
|
"app_auth_token": "201708BB28623ce3d10f4f62875e9ef5cbeebX07", |
||||||
|
"app_refresh_token": "201708BB108a270d8bb6409890d16175a04a7X07", |
||||||
|
"auth_app_id": "appid", |
||||||
|
"expires_in": 31536000, |
||||||
|
"re_expires_in": 32140800, |
||||||
|
"user_id": "2088xxxxx |
||||||
|
} |
||||||
|
""" |
||||||
|
|
||||||
|
if refresh_token: |
||||||
|
biz_content = { |
||||||
|
"grant_type": "refresh_token", |
||||||
|
"refresh_token": refresh_token |
||||||
|
} |
||||||
|
else: |
||||||
|
biz_content = { |
||||||
|
"grant_type": "authorization_code", |
||||||
|
"code": self._app_auth_code |
||||||
|
} |
||||||
|
data = self.build_body( |
||||||
|
"alipay.open.auth.token.app", |
||||||
|
biz_content, |
||||||
|
) |
||||||
|
response_type = "alipay_open_auth_token_app_response" |
||||||
|
return self.verified_sync_response(data, response_type) |
||||||
|
|
||||||
|
def api_alipay_open_auth_token_app_query(self): |
||||||
|
biz_content = {"app_auth_token": self.app_auth_token} |
||||||
|
data = self.build_body( |
||||||
|
"alipay.open.auth.token.app.query", |
||||||
|
biz_content, |
||||||
|
) |
||||||
|
response_type = "alipay_open_auth_token_app_query_response" |
||||||
|
return self.verified_sync_response(data, response_type) |
@ -0,0 +1,10 @@ |
|||||||
|
#!/usr/bin/env python |
||||||
|
# coding: utf-8 |
||||||
|
""" |
||||||
|
compat.py |
||||||
|
~~~~~~~~~~ |
||||||
|
|
||||||
|
""" |
||||||
|
from urllib.parse import quote_plus |
||||||
|
from urllib.request import urlopen |
||||||
|
from base64 import decodebytes, encodebytes |
@ -0,0 +1,26 @@ |
|||||||
|
#!/usr/bin/env python |
||||||
|
# coding: utf-8 |
||||||
|
""" |
||||||
|
exceptions.py |
||||||
|
~~~~~~~~~~ |
||||||
|
|
||||||
|
""" |
||||||
|
|
||||||
|
|
||||||
|
class AliPayException(Exception): |
||||||
|
def __init__(self, code, message): |
||||||
|
self.__code = code |
||||||
|
self.__message = message |
||||||
|
|
||||||
|
def to_unicode(self): |
||||||
|
return "AliPayException: code:{}, message:{}".format(self.__code, self.__message) |
||||||
|
|
||||||
|
def __str__(self): |
||||||
|
return self.to_unicode() |
||||||
|
|
||||||
|
def __repr__(self): |
||||||
|
return self.to_unicode() |
||||||
|
|
||||||
|
|
||||||
|
class AliPayValidationError(Exception): |
||||||
|
pass |
@ -0,0 +1,26 @@ |
|||||||
|
import logging |
||||||
|
import logging.config |
||||||
|
|
||||||
|
logging.config.dictConfig({ |
||||||
|
"version": 1, |
||||||
|
"disable_existing_loggers": False, |
||||||
|
"formatters": { |
||||||
|
"standard": { |
||||||
|
"format": "%(asctime)s [%(levelname)s] %(name)s: %(message)s" |
||||||
|
}, |
||||||
|
}, |
||||||
|
"handlers": { |
||||||
|
"console": { |
||||||
|
"level": "DEBUG", |
||||||
|
"formatter": "standard", |
||||||
|
"class": "logging.StreamHandler", |
||||||
|
}, |
||||||
|
}, |
||||||
|
"loggers": { |
||||||
|
"python-alipay-sdk": { |
||||||
|
"handlers": ["console"], |
||||||
|
"level": "DEBUG", |
||||||
|
} |
||||||
|
} |
||||||
|
}) |
||||||
|
logger = logging.getLogger("python-alipay-sdk") |
@ -0,0 +1,10 @@ |
|||||||
|
""" |
||||||
|
alipay/utils.py |
||||||
|
~~~~~~~~~~ |
||||||
|
|
||||||
|
""" |
||||||
|
|
||||||
|
|
||||||
|
class AliPayConfig: |
||||||
|
def __init__(self, timeout=15): |
||||||
|
self.timeout = timeout |
@ -0,0 +1,73 @@ |
|||||||
|
#!/usr/bin/env python |
||||||
|
# -*- coding:utf-8 -*- |
||||||
|
# project: 3月 |
||||||
|
# author: NinEveN |
||||||
|
# date: 2021/3/18 |
||||||
|
# pip install alipay-sdk-python==3.3.398 |
||||||
|
|
||||||
|
# !/usr/bin/env python |
||||||
|
# -*- coding: utf-8 -*- |
||||||
|
|
||||||
|
|
||||||
|
from api.utils.alipay import AliPay |
||||||
|
from api.utils.alipay.utils import AliPayConfig |
||||||
|
from fir_ser.settings import PAY_CONFIG |
||||||
|
from datetime import datetime, timedelta |
||||||
|
from api.utils.storage.caches import update_order_info |
||||||
|
import json |
||||||
|
import logging |
||||||
|
|
||||||
|
logger = logging.getLogger(__file__) |
||||||
|
|
||||||
|
|
||||||
|
class Alipay(object): |
||||||
|
def __init__(self): |
||||||
|
self.ali_config = PAY_CONFIG.get("ALI") |
||||||
|
self.alipay = self.__get_ali_pay() |
||||||
|
|
||||||
|
def __get_ali_pay(self): |
||||||
|
return AliPay( |
||||||
|
appid=self.ali_config.get("APP_ID"), |
||||||
|
app_notify_url=self.ali_config.get("APP_NOTIFY_URL"), |
||||||
|
app_private_key_string=self.ali_config.get("APP_PRIVATE_KEY"), |
||||||
|
alipay_public_key_string=self.ali_config.get("ALI_PUBLIC_KEY"), |
||||||
|
sign_type="RSA2", # RSA 或者 RSA2 |
||||||
|
debug=False, # 默认False |
||||||
|
verbose=True, # 输出调试数据 |
||||||
|
config=AliPayConfig(timeout=15) # 可选, 请求超时时间 |
||||||
|
) |
||||||
|
|
||||||
|
def get_pay_pc_url(self, out_trade_no, total_amount, passback_params): |
||||||
|
time_expire = (datetime.now() + timedelta(days=1)).strftime("%Y-%m-%d %H:%M:%S") |
||||||
|
order_string = self.alipay.api_alipay_trade_page_pay( |
||||||
|
out_trade_no=out_trade_no, |
||||||
|
total_amount=total_amount, |
||||||
|
subject=self.ali_config.get("SUBJECT"), |
||||||
|
body="充值 %s 元" % total_amount, |
||||||
|
time_expire=time_expire, |
||||||
|
return_url=self.ali_config.get("RETURN_URL"), |
||||||
|
notify_url=self.ali_config.get("APP_NOTIFY_URL"), |
||||||
|
passback_params=json.dumps(passback_params) |
||||||
|
) |
||||||
|
|
||||||
|
return "https://openapi.alipay.com/gateway.do?%s" % order_string |
||||||
|
|
||||||
|
def valid_order(self, data): |
||||||
|
signature = data.pop("sign") |
||||||
|
success = self.alipay.verify(data, signature) |
||||||
|
if success and data["trade_status"] in ("TRADE_SUCCESS", "TRADE_FINISHED"): |
||||||
|
logger.info("付款成功,等待下一步验证 %s" % data) |
||||||
|
app_id = data.get("app_id", "") |
||||||
|
if app_id == self.ali_config.get("APP_ID"): |
||||||
|
out_trade_no = data.get("out_trade_no", "") # 服务器订单号 |
||||||
|
passback_params = data.get("passback_params", "") |
||||||
|
if passback_params: |
||||||
|
ext_parms = json.loads(passback_params) |
||||||
|
user_id = ext_parms.get("user_id") |
||||||
|
payment_number = data.get("trade_no", "") |
||||||
|
return update_order_info(user_id, out_trade_no, payment_number, 1) |
||||||
|
else: |
||||||
|
logger.error("passback_params %s user_id not exists" % passback_params) |
||||||
|
else: |
||||||
|
logger.error("APP_ID 校验失败 response: %s server: %s" % (app_id, self.ali_config.get("APP_ID"))) |
||||||
|
return False |
Loading…
Reference in new issue