> 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: カードを変更
                        // `.authenticationCompleted` イベントで startEmulation を開始したい場合
                        // try await session.startEmulation()
                        break
                    case .posConnected:
                        print("StartPayment:: posConnected")
                        await session.setAlertMessage("POS Connected")
                        // アプリが NFCPresentmentIntentAssertion を取得していない場合、このイベントで startEmulation() を呼び出すことはできません。
                        try await session.startEmulation()
                        break
                    case .posDisconnected:
                        print("StartPayment:: posDisconnected")
                        await session.setAlertMessage("POS Disconnected")
                    case .transactionCompleted(let context):
                        print("StartPayment:: transactionCompleted")
                        await session.setAlertMessage("Transaction Completed")
                        
                        // ユーザーに表示する
                        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 呼び出し中に、指定されたデジタルカード ID をデフォルトカードとして設定しようとした際に発生したエラー。
                            break
                        case .invalidDigitalCardID:
                            // `startPayment(withDigitalCardID:)` に渡されたデジタルカード 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: encountered unknown case")
                    }
                }
                
                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:

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

The question should be specific, self-contained, and written in natural language.
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.
