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))
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()
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()
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()
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
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)
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)
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()
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))
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()
def get_withdraw_channel2(cls): return ProxyChannelConfig.query_latest_one(query_fields=dict( channel_enum=cls.channel_enum2))
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
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()
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()