# -*- coding: utf-8 -*-
from .exceptions import WeChatApiError
import requests
import json
import re
try:
from urllib import quote
except ImportError:
from urllib.parse import quote
[docs]class WeChatApi(object):
"""
WeChat Api just do one thing: give params to wechat and get the data what wechat return.
"""
def __init__(self, settings):
self.settings = settings
self.global_access_token = {}
[docs] def get_code_url(self, redirect_url, state):
"""
Get the url which 302 jump back and bring a code.
:param redirect_url: Jump back url
:param state: Jump back state
:return: url
"""
state = quote(state)
redirect_url = quote(self.settings.REGISTER_URL + redirect_url[1:])
url = ('https://open.weixin.qq.com/connect/oauth2/authorize?' +
'appid=%s&redirect_uri=%s' +
'&response_type=code' +
'&scope=snsapi_userinfo' +
'&state=%s#wechat_redirect') % (self.settings.APP_ID, redirect_url, state)
return url
[docs] def get_access_token(self, code):
"""
Use code for get access token, refresh token, openid etc.
:param code: A code see function get_code_url.
:return: Raw data that wechat returns.
"""
data = requests.get('https://api.weixin.qq.com/sns/oauth2/access_token', params={
'appid': self.settings.APP_ID,
'secret': self.settings.APP_SECRET,
'code': code,
'grant_type': 'authorization_code'
}).json()
return data
[docs] def refresh_access_token(self, refresh_token):
"""
Refresh user access token by refresh token.
:param refresh_token: function get_access_token returns.
:return: Raw data that wechat returns.
"""
data = requests.get('https://api.weixin.qq.com/sns/oauth2/refresh_token', params={
'appid': self.settings.APP_ID,
'grant_type': 'refresh_token',
'refresh_token': refresh_token
}).json()
if 'errcode' in data.keys():
return 'error'
return data
[docs] def get_userinfo(self, openid):
"""
Get user info with global access token (content subscribe, language, remark and groupid).
:param openid: User openid.
:return: Raw data that wechat returns.
"""
access_token = self.settings.GET_GLOBAL_ACCESS_TOKEN(self)
data = {
'access_token': access_token,
'openid': openid,
'lang': 'zh_CN'
}
data = requests.get('https://api.weixin.qq.com/cgi-bin/user/info', params=data).json()
if 'errcode' in data.keys():
raise WeChatApiError('errcode: {}, msg: {}'.format(data['errcode'], data['errmsg']))
return data
[docs] def is_access_token_has_expired(sele, openid, access_token):
"""
Determine whether the user access token has expired
:param openid: User openid.
:param access_token: function get_access_token returns.
:return: Raw data that wechat returns.
"""
data = {
'access_token': access_token,
'openid': openid,
}
url = 'https://api.weixin.qq.com/sns/auth'
data = requests.post(url, params=data).json()
return data
[docs] def get_userinfo_by_token(self, openid, access_token):
"""
Get user info with user access token (without subscribe, language, remark and groupid).
:param openid: User openid.
:param access_token: function get_access_token returns.
:return: Raw data that wechat returns.
"""
data = requests.get('https://api.weixin.qq.com/sns/userinfo', params={
'access_token': access_token,
'openid': openid,
'lang': 'zh_CN'
})
data.encoding = 'utf-8'
return data.json()
[docs] def get_global_access_token(self):
"""
Get global access token.
:return: Raw data that wechat returns.
"""
data = requests.get("https://api.weixin.qq.com/cgi-bin/token", params={
'grant_type': 'client_credential',
'appid': self.settings.APP_ID,
'secret': self.settings.APP_SECRET
}).json()
return data
@staticmethod
def _make_xml(k, v=None):
"""
Recursive generate XML
"""
if not v:
v = k
k = 'xml'
if type(v) is dict:
v = ''.join([WeChatApi._make_xml(key, val) for key, val in v.items()])
elif type(v) is list:
length = len(k) + 2
v = ''.join([WeChatApi._make_xml(k, val) for val in v])[length:(length + 1) * -1]
# TODO elif type(v) in [str, unicode]:
else:
return '<%s><![CDATA[%s]]></%s>' % (k, v, k)
return '<%s>%s</%s>' % (k, v, k)
def _analysis_xml(self, xml):
"""
Convert the XML to dict
"""
if not xml:
return {}
if type(xml) is bytes:
xml = xml.decode("utf8")
return {k: v for v,k in re.findall('\<.*?\>\<\!\[CDATA\[(.*?)\]\]\>\<\/(.*?)\>', xml)}
# 统一下单
def unified_order(self, data):
xml = self._make_xml(data).encode('utf-8')
data = requests.post('https://api.mch.weixin.qq.com/pay/unifiedorder', data=xml).content
return self._analysis_xml(data)
# 查询订单
[docs] def query_order(self, data):
"""
Get order query.
:return: Raw data that wechat returns.
"""
xml = self._make_xml(data).encode('utf-8')
data = requests.post('https://api.mch.weixin.qq.com/pay/orderquery', data=xml).content
return self._analysis_xml(data)
# 关闭订单
[docs] def close_order(self, data):
"""
Get close_order info.
:return: Raw data that wechat returns.
"""
xml = self._make_xml(data).encode('utf-8')
data = requests.post('https://api.mch.weixin.qq.com/pay/closeorder', data=xml).content
return self._analysis_xml(data)
# 申请退款
[docs] def refund_order(self, data):
"""
refund.
:return: Raw data that wechat returns.
"""
xml = self._make_xml(data).encode('utf-8')
data = requests.post(
'https://api.mch.weixin.qq.com/secapi/pay/refund',
data=xml,
cert=(self.settings.CERT_PEM_PATH, self.settings.KEY_PEM_PATH)
).content
return self._analysis_xml(data)
# 查询退款
[docs] def query_refund(self, data):
"""
refund query
:return: Raw data that wechat returns.
"""
xml = self._make_xml(data).encode('utf-8')
data = requests.post('https://api.mch.weixin.qq.com/pay/refundquery', data=xml).content
return self._analysis_xml(data)
# 下载对账单
[docs] def download_bill(self, data):
"""
download bill
:return: Raw data that wechat returns.
"""
xml = self._make_xml(data).encode('utf-8')
data = requests.post('https://api.mch.weixin.qq.com/pay/downloadbill', data=xml)
if data.headers['content-type'] == 'text/plain':
return self._analysis_xml(data.content)
return {
'return_code': 'SUCCESS',
'content': data.content
}
# 交易保障
[docs] def pay_report(self, data):
"""
report
:return: Raw data that wechat returns.
"""
xml = self._make_xml(data).encode('utf-8')
data = requests.post('https://api.mch.weixin.qq.com/payitil/report', data=xml).content
return self._analysis_xml(data)
[docs] def create_group(self, name):
"""
Create a user group.
:param name: Group name.
:return: Raw data that wechat returns.
"""
access_token = self.settings.GET_GLOBAL_ACCESS_TOKEN(self)
data = {
'group': {
'name': name
}
}
url = 'https://api.weixin.qq.com/cgi-bin/groups/create?access_token=%s' + access_token
data = requests.post(url, data=json.dumps(data)).json()
return data
[docs] def get_all_groups(self):
"""
Get all user groups.
:return: Raw data that wechat returns.
"""
access_token = self.settings.GET_GLOBAL_ACCESS_TOKEN(self)
url = "https://api.weixin.qq.com/cgi-bin/groups/get?access_token=" + access_token
req = requests.get(url)
return req.json()
[docs] def get_user_groups(self, openid):
"""
Get all a user groups.
:return: Raw data that wechat returns.
"""
access_token = self.settings.GET_GLOBAL_ACCESS_TOKEN(self)
data = {
'openid': openid
}
url = "https://api.weixin.qq.com/cgi-bin/groups/getid?access_token=" + access_token
data = requests.post(url, data=json.dumps(data)).json()
return data
[docs] def change_group_name(self, groupid, name):
"""
Change group name.
:param groupid: Group ID.
:param name: New name.
:return: Raw data that wechat returns.
"""
access_token = self.settings.GET_GLOBAL_ACCESS_TOKEN(self)
data = {
'group': {
'id': groupid,
'name': name
}
}
url = 'https://api.weixin.qq.com/cgi-bin/groups/update?access_token=%s' + access_token
data = requests.post(url, data=json.dumps(data)).json()
return data
[docs] def change_user_group(self, openid, groupid):
"""
Move user to a new group.
:param openid: User openid.
:param groupid: Group ID.
:return: Raw data that wechat returns.
"""
access_token = self.settings.GET_GLOBAL_ACCESS_TOKEN(self)
data = {
'openid': openid,
'to_groupid': groupid
}
url = 'https://api.weixin.qq.com/cgi-bin/groups/members/update?access_token=%s' + access_token
data = requests.post(url, data=json.dumps(data)).json()
return data
[docs] def del_group(self, groupid):
"""
Delete a group.
:param groupid: Group id.
:return: Raw data that wechat returns.
"""
access_token = self.settings.GET_GLOBAL_ACCESS_TOKEN(self)
data = {
'group': {
'id': groupid
}
}
url = 'https://api.weixin.qq.com/cgi-bin/groups/delete?access_token=%s' + access_token
data = requests.post(url, data=json.dumps(data)).json()
return data
def add_temporary_material(self, **kwargs):
access_token = self.settings.GET_GLOBAL_ACCESS_TOKEN(self)
url = 'https://api.weixin.qq.com/cgi-bin/media/upload?access_token=%s&type=%s' % (access_token, kwargs['type'])
data = requests.post(url, files={'media': kwargs['media']}).json()
return data
def get_temporary_material(self, media_id):
access_token = self.settings.GET_GLOBAL_ACCESS_TOKEN(self)
url = 'https://api.weixin.qq.com/cgi-bin/media/get?access_token={access_token}&media_id={media_id}'.format(
access_token=access_token,
media_id=media_id
)
try:
data = requests.get(url).content
except:
data = None
return data
def add_permanent_material(self, articles):
access_token = self.settings.GET_GLOBAL_ACCESS_TOKEN(self)
url = 'https://api.weixin.qq.com/cgi-bin/material/add_news?access_token=%s' % access_token
data = {'articles': articles}
data = requests.post(url, data=json.dumps(data)).json()
return data
def upload_content_picture(self, media):
access_token = self.settings.GET_GLOBAL_ACCESS_TOKEN(self)
url = 'https://api.weixin.qq.com/cgi-bin/media/uploadimg?access_token=%s' % access_token
data = requests.post(url, files={'media': media}).json()
return data
def add_other_material(self, **kwargs):
access_token = self.settings.GET_GLOBAL_ACCESS_TOKEN(self)
if 'title' in kwargs and 'introduction' in kwargs:
data = {
'type': kwargs['type'],
'description': {
'title': kwargs['title'],
'introduction': kwargs['introduction']
}
}
else:
data = {'type': kwargs['type']}
url = 'https://api.weixin.qq.com/cgi-bin/material/add_material?access_token=%s' % access_token
data = requests.post(url, data=data, files={'media': kwargs['media']}).json()
return data
def get_permanent_material(self, media_id):
access_token = self.settings.GET_GLOBAL_ACCESS_TOKEN(self)
data = {"media_id": media_id}
url = 'https://api.weixin.qq.com/cgi-bin/material/get_material?access_token=%s' % access_token
data = requests.post(url, data=json.dumps(data)).json()
return data
def delete_material(self, media_id):
access_token = self.settings.GET_GLOBAL_ACCESS_TOKEN(self)
data = {"media_id": media_id}
url = 'https://api.weixin.qq.com/cgi-bin/material/del_material?access_token=%s' % access_token
data = requests.post(url, data=json.dumps(data)).json()
return data
def update_material(self, **kwargs):
access_token = self.settings.GET_GLOBAL_ACCESS_TOKEN(self)
data = {
'media_id': kwargs['media_id'],
'index': kwargs['index'],
'articles': {
'title': kwargs['title'],
'thumb_media_id': kwargs['thumb_media_id'],
'author': kwargs['author'],
'digest': kwargs['digest'],
'show_cover_pic': kwargs['show_cover_pic'],
'content': kwargs['content'],
'content_source_url': kwargs['content_source_url']
}
}
url = 'https://api.weixin.qq.com/cgi-bin/material/update_news?access_token=%s' % access_token
data = requests.post(url, data=json.dumps(data)).json()
return data
def get_materials_count(self):
access_token = self.settings.GET_GLOBAL_ACCESS_TOKEN(self)
url = 'https://api.weixin.qq.com/cgi-bin/material/get_materialcount?access_token=%s' % access_token
data = requests.get(url).json()
return data
def get_materials_list(self, material_type, offset, count):
access_token = self.settings.GET_GLOBAL_ACCESS_TOKEN(self)
data = {
"type": material_type,
"offset": offset,
"count": count
}
url = 'https://api.weixin.qq.com/cgi-bin/material/batchget_material?access_token=%s' % access_token
data = requests.post(url, data=json.dumps(data)).json()
return data
def create_scene_qrcode(self, scene_id, expire):
access_token = self.settings.GET_GLOBAL_ACCESS_TOKEN(self)
data = {
'expire_seconds': expire,
'action_name': 'QR_SCENE',
'action_info': {
'scene': {
'scene_id': scene_id
}
}
}
url = 'https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=%s' + access_token
data = requests.post(url, data=json.dumps(data)).json()
return data
def create_limit_scene_qrcode(self, scene_id):
access_token = self.settings.GET_GLOBAL_ACCESS_TOKEN(self)
data = {
'action_name': 'QR_LIMIT_SCENE',
'action_info': {
'scene': {
'scene_id': scene_id
}
}
}
url = 'https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=%s' + access_token
data = requests.post(url, data=json.dumps(data)).json()
return data
def create_limit_str_scene_qrcode(self, scene_str):
access_token = self.settings.GET_GLOBAL_ACCESS_TOKEN(self)
data = {
'action_name': 'QR_LIMIT_SCENE',
'action_info': {
'scene': {
'scene_str': scene_str
}
}
}
url = 'https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=%s' + access_token
data = requests.post(url, data=json.dumps(data)).json()
return data
def create_short_url(self, url):
access_token = self.settings.GET_GLOBAL_ACCESS_TOKEN(self)
data = {
'action': 'long2short',
'long_url': url
}
url = 'https://api.weixin.qq.com/cgi-bin/shorturl?access_token=%s' + access_token
data = requests.post(url, data=json.dumps(data)).json()
return data
[docs] def get_wechat_servers_list(self):
"""
Get wechat servers list
:param data:
:return: Raw data that wechat returns.
"""
access_token = self.settings.GET_GLOBAL_ACCESS_TOKEN(self)
url = "https://api.weixin.qq.com/cgi-bin/getcallbackip?access_token=" + access_token
data = requests.post(url).json()
return data
[docs] def get_variation_number_of_user(self, begin_date, end_date):
"""
Get variation in number od user
:param data:begin_date, end_date
:return:Raw data that wechat returns.
"""
data = {
"begin_date": begin_date,
"end_date": end_date
}
access_token = self.settings.GET_GLOBAL_ACCESS_TOKEN(self)
url = "https://api.weixin.qq.com/datacube/getusersummary?access_token=" + access_token
data = requests.post(url, data=json.dumps(data)).json()
return data
[docs] def get_user_cumulate(self, begin_date, end_date):
"""
GET accumulation of user
:param date:begin_date, end_date
:return:Raw data that wechat returns.
"""
data = {
"begin_date": begin_date,
"end_date": end_date
}
access_token = self.settings.GET_GLOBAL_ACCESS_TOKEN(self)
url = "https://api.weixin.qq.com/datacube/getusercumulate?access_token=" + access_token
data = requests.post(url, data=json.dumps(data)).json()
return data
[docs] def get_article_summary(self, begin_date, end_date):
"""
Get article summary
:param data:begin_date, end_date
:return :Raw data that wechat returns.
"""
data = {
"begin_date": begin_date,
"end_date": end_date
}
access_token = self.settings.GET_GLOBAL_ACCESS_TOKEN(self)
url = "https://api.weixin.qq.com/datacube/getarticlesummary?access_token=" + access_token
data = requests.post(url, data=json.dumps(data)).json()
return data
[docs] def get_article_total(self, begin_date, end_date):
"""
Get article total
:param data:begin_date, end_date
:return :Raw data that wechat returns.
"""
data = {
"begin_date": begin_date,
"end_date": end_date
}
access_token = self.settings.GET_GLOBAL_ACCESS_TOKEN(self)
url = "https://api.weixin.qq.com/datacube/getarticletotal?access_token=" + access_token
data = requests.post(url, data=json.dumps(data)).json()
return data
[docs] def get_user_read(self, begin_date, end_date):
"""
Get user read
:param data:begin_date, end_date
:return :Raw data that wechat returns.
"""
data = {
"begin_date": begin_date,
"end_date": end_date
}
access_token = self.settings.GET_GLOBAL_ACCESS_TOKEN(self)
url = "https://api.weixin.qq.com/datacube/getuserread?access_token=" + access_token
data = requests.post(url, data=json.dumps(data)).json()
return data
[docs] def get_user_read_hour(self, begin_date, end_date):
"""
Get user read hour
param data:begin_date, end_date
return :Raw data that wechat return.
"""
data = {
"begin_date": begin_date,
"end_date": end_date
}
access_token = self.settings.GET_GLOBAL_ACCESS_TOKEN(self)
url = "https://api.weixin.qq.com/datacube/getuserreadhour?access_token=" + access_token
data = requests.post(url, data=json.dumps(data)).json()
return data
[docs] def get_user_share(self, begin_date, end_date):
"""
Get user share
param data:begin_data,end_date
return :Raw data that wechat return.
"""
data = {
"begin_date": begin_date,
"end_date": end_date
}
access_token = self.settings.GET_GLOBAL_ACCESS_TOKEN(self)
url = "https://api.weixin.qq.com/datacube/getusershare?access_token=" + access_token
data = requests.post(url, data=json.dumps(data)).json()
return data
[docs] def get_user_share_hour(self, begin_date, end_date):
"""
Get user share
param data:begin_date, end_date
retur :Raw data that wechat return.
"""
data = {
"begin_date": begin_date,
"end_date": end_date
}
access_token = self.settings.GET_GLOBAL_ACCESS_TOKEN(self)
url = "https://api.weixin.qq.com/datacube/getusersharehour?access_token=" + access_token
data = requests.post(url, data=json.dumps(data)).json()
return data
# TODO 更方便定制
[docs]def get_global_access_token(self):
"""
获取全局 access token
"""
def create_group(self, name):
"""
Create a user group.
:param name: Group name.
:return: Raw data that wechat returns.
"""
access_token = self.settings.GET_GLOBAL_ACCESS_TOKEN(self)
data = {
'group': {
'name': name
}
}
url = 'https://api.weixin.qq.com/cgi-bin/groups/create?access_token=%s' + access_token
data = requests.post(url, data=json.dumps(data)).json()
return data