支付宝支付(Python版)

App支付请求参数说明(必传字段)

appid(应用id)
method (接口名称,若是APP支付接口值是:alipay.trade.app.pay)
format (json)
charset (UTF-8)
sign_type (签名算法类型RSA2和RSA,推荐使用RSA)
sign (签名)
timestamp (请求时间 格式是:2014-07-24 03:07:50)
version (版本号:1.0)
notify_url (支付宝服务器主动通知商户服务器里指定的页面)
biz_content (业务请求参数)
业务参数必传字段
subject(商品标题)
out_trade_no(商户网站唯一订单号)
total_amount (价钱,字符串类型,单位是元)
product_code (销售商品码: APP支付的固定值:QUICK_MSECURITY_PAY)

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
biz_content = {
'subject': subject,
'total_amount': '%.2f' % float(total_amount),
'product_code': 'QUICK_MSECURITY_PAY',
'out_trade_no': out_trade_no,
}

payload = {
'app_id': app_id,
'method': 'alipay.trade.app.pay',
'charset': 'UTF-8',
'format': 'json',
'sign_type': 'RSA2',
'timestamp': timestamp,
'version': '1.0',
'notify_url': notify_url,
# 关于biz_content一定要对biz_content进行json.dumps操作,进行urlencode、生成sign时,会把biz_content整体当成一个value,所以要求该值是一个严格的json.
'biz_content': json.dumps(biz_content, separators=(',', ':')), # separators=(',', ':')可以用来去掉json中的空格来压缩json
}

请求示例的获取

请求参数按照key=value&key=value方式拼接的未签名原始字符串:

1
app_id=2015052600090779&biz_content={"timeout_express":"30m","product_code":"QUICK_MSECURITY_PAY","total_amount":"0.01","subject":"1","body":"我是测试数据","out_trade_no":"IQJZSRC1YMQB5HU"}&charset=utf-8&format=json&method=alipay.trade.app.pay&notify_url=http://domain.merchant.com/payment_notify&sign_type=RSA2&timestamp=2016-08-25 20:26:31&version=1.0

对原始字符串进行签名

  1. 筛选并排序
    获取所有请求参数,不包括字节类型参数,如文件、字节流,剔除sign字段,剔除值为空的参数,并按照第一个字符的键值ASCII码递增排序(字母升序排序),如果遇到相同字符则按照第二个字符的键值ASCII码递增排序,以此类推
  2. 拼接
    将排序后的参数与其对应值,组合成“参数=参数值”的格式,并且把这些参数用&字符连接起来,此时生成的字符串为待签名字符串
  3. 调用签名函数
    使用各自语言对应的SHA256WithRSA(对应sign_type为RSA2)或SHA1WithRSA(对应sign_type为RSA)签名函数利用商户私钥对待签名字符串进行签名,并进行Base64编码
1
2
3
4
5
6
7
8
# 对payload数据进行排序和拼接
def order_data(payload):
lst = []
for key, value in payload.items():
lst.append("{}={}".format(key, value))
lst.sort()
order_payload = "&".join(lst)
return order_payload
1
2
3
4
5
6
7
8
import rsa
import base64

def sign(payload, private_key=None):
private_key = rsa.PrivateKey._load_pkcs1_pem(private_key)
sign = rsa.sign(payload.encode('utf-8'), private_key, SIGN_TYPE)
b64sing = base64.encodebytes(sign).decode("utf8").replace("\n", "")
return b64sing

把生成的签名赋值给sign参数,拼接到请求参数中

最后对请求字符串的所有一级value(biz_content作为一个value)进行encode,编码格式按请求串中的charset为准,没传charset按UTF-8处理,获得最终的请求字符串

1
2
3
4
5
6
7
8
9
10
11
12
13
#  对所有value值进行urlencode操作
from urllib.parse import quote_plus

def urlencode_data(payload, sign):
lst = []
for key, value in payload.items():
lst.append("{}={}".format(
key, quote_plus(value, encoding='utf-8')))
lst.sort()
order_payload = "&".join(lst)
if sign:
order_payload += "&sign=%s" % quote_plus(sign, encoding='utf-8')
return order_payload

进行过urlencode的数据

1
2
app_id=2016081600254796&biz_content=%7B%22subject%22%3A%22%5Cu6d4b%5Cu8bd5%22%2C%22total_amount%22%3A%220.01%22%2C%22product_code%22%3A%22QUICK_MSECURITY_PAY%22%2C%22out_trade_no%22%3A%22alipay20171014%22%7D&charset=UTF-8&format=json&method=alipay.trade.app.pay&notify_url=http%3A%2F%2Fdongwu-inc.com%3A10014%2Falipay%2Fnotify%2F&sign_type=RSA2&timestamp=2017-10-14+14%3A03%3A50&version=1.0&sign=PbDyqQ%2BuAdJXWmEuJSFRq%2FiEPg7CJGbsf%2FexHOSG2%2FiW2TGigeFRKhZJNpxcImdkdcFLmFLlEhTNotfJKMDfhjFx0TJH0vVxQHECnkO5XpVpJ%2F2YZbj3fi8UPe2N%2FiZ9tJ5LBp%2Bj%2BXmBcW55lvsUA5s09bTgn5wU%2BhY4kiB6YO0U1CqARyPd6b2Nhs2A6jN4utyjzoUOvDQwXksmDB48qJSnJfdcfbxP1AtZ4nAVY2vtWZHPtQbj5NqY6jsNd2WxCRVqwHYdMZf0lHPwXa7ygVeALtIDhw%2FYXepOmTNJkYJs4sIpB8p9rpSgoY5A46SDThM90Vg5Q4c0d4kxWnG9%2FA%3D%3D

注意

  1. 需对biz_content进行json.dumps操作将其转化为严格的json格式,生成sign和urlencode时都需要该操作;
  2. 向前端传值时,需对整个数据的value(拼接字符串的value值)进行urlencode,其中包括sign的值;
  3. 因对value进行urlencode, 所以需要biz_content为严格的json格式,单引号和双引号的urlencode不同,导致签名失败的原因大都是该位置出现了问题。