Compras declarativas por Internet con API de solicitud de pago y angular

¿Cuánto tiempo ha pagado en el sitio web con un solo clic usando Google Pay, Apple Pay o una tarjeta predefinida en su navegador?

Raramente lo entiendo.

Todo lo contrario: cada nueva tienda en línea me ofrece otro molde. Y cada vez debo buscar obedientemente mi tarjeta para reimprimir los datos de ella en el sitio. Al día siguiente, querré pagar por algo en otra tienda y repetir este proceso.

Esto no es muy conveniente. Especialmente cuando conoce una alternativa: en los últimos años, el estándar API de solicitud de pago facilita la solución de este problema en los navegadores modernos.

Comprendamos por qué no se usa e intente simplificar el trabajo con él.



¿De qué estás hablando?


Casi todos los navegadores modernos implementan el estándar API de solicitud de pago . Le permite llamar a una ventana modal en el navegador a través de la cual el usuario puede realizar un pago en cuestión de segundos. Así es como puede verse en Chrome con una tarjeta normal desde el navegador:



y aquí está, en Safari cuando paga con una huella digital a través de Apple Pay:



no solo es rápido, sino también funcional: la ventana le permite mostrar información sobre todo el pedido y sobre bienes y servicios individuales dentro él, le permite aclarar la información del cliente y los detalles de entrega. Todo esto se personaliza al crear la solicitud, aunque la conveniencia de la API proporcionada es bastante controvertida.

¿Cómo usar en Angular?


Angular no proporciona abstracciones para utilizar la API de solicitud de pago. La forma más segura de usarlo desde el cuadro en Angular es obtener el Documento del mecanismo de Inyección de Dependencias, obtener el objeto Window y trabajar con 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'),
                     ),
           );
   }
}

Si usa la Solicitud de pago directamente, aparecen todos los problemas de dependencias implícitas: probar el código se vuelve más difícil, la aplicación explota en el SSR porque la Solicitud de pago no existe. Esperamos un objeto global sin abstracciones.

Podemos tomar el token WINDOW de @ ng-web-apis / common para obtener de forma segura el objeto global de DI. Ahora agregue un nuevo PAYMENT_REQUEST_SUPPORT . Le permitirá verificar la compatibilidad de la API de solicitud de pago antes de usarla, y ahora nunca tendremos una llamada accidental a la API en un entorno que no la admite.

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'),
           );
       } 
      ...
   }

Escribamos en estilo angular


Con el enfoque descrito anteriormente, podemos trabajar con pagos de manera bastante segura, pero la usabilidad aún se mantiene al mismo nivel de un navegador API simple: llamamos a un método con tres parámetros, recopilamos una gran cantidad de datos y los llevamos al formato deseado para finalmente llamar método de pago.

Pero en el mundo de Angular, estamos acostumbrados a abstracciones convenientes: un mecanismo de inyección de dependencia, servicios, directivas y flujos. Veamos una solución declarativa que hace que el uso de la API de solicitud de pago sea más rápido y fácil:



en este ejemplo, la cesta es así:

<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>

Todo funciona gracias a tres directivas:


Entonces obtenemos una interfaz simple y conveniente para abrir un pago y procesar su resultado. Y funciona de acuerdo con todos los cánones de Angular Way.

Las directivas mismas están conectadas de una manera bastante simple:

  • La directiva de pago recopila todos los bienes dentro de sí misma utilizando ContentChildren e implementa PaymentDetailsInit, uno de los argumentos necesarios cuando se trabaja con la API de solicitud de pago.

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

  • La directiva de salida, que realiza un seguimiento de los clics en un botón y emite el resultado final del pago, extrae la directiva de pago del árbol de Inyección de dependencias, así como los métodos de pago y las opciones adicionales que establecen los 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));
   }
}

Solución llave en mano


Hemos recopilado e implementado todas las ideas descritas en la biblioteca @ ng-web-apis / payment-request :


Esta es una solución llave en mano que le permite trabajar con la API de solicitud de pago de forma segura y rápida tanto a través del servicio como a través de directivas en el formato descrito anteriormente.

Hemos publicado y apoyado esta biblioteca de @ ng-web-apis, un grupo de código abierto especializado en la implementación de envoltorios angulares livianos para API web nativas, principalmente en un estilo declarativo. Hay otras implementaciones de API en nuestro sitio que no se entregan en Angular de fábrica , pero que pueden interesarle.

All Articles