zoukankan      html  css  js  c++  java
  • 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;
             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;
             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;
             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
    
  • 相关阅读:
    《一个人的村庄》 ——刘亮程
    uva 11020
    Codeforces Round #190 (Div. 2) B. Ciel and Flowers
    hdu3308 线段树——区间合并
    线段树,区间更新
    vim 被墙
    ubuntu12.04 修复Grub2
    windows下mysql数据库忘记密码
    高性能的异步爬虫
    中间件
  • 原文地址:https://www.cnblogs.com/borntodie/p/14431397.html
Copyright © 2011-2022 走看看