Django调用支付宝(alipay)接口实现“获取用户信息”和“页面支付”

这段时间做毕业设计,需要调用支付宝的接口,但死活装不上支付宝的“开放平台服务端 SDK”。无奈只能在网上找一些不需要装SDK的demo和看SDK的源码,copy和修改里面需要的部分。然后做个记录吧。

一、前言

  1. 如果你能装上支付宝的SDK,用那个会方便很多,就不需要折腾了。
  2. 这个demo是在“沙箱环境”上进行的,账号用的是沙箱环境的账号,具体接入教程可查看支付宝沙箱环境,这里就不赘述了。
  3. 需要ip是公网ip,可以使用内网穿透。
  4. 需要安装的包:Cryptodome

二、实现“页面支付”

1. 效果图

image.png
image.png

image.png

image.png

2. 具体流程图

image.png

3. 创建Django项目

  1. 目录图


    image.png
  2. setting.py配置
    更改三处

# 配置APP
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'alipay',
]
# 配置视图文件夹
TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [os.path.join(BASE_DIR, 'templates')],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

# 添加密钥文件的路径
ALIPAY_PUBLIC = os.path.join(BASE_DIR, "trading_platform", "alipay_keys", "alipay_public.txt")
APP_PUBLIC = os.path.join(BASE_DIR, "trading_platform", "alipay_keys", "app_public.txt")
APP_PRIVATE = os.path.join(BASE_DIR, "trading_platform", "alipay_keys", "app_private.txt")
  1. alipayAPI:编写支付宝接口类和密钥
    在“alipayPageTrade”文件夹中创建“alipayAPI”文件夹。


    image.png

    后缀为txt的是三个密钥的文件,注意记录的格式是:

# alipay_public.txt
-----BEGIN PUBLIC KEY-----
你的支付宝公钥
-----END PUBLIC KEY-----

# app_public.txt
-----BEGIN PUBLIC KEY-----
你的app公钥
-----END PUBLIC KEY-----

# app_private.txt
-----BEGIN RSA PRIVATE KEY-----
你的app私钥
-----END RSA PRIVATE KEY-----

编写alipay_custom.py:

from datetime import datetime
from Cryptodome.PublicKey import RSA
from Cryptodome.Signature import PKCS1_v1_5
from Cryptodome.Hash import SHA256
from urllib.parse import quote_plus
from base64 import decodebytes, encodebytes, b64encode
import json


class AliPayPageTrade(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)
  1. urls.py:配置路由
  • alipayPageTrade/urls.py
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('alipay/', include('alipay.urls')),
]
  • alipay/urls.py
from django.urls import path
from . import views

urlpatterns = [
    path('page/', views.AlipayPageTrade.as_view(), name='purchase'),
    path('update/', views.AlipayRedirect.as_view()),
    path('result/', views.AlipayRedirect.as_view()),
]
  1. templates:编写模板
    templates/index.html
<html>

<head>
<title>支付宝页面支付</title>
</head>

<body>
<form action="{% url 'purchase' %}" method="post">
    {% csrf_token %}
    <label>输入金额: </label>
    <input type="text" name="money">
    <input type="submit" value="提交">
</form>
</body>

</html>
  1. alipay/views.py
from django.shortcuts import render,redirect
from django.views.generic.base import View
from django.http import HttpResponse
from alipayPageTrade.alipayAPI.alipay_custom import AliPayPageTrade
from django.conf import settings

def aliPay(A):
    # 初始化支付宝接口实例
    obj = A(
        appid="2016102300746249",                              # 支付宝沙箱里面的APPID,需要改成你自己的
        app_notify_url="http://hello.shenzhuo.vip:12057/alipay/update/",  # 如果支付成功,支付宝会向这个地址发送POST请求(校验是否支付已经完成),此地址要能够在公网进行访问,需要改成你自己的服务器地址
        return_url="http://hello.shenzhuo.vip:12057/alipay/result/",            # 如果支付成功,重定向回到你的网站的地址。需要你自己改,这里是我的服务器地址
        alipay_public_key_path=settings.ALIPAY_PUBLIC,  # 支付宝公钥
        app_private_key_path=settings.APP_PRIVATE,      # 应用私钥
        debug=True,  # 默认False,True表示使用沙箱环境测试
    )
    return obj

# Create your views here.
class AlipayPageTrade(View):
    def get(self, request):
        return render(request, 'index.html')
    def post(self, request):
        # 初始化支付宝实例
        alipay = aliPay(AliPayPageTrade)

        # 初始化需要的数据
        money = request.POST.get("money") # 金额,前端获取
        out_trade_no = 20204131000 # 订单号,后台生成,需不重复
        subject = "iphone10" # 商品名称


        # 更新数据库

        # 拼接url
        query_params = alipay.direct_pay(
                subject=subject,
                out_trade_no=out_trade_no,
                total_amount=money,
            )
        pay_url = "https://openapi.alipaydev.com/gateway.do?{}".format(query_params)

        return redirect(pay_url)

class AlipayRedirect(View):
    def get(self, request):
        return HttpResponse("支付成功")
    def post(self, request):
        # 更新数据库操作
        pass

三、实现“获取用户信息”

1.效果图

image.png
image.png

image.png

image.png

2. 具体流程图

image.png

3. 创建django项目

  1. setting.py的配置和密钥文件的设置参照前面的
  2. alipayAPI/alipay_custom.py:支付宝的接口类
from datetime import datetime
from Cryptodome.PublicKey import RSA
from Cryptodome.Signature import PKCS1_v1_5
from Cryptodome.Hash import SHA256
from urllib.parse import quote_plus
from base64 import decodebytes, encodebytes, b64encode
import json

class AliPayOauthToken:
    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 = fp.read()
        self.alipay_public_key_path = alipay_public_key_path
        with open(self.alipay_public_key_path) as fp:
            self.alipay_public_key = fp.read()

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

    def get_param(self, code):
        data = {
            "app_id": self.appid,
            "method": "alipay.system.oauth.token",
            "charset": "GBK",
            "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
            "version": "1.0",
            "grant_type": "authorization_code",
            "sign_type":"RSA2",
            "code": code
        }

        common_data = self.sign(data)
        query_string = self.url_encode(common_data, common_data["charset"])
        data = self.url_encode(data, common_data["charset"])
        return query_string, data

    def sign(self, data):
        # 开始计算签名
        common_data = data.copy()
        del common_data["grant_type"]
        sign_content = self.get_sign_content(data)
        sign = self.sign_with_rsa2(self.app_private_key, sign_content, data["charset"])
        common_data["sign"] = sign
        for key in common_data.keys():
            if key in data:
                data.pop(key)
        return common_data

    def get_sign_content(self, all_params):
        sign_content = ""
        for (k, v) in sorted(all_params.items()):
            value = v
            if not isinstance(value, str):
                value = json.dumps(value, ensure_ascii=False)
            sign_content += ("&" + k + "=" + value)
        sign_content = sign_content[1:] # sign_content = "key=1232&sign_type=123"
        return sign_content

    def sign_with_rsa2(self, private_key, sign_content, charset):
        import rsa
        sign_content = sign_content.encode(charset)
        signature = rsa.sign(sign_content, rsa.PrivateKey.load_pkcs1(private_key, format='PEM'), 'SHA-256')
        sign = b64encode(signature)
        sign = str(sign, encoding=charset)
        return sign

    def url_encode(self, params, charset):
        query_string = ""
        for (k, v) in params.items():
            value = v
            if not isinstance(value, str):
                value = json.dumps(value, ensure_ascii=False)
            value = quote_plus(value, encoding=charset)
            query_string += ("&" + k + "=" + value)
        query_string = query_string[1:]
        return query_string
  1. views.py:视图函数
from django.shortcuts import render,redirect
from django.views.generic.base import View
from django.http import HttpResponse
from alipayPageTrade.alipayAPI.alipay_custom import AliPayPageTrade, AliPayOauthToken
from django.conf import settings
import uuid
import json
import requests

def aliPay(A):
    # 初始化支付宝接口实例
    obj = A(
        appid="2016102300746249",                              # 支付宝沙箱里面的APPID,需要改成你自己的
        app_notify_url="http://hello.shenzhuo.vip:12057/alipay/update/",  # 如果支付成功,支付宝会向这个地址发送POST请求(校验是否支付已经完成),此地址要能够在公网进行访问,需要改成你自己的服务器地址
        return_url="http://hello.shenzhuo.vip:12057/alipay/result/",            # 如果支付成功,重定向回到你的网站的地址。需要你自己改,这里是我的服务器地址
        alipay_public_key_path=settings.ALIPAY_PUBLIC,  # 支付宝公钥
        app_private_key_path=settings.APP_PRIVATE,      # 应用私钥
        debug=True,  # 默认False,True表示使用沙箱环境测试
    )
    return obj

# Create your views here.
class AlipayPageTrade(View):
    def get(self, request):
        return render(request, 'index.html')

class AlipayLogin(View):
    # 获取支付宝用户信息测试
    def get(self, request):
        from urllib.parse import urlencode
        parameter = {
            "app_id": "2016102300746249",
            "scope": "auth_user",
            "redirect_uri": "http://hello.shenzhuo.vip:12057/alipay/redirect/",
            "state": "init"
        }
        origin_url = "https://openauth.alipaydev.com/oauth2/publicAppAuthorize.htm"

        data = urlencode(parameter)
        alipay_url = origin_url + "?" + data
        print(alipay_url)

        return redirect(alipay_url)

class AlipayLoginRedirect(View):
    # 获取支付宝回调地址
    def get(self, request):
        THREAD_LOCAL_uuid = str(uuid.uuid1())
        headers = {
            'Content-type': 'application/x-www-form-urlencoded;charset=GBK',
            "Cache-Control": "no-cache",
            "Connection": "Keep-Alive",
            "log-uuid": THREAD_LOCAL_uuid
        }
        code = request.GET.get("auth_code")
        alipay = aliPay(AliPayOauthToken)
        query_string, param = alipay.get_param(code)
        url = "https://openapi.alipaydev.com/gateway.do" + "?" + query_string
        response = requests.post(url, data=param, headers=headers)
        print(response.text) # 返回的json数据

        data = json.loads(response.text)
        alipay_id = data["alipay_system_oauth_token_response"]["user_id"]
        print(alipay_id) # 支付宝用户的id号

        return HttpResponse("登录成功")
  1. urls.py:路由配置
from django.urls import path
from . import views

urlpatterns = [
    path('page/', views.AlipayPageTrade.as_view(), 
    path('login/', views.AlipayLogin.as_view(), name='login'),
    path('redirect/', views.AlipayLoginRedirect.as_view()),
]
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,544评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,430评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,764评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,193评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,216评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,182评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,063评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,917评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,329评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,543评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,722评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,425评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,019评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,671评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,825评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,729评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,614评论 2 353

推荐阅读更多精彩内容