微信消息推送和支付宝支付

微信服务号的消息推送

- 公众号
- 已认证公众号
- 服务号
- 已认证服务号
- 企业号

基于:微信认证服务号动推送微信消息。
前提:关注服务号
环境:沙箱环境

微信公号平台:

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')

  

  

原文地址:https://www.cnblogs.com/zenghui-python/p/11704373.html