From f00c9fac4fa467b9d5cf602bb6358195f81f0ade Mon Sep 17 00:00:00 2001 From: Maxi Vila Date: Mon, 20 Apr 2026 03:41:29 -0300 Subject: [PATCH 01/14] feat: add payment methods configuration to community model and wizard flow --- bot/middleware/stage.ts | 1 + bot/modules/community/commands.ts | 6 ++ bot/modules/community/communityContext.ts | 1 + bot/modules/community/index.ts | 7 ++ bot/modules/community/messages.ts | 6 ++ bot/modules/community/scenes.ts | 47 ++++++++++ bot/modules/orders/scenes.ts | 104 +++++++++++++++++++--- locales/de.yaml | 7 ++ locales/en.yaml | 7 ++ locales/es.yaml | 7 ++ locales/fa.yaml | 7 ++ locales/fr.yaml | 7 ++ locales/it.yaml | 7 ++ locales/ko.yaml | 7 ++ locales/pt.yaml | 7 ++ locales/ru.yaml | 7 ++ locales/uk.yaml | 7 ++ models/community.ts | 5 ++ 18 files changed, 236 insertions(+), 11 deletions(-) diff --git a/bot/middleware/stage.ts b/bot/middleware/stage.ts index 424eda3a..96bd2ebd 100644 --- a/bot/middleware/stage.ts +++ b/bot/middleware/stage.ts @@ -23,6 +23,7 @@ export const stageMiddleware = () => { CommunityModule.Scenes.updateFeeCommunityWizard, CommunityModule.Scenes.updateDisputeChannelCommunityWizard, CommunityModule.Scenes.updateLanguageCommunityWizard, + CommunityModule.Scenes.updatePaymentMethodsCommunityWizard, CommunityModule.Scenes.addEarningsInvoiceWizard, addInvoicePHIWizard, OrdersModule.Scenes.createOrder, diff --git a/bot/modules/community/commands.ts b/bot/modules/community/commands.ts index 1170cbd1..78e60525 100644 --- a/bot/modules/community/commands.ts +++ b/bot/modules/community/commands.ts @@ -206,6 +206,12 @@ export const updateCommunity = async ( user, community, }); + } else if (field === 'payment_methods') { + ctx.scene.enter('UPDATE_PAYMENT_METHODS_COMMUNITY_WIZARD_SCENE_ID', { + id, + user, + community, + }); } } catch (error) { logger.error(error); diff --git a/bot/modules/community/communityContext.ts b/bot/modules/community/communityContext.ts index f2c15303..ae8f213d 100644 --- a/bot/modules/community/communityContext.ts +++ b/bot/modules/community/communityContext.ts @@ -45,6 +45,7 @@ export interface CommunityWizardState { seller: UserDocument; type: string; method: string; + selectedMethods: string[]; bot: CommunityContext; message: Message.TextMessage | undefined; error?: any; diff --git a/bot/modules/community/index.ts b/bot/modules/community/index.ts index 9b9eacf9..e9ac6da4 100644 --- a/bot/modules/community/index.ts +++ b/bot/modules/community/index.ts @@ -57,6 +57,13 @@ export const configure = (bot: Telegraf) => { bot.action(/^editLanguageBtn_([0-9a-f]{24})$/, userMiddleware, async ctx => { await commands.updateCommunity(ctx, ctx.match[1], 'language'); }); + bot.action( + /^editPaymentMethodsBtn_([0-9a-f]{24})$/, + userMiddleware, + async ctx => { + await commands.updateCommunity(ctx, ctx.match[1], 'payment_methods'); + }, + ); bot.command('findcomms', userMiddleware, commands.findCommunity); bot.action( diff --git a/bot/modules/community/messages.ts b/bot/modules/community/messages.ts index 37be5e97..94b2c8d6 100644 --- a/bot/modules/community/messages.ts +++ b/bot/modules/community/messages.ts @@ -100,6 +100,12 @@ export const updateCommunityMessage = async (ctx: MainContext) => { callback_data: `editLanguageBtn_${id}`, }, ], + [ + { + text: '✏️ ' + ctx.i18n.t('community_payment_methods'), + callback_data: `editPaymentMethodsBtn_${id}`, + }, + ], [ { text: '💰 ' + ctx.i18n.t('earnings'), diff --git a/bot/modules/community/scenes.ts b/bot/modules/community/scenes.ts index b320a13d..984ef10c 100644 --- a/bot/modules/community/scenes.ts +++ b/bot/modules/community/scenes.ts @@ -916,6 +916,53 @@ export const updateLanguageCommunityWizard = new Scenes.WizardScene( }, ); +export const updatePaymentMethodsCommunityWizard = new Scenes.WizardScene( + 'UPDATE_PAYMENT_METHODS_COMMUNITY_WIZARD_SCENE_ID', + async (ctx: CommunityContext) => { + try { + const { community } = ctx.wizard.state; + const current = community.payment_methods?.join(', ') || ''; + let message = current + ? ctx.i18n.t('current_payment_methods', { methods: current }) + '\n\n' + : ''; + message += ctx.i18n.t('enter_community_payment_methods') + '\n\n'; + message += ctx.i18n.t('wizard_to_exit'); + await ctx.reply(message); + return ctx.wizard.next(); + } catch (error) { + logger.error(error); + ctx.scene.leave(); + } + }, + async (ctx: CommunityContext) => { + try { + if (ctx.message === undefined) return ctx.scene.leave(); + if (!('text' in ctx.message)) return; + + const text = ctx.message.text.trim(); + const methods = text + .split(',') + .map(m => m.trim()) + .filter(m => m.length > 0); + + const max = 20; + if (methods.length > max) { + return await ctx.reply(ctx.i18n.t('max_allowed', { max })); + } + + const { community } = ctx.wizard.state; + community.payment_methods = methods; + await community.save(); + await ctx.reply(ctx.i18n.t('payment_methods_saved')); + + return ctx.scene.leave(); + } catch (error) { + logger.error(error); + ctx.scene.leave(); + } + }, +); + export const addEarningsInvoiceWizard = new Scenes.WizardScene( 'ADD_EARNINGS_INVOICE_WIZARD_SCENE_ID', async (ctx: CommunityContext) => { diff --git a/bot/modules/orders/scenes.ts b/bot/modules/orders/scenes.ts index e7db533e..a4e7ce12 100644 --- a/bot/modules/orders/scenes.ts +++ b/bot/modules/orders/scenes.ts @@ -1,6 +1,7 @@ import { Scenes, Markup } from 'telegraf'; import { logger } from '../../../logger'; import { getCurrency } from '../../../util'; +import { Community } from '../../../models'; import * as ordersActions from '../../ordersActions'; import { publishBuyOrderMessage, @@ -145,19 +146,100 @@ const createOrderSteps = { return ctx.wizard.next(); }, async method(ctx: CommunityContext) { - ctx.wizard.state.handler = async ctx => { - if (ctx.message === undefined) return ctx.scene.leave(); - const { text } = ctx.message; - if (!text) return; - ctx.wizard.state.method = text; - await ctx.wizard.state.updateUI(); - await ctx.deleteMessage(); - return await ctx.telegram.deleteMessage( - prompt.chat.id, - prompt.message_id, + const { user } = ctx.wizard.state; + const stateComm = ctx.wizard.state.community; + const loadedComm = + !stateComm && user?.default_community_id + ? await Community.findById(user.default_community_id) + : null; + const community = stateComm ?? loadedComm; + const paymentMethods = community?.payment_methods; + + if (!paymentMethods || paymentMethods.length === 0) { + ctx.wizard.state.handler = async ctx => { + if (ctx.message === undefined) return ctx.scene.leave(); + if (!('text' in ctx.message)) return; + const { text } = ctx.message; + if (!text) return; + ctx.wizard.state.method = text; + await ctx.wizard.state.updateUI(); + await ctx.deleteMessage(); + return await ctx.telegram.deleteMessage( + prompt.chat.id, + prompt.message_id, + ); + }; + const prompt = await ctx.reply(ctx.i18n.t('enter_payment_method')); + return ctx.wizard.next(); + } + + ctx.wizard.state.selectedMethods = []; + const i18n = ctx.i18n; + + const buildKeyboard = (selected: string[]) => { + const buttons = paymentMethods.map((m, i) => + Markup.button.callback((selected.includes(m) ? '✓ ' : '') + m, `pm_toggle_${i}`), ); + const rows = []; + for (let i = 0; i < buttons.length; i += 2) { + rows.push(buttons.slice(i, i + 2)); + } + rows.push([ + Markup.button.callback(i18n.t('confirm_payment_methods'), 'pm_confirm'), + ]); + return Markup.inlineKeyboard(rows); }; - const prompt = await ctx.reply(ctx.i18n.t('enter_payment_method')); + + const prompt = await ctx.reply( + ctx.i18n.t('select_payment_methods'), + buildKeyboard([]), + ); + + ctx.wizard.state.handler = async ctx => { + if (!ctx.callbackQuery) return; + const data = (ctx.callbackQuery as any).data as string; + + if (data === 'pm_confirm') { + const selected = ctx.wizard.state.selectedMethods || []; + if (selected.length === 0) { + await ctx.answerCbQuery(ctx.i18n.t('no_payment_method_selected')); + return; + } + ctx.wizard.state.method = selected.join(', '); + await ctx.wizard.state.updateUI(); + await ctx.telegram.deleteMessage(prompt.chat.id, prompt.message_id); + await ctx.answerCbQuery(); + return true; + } + + if (data.startsWith('pm_toggle_')) { + const methodIdx = parseInt(data.slice('pm_toggle_'.length)); + const m = paymentMethods[methodIdx]; + if (m === undefined) { + await ctx.answerCbQuery(); + return; + } + const selected = ctx.wizard.state.selectedMethods || []; + const idx = selected.indexOf(m); + if (idx >= 0) { + selected.splice(idx, 1); + } else { + selected.push(m); + } + ctx.wizard.state.selectedMethods = selected; + await ctx.telegram.editMessageReplyMarkup( + prompt.chat.id, + prompt.message_id, + undefined, + buildKeyboard(selected).reply_markup, + ); + await ctx.answerCbQuery(); + return; + } + + await ctx.answerCbQuery(); + }; + return ctx.wizard.next(); }, async priceMargin(ctx: CommunityContext) { diff --git a/locales/de.yaml b/locales/de.yaml index bd190f7d..9b0c18ac 100644 --- a/locales/de.yaml +++ b/locales/de.yaml @@ -706,3 +706,10 @@ unblock_failed: "Fehler beim Freigeben des Benutzers" check_solvers: Ihre Community ${communityName} hat keine Solver. Bitte fügen Sie innerhalb von ${remainingDays} Tagen mindestens einen hinzu, um zu verhindern, dass die Community deaktiviert wird. check_solvers_last_warning: Ihre Community ${communityName} hat keine Solver. Bitte fügen Sie noch heute mindestens einen hinzu, um zu verhindern, dass die Community deaktiviert wird. image_processing_error: Wir hatten einen Fehler beim Verarbeiten des Bildes, bitte warten Sie ein paar Minuten und versuchen Sie es erneut. +community_payment_methods: "Payment methods" +enter_community_payment_methods: "Enter the payment methods accepted in your community, separated by commas (e.g.: Bank transfer, Cash, PayPal)" +current_payment_methods: "Current payment methods: ${methods}" +select_payment_methods: "Select one or more payment methods:" +confirm_payment_methods: "✅ Confirm" +no_payment_method_selected: "Please select at least one payment method" +payment_methods_saved: "Payment methods saved ✅" diff --git a/locales/en.yaml b/locales/en.yaml index 166e6672..73f642f1 100644 --- a/locales/en.yaml +++ b/locales/en.yaml @@ -711,3 +711,10 @@ unblock_failed: "Error unblocking the user" check_solvers: Your community ${communityName} does not have any solvers. Please add at least one within ${remainingDays} days to prevent the community from being disabled. check_solvers_last_warning: Your community ${communityName} does not have any solvers. Please add at least one today to prevent the community from being disabled. image_processing_error: We had an error processing the image, please wait a few minutes and try again +community_payment_methods: "Payment methods" +enter_community_payment_methods: "Enter the payment methods accepted in your community, separated by commas (e.g.: Bank transfer, Cash, PayPal)" +current_payment_methods: "Current payment methods: ${methods}" +select_payment_methods: "Select one or more payment methods:" +confirm_payment_methods: "✅ Confirm" +no_payment_method_selected: "Please select at least one payment method" +payment_methods_saved: "Payment methods saved ✅" diff --git a/locales/es.yaml b/locales/es.yaml index c3140a2a..89903047 100644 --- a/locales/es.yaml +++ b/locales/es.yaml @@ -708,3 +708,10 @@ unblock_failed: "Error al desbloquear al usuario" image_processing_error: Hemos tenido un error procesando la imagen, por favor espera unos minutos y vuelve a intentarlo. check_solvers: Tu comunidad ${communityName} no tiene ningún solucionador. Agregue al menos uno dentro de ${remainingDays} días para evitar que se deshabilite la comunidad. check_solvers_last_warning: Tu comunidad ${communityName} no tiene ningún solucionador. Agregue al menos uno hoy para evitar que la comunidad quede inhabilitada. +community_payment_methods: "Payment methods" +enter_community_payment_methods: "Enter the payment methods accepted in your community, separated by commas (e.g.: Bank transfer, Cash, PayPal)" +current_payment_methods: "Current payment methods: ${methods}" +select_payment_methods: "Select one or more payment methods:" +confirm_payment_methods: "✅ Confirm" +no_payment_method_selected: "Please select at least one payment method" +payment_methods_saved: "Payment methods saved ✅" diff --git a/locales/fa.yaml b/locales/fa.yaml index e8693a43..ddd6d642 100644 --- a/locales/fa.yaml +++ b/locales/fa.yaml @@ -805,3 +805,10 @@ unblock_failed: "خطا در رفع مسدودیت کاربر" check_solvers: اجتماع شما ${communityName} هیچ داوری ندارد. لطفاً برای جلوگیری از غیرفعال شدن اجتماع، تا ${remainingDays} روز آینده حداقل یک داور به آن اضافه کنید. check_solvers_last_warning: اجتماع شما ${communityName} هیچ داوری ندارد. برای جلوگیری از غیرفعال شدن اجتماع، امروز حداقل یک داور به آن اضافه کنید. image_processing_error: هنگام پردازش تصویر با خطایی مواجه شدیم، لطفاً چند دقیقه صبر کرده و دوباره امتحان کنید. +community_payment_methods: "Payment methods" +enter_community_payment_methods: "Enter the payment methods accepted in your community, separated by commas (e.g.: Bank transfer, Cash, PayPal)" +current_payment_methods: "Current payment methods: ${methods}" +select_payment_methods: "Select one or more payment methods:" +confirm_payment_methods: "✅ Confirm" +no_payment_method_selected: "Please select at least one payment method" +payment_methods_saved: "Payment methods saved ✅" diff --git a/locales/fr.yaml b/locales/fr.yaml index 330ae887..81c9520f 100644 --- a/locales/fr.yaml +++ b/locales/fr.yaml @@ -705,3 +705,10 @@ unblock_failed: "Erreur lors du déblocage de l'utilisateur" check_solvers: Votre communauté ${communityName} ne possède aucun solveur. Veuillez en ajouter au moins un dans les ${remainingDays} jours pour éviter que la communauté ne soit désactivée. check_solvers_last_warning: Votre communauté ${communityName} ne possède aucun solveur. Veuillez en ajouter au moins un aujourd'hui pour éviter que la communauté ne soit désactivée. image_processing_error: Nous avons eu une erreur lors du traitement de l'image, veuillez attendre quelques minutes et réessayer. +community_payment_methods: "Payment methods" +enter_community_payment_methods: "Enter the payment methods accepted in your community, separated by commas (e.g.: Bank transfer, Cash, PayPal)" +current_payment_methods: "Current payment methods: ${methods}" +select_payment_methods: "Select one or more payment methods:" +confirm_payment_methods: "✅ Confirm" +no_payment_method_selected: "Please select at least one payment method" +payment_methods_saved: "Payment methods saved ✅" diff --git a/locales/it.yaml b/locales/it.yaml index 05d5ee9a..d4827d25 100644 --- a/locales/it.yaml +++ b/locales/it.yaml @@ -703,3 +703,10 @@ unblock_failed: "Errore nello sbloccare l'utente" check_solvers: La tua community ${communityName} non ha risolutori. Aggiungine almeno uno entro ${remainingDays} giorni per evitare che la community venga disabilitata. check_solvers_last_warning: La tua community ${communityName} non ha risolutori. Per favore aggiungine almeno uno oggi per evitare che la community venga disabilitata. image_processing_error: Abbiamo avuto un errore nel processare l'immagine, per favore attendi qualche minuto e prova di nuovo. +community_payment_methods: "Payment methods" +enter_community_payment_methods: "Enter the payment methods accepted in your community, separated by commas (e.g.: Bank transfer, Cash, PayPal)" +current_payment_methods: "Current payment methods: ${methods}" +select_payment_methods: "Select one or more payment methods:" +confirm_payment_methods: "✅ Confirm" +no_payment_method_selected: "Please select at least one payment method" +payment_methods_saved: "Payment methods saved ✅" diff --git a/locales/ko.yaml b/locales/ko.yaml index 41678056..528d6f8a 100644 --- a/locales/ko.yaml +++ b/locales/ko.yaml @@ -703,3 +703,10 @@ unblock_failed: "사용자 차단 해제 중 오류 발생" check_solvers: ${communityName} 커뮤니티에 해결사가 없습니다. 커뮤니티가 비활성화되는 것을 방지하려면 ${remainingDays}일 이내에 하나 이상 추가하세요. check_solvers_last_warning: ${communityName} 커뮤니티에 해결사가 없습니다. 커뮤니티가 비활성화되는 것을 방지하려면 오늘 하나 이상 추가하세요. image_processing_error: 이미지 처리에 오류가 발생했습니다. 몇 분 후에 다시 시도해 주세요. +community_payment_methods: "Payment methods" +enter_community_payment_methods: "Enter the payment methods accepted in your community, separated by commas (e.g.: Bank transfer, Cash, PayPal)" +current_payment_methods: "Current payment methods: ${methods}" +select_payment_methods: "Select one or more payment methods:" +confirm_payment_methods: "✅ Confirm" +no_payment_method_selected: "Please select at least one payment method" +payment_methods_saved: "Payment methods saved ✅" diff --git a/locales/pt.yaml b/locales/pt.yaml index c95f3492..cb0c9d20 100644 --- a/locales/pt.yaml +++ b/locales/pt.yaml @@ -705,3 +705,10 @@ unblock_failed: "Erro ao desbloquear o usuário" check_solvers: Sua comunidade ${communityName} não possui solucionadores. Adicione pelo menos um dentro de ${remainingDays} dias para evitar que a comunidade seja desativada. check_solvers_last_warning: Sua comunidade ${communityName} não possui solucionadores. Adicione pelo menos um hoje para evitar que a comunidade seja desativada. image_processing_error: Tivemos um erro ao processar a imagem, por favor aguarde alguns minutos e tente novamente. +community_payment_methods: "Payment methods" +enter_community_payment_methods: "Enter the payment methods accepted in your community, separated by commas (e.g.: Bank transfer, Cash, PayPal)" +current_payment_methods: "Current payment methods: ${methods}" +select_payment_methods: "Select one or more payment methods:" +confirm_payment_methods: "✅ Confirm" +no_payment_method_selected: "Please select at least one payment method" +payment_methods_saved: "Payment methods saved ✅" diff --git a/locales/ru.yaml b/locales/ru.yaml index b8e85dcc..e67ad615 100644 --- a/locales/ru.yaml +++ b/locales/ru.yaml @@ -706,3 +706,10 @@ unblock_failed: "Ошибка при разблокировке пользова check_solvers: В вашем сообществе ${communityName} нет решателей. Добавьте хотя бы одно в течение ${remainingDays} дн., чтобы сообщество не было отключено. check_solvers_last_warning: В вашем сообществе ${communityName} нет решателей. Пожалуйста, добавьте хотя бы один сегодня, чтобы предотвратить отключение сообщества. image_processing_error: У нас возникла ошибка при обработке изображения, пожалуйста, подождите несколько минут и попробуйте снова. +community_payment_methods: "Payment methods" +enter_community_payment_methods: "Enter the payment methods accepted in your community, separated by commas (e.g.: Bank transfer, Cash, PayPal)" +current_payment_methods: "Current payment methods: ${methods}" +select_payment_methods: "Select one or more payment methods:" +confirm_payment_methods: "✅ Confirm" +no_payment_method_selected: "Please select at least one payment method" +payment_methods_saved: "Payment methods saved ✅" diff --git a/locales/uk.yaml b/locales/uk.yaml index 1d6435ae..470995d5 100644 --- a/locales/uk.yaml +++ b/locales/uk.yaml @@ -702,3 +702,10 @@ unblock_failed: "Помилка при розблокуванні користу check_solvers: У вашій спільноті ${communityName} немає розв’язувачів. Додайте принаймні одну протягом ${remainingDays} днів, щоб запобігти вимкненню спільноти. check_solvers_last_warning: У вашій спільноті ${communityName} немає розв’язувачів. Будь ласка, додайте принаймні одну сьогодні, щоб запобігти вимкненню спільноти. image_processing_error: У нас виникла помилка при обробці зображення, будь ласка, почекайте кілька хвилин і спробуйте знову. +community_payment_methods: "Payment methods" +enter_community_payment_methods: "Enter the payment methods accepted in your community, separated by commas (e.g.: Bank transfer, Cash, PayPal)" +current_payment_methods: "Current payment methods: ${methods}" +select_payment_methods: "Select one or more payment methods:" +confirm_payment_methods: "✅ Confirm" +no_payment_method_selected: "Please select at least one payment method" +payment_methods_saved: "Payment methods saved ✅" diff --git a/models/community.ts b/models/community.ts index 8b1b8bb2..0434174e 100644 --- a/models/community.ts +++ b/models/community.ts @@ -48,6 +48,7 @@ export interface ICommunity extends Document { banned_users: Types.DocumentArray; public: boolean; currencies: Array; + payment_methods: Array; created_at: Date; nostr_public_key: string; warning_messages_count: number; @@ -83,6 +84,10 @@ const CommunitySchema = new Schema({ trim: true, validate: [currencyLimits, '{PATH} is not within limits'], }, + payment_methods: { + type: [String], + default: [], + }, created_at: { type: Date, default: Date.now }, nostr_public_key: { type: String }, warning_messages_count: { type: Number, default: 0 }, From c31b5c11559cf57166b339e634fb3dab6cde58b8 Mon Sep 17 00:00:00 2001 From: Maxi Vila Date: Mon, 20 Apr 2026 03:41:52 -0300 Subject: [PATCH 02/14] formatting --- bot/modules/orders/scenes.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/bot/modules/orders/scenes.ts b/bot/modules/orders/scenes.ts index a4e7ce12..b5a1b563 100644 --- a/bot/modules/orders/scenes.ts +++ b/bot/modules/orders/scenes.ts @@ -178,7 +178,10 @@ const createOrderSteps = { const buildKeyboard = (selected: string[]) => { const buttons = paymentMethods.map((m, i) => - Markup.button.callback((selected.includes(m) ? '✓ ' : '') + m, `pm_toggle_${i}`), + Markup.button.callback( + (selected.includes(m) ? '✓ ' : '') + m, + `pm_toggle_${i}`, + ), ); const rows = []; for (let i = 0; i < buttons.length; i += 2) { From f9064f02df6ab92616f60d17e9ce3fd6efb2b90e Mon Sep 17 00:00:00 2001 From: Maxi Vila Date: Mon, 27 Apr 2026 11:16:23 -0300 Subject: [PATCH 03/14] i18n: translate payment methods keys to all supported languages --- locales/de.yaml | 14 +++++++------- locales/es.yaml | 14 +++++++------- locales/fa.yaml | 14 +++++++------- locales/fr.yaml | 14 +++++++------- locales/it.yaml | 14 +++++++------- locales/ko.yaml | 14 +++++++------- locales/pt.yaml | 14 +++++++------- locales/ru.yaml | 14 +++++++------- locales/uk.yaml | 14 +++++++------- 9 files changed, 63 insertions(+), 63 deletions(-) diff --git a/locales/de.yaml b/locales/de.yaml index 9b0c18ac..649ed982 100644 --- a/locales/de.yaml +++ b/locales/de.yaml @@ -706,10 +706,10 @@ unblock_failed: "Fehler beim Freigeben des Benutzers" check_solvers: Ihre Community ${communityName} hat keine Solver. Bitte fügen Sie innerhalb von ${remainingDays} Tagen mindestens einen hinzu, um zu verhindern, dass die Community deaktiviert wird. check_solvers_last_warning: Ihre Community ${communityName} hat keine Solver. Bitte fügen Sie noch heute mindestens einen hinzu, um zu verhindern, dass die Community deaktiviert wird. image_processing_error: Wir hatten einen Fehler beim Verarbeiten des Bildes, bitte warten Sie ein paar Minuten und versuchen Sie es erneut. -community_payment_methods: "Payment methods" -enter_community_payment_methods: "Enter the payment methods accepted in your community, separated by commas (e.g.: Bank transfer, Cash, PayPal)" -current_payment_methods: "Current payment methods: ${methods}" -select_payment_methods: "Select one or more payment methods:" -confirm_payment_methods: "✅ Confirm" -no_payment_method_selected: "Please select at least one payment method" -payment_methods_saved: "Payment methods saved ✅" +community_payment_methods: "Zahlungsmethoden" +enter_community_payment_methods: "Gib die in deiner Community akzeptierten Zahlungsmethoden ein, getrennt durch Kommas (z.B.: Banküberweisung, Bargeld, PayPal)" +current_payment_methods: "Aktuelle Zahlungsmethoden: ${methods}" +select_payment_methods: "Wähle eine oder mehrere Zahlungsmethoden:" +confirm_payment_methods: "✅ Bestätigen" +no_payment_method_selected: "Bitte wähle mindestens eine Zahlungsmethode aus" +payment_methods_saved: "Zahlungsmethoden gespeichert ✅" diff --git a/locales/es.yaml b/locales/es.yaml index 89903047..ee76c719 100644 --- a/locales/es.yaml +++ b/locales/es.yaml @@ -708,10 +708,10 @@ unblock_failed: "Error al desbloquear al usuario" image_processing_error: Hemos tenido un error procesando la imagen, por favor espera unos minutos y vuelve a intentarlo. check_solvers: Tu comunidad ${communityName} no tiene ningún solucionador. Agregue al menos uno dentro de ${remainingDays} días para evitar que se deshabilite la comunidad. check_solvers_last_warning: Tu comunidad ${communityName} no tiene ningún solucionador. Agregue al menos uno hoy para evitar que la comunidad quede inhabilitada. -community_payment_methods: "Payment methods" -enter_community_payment_methods: "Enter the payment methods accepted in your community, separated by commas (e.g.: Bank transfer, Cash, PayPal)" -current_payment_methods: "Current payment methods: ${methods}" -select_payment_methods: "Select one or more payment methods:" -confirm_payment_methods: "✅ Confirm" -no_payment_method_selected: "Please select at least one payment method" -payment_methods_saved: "Payment methods saved ✅" +community_payment_methods: "Métodos de pago" +enter_community_payment_methods: "Ingresa los métodos de pago aceptados en tu comunidad, separados por comas (ej.: Transferencia bancaria, Efectivo, PayPal)" +current_payment_methods: "Métodos de pago actuales: ${methods}" +select_payment_methods: "Selecciona uno o más métodos de pago:" +confirm_payment_methods: "✅ Confirmar" +no_payment_method_selected: "Por favor selecciona al menos un método de pago" +payment_methods_saved: "Métodos de pago guardados ✅" diff --git a/locales/fa.yaml b/locales/fa.yaml index ddd6d642..0bb66575 100644 --- a/locales/fa.yaml +++ b/locales/fa.yaml @@ -805,10 +805,10 @@ unblock_failed: "خطا در رفع مسدودیت کاربر" check_solvers: اجتماع شما ${communityName} هیچ داوری ندارد. لطفاً برای جلوگیری از غیرفعال شدن اجتماع، تا ${remainingDays} روز آینده حداقل یک داور به آن اضافه کنید. check_solvers_last_warning: اجتماع شما ${communityName} هیچ داوری ندارد. برای جلوگیری از غیرفعال شدن اجتماع، امروز حداقل یک داور به آن اضافه کنید. image_processing_error: هنگام پردازش تصویر با خطایی مواجه شدیم، لطفاً چند دقیقه صبر کرده و دوباره امتحان کنید. -community_payment_methods: "Payment methods" -enter_community_payment_methods: "Enter the payment methods accepted in your community, separated by commas (e.g.: Bank transfer, Cash, PayPal)" -current_payment_methods: "Current payment methods: ${methods}" -select_payment_methods: "Select one or more payment methods:" -confirm_payment_methods: "✅ Confirm" -no_payment_method_selected: "Please select at least one payment method" -payment_methods_saved: "Payment methods saved ✅" +community_payment_methods: "روش‌های پرداخت" +enter_community_payment_methods: "روش‌های پرداخت پذیرفته‌شده در اجتماع خود را با کاما از هم جدا کرده وارد کنید (مثال: انتقال بانکی، نقد، PayPal)" +current_payment_methods: "روش‌های پرداخت فعلی: ${methods}" +select_payment_methods: "یک یا چند روش پرداخت انتخاب کنید:" +confirm_payment_methods: "✅ تأیید" +no_payment_method_selected: "لطفاً حداقل یک روش پرداخت انتخاب کنید" +payment_methods_saved: "روش‌های پرداخت ذخیره شد ✅" diff --git a/locales/fr.yaml b/locales/fr.yaml index 81c9520f..b92db354 100644 --- a/locales/fr.yaml +++ b/locales/fr.yaml @@ -705,10 +705,10 @@ unblock_failed: "Erreur lors du déblocage de l'utilisateur" check_solvers: Votre communauté ${communityName} ne possède aucun solveur. Veuillez en ajouter au moins un dans les ${remainingDays} jours pour éviter que la communauté ne soit désactivée. check_solvers_last_warning: Votre communauté ${communityName} ne possède aucun solveur. Veuillez en ajouter au moins un aujourd'hui pour éviter que la communauté ne soit désactivée. image_processing_error: Nous avons eu une erreur lors du traitement de l'image, veuillez attendre quelques minutes et réessayer. -community_payment_methods: "Payment methods" -enter_community_payment_methods: "Enter the payment methods accepted in your community, separated by commas (e.g.: Bank transfer, Cash, PayPal)" -current_payment_methods: "Current payment methods: ${methods}" -select_payment_methods: "Select one or more payment methods:" -confirm_payment_methods: "✅ Confirm" -no_payment_method_selected: "Please select at least one payment method" -payment_methods_saved: "Payment methods saved ✅" +community_payment_methods: "Méthodes de paiement" +enter_community_payment_methods: "Entrez les méthodes de paiement acceptées dans votre communauté, séparées par des virgules (ex. : Virement bancaire, Espèces, PayPal)" +current_payment_methods: "Méthodes de paiement actuelles : ${methods}" +select_payment_methods: "Sélectionnez une ou plusieurs méthodes de paiement :" +confirm_payment_methods: "✅ Confirmer" +no_payment_method_selected: "Veuillez sélectionner au moins une méthode de paiement" +payment_methods_saved: "Méthodes de paiement enregistrées ✅" diff --git a/locales/it.yaml b/locales/it.yaml index d4827d25..95d6ffd9 100644 --- a/locales/it.yaml +++ b/locales/it.yaml @@ -703,10 +703,10 @@ unblock_failed: "Errore nello sbloccare l'utente" check_solvers: La tua community ${communityName} non ha risolutori. Aggiungine almeno uno entro ${remainingDays} giorni per evitare che la community venga disabilitata. check_solvers_last_warning: La tua community ${communityName} non ha risolutori. Per favore aggiungine almeno uno oggi per evitare che la community venga disabilitata. image_processing_error: Abbiamo avuto un errore nel processare l'immagine, per favore attendi qualche minuto e prova di nuovo. -community_payment_methods: "Payment methods" -enter_community_payment_methods: "Enter the payment methods accepted in your community, separated by commas (e.g.: Bank transfer, Cash, PayPal)" -current_payment_methods: "Current payment methods: ${methods}" -select_payment_methods: "Select one or more payment methods:" -confirm_payment_methods: "✅ Confirm" -no_payment_method_selected: "Please select at least one payment method" -payment_methods_saved: "Payment methods saved ✅" +community_payment_methods: "Metodi di pagamento" +enter_community_payment_methods: "Inserisci i metodi di pagamento accettati nella tua community, separati da virgole (es.: Bonifico bancario, Contanti, PayPal)" +current_payment_methods: "Metodi di pagamento attuali: ${methods}" +select_payment_methods: "Seleziona uno o più metodi di pagamento:" +confirm_payment_methods: "✅ Conferma" +no_payment_method_selected: "Seleziona almeno un metodo di pagamento" +payment_methods_saved: "Metodi di pagamento salvati ✅" diff --git a/locales/ko.yaml b/locales/ko.yaml index 528d6f8a..d7ea4032 100644 --- a/locales/ko.yaml +++ b/locales/ko.yaml @@ -703,10 +703,10 @@ unblock_failed: "사용자 차단 해제 중 오류 발생" check_solvers: ${communityName} 커뮤니티에 해결사가 없습니다. 커뮤니티가 비활성화되는 것을 방지하려면 ${remainingDays}일 이내에 하나 이상 추가하세요. check_solvers_last_warning: ${communityName} 커뮤니티에 해결사가 없습니다. 커뮤니티가 비활성화되는 것을 방지하려면 오늘 하나 이상 추가하세요. image_processing_error: 이미지 처리에 오류가 발생했습니다. 몇 분 후에 다시 시도해 주세요. -community_payment_methods: "Payment methods" -enter_community_payment_methods: "Enter the payment methods accepted in your community, separated by commas (e.g.: Bank transfer, Cash, PayPal)" -current_payment_methods: "Current payment methods: ${methods}" -select_payment_methods: "Select one or more payment methods:" -confirm_payment_methods: "✅ Confirm" -no_payment_method_selected: "Please select at least one payment method" -payment_methods_saved: "Payment methods saved ✅" +community_payment_methods: "결제 방법" +enter_community_payment_methods: "커뮤니티에서 허용되는 결제 방법을 쉼표로 구분하여 입력하세요 (예: 은행 이체, 현금, PayPal)" +current_payment_methods: "현재 결제 방법: ${methods}" +select_payment_methods: "하나 이상의 결제 방법을 선택하세요:" +confirm_payment_methods: "✅ 확인" +no_payment_method_selected: "결제 방법을 하나 이상 선택해 주세요" +payment_methods_saved: "결제 방법이 저장되었습니다 ✅" diff --git a/locales/pt.yaml b/locales/pt.yaml index cb0c9d20..be8ac01b 100644 --- a/locales/pt.yaml +++ b/locales/pt.yaml @@ -705,10 +705,10 @@ unblock_failed: "Erro ao desbloquear o usuário" check_solvers: Sua comunidade ${communityName} não possui solucionadores. Adicione pelo menos um dentro de ${remainingDays} dias para evitar que a comunidade seja desativada. check_solvers_last_warning: Sua comunidade ${communityName} não possui solucionadores. Adicione pelo menos um hoje para evitar que a comunidade seja desativada. image_processing_error: Tivemos um erro ao processar a imagem, por favor aguarde alguns minutos e tente novamente. -community_payment_methods: "Payment methods" -enter_community_payment_methods: "Enter the payment methods accepted in your community, separated by commas (e.g.: Bank transfer, Cash, PayPal)" -current_payment_methods: "Current payment methods: ${methods}" -select_payment_methods: "Select one or more payment methods:" -confirm_payment_methods: "✅ Confirm" -no_payment_method_selected: "Please select at least one payment method" -payment_methods_saved: "Payment methods saved ✅" +community_payment_methods: "Métodos de pagamento" +enter_community_payment_methods: "Insira os métodos de pagamento aceitos em sua comunidade, separados por vírgulas (ex.: Transferência bancária, Dinheiro, PayPal)" +current_payment_methods: "Métodos de pagamento atuais: ${methods}" +select_payment_methods: "Selecione um ou mais métodos de pagamento:" +confirm_payment_methods: "✅ Confirmar" +no_payment_method_selected: "Por favor selecione pelo menos um método de pagamento" +payment_methods_saved: "Métodos de pagamento salvos ✅" diff --git a/locales/ru.yaml b/locales/ru.yaml index e67ad615..c1638ca5 100644 --- a/locales/ru.yaml +++ b/locales/ru.yaml @@ -706,10 +706,10 @@ unblock_failed: "Ошибка при разблокировке пользова check_solvers: В вашем сообществе ${communityName} нет решателей. Добавьте хотя бы одно в течение ${remainingDays} дн., чтобы сообщество не было отключено. check_solvers_last_warning: В вашем сообществе ${communityName} нет решателей. Пожалуйста, добавьте хотя бы один сегодня, чтобы предотвратить отключение сообщества. image_processing_error: У нас возникла ошибка при обработке изображения, пожалуйста, подождите несколько минут и попробуйте снова. -community_payment_methods: "Payment methods" -enter_community_payment_methods: "Enter the payment methods accepted in your community, separated by commas (e.g.: Bank transfer, Cash, PayPal)" -current_payment_methods: "Current payment methods: ${methods}" -select_payment_methods: "Select one or more payment methods:" -confirm_payment_methods: "✅ Confirm" -no_payment_method_selected: "Please select at least one payment method" -payment_methods_saved: "Payment methods saved ✅" +community_payment_methods: "Способы оплаты" +enter_community_payment_methods: "Введите способы оплаты, принятые в вашем сообществе, разделённые запятыми (напр.: Банковский перевод, Наличные, PayPal)" +current_payment_methods: "Текущие способы оплаты: ${methods}" +select_payment_methods: "Выберите один или несколько способов оплаты:" +confirm_payment_methods: "✅ Подтвердить" +no_payment_method_selected: "Пожалуйста, выберите хотя бы один способ оплаты" +payment_methods_saved: "Способы оплаты сохранены ✅" diff --git a/locales/uk.yaml b/locales/uk.yaml index 470995d5..6018477a 100644 --- a/locales/uk.yaml +++ b/locales/uk.yaml @@ -702,10 +702,10 @@ unblock_failed: "Помилка при розблокуванні користу check_solvers: У вашій спільноті ${communityName} немає розв’язувачів. Додайте принаймні одну протягом ${remainingDays} днів, щоб запобігти вимкненню спільноти. check_solvers_last_warning: У вашій спільноті ${communityName} немає розв’язувачів. Будь ласка, додайте принаймні одну сьогодні, щоб запобігти вимкненню спільноти. image_processing_error: У нас виникла помилка при обробці зображення, будь ласка, почекайте кілька хвилин і спробуйте знову. -community_payment_methods: "Payment methods" -enter_community_payment_methods: "Enter the payment methods accepted in your community, separated by commas (e.g.: Bank transfer, Cash, PayPal)" -current_payment_methods: "Current payment methods: ${methods}" -select_payment_methods: "Select one or more payment methods:" -confirm_payment_methods: "✅ Confirm" -no_payment_method_selected: "Please select at least one payment method" -payment_methods_saved: "Payment methods saved ✅" +community_payment_methods: "Способи оплати" +enter_community_payment_methods: "Введіть способи оплати, прийняті у вашій спільноті, розділені комами (напр.: Банківський переказ, Готівка, PayPal)" +current_payment_methods: "Поточні способи оплати: ${methods}" +select_payment_methods: "Оберіть один або кілька способів оплати:" +confirm_payment_methods: "✅ Підтвердити" +no_payment_method_selected: "Будь ласка, оберіть принаймні один спосіб оплати" +payment_methods_saved: "Способи оплати збережено ✅" From beceb5407c8372f39071ff972ad260c9180ef575 Mon Sep 17 00:00:00 2001 From: Maxi Vila Date: Mon, 27 Apr 2026 11:38:31 -0300 Subject: [PATCH 04/14] fix: add radix to parseInt and guard editMessageReplyMarkup against transient errors --- bot/modules/orders/scenes.ts | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/bot/modules/orders/scenes.ts b/bot/modules/orders/scenes.ts index b5a1b563..66e5ea2b 100644 --- a/bot/modules/orders/scenes.ts +++ b/bot/modules/orders/scenes.ts @@ -216,7 +216,7 @@ const createOrderSteps = { } if (data.startsWith('pm_toggle_')) { - const methodIdx = parseInt(data.slice('pm_toggle_'.length)); + const methodIdx = parseInt(data.slice('pm_toggle_'.length), 10); const m = paymentMethods[methodIdx]; if (m === undefined) { await ctx.answerCbQuery(); @@ -230,12 +230,16 @@ const createOrderSteps = { selected.push(m); } ctx.wizard.state.selectedMethods = selected; - await ctx.telegram.editMessageReplyMarkup( - prompt.chat.id, - prompt.message_id, - undefined, - buildKeyboard(selected).reply_markup, - ); + try { + await ctx.telegram.editMessageReplyMarkup( + prompt.chat.id, + prompt.message_id, + undefined, + buildKeyboard(selected).reply_markup, + ); + } catch (_) { + // ignore transient errors (e.g. "message is not modified" on rapid taps) + } await ctx.answerCbQuery(); return; } From 667039172e0103e30fc9642ec22a05a63323fcc0 Mon Sep 17 00:00:00 2001 From: Maxi Vila Date: Mon, 27 Apr 2026 11:40:52 -0300 Subject: [PATCH 05/14] fix: rename catch variable to satisfy eslint no-unused-vars rule --- bot/modules/orders/scenes.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/modules/orders/scenes.ts b/bot/modules/orders/scenes.ts index 66e5ea2b..2f7b2057 100644 --- a/bot/modules/orders/scenes.ts +++ b/bot/modules/orders/scenes.ts @@ -237,7 +237,7 @@ const createOrderSteps = { undefined, buildKeyboard(selected).reply_markup, ); - } catch (_) { + } catch (_err) { // ignore transient errors (e.g. "message is not modified" on rapid taps) } await ctx.answerCbQuery(); From 3a8abeb10b077525c0d150f64d1cae9359189d7b Mon Sep 17 00:00:00 2001 From: Maxi Vila Date: Wed, 29 Apr 2026 14:40:27 -0300 Subject: [PATCH 06/14] feat: add custom payment method button and /reset command for community (#292) --- bot/modules/community/scenes.ts | 18 ++++++++++++++++- bot/modules/orders/scenes.ts | 35 ++++++++++++++++++++++++++++++++- locales/de.yaml | 3 +++ locales/en.yaml | 3 +++ locales/es.yaml | 3 +++ locales/fa.yaml | 3 +++ locales/fr.yaml | 3 +++ locales/it.yaml | 3 +++ locales/ko.yaml | 3 +++ locales/pt.yaml | 3 +++ locales/ru.yaml | 3 +++ locales/uk.yaml | 3 +++ 12 files changed, 81 insertions(+), 2 deletions(-) diff --git a/bot/modules/community/scenes.ts b/bot/modules/community/scenes.ts index 984ef10c..d8d38650 100644 --- a/bot/modules/community/scenes.ts +++ b/bot/modules/community/scenes.ts @@ -926,7 +926,7 @@ export const updatePaymentMethodsCommunityWizard = new Scenes.WizardScene( ? ctx.i18n.t('current_payment_methods', { methods: current }) + '\n\n' : ''; message += ctx.i18n.t('enter_community_payment_methods') + '\n\n'; - message += ctx.i18n.t('wizard_to_exit'); + message += ctx.i18n.t('payment_methods_wizard_commands'); await ctx.reply(message); return ctx.wizard.next(); } catch (error) { @@ -963,6 +963,22 @@ export const updatePaymentMethodsCommunityWizard = new Scenes.WizardScene( }, ); +updatePaymentMethodsCommunityWizard.command( + 'reset', + async (ctx: CommunityContext) => { + try { + const { community } = ctx.wizard.state; + community.payment_methods = []; + await community.save(); + await ctx.reply(ctx.i18n.t('payment_methods_reset')); + return ctx.scene.leave(); + } catch (error) { + logger.error(error); + ctx.scene.leave(); + } + }, +); + export const addEarningsInvoiceWizard = new Scenes.WizardScene( 'ADD_EARNINGS_INVOICE_WIZARD_SCENE_ID', async (ctx: CommunityContext) => { diff --git a/bot/modules/orders/scenes.ts b/bot/modules/orders/scenes.ts index 2f7b2057..6a68529a 100644 --- a/bot/modules/orders/scenes.ts +++ b/bot/modules/orders/scenes.ts @@ -190,6 +190,9 @@ const createOrderSteps = { rows.push([ Markup.button.callback(i18n.t('confirm_payment_methods'), 'pm_confirm'), ]); + rows.push([ + Markup.button.callback(i18n.t('custom_payment_method'), 'pm_custom'), + ]); return Markup.inlineKeyboard(rows); }; @@ -199,7 +202,17 @@ const createOrderSteps = { ); ctx.wizard.state.handler = async ctx => { - if (!ctx.callbackQuery) return; + if (!ctx.callbackQuery) { + if (ctx.message === undefined || !('text' in ctx.message)) return; + const { text } = ctx.message; + if (!text) return; + ctx.wizard.state.method = text; + await ctx.wizard.state.updateUI(); + await ctx.deleteMessage(); + await ctx.telegram.deleteMessage(prompt.chat.id, prompt.message_id); + return true; + } + const data = (ctx.callbackQuery as any).data as string; if (data === 'pm_confirm') { @@ -215,6 +228,26 @@ const createOrderSteps = { return true; } + if (data === 'pm_custom') { + await ctx.telegram.deleteMessage(prompt.chat.id, prompt.message_id); + await ctx.answerCbQuery(); + const textPrompt = await ctx.reply(ctx.i18n.t('enter_payment_method')); + ctx.wizard.state.handler = async ctx => { + if (ctx.message === undefined || !('text' in ctx.message)) return; + const { text } = ctx.message; + if (!text) return; + ctx.wizard.state.method = text; + await ctx.wizard.state.updateUI(); + await ctx.deleteMessage(); + await ctx.telegram.deleteMessage( + textPrompt.chat.id, + textPrompt.message_id, + ); + return true; + }; + return; + } + if (data.startsWith('pm_toggle_')) { const methodIdx = parseInt(data.slice('pm_toggle_'.length), 10); const m = paymentMethods[methodIdx]; diff --git a/locales/de.yaml b/locales/de.yaml index 649ed982..9a948c64 100644 --- a/locales/de.yaml +++ b/locales/de.yaml @@ -713,3 +713,6 @@ select_payment_methods: "Wähle eine oder mehrere Zahlungsmethoden:" confirm_payment_methods: "✅ Bestätigen" no_payment_method_selected: "Bitte wähle mindestens eine Zahlungsmethode aus" payment_methods_saved: "Zahlungsmethoden gespeichert ✅" +custom_payment_method: "✍️ Benutzerdefinierte Zahlungsmethode" +payment_methods_reset: "Zahlungsmethoden entfernt. Benutzer können jetzt beliebige Zahlungsmethoden frei eingeben." +payment_methods_wizard_commands: "/reset — alle Zahlungsmethoden entfernen und Standardverhalten wiederherstellen\n/exit — ohne Speichern beenden" diff --git a/locales/en.yaml b/locales/en.yaml index 73f642f1..5b2efee4 100644 --- a/locales/en.yaml +++ b/locales/en.yaml @@ -718,3 +718,6 @@ select_payment_methods: "Select one or more payment methods:" confirm_payment_methods: "✅ Confirm" no_payment_method_selected: "Please select at least one payment method" payment_methods_saved: "Payment methods saved ✅" +custom_payment_method: "✍️ Custom payment method" +payment_methods_reset: "Payment methods removed. Users can now enter any payment method freely." +payment_methods_wizard_commands: "/reset — remove all payment methods and restore default behavior\n/exit — exit without saving" diff --git a/locales/es.yaml b/locales/es.yaml index ee76c719..97a08535 100644 --- a/locales/es.yaml +++ b/locales/es.yaml @@ -715,3 +715,6 @@ select_payment_methods: "Selecciona uno o más métodos de pago:" confirm_payment_methods: "✅ Confirmar" no_payment_method_selected: "Por favor selecciona al menos un método de pago" payment_methods_saved: "Métodos de pago guardados ✅" +custom_payment_method: "✍️ Método de pago personalizado" +payment_methods_reset: "Métodos de pago eliminados. Los usuarios ahora pueden ingresar cualquier método de pago libremente." +payment_methods_wizard_commands: "/reset — eliminar todos los métodos de pago y restaurar el comportamiento predeterminado\n/exit — salir sin guardar" diff --git a/locales/fa.yaml b/locales/fa.yaml index 0bb66575..9c9b7ff4 100644 --- a/locales/fa.yaml +++ b/locales/fa.yaml @@ -812,3 +812,6 @@ select_payment_methods: "یک یا چند روش پرداخت انتخاب کن confirm_payment_methods: "✅ تأیید" no_payment_method_selected: "لطفاً حداقل یک روش پرداخت انتخاب کنید" payment_methods_saved: "روش‌های پرداخت ذخیره شد ✅" +custom_payment_method: "✍️ روش پرداخت سفارشی" +payment_methods_reset: "روش‌های پرداخت حذف شدند. کاربران اکنون می‌توانند هر روش پرداختی را آزادانه وارد کنند." +payment_methods_wizard_commands: "/reset — حذف همه روش‌های پرداخت و بازگرداندن رفتار پیش‌فرض\n/exit — خروج بدون ذخیره" diff --git a/locales/fr.yaml b/locales/fr.yaml index b92db354..0626c457 100644 --- a/locales/fr.yaml +++ b/locales/fr.yaml @@ -712,3 +712,6 @@ select_payment_methods: "Sélectionnez une ou plusieurs méthodes de paiement :" confirm_payment_methods: "✅ Confirmer" no_payment_method_selected: "Veuillez sélectionner au moins une méthode de paiement" payment_methods_saved: "Méthodes de paiement enregistrées ✅" +custom_payment_method: "✍️ Méthode de paiement personnalisée" +payment_methods_reset: "Méthodes de paiement supprimées. Les utilisateurs peuvent désormais saisir n'importe quelle méthode de paiement librement." +payment_methods_wizard_commands: "/reset — supprimer toutes les méthodes de paiement et restaurer le comportement par défaut\n/exit — quitter sans enregistrer" diff --git a/locales/it.yaml b/locales/it.yaml index 95d6ffd9..ee892238 100644 --- a/locales/it.yaml +++ b/locales/it.yaml @@ -710,3 +710,6 @@ select_payment_methods: "Seleziona uno o più metodi di pagamento:" confirm_payment_methods: "✅ Conferma" no_payment_method_selected: "Seleziona almeno un metodo di pagamento" payment_methods_saved: "Metodi di pagamento salvati ✅" +custom_payment_method: "✍️ Metodo di pagamento personalizzato" +payment_methods_reset: "Metodi di pagamento rimossi. Gli utenti possono ora inserire qualsiasi metodo di pagamento liberamente." +payment_methods_wizard_commands: "/reset — rimuovere tutti i metodi di pagamento e ripristinare il comportamento predefinito\n/exit — uscire senza salvare" diff --git a/locales/ko.yaml b/locales/ko.yaml index d7ea4032..5fa8bde0 100644 --- a/locales/ko.yaml +++ b/locales/ko.yaml @@ -710,3 +710,6 @@ select_payment_methods: "하나 이상의 결제 방법을 선택하세요:" confirm_payment_methods: "✅ 확인" no_payment_method_selected: "결제 방법을 하나 이상 선택해 주세요" payment_methods_saved: "결제 방법이 저장되었습니다 ✅" +custom_payment_method: "✍️ 사용자 지정 결제 방법" +payment_methods_reset: "결제 방법이 삭제되었습니다. 이제 사용자는 어떤 결제 방법이든 자유롭게 입력할 수 있습니다." +payment_methods_wizard_commands: "/reset — 모든 결제 방법을 삭제하고 기본 동작을 복원합니다\n/exit — 저장하지 않고 종료" diff --git a/locales/pt.yaml b/locales/pt.yaml index be8ac01b..e11a733e 100644 --- a/locales/pt.yaml +++ b/locales/pt.yaml @@ -712,3 +712,6 @@ select_payment_methods: "Selecione um ou mais métodos de pagamento:" confirm_payment_methods: "✅ Confirmar" no_payment_method_selected: "Por favor selecione pelo menos um método de pagamento" payment_methods_saved: "Métodos de pagamento salvos ✅" +custom_payment_method: "✍️ Método de pagamento personalizado" +payment_methods_reset: "Métodos de pagamento removidos. Os usuários agora podem inserir qualquer método de pagamento livremente." +payment_methods_wizard_commands: "/reset — remover todos os métodos de pagamento e restaurar o comportamento padrão\n/exit — sair sem salvar" diff --git a/locales/ru.yaml b/locales/ru.yaml index c1638ca5..0a3cc08c 100644 --- a/locales/ru.yaml +++ b/locales/ru.yaml @@ -713,3 +713,6 @@ select_payment_methods: "Выберите один или несколько с confirm_payment_methods: "✅ Подтвердить" no_payment_method_selected: "Пожалуйста, выберите хотя бы один способ оплаты" payment_methods_saved: "Способы оплаты сохранены ✅" +custom_payment_method: "✍️ Пользовательский способ оплаты" +payment_methods_reset: "Способы оплаты удалены. Пользователи теперь могут свободно вводить любой способ оплаты." +payment_methods_wizard_commands: "/reset — удалить все способы оплаты и восстановить поведение по умолчанию\n/exit — выйти без сохранения" diff --git a/locales/uk.yaml b/locales/uk.yaml index 6018477a..51463fce 100644 --- a/locales/uk.yaml +++ b/locales/uk.yaml @@ -709,3 +709,6 @@ select_payment_methods: "Оберіть один або кілька спосо confirm_payment_methods: "✅ Підтвердити" no_payment_method_selected: "Будь ласка, оберіть принаймні один спосіб оплати" payment_methods_saved: "Способи оплати збережено ✅" +custom_payment_method: "✍️ Власний спосіб оплати" +payment_methods_reset: "Способи оплати видалено. Користувачі тепер можуть вільно вводити будь-який спосіб оплати." +payment_methods_wizard_commands: "/reset — видалити всі способи оплати та відновити поведінку за замовчуванням\n/exit — вийти без збереження" From 53f8b0c11ae9c2980d70fad9386931b6820e2706 Mon Sep 17 00:00:00 2001 From: Maxi Vila Date: Wed, 29 Apr 2026 15:03:45 -0300 Subject: [PATCH 07/14] fix: preserve loaded community and selected methods array in order wizard state --- bot/modules/orders/scenes.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/bot/modules/orders/scenes.ts b/bot/modules/orders/scenes.ts index 6a68529a..33d2e1d8 100644 --- a/bot/modules/orders/scenes.ts +++ b/bot/modules/orders/scenes.ts @@ -31,6 +31,7 @@ export const createOrder = new Scenes.WizardScene( sats, priceMargin, method, + selectedMethods, } = ctx.wizard.state; if (!statusMessage) { const { text } = messages.createOrderWizardStatus( @@ -65,8 +66,13 @@ export const createOrder = new Scenes.WizardScene( if (undefined === priceMargin && sats === 0) return createOrderSteps.priceMargin(ctx); if (undefined === method) return createOrderSteps.method(ctx); - // We remove all special characters from the payment method - const paymentMethod = method.replace(/[&/\\#,+~%.'":*?<>{}]/g, ''); + // We remove all special characters from the payment method. + // When the user selected from the community keyboard the raw array is + // preserved in selectedMethods, so we use it directly to avoid stripping + // the commas that join(', ') produced. + const paymentMethod = selectedMethods?.length + ? selectedMethods.join(', ') + : method.replace(/[&/\\#,+~%.'":*?<>{}]/g, ''); const order = await ordersActions.createOrder(ctx.i18n, ctx, user, { type, @@ -153,6 +159,7 @@ const createOrderSteps = { ? await Community.findById(user.default_community_id) : null; const community = stateComm ?? loadedComm; + if (loadedComm) ctx.wizard.state.community = loadedComm; const paymentMethods = community?.payment_methods; if (!paymentMethods || paymentMethods.length === 0) { From 857fbc062c5a5dcc93c340e66004525e64b13dd8 Mon Sep 17 00:00:00 2001 From: Maxi Vila Date: Thu, 30 Apr 2026 16:05:19 -0300 Subject: [PATCH 08/14] fix: always sanitize payment method regardless of source --- bot/modules/orders/scenes.ts | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/bot/modules/orders/scenes.ts b/bot/modules/orders/scenes.ts index 33d2e1d8..922df35f 100644 --- a/bot/modules/orders/scenes.ts +++ b/bot/modules/orders/scenes.ts @@ -66,13 +66,10 @@ export const createOrder = new Scenes.WizardScene( if (undefined === priceMargin && sats === 0) return createOrderSteps.priceMargin(ctx); if (undefined === method) return createOrderSteps.method(ctx); - // We remove all special characters from the payment method. - // When the user selected from the community keyboard the raw array is - // preserved in selectedMethods, so we use it directly to avoid stripping - // the commas that join(', ') produced. - const paymentMethod = selectedMethods?.length + let paymentMethod = selectedMethods?.length ? selectedMethods.join(', ') - : method.replace(/[&/\\#,+~%.'":*?<>{}]/g, ''); + : method; + paymentMethod = paymentMethod.replace(/[&/\\#,+~%.'":*?<>{}]/g, ''); const order = await ordersActions.createOrder(ctx.i18n, ctx, user, { type, From f80bc90082bbb8a02886036c4502a9b0aadb2eb5 Mon Sep 17 00:00:00 2001 From: lucas Date: Sun, 3 May 2026 22:40:28 -0300 Subject: [PATCH 09/14] When crafting the payment methods text, separate it with a commas when selected from the payment methods wizzard for communities --- bot/modules/orders/scenes.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/bot/modules/orders/scenes.ts b/bot/modules/orders/scenes.ts index 922df35f..e69171fa 100644 --- a/bot/modules/orders/scenes.ts +++ b/bot/modules/orders/scenes.ts @@ -66,10 +66,11 @@ export const createOrder = new Scenes.WizardScene( if (undefined === priceMargin && sats === 0) return createOrderSteps.priceMargin(ctx); if (undefined === method) return createOrderSteps.method(ctx); + + let replaceRegex = /[&/\\#,+~%.'":*?<>{}]/g; let paymentMethod = selectedMethods?.length - ? selectedMethods.join(', ') - : method; - paymentMethod = paymentMethod.replace(/[&/\\#,+~%.'":*?<>{}]/g, ''); + ? selectedMethods.map(m => m.replace(replaceRegex, '')).join(', ') + : method.replace(replaceRegex, ''); const order = await ordersActions.createOrder(ctx.i18n, ctx, user, { type, From 345d87170212d6ae50c4d0cea165ced0d194804b Mon Sep 17 00:00:00 2001 From: lucas Date: Sun, 3 May 2026 22:46:44 -0300 Subject: [PATCH 10/14] Fix lint errors --- bot/modules/orders/scenes.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bot/modules/orders/scenes.ts b/bot/modules/orders/scenes.ts index e69171fa..451e90e8 100644 --- a/bot/modules/orders/scenes.ts +++ b/bot/modules/orders/scenes.ts @@ -67,8 +67,8 @@ export const createOrder = new Scenes.WizardScene( return createOrderSteps.priceMargin(ctx); if (undefined === method) return createOrderSteps.method(ctx); - let replaceRegex = /[&/\\#,+~%.'":*?<>{}]/g; - let paymentMethod = selectedMethods?.length + const replaceRegex = /[&/\\#,+~%.'":*?<>{}]/g; + const paymentMethod = selectedMethods?.length ? selectedMethods.map(m => m.replace(replaceRegex, '')).join(', ') : method.replace(replaceRegex, ''); From 81bb1eec49c5f3a3a59dc859b4b3a4bb664c145f Mon Sep 17 00:00:00 2001 From: lucas Date: Sun, 3 May 2026 23:47:10 -0300 Subject: [PATCH 11/14] Handle coderabbit issues --- bot/modules/orders/scenes.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bot/modules/orders/scenes.ts b/bot/modules/orders/scenes.ts index 451e90e8..1eb6a497 100644 --- a/bot/modules/orders/scenes.ts +++ b/bot/modules/orders/scenes.ts @@ -211,6 +211,7 @@ const createOrderSteps = { if (ctx.message === undefined || !('text' in ctx.message)) return; const { text } = ctx.message; if (!text) return; + ctx.wizard.state.selectedMethods = []; ctx.wizard.state.method = text; await ctx.wizard.state.updateUI(); await ctx.deleteMessage(); @@ -241,6 +242,7 @@ const createOrderSteps = { if (ctx.message === undefined || !('text' in ctx.message)) return; const { text } = ctx.message; if (!text) return; + ctx.wizard.state.selectedMethods = []; ctx.wizard.state.method = text; await ctx.wizard.state.updateUI(); await ctx.deleteMessage(); From 3f2ace3b8b545e586b66e53d80a3c40602cc534f Mon Sep 17 00:00:00 2001 From: lucas Date: Mon, 4 May 2026 00:29:03 -0300 Subject: [PATCH 12/14] Add unit tests for new community payment methods wizzard functionality --- tests/bot/modules/orders/scenes.spec.ts | 237 ++++++++++++++++++++++++ 1 file changed, 237 insertions(+) create mode 100644 tests/bot/modules/orders/scenes.spec.ts diff --git a/tests/bot/modules/orders/scenes.spec.ts b/tests/bot/modules/orders/scenes.spec.ts new file mode 100644 index 00000000..89c11010 --- /dev/null +++ b/tests/bot/modules/orders/scenes.spec.ts @@ -0,0 +1,237 @@ +const { expect } = require('chai'); +const sinon = require('sinon'); +const proxyquire = require('proxyquire'); + +// Mock models +const CommunityMock = { + findById: sinon.stub(), +}; + +// Load scenes with mocked Community +const { createOrder } = proxyquire('../../../../bot/modules/orders/scenes', { + '../../../models': { + Community: CommunityMock, + }, +}); + +describe('Order Creation Wizard - Payment Methods', () => { + let ctx: any; + let sandbox: any; + + beforeEach(() => { + sandbox = sinon.createSandbox(); + ctx = { + i18n: { + t: sandbox.stub().callsFake((key: string) => key), + }, + wizard: { + state: { + user: { tg_id: '123' }, + community: null, + updateUI: sandbox.stub().resolves(), + // To reach 'method' step in step 0: + currency: 'USD', + fiatAmount: [100], + sats: 1000, + priceMargin: 0, + method: undefined, + }, + next: sandbox.stub(), + selectStep: sandbox.stub(), + cursor: 0, + }, + scene: { + leave: sandbox.stub().resolves(), + }, + reply: sandbox.stub().resolves({ chat: { id: 1 }, message_id: 111 }), + telegram: { + deleteMessage: sandbox.stub().resolves(), + editMessageReplyMarkup: sandbox.stub().resolves(), + editMessageText: sandbox.stub().resolves(), + }, + answerCbQuery: sandbox.stub().resolves(), + deleteMessage: sandbox.stub().resolves(), + }; + CommunityMock.findById.reset(); + }); + + afterEach(() => { + sandbox.restore(); + }); + + const runStep0 = async (ctx: any) => { + await createOrder.steps[0](ctx); + }; + + it('should prompt for custom text if community has no payment methods', async () => { + // Community with no payment methods + const community = { + id: 'comm123', + payment_methods: [], + }; + ctx.wizard.state.community = community; + + await runStep0(ctx); + + expect(ctx.reply.calledWith('enter_payment_method')).to.equal(true); + expect(ctx.wizard.next.calledOnce).to.equal(true); + expect(ctx.wizard.state.handler).to.be.a('function'); + + // Test the handler + const handler = ctx.wizard.state.handler; + const testCtx = { + message: { text: 'Custom Method' }, + deleteMessage: sandbox.stub().resolves(), + wizard: ctx.wizard, + telegram: ctx.telegram, + }; + + await handler(testCtx); + expect(ctx.wizard.state.method).to.equal('Custom Method'); + }); + + it('should show keyboard if community has payment methods', async () => { + const community = { + id: 'comm123', + payment_methods: ['Zelle', 'Bank Transfer'], + }; + ctx.wizard.state.community = community; + + await runStep0(ctx); + + expect(ctx.reply.calledWith('select_payment_methods')).to.equal(true); + expect(ctx.wizard.next.calledOnce).to.equal(true); + expect(ctx.wizard.state.selectedMethods).to.deep.equal([]); + }); + + it('should handle multi-select and confirmation', async () => { + const community = { + id: 'comm123', + payment_methods: ['Zelle', 'Bank Transfer'], + }; + ctx.wizard.state.community = community; + + await runStep0(ctx); + const handler = ctx.wizard.state.handler; + + // Simulate toggling Zelle + await handler({ + callbackQuery: { data: 'pm_toggle_0' }, + answerCbQuery: ctx.answerCbQuery, + telegram: ctx.telegram, + wizard: ctx.wizard, + }); + expect(ctx.wizard.state.selectedMethods).to.deep.equal(['Zelle']); + + // Simulate toggling Bank Transfer + await handler({ + callbackQuery: { data: 'pm_toggle_1' }, + answerCbQuery: ctx.answerCbQuery, + telegram: ctx.telegram, + wizard: ctx.wizard, + }); + expect(ctx.wizard.state.selectedMethods).to.deep.equal([ + 'Zelle', + 'Bank Transfer', + ]); + + // Confirm + await handler({ + callbackQuery: { data: 'pm_confirm' }, + answerCbQuery: ctx.answerCbQuery, + telegram: ctx.telegram, + wizard: ctx.wizard, + i18n: ctx.i18n, + }); + expect(ctx.wizard.state.method).to.equal('Zelle, Bank Transfer'); + }); + + it('should show error if confirmation with no selection', async () => { + const community = { + id: 'comm123', + payment_methods: ['Zelle', 'Bank Transfer'], + }; + ctx.wizard.state.community = community; + + await runStep0(ctx); + const handler = ctx.wizard.state.handler; + + await handler({ + callbackQuery: { data: 'pm_confirm' }, + answerCbQuery: ctx.answerCbQuery, + telegram: ctx.telegram, + wizard: ctx.wizard, + i18n: ctx.i18n, + }); + + expect(ctx.answerCbQuery.calledWith('no_payment_method_selected')).to.equal( + true, + ); + expect(ctx.wizard.state.method).to.equal(undefined); + }); + + it('should handle custom free-text fallback and clear selectedMethods', async () => { + const community = { + id: 'comm123', + payment_methods: ['Zelle', 'Bank Transfer'], + }; + ctx.wizard.state.community = community; + + await runStep0(ctx); + const handler = ctx.wizard.state.handler; + + // First select something + ctx.wizard.state.selectedMethods = ['Zelle']; + + // Then click custom + await handler({ + callbackQuery: { data: 'pm_custom' }, + answerCbQuery: ctx.answerCbQuery, + telegram: ctx.telegram, + wizard: ctx.wizard, + i18n: ctx.i18n, + reply: ctx.reply, + }); + + expect(ctx.reply.calledWith('enter_payment_method')).to.equal(true); + const customHandler = ctx.wizard.state.handler; + expect(customHandler).to.not.equal(handler); + + // Enter custom text + await customHandler({ + message: { text: 'Venmo' }, + deleteMessage: sandbox.stub().resolves(), + wizard: ctx.wizard, + telegram: ctx.telegram, + }); + + expect(ctx.wizard.state.method).to.equal('Venmo'); + expect(ctx.wizard.state.selectedMethods).to.deep.equal([]); + }); + + it('should clear selectedMethods on direct free-text input (fallback)', async () => { + const community = { + id: 'comm123', + payment_methods: ['Zelle', 'Bank Transfer'], + }; + ctx.wizard.state.community = community; + + await runStep0(ctx); + const handler = ctx.wizard.state.handler; + + // First select something + ctx.wizard.state.selectedMethods = ['Zelle']; + + // Send direct text message (instead of callback) + await handler({ + message: { text: 'Direct Text' }, + deleteMessage: sandbox.stub().resolves(), + wizard: ctx.wizard, + telegram: ctx.telegram, + }); + + expect(ctx.wizard.state.method).to.equal('Direct Text'); + expect(ctx.wizard.state.selectedMethods).to.deep.equal([]); + }); +}); +export {}; From d01c1afa199d92e3a0146231729f2485744986a2 Mon Sep 17 00:00:00 2001 From: lucas Date: Mon, 4 May 2026 00:51:35 -0300 Subject: [PATCH 13/14] Make max number of payment methods per community configurable by env variable --- .env-sample | 3 +++ bot/modules/community/scenes.ts | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.env-sample b/.env-sample index 604ff56f..8909e807 100644 --- a/.env-sample +++ b/.env-sample @@ -82,6 +82,9 @@ NOSTR_SK='' # Number of currencies allowed in a community COMMUNITY_CURRENCIES=20 +# Max number of payment methods that can be configured in a community for wizzard selection +MAX_COMMUNITY_PAYMENT_METHODS=15 + # List of relays to connect to RELAYS='ws://localhost:7000,ws://localhost:8000,ws://localhost:9000' diff --git a/bot/modules/community/scenes.ts b/bot/modules/community/scenes.ts index e352e4a4..79d9ed3c 100644 --- a/bot/modules/community/scenes.ts +++ b/bot/modules/community/scenes.ts @@ -945,7 +945,7 @@ export const updatePaymentMethodsCommunityWizard = new Scenes.WizardScene( .map(m => m.trim()) .filter(m => m.length > 0); - const max = 20; + const max = parseInt(process.env.MAX_COMMUNITY_PAYMENT_METHODS || '10'); if (methods.length > max) { return await ctx.reply(ctx.i18n.t('max_allowed', { max })); } From 4315a932e13500d5bfc7619190596649900437ca Mon Sep 17 00:00:00 2001 From: lucas Date: Mon, 4 May 2026 01:02:24 -0300 Subject: [PATCH 14/14] Adjust MAX_COMMUNITY_PAYMENT_METHODS to the same value used in the code --- .env-sample | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.env-sample b/.env-sample index 8909e807..009fda2f 100644 --- a/.env-sample +++ b/.env-sample @@ -83,7 +83,7 @@ NOSTR_SK='' COMMUNITY_CURRENCIES=20 # Max number of payment methods that can be configured in a community for wizzard selection -MAX_COMMUNITY_PAYMENT_METHODS=15 +MAX_COMMUNITY_PAYMENT_METHODS=10 # List of relays to connect to RELAYS='ws://localhost:7000,ws://localhost:8000,ws://localhost:9000'