Django實現支付寶付款和微信支付


支付寶支付和微信支付是當今互聯網產品常用的功能,我使用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'

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM