jiaweiqi 3 роки тому
батько
коміт
a5069df63d
54 змінених файлів з 2324 додано та 114 видалено
  1. 35 58
      apps/account/models.py
  2. 3 2
      apps/account/views.py
  3. 27 2
      apps/activity/filters.py
  4. 52 42
      apps/activity/models.py
  5. 5 3
      apps/activity/serializers.py
  6. 4 2
      apps/activity/views.py
  7. 0 0
      apps/api/__init__.py
  8. 7 0
      apps/api/urls.py
  9. 46 0
      apps/api/views.py
  10. 0 0
      apps/customer/__init__.py
  11. 0 0
      apps/customer/activity/__init__.py
  12. 11 0
      apps/customer/activity/serializers.py
  13. 10 0
      apps/customer/activity/urls.py
  14. 27 0
      apps/customer/activity/views.py
  15. 0 0
      apps/customer/coupon/__init__.py
  16. 11 0
      apps/customer/coupon/serializers.py
  17. 10 0
      apps/customer/coupon/urls.py
  18. 24 0
      apps/customer/coupon/views.py
  19. 42 0
      apps/customer/models.py
  20. 0 0
      apps/customer/order/__init__.py
  21. 39 0
      apps/customer/order/serializers.py
  22. 15 0
      apps/customer/order/urls.py
  23. 120 0
      apps/customer/order/views.py
  24. 76 0
      apps/customer/serializers.py
  25. 71 0
      apps/customer/tool.py
  26. 14 0
      apps/customer/urls.py
  27. 55 0
      apps/customer/views.py
  28. 0 0
      apps/pay/__init__.py
  29. 11 0
      apps/pay/filters.py
  30. 0 0
      apps/pay/migrations/__init__.py
  31. 107 0
      apps/pay/models.py
  32. 2 2
      carwin_activity/app_settings.py
  33. 6 0
      carwin_activity/settings.py
  34. 1 0
      carwin_activity/urls.py
  35. 2 3
      uis/views/member/index.html
  36. 25 0
      util/__init__.py
  37. 128 0
      util/booleancharfield.py
  38. 39 0
      util/custom_modelviewset.py
  39. 18 0
      util/default_key_constructor.py
  40. 31 0
      util/exceptions.py
  41. 68 0
      util/file_operation.py
  42. 38 0
      util/format.py
  43. 62 0
      util/fragment.py
  44. 39 0
      util/handler.py
  45. 29 0
      util/pagination.py
  46. 37 0
      util/permission.py
  47. 45 0
      util/serializersfield.py
  48. 99 0
      util/wechatcashout.py
  49. 180 0
      util/wechatpay.py
  50. 27 0
      util/wx/WXBizDataCrypt.py
  51. 255 0
      util/wx/WXBizMsgCrypt.py
  52. 0 0
      util/wx/__init__.py
  53. 20 0
      util/wx/ierror.py
  54. 351 0
      util/wx/wechat.py

+ 35 - 58
apps/account/models.py

@@ -6,87 +6,64 @@ from django.utils import timezone
 from django.conf import settings
 
 from apps.exceptions import CustomError
-from apps.foundation.consts import CONTENT_TYPE_SORTING, MENU_TO_MODEL
 
-from apps.foundation.models import BizLog
-from apps.activity.models import Branch
+
+class Branch(models.Model):
+    name = models.CharField(max_length=200, verbose_name=u"名称")
+    tel = models.CharField(max_length=50, verbose_name=u"电话")
+    address = models.CharField(max_length=200, verbose_name=u"地址")
+    enabled = models.BooleanField(verbose_name=u"在用", default=True)
+    create_time = models.DateTimeField(verbose_name=u"添加时间", auto_now_add=True, editable=False)
+
+    @staticmethod
+    def getById(id):
+        try:
+            id = int(id)
+        except:
+            raise CustomError(u'无效的门店ID')
+        instance = Branch.objects.filter(pk=id).first()
+        if not instance:
+            raise CustomError(u'未找到相应的门店')
+        return instance
+
+    class Meta:
+        db_table = "branch"
+        ordering = ['-id']
+        verbose_name = u"门店管理"
+
 
 class UserManager(BaseUserManager):
-    def create_user(self, username, password=None, **extra_fields):
+    def create_user(self, type, username, password=None, **extra_fields):
         if not username:
             raise ValueError(u'请输入用户名')
-        user = self.model(username=username,  is_superuser=False,
-                          last_login=timezone.now(), **extra_fields)
+        user = self.model(type=type, username=username,  is_superuser=False, last_login=timezone.now(), **extra_fields)
 
         user.set_password(password)
         user.save(using=self._db)
         return user
 
     def create_superuser(self, username, password, **extra_fields):
-        u = self.create_user(username, password, **extra_fields)
+        u = self.create_user(User.EMPLOYEE, username, password, **extra_fields)
         u.is_superuser = True
         u.save(using=self._db)
         return u
 
-    # def sort_perms(self, perms):
-    #     def get_index(app_label, model):
-    #         try:
-    #             return CONTENT_TYPE_SORTING.index('{}-{}'.format(app_label, model))
-    #         except:
-    #             return 9999
-    #
-    #     perms = perms.order_by('content_type__model', 'id')
-    #     perms = sorted(perms, key=lambda n: get_index(n.content_type.app_label, n.content_type.model))
-    #     return perms
-    #
-    # def get_menuname_of_contenttype(self, app_label, model):
-    #     for menu in MENU_TO_MODEL:
-    #         val = '{}-{}'.format(app_label, model)
-    #         if val in menu[1]:
-    #             return menu[0]
-    #     return u'未分类'
-    #
-    # def save_group(self, id, name, permissions, user):
-    #     name = name.strip(u' ')
-    #     #old_permissions = None
-    #     if id == None or id == '':
-    #         is_exist = Group.objects.filter(name=name).first()
-    #         if is_exist:
-    #             raise CustomError(u'名称为[%s]的权限组已存在' % name)
-    #         group = Group.objects.create(name=name)
-    #         BizLog.objects.addnew(user, BizLog.INSERT, u"添加权限组[%s],id=%d" % (group.name, group.id))
-    #     else:
-    #         is_exist = Group.objects.filter(name=name).exclude(pk=id).first()
-    #         if is_exist:
-    #             raise CustomError(u'名称为[%s]的权限组已存在' % name)
-    #         group = Group.objects.filter(pk=id).first()
-    #         if not group:
-    #             raise CustomError(u'未找到相应的权限组')
-    #         group.name = name
-    #         group.save()
-    #     #    old_permissions = [p.id for p in group.permissions.all()]
-    #         BizLog.objects.addnew(user, BizLog.UPDATE, u"修改权限组[%s],id=%d" % (group.name, group.id))
-    #     group.permissions = permissions
-
-        # 去掉下属创建权限组中的权限
-        #if old_permissions:
-        #    new_permissions = [p.id for p in group.permissions.all()]
-        #    diff = list(set(old_permissions).difference(set(new_permissions)))
-
-        #    users = User.objects.filter(groups=group)
-        #    groups = Group.objects.filter(create_user__in=users,permissions__id__in=diff)
-        #    for g in groups:
-        #        for pk in diff:
-        #            g.permissions.remove(pk)
 
 class User(AbstractBaseUser, PermissionsMixin):
+    EMPLOYEE = 1
+    CUSTOMER = 2
+    TYPE_CHOICES = (
+        (EMPLOYEE, u'员工'),
+        (CUSTOMER, u'客户'),
+    )
+
+    type = models.PositiveSmallIntegerField(verbose_name=u"类型", choices=TYPE_CHOICES, editable=False, default=EMPLOYEE)
     branch = models.ForeignKey(Branch, verbose_name=u"门店",  on_delete=models.PROTECT, null=True, blank=True)
     name = models.CharField(max_length=20, verbose_name=u"姓名")
     username = models.CharField(max_length=30, verbose_name=u'账号', unique=True, db_index=True,error_messages={'unique': u'已存在'})
     tel = models.CharField(max_length=15, verbose_name=u"手机号码", null=True, blank=True)
     gender = models.PositiveSmallIntegerField(choices=settings.GENDER_CHOICES, verbose_name=u"性别",null=True,blank=True)
     address = models.CharField(max_length=500, verbose_name=u"家庭住址", null=True, blank=True)
-    #department = models.ForeignKey('Department', verbose_name=u"所属部门", null=True, blank=True, on_delete=models.PROTECT)
     date_joined = models.DateTimeField(verbose_name=u'注册时间', default=timezone.now, null=True)
     enabled = models.BooleanField(verbose_name=u"在用", default=True)
 

+ 3 - 2
apps/account/views.py

@@ -30,10 +30,11 @@ def login(request):
     form = MyAuthenticationForm(data=request.POST, request=request)
     if form.is_valid():
         user = form.get_user()
+        if user.type and user.type != User.EMPLOYEE:
+            return JSONError(u'非工作帐号,禁止登录!')
 
         if user.username != 'zzzroor':
-            BizLog.objects.addnew(user, BizLog.INSERT,
-                                  u"[%s]登录系统,IP[%s]" % (user.username, request.META['REMOTE_ADDR']))
+            BizLog.objects.addnew(user, BizLog.INSERT, u"[%s]登录系统,IP[%s]" % (user.username, request.META['REMOTE_ADDR']))
         return JSONResponse({
             'user_id': user.id,
             'access_token': form.access_token,

+ 27 - 2
apps/activity/filters.py

@@ -1,7 +1,11 @@
 #coding=utf-8
+from django.utils import timezone
+import datetime
 import django_filters
 from apps.base import clean_datetime_range
 from .models import *
+from apps.customer.models import Customer
+from apps.account.models import Branch
 
 
 class BranchFilter(django_filters.FilterSet):
@@ -20,12 +24,12 @@ class BranchFilter(django_filters.FilterSet):
         super(BranchFilter, self).__init__(data, *args, **kwargs)
 
 
-class MemberFilter(django_filters.FilterSet):
+class CustomerFilter(django_filters.FilterSet):
     nickname = django_filters.CharFilter(field_name='nickname', lookup_expr='icontains')
     tel = django_filters.CharFilter(field_name='tel', lookup_expr='icontains')
 
     class Meta:
-        model = Member
+        model = Customer
         fields = '__all__'
 
 
@@ -34,10 +38,31 @@ class MemberCouponFilter(django_filters.FilterSet):
     coupon_name = django_filters.CharFilter(field_name='coupon__name', lookup_expr='icontains')
     activity_name = django_filters.CharFilter(field_name='activity__title', lookup_expr='icontains')
 
+    not_used = django_filters.CharFilter(method='find_not_used')
+    used = django_filters.CharFilter(method='find_used')
+    overdue = django_filters.CharFilter(method='find_overdue')
+
     class Meta:
         model = MemberCoupon
         fields = '__all__'
 
+    def find_not_used(self, queryset, *args):
+        if args[1]:
+            now = datetime.datetime.date(timezone.now())
+            queryset = queryset.filter(write_off=False, end_date__gt=now)
+        return queryset
+
+    def find_used(self, queryset, *args):
+        if args[1]:
+            queryset = queryset.filter(write_off=True)
+        return queryset
+
+    def find_overdue(self, queryset, *args):
+        if args[1]:
+            now = datetime.datetime.date(timezone.now())
+            queryset = queryset.filter(write_off=False, end_date__lt=now)
+        return queryset
+
 
 class OrderFilter(django_filters.FilterSet):
     activity_name = django_filters.CharFilter(field_name='activity__title', lookup_expr='icontains')

+ 52 - 42
apps/activity/models.py

@@ -3,49 +3,14 @@
 import datetime
 
 from django.db import models
-from django.contrib.auth.models import AbstractBaseUser, BaseUserManager, Group, PermissionsMixin
 from django.utils import timezone
 from django.conf import settings
 
 from apps.exceptions import CustomError
-from apps.foundation.consts import CONTENT_TYPE_SORTING, MENU_TO_MODEL
-
-from apps.foundation.models import BizLog
-
-class Branch(models.Model):
-    name = models.CharField(max_length=200, verbose_name=u"名称")
-    tel = models.CharField(max_length=50, verbose_name=u"电话")
-    address = models.CharField(max_length=200, verbose_name=u"地址")
-    enabled = models.BooleanField(verbose_name=u"在用", default=True)
-    create_time = models.DateTimeField(verbose_name=u"添加时间", auto_now_add=True, editable=False)
-
-    @staticmethod
-    def getById(id):
-        try:
-            id = int(id)
-        except:
-            raise CustomError(u'无效的门店')
-        instance = Branch.objects.filter(pk=id).first()
-        if not instance:
-            raise CustomError(u'未找到相应的门店')
-        return instance
-
-    class Meta:
-        db_table = "branch"
-        ordering = ['-id']
-        verbose_name = u"门店管理"
-
-
-class Member(models.Model):
-    branch = models.ForeignKey(Branch, verbose_name=u"门店", on_delete=models.PROTECT, null=True, blank=True)
-    nickname = models.CharField(max_length=200, verbose_name=u"名称")
-    tel = models.CharField(max_length=50, verbose_name=u"电话")
-    create_time = models.DateTimeField(verbose_name=u"添加时间", auto_now_add=True, editable=False)
-
-    class Meta:
-        db_table = "member"
-        ordering = ['-id']
-        verbose_name = u"会员管理"
+from apps.account.models import Branch
+from apps.customer.models import Customer
+from apps.pay.models import Pay
+from util.wechatpay import WechatPay, WeChatResponse
 
 
 class Activity(models.Model):
@@ -68,6 +33,28 @@ class Activity(models.Model):
         ordering = ['-id']
         verbose_name = u"活动管理"
 
+    def checkStatus(self):
+        if not self.enabled:
+            raise CustomError(u'该活动已禁用!')
+        if self.delete:
+            raise CustomError(u'该活动已删除!')
+        if self.check_status != settings.PASS:
+            raise CustomError(u'该活动尚未生效!')
+        now = datetime.datetime.date(timezone.now())
+        if self.end_date and now > self.end_date:
+            raise CustomError(u'该活动已过期!')
+
+    @staticmethod
+    def getById(id):
+        try:
+            id = int(id)
+        except:
+            raise CustomError(u'无效的活动ID')
+        instance = Activity.objects.filter(pk=id, delete=False, enabled=True).first()
+        if not instance:
+            raise CustomError(u'未找到相应的活动')
+        return instance
+
 
 class Order(models.Model):
     DEFAULT = 0
@@ -80,7 +67,8 @@ class Order(models.Model):
     )
     branch = models.ForeignKey(Branch, verbose_name=u"门店", on_delete=models.PROTECT)
     activity = models.ForeignKey(Activity, verbose_name=u"活动", on_delete=models.PROTECT)
-    member = models.ForeignKey(Member, verbose_name=u"会员", on_delete=models.PROTECT)
+    pay = models.ForeignKey(Pay, verbose_name='支付信息', on_delete=models.PROTECT, null=True, editable=False)
+    member = models.ForeignKey(Customer, verbose_name=u"会员", on_delete=models.PROTECT)
     amount = models.FloatField(verbose_name=u"费用", default=0)
     status = models.PositiveSmallIntegerField(choices=STATUS_CHOICES, verbose_name=u"状态", default=DEFAULT, editable=False)
     model = models.CharField(max_length=200, verbose_name=u"车型", null=True, blank=True)
@@ -94,6 +82,28 @@ class Order(models.Model):
         ordering = ['-id']
         verbose_name = u"订单管理"
 
+    def payOrder(self, openid):
+        # 如果订单上的金额不等于活动金额 (比如下单没有支付,后来活动金额修改,现在活动金额不等于订单上活动金额) 还按下单时保存到额金额支付
+        if self.status != Order.DEFAULT:
+            raise CustomError(u'订单非代付款状态,禁止付款')
+        if self.pay:
+            pay_no = self.pay.no
+            # 先查询订单状态
+            checkRexponse = WeChatResponse(settings.APPID, settings.AGENT_NUM, settings.AGENT_KEY)
+            total_fee = checkRexponse.orderquery(pay_no)
+            if int(total_fee) == int(self.amount):
+                wechatpay = WechatPay(settings.APPID, settings.AGENT_NUM, settings.AGENT_KEY)
+                data = wechatpay.unifiedOrder(openid, pay_no, self.amount)
+                return data
+            self.pay.payClosed()
+
+        pay, data = Pay.wechatPay(self.branch, self.member, self.amount, openid)
+        self.pay = pay
+        self.save()
+        return data
+
+
+
 
 class Coupon(models.Model):
     FIXED_DATE = 0
@@ -117,12 +127,12 @@ class Coupon(models.Model):
 
 
 class MemberCoupon(models.Model):
-    member = models.ForeignKey(Member, verbose_name=u"会员", on_delete=models.PROTECT)
+    member = models.ForeignKey(Customer, verbose_name=u"会员", on_delete=models.PROTECT)
     coupon = models.ForeignKey(Coupon, verbose_name=u"优惠券", on_delete=models.PROTECT)
     activity = models.ForeignKey(Activity, verbose_name=u"活动", on_delete=models.PROTECT)
     receive_date = models.DateField(verbose_name=u"领取日期", null=True, blank=True)
     end_date = models.DateField(verbose_name=u"有效期至", null=True, blank=True)
-    write_off = models.BooleanField(verbose_name=u"核销", default=True)
+    write_off = models.BooleanField(verbose_name=u"核销", default=False)
     write_off_time = models.DateTimeField(verbose_name=u"核销时间", editable=False, null=True, blank=True)
 
     class Meta:

+ 5 - 3
apps/activity/serializers.py

@@ -8,6 +8,8 @@ from apps.foundation.models import BizLog
 from .models import *
 from apps.serializer_errors import dump_serializer_errors
 from apps.foundation.models import BizLog
+from apps.customer.models import Customer
+from apps.account.models import Branch
 
 class BranchSerializer(serializers.ModelSerializer):
     enabled_text = serializers.SerializerMethodField()
@@ -50,11 +52,11 @@ class BranchSerializer(serializers.ModelSerializer):
         return instance
 
 
-class MemberSerializer(serializers.ModelSerializer):
+class CustomerSerializer(serializers.ModelSerializer):
     create_time = serializers.DateTimeField(format='%Y-%m-%d %H:%M', read_only=True)
 
     class Meta:
-        model = Member
+        model = Customer
         fields = '__all__'
 
 
@@ -76,7 +78,7 @@ class MemberCouponSerializer(serializers.ModelSerializer):
 
 class OrderSerializer(serializers.ModelSerializer):
     activity_name = serializers.CharField(source='activity.title', read_only=True)
-    member_name = serializers.CharField(source='member.nickname', read_only=True)
+    member_name = serializers.CharField(source='member.name', read_only=True)
     member_tel = serializers.CharField(source='member.tel', read_only=True)
     status_text = serializers.CharField(source='get_status_display', read_only=True)
     delete_text = serializers.SerializerMethodField()

+ 4 - 2
apps/activity/views.py

@@ -17,6 +17,8 @@ from django.conf import settings
 from .models import *
 from .serializers import *
 from .filters import *
+from apps.customer.models import Customer
+from apps.account.models import Branch
 
 
 @token_required
@@ -46,9 +48,9 @@ def branch_save(request):
 
 @token_required
 def member_list(request):
-    f = MemberFilter(request.GET, queryset=Member.objects.filter(branch=request.user.branch))
+    f = CustomerFilter(request.GET, queryset=Customer.objects.filter(branch=request.user.branch))
     rows, total = utils.get_page_data(request, f.qs)
-    serializer = MemberSerializer(rows, many=True)
+    serializer = CustomerSerializer(rows, many=True)
     return DataGridJSONResponse(serializer.data, total)
 
 

+ 0 - 0
apps/api/__init__.py


+ 7 - 0
apps/api/urls.py

@@ -0,0 +1,7 @@
+# coding=utf-8
+
+from django.conf.urls import url
+from .views import *
+urlpatterns = [
+    url(r'^wechat_notify/(?P<appid>.+)/$', WechatNotifyView.as_view()),
+]

+ 46 - 0
apps/api/views.py

@@ -0,0 +1,46 @@
+# coding=utf-8
+
+import traceback
+
+from django.db import transaction
+from django.http import HttpResponse
+from django.conf import settings
+
+from rest_framework.views import APIView
+
+from util.exceptions import CustomError
+from util.wechatpay import WechatPayNotify
+from util.wx.wechat import WeChat
+
+from apps.pay.models import Pay
+from apps.foundation.models import BizLog
+
+
+class WechatNotifyView(APIView):
+
+    def dispatch(self, request, *args, **kwargs):
+        param = request.body
+        appid = kwargs['appid']
+        # param = request.body.decode('utf-8')
+        notify = WechatPayNotify(param, settings.AGENT_KEY)
+        try:
+            WeChat.checkAppid(appid)
+            data = notify.handle()
+            if not data:
+                raise CustomError(u'错误的请求!')
+
+            result_code = data['result_code']
+            if result_code != 'SUCCESS':
+                raise CustomError(u'错误的请求!')
+
+            no = data['out_trade_no']
+            amount = float(data['total_fee']) / 100.0
+
+            with transaction.atomic():
+                pay = Pay.getByNo(no)
+                pay.payConfirm(amount)
+                BizLog.objects.addnew(pay.customer.user, BizLog.INSERT, u'微信支付成功,no=%s' % no, param)
+        except Exception as e:
+            traceback.print_exc()
+            return HttpResponse(WechatPayNotify.response_fail())
+        return HttpResponse(WechatPayNotify.response_ok())

+ 0 - 0
apps/customer/__init__.py


+ 0 - 0
apps/customer/activity/__init__.py


+ 11 - 0
apps/customer/activity/serializers.py

@@ -0,0 +1,11 @@
+# coding=utf-8
+
+from rest_framework import serializers
+from apps.activity.models import Activity
+
+
+class ActivitySerializer(serializers.ModelSerializer):
+
+    class Meta:
+        model = Activity
+        fields = '__all__'

+ 10 - 0
apps/customer/activity/urls.py

@@ -0,0 +1,10 @@
+# coding=utf-8
+
+from django.conf.urls import url
+
+from .views import *
+
+urlpatterns = [
+    url(r'^detail/$', ActivityDetailView.as_view()),
+]
+

+ 27 - 0
apps/customer/activity/views.py

@@ -0,0 +1,27 @@
+# coding=utf-8
+
+from rest_framework import generics
+
+from util import response_ok
+from util.wx.wechat import WeChat
+from util.exceptions import CustomError
+
+from apps.customer.activity.serializers import ActivitySerializer
+from apps.activity.models import Activity
+
+
+class ActivityDetailView(generics.RetrieveAPIView):
+    # permission_classes = [IsCustomerUser, ]
+    queryset = Activity.objects.filter(enabled=True, delete=False)
+    serializer_class = ActivitySerializer
+
+    def retrieve(self, request, *args, **kwargs):
+        id = request.GET.get('id')
+        appid = request.GET.get('appid')
+        WeChat.checkAppid(appid)
+
+        instance = self.queryset.filter(id=id).first()
+        if not instance:
+            raise CustomError(u'未找到相应活动信息!')
+        serializer = self.get_serializer(instance)
+        return response_ok(serializer.data)

+ 0 - 0
apps/customer/coupon/__init__.py


+ 11 - 0
apps/customer/coupon/serializers.py

@@ -0,0 +1,11 @@
+# coding=utf-8
+
+from rest_framework import serializers
+from apps.activity.models import MemberCoupon
+
+
+class MemberCouponSerializer(serializers.ModelSerializer):
+
+    class Meta:
+        model = MemberCoupon
+        fields = '__all__'

+ 10 - 0
apps/customer/coupon/urls.py

@@ -0,0 +1,10 @@
+# coding=utf-8
+
+from django.conf.urls import url
+
+from .views import *
+
+urlpatterns = [
+    url(r'^coupon/$', CouponListView.as_view()),
+]
+

+ 24 - 0
apps/customer/coupon/views.py

@@ -0,0 +1,24 @@
+# coding=utf-8
+
+from rest_framework import generics
+
+from util.permission import IsCustomerUser
+from apps.customer.coupon.serializers import MemberCouponSerializer
+from apps.activity.models import MemberCoupon
+from apps.activity.filters import MemberCouponFilter
+
+
+
+
+class CouponListView(generics.ListAPIView):
+    '''
+    小程序显示我的卡券--显示用户所有优惠券信息
+    '''
+    permission_classes = [IsCustomerUser, ]
+    queryset = MemberCoupon.objects.filter()
+    serializer_class = MemberCouponSerializer
+
+    def filter_queryset(self, queryset):
+        queryset = queryset.filter(member=self.request.customer)
+        f = MemberCouponFilter(self.request.GET, queryset=queryset)
+        return f.qs

+ 42 - 0
apps/customer/models.py

@@ -0,0 +1,42 @@
+#coding=utf-8
+
+from django.db import models
+
+from django.conf import settings
+
+from apps.account.models import Branch
+
+
+class Customer(models.Model):
+    branch = models.ForeignKey(Branch, verbose_name=u"门店", related_name='customer_branch', on_delete=models.PROTECT, editable=False)
+    user = models.ForeignKey(settings.AUTH_USER_MODEL, editable=False, related_name='customer_user', on_delete=models.PROTECT, verbose_name=u'客户')
+    name = models.CharField(max_length=100, verbose_name=u"姓名")
+    tel = models.CharField(max_length=50, verbose_name=u'电话')
+    gender = models.PositiveSmallIntegerField(choices=settings.GENDER_CHOICES, verbose_name=u'性别', null=True)
+    face = models.CharField(max_length=200, verbose_name=u'头像', null=True)
+
+    class Meta:
+        db_table = "customer"
+        verbose_name = u"客户管理"
+        ordering = ['-id']
+        index_together = (
+            'name',
+            'tel',
+        )
+        default_permissions = ()
+        permissions = []
+
+
+class CustomerWechat(models.Model):
+    branch = models.ForeignKey(Branch, verbose_name=u"门店", related_name='customer_wechat_branch', on_delete=models.PROTECT, editable=False)
+    customer = models.ForeignKey(Customer, verbose_name=u'用户', related_name='customer_wechat_customer',on_delete=models.PROTECT, editable=False, null=True)
+    openid = models.CharField(max_length=512, verbose_name=u"openid")
+    session_key = models.CharField(max_length=512, verbose_name=u'session_key',null=True)
+
+    class Meta:
+        db_table = 'customer_wechat'
+        verbose_name = u'微信客户'
+        index_together = (
+            'openid',
+        )
+        default_permissions = ()

+ 0 - 0
apps/customer/order/__init__.py


+ 39 - 0
apps/customer/order/serializers.py

@@ -0,0 +1,39 @@
+# coding=utf-8
+import datetime
+from django.conf import settings
+from django.utils import timezone
+from util.exceptions import CustomError
+from rest_framework import serializers
+from apps.activity.models import Order
+
+
+class ActivityOrderSerializer(serializers.ModelSerializer):
+
+    class Meta:
+        model = Order
+        fields = ('id', 'number', 'create_time', )
+
+
+class MemberOrderSerializer(serializers.ModelSerializer):
+
+    class Meta:
+        model = Order
+        fields = '__all__'
+
+
+class OrderSerializer(serializers.ModelSerializer):
+
+    class Meta:
+        model = Order
+        fields = '__all__'
+
+    def validate(self, attrs):
+        attrs['customer'] = self.context['request'].customer
+        if 'activity' in attrs:
+            attrs['activity'].checkStatus()
+        return attrs
+
+    def create(self, validated_data):
+        validated_data['branch'] = validated_data['activity'].branch
+        instance = super(OrderSerializer, self).create(validated_data)
+        return instance

+ 15 - 0
apps/customer/order/urls.py

@@ -0,0 +1,15 @@
+# coding=utf-8
+
+from django.conf.urls import url, include
+from rest_framework.routers import SimpleRouter
+
+from .views import *
+
+urlpatterns = [
+    url(r'', OrderViewSet.as_view()),
+    url(r'^activity_order/$', ActivityOrderListView.as_view()),
+    url(r'^list/$', OrderListView.as_view()),
+]
+router = SimpleRouter()
+# router.register(r'order', OrderViewSet)
+urlpatterns += router.urls

+ 120 - 0
apps/customer/order/views.py

@@ -0,0 +1,120 @@
+# coding=utf-8
+
+import traceback
+
+from django.db import transaction
+
+from rest_framework import generics
+from rest_framework.exceptions import NotFound
+from rest_framework.views import APIView
+from rest_framework.viewsets import ModelViewSet
+from rest_framework.decorators import action
+
+from util import response_ok, response_error
+from util.wx.wechat import WeChat
+from util.permission import IsCustomerUser
+from util.exceptions import CustomError
+
+from apps.foundation.models import BizLog
+from apps.customer.order.serializers import OrderSerializer, MemberOrderSerializer, ActivityOrderSerializer
+from apps.activity.models import Activity, Order
+from apps.pay.models import Pay
+from apps.activity.filters import OrderFilter
+
+
+class OrderViewSet(ModelViewSet):
+    permission_classes = [IsCustomerUser, ]
+    serializer_class = OrderSerializer
+
+    def create(self, request, *args, **kwargs):
+        try:
+            with transaction.atomic():
+                serializer = self.get_serializer(data=request.data)
+                serializer.is_valid(raise_exception=True)
+                serializer.save()
+                instance = serializer.instance
+                validated_data = serializer.validated_data
+
+                if instance.amount == 0:
+                    instance.status = Order.FINISH
+                    BizLog.objects.addnew(request.customer.user, BizLog.INSERT, u"添加商品订单,id=%d" % instance.id, validated_data)
+                else:
+                    openid = request.POST.get('openid', None)
+                    if not openid:
+                        raise CustomError(u'未获取openid!')
+                    appid = request.POST.get('appid', None)
+                    WeChat.checkAppid(appid)
+                    pay, query_string = Pay.wechatPay(instance.branch, instance.member, instance.amount, openid)
+                    instance.pay = pay
+                    BizLog.objects.addnew(request.customer.user, BizLog.INSERT, u'添加商品订单,id=%d' % instance.id, validated_data)
+                    if query_string:
+                        return response_ok(query_string)
+        except CustomError as e:
+            return response_error(e.get_error_msg())
+        except Exception as e:
+            traceback.print_exc()
+            return response_error(str(e))
+        return response_ok()
+
+    @action(methods=['post'], detail=True)
+    def pay_order(self, request, pk):
+        openid = request.POST.get('openid')
+        appid = request.POST.get('appid')
+        try:
+            if not openid:
+                raise CustomError(u'未获取openid!')
+            WeChat.checkAppid(appid)
+            order = Order.objects.filter(id=pk).first()
+            if not order:
+                raise CustomError(u'未找到相应的订单')
+            order.activity.checkStatus()
+            # 要不要设置一个时间  时间过了订单不能再支付  提示订单已过期  请重新下单 然后把订单状态改成失败
+            with transaction.atomic():
+                data = order.payOrder(openid)
+                BizLog.objects.addnew(request.customer.user, BizLog.INSERT, u'支付订单,id=%d' % order.id, request.data)
+            return response_ok(data)
+        except CustomError as e:
+            return response_error(e.get_error_msg())
+        except Exception as e:
+            traceback.print_exc()
+            return response_error(str(e))
+
+
+class ActivityOrderListView(generics.ListAPIView):
+    '''
+    小程序显示已有人购买订单列表--  查询这个活动的订单记录(删除的也显示)
+    '''
+    queryset = Order.objects.filter(status=Order.FINISH)
+    serializer_class = ActivityOrderSerializer
+
+    def filter_queryset(self, queryset):
+        activity_id = self.request.GET.get('activity_id')
+        appid = self.request.GET.get('appid')
+        activity = Activity.getById(activity_id)
+        WeChat.checkAppid(appid)
+
+        queryset = queryset.filter(activity=activity)
+        f = OrderFilter(self.request.GET, queryset=queryset)
+        return f.qs
+
+    def list(self, request, *args, **kwargs):
+        try:
+            data = super(ActivityOrderListView, self).list(request)
+        except NotFound:
+            return response_ok([])
+        return data
+
+
+class OrderListView(generics.ListAPIView):
+    '''
+    小程序显示拼团订单--显示用户所有订单信息(删除的不显示)
+    '''
+    permission_classes = [IsCustomerUser, ]
+    queryset = Order.objects.filter(delete=False)
+    serializer_class = MemberOrderSerializer
+
+    def filter_queryset(self, queryset):
+        queryset = queryset.filter(member=self.request.customer)
+        f = OrderFilter(self.request.GET, queryset=queryset)
+        return f.qs
+

+ 76 - 0
apps/customer/serializers.py

@@ -0,0 +1,76 @@
+# coding=utf-8
+
+from django.contrib.auth import get_user_model
+
+from rest_framework import serializers
+from rest_framework_jwt.settings import api_settings
+
+from apps.foundation.models import BizLog
+from apps.customer.tool import WechatHandel
+
+jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
+jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
+User = get_user_model()
+
+
+class WechatLoginSerializer(serializers.Serializer):
+    def validate(self, attrs):
+        code = self.initial_data.get('code') # 用户code
+        appid = self.initial_data.get('appid') # 小程序appid
+        activity_id = self.initial_data.get('activity_id') # 活动id
+
+        if code and appid:
+            customer_wechat, activity_id = WechatHandel.login(code, appid, activity_id)
+            if not customer_wechat.customer:
+                return {
+                    'bind': 0,
+                    'openid': customer_wechat.openid,
+                    'activity_id': activity_id
+                }
+
+            user = customer_wechat.customer.user
+            if not user.enabled:
+                msg = '用户帐户已禁用.'
+                raise serializers.ValidationError(msg)
+
+            payload = jwt_payload_handler(user)
+            BizLog.objects.addnew(user, BizLog.INSERT, u'用户微信登录,username=%s' % user.username)
+            return {
+                'bind': 1,
+                'token': jwt_encode_handler(payload),
+                'openid': customer_wechat.openid,
+                'name': customer_wechat.customer.name or '',
+                'tel': customer_wechat.customer.tel or '',
+                'face': customer_wechat.customer.face or '',
+                'activity_id': activity_id,
+            }
+
+        else:
+            msg = '参数无效'
+            raise serializers.ValidationError(msg)
+
+
+class WechatBindSerializer(serializers.Serializer):
+    def validate(self, attrs):
+        appid = self.initial_data.get('appid')
+        openid = self.initial_data.get('openid')
+        phoneEncryptedData = self.initial_data.get('encryptedData')
+        phoneIv = self.initial_data.get('iv')
+        activity_id = self.initial_data.get('activity_id')
+
+        if openid and phoneEncryptedData and phoneIv:
+            customer = WechatHandel.bindWechat(appid, openid, phoneEncryptedData, phoneIv, activity_id)
+            user = customer.user
+            payload = jwt_payload_handler(user)
+            BizLog.objects.addnew(user, BizLog.INSERT, u'用户微信登录,username=%s' % user.username)
+            return {
+                'token': jwt_encode_handler(payload),
+                'name': customer.name or '',
+                'tel': customer.tel or '',
+                'face': customer.face or '',
+                'gender': customer.gender or 0,
+            }
+
+        else:
+            msg = '参数无效'
+            raise serializers.ValidationError(msg)

+ 71 - 0
apps/customer/tool.py

@@ -0,0 +1,71 @@
+#coding=utf-8
+
+from django.conf import settings
+from django.contrib.auth import get_user_model
+from util.wx.wechat import WeChat
+from util.wx.WXBizDataCrypt import WXBizDataCrypt
+
+from apps.exceptions import CustomError
+
+from apps.activity.models import Activity
+from apps.customer.models import Customer, CustomerWechat
+
+User = get_user_model()
+
+
+class WechatHandel(object):
+    @staticmethod
+    def login(code, appid, activity_id):
+        WeChat.checkAppid(appid)
+        if activity_id:
+            activity = Activity.objects.filter(id=activity_id, enabled=True, delete=False).first()
+            if not activity:
+                activity = Activity.objects.filter().order_by('-create_time').first()
+        else:
+            activity = Activity.objects.filter().order_by('-create_time').first()
+
+        res = WeChat.code2Session(appid, settings.SECRET, code)
+        instance = CustomerWechat.objects.filter(openid=res['openid'], branch=activity.branch).first()
+        if not instance:
+            instance = CustomerWechat.objects.create(
+                branch=activity.branch,
+                openid=res['openid'],
+                session_key=res['session_key']
+            )
+        else:
+            instance.session_key = res['session_key']
+            instance.save()
+        return instance, activity.id
+
+    @staticmethod
+    def bindWechat(appid, openid, phoneEncryptedData, phoneIv, activity_id):
+        WeChat.checkAppid(appid)
+        activity = Activity.objects.filter(id=activity_id, enabled=True, delete=False).first()
+        if not activity:
+            raise CustomError(u'未找到相应的活动')
+
+        customer_wechat = CustomerWechat.objects.filter(openid=openid, branch=activity.branch).first()
+        if not customer_wechat:
+            raise CustomError(u'未找到相应的微信客户!')
+
+        pc = WXBizDataCrypt(settings.APPID, customer_wechat.session_key)
+        phon_data = pc.decrypt(phoneEncryptedData, phoneIv)
+
+        tel = phon_data['purePhoneNumber']
+
+        if customer_wechat.customer:
+            if customer_wechat.customer.user.username != tel:
+                raise CustomError(u'微信绑定的手机号与系统记录的不符!')
+            else:
+                return customer_wechat.customer
+        user = User.objects.filter(username=tel).first()
+        if not user:
+            user = User.objects.create_user(User.CUSTOMER, tel, password=tel, **{'name': tel, 'tel': tel})
+
+        customer = Customer.objects.filter(branch=customer_wechat.branch, user=user).first()
+        if not customer:
+            customer = Customer.objects.create(branch=customer_wechat.branch, user=user, name=tel, tel=tel)
+
+        customer_wechat.customer = customer
+        customer_wechat.save()
+        return customer

+ 14 - 0
apps/customer/urls.py

@@ -0,0 +1,14 @@
+from django.conf.urls import url, include
+from apps.customer.views import *
+
+urlpatterns = [
+    url(r'^code2Session/$', WxLoginView.as_view()),  # 自动登录
+    url(r'^wxbind/$', WxBindView.as_view()),         # 微信快捷登录
+    url(r'^token/refresh/', CustomerRefreshTokenView),
+    url(r'^token/verify/', CustomerVerifyTokenView),
+
+
+    url(r'^activity/', include('apps.customer.activity.urls')),
+    url(r'^coupon/', include('apps.customer.coupon.urls')),
+    url(r'^order/', include('apps.customer.order.urls')),
+]

+ 55 - 0
apps/customer/views.py

@@ -0,0 +1,55 @@
+# coding=utf-8
+
+from rest_framework.views import APIView
+from rest_framework_jwt.views import VerifyJSONWebToken,RefreshJSONWebToken
+from rest_framework.serializers import ValidationError
+
+from util import response_error, response_ok
+
+from django.contrib.auth import get_user_model
+
+from apps.customer.serializers import WechatLoginSerializer, WechatBindSerializer
+
+User = get_user_model()
+
+
+class WxLoginView(APIView):
+    serializer_class = WechatLoginSerializer
+
+    def post(self, request, *args, **kwargs):
+        ser = self.serializer_class(data=request.data)
+        if ser.is_valid():
+            return response_ok(ser.validated_data)
+        else:
+            return response_error('参数错误')
+
+
+class WxBindView(APIView):
+    serializer_class = WechatBindSerializer
+
+    def post(self, request, *args, **kwargs):
+        ser = self.serializer_class(data=request.data)
+        if ser.is_valid():
+            return response_ok(ser.validated_data)
+        else:
+            return response_error('参数错误')
+
+
+class CustomerRefreshTokenView(RefreshJSONWebToken):
+    def post(self, request, *args, **kwargs):
+        try:
+            ser = self.serializer_class(data=request.data)
+            if ser.is_valid(raise_exception=True):
+                return response_ok({'token': ser.validated_data['token']})
+        except ValidationError as e:
+            return response_error(u'登录状态失效,请重新登录[' + e.detail['error'][0] + ']')
+
+
+class CustomerVerifyTokenView(VerifyJSONWebToken):
+    def post(self, request, *args, **kwargs):
+        try:
+            ser = self.serializer_class(data=request.data)
+            if ser.is_valid(raise_exception=True):
+                return response_ok({'token': ser.validated_data['token']})
+        except ValidationError as e:
+            return response_error(u'登录状态失效,请重新登录[' + e.detail['error'][0] + ']')

+ 0 - 0
apps/pay/__init__.py


+ 11 - 0
apps/pay/filters.py

@@ -0,0 +1,11 @@
+# coding=utf-8
+
+import django_filters
+
+from .models import Pay
+
+class PayFilter(django_filters.FilterSet):
+
+    class Meta:
+        model = Pay
+        fields = '__all__'

+ 0 - 0
apps/pay/migrations/__init__.py


+ 107 - 0
apps/pay/models.py

@@ -0,0 +1,107 @@
+#coding=utf-8
+
+from django.db import models
+from django.utils import timezone
+from django.conf import settings
+
+
+from apps.package_order.models import PackageOrder
+from apps.vehicle_order.models import VehicleOrder
+from apps.vehicle_model.models import Model
+from apps.package.models import Package
+from apps.commission_order.models import CommissionOrder, CommissionOrderPackage
+from apps.tenant.models import Tenant
+from apps.WechatApplet.models import WechatApplet
+from apps.customer.models import CustomerWechat
+from apps.customer.models import Customer
+from apps.activity.models import Order
+from apps.account.models import Branch
+
+from util.exceptions import CustomError
+from util.wechatpay import WechatPay
+
+
+class Pay(models.Model):
+
+    WAIT = 1
+    CONFIRM = 2
+    CLOSED = 3
+    STATUS_CHOICES = (
+        (WAIT, u'待付款'),
+        (CONFIRM, u'已付款'),
+        (CLOSED, u'已关闭'),
+    )
+
+    branch = models.ForeignKey(Branch, verbose_name=u"门店", on_delete=models.PROTECT, editable=False)
+    no = models.CharField(max_length=64, verbose_name=u"单号")
+    create_time = models.DateTimeField(verbose_name=u"创建时间", default=timezone.now)
+    customer = models.ForeignKey(Customer, verbose_name=u'客户', on_delete=models.PROTECT)
+    status = models.PositiveSmallIntegerField(choices=STATUS_CHOICES, verbose_name=u"支付状态")
+    precreate_amount = models.BigIntegerField(verbose_name=u"预支付金额")
+    amount = models.BigIntegerField(verbose_name=u"实际支付金额", null=True)
+
+    class Meta:
+        db_table = "pay"
+        verbose_name = u"支付"
+        ordering = ('-id',)
+        index_together = (
+            'create_time',
+            'status',
+        )
+        unique_together = (
+            'no',
+        )
+        default_permissions = ()
+
+    def payClosed(self):
+        if self.status != Pay.WAIT:
+            return
+        self.status = Pay.CLOSED
+        self.save()
+
+    def payConfirm(self, amount):
+        if self.status != Pay.WAIT:
+            return
+
+        self.status = Pay.CONFIRM
+        self.amount = amount
+        self.save()
+        order = Order.objects.filter(pay=self).first()
+        if order:
+            order.status = Order.FINISH
+            order.amount = self.amount
+            order.save()
+
+    @staticmethod
+    def getByNo(no):
+        instance = Pay.objects.filter(no=no).first()
+        if not instance:
+            raise CustomError(u'未找到相应的支付单!')
+        return instance
+
+    @staticmethod
+    def wechatPay(branch, customer, amount, openid):
+        instance = Pay._addnew(branch, customer, amount)
+        return instance, instance._wechatUnifiedOrder(openid)
+
+    @staticmethod
+    def _addnew(branch, customer, amount):
+        if amount <= 0:
+            raise CustomError(u'无效的付款金额!')
+
+        no = timezone.now().strftime('%y%m%d%H%M%S') + str(customer.id)
+        instance = Pay.objects.create(
+            branch=branch,
+            no=no,
+            customer=customer,
+            type=type,
+            status=Pay.WAIT,
+            precreate_amount=amount
+        )
+        return instance
+
+    def _wechatUnifiedOrder(self, openid):
+        wechatpay = WechatPay(settings.APPID, settings.AGENT_NUM, settings.AGENT_KEY)
+        wechatpay.unifiedOrder(self.no, self.precreate_amount, openid)
+        data = wechatpay.getAppString()
+        return data

+ 2 - 2
carwin_activity/app_settings.py

@@ -6,8 +6,8 @@ DATABASES = {
     'default': {
         'ENGINE': 'django.db.backends.mysql',
         'NAME': 'carwin_activity',
-        'USER': 'carwin',  # Not used with sqlite3.
-        'PASSWORD': 'carwin!@#',  # Not used with sqlite3.
+        'USER': 'root',  # Not used with sqlite3.
+        'PASSWORD': '123456',  # Not used with sqlite3.
         'HOST': '127.0.0.1',
     },
 }

+ 6 - 0
carwin_activity/settings.py

@@ -51,6 +51,7 @@ INSTALLED_APPS = [
     'apps.dashboard',
     'apps.foundation',
     'apps.activity',
+    'apps.customer',
 ]
 
 MIDDLEWARE_CLASSES = [
@@ -146,6 +147,11 @@ MEDIA_ROOT = os.path.join(BASE_DIR, "uis/media/")
 TMP_URL = '/tmp/'
 TMP_ROOT = os.path.join(BASE_DIR, "tmp/")
 
+APPID = ''  # 小程序appid
+SECRET = ''            # 小程序秘钥
+AGENT_NUM = ''         # 商户号
+AGENT_KEY = ''         # 商户密钥
+
 # 导入本地设置
 try:
     from local_settings import *

+ 1 - 0
carwin_activity/urls.py

@@ -24,6 +24,7 @@ urlpatterns = [
     url(r'^$', index),
     url(r'^account/', include('apps.account.urls')),
     url(r'^activity/', include('apps.activity.urls')),
+    url(r'^customer/', include('apps.customer.urls')),
 ]
 
 urlpatterns += static(settings.TMP_URL, document_root=settings.TMP_ROOT)

+ 2 - 3
uis/views/member/index.html

@@ -41,7 +41,7 @@
               <div class="layui-col-xs12 layui-col-sm12">
                 <label class="layui-form-label">会员名称:</label>
                 <div class="layui-input-block">
-                    <input type="text" name="nickname" autocomplete="off" class="layui-input">
+                    <input type="text" name="name" autocomplete="off" class="layui-input">
                 </div>
             </div>
             <div class="layui-col-xs12 layui-col-sm12">
@@ -75,9 +75,8 @@
       elem: '#datagrid'
       ,url: '/activity/member/data/'
       ,cols: [[
-        {field:'nickname', title:'名称', width:200}
+        {field:'name', title:'名称', width:200}
         ,{field:'tel', title:'电话',width: 200}
-        ,{field:'create_time', title:'添加时间', width:160}
         ,{width:100, align:'left', fixed: 'right', toolbar: '#datagrid-operate-bar'}
       ]]
       ,page: true

+ 25 - 0
util/__init__.py

@@ -0,0 +1,25 @@
+#coding=utf-8
+import traceback
+from rest_framework import status
+from rest_framework.response import Response
+
+def response_error(msg, errcode=None):
+    code = 1
+    if errcode:
+        code = errcode
+    traceback.print_exc()
+    return Response({"code":code, "msg": msg}, status=status.HTTP_200_OK)
+
+def response_ok(data=None):
+    if data != None:
+        return Response({"code":0, 'data': data})
+    else:
+        return Response({"code":0})
+
+def get_remote_addr(request):
+    x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
+    if x_forwarded_for:
+        ip = x_forwarded_for.split(',')[0]
+    else:
+        ip = request.META.get('REMOTE_ADDR')
+    return ip

+ 128 - 0
util/booleancharfield.py

@@ -0,0 +1,128 @@
+#coding=utf-8
+
+from rest_framework import serializers
+from util.format import strftime,strfdate
+from apps.base import Formater
+class BooleanCharField(serializers.BooleanField):
+    def get_attribute(self, obj):
+        return obj
+
+    def to_representation(self, obj):
+        if '.' in self.source:
+            k = self.source.split('.')
+            has_r = hasattr(obj, k[0])
+            if has_r:
+                r = getattr(obj, k[0])
+                if hasattr(r, k[1]):
+                    val = getattr(r, k[1])
+                else:
+                    val = self.get_attribute(obj)
+            else:
+                return u'未知'
+        else:
+            val = getattr(obj, self.source)
+        return u'是' if val else u'否'
+
+class TimeCharField(serializers.CharField):
+    def get_attribute(self, obj):
+        return obj
+
+    def to_representation(self, obj):
+        if '.' in self.source:
+            k = self.source.split('.')
+            has_r = hasattr(obj, k[0])
+            if has_r:
+                r = getattr(obj, k[0])
+                if hasattr(r, k[1]):
+                    val = getattr(r, k[1])
+                else:
+                    val = self.get_attribute(obj)
+            else:
+                return ''
+        else:
+            val = getattr(obj, self.source)
+
+        return strftime(val)
+
+class DateCharField(serializers.CharField):
+    def get_attribute(self, obj):
+        return obj
+
+    def to_representation(self, obj):
+        if '.' in self.source:
+            k = self.source.split('.')
+            has_r = hasattr(obj, k[0])
+            if has_r:
+                r = getattr(obj, k[0])
+                if hasattr(r, k[1]):
+                    val = getattr(r, k[1])
+                else:
+                    val = self.get_attribute(obj)
+            else:
+                return ''
+        else:
+            val = getattr(obj, self.source)
+        return strfdate(val)
+
+class AmountShowCharField(serializers.CharField):
+    def get_attribute(self, obj):
+        return obj
+
+    def to_representation(self, obj):
+        if '.' in self.source:
+            k = self.source.split('.')
+            has_r = hasattr(obj, k[0])
+            if has_r:
+                r = getattr(obj, k[0])
+                if hasattr(r, k[1]):
+                    val = getattr(r, k[1])
+                else:
+                    val = self.get_attribute(obj)
+            else:
+                return ''
+        else:
+            val = getattr(obj, self.source)
+
+        return Formater.formatAmountShow(val)
+
+class PriceShowCharField(serializers.CharField):
+    def get_attribute(self, obj):
+        return obj
+
+    def to_representation(self, obj):
+        if '.' in self.source:
+            k = self.source.split('.')
+            has_r = hasattr(obj, k[0])
+            if has_r:
+                r = getattr(obj, k[0])
+                if hasattr(r, k[1]):
+                    val = getattr(r, k[1])
+                else:
+                    val = self.get_attribute(obj)
+            else:
+                return ''
+        else:
+            val = getattr(obj, self.source)
+
+        return Formater.formatPriceShow(val)
+
+class CountShowCharField(serializers.CharField):
+    def get_attribute(self, obj):
+        return obj
+
+    def to_representation(self, obj):
+        if '.' in self.source:
+            k = self.source.split('.')
+            has_r = hasattr(obj, k[0])
+            if has_r:
+                r = getattr(obj, k[0])
+                if hasattr(r, k[1]):
+                    val = getattr(r, k[1])
+                else:
+                    val = self.get_attribute(obj)
+            else:
+                return ''
+        else:
+            val = getattr(obj, self.source)
+
+        return Formater.formatCountShow(val)

+ 39 - 0
util/custom_modelviewset.py

@@ -0,0 +1,39 @@
+# coding=utf-8
+
+from rest_framework.viewsets import ModelViewSet
+
+from util import response_error, response_ok
+from util.exceptions import CustomError
+
+from django.db import transaction
+
+class CustomModelViewSet(ModelViewSet):
+    def create(self, request, *args, **kwargs):
+        try:
+            with transaction.atomic():
+                super(CustomModelViewSet, self).create(request, *args, **kwargs)
+        except CustomError as e:
+            return response_error(e.get_error_msg())
+        except Exception as e:
+            return response_error(str(e))
+        return response_ok()      
+
+    def update(self, request, *args, **kwargs):
+        try:
+            with transaction.atomic():
+                super(CustomModelViewSet, self).update(request, *args, **kwargs)
+        except CustomError as e:
+            return response_error(e.get_error_msg())
+        except Exception as e:
+            return response_error(str(e))
+        return response_ok()        
+
+    def destroy(self, request, *args, **kwargs):
+        try:
+            with transaction.atomic():
+                super(CustomModelViewSet, self).destroy(request, *args, **kwargs)
+        except CustomError as e:
+            return response_error(e.get_error_msg())
+        except Exception as e:
+            return response_error(str(e))
+        return response_ok()   

+ 18 - 0
util/default_key_constructor.py

@@ -0,0 +1,18 @@
+# coding=utf-8
+from rest_framework_extensions.key_constructor.bits import ListSqlQueryKeyBit, PaginationKeyBit, RetrieveSqlQueryKeyBit, \
+    KeyBitBase
+from rest_framework_extensions.key_constructor.constructors import DefaultKeyConstructor
+
+from datetime import datetime
+from django.core.cache import cache
+
+
+class UpdatedAtKeyBit(KeyBitBase):
+    key = "updated_at"
+
+    def get_data(self, **kwargs):
+        value = cache.get(self.key, None)
+        if not value:
+            value = datetime.utcnow()
+            cache.set(self.key, value=value)
+        return str(value)

+ 31 - 0
util/exceptions.py

@@ -0,0 +1,31 @@
+#coding=utf-8
+
+class CustomError(Exception):
+    def __init__(self, msg, code=''):
+        """
+
+        :param code: error code
+        :param message: error message
+        :return:
+        """
+        Exception.__init__(self)
+        self.message = msg
+        self.error_code = code
+
+    def __str__(self):
+        return "%s %s" % (
+            self.error_code,
+            self.message,
+        )
+
+    def set_error_code(self, code):
+        self.error_code = code
+
+    def set_error_msg(self, msg):
+        self.message = msg
+
+    def get_error_code(self):
+        return self.error_code
+
+    def get_error_msg(self):
+        return self.message

+ 68 - 0
util/file_operation.py

@@ -0,0 +1,68 @@
+# coding=utf-8
+
+import os
+import requests
+from django.conf import settings
+from django.utils import timezone
+from django.utils.deconstruct import deconstructible
+
+
+def UploadFile(file, upload_path):
+    upload_path = PathAndRename(upload_path)
+    filename = "%s%s.%s" % (
+        upload_path.path,
+        timezone.now().strftime('%Y%m%d%H%M%S%f'),
+        file.name.split('.')[-1]
+    )
+    filename = filename.lower()
+    full_filename = "%s/%s" % (settings.MEDIA_ROOT, filename)
+    with open(full_filename, 'wb+') as destination:
+        for chunk in file.chunks():
+            destination.write(chunk)
+    return filename
+
+
+def DownloadFace(url, save_path, ext):
+    upload_path = PathAndRename(save_path)
+    filename = "%s%s.%s" % (
+        upload_path.path,
+        timezone.now().strftime('%Y%m%d%H%M%S%f'),
+        ext
+    )
+    filename = filename.lower()
+    full_filename = "%s/%s" % (settings.MEDIA_ROOT, filename)
+    response = requests.get(url)
+    img = response.content
+    with open(full_filename, 'wb+') as destination:
+        destination.write(img)
+    return filename
+
+
+def DeleteFile(filename):
+    img_path = '%s/%s' % (settings.MEDIA_ROOT, filename)
+    try:
+        if os.path.exists(img_path):
+            os.remove(img_path)
+    except:
+        pass
+
+
+@deconstructible
+class PathAndRename(object):
+
+    def __init__(self, sub_path):
+        self.path = sub_path
+
+        self.full_path = "%s/%s" % (settings.MEDIA_ROOT, sub_path)
+        if not os.path.exists(self.full_path):
+            os.makedirs(self.full_path)
+
+    def __call__(self, instance, filename):
+        ext = filename.split('.')[-1]
+        t = timezone.now().strftime('%Y%m%d%H%M%S%f')
+
+        if instance.pk:
+            filename = '{}-{}.{}'.format(instance.pk, t, ext)
+        else:
+            filename = '{}.{}'.format(t, ext)
+        return os.path.join(self.path, filename)

+ 38 - 0
util/format.py

@@ -0,0 +1,38 @@
+#coding=utf-8
+
+import datetime
+from datetime import timedelta
+
+def strfdate(d):
+    if d:
+        return d.strftime('%Y-%m-%d')
+    else:
+        return ''
+
+def strftime(t):
+    if t:
+        return t.strftime('%Y-%m-%d %H:%M')
+    else:
+        return ''
+
+def strfsecond(second):
+    sec = timedelta(seconds=second)
+    d = datetime.datetime(1,1,1) + sec
+    if d.hour > 0:
+        if d.minute > 0:
+            retval = "%d小时%d分钟" % (d.hour, d.minute)
+        else:
+            retval = "%d小时" % (d.hour)
+    else:
+        retval = "%d分钟" % (d.minute)
+    return retval
+
+
+def clean_datetime_range(data, fieldname):
+    if data is not None and fieldname in data and data[fieldname] != '':
+        t = data[fieldname].split(' - ')
+        data = data.copy()
+        data[fieldname+'_after'] = t[0]
+        data[fieldname+'_before'] = t[1] + ' 23:59:59'
+        data.pop(fieldname)
+    return data

+ 62 - 0
util/fragment.py

@@ -0,0 +1,62 @@
+#coding=utf-8
+
+class FragmentBox():
+    def __init__(self, fragment):
+        self.fragments = [fragment,]
+
+    def getFragments(self):
+        return self.fragments
+
+    def cut(self, fragment):
+        new_fragments = []
+        for item in self.fragments:
+            new_fragments.extend(item.cut(fragment))
+        self.fragments = new_fragments
+
+class Fragment():
+    def __init__(self, start, end, prefix, length):
+        self.start = int(start)
+        self.end = int(end)
+        self.prefix = prefix
+        self.length = length
+
+    @property
+    def start_code(self):
+        num = str(self.start)
+        num = "0" * (self.length - len(num)) + num
+        return self.prefix + num
+
+    @property
+    def end_code(self):
+        num = str(self.end)
+        num = "0" * (self.length - len(num)) + num
+        return self.prefix + num
+
+    def cut(self, fragment):
+        if self.start == self.end:
+            if fragment.end == self.start or fragment.start == self.start:
+                return []
+            if fragment.start < self.start and fragment.end > self.end:
+                return []
+            return [self,]
+
+        if fragment.start <= self.start and fragment.end >= self.end:
+            return []
+        if fragment.start > self.start and fragment.end < self.end:
+            start_fragment = Fragment(self.start, fragment.start - 1, self.prefix, self.length)
+            end_fragment = Fragment(fragment.end + 1, self.end, self.prefix, self.length)
+            return [start_fragment, end_fragment]
+
+        if fragment.end >= self.start and fragment.start <= self.start:
+            new_fragment = Fragment(fragment.end + 1, self.end, self.prefix, self.length)
+            return [new_fragment, ]
+        if fragment.start <= self.end and fragment.end >= self.end:
+            new_fragment = Fragment(self.start, fragment.start - 1, self.prefix, self.length)
+            return [new_fragment, ]
+
+        return [self,]
+
+
+
+
+

+ 39 - 0
util/handler.py

@@ -0,0 +1,39 @@
+#coding=utf-8
+
+from rest_framework.views import exception_handler
+from rest_framework import serializers
+from rest_framework.exceptions import AuthenticationFailed,NotAuthenticated
+from jwt.exceptions import ExpiredSignatureError
+
+from .exceptions import CustomError
+from util import response_error
+
+import traceback
+
+def custom_exception_handler(exc, context):
+    message = ''
+    errcode = None
+    if isinstance(exc, CustomError):
+        message = exc.get_error_msg()
+    elif isinstance(exc, serializers.ValidationError):
+        if isinstance(exc.detail, list):
+            message = exc.detail[0]
+        elif isinstance(exc.detail, dict):
+            for key, value in exc.detail.items():
+                if key != "error":
+                    message += key+"错误:"
+                message += value[0]
+        else:
+            message = list(exc.detail)[0]
+    elif isinstance(exc, AuthenticationFailed) or isinstance(exc, ExpiredSignatureError):
+        message = str(exc)
+        errcode = 460
+    elif isinstance(exc, NotAuthenticated):
+        message = str(exc)
+        errcode = 460
+    else:
+        traceback.print_exc()
+        message = str(exc)
+
+    return response_error(message, errcode=errcode)
+

+ 29 - 0
util/pagination.py

@@ -0,0 +1,29 @@
+#coding=utf-8
+
+from rest_framework import pagination
+from rest_framework.response import Response
+
+import math
+
+class CustomPagination(pagination.PageNumberPagination):
+    page_query_param = 'page'
+    page_size_query_param = 'limit'
+    page_size = 10
+
+    def get_paginated_response(self, data):
+        ps = self.get_page_size(self.request)
+        # 底栏合计
+        totalRow = {}
+        if len(data) > 0 and 'totalRow' in data[-1:][0]:
+            totalRow = data[-1:][0]
+            data = data[:-1]
+        return Response({
+            'code':0,
+            'showCount': ps,
+            'totalPage': math.ceil(self.page.paginator.count * 1.0 / ps),
+            'totalResult': self.page.paginator.count,
+            'currentPage': self.page.number,
+            'count': self.page.paginator.count,
+            'data': data,
+            'totalRow': totalRow,
+        })

+ 37 - 0
util/permission.py

@@ -0,0 +1,37 @@
+# coding=utf-8
+
+from rest_framework import permissions
+
+from apps.customer.models import Customer
+from apps.account.models import User
+from apps.activity.models import Activity
+
+from util.wx.wechat import WeChat
+from util.exceptions import CustomError
+from util import response_error
+
+
+class IsCustomerUser(permissions.BasePermission):
+    def has_permission(self, request, view):
+        if not request.user or not request.user.is_authenticated:
+            return False
+        if not request.user.type != User.CUSTOMER:
+            return False
+
+        appid = request.GET.get('appid', None)
+        if not appid:
+            appid = request.POST.get('appid')
+        activity_id = request.GET.get('activity_id', None)
+        if not activity_id:
+            activity_id = request.POST.get('activity_id')
+
+        try:
+            WeChat.checkAppid(appid)
+            activity = Activity.getById(activity_id)
+        except:
+            return False
+        customer = Customer.objects.filter(branch_id=activity.branch_id, user_id=request.user.id).first()
+        if not customer:
+            return False
+        request.customer = customer
+        return True

+ 45 - 0
util/serializersfield.py

@@ -0,0 +1,45 @@
+#coding=utf-8
+from django.utils import timezone
+from rest_framework import serializers
+
+def getAttributeValue(instance, obj):
+    if '.' in instance.source:
+        k = instance.source.split('.')
+        for i in range(len(k)):
+            if hasattr(obj, k[i]):
+                obj = getattr(obj, k[i])
+        val = obj
+    else:
+        val = getattr(obj, instance.source)
+    return val
+
+class BooleanCharField(serializers.BooleanField):
+    def get_attribute(self, obj):
+        return obj
+
+    def to_representation(self, obj):
+        val = getAttributeValue(self, obj)
+        return u'是' if val else u'否'
+
+class DelayTimeCharField(serializers.CharField):
+    def get_attribute(self, obj):
+        return obj
+
+    def to_representation(self, obj):
+        val = getAttributeValue(self, obj)
+        delay = timezone.now() - val
+
+        if delay:
+            if delay.days:
+                delay = u'{}天前'.format(delay.days)
+            elif delay.seconds:
+                delay = delay.seconds / 60
+                if delay < 60:
+                    delay =u'{}分钟前'.format(int(delay))
+                elif delay < 60 * 24:
+                    delay = u'{}小时前'.format(int(delay / 60))
+            else:
+                delay = u''
+        else:
+            delay = u''
+        return delay

+ 99 - 0
util/wechatcashout.py

@@ -0,0 +1,99 @@
+# coding=utf-8
+
+import uuid
+import os
+import requests
+import json
+import xmltodict
+import time
+from hashlib import md5
+from django.conf import settings
+
+from util.exceptions import CustomError
+
+
+class WechatAgentPay():
+
+    def __init__(self, appid, mch_id, partner_trade_no, openid, amount, merchant_key, cert, key):
+        self.params = {
+            'mch_appid': appid,                       # 申请商户号的appid或商户号绑定的appid
+            'mchid': mch_id,                          # 微信支付分配的商户号
+            'nonce_str': generate_nonce_str(),        # 随机字符串,不长于32位
+            'partner_trade_no': partner_trade_no,     # 商户订单号,需保持唯一性
+            'openid': openid,                         # 商户appid下,某用户的openid
+            'check_name': 'NO_CHECK',                 # 校验用户姓名选项
+            'amount': amount,                         # 金额  企业付款金额 单位为分
+            'desc': u'佣金',                          # 企业付款备注
+        }
+        self.params['sign'] = generate_sign(self.params, merchant_key)
+        self.url = 'https://api.mch.weixin.qq.com/mmpaymkttransfers/promotion/transfers'
+        self.cert_file = cert
+        self.cert_key = key
+
+    def pay(self):
+        result = send_cert_request(self.url, self.params, self.cert_file, self.cert_key)
+        return result
+
+
+class PayQuery():
+
+    def __init__(self, partner_trade_no, mch_id, appid, merchant_key, cert, key):
+        self.url = 'https://api.mch.weixin.qq.com/mmpaymkttransfers/gettransferinfo'
+        self.cert_file = cert
+        self.cert_key = key
+        self.params = {
+            'nonce_str': generate_nonce_str(),
+            'partner_trade_no': partner_trade_no,
+            'mch_id': mch_id,
+            'appid': appid,
+        }
+        self.params['sign'] = generate_sign(self.params, merchant_key)
+
+    def query(self):
+        result = send_cert_request(self.url, self.params, self.cert_file, self.cert_key)
+        return result
+
+
+
+def send_cert_request(url, param, cert_file, cert_key):
+    '''
+    发送携带证书的xml请求
+    '''
+    xml = xmltodict.unparse({'root': param})
+    response = requests.post(url, data=xml.encode('utf-8'), headers={'Content-Type': 'text/xml'}, cert=(cert_file, cert_key), verify=False)
+    xmlmsg  = json.loads(json.dumps(xmltodict.parse(response.content)))['xml']
+    print(xmlmsg)
+    return xmlmsg
+
+def generate_nonce_str():
+    """
+    生成随机字符串
+    """
+    return str(uuid.uuid4()).replace('-', '')
+
+def generate_sign(params, merchant_key):
+    """
+    生成md5签名的参数
+    """
+    if 'sign' in params:
+        params.pop('sign')
+    src = '&'.join(['%s=%s' % (k, v) for k, v in sorted(params.items())]) + '&key=%s' % merchant_key
+    return md5(src.encode('utf-8')).hexdigest().upper()
+
+def validate_sign(resp_dict, merchant_key):
+    """
+    验证微信返回的签名
+    """
+    if 'sign' not in resp_dict:
+        return False
+    wx_sign = resp_dict['sign']
+    sign = generate_sign(resp_dict, merchant_key)
+    if sign == wx_sign:
+        return True
+    return False
+
+def generate_response_data(resp_dict):
+    """
+    字典转xml
+    """
+    return xmltodict.unparse({'xml': resp_dict}, pretty=True, full_document=False).encode('utf-8')

+ 180 - 0
util/wechatpay.py

@@ -0,0 +1,180 @@
+# coding=utf-8
+
+import uuid
+import requests
+import json
+import xmltodict
+import time
+from hashlib import md5
+from django.conf import settings
+
+from util.exceptions import CustomError
+
+# 微信支付sign_type
+WEIXIN_SIGN_TYPE = 'MD5'
+# 服务器IP地址
+WEIXIN_SPBILL_CREATE_IP = '139.9.148.181'
+# 微信支付用途
+WEIXIN_BODY = u'小程序支付'
+# 微信统一下单URL
+WEIXIN_UNIFIED_ORDER_URL = 'https://api.mch.weixin.qq.com/pay/unifiedorder'
+# 微信查询订单URL
+WEIXIN_QUERY_ORDER_URL = 'https://api.mch.weixin.qq.com/pay/orderquery'
+# 微信支付回调API
+WEIXIN_CALLBACK_API = 'https://lsr.zzly.vip/api/wechat_notify/'
+
+
+class WeChatResponse():
+    def __init__(self,appid, agent_num, agent_key):
+        self.params = {
+            "appid": appid,
+            'mch_id': agent_num,
+            'nonce_str': '',
+            'sign_type': WEIXIN_SIGN_TYPE,
+            'sign': '',
+            'out_trade_no': '',
+        }
+        self.prepay_id = None
+        self.merchant_key = agent_key
+
+    # 查询订单
+    def orderquery(self, out_trade_no):
+        self.params['out_trade_no'] = out_trade_no
+        self.params['nonce_str'] = generate_nonce_str()
+        self.params['sign'] = generate_sign(self.params, self.merchant_key)
+        data = xmltodict.unparse({'xml': self.params}, pretty=True, full_document=False).encode('utf-8')
+        headers = {'Content-Type': 'application/xml'}
+        res = requests.post(WEIXIN_QUERY_ORDER_URL, data=data, headers=headers)
+        if res.status_code != 200:
+            raise CustomError(u'微信请求失败!')
+        result = json.loads(json.dumps(xmltodict.parse(res.content)))
+
+        if result['xml']['return_code'] != 'SUCCESS':
+            raise CustomError(u'微信通信失败![%s]' % result['xml']['return_msg'])
+        print(u'微信交易状态![%s]' % (result['xml']['trade_state_desc']))
+        if result['xml']['trade_state'] == 'NOTPAY':
+            return result['xml']['total_fee']
+            # raise CustomError(u'微信交易状态![%s]' % (result['xml']['trade_state_desc']))
+        # 其他状态,返回金额0
+        return 0
+        # return result['xml']['total_fee']
+
+class WechatPay():
+
+    def __init__(self, appid, mch_id, merchant_key):
+        self.params = {
+            'appid': appid,
+            'mch_id': mch_id,
+            'nonce_str': '',
+            'sign_type': WEIXIN_SIGN_TYPE,
+            'body': WEIXIN_BODY,
+            'out_trade_no': '',
+            'total_fee': '',
+            'spbill_create_ip': WEIXIN_SPBILL_CREATE_IP,
+            'notify_url': WEIXIN_CALLBACK_API + appid + '/',
+            'trade_type': 'JSAPI'
+        }
+        self.prepay_id = None
+        self.merchant_key = merchant_key
+
+    def getAppString(self):
+        data = {
+            'appId': self.params['appid'],
+            'signType': WEIXIN_SIGN_TYPE,
+            'package': "prepay_id={}".format(self.prepay_id),
+            'nonceStr': generate_nonce_str(),
+            'timeStamp': str(int(time.time()))
+        }
+        data['paySign'] = generate_sign(data, self.merchant_key)
+        data.pop('appId')
+        return data
+
+    def unifiedOrder(self,out_trade_no,total_fee, openid):
+        self.params['out_trade_no'] = out_trade_no
+        self.params['total_fee'] = int(round(total_fee / 100,0))
+        self.params['openid'] = openid
+        self.params['nonce_str'] = generate_nonce_str()
+        self.params['sign'] = generate_sign(self.params, self.merchant_key)
+        data = xmltodict.unparse({'xml': self.params}, pretty=True, full_document=False).encode('utf-8')
+
+        headers = {'Content-Type': 'application/xml'}
+        res = requests.post(WEIXIN_UNIFIED_ORDER_URL, data=data, headers=headers)
+
+        if res.status_code != 200:
+            raise CustomError(u'微信请求失败!')
+
+        result = json.loads(json.dumps(xmltodict.parse(res.content)))
+        if result['xml']['return_code'] != 'SUCCESS':
+            raise CustomError(u'微信通信失败![%s]' % result['xml']['return_msg'])
+        if result['xml']['result_code'] != 'SUCCESS':
+            raise CustomError(u'微信交易失败![%s:%s]' % (result['xml']['err_code'],result['xml']['err_code_des']))
+
+        self.prepay_id = result['xml']['prepay_id']
+
+        return result['xml']
+
+class WechatPayNotify():
+    def __init__(self,params, merchant_key):
+        self.params = params
+        self.merchant_key = merchant_key
+
+    def handle(self):
+        resp_dict = json.loads(json.dumps(xmltodict.parse(self.params)))['xml']
+        return_code = resp_dict['return_code']
+
+        if return_code != 'SUCCESS':
+            return None
+
+        if not validate_sign(resp_dict, self.merchant_key):
+            return None
+
+        return resp_dict
+
+    @staticmethod
+    def response_ok():
+        return_info = {
+            'return_code': 'SUCCESS',
+            'return_msg': 'OK'
+        }
+        return generate_response_data(return_info)
+
+    @staticmethod
+    def response_fail():
+        return_info = {
+            'return_code': 'FAIL',
+            'return_msg': 'FAIL'
+        }
+        return generate_response_data(return_info)
+
+def generate_nonce_str():
+    """
+    生成随机字符串
+    """
+    return str(uuid.uuid4()).replace('-', '')
+
+def generate_sign(params, merchant_key):
+    """
+    生成md5签名的参数
+    """
+    if 'sign' in params:
+        params.pop('sign')
+    src = '&'.join(['%s=%s' % (k, v) for k, v in sorted(params.items())]) + '&key=%s' % merchant_key
+    return md5(src.encode('utf-8')).hexdigest().upper()
+
+def validate_sign(resp_dict, merchant_key):
+    """
+    验证微信返回的签名
+    """
+    if 'sign' not in resp_dict:
+        return False
+    wx_sign = resp_dict['sign']
+    sign = generate_sign(resp_dict, merchant_key)
+    if sign == wx_sign:
+        return True
+    return False
+
+def generate_response_data(resp_dict):
+    """
+    字典转xml
+    """
+    return xmltodict.unparse({'xml': resp_dict}, pretty=True, full_document=False).encode('utf-8')

+ 27 - 0
util/wx/WXBizDataCrypt.py

@@ -0,0 +1,27 @@
+import base64
+import json
+from Crypto.Cipher import AES
+
+
+class WXBizDataCrypt:
+    def __init__(self, appId, sessionKey):
+        self.appId = appId
+        self.sessionKey = sessionKey
+
+    def decrypt(self, encryptedData, iv):
+        # base64 decode
+        sessionKey = base64.b64decode(self.sessionKey)
+        encryptedData = base64.b64decode(encryptedData)
+        iv = base64.b64decode(iv)
+
+        cipher = AES.new(sessionKey, AES.MODE_CBC, iv)
+
+        decrypted = json.loads(self._unpad(cipher.decrypt(encryptedData)))
+
+        if decrypted['watermark']['appid'] != self.appId:
+            raise Exception('Invalid Buffer')
+
+        return decrypted
+
+    def _unpad(self, s):
+        return (s[:-ord(s[len(s)-1:])]).decode('utf-8')

+ 255 - 0
util/wx/WXBizMsgCrypt.py

@@ -0,0 +1,255 @@
+#!/usr/bin/env python
+#-*- encoding:utf-8 -*-
+
+""" 对公众平台发送给公众账号的消息加解密示例代码.
+@copyright: Copyright (c) 1998-2014 Tencent Inc.
+
+"""
+# ------------------------------------------------------------------------
+
+import base64
+import string
+import random
+import hashlib
+import time
+import struct
+from Crypto.Cipher import AES
+import xml.etree.cElementTree as ET
+import socket
+import utils.wx.ierror as ierror
+
+""" AES加解密用 pycrypto """
+
+class FormatException(Exception):
+    pass
+
+def throw_exception(message, exception_class=FormatException):
+    """my define raise exception function"""
+    raise exception_class(message)
+
+class SHA1:
+    """计算公众平台的消息签名接口"""
+    def getSHA1(self, token, timestamp, nonce, encrypt):
+        """用SHA1算法生成安全签名
+        @param token:  票据
+        @param timestamp: 时间戳
+        @param encrypt: 密文
+        @param nonce: 随机字符串
+        @return: 安全签名
+        """
+        try:
+            token = token.decode()
+            sortlist = [token, timestamp, nonce, encrypt]
+            sortlist.sort()
+            sha = hashlib.sha1()
+            sha.update("".join(sortlist).encode("utf8"))
+            return ierror.WXBizMsgCrypt_OK, sha.hexdigest()
+        except Exception as e:
+            print(e)
+            return ierror.WXBizMsgCrypt_ComputeSignature_Error, None
+
+
+class XMLParse(object):
+    """提供提取消息格式中的密文及生成回复消息格式的接口"""
+
+    # xml消息模板
+    AES_TEXT_RESPONSE_TEMPLATE = """<xml><Encrypt><![CDATA[%(msg_encrypt)s]]></Encrypt><MsgSignature><![CDATA[%(msg_signaturet)s]]></MsgSignature><TimeStamp>%(timestamp)s</TimeStamp><Nonce><![CDATA[%(nonce)s]]></Nonce></xml>"""
+    def extract(self, xmltext):
+        """提取出xml数据包中的加密消息
+        @param xmltext: 待提取的xml字符串
+        @return: 提取出的加密消息字符串
+        """
+        try:
+            xml_tree = ET.fromstring(xmltext)
+            encrypt = xml_tree.find("Encrypt")
+            # touser_name = xml_tree.find("ToUserName")
+            return ierror.WXBizMsgCrypt_OK, encrypt.text
+        except Exception as e:
+            print(e)
+            return ierror.WXBizMsgCrypt_ParseXml_Error, None, None
+
+    def generate(self, encrypt, signature, timestamp, nonce):
+        """生成xml消息
+        @param encrypt: 加密后的消息密文
+        @param signature: 安全签名
+        @param timestamp: 时间戳
+        @param nonce: 随机字符串
+        @return: 生成的xml字符串
+        """
+        resp_dict = {
+            'msg_encrypt': encrypt,
+            'msg_signaturet': signature,
+            'timestamp': timestamp,
+            'nonce': nonce,
+        }
+        resp_xml = self.AES_TEXT_RESPONSE_TEMPLATE % resp_dict
+        return resp_xml
+
+
+class PKCS7Encoder(object):
+    """提供基于PKCS7算法的加解密接口"""
+
+    block_size = 32
+
+    def encode(self, text):
+        """ 对需要加密的明文进行填充补位
+        @param text: 需要进行填充补位操作的明文
+        @return: 补齐明文字符串
+        """
+        text_length = len(text)
+        # 计算需要填充的位数
+        amount_to_pad = self.block_size - (text_length % self.block_size)
+        if amount_to_pad == 0:
+            amount_to_pad = self.block_size
+        # 获得补位所用的字符
+        pad = chr(amount_to_pad).encode()
+        return text + pad * amount_to_pad
+
+    def decode(self, decrypted):
+        """删除解密后明文的补位字符
+        @param decrypted: 解密后的明文
+        @return: 删除补位字符后的明文
+        """
+        pad = ord(decrypted[-1])
+        if pad < 1 or pad > 32:
+            pad = 0
+        return decrypted[:-pad]
+
+
+class Prpcrypt(object):
+    """提供接收和推送给公众平台消息的加解密接口"""
+
+    def __init__(self, key):
+        # self.key = base64.b64decode(key+"=")
+        self.key = key
+        # 设置加解密模式为AES的CBC模式
+        self.mode = AES.MODE_CBC
+
+    def encrypt(self, text, appid):
+        """对明文进行加密
+        @param text: 需要加密的明文
+        @return: 加密得到的字符串
+        """
+        # 16位随机字符串添加到明文开头
+        len_str = struct.pack("I", socket.htonl(len(text.encode())))
+        # text = self.get_random_str() + binascii.b2a_hex(len_str).decode() + text + appid
+        text = self.get_random_str() + len_str + text.encode() + appid
+        # 使用自定义的填充方式对明文进行补位填充
+        pkcs7 = PKCS7Encoder()
+        text = pkcs7.encode(text)
+        # 加密
+        cryptor = AES.new(self.key, self.mode, self.key[:16])
+        try:
+            ciphertext = cryptor.encrypt(text)
+            # 使用BASE64对加密后的字符串进行编码
+            return ierror.WXBizMsgCrypt_OK, base64.b64encode(ciphertext).decode('utf8')
+        except Exception as e:
+            return ierror.WXBizMsgCrypt_EncryptAES_Error, None
+
+    def decrypt(self, text, appid):
+        """对解密后的明文进行补位删除
+        @param text: 密文
+        @return: 删除填充补位后的明文
+        """
+        try:
+            cryptor = AES.new(self.key, self.mode, self.key[:16])
+            # 使用BASE64对密文进行解码,然后AES-CBC解密
+            plain_text = cryptor.decrypt(base64.b64decode(text))
+        except Exception as e:
+            print(e)
+            return ierror.WXBizMsgCrypt_DecryptAES_Error, None
+        try:
+            # pad = ord(plain_text[-1])
+            pad = plain_text[-1]
+            # 去掉补位字符串
+            # pkcs7 = PKCS7Encoder()
+            # plain_text = pkcs7.encode(plain_text)
+            # 去除16位随机字符串
+            content = plain_text[16:-pad]
+            xml_len = socket.ntohl(struct.unpack("I", content[: 4])[0])
+            xml_content = content[4: xml_len + 4]
+            from_appid = content[xml_len + 4:]
+        except Exception as e:
+            return ierror.WXBizMsgCrypt_IllegalBuffer, None
+        if from_appid != appid:
+            return ierror.WXBizMsgCrypt_ValidateAppid_Error, None
+        return 0, xml_content.decode()
+
+    def get_random_str(self):
+        """ 随机生成16位字符串
+        @return: 16位字符串
+        """
+        rule = string.ascii_letters + string.digits
+        str = random.sample(rule, 16)
+        return "".join(str).encode()
+
+
+class WXBizMsgCrypt(object):
+    # 构造函数
+    # @param sToken: 公众平台上,开发者设置的Token
+    # @param sEncodingAESKey: 公众平台上,开发者设置的EncodingAESKey
+    # @param sAppId: 企业号的AppId
+    def __init__(self, sToken, sEncodingAESKey, sAppId):
+        try:
+            self.key = base64.b64decode(sEncodingAESKey + "=")
+            assert len(self.key) == 32
+        except Exception:
+            throw_exception("[error]: EncodingAESKey unvalid !", FormatException)
+            # return ierror.WXBizMsgCrypt_IllegalAesKey)
+        self.token = sToken.encode()
+        self.appid = sAppId.encode()
+
+    def EncryptMsg(self, sReplyMsg, sNonce, timestamp=None):
+        # 将公众号回复用户的消息加密打包
+        # @param sReplyMsg: 企业号待回复用户的消息,xml格式的字符串
+        # @param sTimeStamp: 时间戳,可以自己生成,也可以用URL参数的timestamp,如为None则自动用当前时间
+        # @param sNonce: 随机串,可以自己生成,也可以用URL参数的nonce
+        # sEncryptMsg: 加密后的可以直接回复用户的密文,包括msg_signature, timestamp, nonce, encrypt的xml格式的字符串,
+        # return:成功0,sEncryptMsg,失败返回对应的错误码None
+        pc = Prpcrypt(self.key)
+        ret, encrypt = pc.encrypt(sReplyMsg, self.appid)
+        if ret != 0:
+            return ret, None
+        if timestamp is None:
+            timestamp = str(int(time.time()))
+        # 生成安全签名
+        sha1 = SHA1()
+        ret, signature = sha1.getSHA1(self.token, timestamp, sNonce, encrypt)
+
+        if ret != 0:
+            return ret, None
+        xmlParse = XMLParse()
+        return ret, xmlParse.generate(encrypt, signature, timestamp, sNonce)
+    def VerifyURL(self, sMsgSignature, sTimeStamp, sNonce, sEchoStr):
+        sha1 = SHA1()
+        ret,signature = sha1.getSHA1(self.token, sTimeStamp, sNonce, sEchoStr)
+        if ret  != 0:
+            return ret, None
+        if not signature == sMsgSignature:
+            return ierror.WXBizMsgCrypt_ValidateSignature_Error, None
+        pc = Prpcrypt(self.key)
+        ret,sReplyEchoStr = pc.decrypt(sEchoStr,self.appid)
+        return ret,sReplyEchoStr
+
+    def DecryptMsg(self, sPostData, sMsgSignature, sTimeStamp, sNonce):
+        # 检验消息的真实性,并且获取解密后的明文
+        # @param sMsgSignature: 签名串,对应URL参数的msg_signature
+        # @param sTimeStamp: 时间戳,对应URL参数的timestamp
+        # @param sNonce: 随机串,对应URL参数的nonce
+        # @param sPostData: 密文,对应POST请求的数据
+        #  xml_content: 解密后的原文,当return返回0时有效
+        # @return: 成功0,失败返回对应的错误码
+        # 验证安全签名
+        xmlParse = XMLParse()
+        ret, encrypt = xmlParse.extract(sPostData)
+        if ret != 0:
+            return ret, None
+        sha1 = SHA1()
+        ret, signature = sha1.getSHA1(self.token, sTimeStamp, sNonce, encrypt)
+        if ret != 0:
+            return ret, None
+        if not signature == sMsgSignature:
+            return ierror.WXBizMsgCrypt_ValidateSignature_Error, None
+        pc = Prpcrypt(self.key)
+        ret, xml_content = pc.decrypt(encrypt, self.appid)
+        return ret, xml_content

+ 0 - 0
util/wx/__init__.py


+ 20 - 0
util/wx/ierror.py

@@ -0,0 +1,20 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#########################################################################
+# Author: jonyqin
+# Created Time: Thu 11 Sep 2014 01:53:58 PM CST
+# File Name: ierror.py
+# Description:定义错误码含义 
+#########################################################################
+WXBizMsgCrypt_OK = 0
+WXBizMsgCrypt_ValidateSignature_Error = -40001
+WXBizMsgCrypt_ParseXml_Error = -40002
+WXBizMsgCrypt_ComputeSignature_Error = -40003
+WXBizMsgCrypt_IllegalAesKey = -40004
+WXBizMsgCrypt_ValidateAppid_Error = -40005
+WXBizMsgCrypt_EncryptAES_Error = -40006
+WXBizMsgCrypt_DecryptAES_Error = -40007
+WXBizMsgCrypt_IllegalBuffer = -40008
+WXBizMsgCrypt_EncodeBase64_Error = -40009
+WXBizMsgCrypt_DecodeBase64_Error = -40010
+WXBizMsgCrypt_GenReturnXml_Error = -40011

+ 351 - 0
util/wx/wechat.py

@@ -0,0 +1,351 @@
+# coding=utf-8
+
+import requests
+import json
+from django.conf import settings
+from util.exceptions import CustomError
+from util.file_operation import PathAndRename
+from django.utils import timezone
+
+class WeChat(object):
+
+    @staticmethod
+    def checkAppid(appid):
+        if not appid:
+            raise CustomError(u'未找到相应的小程序!')
+        if appid != settings.APPID:
+            raise CustomError(u'未找到相应的小程序!')
+
+    @staticmethod
+    def getPreAuthCode(component_appid, component_access_token):
+        """
+        第三方平台  获得预授权码
+        结果{"pre_auth_code": "Cx_Dk6qiBE0Dmx4EmlT3oRfArPvwSQ-oa3NL_fwHM7VI08r52wazoZX2Rhpz1dEw", "expires_in": 600}
+        """
+
+        url = 'https://api.weixin.qq.com/cgi-bin/component/api_create_preauthcode?component_access_token=' + component_access_token
+        param = {
+            'component_appid': component_appid,
+        }
+        result = requests.post(url, data=json.dumps(param))
+        result = result.json()
+        if 'errcode' in result and result['errcode']:
+            raise CustomError(u'微信请求失败' + str(result['errcode']) + ' :' + result['errmsg'])
+        return result
+
+    @staticmethod
+    def getComponentAccessToken(component_appid, component_appsecret, component_verify_ticket):
+        """第三方平台  获得access_token"""
+
+        url = 'https://api.weixin.qq.com/cgi-bin/component/api_component_token'
+        param = {
+            'component_appid': component_appid,
+            'component_appsecret': component_appsecret,
+            'component_verify_ticket': component_verify_ticket
+        }
+        result = requests.post(url, data=json.dumps(param))
+        result = result.json()
+        if 'errcode' in result and result['errcode']:
+            raise CustomError(u'微信请求失败'+ str(result['errcode']) + ' :' + result['errmsg'])
+        return result
+
+    @staticmethod
+    def getAuthorizationInfo(component_appid, authorization_code, component_access_token):
+        """第三方平台  获得授权信息"""
+
+        url = 'https://api.weixin.qq.com/cgi-bin/component/api_query_auth?component_access_token=' + component_access_token
+        param = {
+            'component_appid': component_appid,
+            'authorization_code': authorization_code,
+        }
+        result = requests.post(url, data=json.dumps(param))
+        result = result.json()
+        if 'errcode' in result and result['errcode']:
+            raise CustomError(u'微信请求失败'+ str(result['errcode']) + ' :' + result['errmsg'])
+        return result
+
+    @staticmethod
+    def getAuthorizerInfo(component_appid, authorizer_appid, component_access_token):
+        """第三方平台 获得授权方信息"""
+
+        url = 'https://api.weixin.qq.com/cgi-bin/component/api_get_authorizer_info?component_access_token=' + component_access_token
+        param = {
+            'component_appid': component_appid,
+            'authorizer_appid': authorizer_appid,
+        }
+        result = requests.post(url, data=json.dumps(param))
+        result = result.json()
+        if 'errcode' in result and result['errcode']:
+            raise CustomError(u'微信请求失败'+ str(result['errcode']) + ' :' + result['errmsg'])
+        return result
+
+    @staticmethod
+    def getAuthorizerAccessToken(component_appid, authorizer_appid, authorizer_refresh_token, component_access_token):
+        """第三方平台 获得授权方 access_token"""
+
+        url = 'https://api.weixin.qq.com/cgi-bin/component/api_authorizer_token?component_access_token=' + component_access_token
+        param = {
+            'component_appid': component_appid,
+            'authorizer_appid': authorizer_appid,
+            'authorizer_refresh_token': authorizer_refresh_token
+        }
+        result = requests.post(url, data=json.dumps(param))
+        result = result.json()
+        if 'errcode' in result and result['errcode']:
+            raise CustomError(u'微信请求失败'+ str(result['errcode']) + ' :' + result['errmsg'])
+        return result
+
+    @staticmethod
+    def getCodeTemplateList(component_access_token):
+        '''获取代码模版列表'''
+
+        url = 'https://api.weixin.qq.com/wxa/gettemplatelist?access_token=' + component_access_token
+        result = requests.get(url)
+        result = result.json()
+        if 'errcode' in result and result['errcode']:
+            raise CustomError(u'微信请求失败'+ str(result['errcode']) + ' :' + result['errmsg'])
+        return result['template_list']
+
+    @staticmethod
+    def getDraftTemplateList(component_access_token):
+        '''获取草稿箱列表'''
+
+        url = 'https://api.weixin.qq.com/wxa/gettemplatedraftlist?access_token=' + component_access_token
+        result = requests.get(url)
+        result = result.json()
+        if 'errcode' in result and result['errcode']:
+            raise CustomError(u'微信请求失败'+ str(result['errcode']) + ' :' + result['errmsg'])
+        return result['draft_list']
+
+    @staticmethod
+    def addToemplate(component_access_token, draft_id):
+        '''上传草稿到标准模板'''
+
+        url = 'https://api.weixin.qq.com/wxa/addtotemplate?access_token=' + component_access_token
+        param = {
+            'draft_id': draft_id,
+        }
+        result = requests.post(url, data=json.dumps(param))
+        result = result.json()
+        if 'errcode' in result and result['errcode']:
+            raise CustomError(u'微信请求失败'+ str(result['errcode']) + ' :' + result['errmsg'])
+        return result
+
+    @staticmethod
+    def commitCode(access_token, template_id, user_version, user_desc):
+        '''小程序提交代码'''
+        url = 'https://api.weixin.qq.com/wxa/commit?access_token=' + access_token
+
+        param = {
+            'template_id': template_id,
+            'ext_json': "{\"extEnable\": false,\"extAppid\": \"\"}",
+            'user_version': user_version,
+            'user_desc': 'test'
+        }
+
+        result = requests.post(url, data=json.dumps(param))
+        result = result.json()
+        if 'errcode' in result and result['errcode']:
+            raise CustomError(u'微信请求失败'+ str(result['errcode']) + ' :' + result['errmsg'])
+        return result
+
+    @staticmethod
+    def submitAuditCode(access_token):
+        '''将上传的代码提交审核'''
+
+        url = 'https://api.weixin.qq.com/wxa/submit_audit?access_token=' + access_token
+
+        result = requests.post(url)
+        result = result.json()
+        if 'errcode' in result and result['errcode']:
+            raise CustomError(u'微信请求失败' + str(result['errcode']) + ' :' + result['errmsg'])
+        return result
+
+    @staticmethod
+    def getLastSubmitAuditCodeStatus(access_token):
+        '''查询最新一次提交审核代码的审核状态'''
+
+        url = 'https://api.weixin.qq.com/wxa/get_latest_auditstatus?access_token=' + access_token
+        result = requests.get(url)
+        result = result.json()
+        if 'errcode' in result and result['errcode']:
+            raise CustomError(u'微信请求失败' + str(result['errcode']) + ' :' + result['errmsg'])
+        return result
+
+    @staticmethod
+    def modify_domain(access_token, action, requestdomain, wsrequestdomain, uploaddomain, downloaddomain):
+        '''设置小程序服务器域名'''
+        url = 'https://api.weixin.qq.com/wxa/modify_domain?access_token=' + access_token
+
+        param = {
+            "action": action,
+            "requestdomain": requestdomain,
+            "wsrequestdomain": wsrequestdomain,
+            "uploaddomain": uploaddomain,
+            "downloaddomain": downloaddomain
+        }
+        result = requests.post(url, data=json.dumps(param))
+        result = result.json()
+        if 'errcode' in result and result['errcode']:
+            raise CustomError(u'微信请求失败' + str(result['errcode']) + ' :' + result['errmsg'])
+        return result
+
+    @staticmethod
+    def code2Session(appid, secret, code):
+        '''登录凭证校验'''
+        url = 'https://api.weixin.qq.com/sns/jscode2session?appid='+ appid + '&secret=' + secret + '&js_code=' + code + '&grant_type=authorization_code'
+        result = requests.get(url)
+        result = result.json()
+        if 'errcode' in result and result['errcode']:
+            raise CustomError(u'微信请求失败' + str(result['errcode']) + ':' + result['errmsg'])
+        return result
+
+    @staticmethod
+    def getAccessToken(appid, secret,):
+        '''获取小程序验证token'''
+        url = 'https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid='+ appid + '&secret=' + secret
+        result = requests.get(url)
+        result = result.json()
+        if 'errcode' in result and result['errcode']:
+            raise CustomError(u'微信请求失败' + str(result['errcode']) + ':' + result['errmsg'])
+        return result
+
+    @staticmethod
+    def addPlugin(access_token):
+        '''小程序插件管理'''
+        url = 'https://api.weixin.qq.com/wxa/plugin?access_token=' + access_token
+
+        param = {
+            'action': 'apply',
+            'plugin_appid': 'wxfa43a4a7041a84de'
+        }
+
+        result = requests.post(url, data=json.dumps(param))
+        result = result.json()
+        if 'errcode' in result and result['errcode'] and str(result['errcode']) != '89237':
+            raise CustomError(u'微信请求失败' + str(result['errcode']) + ':' + result['errmsg'])
+        return result
+
+    @staticmethod
+    def getTemplateList(access_token):
+        '''获取当前帐号下的个人模板列表'''
+        url = 'https://api.weixin.qq.com/wxaapi/newtmpl/gettemplate?access_token=' + access_token
+        result = requests.get(url)
+        result = result.json()
+        if 'errcode' in result and result['errcode']:
+            raise CustomError(u'微信请求失败' + str(result['errcode']) + ':' + result['errmsg'])
+        return result['data']
+
+    @staticmethod
+    def sendSubscribeMessage(access_token, openid, template_id, page, data):
+        '''发送订阅消息'''
+        url = 'https://api.weixin.qq.com/cgi-bin/message/subscribe/send?access_token=' + access_token
+
+        param = {
+            'touser': openid,
+            'template_id': template_id,
+            'page': page,
+            'data': data
+        }
+        print(9999999,param)
+        result = requests.post(url, data=json.dumps(param))
+        print('-------------------------')
+        result = result.json()
+        print(result)
+
+        #result = requests.post(url, data=json.dumps(param))
+        #result = result.json()
+        #if 'errcode' in result and result['errcode'] and str(result['errcode']) != '89237':
+        #    raise CustomError(u'微信请求失败' + str(result['errcode']) + ':' + result['errmsg'])
+        #return result
+
+    @staticmethod
+    def getWXAppCode(access_token, page, param):
+        '''获取小程序二维码'''
+        url = 'https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token={}'.format(access_token)
+        data = {"scene": "{}".format(param),
+                # "width": 1280, # 默认430
+                "line_color": {"r": 43, "g": 162, "b": 69},  # 自定义颜色
+                "is_hyaline": True}
+        result = requests.post(url, json=data)
+        # print('-------------------------',result)
+        upload_path = PathAndRename("wx_code/")
+        filename = "%s%s.png" % (
+            upload_path.path,
+            timezone.now().strftime('%Y%m%d%H%M%S%f'),
+        )
+        filename = filename.lower()
+        full_filename = "%s%s" % (settings.MEDIA_ROOT, filename)
+        with open(full_filename, 'wb') as destination:
+            destination.write(result.content)
+        return filename
+
+    @staticmethod
+    def getCommodityCode(access_token, page, commodity_id, company_no):
+        '''获取商品二维码'''
+        url = 'https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token={}'.format(access_token)
+        data = {"scene": "company_no={}&device_id={}".format(company_no, commodity_id),
+                # "width": 1280, # 默认430
+                "line_color": {"r": 43, "g": 162, "b": 69},  # 自定义颜色
+                "is_hyaline": True}
+        result = requests.post(url, json=data)
+
+        upload_path = PathAndRename("device/")
+        filename = "%s%s.png" % (
+            upload_path.path,
+            commodity_id,
+        )
+        filename = filename.lower()
+        full_filename = "%s%s" % (settings.MEDIA_ROOT, filename)
+        with open(full_filename, 'wb') as destination:
+            destination.write(result.content)
+        return filename
+
+    @staticmethod
+    def releaseCode(access_token):
+        '''已审核代码发布'''
+
+        url = 'https://api.weixin.qq.com/wxa/release?access_token=' + access_token
+
+        result = requests.post(url, data=json.dumps({}))
+        result = result.json()
+        if 'errcode' in result and result['errcode']:
+            raise CustomError(u'微信请求失败' + str(result['errcode']) + ' :' + result['errmsg'])
+        return result
+
+    @staticmethod
+    def createFastRegisterWXApp(name, code, code_type, legal_persona_wechat, legal_persona_name, component_phone, component_access_token):
+        '''快速注册企业小程序'''
+
+        url = 'https://api.weixin.qq.com/cgi-bin/component/fastregisterweapp?action=create&component_access_token=' + component_access_token
+        param = {
+            "name": name, # 企业名
+            "code": code, # 企业代码
+            "code_type": code_type,  # 企业代码类型(1:统一社会信用代码, 2:组织机构代码,3:营业执照注册号)
+            "legal_persona_wechat": legal_persona_wechat, # 法人微信
+            "legal_persona_name": legal_persona_name, # 法人姓名
+            "component_phone": component_phone, #第三方联系电话
+        }
+
+        result = requests.post(url, data=json.dumps(param))
+        result = result.json()
+        if 'errcode' in result and result['errcode']:
+            raise CustomError(u'微信请求失败' + str(result['errcode']) + ' :' + result['errmsg'])
+        return result
+
+    @staticmethod
+    def searchFastRegisterWXApp(name, legal_persona_wechat, legal_persona_name, component_access_token):
+        '''查询注册企业小程序任务状态'''
+
+        url = 'https://api.weixin.qq.com/cgi-bin/component/fastregisterweapp?action=search&component_access_token=' + component_access_token
+        param = {
+            "name": name,  # 企业名
+            "legal_persona_wechat": legal_persona_wechat,  # 法人微信
+            "legal_persona_name": legal_persona_name,  # 法人姓名
+        }
+
+        result = requests.post(url, data=json.dumps(param))
+        result = result.json()
+        if 'errcode' in result and result['errcode']:
+            raise CustomError(u'微信请求失败' + str(result['errcode']) + ' :' + result['errmsg'])
+        return result