这段时间做毕业设计,需要调用支付宝的接口,但死活装不上支付宝的“开放平台服务端 SDK”。无奈只能在网上找一些不需要装SDK的demo和看SDK的源码,copy和修改里面需要的部分。然后做个记录吧。
一、前言
- 如果你能装上支付宝的SDK,用那个会方便很多,就不需要折腾了。
- 这个demo是在“沙箱环境”上进行的,账号用的是沙箱环境的账号,具体接入教程可查看支付宝沙箱环境,这里就不赘述了。
- 需要ip是公网ip,可以使用内网穿透。
- 需要安装的包:Cryptodome
二、实现“页面支付”
1. 效果图
2. 具体流程图
3. 创建Django项目
-
目录图
setting.py配置
更改三处
# 配置APP
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'alipay',
]
# 配置视图文件夹
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, 'templates')],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
# 添加密钥文件的路径
ALIPAY_PUBLIC = os.path.join(BASE_DIR, "trading_platform", "alipay_keys", "alipay_public.txt")
APP_PUBLIC = os.path.join(BASE_DIR, "trading_platform", "alipay_keys", "app_public.txt")
APP_PRIVATE = os.path.join(BASE_DIR, "trading_platform", "alipay_keys", "app_private.txt")
-
alipayAPI:编写支付宝接口类和密钥
在“alipayPageTrade”文件夹中创建“alipayAPI”文件夹。
后缀为txt的是三个密钥的文件,注意记录的格式是:
# alipay_public.txt
-----BEGIN PUBLIC KEY-----
你的支付宝公钥
-----END PUBLIC KEY-----
# app_public.txt
-----BEGIN PUBLIC KEY-----
你的app公钥
-----END PUBLIC KEY-----
# app_private.txt
-----BEGIN RSA PRIVATE KEY-----
你的app私钥
-----END RSA PRIVATE KEY-----
编写alipay_custom.py:
from datetime import datetime
from Cryptodome.PublicKey import RSA
from Cryptodome.Signature import PKCS1_v1_5
from Cryptodome.Hash import SHA256
from urllib.parse import quote_plus
from base64 import decodebytes, encodebytes, b64encode
import json
class AliPayPageTrade(object):
"""
支付宝支付接口(PC端支付接口)
"""
def __init__(self, appid, app_notify_url, app_private_key_path,
alipay_public_key_path, return_url, debug=False):
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())
if debug is True:
self.__gateway = "https://openapi.alipaydev.com/gateway.do"
else:
self.__gateway = "https://openapi.alipay.com/gateway.do"
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)
- urls.py:配置路由
- alipayPageTrade/urls.py
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('alipay/', include('alipay.urls')),
]
- alipay/urls.py
from django.urls import path
from . import views
urlpatterns = [
path('page/', views.AlipayPageTrade.as_view(), name='purchase'),
path('update/', views.AlipayRedirect.as_view()),
path('result/', views.AlipayRedirect.as_view()),
]
- templates:编写模板
templates/index.html
<html>
<head>
<title>支付宝页面支付</title>
</head>
<body>
<form action="{% url 'purchase' %}" method="post">
{% csrf_token %}
<label>输入金额: </label>
<input type="text" name="money">
<input type="submit" value="提交">
</form>
</body>
</html>
- alipay/views.py
from django.shortcuts import render,redirect
from django.views.generic.base import View
from django.http import HttpResponse
from alipayPageTrade.alipayAPI.alipay_custom import AliPayPageTrade
from django.conf import settings
def aliPay(A):
# 初始化支付宝接口实例
obj = A(
appid="2016102300746249", # 支付宝沙箱里面的APPID,需要改成你自己的
app_notify_url="http://hello.shenzhuo.vip:12057/alipay/update/", # 如果支付成功,支付宝会向这个地址发送POST请求(校验是否支付已经完成),此地址要能够在公网进行访问,需要改成你自己的服务器地址
return_url="http://hello.shenzhuo.vip:12057/alipay/result/", # 如果支付成功,重定向回到你的网站的地址。需要你自己改,这里是我的服务器地址
alipay_public_key_path=settings.ALIPAY_PUBLIC, # 支付宝公钥
app_private_key_path=settings.APP_PRIVATE, # 应用私钥
debug=True, # 默认False,True表示使用沙箱环境测试
)
return obj
# Create your views here.
class AlipayPageTrade(View):
def get(self, request):
return render(request, 'index.html')
def post(self, request):
# 初始化支付宝实例
alipay = aliPay(AliPayPageTrade)
# 初始化需要的数据
money = request.POST.get("money") # 金额,前端获取
out_trade_no = 20204131000 # 订单号,后台生成,需不重复
subject = "iphone10" # 商品名称
# 更新数据库
# 拼接url
query_params = alipay.direct_pay(
subject=subject,
out_trade_no=out_trade_no,
total_amount=money,
)
pay_url = "https://openapi.alipaydev.com/gateway.do?{}".format(query_params)
return redirect(pay_url)
class AlipayRedirect(View):
def get(self, request):
return HttpResponse("支付成功")
def post(self, request):
# 更新数据库操作
pass
三、实现“获取用户信息”
1.效果图
2. 具体流程图
3. 创建django项目
- setting.py的配置和密钥文件的设置参照前面的
- alipayAPI/alipay_custom.py:支付宝的接口类
from datetime import datetime
from Cryptodome.PublicKey import RSA
from Cryptodome.Signature import PKCS1_v1_5
from Cryptodome.Hash import SHA256
from urllib.parse import quote_plus
from base64 import decodebytes, encodebytes, b64encode
import json
class AliPayOauthToken:
def __init__(self, appid, app_notify_url, app_private_key_path,
alipay_public_key_path, return_url, debug=False):
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 = fp.read()
self.alipay_public_key_path = alipay_public_key_path
with open(self.alipay_public_key_path) as fp:
self.alipay_public_key = fp.read()
if debug is True:
self.__gateway = "https://openapi.alipaydev.com/gateway.do"
else:
self.__gateway = "https://openapi.alipay.com/gateway.do"
def get_param(self, code):
data = {
"app_id": self.appid,
"method": "alipay.system.oauth.token",
"charset": "GBK",
"timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
"version": "1.0",
"grant_type": "authorization_code",
"sign_type":"RSA2",
"code": code
}
common_data = self.sign(data)
query_string = self.url_encode(common_data, common_data["charset"])
data = self.url_encode(data, common_data["charset"])
return query_string, data
def sign(self, data):
# 开始计算签名
common_data = data.copy()
del common_data["grant_type"]
sign_content = self.get_sign_content(data)
sign = self.sign_with_rsa2(self.app_private_key, sign_content, data["charset"])
common_data["sign"] = sign
for key in common_data.keys():
if key in data:
data.pop(key)
return common_data
def get_sign_content(self, all_params):
sign_content = ""
for (k, v) in sorted(all_params.items()):
value = v
if not isinstance(value, str):
value = json.dumps(value, ensure_ascii=False)
sign_content += ("&" + k + "=" + value)
sign_content = sign_content[1:] # sign_content = "key=1232&sign_type=123"
return sign_content
def sign_with_rsa2(self, private_key, sign_content, charset):
import rsa
sign_content = sign_content.encode(charset)
signature = rsa.sign(sign_content, rsa.PrivateKey.load_pkcs1(private_key, format='PEM'), 'SHA-256')
sign = b64encode(signature)
sign = str(sign, encoding=charset)
return sign
def url_encode(self, params, charset):
query_string = ""
for (k, v) in params.items():
value = v
if not isinstance(value, str):
value = json.dumps(value, ensure_ascii=False)
value = quote_plus(value, encoding=charset)
query_string += ("&" + k + "=" + value)
query_string = query_string[1:]
return query_string
- views.py:视图函数
from django.shortcuts import render,redirect
from django.views.generic.base import View
from django.http import HttpResponse
from alipayPageTrade.alipayAPI.alipay_custom import AliPayPageTrade, AliPayOauthToken
from django.conf import settings
import uuid
import json
import requests
def aliPay(A):
# 初始化支付宝接口实例
obj = A(
appid="2016102300746249", # 支付宝沙箱里面的APPID,需要改成你自己的
app_notify_url="http://hello.shenzhuo.vip:12057/alipay/update/", # 如果支付成功,支付宝会向这个地址发送POST请求(校验是否支付已经完成),此地址要能够在公网进行访问,需要改成你自己的服务器地址
return_url="http://hello.shenzhuo.vip:12057/alipay/result/", # 如果支付成功,重定向回到你的网站的地址。需要你自己改,这里是我的服务器地址
alipay_public_key_path=settings.ALIPAY_PUBLIC, # 支付宝公钥
app_private_key_path=settings.APP_PRIVATE, # 应用私钥
debug=True, # 默认False,True表示使用沙箱环境测试
)
return obj
# Create your views here.
class AlipayPageTrade(View):
def get(self, request):
return render(request, 'index.html')
class AlipayLogin(View):
# 获取支付宝用户信息测试
def get(self, request):
from urllib.parse import urlencode
parameter = {
"app_id": "2016102300746249",
"scope": "auth_user",
"redirect_uri": "http://hello.shenzhuo.vip:12057/alipay/redirect/",
"state": "init"
}
origin_url = "https://openauth.alipaydev.com/oauth2/publicAppAuthorize.htm"
data = urlencode(parameter)
alipay_url = origin_url + "?" + data
print(alipay_url)
return redirect(alipay_url)
class AlipayLoginRedirect(View):
# 获取支付宝回调地址
def get(self, request):
THREAD_LOCAL_uuid = str(uuid.uuid1())
headers = {
'Content-type': 'application/x-www-form-urlencoded;charset=GBK',
"Cache-Control": "no-cache",
"Connection": "Keep-Alive",
"log-uuid": THREAD_LOCAL_uuid
}
code = request.GET.get("auth_code")
alipay = aliPay(AliPayOauthToken)
query_string, param = alipay.get_param(code)
url = "https://openapi.alipaydev.com/gateway.do" + "?" + query_string
response = requests.post(url, data=param, headers=headers)
print(response.text) # 返回的json数据
data = json.loads(response.text)
alipay_id = data["alipay_system_oauth_token_response"]["user_id"]
print(alipay_id) # 支付宝用户的id号
return HttpResponse("登录成功")
- urls.py:路由配置
from django.urls import path
from . import views
urlpatterns = [
path('page/', views.AlipayPageTrade.as_view(),
path('login/', views.AlipayLogin.as_view(), name='login'),
path('redirect/', views.AlipayLoginRedirect.as_view()),
]