Spend Manager SDK

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

  • Receipt capture
  • Receipt management UI

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 Installation Troubleshooting steps.

Configuring Sensibill Environment Properties

When started, Sensibill SDK will communicate with Sensibill environment. The definition of the environment the SDK will attempt to contact must be specified in Sensibill.plist configuration file, which can be provided in the main bundle of the host app, or in a separate bundle, provided via externalBundle parameter of SDKConfiguration (further referred as an “external bundle”).

If you wish to add Sensibill.plist, and other assets, like custom images and strings, in an external bundle, see iOS External Bundles page for more information.

Note: If a valid configuration file is provided in both the main and external bundle, the file from the external bundle will be used.

Steps

You can download an example of the properly structured properies file: Sensibill.plist

  1. Create Sensibill.plist in the main bundle of your target, or in the external bundle you created.

  2. Add the following required properties to the Sensibill.plist file:

    ENVIRONMENT: String
    CERTIFICATE_PINNING_FILES: Array of Strings

    Note: Please ensure the URL provided for the ENVIRONMENT key does not have a trailing slash.

    If you are using a your company’s Authentication Server, and a custom TokenProvider, no additional values are required. When using Sensibill’s Password Authentication, follow Password Authentication section to provide additional keys.

Configuring Certificate Pinning

By default certificate pinning is enabled on Sensibill SDK. For certificate pinning to function, your application’s Target must include the certificates provided by Sensibill (named COMODO.cer and GLOBOSIGN.cer), and must contain a list of valid certificate names inside the Sensibill.plist file.

Steps

  1. Import certificates provided by Sensibill into your project.

  2. Include them as a Bundle resource in every app’s target that uses the Sensibill SDK and make sure target membership for the certificates is checked for the application.

  3. Add the names of the certificate files under the CERTIFICATE_PINNING_FILES key of Sensibill.plist:

    CERTIFICATE_PINNING_FILES
        Item 0: COMODO.cer
        Item 1: GLOBALSIGN.cer

Adding the Required Entitlements

When a user navigates to the Capture Receipt screen of the Sensibill SDK, SDK will use device’s camera to capture receipts. In order to allow camera usage, your application must prompt the user for consent to use their device’s camera, and thus requires a camera usage entitlement. Additionally Sensibill SDK allows the users to select an existing receipt images from the device’s photo gallery, and to download previously uploaded receipt images. These operations require an access to photo library and an ability to save images in photo library entitlements correspondingly.

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 saving the receipt images to the photo gallery: NSPhotoLibraryAddUsageDescription (displayed as Privacy - Photo Library Additions Usage Description).

Note: If the entitlements were not provided, the application will crash with the following error:

This app has crashed because it attempted to access privacy-sensitive data without a usage description. The app’s Info.plist must contain an NSCameraUsageDescription (or NSPhotoLibraryUsageDescription / NSPhotoLibraryAddUsageDescription) key with a string value explaining to the user how the app uses this data.

To correct the crash, add the required entitlements, as described above.

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, adding the following to your project-level build.gradle file will allow access to the private maven server.

allprojects {
    repositories {
        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'
                }
        }
    }
}

Note: The relase 2022.0.11 of Sensibill SDK uses some of the Java 11 language APIs, and hence requires desugaring. See Java 8+ API desugaring support for more information.

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.

The Sensibill SDK is available through npm.

NPM
# Using npm
npm i @getsensibill/web-sdk

# Using yarn
yarn add @getsensibill/web-sdk

Import

Import the module where needed:

import { SensibillSDK } from '@getsensibill/web-sdk'

Authentication

Sensibill recommends the use of Custom Token Provider Authentication to allow the host app to control token management. The SDK supports OAuth2-compatible tokens, and JWT tokens.

For other authentication options see Authentication Options

Implement a custom token provider, by adhering to TokenProvider protocol, whose function provideTokenReplacement will provide the tokens to iOS SDK:

import Sensibill

class CustomTokenProvider: TokenProvider {

    func provideTokenReplacement(completion: @escaping (Credentials?, Error?) -> ()) {

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

            // On callback from your authentication server

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

            // 2. Parse response to obtain access token
            let accessToken = ...

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

Pass the custom token provider instance to the SDK on start. The SDK will retain an instance of the custom token provider until it is stopped. See Launching the SDK section below for further information.

The SDK will request user access tokens using a TokenProvider passed to the SDK during initialization. The SDK will call TokenProvider.provideTokenReplacement() when a new token is required.
The SDK may request a token from the token provider when:

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

Extra notes:

  • The TokenProvider must provide a unique token for each user
  • The userIdentifier parameter should be used to identify users to the Integration server, which should return a different access token for each user
  • The token provided is used for communication with the Sensibill API. Each token is tied to a particular user, therefore it is important to ensure the token provided is always for the intended user.
  • WARNING: Reusing tokens across users will result in information for the wrong user being displayed in your integration.

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!");
                }
            }
        });
    }
};

You must specify the environment you wish to connect to, failure to do so will result in an invalid token error.

Available environments

  • sandbox -client integration sandbox
  • production

To authenticate pass a valid access token to the SensibillSDK instance:

const instance = await SensibillSDK.create(

    // Provide a reference to the element you want to contain the iframe
    document.getElementById('your-element'),

    // Provide an initialization object which contains your client id along with the environment you wish to 	 // connect to
    { clientID: 'your-client-id', environment: 'sandbox' },

    // Provide a configuration object which contains your access token at a minimum
    { authentication: { accessToken: 'a-valid-access-token' } }
);

To support the automatic refresh of token register a listener as below:

function refreshAuthentication() {
    // Do what you need to get your new token.  This can be synchronous, or asynchronous.  The iframe will wait.
    const myNewToken = getNewToken();

    // Once you have it, set the new token on the instance
    instance.setToken(myNewToken);
}

// Run the refreshAuthentication function whenever the instance asks for a new token
instance.registerListener(refreshAuthentication, 'getNewToken')

Launching the SDK

The start function will first ensure the valid authentication exists for the user (it may call token provider to obtain a new token, or use an existing token if it’s valid). Once the user is 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 on every launch.

Info

The start function will not work in offline mode, it requires a network connection to succeed.

Steps

  1. Create an instance of your custom token provider, and perform all the steps required to make sure the provideTokenReplacement function is ready to be called. Note: token provider instance can always be accessed using SensibillSDK.shared.tokenProvider?
  2. Call the start function
  3. start is an asynchronous operation. Caller must wait for the start callback to return and succeed before attempting any further interaction with the SDK.

Cache Identifier

The cache identifier parameter of the start function allows the SDK to keep a unique cache for the user, and support multiple user identities (for example personal and business accounts) cached on the same device. Hence, the cache identifier should be unique for each user identity. A specific format for identifier depends on hosting application usage of SDK.

Examples of good cache identifiers:

  • User email, e.g. john.smith@example.com
  • User email, together with type of account: john.smith@example.com-business
import Sensibill

// Make sure externalBundle is initialized (if used) - see Configuring SDK section.

let tokenProvider = CustomTokenProvider()
// Additional operations required to prepare token provider

SensibillSDK.start(tokenProvider: tokenProvider, cacheIdentifier: "cacheIdentifier") { error in

    // Check for an error. If an error is returned, interaction with the SDK will not be possible
    guard case .none = error else {
        // Add custom error handling
        return
    }

    // Start interacting with the SDK
}

Error Codes

start may return the following error codes

  • sdkCannotStart will be returned by the start callback if the start function was called when the SDK was already starting, running, or stopping. If the SDK is starting or running, call stop first. If the SDK is stopping, wait for the stop operation to complete.
  • sdkStartWasInterrupted will be returned by the start callback, if the stop function was called while the SDK was starting. This result is provided for information only: since the app called stop while the SDK was starting, the result of start callback is presumably no longer important.

Start Spend Manager

At the minimum, a host UIViewController must be provided to start the Spend Manager. By default, Sensibill’s UI will be presented modally, with an animation, over the provided host. You can customize presentation and animation options. See Launch Spend Manager page for more customization options.

The integrating app must also implement a SensibillUICoordinatorDelegate protocol, and implement its required coordinatorWillTerminateWebUI function.

Example:

private var sensibillUICoordinator = SensibillUICoordinator(host: self)
sensibillUICoordinator.delegate = self
sensibillUICoordinator.start()

...

extension MyViewController: SensibillUICoordinatorDelegate {

    // Called when the SDK requests termination
    func coordinatorWillTerminateWebUI(_ coordinator: SensibillUICoordinator) {
        // Clean up the sensibillUICoordinator
        sensibillUICoordinator = nil
    }

}

Initializing the SDK

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 Kotlin

val sensibillEnv = DefaultEnvironment.RECEIPTS_SANDBOX

val builder = InitializationBuilder(applicationContext, sensibillEnv)

Java

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

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



The TokenProvider created in the previous Authentication must also be passed to this builder.

Kotlin

builder.authTokenProvider(tokenProvider)

Java

builder.authTokenProvider(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));

Opening a Sensibill view

Pass in the name of the element you would like to host the SDK in, this will create an iframe and load the SDK. SensibillSDK.create returns a promise. Use await to ensure instance promise is resolved before referencing instance later.

const instance = await SensibillSDK.create(

    // Provide a reference to the element you want to contain the iframe
    document.getElementById('your-element'),

    // Provide an initialization object which contains your client id
    { clientID: 'your-client-id', environment: 'sandbox' },

    // Provide a configuration object which contains your access token at a minimum
    { authentication: { accessToken: 'a-valid-access-token' } }
);

Stopping the SDK

By default the stop function will first attempt to call Sensibill’s backend to invalidate the user token and clear the user session. Regardless of the function’s success, the local state will be cleared and the result of the remote call will be returned in error of the stop callback.

To disable token invalidation, provide a flag to the stop function: stop(invalidateToken: false) { ... }

Once the stop function is complete, the SDK can be started again.

Note

  • For the stop function having a network connection is optimal, but the SDK will stop and reset its state locally regardless.
  • 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)

Steps

  1. Call the stop function
  2. stop is an asynchronous operation. The caller must wait for the stop callback to return before attempting to start the SDK again.

Note: although stop may return an error in the case of a failed remote call (e.g. when the app is offline), the local state will still be cleared. Hence handling an error returned in stop callback is optional.

Example

 import Sensibill

 SensibillSDK.stop { _ in

    // SDK is stopped
 }

Error Codes

stop may return the following error codes

  • sdkIsAlreadyStopping will be returned by the stop callback, if stop is called while another stop operation is already running.

Note:

  • If stop was called while the application is starting, stop may need to wait to be able to interrupt the start.
  • Two stop operations cannot run simultaneously, but running stop when the SDK is already stopped will return no error and is supported.

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

Additional reading