Deklaratives Einkaufen im Internet mit Zahlungsanforderungs-API und Angular

Wie lange haben Sie auf der Website mit einem Klick mit Google Pay, Apple Pay oder einer vordefinierten Karte in Ihrem Browser bezahlt?

Ich verstehe es selten.

Ganz im Gegenteil: Jeder neue Online-Shop bietet mir eine andere Form. Und ich muss jedes Mal pflichtbewusst nach meiner Karte suchen, um die Daten von dort auf die Website zu drucken. Am nächsten Tag möchte ich für etwas in einem anderen Geschäft bezahlen und diesen Vorgang wiederholen.

Dies ist nicht sehr praktisch. Besonders wenn Sie eine Alternative kennen: In den letzten Jahren hat es der API-Standard für Zahlungsanforderungen leicht gemacht, dieses Problem in modernen Browsern zu lösen.

Lassen Sie uns verstehen, warum es nicht verwendet wird, und versuchen, die Arbeit damit zu vereinfachen.



Worüber redest du?


Fast alle modernen Browser implementieren den API- Standard für Zahlungsanforderungen . Sie können im Browser ein modales Fenster aufrufen, über das der Benutzer innerhalb von Sekunden eine Zahlung vornehmen kann. So kann es in Chrome mit einer normalen Karte aus dem Browser aussehen:



Und hier ist es - in Safari beim Bezahlen mit einem Fingerabdruck über Apple Pay:



Es ist nicht nur schnell, sondern auch funktional: Im Fenster können Sie Informationen zur gesamten Bestellung sowie zu einzelnen Waren und Dienstleistungen anzeigen Mit ihm können Sie Kundeninformationen und Lieferdetails klären. All dies wird beim Erstellen der Anforderung angepasst, obwohl die Bequemlichkeit der bereitgestellten API eher umstritten ist.

Wie in Angular verwenden?


Angular bietet keine Abstraktionen für die Verwendung der Zahlungsanforderungs-API. Die sicherste Möglichkeit, es aus dem Feld in Angular zu verwenden, besteht darin, das Dokument aus dem Abhängigkeitsinjektionsmechanismus abzurufen, das Window-Objekt daraus abzurufen und mit window.PaymentRequest zu arbeiten.

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

Wenn Sie die Zahlungsanforderung direkt verwenden, treten alle Probleme impliziter Abhängigkeiten auf: Es wird schwieriger, den Code zu testen, die Anwendung explodiert in der SSR, da die Zahlungsanforderung nicht vorhanden ist. Wir hoffen auf ein globales Objekt ohne Abstraktionen.

Wir können das WINDOW- Token von @ ng-web-apis / common verwenden , um das globale Objekt sicher von DI abzurufen . Fügen Sie nun einen neuen PAYMENT_REQUEST_SUPPORT hinzu . Damit können Sie die Unterstützung der Zahlungsanforderungs-API überprüfen, bevor Sie sie verwenden. Jetzt werden wir die API niemals versehentlich in einer Umgebung aufrufen, die sie nicht unterstützt.

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

Schreiben wir im Winkelstil


Mit dem oben beschriebenen Ansatz können wir ziemlich sicher mit Zahlungen arbeiten, aber die Benutzerfreundlichkeit bleibt auf dem Niveau eines nackten API-Browsers: Wir rufen eine Methode mit drei Parametern auf, sammeln viele Daten zusammen und bringen sie in das gewünschte Format, um sie schließlich aufzurufen Bezahlverfahren.

In der Welt von Angular sind wir jedoch an bequeme Abstraktionen gewöhnt: einen Mechanismus zur Abhängigkeitsinjektion, Dienste, Anweisungen und Streams. Schauen wir uns eine deklarative Lösung an, die die Verwendung der Zahlungsanforderungs-API schneller und einfacher macht:



In diesem Beispiel sieht der Warenkorb folgendermaßen aus:

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

Alles funktioniert dank drei Richtlinien:


So erhalten wir eine einfache und bequeme Oberfläche zum Öffnen einer Zahlung und zum Verarbeiten des Ergebnisses. Und es funktioniert nach allen Kanonen von Angular Way.

Die Richtlinien selbst sind auf ziemlich einfache Weise miteinander verbunden:

  • Die Zahlungsanweisung sammelt alle Waren in sich mit ContentChildren und implementiert PaymentDetailsInit - eines der erforderlichen Argumente bei der Arbeit mit der Zahlungsanforderungs-API.

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

  • Die Ausgabeanweisung, die Klicks auf eine Schaltfläche verfolgt und das endgültige Zahlungsergebnis ausgibt, ruft die Zahlungsanweisung aus dem Baum "Abhängigkeitsinjektion" sowie Zahlungsmethoden und zusätzliche Optionen ab, die von DI-Token festgelegt werden.

@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));
   }
}

Schlüsselfertige Lösung


Wir haben alle in der Bibliothek @ ng-web-apis / Zahlungsanforderung beschriebenen Ideen gesammelt und umgesetzt :


Dies ist eine schlüsselfertige Lösung, mit der Sie sicher und schnell mit der Zahlungsanforderungs-API sowohl über den Dienst als auch über Anweisungen im oben beschriebenen Format arbeiten können.

Wir haben diese Bibliothek von @ ng-web-apis veröffentlicht und unterstützt, einer Open-Source-Gruppe, die sich auf die Implementierung von leichten Angular-Wrappern für native Web-APIs spezialisiert hat, hauptsächlich in deklarativem Stil. Es gibt andere API-Implementierungen auf unserer Website , die in Angular nicht sofort geliefert werden, aber für Sie von Interesse sein können.

All Articles