SiriKit and Payment Domain

Dmitrijs Beloborodovs
Citadele Bank Developers
5 min readNov 22, 2018
Photo by Andrew Neel on Unsplash

Intro

Since it’s first release of SiriKit, Apple extended support for many domains. Let’s review payment domain. Specifically, accounts aka balance aka quick balance check. After reading this tutorial following will look familiar:

  • INSearchForAccountsIntent
  • INSearchForAccountsIntentResponse
  • INPaymentAccount

Our goal is to ask Siri something like “How much money I have?” and get list of accounts in reply.

Preparation

Application will have 3 main parts:

  • main application (main target; simple single screen app)
  • SiriKit extension (target to interact with Siri)
  • shared library (where accounts fetching logic is); code to share between previous targets.

First, create empty project (File > New > Project… >Single View App) named MyBank. Second, add shared library (File > New > Target… > Cocoa Touch Framework) and use QuickBalance for name. Our main target should depend on shared library.

SiriKit

To get Siri know about our app, let’s enable Siri (in “Project navigator” tab select project > Capabilities > Siri). Next, select the “Info.plist” add new row “Privacy — Siri Usage Description” with custom text.

At some point app must ask permission for Siri. In our app we put it in app delegate. Open AppDelegate.swift, import Intents framework and add in application(_:didFinishLaunchingWithOptions:) permission request:

import Intents...func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {  INPreferences.requestSiriAuthorization { status in    if status == .authorized {      print("Hello, Siri!")    } else {     print("No Siri :(")    }  }  return true}

Run app. You should see blank screen and dialog

Accounts

Every bank application should have accounts. We also create some. To share code between main app and Siri extensions, we put domain object Account and logic to fetch accounts from server inside QuickBalance framework. Let’s create new class (select QuickBalance folder in project structure tree, File > New > File… > Swift File) named Account. We keep Account pretty simple:

public struct Account {  public let name: String  public let balance: Double}

And we also need some service to call (select QuickBalance folder in project structure tree, File > New > File… > Swift File), let’s name in QuickBalanceService. Our service returns three dummy accounts with delay (pretending it is doing real network call):

open class QuickBalanceService {  public init() {}  public func quickBalanceAccounts(_ completion: @escaping ((_ accounts: [Account]) -> Void)) {    var accounts = [Account]()    let account1 = Account(name: "Main Account", balance: 150.0)    let account2 = Account(name: "Saving Account", balance: 300.0)    let account3 = Account(name: "Credit Card Account", balance: -100)    accounts.append(account1)    accounts.append(account2)    accounts.append(account3)    sleep(3)    completion(accounts)  }}

Intents Extension

We are ready to add some Siri to our bank app. Let’s add another target (File > New > Target… > Intent Extension) named QuickBalanceIntentsExtension. Uncheck Include UI Extension checkbox for now and select None for Starting Point. Active scheme if it ask you.

There is not much here yet. Just single file IntentHandler with single function

override func handler(for intent: INIntent) -> Any

We’ll get back here soon.

As I mention before, Siri operates in many domains (= you may ask any questions), but we only interested in accounts data request. Let’s add INSearchForAccountsIntent for supported intents (extension’s Info.plist > NSExtension > NSExtensionAttributes > IntentsSupported > add new item INSearchForAccountsIntent)

When Siri recognise your speech as intent to do something, here is where it goes first. So, whenever we receive request, we should handle it and provide response. Let’s create class for this in extension (select extension folder File > New > File… > Swift File) named QuickBalanceRequestHandler. This class just will call our shared framework and wrap our Account in something Siri understand as INPaymentAccount. First add Account extension to QuickBalanceRequestHandler file and don’t forget to import Intents and QuickBalance framework

import Intentsimport QuickBalanceextension Account {  public var intentAccount: INPaymentAccount {   return INPaymentAccount(nickname: INSpeakableString(spokenPhrase: self.name), number: nil, accountType: .unknown, organizationName: nil, balance: INBalanceAmount(amount: NSDecimalNumber(floatLiteral: self.balance), currencyCode: "EUR"), secondaryBalance: nil) }}

As you can see INPaymentAccount has many fields. But almost all are optional and we won’t touch them for now. Now add QuickBalanceRequestHandler to the end of file:

class QuickBalanceRequestHandler: NSObject, INSearchForAccountsIntentHandling {  func handle(intent: INSearchForAccountsIntent, completion: @escaping (INSearchForAccountsIntentResponse) -> Void) {    QuickBalanceService().quickBalanceAccounts { (accounts) in      let response: INSearchForAccountsIntentResponse      guard accounts.count > 0 else {        response = INSearchForAccountsIntentResponse(code: .failureAccountNotFound, userActivity: .none)        completion(response)        return      }      response = INSearchForAccountsIntentResponse(code: .success, userActivity: .none)      response.accounts = accounts.compactMap({ return $0.intentAccount })      completion(response)    }  }}

Again, not much here. Just trying to fetch account. If no luck, return .failureAccountNotFound code so Siri may reply properly (check INSearchForAccountsIntentResponseCode enum for more options). If accounts found, wrap them in INPaymentAccount and return with .success code.

Now we are ready to handle Siri’s request with proper response. Navigate to IntentHandler file and replace function with:

override func handler(for intent: INIntent) -> Any? {  if intent is INSearchForAccountsIntent {    return QuickBalanceRequestHandler()  }  return .none}

We are ready to talk to Siri. But we won’t. As there might be people around you now. So let’s configure Xcode to do it quietly and launch QuickBalanceIntentsExtension scheme (Xcode > Product > Scheme > Edit scheme… > Run > Info > Siri Intent Query) with question “How much money I have in MyBank app?

And launch QuickBalanceIntentsExtension scheme. You should see something like this

As you might notice, we specified which app use in question. Try to not specify and initially reply might be like this

Eventually, Siri will use proper app even without specifying it.

That’s all for now. Sources are here. Next, we create custom UI for our accounts list and try to teach Siri to reply to more specific questions about accounts.

--

--