Отправка счета и добавление напоминания об оплате в Next.js с помощью Courier API

Отправка счета и добавление напоминания об оплате в Next.js с помощью Courier API

15 февраля 2023 г.

Многие приложения для управления счетами с открытым исходным кодом созданы с помощью Laravel. Как разработчик Javascript, я хотел создать «Решение React» для разработчиков, знакомых с React и Javascript.

Проблема, которую я обнаружил при сборке с помощью сервисов в Node.js, заключается в отсутствии встроенного почтового модуля. Итак, мне пришлось найти сторонний сервис, чтобы сделать это за меня. В этой статье я буду интегрировать Courier для отправки электронных писем для этого проекта https://github.com/fazzaamiarso/invoys.

Предварительные условия

Поскольку эта статья не является вашим типичным продолжением (больше похоже на "пожалуйста, присядьте и посмотрите, как я это делаю"), не обязательно быть знакомым со всеми используемыми технологиями. Однако знакомство с Typescript и Next.js будет полезно для более быстрого понимания.

Технологии в этом блоге:

* Typescript: безопасность типов и автодополнение лучше, верно? * Next.js: готовая к работе среда для создания полнофункционального приложения даже для новичков. * Prisma: отличная ORM для работы с базами данных. Мы используем Prisma из-за ее безопасности типов и автоматического завершения, что обеспечивает отличный опыт разработчика с добавлением машинописного текста. * Trpc: позволяет нам легко создавать сквозную безопасность типов между нашим клиентом Next.js и сервером. * Courier API: отличный сервис/платформа для обработки наших уведомлений, таких как электронная почта, SMS и многое другое.

Вы можете найти полный исходный код здесь для справки.

Цели

Прежде чем создавать функции, давайте определим наши цели.

  1. Отправить ссылку на счет на электронную почту клиента.
  2. Отправить напоминание за день до даты оплаты счета.
  3. Отменить напоминание о сроке оплаты счета, когда счет уже оплачен.
  4. Обработка сетевых ошибок.

Часть 1. Настройка Courier Platform

Перейдем к панели управления Courier. По умолчанию это производственная среда. Поскольку я хочу все проверить, я перейду к тестовой среде, щелкнув раскрывающийся список в правом верхнем углу.

<цитата>

Мы можем позже скопировать все шаблоны в рабочую среду или наоборот.

Теперь я создам бренд для своей электронной почты. уведомления.

Я просто добавлю логотип (учтите, что ширина логотипа фиксирована и составляет 140 пикселей) в шапке и ссылки на социальные сети в футере. Пользовательский интерфейс конструктора довольно прост, поэтому вот окончательный результат.

Не забудьте опубликовать изменения.

Часть 2. Отправка счета на электронную почту

В настоящее время кнопка отправки электронной почты в пользовательском интерфейсе ничего не делает.

Я собираюсь создать файл courier.ts в src/lib/, чтобы сохранить весь код, связанный с Courier. Кроме того, я буду использовать клиентскую библиотеку courier node.js, которая уже абстрагировала все конечные точки Courier API в функции.

Прежде чем я создам функциональность, давайте создадим дизайн уведомлений по электронной почте в Courier’s Designer и настроим провайдера Gmail.

На странице конструктора писем мы увидим, что созданный бренд уже интегрирован. После этого давайте разработаем шаблон в соответствии с необходимыми данными. Вот окончательный результат.

Обратите внимание на значение с {}, которое становится зеленым, это означает, что это переменная, которую можно вставлять динамически. Я также установил кнопку "Просмотреть счет" (или действие) с помощью переменной.

Прежде чем я смогу использовать шаблон, мне нужно создать тестовое событие, щелкнув вкладку предварительного просмотра. Затем появится приглашение назвать событие и установить data в формате JSON. Это поле данных — это то, что будет заполнять значение зеленых переменных {} (данные также можно установить из кода). Поскольку это тестовое событие, я заполню его произвольными значениями.

Далее я опубликую шаблон, чтобы я мог его использовать. Затем перейдите на вкладку «Отправить». Он покажет необходимый код для отправки электронной почты программным способом, а данные будут заполнены предыдущим тестовым событием, которое я создал.

Бэкенд

Я скопирую тестовый AUTH_TOKEN в файл .env, а фрагмент скопирую в src/lib/courier.ts.

const authToken = process.env.COURIER_AUTH_TOKEN;

// email to receive all sent notifications in DEVELOPMENT mode
const testEmail = process.env.COURIER_TEST_EMAIL;

const INVOICE_TEMPLATE_ID = <TEMPLATE_ID>;

const courierClient = CourierClient({
  authorizationToken: authToken,
});

Создайте функцию sendInvoice, которая будет отвечать за отправку электронной почты. Чтобы отправить электронное письмо из кода, я использую функцию courierClient.send().

// src/lib/courier.ts

export const sendInvoice = async ({
  customerName,
  invoiceNumber,
  invoiceViewUrl,
  emailTo,
  productName,
  dueDate,
}: SendInvoice) => {

const recipientEmail = process.env.NODE_ENV === "production" ? emailTo : testEmail;

const { requestId } = await courierClient.send({
      message: {
        to: {
          email: recipientEmail,
        },
        template: INVOICE_TEMPLATE_ID,
        // Data for courier template designer
        data: {
          customerName,
          invoiceNumber,
          invoiceViewUrl,
          productName,
          dueDate,
        },
      },
    });
    return requestId
};

Определите типы для функции sendInvoice.

// src/lib/courier.ts

interface SendInvoice {
  productName: string;
  dueDate: string;
  customerName: string;
  invoiceNumber: string;
  invoiceViewUrl: string;
  emailTo: string;
}

Теперь, когда я могу отправить электронное письмо, я вызову его в конечной точке trpc sendEmail, которая находится в src/server/trpc/router/invoice.ts.< /p>

<цитата>

Помните, что конечная точка trpc — это маршрут API Next.js. В этом случае sendEmail будет то же самое, что и вызов /api/trpc/sendEmail маршрута с fetch под капотом. Подробнее см. https://trpc.io/docs/quickstart.

// src/server/trpc/router/invoice.ts
import { sendInvoice } from '@lib/courier';
import { dayjs } from '@lib/dayjs';

// .....SOMEWHERE BELOW
  sendEmail: protectedProcedure
    .input(
      z.object({
        customerName: z.string(),
        invoiceNumber: z.string(),
        invoiceViewUrl: z.string(),
        emailTo: z.string(),
        invoiceId: z.string(),
        productName: z.string(),
        dueDate: z.date(),
      })
    )
    .mutation(async ({ input }) => {
      const invoiceData = {
        ...input,
        dueDate: dayjs(input.dueDate).format('D MMMM YYYY'),
      };

      await sendInvoice(invoiceData);
    }),

Для тех, кто не знаком с trpc, то, что я сделал, это то же самое, что и обработка запроса POST. Давайте разберемся.

  1. Trpc способ определения запроса входных данных от клиента путем проверки с помощью Zod. Здесь я определяю все данные, необходимые для функции sendInvoice.

.input(
      z.object({
        customerName: z.string(),
        invoiceNumber: z.string(),
        invoiceViewUrl: z.string(),
        emailTo: z.string(),
        invoiceId: z.string(),
        productName: z.string(),
        dueDate: z.date(),
      })
    )
  1. Определить обработчик запроса POST (мутацию).
// input from before
 .mutation(async ({ input }) => {
      const invoiceData = {
        ...input,
        // format a date to string with a defined format. 
        dueDate: dayjs(input.dueDate).format('D MMMM YYYY'), // ex.'2 January 2023'
      };

      // send the email
      await sendInvoice(invoiceData);
    }),

Внешний интерфейс

Теперь я могу начать добавлять функциональность к кнопке отправки электронной почты. Я собираюсь использовать функцию trpc.useMutation(), которая является тонкой оболочкой функции tanstack-queryuseMutation`.

Добавим функцию мутации. При успешном ответе я хочу отправить всплывающее уведомление об успехе в пользовательском интерфейсе.

//src/pages/invoices/[invoiceId]/index.tsx
import toast from 'react-hot-toast';

const InvoiceDetail: NextPage = () => {
// calling the `sendEmail` trpc endpoint with tanstack-query.
  const sendEmailMutation = trpc.invoice.sendEmail.useMutation({
    onSuccess() {
      toast.success('Email sent!');
    }
  });

}

Я могу просто использовать функцию как встроенный обработчик, но я хочу создать новый обработчик для кнопки.

//src/pages/invoices/[invoiceId]/index.tsx

 // still inside the InvoiceDetail component
 const sendInvoiceEmail = () => {
    const hostUrl = window.location.origin;

   // prevent a user from spamming when the API call is not done.
    if (sendEmailMutation.isLoading) return;

    // send input data to `sendEmail` trpc endpoint
    sendEmailMutation.mutate({
      customerName: invoiceDetail.customer.name,
      invoiceNumber: `#${invoiceDetail.invoiceNumber}`,
      invoiceViewUrl: `${hostUrl}/invoices/${invoiceDetail.id}/preview`,
      emailTo: invoiceDetail.customer.email,
      invoiceId: invoiceDetail.id,
      dueDate: invoiceDetail.dueDate,
      productName: invoiceDetail.name,
    });
  };

Теперь я могу прикрепить обработчик к кнопке отправки электронной почты.

//src/pages/invoices/[invoiceId]/index.tsx

<Button
   variant="primary"
   onClick={sendInvoiceEmail}
   isLoading={sendEmailMutation.isLoading}>
   Send to Email
</Button>

Вот рабочий интерфейс.

Часть 3. Отправка напоминания об оплате

Чтобы запланировать напоминание, которое будет отправлено за день до даты оплаты счета, я буду использовать Courier's Automation API< /а>.

Во-первых, давайте создадим шаблон электронной почты в конструкторе Courier. Поскольку я уже проходил этот процесс ранее, вот окончательный результат.

Перед добавлением функции определите типы для параметра и реорганизуйте типы.

Теперь я добавляю функцию scheduleReminder в src/lib/courier

Чтобы отправить напоминание, я вызову scheduleReminder после успешной попытки sendInvoice. Давайте изменим конечную точку sendEmail trpc.

Теперь, если я попытаюсь отправить счет по электронной почте, я должен получить напоминание через 20 секунд, так как я нахожусь в среде разработки.

Часть 4. Отмена напоминания

Наконец все функции готовы. Однако у меня возникла проблема: что, если клиент оплатил до запланированной даты для напоминания об оплате? В настоящее время напоминание по электронной почте все еще будет отправлено. Это не лучший пользовательский опыт и потенциально запутанный клиент. К счастью, в Courier есть функция автоматической отмены.

Давайте добавим функцию cancelAutomationWorkflow, которая может отменить любой рабочий процесс автоматизации в src/lib/courier.ts.

Что такое Cancelation_token? Это уникальный токен, который можно настроить для рабочего процесса автоматизации, поэтому его можно отменить, отправив действие cancel с соответствующим cancelation_token.

Добавьте cancelation_token в scheduleReminder, я использую идентификатор счета в качестве токена.

Я вызову cancelAutomationWorkflow, когда статус счета изменится на PAID в конечной точке updateStatus trpc.

Вот рабочий интерфейс.

Часть 5. Обработка ошибок

Важным примечанием при выполнении сетевых запросов является вероятность неудачных запросов/ошибок. Я хочу обработать ошибку, передав ее клиенту, чтобы ее можно было отразить в пользовательском интерфейсе.

При ошибке Courier API по умолчанию выдает ошибку типа CourierHttpClientError. У меня также будет возвращаемое значение всех функций в src/lib/courier.ts в соответствии с приведенным ниже форматом.

Теперь я могу обрабатывать ошибки, добавляя блок try-catch ко всем функциям в src/lib/courier.ts.

Давайте рассмотрим пример обработки на конечной точке sendEmail trpc.

Часть 6. Переход к производству

Теперь, когда все шаблоны готовы, я скопирую все ресурсы из тестовой среды в рабочую среду. Вот пример.

Заключение

Наконец, все функции интегрированы с Courier. Мы прошли рабочий процесс интеграции Courier API в приложение Next.js. Хотя это и есть в Next.js и trpc, рабочий процесс будет практически таким же, как и с любой другой технологией. Надеюсь, теперь вы сможете самостоятельно интегрировать Courier в свое приложение.

Начните прямо сейчас: https://app.courier.com/signup

Об авторе

Меня зовут Фазза Разак Амиарсо, я веб-разработчик полного цикла из Индонезии. Я также являюсь энтузиастом Open Source. Я люблю делиться своими знаниями и знаниями в своем блоге. Время от времени я помогаю другим разработчикам в FrontendMentor в свободное время.

Свяжитесь со мной на LinkedIn.

Быстрые ссылки

🔗 Курьерская документация

🔗 Добавить в Invoys

🔗 Мотивация инвоев

:::информация Также опубликовано здесь.

:::


Оригинал