def post(self): """ 用户余额调整 :return: """ form, error = UserBalanceEditForm.request_validate() if error: return error.as_response() uid = form.uid.data # 判断该用户id是否存在 user = User.query.filter_by(**dict(id=uid)).first() if not user: return AdjustUserBalanceError(message="系统找不到该用户").as_response() ad_type = BalanceAdjustTypeEnum.PLUS # 当调整状态为 减少时 需要判断 当前用户余额和商户余额值 amount = BalanceKit.round_4down_5up(Decimal(form.amount.data)) if form.adjust_type.data == BalanceAdjustTypeEnum.MINUS: ad_type = BalanceAdjustTypeEnum.MINUS # 获取用户余额 并判断 user_balance_info = UserBalance.query.filter( UserBalance.id == uid).first() if not user_balance_info: return UserBalanceNoFoundError().as_response() user_balance = user_balance_info.real_balance if Decimal(user_balance) - amount < 0: print("用户没有足够的金额", user_balance, amount) return AdjustUserBalanceError( message="用户没有足够的金额").as_response() # 获取商户余额 并判断 merchant_balance_info = MerchantInfo.query.filter_by(**dict( _merchant=user_balance_info.merchant.value)).first() if not merchant_balance_info: return AdjustUserBalanceError(message="该商户信息不存在").as_response() bl_ava = merchant_balance_info.balance_available if Decimal(bl_ava) - amount < 0: print("商户没有足够的余额", bl_ava, amount) return AdjustUserBalanceError( message="商户没有足够的余额").as_response() # 调整用户及商户余额 flag, error = AdjustTransactionCtl.adjust_create( user=user, source=OrderSourceEnum.MANUALLY, amount=amount, order_type=PayTypeEnum.MANUALLY, bl_type=BalanceTypeEnum.AVAILABLE, ad_type=ad_type, comment=form.comment.data) if not flag: return AdjustUserBalanceError(message="余额调整失败").as_response() return ResponseSuccess().as_response()
def construct_request(self, order, params: dict): pay_params = { PayMethodEnum.ZHIFUBAO_H5: dict(channel=7, render_type=SdkRenderType.QR_CODE), PayMethodEnum.WEIXIN_H5: dict(channel=1, render_type=SdkRenderType.QR_CODE) } payment_method = self.channel_enum.conf['payment_method'] request_fields = ['pay_type', 'mch_id', 'order_id', 'channel_id', 'pay_amount', 'name', 'explain', 'remark', 'result_url', 'notify_url', 'client_ip', 'bank_cardtype', 'bank_code', 'is_qrimg', 'is_sdk', 'ts', 'sign', 'ext'] sorted_params = sorted(request_fields) request_body = {} for field in request_fields: if field == "pay_type": request_body[field] = 2 elif field == "mch_id": request_body[field] = int(self.third_config['mch_id']) elif field == "order_id": request_body[field] = order.sys_tx_id elif field == "channel_id": request_body[field] = pay_params[payment_method]['channel'] elif field == "pay_amount": request_body[field] = str(BalanceKit.round_4down_5up(order.amount)) elif field == "name": request_body[field] = "mch name" elif field == "explain": request_body[field] = "explain text" elif field == "remark": request_body[field] = "remark message" elif field == "result_url": request_body[field] = self.third_config['return_url'] elif field == "notify_url": request_body[field] = self.third_config['callback_url'] elif field == "client_ip": request_body[field] = params['client_ip'] elif field == 'is_qrimg': request_body[field] = 0 elif field == 'is_sdk': request_body[field] = 1 elif field == 'ts': request_body[field] = DateTimeKit.get_cur_timestamp() elif field == 'ext': request_body[field] = "ext message" sign_str = "&".join(["{}={}".format(k, request_body[k]) for k in sorted_params if request_body.get(k, False) or k in ["is_qrimg", "is_sdk"]]) # sign_str += self.third_config['secret_key'] print("sign string: ", sign_str) print("request body: ", request_body) render_type = pay_params[payment_method]['render_type'] return request_body, sign_str, render_type
def test_balance_round(self): i = 1 for x in range(0, 10): d = Decimal(self.format_float(x=x, i=i)) v = BalanceKit.round_4down_5up(d) self.assertEqual(d, v) for y in range(0, 10): d = Decimal(self.format_float(x=x, y=y, i=i)) v = BalanceKit.round_4down_5up(d) self.assertEqual(d, v) for z in range(0, 10): # 原始值 d = Decimal(self.format_float(x=x, y=y, z=z, i=i)) # 期望值 if z >= 5: # z进位,y自增 _x = x _y = y + 1 _i = i if _y >= 10: # y进位,x自增 _y = 0 _x += 1 if _x >= 10: # x进位,i自增 _x = 0 _i += 1 e = Decimal(self.format_float(x=_x, y=_y, i=_i)) else: # 舍掉z e = Decimal(self.format_float(x=x, y=y, i=i)) # 实际值 v = BalanceKit.round_4down_5up(d) self.assertEqual(e, v, (x, y, z))
def calc_cost(cls, amount, fee_type: PaymentFeeTypeEnum, fee_value: Decimal): """ 计算成本: 通道收取的手续费 :param amount: :param fee_type: :param fee_value: :return: """ if fee_type == PaymentFeeTypeEnum.PERCENT_PER_ORDER: return BalanceKit.round_4down_5up(amount * fee_value / Decimal(100.0)) return fee_value
def calc_fee(cls, amount: Decimal, fee_type: PaymentFeeTypeEnum, fee_value: Decimal): """ 计算手续费: 我们收取下游商户的手续费 :param amount: :param fee_type: :param fee_value: :return: """ if fee_type == PaymentFeeTypeEnum.PERCENT_PER_ORDER: return BalanceKit.round_4down_5up( Decimal(amount) * fee_value / Decimal(str(100.0))) return fee_value
def post(self): """ 提现接口: 检查支付密码是否正确,如果密码正确则创建用户提现订单 """ form, error = CreateWithdrawOrderForm().request_validate() if error: return error.as_response() uid = g.user.uid merchant = g.user.merchant if not g.user.has_permission(UserPermissionEnum.WITHDRAW): return UserPermissionDeniedError().as_response() amount = BalanceKit.round_4down_5up(Decimal(form.amount.data)) user_bank_id = form.user_bank.data client_ip = form.client_ip.data trade_password = form.trade_password.data # 判断 支付密码是否正确 if not User.verify_payment_password( merchant=merchant, uid=uid, password=trade_password): cache = UserPaymentPasswordLimitCache(uid=uid) cache.incr_times() times = cache.get_left_times() return PaymentPasswordError(message=PaymentPasswordError.message. format(times)).as_response() order, error = WithdrawTransactionCtl.order_create( user=g.user, amount=amount, client_ip=client_ip, user_bank_id=user_bank_id, ) if error: return error.as_response() return ResponseSuccess().as_response()
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_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))