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
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
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
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
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
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)
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)
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()
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="深圳市", )
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)
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()
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)
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'], ))
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'], ))
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()
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()
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
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
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
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
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()
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
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()
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 __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)
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, ''