Płatności Stripe dla platformy Flutter

Marcin Sawicki

Stripe jest ogólnoświatową platformą obsługującą płatności online w 43 krajach, również w Polsce. Niewątpliwą zaletą platformy jest mnogość opcji konfiguracyjnych.

Istnieją trzy rodzaje kont (standard, express i custom) różniących się sposobem integracji i liczbą dostępnych opcji.

Omawiany przykład to prosty market place, w którym jeden użytkownik zamawia wykonanie usługi od drugiego użytkownika. Po potwierdzeniu wykonania usługi środki przekazywane są między użytkownikami, a prowizja trafia na konto Firmy. Opisywana integracja uwzględnia developement własnego backendu ściśle dopasowanego do procesów biznesowych aplikacji.

Na potrzeby przykładu wybraliśmy konto express, które udostępnia wiele opcji niedostępnych dla konta standard i jednocześnie potrzebuje względnie niedużego nakładu pracy podczas integracji.

Stripe a Flutter

Oficjalnie wsparcie Stripe obejmuje natywnie platformy Android, iOS i web w języku JavaScript. Mimo tego, że oficjalnie brak wsparcia dla platformy Flutter wykorzystanie Stripe jest możliwe przy pewnym nakładzie pracy. Zbiór dostępnych pod Flutterem bibliotek jest niewielki. Zawiera dwie pozycje, obie dostępne na portalu pub.dev, oficjalnym repozytorium bibliotek dla platformy Flutter:

  • stripe-payment – biblioteka będąca wrapperem istniejącej i popularnej bibliotek tipsi-stripe, napisanej dla React Native
  • stripe_sdk – biblioteka napisana we Flutterze

Obie mają zalety i wady. Pierwsza wykorzystuje sprawdzone rozwiązania wdrożone przez wiele firm jednak używa natywnych komponentów do wprowadzania danych płatności co jest dużym minusem w momencie, gdy istnieje potrzeba niestandardowego zagnieżdżenia komponentów w strukturze widgetów Fluttera. Tego minusa pozbawiona jest druga biblioteka. Jest to nowa biblioteka, cały czas rozwijana. Posiada bogatsze opcje w porównaniu do stripe_payment jednak użytkownicy zgłaszali problemy ze stabilnością – Flutter rozwija się dynamicznie, biblioteka także. Ze względu na niższe ryzyko i krótszy czas integracji polecamy wykorzystanie biblioteki stripe_payment.

Założenia projektowe

Dane Stripe pozwalające obciążyć rachunek zleceniodawcy przechowywane są po stronie backendu. Aplikacja mobilna zleca obciążanie rachunku oraz obsługuje dodanie nowej metody płatności.

Proces wykorzystujący płatności poprzez platformę Stripe składa się z następujących kroków:

  • Zleceniodawca składa zamówienie i określa kwotę, jaką chciałby zapłacić za usługę.
  • Aplikacja zleca backendowi obciążenie rachunku zleceniodawcy.
  • Backend sprawdza czy zleceniodawca ma zdefiniowaną przynajmniej jedną metodę płatności (w przypadku kilku metod płatności jedna z nich jest ustawiona jako domyślna i ona będzie użyta).
    • Jeżeli istnieje metoda płatności to backend próbuje ją wykorzystać w celu realizacji płatności. O wyniku informuje aplikację mobilną.
    • Jeżeli nie istnieje metoda płatności to backend informuje aplikację mobilną o konieczności dodania nowej metody płatności.
  • Aplikacja mobilna wyświetla komunikat o powodzeniu lub niepowodzeniu przeprowadzonej płatności a w przypadku braku zdefiniowanej metody płatności wyświetla natywny komponent biblioteki stripe_payment do dodawania nowej metody płatności.
  • Po dodaniu nowej metody płatności backend próbuje obciążyć zleceniodawcę zdefiniowaną kwotą i o wyniku informuje aplikację mobilną.

Implementacja

Pierwszym krokiem jest dołączenie pluginu stripe_payment do projektu Flutter. W pliku pubspec.yaml do sekcji dependencies należy dołączyć nazwę pluginu wraz z numerem wersji.

dependencies:
    stripe_sdk: 1.0.7

Wywołanie polecenia flutter pub get spowoduje pobranie i zainstalowanie pluginu. Dodatkowo należy upewnić się, że projekt ma włączone wsparcie dla AndroidX. W pliku android/gradle.properties powinien znaleźć się wpis:

android.useAndroidX=true
android.enableJetifier=true

Z podanego wyżej algorytmu płatności wynika, że plugin stripe_payment jest używany do dodania nowej metody płatności. Jeżeli ten proces zostanie zakończony z powodzeniem to backend uzyska ze Stripe uwierzytelniony identyfikator nowej metody płatności, który będzie mógł być użyty do obciążenia rachunku zleceniodawcy.

Implementację dodawania nowej metody płatności zaczynamy od zaimportowania pluginu stripe_payment.

import 'package:stripe_payment/stripe_payment.dart';

Kolejnym krokiem jest inicjalizacja pluginu przez podanie klucza uzyskanego podczas konfigurowania konta Stripe. W tym celu wystarczy dodać poniższy kod na początku metody main().

StripePayment.setOptions(StripeOptions(publishableKey: ”stripePublishableKey”));

Kod dodający nową metodę płatności będzie zawarty w jednej asynchronicznej metodzie, która zwraca enum z wynikiem operacji. Proces może zakończyć się powodzeniem, niepowodzeniem lub może być anulowany przez użytkownika.

enum AddNewPaymentResult { SUCCESS, FAIL, CANCELLED }

Future< AddNewPaymentResult > stripeAddNewPayment() async {
    // implement adding new payment process here
}

Pierwszym krokiem jest pobranie z backendu sekretnego jednorazowego krótkotrwałego identyfikatora (tzn. client secret). Backend jako zaufana strona pobiera go ze Stripe i przekazuje do aplikacji mobilnej.

String clientSecret = await apiClient.getClientSecret();

Obiekt apiClient obsługuje komunikację z backendem (budowanie requestów, parsowanie response). Jest to standardowa implementacja klienta http, w której następuje serializacja obiektów do formatu JSON, przesłanie zserializowanych obiektów na backend, odbiór danych z backendu w formacie JSON i deserializacja do postaci obiektów.

Następnie wywołujemy metodę pluginu stripe_payment wyświetlającą natywny komponent z polami do uzupełnienia przez zleceniodawcę. Projekt przykładowy wykorzystuje kartę kredytową jako jedyną dostępną metodę płatności.

PaymentMethod paymentMethod = await StripePayment.paymentRequestWithCardForm(CardFormPaymentRequest());

Metoda paymentRequestWithCardForm() zwraca obiekt reprezentujący nową metodę płatności, która jednak nie jest jeszcze uwierzytelniona przez Stripe. Uwierzytelnienie odbywa się przy pomocy uzyskanego wcześniej identyfikatora client secret.

PaymentIntent paymentIntent = PaymentIntent(clientSecret: clientSecret, paymentMethodId: paymentMethod.id);
SetupIntentResult result = await StripePayment.confirmSetupIntent(paymentIntent);

Obiekt result zawiera informację o powodzeniu lub niepowodzeniu operacji. Jeżeli operacja przebiegła pomyślnie to backend otrzyma dane nowej metody płatności bezpośrednio od Stripe poprzez zarejestrowane webhooki. Dane nowej metody płatności nie trafiają do aplikacji mobilnej.

Użytkownik może anulować operację dodawania nowej metody płatności dlatego musimy umieścić powyższy kod (w szczególności metodę paymentRequestWithCardForm()) w bloku try-catch. Plugin stripe_payment wyświetla natywny komponent dlatego obiektem, który łapiemy w bloku catch będzie PlatformException.

try [
    PaymentMethod paymentMethod = 
    await StripePayment.paymentRequestWithCardForm(CardFormPaymentRequest());
} catch (error) {
    if (error is PlatformException && error.code == 'cancelled')
        return AddNewPaymentResult.CANCELLED;
}

Po złożeniu powyższych fragmentów otrzymujemy kod:

enum AddNewPaymentResult { SUCCESS, FAIL, CANCELLED }

Future<AddNewPaymentResult> stripeAddNewPayment() async {
    try {
        String clientSecret = await stripeRepository.getClientSecret();
        PaymentMethod paymentMethod = await StripePayment.paymentRequestWithCardForm(CardFormPaymentRequest());
        PaymentIntent paymentIntent = PaymentIntent(clientSecret: clientSecret, paymentMethodId: paymentMethod.id);
        SetupIntentResult result = await StripePayment.confirmSetupIntent(paymentIntent);
        return result.status == 'success' ? AddNewPaymentResult.SUCCESS : AddNewPaymentResult.FAIL;
    } catch (error) {
        if (error is PlatformException && error.code == 'cancelled')
            return AddNewPaymentResult.CANCELLED;
        else
            return AddNewPaymentResult.FAIL;
    }
}

Po wywołaniu metody stripeAddNewPayment() warto poczekać kilka sekund przed próbą wykorzystania nowej metody płatności. Komunikacja Stripe à backend odbywa się przez webhooki i mogą pojawić się niewielkie opóźnienia.

Wygląd komponentu do dodawania nowej metody płatności jest zależny od platformy. Poniżej na lewy obrazku przedstawiona jest wersja dla Androida, na prawym obrazku wersja dla iOS.

stripe-flutter1

Podsumowanie

Stripe jest platformą bogatą w opcje a jednocześnie niewymagającą dużego nakładu prac integracyjnych. Jeżeli dodamy do tego ogólnoświatowy zasięg otrzymamy ciekawą propozycję obsługi płatności online.

Poprzedni post