咱們在開發的過程當中常常會碰到調用微信或者支付寶接口進行付款,付款完成以後,若是用戶綁定了個人帳號,我只要有活動了,就要給這個關注個人用戶推進消息,讓用戶知道,好比說,咱們常常會關注一些公衆號,而後這些公衆號只要有了消息就會自動給我推送,咱們之後也會遇到這種推送的需求,那麼具體如何使用咱們的代碼來實現這種需求呢?還有就是用戶在付款的時候,我如何給他調用支付寶的接口呢?下面咱們就來具體的學習html
目前咱們的付款方式有支付寶支付,微信支付,銀聯支付,而後使用支付寶支付須要有一個商戶號,這個須要本身公司去申請(須要企業營業執照),申請完以後會有一個商戶號,咱們這邊主要是使用一種沙箱測試環境,沙箱環境地址以下jquery
https://openhome.alipay.com/platform/appDaily.htm?tab=info
進入之後,咱們就能夠申請我的的商戶號了,以下圖所示ajax
第一步、進入沙箱測試環境數據庫
第二步、生成商戶公鑰和支付寶公鑰django
第三步、下載沙箱測試應用json
咱們在使用的時候,調用支付寶接口和支付寶返回給咱們數據的時候,傳輸過程當中數據都是加密的,那麼簡單的複習一下加密方式小程序
對稱加密:所謂的對稱加密就是指加密密鑰和解密密鑰使用的是同一個,這樣只要咱們保證了這個私鑰不泄露,數據就是安全的微信小程序
非對稱加密:加密和解密的密鑰是不一樣的,使用公鑰加密,而後使用私鑰進行解密,公鑰能夠隨意分發給任何人,可是私鑰必定不能泄露,好比支付寶的公鑰就是公開的,用戶使用支付寶的公鑰進行數據的加密,傳輸給支付寶,而後支付寶經過本身的私鑰進行解密,這樣就能夠達到數據的傳輸,並且在傳輸的過程當中,就算被別人截取了,他因爲不知道支付寶的私鑰,因此對於咱們來講這個數據也是安全的。api
咱們在使用加密的時候,須要使用第三方的模塊,安裝以下數組
pip3 install pycryptodome
一、項目結構
二、項目流程介紹
1.拿到商戶號,回調地址,支付寶公鑰,商戶私鑰生成一個alipay對象
2.對象點direct_pay,傳支付金額,支付商品描述,支付訂單號,而後返回一個加密的串,這個加密串包含了第一步的信息
3.將拿到的串拼接到支付寶網關(測試/正式)的後面pay_url="https://openapi.alipaydev.com/gateway.do?{}".format(query_params)
4.向生成的地址發送get請求,會跳轉到支付寶的支付頁面
5.用戶付款完成(此時錢已經進入了商戶號),支付寶收到付款,向咱們配置的兩個回調地址發送請求(get,post)
6.通常get請求用戶展現,post請求用於修改訂單狀態,咱們在拿到請求的時候必定要驗籤(防止仿造支付寶向咱們發送請求)
-get請求通常用於頁面展現
-post請求用於修改訂單狀態
項目中須要配置的東西以下:
1.公鑰私鑰生成方式:https://docs.open.alipay.com/291/105971
支付寶公鑰:商戶號中輸入的應用公鑰,會自動生成一個支付寶公鑰,直接黏貼過來就好
應用私鑰(用戶私鑰)
2.支付寶回調返回數據中會有
singn:必須驗證簽名,驗證經過之後才能進行後續的修改
訂單id:根據訂單id修改訂單狀態
可能遇到的問題處理:
用戶付款了,但正在這時,服務掛掉,收不到支付寶的回調數據,如何處理?
若是服務掛掉了,支付寶會過一段時間自動在回調,在24h內,隔一段時間就會回調,這個時候咱們只須要重啓服務,就能夠 接收到了!
三、代碼展現
首先是路由層,咱們須要用到的是兩個接口,一個用來向支付寶發起付款,一個用來接收支付寶收款成功以後返回給咱們數據的接口,這裏咱們使用的是page1,page2
from django.contrib import admin from django.conf.urls import url from app01 import views urlpatterns = [ url('admin/', admin.site.urls), url(r'^page1/', views.page1), url(r'^page2/', views.page2), ]
視圖函數展現
import json import time from django.shortcuts import render, redirect, HttpResponse from myutils.pay import AliPay def ali(): # 沙箱環境地址:https://openhome.alipay.com/platform/appDaily.htm?tab=info app_id = "2016101000653326" # 支付寶收到用戶的支付,會向商戶發兩個請求,一個get請求,一個post請求 # POST請求,用於最後的檢測 notify_url = "http://127.0.0.1/page2/" # GET請求,用於頁面的跳轉展現 return_url = "http://127.0.0.1/page2/" # 用戶私鑰 merchant_private_key_path = "keys/app_private_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, 是True的時候纔會走沙箱環境,不然就是走的真正的支付接口 ) # 將阿里pay對象返回 return alipay def page1(request): if request.method == "GET": # get請求的話直接返回一個頁面 return render(request, 'page1.html') # 是post請求,那就要開始調用支付寶的接口 else: money = float(request.POST.get('money')) # 這裏是調用ali這個函數,開始執行內部的代碼,而後將返回值接收 alipay = ali() # 調用對象的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請求 # from django.http import JsonResponse # return JsonResponse({'status':100,'url':pay_url}) return redirect(pay_url) def page2(request): # 支付寶若是收到用戶的支付,支付寶會給個人地址發一個post請求,一個get請求 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) # 作二次驗證 sign = post_dict.pop('sign', None) # 經過調用alipay的verify方法去認證 status = alipay.verify(post_dict, sign) print('POST驗證', status) if status: # 修改本身訂單狀態 pass return HttpResponse('POST返回') else: params = request.GET sign = params.pop('sign', None) status = alipay.verify(params, sign) print('GET驗證', status) return HttpResponse('支付成功')
在這裏咱們用到了alipay對象,如今展現alipay對象的代碼,它裏面作的具體操做以下
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 # 這是alipay對象 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 # 回來後的get請求地址 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 data = self.build_body("alipay.trade.page.pay", biz_content, self.return_url) return self.sign_data(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) # 這裏就是拿到排序後的列表套元組[(),(),()] # "{k}={v}&{}={}" name = egon & age = 18 拼接成urlencoded形式的編碼 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 = [] # 對data進行取值 for key, value in data.items(): # 判斷他的value類型是否是字典 if isinstance(value, dict): # 是的話將這個key加到數組中biz_content complex_keys.append(key) # 將字典類型的數據dump出來 for key in complex_keys: # 將字典取出後添加到整個大字典中,而後將後面的內容序列化 separators是用來獲取不那麼緊湊的json格式的數據 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.項目流程分析
1.用戶登陸到系統,掃碼關注個人服務號(這個是微信平臺提供的),如今的用戶並無跟個人系統進行綁定,僅僅只是關注了服務號而已
2.如今咱們要作的就是讓用戶和個人系統進行綁定
生成一個連接地址(微信的連接地址),經過連接地址生成了二維碼,讓用戶去掃碼
用戶掃描,而且確認受權,微信會向咱們的回調地址發送請求,攜帶uid和code回來
咱們的系統拿到這個之後再去調微信的接口發送請求,攜帶着code過去,請求回用戶的openid,其實就是微信的id
將拿到的openid存到當前用戶的數據庫中,完成用戶的綁定
3.一旦用戶購買了課程,給用戶推送消息
獲取到acess_token,向微信的某個接口發請求,拿回token
向微信推送消息的接口發送請求(給誰發,微信id,發送什麼內容),須要攜帶token,而且有模板消息和普通消息
注意:
沙箱環境地址:https://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=sandbox/login
咱們要保持配置文件和微信平臺的回調地址同樣,因此要在微信平臺那裏進行修改
網頁帳號 網頁受權獲取用戶基本信息 無上限 修改 ---》》配置成本身的服務器的地址
這裏咱們須要用到的是request模塊,具體瞭解見鏈https://www.cnblogs.com/liuqingzheng/articles/10226876.html
settings配置
# ############# 微信 ############## WECHAT_CONFIG = { 'app_id': 'wx6edde7a6a97e4fcd', # 微信平臺的商戶的id 'appsecret': '02d8bb38434c75d83e671047a6c7d182', # 這個不要隨便給人看 'redirect_uri': 'http://42.56.89.12/callback/', }
路由層配置
urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^login/$', views.login), url(r'^index/$', views.index), url(r'^bind/$', views.bind), url(r'^bind_qcode/$', views.bind_qcode), # 綁定消息以後返回的路由 url(r'^callback/$', views.callback), # 提供給微信的回調地址 url(r'^sendmsg/$', views.sendmsg), # 主動給用戶推送消息 ]
視圖層
import json import functools import requests from django.conf import settings from django.shortcuts import render, redirect, HttpResponse from django.http import JsonResponse from app01 import models # 沙箱環境地址:https://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=sandbox/login def index(request): obj = models.UserInfo.objects.get(id=1) return render(request, 'index.html', {'obj': obj}) def auth(func): def inner(request, *args, **kwargs): user_info = request.session.get('user_info') if not user_info: return redirect('/login/') return func(request, *args, **kwargs) return inner def login(request): """ 用戶登陸 :param request: :return: """ # models.UserInfo.objects.create(username='luffy',password=123) if request.method == "POST": user = request.POST.get('user') pwd = request.POST.get('pwd') obj = models.UserInfo.objects.filter(username=user, password=pwd).first() if obj: # 登陸成功之後將用戶信息添加到session中 request.session['user_info'] = {'id': obj.id, 'name': obj.username, 'uid': obj.uid} # 重定向到bind return redirect('/bind/') else: return render(request, 'login.html') # 這裏使用了裝飾器 @auth def bind(request): """ 用戶登陸後,關注公衆號,並綁定我的微信(用於之後消息推送) :param request: :return: """ # 直接是去到了一個綁定頁面 return render(request, 'bind.html') @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( # 商戶的appid appid=settings.WECHAT_CONFIG["app_id"], # 'wx6edde7a6a97e4fcd', # 回調地址 redirect_uri=settings.WECHAT_CONFIG["redirect_uri"], # 當前登陸用戶的惟一id,與第三方平臺交互的用戶惟一id state=request.session['user_info']['uid'] # 爲當前用戶生成MD5值 ) ret['data'] = access_url except Exception as e: ret['code'] = 1001 ret['msg'] = str(e) # access_url是微信的地址,裏面包含了商戶的id,真正的回調地址,以及當前的用戶的uid # ret = {'code': 1000, 'data': access_url} /ret = {'code': 1001, 'msg': str(e)} return JsonResponse(ret) def callback(request): """ 用戶在手機微信上掃碼後,微信自動調用該方法。 用於獲取掃碼用戶的惟一ID,之後用於給他推送消息。 :param request: :return: """ # 這是微信經過回調函數發給咱們的消息 code = request.GET.get("code") # 用戶md5值,用戶惟一id state = request.GET.get("state") print(code) # 獲取該用戶openId(用戶惟一,用於給用戶發送消息) 朝微信發送get請求,獲取用戶的微信id # request模塊朝https://api.weixin.qq.com/sns/oauth2/access_token地址發get請求 # 模擬瀏覽器發送請求 res = requests.get( url="https://api.weixin.qq.com/sns/oauth2/access_token", # get請求裏面攜帶的參數 params={ "appid": settings.WECHAT_CONFIG["app_id"], "secret": settings.WECHAT_CONFIG["appsecret"], # 帶着它過去是確認咱們前面剛剛登陸過 "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) # 朝用戶發送推送消息的 def sendmsg(request): # 獲取token 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() # 整個result是一個大字典 if result.get("access_token"): access_token = result.get('access_token') else: access_token = None return access_token # 得到成功後的token access_token = get_access_token() # 根據用戶的uid拿到他的微信號,而後用這個給用戶推送消息 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是放在get請求路勁後面?拼接的東西 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('發送失敗')
模板層綁定二維碼展現
{% load staticfiles %} <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <div style="width: 600px;margin: 0 auto"> <h1>請關注我的服務號,並綁定我的用戶(用於之後的消息提醒)</h1> <div> <h3>第一步:關注我的微信服務號</h3> <img style="height: 100px;width: 100px" src="{% static "img/0.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:{'code':1000,'data':url地址} //result.data 取出來的是什麼?是後臺生成的一個地址 //經過js生成一個二維碼圖片放到div中 $('#qrcode').empty().qrcode({text: result.data}); } }); } </script> </body> </html>
登陸模板
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <form action="/login/" method="post"> {% csrf_token %} <input type="text" name="user" placeholder="用戶名"> <input type="password" name="pwd" placeholder="密碼"> <input type="submit" value="登陸"> </form> </body> </html>
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <title>Title</title> <meta name="viewport" content="width=device-width, initial-scale=1"> </head> <body> <h1>{{ obj.username }} -> {{ obj.wx_id }}</h1> </body> </html>
模型表層展現
import hashlib from django.db import models class UserInfo(models.Model): username = models.CharField("用戶名", max_length=64, unique=True) password = models.CharField("密碼", max_length=64) uid = models.CharField(verbose_name='我的惟一ID',max_length=64, unique=True) wx_id = models.CharField(verbose_name="微信ID", max_length=128, blank=True, null=True, db_index=True) def save(self, *args, **kwargs): # 建立用戶時,爲用戶自動生成我的惟一ID if not self.pk: m = hashlib.md5() m.update(self.username.encode(encoding="utf-8")) self.uid = m.hexdigest() super(UserInfo, self).save(*args, **kwargs)
靜態文件主要是你我的的一些文件和js的配置,這裏則不作一一贅述。