浏览代码

Merge remote-tracking branch 'origin/master'

lyh 3 年之前
父节点
当前提交
68233296cd
共有 50 个文件被更改,包括 2289 次插入113 次删除
  1. 35 58
      apps/account/models.py
  2. 2 0
      apps/account/views.py
  3. 27 2
      apps/activity/filters.py
  4. 136 43
      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. 14 0
      apps/customer/order/urls.py
  23. 121 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. 2 2
      carwin_activity/app_settings.py
  29. 6 0
      carwin_activity/settings.py
  30. 1 0
      carwin_activity/urls.py
  31. 2 3
      uis/views/member/index.html
  32. 25 0
      util/__init__.py
  33. 128 0
      util/booleancharfield.py
  34. 39 0
      util/custom_modelviewset.py
  35. 18 0
      util/default_key_constructor.py
  36. 31 0
      util/exceptions.py
  37. 68 0
      util/file_operation.py
  38. 38 0
      util/format.py
  39. 62 0
      util/fragment.py
  40. 39 0
      util/handler.py
  41. 29 0
      util/pagination.py
  42. 37 0
      util/permission.py
  43. 45 0
      util/serializersfield.py
  44. 99 0
      util/wechatcashout.py
  45. 180 0
      util/wechatpay.py
  46. 27 0
      util/wx/WXBizDataCrypt.py
  47. 255 0
      util/wx/WXBizMsgCrypt.py
  48. 0 0
      util/wx/__init__.py
  49. 20 0
      util/wx/ierror.py
  50. 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 django.conf import settings
 
 
 from apps.exceptions import CustomError
 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):
 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:
         if not username:
             raise ValueError(u'请输入用户名')
             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.set_password(password)
         user.save(using=self._db)
         user.save(using=self._db)
         return user
         return user
 
 
     def create_superuser(self, username, password, **extra_fields):
     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.is_superuser = True
         u.save(using=self._db)
         u.save(using=self._db)
         return u
         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):
 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)
     branch = models.ForeignKey(Branch, verbose_name=u"门店",  on_delete=models.PROTECT, null=True, blank=True)
     name = models.CharField(max_length=20, verbose_name=u"姓名")
     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'已存在'})
     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)
     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)
     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)
     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)
     date_joined = models.DateTimeField(verbose_name=u'注册时间', default=timezone.now, null=True)
     enabled = models.BooleanField(verbose_name=u"在用", default=True)
     enabled = models.BooleanField(verbose_name=u"在用", default=True)
 
 

+ 2 - 0
apps/account/views.py

@@ -30,6 +30,8 @@ def login(request):
     form = MyAuthenticationForm(data=request.POST, request=request)
     form = MyAuthenticationForm(data=request.POST, request=request)
     if form.is_valid():
     if form.is_valid():
         user = form.get_user()
         user = form.get_user()
+        if user.type and user.type != User.EMPLOYEE:
+            return JSONError(u'非工作帐号,禁止登录!')
 
 
         if not user.enabled:
         if not user.enabled:
             return JSONError(u'该账号不可用')
             return JSONError(u'该账号不可用')

+ 27 - 2
apps/activity/filters.py

@@ -1,7 +1,11 @@
 #coding=utf-8
 #coding=utf-8
+from django.utils import timezone
+import datetime
 import django_filters
 import django_filters
 from apps.base import clean_datetime_range
 from apps.base import clean_datetime_range
 from .models import *
 from .models import *
+from apps.customer.models import Customer
+from apps.account.models import Branch
 
 
 
 
 class BranchFilter(django_filters.FilterSet):
 class BranchFilter(django_filters.FilterSet):
@@ -20,12 +24,12 @@ class BranchFilter(django_filters.FilterSet):
         super(BranchFilter, self).__init__(data, *args, **kwargs)
         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')
     nickname = django_filters.CharFilter(field_name='nickname', lookup_expr='icontains')
     tel = django_filters.CharFilter(field_name='tel', lookup_expr='icontains')
     tel = django_filters.CharFilter(field_name='tel', lookup_expr='icontains')
 
 
     class Meta:
     class Meta:
-        model = Member
+        model = Customer
         fields = '__all__'
         fields = '__all__'
 
 
 
 
@@ -34,10 +38,31 @@ class MemberCouponFilter(django_filters.FilterSet):
     coupon_name = django_filters.CharFilter(field_name='coupon__name', lookup_expr='icontains')
     coupon_name = django_filters.CharFilter(field_name='coupon__name', lookup_expr='icontains')
     activity_name = django_filters.CharFilter(field_name='activity__title', 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:
     class Meta:
         model = MemberCoupon
         model = MemberCoupon
         fields = '__all__'
         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):
 class OrderFilter(django_filters.FilterSet):
     activity_name = django_filters.CharFilter(field_name='activity__title', lookup_expr='icontains')
     activity_name = django_filters.CharFilter(field_name='activity__title', lookup_expr='icontains')

+ 136 - 43
apps/activity/models.py

@@ -3,49 +3,13 @@
 import datetime
 import datetime
 
 
 from django.db import models
 from django.db import models
-from django.contrib.auth.models import AbstractBaseUser, BaseUserManager, Group, PermissionsMixin
 from django.utils import timezone
 from django.utils import timezone
 from django.conf import settings
 from django.conf import settings
 
 
 from apps.exceptions import CustomError
 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 util.wechatpay import WechatPay, WeChatResponse
 
 
 
 
 class Activity(models.Model):
 class Activity(models.Model):
@@ -65,7 +29,7 @@ class Activity(models.Model):
     delete = models.BooleanField(verbose_name=u"删除", default=False)
     delete = models.BooleanField(verbose_name=u"删除", default=False)
 
 
     @staticmethod
     @staticmethod
-    def getById(id):
+    def getById1(id):
         try:
         try:
             id = int(id)
             id = int(id)
         except:
         except:
@@ -80,6 +44,114 @@ class Activity(models.Model):
         ordering = ['-id']
         ordering = ['-id']
         verbose_name = u"活动管理"
         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 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
+
 
 
 class Order(models.Model):
 class Order(models.Model):
     DEFAULT = 0
     DEFAULT = 0
@@ -92,7 +164,8 @@ class Order(models.Model):
     )
     )
     branch = models.ForeignKey(Branch, verbose_name=u"门店", on_delete=models.PROTECT)
     branch = models.ForeignKey(Branch, verbose_name=u"门店", on_delete=models.PROTECT)
     activity = models.ForeignKey(Activity, 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)
     amount = models.FloatField(verbose_name=u"费用", default=0)
     status = models.PositiveSmallIntegerField(choices=STATUS_CHOICES, verbose_name=u"状态", default=DEFAULT, editable=False)
     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)
     model = models.CharField(max_length=200, verbose_name=u"车型", null=True, blank=True)
@@ -107,6 +180,26 @@ class Order(models.Model):
         ordering = ['-id']
         ordering = ['-id']
         verbose_name = u"订单管理"
         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):
 class Coupon(models.Model):
     FIXED_DATE = 0
     FIXED_DATE = 0
@@ -142,11 +235,11 @@ class Coupon(models.Model):
 
 
 class MemberCoupon(models.Model):
 class MemberCoupon(models.Model):
     activity = models.ForeignKey(Activity, 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)
+    member = models.ForeignKey(Customer, verbose_name=u"会员", on_delete=models.PROTECT)
     coupon = models.ForeignKey(Coupon, verbose_name=u"优惠券", on_delete=models.PROTECT)
     coupon = models.ForeignKey(Coupon, verbose_name=u"优惠券", on_delete=models.PROTECT)
     receive_date = models.DateField(verbose_name=u"领取日期", null=True, blank=True)
     receive_date = models.DateField(verbose_name=u"领取日期", null=True, blank=True)
     end_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)
     write_off_time = models.DateTimeField(verbose_name=u"核销时间", editable=False, null=True, blank=True)
 
 
     class Meta:
     class Meta:

+ 5 - 3
apps/activity/serializers.py

@@ -9,6 +9,8 @@ from apps.foundation.models import BizLog
 from .models import *
 from .models import *
 from apps.serializer_errors import dump_serializer_errors
 from apps.serializer_errors import dump_serializer_errors
 from apps.foundation.models import BizLog
 from apps.foundation.models import BizLog
+from apps.customer.models import Customer
+from apps.account.models import Branch
 from apps import base
 from apps import base
 
 
 class BranchSerializer(serializers.ModelSerializer):
 class BranchSerializer(serializers.ModelSerializer):
@@ -59,11 +61,11 @@ class BranchSerializer(serializers.ModelSerializer):
         return instance
         return instance
 
 
 
 
-class MemberSerializer(serializers.ModelSerializer):
+class CustomerSerializer(serializers.ModelSerializer):
     create_time = serializers.DateTimeField(format='%Y-%m-%d %H:%M', read_only=True)
     create_time = serializers.DateTimeField(format='%Y-%m-%d %H:%M', read_only=True)
 
 
     class Meta:
     class Meta:
-        model = Member
+        model = Customer
         fields = '__all__'
         fields = '__all__'
 
 
 
 
@@ -85,7 +87,7 @@ class MemberCouponSerializer(serializers.ModelSerializer):
 
 
 class OrderSerializer(serializers.ModelSerializer):
 class OrderSerializer(serializers.ModelSerializer):
     activity_name = serializers.CharField(source='activity.title', read_only=True)
     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)
     member_tel = serializers.CharField(source='member.tel', read_only=True)
     branch_name = serializers.CharField(source='branch.name', read_only=True)
     branch_name = serializers.CharField(source='branch.name', read_only=True)
     status_text = serializers.CharField(source='get_status_display', read_only=True)
     status_text = serializers.CharField(source='get_status_display', read_only=True)

+ 4 - 2
apps/activity/views.py

@@ -17,6 +17,8 @@ from django.conf import settings
 from .models import *
 from .models import *
 from .serializers import *
 from .serializers import *
 from .filters import *
 from .filters import *
+from apps.customer.models import Customer
+from apps.account.models import Branch
 
 
 
 
 @token_required
 @token_required
@@ -55,9 +57,9 @@ def self_branch(request):
 
 
 @token_required
 @token_required
 def member_list(request):
 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)
     rows, total = utils.get_page_data(request, f.qs)
-    serializer = MemberSerializer(rows, many=True)
+    serializer = CustomerSerializer(rows, many=True)
     return DataGridJSONResponse(serializer.data, total)
     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.activity.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

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

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

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

@@ -0,0 +1,121 @@
+# 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.activity.models import Pay
+from apps.activity.filters import OrderFilter
+
+
+class OrderViewSet(ModelViewSet):
+    permission_classes = [IsCustomerUser, ]
+    queryset = Order.objects.filter()
+    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] + ']')

+ 2 - 2
carwin_activity/app_settings.py

@@ -6,8 +6,8 @@ DATABASES = {
     'default': {
     'default': {
         'ENGINE': 'django.db.backends.mysql',
         'ENGINE': 'django.db.backends.mysql',
         'NAME': 'carwin_activity',
         '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',
         'HOST': '127.0.0.1',
     },
     },
 }
 }

+ 6 - 0
carwin_activity/settings.py

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

+ 1 - 0
carwin_activity/urls.py

@@ -24,6 +24,7 @@ urlpatterns = [
     url(r'^$', index),
     url(r'^$', index),
     url(r'^account/', include('apps.account.urls')),
     url(r'^account/', include('apps.account.urls')),
     url(r'^activity/', include('apps.activity.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)
 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">
               <div class="layui-col-xs12 layui-col-sm12">
                 <label class="layui-form-label">会员名称:</label>
                 <label class="layui-form-label">会员名称:</label>
                 <div class="layui-input-block">
                 <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>
             </div>
             <div class="layui-col-xs12 layui-col-sm12">
             <div class="layui-col-xs12 layui-col-sm12">
@@ -75,9 +75,8 @@
       elem: '#datagrid'
       elem: '#datagrid'
       ,url: '/activity/member/data/'
       ,url: '/activity/member/data/'
       ,cols: [[
       ,cols: [[
-        {field:'nickname', title:'名称', width:200}
+        {field:'name', title:'名称', width:200}
         ,{field:'tel', title:'电话',width: 200}
         ,{field:'tel', title:'电话',width: 200}
-        ,{field:'create_time', title:'添加时间', width:160}
         ,{width:100, align:'left', fixed: 'right', toolbar: '#datagrid-operate-bar'}
         ,{width:100, align:'left', fixed: 'right', toolbar: '#datagrid-operate-bar'}
       ]]
       ]]
       ,page: true
       ,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