Android 決済アプリ デベロッパー ガイド

Android 決済アプリを Web Payments と連携させ、お客様のユーザー エクスペリエンスを向上させる方法について説明します。

Payment Request API は、 必要な支払いを入力できる、組み込みのブラウザベースのインターフェース 情報を簡単に入手できます。この API では、プラットフォーム固有の支払いサービスも 。

対応ブラウザ

  • Chrome: 60。 <ph type="x-smartling-placeholder">
  • Edge: 15。 <ph type="x-smartling-placeholder">
  • Firefox: 旗の裏側。
  • Safari: 11.1。 <ph type="x-smartling-placeholder">

ソース

<ph type="x-smartling-placeholder">
</ph>
ウェブ決済を使用するプラットフォーム固有の Google Pay アプリでの購入手続きフロー。

Web Payments を使用すると、Android のインテントのみを使用する場合に比べて、 ブラウザ、セキュリティ、ユーザー エクスペリエンスのバランスを取ります。

  • 決済アプリが販売者のウェブサイトのコンテキスト内でモーダルとして起動される。
  • 実装は既存の決済アプリを補完するもので、以下のことが可能になります。 ユーザーベースを活用できます
  • 決済アプリの署名は、 サイドローディング
  • 決済アプリは複数のお支払い方法に対応できます。
  • 仮想通貨や銀行振込など、あらゆる支払い方法が 統合されていますAndroid デバイスの決済アプリには、 デバイスのハードウェア チップにアクセスする必要がある。
で確認できます。

Android 決済アプリにウェブ決済を実装するには、次の 4 つのステップを行います。

  1. 販売者が決済アプリを検出できるようにします。
  2. お客様がお支払い方法(クレジットなど)を登録しているかどうかを販売者に知らせます。 カード)が表示されます。
  3. お客様に支払いを行ってもらいます。
  4. 呼び出し元の署名証明書を検証します。

Web Payments の実際の動作については、 android-web-payment 説明します。

ステップ 1: 販売者が決済アプリを検出できるようにする

販売者がお客様の決済アプリを使用するには、Google Pay Request API と ご利用いただけるお支払い方法を指定します。 できます

決済アプリに固有のお支払い方法 ID がある場合、 独自の支払い方法を設定できる マニフェストを使用して、ブラウザが 表示されます。

ステップ 2: お客様のお支払い方法が支払い可能な状態になっているかどうかを販売者に知らせる

販売者は hasEnrolledInstrument() を呼び出して、顧客が ユーザーが支払いを行うことができます。Google Chat では このクエリに応答するために、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 サービスは省略可能です。そのようなインテント ハンドラが 決済アプリと通信する場合、ウェブブラウザは、 お支払い。

AIDL

IS_READY_TO_PAY サービスの API は AIDL で定義されています。2 つの AIDL を作成する 次のコンテンツを含むファイル:

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);
}

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() を使用して発信者を確認できます。なお、 これは、onBind メソッドではなく isReadyToPay メソッドで行う必要があります。

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

詳しくは、呼び出し元の署名証明書を確認するをご覧ください。 を呼び出して、呼び出し元パッケージの署名が正しいことを確認します。

ステップ 3: お客様に支払いをしてもらう

販売者が show() を呼び出して、支払いを開始します。 アプリ お客様が支払いを行えるようにします。決済アプリが Android スマートフォンを介して インテント パラメータに取引情報を含むインテント PAY

決済アプリは、決済アプリである methodNamedetails で応答します。 ブラウザからは見えません。ブラウザが details を変換する JSON 逆シリアル化によって販売者の JavaScript オブジェクトに変換しますが、 それ以外はいかなる有効性も強制しません。ブラウザは、 details、パラメータの値が販売者に直接渡されます。

AndroidManifest.xml

PAY インテント フィルタが設定されたアクティビティには、<meta-data> タグ デフォルトの支払い方法 ID を識別します。 アプリ

複数のお支払い方法をサポートするには、<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 は文字列のリストにする必要があります。各文字列は有効な文字列である必要があります。 HTTPS スキームを使用した絶対 URL です。

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

パラメータ

以下のパラメータは、インテント エクストラとしてアクティビティに渡されます。

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

methodNames

使用されているメソッドの名前。要素はモデルのキーで、 methodData 辞書。決済アプリでサポートされている方法です。

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

methodData

methodNames から methodData

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

merchantName

販売者の購入手続きページ(<title> ブラウザのトップレベルのブラウジング コンテキストなど)が、

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

topLevelOrigin

スキームのない販売者のオリジン(スキームのないオリジン 。たとえば、https://mystore.com/checkout は次のようになります。 mystore.com として渡されます。

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

topLevelCertificateChain

販売者の証明書チェーン(最上位の 閲覧コンテキストなど)が含まれます。ローカルホストとディスク上のファイルには null(どちらも安全) SSL 証明書なしのコンテキストで識別されます。各 Parcelable は、 certificate キーとバイト配列値。

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

paymentRequestOrigin

JavaScript で new PaymentRequest(methodData, details, options) コンストラクタを呼び出した iframe ブラウジング コンテキストの、スキームのないオリジン。もし 呼び出された場合、この パラメータが 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.modifierssupportedMethodstotal のみを含む。

paymentRequestId

「push-payment」の PaymentRequest.id フィールド関連付ける必要がある トランザクション状態を記録します。販売者のウェブサイトでは、このフィールドを使用して &quot;push-payment&quot;トランザクションの状態を確認します。

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

レスポンス

アクティビティは、RESULT_OK を使用して setResult を介してレスポンスを送り返すことができます。

setResult(Activity.RESULT_OK, Intent().apply {
  putExtra("methodName", "https://bobbucks.dev/pay")
  putExtra("details", "{\"token\": \"put-some-data-here\"}")
})
finish()

インテント エクストラとして次の 2 つのパラメータを指定する必要があります。

  • methodName: 使用されているメソッドの名前。
  • details: 販売者が行うために必要な情報を含む JSON 文字列。 トランザクションを完了します。成功が true の場合、detailsJSON.parse(details) が成功するように構築します。

トランザクションが完了していない場合は RESULT_CANCELED を渡すことができます。 たとえば、ユーザーが Google Pay で PIN コードの正しい入力に失敗した場合などです。 。ブラウザでは、ユーザーがリクエストする内容を 別の決済アプリを使用していました

setResult(RESULT_CANCELED)
finish()

呼び出された支払いから受信した支払いレスポンスのアクティビティの結果 アプリが RESULT_OK に設定されている場合、Chrome は空でない methodName の有無を確認し、 エクストラで details。検証で不合格だと、Chrome は不承認の 次のいずれかのデベロッパー向けエラーを含む request.show() からの Promise メッセージ:

'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_PAYPAYActivity.getCallingPackage()。目的 想定したブラウザであることを確かめるには、 署名証明書をチェックして、正しいものと一致することを あります。

API レベル 28 以降をターゲットとし、ブラウザと統合する場合 署名証明書が 1 つしかない場合は、 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 には 単一の署名証明書を使用します)。複数の署名証明書があるアプリの場合、 回転させます。

27 以前の古い API レベルをサポートする必要がある場合、または 複数の署名証明書を持つブラウザでは、 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) } }