Exemple #1
0
    def __update_channel(self, data, latest, count):
        ProxyChannelConfig.update_channel(**data)

        channels = list(ProxyChannelConfig.query_all())
        self.assertEqual(count, len(channels))

        all_configs = list(ProxyChannelConfig.filter_latest_items(channels))
        self.assertEqual(latest, len(all_configs))
Exemple #2
0
    def post(self):
        """
        代付通道管理: 代付通道列表
        :return:
        """
        channel_list = []
        channels = ProxyChannelConfig.query_all()
        channels = ProxyChannelConfig.filter_latest_items(channels)

        for channel in channels:
            channel_enum = channel.channel_enum
            channel_conf = channel_enum.conf

            channel_list.append(
                dict(channel_id=channel_enum.value,
                     channel_desc=channel_enum.desc,
                     id=channel_conf['mch_id'],
                     provider=channel_conf['provider'],
                     fee=channel.fee,
                     fee_type=dict(
                         desc=PaymentFeeTypeEnum(channel.fee_type).desc,
                         value=PaymentFeeTypeEnum(channel.fee_type).value),
                     limit_per_min=channel.limit_per_min,
                     limit_per_max=channel.limit_per_max,
                     limit_day_max=channel.limit_day_max,
                     trade_start_time=":".join([
                         str(channel.trade_begin_hour),
                         str(channel.trade_begin_minute)
                     ]),
                     trade_end_time=":".join([
                         str(channel.trade_end_hour),
                         str(channel.trade_end_minute)
                     ]),
                     main_time=dict(maintain_begin=channel.maintain_begin
                                    if channel.maintain_begin else None,
                                    maintain_end=channel.maintain_end
                                    if channel.maintain_begin else None),
                     state=dict(desc=channel.state.desc,
                                value=channel.state.value),
                     reason=channel.get_reason_desc(),
                     banks=[bank.value for bank in channel.banks]))

        channel_list = sorted(channel_list,
                              key=lambda item: item['state']['value'])

        data = dict(counts=len(channel_list), withdraws=channel_list)

        return WithdrawListResult(bs_data=data).as_response()
Exemple #3
0
    def post(self):
        """
        代付通道管理: 删除代付通道
        :return:
        """
        form, error = WithdrawAddForm().request_validate()

        if error:
            return error.as_response()

        if form.start_time.data >= form.end_time.data:
            return DateStartMoreThanError().as_response()

        if form.maintain_begin.data:
            if form.maintain_begin.data >= form.maintain_end.data or form.maintain_begin.data < DateTimeKit.get_cur_datetime(
            ):
                return DateStartMoreThanError().as_response()

        if Decimal(form.limit_per_min.data) >= Decimal(
                form.limit_per_max.data):
            return DataStartMoreThanError().as_response()

        if form.limit_day_max.data and Decimal(
                form.limit_per_max.data) > Decimal(form.limit_day_max.data):
            return PerLimitMustLittleDayLimitError().as_response()

        banks = [PaymentBankEnum(int(bank)) for bank in form.banks.data]

        kwargs = dict(
            fee=form.fee.data,
            fee_type=form.fee_type.data,
            limit_per_min=form.limit_per_min.data,
            limit_per_max=form.limit_per_max.data,
            limit_day_max=form.limit_day_max.data
            if form.limit_day_max.data != "" else 0,
            trade_begin_hour=form.start_time.data.hour,
            trade_begin_minute=form.start_time.data.minute,
            trade_end_hour=form.end_time.data.hour,
            trade_end_minute=form.end_time.data.minute,
            maintain_begin=form.maintain_begin.data
            if form.maintain_begin else "",
            maintain_end=form.maintain_end.data if form.maintain_end else "",
            state=form.state.data,
            banks=banks,
            valid=ModelBase.INVALID)

        rst, error = ProxyChannelConfig.update_channel(form.channel_id.data,
                                                       **kwargs)
        if error:
            return error.as_response()

        # 同步缓存
        # ChannelLimitCacheCtl(PayTypeEnum.WITHDRAW).sync_db_channels_to_cache()

        return ResponseSuccess().as_response()
Exemple #4
0
    def post(self):
        """
        获取当前可用的代付通道
        """
        """
        1. 是否支持银行 banks like bank
        1. 提现金额 _limit_per_max, _limit_per_min
        2. 时间 _maintain_begin, _maintain_end
        """
        form, error = WithDrawSupperBankForm().request_validate()
        if error:
            return error.as_response()

        merchant = form.merchant_name.data
        channels = ProxyChannelConfig.query_all()
        proxy_channels = ProxyChannelConfig.filter_latest_items(channels)

        valid_channels = []
        withdraw_channel = {}

        for channel in proxy_channels:
            if form.bank_type.data not in channel.banks:
                continue

            if not channel.is_channel_valid(merchant.is_test,
                                            form.amount.data):
                continue

            withdraw_channel_key = channel.channel_enum.conf[
                'provider'] + channel.channel_enum.conf['mch_id']
            withdraw_channel[withdraw_channel_key] = channel.channel_id
            valid_channels.append(channel)

        valid_channels = [
            dict(key=channel, value=withdraw_channel[channel])
            for channel in withdraw_channel.keys()
        ]

        return WithdrawChannelResult(bs_data=dict(
            entries=valid_channels)).as_response()
Exemple #5
0
    def order_success(cls, order, tx_amount):
        """
        订单成功处理
        :return:
        """
        params = copy.deepcopy(locals())
        params.pop('order')
        params.pop('cls')
        params['tx_id'] = order.sys_tx_id

        rst = dict(
            code=0,
            msg='',
        )

        # 计算利润
        order_detail = OrderDetailWithdraw.query_by_order_id(
            order.merchant, order.order_id, order.create_time)
        profit = FeeCalculator.calc_profit(order_detail.fee, order_detail.cost)

        order, ref_id = OrderUpdateCtl.update_order_event(
            order.order_id,
            uid=order.uid,
            merchant=order.merchant,
            state=OrderStateEnum.SUCCESS,
            tx_amount=tx_amount,
            profit=profit,
        )
        if not order:
            current_app.logger.error('提现订单修改成功状态失败, ref_id: %s, sys_tx_id: %s',
                                     ref_id, params)
            return False

        # 累计当天通道充值额度
        channel_config = ProxyChannelConfig.query_by_channel_id(
            order.channel_id)
        ChannelLimitCacheCtl.add_day_amount(channel_config.channel_enum,
                                            order.amount)

        cls.do_notify(order=order)

        return True
Exemple #6
0
 def get_config_channels(cls, payment_way: PayTypeEnum, ret_dict=False):
     if payment_way == PayTypeEnum.DEPOSIT:
         return ChannelConfig.get_latest_active_configs(ret_dict)
     else:
         return ProxyChannelConfig.get_latest_active_configs(ret_dict)
Exemple #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)
Exemple #8
0
    def __test_api_channel(self):
        # 充值通道管理 新增通道
        channel_enum = ChannelConfigEnum.CHANNEL_1001
        post_data = dict(channel_id=channel_enum.value,
                         fee="2.3",
                         fee_type="1",
                         limit_per_min="2000",
                         limit_per_max="50000",
                         limit_day_max="50000",
                         start_time="09:00",
                         end_time="23:59",
                         state="10",
                         settlement_type="1",
                         priority="1")

        post_data["state"] = 10
        # 数值型参数类型错误
        self.path = '/channel/deposit/add'

        response = self.do_request(post_data)
        self.assertEqual(response.status_code, ParameterException.code)
        self.assertEqual(response.json['error_code'],
                         ParameterException.error_code)
        # print(response.json['message'], "this field must be String type")
        post_data["state"] = "10"

        # 测试交易日期为空的情况
        response = self.do_request(post_data)
        print(response.json)
        self.assertEqual(ResponseSuccess.code, response.status_code,
                         response.json['message'])
        self.assertEqual(ResponseSuccess.error_code,
                         response.json['error_code'], response.json['message'])
        # print(response.json['message'], "this field must be String type")
        post_data['maintain_begin'] = "2019-09-27 09:10:00"
        post_data['maintain_end'] = "2019-10-20 23:09:00"

        # 测试每笔交易上限大于日交易上限
        post_data['limit_day_max'] = "40000"
        response = self.do_request(post_data)
        print(response.json)
        self.assertEqual(PerLimitMustLittleDayLimitError.code,
                         response.status_code)
        self.assertEqual(PerLimitMustLittleDayLimitError.error_code,
                         response.json['error_code'])
        # print(response.json['message'], "this field must be String type")
        # self.assertEqual(response.json['message'], "单笔交易最大值必须小于当日交易限额")
        post_data['limit_day_max'] = "60000"

        # 时间类型参数业务数据不对
        post_data["maintain_begin"] = "2020-08-23 09:30:00"
        response = self.do_request(post_data)
        self.assertEqual(response.status_code, DateStartMoreThanError.code)
        self.assertEqual(response.json['error_code'],
                         DateStartMoreThanError.error_code)
        self.assertEqual(response.json['message'],
                         DateStartMoreThanError.message)
        post_data["maintain_begin"] = "2019-09-27 09:00:00"

        # 业务类型数据不对
        post_data["limit_per_min"] = "100000"
        response = self.do_request(post_data)
        self.assertEqual(response.status_code, DataStartMoreThanError.code)
        self.assertEqual(response.json['error_code'],
                         DataStartMoreThanError.error_code)
        self.assertEqual(response.json['message'],
                         DataStartMoreThanError.message)
        post_data["limit_per_min"] = "2000"

        # 时间类型数据格式错误
        post_data["maintain_begin"] = "2019/09/27 09:00:00"
        response = self.do_request(post_data)
        self.assertEqual(response.status_code, ParameterException.code)
        self.assertEqual(response.json['error_code'],
                         ParameterException.error_code)
        # self.assertEqual(response.json['message'], "无效的时间格式")
        post_data["maintain_begin"] = "2019-09-27 09:00:00"

        # 测试成功添加数据
        response = self.do_request(post_data)
        self.assertEqual(response.status_code, ResponseSuccess.code)
        channel = ChannelConfig.query_latest_one(
            dict(channel_enum=channel_enum))
        self.assertEqual(channel.channel_enum.value, post_data['channel_id'])
        self.assertEqual(channel.settlement_type.value,
                         int(post_data['settlement_type']))

        # 渠道管理:编辑通道

        self.path = '/channel/deposit/edit'
        post_data['settlement_type'] = '3'
        response = self.do_request(post_data)
        self.assertEqual(response.status_code, ResponseSuccess.code)
        channel = ChannelConfig.query_latest_one(
            dict(channel_enum=channel_enum))
        self.assertEqual(channel.channel_enum.value, post_data['channel_id'])
        self.assertEqual(channel.settlement_type.value, 3)

        # 测试 不支持的 枚举类型数据
        self.path = '/channel/deposit/edit'
        post_data['state'] = '110'
        response = self.do_request(post_data)
        self.assertEqual(response.status_code, ParameterException.code)
        self.assertEqual(response.json['error_code'],
                         ParameterException.error_code)
        # self.assertEqual(response.json['message'], "无效的通道状态")
        post_data['state'] = "10"

        # 代付通道管理: 新增代付通道

        self.path = "/channel/withdraw/add"
        withdraw_postdata = dict(channel_id=channel_enum.value,
                                 fee="2.3",
                                 fee_type="1",
                                 limit_per_min="2000",
                                 limit_per_max="50000",
                                 limit_day_max="50000",
                                 start_time="09:00",
                                 end_time="23:59",
                                 maintain_begin="2019-09-27 09:00:00",
                                 maintain_end="2019-10-20 23:00:00",
                                 state="10",
                                 banks=["1", "2", "4", "6", "3", "5", "15"])

        # 测试参数类型错误
        withdraw_postdata['channel_id'] = '123'
        response = self.do_request(withdraw_postdata)
        self.assertEqual(response.status_code, ParameterException.code)
        self.assertEqual(response.json['error_code'],
                         ParameterException.error_code)
        # self.assertEqual(response.json['message'], str({'channel_id': 'this field must be Integer type'}))

        # 测试时间格式错误
        withdraw_postdata['channel_id'] = channel_enum.value
        withdraw_postdata["maintain_begin"] = "2019/09/27 09:00:00"
        response = self.do_request(withdraw_postdata)
        self.assertEqual(response.status_code, ParameterException.code)
        self.assertEqual(response.json['error_code'],
                         ParameterException.error_code)
        # self.assertEqual(response.json['message'], "无效的时间格式")
        withdraw_postdata["maintain_begin"] = "2019-09-27 09:00:00"

        # 测试成功添加代付通道
        ProxyChannelConfig.delete_all()
        response = self.do_request(withdraw_postdata)
        self.assertEqual(response.status_code, ResponseSuccess.code)
        self.assertEqual(response.json['error_code'],
                         ResponseSuccess.error_code)

        channel = ProxyChannelConfig.query_latest_one(
            dict(channel_enum=channel_enum))
        self.assertEqual(withdraw_postdata['maintain_begin'],
                         DateTimeKit.datetime_to_str(channel.maintain_begin))
        self.assertEqual(withdraw_postdata['channel_id'],
                         channel.channel_enum.value)

        # 测试编辑 代付通道
        self.path = "/channel/withdraw/edit"

        withdraw_postdata['banks'] = ["4", "6", "3"]
        withdraw_postdata['limit_day_max'] = '180000'
        withdraw_postdata['maintain_begin'] = "2019-10-30 09:30:01"
        withdraw_postdata['maintain_end'] = "2019-12-30 09:30:01"
        response = self.do_request(withdraw_postdata)
        self.assertEqual(response.status_code, ResponseSuccess.code)
        self.assertEqual(response.json['error_code'],
                         ResponseSuccess.error_code)

        channel = ProxyChannelConfig.query_latest_one(
            dict(channel_enum=channel_enum))
        self.assertEqual(channel_enum.value, channel.channel_enum.value)
        self.assertEqual(withdraw_postdata['maintain_begin'],
                         DateTimeKit.datetime_to_str(channel.maintain_begin))
        self.assertEqual(withdraw_postdata['maintain_end'],
                         DateTimeKit.datetime_to_str(channel.maintain_end))
        self.assertEqual(
            [PaymentBankEnum(4),
             PaymentBankEnum(6),
             PaymentBankEnum(3)], channel.banks)
        self.assertEqual(180000, channel.limit_day_max)

        # 测试代付列表
        self.path = "/channel/withdraw/list"

        response = self.do_request()
        self.assertEqual(response.status_code, ResponseSuccess.code)
        self.assertEqual(response.json['error_code'],
                         ResponseSuccess.error_code)
        self.assertEqual('1', response.json['data']['counts'])
        self.assertEqual(channel_enum.value,
                         response.json['data']['withdraws'][0]['channel_id'])

        ProxyChannelConfig.delete_all()
Exemple #9
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))
Exemple #10
0
    def order_deal(cls,
                   admin_account,
                   order_id,
                   merchant,
                   channel_id,
                   test=False):
        """

        :param admin_account:
        :param order_id:
        :param merchant:
        :param channel_id:
        :param test: 单元测试填写
        :return:
        """
        params = copy.deepcopy(locals())
        params.pop('cls')

        if EnvironEnum.is_production(current_app.config['FLASK_ENV']) and test:
            # 非调试环境不该填写 channel_tx_id,应该发送请求到第三方去申请
            raise RuntimeError('invalid test param')

        order = OrderWithdraw.query_by_order_id(merchant, order_id)
        if not order:
            msg = '%s, params: %s' % (NoSuchWithdrawOrderError.message, params)
            current_app.logger.error(msg)
            return NoSuchWithdrawOrderError()

        if order.state != OrderStateEnum.ALLOC:
            msg = '%s, params: %s, state: %s, tx_id: %s' % (
                NotAllocOrderError.message, params, order.state.desc,
                order.sys_tx_id)
            current_app.logger.error(msg)
            return NotAllocOrderError(message=NotAllocOrderError.message +
                                      ", 订单状态:" + order.state.desc)

        channel_config = ProxyChannelConfig.query_by_channel_id(channel_id)
        if not channel_config:
            msg = '%s, params: %s' % (InvalidChannelError.message, params)
            current_app.logger.error(msg)
            return InvalidChannelError()

        bank_card = order.get_bank_card()
        if not bank_card:
            msg = '%s, params: %s' % (WithdrawBankNoExistError.message, params)
            current_app.logger.error(msg)
            return WithdrawBankNoExistError()

        # 开始更新订单,根据通道计算费率和成本
        # 通道收取的手续费
        channel_cost = FeeCalculator.calc_cost(order.amount,
                                               channel_config.fee_type,
                                               channel_config.fee)
        tx_amount = order.amount

        channel_enum = channel_config.channel_enum
        if channel_enum.plus_fee_for_withdraw():
            # 特殊通道,要发起金额要加上手续费,通道测扣除手续费才是实际到账金额
            # 实际提款金额=发起金额+通道手续费
            tx_amount += channel_cost

        # 发起支付
        launch_pay = channel_enum.get_launch_pay_func(PayTypeEnum.WITHDRAW)

        if not test:
            # 第三方返回由第三方生成交易ID:channel_tx_id
            rst = launch_pay(
                dict(order_id=order.order_id,
                     tx_id=order.sys_tx_id,
                     amount=tx_amount,
                     channel_cost=channel_cost,
                     bank_code=bank_card.bank_code,
                     bank_name=bank_card.bank_name,
                     bank_account=bank_card.account_name,
                     bank_number=bank_card.card_no,
                     bank_address=bank_card.bank_address,
                     bank_branch=bank_card.branch,
                     province=bank_card.province,
                     city=bank_card.city))

            current_app.logger.info('withdraw launch_pay, params: %s, rst: %s',
                                    params, rst)

            if rst['code'] != 0:
                # 不要改变订单状态,让客服去选择其它通道重试
                # cls.order_fail(order, client_ip)
                current_app.logger.error(
                    '%s, %s, params: %s' %
                    (FailedLaunchWithdrawError.message, rst['msg'], params))
                return FailedLaunchWithdrawError(message=rst['msg'])

        # 发起成功后更新状态为处理中
        order, _ = OrderUpdateCtl.update_order_event(
            order.order_id,
            uid=order.uid,
            merchant=merchant,
            state=OrderStateEnum.DEALING,
            channel_id=channel_id,
            cost=channel_cost,
            deal_time=DateTimeKit.get_cur_datetime(),
            op_account=admin_account,
        )
        if not order:
            msg = '%s, params: %s' % (WithdrawUpdateDealingError.message,
                                      params)
            current_app.logger.error(msg)
            return WithdrawUpdateDealingError()

        return ResponseSuccess()
Exemple #11
0
 def get_withdraw_channel2(cls):
     return ProxyChannelConfig.query_latest_one(query_fields=dict(
         channel_enum=cls.channel_enum2))
Exemple #12
0
    def init_channel2(cls):
        if not cls.get_deposit_channel2():
            # 充值通道配置
            kwargs = dict(fee="2.5",
                          fee_type=PaymentFeeTypeEnum.PERCENT_PER_ORDER,
                          limit_per_min="500",
                          limit_per_max="20000",
                          trade_begin_hour="0",
                          trade_begin_minute="0",
                          trade_end_hour="0",
                          trade_end_minute="0",
                          maintain_begin=DateTimeKit.str_to_datetime(
                              "2019-09-07 09:00:00"),
                          maintain_end=DateTimeKit.str_to_datetime(
                              "2019-09-07 09:00:00"),
                          settlement_type=SettleTypeEnum.D0,
                          state=ChannelStateEnum.TESTING
                          if cls.merchant.is_test else ChannelStateEnum.ONLINE,
                          priority="101")

            ChannelConfig.update_channel(cls.channel_enum2, **kwargs)
            channel_config = ChannelConfig.query_latest_one(query_fields=dict(
                channel_enum=cls.channel_enum2))
            # print(channel_config)

            # limit_min, limit_max = ChannelLimitCacheCtl(PayTypeEnum.DEPOSIT).get_channel_limit()
            limit_min, limit_max = ChannelListHelper.get_channel_limit_range(
                merchant=cls.merchant,
                payment_way=PayTypeEnum.DEPOSIT,
            )
            # print('limit_min: %s, limit_max: %s' % (limit_min, limit_max))
            assert 0 != limit_min
            assert 0 != limit_max

        if not cls.get_withdraw_channel2():
            # 提款代付通道配置
            withdraw_item = dict(
                fee="1.3",
                fee_type=PaymentFeeTypeEnum.PERCENT_PER_ORDER,
                limit_per_min="300",
                limit_per_max="10000",
                limit_day_max="500000",
                trade_begin_hour="0",
                trade_begin_minute="0",
                trade_end_hour="0",
                trade_end_minute="0",
                maintain_begin=DateTimeKit.str_to_datetime(
                    "2019-09-07 09:00:00"),
                maintain_end=DateTimeKit.str_to_datetime(
                    "2019-09-07 09:00:00"),
                state=ChannelStateEnum.TESTING
                if cls.merchant.is_test else ChannelStateEnum.ONLINE,
                banks=[
                    PaymentBankEnum.ZHONGGUO,
                    PaymentBankEnum.GONGSHANG,
                    PaymentBankEnum.JIANSHE,
                ])
            ProxyChannelConfig.update_channel(cls.channel_enum2,
                                              **withdraw_item)

            channel_config = ProxyChannelConfig.query_latest_one(
                query_fields=dict(channel_enum=cls.channel_enum2))
            # print(channel_config)

            # limit_min, limit_max = ChannelLimitCacheCtl(PayTypeEnum.WITHDRAW).get_channel_limit()
            limit_min, limit_max = ChannelListHelper.get_channel_limit_range(
                merchant=cls.merchant,
                payment_way=PayTypeEnum.WITHDRAW,
            )
            # print('limit_min: %s, limit_max: %s' % (limit_min, limit_max))
            assert 0 != limit_min
            assert 0 != limit_max
Exemple #13
0
    def post(self):
        """
        提现订单详情
        :return:
        """
        form, error = WithDrawBankForm().request_validate()
        if error:
            return error.as_response()

        order_id = form.order_id.data
        order_map = GlobalOrderId.query_global_id(order_id)
        merchant = order_map.merchant
        entry = OrderWithdraw.query_by_order_id(order_id=order_id,
                                                merchant=merchant)
        detail = OrderDetailWithdraw.query_by_order_id(merchant=merchant,
                                                       order_id=order_id)
        detail_head = dict(source=entry.source.desc,
                           op_account=detail.op_account,
                           deliver_type=detail.deliver_type.desc
                           if detail.deliver_type else None,
                           create_time=entry.str_create_time,
                           alloc_time=detail.str_alloc_time,
                           deal_time=detail.str_deal_time,
                           done_time=detail.str_done_time,
                           mch_tx_id=entry.mch_tx_id,
                           sys_tx_id=entry.sys_tx_id,
                           state=entry.state.get_back_desc(
                               PayTypeEnum.WITHDRAW),
                           settle=entry.settle.desc,
                           deliver=entry.deliver.desc,
                           amount=entry.amount)
        order_merchant_info = dict(
            merchant_name=merchant.name,
            fee=detail.fee,
            cost=detail.cost,
            profit=detail.profit,
            withdraw_type="测试" if merchant.is_test else "用户提现")
        deliver_info = None
        if entry.channel_id:
            proxy_entry = ProxyChannelConfig.query_by_channel_id(
                entry.channel_id)
            channel_enum = proxy_entry.channel_enum

            deliver_info = dict(channel_name=channel_enum.desc,
                                mch_id=channel_enum.conf['mch_id'],
                                channel_tx_id=entry.channel_tx_id)
        user_info = dict(
            user_id=entry.uid,
            ip=detail.ip,
            location=GeoIpKit(detail.ip).location,
            device="",
        )

        event_entries = OrderEvent.query_model(
            query_fields=dict(order_id=entry.order_id), date=entry.create_time)

        event_log_list = list()

        for event in event_entries:
            order_event = event.data_after[0]
            order_event.update(event.data_after[1])

            if 'state' in order_event:
                state = list(order_event['state'].keys())[0]
                event_log_list.append(
                    dict(
                        operate_type=OrderStateEnum.from_name(
                            state).get_back_desc(PayTypeEnum.WITHDRAW),
                        operator=order_event.get('op_account') or '',
                        result="成功",
                        operate_time=DateTimeKit.timestamp_to_datetime(
                            order_event['update_time']),
                        comment=order_event.get('comment') or '',
                    ))

            if 'deliver' in order_event:
                deliver = list(order_event['deliver'].keys())[0]
                event_log_list.append(
                    dict(
                        operate_type=DeliverStateEnum.from_name(deliver).desc,
                        operator=order_event.get('op_account') or '',
                        result="成功",
                        operate_time=DateTimeKit.timestamp_to_datetime(
                            order_event['update_time']),
                        comment=order_event.get('comment') or '',
                    ))

        return WithdrawOrderDetailResult(
            bs_data=dict(detail_head=detail_head,
                         order_merchant_info=order_merchant_info,
                         deliver_info=deliver_info,
                         user_info=user_info,
                         event_log_list=event_log_list)).as_response()
Exemple #14
0
    def post(self):
        """
        专一付代付回调
        :return:
        """
        if not EnvironEnum.is_local_evn(current_app.config['FLASK_ENV']):
            # 无论如何都记录一条log
            current_app.logger.info(
                'zhuanyifu withdraw callback, ip: %s, data: %s, headers: %s',
                IpKit.get_remote_ip(), request.json, request.headers)

        event = request.headers.get('ChinaRailway-Event')
        signature = request.headers.get('ChinaRailway-Signature')

        form, error = ZhuanYeFuWithdrawForm().request_validate()
        if error:
            current_app.logger.fatal('msg: %s, data: %s', error.message,
                                     request.args)
            return BaseResponse('FAIlURE')

        pp = signature.split('.')

        if event != "Pay.Succeeded":
            return ResponseSuccess(code=500).as_response()

        # 交易ID
        tx_id = form.order.data
        fee = Decimal(form.fee.data)
        order = WithdrawTransactionCtl.get_order(tx_id)
        if not order:
            return ResponseSuccess(
                code=500, message='curr order no found').as_response()

        curr_status = order.state
        if curr_status != OrderStateEnum.DEALING:
            return ResponseSuccess(
                code=500,
                message='curr order status must be DEALING').as_response()

        print(order.channel_id, form.order.data, order.merchant,
              order.create_time, order.order_id, order.uid)

        channel_config = ProxyChannelConfig.query_by_channel_id(
            order.channel_id)
        channel_cost = FeeCalculator.calc_cost(order.amount,
                                               channel_config.fee_type,
                                               channel_config.fee)
        if fee != channel_cost:
            current_app.logger.error(
                "ZYF withdraw fee info order_id:{}, channel_fee: {}, channel_cost:{}"
                .format(order.order_id, fee, channel_cost))

        try:
            flag = CryptoKit.rsa_verify(
                pp[1], pp[0],
                channel_config.channel_enum.conf['plat_public_key'])
            if flag != True:
                return ResponseSuccess(code=500, message='签名错误').as_response()
        except Exception as e:
            return ResponseSuccess(code=500).as_response()

        # 代付金额
        tx_amount = Decimal(form.amount.data)
        # 代付费率
        fee = Decimal(form.fee.data)
        # 通道订单号
        transaction = form.transaction.data
        client_ip = form.client_ip.data
        status = form.status.data

        if str(status) == "1":
            """
            修改订单状态, 记录代付费率
            """
            if not WithdrawTransactionCtl.order_success(order, tx_amount):
                return ResponseSuccess(code=500).as_response()

        elif str(status) == "2":
            """
            代付订单失败, 
            1.给用户退款,给商户退款+手续费
            2. 修改订单状态 
            """
            # order = WithdrawTransactionCtl.get_order(merchant, order_id)
            if not WithdrawTransactionCtl.order_fail(order):
                return ResponseSuccess(code=500).as_response()

        return ResponseSuccess(code=204).as_response()