DAY108 - 路飛學城(四)- 路飛學城之支付寶支付、微信推送

1、支付寶支付

正式環境:html

​ 用營業執照,申請商戶號,appidpython

測試環境:jquery

​ 沙箱環境:https://openhome.alipay.com/platform/appDaily.htm?tab=infoajax

第一步

# 調用AliPay接口
from utils.pay import AliPay
# 配置一些信息
def ali():
    # 沙箱環境地址:https://openhome.alipay.com/platform/appDaily.htm?tab=info
    app_id = "2016092000554611"
    # 支付寶收到用戶的支付,會向商戶發兩個請求,一個get請求,一個post請求
    # POST請求,用於最後的檢測
    notify_url = "http://42.56.89.12:80/page2/"
    # GET請求,用於頁面的跳轉展現,支付成功後商家的頁面
    return_url = "http://42.56.89.12:80/page2/"

    # 應用私鑰
    # https://docs.open.alipay.com/291/105971
    # 從這個網址下載軟件生成應用公鑰和應用私鑰
    # 並配置到keys/app_private_2048.txt中
    merchant_private_key_path = "keys/app_private_2048.txt"
    # 支付寶公鑰
    # 把應用公鑰配置到這裏:https://openhome.alipay.com/platform/appDaily.htm?tab=info
    # 得到支付寶公鑰,並配置到keys/alipay_public_2048.txt
    alipay_public_key_path = "keys/alipay_public_2048.txt"
    # 生成一個AliPay的對象
    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,
    )
    return alipay

第二步

# 確認付款頁面
def page1(request):
    if request.method == "GET":
        return render(request, 'page1.html')
    else:
        # money:支付的金額
        money = float(request.POST.get('money'))
        # 生成一個對象
        alipay = ali()
        # 生成支付的url
        # 對象調用direct_pay,生成了一個地址
        query_params = alipay.direct_pay(
            subject="充氣娃娃",  # 商品簡單描述
            out_trade_no="x2" + str(time.time()),  # 商戶訂單號
            total_amount=money,  # 交易金額(單位: 元 保留倆位小數)
        )

        # 拼接地址
        pay_url = "https://openapi.alipaydev.com/gateway.do?{}".format(query_params)
        print(pay_url)
        # 朝這個地址重定向,發get請求
        return redirect(pay_url)

第三步

# 當支付寶收到錢之後,因爲咱們在ali中配置了回調地址page2,就會跳轉到page2
# 併發送兩個請求:POST請求處理訂單信息;GET請求顯示支付成功頁面
def page2(request):
    alipay = ali()
    if request.method == "POST":
        # 檢測是否支付成功
        # 去請求體中獲取全部返回的數據:狀態/訂單號
        from urllib.parse import parse_qs
        body_str = request.body.decode('utf-8')
        print(body_str)

        post_data = parse_qs(body_str)
        print('支付寶給個人數據:::---------',post_data)
        post_dict = {}
        for k, v in post_data.items():
            post_dict[k] = v[0]
        print('轉完以後的字典',post_dict)
        # 從post_dict能夠獲取訂單號:out_trade_no,從而去數據庫查找並修改訂單狀態

        sign = post_dict.pop('sign', None)
        status = alipay.verify(post_dict, sign)
        print('POST驗證', status)
        return HttpResponse('POST返回')

    else:
        # 得到params
        params = request.GET.dict()
        # 得到sign
        sign = params.pop('sign', None)
        # 把params和sign放入verify()裏處理,返回的結果status
        status = alipay.verify(params, sign)
        print('GET驗證', status)
        return HttpResponse('支付成功')

支付寶python接口

# 須要安裝模塊:pip3 install 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 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)

2、微信推送

目前狀況

​ 咱們的網站,想要對咱們的用戶進行微信推送,用戶也掃描了咱們的二維碼,成爲了咱們的微信用戶,可是要微信推送,就須要該用戶的微信的惟一ID,可咱們本地服務器裏尚未微信的ID,因此如今就須要拿到這個ID數據庫

流程

第一步:

咱們要生成一個二維碼,讓用戶掃描json

1.點擊按鈕api

<div style="width: 600px;margin: 0 auto">
    <h1>請關注路飛學城服務號,並綁定我的用戶(用於之後的消息提醒)</h1>
    <div>
        <h3>第一步:關注路飛學城微信服務號</h3>
        <img style="height: 100px;width: 100px" src="{% static "img/luffy.jpeg" %}">
    </div>
    <input type="button" value="下一步【獲取綁定二維碼】" onclick="getBindUserQcode()">
    <div>
        <h3>第二步:綁定我的帳戶</h3>
        <div id="qrcode" style="width: 250px;height: 250px;background-color: white;margin: 100px auto;"></div>
    </div>
</div>
<script src="{% static "js/jquery.min.js" %}"></script>
{#qrcode 能夠生成二維碼 #}
<script src="{% static "js/jquery.qrcode.min.js" %}"></script>
<script src="{% static "js/qrcode.js" %}"></script>
<script>
    function getBindUserQcode() {
        $.ajax({
            url: '/bind_qcode/',
            type: 'GET',
            success: function (result) {
                console.log(result);
                //result.data 取出來的是什麼?是後臺生成的一個地址
                //經過js生成一個二維碼圖片放到div中
                $('#qrcode').empty().qrcode({text: result.data});
            }
        });
    }
</script>

2.向/bind_qcode/發送了get請求得到二維碼緩存

# 二維碼的本質就是URL地址,你掃描二維碼,就是向這個URL地址發請求
@auth
def bind_qcode(request):
    """
    生成二維碼
    :param request: 
    :return: 
    """
    ret = {'code': 1000}
    try:
        access_url = "https://open.weixin.qq.com/connect/oauth2/authorize?appid={appid}&redirect_uri={redirect_uri}&response_type=code&scope=snsapi_userinfo&state={state}#wechat_redirect"
        access_url = access_url.format(
            # 這三個參數,配置在了setting裏,而且是從微信的沙箱裏得到的虛擬數據
            # 商戶的appid
            appid=settings.WECHAT_CONFIG["app_id"], # 'wx6edde7a6a97e4fcd',
            # 回調地址
            redirect_uri=settings.WECHAT_CONFIG["redirect_uri"],
            # 當前登陸用戶的惟一id
            state=request.session['user_info']['uid'] # 爲當前用戶生成MD5值
        )
        ret['data'] = access_url
    except Exception as e:
        ret['code'] = 1001
        ret['msg'] = str(e)

    # 返回的就是一個拼接好的URL地址
    return JsonResponse(ret)

第二步:

​ 用戶掃描的二維碼,其實就是讓用戶的微信向微信服務器發送一個請求,而且這個請求攜帶了一個回調地址redirect_uri,微信服務器一旦檢測到,就會向這個地址重定向,而且攜帶參數,也就是向咱們的服務器發送了微信的數據服務器

# 回調地址指向了這個視圖callback
def callback(request):
    """
    用戶在手機微信上掃碼後,微信自動調用該方法。
    用於獲取掃碼用戶的惟一ID,之後用於給他推送消息。
    :param request: 
    :return: 
    """
    # 從微信服務端得到數據
    code = request.GET.get("code")

    # 用戶md5值,用戶惟一id
    state = request.GET.get("state")

    
    得到微信服務器發來的數據後,咱們再向微信的https://api.weixin.qq.com/sns/oauth2/access_token地址發get請求,並攜帶參數
    
    這個地址返回了一個JSON的數據,轉成字符串,該數據裏面就擁有用戶的微信惟一ID------openid
    
    # 獲取該用戶openId(用戶惟一,用於給用戶發送消息)
    # request模塊朝https://api.weixin.qq.com/sns/oauth2/access_token地址發get請求
    res = requests.get(
        url="https://api.weixin.qq.com/sns/oauth2/access_token",
        params={
            "appid": 'wx3e1f0883236623f9',
            "secret": '508ec4590702c76e6863be6df01ad95a',
            "code": code,
            "grant_type": 'authorization_code',
        }
    ).json()
    # res.data   是json格式
    # res=json.loads(res.data)
    # res是一個字典
    # 獲取的到openid表示用戶受權成功
    openid = res.get("openid")
    if openid:
        models.UserInfo.objects.filter(uid=state).update(wx_id=openid)
        response = "<h1>受權成功 %s </h1>" % openid
    else:
        response = "<h1>用戶掃碼以後,手機上的提示</h1>"
    return HttpResponse(response)

微信支付流程

第三步

得到微信用戶的ID以後,就能夠開始推送了微信

def sendmsg(request):
    def get_access_token():
        """
        獲取微信全局接口的憑證(默認有效期倆個小時)
        若是不天天請求次數過多, 經過設置緩存便可
        """
        result = requests.get(
            url="https://api.weixin.qq.com/cgi-bin/token",
            params={
                "grant_type": "client_credential",
                "appid": settings.WECHAT_CONFIG['app_id'],
                "secret": settings.WECHAT_CONFIG['appsecret'],
            }
        ).json()
        if result.get("access_token"):
            access_token = result.get('access_token')
        else:
            access_token = None
        return access_token

    # 得到微信用戶的TOKEN
    access_token = get_access_token()
    
    # 從數據庫拿到當前用戶的微信ID
    openid = models.UserInfo.objects.get(id=1).wx_id

    # 自定義推送信息
    def send_custom_msg():
        body = {
            "touser": openid,
            "msgtype": "text",
            "text": {
                "content": 'lqz大帥哥'
            }
        }
        response = requests.post(
            url="https://api.weixin.qq.com/cgi-bin/message/custom/send",
            # 放到路徑?後面的東西
            params={
                'access_token': access_token
            },
            # 這是post請求body體中的內容
            data=bytes(json.dumps(body, ensure_ascii=False), encoding='utf-8')
        )
        # 這裏可根據回執code進行斷定是否發送成功(也能夠根據code根據錯誤信息)
        result = response.json()
        return result
    
    # 模板信息
    def send_template_msg():
        """
        發送模版消息
        """
        res = requests.post(
            url="https://api.weixin.qq.com/cgi-bin/message/template/send",
            params={
                'access_token': access_token
            },
            json={
                "touser": openid,
                "template_id": 'IaSe9s0rukUfKy4ZCbP4p7Hqbgp1L4hG6_EGobO2gMg',
                "data": {
                    "first": {
                        "value": "lqz",
                        "color": "#173177"
                    },
                    "keyword1": {
                        "value": "大帥哥",
                        "color": "#173177"
                    },
                }
            }
        )
        result = res.json()
        return result

    result = send_custom_msg()

    if result.get('errcode') == 0:
        return HttpResponse('發送成功')
    return HttpResponse('發送失敗')
相關文章
相關標籤/搜索