34-支付宝支付

支付宝支付

一、快速连接通道

1. 支付宝

<1>. 支付宝API:六大接口

https://docs.open.alipay.com/270/105900/

<2>. 支付宝工作流程

https://docs.open.alipay.com/270/105898/

<3>. 支付宝8次异步通知机制(支付宝对我们的服务器发送POST请求,索要success7个字符)

https://docs.open.alipay.com/270/105902/

2. 沙箱环境

<1>. 在沙箱环境下实名认证

https://openhome.alipay.com/platform/appDaily.htm?tab=info

<2>. 电脑网站支付API

https://docs.open.alipay.com/270/105900/

<3>. 完成RSA秘钥生成

https://docs.open.alipay.com/291/105971

<4>. 在开发中心的沙箱应用下设置应用公钥

填入生成的公钥文件中的内容

<5>. Python支付宝开源框架

https://github.com/fzlee/alipay

pip install python-alipay-sdk --upgrade
<6>. 公钥私钥设置
"""
# alipay_public_key.pem
-----BEGIN PUBLIC KEY-----
支付宝公钥
-----END PUBLIC KEY-----

# app_private_key.pem
-----BEGIN RSA PRIVATE KEY-----
用户私钥
-----END RSA PRIVATE KEY-----
"""
<7>. 支付宝回调连接

开发:https://openapi.alipay.com/gateway.do
沙箱:https://openapi.alipaydev.com/gateway.do

二、支付流程图

image-20210222140253267

三、支付宝介入入门

1. 流程

'''
# 支付宝开放平台
	1. 服务范围(自研开发服务) -> 实名认证
	2. 控制台 -> 我的应用 -> 创建应用 -> 网页&移动应用 -> 支付接入 -> 应用名称 -> 应用图标 -> 
		1) 移动应用 -> 应用平台 -> Bundle ID ...
		2) 网页应用 (不成功. 需要使用营业执照) -> 网址url -> 简介 			
			注意: 先选择功能再审核
			能力列表:添加能力 -> 支付能力 -> 电脑网站支付  
			开发设置:
				加签管理 -> 公钥 -
				支付宝网关
				应用网关
				授权回调地址
	3. 文档 -> 网页 & 移动应用 接口文档能力列表
    	1) 开放能力: 
    		支付能力 -> 电脑网站支付
    	2) 产品介绍: 
    		注意: 会跳到支付宝的页面, 支付宝会有一个get页面回调, post数据返回后端回调
			费率: 0.6%
		3) 快速接入:
			SDK快速接入: python没有, 只能使用API开发
			支付流程: 下单 -> 商户系统 -> 支付宝 -> 回调(get显示订单结果, post修改订单状态)
		4) 支付API:
			公共请求参数
			请求参数
				订单号   out_trade_no
				总金额   total_amount
				订单标题  subjet
			公共响应参数
				支付宝交易号  trade_no
				我们的订单号  out_trade_no
		5) GitHub开源SDK
			pip install python-alipay-sdk
			
			
# 支付宝沙箱环境				
	1. 沙箱环境地址: https://openhome.alipay.com/platform/appDaily.htm
	2. 沙箱应用:
		APPID
		支付宝网关: 地址中带dev表示沙箱环境, 不带表示正式环境
		加密方式: 使用支付宝提供的密钥生成(支付宝开放平台组助手). 
			之前是xx.jar包, 现在变成xx.exe软件.  需要生成公钥和私钥
			将自己的公钥配置在支付宝中, 支付宝会生成一个支付宝的公钥.
	3. 项目中使用:
		注释 .read这里是操作文件的
    	app_private_key_string   配置自己的私钥
    	alipay_public_key_string 配置支付宝的公钥
    	注意: 不能有空格
    	
    	AliPay类中的参数配置: 
            APPID配置 沙箱环境的APPID
            sign_type 配置自己的 RSA2
            debug=False测试环境, True正式环境
    	
    	alipay.api_alipay_trade_page_pay中的参数配置:
            out_trade_no 配置自己的商品订单号
            total_amount 总金额
            subject 订单标题
            return_url  回调地址 (注意: 需要使用公网地址)
            notify_url  回调地址 
            支付宝网关 + order_string =>  生成连接地址
            提示: 生成连接地址打开会出现钓鱼网站异常
    
	4. 解决提示钓鱼问题:  浏览器里面有多个窗口
		沙箱环境存在的问题, 如果出现问题, 开无痕窗口即可, 付完之后会回调到之前配置的return_url中配置的网页
    	支付宝沙箱环境充值:
	    	控制台 -> 沙箱账号 -> 账户余额    	    

# 支付宝公私密钥生成, sdk使用
	支付宝开放平台组助手使用:  生成公私钥
	    支付宝开放平台下载:https://ideservice.alipay.com/ide/getPluginUrl.htm?clientType=assistant&platform=win&channelType=WEB
        密钥长度: RSA2
        密钥格式: PKCS1
        生成即可
        	
	GitHub开源SDK: 
		支付宝开源框架地址: https://github.com/fzlee/alipay
		pip install python-alipay-sdk
		
# 拓展: 
	xx.apk 如果apk使用QQ 或者 微信传送, 它会改名, 再后面加个.1 -> xx.apk.1. 目的就是防止恶意软件. 
     如果你需要安装, 只需要将后缀名修改过来即可		
'''

2. 测试目录结构

image-20210222140809485

3. t_alipay.py

from alipay import AliPay

app_private_key_string = """-----BEGIN rsa2 PRIVATE KEY-----
MIIEowIBAAKCAQEAr6my/KRUtoPcQzuBt8TZtxLvLtwI8Rf/ETubH6dfi143yuiHd0SnfTctD+ZTmGyRHxuqNwwTNV4CN0d58wuI2F3hky4Tm8ocp8n0tzjlYxDvoh1b4d4ksxXCM0yhSzywdIK+K+Y9VP74uU4mlT47oBFUs6TBK9AAlMfZfoPTUAUjSDF3usUE0IvkbKyv4Yd/cD0Stnqrl5qzplBjrA7H0HSRbw5nrk8Pj8aWnZYAayq3aGCJZxc+UfLKo8rfhV3GY6Tu29cTvTX2K69TZLQYPHkH1+8nLtwQywuWioXHOWwac6fE3270XR41xaUHo9avS48Gr4HNdkTAUtvq6YmfAwIDAQABAoIBAC8AuWPglMpBfi5/PbZuddMGvflL5xib0yRJTripkGc6TrN8hMLlG+vlV6lpd/TRGAO641DXakxdWzpvZbIi4/sBI9q9+YE2E3TSFSjxkG9xmK1ILc3CIw/IQq53UrFPC+ghE8GrWb3ke6kZwDku7cVm3cMz0nxmq8EjuI6ht2kxhUKzdsE9Z1DMOHWiEZSM6dvNMW/axToM2UawwPsermbTa++IJujvzjKH0UJORBqVnC6Ar2tPbEOJ9wbybVihnpHTyaCCWq2XDx/nEkOWeW8oeRp41uXrJf7iaHbYFYA2GlvQT2vMkFU5qKPzKYJDdBZdvlN0dEpEUstx4WYcSAECgYEA1QP1Vr54PFuh7KWxkV7WJz1iZyNcyPHWG4XjzTifK4uTPyciabDhUO1aPmnd7I2GvaRwyCWAZQSn7bIV9rumebEWzMWbcttm2e/iL7lzE1Aj8Ank8pXXaIXQHE+4yUfdE3jsHcTq99EzIY+K7Js4LckvR9sCWiM9J6Za1TuO9WkCgYEA0xwqePmrfYE965quZpbNZZ5uGO6AJZ0YZlK69RwVT5NV1fmdLsqk/4jgJKrZnnspt16cKjDJ5DyeGH4uikL1F6AReDbCPSVJjWqo50Al98K6WDqf6HhJnz+A+dxtgzHbQKUWlWiWuqbeB6ITXnVG0mrBi6jHR247fkh3FkOAh4sCgYEAriN2RVugX3dpgFRUPUsSNzHvZ/F4wK0zI3zpJbPMK4UG8vHDKDP5fncK90sEqYVpSU9NA9HkjLCpt5+GZRYymfkzcmN5GQRTqIZ6mhk5AejZ+DmepeNIWLtR1tS9XGPUuveR04kFA9SaIbj8yiwq5enSlulBIM/fq3qcYSolN7UECgYA3MiMMtEKZMuRsqGm22vDjA9RHYnxQ2U0a28CT+3666ovDwVrOdB9FzJTGIYF6hTs3/V2ZTl5K9WpkfwFOFwmb3rcSlkac1BXyCpQUulny+I/eJ53Nmz2sjF79dRuQ9MUdlsxbzheyv5RHrKGhzcnxlAX8rOlFjNWzQ+EXChkd1wKBgG/B0o8VeajSxcOqlAJZbbcnKzX3V/Fea6TPxUpzG7HCBL0y7xMSt+KtBBQzD+0GRIkwZqhsHw1tCzvTTI74z3DiSMy95atflE9PIyRriKaMiUfSALw6UA5okEis56IGmY5mSZ60O3z+mONjRXXBCeRIjJAd0UByVBjcUoNxABe3
-----END rsa2 PRIVATE KEY-----"""

alipay_public_key_string = """-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCqAQEAgvXw19HTUH0t1thzkoq8KBhDBwFCoDqRJyBYnpN/KOTxTuSoUR0+pLK3vJbeQ0w5GJ/tiHpLh38hc88LNSR5nk26IBXX8WuNmxxC56d/A4/AaqiO3xgs9jKZjvYs0xuaFkwLswMuD8vm3xh6/YCD97EPkDqMY6aqbBdjHv8wOZ2Y/X6uFkANValvx2x+Lf8vSO+I2Iyq0sDmdCFS8LdnKgN5L8GoR1WkorgY6sTs2eV86acb95iAZC+7fVpVWzpX6yxBOL7hDIFDNDXCXhhWnsR3HfILNOMw84/jXdUKnXQIYYGCkSOQlK2hSB8/DtJaOoBTMrvpa29SCeIGvxFJRQIDAQAB
-----END PUBLIC KEY-----"""

alipay = AliPay(
    appid="2021000117620642",
    app_notify_url=None,  # 默认回调url
    app_private_key_string=app_private_key_string,
    # 支付宝的公钥,验证支付宝回传消息使用,不是你自己的公钥,
    alipay_public_key_string=alipay_public_key_string,
    sign_type="RSA2",  # rsa2 或者 RSA2
    debug=False  # 默认False
)


# 如果你是 Python 3的用户,使用默认的字符串即可
subject = "测试订单"

# 电脑网站支付,需要跳转到https://openapi.alipay.com/gateway.do? + order_string
alipay_url = 'https://openapi.alipaydev.com/gateway.do?'
order_string = alipay.api_alipay_trade_page_pay(
    out_trade_no="20161112",              # 订单号, 必须唯一
    total_amount=10,                      # 总金额
    subject=subject,                      # 订单标题
    return_url="http://139.196.184.91/",  # 同步回调(支付成功)
    notify_url="http://139.196.184.91/"   # 异步回调(订单状态) 可选, 不填则使用默认notify url
)

print(alipay_url + order_string)

4. 注意事项

from alipay import AliPay

app_private_key_string = """-----BEGIN rsa2 PRIVATE KEY-----
MIIEowIBAAKCAQEAr6my/KRUtoPcQzuBt8TZtxLvLtwI8Rf/ETubH6dfi143yuiHd0SnfTctD+ZTmGyRHxuqNwwTNV4CN0d58wuI2F3hky4Tm8ocp8n0tzjlYxDvoh1b4d4ksxXCM0yhSzywdIK+K+Y9VP74uU4mlT47oBFUs6TBK9AAlMfZfoPTUAUjSDF3usUE0IvkbKyv4Yd/cD0Stnqrl5qzplBjrA7H0HSRbw5nrk8Pj8aWnZYAayq3aGCJZxc+UfLKo8rfhV3GY6Tu29cTvTX2K69TZLQYPHkH1+8nLtwQywuWioXHOWwac6fE3270XR41xaUHo9avS48Gr4HNdkTAUtvq6YmfAwIDAQABAoIBAC8AuWPglMpBfi5/PbZuddMGvflL5xib0yRJTripkGc6TrN8hMLlG+vlV6lpd/TRGAO641DXakxdWzpvZbIi4/sBI9q9+YE2E3TSFSjxkG9xmK1ILc3CIw/IQq53UrFPC+ghE8GrWb3ke6kZwDku7cVm3cMz0nxmq8EjuI6ht2kxhUKzdsE9Z1DMOHWiEZSM6dvNMW/axToM2UawwPsermbTa++IJujvzjKH0UJORBqVnC6Ar2tPbEOJ9wbybVihnpHTyaCCWq2XDx/nEkOWeW8oeRp41uXrJf7iaHbYFYA2GlvQT2vMkFU5qKPzKYJDdBZdvlN0dEpEUstx4WYcSAECgYEA1QP1Vr54PFuh7KWxkV7WJz1iZyNcyPHWG4XjzTifK4uTPyciabDhUO1aPmnd7I2GvaRwyCWAZQSn7bIV9rumebEWzMWbcttm2e/iL7lzE1Aj8Ank8pXXaIXQHE+4yUfdE3jsHcTq99EzIY+K7Js4LckvR9sCWiM9J6Za1TuO9WkCgYEA0xwqePmrfYE965quZpbNZZ5uGO6AJZ0YZlK69RwVT5NV1fmdLsqk/4jgJKrZnnspt16cKjDJ5DyeGH4uikL1F6AReDbCPSVJjWqo50Al98K6WDqf6HhJnz+A+dxtgzHbQKUWlWiWuqbeB6ITXnVG0mrBi6jHR247fkh3FkOAh4sCgYEAriN2RVugX3dpgFRUPUsSNzHvZ/F4wK0zI3zpJbPMK4UG8vHDKDP5fncK90sEqYVpSU9NA9HkjLCpt5+GZRYymfkzcmN5GQRTqIZ6mhk5AejZ+DmeeNIWLtR1tS9XGPUuveR04kFA9SaIbj8yiwq5enSlulBIM/fq3qcYSolN7UECgYA3MiMMtEKZMuRsqGm22vDjA9RHYnxQ2U0a28CT+3666ovDwVrOdB9FzJTGIYF6hTs3/V2ZTl5K9WpkfwFOFwmb3rcSlkac1BXyCpQUulny+I/eJ53Nmz2sjF79dRuQ9MUdlsxbzheyv5RHrKGhzcnxlAX8rOlFjNWzQ+EXChkd1wKBgG/B0o8VeajSxcOqlAJZbbcnKzX3V/Fea6TPxUpzG7HCBL0y7xMSt+KtBBQzD+0GRIkwZqhsHw1tCzvTTI74z3DiSMy95atflE9PIyRriKaMiUfSALw6UA5okEis56IGmY5mSZ60O3z+mONjRXXBCeRIjJAd0UByVBjcUoNxABe3
-----END rsa2 PRIVATE KEY-----"""

alipay_public_key_string = """-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAgvXw19HTUH0t1thzkoq8KBhDBwFCoDqRJyBYnpN/KOTxTuSoUR0+pLK3vJbeQ0w5GJ/tiHpLh38hc88LNSR5nk26IBXX8WuNmxxC56d/A4/AaqiO3xgs9jKZjvYs0xuaFkwLswMuD8vm3xh6/YCD97EPkDqMY6aqbBdjHv8wOZ2Y/X6uFkANValvx2x+Lf8vSO+I2Iyq0sDmdCFS8LdnKgN5L8GoR1WkorgY6sTs2eV86acb95iAZC+7fVpVWzpX6yxBOL7hDIFDNDXCXhhWnsR3HfILNOMw84/jXdUKnXQIYYGCkSOQlK2hSB8/DtJaOoBTMrvpa29SCeIGvxFJRQIDAQAB
-----END PUBLIC KEY-----"""

alipay = AliPay(
    appid="2021000117620642",
    app_notify_url=None,  # 默认回调url
    app_private_key_string=app_private_key_string,
    # 支付宝的公钥,验证支付宝回传消息使用,不是你自己的公钥,
    alipay_public_key_string=alipay_public_key_string,
    sign_type="RSA2",  # rsa2 或者 RSA2
    debug=False  # 默认False
)


# 如果你是 Python 3的用户,使用默认的字符串即可
subject = "测试订单"

# 电脑网站支付,需要跳转到https://openapi.alipay.com/gateway.do? + order_string
alipay_url = 'https://openapi.alipaydev.com/gateway.do?'
order_string = alipay.api_alipay_trade_page_pay(
    out_trade_no="20161112",              # 订单号, 必须唯一
    total_amount=10,                      # 总金额
    subject=subject,                      # 订单标题
    return_url="http://139.196.184.91/",  # 同步回调(支付成功)
    notify_url="http://139.196.184.91/"   # 异步回调(订单状态) 可选, 不填则使用默认notify url
)

print(alipay_url + order_string)

四、支付宝二次封装

1. GitHub开源框架参考

https://github.com/fzlee/alipay

2. 调用支付宝支付SDK

pip install python-alipay-sdk --upgrade

# 课程出现的错误解决: 抛ssl相关错误,代表缺失该包
pip install pyopenssl

3. 流程

'''
1. libs中新建文件, 文件中新建__init__.py, 新建.py文件
2. 将之前写死的 app...string 等, 修改成从文件中读取 open().read()
3. 新建文件夹存放支付宝公钥和自己的私钥用于被第二步读取
	公钥私钥存放的文件格式是: 
	-----xxx-----
	公钥 或者 私钥
	-----xxx-----
	
4. 新建settings.py文件存放一些常量
5. debug 配置成和 setting.py中的debug一直性
6. 使用三元运算配置支付宝的支付网关
7. 使用__init__.py优化导入的层级
注意: 网站支付alipay.api_alipay_trade_page_pay放到外面书写和订单一起.
'''

4. 目录结构

image-20210222141147460

libs
    ├── al_alipay  							# aliapy二次封装包
    │   ├── __init__.py 				# 包文件
    │   ├── pem							# 公钥私钥文件夹
    │   │   ├── alipay_public_key.pem	# 支付宝公钥文件
    │   │   ├── app_private_key.pem		# 应用私钥文件
    │   ├── pay.py						# 支付文件
    └── └── settings.py  				# 应用配置

5. pem/alipay_public_key.pem

-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAt99Bp0XLP1Zu2WdRu74CMB/tVx1/2thIo8t3oAo8eD8smku1e76PfeOw4iqYMHU32Vq1Fg7BLa9oPMw7Ro+kNjX4jTDz4wC3LA6dUI5OeGxYd9+tkpsBwyg+buVNhhogppQn5rcCzkRFTx0DwlnRUjcs420fS8J2MaCZf+4HJx6PdvQ2+Jo9SDqbnCqrkWy68/Gwpy2O/PUHXFrCs0hY/Pvu8HaK1xUre4yNJx+yyGTAKYEHTz4zq9BKJOlBUB+ZoFD0aanTFKyi4dYXeJaNGDICab21aRKL2S043KzPvBWqPAWE5KRGata5GhBuzQjh6TaXdP/hpJu/tXTD7LO5HwIDAQAB
-----END PUBLIC KEY-----

6. pem/app_private_key.pem

-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAj91mUtyrPlFFkfoLB+66lYcwexzXzEt6SlJuxsj3lW6+8pqla4YKqiUf98DeuBpX+USFm+baYFPqP5FWKyAUmGSDU8T4xD9BwLc+gm7rjeEjE5LzdyMInoEjW0QKXnn6S5y4gGPwI2WjOhg9vfr2R0GTDMTqn4i7zDB/u+wTksX5eDTSpb5VhtFFqYPklLUB+p0eCVE+iuFmJQLw2YvQ2T23mVFvYfpOVUvLZxIaMheERks73TfGRcZpJX3r5rUyn8wLiMPQkhoxS4kBPTZOHr6ymzP2qqjXZ8yNENewczocIu1MYm/HVPm25qJoiwILFmi5Wkt8+85UjXz2uHGiQQIDAQABAoIBAFrdWpCBgcMVb9x4UllLRhvFTDCmEgKdtXqVwBPbwMxoK0BtvodAHSJD2oIQFluvf9sXlU/XqhvnRG+r6wHH/FoYOf5BzWGeFniTBG87gZmWFiJ50T5xKTN7mwo82lO7tQPm0318IaMpwcOipfSFOuHcn/UTtqDWqs6X30WhIOe1IR6y08M9D1bZpnzG/t2If5Hoj2otjuIgdTac6qVJgg/MH4TTnSEss3nAnohnduTGujF7zigimDPPkBOykddk/jXhr3dSba6P/SSBCnqV4J5mZ6fm2mW9FX12VohMIPwcyxke7DnRS8aaeJ0w2jCNtr2s/L33RPxt1q36QcayMEUCgYEA1qlRVQGoPw+975cGuuLtf/GIQe/nKaL4ewKOjmz+CU9q7nXTQZP6Wv1PLe48mhtEvJ67mzdMYFvB1GMUvm9XeWtfJIWChTigDl4kgApbwHStPO4Ka5uxYb3uArREfiuSRhzqg12CzeXz67JH+8hYj2XqMolJQE3BqCentgH++E8CgYEAq5HaWm53esYoiPZ7YkfMI7xtsZNQPFp1kr5ZfGeEwpa6wz65E0lNuwLpGdN2o4gfLCpqTjnemzEvjwflsgcUQ9V2ENuZlGu+CHQ/QtESmnPRClJmWs7/AncmeUzuE+GJWcmP8Ny3WzA8Hj3GlEJRw85OSLRftjDkddm+3bDmiG8CgYEA0CSvq2iG9lbSSg48RVyjBAt0ntL7Z6ERpV0uU6YVkXuDaSOwq4bMHAxBCX1XH+rO9MJJkVDGayytvr4wMBOQUzissaIPlBP7BtN8OvdQTkYUcN80FXGLRwY+UjrvqxOeqwOA1c4HdpTBtspRCDAUxz8AHu6Fq6d53w5MjaxTyfsCgYAioIZMWB0/Jz1JH04ZsNnvoHkXLN81vyAZpNdUt2PhDiDVhRQ/Yz0P8G9xae944+E+vbTE4wMdmVi66mIegzVA15VDo76kaPZqqGOYLbAugg9/oM/NphdrvPaD5tXqJ0vcVkT53OIYwmxvwhIRHC16cmn0XIswrz981ZyjC7ALewKBgDGwoNN4+0lQzQpNF7L3AFs7qk8KdTpNqaaFmzlWpRhWKdP7l0EkxFeP/8n+9Z4t3u3giU6UYWRjR6VGFsJ53Ytr63+XmqUPYfeAyadSPVNNBnDkJ9yrvPfElI4Fu+aroxhcjkTuCgYepD6c4jXq+xsoJz/jMbjv6cnd1T7QDYvL
-----END RSA PRIVATE KEY-----

7. __init__.py

from .alipay_task import alipay, alipay_gateway

8. pay.py

from alipay import AliPay
from . import settings

alipay = AliPay(
	appid=settings.APPID,
	app_notify_url=None,
	app_private_key_string=settings.APP_PRIVATE_KEY_SIRING,
	alipay_public_key_string=settings.ALIPAY_PUBLIC_KEY_SIRING,
	sign_type=settings.SIGN_TYPE,
	debug=settings.DEBUG,
)

gateway = settings.GATEWAY

9. settings.py

import os

APPID = '2021000117613064'

# 默认回调
APP_NOTIFY_URL = None

# 自己私钥
APP_PRIVATE_KEY_SIRING = open(os.path.join(os.path.dirname(__file__), 'pem', 'app_private_kay.pem')).read()

# 阿里公钥
ALIPAY_PUBLIC_KEY_SIRING = open(os.path.join(os.path.dirname(__file__), 'pem', 'alipay_public_key.pem')).read()

# 标签加密类型
SIGN_TYPE = 'RSA2'

# True表示测试沙箱环境
DEBUG = True

# 阿里网关
GATEWAY = 'https://openapi.alipaydev.com/gateway.do?' if DEBUG else 'https://openapi.alipay.com/gateway.do?'

10. 配置文件中配置支付宝替换接口:settings.py|开发人员

# 后台基URL
BASE_URL = 'http://139.196.184.91:8000'  # 注意: 这里的8000上线以后指定的nginx的8000端口, 由nginx的8000端口发送到nginx配置内部的uwsgi的端口中
# 前台基URL
LUFFY_URL = 'http://139.196.184.91'      # 注意: 这里没有写端口默认就是80端口. 
# 支付宝同步异步回调接口配置
# 后台: 支付宝异步回调的接口
NOTIFY_URL = BASE_URL + "/order/success/"
# 前台: 支付宝同步回调接口,没有 / 结尾
RETURN_URL = LUFFY_URL + "/pay/success"

五、后台-支付接口

1. 订单模块表

<1>. 流程
'''
1. 新建订单app, 注册, 子路由urls, 总路由分发, 
2. 表分析
	订单表: 
		订单标题, 总价格, 订单id(自己的), 流水号(支付宝), 订单状态, 支付方式, 支付时间, 订单用户(注意: 导入用户表路径尽量小), 创建时间, 更新时间
		
	订单详情表: 
		订单一对多外键, 课程一对多外键(级联删除改为Set_NULL, null=True), 原价格, 实价
		str的健壮性校验
		
	订单和订单详情表关系分析: 一对多 订单详情是多的一方  一个订单可以有多个订单详情, 一个订单详情不可以同时属于多个订单. 
	
	订单表和课程表关系分析: 多对多   一个订单可以包含多个课程, 一个课程可以属于多个订单 
		重点: 但是我们这里不着不过对订单表与课程表建立多对多的关系,而是通过订单详情表与课程表建立关系. 
	
	订单详情表和课程表关系分析: 一对多 订单详情是多的一方  订单详情多的一方 一个订单详情不可以属于多个课程, 而一个课程可以属于多个订单详情
		
	订单表和用户表关系分析: 一对多  订单是多的一方 一个用户可以下多个订单, 一个订单不能属于多个用户
		on_delete -> DO_NOTHING
		db_constraint=False 
		
	提示: 不继承BaseModel表.  is_show, orders没有必要存在
3. 数据迁移	
'''
<2>. order/models.py
"""
class Order(models.Model):
    # 主键、总金额、订单名、订单号、订单状态、创建时间、支付时间、流水号、支付方式、支付人(外键) - 优惠劵(外键,可为空)
    pass

class OrderDetail(models.Model):
    # 订单号(外键)、商品(外键)、实价、成交价 - 商品数量
    pass
"""
from django.db import models
from user.models import User
from course.models import Course

import utils


class Order(models.Model):
    """订单模型"""
    status_choices = (
        (0, '未支付'),
        (1, '已支付'),
        (2, '已取消'),
        (3, '超时取消'),
    )
    pay_choices = (
        (1, '支付宝'),
        (2, '微信支付'),
    )
    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)
    trade_no = models.CharField(max_length=64, null=True, verbose_name="流水号")
    order_status = models.SmallIntegerField(choices=status_choices, default=0, verbose_name="订单状态")
    pay_type = models.SmallIntegerField(choices=pay_choices, default=1, verbose_name="支付方式")
    pay_time = models.DateTimeField(null=True, verbose_name="支付时间")
    created_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
    updated_time = models.DateTimeField(auto_now=True, verbose_name='更新时间')

    # 订单表和用户表关系分析: 一对多  订单是多的一方 一个用户可以下多个订单, 一个订单不能属于多个用户
    user = models.ForeignKey(User, related_name='order_user', on_delete=models.DO_NOTHING, db_constraint=False,
                             verbose_name="下单用户")

    class Meta:
        db_table = "luffy_order"
        verbose_name = "订单记录"
        verbose_name_plural = "订单记录"

    def __str__(self):
        return "%s - ¥%s" % (self.subject, self.total_amount)

    @property
    def courses(self):
        data_list = []
        for item in self.order_courses.all():
            data_list.append({
                "id": item.id,
                "course_name": item.course.name,
                "real_price": item.real_price,
            })
        return data_list


class OrderDetail(models.Model):
    """订单详情"""
    price = models.DecimalField(max_digits=6, decimal_places=2, verbose_name="课程原价")
    real_price = models.DecimalField(max_digits=6, decimal_places=2, verbose_name="课程实价")

    # 订单和订单详情表关系分析: 一对多 订单详情是多的一方  一个订单可以有多个订单详情, 一个订单详情不可以同时属于多个订单.
    order = models.ForeignKey(Order, related_name='order_courses', on_delete=models.CASCADE, db_constraint=False,
                              verbose_name="订单")
    # 订单详情表和课程表关系分析: 一对多 订单详情是多的一方  订单详情多的一方 一个订单详情不可以属于多个课程, 而一个课程可以属于多个订单详情
    '''
    订单表和课程表关系分析: 多对多   一个订单可以包含多个课程, 一个课程可以属于多个订单 
    !!!重点!!!: 但是我们这里不着不过对订单表与课程表建立多对多的关系,而是通过订单详情表与课程表建立关系.
    '''
    course = models.ForeignKey(Course, related_name='course_orders', on_delete=models.SET_NULL, null=True,
                               db_constraint=False,
                               verbose_name="课程")

    class Meta:
        db_table = "luffy_order_detail"
        verbose_name = "订单详情"
        verbose_name_plural = "订单详情"

    def __str__(self):
        """str的健壮性校验"""
        try:
            return "%s的订单:%s" % (self.course.name, self.order.out_trade_no)
        except Exception as e:
            utils.log.error(str(e))
            return super().__str__()

2. 订单模块接口之支付接口

<1>. 流程
'''
1. 支付接口: 生成订单, 生成支付连接, 返回支付连接
    1) 新建路由pay, payView
    2) 新建视图payView
        # 思路
        order表和orderdetail表插入数据, 重写create方法. 
        生成订单号 uuid
        登录后才能支付 jwt认证
        当前登录用户就是下单用户, 存到order表中
        订单价格校验. 如: 下了三个课程, 总价格100, 前端提交的价格是99
        
        # 实现
        继承 C, G
        新建序列化类 OrderModelSeriailzer
            注意: 这是一个反序列化的表
            # 传输的数据格式
            {course: [1, 2, 3], total_amount: 100, subject: 商品名, pay_type: 1}
            
            # 控制字段
            fields=['total_amount', 'subject', 'pay_type', 'course_list']
            
            # 可以再局部钩子中把course=[1, 2, 3]生成course=[obj1, obj2, obj3] 或者使用 PrimayKeyRElatedField
            course=serialisers.CharField()
            
            # 校验
            1. 校验订单总价格: 获取总价格, 获取课程对象列表从总价格列表中获取每个价格叠加与总价格对比 (注意: 需要返回总价格)
            2. 生成订单号: str(uuid).replace('-', '')
            3. 获取支付用户: 视图中重写create方法借助self.context传将request对象传给序列化类
            4. 生成支付连接: 导入alipay, alipay_gateway. 拷贝, 将post, get2个回调的地址存放到配置文件中(配置到django的配置文件中), 拼接地址返回即可!
            5. 入库(订单, 订单详情): 将user对象存入attrs中, 把订单号存入attrs中, 将pay_url存入self.context中
            6. create方法. 先pop出课程列表对象, 存order表. for循环存入课程详情
           视图中: Response返回给前端的, 前端只需要一个连接, 那么序列化校验的第五步, 在self.context中将它存入, 将它返回给前端
	3) 配置jwt认证
		对PayView类进行限制. 使用内置限制(认证 + 权限)
		内置认证类: JSONWebTokenAUthentication
		内置权限类: isAuthenticated
	4) 序列化中让所有的fields中的字段必填. 有默认值的字段, 就不是必填的. required=True
	5) 出现错误: 支付宝支付的时候pay_total_amount是一个decimal类型, 需要转换成float类型. (提示: decimal累加可以)
	提示: 支付方式目前只写了支付宝的支付方式因此pay_type=1, 3个课程一起买一共138

2. 支付宝异步回调的post接口: 验签, 修改订单状态

3. 当支付宝get回调前端, vue组件一创建, 立马向后端你发一个get请求.(比较绕)
'''
<2>. order/views.py
from rest_framework.viewsets import GenericViewSet
from rest_framework.mixins import CreateModelMixin
from rest_framework import status
from rest_framework_jwt.authentication import JSONWebTokenAuthentication
from rest_framework.permissions import IsAuthenticated

import utils
from . import models
from . import serializer


class PayView(CreateModelMixin, GenericViewSet):
    # 对PayView类进行限制. 使用内置限制(认证 + 权限)
    authentication_classes = [JSONWebTokenAuthentication]
    permission_classes = [IsAuthenticated]

    queryset = models.Order.objects.all()
    serializer_class = serializer.OrderModelSeriailzer

    def create(self, request, *args, **kwargs):
        # 视图中重写create方法借助self.context传将request对象传给序列化类
        serializer = self.get_serializer(data=request.data, context={'request': request})
        serializer.is_valid(raise_exception=True)
        self.perform_create(serializer)
        headers = self.get_success_headers(serializer.data)
        # 视图中: Response返回给前端的, 前端只需要一个连接, 那么序列化校验的第五步, 在self.context中将它存入, 将它返回给前端
        return utils.APIResponse(serializer.context['pay_link'], status=status.HTTP_201_CREATED, headers=headers)
<3>. order/serializer.py
import uuid
from rest_framework import serializers
from rest_framework.exceptions import ValidationError
from django.conf import settings

from . import models
from libs.alipay_sdk import alipay, alipay_gateway


class OrderModelSeriailzer(serializers.ModelSerializer):
    # 可以再局部钩子中把course_list=[1, 2, 3]生成course_list=[obj1, obj2, obj3] 或者使用 PrimayKeyRElatedField
    course_list = serializers.PrimaryKeyRelatedField(write_only=True, many=True, queryset=models.Course.objects.all())

    class Meta:
        model = models.Order
        fields = ['subject', 'total_amount', 'pay_type', 'course_list']
        extra_kwargs = {
            # 序列化中让所有的fields中的字段必填. 有默认值的字段, 就不是必填的. required=True
            'total_amount': {'required': True},
            'pay_type': {'required': True},
        }

    @staticmethod
    def _verify_amount(attrs):
        total_amount = attrs.get('total_amount')
        course_list = attrs.get('course_list')

        course_amount = 0
        for course in course_list:
            course_amount += course.price

        if course_amount == total_amount:
            return total_amount
        raise ValidationError("订单总价错误!")

    @staticmethod
    def _order_number():
        return str(uuid.uuid1()).replace('-', '')

    def _pay_user(self):
        return self.context['request'].user

    def _pay_link(self, out_trade_no, total_amount, subject):

        # print('total_amount:', total_amount, type(total_amount))  # total_amount: 138.00 <class 'decimal.Decimal'>
        order_string = alipay.api_alipay_trade_page_pay(
            out_trade_no=out_trade_no,  # 订单号, 必须唯一
            # 支付宝支付的时候pay_total_amount是一个decimal类型, 需要转换成float类型. (提示: decimal累加可以)
            # 如果不转换成float那么格式就会抛出异常:  Object of type 'Decimal' is not JSON serializable
            # total_amount=total_amount,  # 总金额
            total_amount=float(total_amount),  # 总金额
            subject=subject,  # 订单标题
            return_url=settings.RETURN_URL,  # 同步回调(支付成功)
            notify_url=settings.NOTIFY_URL  # 异步回调(订单状态) 可选, 不填则使用默认notify url
        )
        return alipay_gateway + order_string

    def _before_create(self, attrs, out_trade_no, user, pay_link):
        attrs['out_trade_no'] = out_trade_no
        attrs['user'] = user

        self.context['pay_link'] = pay_link

    def validate(self, attrs):
        """
        1. 校验订单总价格: 获取总价格, 获取课程对象列表从总价格列表中获取每个价格叠加与总价格对比 (注意: 需要返回总价格)
        2. 生成订单号: str(uuid).replace('-', '')
        3. 获取支付用户: 视图中重写create方法借助self.context传将request对象传给序列化类
        4. 生成支付连接: 导入alipay, alipay_gateway. 拷贝, 将post, get2个回调的地址存放到配置文件中(配置到django的配置文件中), 拼接地址返回即可!
        5. 入库(订单, 订单详情): 将user对象存入attrs中, 将pay_link存入self.context中
        """
        # 1. 校验订单总价格
        total_amount = self._verify_amount(attrs)
        # 2. 生成订单号
        order_number = self._order_number()
        # 3. 获取支付用户
        user = self._pay_user()
        # 4. 生成支付连接
        pay_link = self._pay_link(out_trade_no=order_number, total_amount=total_amount, subject=attrs.get('subject'))
        # 5. 入库(订单, 订单详情)
        self._before_create(attrs=attrs, out_trade_no=order_number, user=user, pay_link=pay_link)
        return attrs

    def create(self, validated_data):
        course_list = validated_data.pop('course_list')
        order = models.Order.objects.create(**validated_data)
        for course in course_list:
            models.OrderDetail.objects.create(course=course, price=course.price, real_price=course.price, order=order)
        return order
<4>. settings/dev.py
# 后台基URL
BASE_URL = 'http://139.196.184.91'
# 前台基URL
LUFFY_URL = 'http://139.196.184.91'
# 支付宝同步异步回调接口配置
# 后台异步回调接口
NOTIFY_URL = BASE_URL + "/order/success/"
# 前台同步回调接口,没有 / 结尾
RETURN_URL = LUFFY_URL + "/pay/success"
<5>. luffyapi/urls.py
path('order/',include('order.urls')),
<6>. order/urls.py子路由
from django.urls import path, re_path, include
from . import views

from rest_framework.routers import SimpleRouter

router = SimpleRouter()
router.register('pay', views.PayView, 'pay')
urlpatterns = [
    path('', include(router.urls)),
]

六、前台-支付生成页面

1. 前端跳转到支付宝支付

<1>. 流程
'''
提示: 一共三个地方都有立即购买操作
1. FreeCourse.vue
	1) 定义buy_now()点击触发事件的方法
		从this.$cookies中获取token
		判断如果没有token那么触发this.$message
		发送ajax的post请求, this.$settings.base_url + /order/pay/, headers需要携带认证 Authorization, data需要携带对着数据. 使用另一种用法{}
		获取到pay_link, 前端发送get请求
		window.open(pay_link, '_self')
	2) 付款成功以后需要跳转到/order/success页面, 前端需要success组件. 后端需要success接口	
'''
<2>. FreeCoourse.vue
# template
<span class="buy-now" @click="buy_now(course)">立即购买</span>


# script
methods: {
            buy_now(course) {
                // 获取token, 校验用户是否登录
                let token = this.$cookies.get('token');
                if (!token) {
                    this.$message({
                        message: "请先登录!",
                        type: 'warning',
                    });
                    return false;
                }

                // 发送axios
                this.$axios({
                    method: 'post',
                    url: `${this.$settings.base_url}/order/pay/`,
                    data: {
                        "subject": course.name,
                        // "total_amount": 11,
                        "total_amount": course.price,
                        "pay_type": 1,
                        "course_list": [
                            course.id,
                        ]
                    },
                    headers: {
                        Authorization: `jwt ${this.$cookies.get('token')}`
                    },
                }).then(response => {
                    console.log(response.data);
                    if (response.data.code) {
                        open(response.data.data, '_self');
                    } else {
                        this.$message({
                            message: '订单处理失败!',
                            type: 'warning',
                        })
                    }
                }).catch(error => {
                    this.$message({
                        message: "未知错误!",
                        type: 'warning',
                    })
                })

            },
    ...
}

2. 支付成功前端页面

<1>. 流程
'''
1. 新建PaySuccess.vue组件
2. 配置路由 path: '/pay/success'
	注意: 回调以后会在你的url地址中, 携带者很多东西 
3. 拷贝PaySuccess页面
	提示: 页面只有支付宝回调回来才有数据, 直接查看是没有的
4. create里面有一种特殊用法
5. 同步回调参数
	trade_no 支付宝的流水号
	auth_app_id 商家流水号
	app_id 我们的id号
	页面需要的参数: 订单号, 交易号, 付款时间
'''
<2>. routere/index.js
import PaySuccess from '../views/PaySuccess.vue'

const routes = [
 	...
    {
        path: '/pay/success',
        name: 'PaySuccess',
        component: PaySuccess
    },
];
<3>. 支付宝返回参数
charset=utf-8&

out_trade_no=7f7c7d12d57d45b693e1b49a6b01e1dd&  # 自己的订单号

method=alipay.trade.page.pay.return&

total_amount=39.00&

sign=FUmceqiNMWvxcD%2BUPCHiOTaEwlJ%2FXIXL5UwZWOSI1TwRjPIZVzjRLB4j2G5CQpn472JO8X%2BwMx04dHqjLxqLcY3TRu0XurQ%2FwKTNpyfDrtNuNv0rfGPuVHw52y3blbS7%2FKFVsWryw4%2BBuF2fCrJ4qWH8Zg14Rct7qoMbu73N74WkQtDyzXefiKDbkMMRMfLbelE9TFyeIeygeMId8%2B58mcJMUOh6aQqwpr9bzuBbfJ17fkqU%2F0ys9zGr%2FlDtLL7aAh6BPViqZN%2F9T7byCoferD1BhcSzJNR6V6VuhOdTq8iEaH2XgJT9aIiyHgg3GT1taBBvZX2gK41FSmkguk%2BfsA%3D%3D&

trade_no=2020030722001464020500585462&  # 支付宝的流水号

auth_app_id=2016093000631831&

version=1.0&

app_id=2016093000631831&

sign_type=RSA2&

seller_id=2088102177958114&

timestamp=2020-03-07%2014%3A47%3A48  # 付款时间
`
// 同步回调没与订单状态
<4>. views/PaySuccess.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/Header"

    export default {
        name: "Success",
        data() {
            return {
                result: {},
            };
        },
        created() {
            // 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('&') : [];  // ['a=1', 'b=2']
            //逐个将每一项添加到args对象中
            for (let i = 0; i < items.length; i++) {  // 第一次循环a=1,第二次b=2
                let k_v = items[i].split('=');  // ['a', '1']
                //解码操作,因为查询字符串经过编码的
                if (k_v.length >= 2) {
                    // url编码反解
                    let k = decodeURIComponent(k_v[0]);
                    this.result[k] = decodeURIComponent(k_v[1]);
                    // 没有url编码反解
                    // this.result[k_v[0]] = k_v[1];
                }

            }
            // 解析后的结果
            // console.log(this.result);


            // 把地址栏上面的支付结果,再get请求转发给后端
            this.$axios({
                url: this.$settings.base_url + '/order/success/' + location.search,
                method: 'get',
            }).then(response => {
                console.log(response.data);
            }).catch(() => {
                console.log('支付结果同步失败');
            })
        },
        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>

七、后台-支付成功的备选接口

1. 流程

'''
优化: 后端序列化中判断用户支付金额是否是0, 是0那么就直接修改订单状态, 也不用发送pay_link了

# 前端: created分析
1. localtion.search就可以获取支付好?号后面的参数获取到(包括问号), 使用.substring(1), 取出左边的?号
2. 使用三元表达式, 对params进行split. 以及后面将这种参数进行处理
3. decodeURICompontent, 
4. 把地址栏上面的支付结果, 再get请求发给后端
	this.$settings.base_url + '/order/success/' + localtion.search
	
# 后端
1. 路由: success/  SuccessView
2. 视图:  继承APIView 因为不和序列化类有关系, 和数据库有点关系
	# get:  
		获取前端传递过来的 out_trade_no, 去数据库中查取, 判断订单 order_status 的订单状态是否成功.
		最后返回响应中通过code=0或者code=1返回给前端即可
		
	# post: 支付宝回调
		回调地址: https://github.com/fzlee/alipay/blob/master/README.zh-hans.md#alipay.fund.trans.toaccount.transfer
		回调参数: https://opendocs.alipay.com/open/270/105902/
		注意: 必须data内容返回 success
			request.data可能有2种情况. 如果是json格式是字典, 如果是QuseryDict需要注意
		失败了之后需要记录日志
		成功了之后需要记录日志, 并且修改订单状态, 使用 out_trade_no 作为过来标志, order_status 修改为1, 交易支付时间pay_time=gmt_payment
'''

2. 同步理论参数

charset=utf-8&

out_trade_no=7f7c7d12d57d45b693e1b49a6b01e1dd&

method=alipay.trade.page.pay.return&

total_amount=39.00&

sign=FUmceqiNMWvxcD%2BUPCHiOTaEwlJ%2FXIXL5UwZWOSI1TwRjPIZVzjRLB4j2G5CQpn472JO8X%2BwMx04dHqjLxqLcY3TRu0XurQ%2FwKTNpyfDrtNuNv0rfGPuVHw52y3blbS7%2FKFVsWryw4%2BBuF2fCrJ4qWH8Zg14Rct7qoMbu73N74WkQtDyzXefiKDbkMMRMfLbelE9TFyeIeygeMId8%2B58mcJMUOh6aQqwpr9bzuBbfJ17fkqU%2F0ys9zGr%2FlDtLL7aAh6BPViqZN%2F9T7byCoferD1BhcSzJNR6V6VuhOdTq8iEaH2XgJT9aIiyHgg3GT1taBBvZX2gK41FSmkguk%2BfsA%3D%3D&

trade_no=2020030722001464020500585462&

auth_app_id=2016093000631831&

version=1.0&

app_id=2016093000631831&

sign_type=RSA2&

seller_id=2088102177958114&

timestamp=2020-03-07%2014%3A47%3A48
`
// 同步回调没与订单状态

3. order/urls.py

path('success/',views.successView.as_view()),

4. order/views.py

from rest_framework.views import APIView
from libs.alipay_sdk import alipay


class SuccessView(APIView):
    def get(self, request, *args, **kwargs):
        """
        获取前端传递过来的 out_trade_no, 去数据库中查取, 判断订单 order_status 的订单状态是否成功.
        最后返回响应中通过code=0或者code=1返回给前端即可
        """
        out_trade_no = request.query_params.get('out_trade_no')
        order = models.Order.objects.filter(out_trade_no=out_trade_no).first()
        # order.order_status值为1表示订单成功
        if order.order_status == 1:
            return utils.APIResponse()
        return utils.APIResponse(code=0, msg='失败')

    def post(self, request, *args, **kwargs):
        """
        回调地址: https://github.com/fzlee/alipay/blob/master/README.zh-hans.md#alipay.fund.trans.toaccount.transfer
        回调参数: https://opendocs.alipay.com/open/270/105902/
        注意: 必须data内容返回 success
            request.data可能有2种情况. 如果是json格式是字典, 如果是QuseryDict需要注意
        失败了之后需要记录日志
        成功了之后需要记录日志, 并且修改订单状态, 使用 out_trade_no 作为过来标志, order_status修改为1, 交易支付时间pay_time=gmt_payment
        """
        # request.data类型判断
        data = request.data.dict()
        utils.log(f'data: {data}')
        signature = data.pop("sign")
        out_trade_no = data.get('out_trade_no')
        gmt_payment = data.get('gmt_payment')

        # 校验
        success = alipay.verify(data, signature)
        if 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=gmt_payment)
            utils.log.info(f'{out_trade_no}订单支付成功!')
            # !!!注意!!!: 服务器异步通知页面特性
            '''
            当商户收到服务器异步通知并打印出 success 时,服务器异步通知参数 notify_id 才会失效。
            也就是说在支付宝发送同一条异步通知时(包含商户并未成功打印出 success 导致支付宝重发数次通知),服务器异步通知参数 notify_id 是不变的。
            '''
            return utils.APIResponse(data='success')

        utils.log.error(f'{out_trade_no}订单支付失败!')
        return utils.APIResponse(code=0, msg='失败')

八、上线前准备

1. 后端

# pro.py
'''
DEBUG = False
ALLOWED_HOSTS = ["*"]  # 服务器的公网IP

# 后台基URL
BASE_URL = 'http://139.196.184.91:8000'  # 注意: 这里的8000上线以后指定的nginx的8000端口, 由nginx的8000端口发送到nginx配置内部的uwsgi的端口中
# 前台基URL
LUFFY_URL = 'http://139.196.184.91'      # 注意: 这里没有写端口默认就是80端口.
# 支付宝同步异步回调接口配置
# 后台: 支付宝异步回调的接口
NOTIFY_URL = BASE_URL + "/order/success/"
# 前台: 支付宝同步回调接口,没有 / 结尾
RETURN_URL = LUFFY_URL + "/pay/success"

# 注意: 检查mysql配置, 如果mysql配置的HOST是127.0.0.1, 那么需要检查远端服务器上的mysql本地密码是否正确.
'''

# wsgi.py
'''
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'luffyapi.settings.pro')
'''

# manage.py
'''
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'luffyapi.settings.dev')
'''

# 拷贝manage.py改名manage_pro.py(在项目根路径)
'''
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'luffyapi.settings.pro')
'''

2. 前端

# 配置src/assets/js/settings.py文件
export default {
    // 注意: 这里的8000的端口是nginx的监听端口 
    base_url: 'http://139.196.184.91:8000'  
}

# 将vue代码打包成html, css, js  
cmpn run build
posted @ 2021-02-22 17:01  今天捡到一百块钱  阅读(2669)  评论(0编辑  收藏  举报