Назад в Центр знаний
Платформа

Payment Request API: согласие и безопасный запасной путь

Используйте PaymentRequest с активацией пользователя, безопасным контекстом, обработкой отмены и рабочей альтернативой без сбора платёжных данных.

BotBrowser Team

Документация

Нужна структурированная документация по теме Платформа?

Эта статья относится к редакционной библиотеке. Для пошаговой настройки, справки и постоянных обновлений переходите сразу в соответствующий раздел docs.

Payment Request может открыть платёжное окно под управлением браузера, но завершение show() не доказывает ни авторизацию, ни поступление денег продавцу. BotBrowser воспроизводит собственные фикстуры и проверяет видимые состояния; он не предоставляет реальный платёжный инструмент, не подтверждает банковскую операцию и не обрабатывает учётные данные. Границы должны быть явными: запускайте запрос свежим действием пользователя в защищённом контексте, не принимайте проверку возможности за согласие, отдельно обрабатывайте отмену и тайм-аут, используйте complete() только для финального состояния окна и сохраняйте обычный путь оформления, если API или платёжные данные недоступны.

Поток Payment Request от действия пользователя к сокращённому результату или запасному оформлению.

Кратко

  • new PaymentRequest(methodData, details, options) описывает методы и детали операции; он не запрашивает секреты и не гарантирует наличие конкретного метода.
  • canMakePayment() даёт лишь подсказку о возможности. true не означает согласие, авторизацию, наличие средств и не гарантирует успех show().
  • Вызывайте show() по видимому клику или клавиатурному действию в защищённом контексте. Считайте AbortError, NotAllowedError, тайм-ауты и неподдерживаемые методы обычными состояниями продукта.
  • После проверки ответа закрывайте окно через complete('success'), complete('fail') или complete('unknown'). complete() сообщает результат интерфейса, а не расчёт.
  • Никогда не записывайте платёжные секреты, ответ, данные карты или полные исключения. Оставляйте ручную форму, запрос счёта или другой путь без PaymentRequest.

Содержание

Разделяйте возможность и согласие

Payment Request API позволяет странице описать способы оплаты и попросить браузер посредничать в выборе пользователя. Это не замена платёжному процессору и не способ прочитать карту или кошелёк. Браузер показывает окно, пользователь решает продолжить, а продавцу по-прежнему нужны серверная авторизация, идемпотентность, защита от мошенничества и результат расчёта от провайдера.

canMakePayment() помогает решить, показывать ли быстрый способ оплаты. В зависимости от политик браузера, зарегистрированных методов, защиты приватности и реализации он может вернуть false, true или отказ, причём результат меняется между посещениями. Не скрывайте с его помощью полное оформление, не выводите сведения о кошельке и не создавайте постоянный профиль возможности. Запрашивайте проверку после выбора пути оплаты и храните результат недолго.

Проверка функции также должна иметь чёткие границы:

const paymentSupported = window.isSecureContext && 'PaymentRequest' in window;

if (!paymentSupported) showCheckoutFallback('unsupported');

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

О близкой границе согласия рассказывает статья Web Share и запасной путь: завершённая передача не доказывает принятие данных получателем; границы разрешений Web Share также показывают роль контекста и недавнего действия пользователя. Payment Request ограничен сильнее: ответ может попасть продавцу, но расчёт остаётся ответственностью сервера и процессора. Руководство по защищённым контекстам описывает общую HTTPS-основу мощных функций браузера.

Создайте узкий PaymentRequest

Держите данные метода и детали операции минимальными. Идентификатор сообщает браузеру, какой тип оплаты умеет обрабатывать продавец. details задаёт видимую сумму и необязательные позиции, но не является источником истины. После ответа сервер должен пересчитать итог: цене, налогам, наличию и валюте со страницы доверять нельзя.

const methodData = [
  {
    supportedMethods: 'https://example.test/pay',
    data: { version: 1 },
  },
];

const details = {
  id: 'order-2026-1042',
  total: { label: 'Example order', amount: { currency: 'USD', value: '19.00' } },
  displayItems: [{ label: 'Item', amount: { currency: 'USD', value: '19.00' } }],
};

const options = { requestPayerName: false, requestPayerEmail: false };
const request = new PaymentRequest(methodData, details, options);

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

Пусть каждый объект request относится к одной попытке оформления. При изменении корзины создавайте новый запрос с новым details.id и версией заказа на сервере. Не переиспользуйте объект после тайм-аута или навигации. Короткоживущий операционный токен помогает интерфейсу отклонить поздний ответ от старой корзины.

Запускайте show с активацией

request.show() открывает платёжное окно и возвращает promise с PaymentResponse. Запускайте его непосредственно видимой кнопкой или клавиатурным действием. Не вызывайте при загрузке страницы, из таймера или аналитического обратного вызова и не ждите перед этим работу, которая может израсходовать краткую активацию. Окно также могут заблокировать защищённый контекст, политики браузера, границы iframe или правила платформы.

let activeAttempt = 0;

async function beginPayment() {
  const attempt = ++activeAttempt;
  if (!window.isSecureContext || !('PaymentRequest' in window)) {
    showCheckoutFallback('unsupported');
    return;
  }
  const request = buildRequest();
  showPaymentState('opening');
  try {
    const response = await request.show();
    if (attempt !== activeAttempt) return;
    await handlePaymentResponse(response, request, attempt);
  } catch (error) {
    if (attempt !== activeAttempt) return;
    showPaymentState(error?.name === 'AbortError' ? 'cancelled' : 'rejected');
    showCheckoutFallback(error?.name === 'AbortError' ? 'cancelled' : 'rejected');
  }
}

payButton.addEventListener('click', beginPayment);

На время открытия можно отключить повторные действия, но видимый запасной путь должен остаться. После отмены или отказа возвращайте фокус исходной кнопке. Сообщайте opening, cancelled и fallback через live region, не раскрывая ответ, чтобы пользователь понял следующий шаг без ошибки браузера.

Встроенное оформление требует явного решения по развёртыванию. Политика верхнего уровня или атрибут iframe могут определить доступность оплаты, но делегирование не обходит разрешения и не даёт согласия за пользователя. Отдельно тестируйте верхний origin и встроенный путь; возможность в обычной вкладке не гарантируется во frame другого origin.

Проверьте ответ и завершите окно

PaymentResponse содержит данные метода, запрошенные поля payer и complete(). Проверив структуру, отправляйте серверу только токен, одобренный процессором, по аутентифицированному каналу. Никогда не считайте ответ квитанцией: процессор может отклонить, задержать или позже отозвать авторизацию.

async function handlePaymentResponse(response, request, attempt) {
  showPaymentState('validating');
  let outcome = 'unknown';
  try {
    const result = await postPaymentToken({
      methodName: response.methodName,
      details: response.details,
      orderId: request.id,
      attempt,
    });
    outcome = result.authorized === true ? 'success' : 'fail';
  } catch {
    outcome = 'unknown';
  }
  try {
    await response.complete(outcome);
  } catch {
    // The payment sheet may already have closed; keep the server outcome.
  }
  showPaymentState(outcome);
}

Вызывайте complete() один раз для каждого ответа. success просит браузер показать завершённое состояние, fail означает неудачную попытку, а unknown используется при отсутствии результата сервера. Ни одно значение не доказывает списание, возврат или доставку; достоверное состояние дают ответ процессора и идемпотентная запись заказа.

response.complete() может отклонить вызов, если ответ уже закрыт, браузер завершил окно или документ потерял актуальность. Не превращайте отказ во второе списание. Сохраните результат сервера, предложите новую попытку с новым заказом и не отправляйте один токен дважды.

Классифицируйте отмену, отказ и тайм-аут

Отмена не равна ошибке карты. Пользователь может закрыть окно, передумать или отклонить метод. AbortError обычно означает отмену, но не раскрывает причину. Разделяйте cancelled, rejected, unsupported и processor-failed; сохраняйте корзину и предлагайте ручную форму, не запрашивая платёжные секреты в диагностике.

У тайм-аута должен быть определённый владелец. Страница может задать срок для вызова сервера, но таймер не доказывает, что произошло в окне оплаты. Если запрос сервера истёк после ответа, отметьте unknown, прекратите повторы и проверьте заказ у процессора перед новой попыткой. Поздний обратный вызов не должен перезаписывать новый запуск.

Ошибки метода отличаются от ошибок браузера. Отсутствующий метод может отклонить show() до открытия окна, а политика или заблокированная активация вызвать NotAllowedError. Неверный ответ относится к проверке, отказ процессора относится к серверному этапу. Сохраняйте эти этапы в интерфейсе и поддержке, чтобы из-за политики iframe не просить карту заново.

Используйте ограниченную последовательность: idle, opening, shown, response-received, authorizing, success, fail, unknown, cancelled, rejected, unsupported и fallback. Для каждой попытки записывайте только первый этап сбоя. После закрытия окна не повторяйте show() автоматически и не вызывайте abort() из фоновой очистки, если продукт явно не владеет этим действием отмены.

Данные и запасной путь

Платёжный ответ может содержать чувствительный токен метода, данные payer, ссылку на счёт или непрозрачное значение, которое становится секретом при воспроизведении. Не помещайте response.details, почту payer, фрагменты карты или полные исключения в журналы, URL, снимки, записи сессий, отчёты о сбоях или аналитические измерения. Обезличивание должно охватывать и интеграции за пределами вызова PaymentRequest.

{
  "api": "PaymentRequest",
  "attempt": "pay-7f31",
  "capability": "available",
  "outcome": "cancelled",
  "methodNamePresent": true,
  "paymentDetailsRetained": false,
  "settlement": "not-asserted"
}

Этой записи достаточно, чтобы узнать, открылось ли окно и какой результат показала страница, но не чтобы узнать наличие счёта или списание процессора. Токены должны быть случайными и короткоживущими, диагностические выгрузки должны истекать, а захват для поддержки должен быть явным и проверяемым. Хеширование не делает токен безвредным: стабильное значение всё ещё связывает несколько сеансов.

Сохраните запасной путь и отделите расчёт.

Запасной путь должен быть виден уже на первом экране. Если PaymentRequest отсутствует, canMakePayment() отклонён, show() отменён, метод не поддерживается или ответ процессора неизвестен, оставьте обычную форму, запрос счёта, платёжную ссылку или контактный процесс. Запасной путь сохраняет корзину, клавиатурный доступ, подписи, проверку и дальнейшие инструкции.

Не отключайте запасной путь из-за true от canMakePayment(). Без авторитетного серверного результата процессора не показывайте «платёж завершён» после complete('success'). Не путайте окно браузера с Web Share, Credential Management или интерфейсом кошелька. Payment Request является посредническим интерфейсом оформления, а не хранилищем секретов и не обходом процессора.

В контролируемых фикстурах используйте синтетические идентификаторы методов и вымышленные суммы. Проверяйте отсутствие записанных полей секретов, возврат фокуса после отмены, вызов complete('unknown') при неизвестном результате сервера и доступность ручного пути. Реальные платёжные данные и конечная точка провайдера не должны попадать в автоматизированные фикстуры.

Таблица решений.

Наблюдаемый этапЧто он доказываетБезопасный следующий шаг
API отсутствует или контекст небезопасенPayment Request нельзя запуститьСохраните ручное оформление или счёт
canMakePayment() вернул false или отказПолезной подсказки нетСохраните основное оформление, не выводите данные кошелька
show() отклонён с AbortErrorОкно отменено или закрытоСохраните корзину, запишите cancelled и ждите нового действия
Политика или активация заблокированыБраузер отклонил попыткуПроверьте защищённый контекст, фокус, политику frame и владельца жеста
Получен ответБраузер вернул данные методаСтрого проверьте и отправьте только разрешённые данные токена
Сервер отклонил авторизациюПроцессор не авторизовал попыткуВызовите complete('fail'), объясните запасной путь и не повторяйте отправку
Тайм-аут результата сервераСостояние расчёта неизвестноПри возможности вызовите complete('unknown'), проверьте перед повтором
complete('success') завершёнОкно приняло запрос интерфейсаПоказывайте только авторитетное состояние заказа

Возможности и предел BotBrowser.

В контролируемом контексте BotBrowser можно воспроизвести фикстуру PaymentRequest того же origin с синтетическим методом, проверить ветви возможности и активации, смоделировать отмену или тайм-аут и подтвердить обезличенный видимый результат и доступность запасного пути. Документация расширенных возможностей описывает границы самой страницы. BotBrowser не предоставляет реальный инструмент оплаты, не подтверждает банковские или кошелёчные операции, не доказывает расчёт и не собирает платёжные секреты для теста.

Сохраняйте детерминированную запись фикстуры:

{
  "api": "PaymentRequest",
  "secureContext": true,
  "userActivation": true,
  "canMakePayment": "hint-only",
  "show": "cancelled",
  "complete": "unknown",
  "paymentDetailsRetained": false,
  "fallback": "manual-checkout"
}

Повторяйте тест, меняя только одну переменную: версию браузера, верхний origin, политику iframe, момент активации, синтетический метод, тайм-аут сервера или ветвь отмены. Сравнивайте первое видимое различие. Фикстура доказывает лишь обработку состояния страницы, а не авторизацию платежа или движение средств.

Источники

Практический итог

BotBrowser может воспроизвести синтетический fixture PaymentRequest и проверить активацию, подсказку доступности, отмену и запасной путь, но не предоставляет реальный платёжный инструмент и не доказывает расчёт.

Считайте PaymentRequest только интерфейсом согласия, посредничающим через браузер. Создайте минимальный запрос, вызовите show() свежим действием пользователя в защищённом контексте, проверьте ответ на сервере и оставьте complete() ответственным лишь за состояние окна, без утверждения о расчёте. Разделяйте отмену, отказ, тайм-аут и неподдерживаемый метод, записывайте обезличенный результат и сохраняйте полный путь оформления без PaymentRequest. Контролируемая фикстура BotBrowser доказывает видимые ветви страницы, но не реальное списание.

#Payment Request API#Согласие Пользователя#Защищённый Контекст#Платежи#BotBrowser

Эта статья входит в руководство: Поверхности веб-платформы: карта проверки

Переведите BotBrowser из исследований в продакшн

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