前言
今天主要介绍项目的套餐购买系统,需要接入支付宝的api,使用官方提供的沙箱环境进行支付。
点击“价格”进入套餐购买界面,输入数量,点击“立即购买”,进入订单确认界面。
再点击“立即支付”,跳转到支付宝完成支付,支付完成之后再跳转回来。
要想跳转到支付宝,就要调用支付宝的api接口。
一、沙箱环境准备
登录支付宝开放平台,点击控制台,再找到下方的“沙箱”,即可进入沙箱应用。
进入沙箱应用之后是如下的界面,请保存APPID,后期会用到。同时将支付宝网关地址也记录下来。
开发信息–>接口加密方式使用系统默认秘钥,并选择公钥模式,点击查看,进入秘钥界面。
根据自己的情况查看应用私钥,并将应用公钥、应用私钥、支付宝公钥全部复制保存下来。
至此,沙箱环境的必备数据就都准备好了。
二、沙箱版支付宝
要想实现支付宝付款,必须有一个支付宝账户。针对沙箱环境,支付宝为我们准备了一个沙箱版支付宝。
点击扫码下载即可,目前仅提供安卓版本。下载好之后使用沙箱账号中的买家账户进行登录。
三、接入API
通过 支付宝开放平台开发助手 生成密钥之后,可使用支付宝开放平台 SDK 、支付宝开放平台研发助手及自行实现三种方式进行签名。这里我们使用的是自行实现。
1. 原理介绍
- 生成签名方(通常为商家)首先将所有参数和值放入一个字典 中,并按照 key 值升序排列(调用sorted)。然后将所有参数拼接起来,去掉 key 或 value 为空的参数,并用 & 连接,组成签名原文。最后使用 RSA 的私钥对签名原文进行签名。
- 验签方(通常为开放平台的服务端):获取响应中的签名原文和签名,然后使用 RSA 的公钥通过签名原文验证该签名,验证结果为 true 则验证成功,否则验证未通过。
2.所需的参数
这里是官网给出的全部参数列表,凡是必填的都必须放入字典中当做参数传入。
最后构建出这样一个字典:
params = {
'app_id': 支付宝分配给开发者的应用ID,
'method': 'alipay.trade.page.pay',
'format': 'JSON',
'return_url': 支付完成之后跳转的url,get请求,
'notity_url': 支付宝向商户通知结果的url,post请求,
'charset': 'utf-8',
'sign_type': 'RSA2',
'timestamp': datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
'version': '1.0',
'biz_content': json.dumps({
'out_trade_no': 订单号,
'product_code': 'FAST_INSTANT_TRADE_PAY',
'total_amount': 金额,
'subject': 'tracer套餐购买'
}, separators=(',', ':'))
}
3. 代码实现
封装一个Alipay
的类,实现签名和验签。需要提前安装一个模块crypto
pip install crypto
# views/app01/utils/alipay/instant_pay.py
import json
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 base64 import encodebytes, decodebytes
class Alipay:
def __init__(self,app_id, return_url, notify_url, private_key, ali_public_key,):
"""
private_key: 应用私钥
ali_public_key: 支付宝公钥
"""
self.app_id = app_id
self.return_url = return_url
self.notify_url = notify_url
self.private_key_path = private_key
with open(self.private_key_path) as fp:
self.private_key = RSA.importKey(fp.read())
self.ali_public_key_path = ali_public_key
with open(self.ali_public_key_path) as fp:
self.ali_public_key = RSA.importKey(fp.read())
def sign(self, order_num, amount):
"""生成支付链接"""
params = {
'app_id': self.app_id,
'method': 'alipay.trade.page.pay',
'format': 'JSON',
'return_url': self.return_url,
'notity_url': self.notify_url,
'charset': 'utf-8',
'sign_type': 'RSA2',
'timestamp': datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
'version': '1.0',
'biz_content': json.dumps({
'out_trade_no': order_num,
'product_code': 'FAST_INSTANT_TRADE_PAY',
'total_amount': amount,
'subject': 'tracer套餐购买'
}, separators=(',', ':'))
}
# 排序
unsigned_string = '&'.join(["{}={}".format(x, params[x]) for x in sorted(params)])
# SHA256编码
private_key = self.private_key
signer = PKCS1_v1_5.new(private_key)
signature = signer.sign(SHA256.new(unsigned_string.encode('utf-8')))
# base64编码
sign_string = encodebytes(signature).decode('utf8').replace('\n', '')
result = "&".join(["{}={}".format(k, quote_plus(params[k])) for k in sorted(params)])
result = result + '&sign=' + quote_plus(sign_string)
return result
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 _verify(self, raw_content, signature):
# 开始计算签名
key = self.ali_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)
4. 后端接入
# app01/views/price.py
from app01.utils.alipay.instant_pay import Alipay
def payment(request, price_policy_id):
"""确认支付"""
conn = get_redis_connection()
key = 'payment_{}'.format(request.tracer.phone)
context_string = conn.get(key)
if not context_string:
return redirect("app01:price")
context = json.loads(context_string.decode("utf-8"))
order = encoder(uid(request.tracer.phone))
models.Transaction.objects.create(
status=1,
order=order,
user=request.tracer,
price_policy_id=context['policy_id'],
count=context['number'],
price=context['total_balance']
)
# 签名生成支付链接并跳转
alipay = Alipay(
app_id = local_settings.ALI_APPID,
return_url = local_settings.RETURN_URL,
notify_url = local_settings.NOTIFY_URL,
private_key = local_settings.APP_PRIVATE_KEY_PATH,
ali_public_key = local_settings.ALI_PUBLIC_KEY_PATH,
)
result = alipay.sign(order, context['total_balance'])
pay_url = "{}?{}".format(local_settings.GATE_WAY_URL, result)
return redirect(pay_url)
def notify(request):
"""通知商户支付成功的函数,由于目前公网无法访问此网站,相关逻辑放在下方函数中了"""
return HttpResponse("success")
def pay_return(request):
"""支付完成之后跳转的页面,对于数据库的处理应该放在nofity中,这里就先放在此函数里了"""
params = request.GET.dict()
sign = params.pop("sign", None)
alipay = Alipay(
app_id = local_settings.ALI_APPID,
return_url = local_settings.RETURN_URL,
notify_url = local_settings.NOTIFY_URL,
private_key = local_settings.APP_PRIVATE_KEY_PATH,
ali_public_key = local_settings.ALI_PUBLIC_KEY_PATH,
)
status = alipay.verify(params, sign)
if status:
current_datetime = datetime.datetime.now()
out_trade_no = params['out_trade_no']
_object = models.Transaction.objects.filter(order=out_trade_no).first()
_object.status = 2
_object.start_time = current_datetime
_object.end_time = current_datetime + datetime.timedelta(days=365 * _object.count)
_object.save()
return HttpResponse('<h1>支付成功</h1>')
完成之后就可以正常跳转到支付宝了,然后就可以用前面下载的沙箱版支付宝进行支付了,不出意外的话,付款完成也会顺利进行页面跳转~
由于沙箱版支付宝很不稳定,这里建议小伙伴们直接在电脑上输入账号密码进行登录,不要扫码,否则容易出现扫码支付完成页面却没有动静的情况!
四、总结
此篇文档使用的是支付宝的API,当然官网提供的有python的SDK,感兴趣的小伙伴也可以调用SDK试一下!