支付寶支付
如果想在網站上,通過掃碼支付寶收錢,你必須到支付寶網站https://openhome.alipay.com/platform/home.htm申請賬號,但是正式的,需要你提供營業執照,現在沒有,也不要緊,支付寶還提供一個沙箱的測試環境
服務商注冊:業務只是網站上收個錢,注冊一個支付系統服務商就可以了
注冊完成后,進入我的螞蟻金服開方平台首頁,進行應用創建就可以了(這是正式的,需要營業執照)
程序開發使用時,你只需要按照支付寶提供的接口規則和加密方法進行數據傳遞就可以了
現在沒有營業執照,怎么玩,搞個沙箱環境玩玩
開發者中心-->開發服務-->研發服務,點進去,支付寶會提供一些數據給我們進行測試,其中APPID,支付寶網關(帶dev就是測試地址)
並且支付寶,提供一個測試的支付寶app,需要測試掃碼支付,就要下載這個app,真正的支付寶是不支持沙箱測試碼,不僅如此還在 沙箱賬號 下提供一個測試賬號給你(商家賬號和買家賬號),你用買家賬號登錄這個app就可以了,不要用你自己的支付寶賬號哦
開發程序
發送數據時,需要按阿里的規則對數據加密,這個你也不要太操心,一般阿里也會提供一個SDK,相當於一個對數據進行加密的py文件,但是阿里官方提供了java等的,唯獨沒有python的,你可以到GitHub上去搜一個pay.py,並且這個是依賴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 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)
前端直接讓輸入個支付金額就可以了
<form method="post">
{% csrf_token %}
<input type="text" name="price" placeholder="請輸入要支付的金額">
<input type="submit" value="支付">
</form>
后端走/index,在post分支下
- 獲取支付數據
- 對價格、購買商品信息加密
- 拼接支付寶支付掃碼頁面url
- 跳轉到支付掃碼頁面
對於加密,就要用到剛才提到那個SDK了,在AliPay實例對象的時候需要提供APPID和支付成功后跳轉url,公鑰,私鑰等信息
但是公鑰,私鑰去哪里取呢?此時沙箱環境里是沒有提供的,需要我們生成,那怎么生成了?
點擊沙箱里的 設置應用公鑰,再點設置應用公鑰,輸入框上有個 查看密鑰生成方法 點擊一下,在使用方法里,點擊下載WINDOWS,點擊它下面bat工具就會自動生成 應用公鑰,應用私鑰(會有兩個文件),而且你在沙箱那邊上傳應用公鑰后,它還會自動生成一個支付寶公鑰,而我們調用接口的時候,就要用到支付寶公鑰和應用私鑰
from django.shortcuts import render,redirect,HttpResponse
from django.views.decorators.csrf import csrf_exempt
from utils.pay import AliPay
import time
from django.conf import settings
def aliPay():
obj = AliPay(
appid=settings.APPID,
app_notify_url=settings.NOTIFY_URL, # 如果支付成功,支付寶會向這個地址發送POST請求(校驗是否支付已經完成)
return_url=settings.RETURN_URL, # 如果支付成功,重定向回到你的網站的地址。
alipay_public_key_path=settings.PUB_KEY_PATH, # 支付寶公鑰
app_private_key_path=settings.PRI_KEY_PATH, # 應用私鑰
debug=True, # 默認False,
)
return obj
def index(request):
if request.method == 'GET':
return render(request,'index.html')
alipay = aliPay()
# 對購買的數據進行加密
money = float(request.POST.get('price')) #支付數保留兩位小數
out_trade_no = "x2" + str(time.time()) # 商戶訂單號
# 1. 在數據庫創建一條數據:狀態(待支付)
query_params = alipay.direct_pay(
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)
當然跳轉支付頁面后,商家怎么知道商品有沒有支付成功呢?
所以在上面過程中有個app_notify_url,就是用於支付成功后回調的url,而return_url則是在支付成功后跳轉的url,支付寶要給你的回調url發送post請求,你就必須有公網IP,支付寶才能找到你
def pay_result(request):
"""
支付完成后,跳轉回的地址
:param request:
:return:
"""
params = request.GET.dict()
sign = params.pop('sign', None) #獲取簽名
alipay = aliPay()
status = alipay.verify(params, sign) #驗證是否是阿里返回的數據
if status:
return HttpResponse('支付成功')
return HttpResponse('支付失敗')
@csrf_exempt
def update_order(request):
"""
支付成功后,支付寶向該地址發送的POST請求(用於修改訂單狀態)
:param request:
:return:
"""
if request.method == 'POST':
from urllib.parse import parse_qs
body_str = request.body.decode('utf-8')
post_data = parse_qs(body_str)
post_dict = {}
for k, v in post_data.items():
post_dict[k] = v[0]
alipay = aliPay()
sign = post_dict.pop('sign', None)
status = alipay.verify(post_dict, sign)
if status:
# 修改訂單狀態
out_trade_no = post_dict.get('out_trade_no')
print(out_trade_no)
# 2. 根據訂單號將數據庫中的數據進行更新
return HttpResponse('支付成功')
else:
return HttpResponse('支付失敗')
return HttpResponse('')
微信消息推送
有這么一些業務場景,比如,你購買商品成功,向微信推送一條購買成功的消息...
當然之前是通過短信通知,如果業務沒處理好,短信成本還是很高的,而微信成本相對低得多
一般是認證服務號以及以上級別的,才能推送消息,前提是你要關注我的服務號
已認證服務號也是要營業執照的,所以測試的話,又要用沙箱環境
沙箱環境地址:https://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=sandbox/login
用自己的微信登錄后,就有appID,appsecret等信息提供給你,並且在下面還提供給測試公眾號供你關注,旁邊的用戶列表就是每個用戶微信id 唯一標識,微信在推送消息時,就通過這個唯一標識 來決定發送給哪個用戶,當你掃了這個二維碼點關注后,用戶列表里就會多一條你的微信ID
在開發業務場景下,我們希望微信ID能夠在用戶關注公眾號后能夠自動寫入到我們程序的數據庫中,而不是手動去微信公眾平台復制,或者爬蟲去爬,首先這樣低效,並且你怎么確定哪個微信iD和你當前的用戶有對應的關系?
微信提供了這么一個接口,你只要傳入appid,跳轉url(回調地址),用戶信息,就會幫生成一個url,前端用這個可以用生成二維碼,供用戶掃,掃完后就可以獲取到用戶微信id,並帶上傳入的用戶信息,跳轉到你指定的那個url,這個過程也是要公網IP的
- 關注服務號
- 根據用戶生成 綁定個人賬戶二維碼
- 用戶掃碼,觸發回調地址
- 回調函數里,獲取用戶微信ID並更新
注:要在微信公眾平台下修改 網頁帳號-網頁授權獲取用戶基本信息,修改回調地址域名
前端貼上公眾號關注照片和生成 綁定個人賬戶二維碼
{% 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/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>
<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);
$('#qrcode').empty().qrcode({text: result.data}); #生成二維碼
}
});
}
</script>
</body>
</html>
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):
@functools.wraps(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:
request.session['user_info'] = {'id': obj.id, 'name': obj.username, 'uid': obj.uid}
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=settings.WECHAT_CONFIG["app_id"], # 'wx89085e915d351cae',
redirect_uri=settings.WECHAT_CONFIG["redirect_uri"], # 'http://47.93.4.198/test/',
state=request.session['user_info']['uid'] # 為當前用戶生成MD5值
)
ret['data'] = access_url
except Exception as e:
ret['code'] = 1001
ret['msg'] = str(e)
return JsonResponse(ret)
def callback(request):
"""
用戶在手機微信上掃碼后,微信自動調用該方法。
用於獲取掃碼用戶的唯一ID,以后用於給他推送消息。
:param request:
:return:
"""
code = request.GET.get("code")
# 用戶md5值
state = request.GET.get("state")
# 獲取該用戶openId(用戶唯一,用於給用戶發送消息)
res = requests.get(
url="https://api.weixin.qq.com/sns/oauth2/access_token",
params={
"appid": 'wx89085e915d351cae',
"secret": '64f87abfc664f1d4f11d0ac98b24c42d',
"code": code,
"grant_type": 'authorization_code',
}
).json()
# 獲取的到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)
-
可以給微信用戶推送消息
發送前,我們還要去微信那邊獲取一個token,加上微信ID,才能給用戶發消息
消息也可以發送自定義消息和模板消息(提供變量占位和樣式控制)
不過模板消息,需要template_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
access_token = get_access_token()
openid = models.UserInfo.objects.get(id=1).wx_id
def send_custom_msg():
body = {
"touser": openid,
"msgtype": "text",
"text": {
"content": '雲姐好美呀'
}
}
response = requests.post(
url="https://api.weixin.qq.com/cgi-bin/message/custom/send",
params={
'access_token': access_token
},
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": '0XbLbuNkn3wPPAYRVXM-MZ0gU0tPvVbsjfc1qoSH6CM',
"data": {
"first": {
"value": "雲姐",
"color": "#173177"
},
"keyword1": {
"value": "美女",
"color": "#173177"
},
}
}
)
result = res.json()
return result
result = send_template_msg()
if result.get('errcode') == 0:
return HttpResponse('發送成功')
return HttpResponse('發送失敗')
