Розробка кастомних ендпоінтів API Payload CMS

Наша компанія займається розробкою, підтримкою та обслуговуванням сайтів будь-якої складності. Від простих односторінкових сайтів до масштабних кластерних систем, побудованих на мікро сервісах. Досвід розробників підтверджено сертифікатами від вендорів.
Розробка та обслуговування будь-яких видів сайтів:
Інформаційні сайти або веб-програми
Сайти візитки, landing page, корпоративні сайти, онлайн каталоги, квіз, промо-сайти, блоги, ресурси новин, інформаційні портали, форуми, агрегатори
Сайти або веб-програми електронної комерції
Інтернет-магазини, B2B-портали, маркетплейси, онлайн-обмінники, кешбек-сайти, біржі, дропшиппінг-платформи, парсери товарів
Веб-програми для управління бізнес-процесами
CRM-системи, ERP-системи, корпоративні портали, системи управління виробництвом, парсери інформації
Сайти або веб-програми електронних послуг
Дошки оголошень, онлайн-школи, онлайн-кінотеатри, конструктори сайтів, портали надання електронних послуг, відеохостинги, тематичні портали

Це лише деякі з технічних типів сайтів, з якими ми працюємо, і кожен із них може мати свої специфічні особливості та функціональність, а також бути адаптованим під конкретні потреби та цілі клієнта.

Пропоновані послуги
Показано 1 з 1 послугУсі 2065 послуг
Розробка кастомних ендпоінтів API Payload CMS
Середня
~2-3 робочих дні
Часті питання
Наші компетенції:
Етапи розробки
Останні роботи
  • image_website-b2b-advance_0.png
    Розробка сайту компанії B2B ADVANCE
    1262
  • image_web-applications_feedme_466_0.webp
    Розробка веб-додатків для компанії FEEDME
    1171
  • image_websites_belfingroup_462_0.webp
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    874
  • image_ecommerce_furnoro_435_0.webp
    Розробка інтернет магазину для компанії FURNORO
    1094
  • image_crm_enviok_479_0.webp
    Розробка веб-додатків для компанії Enviok
    831
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Розробка веб-сайту для компанії ФІКСПЕР
    851

Кастомні API-енпойнти Payload CMS

Payload автоматично генерує REST API та GraphQL для всіх колекцій. Кастомні енпойнти потрібні для бізнес-операцій, які не вписуються в стандартний CRUD: оформлення замовлення, інтеграція з платіжною системою, вебхуки від зовнішніх сервісів.

Енпойнти на рівні колекції

// collections/Orders.ts
import type { CollectionConfig, PayloadRequest } from 'payload/types'
import { Response } from 'express'

const Orders: CollectionConfig = {
  slug: 'orders',
  endpoints: [
    // POST /api/orders/checkout
    {
      path: '/checkout',
      method: 'post',
      handler: async (req: PayloadRequest, res: Response) => {
        const { items, customerEmail, shippingAddress } = req.body

        // Валідація
        if (!items?.length) {
          return res.status(400).json({ error: 'Items required' })
        }

        // Підрахунок підсумку
        let total = 0
        const enrichedItems = await Promise.all(
          items.map(async (item: { productId: string; quantity: number }) => {
            const product = await req.payload.findByID({
              collection: 'products',
              id: item.productId,
            })
            total += product.price * item.quantity
            return {
              product: item.productId,
              quantity: item.quantity,
              price: product.price,
              name: product.name,
            }
          })
        )

        // Створення замовлення
        const order = await req.payload.create({
          collection: 'orders',
          data: {
            items: enrichedItems,
            total,
            customerEmail,
            shippingAddress,
            status: 'pending',
          },
          req,
        })

        // Створення платіжної сесії
        const paymentSession = await stripeClient.checkout.sessions.create({
          payment_method_types: ['card'],
          line_items: enrichedItems.map(item => ({
            price_data: {
              currency: 'rub',
              product_data: { name: item.name },
              unit_amount: Math.round(item.price * 100),
            },
            quantity: item.quantity,
          })),
          mode: 'payment',
          success_url: `${process.env.FRONTEND_URL}/order/${order.id}/success`,
          cancel_url: `${process.env.FRONTEND_URL}/cart`,
          metadata: { orderId: String(order.id) },
        })

        return res.json({
          orderId: order.id,
          paymentUrl: paymentSession.url,
        })
      },
    },

    // POST /api/orders/webhook/stripe
    {
      path: '/webhook/stripe',
      method: 'post',
      handler: async (req: PayloadRequest, res: Response) => {
        const sig = req.headers['stripe-signature'] as string

        let event
        try {
          event = stripe.webhooks.constructEvent(
            req.rawBody,
            sig,
            process.env.STRIPE_WEBHOOK_SECRET!
          )
        } catch (err) {
          return res.status(400).json({ error: 'Webhook signature verification failed' })
        }

        if (event.type === 'checkout.session.completed') {
          const session = event.data.object as Stripe.Checkout.Session
          const orderId = session.metadata?.orderId

          await req.payload.update({
            collection: 'orders',
            id: orderId!,
            data: { status: 'paid', paymentId: session.payment_intent as string },
            req,
          })
        }

        return res.json({ received: true })
      },
    },
  ],
}

Глобальні енпойнти в payload.config.ts

// payload.config.ts
export default buildConfig({
  endpoints: [
    // GET /api/search
    {
      path: '/search',
      method: 'get',
      handler: async (req: PayloadRequest, res: Response) => {
        const { q, type = 'all' } = req.query as { q: string; type: string }

        if (!q || q.length < 2) {
          return res.json({ docs: [], totalDocs: 0 })
        }

        const collections = type === 'all' ? ['posts', 'products', 'pages'] : [type]

        const results = await Promise.all(
          collections.map(collection =>
            req.payload.find({
              collection: collection as any,
              where: {
                or: [
                  { title: { like: q } },
                  { description: { like: q } },
                ],
              },
              limit: 5,
            })
          )
        )

        const docs = results.flatMap((r, i) =>
          r.docs.map(doc => ({ ...doc, _collection: collections[i] }))
        )

        return res.json({ docs, totalDocs: docs.length })
      },
    },

    // POST /api/contact
    {
      path: '/contact',
      method: 'post',
      handler: async (req: PayloadRequest, res: Response) => {
        const { name, email, message } = req.body

        if (!name || !email || !message) {
          return res.status(400).json({ error: 'All fields required' })
        }

        // Збереження запиту
        await req.payload.create({
          collection: 'inquiries',
          data: { name, email, message, status: 'new' },
        })

        // Повідомлення адміністраторам
        await emailService.send({
          to: process.env.ADMIN_EMAIL!,
          subject: `New inquiry from ${name}`,
          text: `From: ${name} <${email}>\n\n${message}`,
        })

        return res.json({ success: true })
      },
    },
  ],
})

Middleware для API

// Логування запитів до API
{
  path: '/admin-action',
  method: 'post',
  handler: async (req: PayloadRequest, res: Response) => {
    // Перевірка аутентифікації
    if (!req.user) {
      return res.status(401).json({ error: 'Unauthorized' })
    }

    // Перевірка ролі
    if (req.user.role !== 'admin') {
      return res.status(403).json({ error: 'Forbidden' })
    }

    // Логування дії
    await req.payload.create({
      collection: 'audit-logs',
      data: {
        action: 'admin-action',
        user: req.user.id,
        timestamp: new Date().toISOString(),
        data: req.body,
      },
    })

    // Виконання дії
    return res.json({ success: true })
  },
}

Виклик кастомних енпойнтів

// З Next.js Server Action
'use server'

export async function checkoutAction(items: CartItem[]) {
  const response = await fetch(`${process.env.NEXT_PUBLIC_SERVER_URL}/api/orders/checkout`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ items, customerEmail: '[email protected]' }),
  })

  if (!response.ok) throw new Error('Checkout failed')
  return response.json()
}

Графік

Розробка 3–5 кастомних енпойнтів з інтеграцією платіжної системи та вебхуками — 2–3 дні.