1 创建网页/移动应用
2 配置接口加签方式
涉及到金额的需要上传证书,在上传页面有教程,
在支付宝开放平台秘钥工具中生成CSR证书,会自动保存应用公钥和私钥到电脑上,调用支付宝接口需要应用私钥进行加签
上传完CSR证书后会有三个证书下载, 分别是: alipayRootCert.crt(支付宝根证书), alipayCertPublicKey_RSA2.crt(支付宝证书公钥), appCertPublicKey_网站应用id.crt(网站应用公钥),下载到本地,后续调用接口的时候加签要用到
3 绑定红包产品
发红包需要绑定红包、营销活动红包产品功能
配置完成后,对该网站应用进行上线申请,审核通过后就可以使用了
4 代码实现
现金红包接口文档: https://opendocs.alipay.com/open/029yy9
4.1 实现加签
可参考 https://blog.csdn.net/tm_tsm/article/details/105124809 文章,有点区别在 encode_for_sign 函数中,最后拼接才需要 urllib quote
import time
import binascii
import json
import urllib
import urllib.parse
from Cryptodome.Hash import SHA, SHA256
from Cryptodome.PublicKey import RSA
from Cryptodome.Signature import PKCS1_v1_5
prv_key = "-----BEGIN RSA PRIVATE KEY-----\n" \
"应用私钥" \
"\n-----END RSA PRIVATE KEY-----"
class SignRSA:
MAXLINESIZE = 76 # Excluding the CRLF
MAXBINSIZE = (MAXLINESIZE // 4) * 3
def __init__(self, **kwargs):
self.kwargs = kwargs
self.sign_type = "RSA2" # rsa 用sha, rsa2方式用SHA256
self.private_key = prv_key
@staticmethod
def get_ordered_data(data: dict):
# 还没生成签名 前不能传 sign 和 sign_type 进行排序
complex_keys = [k for k, v in data.items() if isinstance(v, dict)]
# 将字典类型的数据dump出来
for key in complex_keys:
data[key] = json.dumps(data[key], separators=(',', ':'))
return sorted([(k, v) for k, v in data.items()])
@staticmethod
def encode_for_sign(ordered_items, quote=False):
ordered_items.sort()
if quote:
unsigned_str = "&".join('''{}={}'''.format(k, urllib.parse.quote(v)) for k, v in ordered_items)
else:
unsigned_str = "&".join('''{}={}'''.format(k, v) for k, v in ordered_items)
return unsigned_str.encode('utf-8').decode('unicode_escape')
def verify_with_public_key(self, sign):
"""
:parameter sign:
The signature that needs to be validated.
:type sign: byte string
"""
ordered_item = self.get_ordered_data(self.kwargs)
params = "&".join(u"{}={}".format(k, v) for k, v in ordered_item)
# 公钥验签
signer = PKCS1_v1_5.new(RSA.importKey(self.public_key))
if self.sign_type == 'RSA':
msg_hash = SHA.new()
else:
msg_hash = SHA256.new()
msg_hash.update(params.encode("utf8"))
# sign = urllib.parse.unquote_plus(sign)
# sign = self.decodebytes(sign.encode()) # 反操作:base64 编码,转换为unicode表示并移除回车
return signer.verify(msg_hash, self.decodebytes(sign.encode("utf8"))) # true / false
def sign_with_private_key(self):
ordered_item = self.get_ordered_data(self.kwargs)
unsigned_str = self.encode_for_sign(ordered_item)
signer = PKCS1_v1_5.new(RSA.importKey(self.private_key))
print("加签参数: ", unsigned_str)
# rsa 用sha, rsa2方式用SHA256
if self.sign_type == 'RSA':
rand_hash = SHA.new()
else:
rand_hash = SHA256.new()
rand_hash.update(unsigned_str.encode())
signature = signer.sign(rand_hash)
# base64 编码,转换为unicode表示并移除回车
sign = self.encodebytes(signature).decode("utf8").replace("\n", "")
data = self.kwargs
data['sign'] = sign
data['sign_type'] = self.sign_type
ordered_data = self.get_ordered_data(data)
print("加签结果:", sign)
# 在最后拼接的时候才需要 urllib quote
return f'''{self.encode_for_sign(ordered_data, quote=True)}'''
def encodebytes(self, s):
"""Encode a bytestring into a bytes object containing multiple lines
of base-64 data."""
self._input_type_check(s)
pieces = []
for i in range(0, len(s), self.MAXBINSIZE):
chunk = s[i: i + self.MAXBINSIZE]
pieces.append(binascii.b2a_base64(chunk))
return b"".join(pieces)
def decodebytes(self, byte_str):
"""Decode a bytestring of base-64 data into a bytes object."""
self._input_type_check(byte_str)
return binascii.a2b_base64(byte_str)
@staticmethod
def _input_type_check(s):
try:
m = memoryview(s)
except TypeError as err:
msg = "expected bytes-like object, not %s" % s.__class__.__name__
raise TypeError(msg) from err
if m.format not in ('c', 'b', 'B'):
msg = ("expected single byte elements, not %r from %s" %
(m.format, s.__class__.__name__))
raise TypeError(msg)
if m.ndim != 1:
msg = ("expected 1-D data, not %d-D data from %s" %
(m.ndim, s.__class__.__name__))
raise TypeError(msg)
4.2 支付宝根证书sn码、网站应用sn码获取
代码来源: https://blog.csdn.net/weixin_37309049/article/details/105319729
import OpenSSL
import hashlib
import re
def md5(string):
return hashlib.md5(string.encode('utf-8')).hexdigest()
# 应用公钥证书序列号
def get_app_cert_cn(cert_str=None):
cert_str = cert_str or open("appCertPublicKey_网站应用id.crt").read()
cert = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, cert_str)
try:
res = cert.get_signature_algorithm()
# 根据其他语言算法 应该剔除不是sha加密的部分python2 可以用r'sha.+WithRSAEncryption' 但是python3必须是b'sha.+WithRSAEncryption'
if not re.match(b'sha.+WithRSAEncryption', res):
return None
except:
return None
cert_issue = cert.get_issuer()
op = ''
b = list(cert_issue.get_components())
# 证书签发机构排序方式应该是倒序的
for i in range(len(b)):
a = list(b[len(b) - 1 - i])
# 在Python3中直接读取的a[0]为bytes,会影响加密结果,进行decode,兼容python2
opp = "{}={}".format(a[0].decode(), a[1].decode())
op = op + opp + ','
return md5(op[:-1] + str(cert.get_serial_number()))
# 根证书序列号
def get_root_cn_sn():
root_cert = open("alipayRootCert.crt").read()
cert_list = root_cert.split('-----BEGIN CERTIFICATE-----')
root_cert_sn = ''
for i in cert_list:
# print i, len(i)
if not len(i):
continue
cert_sn = get_app_cert_cn('-----BEGIN CERTIFICATE-----' + i)
if cert_sn is not None:
root_cert_sn = root_cert_sn + cert_sn + '_'
return root_cert_sn[:-1]
if __name__ == "__main__":
print("根证书sn:", get_root_cn_sn())
print("应用证书sn:", get_app_cert_cn())
4.3 创建现金红包
import time
import requests
# 导入上面加签的代码
import alipay_countersign
date_str = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime())
alipay_params = {
"app_id": "网站应用id",
"method": "alipay.marketing.campaign.cash.create",
"charset": 'UTF-8',
"sign_type": "RSA2",
"timestamp": date_str,
"version": "1.0",
"alipay_root_cert_sn": "支付宝根证书sn码",
"app_cert_sn": "网站应用sn码",
"biz_content": {
"coupon_name": "answer activity", # 营销活动名称,暂时只能是英文,中文会报验签错误,应该是上面加签的代码还有点问题
"prize_type": "fixed",
"total_money": "10.00",
"total_num": "50",
"prize_msg": "answer activity", # 红包详情页展示的文案
"start_time": "NowTime",
"end_time": "2023-03-10 12:00:00"
}
}
sign_rsa = alipay_countersign.SignRSA(**alipay_params)
order_info = sign_rsa.sign_with_private_key()
url = "https://openapi.alipay.com/gateway.do?" + order_info
headers = {
"content-type": "application/json"
}
data = requests.post(url, headers=headers)
print(data)
print(data.text)
4.4 红包发放
import time
import requests
# 加签代码
import alipay_countersign
date_str = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime())
alipay_params = {
"app_id": "网站应用id",
"method": "alipay.marketing.campaign.cash.trigger",
"charset": 'UTF-8',
"sign_type": "RSA2",
"timestamp": date_str,
"version": "1.0",
"alipay_root_cert_sn": "支付宝根证书sn码",
"app_cert_sn": "网站应用sn码",
"biz_content": {
"user_id": "用户id",
"crowd_no": "创建现金红包活动的活动号",
}
}
sign_rsa = alipay_countersign.SignRSA(**alipay_params)
order_info = sign_rsa.sign_with_private_key()
url = "https://openapi.alipay.com/gateway.do?" + order_info
headers = {
"content-type": "application/json"
}
data = requests.post(url, headers=headers)
print(data)
print(data.text)
红包发放成功后在支付宝app–账单,或者红包–我的红包中可以看到红包流水,个人商家发的红包是没有支付消息通知的,需要在这两个地方才能看到流水详情
4.5 用户id获取
import time
import requests
# 加签
import alipay_countersign
date_str = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime())
alipay_params = {
"app_id": "网站应用id",
"method": "alipay.system.oauth.token",
"charset": 'UTF-8',
"sign_type": "RSA2",
"timestamp": date_str,
"version": "1.0",
"grant_type": "authorization_code",
"alipay_root_cert_sn": "支付宝根证书sn码",
"app_cert_sn": "网站应用sn码",
"code": "f0361800bf764a0c858dbc87671cVX20" # h5获取到的auth_code
}
sign_rsa = alipay_countersign.SignRSA(**alipay_params)
order_info = sign_rsa.sign_with_private_key()
url = "https://openapi.alipay.com/gateway.do?" + order_info
headers = {
"content-type": "application/json"
}
data = requests.post(url, headers=headers)
print(data)
print(data.text)
4.6 h5获取授权code
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta
name="viewport"
content="width=750,user-scalable=yes,target-densitydpi=device-dpi"
/>
<title></title>
<link href="css/ttqhb.css" rel="stylesheet" type="text/css" />
<script src="js/jquery.min.js"></script>
<script src="js/index.js"></script>
</head>
<body style="background: #c72211"></body>
<a onclick="get_red_packet()"><img src="images/btn.png" class="btnimg" /></a>
<img src="images/redbao.jpg" class="img100" />
<script>
function getQueryString(name) {
var reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)", "i");
var r = window.location.search.substr(1).match(reg);
if (r != null) {
return unescape(r[2]);
}
return null;
}
var url =
"https://openauth.alipay.com/oauth2/publicAppAuthorize.htm?app_id=网站应用id&scope=auth_base&redirect_uri=";
var red_uri = encodeURIComponent(
"https://baidu.com//index.html" // 授权成功回跳地址
);
url += red_uri;
var auth_code = getQueryString("auth_code");
if (!auth_code) {
window.location.href = url;
}
</script>
</html>
在支付宝上进入该页面会自动进行授权回调,授权成功后回调改url页面,在url参数中就会有授权auth_code
5 注意事项
需要是签约的商户才能使用转账功能,如果商户未签约需要进行签约