微信服务号的消息推送
- 公众号
- 已认证公众号
- 服务号
- 已认证服务号
- 企业号
基于:微信认证服务号 主动推送微信消息。
前提:关注服务号
环境:沙箱环境
微信公号平台:
https://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=sandbox/login
实例:
import json import functools import requests from django.conf import settings from django.shortcuts import render, redirect, HttpResponse from django_redis import get_redis_connection 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): """ 生成二维码 (本质上就是一个url) 扫描二维码,就是向这个url发送请求 :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/', # 回调地址;就是访问这个access_url后,微信会向 redirect_uri 发送请求(数据里面包含了用户的微信号,以及给用户生成的MD5值) 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(openid),以后用于给他推送消息。 :param request: :return: """ code = request.GET.get("code") print(code) # 用户md5值 state = request.GET.get("state") print(state) # 获取该用户openId(用户唯一,用于给用户发送消息) res = requests.get( url="https://api.weixin.qq.com/sns/oauth2/access_token", params={ "appid": settings.WECHAT_CONFIG["app_id"], "secret": settings.WECHAT_CONFIG["appsecret"], "code": code, "grant_type": 'authorization_code', } ).json() # 获取的到openid表示用户授权成功 openid = res.get("openid") print(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): conn = get_redis_connection('default') 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() #在向微信发送信息的时候,需要先找微信拿一下token值,然后发送信息的时候,需要带上这个token值。 if result.get("access_token"): access_token = result.get('access_token') else: access_token = None return access_token # token值 有效的时间为2小时,所以不用每次都请求。可以存放到redis(缓存中。) access_token=conn.get("token") if not access_token: print("not redis") access_token = get_access_token() conn.set("token",access_token,ex=60*60*2) print(access_token) id = request.session['user_info']['id'] print(id) openid = models.UserInfo.objects.get(id=id).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": 'DyfhSDx0oL-FomFwAbfATFnpV1DihaJBdl2NT4Lzzys', "data": { "student": { "value": "云姐", "color": "#173177" }, "homwork": { "value": "Django项目", "color": "#173177" }, } } ) result = res.json() return result result = send_template_msg() if result.get('errcode') == 0: return HttpResponse('发送成功') return HttpResponse('发送失败')
bind.html
{% load staticfiles %} <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <div style=" 600px;margin: 0 auto"> <h1>请关注路飞学城服务号,并绑定个人用户(用于以后的消息提醒)</h1> <div> <h3>第一步:关注路飞学城微信服务号</h3> <img style="height: 100px; 100px" src="{% static "img/luffy.jpeg" %}"> </div> <input type="button" value="下一步【获取绑定二维码】" onclick="getBindUserQcode()"> <div> <h3>第二步:绑定个人账户</h3> <div id="qrcode" style=" 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>
Django中的
settings.py
# ############# 微信 ############## WECHAT_CONFIG = { 'app_id': 'wx4f7020c38e5578bb', 'appsecret': 'd2f034a0b076156ab2007b99fcf32187', 'redirect_uri': 'http://47.98.134.86/callback/',#授权回调页面域名 没有服务器 } # redis配置 CACHES = { "default": { "BACKEND": "django_redis.cache.RedisCache", "LOCATION": "redis://127.0.0.1:6379", "OPTIONS": { "CLIENT_CLASS": "django_redis.client.DefaultClient", "CONNECTION_POOL_KWARGS": {"max_connections": 100}, "PASSWORD": "zh4350697", } } }
总结:
1. 注册账号 appID:wx89085e915d351cae appsecret:64f87abfc664f1d4f11d0ac98b24c42d 网页授权获取用户基本信息:47.98.134.86 或 域名 2. 关注公众号(已认证的服务号) 3. 生成二维码,用户扫描; 将用户信息发送给微信,微信再将数据发送给设置redirect_uri地址(md5值) 4. 回调地址:47.98.134.86/callback/ - 授权 (获取wx_id 就是授权成功了) - 用户md5 - 获取wx_id 在数据库中更新设置:wx_id 5. 发送消息(模板消息) - wx_id - access_token(2小时有效期)
支付宝支付
支付宝的沙箱环境
https://openhome.alipay.com/platform/home.htm
支付宝的官网的支付接口
utils文件夹下的pay.py文件
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(" ", "") 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)
支付的代码
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 from alipay import models def aliPay(): obj = AliPay( appid=settings.APPID, #支付宝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() # 对购买的数据进行加密 title = request.POST.get('shop') money = float(request.POST.get('price')) out_trade_no = "x2" + str(time.time()) # 1. 在数据库创建一条数据:状态(待支付) title = title order_num = out_trade_no models.Order.objects.create(title=title, order_num=order_num) query_params = alipay.direct_pay( subject=title, # 商品简单描述 out_trade_no=out_trade_no, # 商户订单号 total_amount=money, # 交易金额(单位: 元 保留俩位小数) ) pay_url = "https://openapi.alipaydev.com/gateway.do?{}".format(query_params) return redirect(pay_url) 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: # 原则上是在下面的接口进行修改订单的操作,但是没有服务器,就无法让支付宝发送POST请求,等到公司后就可以了 # 因此现在在这里进行修改订单的操作,方便测试. order_num = params.get("out_trade_no") # 订单号 # 支付成功后修改订单的状态 print(order_num) models.Order.objects.update_or_create(order_num=order_num, defaults={"status": 1}) # order_obj = models.Order.objects.filter(order_num=order_num).first() # order_obj.status = 1 return HttpResponse('支付成功') return HttpResponse('支付失败') # 这个接口需要挂载到服务器上,让支付宝找到这个端口,这样才能发post请求 @csrf_exempt def update_order(request): """ 支付成功后,支付宝向该地址发送的POST请求(用于修改订单状态) :param request: :return: """ # 发送过来的一定是POST请求 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('success') else: return HttpResponse('支付失败') return HttpResponse('')
index.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> <form method="post"> {% csrf_token %} 购买的商品 <input type="text" name="shop"> <input type="text" name="price" placeholder="请输入要支付的金额"> <input type="submit" value="支付"> </form> </body> </html>
settings.py
# 支付相关配置 APPID = "2016101000655824" NOTIFY_URL = "http://127.0.0.1:8000/update_order/" RETURN_URL = "http://127.0.0.1:8000/pay_result/" PRI_KEY_PATH = "keys/app_private_2048.txt" PUB_KEY_PATH = "keys/alipay_public_2048.txt"
keys文件夹下的
alipay_public_2048.txt
-----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAozu/MQKIOeevh6HaYPaSTZ/dkFJQUTEi3jFiigx2ClYvivNbAlKwzdtcvBGr9SyV3oThU8mGkajv91Gk/c06iBtu968MWouygAOGamyRAUWhXjDSginM2R+WGSgaxUKrNOWqfh3IBe1YHA2N/ixaGy/Y1z/Dbl+mdBQPid0aH2sR9gvGYf4WxnUtqQu8An6zS0CGkPyKrlHyRy6nasKvWSfbXd6QgN4FHct7J4m6GbE4qIoUcyXTcUx23YJX/R+q7M0gzwQaBA5jQn7o8QUlfiCW2zN4fTU9+sutHuOEips3bzBdlcNt4Ndp5Skcv6AJcsJ+C4Md/3KwQ77urFj+nQIDAQAB -----END PUBLIC KEY-----
app_private_2048.txt
-----BEGIN RSA PRIVATE KEY----- MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCx92fMNvfhnJJogPyLuKZFyPPggGhwQLAE9SjYNTAJ8+Tbxh6VqztRt8hSmkWCa1Al01qncncUY3zmbucmpehVd9RwM7/rUhbaoMn6YC7+9WlfUj+k6McT1zIqG8MrkB9WbKQ06hCiJGz9DuuWlh+spklsq7RKopP7a4/2VuLF2XN+i6srvrczX6BoSA4fpOvHkOz/abHIBKZI8SUVgq2fMQc2h0lZFYjOXNrnFjBrckA4FRnTzeH7hvW4MDR9dRqxa0p3vx7gdRgQyBdmlFYf7QUFRDCizIS3UBsUbZWr8FflpVSTMEfL+A4aNaElRua605YOTTkqD7HXxBojKtmbAgMBAAECggEAXlx9B9W5d0Gqi4ig34CngOb9EHNlbOePrQFEh+cjspNELJeOzfL9v+V/bPTpmC5IT9YSAij6JLBfoFr7aw2a8/5WgKufvilHfuK0VXI8WOlM1sLohgr6y3VV3ufbgzSmuQ9fvcLB0QcZVtBbb/vnjYaZ9enQ7aXoau3sZkRJd3djcYZYwY8yhOMD9cigm+pr2st2BnLWubydtaTW8ogbwY7ctNWIhq3gDeauWdFFIJmnVpOutJIcvGeo+OgIB06tfYzHvZAMftBiHwkjPH33bWHy0v9IjF9ArIcvrYjYds/nK3e3vMJma8Vp8BRCk4Z/rXDZGK8Pw3RGl+fq29pfGQKBgQDeQMzU+qwXvG3p6g6CvsndWG0e2taYvbK0i5SAspSC2i/9aq85+jt8jqPOK90t0trPygD+wnahiOOVZwSQYT2C7JsvvguXxTDLki/t1j+BKYK8kgmV2SlWMqSpkc5tez9Iq2RzeMZpi+dQWi4xWplw8XXu0E0o2CE1z19HsX6ERQKBgQDM/SMq2vV2mT3WoyT63GbsPeNq/zHhSgGeCFcicUYRxJvqTdem9k8zpH4D4Te8bNrGbLco+3vi3Ad+GwPHLr2Bpqone5NvqHGbaHJpRwFEasiNFwdVNS8gjULDMuuc6tdeXqmdujSQ0/RDF2En7zXnrsREE6zMGqX/w00dy+70XwKBgBHIpZs1I6gSj8jzzY1wrr5jYPfjEuDN7Qq9UHir0W5W/xgL/VFqUHA7Cahpoh0UjiWqSEIaVVu/lFZUE+1pmn5raE99qXfPc4QWgndJeXNgWvGzzciLw9791mcrH5VrEzlBXZxPwbCYXT30uVWBpl1/NKyTRllKUf34Ret6rGDxAoGBAMr5eGIN73Ig6M9oOczAgpU37sDasgxPGGzf+0+ac/RSBsSpkXi8ec47+Z9j2amU68gAjBhjc9c9YZnnrAUFbhY77k4sGeA9HUjx0iAWc9XIGo9CFzuy7tg/p7Ta7dwx2VGTUEZiw3wIs9vfAY/mWCzxq1txU+/CD07CltCDRzfnAoGAWmUTu3YC1cPcZk4B5S6d6scCsQMrGAdwH5HFI7uCZpwx1HcVLTpEyfRgUGL1Buyc9/sq54pxBEVVprhDK0ja72wo9tIlxXUusqkoNCqSFZcq7dMMz2eu7zZUFrl2qjqv3CJAstLV6XIWicJK1X9Lnq1cyB1m+7/VLcC+41RX1F4= -----END RSA PRIVATE KEY-----
支付宝支付的总结:
a. 去支付宝申请 - 正式:营业执照 - 测试:沙箱测试环境 APPID:2016082500309412 买家: nbjsag5718@sandbox.com 111111 111111 b. 开发程序 SDK加密方式 - 官方 - github pay.py 依赖:pip3 install pycryptodome 公钥私钥: - 应用公钥 - 支付宝公钥 - 应用私钥
- 支付成功后,断电宕机 要给支付宝返回一个success。这样支付宝才认为支付成功了。 不然会在一天内持续的给你发送post请求。 - 支付成功:return HttpResponse('success')