Преглед на файлове

Merge remote-tracking branch 'origin/master'

lyh преди 3 години
родител
ревизия
cccc367840
променени са 52 файла, в които са добавени 1099 реда и са изтрити 228 реда
  1. 0 0
      apps/WechatApplet/__init__.py
  2. 0 0
      apps/WechatApplet/migrations/__init__.py
  3. 64 0
      apps/WechatApplet/models.py
  4. 0 0
      apps/account/migrations/__init__.py
  5. 22 17
      apps/activity/filters.py
  6. 0 0
      apps/activity/migrations/__init__.py
  7. 303 28
      apps/activity/models.py
  8. 5 2
      apps/api/views.py
  9. 1 1
      apps/customer/activity/serializers.py
  10. 1 0
      apps/customer/activity/urls.py
  11. 35 6
      apps/customer/activity/views.py
  12. 3 1
      apps/customer/coupon/serializers.py
  13. 1 1
      apps/customer/coupon/urls.py
  14. 0 2
      apps/customer/coupon/views.py
  15. 0 0
      apps/customer/migrations/__init__.py
  16. 35 13
      apps/customer/order/serializers.py
  17. 0 1
      apps/customer/order/urls.py
  18. 39 25
      apps/customer/order/views.py
  19. 2 0
      apps/customer/serializers.py
  20. 7 6
      apps/customer/tool.py
  21. 1 0
      apps/customer/urls.py
  22. 43 0
      apps/customer/views.py
  23. 73 7
      carwin_activity/settings.py
  24. BIN
      uis/media/customer/1-20181017164122424000.png
  25. BIN
      uis/media/customer/1-20181017164122747000.png
  26. BIN
      uis/media/customer/1-20190110101354931000.jpeg
  27. BIN
      uis/media/customer/1-20190110120026324000.jpeg
  28. BIN
      uis/media/customer/7-20190110102942505000.jpeg
  29. BIN
      uis/media/customer/7-20190110103334714000.jpeg
  30. BIN
      uis/media/customer/7-20190110103353432000.jpeg
  31. BIN
      uis/media/customer/7-20190110103442732000.jpeg
  32. BIN
      uis/media/customer/7-20190110104808983000.jpeg
  33. BIN
      uis/media/customer/7-20190110111804327000.jpeg
  34. BIN
      uis/media/customer/7-20190110140948686000.jpeg
  35. BIN
      uis/media/customer/8-20190110101805918000.jpeg
  36. BIN
      uis/media/customer/8-20190110141044866000.jpeg
  37. BIN
      uis/media/customer/9-20190110112049829000.jpeg
  38. BIN
      uis/media/customer/9-20190110114914735000.jpeg
  39. BIN
      uis/media/customer/9-20190110114954166000.jpeg
  40. BIN
      uis/media/office_notice/files/4-20190713140845211000.png
  41. BIN
      uis/media/office_notice/files/4-20190713140917615000.zip
  42. BIN
      uis/media/office_notice/files/4-20190713141009401000.pdf
  43. BIN
      uis/media/purchase_order/invoice/15-20190622101902920000.png
  44. BIN
      uis/media/purchase_order/invoice/15-20190709105008276000.png
  45. BIN
      uis/media/purchase_order/invoice/20190424134614.png
  46. BIN
      uis/media/purchase_order/invoice/202101071033.png
  47. 11 0
      util/format.py
  48. 3 6
      util/permission.py
  49. 236 0
      util/splitaccount_tool.py
  50. 0 99
      util/wechatcashout.py
  51. 195 3
      util/wechatpay.py
  52. 19 10
      util/wx/wechat.py

+ 0 - 0
uis/media/office_notice/files/4-20190713140945996000.wav → apps/WechatApplet/__init__.py


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


+ 64 - 0
apps/WechatApplet/models.py

@@ -0,0 +1,64 @@
+# coding=utf-8
+
+import time
+import os
+from django.conf import settings
+from django.db import models
+from django.utils import timezone
+from util.wx.wechat import WeChat
+from util.exceptions import CustomError
+
+
+class WechatApplet(models.Model):
+    authorizer_appid = models.CharField(max_length=512, verbose_name=u'小程序appid')
+    secret = models.CharField(max_length=512, verbose_name=u'小程序秘钥', null=True)
+
+    authorizer_refresh_token = models.CharField(max_length=512, verbose_name=u'刷新令牌', null=True)
+    authorizer_access_token = models.CharField(max_length=512, verbose_name=u'令牌', null=True)
+    access_token_gtime = models.DateTimeField(verbose_name=u"获取令牌时间", null=True)
+    expires_in = models.IntegerField(verbose_name=u'令牌有效期', null=True)
+
+    agent_num = models.CharField(max_length=512, verbose_name=u'商户号', default="")
+    agent_key = models.CharField(max_length=512, verbose_name=u'商户密钥', default="")
+
+    class Meta:
+        db_table = "wechat_applet"
+        ordering = ['-id']
+        index_together = ()
+        verbose_name = u"小程序"
+        default_permissions = ()
+        permissions = []
+
+    @staticmethod
+    def getByAppid(appid):
+        instance = WechatApplet.objects.filter(authorizer_appid=appid).first()
+        if not instance:
+            raise CustomError(u'未找到相应的小程序!')
+        return instance
+
+    def getAccessToken(self):
+        if self.authorizer_access_token:
+            last_time = time.mktime(self.access_token_gtime.timetuple()) + self.expires_in
+            now = time.mktime(timezone.now().timetuple())
+            if last_time > now:
+                return self.authorizer_access_token
+        gtime = timezone.now()
+        res = WeChat.getAccessToken(self.authorizer_appid, self.secret)
+        self.refreshAccessToken(gtime, res['access_token'], res['expires_in'])
+        return self.authorizer_access_token
+
+    def refreshAccessToken(self, gtime, access_token, expires_in, refresh_token=''):
+        self.authorizer_access_token = access_token
+        self.access_token_gtime = gtime
+        self.expires_in = expires_in
+        self.authorizer_refresh_token = refresh_token
+        self.save()
+
+    def generateActivityCode(self, customer_id, activity_id):
+        filename = "{}{}_{}.png".format("activiytCode/", activity_id, customer_id)
+        full_filename = "{}/{}".format(settings.MEDIA_ROOT, filename)
+        if os.path.exists(full_filename):
+            return filename
+        page = 'pages/index/index'
+        filename = WeChat.getActivityCode(self.getAccessToken(), page, customer_id, activity_id)
+        return filename

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


+ 22 - 17
apps/activity/filters.py

@@ -38,29 +38,22 @@ class MemberCouponFilter(django_filters.FilterSet):
     coupon_name = django_filters.CharFilter(field_name='coupon__name', lookup_expr='icontains')
     activity_name = django_filters.CharFilter(field_name='activity__title', lookup_expr='icontains')
 
-    not_used = django_filters.CharFilter(method='find_not_used')
-    used = django_filters.CharFilter(method='find_used')
-    overdue = django_filters.CharFilter(method='find_overdue')
+    status = django_filters.CharFilter(method='find_coupon_by_status')
 
     class Meta:
         model = MemberCoupon
         fields = '__all__'
 
-    def find_not_used(self, queryset, *args):
+    def find_coupon_by_status(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)
+            if args[1] == '1':#待使用
+                now = datetime.datetime.date(timezone.now())
+                queryset = queryset.filter(write_off=False, end_date__gt=now)
+            elif args[1] == '2':#已使用
+                queryset = queryset.filter(write_off=True)
+            elif args[1] == '3':#已过期
+                now = datetime.datetime.date(timezone.now())
+                queryset = queryset.filter(write_off=False, end_date__lt=now)
         return queryset
 
 
@@ -70,10 +63,22 @@ class OrderFilter(django_filters.FilterSet):
     activity = django_filters.CharFilter(field_name='activity')
     delete = django_filters.CharFilter(field_name='delete')
 
+    order_status = django_filters.CharFilter(method='find_by_order_status')
+
     class Meta:
         model = Order
         fields = '__all__'
 
+    def find_by_order_status(self, queryset, *args):
+        if args[1]:
+            if args[1] == '1': # 进行中
+                queryset = queryset.filter(status=Order.DEFAULT)
+            elif args[1] == '2': # 已完成
+                queryset = queryset.filter(status=Order.FINISH)
+            elif args[1] == '3': # 已失败
+                queryset = queryset.filter(status=Order.FAIL)
+        return queryset
+
 
 
 class CouponFilter(django_filters.FilterSet):

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


+ 303 - 28
apps/activity/models.py

@@ -8,8 +8,8 @@ from django.conf import settings
 
 from apps.exceptions import CustomError
 from apps.account.models import Branch
-from apps.customer.models import Customer
-from util.wechatpay import WechatPay, WeChatResponse
+from apps.customer.models import Customer, CustomerWechat
+from util.wechatpay import WechatPay, WeChatResponse, SplitAccountFuc
 
 
 class Activity(models.Model):
@@ -29,11 +29,11 @@ class Activity(models.Model):
     delete = models.BooleanField(verbose_name=u"删除", default=False)
 
     @staticmethod
-    def getById1(id):
+    def getById(id):
         try:
             id = int(id)
         except:
-            raise CustomError(u'无效的活动')
+            raise CustomError(u'无效的活动Id')
         instance = Activity.objects.filter(pk=id).first()
         if not instance:
             raise CustomError(u'未找到相应的活动')
@@ -67,6 +67,248 @@ class Activity(models.Model):
         return instance
 
 
+class SplitAccount(models.Model):
+    WAIT = 1
+    PROCESSING = 2
+    FINISHED = 3
+    STATUS_CHOICES = (
+        (WAIT, u'待分账'),
+        (PROCESSING, u'处理中'),
+        (FINISHED, 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)
+    status = models.PositiveSmallIntegerField(choices=STATUS_CHOICES, verbose_name=u"分账状态", default=WAIT)
+    amount = models.BigIntegerField(verbose_name=u"分账金额", null=True)  # 单位分
+    order_id = models.CharField(max_length=100, verbose_name=u"微信分账单号", null=True, editable=False)  # 微信分账单号,微信支付系统返回的唯一标识
+
+    class Meta:
+        db_table = "split_account"
+        verbose_name = u"分账"
+        ordering = ('-id',)
+        index_together = (
+            'create_time',
+            'status',
+        )
+        unique_together = (
+            'no',
+        )
+        default_permissions = ()
+
+
+    def unfreezeAccount(self):
+        # 解冻剩余资金  如果由于客户或其他原因 无法完成分账  就手动解除资金冻结
+        pass
+
+    def handSplitAccont(self, spd, spr, pay):
+        '''手动申请分账(在自动申请分账失败后  手动申请分账)'''
+        if spd.status == SplitAccountDetail.SUCCESS:
+            raise CustomError(u'该分账已成功,禁止重复申请!')
+        appid = ''
+        mchid = ''
+        private_key = ''
+        cert_serial_no = ''
+        apiv3_key = ''
+        spc = SplitAccountFuc(appid, mchid, private_key, cert_serial_no, apiv3_key, cert_dir=None, proxy=None)
+        # 状态是"待分账" 的查询分账
+        if spd.status == SplitAccountDetail.PENDING:
+            self.splictAccountQuery(spc, pay.no, self.no, spd)
+
+            if spd.status == SplitAccountDetail.SUCCESS:
+                raise CustomError(u'该分账已成功!')
+
+            if spd.status == SplitAccountDetail.PENDING:
+                raise CustomError(u'该分账正在处理!')
+        # 添加分账接收人
+        self.splictAccountAddReceiver(spc, spr)
+        # 分账申请
+        receivers = [{'account': spd.account, 'amount': spd.amount, 'description': "支付推荐佣金"}]
+        success, data = spc.splitaccount_order(pay.no, self.no, receivers)
+        if not success:
+            raise CustomError(u'分账申请失败!')
+        # 分账请求成功  结果不一定成功
+        self.order_id = data['order_id']
+        if data['state'] == 'PROCESSING':
+            self.status = SplitAccount.PROCESSING
+        if data['state'] == 'FINISHED':
+            self.status = SplitAccount.FINISHED
+        self.save()
+        for item in data['receivers']:
+            # 现在只对一个推荐人分账
+            if item['account'] == spd.account:
+                if item['result'] == 'PENDING':
+                    spd.status = SplitAccountDetail.PENDING
+                elif item['result'] == 'SUCCESS':
+                    spd.status = SplitAccountDetail.SUCCESS
+                elif item['result'] == 'CLOSED':
+                    spd.status = SplitAccountDetail.CLOSED
+                    spd.fail_reason = item['fail_reason']
+                spd.create_time = item['create_time']
+                spd.finish_time = item['finish_time']
+                spd.detail_no = item['detail_id']
+                spd.save()
+
+    def splictAccountAddReceiver(self, split_account_func, spr):
+        '''添加分账接收人'''
+        if spr.status != SplitAccountReceiver.ADD:
+            add = split_account_func.splitaccount_addreceiver(spr.account)
+            if not add:
+                raise CustomError(u'添加分账接收方失败!')
+            spr.status = SplitAccountReceiver.ADD
+            spr.save()
+
+    def splictAccountQuery(self, split_account_func, transaction_id, out_order_no, detail):
+        '''查询分账结果'''
+        success, data = split_account_func.splitaccount_orderquery(transaction_id, out_order_no)
+        if not success:
+            raise CustomError(u'[%s]查询分账结果失败!' % out_order_no)
+        if data['state'] == 'PROCESSING':
+            self.status = SplitAccount.PROCESSING
+        if data['state'] == 'FINISHED':
+            self.status = SplitAccount.FINISHED
+        self.save()
+        for item in data['receivers']:
+            if detail and detail.account == item['account']:
+                if item['result'] == 'SUCCESS':
+                    detail.status = SplitAccountDetail.SUCCESS
+                elif item['result'] == 'CLOSED':
+                    detail.status = SplitAccountDetail.CLOSED
+                    detail.fail_reason = item['fail_reason']
+                detail.create_time = item['create_time']
+                detail.finish_time = item['finish_time']
+                detail.detail_no = item['detail_id']
+                detail.save()
+
+    def splitAccount(self, spd, spr, pay):
+        if spd.status != SplitAccountDetail.DEFAULT:
+            return
+        appid = ''
+        mchid = ''
+        private_key = ''
+        cert_serial_no = ''
+        apiv3_key = ''
+        spc = SplitAccountFuc(appid, mchid, private_key, cert_serial_no, apiv3_key, cert_dir=None, proxy=None)
+
+        if spr.status != SplitAccountReceiver.ADD:
+            add = spc.splitaccount_addreceiver(spr.account)
+            if not add:
+                # 添加分账接收方失败 不再继续
+                return
+            spr.status = SplitAccountReceiver.ADD
+            spr.save()
+
+        receivers = [{'account': spd.account, 'amount': spd.amount, 'description': "支付推荐佣金"}]
+        success, data = spc.splitaccount_order(pay.no, self.no, receivers)
+        if not success:
+            # 分账请求失败 不再继续
+            return
+         # 分账请求成功  结果不一定成功
+        self.order_id = data['order_id']
+        if data['state'] == 'PROCESSING':
+            self.status = SplitAccount.PROCESSING
+        if data['state'] == 'FINISHED':
+            self.status = SplitAccount.FINISHED
+        self.save()
+        for item in data['receivers']:
+            # 现在只对一个推荐人分账
+            if item['account'] == spd.account:
+                if item['result'] == 'PENDING':
+                    spd.status = SplitAccountDetail.PENDING
+                elif item['result'] == 'SUCCESS':
+                    spd.status = SplitAccountDetail.SUCCESS
+                elif item['result'] == 'CLOSED':
+                    spd.status = SplitAccountDetail.CLOSED
+                    spd.fail_reason = item['fail_reason']
+                spd.create_time = item['create_time']
+                spd.finish_time = item['finish_time']
+                spd.detail_no = item['detail_id']
+                spd.save()
+
+    @staticmethod
+    def _addnew(branch, order_id, rebate, customer):
+        if rebate <= 0:
+            return ''
+        customer_wechat = CustomerWechat.objects.filter(customer=customer).first()
+        if not customer_wechat:
+            return ''
+
+        no = "FZ" + timezone.now().strftime('%y%m%d%H%M%S') + str(order_id)
+        instance = SplitAccount.objects.create(
+            branch=branch,
+            no=no,
+            status=SplitAccount.WAIT,
+            amount=rebate
+        )
+
+        detail = SplitAccountDetail.objects.create(
+            main=instance,
+            customer=customer,
+            account=customer_wechat.openid,
+            amount=rebate
+        )
+
+        receiver = SplitAccountReceiver.objects.filter(account=detail.account).first()
+        if not receiver:
+            SplitAccountReceiver.objects.create(
+                account=detail.account
+            )
+        return instance, detail, receiver
+
+
+class SplitAccountDetail(models.Model):
+    DEFAULT = 0
+    PENDING = 1
+    SUCCESS = 2
+    CLOSED = 3
+    STATUS_CHOICES = (
+        (DEFAULT, u'未处理'),
+        (PENDING, u'待分账'),
+        (SUCCESS, u'分账成功'),
+        (CLOSED, u'已关闭'),
+    )
+
+    main = models.ForeignKey(SplitAccount, verbose_name=u'分账单', on_delete=models.PROTECT, editable=False)
+    customer = models.ForeignKey(Customer, verbose_name=u'客户', on_delete=models.PROTECT, editable=False)
+    account = models.CharField(max_length=100, verbose_name=u'接收账户') # 接受方的openid
+    amount = models.BigIntegerField(verbose_name=u'分账金额')  # 单位是分
+    status = models.PositiveSmallIntegerField(choices=STATUS_CHOICES, verbose_name=u"分账结果", default=DEFAULT)
+    fail_reason = models.CharField(max_length=1000, verbose_name=u"分账失败原因", null=True, blank=True)
+    create_time = models.DateTimeField(verbose_name=u"分账创建时间", null=True, blank=True)
+    finish_time = models.DateTimeField(verbose_name=u"分账完成时间", null=True, blank=True)
+    detail_no = models.CharField(verbose_name=u'分账明细单号', max_length=100, null=True, editable=False)
+
+    class Meta:
+        db_table = "split_account_detail"
+        verbose_name = u"分账详细"
+        ordering = ('-id',)
+        index_together = ('status',)
+        default_permissions = ()
+
+
+class SplitAccountReceiver(models.Model):
+    UN_ADD = 1
+    ADD = 2
+    DELETE = 3
+    STATUS_CHOICES = (
+        (UN_ADD, u'待添加'),
+        (ADD, u'已添加'),
+        (DELETE, u'已删除'),
+    )
+
+    account = models.CharField(max_length=100, verbose_name=u'接收账户') # 接受方的openid
+    status = models.PositiveSmallIntegerField(choices=STATUS_CHOICES, verbose_name=u"状态", default=UN_ADD)
+    create_time = models.DateTimeField(verbose_name=u"创建时间", default=timezone.now)
+
+    class Meta:
+        db_table = "split_account_receiver"
+        verbose_name = u"分账接收人"
+        ordering = ('-id',)
+        index_together = ('account', 'status', )
+        default_permissions = ()
+
+
 class Pay(models.Model):
 
     WAIT = 1
@@ -83,8 +325,9 @@ class Pay(models.Model):
     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)
+    profit_sharing = models.CharField(max_length=10, verbose_name=u"是否分账", default="N")
+    precreate_amount = models.BigIntegerField(verbose_name=u"预支付金额") # 单位分
+    amount = models.BigIntegerField(verbose_name=u"实际支付金额", null=True) # 单位分
 
     class Meta:
         db_table = "pay"
@@ -114,9 +357,7 @@ class Pay(models.Model):
         self.save()
         order = Order.objects.filter(pay=self).first()
         if order:
-            order.status = Order.FINISH
-            order.amount = self.amount
-            order.save()
+            order.orderPayConfirm(self.amount)
 
     @staticmethod
     def getByNo(no):
@@ -126,12 +367,12 @@ class Pay(models.Model):
         return instance
 
     @staticmethod
-    def wechatPay(branch, customer, amount, openid):
-        instance = Pay._addnew(branch, customer, amount)
-        return instance, instance._wechatUnifiedOrder(openid)
+    def wechatPay(app, branch, customer, amount, openid, profit_sharing):
+        instance = Pay._addnew(branch, customer, amount, profit_sharing)
+        return instance, instance._wechatUnifiedOrder(app, openid)
 
     @staticmethod
-    def _addnew(branch, customer, amount):
+    def _addnew(branch, customer, amount, profit_sharing):
         if amount <= 0:
             raise CustomError(u'无效的付款金额!')
 
@@ -140,18 +381,26 @@ class Pay(models.Model):
             branch=branch,
             no=no,
             customer=customer,
-            type=type,
             status=Pay.WAIT,
-            precreate_amount=amount
+            precreate_amount=amount,
+            profit_sharing=profit_sharing
         )
         return instance
 
-    def _wechatUnifiedOrder(self, openid):
-        wechatpay = WechatPay(settings.APPID, settings.AGENT_NUM, settings.AGENT_KEY)
-        wechatpay.unifiedOrder(self.no, self.precreate_amount, openid)
+    def _wechatUnifiedOrder(self, app, openid):
+        wechatpay = WechatPay(app.authorizer_appid, app.agent_num, app.agent_key)
+        wechatpay.unifiedOrder(self.no, self.precreate_amount, openid, self.profit_sharing)
         data = wechatpay.getAppString()
         return data
 
+    def split_account(self):
+        order = Order.objects.filter(pay=self).first()
+        if order and self.status == Pay.CONFIRM and self.profit_sharing == "Y":
+            sp, spd, spr = SplitAccount._addnew(self.branch, self.id, self.rebate, self.recommend_member)
+            order.splitaccount = sp
+            order.save()
+            sp.splitAccount(spd, spr, self)
+
 
 class Order(models.Model):
     DEFAULT = 0
@@ -162,18 +411,19 @@ class Order(models.Model):
         (FINISH, u'已完成'),
         (FAIL, u'失败'),
     )
-    branch = models.ForeignKey(Branch, verbose_name=u"门店", on_delete=models.PROTECT)
+    branch = models.ForeignKey(Branch, verbose_name=u"门店", on_delete=models.PROTECT, editable=False)
     activity = models.ForeignKey(Activity, 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)
+    pay = models.ForeignKey(Pay, verbose_name='支付信息', on_delete=models.PROTECT, null=True)
+    splitaccount = models.ForeignKey(SplitAccount, verbose_name='分账信息', on_delete=models.PROTECT, null=True)
+    member = models.ForeignKey(Customer, verbose_name=u"会员", on_delete=models.PROTECT, editable=False)
+    amount = models.BigIntegerField(verbose_name=u"费用", default=0)  # 单位分
     status = models.PositiveSmallIntegerField(choices=STATUS_CHOICES, verbose_name=u"状态", default=DEFAULT, editable=False)
     model = models.CharField(max_length=200, verbose_name=u"车型", null=True, blank=True)
     tel = models.CharField(max_length=100, verbose_name=u"电话", null=True, blank=True)
     number = models.CharField(max_length=50, verbose_name=u"车牌号", null=True, blank=True)
     name = models.CharField(max_length=50, verbose_name=u"姓名", null=True, blank=True)
     recommend_member = models.ForeignKey(Customer, verbose_name=u"推荐人", related_name='recommend_member_ref_customer', on_delete=models.PROTECT, null=True, blank=True, editable=False)
-    rebate = models.FloatField(verbose_name=u"返利", default=0)
+    rebate = models.BigIntegerField(verbose_name=u"返利", default=0)  # 单位分
     create_time = models.DateTimeField(verbose_name=u"添加时间", auto_now_add=True, editable=False)
     delete = models.BooleanField(verbose_name=u"删除", default=False)
 
@@ -182,26 +432,51 @@ class Order(models.Model):
         ordering = ['-id']
         verbose_name = u"订单管理"
 
-    def payOrder(self, openid):
+    def payOrder(self, openid, app):
         # 如果订单上的金额不等于活动金额 (比如下单没有支付,后来活动金额修改,现在活动金额不等于订单上活动金额) 还按下单时保存到额金额支付
         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)
+            checkRexponse = WeChatResponse(app.authorizer_appid, app.agent_num, app.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)
+                wechatpay = WechatPay(app.authorizer_appid, app.agent_num, app.agent_key)
+                data = wechatpay.unifiedOrder(openid, pay_no, self.amount, self.pay.profit_sharing)
                 return data
             self.pay.payClosed()
 
-        pay, data = Pay.wechatPay(self.branch, self.member, self.amount, openid)
+        profit_sharing = "N"
+        if self.rebate > 0 and self.recommend_member:
+            profit_sharing = "Y"
+        pay, data = Pay.wechatPay(app, self.branch, self.member, self.amount, openid, profit_sharing)
         self.pay = pay
         self.save()
         return data
 
+    def orderPayConfirm(self, amount):
+        # 在这进行分账  先去查分账接收人的状态  如果没有就创建一条记录 然后添加分账接收人 然后分账
+        # 如果有且状态是已添加  就直接分账
+        # 如果有且状态是待添加或已删除 就先添加分账接收人 然后分账
+        self.status = Order.FINISH
+        self.amount = amount
+        self.save()
+        coupons = ActivityCoupon.objects.filter(activity=self.activity, coupon__enabled=True)
+        now = datetime.datetime.date(timezone.now())
+        for item in coupons:
+            if item.off_type == Coupon.FIXED_DATE:
+                end_date = item.end_date
+            else:
+                end_date = now + datetime.timedelta(days=item.end_days)
+            MemberCoupon.objects.create(
+                activity=self.activity,
+                member=self.member,
+                coupon=item,
+                receive_date=now,
+                end_date=end_date
+            )
+
 
 class Coupon(models.Model):
     FIXED_DATE = 0

+ 5 - 2
apps/api/views.py

@@ -13,6 +13,7 @@ from util.wechatpay import WechatPayNotify
 from util.wx.wechat import WeChat
 
 from apps.activity.models import Pay
+from apps.WechatApplet.models import WechatApplet
 from apps.foundation.models import BizLog
 
 
@@ -21,10 +22,10 @@ class WechatNotifyView(APIView):
     def dispatch(self, request, *args, **kwargs):
         param = request.body
         appid = kwargs['appid']
+        app = WechatApplet.getByAppid(appid)
         # param = request.body.decode('utf-8')
-        notify = WechatPayNotify(param, settings.AGENT_KEY)
+        notify = WechatPayNotify(param, app.agent_key)
         try:
-            WeChat.checkAppid(appid)
             data = notify.handle()
             if not data:
                 raise CustomError(u'错误的请求!')
@@ -40,6 +41,8 @@ class WechatNotifyView(APIView):
                 pay = Pay.getByNo(no)
                 pay.payConfirm(amount)
                 BizLog.objects.addnew(pay.customer.user, BizLog.INSERT, u'微信支付成功,no=%s' % no, param)
+            # 微信支付成功之后分账
+            pay.split_account()
         except Exception as e:
             traceback.print_exc()
             return HttpResponse(WechatPayNotify.response_fail())

+ 1 - 1
apps/customer/activity/serializers.py

@@ -8,4 +8,4 @@ class ActivitySerializer(serializers.ModelSerializer):
 
     class Meta:
         model = Activity
-        fields = '__all__'
+        fields = ('branch_address', 'branch_name', 'branch_tel', 'describe', 'title', 'amount', )

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

@@ -6,5 +6,6 @@ from .views import *
 
 urlpatterns = [
     url(r'^detail/$', ActivityDetailView.as_view()),
+    url(r'^poster/$', ActivityPosterView.as_view()),
 ]
 

+ 35 - 6
apps/customer/activity/views.py

@@ -1,27 +1,56 @@
 # coding=utf-8
 
+import traceback
+
 from rest_framework import generics
+from rest_framework.views import APIView
+
+from django.conf import settings
 
-from util import response_ok
+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.customer.activity.serializers import ActivitySerializer
 from apps.activity.models import Activity
+from apps.WechatApplet.models import WechatApplet
 
 
 class ActivityDetailView(generics.RetrieveAPIView):
     # permission_classes = [IsCustomerUser, ]
-    queryset = Activity.objects.filter(enabled=True, delete=False)
+    queryset = Activity.objects.filter(enabled=True, delete=False, check_status=settings.PASS)
     serializer_class = ActivitySerializer
 
     def retrieve(self, request, *args, **kwargs):
-        id = request.GET.get('id')
-        appid = request.GET.get('appid')
-        WeChat.checkAppid(appid)
+        activity_id = request.GET.get('activity_id')
 
-        instance = self.queryset.filter(id=id).first()
+        instance = self.queryset.filter(id=activity_id).first()
         if not instance:
             raise CustomError(u'未找到相应活动信息!')
         serializer = self.get_serializer(instance)
         return response_ok(serializer.data)
+
+
+
+class ActivityPosterView(APIView):
+    '''必须登录才能生成海报'''
+    permission_classes = [IsCustomerUser, ]
+
+    def get(self, request):
+        appid = request.GET.get('appid', None)
+        activity_id = request.GET.get('activity_id', None)
+
+        try:
+            app = WechatApplet.getByAppid(appid)
+            activity = Activity.getById(activity_id)
+            wxCode = app.generateActivityCode(request.customer.id, activity.id)
+            serializer = ActivitySerializer(activity)
+            data = serializer.data
+            data['wxCode'] = settings.MEDIA_URL + wxCode
+            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))

+ 3 - 1
apps/customer/coupon/serializers.py

@@ -5,7 +5,9 @@ from apps.activity.models import MemberCoupon
 
 
 class MemberCouponSerializer(serializers.ModelSerializer):
+    activity_name = serializers.CharField(source='activity.title', read_only=True)
+    coupon_name = serializers.CharField(source='coupon.name', read_only=True)
 
     class Meta:
         model = MemberCoupon
-        fields = '__all__'
+        fields = ('activity_name', 'coupon_name', 'receive_date', 'end_date', 'write_off_time', )

+ 1 - 1
apps/customer/coupon/urls.py

@@ -5,6 +5,6 @@ from django.conf.urls import url
 from .views import *
 
 urlpatterns = [
-    url(r'^coupon/$', CouponListView.as_view()),
+    url(r'', CouponListView.as_view()),
 ]
 

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

@@ -8,8 +8,6 @@ from apps.activity.models import MemberCoupon
 from apps.activity.filters import MemberCouponFilter
 
 
-
-
 class CouponListView(generics.ListAPIView):
     '''
     小程序显示我的卡券--显示用户所有优惠券信息

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


+ 35 - 13
apps/customer/order/serializers.py

@@ -1,39 +1,61 @@
 # coding=utf-8
-import datetime
-from django.conf import settings
-from django.utils import timezone
-from util.exceptions import CustomError
+from apps.exceptions import CustomError
 from rest_framework import serializers
 from apps.activity.models import Order
+from apps.customer.models import Customer
+from util.format import Formater
 
 
 class ActivityOrderSerializer(serializers.ModelSerializer):
+    number = serializers.SerializerMethodField()
+    create_time = serializers.DateTimeField(format='%m-%d %H:%M', read_only=True)
 
     class Meta:
         model = Order
-        fields = ('id', 'number', 'create_time', )
+        fields = ('number', 'create_time', )
 
-
-class MemberOrderSerializer(serializers.ModelSerializer):
-
-    class Meta:
-        model = Order
-        fields = '__all__'
+    def get_number(self, obj):
+        if obj.number:
+            l = len(obj.number)
+            if l < 4:
+                return ''
+            return obj.number[:2] + '***' + obj.number[l - 2:l]
+        return ''
 
 
 class OrderSerializer(serializers.ModelSerializer):
+    activity_name = serializers.CharField(source='activity.title', read_only=True)
 
     class Meta:
         model = Order
         fields = '__all__'
 
     def validate(self, attrs):
-        attrs['customer'] = self.context['request'].customer
-        if 'activity' in attrs:
+        attrs['member'] = self.context['request'].customer
+        if 'activity' in attrs and attrs['activity']:
             attrs['activity'].checkStatus()
+            required_signs = []
+            if attrs['activity'].required_signs:
+                required_signs = attrs['activity'].required_signs.split(',')
+            for item in required_signs:
+                if item == u'姓名':
+                    if ('name' not in attrs) or (not attrs['name']):
+                        raise CustomError(u'请输入姓名')
+                if item == u'车牌号':
+                    if ('number' not in attrs) or (not attrs['number']):
+                        raise CustomError(u'请输入车牌号')
+                if item == u'车型':
+                    if ('model' not in attrs) or (not attrs['model']):
+                        raise CustomError(u'请输入车型')
+        if 'customer_id' in attrs and attrs['customer_id']:
+            recommend_member = Customer.objects.filter(id=attrs['customer_id']).first(0)
+            if recommend_member:
+                attrs['recommend_member'] = recommend_member
         return attrs
 
     def create(self, validated_data):
         validated_data['branch'] = validated_data['activity'].branch
+        validated_data['amount'] = Formater.formatPrice(validated_data['activity'].amount)
+        validated_data['rebate'] = Formater.formatPrice(validated_data['activity'].rebate)
         instance = super(OrderSerializer, self).create(validated_data)
         return instance

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

@@ -7,7 +7,6 @@ from .views import *
 
 urlpatterns = [
     url(r'^activity_order/$', ActivityOrderListView.as_view()),
-    url(r'^list/$', OrderListView.as_view()),
 ]
 router = SimpleRouter()
 router.register(r'', OrderViewSet)

+ 39 - 25
apps/customer/order/views.py

@@ -16,17 +16,22 @@ 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.customer.order.serializers import OrderSerializer, ActivityOrderSerializer
+from apps.activity.models import Activity, Order, Pay
+from apps.WechatApplet.models import WechatApplet
 from apps.activity.filters import OrderFilter
 
 
 class OrderViewSet(ModelViewSet):
     permission_classes = [IsCustomerUser, ]
-    queryset = Order.objects.filter()
+    queryset = Order.objects.filter(delete=False)
     serializer_class = OrderSerializer
 
+    def filter_queryset(self, queryset):
+        queryset = queryset.filter(member=self.request.customer)
+        f = OrderFilter(self.request.GET, queryset=queryset)
+        return f.qs
+
     def create(self, request, *args, **kwargs):
         try:
             with transaction.atomic():
@@ -38,15 +43,20 @@ class OrderViewSet(ModelViewSet):
 
                 if instance.amount == 0:
                     instance.status = Order.FINISH
+                    instance.save()
                     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)
+                    app = WechatApplet.getByAppid(appid)
+                    profit_sharing = "N"
+                    if instance.rebate > 0 and instance.recommend_member:
+                        profit_sharing = "Y"
+                    pay, query_string = Pay.wechatPay(app, instance.branch, instance.member, instance.amount, openid, profit_sharing)
                     instance.pay = pay
+                    instance.save()
                     BizLog.objects.addnew(request.customer.user, BizLog.INSERT, u'添加商品订单,id=%d' % instance.id, validated_data)
                     if query_string:
                         return response_ok(query_string)
@@ -64,14 +74,14 @@ class OrderViewSet(ModelViewSet):
         try:
             if not openid:
                 raise CustomError(u'未获取openid!')
-            WeChat.checkAppid(appid)
+            app = WechatApplet.getByAppid(appid)
             order = Order.objects.filter(id=pk).first()
             if not order:
                 raise CustomError(u'未找到相应的订单')
             order.activity.checkStatus()
             # 要不要设置一个时间  时间过了订单不能再支付  提示订单已过期  请重新下单 然后把订单状态改成失败
             with transaction.atomic():
-                data = order.payOrder(openid)
+                data = order.payOrder(openid, app)
                 BizLog.objects.addnew(request.customer.user, BizLog.INSERT, u'支付订单,id=%d' % order.id, request.data)
             return response_ok(data)
         except CustomError as e:
@@ -80,6 +90,27 @@ class OrderViewSet(ModelViewSet):
             traceback.print_exc()
             return response_error(str(e))
 
+    @action(methods=['post'], detail=True)
+    def del_order(self, request, pk):
+
+        try:
+            order = Order.objects.filter(id=pk).first()
+            if not order:
+                raise CustomError(u'未找到相应的订单!')
+            if order.delete:
+                raise CustomError(u'该订单已删除!')
+
+            with transaction.atomic():
+                order.delete = True
+                order.save()
+                BizLog.objects.addnew(request.customer.user, BizLog.DELETE, u'删除订单,id=%d' % order.id, request.data)
+            return response_ok()
+        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):
     '''
@@ -90,9 +121,7 @@ class ActivityOrderListView(generics.ListAPIView):
 
     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)
@@ -104,18 +133,3 @@ class ActivityOrderListView(generics.ListAPIView):
         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
-

+ 2 - 0
apps/customer/serializers.py

@@ -43,6 +43,7 @@ class WechatLoginSerializer(serializers.Serializer):
                 'tel': customer_wechat.customer.tel or '',
                 'face': customer_wechat.customer.face or '',
                 'activity_id': activity_id,
+                'customer_id': customer_wechat.customer.id,
             }
 
         else:
@@ -69,6 +70,7 @@ class WechatBindSerializer(serializers.Serializer):
                 'tel': customer.tel or '',
                 'face': customer.face or '',
                 'gender': customer.gender or 0,
+                'customer_id': customer.id,
             }
 
         else:

+ 7 - 6
apps/customer/tool.py

@@ -8,6 +8,7 @@ from util.wx.WXBizDataCrypt import WXBizDataCrypt
 from apps.exceptions import CustomError
 
 from apps.activity.models import Activity
+from apps.WechatApplet.models import WechatApplet
 from apps.customer.models import Customer, CustomerWechat
 
 User = get_user_model()
@@ -16,15 +17,15 @@ User = get_user_model()
 class WechatHandel(object):
     @staticmethod
     def login(code, appid, activity_id):
-        WeChat.checkAppid(appid)
+        app = WechatApplet.getByAppid(appid)
         if activity_id:
-            activity = Activity.objects.filter(id=activity_id, enabled=True, delete=False).first()
+            activity = Activity.objects.filter(id=activity_id).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)
+        res = WeChat.code2Session(appid, app.secret, code)
         instance = CustomerWechat.objects.filter(openid=res['openid'], branch=activity.branch).first()
         if not instance:
             instance = CustomerWechat.objects.create(
@@ -39,8 +40,8 @@ class WechatHandel(object):
 
     @staticmethod
     def bindWechat(appid, openid, phoneEncryptedData, phoneIv, activity_id):
-        WeChat.checkAppid(appid)
-        activity = Activity.objects.filter(id=activity_id, enabled=True, delete=False).first()
+        WechatApplet.getByAppid(appid)
+        activity = Activity.objects.filter(id=activity_id).first()
         if not activity:
             raise CustomError(u'未找到相应的活动')
 
@@ -48,7 +49,7 @@ class WechatHandel(object):
         if not customer_wechat:
             raise CustomError(u'未找到相应的微信客户!')
 
-        pc = WXBizDataCrypt(settings.APPID, customer_wechat.session_key)
+        pc = WXBizDataCrypt(appid, customer_wechat.session_key)
         phon_data = pc.decrypt(phoneEncryptedData, phoneIv)
 
         tel = phon_data['purePhoneNumber']

+ 1 - 0
apps/customer/urls.py

@@ -4,6 +4,7 @@ from apps.customer.views import *
 urlpatterns = [
     url(r'^code2Session/$', WxLoginView.as_view()),  # 自动登录
     url(r'^wxbind/$', WxBindView.as_view()),         # 微信快捷登录
+    url(r'^setUserInfo/$', SetUserInfoView.as_view()),         # 微信快捷登录
     url(r'^token/refresh/', CustomerRefreshTokenView),
     url(r'^token/verify/', CustomerVerifyTokenView),
 

+ 43 - 0
apps/customer/views.py

@@ -1,14 +1,22 @@
 # coding=utf-8
 
+import traceback
+
 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 util.exceptions import CustomError
+from util.permission import IsCustomerUser
+from util.wx.WXBizDataCrypt import WXBizDataCrypt
 
 from django.contrib.auth import get_user_model
+from django.db import transaction
 
 from apps.customer.serializers import WechatLoginSerializer, WechatBindSerializer
+from apps.activity.models import Activity
+from apps.customer.models import Customer, CustomerWechat
 
 User = get_user_model()
 
@@ -35,6 +43,41 @@ class WxBindView(APIView):
             return response_error('参数错误')
 
 
+class SetUserInfoView(APIView):
+    permission_classes = [IsCustomerUser, ]
+
+    def post(self, request, *args, **kwargs):
+        appid = request.POST.get('appid')
+        openid = request.POST.get('openid')
+        encryptedData = request.POST.get('encryptedData')
+        iv = request.POST.get('iv')
+        try:
+            customer = request.customer
+            customer_wechat = CustomerWechat.objects.filter(openid=openid, customer=customer).first()
+            if not customer_wechat:
+                raise CustomError(u'未找到相应的微信客户!')
+
+            if customer_wechat.customer and customer_wechat.customer.id != customer.id:
+                raise CustomError(u'该微信已同步其他客户!')
+            if not customer_wechat.customer:
+                customer_wechat.customer = customer
+                customer_wechat.save()
+            pc = WXBizDataCrypt(appid, customer_wechat.session_key)
+            result = pc.decrypt(encryptedData, iv)
+            with transaction.atomic():
+                if customer.name == customer.tel:
+                    customer.name = result['nickName']
+                customer.gender = result['gender']
+                customer.face = result['avatarUrl']
+                customer.save()
+        except CustomError as e:
+            return response_error(e.get_error_msg())
+        except Exception as e:
+            traceback.print_exc()
+            return response_error(u'保存失败!')
+        return response_ok({'face':customer.face,'name':customer.name})
+
+
 class CustomerRefreshTokenView(RefreshJSONWebToken):
     def post(self, request, *args, **kwargs):
         try:

+ 73 - 7
carwin_activity/settings.py

@@ -11,7 +11,7 @@ For the full list of settings and their values, see
 https://docs.djangoproject.com/en/1.9/ref/settings/
 """
 
-import os
+import os, datetime
 
 # 软件版本
 VERSION = '1.0.0'
@@ -46,13 +46,17 @@ INSTALLED_APPS = [
     'django.contrib.messages',
     'django.contrib.staticfiles',
 
-    #'rest_framework',
+    'corsheaders',
+    'rest_framework',
+    'rest_framework_jwt',
+    'django_filters',
 
     'apps.account',
     'apps.dashboard',
     'apps.foundation',
     'apps.activity',
     'apps.customer',
+    'apps.WechatApplet',
 ]
 
 MIDDLEWARE_CLASSES = [
@@ -66,8 +70,73 @@ MIDDLEWARE_CLASSES = [
     'django.middleware.clickjacking.XFrameOptionsMiddleware',
 ]
 
+CORS_ALLOW_CREDENTIALS = True  # 允许携带cookie
+CORS_ORIGIN_ALLOW_ALL = True
+CORS_ALLOW_METHODS = (
+    'DELETE',
+    'GET',
+    'OPTIONS',
+    'PATCH',
+    'POST',
+    'PUT',
+    'VIEW',
+)
+CORS_ALLOW_HEADERS = (
+    'XMLHttpRequest',
+    'X_FILENAME',
+    'accept-encoding',
+    'authorization',
+    'content-type',
+    'dnt',
+    'origin',
+    'token',
+    'user-agent',
+    'x-csrftoken',
+    'x-requested-with',
+    'Pragma',
+)
+
+JWT_AUTH = {
+    'JWT_EXPIRATION_DELTA': datetime.timedelta(days=30),
+    'JWT_REFRESH_EXPIRATION_DELTA': datetime.timedelta(days=360),
+    'JWT_ALLOW_REFRESH': True,
+}
+
 REST_FRAMEWORK = {
-    'DEFAULT_FILTER_BACKENDS': ('django_filters.rest_framework.DjangoFilterBackend',)
+    'DEFAULT_PERMISSION_CLASSES': (
+        'rest_framework.permissions.AllowAny',
+    ),
+    'DEFAULT_AUTHENTICATION_CLASSES': (
+        'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
+        #'rest_framework.authentication.SessionAuthentication',
+        #'rest_framework.authentication.BasicAuthentication',
+    ),
+    'DEFAULT_PARSER_CLASSES': (
+        'rest_framework.parsers.JSONParser',
+        'rest_framework.parsers.FormParser',
+        'rest_framework.parsers.MultiPartParser'
+    ),
+    'DEFAULT_RENDERER_CLASSES': (
+        'rest_framework.renderers.JSONRenderer',
+        # 'rest_framework.renderers.BrowsableAPIRenderer',
+    ),
+    'DEFAULT_FILTER_BACKENDS': (
+        'django_filters.rest_framework.DjangoFilterBackend',
+    ),
+    #'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
+    'DEFAULT_PAGINATION_CLASS': 'util.pagination.CustomPagination',
+    #'EXCEPTION_HANDLER': 'rest_framework.views.exception_handler',
+    'EXCEPTION_HANDLER': 'util.handler.custom_exception_handler',
+    'PAGE_SIZE': 10,
+    'DATETIME_FORMAT': "%Y-%m-%d %H:%M:%S",
+    'NON_FIELD_ERRORS_KEY': "error",  # 序列化器错误KEY名称
+}
+
+REST_FRAMEWORK_EXTENSIONS = {
+    # 缓存时间
+    'DEFAULT_CACHE_RESPONSE_TIMEOUT': 60 * 60 * 24,  # 单位s,一天
+    # 缓存存储
+    'DEFAULT_USE_CACHE': 'default',
 }
 
 ROOT_URLCONF = 'carwin_activity.urls'
@@ -77,6 +146,7 @@ LOGIN_URL = "/login/"
 LOGOUT_URL = "/"
 LOGIN_REDIRECT_URL = "/"
 AUTH_USER_MODEL = "account.User"
+AUTHENTICATION_BACKENDS = ['django.contrib.auth.backends.AllowAllUsersModelBackend']
 SESSION_EXPIRE_AT_BROWSER_CLOSE = True
 
 TEMPLATES = [
@@ -148,10 +218,6 @@ MEDIA_ROOT = os.path.join(BASE_DIR, "uis/media/")
 TMP_URL = '/tmp/'
 TMP_ROOT = os.path.join(BASE_DIR, "tmp/")
 
-APPID = ''  # 小程序appid
-SECRET = ''            # 小程序秘钥
-AGENT_NUM = ''         # 商户号
-AGENT_KEY = ''         # 商户密钥
 
 # 导入本地设置
 try:

BIN
uis/media/customer/1-20181017164122424000.png


BIN
uis/media/customer/1-20181017164122747000.png


BIN
uis/media/customer/1-20190110101354931000.jpeg


BIN
uis/media/customer/1-20190110120026324000.jpeg


BIN
uis/media/customer/7-20190110102942505000.jpeg


BIN
uis/media/customer/7-20190110103334714000.jpeg


BIN
uis/media/customer/7-20190110103353432000.jpeg


BIN
uis/media/customer/7-20190110103442732000.jpeg


BIN
uis/media/customer/7-20190110104808983000.jpeg


BIN
uis/media/customer/7-20190110111804327000.jpeg


BIN
uis/media/customer/7-20190110140948686000.jpeg


BIN
uis/media/customer/8-20190110101805918000.jpeg


BIN
uis/media/customer/8-20190110141044866000.jpeg


BIN
uis/media/customer/9-20190110112049829000.jpeg


BIN
uis/media/customer/9-20190110114914735000.jpeg


BIN
uis/media/customer/9-20190110114954166000.jpeg


BIN
uis/media/office_notice/files/4-20190713140845211000.png


BIN
uis/media/office_notice/files/4-20190713140917615000.zip


BIN
uis/media/office_notice/files/4-20190713141009401000.pdf


BIN
uis/media/purchase_order/invoice/15-20190622101902920000.png


BIN
uis/media/purchase_order/invoice/15-20190709105008276000.png


BIN
uis/media/purchase_order/invoice/20190424134614.png


BIN
uis/media/purchase_order/invoice/202101071033.png


+ 11 - 0
util/format.py

@@ -3,6 +3,17 @@
 import datetime
 from datetime import timedelta
 
+
+class Formater():
+    @staticmethod
+    def formatPrice(value):
+        return int(round(float(value or 0) * 100,0))
+
+    @staticmethod
+    def formatPriceShow(value):
+        return '%.2f' % (float(value or 0)/100.0)
+
+
 def strfdate(d):
     if d:
         return d.strftime('%Y-%m-%d')

+ 3 - 6
util/permission.py

@@ -5,17 +5,14 @@ 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
+from apps.WechatApplet.models import WechatApplet
 
 
 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:
+        if not request.user.type or request.user.type != User.CUSTOMER:
             return False
 
         appid = request.GET.get('appid', None)
@@ -26,7 +23,7 @@ class IsCustomerUser(permissions.BasePermission):
             activity_id = request.POST.get('activity_id')
 
         try:
-            WeChat.checkAppid(appid)
+            WechatApplet.getByAppid(appid)
             activity = Activity.getById(activity_id)
         except:
             return False

+ 236 - 0
util/splitaccount_tool.py

@@ -0,0 +1,236 @@
+# coding=utf-8
+
+import os
+import uuid
+import requests
+import json
+import xmltodict
+import time
+from datetime import datetime
+from hashlib import md5
+from django.conf import settings
+
+from util.exceptions import CustomError
+
+from cryptography.exceptions import InvalidSignature, InvalidTag
+from cryptography.hazmat.backends import default_backend
+from cryptography.hazmat.primitives.asymmetric.padding import MGF1, OAEP, PKCS1v15
+from cryptography.hazmat.primitives.ciphers.aead import AESGCM
+from cryptography.hazmat.primitives.hashes import SHA1, SHA256
+from cryptography.hazmat.primitives.hmac import HMAC
+from cryptography.hazmat.primitives.serialization import load_pem_private_key
+from cryptography.x509 import load_pem_x509_certificate
+from base64 import b64decode, b64encode
+
+
+class SplitAccountTool(object):
+    '''直连服务商  分账  文档https://pay.weixin.qq.com/wiki/doc/apiv3/index.shtml'''
+    GET = 'GET'
+    POST = 'POST'
+
+    def __init__(self, appid, mchid, private_key, cert_serial_no, apiv3_key, cert_dir=None, proxy=None):
+        self._mchid = mchid  # 商户号
+        self._appid = appid
+        self._private_key = load_private_key(private_key)  # 商户证书私钥
+        self._cert_serial_no = cert_serial_no  # 商户证书序列号
+        self._apiv3_key = apiv3_key  # 商户APIv3密钥
+        self._cert_dir = cert_dir  # 平台证书存放目录
+        self._gate_way = 'https://api.mch.weixin.qq.com'
+        self._proxy = proxy
+        self._certificates = []
+        self._init_certificates()
+
+    def _init_certificates(self):
+        if self._cert_dir and os.path.exists(self._cert_dir):
+            for file_name in os.listdir(self._cert_dir):
+                if not file_name.lower().endswith('.pem'):
+                    continue
+                with open(self._cert_dir + file_name, encoding="utf-8") as f:
+                    certificate = load_certificate(f.read())
+                now = datetime.utcnow()
+                if certificate and now >= certificate.not_valid_before and now <= certificate.not_valid_after:
+                    self._certificates.append(certificate)
+        if not self._certificates:
+            self._update_certificates()
+        if not self._certificates:
+            raise CustomError('没有wechatpay平台证书,请仔细检查您的初始化参数!')
+
+    def _update_certificates(self):
+        path = '/v3/certificates'
+        self._certificates.clear()
+        code, message = self.request(path, skip_verify=True)
+        if code != 200:
+            return
+        data = json.loads(message).get('data')
+        for value in data:
+            serial_no = value.get('serial_no')
+            effective_time = value.get('effective_time')
+            expire_time = value.get('expire_time')
+            encrypt_certificate = value.get('encrypt_certificate')
+            algorithm = nonce = associated_data = ciphertext = None
+            if encrypt_certificate:
+                algorithm = encrypt_certificate.get('algorithm')
+                nonce = encrypt_certificate.get('nonce')
+                associated_data = encrypt_certificate.get('associated_data')
+                ciphertext = encrypt_certificate.get('ciphertext')
+            if not (serial_no and effective_time and expire_time and algorithm and nonce and associated_data and ciphertext):
+                continue
+            cert_str = aes_decrypt(
+                nonce=nonce,
+                ciphertext=ciphertext,
+                associated_datformat_private_keya=associated_data,
+                apiv3_key=self._apiv3_key)
+            certificate = load_certificate(cert_str)
+            if not certificate:
+                continue
+            now = datetime.utcnow()
+            if now < certificate.not_valid_before or now > certificate.not_valid_after:
+                continue
+            self._certificates.append(certificate)
+            if not self._cert_dir:
+                continue
+            if not os.path.exists(self._cert_dir):
+                os.makedirs(self._cert_dir)
+            if not os.path.exists(self._cert_dir + serial_no + '.pem'):
+                f = open(self._cert_dir + serial_no + '.pem', 'w')
+                f.write(cert_str)
+                f.close()
+
+    def _verify_signature(self, headers, body):
+        signature = headers.get('Wechatpay-Signature')
+        timestamp = headers.get('Wechatpay-Timestamp')
+        nonce = headers.get('Wechatpay-Nonce')
+        serial_no = headers.get('Wechatpay-Serial')
+        cert_found = False
+        for cert in self._certificates:
+            if int('0x' + serial_no, 16) == cert.serial_number:
+                cert_found = True
+                certificate = cert
+                break
+        if not cert_found:
+            self._update_certificates()
+            for cert in self._certificates:
+                if int('0x' + serial_no, 16) == cert.serial_number:
+                    cert_found = True
+                    certificate = cert
+                    break
+            if not cert_found:
+                return False
+        if not rsa_verify(timestamp, nonce, body, signature, certificate):
+            return False
+        return True
+
+    def rsa_sign(self, private_key, sign_str):
+        message = sign_str.encode('UTF-8')
+        signature = private_key.sign(data=message, padding=PKCS1v15(), algorithm=SHA256())
+        sign = b64encode(signature).decode('UTF-8').replace('\n', '')
+        return sign
+
+    def build_authorization(self, path, method, mchid, serial_no, private_key, data=None, nonce_str=None):
+        '''
+        一、构建签名串
+        签名串一共有五行,每一行为一个参数。行尾以 \n(换行符,ASCII编码值为0x0A)结束,包括最后一行。如果参数本身以\n结束,也需要附加一个\n。
+        第一步,获取HTTP请求的方法(GET, POST, PUT)等
+        第二步,获取请求的绝对URL,并去除域名部分得到参与签名的URL。如果请求中有查询参数,URL末尾应附加有'?'和对应的查询字符串。
+        第三步,获取发起请求时的系统当前时间戳,即格林威治时间1970年01月01日00时00分00秒(北京时间1970年01月01日08时00分00秒)起至现在的总秒数,作为请求时间戳。微信支付会拒绝处理很久之前发起的请求,请商户保持自身系统的时间准确。
+        第四步,生成一个请求随机串,可参见生成随机数算法。这里,我们使用命令行直接生成一个。
+        第五步,获取请求中的请求报文主体(request body)。
+            请求方法为GET时,报文主体为空。
+            当请求方法为POST或PUT时,请使用真实发送的JSON报文。
+            图片上传API,请使用meta对应的JSON报文。
+            对于下载证书的接口来说,请求报文主体是一个空串。
+        二、计算签名值
+        绝大多数编程语言提供的签名函数支持对签名数据进行签名。强烈建议商户调用该类函数,使用商户私钥对待签名串进行SHA256 with RSA签名,并对签名结果进行Base64编码得到签名值。
+        三、设置HTTP头
+         微信支付商户API V3要求通过HTTP Authorization头来传递签名  Authorization由认证类型和签名信息两部分组成
+        1.认证类型,目前为WECHATPAY2-SHA256-RSA2048
+        2.签名信息
+            发起请求的商户(包括直连商户、服务商或渠道商)的商户号 mchid
+            商户API证书序列号serial_no,用于声明所使用的证书
+            请求随机串nonce_str
+            时间戳timestamp
+            签名值signature
+        注:以上五项签名信息,无顺序要求。
+        '''
+        timeStamp = str(int(time.time()))
+        nonce_str = nonce_str or ''.join(str(uuid.uuid4()).split('-')).upper()
+        body = data if isinstance(data, str) else json.dumps(data) if data else ''
+        sign_str = '%s\n%s\n%s\n%s\n%s\n' % (method, path, timeStamp, nonce_str, body)
+        signature = self.rsa_sign(private_key=private_key, sign_str=sign_str)
+        authorization = 'WECHATPAY2-SHA256-RSA2048 mchid="%s",nonce_str="%s",signature="%s",timestamp="%s",serial_no="%s"' % (
+        mchid, nonce_str, signature, timeStamp, serial_no)
+        return authorization
+
+    def request(self, path, method=GET, data=None, skip_verify=False, sign_data=None, files=None, cipher_data=False, headers={}):
+        if files:
+            headers.update({'Content-Type': 'multipart/form-data'})
+        else:
+            headers.update({'Content-Type': 'application/json'})
+        headers.update({'Accept': 'application/json'})
+        authorization = self.build_authorization(path, method, self._mchid, self._cert_serial_no, self._private_key, data=sign_data if sign_data else data)
+        headers.update({'Authorization': authorization})
+        if method == SplitAccountTool.GET:
+            response = requests.get(url=self._gate_way + path, headers=headers, proxies=self._proxy)
+        elif method == SplitAccountTool.POST:
+            response = requests.post(url=self._gate_way + path, json=None if files else data, data=data if files else None, headers=headers, files=files, proxies=self._proxy)
+        else:
+            raise CustomError('请选择正确的请求方式!')
+
+        if response.status_code in range(200, 300) and not skip_verify:
+
+            if not self._verify_signature(response.headers, response.text):
+                raise CustomError('签名验证失败!')
+
+        return response.status_code, response.text if 'application/json' in response.headers.get('Content-Type') else response.content
+
+
+def format_private_key(private_key_str):
+    pem_start = '-----BEGIN PRIVATE KEY-----\n'
+    pem_end = '\n-----END PRIVATE KEY-----'
+    if not private_key_str.startswith(pem_start):
+        private_key_str = pem_start + private_key_str
+    if not private_key_str.endswith(pem_end):
+        private_key_str = private_key_str + pem_end
+    return private_key_str
+
+
+def load_private_key(private_key_str):
+    try:
+        return load_pem_private_key(data=format_private_key(private_key_str).encode('UTF-8'), password=None, backend=default_backend())
+    except:
+        raise CustomError('商户证书私钥加载失败!')
+
+
+def rsa_verify(timestamp, nonce, body, signature, certificate):
+    '''验证签名'''
+    sign_str = '%s\n%s\n%s\n' % (timestamp, nonce, body)
+    public_key = certificate.public_key()
+    message = sign_str.encode('UTF-8')
+    signature = b64decode(signature)
+    try:
+        public_key.verify(signature, message, PKCS1v15(), SHA256())
+    except InvalidSignature:
+        return False
+    return True
+
+
+def load_certificate(certificate_str):
+    try:
+        return load_pem_x509_certificate(data=certificate_str.encode('UTF-8'), backend=default_backend())
+    except:
+        return None
+
+
+def aes_decrypt(nonce, ciphertext, associated_data, apiv3_key):
+    '''回调信息解密'''
+    key_bytes = apiv3_key.encode('UTF-8')
+    nonce_bytes = nonce.encode('UTF-8')
+    associated_data_bytes = associated_data.encode('UTF-8')
+    data = b64decode(ciphertext)
+    aesgcm = AESGCM(key=key_bytes)
+    try:
+        result = aesgcm.decrypt(nonce=nonce_bytes, data=data, associated_data=associated_data_bytes).decode('UTF-8')
+    except InvalidTag:
+        result = None
+    return result
+

+ 0 - 99
util/wechatcashout.py

@@ -1,99 +0,0 @@
-# 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')

+ 195 - 3
util/wechatpay.py

@@ -7,8 +7,9 @@ import xmltodict
 import time
 from hashlib import md5
 from django.conf import settings
-
+from util.splitaccount_tool import SplitAccountTool
 from util.exceptions import CustomError
+from apps.foundation.models import BizLog
 
 # 微信支付sign_type
 WEIXIN_SIGN_TYPE = 'MD5'
@@ -24,6 +25,196 @@ WEIXIN_QUERY_ORDER_URL = 'https://api.mch.weixin.qq.com/pay/orderquery'
 WEIXIN_CALLBACK_API = 'https://lsr.zzly.vip/api/wechat_notify/'
 
 
+class SplitAccountFuc(object):
+    '''直连服务商  分账  文档https://pay.weixin.qq.com/wiki/doc/apiv3/index.shtml'''
+
+    def __init__(self,appid, mchid, private_key, cert_serial_no, apiv3_key, cert_dir=None, proxy=None):
+        self._appid = appid
+        self._mchid = mchid
+        self._core = SplitAccountTool(appid, mchid, private_key, cert_serial_no, apiv3_key, cert_dir=cert_dir, proxy=proxy)
+
+    def splitaccount_order(self, transaction_id, out_order_no, receivers):
+        '''
+        请求分账  微信订单支付成功后,服务商代特约商户发起分账请求,将结算后的钱分到分账接收方
+        注意:对同一笔订单最多能发起50次分账请求,每次请求最多分给50个接收方
+        此接口采用异步处理模式,即在接收到商户请求后,优先受理请求再异步处理,最终的分账结果可以通过查询分账接口获取
+        请求分账里边的openid  是需要先调用添加分账接收方接口添加分账关系
+        '''
+        path = "/v3/profitsharing/orders"
+        params = {
+            'appid': self._appid,
+            'transaction_id': transaction_id,    # 微信支付订单号
+            'out_order_no': out_order_no,      # 商户系统内部的分账单号,在商户系统内部唯一,同一分账单号多次请求等同一次。只能是数字、大小写字母_-|*@
+            'receivers': [],
+            'unfreeze_unsplit': True  # 是否解冻剩余未分金额   如果只分一次就填true
+        }
+
+        for item in receivers:
+            receiver_item = {
+                'type': 'PERSONAL_OPENID',
+                'account': item['account'],
+                'amount': int(round(item['amount'], 0)),   # 单位为分  只能为整数  不能超过原订单支付金额及最大分账比例金额
+                'description': item['description']
+            }
+
+            params['receivers'].append(receiver_item)
+
+        success = True
+        data = {}
+        try:
+            code, message = self._core.request(path, SplitAccountTool.POST, data=params)
+            result = json.loads(message)
+            if code == 200:
+                data['order_id'] = result.get('order_id')
+                data['state'] = result.get('state')
+                data['receivers'] = result.get('receivers')
+            else:
+                success = False
+                BizLog.objects.addnew('', BizLog.INSERT, u'[%s]分账失败!原因:' % out_order_no + result.get('code'))
+        except CustomError as e:
+            success = False
+            BizLog.objects.addnew('', BizLog.INSERT, u'[%s]分账失败!原因:' % out_order_no + e.get_error_msg())
+        except Exception as e:
+            success = False
+            BizLog.objects.addnew('', BizLog.INSERT, u'[%s]分账失败!原因:' % out_order_no + str(e))
+
+        return success, data
+
+    def splitaccount_addreceiver(self, account):
+        '''添加分账接收方'''
+        path = "/v3/profitsharing/receivers/add"
+        params = {
+            'appid': self._appid,
+            'type': "PERSONAL_OPENID",
+            'account': account,  # 接收人的openid
+            'relation_type': "USER",  # body子商户与接收方的关系
+        }
+
+        success = True
+        try:
+            code, message = self._core.request(path, SplitAccountTool.POST, data=params)
+            result = json.loads(message)
+            if code != 200:
+                success = False
+                BizLog.objects.addnew('', BizLog.INSERT, u'[%s]添加分账接收方失败!原因:' % account + result.get('code'))
+        except CustomError as e:
+            success = False
+            BizLog.objects.addnew('', BizLog.INSERT, u'[%s]添加分账接收方失败!原因:' % account + e.get_error_msg())
+        except Exception as e:
+            success = False
+            BizLog.objects.addnew('', BizLog.INSERT, u'[%s]添加分账接收方失败!原因:' % account + str(e))
+
+        return success
+
+    def splitaccount_deletereceiver(self, account):
+        '''删除分账接收方'''
+        path = "/v3/profitsharing/receivers/delete"
+        params = {
+            'appid': self._appid,
+            'type': "PERSONAL_OPENID",
+            'account': account,  # 接收人的openid
+        }
+
+        success = True
+        try:
+            code, message = self._core.request(path, SplitAccountTool.POST, data=params)
+            result = json.loads(message)
+            if code != 200:
+                success = False
+                BizLog.objects.addnew('', BizLog.INSERT, u'[%s]删除分账接收方失败!原因:' % account + result.get('code'))
+        except CustomError as e:
+            success = False
+            BizLog.objects.addnew('', BizLog.INSERT, u'[%s]删除分账接收方失败!原因:' % account + e.get_error_msg())
+        except Exception as e:
+            success = False
+            BizLog.objects.addnew('', BizLog.INSERT, u'[%s]删除分账接收方失败!原因:' % account + str(e))
+
+        return success
+
+    def splitaccount_orderquery(self, transaction_id, out_order_no):
+        '''
+        查询分账结果
+        transaction_id  微信支付订单号
+        out_order_no  商户分账单号
+        '''
+        success = True
+        data = {}
+        if transaction_id and out_order_no:
+            path = '/v3/profitsharing/orders/%s?transaction_id=%s' % (out_order_no, transaction_id)
+        else:
+            success = False
+            BizLog.objects.addnew('', BizLog.INSERT, u'[%s]查询分账结果失败!原因:参数错误!' % out_order_no)
+            return success, data
+
+        try:
+            code, message = self._core.request(path)
+            result = json.loads(message)
+            if code == 200:
+                data['order_id'] = result.get('order_id')
+                data['state'] = result.get('state')
+                data['receivers'] = result.get('receivers')
+            else:
+                success = False
+                BizLog.objects.addnew('', BizLog.INSERT, u'[%s]查询分账结果失败!原因:' % out_order_no + result.get('code'))
+        except CustomError as e:
+            success = False
+            BizLog.objects.addnew('', BizLog.INSERT, u'[%s]查询分账结果失败!原因:' % out_order_no + e.get_error_msg())
+        except Exception as e:
+            success = False
+            BizLog.objects.addnew('', BizLog.INSERT, u'[%s]查询分账结果失败!原因:' % out_order_no + str(e))
+        return success, data
+
+    def splitaccount_return(self):
+        '''请求分账回退'''
+        pass
+
+    def splitaccount_returnquery(self):
+        '''查询分账回退结果'''
+        pass
+
+    def splitaccount_unfreeze(self, transaction_id, out_order_no):
+        '''解冻剩余资金'''
+        path = "/v3/profitsharing/orders"
+        params = {
+            'transaction_id': transaction_id,  # 微信支付订单号
+            'out_order_no': out_order_no,  # 商户系统内部的分账单号,在商户系统内部唯一,同一分账单号多次请求等同一次。只能是数字、大小写字母_-|*@
+            'description': "解冻资金"
+        }
+
+        success = True
+        data = {}
+        try:
+            code, message = self._core.request(path, SplitAccountTool.POST, data=params)
+            result = json.loads(message)
+            if code == 200:
+                data['order_id'] = result.get('order_id')
+                data['state'] = result.get('state')
+                data['receivers'] = result.get('receivers')
+            else:
+                success = False
+                BizLog.objects.addnew('', BizLog.INSERT, u'[%s]解冻剩余资金失败!原因:'% out_order_no + result.get('code'))
+        except CustomError as e:
+            success = False
+            BizLog.objects.addnew('', BizLog.INSERT, u'[%s]解冻剩余资金失败!原因:'% out_order_no + e.get_error_msg())
+        except Exception as e:
+            success = False
+            BizLog.objects.addnew('', BizLog.INSERT, u'[%s]解冻剩余资金失败!原因:'% out_order_no + str(e))
+
+        return success, data
+
+    def splitaccount_amountquery(self):
+        '''查询剩余待分金额'''
+        pass
+
+    def splitaccount_configquery(self):
+        '''查询最大分账比例'''
+        pass
+
+    def splitaccount_bill(self):
+        '''申请分账账单'''
+        pass
+
+
 class WeChatResponse():
     def __init__(self,appid, agent_num, agent_key):
         self.params = {
@@ -89,9 +280,10 @@ class WechatPay():
         data.pop('appId')
         return data
 
-    def unifiedOrder(self,out_trade_no,total_fee, openid):
+    def unifiedOrder(self,out_trade_no,total_fee, openid, profit_sharing):
+        self.params['profit_sharing'] = profit_sharing  # 是否分账参数  Y  需要分账  N 不分账  字母大写默认不分账
         self.params['out_trade_no'] = out_trade_no
-        self.params['total_fee'] = int(round(total_fee / 100,0))
+        self.params['total_fee'] = int(round(total_fee, 0))
         self.params['openid'] = openid
         self.params['nonce_str'] = generate_nonce_str()
         self.params['sign'] = generate_sign(self.params, self.merchant_key)

+ 19 - 10
util/wx/wechat.py

@@ -9,13 +9,6 @@ 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):
         """
@@ -247,11 +240,8 @@ class WeChat(object):
             '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()
@@ -280,6 +270,25 @@ class WeChat(object):
             destination.write(result.content)
         return filename
 
+    @staticmethod
+    def getActivityCode(access_token, page, customer_id, activity_id):
+        '''获取活动二维码'''
+        url = 'https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token={}'.format(access_token)
+        data = {
+            "scene": "customer_id={}&activity_id={}".format(customer_id, activity_id),
+            "page": page,
+            "line_color": {"r": 43, "g": 162, "b": 69},  # 自定义颜色
+            "is_hyaline": True,
+        }
+        result = requests.post(url, json=data)
+        upload_path = PathAndRename("activiytCode/")
+        filename = "{}{}_{}.png".format(upload_path.path, activity_id, customer_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 getCommodityCode(access_token, page, commodity_id, company_no):
         '''获取商品二维码'''