withhold.py 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  1. # -*- coding: utf-8 -*-
  2. from __future__ import absolute_import, unicode_literals
  3. import time
  4. import random
  5. import datetime
  6. from optionaldict import optionaldict
  7. from library import timezone
  8. from library.wechatpy.pay.utils import get_external_ip, calculate_signature
  9. from library.wechatpy.pay.base import BaseWeChatPayAPI
  10. class WeChatWithhold(BaseWeChatPayAPI):
  11. def apply_signing(self, plan_id, contract_code, contract_display_account, notify_url,
  12. version="1.0", clientip=None, deviceid=None, mobile=None, email=None, qq=None,
  13. request_serial=None, openid=None, creid=None, outerid=None):
  14. """
  15. 申请签约 api
  16. https://pay.weixin.qq.com/wiki/doc/api/pap.php?chapter=18_1&index=1
  17. :param plan_id: 模板id 协议模板id,设置路径见开发步骤。
  18. :param contract_code: 签约协议号 商户侧的签约协议号,由商户生成
  19. :param contract_display_account: 用户账户展示名称 签约用户的名称,用于页面展示,页面样例可见案例与规范
  20. :param notify_url: 回调通知url 用于接收签约成功消息的回调通知地址,以http或https开头。
  21. :param version: 版本号 固定值1.0
  22. :param request_serial: 可选 请求序列号 商户请求签约时的序列号,商户侧须唯一。序列号主要用于排序,不作为查询条件
  23. :param clientip: 可选 客户端 IP 点分IP格式(客户端IP)
  24. :param deviceid: 可选 设备ID android填imei的一次md5; ios填idfa的一次md5
  25. :param mobile: 可选 手机号 用户手机号
  26. :param email: 可选 邮箱地址 用户邮箱地址
  27. :param qq: 可选 QQ号 用户QQ号
  28. :param openid: 可选 微信open ID 用户微信open ID
  29. :param creid: 可选 身份证号 用户身份证号
  30. :param outerid: 可选 商户侧用户标识 用户在商户侧的标识
  31. :return: 返回的结果数据字典
  32. """
  33. timestamp = int(time.time())
  34. if request_serial is None:
  35. request_serial = int(time.time() * 1000)
  36. data = {
  37. "appid": self.appid,
  38. "mch_id": self.mch_id,
  39. "sub_mch_id": self.sub_mch_id,
  40. "plan_id": plan_id,
  41. "contract_code": contract_code,
  42. "request_serial": request_serial,
  43. "contract_display_account": contract_display_account,
  44. "notify_url": notify_url,
  45. "version": version,
  46. "timestamp": timestamp,
  47. "clientip": clientip,
  48. "deviceid": deviceid,
  49. "mobile": mobile,
  50. "email": email,
  51. "qq": qq,
  52. "openid": openid,
  53. "creid": creid,
  54. "outerid": outerid,
  55. }
  56. data = optionaldict(data)
  57. sign = calculate_signature(data, self._client.api_key)
  58. data["sign"] = sign
  59. return {
  60. "base_url": "{}papay/entrustweb".format(self._client.API_BASE_URL),
  61. "data": data
  62. }
  63. def query_signing(self, contract_id=None, plan_id=None, contract_code=None, openid=None, version="1.0"):
  64. """
  65. 查询签约关系 api
  66. :param contract_id: 可选 委托代扣协议id 委托代扣签约成功后由微信返回的委托代扣协议id,选择contract_id查询,则此参数必填
  67. :param plan_id: 可选 模板id 商户在微信商户平台配置的代扣模板id,选择plan_id+contract_code查询,则此参数必填
  68. :param contract_code: 可选 签约协议号 商户请求签约时传入的签约协议号,商户侧须唯一。选择plan_id+contract_code查询,则此参数必填
  69. :param openid: 可选 openid 用户标识,必须保证与传入appid对应
  70. :param version: 版本号 固定值1.0
  71. :return: 返回的结果信息
  72. """
  73. if not contract_id and not (plan_id and contract_code) and not (plan_id and openid):
  74. raise ValueError("contract_id and (plan_id, contract_code) and (plan_id, openid) must be a choice.")
  75. data = {
  76. "appid": self.appid,
  77. "mch_id": self.mch_id,
  78. "contract_id": contract_id,
  79. "plan_id": plan_id,
  80. "contract_code": contract_code,
  81. "openid": openid,
  82. "version": version,
  83. "nonce_str": None,
  84. }
  85. return self._post('papay/querycontract', data=data)
  86. def apply_deduct(self, body, total_fee, contract_id, notify_url, out_trade_no=None,
  87. detail=None, attach=None, fee_type='CNY', goods_tag=None, clientip=None, deviceid=None,
  88. mobile=None, email=None, qq=None, openid=None, creid=None, outerid=None):
  89. """
  90. 申请扣款 api
  91. :param body: 商品描述 商品或支付单简要描述
  92. :param out_trade_no: 可选 商户订单号 商户系统内部的订单号,32个字符内、可包含字母, 其他说明见商户订单号
  93. :param total_fee: 总金额 订单总金额,单位为分,只能为整数,详见支付金额
  94. :param contract_id: 委托代扣协议id 签约成功后,微信返回的委托代扣协议id
  95. :param notify_url: 回调通知url 接受扣款结果异步回调通知的url
  96. :param detail: 可选 商品详情 商品名称明细列表
  97. :param attach: 可选 附加数据 附加数据,在查询API和支付通知中原样返回,该字段主要用于商户携带订单的自定义数据
  98. :param fee_type: 可选 货币类型 符合ISO 4217标准的三位字母代码,默认人民币:CNY
  99. :param goods_tag: 可选 商品标记 商品标记,代金券或立减优惠功能的参数,说明详见代金券或立减优惠
  100. :param clientip: 可选 客户端 IP 点分IP格式(客户端IP)
  101. :param deviceid: 可选 设备ID android填imei的一次md5; ios填idfa的一次md5
  102. :param mobile: 可选 手机号 用户手机号
  103. :param email: 可选 邮箱地址 用户邮箱地址
  104. :param qq: 可选 QQ号 用户QQ号
  105. :param openid: 可选 微信open ID 用户微信open ID
  106. :param creid: 可选 身份证号 用户身份证号
  107. :param outerid: 可选 商户侧用户标识 用户在商户侧的标识
  108. :return: 返回的结果信息
  109. """
  110. trade_type = 'PAP' # 交易类型 交易类型PAP-微信委托代扣支付
  111. timestamp = int(time.time()) # 10位时间戳
  112. spbill_create_ip = get_external_ip() # 终端IP 调用微信支付API的机器IP
  113. if not out_trade_no:
  114. now = datetime.datetime.fromtimestamp(time.time(), tz=timezone('Asia/Shanghai'))
  115. out_trade_no = '{0}{1}{2}'.format(
  116. self.mch_id,
  117. now.strftime('%Y%m%d%H%M%S'),
  118. random.randint(1000, 10000)
  119. )
  120. data = {
  121. "appid": self.appid,
  122. "mch_id": self.mch_id,
  123. "body": body,
  124. "out_trade_no": out_trade_no,
  125. "total_fee": total_fee,
  126. "trade_type": trade_type,
  127. "contract_id": contract_id,
  128. "notify_url": notify_url,
  129. "detail": detail,
  130. "attach": attach,
  131. "fee_type": fee_type,
  132. "goods_tag": goods_tag,
  133. "clientip": clientip,
  134. "deviceid": deviceid,
  135. "mobile": mobile,
  136. "email": email,
  137. "qq": qq,
  138. "openid": openid,
  139. "creid": creid,
  140. "outerid": outerid,
  141. "timestamp": timestamp,
  142. "spbill_create_ip": spbill_create_ip,
  143. }
  144. return self._post("pay/pappayapply", data=data)
  145. def query_order(self, transaction_id=None, out_trade_no=None):
  146. """
  147. 查询订单 api
  148. :param transaction_id: 二选一 微信订单号 微信的订单号,优先使用
  149. :param out_trade_no: 二选一 商户订单号 商户系统内部的订单号,当没提供transaction_id时需要传这个。
  150. :return: 返回的结果信息
  151. """
  152. if not transaction_id and not out_trade_no:
  153. raise ValueError("transaction_id and out_trade_no must be a choice.")
  154. data = {
  155. "appid": self.appid,
  156. "mch_id": self.mch_id,
  157. "transaction_id": transaction_id,
  158. "out_trade_no": out_trade_no,
  159. }
  160. return self._post("pay/paporderquery", data=data)
  161. def apply_cancel_signing(self, contract_id=None, plan_id=None, contract_code=None,
  162. contract_termination_remark=None, version="1.0"):
  163. """
  164. 申请解约
  165. https://pay.weixin.qq.com/wiki/doc/api/pap.php?chapter=18_4&index=6
  166. :param contract_id: 合同ID
  167. :param plan_id: 模板ID
  168. :param contract_code: 合同号
  169. :param contract_termination_remark: 解约原因
  170. :param version: 版本号
  171. :return:
  172. """
  173. if not (contract_id or (plan_id and contract_code)):
  174. raise ValueError("contract_id and (plan_id, contract_code) must be a choice.")
  175. data = {
  176. "appid": self.appid,
  177. "mch_id": self.mch_id,
  178. "plan_id": plan_id,
  179. "contract_code": contract_code,
  180. "contract_id": contract_id,
  181. "contract_termination_remark": contract_termination_remark,
  182. "version": version,
  183. "nonce_str": None,
  184. }
  185. return self._post("papay/deletecontract", data=data)