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: 送料の値が有効な 10 進数で示されます。
  • 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 は request.show() から拒否された Promise を返します。その際、デベロッパー向けに次のいずれかのエラー メッセージが表示されます。

'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 は、販売者が指定する配送オプションのいずれかの 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()
        }
    }

updatePaymentDetailsPaymentRequestDetailsUpdate WebIDL 辞書と同等のバンドル(modifiers フィールドを秘匿化した後)であり、次のオプションのキーを含みます。

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

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