paint-brush
Enviando uma fatura e adicionando um lembrete de pagamento em Next.js com Courier APIpor@courier
1,495 leituras
1,495 leituras

Enviando uma fatura e adicionando um lembrete de pagamento em Next.js com Courier API

por Courier14m2023/02/14
Read on Terminal Reader

Muito longo; Para ler

Muitos aplicativos de gerenciamento de faturas de código aberto são criados com o Laravel. Eu queria construir a “Solução React” para desenvolvedores familiarizados com React e Javascript. Um problema que encontrei ao construir com serviços em Node.js é que não há mailer integrado. Então, tive que encontrar um serviço terceirizado para fazer isso por mim. Neste artigo, estarei integrando [Courier] para enviar e-mails para este projeto.
featured image - Enviando uma fatura e adicionando um lembrete de pagamento em Next.js com Courier API
Courier HackerNoon profile picture
0-item

Muitos aplicativos de gerenciamento de faturas de código aberto são criados com o Laravel. Como desenvolvedor Javascript, eu queria construir a “Solução React” para desenvolvedores familiarizados com React e Javascript.


Um problema que encontrei ao construir com serviços em Node.js é que não há mailer embutido. Então, tive que encontrar um serviço terceirizado para fazer isso por mim. Neste artigo, estarei integrando o Courier para enviar e-mails para este projeto https://github.com/fazzaamiarso/invoys .

Pré-requisitos

Como este artigo não é um acompanhamento típico (mais como “por favor, sente-se e veja como eu faço”), não é obrigatório estar familiarizado com todas as tecnologias utilizadas. No entanto, a familiaridade com Typescript e Next.js será benéfica para uma compreensão mais rápida.


Tecnologias neste blog:


  • Texto datilografado : segurança de digitação e preenchimento automático são os melhores, certo?

  • Next.js : uma estrutura pronta para produção para criar um aplicativo full-stack, mesmo para iniciantes.

  • Prisma : um ótimo ORM para trabalhar com bancos de dados. Usamos o Prisma por causa de sua segurança de digitação e preenchimento automático, proporcionando uma ótima experiência ao desenvolvedor com o typescript adicionado.

  • Trpc : permite-nos criar facilmente segurança de tipo de ponta a ponta entre nosso cliente e servidor Next.js.

  • Courier API: um ótimo serviço/plataforma para lidar com nossas notificações, como e-mail, SMS e muito mais.


Você pode encontrar o código-fonte completo aqui para referência.

Metas

Antes de construir os recursos, vamos definir nossos objetivos.


  1. Enviar link da fatura para o e-mail do cliente.
  2. Envie um lembrete um dia antes da data de vencimento de uma fatura.
  3. Cancele um lembrete de data de vencimento da fatura quando a fatura já estiver paga.
  4. Tratamento de erros de rede.

Parte 1: Configuração da Plataforma Courier

Vamos para o Painel do Courier. Por padrão, está em um ambiente de produção. Como quero testar as coisas, vou mudar para o ambiente de teste clicando no menu suspenso no canto superior direito.


Podemos copiar todos os modelos posteriormente para produção ou vice-versa.


Agora, criarei uma marca para minhas notificações por e-mail.



Vou apenas adicionar um logotipo (cuidado com a largura do logotipo fixada em 140px) no cabeçalho e links sociais no rodapé. A interface do usuário do designer é bastante direta, então aqui está o resultado final.



Não se esqueça de publicar as alterações.

Parte 2: Enviar fatura para e-mail

Atualmente, o botão enviar e-mail na interface do usuário não está fazendo nada.


Vou criar um arquivo courier.ts em src/lib/ para manter todo o código relacionado ao Courier. Além disso, usarei a biblioteca do cliente courier node.js , que já abstraiu todos os endpoints da API Courier para funções.


Antes de criar a funcionalidade, vamos criar o design de notificação por e-mail no Courier's Designer e configurar o provedor do Gmail.


Na página do designer de e-mail, veremos que a marca criada já está integrada. Depois disso, vamos projetar o modelo de acordo com os dados necessários. Aqui está o resultado final.




Observe o valor com {} que fica verde, significa que é uma variável que pode ser inserida dinamicamente. Também defino o botão (ou ação) 'Ver fatura' com uma variável.


Antes de poder usar o modelo, preciso criar um evento de teste clicando na guia de visualização. Em seguida, ele mostrará um prompt para nomear o evento e definir data no formato JSON. Esse campo de dados é o que preencherá o valor das variáveis {} verdes (os dados também podem ser definidos no código). Como é um evento de teste, vou preenchê-lo com valores arbitrários.


Em seguida, publicarei o modelo para que eu possa usá-lo. Em seguida, vá para a guia enviar. Ele mostrará o código necessário para enviar o e-mail programaticamente e os data serão preenchidos com o evento de teste anterior que criei.



Processo interno

Vou copiar o teste AUTH_TOKEN para o arquivo .env e copiar o trecho para 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, });

Crie uma função sendInvoice que será responsável pelo envio de um e-mail. Para enviar um e-mail a partir do código, utilizo a função 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 };

Defina tipos para a função sendInvoice .

 // src/lib/courier.ts interface SendInvoice { productName: string; dueDate: string; customerName: string; invoiceNumber: string; invoiceViewUrl: string; emailTo: string; }

Agora que posso enviar o e-mail, vou chamá-lo no endpoint sendEmail trpc que reside em src/server/trpc/router/invoice.ts .


Apenas lembre-se de que o endpoint trpc é uma rota de API Next.js. Nesse caso, sendEmail será o mesmo que chamar a rota /api/trpc/sendEmail com fetch sob o capô. Para mais explicações 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); }),


Para quem não está familiarizado com o trpc, o que fiz é o mesmo que lidar com uma solicitação POST . Vamos decompô-lo.


  1. Maneira Trpc de definir a entrada de solicitação do cliente validando com Zod. Aqui defino todos os dados necessários para a função 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. Defina um manipulador de solicitação POST (mutação).
 // 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); }),

Front-end

Agora, posso começar a adicionar a funcionalidade ao botão enviar e-mail. Vou usar a função trpc.useMutation() que é um invólucro fino do useMutation` tanstack-query's .


Vamos adicionar a função de mutação. Na resposta bem-sucedida, desejo enviar um brinde de sucesso na interface do usuário.

 //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!'); } }); }

Posso apenas usar a função como um manipulador embutido, mas quero criar um novo manipulador para o botão.

 //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, }); };

Agora posso anexar o manipulador ao botão enviar e-mail.

 //src/pages/invoices/[invoiceId]/index.tsx <Button variant="primary" onClick={sendInvoiceEmail} isLoading={sendEmailMutation.isLoading}> Send to Email </Button>

Aqui está a IU de trabalho.

Parte 3: Enviar lembrete de pagamento

Para agendar um lembrete que será enviado um dia antes da data de vencimento de uma fatura, vou usar a API de automação do Courier .


Primeiro, vamos projetar o modelo de e-mail no Courier designer. Como já passei pelo processo antes, aqui está o resultado final.


Antes de adicionar a função, defina os tipos para o parâmetro e refatore os tipos.

 // src/lib/courier interface CourierBaseData { customerName: string; invoiceNumber: string; invoiceViewUrl: string; emailTo: string; } interface SendInvoice extends CourierBaseData { productName: string; dueDate: string; } interface ScheduleReminder extends CourierBaseData { scheduledDate: Date; invoiceId: string; }

Agora, adiciono a função scheduleReminder a src/lib/courier

 //src/pages/invoices/[invoiceId]/index.tsx // check if the development environment is production const __IS_PROD__ = process.env.NODE_ENV === 'production'; const PAYMENT_REMINDER_TEMPLATE_ID = '<TEMPLATE_ID>'; export const scheduleReminder = async ({ scheduledDate, emailTo, invoiceViewUrl, invoiceId, customerName, invoiceNumber, }: ScheduleReminder) => { // delay until a day before due date in production, else 20 seconds after sent for development const delayUntilDate = __IS_PROD__ ? scheduledDate : new Date(Date.now() + SECOND_TO_MS * 20); const recipientEmail = __IS_PROD__ ? emailTo : testEmail; // define the automation steps programmatically const { runId } = await courierClient.automations.invokeAdHocAutomation({ automation: { steps: [ // 1. Set delay for the next steps until given date in ISO string { action: 'delay', until: delayUntilDate.toISOString() }, // 2. Send the email notification. Equivalent to `courierClient.send()` { action: 'send', message: { to: { email: recipientEmail }, template: PAYMENT_REMINDER_TEMPLATE_ID, data: { invoiceViewUrl, customerName, invoiceNumber, }, }, }, ], }, }); return runId; };

Para enviar o lembrete, chamarei scheduleReminder após uma tentativa bem-sucedida sendInvoice . Vamos modificar o endpoint sendEmail trpc.

 // src/server/trpc/router/invoice.ts sendEmail: protectedProcedure .input(..) // omitted for brevity .mutation(async ({ input }) => { // multiplier for converting day to milliseconds. const DAY_TO_MS = 1000 * 60 * 60 * 24; // get a day before the due date const scheduledDate = new Date(input.dueDate.getTime() - DAY_TO_MS * 1); const invoiceData = {..}; //omitted for brevity await sendInvoice(invoiceData); //after the invoice is sent, schedule the reminder await scheduleReminder({ ...invoiceData, scheduledDate, }); }

Agora, se eu tentar enviar uma fatura por e-mail, devo receber um lembrete 20 segundos depois, pois estou no ambiente de desenvolvimento.

Parte 4: Cancelar um lembrete

Finalmente, todos os recursos estão prontos. Porém, estou com um problema, e se um cliente tivesse pago antes da data marcada para o lembrete de pagamento? Atualmente, o e-mail de lembrete ainda será enviado. Essa não é uma ótima experiência do usuário e potencialmente um cliente confuso. Felizmente, o Courier tem um recurso de cancelamento de automação.


Vamos adicionar a função cancelAutomationWorkflow que pode cancelar qualquer fluxo de trabalho de automação em src/lib/courier.ts .

 export const cancelAutomationWorkflow = async ({ cancelation_token, }: { cancelation_token: string; }) => { const { runId } = await courierClient.automations.invokeAdHocAutomation({ automation: { // define a cancel action, that sends a cancelation_token steps: [{ action: 'cancel', cancelation_token }], }, }); return runId; };

O que é um cancelation_token? É um token exclusivo que pode ser definido para um fluxo de trabalho de automação, portanto, pode ser cancelado enviando uma ação cancel com um cancelation_token correspondente.


Adicione cancelation_token a scheduleReminder , eu uso o ID da fatura como um token.

 // src/lib/courier.ts export const scheduleReminder = async (..) => { // ...omitted for brevity const { runId } = await courierClient.automations.invokeAdHocAutomation({ automation: { // add cancelation token here cancelation_token: `${invoiceId}-reminder`, steps: [ { action: 'delay', until: delayUntilDate.toISOString() }, // ... omitted for brevity

Chamarei cancelAutomationWorkflow quando o status de uma fatura for atualizado para PAID no ponto de extremidade updateStatus trpc.

 // src/server/trpc/router/invoice.ts updateStatus: protectedProcedure .input(..) // omitted for brevity .mutation(async ({ ctx, input }) => { const { invoiceId, status } = input; // update an invoice's status in database const updatedInvoice = await ctx.prisma.invoice.update({ where: { id: invoiceId }, data: { status }, }); // cancel payment reminder automation workflow if the status is paid. if (updatedInvoice.status === 'PAID') { //call the cancel workflow to cancel the payment reminder for matching cancelation_token. await cancelAutomationWorkflow({ cancelation_token: `${invoiceId}-reminder`, }); } return updatedStatus; }),

Aqui está a interface do usuário em funcionamento.

Parte 5: Tratamento de erros

Uma observação importante ao fazer solicitações de rede é que há possibilidades de solicitações/erros com falha. Quero lidar com o erro lançando-o para o cliente, para que possa ser refletido na interface do usuário.


Em caso de erro, a API Courier gera um erro com o tipo CourierHttpClientError por padrão. Também terei o valor de retorno de todas as funções em src/lib/courier.ts consistente com o formato abaixo.

 // On Success type SuccessResponse = { data: any, error: null } // On Error type ErrorResponse = { data: any, error: string }

Agora, posso lidar com erros adicionando um bloco try-catch a todas as funções em src/lib/courier.ts .

 try { // ..function code // modified return example return { data: runId, error: null }; } catch (error) { // make sure it's an error from Courier if (error instanceof CourierHttpClientError) { return { data: error.data, error: error.message }; } else { return { data: null, error: "Something went wrong!" }; } }

Vamos ver um exemplo de manipulação no endpoint sendEmail trpc.

 // src/server/trpc/router/invoice.ts const { error: sendError } = await sendInvoice(..); if (sendError) throw new TRPCClientError(sendError); const { error: scheduleError } = await scheduleReminder(..); if (scheduleError) throw new TRPCClientError(scheduleError);

Parte 6: Ir para produção

Agora que todos os templates estão prontos, irei copiar todos os assets do ambiente de teste para produção. Aqui está um exemplo.

Conclusão

Finalmente, todos os recursos são integrados ao Courier. Passamos por um fluxo de trabalho de integração da API Courier a um aplicativo Next.js. Embora seja em Next.js e trpc, o fluxo de trabalho será praticamente o mesmo com qualquer outra tecnologia. Espero que agora você possa integrar o Courier em seu aplicativo sozinho.


Comece agora: https://app.courier.com/signup

Sobre o autor

Sou Fazza Razaq Amiarso, um desenvolvedor web full-stack da Indonésia. Também sou um entusiasta do código aberto. Adoro compartilhar meu conhecimento e aprendizado em meu blog . Eu ocasionalmente ajudo outros desenvolvedores no FrontendMentor em meu tempo livre.


Conecte-se comigo no LinkedIn .

Links Rápidos

🔗 Documentos de correio

🔗 Contribua com Invoys

🔗 Invoys Motivation


Publicado também aqui .