Пример #1
0
    def add_balance_to_user(cls, account, value, register=True):
        user = User.query_user(cls.merchant, account=account)
        if not user:
            if not register:
                return -10, "用户未注册"

            user = User.register_account(cls.merchant,
                                         account=account,
                                         ac_type=AccountTypeEnum.MOBILE,
                                         login_pwd=cls.password)

        data = dict(
            uid=user.uid,
            merchant=cls.merchant,
            ref_id=OrderUtils.gen_unique_ref_id(),
            source=OrderSourceEnum.MANUALLY,
            order_type=PayTypeEnum.MANUALLY,
            bl_type=BalanceTypeEnum.AVAILABLE,
            ad_type=BalanceAdjustTypeEnum.PLUS,
            tx_id=OrderUtils.gen_normal_tx_id(user.uid),
            value=Decimal(str(value)),
            comment="手动脚本修改用户可用余额",
        )
        rst, msg = UserBalanceEvent.update_user_balance(**data)
        # print(rst, msg)
        return rst, msg
Пример #2
0
    def create_order(cls):
        merchant = MerchantEnum.TEST
        order_id = OrderTstGid.generate_order_id()
        create_time = DateTimeKit.get_cur_datetime()

        fields = dict(
            uid=0,
            create_time=create_time,
            update_time=create_time,
            order_id=order_id,
            amount=Decimal("234142.33"),
            mch_tx_id=OrderUtils.generate_mch_tx_id(order_id),
            sys_tx_id=OrderUtils.generate_sys_tx_id(order_id),
            source=OrderSourceEnum.MANUALLY,
            state=OrderStateEnum.INIT,
            op_account='test',
            pay_method=PayMethodEnum.ZHIFUBAO_SAOMA,
            notify_url="https://google.com",
            result_url="https://google.com",
            extra=json.dumps(dict(x=1, y=2, z=3)),
        )

        rst = cls.add_model(fields,
                            merchant=merchant,
                            date=create_time,
                            commit=True)
        return rst
Пример #3
0
    def init_merchant(cls):
        # 先创建商户
        if not cls.get_merchant_info():
            MerchantInfo.create_merchant(m_name=cls.merchant,
                                         m_type=MerchantTypeEnum.TEST)

            # 给商户加钱
            rst, msg = MerchantBalanceEvent.update_balance(
                merchant=cls.merchant,
                ref_id=OrderUtils.gen_unique_ref_id(),
                source=OrderSourceEnum.MANUALLY,
                order_type=PayTypeEnum.MANUALLY,
                bl_type=BalanceTypeEnum.AVAILABLE,
                ad_type=BalanceAdjustTypeEnum.PLUS,
                tx_id=OrderUtils.gen_normal_tx_id(10),
                value=100000000,
                comment="手动脚本修改商户可用余额")
            # print(rst, msg)

        merchant_fee_list = list()
        if not cls.get_merchant_fee_config(PayTypeEnum.DEPOSIT):
            # 商户费率配置
            merchant_fee_list.append(
                dict(
                    merchant=cls.merchant,
                    payment_way=PayTypeEnum.DEPOSIT,
                    value="3",
                    fee_type=PaymentFeeTypeEnum.PERCENT_PER_ORDER,
                    payment_method=cls.channel_enum.conf.payment_method,
                ))
            MerchantFeeConfig.update_fee_config(cls.merchant,
                                                merchant_fee_list)

            # 商户费率配置
            merchant_fee_list.append(
                dict(
                    merchant=cls.merchant,
                    payment_way=PayTypeEnum.DEPOSIT,
                    value="3",
                    fee_type=PaymentFeeTypeEnum.PERCENT_PER_ORDER,
                    payment_method=cls.channel_enum2.conf.payment_method,
                ))
            MerchantFeeConfig.update_fee_config(cls.merchant,
                                                merchant_fee_list)

        if not cls.get_merchant_fee_config(PayTypeEnum.WITHDRAW):
            merchant_fee_list.append(
                dict(merchant=cls.merchant,
                     payment_way=PayTypeEnum.WITHDRAW,
                     value="3.2",
                     fee_type=PaymentFeeTypeEnum.PERCENT_PER_ORDER))
            MerchantFeeConfig.update_fee_config(cls.merchant,
                                                merchant_fee_list)

        merchant_config = cls.get_merchant_fee_config(PayTypeEnum.DEPOSIT)
        assert merchant_config.fee_type == PaymentFeeTypeEnum.PERCENT_PER_ORDER

        merchant_config = cls.get_merchant_fee_config(PayTypeEnum.WITHDRAW)
        assert merchant_config.fee_type == PaymentFeeTypeEnum.PERCENT_PER_ORDER
Пример #4
0
    def __change_balance(self, data: dict, result: int, balance_check: dict, event_check: dict):
        """
        修改
        :param data:
        :param result:merchant_balance_event_test_91
        :param balance_check:
        :param event_check:
        :return:
        """
        # 修改余额
        data = copy.deepcopy(data)
        ref_id = OrderUtils.gen_unique_ref_id()
        data['ref_id'] = ref_id
        event_check['tx_id'] = data['tx_id'] = OrderUtils.gen_normal_tx_id(int(data['value']))

        rst, msg = MerchantBalanceEvent.update_balance(**data)
        self.assertEqual(result, rst)

        if rst == 0:
            event_check['ref_id'] = ref_id

        # 验证余额
        merchant = MerchantInfo.query_merchant(data['merchant'])
        self.assertIsNotNone(merchant)
        balance_total = sum(balance_check.values())
        self.assertEqual(balance_total, merchant.balance_total)
        self.assertEqual(balance_check['balance_available'], merchant.balance_available)
        self.assertEqual(balance_check['balance_frozen'], merchant.balance_frozen)
        self.assertEqual(balance_check['balance_income'], merchant.balance_income)

        if rst != 0:
            # 更新失败,就不用验证结果事件了
            return rst

        event = MerchantBalanceEvent.query_event(merchant=merchant.merchant,
                                                 create_time=DateTimeKit.get_cur_datetime(),
                                                 ref_id=ref_id).first()
        value = event_check['value']
        if event_check['ad_type'] == BalanceAdjustTypeEnum.MINUS:
            # 做减法
            value = -event_check['value']

        self.assertEqual(event_check['merchant'], event.merchant)
        self.assertEqual(event_check['source'], event.source)
        self.assertEqual(event_check['bl_type'], event.bl_type)
        self.assertEqual(event_check['ad_type'], event.ad_type)
        self.assertEqual(value, event.value_real)
        self.assertEqual(event_check['tx_id'], event.tx_id)
        self.assertEqual(event_check['comment'], event.comment)
        self.assertEqual(event_check['ref_id'], event.ref_id)

        return rst
Пример #5
0
    def __change_balance(self, data: dict, result: int, balance_check: dict):
        """
        修改
        :param data:
        :param result:
        :param balance_check:
        :return:
        """
        # 修改余额
        data = copy.deepcopy(data)
        ref_id = OrderUtils.gen_unique_ref_id()
        data['ref_id'] = ref_id
        data['tx_id'] = OrderUtils.gen_normal_tx_id(data['uid'])

        rst, msg = UserBalanceEvent.update_user_balance(**data)
        self.assertEqual(result, rst)

        # 验证余额
        balance = UserBalance.query_balance(data['uid'],
                                            data['merchant']).first()
        self.assertIsNotNone(balance)
        self.assertEqual(balance_check['balance_available'],
                         balance.real_balance)

        if rst != 0:
            # 更新失败,就不用验证结果事件了
            return rst

        event = UserBalanceEvent.query_event(
            uid=data['uid'],
            merchant=balance.merchant,
            date=DateTimeKit.get_cur_datetime(),
            ref_id=ref_id).first()
        value = data['value']
        if data['ad_type'] == BalanceAdjustTypeEnum.MINUS:
            # 做减法
            value = -data['value']

        self.assertEqual(data['uid'], event.uid)
        self.assertEqual(data['merchant'], event.merchant)
        self.assertEqual(data['source'], event.source)
        self.assertEqual(data['bl_type'], event.bl_type)
        self.assertEqual(data['ad_type'], event.ad_type)
        self.assertEqual(value, event.value_real)
        self.assertEqual(ref_id, event.ref_id)

        if data['source'] == OrderSourceEnum.MANUALLY:
            self.assertEqual(data['comment'], event.comment)

        return rst
Пример #6
0
 def get_order(cls, tx_id):
     """
     根据交易ID查询订单
     :param tx_id:
     :return:
     """
     order_id = OrderUtils.parse_tx_id(tx_id)
     return cls.get_order_by_order_id(order_id)
Пример #7
0
    def __check_event_params_error(self, uid, merchant):
        ###########################################################
        # 余额修改,参数错误
        ###########################################################

        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("0"),
            comment="xxx",
        )

        # value是0
        rst, msg = UserBalanceEvent.update_user_balance(**data)
        self.assertEqual(rst, -1)

        # value是负数
        data['value'] = Decimal("-100")
        rst, msg = UserBalanceEvent.update_user_balance(**data)
        self.assertEqual(rst, -1)

        # 提款不能用加法
        data['value'] = Decimal("100")
        data['source'] = OrderSourceEnum.TESTING
        data['order_type'] = PayTypeEnum.WITHDRAW
        data['ad_type'] = BalanceAdjustTypeEnum.PLUS
        rst, msg = UserBalanceEvent.update_user_balance(**data)
        self.assertEqual(rst, -3)

        # 充值不能用减法
        data['source'] = OrderSourceEnum.TESTING
        data['order_type'] = PayTypeEnum.DEPOSIT
        data['ad_type'] = BalanceAdjustTypeEnum.MINUS
        rst, msg = UserBalanceEvent.update_user_balance(**data)
        self.assertEqual(rst, -3)

        # 人工操作时,必填调整类型
        data['source'] = OrderSourceEnum.MANUALLY
        data.pop('ad_type')
        rst, msg = UserBalanceEvent.update_user_balance(**data)
        self.assertEqual(rst, -4)

        # 人工操作时,必填调整备注信息
        data['ad_type'] = BalanceAdjustTypeEnum.MINUS
        data.pop('comment')
        rst, msg = UserBalanceEvent.update_user_balance(**data)
        self.assertEqual(rst, -4)
Пример #8
0
    def post(self):
        """
        余额调整
        """
        form, error = MerchantBalanceEditForm.request_validate()
        if error:
            return error.as_response()

        rst, msg = MerchantBalanceEvent.update_balance(
            merchant=form.name.data,
            ref_id=OrderUtils.gen_unique_ref_id(),
            source=OrderSourceEnum.MANUALLY,
            order_type=PayTypeEnum.MANUALLY,
            value=form.amount.data,
            bl_type=form.bl_type.data,
            ad_type=form.ad_type.data,
            tx_id=OrderUtils.gen_normal_tx_id(g.user.uid),
            comment=form.reason.data,
        )

        if rst != 0:
            return MerchantUpdateError(message=msg).as_response()

        return ResponseSuccess().as_response()
Пример #9
0
    def init_user2(cls):
        user = cls.get_user2()
        if not user:
            user = User.register_account(cls.merchant,
                                         account=cls.user_account2,
                                         ac_type=AccountTypeEnum.MOBILE,
                                         login_pwd=cls.password)

            data = dict(
                uid=user.uid,
                merchant=cls.merchant,
                ref_id=OrderUtils.gen_unique_ref_id(),
                source=OrderSourceEnum.MANUALLY,
                order_type=PayTypeEnum.MANUALLY,
                bl_type=BalanceTypeEnum.AVAILABLE,
                ad_type=BalanceAdjustTypeEnum.PLUS,
                tx_id=OrderUtils.gen_normal_tx_id(user.uid),
                value=Decimal("500000000"),
                comment="手动脚本修改用户可用余额",
            )
            rst, msg = UserBalanceEvent.update_user_balance(**data)
            # print(rst, msg)

        # 写入数据库
        if not cls.get_bank_card():
            bank_card = BankCard.add_bank_card(
                user.merchant,
                uid=user.uid,
                bank_name=PaymentBankEnum.ZHONGGUO.desc,
                bank_code=PaymentBankEnum.ZHONGGUO.bank_code,
                card_no=cls.bank_card_no2,
                account_name="王小儿",
                branch="深圳支行",
                province="广东省",
                city="深圳市",
            )
Пример #10
0
    def query_by_tx_id(cls, tx_id):
        """
        根据交易ID查询订单
        :param tx_id:
        :return:
        """
        if cls.is_base_order():
            raise RuntimeError('can not query by base order')

        order_id = OrderUtils.parse_tx_id(tx_id)
        g_order_id = GlobalOrderId.query_global_id(order_id)
        if not g_order_id:
            return None

        return cls.query_by_order_id(g_order_id.merchant, order_id,
                                     g_order_id.create_time)
Пример #11
0
    def manually_withdraw_failed(cls, admin_user, merchant, order_id):
        """
        手动更新提款状态为失败
        退款/审核拒绝
                流程:
            1. 获取创建订单时 扣除 用户及商户的费用
                获取订单 withdrawOrderDetail 数据, 获取 手续费 提现金额
            2. 给用户和商户新增费用
                更新 UserBalance 表
                更新 MerchantBalance表
            3. 修改订单状态
                更新 OrderUpdateCtl
        :param admin_user:
        :param merchant:
        :param order_id:
        :return:
        """
        # 查询该笔订单是否存在
        withdraw_entry = OrderWithdraw.query_by_order_id(merchant=merchant,
                                                         order_id=order_id)
        # 判断是否存在
        if not withdraw_entry:
            return OrderInfoMissingError()

        # 判断订单状态是否为 已认领 或 提现成功
        if withdraw_entry.state not in [
                OrderStateEnum.ALLOC, OrderStateEnum.SUCCESS
        ]:
            return BankOrderStateError()

        detail = OrderDetailWithdraw.query_by_order_id(
            order_id=withdraw_entry.order_id,
            merchant=merchant,
            create_time=withdraw_entry.create_time)
        if not detail:
            return NosuchOrderDetailDataError()

        # 提现订单 手续费 提现订单费用
        fee = detail.fee
        amount = detail.amount

        comment = "出款失败" if withdraw_entry.state == OrderStateEnum.SUCCESS else "系统拒绝"
        order_type = PayTypeEnum.REFUND if withdraw_entry.state == OrderStateEnum.SUCCESS else PayTypeEnum.MANUALLY

        merchant_config = MerchantFeeConfig.query_by_config_id(
            withdraw_entry.mch_fee_id)

        # 更新订单状态
        try:
            with db.auto_commit():

                order, ref_id = OrderUpdateCtl.update_order_event(
                    withdraw_entry.order_id,
                    uid=int(withdraw_entry.uid),
                    merchant=merchant,
                    state=OrderStateEnum.FAIL,
                    tx_amount=withdraw_entry.amount,
                    deliver_type=DeliverTypeEnum.MANUALLY,
                    op_account=admin_user.account,
                    commit=False)

                if not order:
                    msg = WithdrawOrderStateChangeError.message
                    current_app.logger.error(msg)
                    raise WithdrawOrderStateChangeError()

                # 加提现金额
                flag, msg = MerchantBalanceEvent.update_balance(
                    merchant=merchant,
                    ref_id=ref_id,
                    order_type=order_type,
                    bl_type=BalanceTypeEnum.AVAILABLE,
                    value=amount,
                    ad_type=BalanceAdjustTypeEnum.PLUS,
                    tx_id=order.sys_tx_id,
                    source=OrderSourceEnum.MANUALLY,
                    comment=comment,
                    commit=False,
                )
                if flag < 0:
                    msg = '%s' % ("提现退回增加商户余额失败, %s" % msg)
                    current_app.logger.error(msg)
                    raise DepositCallbackUserBalanceError()

                if merchant_config.cost_type == CostTypeEnum.MERCHANT:
                    # 给商户加手续费
                    flag, msg = MerchantBalanceEvent.update_balance(
                        merchant=merchant,
                        ref_id=OrderUtils.gen_unique_ref_id(),
                        order_type=PayTypeEnum.FEE,
                        bl_type=BalanceTypeEnum.AVAILABLE,
                        value=fee,
                        ad_type=BalanceAdjustTypeEnum.PLUS,
                        tx_id=order.sys_tx_id,
                        commit=False,
                        comment=comment,
                        source=OrderSourceEnum.MANUALLY)
                    if flag < 0:
                        msg = '%s' % ("提现退款增加商户手续费失败, %s" % msg)
                        current_app.logger.error(msg)
                        raise DepositCallbackUserBalanceError()

                refund_fee = amount
                if merchant_config.cost_type == CostTypeEnum.USER:
                    # 给用户退回手续费
                    refund_fee += fee

                # 增加用户余额
                flag, msg = UserBalanceEvent.update_user_balance(
                    uid=order.uid,
                    merchant=merchant,
                    ref_id=ref_id,
                    order_type=order_type,
                    bl_type=BalanceTypeEnum.AVAILABLE,
                    value=refund_fee,
                    ad_type=BalanceAdjustTypeEnum.PLUS,
                    tx_id=order.sys_tx_id,
                    commit=False,
                    comment=comment,
                    source=OrderSourceEnum.MANUALLY)
                if flag < 0:
                    msg = '%s' % ("提现退款增加用户余额失败, %s" % msg)
                    current_app.logger.error(msg)
                    raise DepositCallbackUserBalanceError()

        except APIException as e:
            current_app.logger.error(traceback.format_exc())
            return e

        cls.do_notify(
            order=order,
            op_account=admin_user.account,
            comment=comment,
        )

        return ResponseSuccess()
Пример #12
0
    def query_withdraw_order_list(cls, form, export=False):
        """
        查询提现订单列表
        :param form:
        :param export:
        :return:
        """

        merchant = form.merchant_name.data

        if not form.begin_time.data:
            begin_time, end_time = DateTimeKit.get_day_begin_end(DateTimeKit.get_cur_date())
        else:
            begin_time = form.begin_time.data
            end_time = form.end_time.data

        kwargs = {}
        if form.state.data != "0":
            kwargs["_state"] = form.state.data.value

        tx_id = form.order_id.data
        if tx_id:
            if OrderUtils.is_sys_tx_id(tx_id):
                kwargs["sys_tx_id"] = tx_id
            else:
                kwargs["mch_tx_id"] = tx_id

        try:
            order_list = OrderWithdraw.query_by_create_time(begin_time=begin_time, end_time=end_time,
                                                            merchant=merchant).filter_by(**kwargs)
        except MultiMonthQueryException:
            return MultiMonthQueryError().as_response()

        all_channels = dict([(x.channel_id, x) for x in ChannelConfig.query_all()])
        order_detail_dict = dict()

        order_list = OrderFilters.filter_from_order_list(order_list, filters=[
            functools.partial(OrderFilters.filter_tx_id, tx_id),
            functools.partial(OrderFilters.filter_channel, all_channels, form.channel.data),
        ])

        if order_list:
            # 订单详情列表
            order_detail_list = OrderDetailWithdraw.query_by_create_time(begin_time=begin_time, end_time=end_time,
                                                                         merchant=merchant)

            # 订单列表和订单详情列表相互过滤
            order_list, order_detail_dict = OrderFilters.filter_from_detail_list(
                order_list, order_detail_list,
                filters=[
                    functools.partial(
                        OrderFilters.filter_done_time,
                        form.done_begin_time.data,
                        form.done_end_time.data),
                ])

            # 按时间倒序
            order_list = sorted(order_list, key=itemgetter('create_time'), reverse=True)

        if export and order_list:
            return CsvOrderExport.export_withdraw_list_csv(order_list, order_detail_dict, all_channels)

        return cls.render_withdraw_list(form, order_list, order_detail_dict)
Пример #13
0
    def post(self):

        merchant = MerchantEnum.TEST_API
        amount = Decimal(request.form['amount'])

        domain = MerchantDomainConfig.get_latest_domain(merchant)

        # 模拟商户发起支付请求
        scheme_host = UrlKit.get_scheme_host(host=domain)
        url = scheme_host + url_for('gateway_deposit_request')

        if not request.form['payment_type']:
            return redirect(
                scheme_host +
                url_for('gateway_demo_merchant_deposit', error="请选择支付类型"))

        payment_type = PaymentTypeEnum.from_name(request.form['payment_type'])

        # 模拟商户的回调URL
        notify_url = UrlKit.join_host_path(url_for('gateway_demo_notify'),
                                           host=domain)

        post_data = dict(
            merchant_id=merchant.value,
            amount=str(amount),
            mch_tx_id=OrderUtils.generate_mch_tx_id(
                DateTimeKit.get_cur_timestamp()),
            payment_type=payment_type.name,
            notify_url=notify_url,
            user_ip=IpKit.get_remote_ip(),
        )
        print('post_data:', post_data)

        post_data['sign'] = GatewaySign(merchant).generate_sign(post_data)
        post_data['redirect_url'] = "https://google.com"
        post_data['extra'] = json.dumps(dict(x=1, y=2))
        post_data['user_id'] = "100"

        print('post_data:', post_data)

        rsp = requests.post(url, json=post_data)
        if rsp.status_code != 200:
            return redirect(scheme_host +
                            url_for('gateway_demo_merchant_deposit',
                                    error="http请求失败,状态码:%s, url: %s" %
                                    (rsp.status_code, url)))

        if rsp.json()['error_code'] != 200:
            return redirect(scheme_host + url_for(
                'gateway_demo_merchant_deposit', error=rsp.json()['message']))

        sys_tx_id = rsp.json()['data']['sys_tx_id']

        return redirect(scheme_host + url_for(
            'gateway_demo_merchant_deposit',
            success=True,
            post_data=json.dumps(post_data),
            notify_url=scheme_host +
            url_for('demo_deposit_notify', tx_id=sys_tx_id),
            redirect_url=rsp.json()['data']['redirect_url'],
            sys_tx_id=sys_tx_id,
            mch_tx_id=rsp.json()['data']['mch_tx_id'],
            valid_time=rsp.json()['data']['valid_time'],
        ))
Пример #14
0
    def post(self):

        merchant = MerchantEnum.TEST_API
        amount = Decimal(request.form['amount'])

        domain = MerchantDomainConfig.get_latest_domain(merchant)
        # 模拟商户发起支付请求
        scheme_host = UrlKit.get_scheme_host(host=domain)
        url = scheme_host + url_for('gateway_withdraw_request')

        if not request.form['bank_type']:
            return redirect(
                scheme_host +
                url_for('gateway_demo_merchant_withdraw', error="必选选择银行类型"))

        bank_type = PaymentBankEnum.from_name(request.form['bank_type'])

        # 模拟商户的回调URL
        notify_url = UrlKit.join_host_path(url_for('gateway_demo_notify'),
                                           host=domain)

        post_data = dict(
            merchant_id=merchant.value,
            amount=str(amount),
            mch_tx_id=OrderUtils.generate_mch_tx_id(
                DateTimeKit.get_cur_timestamp()),
            bank_type=bank_type.name,
            notify_url=notify_url,
            card_no=request.form['card_no'],
            account_name=request.form['account_name'],
            province=request.form['province'],
            city=request.form['city'],
            user_ip=IpKit.get_remote_ip(),
        )
        print('post_data:', post_data)

        post_data['sign'] = GatewaySign(merchant).generate_sign(post_data)
        post_data['extra'] = json.dumps(dict(x=1, y=2))
        post_data['user_id'] = "100"
        post_data['branch'] = request.form['branch']

        print('post_data:', post_data)

        rsp = requests.post(url, json=post_data)
        if rsp.status_code != 200:
            return redirect(scheme_host +
                            url_for('gateway_demo_merchant_withdraw',
                                    error="http请求失败,状态码:%s, url: %s" %
                                    (rsp.status_code, url)))

        if rsp.json()['error_code'] != 200:
            return redirect(scheme_host + url_for(
                'gateway_demo_merchant_withdraw', error=rsp.json()['message']))

        sys_tx_id = rsp.json()['data']['sys_tx_id']

        return redirect(scheme_host + url_for(
            'gateway_demo_merchant_withdraw',
            success=True,
            post_data=json.dumps(post_data),
            notify_url=scheme_host +
            url_for('demo_withdraw_notify', tx_id=sys_tx_id),
            sys_tx_id=sys_tx_id,
            mch_tx_id=rsp.json()['data']['mch_tx_id'],
        ))
Пример #15
0
    def post(self):
        """
        商户充值订单查询
        :return:
        """
        form, error = DepositOrderSelectForm().request_validate()
        if error:
            return error.as_response()

        user = g.user

        try:
            order_list_query = OrderDeposit.query_by_create_time(
                begin_time=form.start_datetime.data,
                end_time=form.end_datetime.data,
                merchant=MerchantEnum(user.mid))
        except MultiMonthQueryException as e:
            return MultiMonthQueryError().as_response()

        kwargs = {}
        tx_id = form.order_id.data
        if tx_id:
            if OrderUtils.is_sys_tx_id(tx_id):
                kwargs["sys_tx_id"] = tx_id
            else:
                kwargs["mch_tx_id"] = tx_id

        if form.state.data != "0":
            kwargs["_state"] = form.state.data.value

        query = order_list_query.filter_by(**kwargs)
        pagination = query.paginate(form.page_index.data, form.page_size.data,
                                    False)

        entries = pagination.items
        total = pagination.total

        items = []
        channel_lst = list(set([o.channel_id for o in entries]))
        channel_items = {
            Channel.id: Channel
            for Channel in ChannelConfig.query.filter(
                ChannelConfig.id.in_(channel_lst)).all()
        }
        kwargs = {}
        if tx_id and entries:
            kwargs['id'] = entries[0].order_id
        order_items = {
            item.id: item
            for item in OrderDetailDeposit.query_by_create_time(
                begin_time=form.start_datetime.data,
                end_time=form.end_datetime.data,
                merchant=MerchantEnum(user.mid)).filter_by(**kwargs).all()
        }
        for order in entries:
            item_channel = channel_items.get(order.channel_id, {})
            detail = order_items.get(order.order_id, {})
            if not item_channel or not detail:
                continue

            items.append(
                dict(mch_tx_id=order.mch_tx_id,
                     sys_tx_id=order.sys_tx_id,
                     payment_type=item_channel.channel_enum.
                     conf['payment_type'].desc,
                     amount=detail.amount,
                     tx_amount=detail.tx_amount,
                     fee=detail.fee,
                     create_time=order.str_create_time,
                     done_time=order.update_time,
                     state=order.state.desc,
                     deliver=order.deliver.desc))
        items = sorted(items, key=lambda item: item['create_time'])
        return MerchantDepositOrderResult(
            bs_data=dict(entries=items, total=total)).as_response()
Пример #16
0
    def post(self):
        """
        商户提现订单查询
        :return:
        """
        form, error = WithdrawOrderSelectForm().request_validate()
        if error:
            return error.as_response()

        user = g.user

        try:
            order_list_query = OrderWithdraw.query_by_create_time(
                begin_time=form.start_datetime.data,
                end_time=form.end_datetime.data,
                merchant=MerchantEnum(user.mid))

        except MultiMonthQueryException as e:
            return MultiMonthQueryError().as_response()

        kwargs = {}
        tx_id = form.order_id.data
        if tx_id:
            if OrderUtils.is_sys_tx_id(tx_id):
                kwargs["sys_tx_id"] = tx_id
            else:
                kwargs["mch_tx_id"] = tx_id

        if form.state.data != "0":
            kwargs["_state"] = form.state.data.value

        query = order_list_query.filter_by(**kwargs)
        pagination = query.paginate(form.page_index.data, form.page_size.data,
                                    False)

        entries = pagination.items
        total = pagination.total

        bank_lst = list(set([o.bank_id for o in entries]))
        kwargs = {}

        if tx_id and entries:
            kwargs['id'] = entries[0].order_id

        bank_items = dict()
        if not MerchantEnum(user.mid).is_api_merchant:
            bank_items = {
                bank.id: bank
                for bank in BankCard.query.filter(BankCard.id.in_(
                    bank_lst)).all()
            }
        order_items = {
            item.id: item
            for item in OrderDetailWithdraw.query_by_create_time(
                begin_time=form.start_datetime.data,
                end_time=form.end_datetime.data,
                merchant=MerchantEnum(user.mid)).filter_by(**kwargs).all()
        }

        items = []
        for order in entries:
            if not MerchantEnum(user.mid).is_api_merchant:
                bank = bank_items.get(order.bank_id, {})
            else:
                bank = order.get_bank_card()
            detail = order_items.get(order.order_id, {})
            if not bank or not detail:
                continue
            items.append(
                dict(mch_tx_id=order.mch_tx_id,
                     sys_tx_id=order.sys_tx_id,
                     amount=detail.amount,
                     fee=detail.fee,
                     account_name=bank.account_name,
                     bank_name=bank.bank_name,
                     branch="{}{}{}".format(bank.province, bank.city,
                                            bank.branch),
                     card_no=bank.card_no,
                     create_time=order.str_create_time,
                     done_time=order.update_time,
                     state=order.state.desc,
                     deliver=order.deliver.desc))
        items = sorted(items, key=lambda item: item['create_time'])
        return MerchantWithdrawOrderResult(
            bs_data=dict(entries=items, total=total)).as_response()
Пример #17
0
    def adjust_create(user,
                      source,
                      amount,
                      bl_type,
                      order_type,
                      ad_type,
                      comment,
                      tx_id=None,
                      ref_id=None):

        params = copy.deepcopy(locals())
        # params.pop('cls')

        adjust_flag = True

        try:
            # 更新商户及用户余额
            with db.auto_commit():
                if not tx_id:
                    tx_id = OrderUtils.gen_normal_tx_id(user.uid)

                if not ref_id:
                    ref_id = OrderUtils.gen_unique_ref_id()
                # 更新用户余额
                flag, msg = UserBalanceEvent.update_user_balance(
                    uid=user.uid,
                    merchant=user.merchant,
                    ref_id=ref_id,
                    source=source,
                    order_type=order_type,
                    bl_type=bl_type,
                    value=amount,
                    ad_type=ad_type,
                    tx_id=tx_id,
                    comment=comment,
                    commit=False,
                )
                if flag < 0:
                    msg = '%s, params: %s' % ("更新用户余额失败, %s" % msg, params)
                    current_app.logger.error(msg)
                    raise AdjustUserBalanceError(message="更新用户余额失败")

                # 更新商户余额
                flag, msg = MerchantBalanceEvent.update_balance(
                    merchant=user.merchant,
                    ref_id=ref_id,
                    source=source,
                    order_type=order_type,
                    bl_type=bl_type,
                    value=amount,
                    ad_type=ad_type,
                    tx_id=tx_id,
                    comment=comment,
                    commit=False,
                )
                if flag < 0:
                    msg = '%s, params: %s' % ("更新商户余额失败, %s" % msg, params)
                    current_app.logger.error(msg)
                    raise AdjustUserBalanceError(message="更新商户余额失败")

        except APIException as e:
            current_app.logger.error(traceback.format_exc())
            adjust_flag = False
            return adjust_flag, e

        return adjust_flag, None
Пример #18
0
    def create_order_event(
            cls,
            uid,
            amount,
            merchant: MerchantEnum,
            source: OrderSourceEnum,  # 订单来源
            order_type: PayTypeEnum,  # 订单类型
            in_type: InterfaceTypeEnum,  # 商户接入类型
            pay_method: PayMethodEnum = None,  # 支付方法
            create_time=None,
            bank_id=None,  # 用户提现银行卡ID
            ref_id=None,  # 票据ID
            op_account=None,  # 后台管理员账号,后台人工修改数据时必填
            comment=None,  # 管理后台修改备注,后台人工修改数据时必填
            mch_tx_id=None,  # 商户交易ID,当in_type为API时必填,其它时候选填
            channel_id: int = None,  # 通道费率ID,ABSChannelConfig 表中的主键ID,充值必填,提款不填
            mch_fee_id: int = None,  # 商户费率配置ID,MerchantFeeConfig 表的主键ID,充值必填,提款不填
            ip=None,
            fee=None,  # 提现订单,创建的时候就扣了手续费
            notify_url=None,
            result_url=None,
            extra=None,
            bank_info=None,  # 银行卡信息,用于API商户
            cost_type=None,
            commit=True,  # 是否立即提交事务
    ):
        if source == OrderSourceEnum.MANUALLY and not (op_account and comment):
            raise RuntimeError("人工修改订单必填管理员账号和备注")

        if in_type == InterfaceTypeEnum.API and not mch_tx_id:
            raise RuntimeError("开放API必须提供商户交易ID")

        if order_type == PayTypeEnum.DEPOSIT and not (channel_id and mch_fee_id):
            raise RuntimeError("充值必须填写channel_id/mch_fee_id/bank_id")

        if order_type == PayTypeEnum.WITHDRAW:
            if not (fee and mch_fee_id):
                raise RuntimeError("提现必须填写 fee/mch_fee_id")
            if not (bank_info or bank_id):
                raise RuntimeError("提现必须填写 bank_info 或 bank_id")

        create_time = create_time or DateTimeKit.get_cur_datetime()
        order_id = GlobalOrderId.generate_order_id(uid, order_type, create_time, merchant)
        sys_tx_id = OrderUtils.generate_sys_tx_id(order_id)

        if not mch_tx_id:
            mch_tx_id = OrderUtils.generate_mch_tx_id(order_id)

        params = copy.deepcopy(locals())
        params.pop('cls')

        if not ref_id:
            ref_id = OrderConstraint.apply_ref_id(order_id, order_type, OrderStateEnum.INIT)
            if not ref_id:
                return None, ''
            params['ref_id'] = ref_id

        try:
            # 订单模型
            rst, fields = cls.__create_order(**params)
            if rst['code'] != 0:
                OrderConstraint.revoke_order_state(order_id, OrderStateEnum.INIT)
                current_app.logger.error('failed to create order model, rst: %s, params: %s', rst, params)
                return None, ref_id

            models = list(rst['model'].values())
            order = rst['model']['hot']

            # 订单详情
            rst, fields = cls.__create_order_detail(**params)
            if rst['code'] != 0:
                OrderConstraint.revoke_order_state(order_id, OrderStateEnum.INIT)
                current_app.logger.error('failed to create order detail model, rst: %s, params: %s', rst, params)
                return None, ref_id

            models.extend(rst['model'].values())
            order_detail = rst['model']['hot']

            # 订单修改事件
            rst = cls.__create_event(**params)
            order_event = rst['model']
            order_event.data_after = [order.get_after_fields(), order_detail.get_after_fields()]
            models.append(order_event)

            # 提交事务
            assert len(models) == 5
            ModelOpMix.commit_models(models=models, commit=commit)

        except IntegrityError as e:
            OrderConstraint.revoke_order_state(order_id, OrderStateEnum.INIT)
            # sqlalchemy.exc.IntegrityError: (pymysql.err.IntegrityError) (1062, "Duplicate entry '4155' for key 'mch_tx_id'")
            msg = str(e)
            if 'Duplicate' in msg and 'mch_tx_id' in msg:
                # 订单号重复时不能重置订单状态
                msg = "商户订单号重复"
            current_app.logger.error(str(msg), exc_info=True)
            raise Exception(msg)

        except Exception as e:
            # 任何非期望的异常都要回滚状态
            OrderConstraint.revoke_order_state(order_id, OrderStateEnum.INIT)
            current_app.logger.error(str(e), exc_info=True)
            raise e

        return order, ref_id
Пример #19
0
    def order_fail(cls, order):
        """
        订单失败处理
        :return:
        """
        params = copy.deepcopy(locals())
        params.pop('cls')
        params.pop('order')
        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)
        merchant_config = MerchantFeeConfig.query_by_config_id(
            order.mch_fee_id)

        try:
            # 创建提现订单/扣商户余额/扣用户余额,在同一个事务里面
            with db.auto_commit():
                order, ref_id = OrderUpdateCtl.update_order_event(
                    order.order_id,
                    uid=order.uid,
                    merchant=order.merchant,
                    state=OrderStateEnum.FAIL,
                    commit=False,
                )
                if not order:
                    raise RuntimeError('提现订单修改失败状态失败, params: %s' % params)

                # 给商户退回提现订单的发起金额
                flag, msg = MerchantBalanceEvent.update_balance(
                    merchant=order.merchant,
                    ref_id=ref_id,
                    source=order.source,
                    order_type=PayTypeEnum.REFUND,
                    bl_type=BalanceTypeEnum.AVAILABLE,
                    # 订单发起金额
                    value=order.amount,
                    ad_type=BalanceAdjustTypeEnum.PLUS,
                    tx_id=order.sys_tx_id,
                    commit=False,
                )
                # print('update_balance', flag, msg)
                if flag < 0:
                    raise RuntimeError(msg + ", params: %s" % params)

                if merchant_config.cost_type == CostTypeEnum.MERCHANT:
                    # 给商户退回提手续费
                    flag, msg = MerchantBalanceEvent.update_balance(
                        merchant=order.merchant,
                        ref_id=OrderUtils.gen_unique_ref_id(),
                        tx_id=order.sys_tx_id,
                        source=order.source,
                        order_type=PayTypeEnum.FEE,
                        bl_type=BalanceTypeEnum.AVAILABLE,
                        # 收取商户的手续费
                        value=order_detail.fee,
                        ad_type=BalanceAdjustTypeEnum.PLUS,
                        commit=False,
                    )
                    # print('update_balance', flag, msg)
                    if flag < 0:
                        raise RuntimeError(msg + ", params: %s" % params)

                refund_fee = order.amount
                if merchant_config.cost_type == CostTypeEnum.USER:
                    # 给用户退回手续费
                    refund_fee += order_detail.fee

                # 给用户退回发起金额
                flag, msg = UserBalanceEvent.update_user_balance(
                    uid=order.uid,
                    merchant=order.merchant,
                    ref_id=ref_id,
                    source=order.source,
                    order_type=PayTypeEnum.REFUND,
                    bl_type=BalanceTypeEnum.AVAILABLE,
                    # 订单发起金额
                    value=refund_fee,
                    ad_type=BalanceAdjustTypeEnum.PLUS,
                    tx_id=order.sys_tx_id,
                    commit=False,
                )
                # print('update_user_balance', flag, msg)
                if flag < 0:
                    raise RuntimeError(msg + ", params: %s" % params)

        except APIException as e:
            current_app.logger.error(traceback.format_exc())
            return False

        cls.do_notify(order=order)

        return True
Пример #20
0
    def success_order_process(cls,
                              order,
                              tx_amount,
                              channel_tx_id=None,
                              comment: str = '',
                              op_account=None,
                              commit=True):
        """
        处理充值成功的订单
        :param order:
        :param tx_amount: 实际支付金额
        :param channel_tx_id: 通道订单号
        :param comment: 备注
        :param op_account: 备注
        :param commit: 是否立即提交事务
        :return:
        """
        params = copy.deepcopy(locals())
        params.pop('cls')
        params.pop('order')
        params['tx_id'] = order.sys_tx_id

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

        # 计算一笔订单的各种费用
        channel_config = ChannelConfig.query_by_channel_id(order.channel_id)
        merchant_config = MerchantFeeConfig.query_by_config_id(
            order.mch_fee_id)
        order_fee = OrderFeeHelper.calc_order_fee(order, tx_amount,
                                                  channel_config,
                                                  merchant_config)

        try:
            with db.auto_commit(commit):
                order, ref_id = OrderUpdateCtl.update_order_event(
                    order.order_id,
                    uid=order.uid,
                    merchant=order.merchant,
                    state=OrderStateEnum.SUCCESS,
                    channel_tx_id=channel_tx_id,
                    tx_amount=tx_amount,
                    offer=order_fee['offer'],  # 优惠金额
                    fee=order_fee['merchant_fee'],  # 手续费
                    cost=order_fee['channel_cost'],  # 成本金额
                    profit=order_fee['profit'],  # 利润(收入)金额
                    commit=False,
                    pay_method=channel_config.channel_enum.
                    conf['payment_method'],
                    comment=comment,
                    op_account=op_account,
                )
                if not order:
                    msg = '订单更新失败, params: %s' % params
                    raise RuntimeError(msg)

                # 给用户充值
                code, msg = UserBalanceEvent.update_user_balance(
                    uid=order.uid,
                    merchant=order.merchant,
                    ref_id=ref_id,
                    source=order.source,
                    order_type=order.order_type,
                    bl_type=BalanceTypeEnum.AVAILABLE,
                    value=order.amount,
                    ad_type=BalanceAdjustTypeEnum.PLUS,
                    comment=comment,
                    tx_id=order.sys_tx_id,
                    commit=False,
                )
                if code != 0:
                    raise RuntimeError(msg)

                # 根据结算类型获取商户余额变更类型
                balance_type = SettleHelper.get_balance_type_by_settle(
                    channel_config.settlement_type)

                # 更新商户余额,加用户充值金额
                code, msg = MerchantBalanceEvent.update_balance(
                    merchant=order.merchant,
                    ref_id=ref_id,
                    source=order.source,
                    order_type=order.order_type,
                    bl_type=balance_type,
                    value=order.amount,
                    ad_type=BalanceAdjustTypeEnum.PLUS,
                    tx_id=order.sys_tx_id,
                    comment=comment,
                    commit=False,
                )
                if code != 0:
                    raise RuntimeError(msg)

                # 更新商户余额,扣手续费
                ref_id = OrderUtils.gen_unique_ref_id()
                code, msg = MerchantBalanceEvent.update_balance(
                    merchant=order.merchant,
                    ref_id=ref_id,
                    source=order.source,
                    order_type=PayTypeEnum.FEE,
                    bl_type=balance_type,
                    value=order_fee['merchant_fee'],
                    ad_type=BalanceAdjustTypeEnum.MINUS,
                    tx_id=order.sys_tx_id,
                    comment=comment,
                    commit=False,
                )
                if code != 0:
                    raise RuntimeError(msg)

        except RuntimeError as e:
            current_app.logger.error('An error occurred.', exc_info=True)
            return False

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

        cls.do_notify(order)

        return True
Пример #21
0
    def get(self):
        """
        订单修改日志
        :return:
        """
        if not request.args:
            return ResponseSuccess(
                message=
                "参数规则:?merchant=test&date=20190901&order_id=123&uid=123&ref_id=xxx&export=1,"
                "必填参数:merchant,"
                "可选参数:date,order_id,uid,ref_id,export,"
                "当不填写date时,默认查询当天所有的数据").as_response()

        try:
            date = request.args.get('date')
            if date:
                date = DateTimeKit.str_to_datetime(
                    date, DateTimeFormatEnum.TIGHT_DAY_FORMAT, to_date=True)
            else:
                date = DateTimeKit.get_cur_date()
        except:
            return ResponseSuccess(
                message="请输入有效的查询日期,格式为:20190901").as_response()

        q_params = dict()
        order_id = request.args.get('order_id')
        try:
            order_id = OrderUtils.parse_tx_id(order_id)
        except:
            pass
        if order_id:
            q_params['order_id'] = order_id
        uid = request.args.get('uid')
        if uid:
            q_params['uid'] = uid
        ref_id = request.args.get('ref_id')
        if ref_id:
            q_params['ref_id'] = ref_id
        if not q_params:
            return ResponseSuccess(
                message="必须输入 order_id/uid/ref_id 其中一个或多个参数").as_response()

        events = OrderEvent.query_model(query_fields=q_params, date=date)

        rst = list()

        for event in events:
            rst.append(
                dict(
                    create_time=event.create_time,
                    order_id=event.order_id,
                    uid=event.uid,
                    ref_id=event.ref_id,
                    data_before=event.data_before,
                    data_after=event.data_after,
                ))

        rst = sorted(rst, key=lambda x: x['create_time'], reverse=True)
        for x in rst:
            x['create_time'] = DateTimeKit.datetime_to_str(x['create_time'])

        if rst and request.args.get('export'):
            filename = 'order_events_%s.csv' % DateTimeKit.datetime_to_str(
                date, DateTimeFormatEnum.TIGHT_DAY_FORMAT)
            return CsvKit.send_csv(rst,
                                   filename=filename,
                                   fields=rst[0].keys())

        return ResponseSuccess(bs_data=rst).as_response()
Пример #22
0
    def order_create(cls,
                     user,
                     amount,
                     client_ip,
                     user_bank_id=None,
                     bank_info=None,
                     notify_url=None,
                     mch_tx_id=None,
                     extra=None):
        """
        申请创建订单
        :return:
        """
        params = copy.deepcopy(locals())
        params.pop('cls')

        order = None

        if not bank_info:
            card_entry = BankCard.query_bankcard_by_id(card_id=user_bank_id)
            if not card_entry:
                msg = '%s, params: %s' % (WithdrawBankNoExistError.message,
                                          params)
                current_app.logger.error(msg)
                return order, WithdrawBankNoExistError()

        # 判断 金额是否在有效范围 获取代付系统当前最高最低交易金额
        # limit_min, limit_max = ChannelLimitCacheCtl(PayTypeEnum.WITHDRAW).get_channel_limit()
        # if amount < limit_min or amount > limit_max:
        if ChannelListHelper.is_amount_out_of_range(
                amount=amount,
                merchant=user.merchant,
                payment_way=PayTypeEnum.WITHDRAW,
                client_ip=client_ip):
            msg = '%s, params: %s' % (WithdrawOrderAmountInvalidError.message,
                                      params)
            current_app.logger.error(msg)
            return order, WithdrawOrderAmountInvalidError()

        # 商户配置信息
        merchant_config = MerchantFeeConfig.query_latest_one(query_fields=dict(
            merchant=user.merchant,
            payment_way=PayTypeEnum.WITHDRAW,
        ))

        # 手续费计算
        fee_value = FeeCalculator.calc_fee(amount, merchant_config.fee_type,
                                           merchant_config.value)
        user_fee = merchant_fee = 0
        if merchant_config.cost_type == CostTypeEnum.MERCHANT:
            merchant_fee = fee_value
        elif merchant_config.cost_type == CostTypeEnum.USER:
            user_fee = fee_value
            # 用户实际到账金额要减去手续费
            amount -= user_fee

        # 用户余额判断
        if user.uid:
            user_balance = UserBalance.query_balance(
                uid=user.uid, merchant=user.merchant).first()
            if user_balance.real_balance < amount + user_fee:
                msg = '%s, params: %s' % ("用户余额不足", params)
                current_app.logger.error(msg)
                return order, AccountBalanceInsufficientError(message="用户余额不足")

        # 判断商户余额是否充足
        merchant_balance = MerchantInfo.query_merchant(m_name=user.merchant)
        if merchant_balance.balance_available <= amount + merchant_fee:
            msg = '%s, params: %s' % ("商户余额不足", params)
            current_app.logger.error(msg)
            return order, AccountBalanceInsufficientError(message="商户余额不足")

        # 订单来源
        source = OrderSourceEnum.TESTING if user.is_test_user else OrderSourceEnum.ONLINE

        try:
            # 创建提现订单/扣商户余额/扣用户余额,在同一个事务里面
            with db.auto_commit():
                order, ref_id = OrderCreateCtl.create_order_event(
                    uid=user.uid,
                    amount=amount,
                    merchant=user.merchant,
                    source=source,
                    order_type=PayTypeEnum.WITHDRAW,
                    in_type=InterfaceTypeEnum.CASHIER_H5,
                    bank_id=user_bank_id,  # 提现时需要填入 银行卡信息
                    mch_fee_id=merchant_config.config_id,
                    mch_tx_id=mch_tx_id,
                    ip=client_ip,
                    fee=fee_value,
                    cost_type=merchant_config.cost_type,
                    notify_url=notify_url,
                    bank_info=bank_info,
                    extra=extra,
                    commit=False,
                )

                if not order:
                    msg = '%s, params: %s' % (WithdrawOrderCreateError.message,
                                              params)
                    current_app.logger.error(msg)
                    raise WithdrawOrderCreateError()

                # 扣提现金额
                flag, msg = MerchantBalanceEvent.update_balance(
                    merchant=user.merchant,
                    ref_id=ref_id,
                    source=source,
                    order_type=PayTypeEnum.WITHDRAW,
                    bl_type=BalanceTypeEnum.AVAILABLE,
                    value=amount,
                    ad_type=BalanceAdjustTypeEnum.MINUS,
                    tx_id=order.sys_tx_id,
                    commit=False,
                )
                if flag < 0:
                    msg = '%s, params: %s' % ("扣商户余额失败, %s" % msg, params)
                    current_app.logger.error(msg)
                    raise DepositCallbackUserBalanceError(message="扣商户余额失败")

                if merchant_fee:
                    # 扣商户手续费
                    flag, msg = MerchantBalanceEvent.update_balance(
                        merchant=user.merchant,
                        ref_id=OrderUtils.gen_unique_ref_id(),
                        source=source,
                        order_type=PayTypeEnum.FEE,
                        bl_type=BalanceTypeEnum.AVAILABLE,
                        value=merchant_fee,
                        ad_type=BalanceAdjustTypeEnum.MINUS,
                        tx_id=order.sys_tx_id,
                        commit=False,
                    )
                    if flag < 0:
                        msg = '%s, params: %s' % ("扣商户手续费失败, %s" % msg, params)
                        current_app.logger.error(msg)
                        raise DepositCallbackUserBalanceError(
                            message="扣商户手续费失败")

                if user_fee:
                    # 扣除用户手续费
                    flag, msg = UserBalanceEvent.update_user_balance(
                        uid=user.uid,
                        merchant=user.merchant,
                        ref_id=OrderUtils.gen_unique_ref_id(),
                        source=source,
                        order_type=PayTypeEnum.FEE,
                        bl_type=BalanceTypeEnum.AVAILABLE,
                        value=user_fee,
                        ad_type=BalanceAdjustTypeEnum.MINUS,
                        tx_id=order.sys_tx_id,
                        commit=False,
                    )
                    if flag < 0:
                        msg = '%s, params: %s' % ("扣用户手续费失败, %s" % msg, params)
                        current_app.logger.error(msg)
                        raise DepositCallbackUserBalanceError(
                            message="扣用户手续费失败")

                # 扣除用户余额
                flag, msg = UserBalanceEvent.update_user_balance(
                    uid=user.uid,
                    merchant=user.merchant,
                    ref_id=ref_id,
                    source=source,
                    order_type=PayTypeEnum.WITHDRAW,
                    bl_type=BalanceTypeEnum.AVAILABLE,
                    value=amount,
                    ad_type=BalanceAdjustTypeEnum.MINUS,
                    tx_id=order.sys_tx_id,
                    commit=False,
                )
                if flag < 0:
                    msg = '%s, params: %s' % ("扣用户余额失败, %s" % msg, params)
                    current_app.logger.error(msg)
                    raise DepositCallbackUserBalanceError(message="扣用户余额失败")

        except APIException as e:
            current_app.logger.error(traceback.format_exc())
            return order, e

        return order, None
Пример #23
0
    def apply_ref_id(cls, order_id, order_type: PayTypeEnum,
                     state: OrderStateEnum):
        """
        申请修改订单的票据ID
        :param order_id:
        :param order_type:
        :param state:
        :return:
        """
        if not state:
            return OrderUtils.gen_unique_ref_id()

        params = copy.deepcopy(locals())
        params.pop('cls')

        if state == OrderStateEnum.INIT:
            # 初始化状态无需前置状态约束
            model = cls.get_model_obj()
            model.order_id = order_id
            model.state = state
            model.commit_models(model)
            return OrderUtils.gen_unique_ref_id()

        query_params = dict(order_id=order_id, )

        if order_type == PayTypeEnum.WITHDRAW:
            if state == OrderStateEnum.ALLOC:
                query_params.update(_state=OrderStateEnum.INIT.value)
            elif state == OrderStateEnum.DEALING:
                query_params.update(_state=OrderStateEnum.ALLOC.value)
            elif state == OrderStateEnum.SUCCESS:
                query_params.update(_state=OrderStateEnum.DEALING.value)
            elif state == OrderStateEnum.FAIL:
                # 状态变更为失败,无需前置状态约束
                pass
            else:
                raise ValueError('invalid state, params: %s' % params)

        elif order_type == PayTypeEnum.DEPOSIT:
            if state == OrderStateEnum.SUCCESS:
                query_params.update(_state=OrderStateEnum.INIT.value)
            elif state == OrderStateEnum.FAIL:
                # 状态变更为失败,无需前置状态约束
                pass
            else:
                raise ValueError('invalid state, params: %s' % params)

        elif order_type == PayTypeEnum.DEPOSIT:
            if state == OrderStateEnum.FAIL:
                # 只有状态成功后才有退款
                query_params.update(_state=OrderStateEnum.SUCCESS.value)
            else:
                raise ValueError('invalid state, params: %s' % params)

        # 让数据库来确保事务
        with db.auto_commit():
            effect = cls.get_model_cls().query.filter_by(
                **query_params).update(dict(_state=state.value))
            if effect != 1:
                msg = '修改订单状态的票据ID申请失败,query_params: %s, params: %s' % (
                    query_params, params)
                current_app.config['SENTRY_DSN'] and current_app.logger.error(
                    msg)
                return None

        return OrderUtils.gen_unique_ref_id()
Пример #24
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))
Пример #25
0
    def __check_add_event_params_error(self):
        ###########################################################
        # 余额修改,参数错误
        ###########################################################

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

        # value是0
        rst, msg = MerchantBalanceEvent.update_balance(
            merchant=MerchantEnum.TEST,
            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(100),
            value=0,
        )
        self.assertEqual(rst, -1)

        # value是负数
        rst, msg = MerchantBalanceEvent.update_balance(
            merchant=MerchantEnum.TEST,
            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(100),
            value=-1.0,
        )
        self.assertEqual(rst, -1)

        # 提款不能用加法
        rst, msg = MerchantBalanceEvent.update_balance(
            merchant=MerchantEnum.TEST,
            ref_id=ref_id,
            bl_type=BalanceTypeEnum.AVAILABLE,
            source=OrderSourceEnum.TESTING,
            order_type=PayTypeEnum.WITHDRAW,
            ad_type=BalanceAdjustTypeEnum.PLUS,
            value=120.00,
            tx_id=OrderUtils.gen_normal_tx_id(100),
        )
        self.assertEqual(rst, -3)

        # 充值不能用减法
        rst, msg = MerchantBalanceEvent.update_balance(
            merchant=MerchantEnum.TEST,
            ref_id=ref_id,
            bl_type=BalanceTypeEnum.AVAILABLE,
            source=OrderSourceEnum.TESTING,
            order_type=PayTypeEnum.DEPOSIT,
            ad_type=BalanceAdjustTypeEnum.MINUS,
            value=120.00,
            tx_id=OrderUtils.gen_normal_tx_id(100),
        )
        self.assertEqual(rst, -3)

        # 只有充值才能修改在途余额
        rst, msg = MerchantBalanceEvent.update_balance(
            merchant=MerchantEnum.TEST,
            ref_id=ref_id,
            bl_type=BalanceTypeEnum.INCOME,
            source=OrderSourceEnum.TESTING,
            order_type=PayTypeEnum.WITHDRAW,
            ad_type=BalanceAdjustTypeEnum.MINUS,
            value=120.00,
            tx_id=OrderUtils.gen_normal_tx_id(100),
        )
        self.assertEqual(rst, -4)

        # 在途余额不能从提款减少
        rst, msg = MerchantBalanceEvent.update_balance(
            merchant=MerchantEnum.TEST,
            ref_id=ref_id,
            bl_type=BalanceTypeEnum.INCOME,
            source=OrderSourceEnum.TESTING,
            order_type=PayTypeEnum.WITHDRAW,
            ad_type=BalanceAdjustTypeEnum.MINUS,
            value=120.00,
            tx_id=OrderUtils.gen_normal_tx_id(100),
        )
        self.assertEqual(rst, -4)

        # 人工操作时,必填调整类型
        rst, msg = MerchantBalanceEvent.update_balance(
            merchant=MerchantEnum.TEST,
            ref_id=ref_id,
            source=OrderSourceEnum.MANUALLY,
            bl_type=BalanceTypeEnum.AVAILABLE,
            order_type=PayTypeEnum.MANUALLY,
            value=1.00,
            tx_id=OrderUtils.gen_normal_tx_id(100),
        )
        self.assertEqual(rst, -5)

        # 人工操作时,必填调整备注信息
        rst, msg = MerchantBalanceEvent.update_balance(
            merchant=MerchantEnum.TEST,
            ref_id=ref_id,
            source=OrderSourceEnum.MANUALLY,
            bl_type=BalanceTypeEnum.AVAILABLE,
            ad_type=BalanceAdjustTypeEnum.PLUS,
            order_type=PayTypeEnum.MANUALLY,
            value=1.00,
            tx_id=OrderUtils.gen_normal_tx_id(100),
        )
        self.assertEqual(rst, -5)

        # 在途余额不能人工增加
        rst, msg = MerchantBalanceEvent.update_balance(
            merchant=MerchantEnum.TEST,
            ref_id=ref_id,
            bl_type=BalanceTypeEnum.INCOME,
            source=OrderSourceEnum.MANUALLY,
            order_type=PayTypeEnum.MANUALLY,
            ad_type=BalanceAdjustTypeEnum.PLUS,
            value=120.00,
            tx_id=OrderUtils.gen_normal_tx_id(100),
            comment='xxx',
        )
        self.assertEqual(rst, -6)

        # 冻结余额不能从提款减少
        rst, msg = MerchantBalanceEvent.update_balance(
            merchant=MerchantEnum.TEST,
            ref_id=ref_id,
            bl_type=BalanceTypeEnum.FROZEN,
            source=OrderSourceEnum.TESTING,
            order_type=PayTypeEnum.WITHDRAW,
            ad_type=BalanceAdjustTypeEnum.MINUS,
            value=120.00,
            tx_id=OrderUtils.gen_normal_tx_id(100),
        )
        self.assertEqual(rst, -7)

        # 冻结余额不能从充值增加
        rst, msg = MerchantBalanceEvent.update_balance(
            merchant=MerchantEnum.TEST,
            ref_id=ref_id,
            bl_type=BalanceTypeEnum.FROZEN,
            source=OrderSourceEnum.TESTING,
            order_type=PayTypeEnum.DEPOSIT,
            ad_type=BalanceAdjustTypeEnum.PLUS,
            value=120.00,
            tx_id=OrderUtils.gen_normal_tx_id(100),
        )
        self.assertEqual(rst, -7)
Пример #26
0
    def transfer(cls, from_user, to_user, merchant: MerchantEnum, amount, comment: str = ''):
        """
        转账
        ref_id = OrderUtils.gen_unique_ref_id()
        PayTypeEnum.TRANSFER
        商户类型是TEST,那么source就是 TEST
        否则是 ONLINE
        """
        from app.models.user import UserBindInfo

        # 记录一个参数日志, locals自动收集这行代码之前出现过的局部变量
        params = copy.deepcopy(locals())
        params.pop('cls')

        try:
            with db.auto_commit(True):

                # 构造source的值
                if merchant.is_test:
                    source = OrderSourceEnum.TESTING
                else:
                    source = OrderSourceEnum.ONLINE

                ref_id_a = OrderUtils.gen_unique_ref_id()
                ref_id_b = OrderUtils.gen_unique_ref_id()
                tx_id = OrderUtils.gen_normal_tx_id(from_user.uid)

                in_bind = UserBindInfo.query_bind_by_uid(to_user.uid)
                if in_bind:
                    in_account = in_bind.name
                else:
                    in_account = to_user.account

                out_bind = UserBindInfo.query_bind_by_uid(from_user.uid)
                if out_bind:
                    out_account = out_bind.name
                else:
                    out_account = from_user.account

                # A用户扣钱
                flag_a, msg_b = cls.update_user_balance(from_user.uid, merchant,
                                                        ref_id=ref_id_a,
                                                        source=source,
                                                        order_type=PayTypeEnum.TRANSFER,
                                                        value=amount,
                                                        bl_type=BalanceTypeEnum.AVAILABLE,
                                                        ad_type=BalanceAdjustTypeEnum.MINUS,
                                                        comment=comment,
                                                        tx_id=tx_id,
                                                        in_account=in_account,
                                                        out_account=out_account,
                                                        commit=False,
                                                        )

                if flag_a != 0:
                    raise RuntimeError(msg_b)

                # B用户加钱
                flag_b, msg_b = cls.update_user_balance(to_user.uid, merchant,
                                                        ref_id=ref_id_b,
                                                        source=source,
                                                        order_type=PayTypeEnum.TRANSFER,
                                                        value=amount,
                                                        bl_type=BalanceTypeEnum.AVAILABLE,
                                                        ad_type=BalanceAdjustTypeEnum.PLUS,
                                                        comment=comment,
                                                        tx_id=tx_id,
                                                        in_account=in_account,
                                                        out_account=out_account,
                                                        commit=False,
                                                        )

                if flag_b != 0:
                    raise RuntimeError(msg_b)

        except RuntimeError as e:
            # 捕获异常,返回失败
            msg = "%s, params: %s" % (str(e), params)
            current_app.config['SENTRY_DSN'] and current_app.logger.fatal(msg)
            return -100, msg

        return True, ''