diff --git a/fir_ser/api/urls.py b/fir_ser/api/urls.py
index 76c081d..a15642c 100644
--- a/fir_ser/api/urls.py
+++ b/fir_ser/api/urls.py
@@ -54,6 +54,4 @@ urlpatterns = [
re_path("^certification$", CertificationView.as_view()),
re_path("^ali_pay_success$", AliPaySuccess.as_view()),
re_path("^wx_pay_success$", WxPaySuccess.as_view()),
- re_path("^pay_success$", WxPaySuccess.as_view()),
-
]
diff --git a/fir_ser/api/utils/pay/wx.py b/fir_ser/api/utils/pay/wx.py
index e8ea8d4..a51eb5d 100644
--- a/fir_ser/api/utils/pay/wx.py
+++ b/fir_ser/api/utils/pay/wx.py
@@ -3,16 +3,11 @@
# project: 3月
# author: NinEveN
# date: 2021/3/18
-# pip install alipay-sdk-python==3.3.398
-# !/usr/bin/env python
-# -*- coding: utf-8 -*-
-
-
-from api.utils.wxpay import WxPay
+from api.utils.wxpay import WeChatPay, WeChatPayType
from fir_ser.settings import PAY_CONFIG
from datetime import datetime, timedelta
-from api.utils.storage.caches import update_order_info
+from api.utils.storage.caches import update_order_info, update_order_status
import json
import logging
@@ -21,55 +16,67 @@ logger = logging.getLogger(__file__)
class Weixinpay(object):
def __init__(self):
- self.ali_config = PAY_CONFIG.get("ALI")
- self.alipay = self.__get_ali_pay()
+ self.wx_config = PAY_CONFIG.get("WX")
+ self.wxpay = self.__get_wx_pay()
- def __get_ali_pay(self):
- return WxPay(
- appid=self.ali_config.get("APP_ID"),
- app_notify_url=self.ali_config.get("APP_NOTIFY_URL"),
- app_private_key_string=self.ali_config.get("APP_PRIVATE_KEY"),
- alipay_public_key_string=self.ali_config.get("ALI_PUBLIC_KEY"),
- sign_type="RSA2", # RSA 或者 RSA2
- debug=False, # 默认False
- verbose=True, # 输出调试数据
- config=AliPayConfig(timeout=15) # 可选, 请求超时时间
- )
+ def __get_wx_pay(self):
+ return WeChatPay(wechatpay_type=WeChatPayType.NATIVE,
+ mchid=self.wx_config.get('MCH_ID'),
+ parivate_key=self.wx_config.get('APP_PRIVATE_KEY'),
+ cert_serial_no=self.wx_config.get('SERIAL_NO'),
+ appid=self.wx_config.get('APP_ID'),
+ notify_url=self.wx_config.get('APP_NOTIFY_URL'),
+ apiv3_key=self.wx_config.get('API_V3_KEY')
+ )
def get_pay_pc_url(self, out_trade_no, total_amount, passback_params):
- time_expire = (datetime.now() + timedelta(days=1)).strftime("%Y-%m-%d %H:%M:%S")
- order_string = self.alipay.api_alipay_trade_page_pay(
+ time_expire = (datetime.now() + timedelta(days=1)).strftime("%Y-%m-%dT%H:%M:%S+08:00")
+ code, data = self.wxpay.pay(
+ description=self.wx_config.get('SUBJECT'),
out_trade_no=out_trade_no,
- total_amount=total_amount,
- subject=self.ali_config.get("SUBJECT"),
- body="充值 %s 元" % total_amount,
+ amount={
+ 'total': total_amount, # 订单总金额,单位为分。示例值:100
+ 'currency': 'CNY'
+ },
time_expire=time_expire,
- return_url=self.ali_config.get("RETURN_URL"),
- notify_url=self.ali_config.get("APP_NOTIFY_URL"),
- passback_params=json.dumps(passback_params)
+ attach=json.dumps(passback_params),
)
+ print(code, data)
- return "https://openapi.alipay.com/gateway.do?%s" % order_string
-
- def valid_order(self, data):
- signature = data.pop("sign")
- success = self.alipay.verify(data, signature)
- if success and data["trade_status"] in ("TRADE_SUCCESS", "TRADE_FINISHED"):
- logger.info("付款成功,等待下一步验证 %s" % data)
- app_id = data.get("app_id", "")
- if app_id == self.ali_config.get("APP_ID"):
- out_trade_no = data.get("out_trade_no", "") # 服务器订单号
- passback_params = data.get("passback_params", "")
- if passback_params:
- ext_parms = json.loads(passback_params)
- user_id = ext_parms.get("user_id")
- payment_number = data.get("trade_no", "")
- return update_order_info(user_id, out_trade_no, payment_number, 1)
- else:
- logger.error("passback_params %s user_id not exists" % passback_params)
+ def valid_order(self, request):
+ headers = {
+ 'Wechatpay-Signature': request.META.get('HTTP_WECHATPAY_SIGNATURE'),
+ 'Wechatpay-Timestamp': request.META.get('HTTP_WECHATPAY_TIMESTAMP'),
+ 'Wechatpay-Nonce': request.META.get('HTTP_WECHATPAY_NONCE'),
+ 'Wechatpay-Serial': request.META.get('HTTP_WECHATPAY_SERIAL'),
+ }
+ result = self.wxpay.decrypt_callback(headers, request.body.decode('utf-8'))
+ if result:
+ logger.info("付款成功,等待下一步验证 %s" % result)
+ data = json.loads(result)
+ passback_params = data.get("attach", "")
+ out_trade_no = data.get("out_trade_no", "")
+ if passback_params:
+ ext_parms = json.loads(passback_params)
+ user_id = ext_parms.get("user_id")
+ transaction_id = data.get("transaction_id", "")
+ return update_order_info(user_id, out_trade_no, transaction_id, 1)
else:
- logger.error("APP_ID 校验失败 response: %s server: %s" % (app_id, self.ali_config.get("APP_ID")))
+ logger.error("passback_params %s user_id not exists" % passback_params)
+ else:
+ logger.error("消息解密失败")
return False
def update_order_status(self, out_trade_no):
- self.alipay.api_alipay_trade_query()
+ code, data = self.wxpay.query(out_trade_no=out_trade_no)
+ # (0, '交易成功'), (1, '待支付'), (2, '订单已创建'), (3, '退费申请中'), (4, '已退费'), (5, '主动取消'), (6, '超时取消')
+ data = json.loads(data)
+ logger.info("out_trade_no: %s info:%s" % (out_trade_no, data))
+ if code == 200:
+ trade_status = data.get("trade_state", '')
+ if trade_status in ['SUCCESS']:
+ update_order_status(out_trade_no, 0)
+ elif trade_status in ['NOTPAY']:
+ update_order_status(out_trade_no, 2)
+ elif code == 404:
+ update_order_status(out_trade_no, 1)
diff --git a/fir_ser/api/utils/wxpay/__init__.py b/fir_ser/api/utils/wxpay/__init__.py
index c9800a7..1eed32e 100644
--- a/fir_ser/api/utils/wxpay/__init__.py
+++ b/fir_ser/api/utils/wxpay/__init__.py
@@ -1,141 +1,314 @@
-#!/usr/bin/env python
-# -*- coding:utf-8 -*-
-# project: 4月
-# author: NinEveN
-# date: 2021/4/15
-
-import socket
-import time
-import uuid
-import hashlib
-import xml.etree.ElementTree as ET
-import requests
-
-
-def get_nonce_str():
- """
- 获取随机字符串
- :return:
- """
- return str(uuid.uuid4()).replace('-', '')
-
-
-def dict_to_order_xml(dict_data):
- """
- dict to order xml
- ASCII码从小到大排序
- :param dict_data:
- :return:
- """
- xml = [""]
- for k in sorted(dict_data):
- xml.append("<{0}>{1}{0}>".format(k, dict_data.get(k)))
- xml.append("")
- return "".join(xml)
-
-
-def dict_to_xml(dict_data):
- xml = [""]
- for k, v in dict_data.items():
- xml.append("<{0}>{1}{0}>".format(k, v))
- xml.append("")
- return "".join(xml)
-
-
-def xml_to_dict(xml_data):
- """
- xml to dict
- :param xml_data:
- :return:
- """
- xml_dict = {}
- root = ET.fromstring(xml_data)
- for child in root:
- xml_dict[child.tag] = child.text
- return xml_dict
-
-
-class WxPay(object):
- """
- https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=7_4&index=3
- """
-
- def __init__(self, app_id, mch_id, notify_url, merchant_key):
- self.url = 'https://api.mch.weixin.qq.com/v3/pay/transactions/native'
- self.app_id = app_id # 微信分配的小程序ID
- self.mch_id = mch_id # 商户号
- self.notify_url = notify_url # 通知地址
- self.spbill_create_ip = socket.gethostbyname(socket.gethostname()) # 获取本机ip
- self.merchant_key = merchant_key # 商户KEY,修改为自己的
-
- def create_sign(self, pay_data):
+# -*- coding: utf-8 -*-
+import json
+from enum import Enum
+
+from .core import Core, RequestType
+
+
+class WeChatPay():
+ def __init__(self,
+ wechatpay_type,
+ mchid,
+ parivate_key,
+ cert_serial_no,
+ appid,
+ apiv3_key,
+ notify_url=None):
+ """
+ :param wechatpay_type: 微信支付类型,示例值:WeChatPayType.MINIPROG
+ :param mchid: 直连商户号,示例值:'1230000109'
+ :param mch_private_key: 商户证书私钥,示例值:'MIIEvwIBADANBgkqhkiG9w0BAQE...'
+ :param mch_key_serial_no: 商户证书序列号,示例值:'444F4864EA9B34415...'
+ :param appid: 应用ID,示例值:'wxd678efh567hg6787'
+ :param mch_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=parivate_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 = {}
+ params['appid'] = self._appid
+ params['mchid'] = self._mchid
+ params['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})
+ 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'
+ return self._core.request(path, method=RequestType.POST, data=params)
+
+ def close(self, out_trade_no):
+ """关闭订单
+ :param out_trade_no: 商户订单号,示例值:'1217752501201407033233368018'
"""
- 生成签名
- https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=4_3
- :param pay_data:
- :return:
+ 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)
- # 拼接stringA
- string_a = '&'.join(["{0}={1}".format(k, pay_data.get(k)) for k in sorted(pay_data)])
- # 拼接key
- string_sign_temp = '{0}&key={1}'.format(string_a, self.merchant_key).encode('utf-8')
- # md5签名
- sign = hashlib.md5(string_sign_temp).hexdigest()
- return sign.upper()
+ 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 = {}
+ 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 get_pay_info(self, pay_data):
+ def query_refund(self, out_refund_no):
+ """查询单笔退款
+ :param out_refund_no: 商户退款单号,示例值:'1217752501201407033233368018'
"""
- 支付统一下单
- :return:
+ 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'
"""
- # 调用签名函数
-
- post_data = {
- 'appid': self.app_id, # 小程序ID
- 'mch_id': self.mch_id, # 商户号
- 'description': pay_data.get('description'), # 商品描述
- 'out_trade_no': pay_data.get('out_trade_no'), # 商户订单号
- 'time_expire': pay_data.get('time_expire'), # 交易结束时间 示例值:2018-06-08T10:34:56+08:00
- 'attach': pay_data.get('attach'), # 附加数据,在查询API和支付通知中原样返回,可作为自定义参数使用
- 'notify_url': self.notify_url, # 通知地址
- 'amount': {
- 'total': pay_data.get('total'), # 订单总金额,单位为分。示例值:100
- 'currency': 'CNY'
- }
- }
- sign = self.create_sign(post_data)
- post_data['sign'] = sign
-
- xml = dict_to_xml(post_data)
-
- # 统一下单接口请求
- r = requests.post(self.url, data=xml.encode("utf-8"))
- r.encoding = "utf-8"
- res = xml_to_dict(r.text)
- err_code_des = res.get('err_code_des')
- # 出错信息
- if err_code_des:
- return {'code': 40001, 'msg': err_code_des}
- prepay_id = res.get('prepay_id')
-
- return self.re_sign(post_data, prepay_id)
-
- def re_sign(self, post_data, prepay_id):
+ 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'
"""
- 再次对返回的数据签名
- https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=7_7&index=3
- :param post_data:
- :param prepay_id:
- :return:
+ 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'
"""
- pay_sign_data = {
- 'appId': self.app_id, # 注意大小写与统一下单不一致
- 'timeStamp': post_data.get('out_trade_no'),
- 'nonceStr': post_data.get('nonce_str'),
- 'package': 'prepay_id={0}'.format(prepay_id),
- 'signType': 'MD5',
- }
- pay_sign = self.create_sign(pay_sign_data)
- pay_sign_data['paySign'] = pay_sign
- return pay_sign_data
+ 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 = {}
+ 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
diff --git a/fir_ser/api/utils/wxpay/core.py b/fir_ser/api/utils/wxpay/core.py
new file mode 100644
index 0000000..baf9ffa
--- /dev/null
+++ b/fir_ser/api/utils/wxpay/core.py
@@ -0,0 +1,233 @@
+# -*- coding: utf-8 -*-
+import json
+from enum import Enum
+
+import requests
+
+# -*- coding: utf-8 -*-
+
+import time
+import uuid
+from base64 import b64decode, b64encode
+import json
+
+from cryptography.exceptions import InvalidSignature
+from cryptography.hazmat.backends import default_backend
+from cryptography.hazmat.primitives.asymmetric.padding import PKCS1v15
+from cryptography.hazmat.primitives.ciphers.aead import AESGCM
+from cryptography.hazmat.primitives.hashes import SHA256
+from cryptography.hazmat.primitives.serialization import (load_pem_private_key,
+ load_pem_public_key)
+from OpenSSL import crypto
+
+
+def build_authorization(path,
+ method,
+ mchid,
+ serial_no,
+ mch_private_key,
+ data=None,
+ nonce_str=None):
+ timeStamp = str(int(time.time()))
+ nonce_str = nonce_str or ''.join(str(uuid.uuid4()).split('-')).upper()
+ body = json.dumps(data) if data else ''
+ sign_str = method + '\n' + path + '\n' + \
+ timeStamp + '\n' + nonce_str + '\n' + body + '\n'
+ signature = sign(private_key=mch_private_key, sign_str=sign_str)
+ authorization = 'WECHATPAY2-SHA256-RSA2048 mchid="%s",nonce_str="%s",signature="%s",timestamp="%s",serial_no="%s"' % (
+ mchid, nonce_str, signature, timeStamp, serial_no)
+ return authorization
+
+
+def sign(private_key, sign_str):
+ private_key = load_pem_private_key(data=format_private_key(
+ private_key).encode('UTF-8'), password=None, backend=default_backend())
+ message = sign_str.encode('UTF-8')
+ signature = private_key.sign(message, PKCS1v15(), SHA256())
+ sign = b64encode(signature).decode('UTF-8').replace('\n', '')
+ return sign
+
+
+def decrypt(nonce, ciphertext, associated_data, apiv3_key):
+ key_bytes = apiv3_key.encode('UTF-8')
+ nonce_bytes = nonce.encode('UTF-8')
+ associated_data_bytes = associated_data.encode('UTF-8')
+ data = b64decode(ciphertext)
+ aesgcm = AESGCM(key_bytes)
+ return aesgcm.decrypt(nonce_bytes, data, associated_data_bytes).decode('UTF-8')
+
+
+def format_private_key(private_key):
+ pem_start = '-----BEGIN PRIVATE KEY-----\n'
+ pem_end = '\n-----END PRIVATE KEY-----'
+ if not private_key.startswith(pem_start):
+ private_key = pem_start + private_key
+ if not private_key.endswith(pem_end):
+ private_key = private_key + pem_end
+ return private_key
+
+
+def format_certificate(certificate):
+ pem_start = '-----BEGIN CERTIFICATE-----\n'
+ pem_end = '\n-----END CERTIFICATE-----'
+ if not certificate.startswith(pem_start):
+ certificate = pem_start + certificate
+ if not certificate.endswith(pem_end):
+ certificate = certificate + pem_end
+ return certificate
+
+
+def verify(timestamp, nonce, body, signature, certificate):
+ sign_str = '%s\n%s\n%s\n' % (timestamp, nonce, body)
+ public_key_str = dump_public_key(certificate)
+ public_key = load_pem_public_key(data=public_key_str.encode('UTF-8'), backend=default_backend())
+ message = sign_str.encode('UTF-8')
+ signature = b64decode(signature)
+ try:
+ public_key.verify(signature, sign_str.encode('UTF-8'), PKCS1v15(), SHA256())
+ except InvalidSignature:
+ return False
+ return True
+
+
+def certificate_serial_number(certificate):
+ cert = crypto.load_certificate(crypto.FILETYPE_PEM, format_certificate(certificate))
+ try:
+ res = cert.get_signature_algorithm().decode('UTF-8')
+ if res != 'sha256WithRSAEncryption':
+ return None
+ return hex(cert.get_serial_number()).upper()[2:]
+ except:
+ return None
+
+
+def dump_public_key(certificate):
+ cert = crypto.load_certificate(crypto.FILETYPE_PEM, format_certificate(certificate))
+ public_key = crypto.dump_publickey(crypto.FILETYPE_PEM, cert.get_pubkey()).decode("utf-8")
+ return public_key
+
+
+class RequestType(Enum):
+ GET = 0
+ POST = 1
+
+
+class Core():
+ def __init__(self, mchid, cert_serial_no, private_key, apiv3_key):
+ self._mchid = mchid
+ self._cert_serial_no = cert_serial_no
+ self._private_key = private_key
+ self._apiv3_key = apiv3_key
+ self._gate_way = 'https://api.mch.weixin.qq.com'
+ self._certificates = []
+ self._update_certificates()
+
+ def _update_certificates(self):
+ path = '/v3/certificates'
+ code, message = self.request(
+ path,
+ skip_verify=False if self._certificates else True)
+ if code == 200:
+ self._certificates.clear()
+ data = json.loads(message).get('data')
+ for v in data:
+ serial_no = v.get('serial_no')
+ effective_time = v.get('effective_time')
+ expire_time = v.get('expire_time')
+ encrypt_certificate = v.get('encrypt_certificate')
+ algorithm = nonce = associated_data = ciphertext = None
+ if encrypt_certificate:
+ algorithm = encrypt_certificate.get('algorithm')
+ nonce = encrypt_certificate.get('nonce')
+ associated_data = encrypt_certificate.get(
+ 'associated_data')
+ ciphertext = encrypt_certificate.get('ciphertext')
+ if not (
+ serial_no and effective_time and expire_time and algorithm and nonce and associated_data and ciphertext):
+ continue
+ certificate = decrypt(
+ nonce=nonce,
+ ciphertext=ciphertext,
+ associated_data=associated_data,
+ apiv3_key=self._apiv3_key)
+ self._certificates.append(certificate)
+
+ def verify_signature(self, headers, body):
+ signature = headers.get('Wechatpay-Signature')
+ timestamp = headers.get('Wechatpay-Timestamp')
+ nonce = headers.get('Wechatpay-Nonce')
+ serial_no = headers.get('Wechatpay-Serial')
+ verified = False
+ for cert in self._certificates:
+ if serial_no == certificate_serial_number(cert):
+ verified = True
+ certificate = cert
+ break
+ if not verified:
+ self._update_certificates()
+ for cert in self._certificates:
+ if serial_no == certificate_serial_number(cert):
+ verified = True
+ certificate = cert
+ break
+ if not verified:
+ return False
+ if not verify(timestamp, nonce, body, signature, certificate):
+ return False
+ return True
+
+ def request(self, path, method=RequestType.GET, data=None, skip_verify=False):
+ headers = {}
+ headers.update({'Content-Type': 'application/json'})
+ headers.update({'Accept': 'application/json'})
+ headers.update(
+ {'User-Agent': 'wechatpay v3 python sdk(https://github.com/minibear2021/wechatpayv3)'})
+ authorization = build_authorization(
+ path,
+ 'GET' if method == RequestType.GET else 'POST',
+ self._mchid,
+ self._cert_serial_no,
+ self._private_key,
+ data=data)
+ headers.update({'Authorization': authorization})
+ if method == RequestType.GET:
+ response = requests.get(url=self._gate_way + path, headers=headers)
+ else:
+ response = requests.post(
+ self._gate_way + path, json=data, headers=headers)
+
+ if response.status_code in range(200, 300) and not skip_verify:
+ if not self.verify_signature(response.headers, response.text):
+ raise Exception('failed to verify signature')
+ return response.status_code, response.text
+
+ def sign(self, sign_str):
+ return sign(self._private_key, sign_str)
+
+ def decrypt_callback(self, headers, body):
+ if self.verify_signature(headers, body):
+ data = json.loads(body)
+ resource_type = data.get('resource_type')
+ if resource_type != 'encrypt-resource':
+ return None
+ resource = data.get('resource')
+ if not resource:
+ return None
+ algorithm = resource.get('algorithm')
+ if algorithm != 'AEAD_AES_256_GCM':
+ return None
+ nonce = resource.get('nonce')
+ ciphertext = resource.get('ciphertext')
+ associated_data = resource.get('associated_data')
+ if not (nonce and ciphertext):
+ return None
+ if not associated_data:
+ associated_data = ''
+ result = decrypt(
+ nonce=nonce,
+ ciphertext=ciphertext,
+ associated_data=associated_data,
+ apiv3_key=self._apiv3_key)
+ return result
+ else:
+ return None
diff --git a/fir_ser/api/utils/wxpay/utils.py b/fir_ser/api/utils/wxpay/utils.py
new file mode 100644
index 0000000..7d5f35f
--- /dev/null
+++ b/fir_ser/api/utils/wxpay/utils.py
@@ -0,0 +1,101 @@
+# -*- coding: utf-8 -*-
+
+import time
+import uuid
+from base64 import b64decode, b64encode
+import json
+
+from cryptography.exceptions import InvalidSignature
+from cryptography.hazmat.backends import default_backend
+from cryptography.hazmat.primitives.asymmetric.padding import PKCS1v15
+from cryptography.hazmat.primitives.ciphers.aead import AESGCM
+from cryptography.hazmat.primitives.hashes import SHA256
+from cryptography.hazmat.primitives.serialization import (load_pem_private_key,
+ load_pem_public_key)
+from OpenSSL import crypto
+
+
+def build_authorization(path,
+ method,
+ mchid,
+ serial_no,
+ mch_private_key,
+ data=None,
+ nonce_str=None):
+ timeStamp = str(int(time.time()))
+ nonce_str = nonce_str or ''.join(str(uuid.uuid4()).split('-')).upper()
+ body = json.dumps(data) if data else ''
+ sign_str = method + '\n' + path + '\n' + \
+ timeStamp + '\n' + nonce_str + '\n' + body + '\n'
+ signature = sign(private_key=mch_private_key, sign_str=sign_str)
+ authorization = 'WECHATPAY2-SHA256-RSA2048 mchid="%s",nonce_str="%s",signature="%s",timestamp="%s",serial_no="%s"' % (
+ mchid, nonce_str, signature, timeStamp, serial_no)
+ return authorization
+
+
+def sign(private_key, sign_str):
+ private_key = load_pem_private_key(data=format_private_key(
+ private_key).encode('UTF-8'), password=None, backend=default_backend())
+ message = sign_str.encode('UTF-8')
+ signature = private_key.sign(message, PKCS1v15(), SHA256())
+ sign = b64encode(signature).decode('UTF-8').replace('\n', '')
+ return sign
+
+
+def decrypt(nonce, ciphertext, associated_data, apiv3_key):
+ key_bytes = apiv3_key.encode('UTF-8')
+ nonce_bytes = nonce.encode('UTF-8')
+ associated_data_bytes = associated_data.encode('UTF-8')
+ data = b64decode(ciphertext)
+ aesgcm = AESGCM(key_bytes)
+ return aesgcm.decrypt(nonce_bytes, data, associated_data_bytes).decode('UTF-8')
+
+
+def format_private_key(private_key):
+ pem_start = '-----BEGIN PRIVATE KEY-----\n'
+ pem_end = '\n-----END PRIVATE KEY-----'
+ if not private_key.startswith(pem_start):
+ private_key = pem_start + private_key
+ if not private_key.endswith(pem_end):
+ private_key = private_key + pem_end
+ return private_key
+
+
+def format_certificate(certificate):
+ pem_start = '-----BEGIN CERTIFICATE-----\n'
+ pem_end = '\n-----END CERTIFICATE-----'
+ if not certificate.startswith(pem_start):
+ certificate = pem_start + certificate
+ if not certificate.endswith(pem_end):
+ certificate = certificate + pem_end
+ return certificate
+
+
+def verify(timestamp, nonce, body, signature, certificate):
+ sign_str = '%s\n%s\n%s\n' % (timestamp, nonce, body)
+ public_key_str = dump_public_key(certificate)
+ public_key = load_pem_public_key(data=public_key_str.encode('UTF-8'), backend=default_backend())
+ message = sign_str.encode('UTF-8')
+ signature = b64decode(signature)
+ try:
+ public_key.verify(signature, sign_str.encode('UTF-8'), PKCS1v15(), SHA256())
+ except InvalidSignature:
+ return False
+ return True
+
+
+def certificate_serial_number(certificate):
+ cert = crypto.load_certificate(crypto.FILETYPE_PEM, format_certificate(certificate))
+ try:
+ res = cert.get_signature_algorithm().decode('UTF-8')
+ if res != 'sha256WithRSAEncryption':
+ return None
+ return hex(cert.get_serial_number()).upper()[2:]
+ except:
+ return None
+
+
+def dump_public_key(certificate):
+ cert = crypto.load_certificate(crypto.FILETYPE_PEM, format_certificate(certificate))
+ public_key = crypto.dump_publickey(crypto.FILETYPE_PEM, cert.get_pubkey()).decode("utf-8")
+ return public_key
diff --git a/fir_ser/api/utils/wxpay/wx.pay.py b/fir_ser/api/utils/wxpay/wx.pay.py
deleted file mode 100644
index a6d9b56..0000000
--- a/fir_ser/api/utils/wxpay/wx.pay.py
+++ /dev/null
@@ -1,232 +0,0 @@
-#!/usr/bin/env python
-# -*- coding:utf-8 -*-
-# project: 4月
-# author: NinEveN
-# date: 2021/4/15
-
-import time
-import random
-from Cryptodome.PublicKey import RSA
-from Cryptodome.Signature import pkcs1_15
-from Cryptodome.Hash import SHA256
-from base64 import b64encode
-
-mchid = '1608486112'
-app_id = 'wx390e5985fd3699e6'
-serial_no = '27DADA4D2921CDD66B8B20A68276F09B90754922'
-private_key = '''-----BEGIN PRIVATE KEY-----
-MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDbXhNoHljrkS8T
-jXg3+tTkaoOol8FDt0jSGckhzX46gkS16CWYwTthBKurfFtynsJe4uDOphS1ge/r
-QEU3+rWNxqa8o6gHSpp2UTYAz/1oYOlXuSa4NA1uD47lmVZJzad2ybWDSsoeRjFj
-c+X6F0ZiE3FmdN1iHz8NmbP99foih4smv15X+wX5DrsuLuPVHNB4D0fqvY7P5PO3
-wUQXWQNezCYzpPoXX2H/UkyFEFZhWk6/9z3aAkYmqfd6IWPHewOqnVoQRKmo5bXb
-yWbB+QIl/HcSfNtq869s5lLGR2Rl2UX8IFXCcXnRPhSAVIeWfXN26Pc9dz9N4VTU
-yiZ8Y6AHAgMBAAECggEABdS0U1orJufPBogGIAbMzd1+7mZKPtCKYPtKe1mI92kr
-BmLLTQol1+hV39MIYz2RERCaxSNo/YIcrHYi4OALH1+eYvk+qCL1hBuYgeEFbVbW
-HPzQ6KiJitljBPtUbdXHk8K8zmaYhMF84pXcEQ+5UTYPF5gXoloORQBG5oM5SN2g
-2GTgYw1cpDzzRRwnmpvYd1ZydYNj8m6k7I2L1pwzRS/6/whz1sScpfh+91w1IVM0
-WT+pPSdiVtQ7ktmvcTrWj7eNIbcsptZ1QgSV3UHkU0xzLG9N1TqJdHOquunXRS7V
-iw/4NgXveXSTSQrmmZVS+Kdyc+z1iDqwOXmE7hjioQKBgQD7js+40NCAPLYXEfer
-UFvZ6kem9mJIzAUdeTdK4BjYJrcU+UsXRmcWJIPI9HWSr31f/fSfu2SKyBa6+dFF
-SeNHuHPPqQAXrsNuhFG1wcKoNybk7KrsQlXheK7br42565Tegz3UXOKMrnPPlukH
-ZZdlYmwBFjEJvIr9jxJvJoW6qQKBgQDfPb697vR8ruLaecPmsq920iC3AQRanYFK
-dW6U98JkCN9A/LXA1jGyDTEhtlja+5Ylp0M1EnlcZ079Jnvek5haSNnD0xb1nMy8
-P/o0/eWWArTgfjfiJeq1tSdrinGhNz0+Vty74wnbS+5P18H23I5jBlGX5hyNkU4L
-axUfJM9jLwKBgCo/1xVkRNB04eRICT/FlFeqKHSbRvCRC37iv+2ca6/J+M/V+s2i
-7mdipJuYqzKCtNztayt0rrM8Xczzbjlj6n8+NH05FiHkIUCriomrTEUyVh72vNJH
-ZeMjgMK23mfOcEda5YSIQSh9mEfSQbsTTfUiLZ+VGZFYEEP7xo3Se31ZAoGBAJas
-LPYytq8EtrYwowktJwJydoQt2otybRYdRmKjCn/MASrypZWeu/Hpt3SCh1xdnAyT
-5OeILYMxcv2noMksIxMkwl3KNl/V0dVo9O4ZQ4DJGN3AMuWfI9g6iX2q9mCSUPKn
-W9owNbHegN1AyXhdinjJhf6Y4EKohN1uC9Z2WMcfAoGAW90Z2LkqG2fen+R62syP
-aaInnu9bitb9rVENCNGXQHdWmIYBMM5zrg8nX8xNJ+yeGQhgxE+YeSq4FOpe0JkA
-daWIhg++OHN2MBRutj7oL/AFAxyu467YA5+itEJLHNATbOr/s13S66nePNXox/hr
-bIX1aWjPxirQX9mzaL3oEQI=
------END PRIVATE KEY-----'''
-timestamp = str(int(time.time()))
-import uuid
-
-wx_public_key = '''
-
-'''
-
-
-def get_nonce_str():
- """
- 获取随机字符串
- :return:
- """
- return str(uuid.uuid4()).replace('-', '')
-
-
-nonce_str = get_nonce_str()
-
-
-def sign_str(method, url_path, request_body):
- """
- 生成欲签名字符串
- """
- sign_list = [
- method,
- url_path,
- timestamp,
- nonce_str,
- request_body
- ]
- return '\n'.join(sign_list) + '\n'
-
-def sign_response_str(timestamp,nonce_str,request_body):
- """
- 生成欲签名字符串
- """
- sign_list = [
- timestamp,
- nonce_str,
- request_body
- ]
- return '\n'.join(sign_list) + '\n'
-
-def sign(sign_str):
- """
- 生成签名
- """
- rsa_key = RSA.importKey(private_key)
- signer = pkcs1_15.new(rsa_key)
- digest = SHA256.new(sign_str.encode('utf8'))
- sign = b64encode(signer.sign(digest)).decode('utf8')
- return sign
-
-
-def authorization(method, url_path, request_body):
- """
- 生成Authorization
- """
- str = ""
- if isinstance(request_body, dict):
- str = json.dumps(request_body)
- signstr = sign_str(method, url_path, str)
- s = sign(signstr)
- authorization = 'WECHATPAY2-SHA256-RSA2048 ' \
- 'mchid="{mchid}",' \
- 'nonce_str="{nonce_str}",' \
- 'signature="{sign}",' \
- 'timestamp="{timestamp}",' \
- 'serial_no="{serial_no}"'. \
- format(mchid=mchid,
- nonce_str=nonce_str,
- sign=s,
- timestamp=timestamp,
- serial_no=serial_no
- )
- return authorization
-
-
-from datetime import timezone
-
-import json
-
-post_data = {
- 'appid': app_id, # 小程序ID
- 'mchid': mchid, # 商户号
- 'description': '向 FLY分发平台 充值', # 商品描述
- 'out_trade_no': '12021415113114521722839155', # 商户订单号
- # 'time_expire': pay_data.get('time_expire'), # 交易结束时间 示例值:2018-06-08T10:34:56+08:00
- 'attach': json.dumps({'user_id': 11}), # 附加数据,在查询API和支付通知中原样返回,可作为自定义参数使用
- 'notify_url': 'https://app.hehelucky.cn/api/v1/fir/server/pay_success', # 通知地址
- 'amount': {
- 'total': 10, # 订单总金额,单位为分。示例值:100
- 'currency': 'CNY'
- }
-}
-
-
-def make_pay_pc():
- auth = authorization('POST', '/v3/pay/transactions/native', post_data)
- print(auth)
- import requests
- headers = {
- 'Authorization': auth
- }
- req = requests.post('https://api.mch.weixin.qq.com/v3/pay/transactions/native', json=post_data, headers=headers)
-
- print(req.text)
- print(req.status_code)
-
-
-def get_wx_cert():
- auth = authorization('GET', '/v3/certificates', '')
- print(auth)
- import requests
- headers = {
- 'Authorization': auth
- }
- req = requests.get('https://api.mch.weixin.qq.com/v3/certificates', headers=headers)
-
- print(req.text)
- print(req.status_code)
-
-
-from cryptography.hazmat.primitives.ciphers.aead import AESGCM
-import base64
-
-cert_info = {"data": [{"effective_time": "2021-04-15T14:08:03+08:00",
- "encrypt_certificate": {"algorithm": "AEAD_AES_256_GCM", "associated_data": "certificate",
- "ciphertext": "sOuR8QHOOh0QkLBtrsFdrsY6FohdN7JcI6saS7pb/YNowyLlzRQIFmge7C3dILt5DVJu8kzB7dPbqcQmP/J8INSyUqZtFOVVifszWulVyQo23vvC6kT9tHHp/IoPog/Z15rmpyWwmjtrNeWIWILHmmdeSruZKgop4C9L50vQoTBRS3z6JyrACX4OceYwgNR3ICZAu/TNofI6dp92vI9RwIylQxsV1dnzi+m9pYl9IvbF/OGzJazyBPurynFcdcHfq6G7BfKnlmGswh4u5uBqXBoSfzjF14T0clN+sLiZePL0wfyxVRNtAPFkFvsbtiy4ABIbkzwI7T+1Ne/RnLZizpq/4igkILfbtSzBEWO+KRSq2E3ejfKULMZA0uHe0NBVpaAf8f9jR3Bw/16c28hnxidJ+sjf/GMlhm+F3WgzrB7GRuDL97BTdjBKHV5Hm3Q/f94HUrZCUtmoMSihde/I2IE8bI0IPgnuFlOYGZm46yuXtj2aZcr2l9HaT8xCeNnUCPKzY3arVcVZKygKjnTu8Tyvsk4qV54SZTsiQ+hdQohim154401VnP7A2UsKvYQn14xvONkk3POwehe6SJrVwnjlODrKn9qdb2bo0w0yPyV8GkVqi3nn7Eh+Hnq1g6bJb0HXDOpkuf0JF/W0dceMBcTvFTvE0SDAsJgqoIVV1+B4nTCAEbTzmJ5mQKJFL2zzoSXxUZ9ORtWDNzq0Eo0mMnYuivm2fR2IvrjCYAxSXH9X54SmGiENg9usLLfmkp5l0wJqQ29PLB1LEbxI2YonOm0babMd8qfk2d3DVNhm/Uk6uqA+Trk8OtmCTHD6Yt7J4Iz4bqmL4q/1ea2FTIARkg3a+QKj2Yt2tDZeaBHeAsS9et8U82GdP0MQF61OTFhq/NknhyfGUXA6r4lgDN+tmZJyWGkwRhNphXqU6C57JWl8jU2CydPfmTORSlKTScr0fbAYsZQees0TnqnhAYAUFRNsGjdtmQ4fHQDSvXr9ner+e4bsQTqAXzGUYDwtgEjDh26Bn8jpHTkzfC9s491XIHKD5AueNv62JlM4fIDrqJ9o6q2a/skL2EWlegCWWena0eUh+fQdMYzuYg4VAmttk6fwr5S9h15PhyKcPugDyKuXjd3TGVF6oN+pGlyksDUJqrYYvcx16yNdpM4n+B+gf8uCXHpnRd6Ws/g37B0JZUdlIbx6BnXCVGzb8JTHkUOY6ZTQ0ArRn169Ta2DVPUmTIrxID7SZsvYiWiuKywXKfgCdEo3pBPZNthrtBp5GMsJg5dhjsFxsT28FSKolJRfxXViuEs36ybaI9Ks2g6rnBQ8AlpaV6RzWzR8QtTsF481rFK5mC/eaYXFGOGnNmyp12B4ER5UfEm88BBf4xiqk9ZZu8yem7EsomLrovPbF4szYSVyHY2SYalYyjKDZFdH21YUE9lvgQLsBt8LWfOG8PABuUQpEc3a/zVv5F5h+U/oHcdWGirOCIUgI+hk579hpiIdMpOO5YhSrpouuCqX8D+nYpkSbndBGQycfxlTtL2dx0f5DwKqHIPSsKd79F0tqKqodQuRm00KCuqINDNM6TcEwvN8liXy5mgHEbBtWGYlmg6a0Iufy3PvgJkB4hR/gqkEswp7YIJqR2BYoPWLVSROq+d8ExMNHPjihrFRaHDvUecKso7F8Sfq6N6VhScYM6QPk9ow+shc2nsbU+wBl3kHsgaYras+ku+5iHWT8QQk6vWG+RFrfiGfOFzJTXBslt/45ZUc5EdaSUOz9JK+3q8twIl0EBmUM8uThT3uM31/ShdH2ROYz5mdYANERt/23fXOzY4brQJ1gGJ+2wlqKPG/IBemT+1m6z3fGdvPvoQFk02OYmLYIoR0cNa5Y3/MUkGysggflg==",
- "nonce": "1ef9f91582da"}, "expire_time": "2026-04-14T14:08:03+08:00",
- "serial_no": "5091F0B7E805ACD1EDB2A3B7DD04B3A67D177F37"}]}
-
-
-def decrypt_cert(nonce, ciphertext, associated_data):
- key = "60DbP621a9C3162dDd4AB9c2O15a005L"
-
- key_bytes = str.encode(key)
- nonce_bytes = str.encode(nonce)
- ad_bytes = str.encode(associated_data)
- data = base64.b64decode(ciphertext)
-
- aesgcm = AESGCM(key_bytes)
- return aesgcm.decrypt(nonce_bytes, data, ad_bytes)
-
-# encrypt_certificate = cert_info['data'][0]['encrypt_certificate']
-# a=decrypt_cert(encrypt_certificate['nonce'],encrypt_certificate['ciphertext'],encrypt_certificate['associated_data'])
-# print(a)
-# #
-# make_pay_pc()
-
-request_body={'id': '65cce1b4-bbb7-541c-b063-5f284f650196', 'create_time': '2021-04-16T18:12:19+08:00', 'resource_type': 'encrypt-resource', 'event_type': 'TRANSACTION.SUCCESS', 'summary': '支付成功', 'resource': {'original_type': 'transaction', 'algorithm': 'AEAD_AES_256_GCM', 'ciphertext': 'rx9sLCTnnrS3GH/WqSMr+eEJqBFNo75Lb9ZW1EnoOhJ/tbJhXYl3biIR+tj1OK7qhj6ctRFeDc/zwwZ9Y5gPuWMADHd7KhsGmFdhJj4ap+UNq6UFO8g2tF3mGRQ11Cj2BCj0F+31EF8n9UCjm9SE1f0vOSwZFM6tx3/pkEgWyBbHqienr2eWZVlv8qlmUqioYoZNdJbfGNLfD/Uo2OqJg2EtXeEmdglrcC6b5hTuqGD/NcBrj83OvgsaimEbtzhphTGWRWKw2/qJtLVGKTHTMFrJ7g990i6w35vEKALWbAKsDIpCtIwPxYXlxtfxMZsLI/CYLN3Oa0W0DeR7GF+TBYgFsbvMAeryq9plbQm50qMDLfuhXcxIc7XutWGREMAmoS8e8NFtMhhi39QA4BVxSiQvND7qPXQBEESAVL+VDyCDN/SlEU2bEu+E6XD1o5LL/hvOlBnXFtm7mXbKj4j2M1II9De2oYD5R25EOXUi5oB24opaPXgHubSr6QpApQrWl3DGVMsaHVsaB4tWHH8XRaJXZpdDjxzz9DtmmTu7ZCBQifWqmC5vb+78f2+9gEavkHbL4XisKf/IFGUxzW3bKi/042xHVB2Z/JM49+Y=', 'associated_data': 'transaction', 'nonce': 'BFK77WOGPp21'}}
-
-wx_headers = {
- 'HTTP_WECHATPAY_NONCE': 's6pY4nneUbgFh9nJNiSYv1h9ZLC7ruSj',
- 'HTTP_WECHATPAY_SERIAL': '5091F0B7E805ACD1EDB2A3B7DD04B3A67D177F37',
- 'HTTP_WECHATPAY_SIGNATURE': 'T8oiVc5oFsEfNDpOSdTeTiVPyO6rbx9MDEV24EtB4myPZhVYmSsJWBrtPXbYemaEPFJTtkrlzGsc3Rm6PsZoOnxSvKVIbvy60So6y36nRSmgawolTK8ruRorm0qaeRJtUAAsJImmQrMlXSqy5YaZqW3GbbLci4r2UYYoDfaNwSTJiwgit3HFYiIL6rYrtUJ3ekwMW5oyT4Aio37ulGnxymC6Nnyqbos4zpo9y6Nc8upf8rStUJ5ya3d+rZxG6fsPkUOVNz9NwaaKQk2YdelpHjd8K/cfaVNJ1ot82x8ArbEWd0k/Iz02LtLLelfF1V0w//0zmukWxWaCV2K9I+ir6w==',
- 'HTTP_WECHATPAY_TIMESTAMP': '1618567939',
-}
-HTTP_WECHATPAY_TIMESTAMP = wx_headers.get('HTTP_WECHATPAY_TIMESTAMP','')
-HTTP_WECHATPAY_NONCE = wx_headers.get('HTTP_WECHATPAY_NONCE','')
-HTTP_WECHATPAY_SIGNATURE = wx_headers.get('HTTP_WECHATPAY_SIGNATURE','')
-
-WX_CERT_KEY=b'-----BEGIN CERTIFICATE-----\nMIID3DCCAsSgAwIBAgIUUJHwt+gFrNHtsqO33QSzpn0XfzcwDQYJKoZIhvcNAQEL\nBQAwXjELMAkGA1UEBhMCQ04xEzARBgNVBAoTClRlbnBheS5jb20xHTAbBgNVBAsT\nFFRlbnBheS5jb20gQ0EgQ2VudGVyMRswGQYDVQQDExJUZW5wYXkuY29tIFJvb3Qg\nQ0EwHhcNMjEwNDE1MDYwODAzWhcNMjYwNDE0MDYwODAzWjBuMRgwFgYDVQQDDA9U\nZW5wYXkuY29tIHNpZ24xEzARBgNVBAoMClRlbnBheS5jb20xHTAbBgNVBAsMFFRl\nbnBheS5jb20gQ0EgQ2VudGVyMQswCQYDVQQGDAJDTjERMA8GA1UEBwwIU2hlblpo\nZW4wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDONPXG4eQktFWEVbzD\n7ev8eMK9EEL4BBMCcvQCn76PF3wgbvYSO2CLpp7qP1NNJhFDp5ItUOtvz68AD6u1\nPNWxkOMJqdWPXRpewdUo5nr8lZCR4i3XiY+OSO/cA8C8K8mDvVNsMT1wPMp75Vil\nBL2gK5lfjj/Kjoi/aJSU//gQDpuZ+4GHBFQcOK4QqmPY8rdqhX6z93cFEkFDGPUw\nLXE9jZvYGGf5xwKPFKLvjrrg8zX+znJOXOPQtpvKD/RBfHI9ebv/PxepiHw3prM9\nLoIf6FsVaqFqV1tTJZjAgPAgEhRqM53+8PxZTdxJZDCWv/vf4BvG3Bcfw0CSTlYO\npQpJAgMBAAGjgYEwfzAJBgNVHRMEAjAAMAsGA1UdDwQEAwIE8DBlBgNVHR8EXjBc\nMFqgWKBWhlRodHRwOi8vZXZjYS5pdHJ1cy5jb20uY24vcHVibGljL2l0cnVzY3Js\nP0NBPTFCRDQyMjBFNTBEQkMwNEIwNkFEMzk3NTQ5ODQ2QzAxQzNFOEVCRDIwDQYJ\nKoZIhvcNAQELBQADggEBAA508oXmLC0x0VdsW0ThOeN+BXzuqLsKec945BZz0qPm\n/fc3Wn2Ro5yb5tsh94aqyJGmOWgZsWo/nQk2XcE5BPLn7rX0q7uMGCMbJbxfFiuS\nNuJNDSXamYUCGRXgEsZn8mh8EwZ7MKefq2LxtX9VvJF3o1KsvOWaltr5Ra3DBh1F\nghxzyOOimiqn+duT9ZbbA0nfGJjSsLq61rzc/qBMuBsdJnqeYMFs/+AIDuGYNbOj\nFTcK0+fEFfU1H99RGCu4j9jQlOqnB6uXOnr7VSQPfMEpKd6xTmaa01YHWu9oAijO\nupWVgJZI0cC3/L5s5OzlUp8Bc84NOFxSN0yXAZtH1Bc=\n-----END CERTIFICATE-----'
-
-rsa_key = RSA.importKey(WX_CERT_KEY)
-public_key=rsa_key.publickey().exportKey()
-
-Wechatpay_Signature = base64.b64decode(HTTP_WECHATPAY_SIGNATURE)
-
-res_sign_str = sign_response_str(HTTP_WECHATPAY_TIMESTAMP,HTTP_WECHATPAY_NONCE,json.dumps(request_body))
-print(res_sign_str)
-print(Wechatpay_Signature)
-
-key = RSA.importKey(public_key)
-
-# 验证签名
-verifer = pkcs1_15.new(key) # 使用公钥创建校验对象
-
-hasher = SHA256.new(Wechatpay_Signature)# 对收到的消息文本提取摘要
-#hasher.update(message.encode())
-
-verifer.verify(hasher, res_sign_str.encode("utf-8")) # 校验摘要(本来的样子)和收到并解密的签名是否一致
-
-
-# rsa_key = RSA.importKey(private_key)
-# signer = pkcs1_15.new(rsa_key)
-# digest = SHA256.new(sign_str.encode('utf8'))
-# sign = b64encode(signer.sign(digest)).decode('utf8')
\ No newline at end of file
diff --git a/fir_ser/api/views/order.py b/fir_ser/api/views/order.py
index 48832d4..6f46219 100644
--- a/fir_ser/api/views/order.py
+++ b/fir_ser/api/views/order.py
@@ -8,16 +8,14 @@ from rest_framework.views import APIView
from api.utils.response import BaseResponse
from api.utils.auth import ExpiringTokenAuthentication
from rest_framework.response import Response
-from api.models import UserInfo, Price, Order
+from api.models import Price, Order
from api.utils.serializer import PriceSerializer, OrdersSerializer
from rest_framework.pagination import PageNumberPagination
from api.utils.utils import get_order_num, get_choices_dict
from api.utils.storage.caches import update_order_status
import logging
-from django.utils import timezone
from api.utils.pay.ali import Alipay
-from fir_ser.settings import PAY_SUCCESS_URL
-from django.http import HttpResponseRedirect
+from api.utils.pay.wx import Weixinpay
logger = logging.getLogger(__name__)
@@ -60,9 +58,11 @@ class OrderView(APIView):
price_obj = Price.objects.filter(name=price_id).first()
order_obj = Order.objects.filter(account=request.user, order_number=order_number).first()
if order_obj and order_obj.status in [1, 2]:
- alipay = Alipay()
- pay_url = alipay.get_pay_pc_url(order_obj.order_number, order_obj.actual_amount / 100,
- {'user_id': request.user.id})
+ # alipay = Alipay()
+ # pay_url = alipay.get_pay_pc_url(order_obj.order_number, order_obj.actual_amount / 100,
+ # {'user_id': request.user.id})
+ wxpay = Weixinpay()
+ pay_url = wxpay.get_pay_pc_url(order_number, order_obj.actual_amount, {'user_id': request.user.id})
res.data = pay_url
return Response(res.dict)
if price_obj:
@@ -73,8 +73,10 @@ class OrderView(APIView):
account=request.user, status=1, order_type=0, actual_amount=actual_amount,
actual_download_times=price_obj.package_size,
actual_download_gift_times=price_obj.download_count_gift)
- alipay = Alipay()
- pay_url = alipay.get_pay_pc_url(order_number, actual_amount / 100, {'user_id': request.user.id})
+ # alipay = Alipay()
+ # pay_url = alipay.get_pay_pc_url(order_number, actual_amount / 100, {'user_id': request.user.id})
+ wxpay = Weixinpay()
+ pay_url = wxpay.get_pay_pc_url(order_number, actual_amount, {'user_id': request.user.id})
res.data = pay_url
return Response(res.dict)
except Exception as e:
@@ -101,8 +103,11 @@ class OrderView(APIView):
if act == 'cancel' and order_obj.status != 0:
update_order_status(order_number, 5)
elif act == 'status' and order_obj.status in [1, 2]:
- alipay = Alipay()
- alipay.update_order_status(order_obj.order_number)
+ pass
+ # alipay = Alipay()
+ # alipay.update_order_status(order_obj.order_number)
+ # wxpay = Weixinpay()
+ # wxpay.update_order_status(order_obj.order_number)
except Exception as e:
logger.error("%s 订单 %s 更新失败 Exception:%s" % (request.user, order_number, e))
res.code = 1003
@@ -168,11 +173,11 @@ class WxPaySuccess(APIView):
# return HttpResponseRedirect(PAY_SUCCESS_URL)
def post(self, request):
- alipay = Alipay()
msg = 'failure'
- logger.info("支付回调参数:%s" % request.data)
+ logger.info("支付回调参数:%s" % request.body)
logger.info("----- %s" % request.META)
- data = request.data.copy().dict()
- if alipay.valid_order(data):
- msg = 'success'
- return Response(msg)
\ No newline at end of file
+ wxpay = Weixinpay()
+ if wxpay.valid_order(request):
+ return Response(msg)
+ else:
+ return Response(status=201)
diff --git a/fir_ser/fir_ser/settings.py b/fir_ser/fir_ser/settings.py
index 2033dd5..216e4b9 100644
--- a/fir_ser/fir_ser/settings.py
+++ b/fir_ser/fir_ser/settings.py
@@ -481,11 +481,8 @@ aaInnu9bitb9rVENCNGXQHdWmIYBMM5zrg8nX8xNJ+yeGQhgxE+YeSq4FOpe0JkA
daWIhg++OHN2MBRutj7oL/AFAxyu467YA5+itEJLHNATbOr/s13S66nePNXox/hr
bIX1aWjPxirQX9mzaL3oEQI=
-----END PRIVATE KEY-----''',
- 'ALI_PUBLIC_KEY': '''-----BEGIN CERTIFICATE-----
-MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAkru1ulQV1v4q+q38nyzgkdd3evf7C1/Ipu6K+ZFb5FiuxJ7mildkBSuKz/8+TRd+tjgk2lfc2ehK5pja3cxDO/nb25sBoWiU09rtxgXLehLsgRRhatbICrlOnYxg5aiB5odAp3NMRqore4lnVYwfIyL9M49I0G/NbQzYjUQvAQJsnHwc6a6Kuqi1CwR1WXI0sDF9w7KXC4vRFFIUTwI4bVq4HQWI7NhbgEajHM/j6D6Bh/OMcTYnJJzCja0WmZRe5flfCsELlPESOCWUMbYoaNfBzpNvvyOpmRgs9jgy2WY9SeaB9hxwkpr8tOd2Sc7j3221JKCyDaFAX+4zPy7/fQIDAQAB
------END CERTIFICATE-----''',
- 'APP_NOTIFY_URL': 'https://app.hehelucky.cn/api/v1/fir/server/ali_pay_success', # 支付支付回调URL
- # 'RETURN_URL': 'https://app.hehelucky.cn/api/v1/fir/server/ali_pay_success', # 支付前端页面回调URL
+ 'API_V3_KEY': '60DbP621a9C3162dDd4AB9c2O15a005L',
+ 'APP_NOTIFY_URL': 'https://app.hehelucky.cn/api/v1/fir/server/wx_pay_success', # 支付支付回调URL
'RETURN_URL': PAY_SUCCESS_URL, # 支付前端页面回调URL
'SUBJECT': '向 FLY分发平台 充值',
}