支付宝支付业务

在支付宝开发平台中创建项目

在我们的业务中可能会用到需要进行支付的业务,这时我们可以用支付宝给我们提供的api

首先登录支付宝开发平台https://open.alipay.com/platform/home.htm,点击开发接入

SRE实战 互联网时代守护先锋,助力企业售后服务体系运筹帷幄!一键直达领取阿里云限量特价优惠。

支付宝支付业务 随笔 第1张

然后选择支付应用

支付宝支付业务 随笔 第2张

我们就可以开始创建项目了

支付宝支付业务 随笔 第3张

这是创建正式的项目,我们这里可以使用沙箱环境进行测试

支付宝支付业务 随笔 第4张

进入后会自动帮你创建一个项目,并提供相关的信息

支付宝支付业务 随笔 第5张

appid和支付宝网关都是我们访问支付宝支付页面时需要的,下面的应用网关是支付后支付宝会发送post请求的地址,我们可以拿到post请求的信息进行验证,确认是否支付成功,而授权回调地址是支付完成后会跳转的地址,需要进行设置

然后我们需要进行RSA的公钥设置,这里需要下载一个生成公钥的软件,生成后将公钥复制进来,会变成下图所示

支付宝支付业务 随笔 第6张

这里有一个应用公钥和支付宝公钥,需要加以区分,后面会用到

都设置完成后,支付宝还会给我们提供测试的账号,但是要登录需要下载沙箱版的支付宝,只有安卓手机能下...

支付宝支付业务 随笔 第7张

我们可以登录买家账号进行付款,看商家账号是否会收到

设置都完成了,那么我们的项目该怎么使用它呢

使用方法

由于要使用RSA加密,所以我们需要下载一个模块

Linux:
    pip3 install pycrypto
Windows:
    下载 pycryptodome.whl 
        pip3 install pycryptodome.whl

在linux中可以直接下载,但是在window需要先下载一个pycryptodome.whl,然户在本地进行安装,下载地址https://pypi.org/project/pycryptodome/#files

然后我们创建一个django项目,提供以下的url

支付宝支付业务 随笔 第8张
from django.conf.urls import url
from django.contrib import admin
from app01 import views
urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^page1/', views.page1),
    url(r'^page2/', views.page2),
    url(r'^page3/', views.page3),
]
支付宝支付业务 随笔 第9张

一般这种提供接口的服务都会给我们提供一个SDK供我们使用,支付宝也提供了,但是没有pthon的,所以我们只能自己到github上找一个修改了使用

支付宝支付业务 随笔 第10张
from datetime import datetime
from Crypto.PublicKey import RSA
from Crypto.Signature import PKCS1_v1_5
from Crypto.Hash import SHA256
from urllib.parse import quote_plus
from urllib.parse import urlparse, parse_qs
from base64 import decodebytes, encodebytes
import json

class AliPay(object):
    """
    支付宝支付接口(PC端支付接口)
    """

    def __init__(self, appid, app_notify_url, app_private_key_path,
                 alipay_public_key_path, return_url, debug=False):
        self.appid = appid
        self.app_notify_url = app_notify_url
        self.app_private_key_path = app_private_key_path
        self.app_private_key = None
        self.return_url = return_url
        with open(self.app_private_key_path) as fp:
            self.app_private_key = RSA.importKey(fp.read())
        self.alipay_public_key_path = alipay_public_key_path
        with open(self.alipay_public_key_path) as fp:
            self.alipay_public_key = RSA.importKey(fp.read())

        if debug is True:
            self.__gateway = "https://openapi.alipaydev.com/gateway.do"
        else:
            self.__gateway = "https://openapi.alipay.com/gateway.do"

    def direct_pay(self, subject, out_trade_no, total_amount, return_url=None, **kwargs):
        biz_content = {
            "subject": subject,
            "out_trade_no": out_trade_no,
            "total_amount": total_amount,
            "product_code": "FAST_INSTANT_TRADE_PAY",
            # "qr_pay_mode":4
        }

        biz_content.update(kwargs)
        data = self.build_body("alipay.trade.page.pay", biz_content, self.return_url)
        return self.sign_data(data)

    def build_body(self, method, biz_content, return_url=None):
        data = {
            "app_id": self.appid,
            "method": method,
            "charset": "utf-8",
            "sign_type": "RSA2",
            "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
            "version": "1.0",
            "biz_content": biz_content
        }

        if return_url is not None:
            data["notify_url"] = self.app_notify_url
            data["return_url"] = self.return_url

        return data

    def sign_data(self, data):
        data.pop("sign", None)
        # 排序后的字符串
        unsigned_items = self.ordered_data(data)
        unsigned_string = "&".join("{0}={1}".format(k, v) for k, v in unsigned_items)
        sign = self.sign(unsigned_string.encode("utf-8"))
        # ordered_items = self.ordered_data(data)
        quoted_string = "&".join("{0}={1}".format(k, quote_plus(v)) for k, v in unsigned_items)

        # 获得最终的订单信息字符串
        signed_string = quoted_string + "&sign=" + quote_plus(sign)
        return signed_string

    def ordered_data(self, data):
        complex_keys = []
        for key, value in data.items():
            if isinstance(value, dict):
                complex_keys.append(key)

        # 将字典类型的数据dump出来
        for key in complex_keys:
            data[key] = json.dumps(data[key], separators=(',', ':'))

        return sorted([(k, v) for k, v in data.items()])

    def sign(self, unsigned_string):
        # 开始计算签名
        key = self.app_private_key
        signer = PKCS1_v1_5.new(key)
        signature = signer.sign(SHA256.new(unsigned_string))
        # base64 编码,转换为unicode表示并移除回车
        sign = encodebytes(signature).decode("utf8").replace("\n", "")
        return sign

    def _verify(self, raw_content, signature):
        # 开始计算签名
        key = self.alipay_public_key
        signer = PKCS1_v1_5.new(key)
        digest = SHA256.new()
        digest.update(raw_content.encode("utf8"))
        if signer.verify(digest, decodebytes(signature.encode("utf8"))):
            return True
        return False

    def verify(self, data, signature):
        if "sign_type" in data:
            sign_type = data.pop("sign_type")
        # 排序后的字符串
        unsigned_items = self.ordered_data(data)
        message = "&".join(u"{}={}".format(k, v) for k, v in unsigned_items)
        return self._verify(message, signature)
支付宝支付业务 随笔 第11张

将它拷贝到我们的项目目录中

支付宝支付业务 随笔 第12张

然后我们要写我们的视图函数,page1给我们提供了付款的页面

支付宝支付业务 随笔 第13张
from django.shortcuts import render, redirect, HttpResponse
import json
import time
from utils.pay import AliPay
from app01 import models
from django.conf import settings

def alipay_obj():
    alipay = AliPay(
        appid=settings.ALIPAY_APPID,
        app_notify_url="http://47.93.4.198:8004/page2/",
        return_url="http://47.93.4.198:8004/page3/",
        app_private_key_path="keys/app_private_2048.txt",
        alipay_public_key_path="keys/alipay_public_2048.txt",  # 支付宝的公钥,验证支付宝回传消息使用,不是你自己的公钥
        debug=True,  # 默认False,
    )
    return alipay

def page1(request):
    if request.method == "GET":
        return render(request, 'page1.html')
    else:
        # 根据:appid、应用私钥、支付宝公钥、金额、订单名称、订单号 生一个URL,再进行跳转。
        # 1. 要支付的金额
        money = float(request.POST.get('money'))

        alipay = alipay_obj()

        order_num = "xxxxxxxxxxxx" + str(time.time())

        # 生成支付的url
        query_params = alipay.direct_pay(
            subject="充气式文杰",  # 商品简单描述
            out_trade_no=order_num,  # 商户订单号:ijldsddfsdfsdf
            total_amount=money,  # 交易金额(单位: 元 保留俩位小数)
        )

        # 3. query_params,根据appid、应用私钥、支付宝公钥、金额、订单名称、订单号生成的参数。

        # 4. 拼接URL: https://openapi.alipaydev.com/gateway.do + query_params
        pay_url = "https://openapi.alipaydev.com/gateway.do?{}".format(query_params)


        # ####### 在数据库中生成订单 ###########
        models.Order.objects.create(num=order_num,price=money)

        return redirect(pay_url)
支付宝支付业务 随笔 第14张

get请求访问时,我们可以看到一个输入金额和提交的页面

支付宝支付业务 随笔 第15张
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="stylesheet" href="dist/css/bootstrap.css">
</head>
<body>
    <form method="POST">
        {% csrf_token %}
        <input type="text" name="money">
        <input type="submit" value="去支付" />
    </form>


<script></script>
</body>
</html>
支付宝支付业务 随笔 第16张

支付宝支付业务 随笔 第17张

当用户输入了价格后,会发送post请求,后端拿到价格,然后用我们找到的SDK中的AliPay类生成一个对象,这个对象中要包含appid,app_notify_url(支付完成后发送post请求的地址),return_url(支付完成后跳转的地址),app_private_key_path(应用私钥的

路径),alipay_public_key_path(支付宝公钥的路径,注意不是应用公钥)

这里要注意,支付完成后发送post请求的地址和跳转地址应该是公网ip,不然是发送不成功的

公钥和私钥

支付宝支付业务 随笔 第18张

需要注意,文件的内容要加BEGIN和END

支付宝支付业务 随笔 第19张
公钥:
    -----BEGIN PUBLIC KEY-----
    公钥
    -----END PUBLIC KEY-----
私钥:
    -----BEGIN RSA PRIVATE KEY-----
    私钥
    -----END RSA PRIVATE KEY-----
支付宝支付业务 随笔 第20张

拿到这个alipay对象后,使用它的alipay.direct_pay方法生成url的参数,这里要传一些参数,包括商品描述,订单号和金额

# 生成支付的url
query_params = alipay.direct_pay(
    subject="充气式文杰",  # 商品简单描述
    out_trade_no=order_num,  # 商户订单号:ijldsddfsdfsdf
    total_amount=money,  # 交易金额(单位: 元 保留俩位小数)
)

最后将这个参数拼接到支付宝的网关后面,最后跳转这个页面

# 4. 拼接URL: https://openapi.alipaydev.com/gateway.do + query_params
pay_url = "https://openapi.alipaydev.com/gateway.do?{}".format(query_params)

完成了上面的步骤说明已经有一条订单生成了,只是还未完成付款,所以我们要在订单表中添加一条数据

订单表

支付宝支付业务 随笔 第21张
from django.db import models

class Order(models.Model):
    num = models.CharField(max_length=32)
    price = models.FloatField()
    status_choices = (
        (1,'未支付'),
        (2,'已支付')
    )
    status = models.IntegerField(choices=status_choices,default=1)
支付宝支付业务 随笔 第22张

添加数据

# ####### 在数据库中生成订单 ###########
models.Order.objects.create(num=order_num,price=money)

最后跳转页面

return redirect(pay_url)

这里跳转到的就是支付宝的付款页面

支付宝支付业务 随笔 第23张

我们可以用沙箱版的支付版进行付款,付款完成后会发送两个请求,一个post请求,一个get请求

post请求我们发送到了page2的url上,用来进行验证是否付款成功

支付宝支付业务 随笔 第24张
def page2(request):
    """
    支付宝支付成功后,支付宝主动向我的网站发送:post请求,用于通知我支付成功,我来修改订单状态
    PS: 检验数据是否合法
    :param request:
    :return:
    """
    alipay = alipay_obj()

    if request.method == "POST":
        # 检测是否支付成功
        # 去请求体中获取所有返回的数据:状态/订单号
        from urllib.parse import parse_qs
        body_str = request.body.decode('utf-8')
        post_data = parse_qs(body_str)

        post_dict = {}
        for k, v in post_data.items():
            post_dict[k] = v[0]

        sign = post_dict.pop('sign', None)
        # 使用sign+支付宝发来的数据,进行校验
        status = alipay.verify(post_dict, sign)
        if status:
            order_num = post_dict.get('out_trade_no')
            models.Order.objects.filter(num=order_num).update(status=2)

        return HttpResponse('POST返回')
支付宝支付业务 随笔 第25张

在page2中首先我们从request.body中获取返回的数据,再将数据转换成字典的格式,然后我们从字典中获取键为sign的值,再用alipay对象的verify方法得到一个status状态,如果为true说明支付成功了,那么我们从字典中获取订单号

 

 order_num = post_dict.get('out_trade_no'),再将数据库中该订单的状态改为已支付

get请求发送到page3中

支付宝支付业务 随笔 第26张
def page3(request):
    alipay = alipay_obj()
    params = request.GET.dict()
    sign = params.pop('sign', None)
    status = alipay.verify(params, sign)
    if status:
        return HttpResponse('支付成功')
    else:
        return HttpResponse('支付失败')
支付宝支付业务 随笔 第27张

在这里我们也要验证一下是否支付成功,然后来进行相应的跳转

 到这里支付功能就实现了

总结

整体的流程

支付宝支付业务 随笔 第28张
- 申请账号
- 申请appid
- 下载SDK
- URL接口

沙箱环境测试:
         APPID: 2016082500309412
        支付宝网关:https://openapi.alipaydev.com/gateway.do
      数据加密:
                应用公钥
                应用私钥
            
      1. 拷贝 pay.py
      2. 在支付宝填入:应用公钥 -> 支付宝公钥
      3. 拷贝我自己的应用私钥
         PS: 支付宝公钥+应用私钥
         公钥:
            -----BEGIN PUBLIC KEY-----
            公钥
            -----END PUBLIC KEY-----
         私钥:
            -----BEGIN RSA PRIVATE KEY-----
            私钥
            -----END RSA PRIVATE KEY-----
      4. 环境 
         Linux:
            pip3 install pycrypto
         Windows:
            下载 pycryptodome.whl 
                pip3 install pycryptodome.whl 
                pip3 install django 
支付宝支付业务 随笔 第29张

 

,

在支付宝开发平台中创建项目

在我们的业务中可能会用到需要进行支付的业务,这时我们可以用支付宝给我们提供的api

首先登录支付宝开发平台https://open.alipay.com/platform/home.htm,点击开发接入

支付宝支付业务 随笔 第30张

然后选择支付应用

支付宝支付业务 随笔 第31张

我们就可以开始创建项目了

支付宝支付业务 随笔 第32张

这是创建正式的项目,我们这里可以使用沙箱环境进行测试

支付宝支付业务 随笔 第33张

进入后会自动帮你创建一个项目,并提供相关的信息

支付宝支付业务 随笔 第34张

appid和支付宝网关都是我们访问支付宝支付页面时需要的,下面的应用网关是支付后支付宝会发送post请求的地址,我们可以拿到post请求的信息进行验证,确认是否支付成功,而授权回调地址是支付完成后会跳转的地址,需要进行设置

然后我们需要进行RSA的公钥设置,这里需要下载一个生成公钥的软件,生成后将公钥复制进来,会变成下图所示

支付宝支付业务 随笔 第35张

这里有一个应用公钥和支付宝公钥,需要加以区分,后面会用到

都设置完成后,支付宝还会给我们提供测试的账号,但是要登录需要下载沙箱版的支付宝,只有安卓手机能下...

支付宝支付业务 随笔 第36张

我们可以登录买家账号进行付款,看商家账号是否会收到

设置都完成了,那么我们的项目该怎么使用它呢

使用方法

由于要使用RSA加密,所以我们需要下载一个模块

Linux:
    pip3 install pycrypto
Windows:
    下载 pycryptodome.whl 
        pip3 install pycryptodome.whl

在linux中可以直接下载,但是在window需要先下载一个pycryptodome.whl,然户在本地进行安装,下载地址https://pypi.org/project/pycryptodome/#files

然后我们创建一个django项目,提供以下的url

支付宝支付业务 随笔 第37张
from django.conf.urls import url
from django.contrib import admin
from app01 import views
urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^page1/', views.page1),
    url(r'^page2/', views.page2),
    url(r'^page3/', views.page3),
]
支付宝支付业务 随笔 第38张

一般这种提供接口的服务都会给我们提供一个SDK供我们使用,支付宝也提供了,但是没有pthon的,所以我们只能自己到github上找一个修改了使用

支付宝支付业务 随笔 第39张
from datetime import datetime
from Crypto.PublicKey import RSA
from Crypto.Signature import PKCS1_v1_5
from Crypto.Hash import SHA256
from urllib.parse import quote_plus
from urllib.parse import urlparse, parse_qs
from base64 import decodebytes, encodebytes
import json

class AliPay(object):
    """
    支付宝支付接口(PC端支付接口)
    """

    def __init__(self, appid, app_notify_url, app_private_key_path,
                 alipay_public_key_path, return_url, debug=False):
        self.appid = appid
        self.app_notify_url = app_notify_url
        self.app_private_key_path = app_private_key_path
        self.app_private_key = None
        self.return_url = return_url
        with open(self.app_private_key_path) as fp:
            self.app_private_key = RSA.importKey(fp.read())
        self.alipay_public_key_path = alipay_public_key_path
        with open(self.alipay_public_key_path) as fp:
            self.alipay_public_key = RSA.importKey(fp.read())

        if debug is True:
            self.__gateway = "https://openapi.alipaydev.com/gateway.do"
        else:
            self.__gateway = "https://openapi.alipay.com/gateway.do"

    def direct_pay(self, subject, out_trade_no, total_amount, return_url=None, **kwargs):
        biz_content = {
            "subject": subject,
            "out_trade_no": out_trade_no,
            "total_amount": total_amount,
            "product_code": "FAST_INSTANT_TRADE_PAY",
            # "qr_pay_mode":4
        }

        biz_content.update(kwargs)
        data = self.build_body("alipay.trade.page.pay", biz_content, self.return_url)
        return self.sign_data(data)

    def build_body(self, method, biz_content, return_url=None):
        data = {
            "app_id": self.appid,
            "method": method,
            "charset": "utf-8",
            "sign_type": "RSA2",
            "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
            "version": "1.0",
            "biz_content": biz_content
        }

        if return_url is not None:
            data["notify_url"] = self.app_notify_url
            data["return_url"] = self.return_url

        return data

    def sign_data(self, data):
        data.pop("sign", None)
        # 排序后的字符串
        unsigned_items = self.ordered_data(data)
        unsigned_string = "&".join("{0}={1}".format(k, v) for k, v in unsigned_items)
        sign = self.sign(unsigned_string.encode("utf-8"))
        # ordered_items = self.ordered_data(data)
        quoted_string = "&".join("{0}={1}".format(k, quote_plus(v)) for k, v in unsigned_items)

        # 获得最终的订单信息字符串
        signed_string = quoted_string + "&sign=" + quote_plus(sign)
        return signed_string

    def ordered_data(self, data):
        complex_keys = []
        for key, value in data.items():
            if isinstance(value, dict):
                complex_keys.append(key)

        # 将字典类型的数据dump出来
        for key in complex_keys:
            data[key] = json.dumps(data[key], separators=(',', ':'))

        return sorted([(k, v) for k, v in data.items()])

    def sign(self, unsigned_string):
        # 开始计算签名
        key = self.app_private_key
        signer = PKCS1_v1_5.new(key)
        signature = signer.sign(SHA256.new(unsigned_string))
        # base64 编码,转换为unicode表示并移除回车
        sign = encodebytes(signature).decode("utf8").replace("\n", "")
        return sign

    def _verify(self, raw_content, signature):
        # 开始计算签名
        key = self.alipay_public_key
        signer = PKCS1_v1_5.new(key)
        digest = SHA256.new()
        digest.update(raw_content.encode("utf8"))
        if signer.verify(digest, decodebytes(signature.encode("utf8"))):
            return True
        return False

    def verify(self, data, signature):
        if "sign_type" in data:
            sign_type = data.pop("sign_type")
        # 排序后的字符串
        unsigned_items = self.ordered_data(data)
        message = "&".join(u"{}={}".format(k, v) for k, v in unsigned_items)
        return self._verify(message, signature)
支付宝支付业务 随笔 第40张

将它拷贝到我们的项目目录中

支付宝支付业务 随笔 第41张

然后我们要写我们的视图函数,page1给我们提供了付款的页面

支付宝支付业务 随笔 第42张
from django.shortcuts import render, redirect, HttpResponse
import json
import time
from utils.pay import AliPay
from app01 import models
from django.conf import settings

def alipay_obj():
    alipay = AliPay(
        appid=settings.ALIPAY_APPID,
        app_notify_url="http://47.93.4.198:8004/page2/",
        return_url="http://47.93.4.198:8004/page3/",
        app_private_key_path="keys/app_private_2048.txt",
        alipay_public_key_path="keys/alipay_public_2048.txt",  # 支付宝的公钥,验证支付宝回传消息使用,不是你自己的公钥
        debug=True,  # 默认False,
    )
    return alipay

def page1(request):
    if request.method == "GET":
        return render(request, 'page1.html')
    else:
        # 根据:appid、应用私钥、支付宝公钥、金额、订单名称、订单号 生一个URL,再进行跳转。
        # 1. 要支付的金额
        money = float(request.POST.get('money'))

        alipay = alipay_obj()

        order_num = "xxxxxxxxxxxx" + str(time.time())

        # 生成支付的url
        query_params = alipay.direct_pay(
            subject="充气式文杰",  # 商品简单描述
            out_trade_no=order_num,  # 商户订单号:ijldsddfsdfsdf
            total_amount=money,  # 交易金额(单位: 元 保留俩位小数)
        )

        # 3. query_params,根据appid、应用私钥、支付宝公钥、金额、订单名称、订单号生成的参数。

        # 4. 拼接URL: https://openapi.alipaydev.com/gateway.do + query_params
        pay_url = "https://openapi.alipaydev.com/gateway.do?{}".format(query_params)


        # ####### 在数据库中生成订单 ###########
        models.Order.objects.create(num=order_num,price=money)

        return redirect(pay_url)
支付宝支付业务 随笔 第43张

get请求访问时,我们可以看到一个输入金额和提交的页面

支付宝支付业务 随笔 第44张
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="stylesheet" href="dist/css/bootstrap.css">
</head>
<body>
    <form method="POST">
        {% csrf_token %}
        <input type="text" name="money">
        <input type="submit" value="去支付" />
    </form>


<script></script>
</body>
</html>
支付宝支付业务 随笔 第45张

支付宝支付业务 随笔 第46张

当用户输入了价格后,会发送post请求,后端拿到价格,然后用我们找到的SDK中的AliPay类生成一个对象,这个对象中要包含appid,app_notify_url(支付完成后发送post请求的地址),return_url(支付完成后跳转的地址),app_private_key_path(应用私钥的

路径),alipay_public_key_path(支付宝公钥的路径,注意不是应用公钥)

这里要注意,支付完成后发送post请求的地址和跳转地址应该是公网ip,不然是发送不成功的

公钥和私钥

支付宝支付业务 随笔 第47张

需要注意,文件的内容要加BEGIN和END

支付宝支付业务 随笔 第48张
公钥:
    -----BEGIN PUBLIC KEY-----
    公钥
    -----END PUBLIC KEY-----
私钥:
    -----BEGIN RSA PRIVATE KEY-----
    私钥
    -----END RSA PRIVATE KEY-----
支付宝支付业务 随笔 第49张

拿到这个alipay对象后,使用它的alipay.direct_pay方法生成url的参数,这里要传一些参数,包括商品描述,订单号和金额

# 生成支付的url
query_params = alipay.direct_pay(
    subject="充气式文杰",  # 商品简单描述
    out_trade_no=order_num,  # 商户订单号:ijldsddfsdfsdf
    total_amount=money,  # 交易金额(单位: 元 保留俩位小数)
)

最后将这个参数拼接到支付宝的网关后面,最后跳转这个页面

# 4. 拼接URL: https://openapi.alipaydev.com/gateway.do + query_params
pay_url = "https://openapi.alipaydev.com/gateway.do?{}".format(query_params)

完成了上面的步骤说明已经有一条订单生成了,只是还未完成付款,所以我们要在订单表中添加一条数据

订单表

支付宝支付业务 随笔 第50张
from django.db import models

class Order(models.Model):
    num = models.CharField(max_length=32)
    price = models.FloatField()
    status_choices = (
        (1,'未支付'),
        (2,'已支付')
    )
    status = models.IntegerField(choices=status_choices,default=1)
支付宝支付业务 随笔 第51张

添加数据

# ####### 在数据库中生成订单 ###########
models.Order.objects.create(num=order_num,price=money)

最后跳转页面

return redirect(pay_url)

这里跳转到的就是支付宝的付款页面

支付宝支付业务 随笔 第52张

我们可以用沙箱版的支付版进行付款,付款完成后会发送两个请求,一个post请求,一个get请求

post请求我们发送到了page2的url上,用来进行验证是否付款成功

支付宝支付业务 随笔 第53张
def page2(request):
    """
    支付宝支付成功后,支付宝主动向我的网站发送:post请求,用于通知我支付成功,我来修改订单状态
    PS: 检验数据是否合法
    :param request:
    :return:
    """
    alipay = alipay_obj()

    if request.method == "POST":
        # 检测是否支付成功
        # 去请求体中获取所有返回的数据:状态/订单号
        from urllib.parse import parse_qs
        body_str = request.body.decode('utf-8')
        post_data = parse_qs(body_str)

        post_dict = {}
        for k, v in post_data.items():
            post_dict[k] = v[0]

        sign = post_dict.pop('sign', None)
        # 使用sign+支付宝发来的数据,进行校验
        status = alipay.verify(post_dict, sign)
        if status:
            order_num = post_dict.get('out_trade_no')
            models.Order.objects.filter(num=order_num).update(status=2)

        return HttpResponse('POST返回')
支付宝支付业务 随笔 第54张

在page2中首先我们从request.body中获取返回的数据,再将数据转换成字典的格式,然后我们从字典中获取键为sign的值,再用alipay对象的verify方法得到一个status状态,如果为true说明支付成功了,那么我们从字典中获取订单号

 

 order_num = post_dict.get('out_trade_no'),再将数据库中该订单的状态改为已支付

get请求发送到page3中

支付宝支付业务 随笔 第55张
def page3(request):
    alipay = alipay_obj()
    params = request.GET.dict()
    sign = params.pop('sign', None)
    status = alipay.verify(params, sign)
    if status:
        return HttpResponse('支付成功')
    else:
        return HttpResponse('支付失败')
支付宝支付业务 随笔 第56张

在这里我们也要验证一下是否支付成功,然后来进行相应的跳转

 到这里支付功能就实现了

总结

整体的流程

支付宝支付业务 随笔 第57张
- 申请账号
- 申请appid
- 下载SDK
- URL接口

沙箱环境测试:
         APPID: 2016082500309412
        支付宝网关:https://openapi.alipaydev.com/gateway.do
      数据加密:
                应用公钥
                应用私钥
            
      1. 拷贝 pay.py
      2. 在支付宝填入:应用公钥 -> 支付宝公钥
      3. 拷贝我自己的应用私钥
         PS: 支付宝公钥+应用私钥
         公钥:
            -----BEGIN PUBLIC KEY-----
            公钥
            -----END PUBLIC KEY-----
         私钥:
            -----BEGIN RSA PRIVATE KEY-----
            私钥
            -----END RSA PRIVATE KEY-----
      4. 环境 
         Linux:
            pip3 install pycrypto
         Windows:
            下载 pycryptodome.whl 
                pip3 install pycryptodome.whl 
                pip3 install django 
支付宝支付业务 随笔 第58张

 

,

在我们的业务中可能会用到需要进行支付的业务,这时我们可以用支付宝给我们提供的api

首先登录支付宝开发平台https://open.alipay.com/platform/home.htm,点击开发接入

支付宝支付业务 随笔 第59张

然后选择支付应用

支付宝支付业务 随笔 第60张

我们就可以开始创建项目了

支付宝支付业务 随笔 第61张

这是创建正式的项目,我们这里可以使用沙箱环境进行测试

支付宝支付业务 随笔 第62张

进入后会自动帮你创建一个项目,并提供相关的信息

支付宝支付业务 随笔 第63张

appid和支付宝网关都是我们访问支付宝支付页面时需要的,下面的应用网关是支付后支付宝会发送post请求的地址,我们可以拿到post请求的信息进行验证,确认是否支付成功,而授权回调地址是支付完成后会跳转的地址,需要进行设置

然后我们需要进行RSA的公钥设置,这里需要下载一个生成公钥的软件,生成后将公钥复制进来,会变成下图所示

支付宝支付业务 随笔 第64张

这里有一个应用公钥和支付宝公钥,需要加以区分,后面会用到

都设置完成后,支付宝还会给我们提供测试的账号,但是要登录需要下载沙箱版的支付宝,只有安卓手机能下...

支付宝支付业务 随笔 第65张

我们可以登录买家账号进行付款,看商家账号是否会收到

设置都完成了,那么我们的项目该怎么使用它呢

使用方法

由于要使用RSA加密,所以我们需要下载一个模块

Linux:
    pip3 install pycrypto
Windows:
    下载 pycryptodome.whl 
        pip3 install pycryptodome.whl

在linux中可以直接下载,但是在window需要先下载一个pycryptodome.whl,然户在本地进行安装,下载地址https://pypi.org/project/pycryptodome/#files

然后我们创建一个django项目,提供以下的url

支付宝支付业务 随笔 第66张
from django.conf.urls import url
from django.contrib import admin
from app01 import views
urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^page1/', views.page1),
    url(r'^page2/', views.page2),
    url(r'^page3/', views.page3),
]
支付宝支付业务 随笔 第67张

一般这种提供接口的服务都会给我们提供一个SDK供我们使用,支付宝也提供了,但是没有pthon的,所以我们只能自己到github上找一个修改了使用

支付宝支付业务 随笔 第68张
from datetime import datetime
from Crypto.PublicKey import RSA
from Crypto.Signature import PKCS1_v1_5
from Crypto.Hash import SHA256
from urllib.parse import quote_plus
from urllib.parse import urlparse, parse_qs
from base64 import decodebytes, encodebytes
import json

class AliPay(object):
    """
    支付宝支付接口(PC端支付接口)
    """

    def __init__(self, appid, app_notify_url, app_private_key_path,
                 alipay_public_key_path, return_url, debug=False):
        self.appid = appid
        self.app_notify_url = app_notify_url
        self.app_private_key_path = app_private_key_path
        self.app_private_key = None
        self.return_url = return_url
        with open(self.app_private_key_path) as fp:
            self.app_private_key = RSA.importKey(fp.read())
        self.alipay_public_key_path = alipay_public_key_path
        with open(self.alipay_public_key_path) as fp:
            self.alipay_public_key = RSA.importKey(fp.read())

        if debug is True:
            self.__gateway = "https://openapi.alipaydev.com/gateway.do"
        else:
            self.__gateway = "https://openapi.alipay.com/gateway.do"

    def direct_pay(self, subject, out_trade_no, total_amount, return_url=None, **kwargs):
        biz_content = {
            "subject": subject,
            "out_trade_no": out_trade_no,
            "total_amount": total_amount,
            "product_code": "FAST_INSTANT_TRADE_PAY",
            # "qr_pay_mode":4
        }

        biz_content.update(kwargs)
        data = self.build_body("alipay.trade.page.pay", biz_content, self.return_url)
        return self.sign_data(data)

    def build_body(self, method, biz_content, return_url=None):
        data = {
            "app_id": self.appid,
            "method": method,
            "charset": "utf-8",
            "sign_type": "RSA2",
            "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
            "version": "1.0",
            "biz_content": biz_content
        }

        if return_url is not None:
            data["notify_url"] = self.app_notify_url
            data["return_url"] = self.return_url

        return data

    def sign_data(self, data):
        data.pop("sign", None)
        # 排序后的字符串
        unsigned_items = self.ordered_data(data)
        unsigned_string = "&".join("{0}={1}".format(k, v) for k, v in unsigned_items)
        sign = self.sign(unsigned_string.encode("utf-8"))
        # ordered_items = self.ordered_data(data)
        quoted_string = "&".join("{0}={1}".format(k, quote_plus(v)) for k, v in unsigned_items)

        # 获得最终的订单信息字符串
        signed_string = quoted_string + "&sign=" + quote_plus(sign)
        return signed_string

    def ordered_data(self, data):
        complex_keys = []
        for key, value in data.items():
            if isinstance(value, dict):
                complex_keys.append(key)

        # 将字典类型的数据dump出来
        for key in complex_keys:
            data[key] = json.dumps(data[key], separators=(',', ':'))

        return sorted([(k, v) for k, v in data.items()])

    def sign(self, unsigned_string):
        # 开始计算签名
        key = self.app_private_key
        signer = PKCS1_v1_5.new(key)
        signature = signer.sign(SHA256.new(unsigned_string))
        # base64 编码,转换为unicode表示并移除回车
        sign = encodebytes(signature).decode("utf8").replace("\n", "")
        return sign

    def _verify(self, raw_content, signature):
        # 开始计算签名
        key = self.alipay_public_key
        signer = PKCS1_v1_5.new(key)
        digest = SHA256.new()
        digest.update(raw_content.encode("utf8"))
        if signer.verify(digest, decodebytes(signature.encode("utf8"))):
            return True
        return False

    def verify(self, data, signature):
        if "sign_type" in data:
            sign_type = data.pop("sign_type")
        # 排序后的字符串
        unsigned_items = self.ordered_data(data)
        message = "&".join(u"{}={}".format(k, v) for k, v in unsigned_items)
        return self._verify(message, signature)
支付宝支付业务 随笔 第69张

将它拷贝到我们的项目目录中

支付宝支付业务 随笔 第70张

然后我们要写我们的视图函数,page1给我们提供了付款的页面

支付宝支付业务 随笔 第71张
from django.shortcuts import render, redirect, HttpResponse
import json
import time
from utils.pay import AliPay
from app01 import models
from django.conf import settings

def alipay_obj():
    alipay = AliPay(
        appid=settings.ALIPAY_APPID,
        app_notify_url="http://47.93.4.198:8004/page2/",
        return_url="http://47.93.4.198:8004/page3/",
        app_private_key_path="keys/app_private_2048.txt",
        alipay_public_key_path="keys/alipay_public_2048.txt",  # 支付宝的公钥,验证支付宝回传消息使用,不是你自己的公钥
        debug=True,  # 默认False,
    )
    return alipay

def page1(request):
    if request.method == "GET":
        return render(request, 'page1.html')
    else:
        # 根据:appid、应用私钥、支付宝公钥、金额、订单名称、订单号 生一个URL,再进行跳转。
        # 1. 要支付的金额
        money = float(request.POST.get('money'))

        alipay = alipay_obj()

        order_num = "xxxxxxxxxxxx" + str(time.time())

        # 生成支付的url
        query_params = alipay.direct_pay(
            subject="充气式文杰",  # 商品简单描述
            out_trade_no=order_num,  # 商户订单号:ijldsddfsdfsdf
            total_amount=money,  # 交易金额(单位: 元 保留俩位小数)
        )

        # 3. query_params,根据appid、应用私钥、支付宝公钥、金额、订单名称、订单号生成的参数。

        # 4. 拼接URL: https://openapi.alipaydev.com/gateway.do + query_params
        pay_url = "https://openapi.alipaydev.com/gateway.do?{}".format(query_params)


        # ####### 在数据库中生成订单 ###########
        models.Order.objects.create(num=order_num,price=money)

        return redirect(pay_url)
支付宝支付业务 随笔 第72张

get请求访问时,我们可以看到一个输入金额和提交的页面

支付宝支付业务 随笔 第73张
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="stylesheet" href="dist/css/bootstrap.css">
</head>
<body>
    <form method="POST">
        {% csrf_token %}
        <input type="text" name="money">
        <input type="submit" value="去支付" />
    </form>


<script></script>
</body>
</html>
支付宝支付业务 随笔 第74张

支付宝支付业务 随笔 第75张

当用户输入了价格后,会发送post请求,后端拿到价格,然后用我们找到的SDK中的AliPay类生成一个对象,这个对象中要包含appid,app_notify_url(支付完成后发送post请求的地址),return_url(支付完成后跳转的地址),app_private_key_path(应用私钥的

路径),alipay_public_key_path(支付宝公钥的路径,注意不是应用公钥)

这里要注意,支付完成后发送post请求的地址和跳转地址应该是公网ip,不然是发送不成功的

公钥和私钥

支付宝支付业务 随笔 第76张

需要注意,文件的内容要加BEGIN和END

支付宝支付业务 随笔 第77张
公钥:
    -----BEGIN PUBLIC KEY-----
    公钥
    -----END PUBLIC KEY-----
私钥:
    -----BEGIN RSA PRIVATE KEY-----
    私钥
    -----END RSA PRIVATE KEY-----
支付宝支付业务 随笔 第78张

拿到这个alipay对象后,使用它的alipay.direct_pay方法生成url的参数,这里要传一些参数,包括商品描述,订单号和金额

# 生成支付的url
query_params = alipay.direct_pay(
    subject="充气式文杰",  # 商品简单描述
    out_trade_no=order_num,  # 商户订单号:ijldsddfsdfsdf
    total_amount=money,  # 交易金额(单位: 元 保留俩位小数)
)

最后将这个参数拼接到支付宝的网关后面,最后跳转这个页面

# 4. 拼接URL: https://openapi.alipaydev.com/gateway.do + query_params
pay_url = "https://openapi.alipaydev.com/gateway.do?{}".format(query_params)

完成了上面的步骤说明已经有一条订单生成了,只是还未完成付款,所以我们要在订单表中添加一条数据

订单表

支付宝支付业务 随笔 第79张
from django.db import models

class Order(models.Model):
    num = models.CharField(max_length=32)
    price = models.FloatField()
    status_choices = (
        (1,'未支付'),
        (2,'已支付')
    )
    status = models.IntegerField(choices=status_choices,default=1)
支付宝支付业务 随笔 第80张

添加数据

# ####### 在数据库中生成订单 ###########
models.Order.objects.create(num=order_num,price=money)

最后跳转页面

return redirect(pay_url)

这里跳转到的就是支付宝的付款页面

支付宝支付业务 随笔 第81张

我们可以用沙箱版的支付版进行付款,付款完成后会发送两个请求,一个post请求,一个get请求

post请求我们发送到了page2的url上,用来进行验证是否付款成功

支付宝支付业务 随笔 第82张
def page2(request):
    """
    支付宝支付成功后,支付宝主动向我的网站发送:post请求,用于通知我支付成功,我来修改订单状态
    PS: 检验数据是否合法
    :param request:
    :return:
    """
    alipay = alipay_obj()

    if request.method == "POST":
        # 检测是否支付成功
        # 去请求体中获取所有返回的数据:状态/订单号
        from urllib.parse import parse_qs
        body_str = request.body.decode('utf-8')
        post_data = parse_qs(body_str)

        post_dict = {}
        for k, v in post_data.items():
            post_dict[k] = v[0]

        sign = post_dict.pop('sign', None)
        # 使用sign+支付宝发来的数据,进行校验
        status = alipay.verify(post_dict, sign)
        if status:
            order_num = post_dict.get('out_trade_no')
            models.Order.objects.filter(num=order_num).update(status=2)

        return HttpResponse('POST返回')
支付宝支付业务 随笔 第83张

在page2中首先我们从request.body中获取返回的数据,再将数据转换成字典的格式,然后我们从字典中获取键为sign的值,再用alipay对象的verify方法得到一个status状态,如果为true说明支付成功了,那么我们从字典中获取订单号

 

 order_num = post_dict.get('out_trade_no'),再将数据库中该订单的状态改为已支付

get请求发送到page3中

支付宝支付业务 随笔 第84张
def page3(request):
    alipay = alipay_obj()
    params = request.GET.dict()
    sign = params.pop('sign', None)
    status = alipay.verify(params, sign)
    if status:
        return HttpResponse('支付成功')
    else:
        return HttpResponse('支付失败')
支付宝支付业务 随笔 第85张

在这里我们也要验证一下是否支付成功,然后来进行相应的跳转

 到这里支付功能就实现了

总结

整体的流程

支付宝支付业务 随笔 第86张
- 申请账号
- 申请appid
- 下载SDK
- URL接口

沙箱环境测试:
         APPID: 2016082500309412
        支付宝网关:https://openapi.alipaydev.com/gateway.do
      数据加密:
                应用公钥
                应用私钥
            
      1. 拷贝 pay.py
      2. 在支付宝填入:应用公钥 -> 支付宝公钥
      3. 拷贝我自己的应用私钥
         PS: 支付宝公钥+应用私钥
         公钥:
            -----BEGIN PUBLIC KEY-----
            公钥
            -----END PUBLIC KEY-----
         私钥:
            -----BEGIN RSA PRIVATE KEY-----
            私钥
            -----END RSA PRIVATE KEY-----
      4. 环境 
         Linux:
            pip3 install pycrypto
         Windows:
            下载 pycryptodome.whl 
                pip3 install pycryptodome.whl 
                pip3 install django 
支付宝支付业务 随笔 第87张

 

扫码关注我们
微信号:SRE实战
拒绝背锅 运筹帷幄