Dowiedz się, jak dostosować aplikację płatniczą na Androida do obsługi płatności internetowych i zapewnić klientom lepsze wrażenia.
Opublikowano: 5 maja 2020 r., ostatnia aktualizacja: 27 maja 2025 r.
Payment Request API wprowadza do internetu wbudowany interfejs oparty na przeglądarce, który umożliwia użytkownikom łatwiejsze niż kiedykolwiek wprowadzanie wymaganych informacji o płatności. Interfejs API może też wywoływać aplikacje płatnicze specyficzne dla platformy.
W porównaniu z używaniem tylko intencji Androida płatności internetowe zapewniają lepszą integrację z przeglądarką, bezpieczeństwo i wygodę użytkowników:
- Aplikacja płatnicza jest uruchamiana jako okno modalne w kontekście witryny sprzedawcy.
- Implementacja jest uzupełnieniem istniejącej aplikacji płatniczej, co pozwala wykorzystać bazę użytkowników.
- Podpis aplikacji płatniczej jest sprawdzany, aby zapobiec instalowaniu aplikacji z nieznanych źródeł.
- Aplikacje płatnicze mogą obsługiwać wiele form płatności.
- Można zintegrować dowolną formę płatności, np. kryptowalutę, przelewy bankowe itp. Aplikacje płatnicze na urządzeniach z Androidem mogą nawet integrować metody wymagające dostępu do układu sprzętowego na urządzeniu.
Wdrożenie płatności internetowych w aplikacji płatniczej na Androida wymaga 4 kroków:
- Umożliwienie sprzedawcom odkrywania Twojej aplikacji płatniczej.
- Informowanie sprzedawcy, czy klient ma zarejestrowany instrument (np. kartę kredytową), za pomocą którego może zapłacić.
- Umożliwienie klientowi dokonania płatności.
- Sprawdzenie certyfikatu podpisu źródła wywołań.
Aby zobaczyć, jak działają płatności internetowe, zapoznaj się z wersją demonstracyjną android-web-payment.
Krok 1. Umożliwienie sprzedawcom odkrywania Twojej aplikacji płatniczej
Ustaw właściwość related_applications w manifeście aplikacji internetowej zgodnie z
instrukcjami w artykule Konfigurowanie formy płatności.
Aby sprzedawca mógł korzystać z Twojej aplikacji płatniczej, musi używać Payment Request API i określić formę płatności, którą obsługujesz, za pomocą identyfikatora formy płatności.
Jeśli masz identyfikator formy płatności, który jest unikalny dla Twojej aplikacji płatniczej, możesz skonfigurować własny manifest formy płatności, aby przeglądarki mogły wykrywać Twoją aplikację.
Krok 2. Informowanie sprzedawcy, czy klient ma zarejestrowany instrument, za pomocą którego może zapłacić
Sprzedawca może wywołać hasEnrolledInstrument(), aby sprawdzić, czy klient
może dokonać płatności. Aby odpowiedzieć na to zapytanie, możesz zaimplementować IS_READY_TO_PAY jako usługę 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 płatniczej nie ma takiego modułu obsługi intencji, przeglądarka internetowa zakłada, że aplikacja zawsze może dokonywać płatności.
AIDL
Interfejs API usługi IS_READY_TO_PAY jest zdefiniowany w AIDL. Utwórz 2 pliki AIDL z tą treś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);
}
Implementowanie IsReadyToPayService
Najprostsza implementacja IsReadyToPayService jest pokazana w tym przykładzie:
Kotlin
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
}
}
Java
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).
Kotlin
callback?.handleIsReadyToPay(true)
Java
if (callback != null) {
callback.handleIsReadyToPay(true);
}
Uprawnienie
Aby sprawdzić, kto jest elementem wywołującym, możesz użyć Binder.getCallingUid(). Pamiętaj, że musisz to zrobić w metodzie isReadyToPay, a nie w metodzie onBind, ponieważ system operacyjny Android może buforować i ponownie używać połączenia z usługą, co nie powoduje wywołania metody onBind().
Kotlin
override fun isReadyToPay(callback: IsReadyToPayServiceCallback?, parameters: Bundle?) {
try {
val untrustedPackageName = parameters?.getString("packageName")
val actualPackageNames = packageManager.getPackagesForUid(Binder.getCallingUid())
// ...
Java
@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, czy parametry wejściowe nie są równe null. Jest to szczególnie ważne, ponieważ różne wersje lub rozwidlenia systemu operacyjnego Android mogą zachowywać się w nieoczekiwany sposób i powodować błędy, jeśli nie zostaną obsłużone.
Chociaż packageManager.getPackagesForUid() zwykle zwraca pojedynczy element, Twój kod musi obsługiwać rzadki przypadek, w którym źródło wywołań używa wielu nazw pakietów. Dzięki temu Twoja aplikacja pozostanie niezawodna.
Więcej informacji o tym, jak sprawdzić, czy pakiet wywołujący ma prawidłowy podpis, znajdziesz w artykule Sprawdzanie certyfikatu podpisu źródła wywołań o tym, jak sprawdzić, czy pakiet wywołujący ma prawidłowy podpis.
Parametry
Pakiet parameters został dodany w Chrome 139. Zawsze należy sprawdzać, czy nie jest on równy null.
Do usługi w pakiecie parameters przekazywane są te parametry:
packageNamemethodNamesmethodDatatopLevelOriginpaymentRequestOrigintopLevelCertificateChain
Parametr packageName został dodany w Chrome 138. Zanim użyjesz jego wartości, musisz sprawdzić ten parametr pod kątem Binder.getCallingUid(). Ta weryfikacja jest niezbędna, ponieważ pakiet parameters jest w pełni kontrolowany przez element wywołujący, a Binder.getCallingUid() – przez system operacyjny Android.
W WebView i w witrynach innych niż HTTPS, które są zwykle używane do testowania lokalnego, np. http://localhost, parametr topLevelCertificateChain ma wartość null.
Krok 3. Umożliwienie klientowi dokonania płatności
Sprzedawca wywołuje show(), aby uruchomić aplikację płatniczą
, dzięki czemu klient może dokonać płatności. Aplikacja płatnicza jest wywoływana za pomocą intencji Androida PAY z informacjami o transakcji w parametrach intencji.
Aplikacja płatnicza odpowiada za pomocą methodName i details, które są specyficzne dla aplikacji płatniczej i nie są widoczne dla przeglądarki. Przeglądarka konwertuje ciąg details na słownik JavaScriptu dla sprzedawcy za pomocą deserializacji ciągu JSON, ale nie wymusza żadnej innej weryfikacji. Przeglądarka nie modyfikuje details; wartość tego parametru jest przekazywana bezpośrednio do sprzedawcy.
AndroidManifest.xml
Aktywność z filtrem intencji PAY powinna mieć tag <meta-data> który
identyfikuje domyślny identyfikator formy płatności dla
aplikacji.
Aby obsługiwać wiele form płatności, dodaj tag <meta-data> z
<string-array> zasobem.
<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>
android:resource musi być listą ciągów znaków, z których każdy musi być prawidłowym, bezwzględnym adresem URL ze 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 dodatki Intent:
methodNamesmethodDatamerchantNametopLevelOrigintopLevelCertificateChainpaymentRequestOrigintotalmodifierspaymentRequestIdpaymentOptionsshippingOptions
Kotlin
val extras: Bundle? = getIntent()?.extras
Java
Bundle extras = getIntent() != null ? getIntent().getExtras() : null;
methodNames
Nazwy używanych metod. Elementy są kluczami w słowniku methodData. Są to metody obsługiwane przez aplikację płatniczą.
Kotlin
val methodNames: List<String>? = extras.getStringArrayList("methodNames")
Java
List<String> methodNames = extras.getStringArrayList("methodNames");
methodData
Mapowanie każdej z methodNames na
methodData.
Kotlin
val methodData: Bundle? = extras.getBundle("methodData")
Java
Bundle methodData = extras.getBundle("methodData");
merchantName
Zawartość tagu HTML <title> na stronie płatności sprzedawcy (kontekst przeglądania najwyższego poziomu przeglądarki
).
Kotlin
val merchantName: String? = extras.getString("merchantName")
Java
String merchantName = extras.getString("merchantName");
topLevelOrigin
Źródło sprzedawcy bez schematu (źródło bez schematu kontekstu przeglądania najwyższego poziomu). Na przykład https://mystore.com/checkout jest
przekazywane jako mystore.com.
Kotlin
val topLevelOrigin: String? = extras.getString("topLevelOrigin")
Java
String topLevelOrigin = extras.getString("topLevelOrigin");
topLevelCertificateChain
Łańcuch certyfikatów sprzedawcy (łańcuch certyfikatów kontekstu przeglądania najwyższego poziomu). Wartość to null w przypadku WebView, localhost lub pliku na dysku.
Każdy Parcelable to pakiet z kluczem certificate i wartością tablicy bajtów.
Kotlin
val topLevelCertificateChain: Array<Parcelable>? =
extras.getParcelableArray("topLevelCertificateChain")
val list: List<ByteArray>? = topLevelCertificateChain?.mapNotNull { p ->
(p as Bundle).getByteArray("certificate")
}
Java
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 kontekstu przeglądania elementu iframe, który wywołał 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.
Kotlin
val paymentRequestOrigin: String? = extras.getString("paymentRequestOrigin")
Java
String paymentRequestOrigin = extras.getString("paymentRequestOrigin");
total
Ciąg JSON reprezentujący łączną kwotę transakcji.
Kotlin
val total: String? = extras.getString("total")
Java
String total = extras.getString("total");
Oto przykład zawartości ciągu:
{"currency":"USD","value":"25.00"}
modifiers
Wynik JSON.stringify(details.modifiers), gdzie details.modifiers
zawierają tylko supportedMethods, data i total.
paymentRequestId
Pole PaymentRequest.id, które aplikacje „push-payment” powinny powiązać ze stanem transakcji. Witryny sprzedawców będą używać tego pola do wysyłania zapytań do aplikacji „push-payment” o stan transakcji poza pasmem.
Kotlin
val paymentRequestId: String? = extras.getString("paymentRequestId")
Java
String paymentRequestId = extras.getString("paymentRequestId");
Odpowiedź
Aktywność może wysłać odpowiedź za pomocą setResult z RESULT_OK.
Kotlin
setResult(Activity.RESULT_OK, Intent().apply {
putExtra("methodName", "https://bobbucks.dev/pay")
putExtra("details", "{\"token\": \"put-some-data-here\"}")
})
finish()
Java
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 określić 2 parametry jako dodatki intencji:
methodName: nazwa używanej metody.details: ciąg JSON zawierający informacje niezbędne sprzedawcy do sfinalizowania transakcji. Jeśli success ma wartośćtrue,detailsmuszą być skonstruowane w taki sposób, abyJSON.parse(details)zakończyło się powodzeniem. Jeśli nie ma danych, które trzeba zwrócić, ten ciąg może mieć postać"{}", który witryna sprzedawcy otrzyma jako pusty słownik JavaScriptu.
Jeśli użytkownik anuluje transakcję w aplikacji płatniczej, możesz przekazać RESULT_CANCELED. Spowoduje to, że request.show() odrzuci AbortError w witrynie sprzedawcy, wskazując na anulowanie przez użytkownika.
Kotlin
setResult(Activity.RESULT_CANCELED)
finish()
Java
setResult(Activity.RESULT_CANCELED);
finish();
Od Chrome 149 obsługiwane są te wartości wyników:
Kotlin
Activity.RESULT_CANCELED // 0 (0x00000000)
Activity.RESULT_OK // -1 (0xffffffff)
const val INTERNAL_PAYMENT_APP_ERROR = Activity.RESULT_FIRST_USER // 1 (0x00000001)
Java
Activity.RESULT_CANCELED // 0 (0x00000000)
Activity.RESULT_OK // -1 (0xffffffff)
static final int INTERNAL_PAYMENT_APP_ERROR = Activity.RESULT_FIRST_USER; // 1 (0x00000001)
Jeśli aplikacja płatnicza nie działa z powodu błędu wewnętrznego, możesz to wskazać, przekazując Activity.RESULT_FIRST_USER jako kod wyniku.
Jeśli zostanie zwrócony INTERNAL_PAYMENT_APP_ERROR, request.show() odrzuci OperationError w witrynie sprzedawcy, wskazując na błąd w aplikacji płatniczej.
To rozróżnienie między RESULT_CANCELED (0) w przypadku anulowania przez użytkownika, które powoduje AbortError, a INTERNAL_PAYMENT_APP_ERROR (1) w przypadku błędu wewnętrznego aplikacji, który powoduje OperationError, umożliwia sprzedawcom tworzenie lepszych ścieżek użytkownika.
Kotlin
setResult(Activity.RESULT_FIRST_USER)
finish()
Java
setResult(Activity.RESULT_FIRST_USER);
finish();
Jeśli wynik aktywności odpowiedzi płatności otrzymanej z wywołanej aplikacji płatniczej jest ustawiony na RESULT_OK, Chrome sprawdzi, czy w dodatkach nie ma pustych wartości methodName i details. Jeśli weryfikacja się nie powiedzie, Chrome zwróci odrzuconą obietnicę z request.show() z jednym z tych komunikatów o błędach dla deweloperów:
'Payment app returned invalid response. Missing field "details".'
'Payment app returned invalid response. Missing field "methodName".'
Uprawnienie
Aktywność może sprawdzić źródło wywołań za pomocą metody getCallingPackage().
Kotlin
val caller: String? = callingPackage
Java
String caller = getCallingPackage();
Ostatnim krokiem jest sprawdzenie certyfikatu podpisu źródła wywołań, aby potwierdzić, że pakiet wywołujący ma prawidłowy podpis.
Krok 4. Sprawdzenie certyfikatu podpisu źródła wywołań
Możesz sprawdzić nazwę pakietu źródła wywołań za pomocą Binder.getCallingUid() w
IS_READY_TO_PAY, i za pomocą Activity.getCallingPackage() w PAY. Aby sprawdzić, czy źródłem wywołań jest przeglądarka, którą masz na myśli, sprawdź jej certyfikat podpisu i upewnij się, że pasuje do prawidłowej wartości.
Jeśli kierujesz reklamy na interfejs API na poziomie 28 lub wyższym i integrujesz się z przeglądarką, która ma jeden certyfikat podpisu, możesz użyć PackageManager.hasSigningCertificate().
Kotlin
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
)
Java
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 jednym certyfikatem, ponieważ prawidłowo obsługuje rotację certyfikatów. (Chrome ma jeden certyfikat podpisu). Aplikacje, które mają wiele certyfikatów podpisu, nie mogą ich obracać.
Jeśli musisz obsługiwać interfejsy API na poziomie 27 i niższym lub jeśli musisz obsługiwać przeglądarki z wieloma certyfikatami podpisu, możesz użyć PackageManager.GET_SIGNATURES.
Kotlin
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)
Java
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 obserwować błędy lub komunikaty informacyjne, użyj tego polecenia:
adb logcat | grep -i pay