Разработка формы с интеграцией оплаты на сайте

Наша компания занимается разработкой, поддержкой и обслуживанием сайтов любой сложности. От простых одностраничных сайтов до масштабных кластерных систем построенных на микро сервисах. Опыт разработчиков подтвержден сертификатами от вендоров.

Разработка и обслуживание любых видов сайтов:

Информационные сайты или веб-приложения
Сайты визитки, landing page, корпоративные сайты, онлайн каталоги, квиз, промо-сайты, блоги, новостные ресурсы, информационные порталы, форумы, агрегаторы
Сайты или веб-приложения электронной коммерции
Интернет-магазины, B2B-порталы, маркетплейсы, онлайн-обменники, кэшбэк-сайты, биржи, дропшиппинг-платформы, парсеры товаров
Веб-приложения для управления бизнес-процессами
CRM-системы, ERP-системы, корпоративные порталы, системы управления производством, парсеры информации
Сайты или веб-приложения электронных услуг
Доски объявлений, онлайн-школы, онлайн-кинотеатры, конструкторы сайтов, порталы предоставления электронных услуг, видеохостинги, тематические порталы

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

Предлагаемые услуги
Показано 1 из 1 услугВсе 2065 услуг
Разработка формы с интеграцией оплаты на сайте
Средняя
~3-5 рабочих дней
Часто задаваемые вопросы

Наши компетенции:

Этапы разработки

Последние работы

  • 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

Разработка формы с интеграцией оплаты на сайте

Форма оплаты — точка, где пользователь превращается в покупателя или уходит навсегда. Здесь нет места для редиректов на сторонние страницы с чужим дизайном, для непонятных ошибок без объяснений, для полей, которые сбрасываются после неудачной попытки. Задача — встроить приём платежей прямо в интерфейс сайта так, чтобы пользователь не покидал страницу и не терял контекст.

Архитектура встроенной формы

Современные платёжные шлюзы предоставляют два варианта интеграции: редирект на страницу шлюза и встроенный виджет (embedded form). Второй вариант предпочтителен для большинства сайтов, потому что сохраняет визуальный контекст и повышает доверие.

Типичная схема для Stripe:

// Инициализация Stripe Elements
const stripe = Stripe('pk_live_...');
const elements = stripe.elements({
  appearance: {
    theme: 'flat',
    variables: {
      colorPrimary: '#0f172a',
      fontFamily: 'Inter, sans-serif',
    },
  },
});

const paymentElement = elements.create('payment', {
  layout: { type: 'tabs', defaultCollapsed: false },
});
paymentElement.mount('#payment-element');

// Обработка сабмита
const form = document.getElementById('payment-form');
form.addEventListener('submit', async (e) => {
  e.preventDefault();
  const { error } = await stripe.confirmPayment({
    elements,
    confirmParams: {
      return_url: 'https://example.com/order/complete',
    },
  });
  if (error) {
    showError(error.message);
  }
});

Для российского рынка чаще используется ЮКасса (бывший Яндекс.Касса) или CloudPayments. CloudPayments предоставляет собственный SDK для встроенной формы:

var widget = new cp.CloudPayments();
widget.charge(
  {
    publicId: 'pk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
    description: 'Заказ #12345',
    amount: 4990,
    currency: 'RUB',
    invoiceId: '12345',
    email: '[email protected]',
    skin: 'mini',
    data: { orderId: '12345', userId: 42 },
  },
  function (options) {
    // success callback
    updateOrderStatus(options.invoiceId, 'paid');
  },
  function (reason, options) {
    // fail callback
    logPaymentError(reason, options);
  }
);

Серверная часть: создание платёжного намерения

Никакая логика суммы не должна идти со стороны клиента. Браузер не доверяет сумме из hidden-поля — её подменяют. Сервер создаёт платёжное намерение с реальной суммой из базы данных:

// Laravel — создание PaymentIntent через Stripe
use Stripe\StripeClient;

public function createPaymentIntent(Request $request): JsonResponse
{
    $order = Order::findOrFail($request->order_id);

    // Проверка, что заказ принадлежит текущему пользователю
    abort_if($order->user_id !== auth()->id(), 403);

    $stripe = new StripeClient(config('services.stripe.secret'));

    $intent = $stripe->paymentIntents->create([
        'amount'   => $order->total_cents, // в копейках/центах
        'currency' => 'rub',
        'metadata' => [
            'order_id' => $order->id,
            'user_id'  => $order->user_id,
        ],
        'automatic_payment_methods' => ['enabled' => true],
    ]);

    return response()->json([
        'client_secret' => $intent->client_secret,
    ]);
}

Клиент получает только client_secret — он не содержит суммы, не может быть использован для изменения параметров.

Webhook и подтверждение оплаты

Форма на фронтенде сообщает об успехе, но это не гарантия. Деньги могут зависнуть, банк может отклонить транзакцию уже после редиректа. Единственный надёжный источник истины — webhook от платёжного шлюза.

// Обработчик webhook Stripe
public function handleWebhook(Request $request): Response
{
    $payload = $request->getContent();
    $sigHeader = $request->header('Stripe-Signature');

    try {
        $event = \Stripe\Webhook::constructEvent(
            $payload,
            $sigHeader,
            config('services.stripe.webhook_secret')
        );
    } catch (\Stripe\Exception\SignatureVerificationException $e) {
        return response('Invalid signature', 400);
    }

    match ($event->type) {
        'payment_intent.succeeded'       => $this->handlePaymentSucceeded($event->data->object),
        'payment_intent.payment_failed'  => $this->handlePaymentFailed($event->data->object),
        'charge.dispute.created'         => $this->handleDispute($event->data->object),
        default                          => null,
    };

    return response('OK', 200);
}

private function handlePaymentSucceeded(\Stripe\PaymentIntent $intent): void
{
    $order = Order::where('stripe_payment_intent', $intent->id)->firstOrFail();
    $order->update(['status' => 'paid', 'paid_at' => now()]);

    // Отправка чека, запуск доставки, уведомление
    dispatch(new SendReceiptJob($order));
    dispatch(new InitiateShippingJob($order));
}

Для ЮКасса аналогичная схема через HMAC-подпись:

$body = $request->getContent();
$key = config('services.yookassa.secret_key');
// ЮКасса не использует подпись для webhook — проверяем через API
$notification = new \YooKassa\Model\Notification\NotificationSucceeded(
    json_decode($body, true)
);
$payment = $notification->getObject();

UX-детали, которые влияют на конверсию

Валидация в реальном времени. Номер карты должен форматироваться группами по 4 цифры прямо в процессе ввода. Срок действия — автоматически добавлять /. Если Luhn-проверка не проходит — сообщать сразу, не ждать сабмита.

Сохранение прогресса. Если пользователь заполнил email, имя, адрес — и форма оплаты упала с ошибкой, все поля должны остаться. Очищать только CVV (требование PCI DSS).

Индикация состояния. Кнопка «Оплатить» должна показывать спиннер во время запроса и блокироваться от повторного нажатия. Двойная оплата — реальная проблема.

Мобильная клавиатура. Поле номера карты должно открывать числовую клавиатуру (inputmode="numeric"), а не буквенную. Мелочь, которую забывают в половине случаев.

<input
  type="text"
  inputmode="numeric"
  autocomplete="cc-number"
  placeholder="0000 0000 0000 0000"
  pattern="[0-9\s]{13,19}"
/>

Безопасность и соответствие PCI DSS

Данные карты никогда не должны проходить через ваш сервер — только через iframe платёжного шлюза или его JavaScript-библиотеку. Это называется уровень PCI DSS SAQ A (самый простой для мерчанта).

Если данные карты хотя бы на миллисекунду оказались в вашем приложении — вы автоматически переходите на уровень SAQ D с ежегодным аудитом, пентестом и несколькими сотнями обязательных требований.

Content Security Policy для страниц с формой оплаты:

Content-Security-Policy:
  default-src 'self';
  script-src 'self' https://js.stripe.com https://widget.cloudpayments.ru;
  frame-src https://js.stripe.com https://widget.cloudpayments.ru;
  connect-src 'self' https://api.stripe.com;

Поддержка нескольких методов оплаты

Stripe Payment Element из коробки показывает карты, Apple Pay, Google Pay, SEPA, Klarna и ещё десяток методов — автоматически, в зависимости от страны пользователя и его браузера.

CloudPayments поддерживает карты, СБП (Система Быстрых Платежей), Tinkoff Pay. СБП особенно полезен: меньше комиссия, высокая конверсия на мобильных устройствах, нет необходимости вводить данные карты.

Для настройки Apple Pay через CloudPayments нужна верификация домена — разместить файл на /.well-known/apple-developer-merchantid-domain-association. Это обязательное требование Apple.

Сроки и этапы

Типичная интеграция для одного шлюза с тестовым окружением занимает 3–5 рабочих дней. Это включает: настройку аккаунта мерчанта, серверную часть (создание платёжного намерения, webhook), клиентскую форму, тестирование на тестовых картах, переключение на боевой режим.

Добавление второго шлюза (например, для резервирования) — ещё 2–3 дня на логику маршрутизации платежей.

Интеграция фискализации (54-ФЗ, отправка чеков через ОФД) — отдельная задача, 2–4 дня, зависит от того, есть ли у шлюза встроенная поддержка (у ЮКасса и CloudPayments есть).