Add "demo-taxi-orders" command in Telegram bot on NestJS
Tags: kaufmanbot, nestjs, telegram, taxi
Published: 2022-11-06 08:35:16

## Links

https://github.com/EndyKaufman/kaufman-bot - source code of bot

https://github.com/EndyKaufman/kaufman-bot/tree/master/libs/demo-taxi-orders/server/src/lib - source code of command

https://telegram.me/KaufmanBot - current bot in telegram

## Usage example
{% embed https://youtu.be/SrMn0CDFxTY %}

## Create main files

## Types
_libs/demo-taxi-orders/server/src/lib/demo-taxi-orders.types.ts_
```ts
export enum NavigationButtons {
  Prev = 'Prev',
  Next = 'Next',
  Done = 'Done',
  Cancel = 'Cancel',
}

export enum DemoTaxiOrdersSteps {
  Start = 'Start',
  Direction = 'Direction',
  CountOfPassengers = 'CountOfPassengers',
  ContactPhone = 'ContactPhone',
  Complete = 'Complete',
  End = 'End',
}

export enum Direction {
  City = 'City',
  Village = 'Village',
}

export enum CountOfPassengers {
  P1 = '1',
  P2 = '2',
  P3 = '3',
  P4 = '4',
}

export type DemoTaxiLocalContext = {
  currentStep?: DemoTaxiOrdersSteps;
  direction?: Direction;
  countOfPassengers?: CountOfPassengers;
  contactPhone?: string;
  stateMessageId?: string;
  contactPhoneMessageId?: string;
  contact?: {
    phone_number: string;
    first_name: string;
    user_id: number;
  };
};

```

### Module options interface
_libs/demo-taxi-orders/server/src/lib/demo-taxi-orders.config.ts_
```ts
import { BotCommandsProviderActionMsg } from '@kaufman-bot/core-server';
import { Telegram } from 'telegraf';

export const DEMO_TAXI_ORDERS_CONFIG = 'DEMO_TAXI_ORDERS_CONFIG';

export interface DemoTaxiOrdersConfig {
  title: string;
  name: string;
  descriptions: string;
  usage: string[];
  spyWords: string[];
  botName: { [langCode: string]: string };
  category: string;
  onComplete?: <
    TDemoTaxiLocalContext,
    TMsg extends BotCommandsProviderActionMsg<TDemoTaxiLocalContext> = BotCommandsProviderActionMsg<TDemoTaxiLocalContext>
  >(
    msg: TMsg,
    ctx: { telegram: Telegram },
    message: string
  ) => Promise<unknown>;
}

```

### Module
_libs/demo-taxi-orders/server/src/lib/demo-taxi-orders.module.ts_
```ts
import {
  BotCommandsCategory,
  BotCommandsModule,
  BOT_COMMANDS_PROVIDER,
} from '@kaufman-bot/core-server';
import { DynamicModule, Module } from '@nestjs/common';
import { getText } from 'class-validator-multi-lang';
import { TranslatesModule } from 'nestjs-translates';
import { DemoTaxiOrders0CancelService } from './demo-taxi-orders-services/demo-taxi-orders-0-cancel.service';
import { DemoTaxiOrders1DirectionService } from './demo-taxi-orders-services/demo-taxi-orders-1-direction.service';
import { DemoTaxiOrders2CountOfPassengersService } from './demo-taxi-orders-services/demo-taxi-orders-2-count-of-passengers.service';
import { DemoTaxiOrders3ContactPhoneService } from './demo-taxi-orders-services/demo-taxi-orders-3-contact-phone.service';
import { DemoTaxiOrders4CompleteService } from './demo-taxi-orders-services/demo-taxi-orders-4-complete.service';
import { DemoTaxiOrdersRenderService } from './demo-taxi-orders-services/demo-taxi-orders-render.service';
import { DemoTaxiOrdersService } from './demo-taxi-orders-services/demo-taxi-orders.service';
import {
  DemoTaxiOrdersConfig,
  DEMO_TAXI_ORDERS_CONFIG,
} from './demo-taxi-orders.config';

@Module({
  imports: [TranslatesModule, BotCommandsModule, BotCommandsModule],
  exports: [TranslatesModule, BotCommandsModule, BotCommandsModule],
})
export class DemoTaxiOrdersModule {
  static forRoot(
    config?: Pick<DemoTaxiOrdersConfig, 'onComplete'>
  ): DynamicModule {
    return {
      module: DemoTaxiOrdersModule,
      providers: [
        {
          provide: DEMO_TAXI_ORDERS_CONFIG,
          useValue: <DemoTaxiOrdersConfig>{
            title: getText('Demo taxi orders commands'),
            name: 'taxi',
            usage: [getText('get taxi'), getText('taxi help')],
            descriptions: getText('Commands for demo taxi orders'),
            spyWords: [getText('taxi')],
            category: BotCommandsCategory.user,
            ...config,
          },
        },
        DemoTaxiOrders0CancelService,
        DemoTaxiOrders1DirectionService,
        DemoTaxiOrders2CountOfPassengersService,
        DemoTaxiOrders3ContactPhoneService,
        DemoTaxiOrders4CompleteService,
        DemoTaxiOrdersRenderService,
        {
          provide: BOT_COMMANDS_PROVIDER,
          useClass: DemoTaxiOrdersService,
        },
      ],
      exports: [DEMO_TAXI_ORDERS_CONFIG],
    };
  }
}

```

## Services

### Service for rendering keyboard and text to send to user
_libs/demo-taxi-orders/server/src/lib/demo-taxi-orders-services/demo-taxi-orders-render.service.ts_
```ts
import { Injectable } from '@nestjs/common';
import { getText } from 'class-validator-multi-lang';
import { TranslatesService } from 'nestjs-translates';
import { Markup } from 'telegraf';
import {
  CountOfPassengers,
  DemoTaxiLocalContext,
  DemoTaxiOrdersSteps,
  Direction,
  NavigationButtons,
} from '../demo-taxi-orders.types';

@Injectable()
export class DemoTaxiOrdersRenderService {
  constructor(private readonly translatesService: TranslatesService) {}

  render(locale: string, localContext: DemoTaxiLocalContext) {
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    let mainButtons: any[] = [];
    if (localContext.currentStep === DemoTaxiOrdersSteps.Direction) {
      mainButtons = [
        Markup.button.callback(
          '🌆 ' + this.getTranslatedDirectionTextByEnum(Direction.City, locale),
          Direction.City
        ),
        Markup.button.callback(
          '🏡 ' +
            this.getTranslatedDirectionTextByEnum(Direction.Village, locale),
          Direction.Village
        ),
      ];
    }

    if (localContext.currentStep === DemoTaxiOrdersSteps.CountOfPassengers) {
      mainButtons = [
        Markup.button.callback('1️⃣', CountOfPassengers.P1),
        Markup.button.callback('2️⃣', CountOfPassengers.P2),
        Markup.button.callback('3️⃣', CountOfPassengers.P3),
        Markup.button.callback('4️⃣', CountOfPassengers.P4),
      ];
    }

    if (
      localContext.currentStep === DemoTaxiOrdersSteps.Complete ||
      localContext.currentStep === DemoTaxiOrdersSteps.ContactPhone
    ) {
      mainButtons = [];
    }

    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    let navButtons: any[] = [];
    if (localContext.currentStep === DemoTaxiOrdersSteps.Direction) {
      navButtons = [
        Markup.button.callback(
          '❌ ' + this.translatesService.translate(getText('Cancel'), locale),
          NavigationButtons.Cancel
        ),
        Markup.button.callback(
          '➡️ ' + this.translatesService.translate(getText('Skip'), locale),
          NavigationButtons.Next
        ),
      ];
    }

    if (
      localContext.currentStep === DemoTaxiOrdersSteps.CountOfPassengers ||
      localContext.currentStep === DemoTaxiOrdersSteps.ContactPhone
    ) {
      navButtons = [
        Markup.button.callback(
          '❌ ' + this.translatesService.translate(getText('Cancel'), locale),
          NavigationButtons.Cancel
        ),
        Markup.button.callback(
          '⬅️ ' + this.translatesService.translate(getText('Prev'), locale),
          NavigationButtons.Prev
        ),
        Markup.button.callback(
          '➡️ ' + this.translatesService.translate(getText('Skip'), locale),
          NavigationButtons.Next
        ),
      ];
    }

    if (localContext.currentStep === DemoTaxiOrdersSteps.Complete) {
      navButtons = [
        Markup.button.callback(
          '❌ ' + this.translatesService.translate(getText('Cancel'), locale),
          NavigationButtons.Cancel
        ),
        Markup.button.callback(
          '⬅️ ' + this.translatesService.translate(getText('Prev'), locale),
          NavigationButtons.Prev
        ),
        Markup.button.callback(
          '✅ ' + this.translatesService.translate(getText('Done'), locale),
          NavigationButtons.Done
        ),
      ];
    }
    return {
      text: [
        this.getCompleteInfo(locale, localContext),
        this.getDirectionInfo(localContext, locale),
        this.getCountOfPassengersInfo(localContext, locale),
        this.getContactPhone(localContext, locale),
      ]
        .filter(Boolean)
        .join('\n'),
      custom: {
        ...Markup.inlineKeyboard([mainButtons, navButtons]),
      },
      context: localContext,
      callback: async (result, context) => {
        Object.assign(context, localContext);
      },
    };
  }

  private getDirectionInfo(localContext: DemoTaxiLocalContext, locale: string) {
    let text = '';
    if (localContext.currentStep === DemoTaxiOrdersSteps.Direction) {
      if (localContext.direction) {
        text = this.translatesService.translate(
          getText(`Please choice direction (current: {value})`),
          locale
        );
      } else {
        text = this.translatesService.translate(
          getText('Please choice direction'),
          locale
        );
      }
    } else {
      if (localContext.direction) {
        text = this.translatesService.translate(
          getText(`Direction - {value}`),
          locale
        );
      }
    }
    return text.replace(
      '{value}',
      this.getTranslatedDirectionTextByEnum(localContext.direction, locale)
    );
  }

  private getCompleteInfo(locale: string, localContext: DemoTaxiLocalContext) {
    if (localContext.currentStep === DemoTaxiOrdersSteps.Complete) {
      return [
        this.translatesService.translate(getText(`Taxi order:`), locale),
      ].join('\n');
    }
    if (localContext.currentStep === DemoTaxiOrdersSteps.End) {
      return [
        this.translatesService.translate(
          getText(`Taxi order was completed`),
          locale
        ),
        this.translatesService.translate(getText(`Parameters:`), locale),
      ].join('\n');
    }
    return '';
  }

  private getContactPhone(localContext: DemoTaxiLocalContext, locale: string) {
    let text = '';
    if (localContext.currentStep === DemoTaxiOrdersSteps.ContactPhone) {
      if (localContext.contactPhone) {
        text = this.translatesService.translate(
          getText(`Please send contact phone (current: {value})`),
          locale
        );
      } else {
        text = this.translatesService.translate(
          getText('Please send contact phone'),
          locale
        );
      }
    } else {
      if (localContext.contactPhone) {
        text = this.translatesService.translate(
          getText(`Contact phone - {value}`),
          locale
        );
      }
    }
    return text.replace('{value}', localContext.contactPhone || '');
  }

  private getCountOfPassengersInfo(
    localContext: DemoTaxiLocalContext,
    locale: string
  ) {
    let text = '';
    if (localContext.currentStep === DemoTaxiOrdersSteps.CountOfPassengers) {
      if (localContext.countOfPassengers) {
        text = this.translatesService.translate(
          getText(`Please choice count of passengers (current: {value})`),
          locale
        );
      } else {
        text = this.translatesService.translate(
          getText('Please choice count of passengers'),
          locale
        );
      }
    } else {
      if (localContext.countOfPassengers) {
        text = this.translatesService.translate(
          getText(`Count of passengers - {value}`),
          locale
        );
      }
    }
    return text.replace('{value}', localContext.countOfPassengers || '');
  }

  private getTranslatedDirectionTextByEnum(
    direction: Direction | undefined,
    locale: string
  ) {
    if (direction === Direction.City) {
      return this.translatesService.translate(getText('City'), locale);
    }
    if (direction === Direction.Village) {
      return this.translatesService.translate(getText('Village'), locale);
    }
    return '';
  }
}

```

### Service for processing steps
_libs/demo-taxi-orders/server/src/lib/demo-taxi-orders-services/demo-taxi-orders.service.ts_
```ts
import {
  BotCommandsEnum,
  BotCommandsProvider,
  BotCommandsProviderActionMsg,
  BotCommandsProviderActionResultType,
  BotCommandsToolsService,
  OnContextBotCommands,
} from '@kaufman-bot/core-server';
import { Inject, Injectable } from '@nestjs/common';
import { getText } from 'class-validator-multi-lang';
import { Telegram } from 'telegraf';
import {
  DemoTaxiOrdersConfig,
  DEMO_TAXI_ORDERS_CONFIG,
} from '../demo-taxi-orders.config';
import {
  DemoTaxiLocalContext,
  DemoTaxiOrdersSteps,
  NavigationButtons,
} from '../demo-taxi-orders.types';
import { DemoTaxiOrders0CancelService } from './demo-taxi-orders-0-cancel.service';
import { DemoTaxiOrders1DirectionService } from './demo-taxi-orders-1-direction.service';
import { DemoTaxiOrders2CountOfPassengersService } from './demo-taxi-orders-2-count-of-passengers.service';
import { DemoTaxiOrders3ContactPhoneService } from './demo-taxi-orders-3-contact-phone.service';
import { DemoTaxiOrders4CompleteService } from './demo-taxi-orders-4-complete.service';
import { DemoTaxiOrdersRenderService } from './demo-taxi-orders-render.service';

@Injectable()
export class DemoTaxiOrdersService
  implements
    BotCommandsProvider<DemoTaxiLocalContext>,
    OnContextBotCommands<DemoTaxiLocalContext>
{
  handlerId = DemoTaxiOrdersService.name;

  constructor(
    @Inject(DEMO_TAXI_ORDERS_CONFIG)
    private readonly config: DemoTaxiOrdersConfig,
    private readonly botCommandsToolsService: BotCommandsToolsService,
    private readonly demoTaxiOrdersRenderService: DemoTaxiOrdersRenderService,
    private readonly demoTaxiOrders0CancelProcessorService: DemoTaxiOrders0CancelService,
    private readonly demoTaxiOrders1DirectionProcessorService: DemoTaxiOrders1DirectionService,
    private readonly demoTaxiOrders2CountOfPassengersService: DemoTaxiOrders2CountOfPassengersService,
    private readonly demoTaxiOrders3ContactPhoneService: DemoTaxiOrders3ContactPhoneService,
    private readonly demoTaxiOrders4CompleteService: DemoTaxiOrders4CompleteService
  ) {}

  async onContextBotCommands<
    TMsg extends BotCommandsProviderActionMsg<DemoTaxiLocalContext> = BotCommandsProviderActionMsg<DemoTaxiLocalContext>
  >(
    msg: TMsg,
    ctx: { telegram: Telegram }
  ): Promise<BotCommandsProviderActionResultType<TMsg>> {
    const currentStep = msg.context.currentStep;

    if (currentStep === DemoTaxiOrdersSteps.End) {
      return null;
    }

    if (
      currentStep &&
      Object.keys(DemoTaxiOrdersSteps).includes(currentStep) &&
      msg.data === NavigationButtons.Cancel
    ) {
      return await this.demoTaxiOrders0CancelProcessorService.process(msg, ctx);
    }

    if (currentStep === DemoTaxiOrdersSteps.Direction) {
      return await this.demoTaxiOrders1DirectionProcessorService.process(
        msg,
        ctx
      );
    }

    if (currentStep === DemoTaxiOrdersSteps.CountOfPassengers) {
      return await this.demoTaxiOrders2CountOfPassengersService.process(
        msg,
        ctx
      );
    }

    if (currentStep === DemoTaxiOrdersSteps.ContactPhone) {
      return await this.demoTaxiOrders3ContactPhoneService.process(msg, ctx);
    }

    if (currentStep === DemoTaxiOrdersSteps.Complete) {
      return await this.demoTaxiOrders4CompleteService.process(msg, ctx);
    }
    return null;
  }

  async onMessage<
    TMsg extends BotCommandsProviderActionMsg<DemoTaxiLocalContext> = BotCommandsProviderActionMsg<DemoTaxiLocalContext>
  >(msg: TMsg): Promise<BotCommandsProviderActionResultType<TMsg>> {
    if (this.checkSpyWords({ msg })) {
      const locale = this.botCommandsToolsService.getLocale(msg, 'en');

      if (
        this.botCommandsToolsService.checkCommands(
          msg.text,
          [BotCommandsEnum.help],
          locale
        )
      ) {
        return {
          type: 'markdown',
          message: msg,
          markdown: this.botCommandsToolsService.generateHelpMessage(msg, {
            locale,
            name: this.config.title,
            descriptions: this.config.descriptions,
            usage: this.config.usage,
            category: this.config.category,
          }),
        };
      }

      if (
        this.botCommandsToolsService.checkCommands(
          msg.text,
          [getText('start'), getText('get')],
          locale
        )
      ) {
        const currentStep =
          msg.context.currentStep || DemoTaxiOrdersSteps.Start;
        if (currentStep === DemoTaxiOrdersSteps.Start) {
          return {
            newState: true,
            type: 'text',
            ...this.demoTaxiOrdersRenderService.render(locale, {
              ...msg.context,
              currentStep: DemoTaxiOrdersSteps.Direction,
            }),
            message: msg,
          };
        }
      }
    }
    return null;
  }

  async onHelp<
    TMsg extends BotCommandsProviderActionMsg<DemoTaxiLocalContext> = BotCommandsProviderActionMsg<DemoTaxiLocalContext>
  >(msg: TMsg): Promise<BotCommandsProviderActionResultType<TMsg>> {
    return await this.onMessage({
      ...msg,
      text: `${this.config.name} ${BotCommandsEnum.help}`,
    });
  }

  checkSpyWords({ msg }: { msg: BotCommandsProviderActionMsg }) {
    const locale = this.botCommandsToolsService.getLocale(msg, 'en');
    return this.config.spyWords.find((spyWord) =>
      this.botCommandsToolsService.checkCommands(msg.text, [spyWord], locale)
    );
  }
}

```

### Service for processing cancel step
_libs/demo-taxi-orders/server/src/lib/demo-taxi-orders-services/demo-taxi-orders-0-cancel.service.ts_
```ts
import {
  BotCommandsProviderActionMsg,
  BotCommandsProviderActionResultType,
  BotCommandsToolsService,
} from '@kaufman-bot/core-server';
import { Injectable } from '@nestjs/common';
import { Telegram } from 'telegraf';
import { DemoTaxiLocalContext } from '../demo-taxi-orders.types';

@Injectable()
export class DemoTaxiOrders0CancelService {
  constructor(
    private readonly botCommandsToolsService: BotCommandsToolsService
  ) {}

  async process<
    TMsg extends BotCommandsProviderActionMsg<DemoTaxiLocalContext> = BotCommandsProviderActionMsg<DemoTaxiLocalContext>
  >(
    msg: TMsg,
    ctx: { telegram: Telegram }
  ): Promise<BotCommandsProviderActionResultType<TMsg>> {
    await ctx.telegram.deleteMessage(
      this.botCommandsToolsService.getChatId(msg),
      +this.botCommandsToolsService.getContextMessageId(msg)
    );
    msg.handlerStop = true;
    return {
      type: 'stop',
      message: msg,
    };
  }
}

```

### Service for processing direction step
_libs/demo-taxi-orders/server/src/lib/demo-taxi-orders-services/demo-taxi-orders-1-direction.service.ts_
```ts
import {
  BotCommandsProviderActionMsg,
  BotCommandsProviderActionResultType,
  BotCommandsToolsService,
} from '@kaufman-bot/core-server';
import { Injectable } from '@nestjs/common';
import { Telegram } from 'telegraf';
import {
  DemoTaxiLocalContext,
  DemoTaxiOrdersSteps,
  Direction,
} from '../demo-taxi-orders.types';
import { DemoTaxiOrdersRenderService } from './demo-taxi-orders-render.service';

@Injectable()
export class DemoTaxiOrders1DirectionService {
  constructor(
    private readonly botCommandsToolsService: BotCommandsToolsService,
    private readonly demoTaxiOrdersRenderService: DemoTaxiOrdersRenderService
  ) {}

  async process<
    TMsg extends BotCommandsProviderActionMsg<DemoTaxiLocalContext> = BotCommandsProviderActionMsg<DemoTaxiLocalContext>
  >(
    msg: TMsg,
    ctx: { telegram: Telegram }
  ): Promise<BotCommandsProviderActionResultType<TMsg>> {
    const locale = this.botCommandsToolsService.getLocale(msg, 'en');
    const renderedData = this.demoTaxiOrdersRenderService.render(locale, {
      ...msg.context,
      currentStep: DemoTaxiOrdersSteps.CountOfPassengers,
      direction: (Object.keys(Direction).includes(msg.data)
        ? msg.data || msg.context.direction
        : undefined) as Direction,
    });

    await ctx.telegram.editMessageText(
      this.botCommandsToolsService.getChatId(msg),
      +this.botCommandsToolsService.getContextMessageId(msg),
      undefined,
      renderedData.text,
      renderedData.custom
    );

    return {
      type: 'message',
      message: msg,
      context: renderedData.context,
    };
  }
}

```

### Service for processing count-of-passengers step
_libs/demo-taxi-orders/server/src/lib/demo-taxi-orders-services/demo-taxi-orders-2-count-of-passengers.service.ts_
```ts
import {
  BotCommandsProviderActionMsg,
  BotCommandsProviderActionResultType,
  BotCommandsToolsService,
} from '@kaufman-bot/core-server';
import { Injectable } from '@nestjs/common';
import { Telegram } from 'telegraf';
import {
  CountOfPassengers,
  DemoTaxiLocalContext,
  DemoTaxiOrdersSteps,
  NavigationButtons,
} from '../demo-taxi-orders.types';
import { DemoTaxiOrdersRenderService } from './demo-taxi-orders-render.service';

@Injectable()
export class DemoTaxiOrders2CountOfPassengersService {
  constructor(
    private readonly botCommandsToolsService: BotCommandsToolsService,
    private readonly demoTaxiOrdersRenderService: DemoTaxiOrdersRenderService
  ) {}

  async process<
    TMsg extends BotCommandsProviderActionMsg<DemoTaxiLocalContext> = BotCommandsProviderActionMsg<DemoTaxiLocalContext>
  >(
    msg: TMsg,
    ctx: { telegram: Telegram }
  ): Promise<BotCommandsProviderActionResultType<TMsg>> {
    const locale = this.botCommandsToolsService.getLocale(msg, 'en');
    const renderedData =
      msg.data === NavigationButtons.Prev
        ? this.demoTaxiOrdersRenderService.render(locale, {
            ...msg.context,
            currentStep: DemoTaxiOrdersSteps.Direction,
          })
        : this.demoTaxiOrdersRenderService.render(locale, {
            ...msg.context,
            currentStep: DemoTaxiOrdersSteps.ContactPhone,
            stateMessageId:
              this.botCommandsToolsService.getContextMessageId(msg),
            countOfPassengers: (Object.values(CountOfPassengers).find(
              (value) => +value === +msg.data
            )
              ? msg.data || msg.context.countOfPassengers
              : undefined) as CountOfPassengers,
          });

    await ctx.telegram.editMessageText(
      this.botCommandsToolsService.getChatId(msg),
      +this.botCommandsToolsService.getContextMessageId(msg),
      undefined,
      renderedData.text,
      renderedData.custom
    );

    return {
      type: 'message',
      message: msg,
      context: renderedData.context,
    };
  }
}

```

### Service for processing contact-phone step
_libs/demo-taxi-orders/server/src/lib/demo-taxi-orders-services/demo-taxi-orders-3-contact-phone.service.ts_
```ts
import {
  BotCommandsProviderActionMsg,
  BotCommandsProviderActionResultType,
  BotCommandsToolsService,
} from '@kaufman-bot/core-server';
import { Injectable } from '@nestjs/common';
import { Telegram } from 'telegraf';
import {
  DemoTaxiLocalContext,
  DemoTaxiOrdersSteps,
  NavigationButtons,
} from '../demo-taxi-orders.types';
import { DemoTaxiOrdersRenderService } from './demo-taxi-orders-render.service';

@Injectable()
export class DemoTaxiOrders3ContactPhoneService {
  constructor(
    private readonly botCommandsToolsService: BotCommandsToolsService,
    private readonly demoTaxiOrdersRenderService: DemoTaxiOrdersRenderService
  ) {}

  async process<
    TMsg extends BotCommandsProviderActionMsg<DemoTaxiLocalContext> = BotCommandsProviderActionMsg<DemoTaxiLocalContext>
  >(
    msg: TMsg,
    ctx: { telegram: Telegram }
  ): Promise<BotCommandsProviderActionResultType<TMsg>> {
    const locale = this.botCommandsToolsService.getLocale(msg, 'en');
    let renderedData = this.demoTaxiOrdersRenderService.render(locale, {
      ...msg.context,
      currentStep: DemoTaxiOrdersSteps.Complete,
      contact: msg.contact,
      contactPhone: !Object.keys(NavigationButtons).includes(msg.data)
        ? msg.text || msg.contact.phone_number
        : msg.context.contactPhone,
      contactPhoneMessageId:
        msg.context.contactPhoneMessageId === undefined
          ? this.botCommandsToolsService.getContextMessageId(msg)
          : msg.context.contactPhoneMessageId,
    });

    if (msg.data === NavigationButtons.Prev) {
      renderedData = this.demoTaxiOrdersRenderService.render(locale, {
        ...msg.context,
        currentStep: DemoTaxiOrdersSteps.CountOfPassengers,
      });
    }

    if (renderedData.context.contactPhoneMessageId === undefined) {
      await ctx.telegram.editMessageText(
        this.botCommandsToolsService.getChatId(msg),
        +this.botCommandsToolsService.getContextMessageId(msg),
        undefined,
        renderedData.text,
        renderedData.custom
      );

      return {
        type: 'message',
        message: msg,
        context: renderedData.context,
      };
    } else {
      if (renderedData.context.stateMessageId) {
        await ctx.telegram.editMessageText(
          this.botCommandsToolsService.getChatId(msg),
          +renderedData.context.stateMessageId,
          undefined,
          this.demoTaxiOrdersRenderService.render(locale, {
            currentStep: DemoTaxiOrdersSteps.ContactPhone,
          }).text
        );
      }

      return {
        type: 'text',
        ...this.demoTaxiOrdersRenderService.render(locale, {
          ...renderedData.context,
          stateMessageId: undefined,
          contactPhoneMessageId: undefined,
        }),
        message: msg,
      };
    }
  }
}

```

### Service for processing complete step
_libs/demo-taxi-orders/server/src/lib/demo-taxi-orders-services/demo-taxi-orders-4-complete.service.ts_
```ts
import {
  BotCommandsProviderActionMsg,
  BotCommandsProviderActionResultType,
  BotCommandsToolsService,
} from '@kaufman-bot/core-server';
import { Inject, Injectable } from '@nestjs/common';
import { Telegram } from 'telegraf';
import {
  DemoTaxiOrdersConfig,
  DEMO_TAXI_ORDERS_CONFIG,
} from '../demo-taxi-orders.config';
import {
  DemoTaxiLocalContext,
  DemoTaxiOrdersSteps,
  NavigationButtons,
} from '../demo-taxi-orders.types';
import { DemoTaxiOrdersRenderService } from './demo-taxi-orders-render.service';

@Injectable()
export class DemoTaxiOrders4CompleteService {
  constructor(
    @Inject(DEMO_TAXI_ORDERS_CONFIG)
    private readonly config: DemoTaxiOrdersConfig,
    private readonly botCommandsToolsService: BotCommandsToolsService,
    private readonly demoTaxiOrdersRenderService: DemoTaxiOrdersRenderService
  ) {}

  async process<
    TMsg extends BotCommandsProviderActionMsg<DemoTaxiLocalContext> = BotCommandsProviderActionMsg<DemoTaxiLocalContext>
  >(
    msg: TMsg,
    ctx: { telegram: Telegram }
  ): Promise<BotCommandsProviderActionResultType<TMsg>> {
    const locale = this.botCommandsToolsService.getLocale(msg, 'en');
    const renderedData =
      msg.data === NavigationButtons.Prev
        ? this.demoTaxiOrdersRenderService.render(locale, {
            ...msg.context,
            stateMessageId:
              this.botCommandsToolsService.getContextMessageId(msg),
            currentStep: DemoTaxiOrdersSteps.ContactPhone,
          })
        : this.demoTaxiOrdersRenderService.render(locale, {
            ...msg.context,
            currentStep: DemoTaxiOrdersSteps.End,
          });

    await ctx.telegram.editMessageText(
      this.botCommandsToolsService.getChatId(msg),
      +this.botCommandsToolsService.getContextMessageId(msg),
      undefined,
      renderedData.text,
      msg.data === NavigationButtons.Done ? undefined : renderedData.custom
    );

    if (this.config.onComplete) {
      await this.config.onComplete(msg, ctx, renderedData.text);
    }

    return {
      type: 'message',
      message: msg,
      context: renderedData.context,
    };
  }
}

```