> For the complete documentation index, see [llms.txt](https://docs.payments.thalescloud.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.payments.thalescloud.io/push-provisioning/ja/implement-push-provisioning/implement-view-and-control/in-app-authentication-for-visa-ctf.md).

# Visa CTFのアプリ内認証

{% hint style="danger" %}
アプリ内認証フローを使用するには、トークナイゼーションサービスが必要です。Thales D1 SDK を使用します。
{% endhint %}

Visa Cloud Token Framework (CTF) は、トークナイゼーション後のeコマース向けデジタルカードに対する信頼を高めるために設計された Visa のフレームワークです。

CTF は 2 つのフローを定義します。デバイスバインディングとカード所有者確認です。

これらのフローにより、加盟店はイシュアとのカード所有者ステップアップ認証フローをトリガーすることで、デジタルカードへの信頼を高めることができます。

フルフローと Visa Cloud Token Framework (CTF) の詳細については、次のセクションを参照してください [Visa Cloud トークン フレームワーク](/tokenization/ja/implement-tokenization/post-tokenization-requests/visa-cloud-token-framework-ctf.md) トークナイゼーションのドキュメント内の

Visa CTF のアプリ内認証フローは、で説明されているフローと非常によく似ています [アプリ内認証](/push-provisioning/ja/implement-push-provisioning/implement-view-and-control/in-app-authentication.md).

手順 \[01] から \[10] までは実質的に同じです。

主な違いはペイロード形式であり、以下の表に示します:

<table><thead><tr><th>デバイスバインディング - ペイロード</th><th>カード所有者確認 - ペイロード</th></tr></thead><tbody><tr><td><pre><code>{
  "panReferenceID": "V-3815023863409817870482",
  "tokenRequestorID": "42301999123",
  "tokenReferenceID": "DNITHE381502386342002358",
  "panLast4": "1234",
  "deviceID": "DEiOiJBMjU2R_0NNS1-ciLCJiI",
  "walletAccountID": "AiOiJBMjU-2_R0NNS1ciLCJiI6",
  "deviceIndex": "1",
  "理由コード": "TOKEN_DEVICE_BINDING"
}
</code></pre></td><td><pre><code>{
  "panReferenceID": "V-3815023863409817870482",
  "tokenRequestorID": "42301999123",
  "tokenReferenceID": "DNITHE381502386342002358",
  "panLast4": "1234",
  "deviceID": "null",
  "walletAccountID": "AiOiJBMjU-2_R0NNS1ciLCJiI6",
  "デバイスインデックス": "null",
  "理由コード": "カード所有者_ステップアップ"
}
</code></pre></td></tr></tbody></table>

標準のトークン化認証と比べて、イシュアアプリケーションは次を検査する必要があります:

* **理由コード**：CTF認証の理由を特定します。
  * `トークンデバイスバインディング`: デバイスのバインドフロー。
  * `カード所有者_ステップアップ`: カード所有者の認証フロー。
* **deviceIndex**: デバイス バインディング フローで使用されます。これは Visa のデバイス参照です。D1 SDK では、bindingReference にマップされます `bindingReference`.

シーケンス図を参照する場合は [アプリ内認証](/push-provisioning/ja/implement-push-provisioning/implement-view-and-control/in-app-authentication.md)、ステップ \[11] と \[12] を新しい API 呼び出しに置き換えてください。

この場合、デジタルカードのアクティベーションはありません。代わりに、認証結果を Visa VTS に伝播する必要があります。

D1 SDK は、イシュアアプリケーションが結果を報告できるように、次の API を公開しています:

1. 〜については `トークンデバイスバインディング`、呼び出します `DigitalCardService.approveBinding` を `digitalCardID` と `deviceIndex`.
2. 〜については `カード所有者_ステップアップ`、呼び出します `DigitalCardService.approveCardholderVerification` を `digitalCardID`.

D1 SDK では、次のこともできます:

1. バインド済みデバイスの一覧を次の形式で取得します: `deviceBindingList` when `getDigitalCardList()` が呼び出されたとき。詳細は、 [デジタルカードの表示と制御](/push-provisioning/ja/implement-push-provisioning/implement-view-and-control/view-and-control-digital-cards.md).
2. 次を呼び出してデバイスのバインド解除を行います `DigitalCardService.unbindDevice`。参照 [デバイスのバインド解除](/push-provisioning/ja/implement-push-provisioning/implement-view-and-control/in-app-authentication-for-visa-ctf/unbind-a-device.md).

以下の例では、ペイロードを解析し、バインド要求を承認する方法を示します。

{% hint style="info" %}
注記

以下のフィールドマッピングが適用されます:

* `tokenReferenceID` = `digitalCardID`
* `deviceIndex` = `bindingReference`
  {% endhint %}

{% tabs %}
{% tab title="Android" %}

```kotlin
private const val VISA_STEPUP_RESULT_RESPONSE = "STEP_UP_RESPONSE"
private const val STEPUP_RESULT_APPROVED = "approved"
private const val STEPUP_RESULT_DECLINED = "declined"
private const val STEPUP_RESULT_FAILED = "failure"

val visaPayload = intent.getStringExtra(Intent.EXTRA_TEXT) ?: return

val payloadJson = try {
    val decodedPayload = String(Base64.decode(visaPayload, Base64.DEFAULT), Charsets.UTF_8)
    JSONObject(decodedPayload)
} catch (_: IllegalArgumentException) {
    // 無効なbase64ペイロード
    return
} catch (_: JSONException) {
    // 無効なJSONペイロード
    return
}

val reasonCode = payloadJson.optString("reasonCode", "")
val digitalCardID = payloadJson.optString("tokenReferenceID", "") // tokenReferenceID -> digitalCardID
val bindingReference = payloadJson.optString("deviceIndex", "") // deviceIndex -> bindingReference

if ("TOKEN_DEVICE_BINDING" == reasonCode &&
    digitalCardID.isNotEmpty() &&
    bindingReference.isNotEmpty()) {

    d1Task.digitalCardService().approveBinding(
        digitalCardID,
        bindingReference,
        DigitalCardService.BindingApprovalReason.USER_DECISION,
        object : D1Task.Callback<Void> {
            override fun onSuccess(data: Void?) {
                sendResultToTokenRequestorApp(activity, null)
            }

            override fun onError(e: D1Exception) {
                sendResultToTokenRequestorApp(activity, e)
            }
        }
    )
} else {
    // その他のフロー（例: アクティベーション）
}

private fun sendResultToTokenRequestorApp(activity: Activity, exception: D1Exception?) {
    val result = Intent()
    // 値の候補: "approved"、"declined"、または "failure"
    val stepupResult = if (exception == null) STEPUP_RESULT_APPROVED else STEPUP_RESULT_FAILED
    result.putExtra(VISA_STEPUP_RESULT_RESPONSE, stepupResult)
    // イシュアが認証コードによるアクティベーションを使用している場合に適用されます
    activity.setResult(Activity.RESULT_OK, result)
    activity.finish()
}
```

```java
private static final String VISA_STEPUP_RESULT_RESPONSE = "STEP_UP_RESPONSE";
private static final String STEPUP_RESULT_APPROVED = "approved";
private static final String STEPUP_RESULT_DECLINED = "declined";
private static final String STEPUP_RESULT_FAILED = "failure";

String visaPayload = getIntent().getStringExtra(Intent.EXTRA_TEXT);
if (visaPayload == null) {
    return;
}

JSONObject payloadJson;
try {
    byte[] decodedPayloadBytes = Base64.decode(visaPayload, Base64.DEFAULT);
    String decodedPayload = new String(decodedPayloadBytes, StandardCharsets.UTF_8);
    payloadJson = new JSONObject(decodedPayload);
} catch (IllegalArgumentException | JSONException e) {
    // 無効な base64/JSON ペイロード
    return;
}

String reasonCode = payloadJson.optString("reasonCode", "");
String digitalCardID = payloadJson.optString("tokenReferenceID", ""); // tokenReferenceID -> digitalCardID
String bindingReference = payloadJson.optString("deviceIndex", ""); // deviceIndex -> bindingReference

if ("TOKEN_DEVICE_BINDING".equals(reasonCode) &&
        !digitalCardID.isEmpty() &&
        !bindingReference.isEmpty()) {

    d1Task.digitalCardService().approveBinding(
            digitalCardID,
            bindingReference,
            DigitalCardService.BindingApprovalReason.USER_DECISION,
            new D1Task.Callback<Void>() {
                @Override
                public void onSuccess(Void data) {
                    sendResultToTokenRequestorApp(activity, null);
                }

                @Override
                public void onError(@NonNull D1Exception exception) {
                    sendResultToTokenRequestorApp(activity, exception);
                }
            });
} else {
    // その他のフロー（例: アクティベーション）
}

private void sendResultToTokenRequestorApp(Activity activity, D1Exception exception) {
    Intent result = new Intent ();
    // 値の候補: "approved"、"declined"、または "failure"
    String stepupResult = (exception == null) ? STEPUP_RESULT_APPROVED : STEPUP_RESULT_FAILED;
    result.putExtra(VISA_STEPUP_RESULT_RESPONSE, stepupResult);
    // イシュアが認証コードによるアクティベーションを使用している場合に適用されます
    activity.setResult(RESULT_OK, result);
    activity.finish();
}
```

{% endtab %}

{% tab title="iOS" %}

```swift
// Walletアプリからのアプリ遷移をサポートするため。
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
    guard let components = URLComponents(url: url, resolvingAgainstBaseURL: true) else { return false }

    let queryDict = components.queryItems?.reduce(into: [String: String]()) { dict, item in
        dict[item.name] = item.value
    } ?? [:]

    switch components.host {
    // Visaのアプリ間連携を処理するサンプルサービス名
    case "visaCTF":
        guard let wpCallback = queryDict["wpcallback"]?.removingPercentEncoding,
              let encodedPayload = queryDict["a2apayload"],
              let payloadData = Data(base64Encoded: encodedPayload.replacingOccurrences(of: " ", with: "+")),
              let json = try? JSONSerialization.jsonObject(with: payloadData) as? [String: Any],
              let reasonCode = json["reasonCode"] as? String else {
            return false
        }

        switch reasonCode {
        case "TOKEN_DEVICE_BINDING":
            guard let digitalCardID = json["tokenReferenceID"] as? String,
                  let bindingReference = json["deviceIndex"] as? String else {
                return false
            }

            Task {
                do {
                    try await d1Task.digitalCardService().approveBinding(
                        digitalCardID,
                        bindingReference: bindingReference,
                        reason: nil
                    )
                    sendResultToApp(wpCallback: wpCallback, stepupResult: "承認済み")
                } catch {
                    sendResultToApp(wpCallback: wpCallback, stepupResult: "失敗")
                }
            }

        default:
            return false
        }

        true を返す
    default:
        return false
    }
}

private func sendResultToApp(wpCallback: String, stepupResult: String) {
    guard var components = URLComponents(string: wpCallback) else { return }

    var items = components.queryItems ?? []
    items.append(URLQueryItem(name: "stepupresponse", value: stepupResult))
    components.queryItems = items

    if let callbackURL = components.url {
        UIApplication.shared.open(callbackURL)
    }
}
```

{% endtab %}
{% endtabs %}

D1 SDK への完全なアクセスについては、ご確認ください [API リファレンス](/push-provisioning/ja/integrate-the-d1-sdk/api-reference.md).


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.payments.thalescloud.io/push-provisioning/ja/implement-push-provisioning/implement-view-and-control/in-app-authentication-for-visa-ctf.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
