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
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.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 runpod 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.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
tofalse
.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
totrue
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
- Open
Info.plist
of your application. - 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 aCredentials
object with valid access token, or an error.
Please ensure that provideTokenReplacement
calls completion
in all cases, either with Credentials
or Error
.
Steps
(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).
(Required) Obtain a new access token from your integration server.
(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 withawait
, 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
Follow the login process for your Integration Server to authenticate the user. This process should also provide a
userIdentifier
value forstart
Create an instance of your
TokenProvider
, and perform any steps neccessary to make sure theprovideTokenReplacement
function is ready to be called.(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.
Create an instance of
SensibillSDK.Configuration
- with predefined or custom
Sensibill.SensibillAPIClient.Environment
, or a customSensibill.SensibillAPIClient.Configuration
- and optional
Sensibill.Branding
- and optional
Sensibill.Capture.FeatureFlags
- with predefined or custom
Call the
SensibillSDK.start
function in a form convenient for your app.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, additional customization 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 inResult<Void, Error>
of thestop
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.
- This behavior can be disabled by using
The
stop
function is provided in 2 forms:- As an
async
method, which can be called withawait
, and does not require a callback parameter - A method with the traditional
completion
callback.
- As an
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 theError
), 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
- Call the
stop
function - 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.