Узнайте, как адаптировать платежное приложение Android для работы с веб-платежами и повысить удобство использования для клиентов.
API запроса платежа предоставляет в Интернет встроенный браузерный интерфейс, который позволяет пользователям вводить необходимую платежную информацию проще, чем когда-либо прежде. API также может вызывать платежные приложения для конкретной платформы.
По сравнению с использованием только Android Intents, веб-платежи обеспечивают лучшую интеграцию с браузером, безопасность и удобство использования:
- Платежное приложение запускается модально в контексте веб-сайта продавца.
- Реализация дополняет ваше существующее платежное приложение, позволяя вам использовать преимущества своей пользовательской базы.
- Подпись платежного приложения проверяется, чтобы предотвратить неопубликованную загрузку .
- Платежные приложения могут поддерживать несколько способов оплаты.
- Можно интегрировать любой способ оплаты, например криптовалюту, банковские переводы и т. д. Платежные приложения на устройствах Android могут даже интегрировать методы, требующие доступа к аппаратному чипу устройства.
Для реализации веб-платежей в платежном приложении Android требуется четыре шага:
- Позвольте продавцам узнать о вашем платежном приложении.
- Сообщите продавцу, есть ли у клиента зарегистрированный инструмент (например, кредитная карта), готовый к оплате.
- Позвольте клиенту произвести оплату.
- Проверьте сертификат подписи вызывающего абонента.
Чтобы увидеть веб-платежи в действии, посмотрите демо-версию android-web-paying .
Шаг 1. Позвольте продавцам узнать о вашем платежном приложении
Чтобы продавец мог использовать ваше платежное приложение, ему необходимо использовать API запроса платежа и указать поддерживаемый вами метод оплаты, используя идентификатор метода оплаты .
Если у вас есть идентификатор способа оплаты, уникальный для вашего платежного приложения, вы можете настроить собственный манифест способа оплаты , чтобы браузеры могли обнаружить ваше приложение.
Шаг 2. Сообщите продавцу, есть ли у клиента зарегистрированный инструмент, готовый к оплате.
Продавец может вызвать hasEnrolledInstrument()
, чтобы узнать, может ли покупатель произвести платеж . Вы можете реализовать IS_READY_TO_PAY
как службу Android для ответа на этот запрос.
AndroidManifest.xml
Объявите свою службу с фильтром намерений с помощью действия 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>
Служба IS_READY_TO_PAY
является необязательной. Если в платежном приложении такого обработчика намерений нет, веб-браузер предполагает, что приложение всегда может совершать платежи.
АИДЛ
API для службы IS_READY_TO_PAY
определен в AIDL. Создайте два файла AIDL со следующим содержимым:
app/src/main/aidl/org/chromium/IsReadyToPayServiceCallback.aidl
package org.chromium;
interface IsReadyToPayServiceCallback {
oneway void handleIsReadyToPay(boolean isReadyToPay);
}
приложение/src/main/aidl/org/chromium/IsReadyToPayService.aidl
package org.chromium;
import org.chromium.IsReadyToPayServiceCallback;
interface IsReadyToPayService {
oneway void isReadyToPay(IsReadyToPayServiceCallback callback);
}
Реализация IsReadyToPayService
Простейшая реализация IsReadyToPayService
показана в следующем примере:
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
}
}
Ответ
Служба может отправить свой ответ с помощью метода handleIsReadyToPay(Boolean)
.
callback?.handleIsReadyToPay(true)
Разрешение
Вы можете использовать Binder.getCallingUid()
чтобы проверить, кто звонит. Обратите внимание, что это необходимо сделать в методе isReadyToPay
, а не в методе onBind
.
override fun isReadyToPay(callback: IsReadyToPayServiceCallback?) {
try {
val callingPackage = packageManager.getNameForUid(Binder.getCallingUid())
// …
См. раздел Проверка сертификата подписи вызывающего абонента , чтобы узнать, как проверить, что вызывающий пакет имеет правильную подпись.
Шаг 3. Позвольте клиенту произвести оплату
Продавец вызывает show()
чтобы запустить платежное приложение , чтобы покупатель мог совершить платеж. Платежное приложение вызывается через намерение Android PAY
с информацией о транзакции в параметрах намерения.
Платежное приложение отвечает методом methodName
и details
, которые зависят от платежного приложения и непрозрачны для браузера. Браузер преобразует строку details
в объект JavaScript для продавца посредством десериализации JSON, но не обеспечивает никакой достоверности, кроме этого. Браузер не изменяет details
; значение этого параметра передается непосредственно продавцу.
AndroidManifest.xml
Действие с фильтром намерений PAY
должно иметь тег <meta-data>
, который идентифицирует идентификатор способа оплаты по умолчанию для приложения .
Для поддержки нескольких способов оплаты добавьте тег <meta-data>
к ресурсу <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>
resource
должен представлять собой список строк, каждая из которых должна представлять собой действительный абсолютный URL-адрес со схемой HTTPS, как показано здесь.
<?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>
Параметры
Следующие параметры передаются в активность как дополнительные функции Intent:
-
methodNames
-
methodData
-
topLevelOrigin
-
topLevelCertificateChain
-
paymentRequestOrigin
-
total
-
modifiers
-
paymentRequestId
val extras: Bundle? = intent?.extras
Имена методов
Названия используемых методов. Элементы — это ключи в словаре methodData
. Это методы, которые поддерживает платежное приложение.
val methodNames: List<String>? = extras.getStringArrayList("methodNames")
methodData
Сопоставление каждого из methodNames
с methodData
.
val methodData: Bundle? = extras.getBundle("methodData")
имя продавца
Содержимое HTML-тега <title>
страницы оформления заказа продавца (контекст просмотра верхнего уровня браузера).
val merchantName: String? = extras.getString("merchantName")
topLevelOrigin
Происхождение продавца без схемы (Происхождение контекста просмотра верхнего уровня без схемы). Например, https://mystore.com/checkout
передается как mystore.com
.
val topLevelOrigin: String? = extras.getString("topLevelOrigin")
topLevelCertificateChain
Цепочка сертификатов продавца (Цепочка сертификатов контекста просмотра верхнего уровня). Значение NULL для локального хоста и файла на диске, которые являются безопасными контекстами без сертификатов SSL. Каждый Parcelable
представляет собой Bundle с ключом certificate
и значением массива байтов.
val topLevelCertificateChain: Array<Parcelable>? =
extras.getParcelableArray("topLevelCertificateChain")
val list: List<ByteArray>? = topLevelCertificateChain?.mapNotNull { p ->
(p as Bundle).getByteArray("certificate")
}
paymentRequestOrigin
Бессхемное происхождение контекста просмотра iframe, который вызывал new PaymentRequest(methodData, details, options)
в JavaScript. Если конструктор был вызван из контекста верхнего уровня, то значение этого параметра равно значению параметра topLevelOrigin
.
val paymentRequestOrigin: String? = extras.getString("paymentRequestOrigin")
total
Строка JSON, представляющая общую сумму транзакции.
val total: String? = extras.getString("total")
Вот пример содержимого строки:
{"currency":"USD","value":"25.00"}
modifiers
Вывод JSON.stringify(details.modifiers)
, где details.modifiers
содержит только supportedMethods
и total
.
paymentRequestId
Поле PaymentRequest.id
, которое приложения «принудительной оплаты» должны связать с состоянием транзакции. Веб-сайты торговцев будут использовать это поле для запроса приложений «принудительных платежей» о состоянии транзакции вне диапазона.
val paymentRequestId: String? = extras.getString("paymentRequestId")
Ответ
Действие может отправить свой ответ обратно через setResult
с RESULT_OK
.
setResult(Activity.RESULT_OK, Intent().apply {
putExtra("methodName", "https://bobbucks.dev/pay")
putExtra("details", "{\"token\": \"put-some-data-here\"}")
})
finish()
Вы должны указать два параметра в качестве дополнительных параметров Intent:
-
methodName
: имя используемого метода. -
details
: строка JSON, содержащая информацию, необходимую продавцу для завершения транзакции. Если успех равенtrue
, тоdetails
должны быть созданы таким образом, чтобыJSON.parse(details)
завершился успешно.
Вы можете передать RESULT_CANCELED
, если транзакция не была завершена в платежном приложении, например, если пользователь не смог ввести правильный PIN-код для своей учетной записи в платежном приложении. Браузер может позволить пользователю выбрать другое платежное приложение.
setResult(RESULT_CANCELED)
finish()
Если для результата активности платежного ответа, полученного от вызванного платежного приложения, установлено значение RESULT_OK
, Chrome проверит непустое methodName
и details
в дополнительных параметрах. Если проверка не пройдена, Chrome вернет отклоненное обещание из request.show()
с одним из следующих сообщений об ошибке разработчика:
'Payment app returned invalid response. Missing field "details".'
'Payment app returned invalid response. Missing field "methodName".'
Разрешение
Действие может проверить вызывающую сторону с помощью метода getCallingPackage()
.
val caller: String? = callingPackage
Последним шагом является проверка сертификата подписи вызывающего абонента, чтобы убедиться, что вызывающий пакет имеет правильную подпись.
Шаг 4. Проверьте сертификат подписи вызывающего абонента
Вы можете проверить имя пакета вызывающего абонента с помощью Binder.getCallingUid()
в IS_READY_TO_PAY
и с помощью Activity.getCallingPackage()
в PAY
. Чтобы действительно убедиться, что вызывающим абонентом является браузер, который вы имеете в виду, вам следует проверить его сертификат подписи и убедиться, что он соответствует правильному значению.
Если вы ориентируетесь на уровень API 28 и выше и выполняете интеграцию с браузером, имеющим один сертификат подписи, вы можете использовать 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()
предпочтителен для браузеров с одним сертификатом, поскольку он правильно обрабатывает ротацию сертификатов. (У Chrome один сертификат подписи.) Приложения, имеющие несколько сертификатов подписи, не могут их чередовать.
Если вам необходимо поддерживать более старые уровни API 27 и ниже или если вам нужно обрабатывать браузеры с несколькими сертификатами подписи, вы можете использовать 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) } }