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: 25 marca 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.
W porównaniu z wykorzystywaniem tylko intencji Androida płatności internetowe zapewniają lepszą integrację z przeglądarką, bezpieczeństwo i wygodę 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:
- Pozwól sprzedawcom odkryć Twoją aplikację płatniczą.
- Poinformuj sprzedawcę, jeśli klient ma zarejestrowany instrument płatniczy (np. kartę kredytową), który jest gotowy do zapłaty.
- Pozwól klientowi dokonać płatności.
- 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 formy 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ą:
app/src/main/aidl/org/chromium/IsReadyToPayServiceCallback.aidl
package org.chromium;
interface IsReadyToPayServiceCallback {
oneway void handleIsReadyToPay(boolean isReadyToPay);
}
app/src/main/aidl/org/chromium/IsReadyToPayService.aidl
package org.chromium;
import org.chromium.IsReadyToPayServiceCallback;
interface IsReadyToPayService {
oneway void isReadyToPay(IsReadyToPayServiceCallback callback);
}
Stosowanie dyrektywy IsReadyToPayService
Najprostsze wdrożenie IsReadyToPayService
pokazano w tym przykładzie:
class SampleIsReadyToPayService : Service() {
private val binder = object : IsReadyToPayService.Stub() {
override fun isReadyToPay(callback: IsReadyToPayServiceCallback?) {
callback?.handleIsReadyToPay(true)
}
}
override fun onBind(intent: Intent?): IBinder? {
return binder
}
}
Odpowiedź
Usługa może wysłać odpowiedź za pomocą metody handleIsReadyToPay(Boolean)
.
callback?.handleIsReadyToPay(true)
Uprawnienie
Aby sprawdzić, kto jest dzwoniącym, możesz użyć metody Binder.getCallingUid()
. Pamiętaj, że musisz to zrobić w ramach metody isReadyToPay
, a nie onBind
.
override fun isReadyToPay(callback: IsReadyToPayServiceCallback?) {
try {
val callingPackage = packageManager.getNameForUid(Binder.getCallingUid())
// …
Aby dowiedzieć się, jak sprawdzić, czy pakiet wywołujący ma prawidłową sygnaturę, zapoznaj się z artykułem Weryfikowanie certyfikatu podpisującego.
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ą intencji AndroidaPAY
z informacjami o transakcji w parametrach intencji.
Aplikacja do płatności odpowiada z wartościami methodName
i details
, które są specyficzne dla aplikacji do płatności i nie są widoczne dla przeglądarki. Przeglądarka zamienia ciąg details
na obiekt JavaScript dla sprzedawcy za pomocą deserializacji JSON, ale nie narzuca żadnych dodatkowych wymagań dotyczących ważności. Przeglądarka nie modyfikuje parametru details
; jego wartość jest przekazywana 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/method_names" />
</activity>
Wartość 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="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 intencji:
methodNames
methodData
topLevelOrigin
topLevelCertificateChain
paymentRequestOrigin
total
modifiers
paymentRequestId
val extras: Bundle? = intent?.extras
methodNames
nazwy używanych metod. Elementy to klucze w słowniku methodData
. Są to formy płatności obsługiwane przez aplikację.
val methodNames: List<String>? = extras.getStringArrayList("methodNames")
methodData
mapowanie każdego z parametrów methodNames
na parametr methodData
;
val methodData: Bundle? = extras.getBundle("methodData")
merchantName
Zawartość tagu HTML <title>
na stronie płatności sprzedawcy (kontekst przeglądania na najwyższym poziomie w przeglądarce).
val merchantName: String? = 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
.
val topLevelOrigin: String? = extras.getString("topLevelOrigin")
topLevelCertificateChain
łańcuch certyfikatów sprzedawcy (łańcuch certyfikatów najwyższego poziomu w kontekście przeglądania); Wartość null w przypadku localhost i pliku na dysku, które są bezpiecznymi kontekstami bez certyfikatów SSL. Każdy element Parcelable
to pakiet z kluczem certificate
i wartością tablicy bajtów.
val topLevelCertificateChain: Array<Parcelable>? =
extras.getParcelableArray("topLevelCertificateChain")
val list: List<ByteArray>? = topLevelCertificateChain?.mapNotNull { p ->
(p as Bundle).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
.
val paymentRequestOrigin: String? = extras.getString("paymentRequestOrigin")
total
Ciąg JSON reprezentujący łączną kwotę transakcji.
val total: String? = 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 details.modifiers
zawiera tylko supportedMethods
i total
.
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.
val paymentRequestId: String? = extras.getString("paymentRequestId")
Odpowiedź
Aktywność może wysłać odpowiedź z poziomu setResult
za pomocą RESULT_OK
.
setResult(Activity.RESULT_OK, Intent().apply {
putExtra("methodName", "https://bobbucks.dev/pay")
putExtra("details", "{\"token\": \"put-some-data-here\"}")
})
finish()
Musisz podać 2 parametry jako dodatkowe informacje o intencji:
methodName
: nazwa używanej metody.details
: ciąg tekstowy JSON zawierający informacje potrzebne sprzedawcy do przeprowadzenia transakcji. Jeśli sukces totrue
, todetails
musi być skonstruowany w taki sposób, abyJSON.parse(details)
zadziałał.
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.
setResult(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 methodName
i details
. Jeśli weryfikacja się nie powiedzie, Chrome zwróci odrzucone promise z poziomu 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()
.
val caller: String? = callingPackage
Ostatnim krokiem jest sprawdzenie certyfikatu podpisującego, aby potwierdzić, że pakiet wywołania ma prawidłowy podpis.
Krok 4. Sprawdź certyfikat podpisywania dzwoniącego
Nazwę pakietu dzwoniącego możesz sprawdzić w polu Binder.getCallingUid()
w IS_READY_TO_PAY
i w polu Activity.getCallingPackage()
w 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 się na interfejs API w wersji 28 lub nowszej i integrujesz się z przeglądarką z jednym certyfikatem podpisywania, możesz użyć PackageManager.hasSigningCertificate()
.
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
)
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 zamieniać.
Jeśli musisz obsługiwać starsze poziomy interfejsu API (27 i starsze) lub musisz obsługiwać przeglądarki z wieloma certyfikatami podpisywania, możesz użyć PackageManager.GET_SIGNATURES
.
val packageName: String = … // The caller's package name
val certificates: Set<ByteArray> = … // The correct set of signing certificates
val packageInfo = getPackageInfo(packageName, PackageManager.GET_SIGNATURES)
val sha256 = MessageDigest.getInstance("SHA-256")
val signatures = packageInfo.signatures.map { sha256.digest(it.toByteArray()) }
val verified = signatures.size == certificates.size &&
signatures.all { s -> certificates.any { it.contentEquals(s) } }