paint-brush
Gửi hóa đơn và thêm lời nhắc thanh toán trong Next.js với Courier APIby@courier
1,427
1,427

Gửi hóa đơn và thêm lời nhắc thanh toán trong Next.js với Courier API

Courier14m2023/02/14
Read on Terminal Reader

Rất nhiều ứng dụng quản lý hóa đơn mã nguồn mở được xây dựng bằng Laravel. Tôi muốn xây dựng “Giải pháp React” cho các nhà phát triển đã quen thuộc với React và Javascript. Một vấn đề tôi gặp phải khi xây dựng với các dịch vụ trong Node.js là không có trình gửi thư tích hợp sẵn. Vì vậy, tôi đã phải tìm một dịch vụ bên thứ 3 để làm điều đó cho tôi. Trong bài viết này, tôi sẽ tích hợp [Chuyển phát nhanh] để gửi email cho dự án này.
featured image - Gửi hóa đơn và thêm lời nhắc thanh toán trong Next.js với Courier API
Courier HackerNoon profile picture
0-item

Rất nhiều ứng dụng quản lý hóa đơn mã nguồn mở được xây dựng bằng Laravel. Là một nhà phát triển Javascript, tôi muốn xây dựng “Giải pháp React” cho các nhà phát triển đã quen thuộc với React và Javascript.


Một vấn đề tôi gặp phải khi xây dựng với các dịch vụ trong Node.js là không có trình gửi thư tích hợp sẵn. Vì vậy, tôi đã phải tìm một dịch vụ bên thứ 3 để làm điều đó cho tôi. Trong bài viết này, tôi sẽ tích hợp Chuyển phát nhanh để gửi email cho dự án này https://github.com/fazzaamiarso/invoys .

điều kiện tiên quyết

Vì bài viết này không phải là bài viết thông thường của bạn (giống như “hãy ngồi yên và xem tôi làm như thế nào”), nên bạn không bắt buộc phải làm quen với tất cả các công nghệ được sử dụng. Tuy nhiên, việc làm quen với Typescript và Next.js sẽ giúp bạn hiểu nhanh hơn.


Công nghệ trong blog này:


  • Bản đánh máy : loại an toàn và tự động hoàn thành là tốt nhất, phải không?

  • Next.js : khung sẵn sàng sản xuất để xây dựng ứng dụng toàn ngăn xếp, ngay cả đối với người mới bắt đầu.

  • Prisma : một ORM tuyệt vời để làm việc với cơ sở dữ liệu. Chúng tôi sử dụng Prisma vì loại an toàn và tự động hoàn thành, mang lại trải nghiệm tuyệt vời cho nhà phát triển với bản thảo được thêm vào.

  • Trpc : cho phép chúng tôi dễ dàng xây dựng kiểu an toàn từ đầu đến cuối giữa máy khách và máy chủ Next.js của chúng tôi.

  • Courier API: một dịch vụ/nền tảng tuyệt vời để xử lý các thông báo của chúng tôi, chẳng hạn như email, SMS, v.v.


Bạn có thể tìm thấy mã nguồn đầy đủ ở đây để tham khảo.

Bàn thắng

Trước khi xây dựng các tính năng, hãy xác định mục tiêu của chúng tôi.


  1. Gửi liên kết hóa đơn đến email của khách hàng.
  2. Gửi lời nhắc một ngày trước ngày đến hạn của hóa đơn.
  3. Hủy lời nhắc ngày đáo hạn hóa đơn khi hóa đơn đã được thanh toán.
  4. Xử lý lỗi mạng.

Phần 1: Thiết lập nền tảng chuyển phát nhanh

Hãy chuyển đến Bảng điều khiển chuyển phát nhanh. Theo mặc định, nó ở trong môi trường sản xuất. Vì tôi muốn thử nghiệm mọi thứ nên tôi sẽ chuyển sang môi trường thử nghiệm bằng cách nhấp vào menu thả xuống ở góc trên cùng bên phải.


Chúng tôi có thể sao chép tất cả các mẫu sau đó để sản xuất hoặc ngược lại.


Bây giờ, tôi sẽ tạo thương hiệu cho các thông báo qua email của mình.



Tôi sẽ chỉ thêm một logo (lưu ý rằng chiều rộng của logo được cố định là 140px) trên tiêu đề và các liên kết xã hội ở chân trang. Giao diện người dùng thiết kế khá đơn giản, vì vậy đây là kết quả cuối cùng.



Đừng quên xuất bản các thay đổi.

Phần 2: Gửi hóa đơn đến Email

Hiện tại, nút gửi email trên giao diện người dùng không hoạt động.


Tôi sẽ tạo một tệp courier.ts trong src/lib/ để giữ tất cả mã liên quan đến Courier. Ngoài ra, tôi sẽ sử dụng thư viện máy khách node.js chuyển phát nhanh đã trừu tượng hóa tất cả các điểm cuối API Courier thành các chức năng.


Trước khi tôi xây dựng chức năng, hãy tạo thiết kế thông báo email trong Courier's Designer và thiết lập nhà cung cấp Gmail.


Trên trang thiết kế email, chúng ta sẽ thấy rằng thương hiệu được tạo đã được tích hợp sẵn. Sau đó, hãy thiết kế mẫu phù hợp với dữ liệu cần thiết. Đây là kết quả cuối cùng.




Lưu ý rằng giá trị có {} chuyển sang màu xanh lá cây, điều đó có nghĩa là đó là một biến có thể được chèn động. Tôi cũng đặt nút (hoặc hành động) 'Xem hóa đơn' bằng một biến.


Trước khi có thể sử dụng mẫu, tôi cần tạo một sự kiện thử nghiệm bằng cách nhấp vào tab xem trước. Sau đó, nó sẽ hiển thị lời nhắc đặt tên cho sự kiện và đặt data ở định dạng JSON. Trường dữ liệu đó là trường sẽ điền giá trị của các biến {} màu xanh lá cây (dữ liệu cũng có thể được đặt từ mã). Vì đây là một sự kiện thử nghiệm nên tôi sẽ điền vào đó các giá trị tùy ý.


Tiếp theo, tôi sẽ xuất bản mẫu để tôi có thể sử dụng nó. Sau đó, chuyển đến tab gửi. Nó sẽ hiển thị mã cần thiết để gửi email theo chương trình và data sẽ được điền bằng sự kiện thử nghiệm trước đó mà tôi đã tạo.



phụ trợ

Tôi sẽ sao chép bài kiểm tra AUTH_TOKEN vào tệp .env và sao chép đoạn trích vào 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, });

Tạo một chức năng sendInvoice sẽ chịu trách nhiệm gửi email. Để gửi một email từ mã, tôi sử dụng hàm 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 };

Xác định các loại cho chức năng sendInvoice .

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

Bây giờ tôi có thể gửi email, tôi sẽ gọi nó trong điểm cuối sendEmail trpc nằm trong src/server/trpc/router/invoice.ts .


Chỉ cần nhớ rằng điểm cuối trpc là một tuyến API Next.js. Trong trường hợp này, sendEmail sẽ giống như gọi tuyến đường /api/trpc/sendEmail với fetch dưới mui xe. Để được giải thích thêm 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); }),


Đối với những người không quen thuộc với trpc, những gì tôi đã làm giống như xử lý một yêu cầu POST . Hãy chia nhỏ nó ra.


  1. Trpc cách xác định đầu vào yêu cầu từ máy khách bằng cách xác thực với Zod. Ở đây tôi xác định tất cả dữ liệu cần thiết cho hàm 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. Xác định trình xử lý yêu cầu POST (đột biến).
 // 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); }),

giao diện người dùng

Bây giờ, tôi có thể bắt đầu thêm chức năng vào nút gửi email. Tôi sẽ sử dụng hàm trpc.useMutation() là một trình bao bọc mỏng của useMutation` tanstack-query's .


Hãy thêm chức năng đột biến. Khi phản hồi thành công, tôi muốn chúc mừng thành công trên giao diện người dùng.

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

Tôi chỉ có thể sử dụng chức năng này như một trình xử lý nội tuyến, nhưng tôi muốn tạo một trình xử lý mới cho nút.

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

Bây giờ tôi có thể đính kèm trình xử lý vào nút gửi email.

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

Đây là giao diện người dùng đang hoạt động.

Phần 3: Gửi lời nhắc thanh toán

Để lên lịch lời nhắc sẽ được gửi một ngày trước ngày đến hạn của hóa đơn, tôi sẽ sử dụng API tự động hóa của Courier .


Trước tiên, hãy thiết kế mẫu email trong Courier designer. Như tôi đã trải qua quá trình trước đây, đây là kết quả cuối cùng.


Trước khi thêm chức năng, hãy xác định loại cho tham số và cấu trúc lại các loại.

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

Bây giờ, tôi thêm chức năng scheduleReminder vào 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; };

Để gửi lời nhắc, tôi sẽ gọi scheduleReminder sau khi sendInvoice thành công. Hãy sửa đổi điểm cuối 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, }); }

Bây giờ nếu tôi cố gắng gửi hóa đơn qua email, tôi sẽ nhận được lời nhắc sau 20 giây vì tôi đang ở trong môi trường phát triển.

Phần 4: Hủy lời nhắc

Cuối cùng, tất cả các tính năng đã sẵn sàng. Tuy nhiên, tôi gặp một vấn đề, nếu một khách hàng đã thanh toán trước ngày được lên lịch để nhắc nhở thanh toán thì sao? Hiện tại, email nhắc nhở vẫn sẽ được gửi. Đó không phải là trải nghiệm người dùng tuyệt vời và có thể khiến khách hàng bối rối. Rất may, Courier có tính năng hủy tự động.


Hãy thêm hàm cancelAutomationWorkflow có thể hủy mọi quy trình tự động hóa trong 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; };

Cancel_token là gì? Đó là một mã thông báo duy nhất có thể được đặt thành quy trình làm việc tự động hóa, do đó, mã này có thể bị hủy bằng cách gửi một hành động cancel với một cancelation_token phù hợp.


Thêm cancelation_token vào scheduleReminder , tôi sử dụng Id của hóa đơn làm mã thông báo.

 // 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

Tôi sẽ gọi cancelAutomationWorkflow khi trạng thái của hóa đơn được cập nhật thành PAID trong điểm cuối 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; }),

Đây là giao diện người dùng làm việc.

Phần 5: Xử lý lỗi

Một lưu ý quan trọng khi thực hiện các yêu cầu mạng là có khả năng các yêu cầu/lỗi không thành công. Tôi muốn xử lý lỗi bằng cách ném nó cho ứng dụng khách để nó có thể được phản ánh trong giao diện người dùng.


Khi có lỗi, Courier API đưa ra lỗi với loại CourierHttpClientError theo mặc định. Tôi cũng sẽ có giá trị trả về của tất cả các hàm trong src/lib/courier.ts phù hợp với định dạng bên dưới.

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

Bây giờ, tôi có thể xử lý lỗi bằng cách thêm khối try-catch vào tất cả các chức năng trong 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!" }; } }

Hãy xem một ví dụ xử lý trên điểm cuối 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);

Phần 6: Đi đến Sản xuất

Bây giờ tất cả các mẫu đã sẵn sàng, tôi sẽ sao chép tất cả nội dung trong môi trường thử nghiệm sang sản xuất. Đây là một ví dụ.

Phần kết luận

Cuối cùng, tất cả các tính năng được tích hợp với Courier. Chúng ta đã trải qua quy trình tích hợp Courier API vào ứng dụng Next.js. Mặc dù có trong Next.js và trpc, nhưng quy trình làm việc sẽ khá giống với bất kỳ công nghệ nào khác. Tôi hy vọng bây giờ bạn có thể tự tích hợp Courier vào ứng dụng của mình.


Bắt đầu ngay bây giờ: https://app.courier.com/signup

Giới thiệu về tác giả

Tôi là Fazza Razaq Amiarso, một nhà phát triển web toàn diện đến từ Indonesia. Tôi cũng là một người đam mê Mã nguồn mở. Tôi thích chia sẻ kiến thức và học tập của mình trên blog của mình . Tôi thỉnh thoảng giúp các nhà phát triển khác trên FrontendMentor khi rảnh rỗi.


Kết nối với tôi trên LinkedIn .

đường dẫn nhanh

🔗 Tài liệu chuyển phát nhanh

🔗 Đóng góp cho Invoys

🔗 Invoys Động lực


Cũng được xuất bản ở đây .