xync-bot 0.3.7.dev1__py3-none-any.whl → 0.3.24__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of xync-bot might be problematic. Click here for more details.

@@ -0,0 +1,247 @@
1
+ from asyncio import gather
2
+ from datetime import datetime
3
+
4
+ from aiogram import Router, F
5
+ from aiogram.filters import Command
6
+ from aiogram.types import Message, CallbackQuery
7
+ from aiogram.fsm.context import FSMContext
8
+ from xync_bot.routers.pay.dep import fill_creds, fill_actors, dlt, ans, Store
9
+ from xync_schema import models
10
+
11
+ from xync_bot.routers.pay import cd, dep, window
12
+
13
+ pr = Router(name="pay")
14
+
15
+
16
+ @pr.message(Command("pay"))
17
+ async def h_start(msg: Message, store: Store):
18
+ """Step 1: Select a target type"""
19
+ store.curr.is_target = True
20
+ await gather(window.type_select(msg, store), dlt(msg))
21
+ store.perm.user = await models.User.get(username_id=msg.from_user.id)
22
+ store.perm.creds, store.perm.cur_creds = await fill_creds(store.perm.user.person_id)
23
+ store.perm.actors = await fill_actors(store.perm.user.person_id)
24
+
25
+
26
+ @pr.callback_query(cd.MoneyType.filter(F.is_fiat))
27
+ async def h_got_fiat_type(query: CallbackQuery, store: Store):
28
+ """Step 2f: Select cur"""
29
+ store.curr.is_fiat = True
30
+ await gather(window.cur_select(query.message, store), ans(query, "Понял, фиат"))
31
+
32
+
33
+ @pr.callback_query(cd.MoneyType.filter(F.is_fiat.__eq__(0)))
34
+ async def h_got_crypto_type(query: CallbackQuery, store: Store):
35
+ """Step 2c: Select coin"""
36
+ store.curr.is_fiat = False
37
+ await gather(window.coin_select(query.message, store), ans(query, "Понял, крипта"))
38
+
39
+
40
+ @pr.callback_query(cd.Coin.filter())
41
+ async def h_got_coin(query: CallbackQuery, callback_data: cd.Coin, store: Store):
42
+ """Step 3c: Select target ex"""
43
+ setattr(store.pay, ("t" if store.curr.is_target else "s") + "_coin_id", callback_data.id)
44
+ await gather(window.ex_select(query.message, store), ans(query, "Эта монета есть на следующих биржах"))
45
+
46
+
47
+ @pr.callback_query(cd.Cur.filter())
48
+ async def h_got_cur(query: CallbackQuery, callback_data: cd.Cur, store: Store):
49
+ """Step 3f: Select target pm"""
50
+ setattr(store.pay, ("t" if store.curr.is_target else "s") + "_cur_id", callback_data.id)
51
+ await gather(window.pm(query.message, store), ans(query, "Вот платежные системы доступные для этой валюты"))
52
+
53
+
54
+ @pr.callback_query(cd.Pm.filter(F.is_target))
55
+ async def h_got_target_pm(query: CallbackQuery, callback_data: cd.Pm, state: FSMContext, store: Store):
56
+ """Step 4f: Fill target cred.detail"""
57
+ store.pay.t_pmcur_id = callback_data.pmcur_id
58
+ await gather(
59
+ window.fill_cred_dtl(query.message, store),
60
+ ans(query, "Теперь нужны реквизиты"),
61
+ state.set_state(dep.CredState.detail),
62
+ )
63
+
64
+
65
+ @pr.callback_query(cd.Cred.filter())
66
+ async def h_got_cred(query: CallbackQuery, callback_data: cd.Cred, state: FSMContext, store: Store):
67
+ store.pay.cred_id = callback_data.id
68
+ await gather(
69
+ window.amount(query.message, store), ans(query, "Теперь нужна сумма"), state.set_state(dep.PaymentState.amount)
70
+ )
71
+
72
+
73
+ @pr.message(dep.CredState.detail)
74
+ async def h_got_cred_dtl(msg: Message, state: FSMContext, store: Store):
75
+ """Step 4.1f: Fill target cred.name"""
76
+ store.pay.cred_dtl = msg.text
77
+ await gather(window.fill_cred_name(msg, store), dlt(msg), state.set_state(dep.CredState.name))
78
+
79
+
80
+ @pr.message(dep.CredState.name)
81
+ async def h_got_cred_name(msg: Message, state: FSMContext, store: Store):
82
+ """Step 5f: Save target cred"""
83
+ cred, _ = await models.Cred.update_or_create(
84
+ {"name": msg.text},
85
+ detail=store.pay.cred_dtl,
86
+ person_id=store.perm.user.person_id,
87
+ pmcur_id=store.pay.t_pmcur_id,
88
+ )
89
+ store.pay.cred_id = cred.id
90
+ store.perm.creds[cred.id] = cred
91
+ await gather(window.amount(msg, store), dlt(msg), state.set_state(dep.PaymentState.amount))
92
+
93
+
94
+ @pr.callback_query(cd.Ex.filter())
95
+ async def h_got_ex(query: CallbackQuery, callback_data: cd.Ex, state: FSMContext, store: Store):
96
+ """Step 4c: Save target"""
97
+ ist = store.curr.is_target
98
+ setattr(store.pay, ("t" if ist else "s") + "_ex_id", callback_data.id)
99
+ if ist:
100
+ await window.amount(query.message, store)
101
+ actor_id = store.perm.actors[store.pay.t_ex_id]
102
+ addr = await models.Addr.get(coin_id=store.pay.t_coin_id, actor_id=actor_id)
103
+ store.pay.addr_id = addr.id
104
+ else:
105
+ await window.set_ppo(query.message, store)
106
+ await ans(query, f"Биржа {store.glob.exs[callback_data.id]} выбрана")
107
+ await state.set_state(dep.PaymentState.amount)
108
+
109
+
110
+ @pr.message(dep.PaymentState.amount)
111
+ async def h_got_amount(msg: Message, state: FSMContext, store: Store):
112
+ """Step 6: Save a target amount"""
113
+ if not msg.text.isnumeric():
114
+ store.curr.msg_to_del = await msg.answer("Пожалуйста, введите корректное число")
115
+ return
116
+ if store.curr.msg_to_del:
117
+ await store.curr.msg_to_del.delete()
118
+ store.pay.amount = float(msg.text)
119
+ """Step 7: Select source type"""
120
+ store.curr.is_target = False
121
+ await gather((window.type_select if store.curr.is_fiat else window.cur_select)(msg, store), dlt(msg), state.clear())
122
+
123
+
124
+ @pr.callback_query(cd.Pm.filter(F.is_target.__eq__(0)))
125
+ async def h_got_source_pm(query: CallbackQuery, callback_data: cd.Pm, store: Store):
126
+ store.pay.s_pmcur_id = callback_data.pmcur_id
127
+ await gather(
128
+ window.set_ppo(query.message, store),
129
+ ans(query, store.glob.pmcurs[callback_data.pmcur_id]),
130
+ )
131
+
132
+
133
+ @pr.callback_query(cd.Ppo.filter())
134
+ async def h_got_ppo(query: CallbackQuery, callback_data: cd.Ppo, store: Store):
135
+ store.pay.ppo = callback_data.num
136
+ await gather(window.set_urgency(query.message, store), ans(query, str(callback_data.num)))
137
+
138
+
139
+ @pr.callback_query(cd.Time.filter())
140
+ async def h_got_urgency(query: CallbackQuery, callback_data: cd.Time, store: Store):
141
+ store.pay.urg = callback_data.minutes
142
+ await window.create_payreq(query.message, store)
143
+ await ans(query, f"Ok {callback_data.minutes} min.")
144
+
145
+
146
+ # ACTIONS
147
+ @pr.callback_query(cd.Action.filter(F.act.__eq__(cd.ActionType.received)))
148
+ async def payment_confirmed(query: CallbackQuery, state: FSMContext):
149
+ await ans(query, None)
150
+ payed_at = datetime.now()
151
+ await state.update_data(timer_active=False, payed_at_formatted=payed_at)
152
+ data = await state.get_data()
153
+ if data.get("pay_req_id"):
154
+ pay_req = await models.PayReq.get(id=data["pay_req_id"])
155
+ pay_req.payed_at = payed_at
156
+ await pay_req.save()
157
+ await state.clear()
158
+ await window.success(query.message)
159
+
160
+
161
+ @pr.callback_query(cd.Action.filter(F.act.__eq__(cd.ActionType.not_received)))
162
+ async def no_payment(query: CallbackQuery, state: FSMContext):
163
+ await ans(query, None)
164
+ await state.update_data(timer_active=False)
165
+ await query.message.edit_text("Платеж не получен!")
166
+ await query.message.answer("укажите детали платежа")
167
+ await state.clear()
168
+ await state.set_state(dep.Report.text)
169
+
170
+
171
+ @pr.message(dep.Report.text)
172
+ async def payment_not_specified(msg: Message, state: FSMContext):
173
+ await state.update_data(text=msg.text)
174
+ data = await state.get_data()
175
+ complaint_text = (
176
+ f"Жалоба на неполученный платеж:\n"
177
+ f"Пользователь: @{msg.from_user.username or msg.from_user.id}\n"
178
+ f"Детали платежа: {data['text']}\n"
179
+ f"Время: {msg.date.strftime('%Y-%m-%d %H:%M:%S')}"
180
+ )
181
+ await msg.bot.send_message(chat_id="xyncpay", text=complaint_text)
182
+
183
+
184
+ # NAVIGATION
185
+ @pr.callback_query(cd.PayNav.filter(F.to.in_([cd.PayStep.t_type, cd.PayStep.s_type])))
186
+ async def handle_home(query: CallbackQuery, state: FSMContext, store: Store):
187
+ await gather(window.type_select(query.message, store), state.clear(), ans(query, "Создаем платеж заново"))
188
+
189
+
190
+ @pr.callback_query(cd.PayNav.filter(F.to.in_([cd.PayStep.t_coin, cd.PayStep.s_coin])))
191
+ async def to_coin_select(query: CallbackQuery, state: FSMContext, store: Store):
192
+ await ans(query, None)
193
+ is_target = await state.get_value("is_target")
194
+ pref = "t" if is_target else "s"
195
+ await state.update_data({pref + "_ex_id": None, pref + "_coin_id": None})
196
+ await window.coin_select(query.message, store)
197
+
198
+
199
+ @pr.callback_query(cd.PayNav.filter(F.to.in_([cd.PayStep.t_cur, cd.PayStep.s_cur])))
200
+ async def to_cur_select(query: CallbackQuery, state: FSMContext, store: Store):
201
+ await ans(query, None)
202
+ is_target = await state.get_value("is_target")
203
+ pref = "t" if is_target else "s"
204
+ await state.update_data({pref + "_pmcur_id": None, pref + "_cur_id": None})
205
+ await window.cur_select(query.message, store)
206
+
207
+
208
+ @pr.callback_query(cd.PayNav.filter(F.to.in_([cd.PayStep.t_pm, cd.PayStep.s_pm])))
209
+ async def to_pm_select(query: CallbackQuery, store: Store):
210
+ await ans(query, None)
211
+ await window.pm(query.message, store)
212
+
213
+
214
+ @pr.callback_query(cd.PayNav.filter(F.to.__eq__(cd.PayStep.t_cred_dtl)))
215
+ async def back_to_cred_detail(query: CallbackQuery, state: FSMContext, store: Store):
216
+ await ans(query, None)
217
+ await state.update_data(detail=None)
218
+ await window.fill_cred_dtl(query.message, store)
219
+
220
+
221
+ @pr.callback_query(cd.PayNav.filter(F.to.__eq__(cd.PayStep.t_cred_name)))
222
+ async def back_to_cred_name(query: CallbackQuery, state: FSMContext, store: Store):
223
+ await ans(query, None)
224
+ await state.update_data(name=None)
225
+ await window.fill_cred_name(query.message, store)
226
+
227
+
228
+ @pr.callback_query(cd.PayNav.filter(F.to.in_([cd.PayStep.t_ex, cd.PayStep.s_ex])))
229
+ async def back_to_ex_select(query: CallbackQuery, state: FSMContext, store: Store):
230
+ await ans(query, None)
231
+ await state.update_data({("t" if await state.get_value("is_target") else "s") + "ex_id": None})
232
+ await window.ex_select(query.message, store)
233
+
234
+
235
+ @pr.callback_query(cd.PayNav.filter(F.to.__eq__(cd.PayStep.t_amount)))
236
+ async def back_to_amount(query: CallbackQuery, state: FSMContext, store: Store):
237
+ await ans(query, None)
238
+ await state.update_data(amount=None)
239
+ await window.amount(query.message, store)
240
+ await state.set_state(dep.PaymentState.amount)
241
+
242
+
243
+ @pr.callback_query(cd.PayNav.filter(F.to.in_([cd.PayStep.t_pm])))
244
+ async def back_to_payment(query: CallbackQuery, state: FSMContext, store: Store):
245
+ await ans(query, None)
246
+ await state.update_data(payment=None)
247
+ await window.pm(query.message, store)
@@ -0,0 +1,244 @@
1
+ from asyncio import sleep
2
+ from datetime import datetime, timedelta
3
+
4
+ from aiogram.fsm.context import FSMContext
5
+ from aiogram.types import Message, InlineKeyboardMarkup, InlineKeyboardButton
6
+ from aiogram.utils.keyboard import InlineKeyboardBuilder
7
+ from xync_schema import models
8
+
9
+ from xync_bot.routers.pay.dep import edt, Store, fmt_sec
10
+ from xync_bot.routers.pay import cd, dep
11
+
12
+
13
+ async def type_select(msg: Message, store: Store):
14
+ """Step 1: Select type"""
15
+ ist: bool = store.curr.is_target
16
+ rm = InlineKeyboardMarkup(
17
+ inline_keyboard=[
18
+ [
19
+ InlineKeyboardButton(
20
+ text="Банковская валюта", callback_data=cd.MoneyType(is_fiat=1, is_target=ist).pack()
21
+ ),
22
+ InlineKeyboardButton(
23
+ text="Крипта", callback_data=cd.MoneyType(is_fiat=0, is_target=store.curr.is_target).pack()
24
+ ),
25
+ ]
26
+ ]
27
+ )
28
+ if store.curr.is_target:
29
+ txt = "Что нужно?"
30
+ else:
31
+ if store.pay.t_coin_id:
32
+ inf = f"{store.glob.coins[store.pay.t_coin_id]} на {store.glob.exs[store.pay.t_ex_id]}:{store.pay.addr_id}"
33
+ else:
34
+ cur = store.glob.curs[store.pay.t_cur_id].ticker
35
+ cred: models.Cred = store.perm.creds[store.pay.cred_id]
36
+ inf = f"{cur} на {store.glob.pmcurs[store.pay.t_pmcur_id]}: {cred.repr()}"
37
+ txt = f"Нужен платеж: {store.pay.amount} {inf}\nЧем будете платить?"
38
+ if store.perm.msg_id:
39
+ await edt(msg, txt, rm)
40
+ else:
41
+ msg = await msg.answer(txt, reply_markup=rm)
42
+ store.perm.msg_id = msg.message_id
43
+
44
+
45
+ async def cur_select(msg: Message, store: Store):
46
+ """Common using cur func"""
47
+ builder = InlineKeyboardBuilder()
48
+ ist: bool = store.curr.is_target
49
+ for cur_id, cur in store.glob.curs.items():
50
+ builder.button(text=cur.ticker + dep.flags[cur.ticker], callback_data=cd.Cur(id=cur_id, is_target=ist))
51
+ builder.button(text="Назад к выбору типа", callback_data=cd.PayNav(to=cd.PayStep.t_type))
52
+ builder.adjust(3, 3, 3, 3, 3, 1)
53
+ sfx = "ую нужно" if ist else "ой платишь"
54
+ await edt(msg, "Выбери валюту котор" + sfx, builder.as_markup())
55
+
56
+
57
+ async def coin_select(msg: Message, store: Store):
58
+ """Common using coin func"""
59
+ builder = InlineKeyboardBuilder()
60
+ for coin_id, ticker in store.glob.coins.items():
61
+ builder.button(text=ticker, callback_data=cd.Coin(id=coin_id, is_target=store.curr.is_target))
62
+ builder.button(
63
+ text="Назад к выбору типа",
64
+ callback_data=cd.PayNav(to=cd.PayStep.t_type if store.curr.is_target else cd.PayStep.s_type),
65
+ )
66
+ builder.adjust(1)
67
+ sfx = "ую нужно" if store.curr.is_target else "ой платишь"
68
+ await msg.edit_text("Выберите монету котор" + sfx, reply_markup=builder.as_markup())
69
+
70
+
71
+ async def ex_select(msg: Message, store: Store):
72
+ ist = store.curr.is_target
73
+ coin_id = getattr(store.pay, ("t" if ist else "s") + "_coin_id")
74
+ builder = InlineKeyboardBuilder()
75
+ for ex_id in store.glob.coinexs[coin_id]:
76
+ builder.button(text=store.glob.exs[ex_id], callback_data=cd.Ex(id=ex_id, is_target=ist))
77
+ builder.button(
78
+ text="Назад к выбору монеты", callback_data=cd.PayNav(to=cd.PayStep.t_coin if ist else cd.PayStep.s_coin)
79
+ )
80
+ builder.button(text="Домой", callback_data=cd.PayNav(to=cd.PayStep.t_type))
81
+ builder.adjust(1)
82
+ keyboard = builder.as_markup()
83
+ await msg.edit_text("На какую биржу?" if ist else "С какой биржи?", reply_markup=keyboard)
84
+
85
+
86
+ async def pm(msg: Message, store: Store):
87
+ ist = store.curr.is_target
88
+ cur_id = getattr(store.pay, ("t" if ist else "s") + "_cur_id")
89
+ builder = InlineKeyboardBuilder()
90
+ for pmcur_id in store.glob.curpms[cur_id]:
91
+ builder.button(text=store.glob.pmcurs[pmcur_id], callback_data=cd.Pm(pmcur_id=pmcur_id, is_target=ist))
92
+ builder.button(
93
+ text="Назад к выбору валюты", callback_data=cd.PayNav(to=cd.PayStep.t_cur if ist else cd.PayStep.s_cur)
94
+ )
95
+ builder.button(text="Домой", callback_data=cd.PayNav(to=cd.PayStep.t_type))
96
+ builder.adjust(1)
97
+ keyboard = builder.as_markup()
98
+ await msg.edit_text("На какую платежную систему?" if ist else "C какой платежной системы?", reply_markup=keyboard)
99
+
100
+
101
+ async def fill_cred_dtl(msg: Message, store: Store):
102
+ builder = InlineKeyboardBuilder()
103
+ txt = "В"
104
+ if cred_ids := store.perm.cur_creds.get(store.pay.t_pmcur_id):
105
+ for cred_id in cred_ids:
106
+ cred = store.perm.creds[cred_id]
107
+ builder.button(text=cred.repr(), callback_data=cd.Cred(id=cred_id))
108
+ txt = "Выберите реквизиты куда нужно получить деньги, если в списке нет нужных, то\nв"
109
+
110
+ builder.button(text="Назад к выбору платежной системы", callback_data=cd.PayNav(to=cd.PayStep.t_pm))
111
+ builder.button(text="Домой", callback_data=cd.PayNav(to=cd.PayStep.t_type))
112
+ builder.adjust(2)
113
+
114
+ await msg.edit_text(
115
+ f"{txt}ведите номер для {store.glob.pmcurs[store.pay.t_pmcur_id]}:", reply_markup=builder.as_markup()
116
+ )
117
+
118
+
119
+ async def fill_cred_name(msg: Message, store: Store):
120
+ builder = InlineKeyboardBuilder()
121
+ builder.button(text="Назад к вводу реквизитов", callback_data=cd.PayNav(to=cd.PayStep.t_cred_dtl))
122
+ builder.button(text="Домой", callback_data=cd.PayNav(to=cd.PayStep.t_type))
123
+ builder.adjust(2)
124
+ cur = store.glob.curs[store.pay.t_cur_id]
125
+ payment = store.glob.pmcurs[store.pay.t_pmcur_id]
126
+ detail = store.pay.cred_dtl
127
+ await edt(msg, f"{cur.ticker}:{payment}:{detail}: Введите имя получателя", builder.as_markup())
128
+
129
+
130
+ async def amount(msg: Message, store: Store):
131
+ """Step 5: Filling target amount"""
132
+ builder = InlineKeyboardBuilder()
133
+ if store.curr.is_fiat:
134
+ cur_coin = store.glob.curs[store.pay.t_cur_id].ticker
135
+ builder.button(text="Назад к вводу имени", callback_data=cd.PayNav(to=cd.PayStep.t_cred_name))
136
+ t_name = store.glob.pmcurs[store.pay.t_pmcur_id]
137
+ else:
138
+ cur_coin = store.glob.coins[store.pay.t_coin_id]
139
+ builder.button(text="Назад к выбору биржи", callback_data=cd.PayNav(to=cd.PayStep.t_ex))
140
+ t_name = store.glob.exs[store.pay.t_ex_id]
141
+
142
+ builder.button(text="Домой", callback_data=cd.PayNav(to=cd.PayStep.t_type))
143
+ builder.adjust(2)
144
+
145
+ await edt(msg, f"Введите нужную сумму {cur_coin} для {t_name}", builder.as_markup())
146
+
147
+
148
+ async def set_ppo(msg: Message, store: Store):
149
+ ist = store.curr.is_target
150
+ if nppo := await store.need_ppo():
151
+ builder = InlineKeyboardBuilder()
152
+ builder.button(text="Нет", callback_data=cd.Ppo(num=1, is_target=ist)).button(
153
+ text="Да", callback_data=cd.Ppo(num=2, is_target=ist)
154
+ )
155
+ if nppo > 1:
156
+ builder.button(text="Да хоть 3мя", callback_data=cd.Ppo(num=3, is_target=ist))
157
+ builder.adjust(2)
158
+ await edt(msg, f"2мя платежами сможете {'принять' if ist else 'отравить'}?", builder.as_markup())
159
+ elif ist:
160
+ store.curr.is_target = False
161
+ await type_select(msg)
162
+ else:
163
+ await set_urgency(msg)
164
+
165
+
166
+ async def set_urgency(msg: Message, store: Store):
167
+ if not store.curr.is_fiat or await store.client_have_coin_amount():
168
+ return await create_payreq(msg) # next
169
+ builder = InlineKeyboardBuilder()
170
+ (
171
+ builder.button(text="1 мин", callback_data=cd.Time(minutes=1))
172
+ .button(text="5 мин", callback_data=cd.Time(minutes=5))
173
+ .button(text="30 мин", callback_data=cd.Time(minutes=30))
174
+ .button(text="3 часа", callback_data=cd.Time(minutes=180))
175
+ .button(text="сутки", callback_data=cd.Time(minutes=60 * 24))
176
+ .button(text="Назад к выбору платежной\nсистемы для отправки", callback_data=cd.PayNav(to=cd.PayStep.s_pm))
177
+ .button(text="Домой", callback_data=cd.PayNav(to=cd.PayStep.t_type))
178
+ .adjust(2, 2, 1, 1, 1)
179
+ )
180
+ return await edt(msg, "Сколько можешь ждать?", builder.as_markup())
181
+
182
+
183
+ async def create_payreq(msg: Message, store: Store):
184
+ pay_req, _ = await models.PayReq.update_or_create(
185
+ {"pay_until": datetime.now() + timedelta(minutes=store.pay.urg)},
186
+ amount=store.pay.amount,
187
+ parts=store.pay.ppo,
188
+ addr_id=store.pay.addr_id,
189
+ cred_id=store.pay.cred_id,
190
+ user=store.perm.user,
191
+ )
192
+ store.pay.pr_id = pay_req.id
193
+ inp, txt = await store.get_merch_target()
194
+ ccred, ctxt = await store.client_target_repr()
195
+ txt += f"\nИ получите {store.pay.amount} {ctxt} в течение {fmt_sec(store.pay.urg * 60)}"
196
+ if store.pay.ppo > 1:
197
+ txt += f" максимум {store.pay.ppo} платежами"
198
+ rm = InlineKeyboardMarkup(
199
+ inline_keyboard=[
200
+ [InlineKeyboardButton(text="Отправил", callback_data=cd.Action(act=cd.ActionType.sent).pack())],
201
+ ]
202
+ )
203
+ await edt(msg, f"Отправьте {100500} " + txt, rm) # todo: get rate
204
+
205
+ # create_task(window.run_timer(msg))В
206
+
207
+
208
+ async def run_timer(message, state: FSMContext):
209
+ builder = InlineKeyboardBuilder()
210
+ builder.button(text="Платеж получен", callback_data=cd.Action(act=cd.ActionType.received))
211
+
212
+ seconds = await state.get_value("timer") * 60
213
+
214
+ try:
215
+ await message.edit_text(f"⏳ Осталось {fmt_sec(seconds)}", reply_markup=builder.as_markup())
216
+ except Exception:
217
+ return
218
+
219
+ while seconds > 0:
220
+ await sleep(1)
221
+ seconds -= 1
222
+ try:
223
+ await message.edit_text(f"⏳ Осталось {fmt_sec(seconds)}", reply_markup=builder.as_markup())
224
+ await state.update_data(timer=seconds)
225
+ except Exception:
226
+ break
227
+
228
+ if seconds <= 0:
229
+ builder = InlineKeyboardBuilder()
230
+ builder.button(text="Платеж получен", callback_data=cd.Action(act=cd.ActionType.received))
231
+ builder.button(text="Денег нет", callback_data=cd.Action(act=cd.ActionType.not_received))
232
+ try:
233
+ await message.edit_text("⏳ Время вышло!", reply_markup=builder.as_markup())
234
+ except Exception:
235
+ pass
236
+
237
+
238
+ async def success(msg: Message):
239
+ rm = InlineKeyboardMarkup(
240
+ inline_keyboard=[
241
+ [InlineKeyboardButton(text="Новый платеж💸", callback_data=cd.PayNav(to=cd.PayStep.t_type).pack())]
242
+ ]
243
+ )
244
+ await msg.edit_text("✅ Платеж успешно подтвержден", reply_markup=rm)
xync_bot/shared.py CHANGED
@@ -3,3 +3,22 @@ from aiogram.filters.callback_data import CallbackData
3
3
 
4
4
  class NavCallbackData(CallbackData, prefix="nav"): # navigate menu
5
5
  to: str
6
+
7
+
8
+ flags = {
9
+ "RUB": "🇷🇺",
10
+ "THB": "🇹🇭",
11
+ "IDR": "🇮🇩",
12
+ "TRY": "🇹🇷",
13
+ "GEL": "🇬🇪",
14
+ "VND": "🇻🇳",
15
+ "AED": "🇦🇪",
16
+ "AMD": "🇦🇲",
17
+ "AZN": "🇦🇿",
18
+ "CNY": "🇨🇳",
19
+ "EUR": "🇪🇺",
20
+ "HKD": "🇭🇰",
21
+ "INR": "🇮🇳",
22
+ "PHP": "🇵🇭",
23
+ "USD": "🇺🇸",
24
+ }
xync_bot/store.py ADDED
@@ -0,0 +1,150 @@
1
+ import logging
2
+ from aiogram.types import Message
3
+ from tortoise.functions import Min
4
+ from x_model.func import ArrayAgg
5
+ from x_auth.enums import Role
6
+ from xync_schema.models import Addr, Asset, Cred, Coin, Pmcur, Cur, User, Ex, Pmex
7
+
8
+ from xync_bot.shared import flags
9
+
10
+
11
+ class SingleStore(type):
12
+ _store = None
13
+
14
+ async def __call__(cls):
15
+ if not cls._store:
16
+ cls._store = super(SingleStore, cls).__call__()
17
+ cls._store.coins = {k: v for k, v in await Coin.all().order_by("ticker").values_list("id", "ticker")}
18
+ curs = {c.id: c for c in await Cur.filter(ticker__in=flags.keys()).order_by("ticker")}
19
+ cls._store.curs = curs
20
+ cls._store.exs = {k: v for k, v in await Ex.all().values_list("id", "name")}
21
+ cls._store.pmcurs = {
22
+ k: v
23
+ for k, v in await Pmex.filter(pm__pmcurs__cur_id__in=cls._store.curs.keys())
24
+ .annotate(sname=Min("name"))
25
+ .group_by("pm__pmcurs__id")
26
+ .values_list("pm__pmcurs__id", "sname")
27
+ }
28
+ cls._store.coinexs = {
29
+ c.id: [ex.ex_id for ex in c.coinexs] for c in await Coin.all().prefetch_related("coinexs")
30
+ }
31
+ cls._store.curpms = {
32
+ cur_id: ids
33
+ for cur_id, ids in await Pmcur.filter(cur_id__in=curs.keys())
34
+ .annotate(ids=ArrayAgg("id"))
35
+ .group_by("cur_id")
36
+ .values_list("cur_id", "ids")
37
+ }
38
+ cls._store.curpms = {
39
+ cur_id: ids
40
+ for cur_id, ids in await Pmcur.filter(cur_id__in=curs.keys())
41
+ .annotate(ids=ArrayAgg("id"))
42
+ .group_by("cur_id")
43
+ .values_list("cur_id", "ids")
44
+ }
45
+
46
+ return cls._store
47
+
48
+
49
+ class Store:
50
+ class Global(metaclass=SingleStore):
51
+ coins: dict[int, str] # id:ticker
52
+ curs: dict[int, Cur] # id:Cur
53
+ exs: dict[int, str] # id:name
54
+ coinexs: dict[int, list[int]] # id:[ex_ids]
55
+ pmcurs: dict[int, str] # pmcur_id:name
56
+ curpms: dict[int, list[int]] # id:[pmcur_ids]
57
+
58
+ class Permanent:
59
+ msg_id: int = None
60
+ user: User = None
61
+ actors: dict[int, int] = None # key=ex_id
62
+ creds: dict[int, Cred] = None # key=cred_id
63
+ cur_creds: dict[int, list[int]] = None # pmcur_id:[cred_ids]
64
+
65
+ class Current:
66
+ is_target: bool = True
67
+ is_fiat: bool = None
68
+ msg_to_del: Message = None
69
+
70
+ class Payment:
71
+ t_cur_id: int = None
72
+ s_cur_id: int = None
73
+ t_coin_id: int = None
74
+ s_coin_id: int = None
75
+ t_pmcur_id: int = None
76
+ s_pmcur_id: int = None
77
+ t_ex_id: int = None
78
+ s_ex_id: int = None
79
+ amount: int | float = None
80
+ ppo: int = 1
81
+ addr_id: int = None
82
+ cred_dtl: str = None
83
+ cred_id: int = None
84
+ urg: int = 5
85
+ pr_id: int = None
86
+
87
+ glob: Global
88
+ perm: Permanent = Permanent()
89
+ pay: Payment = Payment()
90
+ curr: Current = Current()
91
+
92
+ async def xync_have_coin_amount(self) -> bool:
93
+ assets = await Asset.filter(
94
+ addr__coin_id=self.pay.t_coin_id, addr__ex_id=self.pay.t_ex_id, addr__actor__user__role__in=Role.ADMIN
95
+ )
96
+ return self.pay.amount <= sum(a.free for a in assets)
97
+
98
+ async def client_have_coin_amount(self) -> bool:
99
+ assets = await Asset.filter(addr__coin_id=self.pay.t_coin_id, addr__actor_id__in=self.perm.actors.values())
100
+ return self.pay.amount <= sum(a.free for a in assets)
101
+
102
+ async def need_ppo(self):
103
+ cur_id = getattr(self.pay, ("t" if self.curr.is_target else "s") + "_cur_id")
104
+ usd_amount = self.pay.amount * self.glob.curs[cur_id].rate
105
+ if usd_amount < 50:
106
+ return 0
107
+ elif usd_amount > 100:
108
+ return 2
109
+ else:
110
+ return 1
111
+
112
+ async def client_target_repr(self) -> tuple[Addr | Cred, str]:
113
+ if self.pay.t_ex_id:
114
+ addr_to = (
115
+ await Addr.filter(actor__ex_id=self.pay.t_ex_id, coin_id=self.pay.t_coin_id, actor__user=self.perm.user)
116
+ .prefetch_related("actor")
117
+ .first()
118
+ )
119
+ ex, coin = self.glob.exs[self.pay.s_ex_id], self.glob.coins[self.pay.s_coin_id]
120
+ if not addr_to:
121
+ logging.error(f"No {coin} addr in {ex} for user: {self.perm.user.username_id}")
122
+ return addr_to, f"{coin} на {ex} по id: `{addr_to.actor.exid}`"
123
+ # иначе: реквизиты для фиата
124
+ cur, pm = self.glob.curs[self.pay.t_cur_id], self.glob.pmcurs[self.pay.t_pmcur_id]
125
+ cred = self.perm.creds[self.pay.cred_id]
126
+ return cred, f"{cur.ticker} на {pm} по номеру: {cred.repr()}"
127
+
128
+ async def get_merch_target(self) -> tuple[Addr | Cred, str]:
129
+ if self.pay.s_ex_id:
130
+ addr_in = (
131
+ await Addr.filter(
132
+ actor__ex_id=self.pay.s_ex_id, coin_id=self.pay.s_coin_id, actor__user__role__gte=Role.ADMIN
133
+ )
134
+ .prefetch_related("actor")
135
+ .first()
136
+ )
137
+ ex, coin = self.glob.exs[self.pay.s_ex_id], self.glob.coins[self.pay.s_coin_id]
138
+ if not addr_in:
139
+ logging.error(f"No {coin} addr in {ex}")
140
+ return addr_in, f"{coin} на {ex} по id: `{addr_in.actor.exid}`"
141
+ # иначе: реквизиты для фиатной оплаты
142
+ s_pmcur = await Pmcur.get(id=self.pay.s_pmcur_id).prefetch_related("pm__grp")
143
+ cred = await Cred.filter(
144
+ **({"pmcur__pm__grp": s_pmcur.pm.grp} if s_pmcur.pm.grp else {"pmcur_id": self.pay.s_pmcur_id}),
145
+ person__user__role__gte=Role.ADMIN,
146
+ ).first() # todo: order by fiat.target-fiat.amount
147
+ cur, pm = self.glob.curs[self.pay.s_cur_id], self.glob.pmcurs[self.pay.s_pmcur_id]
148
+ if not cred:
149
+ logging.error(f"No {cur.ticker} cred for {pm}")
150
+ return cred, f"{cur.ticker} на {pm} по номеру: {cred.repr()}"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: xync-bot
3
- Version: 0.3.7.dev1
3
+ Version: 0.3.24
4
4
  Summary: Telegram bot with web app for xync net
5
5
  Author-email: Artemiev <mixartemev@gmail.com>
6
6
  License-Expression: GPL-3.0-or-later
@@ -8,6 +8,8 @@ Project-URL: Homepage, https://gitlab.com/xync/back/tg-bot
8
8
  Project-URL: Repository, https://gitlab.com/xync/back/tg-bot
9
9
  Keywords: aiogram,cyrtranslit,xync-net
10
10
  Requires-Python: >=3.12
11
+ Requires-Dist: cyrtranslit
12
+ Requires-Dist: xn-auth
11
13
  Requires-Dist: xync-schema
12
14
  Provides-Extra: dev
13
15
  Requires-Dist: PGram; extra == "dev"