You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
flyapps/fir_ser/api/utils/ali/pay/basic.py

199 lines
5.2 KiB

#! /usr/bin/env python
# -*- coding: utf-8 -*-
# Date: 2018/8/2
import json
import inspect
from urllib.parse import quote_plus
from Crypto.PublicKey import RSA
from django.utils.timezone import now
from .. import BaseAli
from ..tools import calculate_signature
from .api.base import BaseAliPayAPI
from . import api
def _is_api_endpoint(instance):
return issubclass(instance.__class__, BaseAliPayAPI)
class AliPay(BaseAli):
# 在构造函数中初始化
API_BASE_URL = ""
# PC支付
pc = api.AliPage()
# Wap支付
wap = api.AliWap()
# App支付
app = api.AliApp()
# 转账
transfer = api.AliTransfer()
# 统一收单相关
order = api.AliOrder()
def __init__(
self, app_id, app_private_key_path, ali_public_key_path,
notify_url, return_url, debug,
):
# 如果是调试模式,则用调试api
if debug:
setattr(self, "API_BASE_URL", "https://openapi.alipaydev.com/gateway.do")
else:
setattr(self, "API_BASE_URL", "https://openapi.alipay.com/gateway.do")
# 暂时用不到父类方法参数(设置默认值)
super(AliPay, self).__init__(timeout=3, auto_retry=True)
self.app_id = app_id
self.notify_url = notify_url
self.return_url = return_url
# 加载应用的私钥
with open(app_private_key_path) as fp:
self.app_private_key = RSA.importKey(fp.read())
# 加载支付宝公钥
with open(ali_public_key_path) as fp:
self.ali_public_key = RSA.importKey(fp.read())
def __new__(cls, *args, **kwargs):
self = super(AliPay, cls).__new__(cls)
api_endpoints = inspect.getmembers(self, _is_api_endpoint)
for name, api_ins in api_endpoints:
api_cls = type(api_ins)
api_ins = api_cls(self)
setattr(self, name, api_ins)
return self
def request(self, method, url_or_endpoint, **kwargs):
if not url_or_endpoint.startswith(('http://', 'https://')):
api_base_url = kwargs.pop('api_base_url', self.API_BASE_URL)
url = '{base}{endpoint}'.format(
base=api_base_url,
endpoint=url_or_endpoint
)
else:
url = url_or_endpoint
res = self._http.request(
method=method,
url=url,
**kwargs
)
res.raise_for_status()
return self._handle_result(res)
def _handle_result(self, res):
"""解析请求结果并校验签名
"""
return res.json()
def get_url_params(self, data):
data.pop("sign", None)
# 排序后[(k, v), ...]
ordered_items = sorted(
((k, v if not isinstance(v, dict) else json.dumps(v, separators=(',', ':')))
for k, v in data.items())
)
# 拼接成待签名的字符串
unsigned_string = "&".join("{0}={1}".format(k, v) for k, v in ordered_items)
# 对上一步得到的字符串进行签名
sign = calculate_signature(unsigned_string.encode("utf-8"), self.app_private_key)
# 处理URL
quoted_string = "&".join("{0}={1}".format(k, quote_plus(v)) for k, v in ordered_items)
# 添加签名,获得最终的订单信息字符串
signed_string = quoted_string + "&sign=" + quote_plus(sign)
return signed_string
def build_body(self, method, biz_content, notify_url=None, return_url=None):
"""构建请求体
公有请求体 + 业务请求体
Parameters
----------
method : string
请求API
biz_content: dict
业务参数
notify_url: string, default: None
异步通知地址
return_url: string, default: None
同步地址
Returns
-------
请求参数: dict
"""
data = {
"app_id": self.app_id,
"method": method,
"charset": "utf-8",
"sign_type": "RSA2",
"timestamp": now().strftime("%Y-%m-%d %H:%M:%S"),
"version": "1.0",
"biz_content": biz_content
}
# 同步回调
if notify_url is not None:
data["notify_url"] = notify_url
# 异步回调
if return_url is not None:
data["return_url"] = return_url
return data
def generate_url(self, method, biz_content, notify_url=None, return_url=None):
"""构建请求体.
公有请求体 + 业务请求体
Parameters
----------
method : string
请求API
biz_content: dict
业务参数
notify_url: string, default: None
异步通知 `url`
return_url: string, default: None
同步通知 `url`
Returns
-------
string
"""
if notify_url is None:
notify_url = self.notify_url
if return_url is None:
return_url = self.return_url
body = self.build_body(method, biz_content, notify_url, return_url)
params = self.get_url_params(body)
return "{}?{}".format(self.API_BASE_URL, params)