微信服务号的消息推送
- 公众号
- 已认证公众号
- 服务号
- 已认证服务号
- 企业号
基于:微信认证服务号 主动推送微信消息。
前提:关注服务号
环境:沙箱环境
微信公号平台:
https://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=sandbox/login
实例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 | 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | {% 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 > |
Django中的
settings.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | # ############# 微信 ############## 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 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | 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文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 | 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) |
支付的代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 | 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | <! 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
1 2 3 4 5 6 | # 支付相关配置 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
1 2 3 | - - - - - 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
1 2 3 | - - - - - 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 - - - - - |
支付宝支付的总结:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | a. 去支付宝申请 - 正式:营业执照 - 测试:沙箱测试环境 APPID: 2016082500309412 买家: nbjsag5718@sandbox.com 111111 111111 b. 开发程序 SDK加密方式 - 官方 - github pay.py 依赖:pip3 install pycryptodome 公钥私钥: - 应用公钥 - 支付宝公钥 - 应用私钥 |
1 2 3 | - 支付成功后,断电宕机 要给支付宝返回一个success。这样支付宝才认为支付成功了。 不然会在一天内持续的给你发送post请求。 - 支付成功: return HttpResponse( 'success' ) |
【推荐】还在用 ECharts 开发大屏?试试这款永久免费的开源 BI 工具!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步