python的微信支付代码
from __future__ import unicode_literals
import time
import string
import random
import hashlib
import requests
from .base import Map, WeixinError
try:
from flask import request
except Exception:
request = None
from lxml import etree
__all__ = ("WeixinPayError", "WeixinPay")
FAIL = "FAIL"
SUCCESS = "SUCCESS"
class WeixinPayError(WeixinError):
def __init__(self, msg):
super(WeixinPayError, self).__init__(msg)
class WeixinPay(object):
PAY_HOST = "https://api.mch.weixin.qq.com"
def __init__(self, app_id, mch_id, mch_key, notify_url, key=None, cert=None):
self.app_id = app_id
self.mch_id = mch_id
self.mch_key = mch_key
self.notify_url = notify_url
self.key = key
self.cert = cert
self.sess = requests.Session()
@property
def timestamp(self):
return str(int(time.time()))
@property
def remote_addr(self):
if request is not None:
return request.remote_addr
return ""
@property
def nonce_str(self):
char = string.ascii_letters + string.digits
return "".join(random.choice(char) for _ in range(32))
def sign(self, raw):
raw = [(k, str(raw[k]) if isinstance(raw[k], int) else raw[k])
for k in sorted(raw.keys())]
s = "&".join("=".join(kv) for kv in raw if kv[1])
s += "&key={0}".format(self.mch_key)
return hashlib.md5(s.encode("utf-8")).hexdigest().upper()
def check(self, data):
sign = data.pop("sign")
return sign == self.sign(data)
def to_xml(self, raw):
s = ""
for k, v in raw.items():
s += "<{0}>{1}</{0}>".format(k, v)
s = "<xml>{0}</xml>".format(s)
return s.encode("utf-8")
def to_dict(self, content):
raw = {}
root = etree.fromstring(content.encode("utf-8"),
parser=etree.XMLParser(resolve_entities=False))
for child in root:
raw[child.tag] = child.text
return raw
def _fetch(self, url, data, use_cert=False, appid=True):
if appid:
data.setdefault("appid", self.app_id)
data.setdefault("mch_id", self.mch_id)
data.setdefault("nonce_str", self.nonce_str)
data.setdefault("sign", self.sign(data))
if use_cert:
resp = self.sess.post(url, data=self.to_xml(data), cert=(self.cert, self.key))
else:
resp = self.sess.post(url, data=self.to_xml(data))
content = resp.content.decode("utf-8")
if "return_code" in content:
data = Map(self.to_dict(content))
if data.return_code == FAIL:
raise WeixinPayError(data.return_msg)
if "result_code" in content and data.result_code == FAIL:
raise WeixinPayError(data.err_code_des)
return data
return content
def reply(self, msg, ok=True):
code = SUCCESS if ok else FAIL
return self.to_xml(dict(return_code=code, return_msg=msg))
def unified_order(self, **data):
"""
统一下单
out_trade_no、body、total_fee、trade_type必填
app_id, mchid, nonce_str自动填写
spbill_create_ip 在flask框架下可以自动填写, 非flask框架需要主动传入此参数
"""
url = self.PAY_HOST + '/pay/unifiedorder'
# 必填参数
if "out_trade_no" not in data:
raise WeixinPayError("缺少统一支付接口必填参数out_trade_no")
if "body" not in data:
raise WeixinPayError("缺少统一支付接口必填参数body")
if "total_fee" not in data:
raise WeixinPayError("缺少统一支付接口必填参数total_fee")
if "trade_type" not in data:
raise WeixinPayError("缺少统一支付接口必填参数trade_type")
# 关联参数
if data["trade_type"] == "JSAPI" and "openid" not in data:
raise WeixinPayError("trade_type为JSAPI时,openid为必填参数")
if data["trade_type"] == "NATIVE" and "product_id" not in data:
raise WeixinPayError("trade_type为NATIVE时,product_id为必填参数")
data.setdefault("notify_url", self.notify_url)
if "spbill_create_ip" not in data:
data.setdefault("spbill_create_ip", self.remote_addr)
raw = self._fetch(url, data)
return raw
def jsapi(self, **kwargs):
"""
生成给JavaScript调用的数据
详细规则参考 https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_7&index=6
"""
kwargs.setdefault("trade_type", "JSAPI")
raw = self.unified_order(**kwargs)
package = "prepay_id={0}".format(raw["prepay_id"])
nonce_str = self.nonce_str
raw = dict(appId=self.app_id, timeStamp=self.timestamp,
nonceStr=self.nonce_str, package=package,
signType='MD5')
sign = self.sign(raw)
raw['paySign'] = sign
return raw
order_jsapi = jsapi
def order_h5(self, **kwargs):
"""
H5 支付
详细规则参考 https://pay.weixin.qq.com/wiki/doc/api/H5.php?chapter=9_20&index=1
"""
kwargs.setdefault('trade_type', 'MWEB')
return self.unified_order(**kwargs)
def order_qr(self, **kwargs):
"""
Native 支付(扫码支付)
详细规则参考 https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=9_1
"""
kwargs.setdefault('trade_type', 'NATIVE')
return self.unified_order(**kwargs)
def qrcode_url(self, product_id, shorten=False):
params = {
'appid': self.app_id,
'mch_id': self.mch_id,
'product_id': product_id,
'time_stamp': self.timestamp,
'nonce_str': self.nonce_str,
}
params['sign'] = self.sign(params)
text = ('weixin://wxpay/bizpayurl?sign=%(sign)s&appid=%(appid)s&mch_id=%(mch_id)s'
'&product_id=%(product_id)s&time_stamp=%(time_stamp)s&nonce_str=%(nonce_str)s') % params
if shorten:
return self.qrcode_url_shorten(long_url=urlencode(text))
return text
def qrcode_url_shorten(self, **data):
url = self.PAY_HOST + '/tools/shorturl'
if 'long_url' not in data:
raise WechatPayError('缺少转换短链接接口必填参数long_url')
return self._fetch(url, data)['short_url']
def order_query(self, **data):
"""
订单查询
out_trade_no, transaction_id至少填一个
appid, mchid, nonce_str不需要填入
"""
url = self.PAY_HOST + '/pay/orderquery'
if "out_trade_no" not in data and "transaction_id" not in data:
raise WeixinPayError("订单查询接口中,out_trade_no、transaction_id至少填一个")
return self._fetch(url, data)
def close_order(self, out_trade_no, **data):
"""
关闭订单
out_trade_no必填
appid, mchid, nonce_str不需要填入
"""
url = self.PAY_HOST + '/pay/closeorder'
data.setdefault("out_trade_no", out_trade_no)
return self._fetch(url, data)
def refund(self, **data):
"""
申请退款
out_trade_no、transaction_id至少填一个且
out_refund_no、total_fee、refund_fee、op_user_id为必填参数
appid、mchid、nonce_str不需要填入
"""
url = self.PAY_HOST + '/secapi/pay/refund'
if not self.key or not self.cert:
raise WeixinPayError("退款申请接口需要双向证书")
if "out_trade_no" not in data and "transaction_id" not in data:
raise WeixinPayError("退款申请接口中,out_trade_no、transaction_id至少填一个")
if "out_refund_no" not in data:
raise WeixinPayError("退款申请接口中,缺少必填参数out_refund_no");
if "total_fee" not in data:
raise WeixinPayError("退款申请接口中,缺少必填参数total_fee");
if "refund_fee" not in data:
raise WeixinPayError("退款申请接口中,缺少必填参数refund_fee");
return self._fetch(url, data, True)
def refund_query(self, **data):
"""
查询退款
提交退款申请后,通过调用该接口查询退款状态。退款有一定延时,
用零钱支付的退款20分钟内到账,银行卡支付的退款3个工作日后重新查询退款状态。
out_refund_no、out_trade_no、transaction_id、refund_id四个参数必填一个
appid、mchid、nonce_str不需要填入
"""
url = self.PAY_HOST + '/pay/refundquery'
if "out_refund_no" not in data and "out_trade_no" not in data \
and "transaction_id" not in data and "refund_id" not in data:
raise WeixinPayError("退款查询接口中,out_refund_no、out_trade_no、transaction_id、refund_id四个参数必填一个")
return self._fetch(url, data)
def download_bill(self, bill_date, bill_type="ALL", **data):
"""
下载对账单
bill_date、bill_type为必填参数
appid、mchid、nonce_str不需要填入
"""
url = self.PAY_HOST + '/pay/downloadbill'
data.setdefault("bill_date", bill_date)
data.setdefault("bill_type", bill_type)
if "bill_date" not in data:
raise WeixinPayError("对账单接口中,缺少必填参数bill_date")
return self._fetch(url, data)
def pay_individual(self, **data):
"""
企业付款到零钱
"""
url = self.PAY_HOST + '/mmpaymkttransfers/promotion/transfers'
if not self.key or not self.cert:
raise WeixinPayError("企业接口需要双向证书")
if "partner_trade_no" not in data:
raise WeixinPayError("企业付款接口中, 缺少必要的参数partner_trade_no")
if "openid" not in data:
raise WeixinPayError("企业付款接口中,缺少必填参数openid")
if "amount" not in data:
raise WeixinPayError("企业付款接口中,缺少必填参数amount")
if "desc" not in data:
raise WeixinPayError("企业付款接口中,缺少必填参数desc")
data.setdefault('check_name', 'NO_CHECK')
return self._fetch_pay(url, data, True)
def pay_individual_to_card(self, **data):
"""企业付款到银行卡"""
url = self.PAY_HOST + '/mmpaysptrans/pay_bank'
if not self.key or not self.cert:
raise WeixinPayError("企业接口需要双向证书")
if "partner_trade_no" not in data:
raise WeixinPayError("企业付款接口中, 缺少必要的参数partner_trade_no")
if "enc_bank_no" not in data:
raise WeixinPayError("企业付款接口中,缺少必填参数enc_bank_no")
if "enc_true_name" not in data:
raise WeixinPayError("企业付款接口中,缺少必填参数enc_true_name")
if "bank_code" not in data:
raise WeixinPayError("企业付款接口中,缺少必填参数bank_code")
if "amount" not in data:
raise WeixinPayError("企业付款接口中,缺少必填参数amount")
return self._fetch(url, data, True, False)
def pay_individual_bank_query(self, **data):
"""企业付款到银行卡查询"""
url = self.PAY_HOST + '/mmpaysptrans/query_bank'
if not self.key or not self.cert:
raise WeixinPayError("企业接口需要双向证书'")
if "partner_trade_no" not in data:
raise WeixinPayError("企业付款接口中, 缺少必要的参数partner_trade_no")
return self._fetch(url, data, True, False)
def pay_individual_query(self, **data):
"""企业付款到零钱查询"""
url = self.PAY_HOST + '/mmpaymkttransfers/gettransferinfo'
if not self.key or not self.cert:
raise WeixinPayError("企业接口需要双向证书'")
if "partner_trade_no" not in data:
raise WeixinPayError("企业付款接口中, 缺少必要的参数partner_trade_no")
return self._fetch(url, data, True)
def _fetch_pay(self, url, data, use_cert=False):
data.setdefault("mch_appid", self.app_id)
data.setdefault("mchid", self.mch_id)
data.setdefault("nonce_str", self.nonce_str)
data.setdefault("sign", self.sign(data))
if use_cert:
resp = self.sess.post(url, data=self.to_xml(data), cert=(self.cert, self.key))
else:
resp = self.sess.post(url, data=self.to_xml(data))
content = resp.content.decode("utf-8")
if "return_code" in content:
data = Map(self.to_dict(content))
if data.return_code == FAIL:
raise WeixinPayError(data.return_msg)
if "result_code" in content and data.result_code == FAIL:
raise WeixinPayError(data.err_code_des)
return data
return content