1. 支付宝API文档

* 1. 官方文档地址: https://opendocs.alipay.com/common/02fwvj/* 2. 支付宝API:六大接口  地址: https://docs.open.alipay.com/270/105900/* 3. 支付宝工作流程  地址: https://docs.open.alipay.com/270/105898/* 4. 支付宝8次异步通知机制(支付宝对我们服务器发送POST请求, 索要 success 7个字符)地址: https://docs.open.alipay.com/270/105902/

2. 沙箱环境下测试支付

2.1 测试步骤

沙箱环境是协助开发者进行接口开发及主要功能联调的模拟环境.
* 1. 电脑网站支付API: https://docs.open.alipay.com/270/105900/* 2. 在沙箱环境下实名认证地址: https://openhome.alipay.com/platform/appDaily.htm?tab=info* 3. 完成RSA密钥生成:https://docs.open.alipay.com/291/105971* 4. 在开发中心的沙箱应用下设置应用公钥:填入生成的公钥文件中的内容* 5. Python支付宝开源第三方框架:https://github.com/fzlee/alipay* 6. pip install python-alipay-sdk --upgrade* 7. 代码的密钥配置格式alipay_public_key.pem = """-----BEGIN PUBLIC KEY-----支付宝公钥-----END PUBLIC KEY-----"""app_private_key.pem = """-----BEGIN RSA PRIVATE KEY-----用户私钥-----END RSA PRIVATE KEY-----"""* 8. 支付宝网关真实:https://openapi.alipay.com/gateway.do沙箱:https://openapi.alipaydev.com/gateway.do  # 带dev

2.2 沙箱环境配置

* 1. 认证
https://developers.alipay.com/dev/workspace/register?from=http%3A%2F%2Fopenhome.alipay.com%2Fplatform%2FappDaily.html

* 2. 配置沙箱应用1. 选择网页&移动应用2. 接口加签方式选择自定义密钥3. 公钥模式->设置并查看

* 3. 下载&安装 支付宝密钥生成器下载地址: https://gw.alipayobjects.com/os/bmw-prod/02b946e1-9faf-4394-8004-d241443c874e.exe

* 4. 生成密钥

* 5. 复制公钥

* 6. 将应用公钥填写到到沙箱中

* 7. 生成支付宝公钥

* 8. 沙箱账户

* 9. 下载支付宝客户端沙箱版本

* 10. 手机端安装支付宝沙箱版买家账号 aijatb2766@sandbox.com登录密码 111111支付密码 111111

2.3 测试代码

* 1. 安装python-alipay-sdk模块pip install python-alipay-sdk如果抛ssl相关错误, 代表缺失pyopenssl模块pip install pyopenssl
* 2. 测试代码 (设置密钥的时候不要有多余的空格)
from alipay import AliPay# 应用程序私钥
app_private_key_string = """-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAhe3/9BI+qAaoRprc1f3liRA9v9WYL/n9Xp9Cc+PPcbkfrI/pN7q/GOaaYf521wbCEXmZYW4qQVX4mP4i2S05FwRu2B1/7Ux4oREyM+0vazHrBCNyE7erqGV2+8GsLDJRnyQXApbN79FR40+chrlc5Dt29mGCtv1DKz1l/mnNJKj0srP55joxXfLmDwfeLcNcTEblJsxpgi1TKlnEG0b/0RtkFhGGOsUrClNiLxBK5iZJBUt+QX2P97MDHj8BIqLqKITBe+aSbwHcDzl587S9cq1142y41NVhEjlC20+2iHsESA82HQo8iH7i1A6BzHvRlvk7HoUqeYb8p91ZLI0d7QIDAQABAoIBAAuppQ9RA2nIYqD7XV25JWLhKi9pXz5WS60Qu02yOd9SWqLBSXLT7U4yzqDX8utYqE+zQhsM59sWrHZOMySsXntVpH1nXDuC3EJSaAfDkMyJ5UhP+eAjr2wTod/chqy2mQr9ro9IKJjIppPf2+aTf7ZUQ1DDPwnGVjIOv7H+7qFRf/XBUVyyW1j4uwFlN/ZporDa7B6lwqWgH9+Yvo7w3/Pmjo78z2BKZzPQJa2sq3WxolcphDImflycHzno8jIn+QnB3XmlCu7yxsx4HYXMQMKY8i3gzPejdOdWsjbAuxI3mfyyELN2W8ZCpaZKWfKEhpxR4DA56byZtESWN+s1HmECgYEA7WFD1vhO2gwWyiFBzyu9yHOTpmcH6mBJ30Km7mErPHD3yHOiaCv59Ba4XTD629073m3Q2z6EXpmv7Fwz+uezac12BCBeg7AU9WfBJPiSHBRvwR4qXbZzbU91tElWDFYlwCmjd3Sp5oTGWSpWU/BW3hkxx6KYIwnYUIDIhTChCQUCgYEAkG9j4pp/XisOCUM3cQmTaUzDWZfOILDQUjrWCnLEHfPJPrX5qjtkSkZ8Z6Q4GjGNVg5m4i98VRGkt3rY0wsnsuCDFrws7TzlvDVP9fo0DgTalO4JCQ4gE9fGZypbjRYtbHobTEsGnGmeE3eRrlY5cfNfcwfrr1vKceo7slJzNckCgYAd+8kr4BVlqV0/js/XMTk5lo+x1xXC3wK1tp+LQK7LZaGGqkR7UAK0eCI1czhciSdEwy48YzspD9SO0F6odJfO52revo/xpk4faUmWN+eMsHAlPoAvchpGVmERsqmxyTffe+Lv9cZ4HZFINfbNh3ARgbEt/DWnR1kRYhLx7+CHWQKBgQCNWGTwkm9IsWu4Bs6P0WYwK04VNGklNsN3ZVqnuO5RvYxY0W71d8/KnDYMmvnIMGv3JnrqqLvM6EpAwHjF92mvNOU0b4yr0eelCqsoteURPxDFpDi1YtxjbssblKkpZeWn/csPG3DpyrZGqMGpUXpAGIJ1KPAtmO+CEU7AUM2seQKBgQDmPWXLUXx6FKRVUYLgALEY+PkIc3feRw1gdhSa7Q8/sQw8jugD92YnZ9gdBkZxIlPHqiMpvba+adbFTcTIPx6Myoy8X8mfWFTHU0su+tiwiUXGWkxAvYU/8ebSABs6HRgFGkbJEDU7TqPOvDCLFjAFdTQJ/LKGwbI+E7XfR0f4Sw==
-----END RSA PRIVATE KEY-----"""# 支付宝公钥
alipay_public_key_string = """-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtfLBZtfSKJ3mwThLuRnLO1eEh5jf2fXDlC06VLH/lmFFJrZBfkJqzE9T41k5JoGpluI6r03vo6Vx29UYOP0bf531UUr9Bpg2TLBwmUVQYoY15plsUjKkqTqDYq55m3cYJpn0TBROMyurbGs4i0TcNq35zjC2wbl0DWmxmOhWyg3NhUu9uqWUJGbfxTuowKQxaK/3XDoOLx+mQCytz0JvNevUhBhOu6eZLCh3hNOswu8gMf3a5kCReya24uXOd26SKd5HYlwFHk4ndSSjwIYulQs9hnlSbkJn+zykjc6qdDQLMWuqkSKlb5Snu7TCh5FNUO4KsFqk2bEXgysJy23lhwIDAQAB
-----END PUBLIC KEY-----"""
alipay = AliPay(# 应用idappid="2021000120612450",# 默认回调app_notify_url=None,# 应用私钥字符串app_private_key_string=app_private_key_string,# 支付宝公钥字符串alipay_public_key_string=alipay_public_key_string,# 加密方式sign_type="RSA2",  # RSA or RSA2# 开启调试(上线关闭)debug=True
)# 网关地址(上线之后需要修改)
alipay_url = 'https://openapi.alipaydev.com/gateway.do?'
# 生成对象(交易页面)
order_string = alipay.api_alipay_trade_page_pay(# 订单号唯一标识out_trade_no="202206041111",# 价格total_amount=9.9,# 商品名称subject='<<肾宝>>男人要肾好,就要喝肾宝。喝了之后,比刘翔快,比姚明高。一瓶提神醒脑,两瓶永不疲劳,三瓶长生不老。噢耶...',# 支付成功 get同步回调, 前台展示的页面, # return_url="",# 支付成功 post异步回调, 向后端发送请求 修改订单状态(以支付)# notify_url=""
)# 生成支付链接 (网关 + 交易页面)
print(alipay_url + order_string)
* 3. 运行程序, 得到一个商品支付地址:
https://openapi.alipaydev.com/gateway.do?app_id=2021000120612450&biz_content=%7B%22subject%22%3A%22%5Cu7537%5Cu4eba%5Cu8981%5Cu80be%5Cu597d%5Cuff0c%5Cu5c31%5Cu8981%5Cu559d%5Cu80be%5Cu5b9d%5Cu3002%5Cu559d%5Cu4e86%5Cu4e4b%5Cu540e%5Cuff0c%5Cu6bd4%5Cu5218%5Cu7fd4%5Cu5feb%5Cuff0c%5Cu6bd4%5Cu59da%5Cu660e%5Cu9ad8%5Cu3002%5Cu4e00%5Cu74f6%5Cu63d0%5Cu795e%5Cu9192%5Cu8111%5Cuff0c%5Cu4e24%5Cu74f6%5Cu6c38%5Cu4e0d%5Cu75b2%5Cu52b3%5Cuff0c%5Cu4e09%5Cu74f6%5Cu957f%5Cu751f%5Cu4e0d%5Cu8001%5Cu3002%5Cu5662%5Cu8036...%22%2C%22out_trade_no%22%3A%22%5Cu80be%5Cu5b9d%22%2C%22total_amount%22%3A9.9%2C%22product_code%22%3A%22FAST_INSTANT_TRADE_PAY%22%7D&charset=utf-8&method=alipay.trade.page.pay&notify_url=https%3A%2F%2Fwww.luffycity.com%2Ffree-course&return_url=https%3A%2F%2Fwww.luffycity.com%2Ffree-course&sign_type=RSA2&timestamp=2022-06-03+23%3A26%3A25&version=1.0&sign=BttMCJX7M6PGFNq1ldnaxR0HM1zLcVRbCeC7xmM3NDceOvs2U8tnRHmZb1gKlbozH2T8arQfPHrtJQ4dkjpptQHJMGWD6AU0cQzSi%2F4dI3m5GKEK4Y96w9OC1UNTjp80TXwZ1OlPCTzpHB4SAkjHeRSbxPXGJpvsvt7TjKOhEIbZ02xNAfawLa%2F2ptv6FXtplSb1ckmo1aV2k%2Fa8rQxRUVA52eeM9XNoH7GpoUiDAi47NZalgm1bcsrvrHfDizR83AU39hddA8EkqxFFAlFAOpVCRH6ZWSFgxJfTap4b6KkViYkaee74eELMgvG9qcuV9b04DB3JdFfbctl1VXyqrQ%3D%3D
* 4. 使用浏览器打开地址(如果浏览器打开的网站太多, 可能会提示钓鱼网站之类的提示.)

* 5. 使用支付宝沙箱版本支付

* 6. 沙箱账户中查看余额

3. 二次封装支付宝

在项目lib目录下重新封装支付宝
配置可以写在iPay下的settings.py文件或项目的dev.py配置文件中.
lib├── iPay                            # aliapy二次封装包│   ├── __init__.py             # 包文件│   ├── pem                       # 公钥私钥文件夹│   │   ├── alipay_public_key.pem    # 支付宝公钥文件│   │   ├── app_private_key.pem        # 应用私钥文件│   ├── pay.py                     # 支付文件└── └── settings.py | dev.py         # 应用配置
* 1. 在项目lib目录下新建iPay目录, 再按照目录结构创建其他文件和目录.
* 2. 在app_private_key.pem中写入支付宝公钥
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAhe3/9BI+qAaoRprc1f3liRA9v9WYL/n9Xp9Cc+PPcbkfrI/pN7q/GOaaYf521wbCEXmZYW4qQVX4mP4i2S05FwRu2B1/7Ux4oREyM+0vazHrBCNyE7erqGV2+8GsLDJRnyQXApbN79FR40+chrlc5Dt29mGCtv1DKz1l/mnNJKj0srP55joxXfLmDwfeLcNcTEblJsxpgi1TKlnEG0b/0RtkFhGGOsUrClNiLxBK5iZJBUt+QX2P97MDHj8BIqLqKITBe+aSbwHcDzl587S9cq1142y41NVhEjlC20+2iHsESA82HQo8iH7i1A6BzHvRlvk7HoUqeYb8p91ZLI0d7QIDAQABAoIBAAuppQ9RA2nIYqD7XV25JWLhKi9pXz5WS60Qu02yOd9SWqLBSXLT7U4yzqDX8utYqE+zQhsM59sWrHZOMySsXntVpH1nXDuC3EJSaAfDkMyJ5UhP+eAjr2wTod/chqy2mQr9ro9IKJjIppPf2+aTf7ZUQ1DDPwnGVjIOv7H+7qFRf/XBUVyyW1j4uwFlN/ZporDa7B6lwqWgH9+Yvo7w3/Pmjo78z2BKZzPQJa2sq3WxolcphDImflycHzno8jIn+QnB3XmlCu7yxsx4HYXMQMKY8i3gzPejdOdWsjbAuxI3mfyyELN2W8ZCpaZKWfKEhpxR4DA56byZtESWN+s1HmECgYEA7WFD1vhO2gwWyiFBzyu9yHOTpmcH6mBJ30Km7mErPHD3yHOiaCv59Ba4XTD629073m3Q2z6EXpmv7Fwz+uezac12BCBeg7AU9WfBJPiSHBRvwR4qXbZzbU91tElWDFYlwCmjd3Sp5oTGWSpWU/BW3hkxx6KYIwnYUIDIhTChCQUCgYEAkG9j4pp/XisOCUM3cQmTaUzDWZfOILDQUjrWCnLEHfPJPrX5qjtkSkZ8Z6Q4GjGNVg5m4i98VRGkt3rY0wsnsuCDFrws7TzlvDVP9fo0DgTalO4JCQ4gE9fGZypbjRYtbHobTEsGnGmeE3eRrlY5cfNfcwfrr1vKceo7slJzNckCgYAd+8kr4BVlqV0/js/XMTk5lo+x1xXC3wK1tp+LQK7LZaGGqkR7UAK0eCI1czhciSdEwy48YzspD9SO0F6odJfO52revo/xpk4faUmWN+eMsHAlPoAvchpGVmERsqmxyTffe+Lv9cZ4HZFINfbNh3ARgbEt/DWnR1kRYhLx7+CHWQKBgQCNWGTwkm9IsWu4Bs6P0WYwK04VNGklNsN3ZVqnuO5RvYxY0W71d8/KnDYMmvnIMGv3JnrqqLvM6EpAwHjF92mvNOU0b4yr0eelCqsoteURPxDFpDi1YtxjbssblKkpZeWn/csPG3DpyrZGqMGpUXpAGIJ1KPAtmO+CEU7AUM2seQKBgQDmPWXLUXx6FKRVUYLgALEY+PkIc3feRw1gdhSa7Q8/sQw8jugD92YnZ9gdBkZxIlPHqiMpvba+adbFTcTIPx6Myoy8X8mfWFTHU0su+tiwiUXGWkxAvYU/8ebSABs6HRgFGkbJEDU7TqPOvDCLFjAFdTQJ/LKGwbI+E7XfR0f4Sw==
-----END RSA PRIVATE KEY-----
* 3. 在alipay_public_key.pem文件中写入应用程序私钥
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtfLBZtfSKJ3mwThLuRnLO1eEh5jf2fXDlC06VLH/lmFFJrZBfkJqzE9T41k5JoGpluI6r03vo6Vx29UYOP0bf531UUr9Bpg2TLBwmUVQYoY15plsUjKkqTqDYq55m3cYJpn0TBROMyurbGs4i0TcNq35zjC2wbl0DWmxmOhWyg3NhUu9uqWUJGbfxTuowKQxaK/3XDoOLx+mQCytz0JvNevUhBhOu6eZLCh3hNOswu8gMf3a5kCReya24uXOd26SKd5HYlwFHk4ndSSjwIYulQs9hnlSbkJn+zykjc6qdDQLMWuqkSKlb5Snu7TCh5FNUO4KsFqk2bEXgysJy23lhwIDAQAB
-----END PUBLIC KEY-----
* 4. settings.py 配置文件中填写参数
import os# 获取到iPay的路径
BASE_DIR = os.path.dirname(__file__)
print(BASE_DIR)
# 得到iPay下的pem目录路径
PEM_DIR = os.path.join(BASE_DIR, 'pem')
# 应用程序私钥地址
APP_PRIVATE_KEY_URL = os.path.join(PEM_DIR, 'app_private_key.pem')
# 支付宝公钥地址
ALIPAY_PUBLIC_KEY_URL = os.path.join(PEM_DIR, 'alipay_public_key.pem')# 应用id
APPID = '2021000120612450'# 默认回调
APP_NOTIFY_URL = None# 应用私钥字符串
with open(APP_PRIVATE_KEY_URL, mode='r', encoding='utf8') as rf1:APP_PRIVATE_KEY_STRING = rf1.read()# 支付宝公钥字符串
with open(ALIPAY_PUBLIC_KEY_URL, mode='r', encoding='utf8') as rf2:ALIPAY_PUBLIC_KEY_STRING = rf2.read()# 加密方式
SIGN_TYPE = "RSA2"# BUG模式
DEBUG = True# 沙箱网关 ( DEBUG = True )
dev = 'https://openapi.alipaydev.com/gateway.do?'# 真实网关 ( DEBUG = False )
master = 'https://openapi.alipay.com/gateway.do?'# 网关
ALIPAY_URL = dev if DEBUG else master
* 5. 在pay.py生成alipay支付对象
# 导入配置参数
from .settings import *# 导入AliPay模块
from alipay import AliPay# 生成alipay对象
alipay = AliPay(# 应用idappid=APPID,# 默认回调app_notify_url=APP_NOTIFY_URL,# 应用私钥字符串app_private_key_string=APP_PRIVATE_KEY_STRING,# 支付宝公钥字符串alipay_public_key_string=ALIPAY_PUBLIC_KEY_STRING,# 加密方式sign_type=SIGN_TYPE,# 调试开关(上线设置False)debug=False,)
* 6. __init__.py 中导入alipay对象 与 网关在导入iPay时会自动触发__init__.py,则导入alipay对象 与 网关
# 导入alipay对象 与 网关
from .pay import alipay
from .settings import ALIPAY_URL"""
在导入iPay时则导入alipay对象 与 网关
from luffy/lib import iPay
iPay.alipay ...
iPay.ALIPAY_URL ...
"""

4. 订单接口

4.1 创建app

* 1. 新建订单app
cd luffy/apps/
python ../../manage.py startapp order
* 2. 注册app
INSTALLED_APPS = [...'order'
* 3. 总路由配置
path('order/', include('order.urls')),
* 4. 子路由 先在order目录下新建urls.py
from django.urls import re_path, includefrom . import views
# 导入SimpleRouter
from rest_framework.routers import SimpleRouter# 生成对象
router = SimpleRouter()
# 注册路由
router.register('pay', views.PayView, 'pay')# 路由列表
urlpatterns = [re_path('', include(router.urls)),
]

4.2 表设置

1. 订单表
2. 订单表与课程表的第三张表
from django.db import models# 导入用户表
from user.models import User
# 导入课程表
from course.models import Course# 订单表
class Order(models.Model):"""用户表(一) 对 (多)订单表一个用户可以有多个订单, 一个订单只能有一个用户. 一对多关系, 关联字段写在多的一方."""# 支付状态status_choices = ((0, '未支付'),(1, '已支付'),(2, '以超时'),(3, '超时取消'))# 支付状态pay_choices = ((0, '支付宝'),(1, '微信'))# 订单标签subject = models.CharField(max_length=150, verbose_name='订单标题')# 订单总价total_amount = models.DecimalField(max_digits=10, decimal_places=2, verbose_name='订单总价', default=0)# 订单号 唯一out_trade_no = models.CharField(max_length=64, verbose_name='订单号', unique=True)# 订单状态order_status = models.SmallIntegerField(choices=status_choices, default=0, verbose_name='订单状态')# 支付方式pay_type = models.SmallIntegerField(choices=pay_choices, default=0, verbose_name='支付方式')# 支付时间pay_time = models.DateTimeField(null=True, verbose_name='支付时间')# 外键user = models.ForeignKey(to=User, related_name='order_user', on_delete=models.DO_NOTHING, db_constraint=False,verbose_name='下单用户')# 创建时间created_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')# 最后更新时间updated_time = models.DateTimeField(auto_now=True, verbose_name='最后更新时间')# 定义元类class Meta:# 表名db_table = 'luffy_order'# 定义一个变量verbose_name = '订单记录'# 后台展示的表名verbose_name_plural = verbose_name# 打印对象时展示商品的名字与价格def __str__(self):return f'{self.subject} - ¥:{self.total_amount}'# 订单详情
class OrderDetail(models.Model):"""订单表(多) 对 (多)课程表一个订单可以有多个课程, 一个课程可以在对个订单中第三张表外键关联订单表, 关联课程表, 创建额外的字段"""# 外键 关联订单表order = models.ForeignKey(to=Order, related_name='order_courses', on_delete=models.CASCADE, db_constraint=False,verbose_name='订单外键')# 外键 关联关联课程表course = models.ForeignKey(to=Course, related_name='course_orders', on_delete=models.CASCADE, db_constraint=False,verbose_name='课程外键')# 原价price = models.DecimalField(max_digits=6, decimal_places=2, verbose_name='课程原价')# 实价real_price = models.DecimalField(max_digits=6, decimal_places=2, verbose_name='课程实价')# 定义元类class Meta:# 表名db_table = 'luffy_order_detail'# 定义一个变量verbose_name = '订单详情'# 定向详情verbose_name_plural = verbose_name# 打印对象时展示 课程名字:课程价格def __str__(self):try:return f'{self.course.name}的订单: {self.order.out_trade_no}'# 没有录数据的时候不会至于报错except:return super().__str__()
数据库迁移:
python manage.py makemigrations
python manage.py migrate
# 修改order目录下的admin.py 文件将表注册到后台
import xadminfrom . import models
# 将订单表与订单详情表上条件到后台管理
xadmin.site.register(models.Order)
xadmin.site.register(models.OrderDetail)
# 后台展示中文菜单
# 修改order下的__init__.py
default_app_config = "order.apps.OrderConfig"# 修改order下的apps.py
from django.apps import AppConfigclass OrderConfig(AppConfig):name = 'order'verbose_name = '订单'

4.3 接口

支付接口分析
1. 支付接口生成订单, 生成支付连接, 返回支付链接2. 支付成功时: 支付宝异步回调的post接口验证签名, 修改订单状态3. 支付成功时: 支付宝同步get回调 (支付成功后展示的页面)vue页面创建时 生命周期钩子函数发送请求去后端查询订单状态(可有可无)
1. 订单总价校验从数据库中获取对应的课程对象, 课程的价格取出来累加, 再与前端传递的值比较是否相等.
2. 生成订单号使用一个uuid生成一个随机字符串
3. 支付链接生成使用自己封装的支付宝模块生成支付连接, 将支付链接存放在序列化对象的context属性中.
4. request.user 中获取用户对象视图类中重写create方法, 为context参数设置值为request, 序列化中取出用户对象
5. 入库(两张表)的信息准备将用户对象保存到attrs字典中
6. 重写序列化的create方法将课程从validated_data字典中pop出来...先创建支付表的数据, 在遍历创建支付详情表的数据
* 1. 模型序列化器
# 导入模型序列化
from rest_framework import serializers# 导入模型层
from . import models# 导入异常模块
from rest_framework.exceptions import ValidationErrorclass OrderModelSerializer(serializers.ModelSerializer):course_obj = models.Course.objects.all()"""PrimaryKeyRelatedField 将 {'course': [课程id1, 课程id2, ...]} 转为 {'course': [课程1_obj, 课程2_obj, ...]}queryset参数对于转换表的queryset对象, 提交的值是一个列表, 设置many=True, 只写模式Course表与 Order可以没有关系,只要设置queryset的参数值是一张表的数据即可 通过id获取到表对应的数据对象  """course = serializers.PrimaryKeyRelatedField(queryset=course_obj, many=True, write_only=True)class Meta:model = models.Order# 转换的字段 订单标签 价格 支付方式 购买的课程fields = ['subject', 'total_amount', 'pay_type', 'course']# (设置了默认值, 不会提示价格 支付方式字段必填, 使用require提示字段必须填写)extra_kwargs = {'total_amount': {'required': True},'pay_type': {'required': True},}# 全局校验def validate(self, attrs):# 1. 校验价格, 检验不成功直接抛出异常, 校验成功返回价格total_amount = self._check_price(attrs)# 2. 生成订单号out_trade_no = self._gen_out_trade_no()# 3. 生成支付链接并保存到context参数中self._gen_pay_url(attrs, out_trade_no, total_amount)# 4. 获取用户对象 在视图类中重写create方法, 在生成模型序列化对象时将request通过context参数传递user_obj = self._get_user()# 5. 写入数据库前准备self._before_create(attrs, user_obj, out_trade_no)return attrs# 价格校验@staticmethoddef _check_price(attrs):# 获取提交的价格total_amount = attrs.get('total_amount')# 获取课程对象course_list = attrs.get('course')# 计算数据库中的价格total_price = 0for course_obj in course_list:total_price += course_obj.price# 判断值是都相等的if total_amount != total_price:# 价格不一致, 直接抛出异常raise ValidationError('价格不合法')return total_amount# 生成订单号@staticmethoddef _gen_out_trade_no():# 订单号是一个唯一字符串, 使用uuid即可from uuid import uuid4# uuid的类型值的格式 bce5eed9-8985-42ab-b37a-51bf8909c1c5  类型是  <class 'uuid.UUID'># 将类类型转为str类型, 再将-去除 bce5eed9898542abb37a51bf8909c1c5return str(uuid4()).replace('-', '')# 生成支付链接def _gen_pay_url(self, attrs, out_trade_no, total_amount):# 导入支付模块from luffy.lib import iPay# 获取商品名称(前端传入)subject = attrs.get('subject')# 导入回调地址from django.conf import settings# 网关地址alipay_url = iPay.ALIPAY_URL# total_amount是Decimal类型, 支付宝无法识别, 转为float类型即可print(total_amount, type(total_amount))  # 138.00 <class 'decimal.Decimal'>total_amount = float(total_amount)# 生成对象order_string = iPay.alipay.api_alipay_trade_page_pay(# 订单号唯一标识out_trade_no=out_trade_no,# 价格total_amount=total_amount,# 商品名称subject=subject,# 支付成功 get同步回调, 前台展示的页面,return_url=settings.NOTIFY_URL,# 支付成功 post异步回调, 向后端发送请求 修改订单状态(以支付)notify_url=settings.RETURN_URL)# 生成支付地址pay_url = alipay_url + order_string# 将支付链接保存到context中self.context['pay_url'] = pay_url# 获取用户def _get_user(self):# 从context中获取request对象request = self.context.get('request')# 放回user对象(登入成功之后request.user会有用户的数据对象)return request.user# 写入数据前准备@staticmethoddef _before_create(attrs, user_obj, out_trade_no):# 将用户对象写入到序列化字典中attrs['user'] = user_obj# 将订单号写入到序列化字典中attrs['out_trade_no'] = out_trade_nodef create(self, validated_data):# 将课程列表虚拟字段pop掉course_list = validated_data.pop('course')# 支付表数据写入(需要的数据, 'subject', 'total_amount', 'course', 'user')order_obj = models.Order.objects.create(**validated_data)# 写入支付详情表, 需要的数据(order, course, price, real_price)for course_obj in course_list:models.OrderDetail.objects.create(order=order_obj, course=course_obj, price=course_obj.price,real_price=course_obj.price)# 将支付对象返回, 返回了也没用到return order_obj
# 支付宝回调地地址
# 后台URL
BASE_URL = 'http://127.0.0.1:8000'
# 前台URL
LUFFY_URL = 'http://127.0.0.1:8080'# 后台异步回调地址
RETURN_URL = BASE_URL + '/order/success/'
# 前台同步回调地址(没有/结尾)
NOTIFY_URL = LUFFY_URL + '/pay/success'
* 2. 视图类单独使用rest_framework_jwt.authentication.JSONWebTokenAuthentication的话所有游客都可以访问, request.user 是AnonymousUser 匿名用户, 使用rest_framework内置权限类, 对登入用户进行校验, 匿名用户禁止访问!
from rest_framework.viewsets import GenericViewSet
from rest_framework.mixins import CreateModelMixin
from . import models
from .serializer import OrderModelSerializer
from rest_framework.response import Response
from rest_framework_jwt.authentication import JSONWebTokenAuthentication
from rest_framework.permissions import IsAuthenticated# 生成支付订单
class PayView(GenericViewSet, CreateModelMixin):# 登入认证类 + 限制类 现在限制只有登入用户才能使用authentication_classes = [JSONWebTokenAuthentication]  # 没有用户登入返回permission_classes = [IsAuthenticated]# 使用的表数据queryset = models.Order.objects.all()# 使用的模型序列化类serializer_class = OrderModelSerializer# 重写视图类的 create方法 (复制CreateModelMixin的create方法进行修改)def create(self, request, *args, **kwargs):# 生成序列化器时, 将request对象创建给模型序列化器 通过context参数serializer = self.get_serializer(data=request.data, context=request)# 校验数据serializer.is_valid(raise_exception=True)# 保存数据self.perform_create(serializer)headers = self.get_success_headers(serializer.data)return Response(serializer.context.get('pay_url'))

4.4 接口测试

* 1. 接口地址: http://127.0.0.1:8000/order/pay/

* 2. 需要先登入(post请求)登入地址: http://127.0.0.1:8000/user/login/?username=root&password=zxc123456

* 3. 携带token访问支付接口
key: Authorization
value: jwt eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxLCJ1c2VybmFtZSI6InJvb3QiLCJleHAiOjE2NTUyOTg4NTYsImVtYWlsIjoiMTM2QHFxLmNvbSJ9.7vHsFCMCM-8_Uc5-bOvEFMvUf4d5fBtCGayQG53mb2c
* token前面携带 jwt空格

* 4. 价格不合法测试三门课程都现在, 价格是138, 现在随便填写一个错误的价格

* 5. 价格正常测试, 返回支付地址

4.5 前台点击购买事件

为点击购买绑定事件, 免费课程页面, 页面详情, 搜索页面, 三个位置.
只有等有用户才能购买:
登入成功之后 this.$cookies.set('token', response.data.token) 将token保存到了scookies中
先从cokkies中获取token, 如果有值则可以发送请求, 如果没有值则提示用户登入.拿到支付地址之后跳转到支付页面(新开设页面)
window.open(url) 简写 open(url)
在当前页面打开网址
open(url, '_self')
<span class="buy-now" @click="buy_now(course)">立即购买</span>
// 购买课程
buy_now(course) {// 从cookies中获取tokenlet token = this.$cookies.get('token')// 判断是否为登入状态if (!token) {// 提示用户登入this.$message({message: '请先登入!'})return false}// 用户已经登入向后台发送请求this.$axios({method: 'post', url: this.$settings.base_url + '/order/pay/',headers: {Authorization: 'jwt ' + token}, data: {// 课程的名字作为商品的标题"subject": course.name,// 课程的价格"total_amount": course.price,// 支付方式"pay_type": 1,// 课程的id"course": [course.id]}}).then(response => {// 访问成功获取到支付链接 直接从response.data中获取数据(后端直接返回链接, 不是返回request.data)// 视图序列化器中报错会 需要从response.data.dat中获取错误信息// console.log(response.data)// 跳转到支付地址if (response.data.data) {this.$message({message: response.data.data  // 异常新 例如 { "total_amount": [ "该字段是必填项。" ] }})return false}// 返回的不是错误信息那么就是支付链接let pay_url = response.data// console.log(pay_url)// 跳转到支付链接open(pay_url, '_self')}).catch(error => {this.$message({message: '未知错误, 请联系管理员!'})})
}

4.6 同步回调页面

两部分:
1. 分析支付宝回调参数展示订单的支付情况
2. 向后端get请求, 查询后端是否修改订单状态
* 1. 新建支付成功展示的页面 PayCourse.vue
<template><div class="pay-success"><!--如果是单独的页面,就没必要展示导航栏(带有登录的用户)--><Header/><div class="main"><div class="title"><div class="success-tips"><p class="tips">您已成功购买 1 门课程!</p></div></div><div class="order-info"><p class="info"><b>订单号:</b><span>{{ result.out_trade_no }}</span></p><p class="info"><b>交易号:</b><span>{{ result.trade_no }}</span></p><p class="info"><b>付款时间:</b><span><span>{{ result.timestamp }}</span></span></p></div><div class="study"><span>立即学习</span></div></div></div>
</template><script>
import Header from "@/components/Head"export default {name: "PayCourse",data() {return {result: {},};},created() {// location.search 获取 url后拼接的参数:?及后面的所有参数 => ?a=1&b=2// console.log(location.search);// 解析支付宝回调的url参数let params = location.search.substring(1);  // 去除? 保留 a=1&b=2// 按&切分得到一个元组, 要有是一个空元组let items = params.length ? params.split('&') : [];// console.log(items)/*0: "charset=utf-8"1: "out_trade_no=1c6bd7ee950842249a7cb0b6956224d2"...*/// 将每一项逐个添加到args对象中for (let i = 0; i < items.length; i++) {  // 第一次循环a=1,第二次b=2// 按=切分得到 k = vlet k_v = items[i].split('=');  // ['a', '1']// 列表元素少与2直接忽略, 有键缺值的情况if (k_v.length >= 2) {// url编码反解 解码操作,因为查询字符串经过编码的// 解码klet k = decodeURIComponent(k_v[0]);// 解码value 并保存到result对象中this.result[k] = decodeURIComponent(k_v[1]);}}// 解析后的结果// console.log(this.result);/*app_id: "2021000120612450"auth_app_id: "2021000120612450"charset: "utf-8"*/// 把地址栏上面的支付结果,再get请求转发给后端this.$axios({method: 'get',// 携带同步回调的参数访问后端url: this.$settings.base_url + '/order/success/' + location.search,}).then(response => {console.log(response.data)if (response.data) {this.$message({message: '支付结果同步成功'})} else {this.$message({message: '支付结果同步失败'})}}).catch(() => {this.$message({message: '查询订单支付状态失败'})})},components: {Header,}
}
</script><style scoped>
.main {padding: 60px 0;margin: 0 auto;width: 1200px;background: #fff;
}.main .title {display: flex;-ms-flex-align: center;align-items: center;padding: 25px 40px;border-bottom: 1px solid #f2f2f2;
}.main .title .success-tips {box-sizing: border-box;
}.title img {vertical-align: middle;width: 60px;height: 60px;margin-right: 40px;
}.title .success-tips {box-sizing: border-box;
}.title .tips {font-size: 26px;color: #000;
}.info span {color: #ec6730;
}.order-info {padding: 25px 48px;padding-bottom: 15px;border-bottom: 1px solid #f2f2f2;
}.order-info p {display: -ms-flexbox;display: flex;margin-bottom: 10px;font-size: 16px;
}.order-info p b {font-weight: 400;color: #9d9d9d;white-space: nowrap;
}.study {padding: 25px 40px;
}.study span {display: block;width: 140px;height: 42px;text-align: center;line-height: 42px;cursor: pointer;background: #ffc210;border-radius: 6px;font-size: 16px;color: #fff;
}
</style>
* 2. 设置PayCourse的路由
// 导入支付成功提示页面
import PayCourse from "@/views/PayCourse"// 支付成功提示页面 (与后台中配置的一致)
{path: '/pay/success',name: 'PayCourse',component: PayCourse
},
# 前台同步回调地址(没有/结尾)
RETURN_URL = LUFFY_URL + '/pay/success'
* 3. vue中url地址中多余的/#/不删除的话回调地址路由匹配不成功http://127.0.0.1:8080/pay/success 路由匹配不成功
// 路由中设置 mode: "history", 删除url中多余的/#/
const router = new VueRouter({mode: "history",routes
})
* 4. 购买课程测试, 购买成功之后回调地址携带参数

回调地址+参数
http://127.0.0.1:8080/pay/success?
// 编码格式
charset=utf8
// 订单号
&out_trade_no=ffb82dd39bf043808c311b4ae983d9ec&method=alipay.trade.page.pay.return
// 价格
&total_amount=39.00
// 签名
&sign=KFPDpjwAGuUa73qsBctb0m9Nj0514PoQnxcMyH%2Fzw1rPmzY%2F1%2BTwH2v4BB1hSyc5%2BRV6TIc%2Bvz1SrchxtND20HU%2BOjLuT%2BxSlGK034OFUDpxtiy4ofkYWWqcvxz84sQ8BUifdGKWDFoH1cgagbJVGl3DO1Lce5HbsvNZY2GeYQTC%2BTIWCUMKCcWYP375%2FQFCFoMUWj6LNcIQk3rrKMNJm9B%2F%2BwryrcRMJT1cNZnuLhKiYTzcIFRWnDyVu4aVfKa3xJtIEbTKAqIVHK9LuV%2BkrkBqD1GAwOy1gwIlrkB4VJ%2BvwPuyRpmVzGK7zVq6JIVzZNVawjFBhduwuoG0eUH7oA%3D%3D
// 支付宝流水号
&trade_no=2022060922001441860503213387
// 授权商户的App id
&auth_app_id=2021000120612450
// 版本
&version=1.0
// 应用id
&app_id=2021000120612450
// 签名类型
&sign_type=RSA2
// 卖方id
&seller_id=2088621959339670
// 时间戳
&timestamp=2022-06-09+11%3A50%3A44#/
location.search 获取 url后拼接的参数
先去除? 在按&切分得到元组, 元组的元素'k=v',
在遍历元素, 按=切分得到一个元组 ['k', 'v']
对k, v的字符进行解码保存到一个对象中

4.7 订单状态接口

1. 查询订单状态接口 get请求
2. 修改订饭状态接口 post请求
* 1. 路由order.py的urls.py
# 订单状态接口
re_path('success', views.SuccessView.as_view())
# 支付状态
class SuccessView(APIView):@staticmethoddef get(request):# 从参数中获取订单号去数据库中查询订单的状态out_trade_no = request.query_params.get('out_trade_no')order_obj = models.Order.objects.filter(out_trade_no=out_trade_no).first()# 订单状态order_status = order_obj.order_status# 判断订单状态if order_status != 1:  # 1 为支付成功状态return Response(False)return Response(True)# 支付宝异步回调(只能上线之后访问, 支付宝无法访问到我电脑本地的地址)@staticmethoddef post(request):# 导入支付类from lib.iPay import alipayfrom utils.logger import log# 获取订单号out_trade_no = request.data.get('out_trade_no')# 获取支付时间pay_time = request.data.get('gmt_payment')# 验证签名 支付宝返回的urlencode格式被转为queryset_dictdata = request.data.dict()  # 装为普通字段sign = data.pop('sign')success = alipay.verify(data, sign)# 验证成功, 并且 data中trade_status的值必须是TRADE_SUCCESS或TRADE_FINISHEDif success and data['trade_status'] in ('TRADE_SUCCESS', 'TRADE_FINISHED'):#  更新订单状态与支付时间models.Order.objects.filter(out_trade_no=out_trade_no).update(order_status=1, pay_time=pay_time)# 记录日志log.info(f'{out_trade_no}支付成功')return Response('success')  # 验证成功必须返回successlog.info(f'{out_trade_no}支付失败')return Response('error')  # 验证不成功

5. 上传到远程仓库

在Terminai中输入git命令
5.1 前端
# 添加到暂存区
PS D:\vue\luffy_vue> git add .
# 提交到版本库
PS D:\vue\luffy_vue> git commit -m '完成支付页面'
# 下拉
PS F:\synchro\Project\luffy> git pull origin dev
# 上传到远端仓库
PS F:\synchro\Project\luffy> git push origin dev
5.2 后端
# 添加到暂存区
PS F:\synchro\Project\luffy> git add .
# 提交到版本库
PS F:\synchro\Project\luffy> git commit -m '完成支付功能'
# 下拉
PS F:\synchro\Project\luffy> git pull origin dev
# 上传到远端仓库
PS F:\synchro\Project\luffy> git push origin dev

5.学城项目 支付宝支付相关推荐

  1. 路飞学城项目介绍与分析

    路飞学城项目介绍与分析 文章目录 路飞学城项目介绍与分析 一.企业的web项目类型 二.企业项目开发流程 三.立项申请阶段 四.需求分析 1. 首页 2.注册登录 3.课程列表 4.课程详情 5.购物 ...

  2. Python高级全栈开发实战 老男孩课程S16+路飞学城项目+女神串讲 Python全栈直通车课程

    python高级全栈开发实战 老男孩课程S16+路飞学城项目+女神串讲 Python全栈直通车课程 Python高级全栈开发实战老男孩课程,是可以帮助同学们从零基础开始到项目开发实战的全栈课程,内容非 ...

  3. 路飞学城项目之课程搜索关键字接口及支付宝支付功能

    文章目录 1.课程之搜索关键字接口 2.支付宝支付功能 2.1.简单介绍 2.2.aliapy二次封装包 2.2.1.GitHub开源框架 2.2.2.依赖 2.2.3.结构 2.2.4.alipay ...

  4. 0. 学城项目 后端环境配置

    1. 企业的web项目类型 1. 商城 (某某团购)1.1 B2C直销商城 商家与会员直接交易(Busioness To Customer)1.2 B2B批发商城 商家与商家直接交易1.3 B2B2C ...

  5. 无人驾驶8: 粒子滤波定位(优达学城项目)

    优达学城无人车定位的项目实现: 粒子滤波算法流程图 粒子滤波的伪代码: step1:初始化 理论上说,当粒子数量足够多时,能准确地呈现贝叶斯后验分布,如果粒子太少,可能漏掉准确位置,粒子数量太多,会拖 ...

  6. 路飞学城项目之集成支付宝支付功能

    文章目录 1.订单表设计 2.支付接口类:order/views.py 3.支付接口序列化类:model/serializers 4.支付生成页面 5.支付成功的回调页面 6.支付成功的回调接口 1. ...

  7. 路飞学城项目之项目上线

    文章目录 1.购买服务器 2.连接服务器 3.服务器命令 3.1.管理员权限 3.2.配置终端 3.3.更新系统软件包 3.4.安装软件管理包和可能使用的依赖 3.5.检测是否成功:会将git作为依赖 ...

  8. 4.学城项目 课程接口

    1. Django缓存到redis中 django_redis模块.(继承django的cache, 使用方式进而cache的使用一致, 数据被加密存到redis中) * 1. pip install ...

  9. 路飞学城项目之首页轮播图定时更新、课程页面前端及课程表分析

    文章目录 1.首页轮播图定时更新(使用celery) 2.课程页面前端 3.课程表分析 3.1.课程表相关模型(实战课为例) 3.2.课程表数据录入 3.3.课程分类接口 3.3.课程分类前端展示(对 ...

  10. 2.学城项目 头部底部组件首页轮播图

    1. 下载框架/组件/库 * 1. 下载项目中需要使用的框架个组件. 1. axion 网络请求的第三方框架 (之前测试前后端通信已经安装过了) cnpm install axios -S 2. vu ...

最新文章

  1. 19.04.02笔记
  2. WPF ListView DoubleClick
  3. 针对校园某服务器的一次渗透测试
  4. 【Transformer】ACMix:On the Integration of Self-Attention and Convolution
  5. .NET Framework VS .NET Core
  6. 居中的文字在小屏幕下后面的换行
  7. 爱奇艺网络协程编写高并发应用实践
  8. .net反编译工具ILSpy
  9. h5背景图片尺寸怎么设置_html如何设置图片大小
  10. 树莓派摄像头——图像 视频采集
  11. java 佛祖保佑_佛祖保佑 永无bug 注释模板设置详解(仅供娱乐)
  12. java闹钟_JAVA 闹钟程序
  13. 无人值守安装操作系统
  14. 笔记本电脑桌面上计算机打不开怎么办,笔记本电脑开了机一直进不去桌面怎么办...
  15. 手机编程python可以实现吗?有哪些软件值得推荐?
  16. htc legend 升级到 android 2.2 froyo 后,输入法问题
  17. 巧借“中国制造2025”东风占领“智”高点
  18. 如何利用海关数据开发客户?
  19. eclispe override报错的解决办法
  20. target_link_libraries 和link_libraries区别

热门文章

  1. python operator用法,Python operator.eq()函数与示例
  2. OJ刷题二——设计输出实数格式
  3. 王家林Spark视频
  4. 201621123068 Week03-面向对象入门
  5. 【历史上的今天】7 月 23 日:Novell 挑战微软;计算机界的教育家出生;第一颗 Landsat 卫星发射
  6. 5G消息富媒体最新形态
  7. android 面包屑控件,Android —— 面包屑控件(BreadcrumbTreeView)
  8. 华为云迁移工具推荐最佳实践:物理服务器迁移到华为云
  9. 计算机模拟水循环的过程,袋装水模拟做科学小实验水循环(步骤图解)
  10. 系统封装 EasyBoot如何将WIN7安装版提取到光盘