好好学习,天天向上

博客园 首页 联系 订阅 管理

支付宝支付和微信支付是当今互联网产品常用的功能,我使用Django Rest Framework实现了网页上支付宝支付和微信支付的一个通用服务,提供rpc接口给其他服务,包括获取支付宝支付页面url的rpc接口、支付宝支付成功异步回调http接口、获取微信支付二维码rpc接口、主动查询微信订单是否支付的rpc接口等。

支付宝网站支付需要蚂蚁金服开放平台账号,创建应用、配置秘钥等步骤请参考:蚂蚁金服支付宝电脑网站支付快速接入

微信网站支付需要到微信支付官网注册服务商账号,

目录结构如下:

1、models.py

 1 from django.db import models
 2 from django.contrib.postgres.fields import ArrayField
 3 
 4 
 5 # Create your models here.
 6 
 7 
 8 class BaseModel(models.Model):
 9     """
10         基础模型
11     """
12     created_time = models.DateTimeField(auto_now_add=True, verbose_name="创建时间")
13     updated_time = models.DateTimeField(auto_now=True, verbose_name="修改时间")
14     created_by = models.IntegerField(verbose_name="创建人ID")
15     updated_by = models.IntegerField(verbose_name="修改人ID")
16     is_active = models.BooleanField(default=True, verbose_name='是否正常')
17 
18     class Meta:
19         abstract = True
20 
21 
22 class Alipay(BaseModel):
23     """
24         支付
25     """
26 
27     subject = models.CharField(max_length=256, verbose_name="订单标题")
28     out_trade_no = models.CharField(max_length=64, unique=True, verbose_name="唯一订单号")
29     trade_no = models.CharField(default="", max_length=64, verbose_name="支付宝系统中的交易流水号")
30     total_amount = models.DecimalField(max_digits=11, decimal_places=2, verbose_name="订单的资金总额")
31     return_url = models.CharField(max_length=500, verbose_name="支付完成同步跳转地址")
32     notify_url = models.CharField(max_length=500, verbose_name="支付完成异步通知rpc地址")
33     pay_time = models.DateTimeField(null=True, blank=True, verbose_name="支付时间")
34     pay_nos = ArrayField(models.CharField(max_length=100), default=[], verbose_name='同一订单的支付ID数组')
35 
36     class Meta:
37         verbose_name = "阿里支付"
38         verbose_name_plural = verbose_name
39         ordering = ('-created_time',)
40 
41 
42 class Wxorder(BaseModel):
43     """
44         订单
45     """
46     body = models.CharField(max_length=256, verbose_name="商品描述")
47     out_trade_no = models.CharField(max_length=64, unique=True, verbose_name="订单号")
48     transaction_id = models.CharField(default="", max_length=64, verbose_name="微信支付订单号")
49     total_fee = models.BigIntegerField(verbose_name="订单的资金总额,单位为分")
50     product_id = models.CharField(max_length=16, verbose_name="商品ID")
51     notify_url = models.CharField(max_length=500, verbose_name="支付完成通知url")
52     pay_time = models.DateTimeField(null=True, blank=True, verbose_name="支付时间")
53 
54     class Meta:
55         verbose_name = "微信订单"
56         verbose_name_plural = verbose_name
57         ordering = ('-created_time',)
58 
59 
60 class Wxpay(BaseModel):
61     """
62         微信支付
63     """
64     out_trade_no = models.CharField(null=True, blank=True, max_length=64, verbose_name="订单号")
65     pay_no = models.CharField(null=True, blank=True, max_length=64, unique=True, verbose_name="支付唯一订单号")
66     code_url = models.CharField(null=True, blank=True, max_length=100, verbose_name="二维码地址")
67     nonce_str = models.CharField(null=True, blank=True, max_length=32, verbose_name="随机字符串")
68 
69     class Meta:
70         verbose_name = "微信支付"
71         verbose_name_plural = verbose_name
72         ordering = ('-created_time',)

2、serializers.py:

 1 from django.conf import settings
 2 from rest_framework import serializers
 3 
 4 from pay.models import Alipay, Wxpay
 5 
 6 
 7 class BaseSerializer(serializers.ModelSerializer):
 8     created_time = serializers.DateTimeField(format=settings.DATETIME_FORMAT, read_only=True)
 9     updated_time = serializers.DateTimeField(format=settings.DATETIME_FORMAT, read_only=True)
10     is_active = serializers.BooleanField(read_only=True)
11 
12     class Meta:
13         model = None
14 
15 
16 class AlipaySerializer(BaseSerializer):
17     """
18         阿里支付序列化类
19     """
20 
21     class Meta:
22         model = Alipay
23         fields = "__all__"
24 
25 
26 class WxpaySerializer(BaseSerializer):
27     """
28         阿里支付序列化类
29     """
30 
31     class Meta:
32         model = Wxpay
33         fields = "__all__"

3、views.py:

  1 # -*- coding=utf-8 -*-
  2 # Create your views here.
  3 import time
  4 import dicttoxml
  5 
  6 from jsonrpc import jsonrpc_method
  7 from rest_framework.decorators import list_route
  8 from rest_framework.response import Response
  9 from rest_framework.viewsets import ModelViewSet
 10 from rest_framework.views import APIView
 11 from rest_framework_xml.parsers import XMLParser
 12 from rest_framework_xml.renderers import XMLRenderer
 13 from tokenauth.decorators import is_login
 14 
 15 from pay import utils
 16 from pay.weixin_pay import WeiXinPay, UnifiedOrderPay, OrderQuery
 17 from pay.UUIDTools import UUIDTools
 18 from pay.models import Alipay, Wxpay, Wxorder
 19 from pay.serializers import AlipaySerializer, WxpaySerializer
 20 from pay.utils import UnActiveModelMixin
 21 from pay.alipay import AliPay
 22 from PAY_SERVICE.settings.base import APPID, PRIVATE_KEY_PATH, \
 23     ALI_PUB_KEY_PATH, ALIPAY_CALLBACK_URL, \
 24     WXAPPID, WX_PAY_KEY, WX_MCH_ID, WXPAY_CALLBACK_URL
 25 
 26 NOTIFY_URL = ALIPAY_CALLBACK_URL + 'api/v1.0/pay/alipay/notify/'
 27 
 28 
 29 
 30 class AlipayViewSet(ModelViewSet):
 31     queryset = Alipay.objects.filter(is_active=True)
 32     serializer_class = AlipaySerializer
 33 
 34     @list_route(methods=['post'])
 35     def notify(self, request):
 36         """ 
 37             处理支付宝的notify_url
 38         :param request:
 39         :return:
 40         """
 41         processed_dict = {}
 42         for k, v in request.data.items():
 43             processed_dict[k] = v
 44         app_id = processed_dict.get('app_id')
 45         pay_no = processed_dict.get('out_trade_no')
 46         trade_no = processed_dict.get('trade_no')
 47         total_amount = processed_dict.get('total_amount')
 48         pay_time = time.strftime('%Y-%m-%d %H:%M:%S',time.localtime())
 49 
 50         alipay = Alipay.objects.filter(pay_nos__contains=[pay_no]).values().first()
 51         if alipay is None:
 52             return Response("failed")
 53         if str(alipay.get('total_amount')) != str(total_amount):
 54             return Response("failed")
 55         if app_id != APPID:
 56             return Response("failed")
 57         if alipay.get('trade_no') != "":
 58             return Response("failed")
 59 
 60         sign = processed_dict.pop('sign', None)
 61 
 62         ali_pay = AliPay(
 63             appid=APPID,
 64             app_notify_url=NOTIFY_URL,
 65             app_private_key_path=PRIVATE_KEY_PATH,
 66             alipay_public_key_path=ALI_PUB_KEY_PATH,
 67             debug=True,  # 默认False,
 68             return_url=alipay.get('return_url')
 69         )
 70 
 71         is_verify = ali_pay.verify(processed_dict, sign)
 72 
 73         if is_verify is True:
 74             Alipay.objects.filter(pk=alipay.get('id')).update(pay_time=pay_time, trade_no=trade_no)
 75             ret = utils.request_thrift('TradingManager', 'notify',
 76                 settings.TRADING_RPC_IP, int(settings.TRADING_RPC_PORT),
 77                 alipay.get('out_trade_no'), str(pay_time))
 78             if ret == "success":
 79                 return Response("success")
 80 
 81 
 82 class WxpayViewSet(ModelViewSet):
 83     queryset = Wxpay.objects.filter(is_active=True)
 84     serializer_class = WxpaySerializer
 85     parser_classes = (XMLParser,)
 86     renderer_classes = (XMLRenderer,)
 87 
 88 
 89 @jsonrpc_method('pay.get_alipay_url')
 90 def get_alipay_url(request, subject, out_trade_no, total_amount, return_url, notify_url, user_id):
 91     recode = Alipay.objects.filter(out_trade_no=out_trade_no).values().first()
 92     if recode is not None:
 93         pay_no = UUIDTools.datetime_random()
 94         alipay = Alipay.objects.get(pk=recode.get('id'))
 95         alipay.pay_nos.append(pay_no)
 96         alipay.save()
 97     else:
 98         pay_no = out_trade_no
 99         Alipay.objects.create(subject=subject,
100                               out_trade_no=out_trade_no,
101                               total_amount=total_amount,
102                               return_url=return_url,
103                               notify_url=notify_url,
104                               pay_nos=[pay_no],
105                               created_by=user_id,
106                               updated_by=user_id
107                               )
108 
109     ali_pay = AliPay(
110         appid=APPID,
111         app_notify_url=NOTIFY_URL,
112         app_private_key_path=PRIVATE_KEY_PATH,
113         alipay_public_key_path=ALI_PUB_KEY_PATH,
114         debug=True,  # 默认False,
115         return_url=return_url
116     )
117 
118     total_amount = "%.2f" % float(total_amount)
119     url = ali_pay.direct_pay(
120         subject=subject,
121         out_trade_no=pay_no,
122         total_amount=total_amount
123     )
124     # 沙箱环境网关
125     # alipay_url = "https://openapi.alipaydev.com/gateway.do?{data}".format(data=url)
126     # 正式环境网关
127     alipay_url = "https://openapi.alipay.com/gateway.do?{data}".format(data=url)
128     return alipay_url
129 
130 
131 @jsonrpc_method('pay.get_wxpay_url')
132 def get_wxpay_url(request, out_trade_no, body, total_fee, notify_url, product_id, user_id):
133     recode = Wxorder.objects.filter(out_trade_no=out_trade_no).values().first()
134     if recode is None:
135         Wxorder.objects.create(
136             out_trade_no=out_trade_no,
137             body=body,
138             total_fee=total_fee,
139             notify_url=notify_url,
140             product_id=product_id,
141             created_by=user_id,
142             updated_by=user_id
143         )
144 
145     pay_no = UUIDTools.datetime_random()
146     pay = UnifiedOrderPay(WXAPPID, WX_MCH_ID, WX_PAY_KEY)
147     response = pay.post(body, pay_no, total_fee,
148                         WXPAY_CALLBACK_URL.split('://')[1].split(':')[0], WX_NOTIFY_URL)
149     if response and response["return_code"] == "SUCCESS" and response["result_code"] == "SUCCESS":
150         wxorder = Wxorder.objects.filter(out_trade_no=out_trade_no).values().first()
151         Wxpay.objects.create(
152             out_trade_no=out_trade_no,
153             pay_no=pay_no,
154             code_url=response.get('code_url'),
155             nonce_str=response.get('nonce_str'),
156             created_by=user_id,
157             updated_by=user_id
158         )
159         return response.get('code_url')
160 
161 
162 @jsonrpc_method('pay.wx_order_query')
163 def wx_order_query(request, out_trade_no):
164     wxpays = Wxpay.objects.filter(out_trade_no=out_trade_no).values()
165     pay = OrderQuery(WXAPPID, WX_MCH_ID, WX_PAY_KEY)
166     for wxpay in wxpays:
167         response = pay.post(wxpay.get('pay_no'))
168         if response and response["return_code"] == "SUCCESS" \
169                 and response["result_code"] == "SUCCESS":
170             trade_state = response["trade_state"]
171             if trade_state == "SUCCESS":  # 支付成功
172                 pay_time = response["time_end"]
173                 transaction_id = response["transaction_id"]
174                 Wxorder.objects.filter(out_trade_no=out_trade_no).update(
175                     pay_time=time.strftime("%Y-%m-%d %H:%M:%S",
176                                            time.strptime(pay_time, "%Y%m%d%H%M%S")),
177                     transaction_id=transaction_id
178                 )
179                 return {"success": True, "pay_time": pay_time}
180     return {"success": False}

4、alipay.py:

  1 # -*- coding: utf-8 -*-
  2 
  3 # pip install pycryptodome
  4 
  5 from datetime import datetime
  6 from Crypto.PublicKey import RSA
  7 from Crypto.Signature import PKCS1_v1_5
  8 from Crypto.Hash import SHA256
  9 from base64 import b64encode, b64decode
 10 from urllib.parse import quote_plus
 11 from urllib.parse import urlparse, parse_qs
 12 from urllib.request import urlopen
 13 from base64 import decodebytes, encodebytes
 14 
 15 import json
 16 
 17 
 18 class AliPay(object):
 19     """
 20     支付宝支付接口
 21     """
 22 
 23     def __init__(self, appid, app_notify_url, app_private_key_path,
 24                  alipay_public_key_path, return_url, debug=False):
 25         self.appid = appid
 26         self.app_notify_url = app_notify_url
 27         self.app_private_key_path = app_private_key_path
 28         self.app_private_key = None
 29         self.return_url = return_url
 30         with open(self.app_private_key_path) as fp:
 31             self.app_private_key = RSA.importKey(fp.read())
 32 
 33         self.alipay_public_key_path = alipay_public_key_path
 34         with open(self.alipay_public_key_path) as fp:
 35             self.alipay_public_key = RSA.import_key(fp.read())
 36 
 37         if debug is True:
 38             self.__gateway = "https://openapi.alipaydev.com/gateway.do"
 39         else:
 40             self.__gateway = "https://openapi.alipay.com/gateway.do"
 41 
 42     def direct_pay(self, subject, out_trade_no, total_amount, return_url=None, **kwargs):
 43         biz_content = {
 44             "subject": subject,
 45             "out_trade_no": out_trade_no,
 46             "total_amount": total_amount,
 47             "product_code": "FAST_INSTANT_TRADE_PAY",
 48             # "qr_pay_mode":4
 49         }
 50 
 51         biz_content.update(kwargs)
 52         data = self.build_body("alipay.trade.page.pay", biz_content, self.return_url)
 53         return self.sign_data(data)
 54 
 55     def build_body(self, method, biz_content, return_url=None):
 56         data = {
 57             "app_id": self.appid,
 58             "method": method,
 59             "charset": "utf-8",
 60             "sign_type": "RSA2",
 61             "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
 62             "version": "1.0",
 63             "biz_content": biz_content
 64         }
 65 
 66         if return_url is not None:
 67             data["notify_url"] = self.app_notify_url
 68             data["return_url"] = self.return_url
 69 
 70         return data
 71 
 72     def sign_data(self, data):
 73         data.pop("sign", None)
 74         # 排序后的字符串
 75         unsigned_items = self.ordered_data(data)
 76         unsigned_string = "&".join("{0}={1}".format(k, v) for k, v in unsigned_items)
 77         sign = self.sign(unsigned_string.encode("utf-8"))
 78         ordered_items = self.ordered_data(data)
 79         quoted_string = "&".join("{0}={1}".format(k, quote_plus(v)) for k, v in ordered_items)
 80 
 81         # 获得最终的订单信息字符串
 82         signed_string = quoted_string + "&sign=" + quote_plus(sign)
 83         return signed_string
 84 
 85     def ordered_data(self, data):
 86         complex_keys = []
 87         for key, value in data.items():
 88             if isinstance(value, dict):
 89                 complex_keys.append(key)
 90 
 91         # 将字典类型的数据dump出来
 92         for key in complex_keys:
 93             data[key] = json.dumps(data[key], separators=(',', ':'))
 94 
 95         return sorted([(k, v) for k, v in data.items()])
 96 
 97     def sign(self, unsigned_string):
 98         # 开始计算签名
 99         key = self.app_private_key
100         signer = PKCS1_v1_5.new(key)
101         signature = signer.sign(SHA256.new(unsigned_string))
102         # base64 编码,转换为unicode表示并移除回车
103         sign = encodebytes(signature).decode("utf8").replace("\n", "")
104         return sign
105 
106     def _verify(self, raw_content, signature):
107         # 开始计算签名
108         key = self.alipay_public_key
109         signer = PKCS1_v1_5.new(key)
110         digest = SHA256.new()
111         digest.update(raw_content.encode("utf8"))
112         if signer.verify(digest, decodebytes(signature.encode("utf8"))):
113             return True
114         return False
115 
116     def verify(self, data, signature):
117         if "sign_type" in data:
118             sign_type = data.pop("sign_type")
119         # 排序后的字符串
120         unsigned_items = self.ordered_data(data)
121         message = "&".join(u"{}={}".format(k, v) for k, v in unsigned_items)
122         return self._verify(message, signature)
123 
124 
125 if __name__ == "__main__":
126     alipay = AliPay(
127         appid="2016081500252338",
128         app_notify_url="http://projectsedus.com/",
129         app_private_key_path="keys/private_2048.txt",
130         alipay_public_key_path="keys/alipay_key_2048.txt",  # 支付宝的公钥,验证支付宝回传消息使用,不是你自己的公钥,
131         debug=True,  # 默认False,
132         return_url="http://192.168.247.129:8000/"
133     )
134 
135     url = alipay.direct_pay(
136         subject="测试订单",
137         out_trade_no="20170202126666",
138         total_amount=1000
139     )
140     re_url = "https://openapi.alipaydev.com/gateway.do?{data}".format(data=url)
141     print(re_url)

5、wxpay.py:

  1 # -*- coding=utf-8 -*-
  2 
  3 import time
  4 import json
  5 import hashlib
  6 import requests
  7 
  8 from pay.utils import (smart_str, dict_to_xml, calculate_sign, random_str,
  9                        post_xml, xml_to_dict, validate_post_xml, format_url)
 10 
 11 # from local_settings import appid, mch_id, api_key
 12 
 13 
 14 OAUTH2_AUTHORIZE_URL = "https://open.weixin.qq.com/connect/oauth2/authorize?%s"
 15 OAUTH2_ACCESS_TOKEN_URL = "https://api.weixin.qq.com/sns/oauth2/access_token?%s"
 16 
 17 
 18 class WeiXinPay(object):
 19     def __init__(self, appid, mch_id, api_key):
 20         self.appid = appid  # 微信公众号身份的唯一标识。审核通过后,在微信发送的邮件中查看
 21         self.mch_id = mch_id  # 受理商ID,身份标识
 22         self.api_key = api_key  # 商户支付密钥Key。审核通过后,在微信发送的邮件中查看
 23         self.common_params = {
 24             "appid": self.appid,
 25             "mch_id": self.mch_id,
 26         }
 27         self.params = {}
 28         self.url = ""
 29         self.trade_type = ""
 30 
 31     def set_params(self, **kwargs):
 32         self.params = {}
 33         for (k, v) in kwargs.items():
 34             self.params[k] = smart_str(v)
 35 
 36         self.params["nonce_str"] = random_str(32)
 37         if self.trade_type:
 38             self.params["trade_type"] = self.trade_type
 39         self.params.update(self.common_params)
 40 
 41     def post_xml(self):
 42         sign = calculate_sign(self.params, self.api_key)
 43         xml = dict_to_xml(self.params, sign)
 44         response = post_xml(self.url, xml)
 45         return xml_to_dict(response.text)
 46 
 47     def valiate_xml(self, xml):
 48         return validate_post_xml(xml, self.appid, self.mch_id, self.api_key)
 49 
 50     def get_error_code_desc(self, error_code):
 51         error_desc = {
 52             "SYSTEMERROR": u"接口后台错误",
 53             "INVALID_TRANSACTIONID": u"无效 transaction_id",
 54             "PARAM_ERROR": u"提交参数错误",
 55             "ORDERPAID": u"订单已支付",
 56             "OUT_TRADE_NO_USED": u"商户订单号重复",
 57             "NOAUTH": u"商户无权限",
 58             "NOTENOUGH": u"余额丌足",
 59             "NOTSUPORTCARD": u"不支持卡类型",
 60             "ORDERCLOSED": u"订单已关闭",
 61             "BANKERROR": u"银行系统异常",
 62             "REFUND_FEE_INVALID": u"退款金额大亍支付金额",
 63             "ORDERNOTEXIST": u"订单不存在",
 64         }
 65         return error_desc.get(error_code.strip().upper(), u"未知错误")
 66 
 67 
 68 class UnifiedOrderPay(WeiXinPay):
 69     """发送预支付单"""
 70 
 71     def __init__(self, appid, mch_id, api_key):
 72         super(UnifiedOrderPay, self).__init__(appid, mch_id, api_key)
 73         self.url = "https://api.mch.weixin.qq.com/pay/unifiedorder"
 74         self.trade_type = "NATIVE"
 75 
 76     def post(self, body, out_trade_no, total_fee, spbill_create_ip, notify_url, **kwargs):
 77         tmp_kwargs = {
 78             "body": body,
 79             "out_trade_no": out_trade_no,
 80             "total_fee": total_fee,
 81             "spbill_create_ip": spbill_create_ip,
 82             "notify_url": notify_url,
 83         }
 84         tmp_kwargs.update(**kwargs)
 85         self.set_params(**tmp_kwargs)
 86         return self.post_xml()[1]
 87 
 88 
 89 class OrderQuery(WeiXinPay):
 90     """订单状态查询"""
 91 
 92     def __init__(self, appid, mch_id, api_key):
 93         super(OrderQuery, self).__init__(appid, mch_id, api_key)
 94         self.url = "https://api.mch.weixin.qq.com/pay/orderquery"
 95 
 96     def post(self, out_trade_no):
 97         self.set_params(out_trade_no=out_trade_no)
 98         return self.post_xml()[1]
 99 
100 
101 class JsAPIOrderPay(UnifiedOrderPay):
102     """H5页面的Js调用类"""
103 
104     def __init__(self, appid, mch_id, api_key, app_secret):
105         super(JsAPIOrderPay, self).__init__(appid, mch_id, api_key)
106         self.app_secret = app_secret
107         self.trade_type = "JSAPI"
108 
109     def create_oauth_url_for_code(self, redirect_uri):
110         url_params = {
111             "appid": self.appid,
112             "redirect_uri": redirect_uri,  # 一般是回调当前页面
113             "response_type": "code",
114             "scope": "snsapi_base",
115             "state": "STATE#wechat_redirect"
116         }
117         url = format_url(url_params)
118         return OAUTH2_AUTHORIZE_URL % url
119 
120     def _create_oauth_url_for_openid(self, code):
121         url_params = {
122             "appid": self.appid,
123             "secret": self.app_secret,
124             "code": code,
125             "grant_type": "authorization_code",
126         }
127         url = format_url(url_params)
128         return OAUTH2_ACCESS_TOKEN_URL % url
129 
130     def _get_oauth_info(self, code):
131         """
132         获取OAuth2的信息:access_token、expires_in、refresh_token、openid、scope
133         返回结果为字典,可使用["xxx"]或.get("xxx", None)的方式进行读取
134         """
135         url = self._create_oauth_url_for_openid(code)
136         response = requests.get(url)
137         return response.json() if response else None
138 
139     def _get_openid(self, code):
140         oauth_info = self._get_oauth_info(code)
141         if oauth_info:
142             return oauth_info.get("openid", None)
143         return None
144 
145     def _get_json_js_api_params(self, prepay_id):
146         js_params = {
147             "appId": self.appid,
148             "timeStamp": "%d" % time.time(),
149             "nonceStr": random_str(32),
150             "package": "prepay_id=%s" % prepay_id,
151             "signType": "MD5",
152         }
153         js_params["paySign"] = calculate_sign(js_params, self.api_key)
154         return js_params
155 
156     def post(self, body, out_trade_no, total_fee, spbill_create_ip, notify_url, code):
157         if code:
158             open_id = self._get_openid(code)
159             if open_id:
160                 # 直接调用基类的post方法查询prepay_id,如果成功,返回一个字典
161                 unified_order = super(JsAPIOrderPay, self).post(body, out_trade_no, total_fee, spbill_create_ip,
162                                                                 notify_url, open_id=open_id)
163                 if unified_order:
164                     prepay_id = unified_order.get("prepay_id", None)
165                     if prepay_id:
166                         return self._get_json_js_api_params(prepay_id)
167         return None
View Code

6、utils.py:

  1 # -*- coding=utf-8 -*-
  2 import hashlib
  3 import re
  4 import types
  5 from random import Random
  6 import requests
  7 import thriftpy
  8 
  9 from django.conf import settings
 10 from django.core.exceptions import FieldDoesNotExist
 11 from django.db import models
 12 from django.db.models.fields.reverse_related import ForeignObjectRel
 13 from rest_framework.pagination import PageNumberPagination
 14 from thriftpy.rpc import make_client
 15 
 16 from pay.exception_handler import ForeignObjectRelDeleteError, ModelDontHaveIsActiveFiled, logger
 17 
 18 
 19 def smart_str(s, encoding='utf-8', strings_only=False, errors='strict'):
 20     """
 21     Returns a bytestring version of 's', encoded as specified in 'encoding'.
 22     If strings_only is True, don't convert (some) non-string-like objects.
 23     """
 24     if strings_only and isinstance(s, (types.NoneType, int)):
 25         return s
 26     if not isinstance(s, str):
 27         try:
 28             return str(s)
 29         except UnicodeEncodeError:
 30             if isinstance(s, Exception):
 31                 # An Exception subclass containing non-ASCII data that doesn't
 32                 # know how to print itself properly. We shouldn't raise a
 33                 # further exception.
 34                 return ' '.join([smart_str(arg, encoding, strings_only,
 35                                            errors) for arg in s])
 36             return unicode(s).encode(encoding, errors)
 37     elif s and encoding != 'utf-8':
 38         return s.decode('utf-8', errors).encode(encoding, errors)
 39     else:
 40         return s
 41 
 42 
 43 def format_url(params, api_key=None):
 44     url = "&".join(['%s=%s' % (key, smart_str(params[key])) for key in sorted(params)])
 45     if api_key:
 46         url = '%s&key=%s' % (url, api_key)
 47     return url
 48 
 49 
 50 def calculate_sign(params, api_key):
 51     # 签名步骤一:按字典序排序参数, 在string后加入KEY
 52     url = format_url(params, api_key)
 53     # 签名步骤二:MD5加密, 所有字符转为大写
 54     return hashlib.md5(url.encode('utf-8')).hexdigest().upper()
 55 
 56 
 57 def dict_to_xml(params, sign):
 58     xml = ["<xml>", ]
 59     for (k, v) in params.items():
 60         if (v.isdigit()):
 61             xml.append('<%s>%s</%s>' % (k, v, k))
 62         else:
 63             xml.append('<%s><![CDATA[%s]]></%s>' % (k, v, k))
 64     xml.append('<sign><![CDATA[%s]]></sign></xml>' % sign)
 65     return ''.join(xml)
 66 
 67 
 68 def xml_to_dict(xml):
 69     if xml[0:5].upper() != "<XML>" and xml[-6].upper() != "</XML>":
 70         return None, None
 71 
 72     result = {}
 73     sign = None
 74     content = ''.join(xml[5:-6].strip().split('\n'))
 75 
 76     pattern = re.compile(r"<(?P<key>.+)>(?P<value>.+)</(?P=key)>")
 77     m = pattern.match(content)
 78     while (m):
 79         key = m.group("key").strip()
 80         value = m.group("value").strip()
 81         if value != "<![CDATA[]]>":
 82             pattern_inner = re.compile(r"<!\[CDATA\[(?P<inner_val>.+)\]\]>")
 83             inner_m = pattern_inner.match(value)
 84             if inner_m:
 85                 value = inner_m.group("inner_val").strip()
 86             if key == "sign":
 87                 sign = value
 88             else:
 89                 result[key] = value
 90 
 91         next_index = m.end("value") + len(key) + 3
 92         if next_index >= len(content):
 93             break
 94         content = content[next_index:]
 95         m = pattern.match(content)
 96 
 97     return sign, result
 98 
 99 
100 def validate_post_xml(xml, appid, mch_id, api_key):
101     sign, params = xml_to_dict(xml)
102     if (not sign) or (not params):
103         return None
104 
105     remote_sign = calculate_sign(params, api_key)
106     if sign != remote_sign:
107         return None
108 
109     if params["appid"] != appid or params["mch_id"] != mch_id:
110         return None
111 
112     return params
113 
114 
115 def random_str(randomlength=8):
116     chars = 'abcdefghijklmnopqrstuvwxyz0123456789'
117     random = Random()
118     return "".join([chars[random.randint(0, len(chars) - 1)] for i in range(randomlength)])
119 
120 
121 def post_xml(url, xml):
122     return requests.post(url, data=xml.encode('utf-8'), verify=False)
123 
124 
125 class UnActiveModelMixin(object):
126     """
127     删除一个对象,并不真删除,级联将对应外键对象的is_active设置为false,需要外键对象都有is_active字段.
128     """
129 
130     def perform_destroy(self, instance):
131         rel_fileds = [f for f in instance._meta.get_fields() if isinstance(f, ForeignObjectRel)]
132 
133         links = [f.get_accessor_name() for f in rel_fileds]
134 
135         for link in links:
136             manager = getattr(instance, link, None)
137             if not manager:
138                 continue
139             if isinstance(manager, models.Model):
140                 if hasattr(manager, 'is_active') and manager.is_active:
141                     manager.is_active = False
142                     manager.save()
143                     raise ForeignObjectRelDeleteError(u'{} 上有关联数据'.format(link))
144             else:
145                 if not manager.count():
146                     continue
147                 try:
148                     manager.model._meta.get_field('is_active')
149                     manager.filter(is_active=True).update(is_active=False)
150                 except FieldDoesNotExist as ex:
151                     # 理论上,级联删除的model上面应该也有is_active字段,否则代码逻辑应该有问题
152                     logger.warn(ex)
153                     raise ModelDontHaveIsActiveFiled(
154                         '{}.{} 没有is_active字段, 请检查程序逻辑'.format(
155                             manager.model.__module__,
156                             manager.model.__class__.__name__
157                         ))
158         instance.is_active = False
159         instance.save()
160 
161     def get_queryset(self):
162         return self.queryset.filter(is_active=True)
163 
164 
165 class StandardResultsSetPagination(PageNumberPagination):
166     page_size_query_param = 'size'

 

posted on 2018-03-04 19:21  Fighting蔚  阅读(10505)  评论(1编辑  收藏  举报