Internet declarative shopping dengan API Permintaan Pembayaran dan Angular

Berapa lama Anda membayar di situs web dalam satu klik menggunakan Google Pay, Apple Pay atau kartu yang telah ditentukan di browser Anda?

Saya jarang mendapatkannya.

Justru sebaliknya: setiap toko online baru menawarkan saya cetakan lain. Dan saya harus setiap waktu dengan patuh mencari kartu saya untuk mencetak ulang data dari itu ke situs. Hari berikutnya saya akan ingin membayar sesuatu di toko lain dan ulangi proses ini.

Ini sangat tidak nyaman. Terutama ketika Anda tahu tentang suatu alternatif: dalam beberapa tahun terakhir, standar API Permintaan Pembayaran memudahkan untuk menyelesaikan masalah ini di browser modern.

Mari kita pahami mengapa itu tidak digunakan, dan cobalah untuk menyederhanakan pekerjaan dengannya.



Apa yang kamu bicarakan?


Hampir semua browser modern menerapkan standar API Permintaan Pembayaran . Ini memungkinkan Anda untuk memanggil jendela modal di browser tempat pengguna dapat melakukan pembayaran dalam hitungan detik. Ini adalah bagaimana ia dapat terlihat di Chrome dengan kartu reguler dari browser:



Dan ini dia di Safari ketika membayar dengan sidik jari melalui Apple Pay:



Tidak hanya cepat, tetapi juga fungsional: jendela memungkinkan Anda untuk menampilkan informasi tentang seluruh pesanan dan pada masing-masing barang dan jasa di dalam dia, memungkinkan Anda untuk mengklarifikasi informasi pelanggan dan detail pengiriman. Semua ini disesuaikan saat membuat permintaan, meskipun kenyamanan API yang disediakan agak kontroversial.

Bagaimana cara menggunakan di Angular?


Angular tidak menyediakan abstraksi untuk menggunakan API Permintaan Pembayaran. Cara paling aman untuk menggunakannya dari kotak di Angular adalah untuk mendapatkan Dokumen dari mekanisme Injeksi Ketergantungan, dapatkan objek Window darinya dan bekerja dengan 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'),
                     ),
           );
   }
}

Jika Anda menggunakan Permintaan Pembayaran secara langsung, maka semua masalah dependensi implisit muncul: semakin sulit untuk menguji kode, aplikasi meledak di SSR karena Permintaan Pembayaran tidak ada. Kami berharap untuk objek global tanpa abstraksi.

Kita dapat mengambil token WINDOW dari @ ng-web-apis / common untuk mendapatkan objek global dengan aman dari DI. Sekarang tambahkan PAYMENT_REQUEST_SUPPORT baru . Ini akan memungkinkan Anda untuk memeriksa dukungan API Permintaan Pembayaran sebelum menggunakannya, dan sekarang kami tidak akan pernah memiliki panggilan API yang tidak disengaja di lingkungan yang tidak mendukungnya.

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

Mari kita menulis dengan gaya Angular


Dengan pendekatan yang dijelaskan di atas, kami dapat bekerja dengan pembayaran dengan cukup aman, tetapi kegunaannya masih tetap pada tingkat yang sama dengan browser API kosong: kami memanggil metode dengan tiga parameter, mengumpulkan banyak data bersama-sama dan membawanya ke format yang diinginkan untuk akhirnya memanggil Cara Pembayaran.

Tetapi di dunia Angular, kita terbiasa dengan abstraksi yang nyaman: mekanisme injeksi ketergantungan, layanan, arahan, dan stream. Mari kita lihat solusi deklaratif yang menjadikan penggunaan API Permintaan Pembayaran lebih cepat dan lebih mudah:



Dalam contoh ini, keranjangnya seperti ini:

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

Semuanya bekerja berkat tiga arahan:


Jadi kami mendapatkan antarmuka yang sederhana dan nyaman untuk membuka pembayaran dan memproses hasilnya. Dan itu bekerja sesuai dengan semua kanon Angular Way.

Arahan itu sendiri terhubung dengan cara yang agak sederhana:

  • Arahan pembayaran mengumpulkan semua barang di dalam dirinya menggunakan ContentChildren dan mengimplementasikan PaymentDetailsInit - salah satu argumen yang diperlukan saat bekerja dengan API Permintaan Pembayaran.

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

  • Arahan keluaran, yang melacak klik pada tombol dan memancarkan hasil pembayaran akhir, menarik keluar arahan pembayaran dari pohon Injeksi Ketergantungan, serta metode pembayaran dan opsi tambahan yang ditetapkan oleh token 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));
   }
}

Solusi turnkey


Kami telah mengumpulkan dan menerapkan semua ide yang dijelaskan di perpustakaan @ ng-web-apis / payment-request :


Ini adalah solusi turnkey yang memungkinkan Anda untuk bekerja dengan API Permintaan Pembayaran dengan aman dan cepat baik melalui layanan maupun melalui arahan dalam format yang dijelaskan di atas.

Kami telah menerbitkan dan mendukung perpustakaan ini dari @ ng-web-apis, grup sumber terbuka yang mengkhususkan diri dalam penerapan pembungkus sudut ringan untuk API Web asli, terutama dalam gaya deklaratif. Ada implementasi API lain di situs kami yang tidak dikirim dalam Angular out of the box, tetapi mungkin menarik bagi Anda.

All Articles