Android 支払いアプリから配送情報と連絡先情報を提供する

Web Payments API を使用して、Android 決済アプリを更新し、配送先住所と支払人の連絡先情報を提供する方法。

Sahel Sharify
Sahel Sharify

ウェブフォームから配送先住所や連絡先情報を入力するのは、お客様にとって煩雑な作業です。エラーが発生してコンバージョン率が低下する可能性があります。

そのため、Payment Request API は、配送先住所と連絡先情報をリクエストする機能をサポートしています。これには、次のような複数のメリットがあります。

  • 数回タップするだけで正しい住所を選択できます。
  • 住所は常に標準化された形式で返されます。
  • 間違った住所を送信する可能性は低くなります。

ブラウザは、統一された支払いエクスペリエンスを提供するために、配送先住所と連絡先情報の収集を支払いアプリに延期できます。この機能は委任と呼ばれます。

Chrome は可能な限り、顧客の配送先住所と連絡先情報の収集を、呼び出された Android 支払いアプリに委任します。委任により、購入手続き時の負担が軽減されます。

販売者のウェブサイトでは、顧客が選択した配送先住所と配送オプションに応じて、配送オプションと合計金額が動的に更新されます。

配送オプションと配送先住所の変更。配送オプションと合計金額に動的にどう影響するかを確認します。

既存の Android 支払いアプリに委任サポートを追加するには、次の手順を行います。

  1. サポートされている委任を宣言する
  2. PAY インテント エクストラを解析して、必要な支払いオプションを特定します
  3. 支払いレスポンスで必要な情報を提供します
  4. [省略可] 動的フローをサポートする:
    1. ユーザーが選択したお支払い方法、配送先住所、配送オプションの変更について販売者に通知する
    2. 販売者から最新のお支払いの詳細(選択した配送オプションの費用に基づいて調整された合計金額など)を受け取る

サポートされている委任を宣言する

ブラウザは、決済アプリが提供できる追加情報のリストを認識し、その情報の収集をアプリに委任できるようにする必要があります。サポートされている委任をアプリの AndroidManifest.xml<meta-data> として宣言します。

<activity
  android:name=".PaymentActivity"
  …
  <meta-data
    android:name="org.chromium.payment_supported_delegations"
    android:resource="@array/supported_delegations" />
</activity>

<resource> には、次の有効な値から選択した文字列のリストを指定する必要があります。

[ "payerName", "payerEmail", "payerPhone", "shippingAddress" ]

次の例では、配送先住所と支払人のメールアドレスのみを指定できます。

<?xml version="1.0" encoding="utf-8"?>
<resources>
  <string-array name="supported_delegations">
    <item>payerEmail</item>
    <item>shippingAddress</item>
  </string-array>
</resources>

PAY インテント エクストラを解析して必要な支払いオプションを取得

販売者は paymentOptions 辞書を使用して、追加の必須情報を指定できます。Chrome は、以下のパラメータをインテント エクストラとして PAY アクティビティに渡すことで、アプリが必要とするオプションのリストを提供します。

paymentOptions

paymentOptions は、アプリで委任サポートが宣言されている、販売者が指定した支払いオプションのサブセットです。

val paymentOptions: Bundle? = extras.getBundle("paymentOptions")
val requestPayerName: Boolean? = paymentOptions?.getBoolean("requestPayerName")
val requestPayerPhone: Boolean? = paymentOptions?.getBoolean("requestPayerPhone")
val requestPayerEmail: Boolean? = paymentOptions?.getBoolean("requestPayerEmail")
val requestShipping: Boolean? = paymentOptions?.getBoolean("requestShipping")
val shippingType: String? = paymentOptions?.getString("shippingType")

次のパラメータを含めることができます。

  • requestPayerName - 支払人の名前が必須かどうかを示すブール値。
  • requestPayerPhone - 支払人のスマートフォンが必須かどうかを示すブール値。
  • requestPayerEmail - 支払人のメールアドレスが必須かどうかを示すブール値。
  • requestShipping - 配送情報が必須かどうかを示すブール値。
  • shippingType - 送料のタイプを示す文字列です。配送タイプは "shipping""delivery""pickup" のいずれかです。アプリの UI でこのヒントは、ユーザーの住所または配送オプションの選択を尋ねるときに使用できます。

shippingOptions

shippingOptions は、販売者が指定した配送オプションの Parcelable 配列です。このパラメータは、paymentOptions.requestShipping == true の場合にのみ存在します。

val shippingOptions: List<ShippingOption>? =
    extras.getParcelableArray("shippingOptions")?.mapNotNull {
        p -> from(p as Bundle)
    }

各配送オプションは、次のキーを含む Bundle です。

  • id - 配送オプションの ID。
  • label - ユーザーに表示される配送オプションのラベル。
  • amount - 文字列値を持つ currency キーと value キーを含む送料バンドル。
    • currency は、送料の通貨を ISO4217 の正しい形式の 3 文字のアルファベットコードで示します。
    • value は、送料を有効な小数値で表示します。
  • selected - 支払いアプリに配送オプションを表示する際に配送オプションを選択するかどうか。

selected 以外のキーはすべて文字列値を持ちます。selected はブール値です。

val id: String = bundle.getString("id")
val label: String = bundle.getString("label")
val amount: Bundle = bundle.getBundle("amount")
val selected: Boolean = bundle.getBoolean("selected", false)

支払いレスポンスで必要な情報を提供する

アプリは、PAY アクティビティに対するレスポンスに必要な追加情報を含める必要があります。

そのためには、以下のパラメータをインテント エクストラとして指定する必要があります。

  • payerName - 支払人の氏名。paymentOptions.requestPayerName が true の場合は、空でない文字列にする必要があります。
  • payerPhone - 支払人の電話番号。paymentOptions.requestPayerPhone が true の場合は、空でない文字列にする必要があります。
  • payerEmail - 支払人のメールアドレス。paymentOptions.requestPayerEmail が true の場合は、空でない文字列にする必要があります。
  • shippingAddress - ユーザー指定の配送先住所。paymentOptions.requestShipping が true の場合、空でないバンドルになります。バンドルには、物理アドレスのさまざまな部分を表す次のキーが含まれている必要があります。
    • city
    • countryCode
    • dependentLocality
    • organization
    • phone
    • postalCode
    • recipient
    • region
    • sortingCode
    • addressLine addressLine 以外のすべてのキーは文字列値を持ちます。addressLine は文字列の配列です。
  • shippingOptionId - ユーザーが選択した配送オプションの ID。paymentOptions.requestShipping が true の場合は、空でない文字列にする必要があります。

支払いレスポンスを検証する

呼び出された支払いアプリから受信した支払いレスポンスのアクティビティ結果が RESULT_OK に設定されている場合、Chrome はエクストラに必須の追加情報があるかどうかをチェックします。検証で不合格になると、Chrome は拒否された Promise を request.show() から返し、次のいずれかのデベロッパー向けエラー メッセージが返されます。

'Payment app returned invalid response. Missing field "payerEmail".'
'Payment app returned invalid response. Missing field "payerName".'
'Payment app returned invalid response. Missing field "payerPhone".'
'Payment app returned invalid shipping address in response.'
'... is not a valid CLDR country code, should be 2 upper case letters [A-Z]'
'Payment app returned invalid response. Missing field "shipping option".'

次のコードサンプルは、有効なレスポンスの例です。

fun Intent.populateRequestedPaymentOptions() {
    if (requestPayerName) {
        putExtra("payerName", "John Smith")
    }
    if (requestPayerPhone) {
        putExtra("payerPhone", "4169158200")
    }
    if (requestPayerEmail) {
        putExtra("payerEmail", "john.smith@gmail.com")
    }
    if(requestShipping) {
        val address: Bundle = Bundle()
        address.putString("countryCode", "CA")
        val addressLines: Array<String> =
                arrayOf<String>("111 Richmond st. West")
        address.putStringArray("addressLines", addressLines)
        address.putString("region", "Ontario")
        address.putString("city", "Toronto")
        address.putString("postalCode", "M5H2G4")
        address.putString("recipient", "John Smith")
        address.putString("phone", "4169158200")
        putExtra("shippingAddress", address)
        putExtra("shippingOptionId", "standard")
    }
}

省略可: 動的フローをサポートする

ユーザーがエクスプレス配送オプションを選択した場合や、ユーザーが国際配送オプションを選択したときに利用可能な配送オプションのリストや価格が変更された場合など、取引の合計金額が増加することがあります。ユーザーが選択した配送先住所やオプションをアプリで提供する場合、配送先住所やオプションの変更を販売者に通知し、更新されたお支払い情報(販売者が提供)をユーザーに表示できるようにする必要があります。

AIDL

新しい変更を販売者に通知するには、Chrome の AndroidManifest.xml で宣言されている PaymentDetailsUpdateService サービスを使用します。このサービスを使用するには、次の内容の 2 つの AIDL ファイルを作成します。

app/src/main/aidl/org/chromium/components/payments/IPaymentDetailsUpdateService

package org.chromium.components.payments;
import android.os.Bundle;

interface IPaymentDetailsUpdateServiceCallback {
    oneway void updateWith(in Bundle updatedPaymentDetails);

    oneway void paymentDetailsNotUpdated();
}

app/src/main/aidl/org/chromium/components/payments/IPaymentDetailsUpdateServiceCallback

package org.chromium.components.payments;
import android.os.Bundle;
import org.chromium.components.payments.IPaymentDetailsUpdateServiceCallback;

interface IPaymentDetailsUpdateService {
    oneway void changePaymentMethod(in Bundle paymentHandlerMethodData,
            IPaymentDetailsUpdateServiceCallback callback);

    oneway void changeShippingOption(in String shippingOptionId,
            IPaymentDetailsUpdateServiceCallback callback);

    oneway void changeShippingAddress(in Bundle shippingAddress,
            IPaymentDetailsUpdateServiceCallback callback);
}

ユーザーが選択したお支払い方法、配送先住所、配送オプションの変更について販売者に通知する

private fun bind() {
    // The action is introduced in Chrome version 92, which supports the service in Chrome
    // and other browsers (e.g., WebLayer).
    val newIntent = Intent("org.chromium.intent.action.UPDATE_PAYMENT_DETAILS")
        .setPackage(callingBrowserPackage)
    if (packageManager.resolveService(newIntent, PackageManager.GET_RESOLVED_FILTER) == null) {
        // Fallback to Chrome-only approach.
        newIntent.setClassName(
            callingBrowserPackage,
            "org.chromium.components.payments.PaymentDetailsUpdateService")
        newIntent.action = IPaymentDetailsUpdateService::class.java.name
    }
    isBound = bindService(newIntent, connection, Context.BIND_AUTO_CREATE)
}

private val connection = object : ServiceConnection {
    override fun onServiceConnected(className: ComponentName, service: IBinder) {
        val service = IPaymentDetailsUpdateService.Stub.asInterface(service)
        try {
            if (isOptionChange) {
                service?.changeShippingOption(selectedOptionId, callback)
            } else (isAddressChange) {
                service?.changeShippingAddress(selectedAddress, callback)
            } else {
                service?.changePaymentMethod(methodData, callback)
            }
        } catch (e: RemoteException) {
            // Handle the remote exception
        }
    }
}

サービスの開始インテントに使用される callingPackageName は、支払いリクエストを開始したブラウザに応じて、次のいずれかの値になります。

Chrome チャンネル パッケージ名
安定的 "com.android.chrome"
ベータ版 "com.chrome.beta"
開発 "com.chrome.dev"
Canary "com.chrome.canary"
Chromium "org.chromium.chrome"
Google クイック検索ボックス(WebLayer 埋め込み機能) "com.google.android.googlequicksearchbox"

changePaymentMethod

ユーザーが選択したお支払い方法の変更を販売者に通知します。paymentHandlerMethodData バンドルには、文字列値を持つ methodName キーとオプションの details キーが含まれています。Chrome は、空でない methodName を含む空でないバンドルをチェックし、検証が失敗した場合は、callback.updateWith を介して次のいずれかのエラー メッセージを含む updatePaymentDetails を送信します。

'Method data required.'
'Method name required.'

changeShippingOption

ユーザーが選択した配送オプションの変更を販売者に通知します。 shippingOptionId は、販売者が指定した配送オプションのいずれか 1 つの ID にする必要があります。Chrome は空でない shippingOptionId をチェックし、検証が失敗した場合は callback.updateWith を介して次のエラー メッセージを含む updatePaymentDetails を送信します。

'Shipping option identifier required.'

changeShippingAddress

ユーザーが指定した配送先住所が変更されたことを販売者に通知します。Chrome は、有効な countryCode を持つ空でない shippingAddress バンドルをチェックし、検証が失敗した場合は callback.updateWith を介して次のエラー メッセージを含む updatePaymentDetails を送信します。

'Payment app returned invalid shipping address in response.'

無効な状態のエラー メッセージ

Chrome は、変更リクエストの受信時に無効な状態を検出すると、除去された updatePaymentDetails バンドルで callback.updateWith を呼び出します。バンドルには、"Invalid state" が指定された error キーのみが含まれます。無効な状態の例を次に示します。

  • Chrome が以前の変更(進行中の変更イベントなど)に対する販売者の応答を待機している場合。
  • 支払いアプリが提供する配送オプションの ID が、販売者が指定した配送オプションのいずれにも属していない。

更新されたお支払い情報を販売者から受け取る

private fun unbind() {
    if (isBound) {
        unbindService(connection)
        isBound = false
    }
}

private val callback: IPaymentDetailsUpdateServiceCallback =
    object : IPaymentDetailsUpdateServiceCallback.Stub() {
        override fun paymentDetailsNotUpdated() {
            // Payment request details have not changed.
            unbind()
        }

        override fun updateWith(updatedPaymentDetails: Bundle) {
            newPaymentDetails = updatedPaymentDetails
            unbind()
        }
    }

updatePaymentDetails は、PaymentRequestDetailsUpdate WebIDL 辞書と同等のバンドルで(modifiers フィールドを削除した後)、次のオプションのキーを含みます。

  • total - currency キーと value キーを含むバンドル。どちらのキーも文字列値を持ちます。
  • shippingOptions - 配送オプションの Parcelable 配列
  • error - 一般的なエラー メッセージ(changeShippingOption で有効な配送オプション ID が提供されていない場合など)を含む文字列。
  • stringifiedPaymentMethodErrors - お支払い方法の検証エラーを表す JSON 文字列
  • addressErrors - 配送先住所や文字列値と同一のオプションのキーを含むバンドル。各キーは、配送先住所の対応する部分に関連する検証エラーを表します。

キーが存在しない場合、その値は変更されていません。