Przewodnik dla deweloperów aplikacji płatniczych na Androida

Dowiedz się, jak dostosować aplikację do płatności na Androida do płatności internetowych i zapewnić klientom lepsze wrażenia.

Data publikacji: 5 maja 2020 r., ostatnia aktualizacja: 27 maja 2025 r.

Interfejs Payment Request API udostępnia w internecie wbudowany interfejs oparty na przeglądarce, który pozwala użytkownikom łatwiej niż kiedykolwiek wcześniej wpisywać wymagane dane do płatności. Interfejs API może też wywoływać aplikacje do płatności na konkretnej platformie.

Browser Support

  • Chrome: 60.
  • Edge: 15.
  • Firefox: behind a flag.
  • Safari: 11.1.

Source

Proces płatności w aplikacji Google Pay na danej platformie, która korzysta z płatności internetowych.

W porównaniu z wykorzystywaniem tylko komend Androida płatności internetowe zapewniają lepszą integrację z przeglądarką, bezpieczeństwo i wrażenia użytkowników:

  • Aplikacja do płatności uruchamia się jako okno modalne w kontekście strony sprzedawcy.
  • Wdrożenie jest uzupełnieniem Twojej dotychczasowej aplikacji do płatności, dzięki czemu możesz wykorzystać grono użytkowników.
  • Aby zapobiec instalowaniu aplikacji z zewnętrznych źródeł, sprawdzany jest podpis aplikacji do płatności.
  • Aplikacje do płatności mogą obsługiwać wiele form płatności.
  • Można zintegrować dowolną formę płatności, np. kryptowalutę czy przelewy bankowe. Aplikacje do płatności na urządzeniach z Androidem mogą nawet integrować metody, które wymagają dostępu do układu sprzętowego na urządzeniu.

Aby wdrożyć płatności internetowe w aplikacji płatniczej na Androida, wykonaj 4 kroki:

  1. Pozwól sprzedawcom odkryć Twoją aplikację płatniczą.
  2. Poinformuj sprzedawcę, jeśli klient ma zarejestrowany instrument płatniczy (np. kartę kredytową), który jest gotowy do zapłaty.
  3. Pozwól klientowi dokonać płatności.
  4. Sprawdź certyfikat podpisywania rozmówcy.

Aby zobaczyć, jak działają płatności internetowe, zapoznaj się z demonstracją android-web-payment.

Krok 1. Pozwól sprzedawcom znaleźć Twoją aplikację do płatności

W pliku manifestu aplikacji internetowej ustaw właściwość related_applications zgodnie z instrukcjami w artykule Konfigurowanie metody płatności.

Aby sprzedawca mógł korzystać z Twojej aplikacji do płatności, musi użyć interfejsu PaymentRequest API i określić obsługiwaną formę płatności za pomocą identyfikatora formy płatności.

Jeśli masz identyfikator formy płatności, który jest unikalny dla Twojej aplikacji do płatności, możesz skonfigurować własny plik manifestu formy płatności, aby przeglądarki mogły wykryć Twoją aplikację.

Krok 2. Poinformuj sprzedawcę, jeśli klient ma zarejestrowany instrument, którym może zapłacić

Sprzedawca może zadzwonić pod numer hasEnrolledInstrument(), aby zapytać, czy klient może dokonać płatności. Aby uzyskać odpowiedź na to pytanie, możesz zaimplementować usługę IS_READY_TO_PAY jako usługę na Androida.

AndroidManifest.xml

Zadeklaruj usługę za pomocą filtra intencji z działaniem org.chromium.intent.action.IS_READY_TO_PAY.

<service
  android:name=".SampleIsReadyToPayService"
  android:exported="true">
  <intent-filter>
    <action android:name="org.chromium.intent.action.IS_READY_TO_PAY" />
  </intent-filter>
</service>

Usługa IS_READY_TO_PAY jest opcjonalna. Jeśli w aplikacji do płatności nie ma takiego modułu, przeglądarka zakłada, że aplikacja może zawsze dokonywać płatności.

AIDL

Interfejs API usługi IS_READY_TO_PAY jest zdefiniowany w pliku AIDL. Utwórz 2 pliki AIDL z tą zawartością:

org/chromium/IsReadyToPayServiceCallback.aidl

package org.chromium;

interface IsReadyToPayServiceCallback {
    oneway void handleIsReadyToPay(boolean isReadyToPay);
}

org/chromium/IsReadyToPayService.aidl

package org.chromium;

import org.chromium.IsReadyToPayServiceCallback;

interface IsReadyToPayService {
    oneway void isReadyToPay(IsReadyToPayServiceCallback callback, in Bundle parameters);
}

Stosowanie dyrektywy IsReadyToPayService

Najprostsze wdrożenie IsReadyToPayService pokazano w tym przykładzie:

KotlinJava
class SampleIsReadyToPayService : Service() {
    private val binder = object : IsReadyToPayService.Stub() {
        override fun isReadyToPay(callback: IsReadyToPayServiceCallback?, parameters: Bundle?) {
            callback?.handleIsReadyToPay(true)
        }
    }

    override fun onBind(intent: Intent?): IBinder? {
        return binder
    }
}
import org.chromium.IsReadyToPayService;

public class SampleIsReadyToPayService extends Service {
    private final IsReadyToPayService.Stub mBinder =
        new IsReadyToPayService.Stub() {
            @Override
            public void isReadyToPay(IsReadyToPayServiceCallback callback, Bundle parameters) {
                if (callback != null) {
                    callback.handleIsReadyToPay(true);
                }
            }
        };

    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }
}

Odpowiedź

Usługa może wysłać odpowiedź za pomocą metody handleIsReadyToPay(Boolean).

KotlinJava
callback?.handleIsReadyToPay(true)
if (callback != null) {
    callback.handleIsReadyToPay(true);
}

Uprawnienie

Aby sprawdzić, kto dzwoni, możesz użyć metody Binder.getCallingUid(). Pamiętaj, że musisz to zrobić w ramach metody isReadyToPay, a nie onBind, ponieważ system operacyjny Android może przechowywać w pamięci podręcznej i ponownie używać połączenia z usługą, co nie powoduje wywołania metody onBind().

KotlinJava
override fun isReadyToPay(callback: IsReadyToPayServiceCallback?, parameters: Bundle?) {
    try {
        val untrustedPackageName = parameters?.getString("packageName")
        val actualPackageNames = packageManager.getPackagesForUid(Binder.getCallingUid())
        // ...
@Override
public void isReadyToPay(IsReadyToPayServiceCallback callback, Bundle parameters) {
    try {
        String untrustedPackageName = parameters != null
                ? parameters.getString("packageName")
                : null;
        String[] actualPackageNames = packageManager.getPackagesForUid(Binder.getCallingUid());
        // ...

Podczas odbierania wywołań IPC zawsze sprawdzaj parametry wejściowe null. Jest to szczególnie ważne, ponieważ różne wersje lub gałęzie systemu Android mogą zachowywać się w nieoczekiwany sposób i w efekcie prowadzić do błędów.

Funkcja packageManager.getPackagesForUid() zazwyczaj zwraca pojedynczy element, ale Twój kod musi obsługiwać rzadkie przypadki, w których wywołujący używa wielu nazw pakietów. Dzięki temu aplikacja będzie stabilna.

Więcej informacji o sprawdzaniu, czy pakiet wywołujący ma prawidłową sygnaturę, znajdziesz w artykule Weryfikowanie certyfikatu podpisującego.

Parametry

Pakiet parameters został dodany w Chrome 139. Wartość ta powinna zawsze być sprawdzana w porównaniu z wartością null.

Te parametry są przekazywane do usługi w pakiecie parameters:

  • packageName
  • methodNames
  • methodData
  • topLevelOrigin
  • paymentRequestOrigin
  • topLevelCertificateChain

packageName została dodana w Chrome 138. Zanim użyjesz wartości tego parametru, musisz sprawdzić, czy jest on zgodny z parametrem Binder.getCallingUid(). Ta weryfikacja jest niezbędna, ponieważ pakiet parameters jest w pełni kontrolowany przez osobę dzwoniącą, a pakiet Binder.getCallingUid() jest kontrolowany przez system operacyjny Android.

topLevelCertificateChain jest null w WebView i w witrynach bez protokołu https, które są zwykle używane do testowania lokalnego, takich jak http://localhost.

Krok 3. Pozwól klientowi dokonać płatności

Sprzedawca dzwoni pod numer show(), aby uruchomić aplikację do płatności, dzięki której klient może dokonać płatności. Aplikacja do płatności jest wywoływana za pomocą intencjonalnego AndroidaPAY z informacjami o transakcji w parametrach intencjonalnych.

Aplikacja do płatności odpowiada z wartościami methodNamedetails, które są specyficzne dla aplikacji do płatności i nie są widoczne dla przeglądarki. Przeglądarka zamienia ciąg details na słownik JavaScriptu dla sprzedawcy za pomocą deserializacji ciągu JSON, ale nie narzuca żadnych dodatkowych wymagań dotyczących poprawności. Przeglądarka nie zmienia wartości parametru details, który jest przekazywany bezpośrednio do sprzedawcy.

AndroidManifest.xml

Aktywność z filtrem zamiaru PAY powinna mieć tag <meta-data>, który identyfikuje domyślny identyfikator metody płatności aplikacji.

Aby obsługiwać wiele form płatności, dodaj tag <meta-data> z zasobami <string-array>.

<activity
  android:name=".PaymentActivity"
  android:theme="@style/Theme.SamplePay.Dialog">
  <intent-filter>
    <action android:name="org.chromium.intent.action.PAY" />
  </intent-filter>

  <meta-data
    android:name="org.chromium.default_payment_method_name"
    android:value="https://bobbucks.dev/pay" />
  <meta-data
    android:name="org.chromium.payment_method_names"
    android:resource="@array/chromium_payment_method_names" />
</activity>

Wartość android:resource musi być listą ciągów, z których każdy musi być prawidłowym adresem URL bezwzględnym z schematem HTTPS, jak pokazano tutaj.

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string-array name="chromium_payment_method_names">
        <item>https://alicepay.com/put/optional/path/here</item>
        <item>https://charliepay.com/put/optional/path/here</item>
    </string-array>
</resources>

Parametry

Te parametry są przekazywane do aktywności jako dodatkowe parametry Intent:

  • methodNames
  • methodData
  • merchantName
  • topLevelOrigin
  • topLevelCertificateChain
  • paymentRequestOrigin
  • total
  • modifiers
  • paymentRequestId
  • paymentOptions
  • shippingOptions

KotlinJava
val extras: Bundle? = getIntent()?.extras
Bundle extras = getIntent() != null ? getIntent().getExtras() : null;

methodNames

nazwy używanych metod. Elementy to klucze w słowniku methodData. Są to formy płatności obsługiwane przez aplikację do płatności.

KotlinJava
val methodNames: List<String>? = extras.getStringArrayList("methodNames")
List<String> methodNames = extras.getStringArrayList("methodNames");

methodData

mapowanie każdego z methodNames na methodData.

KotlinJava
val methodData: Bundle? = extras.getBundle("methodData")
Bundle methodData = extras.getBundle("methodData");

merchantName

Zawartość tagu HTML <title> na stronie płatności sprzedawcy (kontekst przeglądania na najwyższym poziomie w przeglądarce).

KotlinJava
val merchantName: String? = extras.getString("merchantName")
String merchantName = extras.getString("merchantName");

topLevelOrigin

Pochodzenie sprzedawcy bez schematu (bezschematowe pochodzenie w kontekście przeglądania na najwyższym poziomie). Na przykład wartość https://mystore.com/checkout jest przekazywana jako mystore.com.

KotlinJava
val topLevelOrigin: String? = extras.getString("topLevelOrigin")
String topLevelOrigin = extras.getString("topLevelOrigin");

topLevelCertificateChain

łańcuch certyfikatów sprzedawcy (łańcuch certyfikatów najwyższego poziomu kontekstu przeglądania); Wartość to null w przypadku WebView, localhost lub pliku na dysku. Każdy element Parcelable to pakiet z kluczem certificate i wartością tablicy bajtów.

KotlinJava
val topLevelCertificateChain: Array<Parcelable>? =
        extras.getParcelableArray("topLevelCertificateChain")
val list: List<ByteArray>? = topLevelCertificateChain?.mapNotNull { p ->
    (p as Bundle).getByteArray("certificate")
}
Parcelable[] topLevelCertificateChain =
        extras.getParcelableArray("topLevelCertificateChain");
if (topLevelCertificateChain != null) {
    for (Parcelable p : topLevelCertificateChain) {
        if (p != null && p instanceof Bundle) {
            ((Bundle) p).getByteArray("certificate");
        }
    }
}

paymentRequestOrigin

Źródło bez schematu w kontekście przeglądania iframe, które wywołało konstruktor new PaymentRequest(methodData, details, options) w JavaScript. Jeśli konstruktor został wywołany z kontekstu najwyższego poziomu, wartość tego parametru jest równa wartości parametru topLevelOrigin.

KotlinJava
val paymentRequestOrigin: String? = extras.getString("paymentRequestOrigin")
String paymentRequestOrigin = extras.getString("paymentRequestOrigin");

total

Ciąg JSON reprezentujący łączną kwotę transakcji.

KotlinJava
val total: String? = extras.getString("total")
String total = extras.getString("total");

Oto przykład treści ciągu znaków:

{"currency":"USD","value":"25.00"}

modifiers

Dane wyjściowe funkcji JSON.stringify(details.modifiers), w których element details.modifiers zawiera tylko wartości supportedMethods, datatotal.

paymentRequestId

Pole PaymentRequest.id, które aplikacje obsługujące „płatność push” powinny powiązać ze stanem transakcji. Strony sprzedawców będą używać tego pola do wysyłania zapytań do aplikacji „push-payment” w celu uzyskania informacji o stanie transakcji poza kanałem.

KotlinJava
val paymentRequestId: String? = extras.getString("paymentRequestId")
String paymentRequestId = extras.getString("paymentRequestId");

Odpowiedź

Aktywność może wysłać odpowiedź z poziomu setResult za pomocą RESULT_OK.

KotlinJava
setResult(Activity.RESULT_OK, Intent().apply {
    putExtra("methodName", "https://bobbucks.dev/pay")
    putExtra("details", "{\"token\": \"put-some-data-here\"}")
})
finish()
Intent result = new Intent();
Bundle extras = new Bundle();
extras.putString("methodName", "https://bobbucks.dev/pay");
extras.putString("details", "{\"token\": \"put-some-data-here\"}");
result.putExtras(extras);
setResult(Activity.RESULT_OK, result);
finish();

Musisz podać 2 parametry jako dodatkowe informacje o intencji:

  • methodName: nazwa używanej metody.
  • details: ciąg znaków JSON zawierający informacje potrzebne sprzedawcy do przeprowadzenia transakcji. Jeśli sukces to true, to details musi być skonstruowany w taki sposób, aby JSON.parse(details) zadziałał. Jeśli nie ma danych do zwrócenia, ciąg może być "{}", który witryna sprzedawcy otrzyma jako pusty słownik JavaScriptu.

Możesz przekazać RESULT_CANCELED, jeśli transakcja nie została ukończona w aplikacji do płatności, na przykład jeśli użytkownik nie wpisał prawidłowego kodu PIN do swojego konta w aplikacji do płatności. Przeglądarka może pozwolić użytkownikowi wybrać inną aplikację do płatności.

KotlinJava
setResult(Activity.RESULT_CANCELED)
finish()
setResult(Activity.RESULT_CANCELED);
finish();

Jeśli wynik aktywności odpowiedzi na płatność otrzymanej z wywołanej aplikacji do płatności ma wartość RESULT_OK, Chrome sprawdzi, czy w swoich dodatkach są niepuste wartości methodNamedetails. Jeśli weryfikacja się nie powiedzie, Chrome zwróci odrzucone obiecanie z request.show() z jednym z tych komunikatów o błędzie dla dewelopera:

'Payment app returned invalid response. Missing field "details".'
'Payment app returned invalid response. Missing field "methodName".'

Uprawnienie

Aktywność może sprawdzić dzwoniącego za pomocą metody getCallingPackage().

KotlinJava
val caller: String? = callingPackage
String caller = getCallingPackage();

Ostatnim krokiem jest sprawdzenie certyfikatu podpisującego, aby potwierdzić, że pakiet wywołania ma prawidłowy podpis.

Krok 4. Sprawdź certyfikat podpisywania wywołującego.

Nazwę pakietu dzwoniącego możesz sprawdzić w polu Binder.getCallingUid()IS_READY_TO_PAY i w polu Activity.getCallingPackage()PAY. Aby potwierdzić, że wywołujący jest przeglądarką, o której myślisz, sprawdź jej certyfikat podpisywania i upewnij się, że jest on zgodny z prawidłową wartością.

Jeśli kierujesz aplikację na poziom interfejsu API 28 lub nowszy i integrujesz ją z przeglądarką z jednym certyfikatem podpisywania, możesz użyć PackageManager.hasSigningCertificate().

KotlinJava
val packageName: String =  // The caller's package name
val certificate: ByteArray =  // The correct signing certificate
val verified = packageManager.hasSigningCertificate(
        callingPackage,
        certificate,
        PackageManager.CERT_INPUT_SHA256
)
String packageName =  // The caller's package name
byte[] certificate =  // The correct signing certificate
boolean verified = packageManager.hasSigningCertificate(
        callingPackage,
        certificate,
        PackageManager.CERT_INPUT_SHA256);

PackageManager.hasSigningCertificate() jest preferowany w przypadku przeglądarek z pojedynczym certyfikatem, ponieważ poprawnie obsługuje rotację certyfikatów. (Chrome ma jeden certyfikat podpisywania). Aplikacje, które mają wiele certyfikatów podpisywania, nie mogą ich rotować.

Jeśli musisz obsługiwać poziomy interfejsu API 27 lub starsze albo musisz obsługiwać przeglądarki z wieloma certyfikatami podpisywania, możesz użyć interfejsu PackageManager.GET_SIGNATURES.

KotlinJava
val packageName: String =  // The caller's package name
val expected: Set<String> =  // The correct set of signing certificates

val packageInfo = packageManager.getPackageInfo(packageName, PackageManager.GET_SIGNATURES)
val sha256 = MessageDigest.getInstance("SHA-256")
val actual = packageInfo.signatures.map {
    SerializeByteArrayToString(sha256.digest(it.toByteArray()))
}
val verified = actual.equals(expected)
String packageName  =  // The caller's package name
Set<String> expected =  // The correct set of signing certificates

PackageInfo packageInfo =
        packageManager.getPackageInfo(packageName, PackageManager.GET_SIGNATURES);
MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
Set<String> actual = new HashSet<>();
for (Signature it : packageInfo.signatures) {
    actual.add(SerializeByteArrayToString(sha256.digest(it.toByteArray())));
}
boolean verified = actual.equals(expected);

Debugowanie

Aby sprawdzić błędy lub komunikaty informacyjne, użyj tego polecenia:

adb logcat | grep -i pay