Exemplo n.º 1
0
    def post(self):
        """
        用户余额调整
        :return:
        """

        form, error = UserBalanceEditForm.request_validate()
        if error:
            return error.as_response()

        uid = form.uid.data
        # 判断该用户id是否存在
        user = User.query.filter_by(**dict(id=uid)).first()
        if not user:
            return AdjustUserBalanceError(message="系统找不到该用户").as_response()

        ad_type = BalanceAdjustTypeEnum.PLUS

        # 当调整状态为 减少时 需要判断 当前用户余额和商户余额值
        amount = BalanceKit.round_4down_5up(Decimal(form.amount.data))
        if form.adjust_type.data == BalanceAdjustTypeEnum.MINUS:
            ad_type = BalanceAdjustTypeEnum.MINUS
            # 获取用户余额 并判断
            user_balance_info = UserBalance.query.filter(
                UserBalance.id == uid).first()
            if not user_balance_info:
                return UserBalanceNoFoundError().as_response()

            user_balance = user_balance_info.real_balance
            if Decimal(user_balance) - amount < 0:
                print("用户没有足够的金额", user_balance, amount)
                return AdjustUserBalanceError(
                    message="用户没有足够的金额").as_response()

            # 获取商户余额 并判断
            merchant_balance_info = MerchantInfo.query.filter_by(**dict(
                _merchant=user_balance_info.merchant.value)).first()
            if not merchant_balance_info:
                return AdjustUserBalanceError(message="该商户信息不存在").as_response()

            bl_ava = merchant_balance_info.balance_available
            if Decimal(bl_ava) - amount < 0:
                print("商户没有足够的余额", bl_ava, amount)
                return AdjustUserBalanceError(
                    message="商户没有足够的余额").as_response()

        # 调整用户及商户余额
        flag, error = AdjustTransactionCtl.adjust_create(
            user=user,
            source=OrderSourceEnum.MANUALLY,
            amount=amount,
            order_type=PayTypeEnum.MANUALLY,
            bl_type=BalanceTypeEnum.AVAILABLE,
            ad_type=ad_type,
            comment=form.comment.data)

        if not flag:
            return AdjustUserBalanceError(message="余额调整失败").as_response()

        return ResponseSuccess().as_response()
Exemplo n.º 2
0
    def construct_request(self, order, params: dict):
        pay_params = {
            PayMethodEnum.ZHIFUBAO_H5: dict(channel=7, render_type=SdkRenderType.QR_CODE),
            PayMethodEnum.WEIXIN_H5: dict(channel=1, render_type=SdkRenderType.QR_CODE)
        }

        payment_method = self.channel_enum.conf['payment_method']
        request_fields = ['pay_type', 'mch_id', 'order_id', 'channel_id', 'pay_amount', 'name',
                          'explain', 'remark', 'result_url', 'notify_url', 'client_ip',
                          'bank_cardtype', 'bank_code', 'is_qrimg', 'is_sdk', 'ts', 'sign', 'ext']
        sorted_params = sorted(request_fields)

        request_body = {}

        for field in request_fields:
            if field == "pay_type":
                request_body[field] = 2
            elif field == "mch_id":
                request_body[field] = int(self.third_config['mch_id'])
            elif field == "order_id":
                request_body[field] = order.sys_tx_id
            elif field == "channel_id":
                request_body[field] = pay_params[payment_method]['channel']
            elif field == "pay_amount":
                request_body[field] = str(BalanceKit.round_4down_5up(order.amount))
            elif field == "name":
                request_body[field] = "mch name"
            elif field == "explain":
                request_body[field] = "explain text"
            elif field == "remark":
                request_body[field] = "remark message"
            elif field == "result_url":
                request_body[field] = self.third_config['return_url']
            elif field == "notify_url":
                request_body[field] = self.third_config['callback_url']
            elif field == "client_ip":
                request_body[field] = params['client_ip']
            elif field == 'is_qrimg':
                request_body[field] = 0
            elif field == 'is_sdk':
                request_body[field] = 1
            elif field == 'ts':
                request_body[field] = DateTimeKit.get_cur_timestamp()
            elif field == 'ext':
                request_body[field] = "ext message"

        sign_str = "&".join(["{}={}".format(k, request_body[k]) for k in sorted_params if
                             request_body.get(k, False) or k in ["is_qrimg", "is_sdk"]])
        # sign_str += self.third_config['secret_key']

        print("sign string: ", sign_str)
        print("request body: ", request_body)

        render_type = pay_params[payment_method]['render_type']

        return request_body, sign_str, render_type
Exemplo n.º 3
0
    def test_balance_round(self):
        i = 1
        for x in range(0, 10):
            d = Decimal(self.format_float(x=x, i=i))
            v = BalanceKit.round_4down_5up(d)
            self.assertEqual(d, v)

            for y in range(0, 10):
                d = Decimal(self.format_float(x=x, y=y, i=i))
                v = BalanceKit.round_4down_5up(d)
                self.assertEqual(d, v)

                for z in range(0, 10):
                    # 原始值
                    d = Decimal(self.format_float(x=x, y=y, z=z, i=i))

                    # 期望值
                    if z >= 5:
                        # z进位,y自增
                        _x = x
                        _y = y + 1
                        _i = i
                        if _y >= 10:
                            # y进位,x自增
                            _y = 0
                            _x += 1
                            if _x >= 10:
                                # x进位,i自增
                                _x = 0
                                _i += 1
                        e = Decimal(self.format_float(x=_x, y=_y, i=_i))
                    else:
                        # 舍掉z
                        e = Decimal(self.format_float(x=x, y=y, i=i))

                    # 实际值
                    v = BalanceKit.round_4down_5up(d)

                    self.assertEqual(e, v, (x, y, z))
Exemplo n.º 4
0
 def calc_cost(cls, amount, fee_type: PaymentFeeTypeEnum,
               fee_value: Decimal):
     """
     计算成本: 通道收取的手续费
     :param amount:
     :param fee_type:
     :param fee_value:
     :return:
     """
     if fee_type == PaymentFeeTypeEnum.PERCENT_PER_ORDER:
         return BalanceKit.round_4down_5up(amount * fee_value /
                                           Decimal(100.0))
     return fee_value
Exemplo n.º 5
0
 def calc_fee(cls, amount: Decimal, fee_type: PaymentFeeTypeEnum,
              fee_value: Decimal):
     """
     计算手续费: 我们收取下游商户的手续费
     :param amount:
     :param fee_type:
     :param fee_value:
     :return:
     """
     if fee_type == PaymentFeeTypeEnum.PERCENT_PER_ORDER:
         return BalanceKit.round_4down_5up(
             Decimal(amount) * fee_value / Decimal(str(100.0)))
     return fee_value
Exemplo n.º 6
0
    def post(self):
        """
        提现接口: 检查支付密码是否正确,如果密码正确则创建用户提现订单
        """
        form, error = CreateWithdrawOrderForm().request_validate()
        if error:
            return error.as_response()

        uid = g.user.uid
        merchant = g.user.merchant

        if not g.user.has_permission(UserPermissionEnum.WITHDRAW):
            return UserPermissionDeniedError().as_response()

        amount = BalanceKit.round_4down_5up(Decimal(form.amount.data))
        user_bank_id = form.user_bank.data
        client_ip = form.client_ip.data
        trade_password = form.trade_password.data

        # 判断 支付密码是否正确
        if not User.verify_payment_password(
                merchant=merchant, uid=uid, password=trade_password):
            cache = UserPaymentPasswordLimitCache(uid=uid)
            cache.incr_times()
            times = cache.get_left_times()
            return PaymentPasswordError(message=PaymentPasswordError.message.
                                        format(times)).as_response()

        order, error = WithdrawTransactionCtl.order_create(
            user=g.user,
            amount=amount,
            client_ip=client_ip,
            user_bank_id=user_bank_id,
        )
        if error:
            return error.as_response()

        return ResponseSuccess().as_response()
Exemplo n.º 7
0
    def __test_api_withdraw(self):
        """
        1. 新建用户提现订单
        :return:
        """
        order_cls = OrderWithdraw
        uid = 1000

        channel_enum = ChannelConfigEnum.CHANNEL_1001

        banks = [
            PaymentBankEnum(int(bank))
            for bank in [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
        ]
        proxy_channel = dict(fee=Decimal("2.5"),
                             fee_type=PaymentFeeTypeEnum.PERCENT_PER_ORDER,
                             limit_per_min=300,
                             limit_per_max=1000,
                             limit_day_max=0,
                             trade_begin_hour=0,
                             trade_begin_minute=0,
                             trade_end_hour=23,
                             trade_end_minute=59,
                             maintain_begin=DateTimeKit.str_to_datetime(
                                 "2019-12-11 09:00:00",
                                 DateTimeFormatEnum.SECONDS_FORMAT),
                             maintain_end=DateTimeKit.str_to_datetime(
                                 "2025-12-20 23:00:00",
                                 DateTimeFormatEnum.SECONDS_FORMAT),
                             state=ChannelStateEnum.TESTING,
                             banks=banks)
        ProxyChannelConfig.update_channel(channel_enum, **proxy_channel)

        merchant = MerchantEnum.TEST

        # 准备配置数据
        bank = BankCard.add_bank_card(
            merchant,
            uid=uid,
            bank_name="中国工商银行",
            bank_code="ICBC",
            card_no="6212260405014627955",
            account_name="张三",
            branch="广东东莞东莞市长安镇支行",
            province="广东省",
            city="东莞市",
        )

        OrderMixes.add_one_channel_config(channel_enum)
        OrderMixes.add_one_merchant_config(merchant,
                                           channel_enum,
                                           payment_way=PayTypeEnum.WITHDRAW)

        channel_config = ChannelConfig.query_latest_one(
            dict(channel_enum=channel_enum))

        merchant_fee_config = MerchantFeeConfig.query_latest_one(
            dict(
                merchant=merchant,
                payment_way=PayTypeEnum.WITHDRAW,
                payment_method=channel_enum.conf.payment_method,
            ))

        amount = Decimal("500")
        fee = BalanceKit.round_4down_5up(
            Decimal(merchant_fee_config.value) * amount / Decimal(100))
        # 创建提现订单
        params = dict(
            uid=uid,
            merchant=merchant,
            channel_id=channel_config.channel_id,
            mch_fee_id=merchant_fee_config.config_id,
            source=OrderSourceEnum.TESTING,
            order_type=PayTypeEnum.WITHDRAW,
            in_type=InterfaceTypeEnum.CASHIER_H5,
            amount=amount,
            bank_id=bank.id,
            fee=fee,
        )
        order, ref_id = OrderCreateCtl.create_order_event(**params)
        event = OrderEvent.query_one(dict(ref_id=ref_id),
                                     merchant=merchant,
                                     date=order.create_time)

        data = order_cls.query_by_order_id(order_id=event.order_id,
                                           merchant=merchant)

        begin_time, end_time = DateTimeKit.get_month_begin_end(
            year=int(DateTimeKit.get_cur_datetime().year),
            month=int(DateTimeKit.get_cur_datetime().month))
        withdraw_params = dict(merchant_name="TEST",
                               page_size=10,
                               page_index=1,
                               begin_time=DateTimeKit.datetime_to_str(
                                   begin_time,
                                   DateTimeFormatEnum.SECONDS_FORMAT),
                               end_time=DateTimeKit.datetime_to_str(
                                   end_time,
                                   DateTimeFormatEnum.SECONDS_FORMAT),
                               state="0")

        self.path = "/trade_manage/withdraw/list"
        # 通过接口 查询提现订单
        response = self.do_request(json_data=withdraw_params)
        print(response.json, "&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&")
        self.assertEqual("1", response.json['data']['total'])
        self.assertEqual("待认领", response.json['data']['entries'][0]['state'])

        self.path = "/trade_manage/order/allowed"
        # 通过接口, 认领订单
        allowed_params = dict(order_id=order.id, merchant_name="TEST")
        response = self.do_request(allowed_params)
        self.assertEqual(ResponseSuccess.code, response.status_code)
        self.assertEqual(ResponseSuccess.error_code,
                         response.json['error_code'])
        # 查询当前订单状态是否已修改为已认领
        data = order_cls.query_by_order_id(order_id=event.order_id,
                                           merchant=merchant)
        self.assertEqual(OrderStateEnum.ALLOC, data.state)

        # 通过接口查询 审核列表 已有的认领订单为1
        self.path = '/trade_manage/withdraw/review/list'

        request_review_params = dict(
            year=str(DateTimeKit.get_cur_datetime().year),
            mouth=str(DateTimeKit.get_cur_datetime().month))
        response = self.do_request(json_data=request_review_params)
        self.assertEqual(1, len(response.json['data']['entries']))
        self.assertEqual("已认领", response.json['data']['entries'][0]['state'])

        # 通过接口查询 当前可用的 代付通道
        proxy_channel_suppor = dict(bank_type=bank.bank_enum.name,
                                    merchant_name="TEST",
                                    amount=str(amount))

        self.path = "/trade_manage/withdraw/available/channel"

        response = self.do_request(json_data=proxy_channel_suppor)
        self.assertEqual(WithdrawBankEntryResult.code, response.status_code)
        self.assertEqual(WithdrawBankEntryResult.error_code,
                         response.json['error_code'])
        self.assertEqual(
            channel_enum.conf['provider'] + channel_enum.conf['mch_id'],
            response.json['data']['entries'][0]['key'])

        # 测试人工出款 处理订单
        self.path = '/trade_manage/withdraw/person/execute'

        execute_params = dict(order_id=order.order_id, merchant="Test")

        response = self.do_request(json_data=execute_params)
        self.assertEqual(ResponseSuccess.code, response.status_code)
        self.assertEqual(ResponseSuccess.error_code,
                         response.json['error_code'])
        data = order_cls.query_by_order_id(order_id=event.order_id,
                                           merchant=merchant)
        self.assertEqual(OrderStateEnum.DEALING, data.state)

        # 测试人工出款  出款

        self.path = "/trade_manage/withdraw/person/done"
        done_params = dict(order_id=order.order_id,
                           merchant='TEST',
                           comment='测试',
                           fee='5')

        response = self.do_request(json_data=done_params)
        self.assertEqual(ResponseSuccess.code, response.status_code)
        self.assertEqual(ResponseSuccess.error_code,
                         response.json['error_code'])
        data = order_cls.query_by_order_id(order_id=event.order_id,
                                           merchant=merchant)
        self.assertEqual(OrderStateEnum.SUCCESS, data.state)
Exemplo n.º 8
0
    def __test_api_withdraw(self):
        """
        后台准备数据:
            充值通道数据
            代付通道数据
            商户费率配置数据

        钱包端:
            1. 创建充值订单
            2. 充值
            3. 用户设置支付密码
            4. 用户绑定银行卡
            5. 获取充值配置信息(用户余额,充值最低最高限制)

        发起提现请求:

        :return:
        """
        merchant = MerchantEnum.from_name("TEST")
        info = dict(merchant=merchant,
                    account="+8618988888888",
                    auth_code="8888",
                    password="******",
                    trade_pwd="b943a52cc24dcdd12bf2ba3afda92351",
                    ac_type=AccountTypeEnum.MOBILE)
        user = User.register_account(info['merchant'], info['account'],
                                     info['ac_type'], info['password'])

        self.path = '/auth/account/login'

        login_data = dict(number=info['account'], password=info['password'])
        response = self.do_request(login_data)
        self.assertEqual(ResponseSuccessLogin.code, response.status_code)
        self.assertEqual(ResponseSuccessLogin.error_code,
                         response.json['error_code'])
        self.token = response.json['data']['token']

        self.path = "/withdraw/banks/list"

        # 1. 向数据库添加代付通道信息
        withdraw_item = dict(fee="2.5",
                             fee_type=PaymentFeeTypeEnum(1),
                             limit_per_min="200",
                             limit_per_max="5000",
                             limit_day_max="50000",
                             trade_begin_hour="00",
                             trade_begin_minute="00",
                             trade_end_hour="23",
                             trade_end_minute="59",
                             maintain_begin=DateTimeKit.str_to_datetime(
                                 "2019-09-27 00:00:00",
                                 DateTimeFormatEnum.SECONDS_FORMAT),
                             maintain_end=DateTimeKit.str_to_datetime(
                                 "2019-10-20 23:59:00",
                                 DateTimeFormatEnum.SECONDS_FORMAT),
                             state=ChannelStateEnum(10),
                             banks=[
                                 PaymentBankEnum(1),
                                 PaymentBankEnum(2),
                                 PaymentBankEnum(4),
                                 PaymentBankEnum(3),
                                 PaymentBankEnum(15)
                             ])

        ProxyChannelConfig.update_channel(ChannelConfigEnum.CHANNEL_1001,
                                          **withdraw_item)

        # 2. 向数据库插入 商户费率配置信息
        # 充值费率设置
        merchant_fee_dict = []
        merchant_fee_dict.append(
            dict(
                merchant=MerchantEnum.from_name('TEST'),
                payment_way=PayTypeEnum.DEPOSIT,
                value="3.5",
                fee_type=PaymentFeeTypeEnum.PERCENT_PER_ORDER,
                payment_method=PayMethodEnum.ZHIFUBAO_SAOMA,
            ))

        # 提现费率
        merchant_fee_dict.append(
            dict(
                merchant=MerchantEnum.from_name('TEST'),
                payment_way=PayTypeEnum.WITHDRAW,
                value="3.5",
                fee_type=PaymentFeeTypeEnum.PERCENT_PER_ORDER,
            ))

        rst, error = MerchantFeeConfig.update_fee_config(
            merchant, merchant_fee_dict)
        self.assertEqual(True, rst)

        # 3. 给用户和商户充值
        uid = user.uid

        ref_id = hashlib.md5('lakjdflasjfadl;kfja'.encode('utf8')).hexdigest()

        data = dict(
            uid=uid,
            merchant=merchant,
            ref_id=ref_id,
            source=OrderSourceEnum.TESTING,
            order_type=PayTypeEnum.DEPOSIT,
            bl_type=BalanceTypeEnum.AVAILABLE,
            ad_type=BalanceAdjustTypeEnum.PLUS,
            tx_id=OrderUtils.gen_normal_tx_id(uid),
            value=Decimal("10000.00"),
            comment="xxx",
        )
        rst, msg = UserBalanceEvent.update_user_balance(**data)
        self.assertEqual(0, rst)

        balance = UserBalance.query_balance(data['uid'],
                                            data['merchant']).first()

        # 添加商户余额
        data = dict(
            merchant=MerchantEnum.TEST,
            source=OrderSourceEnum.TESTING,
            order_type=PayTypeEnum.DEPOSIT,
            bl_type=BalanceTypeEnum.AVAILABLE,
            ad_type=BalanceAdjustTypeEnum.PLUS,
            tx_id=OrderUtils.gen_normal_tx_id(100),
            value=Decimal("10000.00"),
            comment=msg,
        )

        ref_id = hashlib.md5(
            RandomString.gen_random_str(
                length=128).encode('utf8')).hexdigest()
        data['ref_id'] = ref_id
        event_check = dict(total=1)
        event_check.update(data)
        rst, msg = MerchantBalanceEvent.update_balance(**data)

        self.assertEqual(0, rst)

        # 设置支付密码
        flag = User.set_payment_password(merchant,
                                         uid=uid,
                                         trade_pwd=info["trade_pwd"])

        self.assertEqual(True, flag)

        # 绑定银行卡
        bank_info = {
            "payment_password": info['trade_pwd'],
            "bank_name": "中国工商银行",
            "bank_code": "ICBC",
            "card_no": "6212260405014627955",
            "account_name": "张三",
            "branch": "广东东莞东莞市长安镇支行",
            "province": "广东省",
            "city": "东莞市"
        }

        flag = BankCard.add_bank_card(merchant,
                                      uid=uid,
                                      bank_name=bank_info['bank_name'],
                                      bank_code=bank_info['bank_code'],
                                      card_no=bank_info['card_no'],
                                      account_name=bank_info['account_name'],
                                      branch=bank_info['branch'],
                                      province=bank_info['province'],
                                      city=bank_info['city'])
        self.assertEqual(bank_info['card_no'], flag.card_no)

        self.path = "/withdraw/limit/config/get"
        response = self.do_request()
        self.assertEqual(ResponseBankWithdraw.code, response.status_code)
        self.assertEqual(ResponseBankWithdraw.error_code,
                         response.json['error_code'])
        self.assertEqual("10000", response.json['data']['balance'])
        self.assertEqual("200", response.json['data']['limit_min'])
        self.assertEqual("5000", response.json['data']['limit_max'])

        self.path = "/withdraw/order/create"
        create_data = dict(amount=1000.001,
                           user_bank=1,
                           trade_password=info['trade_pwd'])

        # 测试小于 最低限额

        create_data['amount'] = 100

        response = self.do_request(json_data=create_data)
        self.assertEqual(WithdrawOrderAmountInvalidError.code,
                         response.status_code)
        self.assertEqual(WithdrawOrderAmountInvalidError.error_code,
                         response.json['error_code'])

        create_data['amount'] = 6000

        response = self.do_request(json_data=create_data)
        self.assertEqual(WithdrawOrderAmountInvalidError.code,
                         response.status_code)
        self.assertEqual(WithdrawOrderAmountInvalidError.error_code,
                         response.json['error_code'])

        create_data['amount'] = str(500.56)
        create_data['user_bank'] = 100

        response = self.do_request(json_data=create_data)
        self.assertEqual(WithdrawBankNoExistError.code, response.status_code)
        self.assertEqual(WithdrawBankNoExistError.error_code,
                         response.json['error_code'], response.json['message'])

        use_balance = UserBalance.query_balance(user.uid, merchant).first()
        ori_merchant = MerchantInfo.query_merchant(merchant)

        balance = ori_merchant.bl_ava - BalanceKit.round_4down_5up(
            Decimal(create_data['amount'])) * 100 - BalanceKit.round_4down_5up(
                Decimal(create_data['amount']) * Decimal(3.5))
        merchant_balance = BalanceKit.round_4down_5up(
            balance / Decimal(100)) * 100

        u_balance = BalanceKit.round_4down_5up(
            Decimal(use_balance.balance) / Decimal(100) -
            Decimal(create_data['amount'])) * Decimal(100)
        create_data['user_bank'] = 1

        response = self.do_request(json_data=create_data)
        self.assertEqual(ResponseSuccess.code, response.status_code)
        self.assertEqual(ResponseSuccess.error_code,
                         response.json['error_code'])

        cur_balance = UserBalance.query_balance(user.uid, merchant).first()
        cur_merchant = MerchantInfo.query_merchant(merchant)

        self.assertEqual(int(merchant_balance), int(cur_merchant.bl_ava))
        self.assertEqual(int(u_balance), int(cur_balance.balance))