> 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/nfc-wallet-sdk-ios/ja/implement-nfc-wallet/make-payments/implement-contactless-payment/full-implementation-example.md).

# 完全な実装例

このコードサンプルを使って、前の手順がどのように組み合わさるかを理解してください。

以下から非接触決済を開始します:

* **ダブルクリック** (`NFCWindowSceneEvent.presentation`)
* **フィールド検出** (`NFCWindowSceneEvent.readerDetected`)

{% hint style="info" %}
このサンプルでは、個々の実装手順をすでに確認済みであることを前提としています。
{% endhint %}

### 完全なソースコード

{% code title="SceneDelegate.swift" expandable="true" %}

```swift
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
    
    var window: UIWindow?
    var rootViewController: UIViewController?
    let session = ContactlessPaymentSession()
    
    // ダブルクリックまたはフィールド検出が検出された場合は true に設定します
    var isDefaultPaymentApp = false
    
    // iOS 18 ではフィールド検出時にエミュレーションを開始する必要がある場合があるため、実施します
    var isPaymentOngoing = false
    
    // アプリケーションが実行されていない場合に、ダブルクリックまたはフィールド検出アクションによる非接触決済の開始をサポートするには:
    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        // このメソッドを使用して、必要に応じて UIWindow `window` を構成し、提供された UIWindowScene `scene` に追加します。
        // ストーリーボードを使用している場合、`window` プロパティは自動的に初期化され、シーンに追加されます。
        // このデリゲートは、接続中のシーンまたはセッションが新規であることを意味しません（代わりに `application:configurationForConnectingSceneSession` を参照してください）。
        guard let windowScene = (scene as? UIWindowScene) else { return }
        
        let storyboard = UIStoryboard(name: "Main", bundle: nil)
        let landingVC = storyboard.instantiateViewController(identifier: "LandingSB")
        
        let window = UIWindow(windowScene: windowScene)
        window.rootViewController = landingVC
        window.makeKeyAndVisible()
        self.window = window
        
        rootViewController = landingVC
        
        // 画面に入るたびに false に設定します
        isDefaultPaymentApp = false
        
        if #available(iOS 17.4, *) {
            if let nfcEvent = connectionOptions.nfcEvent {
                switch nfcEvent {
                case .presentation:
                    // デフォルトの決済アプリの場合にのみトリガーされるため true に設定します
                    isDefaultPaymentApp = true
                    startPayment()
                case .readerDetected:
                    // デフォルトの決済アプリの場合にのみトリガーされるため true に設定します
                    isDefaultPaymentApp = true
                    handleFieldDetect(withCurrentVC: window.rootViewController)
                @unknown default: break
                }
            }
        }
    }
    
    func sceneDidEnterBackground(_ scene: UIScene) {
        // 退出するたびに false に設定します。別のアプリを既定に設定する可能性があるためです
        isDefaultPaymentApp = false
        
        // シーンがフォアグラウンドからバックグラウンドへ移行するときに呼び出されます。
        // このメソッドを使用してデータを保存し、共有リソースを解放し、シーン固有の状態情報を十分に保存します
        // これにより、シーンを現在の状態に復元できます。
        if #available(iOS 17.4, *) {
            Task {
                // デフォルトの決済アプリでは、セッション開始後、APDU を受け入れてバックグラウンドで決済を完了します
                // これは、アプリがバックグラウンドに移行する際にキャンセルするためです。
                // このアクションは、アプリが決済画面以外の画面へ移動する場合などにも推奨されます。
                await session.cancel()
            }
        }
    }
    

}

extension SceneDelegate: NFCWindowSceneDelegate {
    @available(iOS 17.4, *)
    // アプリケーションがバックグラウンドまたはフォアグラウンドにある場合に、ダブルクリックまたはフィールド検出アクションによる非接触決済の開始をサポートするには:
    func windowScene(_ windowScene: UIWindowScene, didReceiveNFCWindowSceneEvent event: NFCWindowSceneEvent) {
        switch event {
        case .presentation:
            isDefaultPaymentApp = true
            startPayment()
        case .readerDetected:
            isDefaultPaymentApp = true
            handleFieldDetect(withCurrentVC: rootViewController)
        @unknown default: break
        }
    }
}

extension SceneDelegate {
    
    // ダブルクリックで支払い開始を示す UI を表示します
    func displayAlertWithDoubleClickStartPayment(withCurrentVC currentVC: UIViewController?) {
        let alertController = UIAlertController(title: "支払いを開始するにはダブルクリックしてください", message: nil, preferredStyle: .alert)
        alertController.addAction(
            UIAlertAction(title: "OK", style: .default)
        )
        currentVC?.present(alertController, animated: false)
    }
    
    // ダブルクリックのプロンプトが必要な場合は true を返します
    @available(iOS 17.4, *)
    func handleFieldDetect(withCurrentVC currentVC: UIViewController?){
        // 通常のフィールド検出か、進行中の paymentSession に対する readerDetect とみなすかを判断します
        // iOS 18 以降のみ
        if #available(iOS 18, *), isPaymentOngoing {
            // 進行中の支払いで readerDetect イベントを受信した iOS 18 以降の場合
            Task {
                try await self.session.startEmulation()
            }
        } else {
            // 以下のシナリオではフィールド検出を通常どおり処理します:
            // - iOS 17.4 以降、支払いの進行中/非進行中を問わず。
            // - iOS 18.x、支払いが進行中でない場合。
            
            let biometricType = biometricType()
            // 指紋の場合は直接支払いを開始します
            if biometricType == .touchID {
                startPayment()
            } else {
                displayAlertWithDoubleClickStartPayment(withCurrentVC: currentVC)
            }
        }
    }
    
    func startPayment(withDigitalCardID digitalCardID: String? = nil) {
        Task {
            
            // SDK を初期化します
            try await TSHPay.shared.configure(withVerificationMethod: .userPresence)
            
            if #available(iOS 17.4, *) {
                
                // 支払いが進行中であることを示します。フィールド検出および iOS 18 で必要です
                isPaymentOngoing = false
                                
                // デフォルトの決済アプリかどうか、および presentmentIntent が有効かどうかを確認します
                if !isDefaultPaymentApp, !PresentmentIntentWrapper.isValid() {
                    do {
                        // デフォルトの決済アプリとして検出されていないため、presentmentIntentAssertion の取得を試みます
                        try await PresentmentIntentWrapper.acquire()
                        print("presentmentIntent assertion acquired \(PresentmentIntentWrapper.isValid())")
                    } catch {
                        // 失敗しました... エラーまたはクールダウンが原因です
                        // ここでは直接エミュレーションを開始する必要があります。少なくともアプリの画面遷移は発生しません
                    }
                }
                
                if let digitalCardID {
                    print("StartPayment:: with digitalCardID")
                    await session.startPayment(withDigitalCardID: digitalCardID)
                } else {
                    print("StartPayment:: without digitalCardID")
                    await session.startPayment()
                }
                
                
                for await event in await session.eventStream {
                    switch event {
                    case .authenticationRequired(let action):
                        print("StartPayment:: authenticationRequired")
                        Task {
                            action.proceed()
                        }
                    case .authenticationCompleted:
                        print("StartPayment:: authenticationCompleted")
                        isPaymentOngoing = true
                        // シナリオ A: カードを変更
                        // Event .authenticationCompleted で startEmulation したい場合
                        // try await session.startEmulation()
                        break
                    case .posConnected:
                        print("StartPayment:: posConnected")
                        await session.setAlertMessage("POS 接続済み")
                        // アプリケーションが NFCPresentmentIntentAssertion を取得していない場合、このイベントで startEmulation() を呼び出すことはできません。
                        try await session.startEmulation()
                        break
                    case .posDisconnected:
                        print("StartPayment:: posDisconnected")
                        await session.setAlertMessage("POS 切断済み")
                    case .transactionCompleted(let context):
                        print("StartPayment:: transactionCompleted")
                        await session.setAlertMessage("取引完了")
                        
                        // ユーザーに表示
                        displayTransactionContext(context)
                    case .errorEncountered(let error):
                        switch (error as? ContactlessPaymentSession.Error) {
                        case .cancelled:
                            // エンドユーザーが取引をキャンセルしました。
                            break
                        case .maxSessionDurationReached:
                            // 取引がタイムアウトしたことをエンドユーザーに表示します。
                            break
                        case .nfcPermissionNotAccepted:
                            // 権限が許可されていないことをエンドユーザーに表示します。
                            break
                        case .systemEligibilityFailed:
                            // システムが非接触決済に適していないことをエンドユーザーに表示します。
                            // 例: Apple ID またはデバイスの場所が EEA ではありません。
                            break
                        case .noPaymentKeys:
                            // 補充が必要です。補充ガイドを参照してください。
                            break
                        case .noDefaultCard:
                            // 取引前にデフォルトカードを設定する必要があります。
                            break
                        case .keychainError:
                            // エンドユーザーにエラーメッセージを表示します
                            break
                        case .apduFailure(let reason):
                            // 再試行します
                            break
                        case .unknown(let underlying):
                            // エンドユーザーにエラーメッセージを表示します
                            break
                        case .transmissionError:
                            // デバイスと POS リーダー間の APDU 交換でエラーが発生しました
                            break
                        case .sessionInvalidated:
                            // カードセッションはシステムによって無効化されました
                            break
                        case .deviceEnvironmentUnsafe(_):
                            // デバイス環境が安全でないエラー
                            break
                        case .setDefaultCardFailure:
                            // `startPayment(withDigitalCardID:)` API 呼び出し中に、提供された Digital Card ID をデフォルトカードとして設定しようとしてエラーが発生しました。
                            break
                        case .invalidDigitalCardID:
                            // ``startPayment(withDigitalCardID:)`` で提供された Digital Card ID が無効です
                            break
                        case .authenticationExpired:
                            // 認証の検証後に支払いが行われた際に発生するエラーです。
                            break
                        case .posNotSupported:
                            // POS の一覧に選択した AID がない場合に発生するエラーです。
                            break
                        case .cardNotSupported:
                            // このカードは非接触決済に対応していません。
                            break
                        case .cardNotActive:
                            // 選択したカードはアクティブではありません。
                            break
                        case .authenticationFailed:
                            // 認証が失敗したときに発生するエラーです。
                            break
                        case .authenticationKeyInvalidated(let underlying):
                            // デバイスのパスコードがオフになったかリセットされ、セキュリティリセットがトリガーされたため、認証キーは無効化されました。
                            // このエラーが発生すると、SDK は保存された認証情報を自動的に消去します。
                            // アプリは再登録の手順をユーザーに案内する必要があります。
                            // カードを再読み込みする通知を送信します。 
                            showAlert(message: underlying.localizedDescription) {
                              NotificationCenter.default.post(name: .didReceiveRequestForReloadCard, object: nil)
                            }
                            break
                        case .biometricNotEnrolled(let message):
                            // 生体認証の登録を行うよう、エンドユーザーにメッセージを表示します。
                            showAlert(message: "\(message)")
                            break
                        @unknown default:
                            break
                        }
                    @unknown default:
                        fatalError("fixme: 未知のケースに遭遇しました")
                    }
                }
                
                isPaymentOngoing = false
            } else {
                // 以前のバージョンではフォールバックします。
                let alertController = UIAlertController(title: "iOS 17.4 以降のデバイスを使用してください", message: nil, preferredStyle: .alert)
                alertController.addAction(
                    UIAlertAction(title: "OK", style: .default)
                )
                rootViewController?.present(alertController, animated: false)
            }
        }
    }
    
    func biometricType() -> LABiometryType {
        let authContext = LAContext()
        if #available(iOS 11, *) {
            authContext.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: nil)
            return authContext.biometryType
        }
        return LABiometryType.none
    }
}
```

{% endcode %}


---

# 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/nfc-wallet-sdk-ios/ja/implement-nfc-wallet/make-payments/implement-contactless-payment/full-implementation-example.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.
