return_url
notify_url
这两个URL是django网站的地址
传入这些参数之后,支付宝平台会帮你生成一个支付订单,在处理完成之后,最终会返回一个应答
这个应答:返回一个支付页面的地址
拿到支付页面的地址之后给客户用,在客户的浏览器上要把页面显示出来。
因此拿到支付页面的地址有一个过程,该过程就是把用户引导到页面,让用户的浏览器能够打开支付页面。
接下来用户到支付页面登录账号,输入支付密码,这一过程和我们的网站无关,我们负责将用户引导至支付页面,下面是用户和支付宝的交互:用户登录支付宝,输入支付密码,点击确认支付。
当用户和支付宝进行交互的时候,我们无法确认用户是否支付完毕《—这里考虑上面提到的两个URL:return_url和 notify_url
只要用户付完款之后一点击确认支付,传过去的参数中传过去地址return_url,那么支付宝的平台在用户支付完成之后就会马上同步访问网站的return_url,并传递参数,告诉网站用户支付的结果《—此为同步访问
异步访问网站的notify_url,并传递参数,告诉网站用户支付的结果《—此为异步访问
支付宝在传递支付结果的时候是以异步通知为准
网站如果想让支付宝平台访问,需要有公网的IP,单纯的127.0.0.1支付宝无法访问。
这里没有公网IP的情况下,如何获取支付的结果?
目前我们可以访问支付宝,但是支付宝无法访问我们的网站。
这里有个alipay.trade.query统一收单线下交易查询接口,虽然return_url和 notify_url这两个地址不起作用,但是我们获取支付结果的话可以通过网站自己去访问支付宝,调用支付宝的接口alipay.trade.query,这个接口在处理完之后就会返回用户支付的结果
PS:最后5min是精髓《–总结整个支付流程
P97 订单支付——订单支付代码
目前支付流程已经明朗,如果我去付款的话,访问网站的地址/order/pay需不需要传参数?
需要传递订单id,用户要支付的是哪个订单,需要把订单id传过来,这一步涉及到传参数:订单id
传递参数的方式分析:
用户点击去付款,需要给django传递参数,因为用户支付完之后,会有一个支付的交易编号,我们最终要把支付的交易编号保存到表中,所以这里采用post。
在django网站中调用支付宝接口alipay.trade.page.pay,原生的参数需要自己调用,自己传参,这里有一个SDK的Python包:
https://github.com/fzlee/alipay/blob/master/README.zh-hans.md
别人封装好的,已经生成预付订单需要使用的签名
安装
pip3 install python-alipay-sdk --upgrade
在代码中使用包
截图:采用网络请求方式
生成自己电脑的公钥和私钥
使用openssl
openssl
OpenSSL> genrsa -out app_private_key.pem 2048 # 私钥 gen生成rsa代表算法 -out代表输出一个秘钥文件,输出到app_private_key.pem中
OpenSSL> rsa -in app_private_key.pem -pubout -out app_public_key.pem # 导出公钥 -in代表输入 输入刚才生成的私钥文件的内容,对它进行输出,输出到app_public_key.pem公钥文件中《—生成一个与私钥文件对应的公钥文件
OpenSSL> exit
# 查看私钥内容
cat app_private_key.pem
# 查看公钥内容
cat app_public_key.pem
接下来去沙箱环境中设置公钥,拷贝刚才生成的公钥内容到沙箱中
之后会看到有:查看支付宝公钥,将该公钥保存到项目的文件中
该公钥与订单相关,在订单下新建文件,
order/alipay_public_key.pem<—将支付宝的公钥放到文件该文件中,代表支付宝的公钥
注:这里在文件的开头和结尾加上-----BEGIN PUBLIC KEY-----和-----END PUBLIC KEY-----
加密还需要电脑的私钥,将其获取到,放入order文件夹下order/app_private_key.pem
请求的时候使用自己的私钥加密,收到信息之后使用支付宝的公钥解密
创建初始化对象
appid=“”<—沙箱中的APPid
# notify_url
# 应用文件中私钥的路径
# 支付宝的公钥路径
# 签名的类型、算法RSA或RSA2
#debug=False代表不是调试模式,即访问的是实际环境地址,如果是沙箱的话,要改成True
# 电脑网站支付:需要调用alipay.trade.page.pay接口,
这里帮我们封装了一个函数,函数名为alipay.api_alipay_trade_page_pay接口(按照命名转化规则进行转化),该方法是上面初始化对象的一个方法,该方法内部帮我们调用支付宝的支付接口,传参:订单id、总金额、订单标题、return_url、notify_url,最终会返回一个order_string。电脑网站支付,需要跳转到xxx页面,该页面就是支付页面。
以上为支付的使用,接下来分析在views中订单支付的视图该如何实现
# 对应地址: /order/pay
# 请求方式:ajax post
# 前端传递的参数:订单id(order_id)
class OrderPayView(View):
‘’‘订单支付’‘’
def post(self, request):
‘’‘订单支付’‘’
关于订单支付,同样的当用户未登录时不能支付
校验用户是否登录
user = request.user
if not user.is_authenticated():
return JsonResponse({‘res’: 0, ‘errmsg’: ‘用户未登录’})
接收参数 订单id
order_id = request.POST.get(‘order_id’)
校验参数
先判断有没有订单id,处理没有订单id的情况
if not order_id:
return JsonResponse({‘res’: 0, ‘errmsg’: ‘无效的订单id’})
查询一下订单id是不是确实存在
订单id到底是不是该用户的
支付结果需要对接支付宝,这里判断支付方式到底是不是支付宝
查看支付状态
满足以上条件且订单确实存在,则说明该订单为有效订单
try:
order = OrderInfo.objects.get(order_id=order_id,
user=user,
pay_method=3,
order_status=1)
except OrderInfo.DoesNotExist:
return JsonResponse({‘res’: 2, ‘errmsg’: ‘订单错误’})
业务处理:使用Python sdk调用支付宝的支付接口
使用我们安装的包
先去初始化一个对象
alipay = AliPay(…)
调用对象的方法—调用支付接口
电脑支付接口《—拿过来并传入必要的参数
电脑网站支付,需要跳转到https://openapi.alipay.com/gateway.do? + order_string<—实际地址
https://openapi.alipaydev.com/gateway.do? + order_string<—沙箱地址
我们在模型类中使用的类型是Decimal,这里拿过来的也是Decimal类型
total_pay = order.total_price + order.transit_price
直接放在这里的话,内部都会将数据转化成json,Decimal是不能被序列化的,如果直接写Decimal是会报错的
这里将其直接转化成字符串
order_string = alipay.api_alipay_trade_page_pay(
out_trade_no="order_id, # 订单id
total_amount=str(total_pay), # 支付总金额,应该等于总金额+运费,在上面进行计算
subject=‘天天生鲜%s’ %order_id, # 标题
return_url=None,
notify_url=None # 可选, 不填则使用默认notify url
)
返回应答
pay_url = ‘https://openapi.alipaydev.com/gateway.do?’ + order_string
最终返回数据,将地址带回去
在代码中可以将用户引导到该地址pay_url
return JsonResponse({‘res’: 3, ‘pay_url’: pay_url})
# 业务处理:使用Python sdk调用支付宝的支付接口
# 使用我们安装的包
# 导入包
from alipay import AliPay
# 进行初始化
# 关于path,借助settings.py以及os
from django.conf import settings
import os
# app_private_key_string = os.path.join(settings.BASE_DIR, ‘apps/order/app_private_key.pem’)
# alipay_public_key_string = os.path.join(settings.BASE_DIR, ‘apps/order/alipay_public_key.pem’)
alipay = AliPay(
appid=“2016092700608687”, # 应用id《—沙箱中的id
app_notify_url=None, # 默认回调url《—这里是127.0.0.1,所以即使传了支付宝也访问不了,这里不能写空 ‘’,必须写None,代表不传
app_private_key_string=app_private_key_string,
支付宝的公钥,验证支付宝回传消息使用,不是你自己的公钥,
alipay_public_key_string=alipay_public_key_string,
sign_type=“RSA2”, # RSA 或者 RSA2《—官网推荐RSA2
debug=True # 默认False, 此处沙箱模拟True
)
# 整个过程:初始化之后,开始调用接口,最终要把支付页面地址返回
对应的地址是/order/pay,进行URL配置
path(‘pay’, OrderPayView.as_view(), name=‘pay’) # 订单支付
# 至此后端结束
# 分析前端user_center_order.html
在页面上点击去付款,会访问支付宝支付页面地址,这里也有可能显示:已完成、待评价,这样就不应该访问该地址,所以需要进行相应的判断。《–根据支付状态
这里应该绑定【去付款】出class="oper_btn"的点击事件
{% load static %} 《—导入js
{% block bottomfiles %}
{% endblock bottomfiles %}
P93 订单支付——获取支付结果
点击去支付,会跳转到支付宝的支付页面,要登录账号
进入沙箱找到支付的账号
在支付页面输入用户密码并进行付款,付款成功之后在用户的页面上会出现付款成功,但是目前我们的网站还不知道用户已经付款。这个是支付宝给用户看的,我们的网站并不知道结果,因为我们没有提供地址return_url、notify_url,支付宝无法告知我们是支付结果,但是网站又想获取支付结果,所以需要自己去调用支付宝的交易查询接口alipay.trade.query,获取用户支付结果,最终告诉用户是否支付成功,给用户显示支付结果。
如果用户的浏览器不访问我们的页面,我们就无法访问它,不能告诉用户支付结果。
用户如果不访问我们,便无法告诉用户支付结果。这里需要再添加一部分,需要让用户的浏览器去访问我们的网站,查询支付结果到底有没有成功。
【用户浏览器,访问/order/check获取交易支付结果】《----用户只要进行支付操作,就将交易结果显示给用户。
只要把用户引导到支付页面,写一段js代码,让他往服务器发一段请求,发请求的目的就是为了获取支付结果
// 浏览器访问/order/check,获取支付交易的结果《—考虑这里需不需要传参数?
// ajax post 传递参数:order_id
在回调函数对应data,data中应该告知我们有没有支付成功,根据data的返回结果告知用户该订单支付有没有成功
编写对应的视图函数,在初始化之前都与查询支付结果一致,最后调用的支付宝接口不同
# 请求方式: ajax post
# 前端请求的参数: 订单id(order_id)
# order/pay
class OrderPayView(View):
“”“订单支付”“”
def post(self, request):
用户是否登录
user = request.user
if not user.is_authenticated:
return JsonResponse({‘res’: 0, ‘errmsg’: ‘用户未登录’})
接收参数
order_id = request.POST.get(‘order_id’)
校验参数
if not order_id:
return JsonResponse({‘res’: 1, ‘errmsg’: ‘无效的订单’})
try:
order = OrderInfo.objects.get(order_id=order_id,
user=user,
pay_method=3,
order_status=1)
except OrderInfo.DoesNotExist:
return JsonResponse({‘res’: 2, ‘errmsg’: ‘订单错误’})
业务处理:使用python sdk调用支付宝的查询接口
alipay初始化
app_private_key_string = open(“apps/order/app_private_key.pem”).read()
alipay_public_key_string = open(“apps/order/alipay_public_key.pem”).read()
app_private_key_string == “”"
-----BEGIN RSA PRIVATE KEY-----
base64 encoded content
-----END RSA PRIVATE KEY-----
“”"
alipay_public_key_string == “”"
-----BEGIN PUBLIC KEY-----
base64 encoded content
-----END PUBLIC KEY-----
“”"
app_private_key_string = os.path.join(settings.BASE_DIR, ‘apps/order/app_private_key.pem’)
alipay_public_key_string = os.path.join(settings.BASE_DIR, ‘apps/order/alipay_public_key.pem’)
alipay = AliPay(
appid=“2016092700608687”, # 应用id
app_notify_url=None, # 默认回调url
app_private_key_string=app_private_key_string,
支付宝的公钥,验证支付宝回传消息使用,不是你自己的公钥,
alipay_public_key_string=alipay_public_key_string,
sign_type=“RSA2”, # RSA 或者 RSA2
debug=True # 默认False, 此处沙箱模拟True
)
调用支付宝的交易查询接口
while True:
response = alipay.api_alipay_trade_query(order_id)
response = {
“trade_no”: “2017032121001004070200176844”, # 支付宝交易号
“code”: “10000”, # 接口调用是否成功
“invoice_amount”: “20.00”,
“open_id”: “20880072506750308812798160715407”,
“fund_bill_list”: [
{
“amount”: “20.00”,
“fund_channel”: “ALIPAYACCOUNT”
}
],
“buyer_logon_id”: “csq***@sandbox.com”,
“send_pay_date”: “2017-03-21 13:29:17”,
“receipt_amount”: “20.00”,
“out_trade_no”: “out_trade_no15”,
“buyer_pay_amount”: “20.00”,
“buyer_user_id”: “2088102169481075”,
“msg”: “Success”,
“point_amount”: “0.00”,
“trade_status”: “TRADE_SUCCESS”, # 支付结果
“total_amount”: “20.00”
}
查询接口调用是否成功
code = response.get(‘code’)
接口调用是否成功 && 支付是否成功
if code == ‘10000’ and response.get(‘trade_status’) == ‘TRADE_SUCCESS’:
支付成功
获取支付宝交易号
trade_no = response.get(‘trade_no’)
更新订单状态
order.trade_no = trade_no
order.order_status = 4 # 待评价
order.save()
返回应答
return JsonResponse({‘res’: 3, ‘message’: ‘支付成功’})
elif code == ‘40004’ or (code == ‘10000’ and response.get(‘trade_status’) == ‘WAIT_BUYER_PAY’):
等待买家付款
import time
time.sleep(5)
continue
else:
支付出错
return JsonResponse({‘res’: 4, ‘errmsg’: ‘支付失败’})
注:当code返回值为’40004’ 时,需要进行处理
处理js
$.post(‘/order/check’, params, function () {
if (data.res == 3){
alert(‘支付成功’)
//重新加载
location.reload()
}
else{
alert(data.errmsg)
}
})
配置order/chexk的URL,进行测试
P98 订单评论代码走读
目前页面已经可以查询支付结果,页面上已经是待评价,但是后面依旧显示【去付款】,此时应该显示【待评论】,点击之后会跳转到相应的评论页面。查看源代码可以看到,此时td中的自定义属性status=4,这部分可以通过在页面中添加js,根据状态status的不同,改成不同的内容。
评论的代码:遍历每个获取到的a标签,遍历的时候要获取到自定义属性status,
如果status=1,则设置为去支付,
如果status=4,则设置为去评价,
如果status=5,则设置为已完成。
订单评论URL的设置:
path(‘comment/(?P<order_id>.+)’, CommentView.as_view(), name=‘comment’) # 订单评价
下面如果用户去点击它,不同的状态对应不同的处理
{% block bottomfiles %}
{% endblock bottomfiles %}
测试js代码无响应时:检查元素–》network
评价页面的地址:/order/comment/order_id
点击去评价—》跳转到对应的页面 /order/comment/order_id,去路由系统中查找对应的views函数。
分析评价页面显示内容:
基本信息和订单页面显示基本一致,只需 在下方添加一个评论的输入框
view中的类视图:【get基与订单页面显示时的处理过程一致,无需详述】
class OrderCommentView(LoginRequiredMixin, View):
def get(self, request, order_id):
“”“展示评论页”“”
…
在页面order_comment.html中,前面商品显示和订单页面一致,在下方每个商品对应一个评论的输入框,评论框中定义了一个元素input隐藏域
name=“sku_{{ forloop.counter }}”
该元素对应的值:评论的时候需要接收是哪一个商品的评论,隐藏域对应的值是跟这个商品关联的商品id。商品对应的评论内容对应在输入的文本框中,文本框中对应的内容是 name=“content_{{ forloop.counter }}”
将其通通放置在表单中,在表单中还包含了两个隐藏域
其一是订单id,你需要对那个订单中的商品信息进行评论,在其中写一个隐藏域为 name=“order_id”,其值为订单id—》value=“{{ order.order_id }}”
以及订单中有几个商品total_count,其值为订单中跟订单关联的商品的长度,即订单中商品的个数
用户输入完后点击提交,订单中的商品都会被提交。
点击提交之后,代表向当前地址发起一个post请求,在对应的视图中会有一个对应的post函数,处理评论内容
在该部分,首先要在地址中获取到订单的id,获取到之后进行数据校验,订单中有几个商品就应该对应多少评论,将total_count值进行接收,接收之后就可以获取到评论一共有多少条。
# 循环获取订单中商品的评论内容《—注:每个商品对应的名字是不一样的,通过forloop.counter1234往下数,遍历几次就应该有几个,在处理的时候也应该通过遍历拿到对应的值,每找到一个就应该获取商品的id,以及对应的评论内容,将其赋值给order_goods的comment,并做一个更新。
如果获取到了所有的订单商品的评论且进行了更新,则将该订单的状态设置为已完成,进行更新,最终将页面跳转到订单页面
拿到数字之后需要循环获取商品的评论,从1到total_count+1,获取到的 第一次就是1,在循环中获取被评论的商品id,名字对应的值是商品的id,获取到值之后即可获取到这是哪一条商品的评论。获取到评论内容sku_1对应下面的content_1,其余同理。没有内容则返回空。
根据order和sku_id查询到order_goods中,并将评论内容写到对应的order_goods.comment中,如果出错,则continue,继续看下一个。如果查到了,则需要将order_goods中的内容修改为接收到的content,并进行保存。
进行测试:下单,评论
此时商品详情页的评论部分应该会显示评论内容(此时在商品介绍下面显示的,进行修改),在detail.html页面写一段js代码
此时对应的元素中添加id=“tag_detail”,id=“tag_comment”
- 商品介绍
- 评论
- {{ sku.goods.detail|safe }}
# style="display: none;默认不显示
{% for sku in sku_orders %}
评论时间: {{ sku.update\_time }} 用户名:{{ sku.order.user.username }}- 评论内容: {{ sku.comment }}
{% empty %}
暂时没有评论信息
{% endfor %}
商品介绍和评论只显示一个,且点击哪一个,哪一个应该处于激活状态
class=‘active’
// 商品介绍与评论tag的切换
$(‘#tag_detail’).click(function () {
$(‘#tag_comment’).removeClass(‘active’)
$(this).addClass(‘active’)
$(‘#tab_detail’).show()
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)
Java高频面试专题合集解析:
当然在这还有更多整理总结的Java进阶学习笔记和面试题未展示,其中囊括了Dubbo、Redis、Netty、zookeeper、Spring cloud、分布式、高并发等架构资料和完整的Java架构学习进阶导图!
更多Java架构进阶资料展示
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!**因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。[外链图片转存中…(img-AV1QGeC1-1713391391888)]
[外链图片转存中…(img-D6pJ9Tpj-1713391391888)]
[外链图片转存中…(img-gGEXcDgO-1713391391889)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)
Java高频面试专题合集解析:
[外链图片转存中…(img-bPMbzPEm-1713391391889)]
当然在这还有更多整理总结的Java进阶学习笔记和面试题未展示,其中囊括了Dubbo、Redis、Netty、zookeeper、Spring cloud、分布式、高并发等架构资料和完整的Java架构学习进阶导图!
[外链图片转存中…(img-JGnHddd5-1713391391889)]
更多Java架构进阶资料展示
[外链图片转存中…(img-ecnru6II-1713391391889)]
[外链图片转存中…(img-sI605PRo-1713391391889)]
[外链图片转存中…(img-19fqHEQT-1713391391890)]
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!