支付宝支付环境搭建

一、设置沙箱账户

  1、通过支付宝登陆到蚂蚁金服开放平台(https://openhome.alipay.com/platform/appDaily.htm?tab=info)

    注释:只要登陆上后沙箱应用上面就有自己的APPID和支付宝网关,支付宝网关就是支付宝给我们的api接口,而RSA2(SHA256)密钥是需要自己设置德。

  2、设置密钥

    

    下载支付宝自动生成密钥软件,将自己的应用私钥保存在本地,应用公钥发送给支付宝点击确认后就会得到支付宝发送给我们的支付宝公钥

  3、下载支付宝钱包

    通过手机扫码下载支付宝沙箱钱包,沙箱钱包的用户名和密码在在沙箱账户中查找

 二、设置Alipay

  1、由于支付宝不提供python版本的sdk,所以需要手动编写python版本sdk。

  2、编写python版本sdk

    1、项目目录结构

      

    2、settings配置(主要写一些必须的配置项)

import os

BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

class AliPayConfig(object):
    # 商户app_id,即沙箱的APPID
    app_id = "2016081500252288"

    # 商户私钥路径
    merchant_private_key_path = os.path.join(BASE_DIR, "keys", "app_private_2048.txt")

    # 支付宝公钥路径
    alipay_public_key_path = os.path.join(BASE_DIR, "keys", "alipay_public_2048.txt")

    # 服务器异步通知页面路径 需http: // 格式的完整路径,不能加?id = 123 这类自定义参数,必须外网可以正常访问
    # 向支付宝支付成功后,支付宝会执行回调函数来通知服务器的支付状态,即支付宝发送post请求是支付宝支付成功后调用服务器的验证函数,服务器来判断是否是真的支付成功
    notify_url = "http://47.94.172.250:8804/api/v1/trade/alipay/"

    # 页面跳转同步通知页面路径 需http: // 格式的完整路径,不能加?id = 123 这类自定义参数,必须外网可以正常访问
    # 向支付宝支付成功后,支付宝会执行回调函数来通知服务器的支付状态,即支付宝发送get请求是支付宝支付成功后调用服务器的支付成功的页面信息
    return_url = "http://47.94.172.250:8804/api/v1/trade/alipay/"

    # 签名方式(当前只支持RSA和RSA2)
    sign_type = "RSA2"

    # 字符编码格式
    charset = "utf-8"

    # 支付宝网关(如果是线上环境的话, dev 这三个字去掉即可)
    gateway_url = "https://openapi.alipaydev.com/gateway.do"

    # 异步通知参数DOC(支付宝会主动发起POST请求)
    notify_doc = "https://docs.open.alipay.com/270/105902/"
View Code

 

     3、支付宝公钥和应用私钥配置

      注释:1、主要是存放在keys文件中,alipay_public_2048.txt中保存的是支付宝公钥,app_private_2048.txt中保存的是应用私钥。

          2、无论是支付宝公钥还是应用私钥在文件中的写法必须遵循如下格式:

            -----BEGIN RSA PRIVATE KEY-----
              中间写支付宝公钥或是应用私钥
            -----END RSA PRIVATE KEY-----

    4、alipay文件配置

    注释:配置之前必须先安装Crypto和pycryptodome俩模块

 

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
from config.settings import AliPayConfig
import json

# 文档链接(支付成功回调通知参数等相关, 支付宝交易状态是 TRADE_SUCCESS 会主动发起异步通知)
# https://docs.open.alipay.com/270/105902/

# 沙箱测试环境可使用沙箱支付宝支付
# 账号: rjoegs7144@sandbox.com
# 密码: 111111
# 支付密码: 111111

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:  #alipaydev是测试环境,alipay是生产环境
            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)

#上面的类就是支付宝的api接口,以下只需要调实例化产生api接口类对象后
if __name__ == "__main__":
#支付宝的api接口类实例化
    alipay = AliPay(
        appid=AliPayConfig.app_id,      #APPDI
        app_notify_url=AliPayConfig.notify_url,   #支付成功后的post请求
        return_url=AliPayConfig.return_url,     #支付成功后的get请求
        app_private_key_path=AliPayConfig.merchant_private_key_path,   #应用私钥
        alipay_public_key_path=AliPayConfig.alipay_public_key_path,  # 支付宝的公钥,验证支付宝回传消息使用,不是你自己的公钥
        debug=True,  # 默认False,是否是测试环境还是生产环境
    )

    #调用实例化对象下的direct_pay方法,生成支付的url
  import time
    query_params = alipay.direct_pay(
        subject="luffycity",  # 商品简单描述,可自定义哦
        out_trade_no="%s"time.time(),  # 商户订单号,可自定义但是商品订单号必须是唯一的,否则发送给支付宝不会被处理
        total_amount=100.00,  # 交易金额(单位: 元 保留俩位小数)
    )

    pay_url = "https://openapi.alipaydev.com/gateway.do?{}".format(query_params)#这就是生成的支付宝api接口
    print(pay_url)

 # 这一部分是验证url的签名,即支付成功后支付宝通过post请求返回给服务器的验证数据
    return_url = 'http://47.92.87.172:8000/?total_amount=100.00&timestamp=2017-08-15+23%3A53%3A34&sign=e9E9UE0AxR84NK8TP1CicX6aZL8VQj68ylugWGHnM79zA7BKTIuxxkf%2Fvhd
            DYz4XOLzNf9pTJxTDt8tTAAx%2FfUAJln4WAeZbacf1Gp4IzodcqU%2FsIc4z93xlfIZ7OLBoWW0kpKQ8AdOxrWBMXZck%2F1cffy4Ya2dWOYM6Pcdpd94CLNRPlH6kFsMCJCbhqvyJTflxdp
            VQ9kpH%2B%2Fhpqrqvm678vLwM%2B29LgqsLq0lojFWLe5ZGS1iFBdKiQI6wZiisBff%2BdAKT9Wcao3XeBUGigzUmVyEoVIcWJBH0Q8KTwz6IRC0S74FtfDWTafplUHlL%2Fnf6j%2FQd1y6
            Wcr2A5Kl6BQ%3D%3D&trade_no=2017081521001004340200204115&sign_type=RSA2&auth_app_id=2016080600180695&charset=utf-8&seller_id=2088102170208070&meth
            od=alipay.trade.page.pay.return&app_id=2016080600180695&out_trade_no=20170202185&version=1.0' #这一部分就是支付宝返回给服务器的数据
    o = urlparse(return_url)  #路径解析,就相当于是取request.body中的数据
    query = parse_qs(o.query)  #将数据转换成字典
    processed_query = {}
    ali_sign = query.pop("sign")[0]
  for key, value in query.items(): 
    processed_query[key] = value[0] # 验证签名校验的结果,就是支付宝发的post请求验证
  print(alipay.verify(processed_query, ali_sign))  #调用alipay对象下的verify认证方法,如果返回True就证明支付成功,否则失败
View Code

 

   3、根据步骤2中的讲解,自己编写支付宝支付框架

    1、如图所示:

      

    2、pay.py文件(该文件内就只写了个类,该类就是支付宝api的接口类)

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)
View Code

 

      3、page1.html文件

<!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>
View Code

 

       4、url.py文件

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),
]
View Code

 

      5、key文件夹

         1、pri文件存放的是应用私钥

         2、pub文件存放的是支付宝公钥

    6、views.py文件

from django.shortcuts import render, redirect, HttpResponse
from utils.pay import AliPay
import json
import time

#将实例化的api接口和参数存放子啊函数中
def ali():
    #APPID
    app_id = "2016082500309412"

    # POST请求,支付宝回调函数,用于支付成功后的验证
    notify_url = "http://www.dabo.com:8009/page2/"

    # GET请求,支付宝回调函数,用于支付成功后返回给用户的页面信息
    return_url = "http://www.dabo.com:8009/page2/"

    merchant_private_key_path = "keys/pri"  #应用私钥
    alipay_public_key_path = "keys/pub"  #支付宝公钥

    #实例化api接口类
    alipay = AliPay(
        appid=app_id,
        app_notify_url=notify_url,
        return_url=return_url,
        app_private_key_path=merchant_private_key_path,
        alipay_public_key_path=alipay_public_key_path,
        debug=True,  # 默认False,如果是True则为测试环境,如果为Flase则为生成环境
    )
    return alipay


def page1(request):
    if request.method == "GET":
        return render(request, 'page1.html')
    else:
        money = float(request.POST.get('money'))
        alipay = ali()          # 生成支付的url
        query_params = alipay.direct_pay(    #调用支付宝对象下的direct_pay方法
            subject="充气式韩红",  # 商品简单描述
            out_trade_no="x2" + str(time.time()),  # 商户订单号,有切必须唯一
            total_amount=money,  # 交易金额(单位: 元 保留俩位小数)
        )
        #支付宝api完整路径
        pay_url = "https://openapi.alipaydev.com/gateway.do?{}".format(query_params)

        return redirect(pay_url)


def page2(request):
    alipay = ali()
    if request.method == "POST":
        # 检测是否支付成功
        # 去请求体中获取所有返回的数据:状态/订单号
        from urllib.parse import parse_qs


        """
        # request.body                  => 字节类型
        # request.body.decode('utf-8')  => 字符串类型
        {"k1":["v1"],"k2":["v1"]}
        k1=[v1]&k2=[v2]
        """
        body_str = request.body.decode('utf-8')
        post_data = parse_qs(body_str)#parse_qs是将字符串直接json成字典的形式
        # {k1:[v1,],k2:[v2,]},这是支付宝返回给我们的json数据,但是不是我们想要的数据,

        post_dict = {}
        for k, v in post_data.items():#所以通过这种方式可以转换成我们想要的{k1:v1}格式的数据
            post_dict[k] = v[0]


        print(post_dict)
        """
        {'gmt_create': '2017-11-24 14:53:41', 'charset': 'utf-8', 'gmt_payment': '2017-11-24 14:53:48', 'notify_time': '2017-11-24 14:57:05', 'subject': '充气式韩红', 'sign': 'YwkPI9BObXZyhq4LM8//MixPdsVDcZu4BGPjB0qnq2zQj0SutGVU0guneuONfBoTsj4XUMRlQsPTHvETerjvrudGdsFoA9ZxIp/FsZDNgqn9i20IPaNTXOtQGhy5QUetMO11Lo10lnK15VYhraHkQTohho2R4q2U6xR/N4SB1OovKlUQ5arbiknUxR+3cXmRi090db9aWSq4+wLuqhpVOhnDTY83yKD9Ky8KDC9dQDgh4p0Ut6c+PpD2sbabooJBrDnOHqmE02TIRiipULVrRcAAtB72NBgVBebd4VTtxSZTxGvlnS/VCRbpN8lSr5p1Ou72I2nFhfrCuqmGRILwqw==', 'buyer_id': '2088102174924590', 'invoice_amount': '666.00', 'version': '1.0', 'notify_id': '11aab5323df78d1b3dba3e5aaf7636dkjy', 'fund_bill_list': '[{"amount":"666.00","fundChannel":"ALIPAYACCOUNT"}]', 'notify_type': 'trade_status_sync', 'out_trade_no': 'x21511506412.4733646', 'total_amount': '666.00', 'trade_status': 'TRADE_SUCCESS', 'trade_no': '2017112421001004590200343962', 'auth_app_id': '2016082500309412', 'receipt_amount': '666.00', 'point_amount': '0.00', 'app_id': '2016082500309412', 'buyer_pay_amount': '666.00', 'sign_type': 'RSA2', 'seller_id': '2088102172939262'}
        {'stade_status': "trade_success",'order':'x2123123123123'}
        """
        sign = post_dict.pop('sign', None)

        status = alipay.verify(post_dict, sign)#把数据和签名放在一起进行进行校验,验证是否支付成功
        if status:
            #print(post_dict['stade_status'])#支付宝系统的交易状态,stade_status这个写的有点问题,单词好像写错了
            print(post_dict['out_trade_no'])#商品网站唯一订单号

        return HttpResponse('POST返回')
    else:
        # QueryDict = {'k':[1],'k1':[11,22,3]}
        params = request.GET.dict()
        sign = params.pop('sign', None)
        status = alipay.verify(params, sign)
        print('GET验证', status)
        return HttpResponse('支付成功')
View Code

 

 

   

 

posted @ 2017-11-24 19:25  mx轩  阅读(1841)  评论(0编辑  收藏  举报