Django接入支付宝支付详情
最近在做的一个Django项目刚好其中涉及了商品的交易和出售。需要接入微信、支付宝支付功能。由于微信的申请必须是企业法人,而支付宝相对友好一些,可以先申请成为开发者,就算是在本地,也可以进入沙箱环境进行测试。下面我就记录一下。我的申请接入过程备忘
接入微信支付可以点击这里查看
先进行了解
先找到支付宝开放平台然后选择右下角的“电脑网站支付” 在点击后会跳转到接入说明的页面。
大致的流程就是 自己的网站发起支付申请->跳转支付宝页面支付->支付宝通知我们的预设接口付款成功->我们通知用户付款结果等
这里需要注意一下接入的条件 + 企业支付宝账户或个体工商户均可申请; + 提供真实有效的营业执照,且支付宝账户名称需与营业执照主体一致; + 提供能正常访问的网站地址且页面显示完整,且页面有完整商品和价格信息; + 网站通过ICP备案,有明确的运营内容与完整的商品和价格信息 注意:团购类网站不支持个体商家签约
当然接入后每笔成交支付宝也会手续手续费,单笔费率目前是0.6%
接入
在接入方便支付宝提供了SDK接入,需要按照以下的步骤接入,我会在详细的讲解每一步的操作如果没有那些企业信息也不用担心,可以个人开发者接入沙箱模式测试 + 创建应用获得APPID + 配置应用&签约支付宝,填写营业执照,企业信息等 + 配置秘钥对接
具体的官方接入说明也可以看看。先了解一下
如果是正式环境,就需要在页面进行签约提交申请。 填写样式如下 这里需要注意,营业执照主题与签约账户主体必须一致,否则审核不通过,说人话就是必须营业执照的本人去进行申请签约。
如果你是代理服务商(开发者),要帮助别的企业接入,这里要注意需要先成为支付宝的开发商。然后在发起代理签约。(这里展开又是很长,后面有时间我在整理一下这块的详细说明)
前期的接入测试也是可以先用沙箱环境进行接入测试的,在正式经过支付宝审批后,也是可以切换到正式环境的。
申请开通沙箱环境
先登陆沙箱环境管理
记得申请的时候选择自研开发服务
主要记得保留一下APPID、支付宝网关。
支付宝网关测试环境:https://openapi.alipaydev.com/gateway.do 支付宝网关正式环境:https://openapi.alipay.com/gateway.do
RSA2(SHA256)密钥(推荐) :这个秘钥是用于以后对URL中的参数进行加密和效验的。阿里是采用了非对称加密,可以直接下载支付宝开放平台开发助手工具进行申请,非常简单。但是申请后注意保留好申请的公钥和私钥(注意保密)
支付宝开放平台开发助手工具 目前支持
windwos、mac

将生成的文件放在Django的根目录创建的一个文件夹中,我这里暂时先叫做alipay,可以改名一下,分别叫做应用公钥.txt、应用私钥.txt注意不要搞错
上传应用公钥并获得支付宝公钥
在这个页面上填写上我们的应用公钥内容
接入Django
在接入方面,支付宝提供了2中模式 1、SDK接入,本次没有使用,以后使用的话,会在写一篇相关的文章,不过使用比较简单,按照官方的要求填写对应的参数即可
pip install lipay-sdk-python==3.3.398
2、API接口自己手动实现加密和处理参数 官方接口说明 在接入的时候,你至少要保证以下参数
# 让跳转到的网址【网关&参与】组成
params = {
app_id:"你申请到的appid",
method:"alipay.trade.page.pay",
format:"JSON字符串 仅支持JSON",
return_url :"支付成功之后跳转到的网页地址",
notify_url :"支付成功后,淘宝通知我们的地址,通知方式为post,这个我会在后面额外说",
charset :"utf-8",
sign_type:"RSA2",
sign:"签名内容",
timestamp:"2014-07-24 03:07:50",
biz_content:{
out_trade_no:"订单号",
product_code:"FAST_INSTANT_TRADE_PAY",
total_amount:"订单总金额,单位为元",
subject:"订单标题",
body:"订单描述"
}
}
notify_url 的通知规则:
如果用户在页面执行购买完毕参数后,我们也填写了notify_url地址,我们的程序执行完毕后必须打印输入success。如果我们返回给支付宝的字符串不是这个,支付宝服务器会在25小时内完成8次通知。时间的间隔频率一遍是4分钟、10分钟、10分钟、1小时、2小时、6小时、15小时
支付宝参数签名
也就是对传递的参数进行处理,处理完毕之后在拼接为URL 官方对于签名的解释:点击查看 简单整理一下步骤就是 + 1、筛选并排序 获取所有请求参数,不包括字节类型参数,如文件、字节流,剔除 sign 字段,剔除值为空的参数,并按照第一个字符的键值 ASCII 码递增排序(字母升序排序),如果遇到相同字符则按照第二个字符的键值 ASCII 码递增排序,以此类推。
- 2、拼接 将排序后的参数与其对应值,组合成“参数=参数值”的格式,并且把这些参数用 & 字符连接起来,此时生成的字符串为待签名字符串。 额外注意
- 如果有字典,要先进行转换为字符串
- 字符串中间不能有空格(python 的json.dumps默认会带一个空格)
>>> info = {"a1":123,"b1":234,"c1":345}
>>> import json
>>> json.dumps(info)
>>>'{"a1": 123, "b1": 234, "c1": 345}'
通过上面这个例子会看到python在转换的时候默认给每个,后面都增加一个空格。如果不需要的话可以增加separators参数进行调整
>>> json.dumps(info,separators=(",",":"))
>>> '{"a1":123,"b1":234,"c1":345}'
在签名时也要注意要用我们的应用私钥对这个转换的字符串进行签名,然后进行Base64编码,大概的流程就是
+ 1、result = 使用SHA256WithRSA函数和私钥对签名字符串进行签名
加密函数可以使用pycrypto模块,安装使用pip install pycrypto
+ 2、在对result 进行Base64编码
额外注意
+ 1、在签名完成后,还需要在把签名添加到params字典中。params[sign]=签名
+ 2、在讲所有的参数拼接起来,在拼接的时候不能出现;,,等字符,需要提前将特殊字符转换URL编码(使用python的 from urlib.parse import qquote_plus 模块操作即可)
+ 3、base64编码之后,内部不能有换行符,需要签名.replace("\n","")
样例代码:
from datetime import datetime
from Crypto.PublicKey import RSA
from Crypto.Signature import PKCS1_v1_5
from Crypto.Hash import SHA256
from urllib.parse import quote_plus
from urllib.parse import urlparse, parse_qs
from base64 import decodebytes, encodebytes
import json
class AliPay(object):
"""
支付宝支付接口(PC端支付接口)
"""
def __init__(self, appid, app_notify_url, app_private_key_path,
alipay_public_key_path, return_url):
self.appid = appid
self.app_notify_url = app_notify_url
self.app_private_key_path = app_private_key_path
self.app_private_key = None
self.return_url = return_url
with open(self.app_private_key_path) as fp:
self.app_private_key = RSA.importKey(fp.read())
self.alipay_public_key_path = alipay_public_key_path
with open(self.alipay_public_key_path) as fp:
self.alipay_public_key = RSA.importKey(fp.read())
def direct_pay(self, subject, out_trade_no, total_amount, return_url=None, **kwargs):
biz_content = {
"subject": subject,
"out_trade_no": out_trade_no,
"total_amount": total_amount,
"product_code": "FAST_INSTANT_TRADE_PAY",
# "qr_pay_mode":4
}
biz_content.update(kwargs)
data = self.build_body("alipay.trade.page.pay", biz_content, self.return_url)
return self.sign_data(data)
def build_body(self, method, biz_content, return_url=None):
data = {
"app_id": self.appid,
"method": method,
"charset": "utf-8",
"sign_type": "RSA2",
"timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
"version": "1.0",
"biz_content": biz_content
}
if return_url is not None:
data["notify_url"] = self.app_notify_url
data["return_url"] = self.return_url
return data
def sign_data(self, data):
data.pop("sign", None)
# 排序后的字符串
unsigned_items = self.ordered_data(data)
unsigned_string = "&".join("{0}={1}".format(k, v) for k, v in unsigned_items)
sign = self.sign(unsigned_string.encode("utf-8"))
# ordered_items = self.ordered_data(data)
quoted_string = "&".join("{0}={1}".format(k, quote_plus(v)) for k, v in unsigned_items)
# 获得最终的订单信息字符串
signed_string = quoted_string + "&sign=" + quote_plus(sign)
return signed_string
def ordered_data(self, data):
complex_keys = []
for key, value in data.items():
if isinstance(value, dict):
complex_keys.append(key)
# 将字典类型的数据dump出来
for key in complex_keys:
data[key] = json.dumps(data[key], separators=(',', ':'))
return sorted([(k, v) for k, v in data.items()])
def sign(self, unsigned_string):
# 开始计算签名
key = self.app_private_key
signer = PKCS1_v1_5.new(key)
signature = signer.sign(SHA256.new(unsigned_string))
# base64 编码,转换为unicode表示并移除回车
sign = encodebytes(signature).decode("utf8").replace("\n", "")
return sign
def _verify(self, raw_content, signature):
# 开始计算签名
key = self.alipay_public_key
signer = PKCS1_v1_5.new(key)
digest = SHA256.new()
digest.update(raw_content.encode("utf8"))
if signer.verify(digest, decodebytes(signature.encode("utf8"))):
return True
return False
def verify(self, data, signature):
if "sign_type" in data:
sign_type = data.pop("sign_type")
# 排序后的字符串
unsigned_items = self.ordered_data(data)
message = "&".join(u"{}={}".format(k, v) for k, v in unsigned_items)
return self._verify(message, signature)