├── .gitignore ├── README.md ├── example.py └── wx_pay.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Project 2 | .idea/ 3 | user_media/ 4 | migrations/ 5 | 6 | # Byte-compiled / optimized / DLL files 7 | __pycache__/ 8 | *.py[cod] 9 | *$py.class 10 | 11 | # C extensions 12 | *.so 13 | 14 | # Distribution / packaging 15 | .Python 16 | env/ 17 | build/ 18 | develop-eggs/ 19 | dist/ 20 | downloads/ 21 | eggs/ 22 | .eggs/ 23 | lib/ 24 | lib64/ 25 | parts/ 26 | sdist/ 27 | var/ 28 | test_tools.py 29 | 30 | *.egg-info/ 31 | .installed.cfg 32 | *.egg 33 | 34 | # PyInstaller 35 | # Usually these files are written by a python script from a template 36 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 37 | *.manifest 38 | *.spec 39 | 40 | # Installer logs 41 | pip-log.txt 42 | pip-delete-this-directory.txt 43 | 44 | # Unit test / coverage reports 45 | htmlcov/ 46 | .tox/ 47 | .coverage 48 | .coverage.* 49 | .cache 50 | nosetests.xml 51 | coverage.xml 52 | *,cover 53 | .hypothesis/ 54 | 55 | # Translations 56 | *.mo 57 | *.pot 58 | 59 | # Django stuff: 60 | *.log 61 | local_settings.py 62 | 63 | # Flask stuff: 64 | instance/ 65 | .webassets-cache 66 | 67 | # Scrapy stuff: 68 | .scrapy 69 | 70 | # Sphinx documentation 71 | docs/_build/ 72 | 73 | # PyBuilder 74 | target/ 75 | 76 | # IPython Notebook 77 | .ipynb_checkpoints 78 | 79 | # pyenv 80 | .python-version 81 | 82 | # celery beat schedule file 83 | celerybeat-schedule 84 | 85 | # dotenv 86 | .env 87 | 88 | # virtualenv 89 | venv/ 90 | ENV/ 91 | 92 | # Spyder project settings 93 | .spyderproject 94 | 95 | # Rope project settings 96 | .ropeproject 97 | # apps/static/css/css/ 98 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 已停止维护 2 | 3 | # 微信支付 4 | 5 | 参考文档 [https://pay.weixin.qq.com/wiki/doc/api/jsapi.php](https://pay.weixin.qq.com/wiki/doc/api/jsapi.php) 6 | 7 | ## 使用 8 | 9 | 首先引入包 10 | ```python 11 | from wx_pay import WxPay, WxPayError 12 | ``` 13 | 14 | 构造微信支付类,传入配置微信支付参数 15 | ```python 16 | wx_pay = WxPay( 17 | wx_app_id='WX_APP_ID', # 微信平台appid 18 | wx_mch_id='WX_MCH_ID', # 微信支付商户号 19 | wx_mch_key='WX_MCH_KEY', 20 | # wx_mch_key 微信支付重要密钥,请登录微信支付商户平台,在 账户中心-API安全-设置API密钥设置 21 | wx_notify_url='http://www.example.com/pay/weixin/notify' 22 | # wx_notify_url 接受微信付款消息通知地址(通常比自己把支付成功信号写在js里要安全得多,推荐使用这个来接收微信支付成功通知) 23 | # wx_notify_url 开发详见https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_7 24 | ) 25 | ``` 26 | 27 | 创建订单 28 | ```python 29 | data = wx_pay.js_pay_api( 30 | openid=u'***user_openid***', # 付款用户openid 31 | body=u'***商品名称/付款显示名称***', # 例如:饭卡充值100元 32 | total_fee=100 # total_fee 单位是 分, 100 = 1元 33 | ) 34 | ``` 35 | 36 | 查询订单 37 | ```python 38 | data = wx_pay.order_query( 39 | # 下面两个参数二选一 40 | out_trade_no=u'***商户订单号***', 41 | # transaction_id=u'***微信订单号***' 42 | ) 43 | ``` 44 | 45 | 关闭订单 46 | ```python 47 | data = wx_pay.close_order( 48 | out_trade_no=u'***商户订单号***' 49 | ) 50 | ``` 51 | 52 | 申请退款 53 | ```python 54 | data = wx_pay.refund( 55 | # 证书获取方法请阅读:https://pay.weixin.qq.com/wiki/doc/api/tools/cash_coupon.php?chapter=4_3 56 | # api_cert_path: 微信支付商户证书(apiclient_cert.pem)的本地保存路径 57 | api_cert_path='/home/xxx/SERVER/ext_file/apiclient_cert.pem', 58 | # api_cert_path: 微信支付商户证书(apiclient_key.pem)的本地保存路径 59 | api_key_path='/home/xxx/SERVER/ext_file/apiclient_key.pem', 60 | out_trade_no=u'***商户订单号***', 61 | # out_refund_no=u'***商户退款单号***', 商户退款单号可自动生成,按需使用 62 | total_fee=500, # 支付时下单总金额 单位分 63 | refund_fee=500, # 要退款的金额 单位分 64 | ) 65 | ``` 66 | 67 | 退款查询 68 | ```python 69 | data = wx_pay.refund_query( 70 | # 以下传入参数四选一即可 71 | out_refund_no=u'***商户退款单号***', 72 | # out_trade_no=u'***商户订单号***', 73 | # transaction_id=u'***微信订单号***', 74 | # refund_id=u'***微信退款单号***', 75 | ) 76 | ``` 77 | 78 | 下载对账单 79 | ```python 80 | print wx_pay.download_bill( 81 | # 对账单日期 82 | bill_date='20161228', 83 | # 账单类型(ALL-当日所有订单信息,[默认]SUCCESS-当日成功支付的订单, REFUND-当日退款订单) 84 | bill_type='ALL' 85 | ) 86 | ``` 87 | 88 | 给用户发红包(使用前需要到微信支付产品中心开通此功能) 89 | ```python 90 | wx_pay.send_red_pack( 91 | # 证书获取方法请阅读:https://pay.weixin.qq.com/wiki/doc/api/tools/cash_coupon.php?chapter=4_3 92 | # api_cert_path: 微信支付商户证书(apiclient_cert.pem)的本地保存路径 93 | api_cert_path='/home/xxx/SERVER/ext_file/apiclient_cert.pem', 94 | # api_cert_path: 微信支付商户证书(apiclient_key.pem)的本地保存路径 95 | api_key_path='/home/xxx/SERVER/ext_file/apiclient_key.pem', 96 | send_name=u'***公众号发送红包测试***', # 红包名称 97 | re_openid=u'***to_user_openid***', # 要接收红包的用户openid 98 | total_amount=100, # total_fee 单位是 分, 100 = 1元, 最大499元 99 | wishing=u'***感谢参与测试***', # 祝福语 100 | client_ip=u'222.222.222.222', # 调用微信发红包接口服务器公网IP地址 101 | act_name=u'***微信支付测试系统***', # 活动名称 102 | remark=u'***感谢参与***' # 备注 103 | ) 104 | ``` 105 | 106 | 用企业付款功能给用户转账(使用前需要到微信支付产品中心开通此功能) 107 | ```python 108 | wx_pay.enterprise_payment( 109 | # 证书获取方法请阅读:https://pay.weixin.qq.com/wiki/doc/api/tools/cash_coupon.php?chapter=4_3 110 | # api_cert_path: 微信支付商户证书(apiclient_cert.pem)的本地保存路径 111 | api_cert_path='/home/xxx/SERVER/ext_file/apiclient_cert.pem', 112 | # api_cert_path: 微信支付商户证书(apiclient_key.pem)的本地保存路径 113 | api_key_path='/home/xxx/SERVER/ext_file/apiclient_key.pem', 114 | openid=u'***to_user_openid***', # 要接收转账的用户openid 115 | check_name=True, # 是否强制校验收款用户姓名 116 | # 如果check_name为True,下面re_user_name必须传入 117 | # 如果check_name为False,请删除下一行参数re_user_name 118 | re_user_name=u'***客户的真实姓名***', # 校验不成功付款会是失败 119 | amount=100, # amount 单位是 分, 100 = 1元, 单用户 单笔上限/当日上限:2W/2W 120 | desc=u'充值失败退款', # 付款原因 121 | spbill_create_ip='222.222.222.222', # 调用微信企业付款接口服务器公网IP地址 122 | ) 123 | ``` 124 | 125 | 提交刷卡支付请求(通过微信钱包付款码的方式付款) 126 | 商户需可通过 设备扫码 等方式获取到客户付款码,向微信提交支付请求 127 | ```python 128 | wx_pay.swiping_card_payment( 129 | body=u'***商品名称/付款显示名称***', # 例如:综合超市 130 | total_fee=100, # total_fee 单位是 分, 100 = 1元, 单用户 单笔上限/当日上限:2W/2W 131 | auth_code='131336161431593669', # 扫码支付授权码,设备读取用户微信中的条码或者二维码信息(注:用户刷卡条形码规则:18位纯数字,以10、11、12、13、14、15开头) 132 | spbill_create_ip='222.222.222.222', # 调用微信企业付款接口服务器公网IP地址 133 | ) 134 | ``` 135 | 136 | ## 工具函数 137 | 138 | 签名 139 | ```python 140 | wx_pay.sign(dict(openid="xxxxxxxxxxxxxxxxxx", total_fee=100)) 141 | ``` 142 | 143 | 32位随机字符串 144 | ```python 145 | wx_pay.nonce_str() 146 | ``` 147 | 148 | 验证签名 149 | ```python 150 | wx_pay.check(dict(openid="xxxxxxxxxxxxxxxxxx", total_fee=100, sign="signsignsignsign")) 151 | ``` 152 | 153 | 生成微信前端JS配置参数 154 | ```text 155 | 详见example.py的wx_js_config方法, 用来生成前端使用微信js的必要参数 156 | ``` 157 | 158 | ## License 159 | The MIT License(http://opensource.org/licenses/MIT) 160 | 161 | 请自由地享受和参与开源 162 | 163 | ## 贡献 164 | 165 | 如果你有好的意见或建议,欢迎给我们提issue或pull request,为提升Python调用微信支付体验贡献力量 166 | -------------------------------------------------------------------------------- /example.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from hashlib import sha1 5 | from time import time 6 | 7 | from flask import jsonify 8 | 9 | from wx_pay import WxPay, WxPayError 10 | 11 | 12 | def wx_js_config(): 13 | """ 14 | 生成前端 调用微信js的配置参数 15 | """ 16 | config_args = { 17 | 'noncestr': WxPay.nonce_str(), 18 | 'jsapi_ticket': 'xxxxxx', 19 | # jsapi_ticket 一个类似ACCESS_TOKEN的参数, 20 | # 详见 https://mp.weixin.qq.com/wiki?action=doc&id=mp1421141115&t=0.6103989146089088#jssdkshiyongbuzhou 21 | 'timestamp': int(time()), 22 | 'url': 'http://www.example.com/pay/goods=3' # 使用js_api的网页网址 23 | } 24 | raw = [(k, str(config_args[k]) if isinstance(config_args[k], (int, float)) else config_args[k]) 25 | for k in sorted(config_args.keys())] 26 | s = "&".join("=".join(kv) for kv in raw if kv[1]) 27 | return { 28 | 'signature': sha1(s).hexdigest(), 29 | 'timestamp': config_args['timestamp'], 30 | 'nonce_str': config_args['noncestr'] 31 | } 32 | 33 | 34 | def create_pay_example(): 35 | """ 36 | 生成微信JS接口下单所需要的参数 example 37 | """ 38 | wx_pay = WxPay( 39 | wx_app_id='WX_APP_ID', # 微信平台appid 40 | wx_mch_id='WX_MCH_ID', # 微信支付商户号 41 | wx_mch_key='WX_MCH_KEY', 42 | # wx_mch_key 微信支付重要密钥,请登录微信支付商户平台,在 账户中心-API安全-设置API密钥设置 43 | wx_notify_url='http://www.example.com/pay/weixin/notify' 44 | # wx_notify_url 接受微信付款消息通知地址(通常比自己把支付成功信号写在js里要安全得多,推荐使用这个来接收微信支付成功通知) 45 | # wx_notify_url 开发详见https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_7 46 | ) 47 | try: 48 | pay_data = wx_pay.js_pay_api( 49 | openid=u'***user_openid***', # 付款用户openid 50 | body=u'***商品名称/付款显示名称***', # 例如:饭卡充值100元 51 | total_fee=100 # total_fee 单位是 分, 100 = 1元 52 | # spbill_create_ip='210.50.0.10' # 若不使用flask框架,则需要传入调用微信支付的用户ip地址 53 | ) 54 | print pay_data 55 | # 订单生成后将请将返回的json数据 传入前端页面微信支付js的参数部分 56 | return jsonify(pay_data) 57 | except WxPayError, e: 58 | return e.message, 400 59 | 60 | 61 | def order_query_example(): 62 | """ 63 | 查询订单 example 64 | """ 65 | wx_pay = WxPay( 66 | wx_app_id='WX_APP_ID', 67 | wx_mch_id='WX_MCH_ID', 68 | wx_mch_key='WX_MCH_KEY', 69 | wx_notify_url='http://www.example.com/pay/weixin/notify' 70 | ) 71 | data = wx_pay.order_query( 72 | # 下面两个参数二选一 73 | out_trade_no=u'***商户订单号***', 74 | # transaction_id=u'***微信订单号***' 75 | ) 76 | 77 | 78 | def close_order_example(): 79 | """ 80 | 关闭订单 example 81 | """ 82 | wx_pay = WxPay( 83 | wx_app_id='WX_APP_ID', 84 | wx_mch_id='WX_MCH_ID', 85 | wx_mch_key='WX_MCH_KEY', 86 | wx_notify_url='http://www.example.com/pay/weixin/notify' 87 | ) 88 | data = wx_pay.close_order( 89 | out_trade_no=u'***商户订单号***' 90 | ) 91 | 92 | 93 | def refund_example(): 94 | """ 95 | 申请退款 example 96 | """ 97 | wx_pay = WxPay( 98 | wx_app_id='WX_APP_ID', 99 | wx_mch_id='WX_MCH_ID', 100 | wx_mch_key='WX_MCH_KEY', 101 | wx_notify_url='WX_NOTIFY_URL' 102 | ) 103 | data = wx_pay.refund( 104 | # 证书获取方法请阅读:https://pay.weixin.qq.com/wiki/doc/api/tools/cash_coupon.php?chapter=4_3 105 | # api_client_cert_path: 微信支付商户证书(apiclient_cert.pem)的本地保存路径 106 | api_cert_path='/home/xxx/SERVER/ext_file/apiclient_cert.pem', 107 | # api_client_cert_path: 微信支付商户证书(apiclient_key.pem)的本地保存路径 108 | api_key_path='/home/xxx/SERVER/ext_file/apiclient_key.pem', 109 | out_trade_no=u'***商户订单号***', 110 | # out_refund_no=u'***商户退款单号***', 商户退款单号可自动生成,按需使用 111 | total_fee=500, # 支付时下单总金额 单位分 112 | refund_fee=500, # 要退款的金额 单位分 113 | ) 114 | 115 | 116 | def refund_query_example(): 117 | """ 118 | 退款查询 example 119 | """ 120 | wx_pay = WxPay( 121 | wx_app_id='WX_APP_ID', 122 | wx_mch_id='WX_MCH_ID', 123 | wx_mch_key='WX_MCH_KEY', 124 | wx_notify_url='http://www.example.com/pay/weixin/notify' 125 | ) 126 | data = wx_pay.refund_query( 127 | # 以下传入参数四选一即可 128 | out_refund_no=u'***商户退款单号***', 129 | # out_trade_no=u'***商户订单号***', 130 | # transaction_id=u'***微信订单号***', 131 | # refund_id=u'***微信退款单号***', 132 | ) 133 | 134 | 135 | def download_bill_example(): 136 | """ 137 | 下载对账单 example 138 | """ 139 | wx_pay = WxPay( 140 | wx_app_id='WX_APP_ID', 141 | wx_mch_id='WX_MCH_ID', 142 | wx_mch_key='WX_MCH_KEY', 143 | wx_notify_url='http://www.example.com/pay/weixin/notify' 144 | ) 145 | print wx_pay.download_bill( 146 | bill_date='20161228', # 对账单日期 147 | bill_type='ALL' # 账单类型(ALL-当日所有订单信息,[默认]SUCCESS-当日成功支付的订单, REFUND-当日退款订单) 148 | ) 149 | 150 | 151 | def send_red_pack_to_user_example(): 152 | """ 153 | 向个人用户发红包example 154 | """ 155 | wx_pay = WxPay( 156 | wx_app_id='WX_APP_ID', 157 | wx_mch_id='WX_MCH_ID', 158 | wx_mch_key='WX_MCH_KEY', 159 | wx_notify_url='WX_NOTIFY_URL' 160 | ) 161 | wx_pay.send_red_pack( 162 | # 证书获取方法请阅读:https://pay.weixin.qq.com/wiki/doc/api/tools/cash_coupon.php?chapter=4_3 163 | # api_cert_path: 微信支付商户证书(apiclient_cert.pem)的本地保存路径 164 | api_cert_path='/home/xxx/SERVER/ext_file/apiclient_cert.pem', 165 | # api_cert_path: 微信支付商户证书(apiclient_key.pem)的本地保存路径 166 | api_key_path='/home/xxx/SERVER/ext_file/apiclient_key.pem', 167 | send_name=u'微信支付测试', # 红包名称 168 | re_openid=u'***to_user_openid***', # 要接收红包的用户openid 169 | total_amount=100, # total_fee 单位是 分, 100 = 1元, 最大499元 170 | wishing=u'感谢参与测试', # 祝福语 171 | client_ip=u'222.222.222.222', # 调用微信发红包接口服务器公网IP地址 172 | act_name=u'微信支付测试系统', # 活动名称 173 | remark=u'感谢参与' # 备注 174 | ) 175 | 176 | 177 | def enterprise_payment_to_wallet(): 178 | """ 179 | 直接转账到客户微信钱包 180 | """ 181 | wx_pay = WxPay( 182 | wx_app_id='WX_APP_ID', 183 | wx_mch_id='WX_MCH_ID', 184 | wx_mch_key='WX_MCH_KEY', 185 | wx_notify_url='WX_NOTIFY_URL' 186 | ) 187 | wx_pay.enterprise_payment( 188 | # 证书获取方法请阅读:https://pay.weixin.qq.com/wiki/doc/api/tools/cash_coupon.php?chapter=4_3 189 | # api_cert_path: 微信支付商户证书(apiclient_cert.pem)的本地保存路径 190 | api_cert_path='/home/xxx/SERVER/ext_file/apiclient_cert.pem', 191 | # api_cert_path: 微信支付商户证书(apiclient_key.pem)的本地保存路径 192 | api_key_path='/home/xxx/SERVER/ext_file/apiclient_key.pem', 193 | openid=u'***to_user_openid***', # 要接收转账的用户openid 194 | check_name=True, # 是否强制校验收款用户姓名 195 | # 如果check_name为True,下面re_user_name必须传入 196 | # 如果check_name为False,请删除下一行参数re_user_name 197 | re_user_name=u'***客户的真实姓名***', # 校验不成功付款会是失败 198 | amount=100, # total_fee 单位是 分, 100 = 1元, 单用户 单笔上限/当日上限:2W/2W 199 | desc=u'充值失败退款', # 付款原因 200 | spbill_create_ip='222.222.222.222', # 调用微信企业付款接口服务器公网IP地址 201 | ) 202 | 203 | 204 | def swiping_card_example(): 205 | """ 206 | 刷卡支付 example 207 | """ 208 | wx_pay = WxPay( 209 | wx_app_id='WX_APP_ID', 210 | wx_mch_id='WX_MCH_ID', 211 | wx_mch_key='WX_MCH_KEY', 212 | wx_notify_url='http://www.example.com/pay/weixin/notify' 213 | ) 214 | wx_pay.swiping_card_payment( 215 | body=u'***商品名称/付款显示名称***', # 例如:综合超市 216 | total_fee=100, # total_fee 消费金额 单位是 分 217 | auth_code='131336161431593669', # 获取到的客户微信付款码 218 | spbill_create_ip='222.222.222.222', # 调用微信企业付款接口服务器公网IP地址 219 | ) 220 | 221 | 222 | if __name__ == "__main__": 223 | pass 224 | -------------------------------------------------------------------------------- /wx_pay.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import hashlib 3 | import random 4 | import string 5 | import time 6 | import urllib2 7 | 8 | import requests 9 | 10 | try: 11 | from flask import request 12 | except ImportError: 13 | request = None 14 | 15 | try: 16 | from xml.etree import cElementTree as ETree 17 | except ImportError: 18 | from xml.etree import ElementTree as ETree 19 | 20 | 21 | class WxPayError(Exception): 22 | def __init__(self, msg): 23 | super(WxPayError, self).__init__(msg) 24 | 25 | 26 | class WxPay(object): 27 | def __init__(self, wx_app_id, wx_mch_id, wx_mch_key, wx_notify_url): 28 | self.opener = urllib2.build_opener(urllib2.HTTPSHandler()) 29 | self.WX_APP_ID = wx_app_id 30 | self.WX_MCH_ID = wx_mch_id 31 | self.WX_MCH_KEY = wx_mch_key 32 | self.WX_NOTIFY_URL = wx_notify_url 33 | 34 | @staticmethod 35 | def user_ip_address(): 36 | return request.remote_addr if request else None 37 | 38 | @staticmethod 39 | def nonce_str(length=32): 40 | char = string.ascii_letters + string.digits 41 | return "".join(random.choice(char) for _ in range(length)) 42 | 43 | @staticmethod 44 | def to_utf8(raw): 45 | return raw.encode("utf-8") if isinstance(raw, unicode) else raw 46 | 47 | @staticmethod 48 | def to_dict(content): 49 | raw = {} 50 | root = ETree.fromstring(content) 51 | for child in root: 52 | raw[child.tag] = child.text 53 | return raw 54 | 55 | @staticmethod 56 | def random_num(length): 57 | digit_list = list(string.digits) 58 | random.shuffle(digit_list) 59 | return ''.join(digit_list[:length]) 60 | 61 | def sign(self, raw): 62 | """ 63 | 生成签名 64 | 参考微信签名生成算法 65 | https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=4_3 66 | """ 67 | raw = [(k, str(raw[k]) if isinstance(raw[k], (int, float)) else raw[k]) for k in sorted(raw.keys())] 68 | s = "&".join("=".join(kv) for kv in raw if kv[1]) 69 | s += "&key={0}".format(self.WX_MCH_KEY) 70 | return hashlib.md5(self.to_utf8(s)).hexdigest().upper() 71 | 72 | def check(self, raw): 73 | """ 74 | 验证签名是否正确 75 | """ 76 | sign = raw.pop("sign") 77 | return sign == self.sign(raw) 78 | 79 | def to_xml(self, raw): 80 | s = "" 81 | for k, v in raw.iteritems(): 82 | s += "<{0}>{1}".format(k, self.to_utf8(v), k) 83 | return "{0}".format(s) 84 | 85 | def fetch(self, url, data): 86 | req = urllib2.Request(url, data=self.to_xml(data)) 87 | try: 88 | resp = self.opener.open(req, timeout=20) 89 | except urllib2.HTTPError, e: 90 | resp = e 91 | re_info = resp.read() 92 | try: 93 | return self.to_dict(re_info) 94 | except ETree.ParseError: 95 | return re_info 96 | 97 | def fetch_with_ssl(self, url, data, api_client_cert_path, api_client_key_path): 98 | req = requests.post(url, data=self.to_xml(data), 99 | cert=(api_client_cert_path, api_client_key_path)) 100 | return self.to_dict(req.content) 101 | 102 | def reply(self, msg, ok=True): 103 | code = "SUCCESS" if ok else "FAIL" 104 | return self.to_xml(dict(return_code=code, return_msg=msg)) 105 | 106 | def unified_order(self, **data): 107 | """ 108 | 统一下单 109 | 详细规则参考 https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_1 110 | 111 | :param data: out_trade_no, body, total_fee, trade_type 112 | out_trade_no: 商户订单号 113 | body: 商品描述 114 | total_fee: 标价金额, 整数, 单位 分 115 | trade_type: 交易类型 116 | user_ip 在flask框架下可以自动填写, 非flask框架需传入spbill_create_ip 117 | :return: 统一下单生成结果 118 | """ 119 | url = "https://api.mch.weixin.qq.com/pay/unifiedorder" 120 | 121 | # 必填参数 122 | if "out_trade_no" not in data: 123 | raise WxPayError(u"缺少统一支付接口必填参数out_trade_no") 124 | if "body" not in data: 125 | raise WxPayError(u"缺少统一支付接口必填参数body") 126 | if "total_fee" not in data: 127 | raise WxPayError(u"缺少统一支付接口必填参数total_fee") 128 | if "trade_type" not in data: 129 | raise WxPayError(u"缺少统一支付接口必填参数trade_type") 130 | 131 | # 关联参数 132 | if data["trade_type"] == "JSAPI" and "openid" not in data: 133 | raise WxPayError(u"trade_type为JSAPI时,openid为必填参数") 134 | if data["trade_type"] == "NATIVE" and "product_id" not in data: 135 | raise WxPayError(u"trade_type为NATIVE时,product_id为必填参数") 136 | user_ip = self.user_ip_address() 137 | if not user_ip and "spbill_create_ip" not in data: 138 | raise WxPayError(u"当前未使用flask框架,缺少统一支付接口必填参数spbill_create_ip") 139 | 140 | data.setdefault("appid", self.WX_APP_ID) 141 | data.setdefault("mch_id", self.WX_MCH_ID) 142 | data.setdefault("notify_url", self.WX_NOTIFY_URL) 143 | data.setdefault("nonce_str", self.nonce_str()) 144 | data.setdefault("spbill_create_ip", user_ip) 145 | data.setdefault("sign", self.sign(data)) 146 | 147 | raw = self.fetch(url, data) 148 | if raw["return_code"] == "FAIL": 149 | raise WxPayError(raw["return_msg"]) 150 | err_msg = raw.get("err_code_des") 151 | if err_msg: 152 | raise WxPayError(err_msg) 153 | return raw 154 | 155 | def js_pay_api(self, **kwargs): 156 | """ 157 | 生成给JavaScript调用的数据 158 | 详细规则参考 https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_7&index=6 159 | 160 | :param kwargs: openid, body, total_fee 161 | openid: 用户openid 162 | body: 商品名称 163 | total_fee: 标价金额, 整数, 单位 分 164 | out_trade_no: 商户订单号, 若未传入则自动生成 165 | :return: 生成微信JS接口支付所需的信息 166 | """ 167 | kwargs.setdefault("trade_type", "JSAPI") 168 | if "out_trade_no" not in kwargs: 169 | kwargs.setdefault("out_trade_no", self.nonce_str()) 170 | raw = self.unified_order(**kwargs) 171 | package = "prepay_id={0}".format(raw["prepay_id"]) 172 | timestamp = int(time.time()) 173 | nonce_str = self.nonce_str() 174 | raw = dict(appId=self.WX_APP_ID, timeStamp=timestamp, 175 | nonceStr=nonce_str, package=package, signType="MD5") 176 | sign = self.sign(raw) 177 | return dict(package=package, appId=self.WX_APP_ID, 178 | timeStamp=timestamp, nonceStr=nonce_str, sign=sign) 179 | 180 | def order_query(self, **data): 181 | """ 182 | 订单查询 183 | 详细规则参考 https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_2 184 | 185 | :param data: out_trade_no, transaction_id至少填一个 186 | out_trade_no: 商户订单号 187 | transaction_id: 微信订单号 188 | :return: 订单查询结果 189 | """ 190 | url = "https://api.mch.weixin.qq.com/pay/orderquery" 191 | 192 | if "out_trade_no" not in data and "transaction_id" not in data: 193 | raise WxPayError(u"订单查询接口中,out_trade_no、transaction_id至少填一个") 194 | data.setdefault("appid", self.WX_APP_ID) 195 | data.setdefault("mch_id", self.WX_MCH_ID) 196 | data.setdefault("nonce_str", self.nonce_str()) 197 | data.setdefault("sign", self.sign(data)) 198 | 199 | raw = self.fetch(url, data) 200 | if raw["return_code"] == "FAIL": 201 | raise WxPayError(raw["return_msg"]) 202 | return raw 203 | 204 | def close_order(self, out_trade_no): 205 | """ 206 | 关闭订单 207 | 详细规则参考 https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_3 208 | 209 | :param out_trade_no: 商户订单号 210 | :return: 申请关闭订单结果 211 | """ 212 | url = "https://api.mch.weixin.qq.com/pay/closeorder" 213 | data = { 214 | 'out_trade_no': out_trade_no, 215 | 'appid': self.WX_APP_ID, 216 | 'mch_id': self.WX_MCH_ID, 217 | 'nonce_str': self.nonce_str(), 218 | } 219 | data["sign"] = self.sign(data) 220 | raw = self.fetch(url, data) 221 | if raw["return_code"] == "FAIL": 222 | raise WxPayError(raw["return_msg"]) 223 | return raw 224 | 225 | def refund(self, api_cert_path, api_key_path, **data): 226 | """ 227 | 申请退款 228 | 详细规则参考 https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_4 229 | 230 | :param api_cert_path: 微信支付商户证书路径,此证书(apiclient_cert.pem)需要先到微信支付商户平台获取,下载后保存至服务器 231 | :param api_key_path: 微信支付商户证书路径,此证书(apiclient_key.pem)需要先到微信支付商户平台获取,下载后保存至服务器 232 | :param data: out_trade_no、transaction_id至少填一个, out_refund_no, total_fee, refund_fee 233 | out_trade_no: 商户订单号 234 | transaction_id: 微信订单号 235 | out_refund_no: 商户退款单号(若未传入则自动生成) 236 | total_fee: 订单金额 237 | refund_fee: 退款金额 238 | :return: 退款申请返回结果 239 | """ 240 | url = "https://api.mch.weixin.qq.com/secapi/pay/refund" 241 | if "out_trade_no" not in data and "transaction_id" not in data: 242 | raise WxPayError(u"订单查询接口中,out_trade_no、transaction_id至少填一个") 243 | if "total_fee" not in data: 244 | raise WxPayError(u"退款申请接口中,缺少必填参数total_fee") 245 | if "refund_fee" not in data: 246 | raise WxPayError(u"退款申请接口中,缺少必填参数refund_fee") 247 | if "out_refund_no" not in data: 248 | data.setdefault("out_refund_no", self.nonce_str()) 249 | 250 | data.setdefault("appid", self.WX_APP_ID) 251 | data.setdefault("mch_id", self.WX_MCH_ID) 252 | data.setdefault("op_user_id", self.WX_MCH_ID) 253 | data.setdefault("nonce_str", self.nonce_str()) 254 | data.setdefault("sign", self.sign(data)) 255 | 256 | raw = self.fetch_with_ssl(url, data, api_cert_path, api_key_path) 257 | if raw["return_code"] == "FAIL": 258 | raise WxPayError(raw["return_msg"]) 259 | return raw 260 | 261 | def refund_query(self, **data): 262 | """ 263 | 查询退款 264 | 提交退款申请后,通过调用该接口查询退款状态。退款有一定延时, 265 | 用零钱支付的退款20分钟内到账,银行卡支付的退款3个工作日后重新查询退款状态。 266 | 详细规则参考 https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_5 267 | 268 | :param data: out_refund_no、out_trade_no、transaction_id、refund_id四个参数必填一个 269 | out_refund_no: 商户退款单号 270 | out_trade_no: 商户订单号 271 | transaction_id: 微信订单号 272 | refund_id: 微信退款单号 273 | 274 | :return: 退款查询结果 275 | """ 276 | url = "https://api.mch.weixin.qq.com/pay/refundquery" 277 | if "out_refund_no" not in data and "out_trade_no" not in data \ 278 | and "transaction_id" not in data and "refund_id" not in data: 279 | raise WxPayError(u"退款查询接口中,out_refund_no、out_trade_no、transaction_id、refund_id四个参数必填一个") 280 | 281 | data.setdefault("appid", self.WX_APP_ID) 282 | data.setdefault("mch_id", self.WX_MCH_ID) 283 | data.setdefault("nonce_str", self.nonce_str()) 284 | data.setdefault("sign", self.sign(data)) 285 | 286 | raw = self.fetch(url, data) 287 | if raw["return_code"] == "FAIL": 288 | raise WxPayError(raw["return_msg"]) 289 | return raw 290 | 291 | def download_bill(self, bill_date, bill_type=None): 292 | """ 293 | 下载对账单 294 | 详细规则参考 https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_6 295 | 296 | :param bill_date: 对账单日期 297 | :param bill_type: 账单类型(ALL-当日所有订单信息,[默认]SUCCESS-当日成功支付的订单, REFUND-当日退款订单) 298 | 299 | :return: 数据流形式账单 300 | """ 301 | url = "https://api.mch.weixin.qq.com/pay/downloadbill" 302 | data = { 303 | 'bill_date': bill_date, 304 | 'bill_type': bill_type if bill_type else 'SUCCESS', 305 | 'appid': self.WX_APP_ID, 306 | 'mch_id': self.WX_MCH_ID, 307 | 'nonce_str': self.nonce_str() 308 | } 309 | data['sign'] = self.sign(data) 310 | raw = self.fetch(url, data) 311 | return raw 312 | 313 | def send_red_pack(self, api_cert_path, api_key_path, **data): 314 | """ 315 | 发给用户微信红包 316 | 详细规则参考 https://pay.weixin.qq.com/wiki/doc/api/tools/cash_coupon.php?chapter=13_4&index=3 317 | 318 | :param api_cert_path: 微信支付商户证书路径,此证书(apiclient_cert.pem)需要先到微信支付商户平台获取,下载后保存至服务器 319 | :param api_key_path: 微信支付商户证书路径,此证书(apiclient_key.pem)需要先到微信支付商户平台获取,下载后保存至服务器 320 | :param data: send_name, re_openid, total_amount, wishing, client_ip, act_name, remark 321 | send_name: 商户名称 例如: 天虹百货 322 | re_openid: 用户openid 323 | total_amount: 付款金额 324 | wishing: 红包祝福语 例如: 感谢您参加猜灯谜活动,祝您元宵节快乐! 325 | client_ip: 调用接口的机器Ip地址, 注:此地址为服务器地址 326 | act_name: 活动名称 例如: 猜灯谜抢红包活动 327 | remark: 备注 例如: 猜越多得越多,快来抢! 328 | :return: 红包发放结果 329 | """ 330 | url = "https://api.mch.weixin.qq.com/mmpaymkttransfers/sendredpack" 331 | if "send_name" not in data: 332 | raise WxPayError(u"向用户发送红包接口中,缺少必填参数send_name") 333 | if "re_openid" not in data: 334 | raise WxPayError(u"向用户发送红包接口中,缺少必填参数re_openid") 335 | if "total_amount" not in data: 336 | raise WxPayError(u"向用户发送红包接口中,缺少必填参数total_amount") 337 | if "wishing" not in data: 338 | raise WxPayError(u"向用户发送红包接口中,缺少必填参数wishing") 339 | if "client_ip" not in data: 340 | raise WxPayError(u"向用户发送红包接口中,缺少必填参数client_ip") 341 | if "act_name" not in data: 342 | raise WxPayError(u"向用户发送红包接口中,缺少必填参数act_name") 343 | if "remark" not in data: 344 | raise WxPayError(u"向用户发送红包接口中,缺少必填参数remark") 345 | 346 | data.setdefault("wxappid", self.WX_APP_ID) 347 | data.setdefault("mch_id", self.WX_MCH_ID) 348 | data.setdefault("nonce_str", self.nonce_str()) 349 | data.setdefault("mch_billno", u'{0}{1}{2}'.format( 350 | self.WX_MCH_ID, time.strftime('%Y%m%d', time.localtime(time.time())), self.random_num(10) 351 | )) 352 | data.setdefault("total_num", 1) 353 | data.setdefault("scene_id", 'PRODUCT_4') 354 | data.setdefault("sign", self.sign(data)) 355 | 356 | raw = self.fetch_with_ssl(url, data, api_cert_path, api_key_path) 357 | if raw["return_code"] == "FAIL": 358 | raise WxPayError(raw["return_msg"]) 359 | return raw 360 | 361 | def enterprise_payment(self, api_cert_path, api_key_path, **data): 362 | """ 363 | 使用企业对个人付款功能 364 | 详细规则参考 https://pay.weixin.qq.com/wiki/doc/api/tools/mch_pay.php?chapter=14_2 365 | 366 | :param api_cert_path: 微信支付商户证书路径,此证书(apiclient_cert.pem)需要先到微信支付商户平台获取,下载后保存至服务器 367 | :param api_key_path: 微信支付商户证书路径,此证书(apiclient_key.pem)需要先到微信支付商户平台获取,下载后保存至服务器 368 | :param data: openid, check_name, re_user_name, amount, desc, spbill_create_ip 369 | openid: 用户openid 370 | check_name: 是否校验用户姓名 371 | re_user_name: 如果 check_name 为True,则填写,否则不带此参数 372 | amount: 金额: 企业付款金额,单位为分 373 | desc: 企业付款描述信息 374 | spbill_create_ip: 调用接口的机器Ip地址, 注:此地址为服务器地址 375 | :return: 企业转账结果 376 | """ 377 | url = "https://api.mch.weixin.qq.com/mmpaymkttransfers/promotion/transfers" 378 | if "openid" not in data: 379 | raise WxPayError(u"企业付款申请接口中,缺少必填参数openid") 380 | if "check_name" not in data: 381 | raise WxPayError(u"企业付款申请接口中,缺少必填参数check_name") 382 | if data['check_name'] and "re_user_name" not in data: 383 | raise WxPayError(u"企业付款申请接口中,缺少必填参数re_user_name") 384 | if "amount" not in data: 385 | raise WxPayError(u"企业付款申请接口中,缺少必填参数amount") 386 | if "desc" not in data: 387 | raise WxPayError(u"企业付款申请接口中,缺少必填参数desc") 388 | if "spbill_create_ip" not in data: 389 | raise WxPayError(u"企业付款申请接口中,缺少必填参数spbill_create_ip") 390 | 391 | data.setdefault("mch_appid", self.WX_APP_ID) 392 | data.setdefault("mchid", self.WX_MCH_ID) 393 | data.setdefault("nonce_str", self.nonce_str()) 394 | data.setdefault("partner_trade_no", u'{0}{1}{2}'.format( 395 | self.WX_MCH_ID, time.strftime('%Y%m%d', time.localtime(time.time())), self.random_num(10) 396 | )) 397 | data['check_name'] = 'FORCE_CHECK' if data['check_name'] else 'NO_CHECK' 398 | data.setdefault("sign", self.sign(data)) 399 | 400 | raw = self.fetch_with_ssl(url, data, api_cert_path, api_key_path) 401 | if raw["return_code"] == "FAIL": 402 | raise WxPayError(raw["return_msg"]) 403 | return raw 404 | 405 | def swiping_card_payment(self, **data): 406 | """ 407 | 提交刷卡支付 408 | 详细规则参考 https://pay.weixin.qq.com/wiki/doc/api/micropay.php?chapter=9_10&index=1 409 | 410 | :param data: body, out_trade_no, total_fee, auth_code, (可选参数 device_info, detail, goods_tag, limit_pay) 411 | body: 商品描述 412 | *out_trade_no: 商户订单号 413 | total_fee: 标价金额, 整数, 单位 分 414 | auth_code: 微信支付二维码扫描结果 415 | *device_info: 终端设备号(商户自定义,如门店编号) 416 | user_ip 在flask框架下可以自动填写, 非flask框架需传入spbill_create_ip 417 | :return: 统一下单生成结果 418 | """ 419 | url = "https://api.mch.weixin.qq.com/pay/micropay" 420 | 421 | # 必填参数 422 | if "body" not in data: 423 | raise WxPayError(u"缺少刷卡支付接口必填参数body") 424 | if "total_fee" not in data: 425 | raise WxPayError(u"缺少刷卡支付接口必填参数total_fee") 426 | if "out_trade_no" not in data: 427 | data.setdefault("out_trade_no", self.nonce_str()) 428 | 429 | user_ip = self.user_ip_address() 430 | if not user_ip and "spbill_create_ip" not in data: 431 | raise WxPayError(u"当前未使用flask框架,缺少刷卡支付接口必填参数spbill_create_ip") 432 | 433 | data.setdefault("appid", self.WX_APP_ID) 434 | data.setdefault("mch_id", self.WX_MCH_ID) 435 | data.setdefault("nonce_str", self.nonce_str()) 436 | data.setdefault("spbill_create_ip", user_ip) 437 | data.setdefault("sign", self.sign(data)) 438 | 439 | raw = self.fetch(url, data) 440 | if raw["return_code"] == "FAIL": 441 | raise WxPayError(raw["return_msg"]) 442 | err_msg = raw.get("err_code_des") 443 | if err_msg: 444 | raise WxPayError(err_msg) 445 | return raw 446 | --------------------------------------------------------------------------------