python 支付宝营销活动现金红包开发接入流程-含接口调用加签

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)

现金红包创建完成之后在 data.text 响应中会有一个订单支付的url,需要登录这个订单管理后台支付这个现金红包活动的订单才算创建完成,否则红包活动是无效的

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 注意事项

需要是签约的商户才能使用转账功能,如果商户未签约需要进行签约

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值