1、生成(验证)签名的代码(安装包:pycryptodomex==3.9.4。有个sdk:python-alipay-sdk),密钥类型RSA 用SHA。苹果手机验证通过,Android手机应该也一样
import binascii
import json
import urllib
from Cryptodome.Hash import SHA, SHA256
from Cryptodome.PublicKey import RSA
from Cryptodome.Signature import PKCS1_v1_5
from myapp.alipay.configs import alipay_public_key_begin_end, alipay_merchant_private_key_begin_end, sign_type
from utils.helper import SimException
class SignRSA(object):
MAXLINESIZE = 76 # Excluding the CRLF
MAXBINSIZE = (MAXLINESIZE // 4) * 3
def __init__(self, **kwargs):
self.kwargs = kwargs
self.sign_type = sign_type # rsa 用sha, rsa2方式用SHA256
self.private_key = alipay_merchant_private_key_begin_end
self.public_key = alipay_public_key_begin_end # alipay_sand_public_key_begin_end
if sign_type not in ("RSA", "RSA2"):
raise SimException("Unsupported sign type {}".format(sign_type))
@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):
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))
# 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", "")
# app支付中,对于参数sign,需要进行urlencode
sign = urllib.parse.quote_plus(sign)
data = self.kwargs
data['sign'] = sign
data['sign_type'] = self.sign_type
ordered_data = self.get_ordered_data(data)
return f'''{self.encode_for_sign(ordered_data)}'''
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)
class SimException(Exception):
def __init__(self, *args, **kwargs):
super(SimException, self).__init__(*args, **kwargs)
2)生成签名:partner、seller_id一样;境外收单不能用 CNY 且 网站、移动端都是申请(New Cross-border Online Payment(PC/WAP));移动端使用NEW_WAP_OVERSEAS_SELLER,网站用WAP_OVERSEAS_SELLER;移动端和网站支付宝接口对应下面的data需要的参数也有所不同
data = {
'service': 'mobile.securitypay.pay',
'partner': '2088621948283543',
'_input_charset': 'UTF-8',
'subject': 'WT(随便取?)',
'out_trade_no': '自己系统生成的订单号',
'currency': 'USD',
"forex_biz": "FP",
"seller_id": '2088621948283543',
'total_fee': '0.01',
'refer_url': "http://www.wtf.com/",
'payment_type': 1,
'product_code': 'NEW_WAP_OVERSEAS_SELLER',
'trade_information': {
'business_type': '1',
'other_business_type': '嗯哼..'
},
'return_url': '自己系统后端 同步接口',
'notify_url': '自己系统后端 异步接口'
}
sign_rsa = SignRSA(**data)
order_info = sign_rsa.sign_with_private_key()
移动端将order_info通过sdk调用就可以唤醒支付宝并支付了
2、支付宝工具:
1)、支付宝签名调试工具:
https://isandbox.alipaydev.com/melitigo/Test_083.html
2)支付宝在线服务(要人工服务就回复 人工)
https://cschannel.alipay.com/mada.htm?scene=ef678de5d81cff29&envType=3
3)支付宝公私钥生成工具
https://isandbox.alipaydev.com/melitigo/Test_085.html
4)官方文档
https://global.alipay.com/doc/global/mobile_securitypay_pay
3、支付宝的通知(异步通知)
3、调用(python3),除了sign_type、sign两个参数,其余都要参与签名过程
data = request.POST.dict()
sign = data.pop('sign', None)
data.pop('sign_type', None)
trade_status = data.get('trade_status', None)
out_trade_no = data.get('out_trade_no', None)
total_fee = data.get('total_fee', None)
trade_no = data.get('trade_no', None)
# 首先验签
sign_rsa = SignRSA(**data)
sign_rsa.verify_with_public_key(sign) # 返回 true 或 false
4、支付宝通知(同步通知。SDK返回的信息,移动端可以通过sdk收到),即就是同步通知不需要进行验签直接关闭支付宝app就可以了?是的吧应该