- Регистрация
- 14.05.16
- Сообщения
- 11.398
- Реакции
- 501
- Репутация
- 0
Как давно вы платили на веб-сайте в один клик с помощью Google Pay, Apple Pay или заранее заданной в браузере картой?
У меня такое получается редко.
Даже наоборот: каждый новый интернет-магазин предлагает мне очередную формочку. А я должен каждый раз покорно искать свою карту, чтобы перепечатать данные с нее на сайт. На следующий день я захочу оплатить что-нибудь в другом магазине и повторю этот процесс.
Это не очень удобно. Особенно когда знаешь об альтернативе: в последние пару лет стандарт Payment Request API позволяет легко решать эту проблему в современных браузерах.
Давайте разберемся, почему его не используют, и попробуем упростить работу с ним.
О чем речь?
А вот так — в Safari при оплате отпечатком пальца через Apple Pay:
Это не только быстро, но и функционально: окно позволяет выводить информацию по всему заказу и по отдельным товарам и услугам внутри него, позволяет уточнить информацию о клиенте и детали по доставке. Все это кастомизируется при создании запроса, хотя и удобство у предоставляемого API довольно спорное.
Как использовать в Angular?
Angular не предоставляет абстракций для использования Payment Request API. Самый безопасный путь использования из коробки в Angular: достать Document из механизма Dependency Injection, получить из него объект Window и работать с window.PaymentRequest.
import {DOCUMENT} from '@angular/common';
import {Inject, Injectable} from '@angular/core';
@Injectable()
export class PaymentService {
constructor(
@Inject(DOCUMENT)
private readonly documentRef: Document,
) {}
pay(
methodData: PaymentMethodData[],
details: PaymentDetailsInit,
options: PaymentOptions = {},
): Promise
{
if (
this.documentRef.defaultView === null ||
!('PaymentRequest' in this.documentRef.defaultView)
) {
return Promise.reject(new Error('PaymentRequest is not supported'));
}
const gateway = new PaymentRequest(methodData, details, options);
return gateway
.canMakePayment()
.then(canPay =>
canPay
? gateway.show()
: Promise.reject(
new Error('Payment Request cannot make the payment'),
),
);
}
}
Если использовать Payment Request напрямую, то появляются все проблемы неявных зависимостей: тестировать код становится тяжелее, в SSR приложение взрывается, потому что Payment Request не существует. Надеемся на глобальный объект без каких-либо абстракций.
Мы можем взять токен WINDOW из
export const PAYMENT_REQUEST_SUPPORT = new InjectionToken(
'Is Payment Request Api supported?',
{
factory: () => !!inject(WINDOW).PaymentRequest,
},
);
export class PaymentRequestService {
constructor(
@Inject(PAYMENT_REQUEST_SUPPORT) private readonly supported: boolean,
...
) {}
request(...): Promise
{
if (!this.supported) {
return Promise.reject(
new Error('Payment Request is not supported in your browser'),
);
}
...
}
Давайте писать в стиле Angular
С описанным выше подходом мы можем достаточно безопасно работать с платежами, но удобство работы все еще остается на том же уровне «голого» API-браузера: мы вызываем метод с тремя параметрами, собираем множество данных воедино и приводим их к нужному формату, чтобы наконец вызвать метод платежа.
Но в мире Ангуляра мы привыкли к удобным абстракциям: механизму внедрения зависимостей, сервисам, директивам и стримам. Давайте посмотрим на декларативное решение, которое позволяет сделать использование Payment Request API быстрее и проще:
В этом примере корзина представляет собой вот такой код:
{{ cartItem.label }} ({{ cartItem.amount.value }} {{ cartItem.amount.currency }})
Total: {{ totalSum }} ₽
Buy
Все работает благодаря трем директивам:
Так мы получаем простой и удобный интерфейс для открытия платежа и обработки его результата. Причем работает он по всем канонам Angular Way.
Сами директивы связаны довольно простым образом:
@Directive({
selector: '[waPayment][paymentTotal]',
})
export class PaymentDirective implements PaymentDetailsInit {
...
@ContentChildren(PaymentItemDirective)
set paymentItems(items: QueryList
) {
this.displayItems = items.toArray();
}
displayItems?: PaymentItem[];
}
@Directive({
selector: '[waPaymentSubmit]',
})
export class PaymentSubmitDirective {
@Output()
waPaymentSubmit: Observable
;
@Output()
waPaymentError: Observable;
constructor(
@Inject(PaymentDirective) paymentHost: PaymentDetailsInit,
@Inject(PaymentRequestService) paymentRequest: PaymentRequestService,
@Inject(ElementRef) {nativeElement}: ElementRef,
@Inject(PAYMENT_METHODS) methods: PaymentMethodData[],
@Inject(PAYMENT_OPTIONS) options: PaymentOptions,
) {
const requests$ = fromEvent(nativeElement, 'click').pipe(
switchMap(() =>
from(paymentRequest.request({...paymentHost}, methods, options)).pipe(
catchError(error => of(error)),
),
),
share(),
);
this.waPaymentSubmit = requests$.pipe(filter(response => !isError(response)));
this.waPaymentError = requests$.pipe(filter(isError));
}
}
Готовое решение
Все описанные идеи мы собрали и реализовали в библиотеке
Это готовое решение, которое позволяет работать с Payment Request API безопасно и быстро как через сервис, так и через директивы в описанном выше формате.
Эту библиотеку мы опубликовали и поддерживаем от @ng-web-apis — опенсорсной группы, специализирующейся на реализации легких Angular-оберток для нативных Web API, преимущественно в декларативном стиле. На нашем сайте
У меня такое получается редко.
Даже наоборот: каждый новый интернет-магазин предлагает мне очередную формочку. А я должен каждый раз покорно искать свою карту, чтобы перепечатать данные с нее на сайт. На следующий день я захочу оплатить что-нибудь в другом магазине и повторю этот процесс.
Это не очень удобно. Особенно когда знаешь об альтернативе: в последние пару лет стандарт Payment Request API позволяет легко решать эту проблему в современных браузерах.
Давайте разберемся, почему его не используют, и попробуем упростить работу с ним.
О чем речь?
You must be registered for see links
во всех современных браузерах реализован стандарт
You must be registered for see links
. Он позволяет вызвать модальное окно в браузере, через которое пользователь сможет провести платеж в считанные секунды. Вот так это может выглядеть в Chrome с обычной карточкой из браузера:А вот так — в Safari при оплате отпечатком пальца через Apple Pay:
Это не только быстро, но и функционально: окно позволяет выводить информацию по всему заказу и по отдельным товарам и услугам внутри него, позволяет уточнить информацию о клиенте и детали по доставке. Все это кастомизируется при создании запроса, хотя и удобство у предоставляемого API довольно спорное.
Как использовать в Angular?
Angular не предоставляет абстракций для использования Payment Request API. Самый безопасный путь использования из коробки в Angular: достать Document из механизма Dependency Injection, получить из него объект Window и работать с window.PaymentRequest.
import {DOCUMENT} from '@angular/common';
import {Inject, Injectable} from '@angular/core';
@Injectable()
export class PaymentService {
constructor(
@Inject(DOCUMENT)
private readonly documentRef: Document,
) {}
pay(
methodData: PaymentMethodData[],
details: PaymentDetailsInit,
options: PaymentOptions = {},
): Promise
{
if (
this.documentRef.defaultView === null ||
!('PaymentRequest' in this.documentRef.defaultView)
) {
return Promise.reject(new Error('PaymentRequest is not supported'));
}
const gateway = new PaymentRequest(methodData, details, options);
return gateway
.canMakePayment()
.then(canPay =>
canPay
? gateway.show()
: Promise.reject(
new Error('Payment Request cannot make the payment'),
),
);
}
}
Если использовать Payment Request напрямую, то появляются все проблемы неявных зависимостей: тестировать код становится тяжелее, в SSR приложение взрывается, потому что Payment Request не существует. Надеемся на глобальный объект без каких-либо абстракций.
Мы можем взять токен WINDOW из
You must be registered for see links
, чтобы безопасно получить глобальный объект из DI. Теперь добавим новый PAYMENT_REQUEST_SUPPORT. Он позволит проверять поддержку Payment Request API перед его использованием, и теперь у нас никогда не произойдет случайного вызова API в среде, которая его не поддерживает.export const PAYMENT_REQUEST_SUPPORT = new InjectionToken(
'Is Payment Request Api supported?',
{
factory: () => !!inject(WINDOW).PaymentRequest,
},
);
export class PaymentRequestService {
constructor(
@Inject(PAYMENT_REQUEST_SUPPORT) private readonly supported: boolean,
...
) {}
request(...): Promise
{
if (!this.supported) {
return Promise.reject(
new Error('Payment Request is not supported in your browser'),
);
}
...
}
Давайте писать в стиле Angular
С описанным выше подходом мы можем достаточно безопасно работать с платежами, но удобство работы все еще остается на том же уровне «голого» API-браузера: мы вызываем метод с тремя параметрами, собираем множество данных воедино и приводим их к нужному формату, чтобы наконец вызвать метод платежа.
Но в мире Ангуляра мы привыкли к удобным абстракциям: механизму внедрения зависимостей, сервисам, директивам и стримам. Давайте посмотрим на декларативное решение, которое позволяет сделать использование Payment Request API быстрее и проще:
В этом примере корзина представляет собой вот такой код:
{{ cartItem.label }} ({{ cartItem.amount.value }} {{ cartItem.amount.currency }})
Total: {{ totalSum }} ₽
Buy
Все работает благодаря трем директивам:
-
You must be registered for see linksдиректива, которая определяет область отдельного платежа в шаблоне и принимает в себя объектYou must be registered for see linksс информацией о названии платежа и его итоговой суммой
- Каждый товар в корзине — директива
You must be registered for see links. Инпуты этой директивы позволяют собрать объектYou must be registered for see linksкаждого отдельного товара декларативно.
- Нажатие на кнопку запускает модальное окно Payment Request API в браузере. Ответом модального окна может быть
You must be registered for see linksили ошибка. ДирективаYou must be registered for see linksпозволяет отлавливать оба этих исхода обычными ангуляровскими аутпутами.
Так мы получаем простой и удобный интерфейс для открытия платежа и обработки его результата. Причем работает он по всем канонам Angular Way.
Сами директивы связаны довольно простым образом:
- Директива платежа собирает все товары внутри себя с помощью ContentChildren и имплементирует PaymentDetailsInit — один из обязательных аргументов при работе с Payment Request API.
@Directive({
selector: '[waPayment][paymentTotal]',
})
export class PaymentDirective implements PaymentDetailsInit {
...
@ContentChildren(PaymentItemDirective)
set paymentItems(items: QueryList
) {
this.displayItems = items.toArray();
}
displayItems?: PaymentItem[];
}
- Директива-аутпут, которая отслеживает клики по кнопке и эмитит итоговый результат платежа, вытаскивает директиву платежа из дерева Dependency Injection, а также методы платежей и дополнительные опции, которые задаются DI-токенами.
@Directive({
selector: '[waPaymentSubmit]',
})
export class PaymentSubmitDirective {
@Output()
waPaymentSubmit: Observable
;
@Output()
waPaymentError: Observable;
constructor(
@Inject(PaymentDirective) paymentHost: PaymentDetailsInit,
@Inject(PaymentRequestService) paymentRequest: PaymentRequestService,
@Inject(ElementRef) {nativeElement}: ElementRef,
@Inject(PAYMENT_METHODS) methods: PaymentMethodData[],
@Inject(PAYMENT_OPTIONS) options: PaymentOptions,
) {
const requests$ = fromEvent(nativeElement, 'click').pipe(
switchMap(() =>
from(paymentRequest.request({...paymentHost}, methods, options)).pipe(
catchError(error => of(error)),
),
),
share(),
);
this.waPaymentSubmit = requests$.pipe(filter(response => !isError(response)));
this.waPaymentError = requests$.pipe(filter(isError));
}
}
Готовое решение
Все описанные идеи мы собрали и реализовали в библиотеке
You must be registered for see links
:-
You must be registered for see links
-
You must be registered for see links, с которого сделаны скриншоты и гифки в этой статье.
Это готовое решение, которое позволяет работать с Payment Request API безопасно и быстро как через сервис, так и через директивы в описанном выше формате.
Эту библиотеку мы опубликовали и поддерживаем от @ng-web-apis — опенсорсной группы, специализирующейся на реализации легких Angular-оберток для нативных Web API, преимущественно в декларативном стиле. На нашем сайте
You must be registered for see links
, которые не поставляются в Angular из коробки, но могут заинтересовать вас.