DAY108 - 路飛學城(四)- 路飛學城之支付寶支付、微信推送
阿新 • • 發佈:2018-12-29
一、支付寶支付
正式環境:
用營業執照,申請商戶號,appid
測試環境:
沙箱環境:https://openhome.alipay.com/platform/appDaily.htm?tab=info
第一步
# 呼叫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)
二、微信推送
目前情況
我們的網站,想要對我們的使用者進行微信推送,使用者也掃描了我們的二維碼,成為了我們的微信使用者,但是要微信推送,就需要該使用者的微信的唯一ID,可我們本地伺服器裡還沒有微信的ID,所以現在就需要拿到這個ID
流程
第一步:
我們要生成一個二維碼,讓使用者掃描
1.點選按鈕
<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('傳送失敗')