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