Przewodnik dla deweloperów aplikacji płatniczych na Androida

Dowiedz się, jak dostosować aplikację do płatności na Androida, aby współpracowała z płatnościami internetowymi i zapewniła klientom większą wygodę.

Payment Request API to interfejs wbudowany w przeglądarkę, który pozwala użytkownikom łatwiej niż kiedykolwiek wcześniej wpisywać wymagane informacje o płatności. Może też wywoływać aplikacje płatnicze na konkretnej platformie.

Obsługa przeglądarek

  • 60
  • 15
  • 11.1

Źródło

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

W porównaniu do korzystania tylko z intencji na Androida płatności internetowe umożliwiają lepszą integrację z przeglądarką, zabezpieczeniami i wygodą użytkowników:

  • Aplikacja płatnicza jest uruchamiana w oknie modalnym na stronie sprzedawcy.
  • Uzupełnia ona Twoją istniejącą aplikację płatniczą i umożliwia Ci wykorzystanie potencjału użytkowników.
  • Podpis aplikacji płatniczej jest sprawdzany, aby zapobiec instalowaniu z nieoficjalnych źródeł.
  • Aplikacje do płatności mogą obsługiwać wiele form płatności.
  • Integracja może obejmować wszystkie formy płatności, takie jak kryptowaluty, przelewy bankowe itp. Aplikacje do płatności na urządzeniach z Androidem mogą też integrować metody, które wymagają dostępu do układu sprzętowego urządzenia.

Wdrożenie płatności internetowych w aplikacji do płatności na Androida wymaga wykonania 4 kroków:

  1. Ułatw sprzedawcom znalezienie Twojej aplikacji płatniczej.
  2. Poinformuj sprzedawcę, czy klient ma zarejestrowany instrument (np. kartę kredytową), który może już zapłacić.
  3. Umożliwienie klientom dokonania płatności.
  4. Sprawdź certyfikat podpisywania elementu wywołującego.

Aby zobaczyć, jak działa płatność internetowa, zapoznaj się z prezentacją android-web-payment.

Krok 1. Ułatw sprzedawcom znalezienie Twojej aplikacji płatniczej

Aby sprzedawca mógł skorzystać z Twojej aplikacji płatniczej, 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 płatniczej, możesz skonfigurować własny plik manifestu, aby przeglądarki mogły wykrywać Twoją aplikację.

Krok 2. Poinformuj sprzedawcę, czy klient ma zarejestrowany instrument, który może już zapłacić

Sprzedawca może wywołać hasEnrolledInstrument(), aby zapytać, czy klient może dokonać płatności. Aby odpowiedzieć na to zapytanie, możesz zaimplementować IS_READY_TO_PAY jako usługę na Androida.

AndroidManifest.xml

Zadeklaruj usługę za pomocą filtra intencji, używając działania 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 przyjmuje, że aplikacja zawsze może dokonywać płatności.

AIDL

Interfejs API dla usługi IS_READY_TO_PAY jest zdefiniowany w AIDL. Utwórz 2 pliki AIDL o tej treś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

Najprostszą implementację obiektu IsReadyToPayService przedstawiono 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)

Uprawnienia

Za pomocą metody Binder.getCallingUid() możesz sprawdzić, kto wywołuje użytkownika. Pamiętaj, że trzeba to zrobić w metodzie isReadyToPay, a nie w metodzie onBind.

override fun isReadyToPay(callback: IsReadyToPayServiceCallback?) {
  try {
    val callingPackage = packageManager.getNameForUid(Binder.getCallingUid())
    // …

Informacje o tym, jak sprawdzić, czy pakiet wywołujący ma właściwy podpis, znajdziesz w sekcji Sprawdzanie certyfikatu podpisywania elementu wywołującego.

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

Sprzedawca wywołuje show(), aby uruchomić aplikację płatniczą, aby klient mógł 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 w odpowiedzi wysyła w odpowiedzi methodName i details, które są związane z aplikacją płatniczą i są nieprzezroczyste dla przeglądarki. Przeglądarka konwertuje ciąg znaków details na obiekt JavaScript dla sprzedawcy za pomocą deserializacji JSON, ale nie egzekwuje żadnej innej wartości. Przeglądarka nie modyfikuje parametru details. Wartość tego parametru jest przekazywana bezpośrednio do sprzedawcy.

AndroidManifest.xml

Aktywność z filtrem intencji PAY powinna zawierać tag <meta-data> identyfikujący domyślny identyfikator formy płatności dla aplikacji.

Aby obsługiwać wiele form płatności, dodaj tag <meta-data> z zasobem <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>

Element resource musi być listą ciągów tekstowych, z których każdy musi być prawidłowym, bezwzględnym adresem URL o schemacie 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 działania jako dodatki do intencji:

  • methodNames
  • methodData
  • topLevelOrigin
  • topLevelCertificateChain
  • paymentRequestOrigin
  • total
  • modifiers
  • paymentRequestId
val extras: Bundle? = intent?.extras

methodNames

Nazwy stosowanych metod. Elementy to klucze w słowniku methodData. Te formy są obsługiwane przez aplikację płatniczą.

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

methodData

Mapowanie z każdego obiektu methodNames na methodData.

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

merchantName

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

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

topLevelOrigin

Pochodzenie sprzedawcy bez schematu (bez schematów pochodzenie kontekstu przeglądania najwyższego poziomu). 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 kontekstu przeglądania najwyższego poziomu). Wartość null w przypadku hosta lokalnego 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ą w tablicy bajtów.

val topLevelCertificateChain: Array<Parcelable>? =
    extras.getParcelableArray("topLevelCertificateChain")
val list: List<ByteArray>? = topLevelCertificateChain?.mapNotNull { p ->
  (p as Bundle).getByteArray("certificate")
}

paymentRequestOrigin

Bez schematu pochodzenie kontekstu przeglądania iframe, które wywołało konstruktor new PaymentRequest(methodData, details, options) w JavaScripcie. 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 znaków JSON reprezentujący łączną kwotę transakcji.

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

Oto przykładowa zawartość tego ciągu znaków:

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

modifiers

Dane wyjściowe funkcji JSON.stringify(details.modifiers), gdzie details.modifiers zawiera tylko wartości supportedMethods i total.

paymentRequestId

Pole PaymentRequest.id, które aplikacje typu „push” powinny powiązać ze stanem transakcji. Strony sprzedawców będą używać tego pola do wysyłania zapytań dotyczących aplikacji typu „push” na potrzeby stanu transakcji.

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

Odpowiedź

Aktywność może wysłać odpowiedź przez 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 określić 2 parametry jako dodatki do intencji:

  • methodName: nazwa używanej metody.
  • details: ciąg znaków JSON zawierający informacje niezbędne sprzedawcy do zrealizowania transakcji. Jeśli sukcesem jest true, musisz utworzyć kolumnę details w taki sposób, aby parametr JSON.parse(details) działał.

Możesz przekazać RESULT_CANCELED, jeśli transakcja nie została zrealizowana w aplikacji płatniczej, na przykład wtedy, gdy użytkownik nie podał prawidłowego kodu PIN do swojego konta w aplikacji płatniczej. Przeglądarka może pozwolić użytkownikowi wybrać inną aplikację płatniczą.

setResult(RESULT_CANCELED)
finish()

Jeśli wynik aktywności odpowiedzi na płatność otrzymaną z wywołanej aplikacji płatniczej jest ustawiony na RESULT_OK, Chrome sprawdzi, czy w dodatkach nie ma pustych elementów methodName i details. Jeśli weryfikacja się nie powiedzie, Chrome zwróci od request.show() komunikat o odrzuceniu z jednym z tych komunikatów o błędzie:

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

Uprawnienia

Aktywność może sprawdzać element wywołujący za pomocą metody getCallingPackage().

val caller: String? = callingPackage

Ostatnim krokiem jest sprawdzenie certyfikatu podpisywania elementu wywołującego, aby potwierdzić, że pakiet wywołań ma właściwy podpis.

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

Nazwę pakietu rozmówcy możesz sprawdzić w aplikacji Binder.getCallingUid() w systemie IS_READY_TO_PAY, a w PAY – w Activity.getCallingPackage(). Aby rzeczywiście upewnić się, że obiekt wywołujący jest przeglądarką, o które Ci chodzi, sprawdź jego certyfikat podpisywania i upewnij się, że jest zgodny z prawidłową wartością.

Jeśli kierujesz aplikację na poziom API 28 lub wyższy i przeprowadzasz integrację z przeglądarką, która ma jeden certyfikat 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
)

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

Jeśli chcesz obsługiwać starsze interfejsy API 27 i niższe lub jeśli chcesz 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) } }