Apple Pay и Google Pay в веб-приложении: что было реально в 2016 году (Россия)
Коротко: Payment Request API (W3C, 2016) - единый интерфейс для Apple Pay, Android Pay и сохранённых карт. Chrome 53+ на Android поддерживал его. Safari на iOS 10+ поддерживал Apple Pay. В России в 2016 году поддержку имели Яндекс.Касса, Сбербанк Онлайн и несколько банков. Для реального использования - нужен банк-эквайер с поддержкой.
Что такое Payment Request API
// W3C Payment Request API - стандартизованный интерфейс оплаты в браузере
// Браузер показывает нативный диалог оплаты
// Пользователь не вводит данные карты - использует сохранённые в устройстве
// Поддержка в 2016:
// Chrome 53+ on Android - Google Pay / сохранённые карты
// Safari 10+ on iOS/macOS - Apple Pay (только Apple устройства)
// Edge - Microsoft Pay (позже)
// Firefox - не поддерживал
if (!window.PaymentRequest) {
// Браузер не поддерживает Payment Request API
// Показать обычную форму карты
showTraditionalCheckout();
return;
}
Интеграция Payment Request API
// checkout.js - Payment Request API для веб-магазина
async function startPayment(cart) {
// Методы оплаты
const paymentMethods = [
{
supportedMethods: 'basic-card', // Стандартные банковские карты
data: {
supportedNetworks: ['visa', 'mastercard', 'mir'],
supportedTypes: ['debit', 'credit']
}
},
// Android Pay (2016 name, later renamed Google Pay)
{
supportedMethods: 'https://android.com/pay',
data: {
merchantName: 'Мой Магазин',
merchantId: 'your-merchant-id',
environment: 'PRODUCTION', // или 'TEST'
allowedCardNetworks: ['VISA', 'MASTERCARD'],
paymentMethodTokenizationParameters: {
tokenizationType: 'PAYMENT_GATEWAY',
parameters: {
gateway: 'yandexkassa', // Ваш эквайер
gatewayMerchantId: 'your-ykassa-id'
}
}
}
},
// Apple Pay
{
supportedMethods: 'https://apple.com/apple-pay',
data: {
version: 3,
merchantIdentifier: 'merchant.ru.myshop',
merchantCapabilities: ['supports3DS'],
supportedNetworks: ['visa', 'masterCard'],
countryCode: 'RU'
}
}
];
// Что показывать в диалоге оплаты
const paymentDetails = {
id: 'order-' + cart.orderId,
displayItems: cart.items.map(item => ({
label: item.name,
amount: { currency: 'RUB', value: String(item.price) }
})),
shippingOptions: [
{
id: 'courier',
label: 'Курьер (1 день)',
amount: { currency: 'RUB', value: '300' },
selected: true,
},
{
id: 'pickup',
label: 'Самовывоз',
amount: { currency: 'RUB', value: '0' },
}
],
total: {
label: 'Итого',
amount: { currency: 'RUB', value: String(cart.total + 300) }
}
};
const options = {
requestShipping: true,
requestPayerName: true,
requestPayerEmail: true,
requestPayerPhone: true,
shippingType: 'delivery',
};
try {
const request = new PaymentRequest(paymentMethods, paymentDetails, options);
// Обновить итог при смене способа доставки
request.addEventListener('shippingoptionchange', event => {
const selectedOption = request.shippingOption;
const shippingCost = selectedOption === 'pickup' ? 0 : 300;
event.updateWith({
...paymentDetails,
total: {
label: 'Итого',
amount: { currency: 'RUB', value: String(cart.total + shippingCost) }
}
});
});
// Проверить что хотя бы один метод поддерживается
const canMakePayment = await request.canMakePayment();
if (!canMakePayment) {
showTraditionalCheckout();
return;
}
// Показать диалог
const paymentResponse = await request.show();
// Отправить платёжные данные на сервер
const serverResponse = await processPaymentOnServer(paymentResponse);
if (serverResponse.success) {
paymentResponse.complete('success');
redirectToOrderSuccess(serverResponse.orderId);
} else {
paymentResponse.complete('fail');
showError('Ошибка оплаты: ' + serverResponse.error);
}
} catch (error) {
if (error.name === 'AbortError') {
console.log('Пользователь отменил оплату');
} else {
console.error('Payment Request error:', error);
showTraditionalCheckout();
}
}
}
async function processPaymentOnServer(paymentResponse) {
const response = await fetch('/api/process-payment', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
methodName: paymentResponse.methodName,
details: paymentResponse.details, // Токен или данные карты
payerName: paymentResponse.payerName,
payerEmail: paymentResponse.payerEmail,
payerPhone: paymentResponse.payerPhone,
shippingAddress: paymentResponse.shippingAddress,
shippingOption: paymentResponse.shippingOption,
})
});
return response.json();
}
Apple Pay Domain Verification
# Для Apple Pay требуется верификация домена
# Создать файл в корне сайта:
mkdir -p .well-known
# Скачать файл с Apple Developer Portal
# Поместить в: .well-known/apple-developer-merchantid-domain-association
# nginx: раздавать файл без расширения
location = /.well-known/apple-developer-merchantid-domain-association {
default_type text/plain;
root /var/www/html;
}
Серверная обработка Apple Pay токена
<?php
// ProcessPaymentController.php
public function process(Request $request): JsonResponse {
$methodName = $request->input('methodName');
$details = $request->input('details');
switch ($methodName) {
case 'https://apple.com/apple-pay':
// Apple Pay: details содержит зашифрованный токен
// Передаём в платёжный шлюз (Яндекс.Касса, Сбербанк)
$result = $this->yandexKassa->chargeApplePay($details['token']);
break;
case 'https://android.com/pay':
// Android Pay: details содержит tokenized payment data
$result = $this->yandexKassa->chargeAndroidPay($details['paymentToken']);
break;
case 'basic-card':
// Обычная карта
$result = $this->yandexKassa->chargeCard([
'cardNumber' => $details['cardNumber'],
'cardExpiry' => $details['cardExpiryMonth'] . '/' . $details['cardExpiryYear'],
'cardCVC' => $details['cardSecurityCode'],
]);
break;
default:
return response()->json(['success' => false, 'error' => 'Unknown payment method']);
}
return response()->json([
'success' => $result['status'] === 'success',
'orderId' => $result['orderId'] ?? null,
'error' => $result['error'] ?? null,
]);
}
Реальность 2016 года в России
| Метод | Браузерная поддержка | Банков-эквайеров с поддержкой |
|---|---|---|
| Apple Pay Web | Safari 10+ | Яндекс.Касса, ВТБ, Сбербанк |
| Android Pay | Chrome 53+ Android | Яндекс.Касса |
| basic-card (Payment Request API) | Chrome 53+, Edge | Все через Яндекс.Кассу |
| Обычная форма | Все | Все |
Мы внедрили Payment Request API в конце 2016 года. Конверсия на устройствах с поддержкой выросла на 18%: нативный диалог проще, чем 10-полевая форма карты.
Наибольший эффект - на мобильных устройствах. Набирать данные карты на телефоне сложно. Нажать кнопку Face ID/отпечаток - легко.