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.
- xync_bot/__main__.py +36 -0
- xync_bot/loader.py +2 -0
- xync_bot/routers/__init__.py +39 -6
- xync_bot/routers/cond/__init__.py +67 -155
- xync_bot/routers/cond/func.py +118 -0
- xync_bot/routers/{main.py → main/handler.py} +53 -86
- xync_bot/routers/pay/cd.py +49 -0
- xync_bot/routers/pay/dep.py +270 -0
- xync_bot/routers/pay/handler.py +247 -0
- xync_bot/routers/pay/window.py +244 -0
- xync_bot/shared.py +19 -0
- xync_bot/store.py +150 -0
- {xync_bot-0.3.7.dev1.dist-info → xync_bot-0.3.24.dist-info}/METADATA +3 -1
- xync_bot-0.3.24.dist-info/RECORD +22 -0
- xync_bot/__init__.py +0 -20
- xync_bot-0.3.7.dev1.dist-info/RECORD +0 -16
- /xync_bot/routers/{cond/cond.py → main/__init__.py} +0 -0
- {xync_bot-0.3.7.dev1.dist-info → xync_bot-0.3.24.dist-info}/WHEEL +0 -0
- {xync_bot-0.3.7.dev1.dist-info → xync_bot-0.3.24.dist-info}/top_level.txt +0 -0
|
@@ -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.
|
|
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"
|