Compras declarativas na Internet com API de solicitação de pagamento e Angular

Quanto tempo você paga no site em um clique usando o Google Pay, Apple Pay ou um cartão predefinido no seu navegador?

Eu raramente entendo.

Muito pelo contrário: toda nova loja online me oferece outro molde. E devo sempre procurar obedientemente meu cartão para reimprimir os dados dele no site. No dia seguinte, vou querer pagar por algo em outra loja e repetir esse processo.

Isso não é muito conveniente. Especialmente quando você conhece uma alternativa: nos últimos dois anos, o padrão da API de solicitação de pagamento facilita a solução desse problema nos navegadores modernos.

Vamos entender por que não é usado e tentar simplificar o trabalho com ele.



Do que você está falando?


Quase todos os navegadores modernos implementam o padrão da API de solicitação de pagamento . Ele permite que você chame uma janela modal no navegador através da qual o usuário pode efetuar um pagamento em questão de segundos. É assim que ele pode parecer no Chrome com um cartão normal do navegador:



e aqui está - no Safari ao pagar com uma impressão digital via Apple Pay:



não é apenas rápido, mas também funcional: a janela permite exibir informações sobre todo o pedido e sobre bens e serviços individuais permite esclarecer as informações do cliente e os detalhes da entrega. Tudo isso é personalizado ao criar a solicitação, embora a conveniência da API fornecida seja bastante controversa.

Como usar em Angular?


Angular não fornece abstrações para usar a API de solicitação de pagamento. A maneira mais segura de usá-lo na caixa do Angular é obter o documento do mecanismo de injeção de dependência, obter o objeto Window e trabalhar com 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<PaymentResponse> {
       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'),
                     ),
           );
   }
}

Se você usar a Solicitação de Pagamento diretamente, todos os problemas das dependências implícitas aparecerão: o teste do código fica mais difícil, o aplicativo explode no SSR porque a Solicitação de Pagamento não existe. Esperamos um objeto global sem abstrações.

Podemos pegar o token do WINDOW em @ ng-web-apis / common para obter com segurança o objeto global do DI. Agora adicione um novo PAYMENT_REQUEST_SUPPORT . Ele permitirá que você verifique o suporte da API de solicitação de pagamento antes de usá-la, e agora nunca teremos uma chamada acidental para a API em um ambiente que não a suporte.

export const PAYMENT_REQUEST_SUPPORT = new InjectionToken<boolean>(
   'Is Payment Request Api supported?',
   {
       factory: () => !!inject(WINDOW).PaymentRequest,
   },
);

export class PaymentRequestService {
   constructor(
       @Inject(PAYMENT_REQUEST_SUPPORT) private readonly supported: boolean,
       ...
    ) {}
 
    request(...): Promise<PaymentResponse> {
       if (!this.supported) {
           return Promise.reject(
               new Error('Payment Request is not supported in your browser'),
           );
       } 
      ...
   }

Vamos escrever em estilo Angular


Com a abordagem descrita acima, podemos trabalhar com pagamentos com bastante segurança, mas a usabilidade ainda permanece no mesmo nível de um navegador de API: chamamos um método com três parâmetros, coletamos muitos dados e os trazemos para o formato desejado para finalmente chamar Forma de pagamento.

Mas no mundo da Angular, estamos acostumados a abstrações convenientes: um mecanismo de injeção de dependência, serviços, diretivas e fluxos. Vejamos uma solução declarativa que torna o uso da API de solicitação de pagamento mais rápido e fácil:



neste exemplo, a cesta é assim:

<div waPayment [paymentTotal]="total">
   <div
       *ngFor="let cartItem of shippingCart"
       waPaymentItem
       [paymentLabel]="cartItem.label"
       [paymentAmount]="cartItem.amount"
   >
       {{ cartItem.label }} ({{ cartItem.amount.value }} {{ cartItem.amount.currency }})
   </div>
 
   <b>Total:</b>  {{ totalSum }} ₽
 
   <button
       [disabled]="shippingCart.length === 0"
       (waPaymentSubmit)="onPayment($event)"
       (waPaymentError)="onPaymentError($event)"
   >
       Buy
   </button>
</div>

Tudo funciona graças a três diretivas:


Portanto, obtemos uma interface simples e conveniente para abrir um pagamento e processar seu resultado. E funciona de acordo com todos os cânones do Caminho Angular.

As próprias diretivas estão conectadas de uma maneira bastante simples:

  • A diretiva de pagamento coleta todas as mercadorias dentro de si, usando ContentChildren, e implementa PaymentDetailsInit - um dos argumentos necessários ao trabalhar com a API de Solicitação de Pagamento.

@Directive({
   selector: '[waPayment][paymentTotal]',
})
export class PaymentDirective implements PaymentDetailsInit {
   ...
   @ContentChildren(PaymentItemDirective)
   set paymentItems(items: QueryList<PaymentItem>) {
       this.displayItems = items.toArray();
   }
 
   displayItems?: PaymentItem[];
}

  • A diretiva de saída, que rastreia cliques em um botão e emite o resultado final do pagamento, retira a diretiva de pagamento da árvore Injeção de Dependências, bem como métodos de pagamento e opções adicionais definidas por tokens DI.

@Directive({
   selector: '[waPaymentSubmit]',
})
export class PaymentSubmitDirective {
   @Output()
   waPaymentSubmit: Observable<PaymentResponse>;
 
   @Output()
   waPaymentError: Observable<Error | DOMException>;
 
   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));
   }
}

Solução chave na mão


Reunimos e implementamos todas as idéias descritas na biblioteca @ ng-web-apis / payment-request :


Esta é uma solução pronta para uso que permite que você trabalhe com a API de solicitação de pagamento com segurança e rapidez, através do serviço e das diretivas no formato descrito acima.

Publicamos e suportamos esta biblioteca do @ ng-web-apis, um grupo de código aberto especializado na implementação de wrappers Angular leves para APIs da Web nativas, principalmente em estilo declarativo. Existem outras implementações de API em nosso site que não são entregues em Angular imediatamente, mas podem lhe interessar.

All Articles