Get Started

This guide will take you through the process of setting up the Spend Manager SDK, incorporating:

  • Installation
  • Authentication
  • Start/Stop procedures

For a guide on setting up standalone capture to only capture images see Standalone Capture .

Before you begin

Before you can authenticate and run the SDK you will need to setup your authentication server.

To arrange access to our SDK repositories and authentication please contact your Account Manager.

Installation

Adding Sensibill SDK to a Project

The Sensibill SDK for iOS is distributed in two formats: a binary package format (XCFramework), and a universal archive (legacy format). Both formats can be installed using CocoaPods dependency manager (a preferred method), or by adding the dependency manually to your project.

This section explains how to install Sensibill SDK for iOS in XCFramework format using CocoaPods dependency manager. For other options, please see Installation Options page.

Steps

  1. If your project isn’t using CocoaPods already, follow the Installation and Getting Started guides on the official CocoaPods website . Once your project is integrated with the CocoaPods, you should have a Podfile in the root directory of your project.

  2. Add the Sensibill SDK for iOS as a dependency to your project’s Podfile in the following format:

    pod 'Sensibill', :git => 'git@github.com:getsensibill/sdk-ios.git', :tag => 'vX.Y.Z'

    where vX.Y.Z represents to version of the SDK you want to use.

    Note: once Sensibill SDK was added to Podfile, don’t forget to run pod install from the root directory of your application’s project in your terminal. This will add Sensibill SDK for iOS to your project’s Pods.

  3. Validate the integration by importing the Sensibill module into one of your Swift classes:

    import Sensibill

    and try to build your project. If the project builds successfully, the Sensibill SDK for iOS was integrated correctly. Otherwise follow the Troubleshooting steps on Installation Options page.

Adding the Required Entitlements

  • When a user navigates to the Capture Document screen of the Sensibill SDK, SDK will use device’s camera to capture documents. In order to allow camera usage, your application must prompt the user for consent to use their device’s camera, and thus requires a Privacy - Camera Usage Description entitlement.

  • Sensibill SDK also allows the users to select existing document images from the device’s photo gallery, which requires a Privacy - Photo Library Usage Description entitlement. Alternatively you can disable gallery access by setting the Capture.FeatureFlags.enableImageGallery to false.

  • If you would like to attach user’s device Location data to captured document, the Privacy - Location When In Use Usage Description entitlement is required. By default the location data is not collected, and you need to set Capture.FeatureFlags.attachLocationData to true to enable attaching the location data. Note that if user denies the access to the location, the location data will not be attached to a document, even if it was enabled for the app.

Steps

  1. Open Info.plist of your application.
  2. Locate or add each of the following entitlements, and provide a description of why the entitlement is required.

Required Entitlements

  • For the camera usage: NSCameraUsageDescription (displayed as Privacy - Camera Usage Description)
  • For the photo gallery access: NSPhotoLibraryUsageDescription (displayed as Privacy - Photo Library Usage Description).
  • For attaching user’s location data: NSLocationWhenInUseUsageDescription (displayed as Privacy - Location When In Use Usage Description).

The primary method of installing the SDK is via Sensibill’s private maven server. All stable SDK artifacts are stored on a private maven server than can only be accessed using an authorized username and password. Contact Sensibill’s Client Services to get a username and password. Once you have maven credentials, one of the two following changes to your gradle configuration will allow you to import Sensibill SDK dependencies.

// (old gradle style) IF Maven repositories are listed in the project level `build.gradle`
// file, Sensibill's Maven server should be added to the `allprojects.repositories` block
allprojects {
    repositories {
        // Other Maven Servers ...

        maven {
            // Sensibill SDK repository
            url 'https://maven.pkg.github.com/getsensibill/sdk-android'
            credentials {
                username 'to be provided by sensibill'
                password 'to be provided by sensibill'
            }
        }
    }
}

// (new gradle style) IF Maven repositories are listed in the `settings.gradle` file,
// Sensibill's Maven server should be added in the `dependencyResolutionManagement.repositories` block
dependencyResolutionManagement {
    repositories {
        // Other Maven Servers ...

        maven {
            // Sensibill SDK repository
            url 'https://maven.pkg.github.com/getsensibill/sdk-android'
            credentials {
                username 'to be provided by sensibill'
                password 'to be provided by sensibill'
            }
        }
    }
}

Sensibill Gradle Dependencies

Once you have access to Sensibill Artifacts, adding the SDK to your project is a matter of adding the correct dependencies to your app level build.gradle file.

For the full SDK add the below where x.x.x is the version.

implementation "com.getsensibill:sensibill-sdk-all:X.X.X"

See Installation options for other options.

Authentication

In order to perform user-specific tasks (such as retrieving or submitting documents for processing), the Sensibill SDK requires a valid User Access Token it can use for communication with the Sensibill API.

In an SDK integration, the host app will implement the logic of retrieving and refreshing the token from the (integrator created) “Integration Server”, and be responsible for providing this token to the Sensibill SDK on-demand via the TokenProvider interface. For more information about Integration Server, see the Sensibill API Authentication page.

The SDK will request a token from the TokenProvider when:

  • The SDK is starting
  • The previous token has expired
  • The previous token is invalid

The Android and iOS SDKs will request user access tokens using the TokenProvider that was passed to the SDK during initialization. The SDK will call an asynchronous function TokenProvider.provideTokenReplacement() when a new token is required. The TokenProvider should be designed to retrieve a new token on demand, when the provideTokenReplacement() function is called. It doesn’t need to cache the token, or ensure token validity at all times. See a language-specific section below for more information on how a TokenProvider should be implemented.

Important

  • The token provided is used for communication with the Sensibill API and will be used to access a user-specific data.
  • Each token is tied to a particular user, therefore it is important to ensure the token provided is always for the intended user.
  • Reusing tokens across users will result in information for the wrong user being displayed in your integration.
  • Avoid caching tokens inside your TokenProvider implementation. The SDK already caches provided tokens and will only request a new token if the cached token is no longer valid.

Integrator must implement a token provider by adhering to TokenProvider protocol. The protocol consists of a single function - provideTokenReplacement which will be called by Sensibill SDK any time it requires a new or updated token.

Parameters:

  • A userIdentifier - this is the user identifier for which SDK would like to get a token refresh. It’s recommended to ensure that the provided user identifier is for currently authenticated user.

  • A completion callback, which will either return a Credentials object with valid access token, or an error.

Please ensure that provideTokenReplacement calls completion in all cases, either with Credentials or Error.

Steps

  1. (Recommended) Ensure the requested token is for currently authenticated user. The “current user identifier” can be stored wherever it makes sense for your app (for example in the class responsible for app authentication).

  2. (Required) Obtain a new access token from your integration server.

  3. (Required) Process the response from your authentication server. Although the exact operations you need to perform depend on your integration server implementation, the typical set of actions includes:

    • Check for errors. If error is returned, pass this error in callback to SDK
    • Parse the response to ensure the access token was obtained
    • Construct a Credentials object and return it in a callback to SDK
import Sensibill

class CustomTokenProvider: TokenProvider {

    /// TokenProvider implementation
    func provideTokenReplacement(userIdentifier: String, completion: @escaping (Credentials?, Error?) -> Void) {
        
        // Step 1: (Recommended) Ensure the requested token is for currently authenticated user
        guard userIdentifier == <current user identifier> else {
            completion(nil, Errors.userIdentifierMismatch)
            return
        }

        // Step 2: Obtain a token from your integration server
        var request = URLRequest(...)
        let task = URLSession.shared.dataTask(with: request) { (data, response, error) in

            // Step 3: Process the response from your authentication server

            // 3.1 Check for errors. If error is returned, pass this error in callback to SDK:
            if error != nil {
                completion(nil, error)
                return
            }

            // 3.2 Parse the response to ensure the access token was obtained
            guard let accessToken = // ... else {
                completion(nil, Errors.accessTokenWasNotProvided)
                return
            }

            // 3.3 Construct a Credentials object and return it in a callback to SDK:
            let credentials = Credentials(accessToken: accessToken)
            completion(credentials, nil)
        }
        task.resume()
    }
}

extension CustomTokenProvider {
    
    enum Errors: Error {
        case userIdentifierMismatch
        case accessTokenWasNotProvided
    }
}

An instance of the token provider must be passed to the SDK on SensibillSDK.start. See Launching the SDK section below for further information.

Note:

  • The userIdentifier parameter should be used to identify users to the Integration server, which should return a different access token for each user

Kotlin

val tokenProvider = TokenProvider.fromLambda { oldAuthToken, userIdentifier, listener ->
    // The Sensibill SDK calls this method when a new token is required.
    // Provide new valid token for user matching userIdentifier
    MyIntegrationServer.fetchNewAccessToken(object : IntegrationServerListener {
        override fun onNewAccessTokenRetrieved(newAccessToken: String?) {
            if (!newAccessToken.isNullOrEmpty()) {
                // Pass new access token to SDK via the `onTokenProvided` method
                listener.onTokenProvided(newAccessToken)
            } else {
                // If unable to provide new access token, call the `onFailed` method
                listener.onFailed("Couldn't retrieve new token!")
            }
        }
    })
}

Java

final TokenProvider tokenProvider = new TokenProvider() {
    @Override
    public void provideTokenReplacement(@Nullable String oldAuthToken, @NotNull String userIdentifier, @NotNull TokenProvider.OnTokenProviderListener listener) {
        // The Sensibill SDK calls this method when a new token is required.
        // Provide new valid token for user matching userIdentifier.
        MyIntegrationServer.fetchNewAccessToken(new IntegrationServerListener() {
            @Override
            public void onNewAccessTokenRetrieved(String newAccessToken) {
                if (newAccessToken != null && !newAccessToken.isEmpty()) {
                    // Pass new access token to SDK via the `onTokenProvided` method
                    listener.onTokenProvided(newAccessToken);
                } else {
                    // If unable to provide new access token, call the `onFailed` method
                    listener.onFailed("Couldn't retrieve new token!");
                }
            }
        });
    }
};

Launching the SDK

The SDK is launched using the SensibillSDK.start function. On startup, SDK will ensure a valid authentication can be obtained. It may contact the TokenProvider to obtain a token if no valid token is available. Once authenticated, the start function will acquire the information required for interaction with SDK. This information is cached on device , but is verified and updated from Sensibill’s backend every time the SDK is started.

Notes:

  • The start function will not work in offline mode, it requires a network connection to succeed.
  • Caller must wait for the start callback to succeed before attempting any further interaction with the Sensibill SDK.

Parameters

User Identifier

The user identifier parameter of the SensibillSDK.start function allows the SDK to ensure that both, integrating app, and SDK are working in context of the same user, and support multiple user identities (for example personal and business accounts) used on the same device. A hosting application can provide user identifier in any convenient format, as long as it satisfies the following conditions:

  • The user identifier should be unique for each user identity.
  • A user identifier for the same user should not change from session to session.

For example a user identifier could be a user email (e.g. john.smith@example.com), user email with additional info, (e.g. type of account: john.smith@example.com-business), a hashed user email, or a UUID, linked to a particular user account number.

Once SDK has started, you can access current user identity, used by SDK via the SensibillSDK.shared.identityService.userIdentity property.

Token Provider

An instance of custom TokenProvider created in previous Authentication section. On successful start, the SDK will retain the instance of token provider, accessible via the SensibillSDK.shared.identityService.tokenProvider property. The property will be set to nil when SensibillSDK.stop is called.

Configuration

An instance of SensibillSDK.Configuration, which encompasses:

  • (Required) A definition of the environment to which Sensibill API Client in the SDK should connect. In simplest form the environment can be provided as a constant - .beta (beta.getsensibill.com), .sandbox (receipts-sandbox.sensibill.io), or .production (receipts.getsensibill.com). For more configuration options, see Accessing Sensibill API section.
  • (Optional) A custom branding configuration (Sensibill.Branding). If not provided, the default branding will be used. See Customize Branding page for more information on branding customization.
  • (Optional) A custom settings for Capture’s feature flag (Sensibill.Capture.FeatureFlags). If not provided, default seettings will be used. See Feature Switching for more details on Capture features configuration.

Methods

The SensibillSDK.start is provided in 2 forms:

  • As an async method, which can be called with await, and does not require a callback parameter
  • A method with the traditional completion callback.

Both methods follow the same logic, and are provided for convenience.

Result

Both method return the result in format Result<Void, Error>. That is: nothing is returned in case, of success, and Error will be provided in case of the failure.

Steps

  1. Follow the login process for your Integration Server to authenticate the user. This process should also provide a userIdentifier value for start

  2. Create an instance of your TokenProvider, and perform any steps neccessary to make sure the provideTokenReplacement function is ready to be called.

  3. (Optional) Create instances of custom API configuration, branding, and Capture feature flags, if desired. Those options are not covered in the below example, but are discussed in dedicated sections, mentioned in the Configuration parameter definition above.

  4. Create an instance of SensibillSDK.Configuration

    • with predefined or custom Sensibill.SensibillAPIClient.Environment, or a custom Sensibill.SensibillAPIClient.Configuration
    • and optional Sensibill.Branding
    • and optional Sensibill.Capture.FeatureFlags
  5. Call the SensibillSDK.start function in a form convenient for your app.

  6. Handle success and failure result of start execution.

import Sensibill

// 1. Follow the *login* process for your Integration Server to authenticate the user. 
// ...
let userIdentifier = // ... provided after successful login

// 2. Create an instance of your `TokenProvider`
let tokenProvider = CustomTokenProvider()

// 3. (Optional) Create instances of custom API configuration, branding, and Capture feature flags, if desired
// (Not covered in this eexample)

// 4. Create an instance of SensibillSDK.Configuration
let configuration = SensibillSDK.Configuration(environment: .beta)

// 5. Call the SensibillSDK.start
let result = await SensibillSDK.start(
    userIdentifier: userIdentifier,
    tokenProvider: tokenProvider,
    configuration: configuration
)

// 6. Handle success and failure result
switch result {
case .success:
    // Start interacting with SDK
case .failure(let error):
    // Handle error, for example:
    print(error.localizedDescription)
}

Initializing the SDK

Info

NOTE: For more detailed documentation about the Android Sensibill SDK Lifecycle, the process itself, as well as what customization options are available when starting the Sensibill SDK please see the Android Lifecycle page of the documentation.

InitializationBuilder

Initialization can begin by using the InitializationBuilder to create a builder object which will be used to build the Initializer required to initialize the SDK.

Start by creating the builder with the application context and a Sensibill DefaultEnvironment , and the TokenProvider created in the previous Authentication section.

Kotlin

val sensibillEnv = DefaultEnvironment.RECEIPTS_SANDBOX

val builder = InitializationBuilder(applicationContext, sensibillEnv, tokenProvider)

Java

final DefaultEnvironment sensibillEnv = DefaultEnvironment.RECEIPTS_SANDBOX;
final Context applicationContext = getApplicationContext();

final InitializationBuilder builder = new InitializationBuilder(applicationContext, sensibillEnv, tokenProvider);



If required, some additional customization such as the certificate pinning settings can also be provided here. See the InitializationBuilder Reference Documentation for a complete list of customizations that can be provided.

Build and Initialize

Once the InitializationBuilder object is created and optional extensions are added, an Initializer can be built by calling builder.build(). This can then be passed to the SensibillSDK.initialize() method along with an SDKInitializeListener.
See below for example:

NOTE: By the Sensibill SDK lifecycle rules, if SensibillSDK.initialize() and / or SensibillSDK.start() have already been called in this instance of the app, SensibillSDK.release() should be called before re-initializing.

Kotlin

SensibillSDK.initialize(builder.build(), object : SDKInitializeListener {
    override fun onInitialized() {
        // Initialization succeeded, you can now continue with starting the SDK
    }

    override fun onInitializationFailed() {
        // Initialization failed.  Try again.
    }
})

Java

SensibillSDK.getInstance().initialize(builder.build(), new SDKInitializeListener() {
    @Override
    public void onInitialized() {
        // Initialization succeeded, you can now continue with starting the SDK
    }

    @Override
    public void onInitializationFailed() {
        // Initialization failed.  Try again.
    }
});

Starting the SDK

Once the SDK has been initialized, it can then be started with SensibillSDK.start().

Note: the userIdentifier you provide to .start() will be the one passed to your TokenProvider when the SDK requests new tokens.

Kotlin

val userIdentifier = "currentUser"
SensibillSDK.start(userIdentifier, object : SDKStartup {
    override fun onSDKStarted() {
        // The SDK has successfully started.  All functionality can now be safely used.
    }

    override fun onSDKFailed(loginError: LoginError?, errorMessage: String?) {
        // The SDK failed to start.  This may be because it has not been initialized yet, or the `TokenProvider`
        // was not able to provide a token.  See `loginError` for details
    }
})

Java

final String userIdentifier = "currentUser";
SensibillSDK.getInstance().start(userIdentifier, new SDKStartup() {
    @Override
    public void onSDKStarted() {
        // The SDK has successfully started.  All functionality can now be safely used.
    }

    @Override
    public void onSDKFailed(LoginError loginError, String errorMessage) {
        // The SDK failed to start.  This may be because it has not been initialized yet, or the `TokenProvider`
        // was not able to provide a token.  See `loginError` for details
    }
});

Launching Spend Manager UI

Once the SensibillSDK is started, we can launch the Spend Manager UI.

The most basic way to launch the Spend Manager UI is to directly start WebUiActivity. This will use the default configurations, navigating directly to the dashboard.

Kotlin

startActivity(Intent(this, WebUiActivity::class.java))

Java

startActivity(new Intent(this, WebUiActivity.class));

Stopping the SDK

The stop function will always clean the local state of the SDK, so it’s always safe to run start after stop has completed. Additionally, there are a few customizations an integrator can make in order to better accommodate the stop function for their needs:

  • By default the stop function will first attempt to call Sensibill’s backend to invalidate the user token and clear the user session. The result of the remote call will be returned in Result<Void, Error> of the stop function.

    • This behavior can be disabled by using stop(invalidateToken: false).
    • Even in case the remote token invalidation fails, the local state will be cleared, so the returned Error should not be considered as failure to stop, rather a failure to invalidate the token.
  • The stop function is provided in 2 forms:

    • As an async method, which can be called with await, and does not require a callback parameter
    • A method with the traditional completion callback.

Notes:

  • The caller must wait for the stop callback to return before attempting to start the SDK again.
  • For the stop function having a network connection is optimal, but the SDK will stop and reset its state locally regardless.
  • Once the stop function is complete (and regardless of whether it retuns the Error), the SDK can be started again.
  • If stop was called while the application is starting, stop may need to wait to be able to interrupt the start.
  • The stop function cannot run if another stop function is already in progress.
  • Calling stop on already stopped SDK is supported (clears local state only). This can be done in cases when it’s desired to ensure that the SDK is in clear stoppped state.

Steps

  1. Call the stop function
  2. Optionally, handle success and failure result of stop execution.

Example

import Sensibill

let _ = await SensibillSDK.stop()

Once Sensibill SDK functionality is no longer being used, the SDK should be stopped in order to free up resources.

To stop the SDK call SensibillSDK.release().

Kotlin

SensibillSDK.release()

Java

SensibillSDK.getInstance().release();

Not applicable.

Next steps