建立 NFC 智慧卡應用程式

重要

本主題僅適用於 Windows 10 移動版。

本主題介紹如何使用主機卡模擬 (HCE) 直接與近場通信 (NFC) 讀卡器通信,並讓您的客戶透過手機 (而不是實體卡) 存取您的服務,而無需移動網路營運商 (行動網路營運商)。

開發 HCE 應用程式所需的專案

若要開發以 HCE 為基礎的卡片模擬應用程式,您需要安裝 Microsoft Visual Studio 2015 (請參閱 Visual Studio 下載頁面) (包括 Windows 開發人員工具) 和 Windows 10 行動版模擬器

如需取得設定的詳細資訊,請參閱使用適用於 Windows 10 行動版的 Microsoft 模擬器進行測試。

或者,如果您想要使用實際的 Windows 10 行動版裝置進行測試,而不是包含的 Windows 10 行動版模擬器,您也需要下列專案。

  • 具有 NFC HCE 支援的 Windows 10 行動版裝置。
  • 支援 ISO/IEC 14443-4 和 ISO/IEC 7816-4 通訊協定的讀取器終端機

Windows 10 Mobile 實現的 HCE 服務提供以下功能。

  • 應用程式可以針對想要仿真的卡片註冊小程式標識碼 (AID)。
  • 應用程式通訊協議資料單位 (APDU) 命令和回應組的衝突解決和路由,會根據外部讀取卡選取和使用者喜好設定,對其中一個已註冊的應用程式。
  • 因使用者動作而處理應用程式的事件和通知。

Windows 10 支援以 ISO-DEP (ISO-IEC 14443-4) 為基礎的智慧卡模擬,並使用 ISO-IEC 7816-4 規格中所定義的 APTU 進行通訊。 Windows 10 支援適用於 HCE 應用程式的 ISO/IEC 14443-4 Type A 技術。 類型 B、類型 F 和非 ISO-DEP (例如 MIFARE) 技術預設會路由傳送至 SIM。

只有 Windows 10 行動版裝置已啟用卡片模擬功能。 其他版本的 Windows 10 無法使用 SIM 型和 HCE 型卡片模擬。

下圖顯示 HCE 和 SIM 卡模擬支援的架構。

HCE 和 SIM 卡擬真架構

應用程式選取和 AID 路由

若要開發 HCE 應用程式,您必須了解 Windows 10 行動版裝置如何將 AID 路由傳送至特定應用程式,因為使用者可以安裝多個不同的 HCE 應用程式。 每個應用程式都可以註冊多個 HCE 和 SIM 卡。

當使用者點選其 Windows 10 行動版裝置到終端機時,資料會自動路由傳送到裝置上安裝的適當應用程式。 此路由是以 5-16 位元組識別碼小程式識別碼 (AID) 為基礎。 在點選期間,外部終端機會傳輸 SELECT 命令 APDU 以指定 AID,讓所有後續的 APDU 命令路由傳送至該命令。 後續的 SELECT 命令將會再次變更路由。 根據應用程式和使用者設定所註冊的 AID,APDU 流量會路由傳送至特定應用程式,以傳送回應 APDU。 請注意,終端機可能會想要在相同的點選期間與數個不同的應用程式通訊。 因此,您必須確保應用程式的背景工作在停用時儘快結束,讓另一個應用程式的背景工作騰出空間以回應 APDU。 我們將在本主題稍後討論背景工作。

HCE 應用程式必須向可處理的特定 AID 註冊自己,才能接收 AID 的 APD。 應用程式透過使用 AID 群組來聲明 AID。 AID 組在概念上等同於單獨的實體卡。 例如,一張信用卡被聲明為具有 AID 群組,而來自不同銀行的第二張信用卡被聲明為具有不同的第二個 AID 群組,即使它們可能具有相同的 AID。

支付 AID 組的衝突解決

當應用程式註冊實體卡 (AID群組) 時,它可以將AID群組類別聲明為「付款」或「其他」。 雖然在任何給定時間都可以註冊多個支付 AID 群組,但每次只能啟用這些支付 AID 群組中的一個,由使用者選擇。 造成這種行為的原因是,使用者希望能夠有意識地選擇要使用的單一支付卡、信用卡或金融卡,這樣他們在將裝置觸碰終端時就不會使用不同的意外卡進行支付。

但是,可以同時啟用註冊為「其他」的多個 AID 群組,無需使用者互動。 有這種行為的原因是,其他類型的卡片 (例如會員卡、優惠券或交通卡) 預計在使用者點擊手機時,無需任何動作或提示即可使用。

註冊為「付款」的所有 AID 群組都會出現在 NFC 設定 頁面的卡片清單中,使用者可以在其中選取其預設付款卡。 當選擇預設支付卡時,註冊該支付 AID 群組的應用程式將成為預設支付應用程式。 預設支付應用程式無需使用者互動即可啟用或停用其任何 AID 群組。 如果使用者拒絕預設支付應用程式提示,則目前預設支付應用程式 (如有) 將繼續保持預設狀態。 以下螢幕截圖顯示了 NFC 設定頁面。

NFC 設定頁面的螢幕截圖

使用上面的範例螢幕截圖,如果使用者將其預設支付卡變更為另一張未由「HCE 應用程式 1」註冊的卡,系統會建立一條確認提示以徵求使用者的同意。 但是,如果使用者將預設支付卡變更為「HCE Application 1」註冊的另一張卡,系統不會為使用者建立確認提示,因為「HCE Application1」已經是預設支付應用程式。

非支付 AID 組的衝突解決

歸類為「其他」的非支付卡不會出現在 NFC 設定頁面中。

您的應用程式可以使用與付款 AID 群組相同的方式建立、註冊及啟用非付款 AID 群組。 主要區別在於,對於非付款 AID 群組,模擬類別設定為「其他」而不是「付款」。 向系統註冊 AID 群組之後,您必須讓 AID 群組接收 NFC 流量。 當您嘗試啟用非付費 AID 群組來接收流量時,系統不會提示使用者進行確認,除非與系統中已由其他應用程式註冊的 AID 之一存在衝突。 如果有衝突,如果使用者選擇啟用新註冊的 AID 群組,系統將提示使用者有關將停用哪張卡片及其關聯應用程式的資訊。

與基於 SIM 的 NFC 應用程式共存

在 Windows 10 行動版中,系統設定 NFC 控制器路由表,用於在控制器層做出路由決策。 此表包含以下項目的路由資訊。

  • 個別 AID 路由。
  • 基於協定的路由 (ISO-DEP)。
  • 基於技術的路由 (NFC-A/B/F)。

當外部讀取器傳送「SELECT AID」命令時,NFC 控制器會先檢查路由表中的 AID 路由是否有相符專案。 如果沒有匹配,它將使用基於協定的路由作為 ISO-DEP (14443-4-A) 流量的預設路由。 對於任何其他非 ISO-DEP 流量,它會使用以技術為基礎的路由。

Windows 10 行動版 在 NFC 設定 頁面中提供功能表選項「SIM 卡」,以繼續使用舊版 Windows 電話 8.1 SIM 型應用程式,而不會向系統註冊其 AID。 如果使用者選擇「SIM 卡」作為預設支付卡,則 ISO-DEP 路由將設定為 UICC,對於下拉式選單中的所有其他選擇,ISO-DEP 路由將設定為主機。

當裝置第一次使用 Windows 10 行動版 開機時,具有 SE 啟用 SIM 卡的裝置,ISO-DEP 路由會設定為「SIM 卡」。 當使用者安裝已啟用 HCE 的應用程式,且該應用程式啟用任何 HCE AID 群組註冊時,ISO-DEP 路由會指向主機。 新的 SIM 卡型應用程式需要在 SIM 卡中註冊 AID,才能在控制器路由表中填入特定的 AID 路由。

建立以 HCE 為基礎的應用程式

您的 HCE 應用程式有兩個部分。

  • 使用者互動的主要前景應用程式。
  • 由系統觸發的背景工作,以處理指定 AID 的 APTU。

由於響應 NFC 點擊而載入背景工作的效能要求極其嚴格,因此我們建議您的整個背景工作使用 C++/CX 本機程式碼 (包括您依賴的任何依賴項、參考或庫) 而不是 C# 來實現或託管程式碼。 雖然 C# 和託管程式碼通常表現良好,但存在一些開銷,例如載入 .NET CLR,可以透過用 C++/CX 編寫來避免這些開銷。

建立並註冊您的背景工作

您需要在 HCE 應用程式中建立一個背景工作,用於處理和回應系統路由到它的 APDU。 第一次啟動您的應用程式時,前景會註冊 HCE 背景工作,以實作 IBackgroundTaskRegistration 介面,如下列程式碼所示。

var taskBuilder = new BackgroundTaskBuilder();
taskBuilder.Name = bgTaskName;
taskBuilder.TaskEntryPoint = taskEntryPoint;
taskBuilder.SetTrigger(new SmartCardTrigger(SmartCardTriggerType.EmulatorHostApplicationActivated));
bgTask = taskBuilder.Register();

請注意,工作觸發程式設定為 SmartCardTriggerTypeEmulatorHostApplicationActivated。 這表示每當 OS 解析至應用程式的 OS 收到 SELECT AID 命令 APDU 時,就會啟動您的背景工作。

接收和回應 APTU

當您的應用程式有一個以 APDU 為目標時,系統會啟動您的背景工作。 您的背景工作會接收透過 SmartCardEmulatorApduReceivedEventArgs 物件的 CommandApdu 屬性傳遞的 APDU,並使用相同物件的 TryRespondAsync 方法回應 APDU。 出於效能原因,請考慮將背景工作保留為輕型操作。 例如,立即回應 APTU,並在所有處理完成時結束背景工作。 由於 NFC 交易的性質,使用者通常會在非常短的時間內對讀取器保留其裝置。 您的背景工作會繼續接收來自讀取器的流量,直到您的連線停用為止,在此情況下,您會收到 SmartCardEmulatorConnectionDeactivatedEventArgs 物件。 您可以停用連線,因為 SmartCardEmulatorConnectionDeactivatedEventArgs.Reason 屬性中所述的下列原因。

  • 如果使用 ConnectionLost 值停用連線,這表示使用者已將裝置從讀取器中移除。 如果您的應用程式需要使用者點選到終端機的時間較長,您可以考慮提示他們提供意見反應。 您應該快速終止背景工作 (完成延遲),以確保它們是否再次點選,不會延遲等候先前的背景工作結束。
  • 如果使用 ConnectionRedirected 停用連線,這表示終端機傳送了導向至不同 AID 的新 SELECT AID 命令 APDU。 在此情況下,您的應用程式應該立即結束背景工作 (完成延遲),以允許另一個背景工作執行。

背景工作也應該在 IBackgroundTaskInstance interface 上註冊 Canceled event,並同樣地快速結束背景工作 (藉由完成延遲),因為當系統完成背景工作時會引發此事件。 以下是示範 HCE 應用程式背景工作的程式碼。

void BgTask::Run(
    IBackgroundTaskInstance^ taskInstance)
{
    m_triggerDetails = static_cast<SmartCardTriggerDetails^>(taskInstance->TriggerDetails);
    if (m_triggerDetails == nullptr)
    {
        // May be not a smart card event that triggered us
        return;
    }

    m_emulator = m_triggerDetails->Emulator;
    m_taskInstance = taskInstance;

    switch (m_triggerDetails->TriggerType)
    {
    case SmartCardTriggerType::EmulatorHostApplicationActivated:
        HandleHceActivation();
        break;

    case SmartCardTriggerType::EmulatorAppletIdGroupRegistrationChanged:
        HandleRegistrationChange();
        break;

    default:
        break;
    }
}

void BgTask::HandleHceActivation()
{
 try
 {
        auto lock = m_srwLock.LockShared();
        // Take a deferral to keep this background task alive even after this "Run" method returns
        // You must complete this deferal immediately after you have done processing the current transaction
        m_deferral = m_taskInstance->GetDeferral();

        DebugLog(L"*** HCE Activation Background Task Started ***");

        // Set up a handler for if the background task is cancelled, we must immediately complete our deferral
        m_taskInstance->Canceled += ref new Windows::ApplicationModel::Background::BackgroundTaskCanceledEventHandler(
            [this](
            IBackgroundTaskInstance^ sender,
            BackgroundTaskCancellationReason reason)
        {
            DebugLog(L"Cancelled");
            DebugLog(reason.ToString()->Data());
            EndTask();
        });

        if (Windows::Phone::System::SystemProtection::ScreenLocked)
        {
            auto denyIfLocked = Windows::Storage::ApplicationData::Current->RoamingSettings->Values->Lookup("DenyIfPhoneLocked");
            if (denyIfLocked != nullptr && (bool)denyIfLocked == true)
            {
                // The phone is locked, and our current user setting is to deny transactions while locked so let the user know
                // Denied
                DoLaunch(Denied, L"Phone was locked at the time of tap");

                // We still need to respond to APDUs in a timely manner, even though we will just return failure
                m_fDenyTransactions = true;
            }
        }
        else
        {
            m_fDenyTransactions = false;
        }

        m_emulator->ApduReceived += ref new TypedEventHandler<SmartCardEmulator^, SmartCardEmulatorApduReceivedEventArgs^>(
            this, &BgTask::ApduReceived);

        m_emulator->ConnectionDeactivated += ref new TypedEventHandler<SmartCardEmulator^, SmartCardEmulatorConnectionDeactivatedEventArgs^>(
                [this](
                SmartCardEmulator^ emulator,
                SmartCardEmulatorConnectionDeactivatedEventArgs^ eventArgs)
            {
                DebugLog(L"Connection deactivated");
                EndTask();
            });

  m_emulator->Start();
        DebugLog(L"Emulator started");
 }
 catch (Exception^ e)
 {
        DebugLog(("Exception in Run: " + e->ToString())->Data());
        EndTask();
 }
}

建立和註冊 AID 群組

在布建卡片的第一次啟動期間,您將使用系統建立並註冊 AID 群組。 系統會根據已註冊的 AI 和使用者設定,判斷外部讀取器想要與 APD 通訊的應用程式,並據以路由傳送 APD。

大多數支付卡註冊相同的 AID、近距離支付系統環境 (PPSE),以及其他支付網路卡特定的 AID。 每個 AID 群組都代表卡片,當使用者啟用卡片時,群組中的所有 AI 都會啟用。 同樣地,當使用者停用卡片時,群組中的所有 AID 都會停用。

若要註冊 AID 群組,您必須建立 SmartCardAppletIdGroup 物件,並設定其屬性以反映這是以 HCE 為基礎的付款卡。 您的顯示名稱應該對使用者俱有描述性,因為它將顯示在 NFC 設定選單以及使用者提示中。 針對 HCE 付款卡,SmartCardEmulationCategoryPaymentSmartCardEmulationType 屬性應設定為 Host

public static byte[] AID_PPSE =
        {
            // File name "2PAY.SYS.DDF01" (14 bytes)
            (byte)'2', (byte)'P', (byte)'A', (byte)'Y',
            (byte)'.', (byte)'S', (byte)'Y', (byte)'S',
            (byte)'.', (byte)'D', (byte)'D', (byte)'F', (byte)'0', (byte)'1'
        };

var appletIdGroup = new SmartCardAppletIdGroup(
                        "Example DisplayName",
                                new List<IBuffer> {AID_PPSE.AsBuffer()},
                                SmartCardEmulationCategory.Payment,
                                SmartCardEmulationType.Host);

針對 HCE 非付款卡,SmartCardEmulationCategoryPaymentSmartCardEmulationType 屬性應設定為 Host

public static byte[] AID_OTHER =
        {
            (byte)'1', (byte)'2', (byte)'3', (byte)'4',
            (byte)'5', (byte)'6', (byte)'7', (byte)'8',
            (byte)'O', (byte)'T', (byte)'H', (byte)'E', (byte)'R'
        };

var appletIdGroup = new SmartCardAppletIdGroup(
                        "Example DisplayName",
                                new List<IBuffer> {AID_OTHER.AsBuffer()},
                                SmartCardEmulationCategory.Other,
                                SmartCardEmulationType.Host);

每個 AID 群組最多可以包含 9 個 AID (長度為 5-16 個字節)。

使用 RegisterAppletIdGroupAsync 方法向系統註冊您的 AID 群組,這會傳回 SmartCardAppletIdGroupRegistration 物件。 根據預設,註冊物件的 ActivationPolicy 屬性會設定為 Disabled。 這表示即使您的 AID 已向系統註冊,但尚未啟用它們,而且不會接收流量。

reg = await SmartCardEmulator.RegisterAppletIdGroupAsync(appletIdGroup);

您可以使用 SmartCardAppletIdGroupRegistration 類別的 RequestActivationPolicyChangeAsync 方法來啟用已註冊的卡片 (AID 群組),如下所示。 因為系統一次只能啟用單一付款卡,因此將付款 AID 群組的 ActivationPolicy 設定為 Enabled,與設定預設付款卡相同。 無論是否已選取預設付款卡,都會提示使用者允許此卡片作為預設付款卡。 如果您的應用程式已經是預設付款應用程式,而且只是在它自己的 AID 群組之間變更,則此陳述不是真的。 每個應用程式最多可以註冊 10 個 AID 群組。

reg.RequestActivationPolicyChangeAsync(AppletIdGroupActivationPolicy.Enabled);

您可以使用 OS 查詢應用程式的已註冊 AID 群組,並使用 GetAppletIdGroupRegistrationsAsync 方法來檢查其啟用原則。

當您將付款卡的啟用原則從停用變更為 已啟用時,系統才會提示使用者,只有當您的應用程式還不是預設付款應用程式時。 只有在發生 AID 衝突時,才會提示您將非付款卡的啟用原則從停用變更為 已啟用

var registrations = await SmartCardEmulator.GetAppletIdGroupRegistrationsAsync();
    foreach (var registration in registrations)
    {
registration.RequestActivationPolicyChangeAsync (AppletIdGroupActivationPolicy.Enabled);
    }

啟用原則變更時的事件通知

在您的背景工作中,您可以在應用程式外部變更其中一個 AID 群組註冊的啟用原則時,註冊以接收的事件。 例如,使用者可能會透過 NFC 設定功能表,將預設付款應用程式從其中一張卡片變更為另一個應用程式裝載的另一張卡片。 如果您的應用程式需要知道此變更,例如更新動態磚等內部設定,您可以接收此變更的事件通知,並據以採取動作。

var taskBuilder = new BackgroundTaskBuilder();
taskBuilder.Name = bgTaskName;
taskBuilder.TaskEntryPoint = taskEntryPoint;
taskBuilder.SetTrigger(new SmartCardTrigger(SmartCardTriggerType.EmulatorAppletIdGroupRegistrationChanged));
bgTask = taskBuilder.Register();

前景覆寫行為

當您的應用程式在前景時,您可以將任何 AID 群組註冊的 ActivationPolicy 變更為 ForegroundOverride,而不提示使用者。 當使用者在應用程式處於前景時,將裝置點選到終端機時,即使使用者未選擇任何付款卡作為其預設付款卡,流量也會路由傳送至您的應用程式。 當您將卡片的啟用原則變更為 ForegroundOverride 時,此變更只會暫時性,直到您的應用程式離開前景,且不會變更使用者設定的目前預設付款卡。 您可以從前景應用程式變更付款或非付款卡的 ActivationPolicy,如下所示。 請注意,RequestActivationPolicyChangeAsync 方法只能從前景應用程式呼叫,而且無法從背景工作呼叫。

reg.RequestActivationPolicyChangeAsync(AppletIdGroupActivationPolicy.ForegroundOverride);

此外,您可以註冊包含單一 0 長度 AID 的 AID 群組,這會導致系統路由傳送所有 APDU,而不論 AID 為何,以及包含在收到 SELECT AID 命令之前傳送的任何命令 APDU。 不過,這類 AID 群組只能在您的應用程式處於前景時運作,因為它只能設定為 ForegroundOverride 且無法永久啟用。 此外,此機制適用於 SmartCardEmulationType 列舉的 HostUICC 值,以將所有流量路由傳送至 HCE 背景工作,或傳送至 SIM 卡。

public static byte[] AID_Foreground =
        {};

var appletIdGroup = new SmartCardAppletIdGroup(
                        "Example DisplayName",
                                new List<IBuffer> {AID_Foreground.AsBuffer()},
                                SmartCardEmulationCategory.Other,
                                SmartCardEmulationType.Host);
reg = await SmartCardEmulator.RegisterAppletIdGroupAsync(appletIdGroup);
reg.RequestActivationPolicyChangeAsync(AppletIdGroupActivationPolicy.ForegroundOverride);

檢查 NFC 和 HCE 支援

您的應用程式應該先檢查裝置是否有 NFC 硬體、支援卡片模擬功能,以及支援主機卡模擬,再向使用者提供這類功能。

NFC 智慧卡模擬功能僅在 Windows 10 行動版上啟用,因此嘗試在任何其他版本的 Windows 10 中使用智慧卡模擬器 API 將導致錯誤。 您可以在下列代碼段中檢查智慧卡 API 支援。

Windows.Foundation.Metadata.ApiInformation.IsTypePresent("Windows.Devices.SmartCards.SmartCardEmulator");

您也可以檢查裝置是否有 NFC 硬體能夠進行某種形式的卡片模擬,方法是檢查 ㄌSmartCardEmulator.GetDefaultAsync 方法是否傳回 Null。 如果這樣做,則裝置上不支援 NFC 卡模擬。

var smartcardemulator = await SmartCardEmulator.GetDefaultAsync();<

HCE 和 AID 型 UICC 路由的支援僅適用於最近啟動的裝置,例如 Lumia 730、830、640 和 640 XL。 任何執行 Windows 10 行動版及之後的新 NFC 功能裝置都應該支援 HCE。 您的應用程式可以檢查 HCE 支援,如下所示。

Smartcardemulator.IsHostCardEmulationSupported();

鎖定畫面和關閉行為

Windows 10 行動裝置版 具有裝置層級卡片模擬設定,可由電信業者或裝置製造商設定。 預設情況下,「觸碰付款」切換處於停用狀態,並且「裝置等級的啟用策略」設定為「始終」,除非 MO 或 OEM 覆蓋這些值。

您的應用程式可以在裝置層級查詢 EnablementPolicy 的值,並根據每個狀態中應用程式所需的行為,針對每個案例採取動作。

SmartCardEmulator emulator = await SmartCardEmulator.GetDefaultAsync();

switch (emulator.EnablementPolicy)
{
case Never:
// you can take the user to the NFC settings to turn "tap and pay" on
await Windows.System.Launcher.LaunchUriAsync(new Uri("ms-settings-nfctransactions:"));
break;

 case Always:
return "Card emulation always on";

 case ScreenOn:
 return "Card emulation on only when screen is on";

 case ScreenUnlocked:
 return "Card emulation on only when screen unlocked";
}

只有當外部閱讀器選擇解析為您的應用程式的 AID 時,即使手機被鎖定和/或螢幕關閉,您的應用程式的背景工作也會啟動。 您可以在背景工作中回應來自閱讀器的命令,但如果您需要使用者的任何輸入或想要向使用者顯示訊息,您可以使用一些參數啟動前景應用程式。 背景工作可以使用下列行為來啟動前景應用程式。

  • 在裝置鎖定畫面下 (使用者只有在解鎖裝置後才會看到您的前景應用程式)
  • 裝置鎖定畫面上方 (使用者關閉您的應用程式後,裝置仍處於鎖定狀態)
        if (Windows::Phone::System::SystemProtection::ScreenLocked)
        {
            // Launch above the lock with some arguments
            var result = await eventDetails.TryLaunchSelfAsync("app-specific arguments", SmartCardLaunchBehavior.AboveLock);
        }

SIM 卡型應用程式的 AID 註冊和其他更新

使用 SIM 卡模擬應用程式作為安全元素,可以向 Windows 服務註冊,以宣告 SIM 卡上支援的 AID。 此註冊與 HCE 型應用程式註冊非常類似。 唯一的差異是 SmartCardEmulationType,它應該設定為 SIM 型應用程式的 Uicc。 由於付款卡註冊的結果,卡片的顯示名稱也會填入NFC 設定功能表中。

var appletIdGroup = new SmartCardAppletIdGroup(
                        "Example DisplayName",
                                new List<IBuffer> {AID_PPSE.AsBuffer()},
                                SmartCardEmulationCategory.Payment,
                                SmartCardEmulationType.Uicc);