Przewodnik dla deweloperów aplikacji płatniczych na Androida

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

Interfejs Payment Request API udostępnia internetowy, wbudowany interfejs w przeglądarce, który umożliwia użytkownikowi wprowadzenie wymaganych płatności dostęp do informacji jest teraz łatwiejszy niż kiedykolwiek wcześniej. Interfejs API może również wywoływać płatność na poziomie platformy aplikacji.

Obsługa przeglądarek

  • Chrome: 60.
  • Krawędź: 15.
  • Firefox: za flagą.
  • Safari: 11.1

Źródło

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

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

  • Aplikacja płatnicza jest uruchamiana jako okno modalne w kontekście witryny sprzedawcy.
  • Implementacja tej aplikacji uzupełnia obecną aplikację płatniczą, dzięki czemu możesz: wykorzystaj swoją bazę użytkowników.
  • Podpis aplikacji płatniczej jest sprawdzany, aby zapobiec instalowanie z nieoficjalnych źródeł.
  • Aplikacje płatnicze mogą obsługiwać wiele form płatności.
  • Każda forma płatności, taka jak kryptowaluta, przelewy bankowe itp., może być i integracja z internetem. Aplikacje do płatności na urządzeniach z Androidem mogą nawet integrować formy, wymaga dostępu do układu sprzętowego na urządzeniu.
.

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

  1. Pozwól sprzedawcom znaleźć Twoją aplikację płatniczą.
  2. Poinformuj sprzedawcę, czy klient ma zarejestrowany instrument (np. środki płatnicze) którą możesz zapłacić.
  3. Pozwól klientowi dokonać płatności.
  4. Sprawdź certyfikat podpisywania elementu wywołującego.

Aby zobaczyć, jak działają płatności internetowe, sprawdź android-web-payment wersji demonstracyjnej.

Krok 1. Pozwól sprzedawcom odkryć Twoją aplikację płatniczą

Aby sprzedawca mógł korzystać z Twojej aplikacji płatniczej, musi obsługiwać Request API oraz określenie obsługiwanej formy płatności za pomocą 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łasną formę płatności manifestu, aby przeglądarki mogły odkryć Twoją aplikację.

Krok 2. Poinformuj sprzedawcę, czy klient ma zarejestrowany instrument, który jest gotowy do płatności

Sprzedawca może zadzwonić pod numer hasEnrolledInstrument(), aby zapytać, czy klient może dokonać płatności. Dostępne opcje zaimplementuj IS_READY_TO_PAY jako usługę na Androida, aby odpowiedzieć na to pytanie.

AndroidManifest.xml

Deklarowanie usługi 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 nie ma takiego modułu obsługi intencji w aplikacji płatniczej, przeglądarka zakłada, że aplikacja zawsze może płatności.

AIDL

Interfejs API usługi IS_READY_TO_PAY został zdefiniowany w AIDL. Utwórz dwa AIDL o następującej 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

Najprostsza implementacja funkcji IsReadyToPayService pokazana jest poniżej przykład:

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

Za pomocą Binder.getCallingUid() możesz sprawdzić, kto jest dzwoniący. Pamiętaj, że musisz 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 to zrobić, znajdziesz w sekcji Sprawdzanie certyfikatu podpisywania rozmówcy aby sprawdzić, czy pakiet do wykonywania połączeń ma właściwy podpis.

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

Sprzedawca dzwoni pod numer show(), aby rozpocząć płatność aplikacja aby klient mógł dokonać płatności. Aplikacja płatnicza jest wywoływana przez Androida. intencja PAY z informacjami o transakcji w parametrach intencji.

W odpowiedzi aplikacja płatnicza wysyła komunikat methodName i details, które są aplikacjami płatniczymi są określone i nieprzezroczyste dla przeglądarki. Przeglądarka konwertuje details do obiektu JavaScript dla sprzedawcy za pomocą deserializacji JSON, ale nie egzekwuje żadnej ważności poza tym dokumentem. Przeglądarka nie modyfikuje details; wartość tego parametru jest przekazywana bezpośrednio sprzedawcy.

AndroidManifest.xml

Działanie z filtrem intencji PAY powinno zawierać tag <meta-data>, identyfikuje domyślną formę płatności

Aby obsługiwać wiele form płatności, dodaj tag <meta-data> ze znakiem <string-array> zasób.

<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>

resource musi być listą ciągów, z których każdy musi być prawidłowym ciągiem znaków, bezwzględny URL ze 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 dodatki związane z intencją:

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

methodNames

Nazwy używanych metod. Elementy to klucze w Słownik methodData. Aplikacja płatnicza obsługuje te formy płatności.

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

methodData

Mapowanie z każdego pola methodNames na methodData

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

merchantName

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

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

topLevelOrigin

Pochodzenie sprzedawcy bez schematu (bez schematu pochodzenia wartości w kontekście przeglądania na najwyższym poziomie). Przykład: https://mystore.com/checkout to przekazano 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ść pusta dla hosta lokalnego i pliku na dysku – oba te tryby są bezpieczne kontekstach bez certyfikatów SSL. Każdy element Parcelable to pakiet z certificate i wartość tablicy bajtów.

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

paymentRequestOrigin

Bezschematyczne źródło kontekstu przeglądania elementu 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 równa się 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")

Przykładowa zawartość ciągu znaków:

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

modifiers

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

paymentRequestId

Pole PaymentRequest.id „push-payment” aplikacje, które powinny być powiązane z stanu transakcji. Witryny sprzedawców będą używać tego pola do wysyłania zapytań &quot;push-payment&quot; są zbyt długie i niezależne od stanu transakcji.

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

Odpowiedź

Działanie może wysłać odpowiedź z powrotem za pomocą usługi setResult za pomocą usługi 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 sfinalizować transakcję. Jeśli powodzenie wynosi true, pole details musi być skonstruowane w taki sposób, aby JSON.parse(details) odniosło sukces.

Możesz przekazać RESULT_CANCELED, jeśli transakcja nie została zrealizowana w w aplikacji płatniczej, np. jeśli użytkownik nie wpisze prawidłowego kodu PIN konto w aplikacji do wykonywania płatności. Przeglądarka może umożliwić użytkownikowi innej aplikacji płatniczej.

setResult(RESULT_CANCELED)
finish()

Jeśli wynikiem działania na płatność jest otrzymana odpowiedź z wywołanej płatności aplikacja jest ustawiona na RESULT_OK, Chrome sprawdzi, czy nie ma niepustych pól methodName i details w dodatkach. Jeśli weryfikacja się nie powiedzie, Chrome zwróci błąd obietnica od request.show() z jednym z tych błędów dewelopera wiadomości:

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

Uprawnienie

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

val caller: String? = callingPackage

Ostatnim krokiem jest zweryfikowanie certyfikatu podpisywania wywołującego w celu potwierdzenia, że pakiet do wykonywania połączeń ma właściwy podpis.

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

Możesz sprawdzić nazwę przesyłki rozmówcy, używając polecenia Binder.getCallingUid() w IS_READY_TO_PAY i Activity.getCallingPackage() w: PAY. Aby należy sprawdzić, czy rozmówcą jest przeglądarka, sprawdź jego certyfikat podpisywania i upewnij się, że jest zgodny z prawidłowym .

Jeśli kierujesz treści na interfejs API na poziomie 28 lub wyższym i przeprowadzasz integrację z przeglądarką dla którego jest tylko 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
)

Dla pojedynczego certyfikatu preferowana jest domena PackageManager.hasSigningCertificate() przeglądarek, ponieważ poprawnie obsługuje rotację certyfikatów. (Chrome ma pojedynczy certyfikat podpisywania). Aplikacje z wieloma certyfikatami podpisywania nie mogą lub je obrócić.

Jeśli musisz obsługiwać starsze poziomy interfejsu API 27 i niższe lub jeśli: przeglądarek 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) } }