# -*- coding: utf-8 -*- from enum import Enum from .core import Core, RequestType class WeChatPay(object): def __init__(self, wechatpay_type, mchid, private_key, cert_serial_no, appid, apiv3_key, notify_url=None): """ :param wechatpay_type: 微信支付类型,示例值:WeChatPayType.MINIPROG :param mchid: 直连商户号,示例值:'1230000109' :param private_key: 商户证书私钥,示例值:'MIIEvwIBADANBgkqhkiG9w0BAQE...' :param cert_serial_no: 商户证书序列号,示例值:'444F4864EA9B34415...' :param appid: 应用ID,示例值:'wxd678efh567hg6787' :param apiv3_key: 商户APIv3密钥,示例值:'a12d3924fd499edac8a5efc...' :param notify_url: 通知地址,示例值:'https://www.weixin.qq.com/wxpay/pay.php' """ self._type = wechatpay_type self._mchid = mchid self._appid = appid self._notify_url = notify_url self._core = Core(mchid=self._mchid, cert_serial_no=cert_serial_no, private_key=private_key, apiv3_key=apiv3_key) def pay(self, description, out_trade_no, amount, payer=None, time_expire=None, attach=None, goods_tag=None, detail=None, scene_info=None, settle_info=None, notify_url=None): """统一下单 :return code, message: :param description: 商品描述,示例值:'Image形象店-深圳腾大-QQ公仔' :param out_trade_no: 商户订单号,示例值:'1217752501201407033233368018' :param amount: 订单金额,示例值:{'total':100, 'currency':'CNY'} :param payer: 支付者,示例值:{'openid':'oHkLxtx0vUqe-18p_AXTZ1innxkCY'} :param time_expire: 交易结束时间,示例值:'2018-06-08T10:34:56+08:00' :param attach: 附加数据,示例值:'自定义数据' :param goods_tag: 订单优惠标记,示例值:'WXG' :param detail: 优惠功能,示例值:{'cost_price':608800, 'invoice_id':'微信123', 'goods_detail':[{'merchant_goods_id':'商品编码', 'wechatpay_goods_id':'1001', 'goods_name':'iPhoneX 256G', 'quantity':1, 'unit_price':828800}]} :param scene_info: 场景信息,示例值:{'payer_client_ip':'14.23.150.211', 'device_id':'013467007045764', 'store_info':{'id':'0001', 'name':'腾讯大厦分店', 'area_code':'440305', 'address':'广东省深圳市南山区科技中一道10000号'}} :param settle_info: 结算信息,示例值:{'profit_sharing':False} :param notify_url: 通知地址,示例值:'https://www.weixin.qq.com/wxpay/pay.php' """ params = {'appid': self._appid, 'mchid': self._mchid, 'notify_url': notify_url or self._notify_url} if description: params.update({'description': description}) else: raise Exception('description is not assigned.') if out_trade_no: params.update({'out_trade_no': out_trade_no}) else: raise Exception('out_trade_no is not assigned.') if amount: params.update({'amount': amount}) else: raise Exception('amount is not assigned.') if payer: params.update({'payer': payer}) if scene_info: params.update({'scene_info': scene_info}) if time_expire: params.update({'time_expire': time_expire}) if attach: params.update({'attach': attach}) if goods_tag: params.update({'goods_tag': goods_tag}) if detail: params.update({'detail': detail}) if settle_info: params.update({'settle_info': settle_info}) path = "" if self._type in [WeChatPayType.JSAPI, WeChatPayType.MINIPROG]: if not payer: raise Exception('payer is not assigned') path = '/v3/pay/transactions/jsapi' elif self._type == WeChatPayType.APP: path = '/v3/pay/transactions/app' elif self._type == WeChatPayType.H5: if not scene_info: raise Exception('scene_info is not assigned.') path = '/v3/pay/transactions/h5' elif self._type == WeChatPayType.NATIVE: path = '/v3/pay/transactions/native' if path: return self._core.request(path, method=RequestType.POST, data=params) else: raise Exception('path is not assigned') def close(self, out_trade_no): """关闭订单 :param out_trade_no: 商户订单号,示例值:'1217752501201407033233368018' """ if not out_trade_no: raise Exception('out_trade_no is not assigned.') path = '/v3/pay/transactions/out-trade-no/%s/close' % out_trade_no params = {'mchid': self._mchid} return self._core.request(path, method=RequestType.POST, data=params) def query(self, transaction_id=None, out_trade_no=None): """查询订单 :param transaction_id: 微信支付订单号,示例值:1217752501201407033233368018 :param out_trade_no: 商户订单号,示例值:1217752501201407033233368018 """ if not (transaction_id or out_trade_no): raise Exception('params is not assigned') if transaction_id: path = '/v3/pay/transactions/id/%s' % transaction_id else: path = '/v3/pay/transactions/out-trade-no/%s' % out_trade_no path = '%s?mchid=%s' % (path, self._mchid) return self._core.request(path) def refund(self, out_refund_no, amount, transaction_id=None, out_trade_no=None, reason=None, funds_account=None, goods_detail=None, notify_url=None): """申请退款 :param out_refund_no: 商户退款单号,示例值:'1217752501201407033233368018' :param amount: 金额信息,示例值:{'refund':888, 'total':888, 'currency':'CNY'} :param transaction_id: 微信支付订单号,示例值:'1217752501201407033233368018' :param out_trade_no: 商户订单号,示例值:'1217752501201407033233368018' :param reason: 退款原因,示例值:'商品已售完' :param funds_account: 退款资金来源,示例值:'AVAILABLE' :param goods_detail: 退款商品,示例值:{'merchant_goods_id':'1217752501201407033233368018', 'wechatpay_goods_id':'1001', 'goods_name':'iPhone6s 16G', 'unit_price':528800, 'refund_amount':528800, 'refund_quantity':1} :param notify_url: 通知地址,示例值:'https://www.weixin.qq.com/wxpay/pay.php' """ params = {'notify_url': notify_url or self._notify_url} if out_refund_no: params.update({'out_refund_no': out_refund_no}) else: raise Exception('out_refund_no is not assigned.') if amount: params.update({'amount': amount}) else: raise Exception('amount is not assigned.') if transaction_id: params.update({'transaction_id': transaction_id}) if out_trade_no: params.update({'out_trade_no': out_trade_no}) if reason: params.update({'reason': reason}) if funds_account: params.update({'funds_account': funds_account}) if goods_detail: params.update({'goods_detail': goods_detail}) path = '/v3/refund/domestic/refunds' return self._core.request(path, method=RequestType.POST, data=params) def query_refund(self, out_refund_no): """查询单笔退款 :param out_refund_no: 商户退款单号,示例值:'1217752501201407033233368018' """ path = '/v3/refund/domestic/refunds/%s' % out_refund_no return self._core.request(path) def trade_bill(self, bill_date, bill_type='ALL', tar_type='GZIP'): """申请交易账单 :param bill_date: 账单日期,示例值:'2019-06-11' :param bill_type: 账单类型, 默认值:'ALL' :param tar_type: 压缩类型,默认值:'GZIP' """ path = '/v3/bill/tradebill?bill_date=%s&bill_type=%s&tar_type=%s' % ( bill_date, bill_type, tar_type) return self._core.request(path) def fundflow_bill(self, bill_date, account_type='BASIC', tar_type='GZIP'): """申请资金账单 :param bill_date: 账单日期,示例值:'2019-06-11' :param account_type: 资金账户类型, 默认值:'BASIC',基本账户, 可选:'OPERATION',运营账户;'FEES',手续费账户 :param tar_type: 压缩类型,默认值:'GZIP' """ path = '/v3/bill/fundflowbill?bill_date=%s&account_type=%s&tar_type=%s' % ( bill_date, account_type, tar_type) return self._core.request(path) def download_bill(self, url): """下载账单 :param url: 账单下载地址,示例值:'https://api.mch.weixin.qq.com/v3/billdownload/file?token=xxx' """ path = url[len(self._core._gate_way):] if url.startswith( self._core._gate_way) else url return self._core.request(path) # def certificates(self): # """下载微信支付平台证书 # """ # return self._core.certificates def combine_pay(self, combine_out_trade_no, sub_orders, scene_info=None, combine_payer_info=None, time_start=None, time_expire=None, combine_appid=None, combine_mchid=None, notify_url=None): """合单支付下单 :param combine_out_trade_no: 合单商户订单号, 示例值:'P20150806125346' :param sub_orders: 子单信息,示例值:[{'mchid':'1900000109', 'attach':'深圳分店', 'amount':{'total_amount':100,'currency':'CNY'}, 'out_trade_no':'20150806125346', 'description':'腾讯充值中心-QQ会员充值', 'settle_info':{'profit_sharing':False, 'subsidy_amount':10}}] :param scene_info: 场景信息, 示例值:{'device_id':'POS1:123', 'payer_client_ip':'14.17.22.32'} :param combine_payer_info: 支付者, 示例值:{'openid':'oUpF8uMuAJO_M2pxb1Q9zNjWeS6o'} :param time_start: 交易起始时间,示例值:'2019-12-31T15:59:59+08:00' :param time_expire: 交易结束时间, 示例值:'2019-12-31T15:59:59+08:00' :param combine_appid: 合单商户appid, 示例值:'wxd678efh567hg6787' :param combine_mchid: 合单发起方商户号,示例值:'1900000109' :param notify_url: 通知地址, 示例值:'https://yourapp.com/notify' """ params = {} params['combine_appid'] = combine_appid or self._appid params['combine_mchid'] = combine_mchid or self._mchid params['notify_url'] = notify_url or self._notify_url if combine_out_trade_no: params.update({'combine_out_trade_no': combine_out_trade_no}) else: raise Exception('combine_out_trade_no is not assigned.') if sub_orders: params.update({'sub_orders': sub_orders}) else: raise Exception('sub_orders is not assigned.') if scene_info: params.update({'scene_info': scene_info}) if combine_payer_info: params.update({'combine_payer_info': combine_payer_info}) if time_start: params.update({'time_start': time_start}) if time_expire: params.update({'time_expire': time_expire}) if self._type in [WeChatPayType.JSAPI, WeChatPayType.MINIPROG]: if not combine_payer_info: raise Exception('combine_payer_info is not assigned') path = '/v3/combine-transactions/jsapi' elif self._type == WeChatPayType.APP: path = '/v3/combine-transactions/app' elif self._type == WeChatPayType.H5: if not scene_info: raise Exception('scene_info is not assigned.') path = '/v3/combine-transactions/h5' elif self._type == WeChatPayType.NATIVE: path = '/v3/combine-transactions/native' return self._core.request(path, method=RequestType.POST, data=params) def combine_query(self, combine_out_trade_no): """合单查询订单 :param combine_out_trade_no: 合单商户订单号,示例值:P20150806125346 """ params = {} if not combine_out_trade_no: raise Exception('param combine_out_trade_no is not assigned') else: params.update({'combine_out_trade_no': combine_out_trade_no}) path = '/v3/combine-transactions/out-trade-no/%s' % combine_out_trade_no return self._core.request(path) def combine_close(self, combine_out_trade_no, sub_orders, combine_appid=None): """合单关闭订单 :param combine_out_trade_no: 合单商户订单号,示例值:'P20150806125346' :param sub_orders: 子单信息, 示例值:[{'mchid': '1900000109', 'out_trade_no': '20150806125346'}] :param combine_appid: 合单商户appid, 示例值:'wxd678efh567hg6787' """ params = {'combine_appid': combine_appid or self._appid} if not combine_out_trade_no: raise Exception('combine_out_trade_no is not assigned.') if not sub_orders: raise Exception('sub_orders is not assigned.') else: params.update({'sub_orders': sub_orders}) path = '/v3/combine-transactions/out-trade-no/%s/close' % combine_out_trade_no return self._core.request(path, method=RequestType.POST, data=params) def sign(self, data): """计算签名值paySign,供JSAPI、APP、NATIVE调起支付时使用 :param data: 需要签名的参数清单,示例值:['wx888','1414561699','5K8264ILTKCH16CQ2502S....','prepay_id=wx201410272009395522657....'] """ sign_str = '\n'.join(data) + '\n' return self._core.sign(sign_str) def decrypt_callback(self, headers, body): """解密回调接口收到的信息 """ return self._core.decrypt_callback(headers, body) class WeChatPayType(Enum): JSAPI = 0 APP = 1 H5 = 2 NATIVE = 3 MINIPROG = 4