优化工单模块,增加企微模块

This commit is contained in:
mgw
2024-07-10 15:58:47 +08:00
parent e8512b23e4
commit 6b140fe6dd
134 changed files with 12830 additions and 2 deletions

View File

@@ -0,0 +1,199 @@
# -*- coding: utf-8 -*-
from __future__ import absolute_import, unicode_literals
import sys
import inspect
import logging
import requests
import xmltodict
from xml.parsers.expat import ExpatError
from optionaldict import optionaldict
from wechatpy.utils import random_string
from wechatpy.exceptions import WeChatPayException, InvalidSignatureException
from wechatpy.pay.utils import (
calculate_signature, _check_signature, dict_to_xml
)
from wechatpy.pay.base import BaseWeChatPayAPI
from wechatpy.pay import api
logger = logging.getLogger(__name__)
def _is_api_endpoint(obj):
return isinstance(obj, BaseWeChatPayAPI)
class WeChatPay(object):
"""
微信红包接口
:param appid: 微信公众号 appid
:param api_key: 商户 key
:param mch_id: 商户号
:param sub_mch_id: 可选,子商户号,受理模式下必填
:param mch_cert: 必填,商户证书路径
:param mch_key: 必填,商户证书私钥路径
"""
redpack = api.WeChatRedpack()
"""红包接口"""
transfer = api.WeChatTransfer()
"""企业付款接口"""
coupon = api.WeChatCoupon()
"""代金券接口"""
order = api.WeChatOrder()
"""订单接口"""
refund = api.WeChatRefund()
"""退款接口"""
micropay = api.WeChatMicroPay()
"""刷卡支付接口"""
tools = api.WeChatTools()
"""工具类接口"""
jsapi = api.WeChatJSAPI()
API_BASE_URL = 'https://api.mch.weixin.qq.com/'
def __new__(cls, *args, **kwargs):
self = super(WeChatPay, cls).__new__(cls)
if sys.version_info[:2] == (2, 6):
import copy
# Python 2.6 inspect.gemembers bug workaround
# http://bugs.python.org/issue1785
for name, _api in self.__class__.__dict__.items():
if isinstance(_api, BaseWeChatPayAPI):
_api = copy.deepcopy(_api)
_api._client = self
setattr(self, name, _api)
else:
api_endpoints = inspect.getmembers(self, _is_api_endpoint)
for name, _api in api_endpoints:
api_cls = type(_api)
_api = api_cls(self)
setattr(self, name, _api)
return self
def __init__(self, appid, api_key, mch_id, sub_mch_id=None,
mch_cert=None, mch_key=None):
"""
:param appid: 微信公众号 appid
:param api_key: 商户 key
:param mch_id: 商户号
:param sub_mch_id: 可选,子商户号,受理模式下必填
:param mch_cert: 商户证书路径
:param mch_key: 商户证书私钥路径
"""
self.appid = appid
self.api_key = api_key
self.mch_id = mch_id
self.sub_mch_id = sub_mch_id
self.mch_cert = mch_cert
self.mch_key = mch_key
def _request(self, method, url_or_endpoint, **kwargs):
if not url_or_endpoint.startswith(('http://', 'https://')):
api_base_url = kwargs.pop('api_base_url', self.API_BASE_URL)
url = '{base}{endpoint}'.format(
base=api_base_url,
endpoint=url_or_endpoint
)
else:
url = url_or_endpoint
if isinstance(kwargs.get('data', ''), dict):
data = optionaldict(kwargs['data'])
if 'mchid' not in data:
# Fuck Tencent
data.setdefault('mch_id', self.mch_id)
data.setdefault('sub_mch_id', self.sub_mch_id)
data.setdefault('nonce_str', random_string(32))
sign = calculate_signature(data, self.api_key)
body = dict_to_xml(data, sign)
body = body.encode('utf-8')
kwargs['data'] = body
# 商户证书
if self.mch_cert and self.mch_key:
kwargs['cert'] = (self.mch_cert, self.mch_key)
res = requests.request(
method=method,
url=url,
**kwargs
)
try:
res.raise_for_status()
except requests.RequestException as reqe:
raise WeChatPayException(
return_code=None,
client=self,
request=reqe.request,
response=reqe.response
)
return self._handle_result(res)
def _handle_result(self, res):
res.encoding = 'utf-8'
xml = res.text
try:
data = xmltodict.parse(xml)['xml']
except (xmltodict.ParsingInterrupted, ExpatError):
# 解析 XML 失败
logger.debug('WeChat payment result xml parsing error', exc_info=True)
return xml
return_code = data['return_code']
return_msg = data.get('return_msg')
result_code = data.get('result_code')
errcode = data.get('err_code')
errmsg = data.get('err_code_des')
if return_code != 'SUCCESS' or result_code != 'SUCCESS':
# 返回状态码不为成功
raise WeChatPayException(
return_code,
result_code,
return_msg,
errcode,
errmsg,
client=self,
request=res.request,
response=res
)
return data
def get(self, url, **kwargs):
return self._request(
method='get',
url_or_endpoint=url,
**kwargs
)
def post(self, url, **kwargs):
return self._request(
method='post',
url_or_endpoint=url,
**kwargs
)
def check_signature(self, params):
return _check_signature(params, self.api_key)
def parse_payment_result(self, xml):
"""解析微信支付结果通知"""
try:
data = xmltodict.parse(xml)
except (xmltodict.ParsingInterrupted, ExpatError):
raise InvalidSignatureException()
if not data or 'xml' not in data:
raise InvalidSignatureException()
data = data['xml']
sign = data.pop('sign', None)
real_sign = calculate_signature(data, self.api_key)
if sign != real_sign:
raise InvalidSignatureException()
data['sign'] = sign
return data

View File

@@ -0,0 +1,10 @@
# -*- coding: utf-8 -*-
from __future__ import absolute_import, unicode_literals
from wechatpy.pay.api.redpack import WeChatRedpack # NOQA
from wechatpy.pay.api.transfer import WeChatTransfer # NOQA
from wechatpy.pay.api.coupon import WeChatCoupon # NOQA
from wechatpy.pay.api.order import WeChatOrder # NOQA
from wechatpy.pay.api.refund import WeChatRefund # NOQA
from wechatpy.pay.api.tools import WeChatTools # NOQA
from wechatpy.pay.api.jsapi import WeChatJSAPI # NOQA
from wechatpy.pay.api.micropay import WeChatMicroPay # NOQA

View File

@@ -0,0 +1,82 @@
# -*- coding: utf-8 -*-
from __future__ import absolute_import, unicode_literals
import random
from datetime import datetime
from wechatpy.pay.base import BaseWeChatPayAPI
class WeChatCoupon(BaseWeChatPayAPI):
def send(self, user_id, stock_id, op_user_id=None, device_info=None,
out_trade_no=None):
"""
发放代金券
:param user_id: 用户在公众号下的 openid
:param stock_id: 代金券批次 ID
:param op_user_id: 可选,操作员账号,默认为商户号
:param device_info: 可选,微信支付分配的终端设备号
:param out_trade_no: 可选,商户订单号,需保持唯一性,默认自动生成
:return: 返回的结果信息
"""
if not out_trade_no:
now = datetime.now()
out_trade_no = '{0}{1}{2}'.format(
self.mch_id,
now.strftime('%Y%m%d%H%M%S'),
random.randint(1000, 10000)
)
data = {
'appid': self.appid,
'coupon_stock_id': stock_id,
'openid': user_id,
'openid_count': 1,
'partner_trade_no': out_trade_no,
'op_user_id': op_user_id,
'device_info': device_info,
'version': '1.0',
'type': 'XML',
}
return self._post('mmpaymkttransfers/send_coupon', data=data)
def query_stock(self, stock_id, op_user_id=None, device_info=None):
"""
查询代金券批次
:param stock_id: 代金券批次 ID
:param op_user_id: 可选,操作员账号,默认为商户号
:param device_info: 可选,微信支付分配的终端设备号
:return: 返回的结果信息
"""
data = {
'appid': self.appid,
'coupon_stock_id': stock_id,
'op_user_id': op_user_id,
'device_info': device_info,
'version': '1.0',
'type': 'XML',
}
return self._post('mmpaymkttransfers/query_coupon_stock', data=data)
def query_coupon(self, coupon_id, user_id,
op_user_id=None, device_info=None):
"""
查询代金券信息
:param coupon_id: 代金券 ID
:param user_id: 用户在公众号下的 openid
:param op_user_id: 可选,操作员账号,默认为商户号
:param device_info: 可选,微信支付分配的终端设备号
:return: 返回的结果信息
"""
data = {
'coupon_id': coupon_id,
'openid': user_id,
'appid': self.appid,
'op_user_id': op_user_id,
'device_info': device_info,
'version': '1.0',
'type': 'XML',
}
return self._post('promotion/query_coupon', data=data)

View File

@@ -0,0 +1,48 @@
# -*- coding: utf-8 -*-
from __future__ import absolute_import, unicode_literals
import time
from wechatpy.utils import random_string, to_text
from wechatpy.pay.base import BaseWeChatPayAPI
from wechatpy.pay.utils import calculate_signature
class WeChatJSAPI(BaseWeChatPayAPI):
def get_jsapi_signature(self, prepay_id, timestamp=None, nonce_str=None):
"""
获取 JSAPI 签名
:param prepay_id: 统一下单接口返回的 prepay_id 参数值
:param timestamp: 可选,时间戳,默认为当前时间戳
:param nonce_str: 可选,随机字符串,默认自动生成
:return: 签名
"""
data = {
'appId': self.appid,
'timeStamp': timestamp or to_text(int(time.time())),
'nonceStr': nonce_str or random_string(32),
'signType': 'MD5',
'package': 'prepay_id={0}'.format(prepay_id),
}
return calculate_signature(data, self._client.api_key)
def get_jsapi_params(self, prepay_id, timestamp=None, nonce_str=None):
"""
获取 JSAPI 参数
:param prepay_id: 统一下单接口返回的 prepay_id 参数值
:param timestamp: 可选,时间戳,默认为当前时间戳
:param nonce_str: 可选,随机字符串,默认自动生成
:return: 签名
"""
data = {
'appId': self.appid,
'timeStamp': timestamp or to_text(int(time.time())),
'nonceStr': nonce_str or random_string(32),
'signType': 'MD5',
'package': 'prepay_id={0}'.format(prepay_id),
}
sign = calculate_signature(data, self._client.api_key)
data['paySign'] = sign
return data

View File

@@ -0,0 +1,64 @@
# -*- coding: utf-8 -*-
from __future__ import absolute_import, unicode_literals
import random
from datetime import datetime
from wechatpy.pay.utils import get_external_ip
from wechatpy.pay.base import BaseWeChatPayAPI
class WeChatMicroPay(BaseWeChatPayAPI):
def create(self, body, total_fee, auth_code, client_ip=None, out_trade_no=None, detail=None, attach=None,
fee_type='CNY', goods_tag=None, device_info=None, limit_pay=None):
"""
刷卡支付接口
:param device_info: 可选,终端设备号(商户自定义,如门店编号)
:param body: 商品描述
:param detail: 可选,商品详情
:param attach: 可选附加数据在查询API和支付通知中原样返回该字段主要用于商户携带订单的自定义数据
:param client_ip: 可选APP和网页支付提交用户端ipNative支付填调用微信支付API的机器IP
:param out_trade_no: 可选,商户订单号,默认自动生成
:param total_fee: 总金额,单位分
:param fee_type: 可选符合ISO 4217标准的三位字母代码默认人民币CNY
:param goods_tag: 可选,商品标记,代金券或立减优惠功能的参数
:param limit_pay: 可选指定支付方式no_credit--指定不能使用信用卡支付
:param auth_code: 授权码,扫码支付授权码,设备读取用户微信中的条码或者二维码信息
:return: 返回的结果数据
"""
now = datetime.now()
if not out_trade_no:
out_trade_no = '{0}{1}{2}'.format(
self.mch_id,
now.strftime('%Y%m%d%H%M%S'),
random.randint(1000, 10000)
)
data = {
'appid': self.appid,
'device_info': device_info,
'body': body,
'detail': detail,
'attach': attach,
'out_trade_no': out_trade_no,
'total_fee': total_fee,
'fee_type': fee_type,
'spbill_create_ip': client_ip or get_external_ip(),
'goods_tag': goods_tag,
'limit_pay': limit_pay,
'auth_code': auth_code,
}
return self._post('pay/micropay', data=data)
def query(self, transaction_id=None, out_trade_no=None):
"""
查询订单
:param transaction_id: 微信的订单号,优先使用
:param out_trade_no: 商户系统内部的订单号当没提供transaction_id时需要传这个。
:return: 返回的结果数据
"""
data = {
'appid': self.appid,
'transaction_id': transaction_id,
'out_trade_no': out_trade_no,
}
return self._post('pay/orderquery', data=data)

View File

@@ -0,0 +1,138 @@
# -*- coding: utf-8 -*-
from __future__ import absolute_import, unicode_literals
import time
import random
from datetime import datetime, timedelta
from wechatpy.utils import timezone
from wechatpy.pay.utils import get_external_ip
from wechatpy.pay.base import BaseWeChatPayAPI
from wechatpy.utils import random_string, to_text
from wechatpy.pay.utils import calculate_signature
class WeChatOrder(BaseWeChatPayAPI):
def create(self, trade_type, body, total_fee, notify_url, client_ip=None,
user_id=None, out_trade_no=None, detail=None, attach=None,
fee_type='CNY', time_start=None, time_expire=None,
goods_tag=None, product_id=None, device_info=None, limit_pay=None):
"""
统一下单接口
:param trade_type: 交易类型取值如下JSAPINATIVEAPPWAP
:param body: 商品描述
:param total_fee: 总金额,单位分
:param notify_url: 接收微信支付异步通知回调地址
:param client_ip: 可选APP和网页支付提交用户端ipNative支付填调用微信支付API的机器IP
:param user_id: 可选用户在商户appid下的唯一标识。trade_type=JSAPI此参数必传
:param out_trade_no: 可选,商户订单号,默认自动生成
:param detail: 可选,商品详情
:param attach: 可选附加数据在查询API和支付通知中原样返回该字段主要用于商户携带订单的自定义数据
:param fee_type: 可选符合ISO 4217标准的三位字母代码默认人民币CNY
:param time_start: 可选,订单生成时间,默认为当前时间
:param time_expire: 可选,订单失效时间,默认为订单生成时间后两小时
:param goods_tag: 可选,商品标记,代金券或立减优惠功能的参数
:param product_id: 可选trade_type=NATIVE此参数必传。此id为二维码中包含的商品ID商户自行定义
:param device_info: 可选,终端设备号(门店号或收银设备ID)注意PC网页或公众号内支付请传"WEB"
:param limit_pay: 可选指定支付方式no_credit--指定不能使用信用卡支付
:return: 返回的结果数据
"""
now = datetime.fromtimestamp(time.time(), tz=timezone('Asia/Shanghai'))
hours_later = now + timedelta(hours=2)
if time_start is None:
time_start = now
if time_expire is None:
time_expire = hours_later
if not out_trade_no:
out_trade_no = '{0}{1}{2}'.format(
self.mch_id,
now.strftime('%Y%m%d%H%M%S'),
random.randint(1000, 10000)
)
data = {
'appid': self.appid,
'device_info': device_info,
'body': body,
'detail': detail,
'attach': attach,
'out_trade_no': out_trade_no,
'fee_type': fee_type,
'total_fee': total_fee,
'spbill_create_ip': client_ip or get_external_ip(),
'time_start': time_start.strftime('%Y%m%d%H%M%S'),
'time_expire': time_expire.strftime('%Y%m%d%H%M%S'),
'goods_tag': goods_tag,
'notify_url': notify_url,
'trade_type': trade_type,
'limit_pay': limit_pay,
'product_id': product_id,
'openid': user_id,
}
return self._post('pay/unifiedorder', data=data)
def query(self, transaction_id=None, out_trade_no=None):
"""
查询订单
:param transaction_id: 微信的订单号,优先使用
:param out_trade_no: 商户系统内部的订单号当没提供transaction_id时需要传这个。
:return: 返回的结果数据
"""
data = {
'appid': self.appid,
'transaction_id': transaction_id,
'out_trade_no': out_trade_no,
}
return self._post('pay/orderquery', data=data)
def close(self, out_trade_no):
"""
关闭订单
:param out_trade_no: 商户系统内部的订单号
:return: 返回的结果数据
"""
data = {
'appid': self.appid,
'out_trade_no': out_trade_no,
}
return self._post('pay/closeorder', data=data)
def get_appapi_params(self, prepay_id, timestamp=None, nonce_str=None):
"""
获取 APP 支付参数
:param prepay_id: 统一下单接口返回的 prepay_id 参数值
:param timestamp: 可选,时间戳,默认为当前时间戳
:param nonce_str: 可选,随机字符串,默认自动生成
:return: 签名
"""
data = {
'appid': self.appid,
'partnerid': self.mch_id,
'prepayid': prepay_id,
'package': 'Sign=WXPay',
'timestamp': timestamp or to_text(int(time.time())),
'noncestr': nonce_str or random_string(32)
}
sign = calculate_signature(data, self._client.api_key)
data['sign'] = sign
return data
def reverse(self, transaction_id=None, out_trade_no=None):
"""
撤销订单
:param transaction_id: 可选,微信的订单号,优先使用
:param out_trade_no: 可选,商户系统内部的订单号,
transaction_id、out_trade_no二选一
如果同时存在优先级transaction_id> out_trade_no
:return: 返回的结果数据
"""
data = {
'appid': self.appid,
'transaction_id': transaction_id,
'out_trade_no': out_trade_no,
}
return self._post('secapi/pay/reverse', data=data)

View File

@@ -0,0 +1,125 @@
# -*- coding: utf-8 -*-
from __future__ import absolute_import, unicode_literals
import random
from datetime import datetime
from wechatpy.pay.utils import get_external_ip
from wechatpy.pay.base import BaseWeChatPayAPI
class WeChatRedpack(BaseWeChatPayAPI):
def send(self, user_id, total_amount, send_name, act_name,
wishing, remark, total_num=1, client_ip=None,
nick_name=None, min_value=None,
max_value=None, out_trade_no=None, logo_imgurl=None):
"""
发送现金红包
:param user_id: 接收红包的用户在公众号下的 openid
:param total_amount: 红包金额,单位分
:param send_name: 商户名称
:param nick_name: 可选,提供方名称,默认和商户名称相同
:param act_name: 活动名称
:param wishing: 红包祝福语
:param remark: 备注
:param client_ip: 可选,调用接口的机器 IP 地址
:param total_num: 可选,红包发放总人数,默认为 1
:param min_value: 可选,最小红包金额,单位分
:param max_value: 可选,最大红包金额,单位分
:param out_trade_no: 可选,商户订单号,默认会自动生成
:param logo_imgurl: 可选,商户 Logo 的 URL
:return: 返回的结果数据字典
"""
if not out_trade_no:
now = datetime.now()
out_trade_no = '{0}{1}{2}'.format(
self.mch_id,
now.strftime('%Y%m%d%H%M%S'),
random.randint(1000, 10000)
)
data = {
'wxappid': self.appid,
're_openid': user_id,
'total_amount': total_amount,
'nick_name': nick_name or send_name,
'send_name': send_name,
'act_name': act_name,
'wishing': wishing,
'remark': remark,
'client_ip': client_ip or get_external_ip(),
'total_num': total_num,
'min_value': min_value or total_amount,
'max_value': max_value or total_amount,
'mch_billno': out_trade_no,
'logo_imgurl': logo_imgurl,
}
return self._post('mmpaymkttransfers/sendredpack', data=data)
def send_group(self, user_id, total_amount, send_name, act_name, wishing,
remark, total_num, client_ip=None, amt_type="ALL_RAND",
amt_list=None, out_trade_no=None,
logo_imgurl=None, watermark_imgurl=None,
banner_imgurl=None):
"""
发送裂变红包
:param user_id: 接收红包的用户在公众号下的 openid
:param total_amount: 红包金额,单位分
:param send_name: 商户名称
:param act_name: 活动名称
:param wishing: 红包祝福语
:param remark: 备注
:param total_num: 红包发放总人数
:param client_ip: 可选,调用接口的机器 IP 地址
:param amt_type: 可选,红包金额设置方式
ALL_RAND—全部随机,商户指定总金额和红包发放总人数,由微信支付随机计算出各红包金额
ALL_SPECIFIED—全部自定义
SEED_SPECIFIED—种子红包自定义其他随机
:param amt_list: 可选,各红包具体金额,自定义金额时必须设置,单位分
:param out_trade_no: 可选,商户订单号,默认会自动生成
:param logo_imgurl: 可选,商户 Logo 的 URL
:param watermark_imgurl: 可选,背景水印图片 URL
:param banner_imgurl: 红包详情页面的 banner 图片 URL
:return: 返回的结果数据字典
"""
if not out_trade_no:
now = datetime.now()
out_trade_no = '{0}{1}{2}'.format(
self._client.mch_id,
now.strftime('%Y%m%d%H%M%S'),
random.randint(1000, 10000)
)
data = {
'wxappid': self.appid,
're_openid': user_id,
'total_amount': total_amount,
'send_name': send_name,
'act_name': act_name,
'wishing': wishing,
'remark': remark,
'total_num': total_num,
'client_ip': client_ip or get_external_ip(),
'amt_type': amt_type,
'amt_list': amt_list,
'mch_billno': out_trade_no,
'logo_imgurl': logo_imgurl,
'watermark_imgurl': watermark_imgurl,
'banner_imgurl': banner_imgurl,
}
return self._post('mmpaymkttransfers/sendgroupredpack', data=data)
def query(self, out_trade_no, bill_type='MCHT'):
"""
查询红包发放记录
:param out_trade_no: 商户订单号
:param bill_type: 可选,订单类型,目前固定为 MCHT
:return: 返回的红包发放记录信息
"""
data = {
'mch_billno': out_trade_no,
'bill_type': bill_type,
'appid': self.appid,
}
return self._post('mmpaymkttransfers/gethbinfo', data=data)

View File

@@ -0,0 +1,61 @@
# -*- coding: utf-8 -*-
from __future__ import absolute_import, unicode_literals
from wechatpy.pay.base import BaseWeChatPayAPI
class WeChatRefund(BaseWeChatPayAPI):
def apply(self, total_fee, refund_fee, out_refund_no, transaction_id=None,
out_trade_no=None, fee_type='CNY', op_user_id=None,
device_info=None,notify_url=None,refund_account='REFUND_SOURCE_UNSETTLED_FUNDS'):
"""
申请退款
:param total_fee: 订单总金额,单位为分
:param refund_fee: 退款总金额,单位为分
:param out_refund_no: 商户系统内部的退款单号,商户系统内部唯一,同一退款单号多次请求只退一笔
:param transaction_id: 可选,微信订单号
:param out_trade_no: 可选,商户系统内部的订单号,与 transaction_id 二选一
:param fee_type: 可选货币类型符合ISO 4217标准的三位字母代码默认人民币CNY
:param op_user_id: 可选,操作员帐号, 默认为商户号
:param refund_account: 可选,退款资金来源,仅针对老资金流商户使用,默认使用未结算资金退款
:param device_info: 可选,终端设备号
:return: 返回的结果数据
"""
data = {
'appid': self.appid,
'device_info': device_info,
'transaction_id': transaction_id,
'out_trade_no': out_trade_no,
'out_refund_no': out_refund_no,
'total_fee': total_fee,
'refund_fee': refund_fee,
'refund_fee_type': fee_type,
'op_user_id': op_user_id if op_user_id else self.mch_id,
"notify_url":notify_url,
'refund_account': refund_account,
}
return self._post('secapi/pay/refund', data=data)
def query(self, refund_id=None, out_refund_no=None, transaction_id=None,
out_trade_no=None, device_info=None):
"""
查询退款
:param refund_id: 微信退款单号
:param out_refund_no: 商户退款单号
:param transaction_id: 微信订单号
:param out_trade_no: 商户系统内部的订单号
:param device_info: 可选,终端设备号
:return: 返回的结果数据
"""
data = {
'appid': self.appid,
'device_info': device_info,
'transaction_id': transaction_id,
'out_trade_no': out_trade_no,
'out_refund_no': out_refund_no,
'refund_id': refund_id,
}
return self._post('pay/refundquery', data=data)

View File

@@ -0,0 +1,57 @@
# -*- coding: utf-8 -*-
from __future__ import absolute_import, unicode_literals
from datetime import datetime, date
from wechatpy.pay.base import BaseWeChatPayAPI
class WeChatTools(BaseWeChatPayAPI):
def short_url(self, long_url):
"""
长链接转短链接
:param long_url: 长链接
:return: 返回的结果数据
"""
data = {
'appid': self.appid,
'long_url': long_url,
}
return self._post('tools/shorturl', data=data)
def download_bill(self, bill_date, bill_type='ALL', device_info=None):
"""
下载对账单
:param bill_date: 下载对账单的日期
:param bill_type: 账单类型ALL返回当日所有订单信息默认值
SUCCESS返回当日成功支付的订单,
REFUND返回当日退款订单,
REVOKED已撤销的订单
:param device_info: 微信支付分配的终端设备号,填写此字段,只下载该设备号的对账单
:return: 返回的结果数据
"""
if isinstance(bill_date, (datetime, date)):
bill_date = bill_date.strftime('%Y%m%d')
data = {
'appid': self.appid,
'bill_date': bill_date,
'bill_type': bill_type,
'device_info': device_info,
}
return self._post('pay/downloadbill', data=data)
def auto_code_to_openid(self, auth_code):
"""
授权码查询 openid 接口
:param auth_code: 扫码支付授权码,设备读取用户微信中的条码或者二维码信息
:return: 返回的结果数据
"""
data = {
'appid': self.appid,
'auth_code': auth_code,
}
return self._post('tools/authcodetoopenid', data=data)

View File

@@ -0,0 +1,65 @@
# -*- coding: utf-8 -*-
from __future__ import absolute_import, unicode_literals
import random
from datetime import datetime
from wechatpy.pay.utils import get_external_ip
from wechatpy.pay.base import BaseWeChatPayAPI
class WeChatTransfer(BaseWeChatPayAPI):
def transfer(self, user_id, amount, desc, client_ip=None,
check_name='OPTION_CHECK', real_name=None,
out_trade_no=None, device_info=None):
"""
企业付款接口
:param user_id: 接受收红包的用户在公众号下的 openid
:param amount: 付款金额,单位分
:param desc: 付款说明
:param client_ip: 可选,调用接口机器的 IP 地址
:param check_name: 可选,校验用户姓名选项,
NO_CHECK不校验真实姓名,
FORCE_CHECK强校验真实姓名未实名认证的用户会校验失败无法转账,
OPTION_CHECK针对已实名认证的用户才校验真实姓名未实名认证用户不校验可以转账成功,
默认为 OPTION_CHECK
:param real_name: 可选,收款用户真实姓名,
如果check_name设置为FORCE_CHECK或OPTION_CHECK则必填用户真实姓名
:param out_trade_no: 可选,商户订单号,需保持唯一性,默认自动生成
:param device_info: 可选,微信支付分配的终端设备号
:return: 返回的结果信息
"""
if not out_trade_no:
now = datetime.now()
out_trade_no = '{0}{1}{2}'.format(
self.mch_id,
now.strftime('%Y%m%d%H%M%S'),
random.randint(1000, 10000)
)
data = {
'mch_appid': self.appid,
'mchid': self.mch_id,
'device_info': device_info,
'partner_trade_no': out_trade_no,
'openid': user_id,
'check_name': check_name,
're_user_name': real_name,
'amount': amount,
'desc': desc,
'spbill_create_ip': client_ip or get_external_ip(),
}
return self._post('mmpaymkttransfers/promotion/transfers', data=data)
def query(self, out_trade_no):
"""
企业付款查询接口
:param out_trade_no: 商户调用企业付款API时使用的商户订单号
:return: 返回的结果数据
"""
data = {
'appid': self.appid,
'partner_trade_no': out_trade_no,
}
return self._post('mmpaymkttransfers/gettransferinfo', data=data)

View File

@@ -0,0 +1,30 @@
# -*- coding: utf-8 -*-
from __future__ import absolute_import, unicode_literals
class BaseWeChatPayAPI(object):
""" WeChat Pay API base class """
def __init__(self, client=None):
self._client = client
def _get(self, url, **kwargs):
if getattr(self, 'API_BASE_URL', None):
kwargs['api_base_url'] = self.API_BASE_URL
return self._client.get(url, **kwargs)
def _post(self, url, **kwargs):
if getattr(self, 'API_BASE_URL', None):
kwargs['api_base_url'] = self.API_BASE_URL
return self._client.post(url, **kwargs)
@property
def appid(self):
return self._client.appid
@property
def mch_id(self):
return self._client.mch_id
@property
def sub_mch_id(self):
return self._client.sub_mch_id

View File

@@ -0,0 +1,54 @@
# -*- coding: utf-8 -*-
from __future__ import absolute_import, unicode_literals
import copy
import hashlib
import socket
import six
from wechatpy.utils import to_binary, to_text
def format_url(params, api_key=None):
data = [to_binary('{0}={1}'.format(k, params[k])) for k in sorted(params) if params[k]]
if api_key:
data.append(to_binary('key={0}'.format(api_key)))
return b"&".join(data)
def calculate_signature(params, api_key):
url = format_url(params, api_key)
return to_text(hashlib.md5(url).hexdigest().upper())
def _check_signature(params, api_key):
_params = copy.deepcopy(params)
sign = _params.pop('sign', '')
return sign == calculate_signature(_params, api_key)
def dict_to_xml(d, sign):
xml = ['<xml>\n']
for k in sorted(d):
# use sorted to avoid test error on Py3k
v = d[k]
if isinstance(v, six.integer_types) or v.isdigit():
xml.append('<{0}>{1}</{0}>\n'.format(to_text(k), to_text(v)))
else:
xml.append(
'<{0}><![CDATA[{1}]]></{0}>\n'.format(to_text(k), to_text(v))
)
xml.append('<sign><![CDATA[{0}]]></sign>\n</xml>'.format(to_text(sign)))
return ''.join(xml)
def get_external_ip():
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
try:
wechat_ip = socket.gethostbyname('api.mch.weixin.qq.com')
sock.connect((wechat_ip, 80))
addr, port = sock.getsockname()
sock.close()
return addr
except socket.error:
return '127.0.0.1'