# iOS Integration
# General Information
# Base Modules
AnybillBase
-
Authentication
- Login
- Register (Deprecated)
- Logout
- Delete Account
- Get Qr Code Data
- Token Provider
- Profile Picture Receipt
- Get Receipt List
- Add receipt with qr code
- Export Receipts Category (Deprecated)
- Get category list
- Get specific category
# Additional Modules
# Getting Started
# Cocoapods Plugin
In order to resolve the anybill SDK using Cocoapods, you need the cocoapods-art plugin which presents Artifactory repositories as Specs repos and pod sources.
You can download the cocoapods-art (opens new window) plugin as a Gem, and its sources can be found on Github (opens new window)
To resolve the artifactory repository, execute the following steps:
- Install cocoapods-art plugin
gem install cocoapods-art
- The next step is to add the repository by using the pod 'repo-art add' command:
pod repo-art add anybill_ios_sdk "https://anybill.jfrog.io/artifactory/api/pods/anybill_ios_sdk"
- Once the repository is added, add the following in your Podfile:
plugin 'cocoapods-art', :sources => [
'anybill_ios_sdk'
]
repo-art uses authentication as specified in your standard netrc file.
machine anybill.jfrog.io
login <USERNAME>
password <PASSWORD>
Further information about the cocoapods plugin can be found on the artifactory documentation (opens new window).
# Integration with Cocoapods
First of all you need to specify the usage of dynamic frameworks in your target/project with
use_frameworks!
, if you did not already.Then you can add the desired artifacts to your podfile (The AnybillBase module is required for the basic usage of the SDK)
Below is the podfile of an example project, which implements the anybill SDK:
platform :ios, '14.0'
source 'https://github.com/CocoaPods/Specs.git'
plugin 'cocoapods-art', :sources => [
'anybill_ios_sdk'
]
target 'your target name' do
use_frameworks!
pod 'AnybillBase'
end
# Add this script for Cocoapods <1.11.0 to fix a Cocoapods problem with XCFrameworks
post_install do |installer|
installer.pods_project.targets.each do |target|
target.build_configurations.each do |config|
config.build_settings['BUILD_LIBRARY_FOR_DISTRIBUTION'] = 'YES'
end
end
end
# Integration with Swift Package Manager
- First of all you need to set the package-registry in the root folder of your application using the following command:
swift package-registry set --global "https://anybill.jfrog.io/artifactory/api/swift/anybill_ios_sdk-spm"
- Then you need to make sure you are logged in to jfrog in order to resolve the package
swift package-registry login https://anybill.jfrog.io/artifactory/api/swift/anybill_ios_sdk-spm --username yourUsername --password yourPassword
- Add the desired anybill modules in Xcode: File > Add package dependencies. Search for e.g. 'anybill.AnybillBase' and add the dependency.
# Instantiate the log module in your AppDelegate file
In your AppDelegate file import AnybillBase
and add AnybillLogger.initLogger()
to the body of the application(_:didFinishLaunchingWithOptions:) method
# Setting the client Id
To set the Client Id in your app, add the Id as value for the key anybill_client_id
in your info.plist file. This is going to allow us to hook all of your API activity to your Client Id which can be used for analytics or support purposes later on.
# Change the api mode to staging
For developing purposes you can change the api environment to staging by adding the staging url (https://app.stg.anybill.de/) as value for the key anybill_base_url
in your info.plist file
# Usage of the SDK
The usage of the individual methods in the SDK are mostly consistent. Below is an example of the usage of the login method in a view controller. A detailed documentation of the individual methods including return types and error codes can be found in the technical documentation of the corresponding module.
import AnybillBase
class ViewController: UIViewController {
let authService = AuthProvider.shared
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
loginUser()
}
func loginUser() {
authService.loginUser(username: "username", password: "password") { result in
switch result {
case .success:
// Continue
case .failure(let error):
// Handle error
// For example:
switch error.type {
case .genericError:
// handle errors 400..499
case .networkError:
// handle network error
case .noUserError:
// handle no user error
case .invalidRefreshTokenError:
// handle error when access token could not be refreshed
default:
// handle unknown error
}
}
}
}
}
# Error Handling
The anybill sdk uses a custom error handling model. All methods return either the result object or an AnybillError
object containting the error type, description and code.
/**
Error model used to describe api call failures
- code: Int with the response code of the api call
- title: String with the title of the error
- message: String with a description of the error
- errors: Dictionary with error descriptions returned from our backend
- traceId: String used for error logging
- type: AnybillErrorType used to distinguish between error types
*/
public struct AnybillError: Error {
public let code: Int
public let title: String?
public let message: String?
public let errors: [String: [String]]?
public let traceId: String?
public let type: AnybillErrorType
}
/**
Enum used to distinguish between generic and network errors
*/
public enum AnybillErrorType: Codable {
/// This error type is used for generic errors that do not fit into any other category.
case genericError,
/// This error type is used when there is a network error while making a request.
networkError,
/// This error type is used when there is an issue with refreshing the user's access token.
invalidRefreshTokenError,
/// This error type is used when there is no user logged in.
noUserError,
/// This error type is used when the issue can't be defined. Typically a critcal error.
unknown
}
Example usage of the error model based on the login method of the AuthProvider.
authService.loginUser(username: username, password: password) { result in
switch result {
case .success(()):
//Continue
case .failure(let error):
switch error.type {
case .genericError:
// handle errors 400..499
case .networkError:
// handle network error
case .noUserError:
// handle no user error
case .invalidRefreshTokenError:
// handle error when access token could not be refreshed
default:
// handle unknown error
}
}
}
# App Link
Anybill Applink is our way to enable linking into mobile apps from our receipt website by directly opening the app or redirecting to the app stores while persisting data. If you are integrating our sdk and want to make use of this feature, please contact us beforehand.
# Integrate app link and automatically open the new receipt
To automatically add a receipt and open the app, that integrates the anybill sdk, you have to set up universal links. Therefore you have to enable the capability 'Associated Domains' and register the domain "applinks:applink.anybill.de". Additionally the bundle identifier of your application has to be in the apple-app-site-association file of our server hosting the app link web page (If that is not the case please contact us).
Next you have to get the link that opened your application. In swiftUI you can use the '.onOpenURL' handler for that task:
struct ContentView: View {
@State private var tab = 1
var body: some View {
TabView(selection: $tab) {
Home()
.tabItem {
Label("Home", image: "home")
.foregroundColor(.black)
}
.tag(1)
AnybillUIModule()
.tabItem {
Label("Receipts", image: "anybillLogo")
.foregroundColor(.black)
}
.tag(2)
Settings()
.tabItem {
Label("Settings", systemImage: "gearshape")
.foregroundColor(.black)
}
.tag(3)
}
.onOpenURL { url in
if AnybillAppLink.checkForAnybillAppLink(url: url) != nil {
self.tab = 2
}
}
}
}
In UIKit you can get the url in the SceneDelegate:
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
if let url = connectionOptions.userActivities.first?.webpageURL {
if AnybillAppLink.checkForAnybillAppLink(url: url) != nil, let tabBarController = window?.rootViewController as? UITabBarController {
tabBarController.selectedIndex = 1
}
}
}
And if the app is already running:
func scene(_ scene: UIScene, continue userActivity: NSUserActivity) {
if let url = userActivity.webpageURL {
if AnybillAppLink.checkForAnybillAppLink(url: url) != nil, let tabBarController = window?.rootViewController as? UITabBarController {
tabBarController.selectedIndex = 1
}
}
}
Next use 'AnybillAppLink.checkForAnybillAppLink(url: URL)' to check if the recieved url was a valid anybill url and if it contains information to add a new receipt. If that is the case simply navigate to the anybill ui module or use the returning 'AnybillURLData' object to manually add the receipt to the users account.
# Get the receipt if the app is not installed yet
If the user tries to add the receipt via your application and it is not installed on the users device, the page will redirect to your application in the app store and the url is saved to the users clipboard. To ensure that the user still gets the receipt, you need to call 'AnybillAppLink.checkForAnybillAppLinkData()' on the app start. This method checks if it is the first app start and if that is the case it checks the users clipboard for any saved url data. If the returing data is not equal to nil simply navigate to the anbill ui module or handle the data manually.
SwiftUI example:
struct ContentView: View {
@State private var tab = 1
var body: some View {
TabView(selection: $tab) {
Home()
.tabItem {
Label("Home", image: "home")
.foregroundColor(.black)
}
.tag(1)
AnybillUIModule()
.tabItem {
Label("Receipts", image: "anybillLogo")
.foregroundColor(.black)
}
.tag(2)
Settings()
.tabItem {
Label("Settings", systemImage: "gearshape")
.foregroundColor(.black)
}
.tag(3)
}
.onAppear {
Task {
if await AnybillAppLink.checkForAnybillAppLinkData() != nil {
self.tab = 2
}
}
}
.onOpenURL { url in
if AnybillAppLink.checkForAnybillAppLink(url: url) != nil {
self.tab = 2
}
}
}
}
UIKit example:
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
if let tabBarController = window?.rootViewController as? UITabBarController {
Task {
if await AnybillAppLink.checkForAnybillAppLinkData() != nil {
tabBarController.selectedIndex = 1
}
}
guard let _ = (scene as? UIWindowScene) else { return }
}
}
# Push Notifications
The anybill SDK uses push notifications to inform the user about certain events. A detailed list of these events can be found in the push notification tab of the App API documentation.
To enable Push Notifications you'll have to provide a APN Authentication Key of your application. After creating a Firebase application in the anybill Firebase project, you are going to receive a Google App Id and Firebase Client Id, which you will have to use in your application code. Please contact us before implementing anybill Push Notifications (dev@anybill.de).
# Integration without firebase already being integrated in your project
First of all you need to import Firebase and AnybillBase in your AppDelegate.
Then add the following code to the body of your application(_:didFinishLaunchingWithOptions:)
function:
AnybillFirebaseInitializer().initFirebaseProject(googleAppId: "yourGoogleAppId", firebaseClientId: "yourFirebaseClientId")
Messaging.messaging().delegate = self
if #available(iOS 10.0, *) {
// For iOS 10 display notification (sent via APNS)
UNUserNotificationCenter.current().delegate = self
let authOptions: UNAuthorizationOptions = [.alert, .badge, .sound]
UNUserNotificationCenter.current().requestAuthorization( options: authOptions, completionHandler: {_, _ in })
} else {
let settings: UIUserNotificationSettings = UIUserNotificationSettings(types: [.alert, .badge, .sound], categories: nil)
UIApplication.shared.registerUserNotificationSettings(settings)
}
application.registerForRemoteNotifications()
Subsequently add these extensions to the end of your AppDelegate file:
@available(iOS 10, *)
extension AppDelegate : UNUserNotificationCenterDelegate {
// Receive displayed notifications for iOS 10 devices.
func userNotificationCenter(_ center: UNUserNotificationCenter,
willPresent notification: UNNotification,
withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
//Called when the app is about to show a notification
// Change this to your preferred presentation option
completionHandler([[.banner, .sound]])
}
func userNotificationCenter(_ center: UNUserNotificationCenter,
didReceive response: UNNotificationResponse,
withCompletionHandler completionHandler: @escaping () -> Void) {
//Called when the users clicks the notification
completionHandler()
}
}
extension AppDelegate : MessagingDelegate {
func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String?) {
let dataDict:[String: String] = ["token": fcmToken ?? ""]
NotificationCenter.default.post(name: Notification.Name("FCMToken"), object: nil, userInfo: dataDict)
//Notify the anybill sdk to update its FCM token
AnybillFirebaseInitializer().didReceiveRegistrationToken()
}
# Integration with firebase already being integrated in your project
If you already have firebase integrated in your app, simply follow the steps mentioned above and initialize the anybill firebase project after you initialized your firebase project
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Initialize your Firebase project:
FirebaseApp.configure()
// Initialize the anybill Firebase project:
AnybillFirebaseInitializer().initFirebaseProject(googleAppId: "yourGoogleAppId", firebaseClientId: "yourFirebaseClientId")
Messaging.messaging().delegate = self
...
}
# Default handling of push notifications
As just demonstrated, notifications are handled inside the userNotificationCenter()
method. The simplest way of handling the notifications is to just dislpay them
as local iOS notifications. However the SDK also offers the possibility to display them on UI components of the SDK as a snackbar. Additionally some special notifications trigger
specific actions when handled by the SDK such as automatically updating the list of bills when a notifications arrives, stating that a new receipt has been added to the logged in account. To enable the SDK internal handling of notifications, the method handleAnybillNotification
of the class AnybillNotificationCenter
can be called like so:
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
AnybillNotificationCenter.shared.handleAnybillNotification(notification: notification)
completionHandler(.sound)
}
To avoid displaying of multiple snackbars/notifications it is advised to first check if the SDK UI is currently being used and based on the result either let the SDK handle the notification or let the implementing app display the notification.
# Open added receipt after push notification
To open a receipt in the anybill ui module, that was added though a push notification you need to implement the observable object 'NotificationManager.shared' and pass it to the AnybillUIModule view as an environmentObject.
Additionally you have to listen for push notifications and pass the recieving 'UNNotification' object to the 'dumbData' parameter of the 'NotificationManager.shared'.
If the notification is a valid add receipt notification from anybill the notification data will be parsed and the relevant information will be available through the @Published variable 'anybillNotificationResult'.
You should listen for changes of the 'anybillNotificationResult' parameter and naviagte to the anybill ui module if it is not equal to nil.
Passing the notification info to the NotificationManager:
func userNotificationCenter(_ center: UNUserNotificationCenter,
willPresent notification: UNNotification,
withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions)
-> Void) {
self.notificationManager.dumbData = notification
}
func userNotificationCenter(_ center: UNUserNotificationCenter,
didReceive response: UNNotificationResponse,
withCompletionHandler completionHandler: @escaping () -> Void) {
self.notificationManager.dumbData = response.notification
completionHandler()
}
Example of naviagting to the anybill ui module after an add receipt push notification in swiftUI:
(Note that the NotificationManager instance is passed to the ContentView as an environmentObject)
struct ContentView: View {
@State private var tab = 1
@EnvironmentObject var notificationCenter: NotificationManager
var body: some View {
TabView(selection: $tab) {
Home()
.tabItem {
Label("Home", image: "home")
.foregroundColor(.black)
}
.tag(1)
AnybillUIModule()
.tabItem {
Label("Receipts", image: "anybillLogo")
.foregroundColor(.black)
}
.tag(2)
Settings()
.tabItem {
Label("Settings", systemImage: "gearshape")
.foregroundColor(.black)
}
.tag(3)
}
.onAppear {
if notificationCenter.anybillNotificationResult != nil {
self.tab = 2
}
}
.onChange(of: notificationCenter.anybillNotificationResult) { _ in
self.tab = 2
}
}
}
Example of naviagting to the anybill ui module in UIKit:
class CustomTabBarController: UITabBarController {
let notificationManager = NotificationManager.shared
var cancellableBag = Set<AnyCancellable>()
override func viewDidLoad() {
super.viewDidLoad()
notificationManager.$anybillNotificationResult.sink { value in
self.selectedIndex = 1
}.store(in: &cancellableBag)
}
}
# AnybillBase
# Authentication
The AnybillBase module handles among other things the authentication in the SDK.
The authentication methods are accessable through the AuthProvider singleton.
let authService = AuthProvider.shared
# Login
Login Anybill User
Anybill users can be logged in using the loginUser()
method of the AuthProvider. It requires valid login information of a registered anybill user (email and password).
authService.loginUser(username: username, password: password) { result in
switch result {
case .success(()):
//Continue
case .failure(let error):
switch error.type {
case .genericError:
// handle errors 400..499
case .networkError:
// handle network error
case .noUserError:
// handle no user error
case .invalidRefreshTokenError:
// handle error when access token could not be refreshed
default:
// handle unknown error
}
}
}
[DEPRECATED]
Login Anonymous User
With the createAnonymousUser()
method, you can create an anonymous user account without an email or password. The account can use the anybill services with certain restrictions. Later on the account can be converted to a normal anybill account with email, password and further user information.
authService.createAnonymousUser { result in
switch result {
case .success(let anonymousUser):
//Continue
case .failure(let error):
switch error.type {
case .genericError:
// handle errors 400..499
case .networkError:
// handle network error
case .noUserError:
// handle no user error
case .invalidRefreshTokenError:
// handle error when access token could not be refreshed
default:
// handle unknown error
}
}
}
Note: After an App or its data gets deleted, the anonymous account cannot be restored because there is no reference to the old account left.
Linked token user
If your App has an own user account system you can link an anybill user to your user account by using the linked token user:
- Get a token from the anybill Partner API by linking your account system to an anybill id.
- Create an instance of
TokenUser
with the received token-information - Login the TokenUser with the anybill sdk
let tokenUser = TokenUser.init(accessToken: accessToken, expiresIn: expiresIn, refreshToken: refreshToken)
AuthProvider.loginTokenUser(tokenUser){ result in
switch result {
case .success(()):
//Continue
case .failure(let error):
switch error.type {
case .genericError:
// handle errors 400..499
case .networkError:
// handle network error
case .noUserError:
// handle no user error
case .invalidRefreshTokenError:
// handle error when access token could not be refreshed
default:
// handle unknown error
}
}
}
Further information about the login possibilities can be found on the anybill sdk authentication page.
# Listen for refresh token failure with Token User
If you are using the anybill token user, you have to register a notification observer in your app to listen if the refresh token call failes and get a new Token for the user via our partnerplatform API.
- Register notification observer:
NotificationCenter.default.addObserver(self, selector: #selector(getNewToken(_:)), name: Notification.Name(rawValue: ApiConfig.REFRESH_TOKEN_FAILED_OBSERVER), object: nil)
- Write function that gets a new token from the anybill api and log in the user with the new token
[DEPRECATED]
# Register
Register Anybill User
To register a new anybill user use the preregister()
method, which sends a validation code to the given user email. You can validate a register code with the validateRegistrationCode()
method. Use the finalizeRegistration()
method to complete the registration process.
By setting autoLogin
to true
the user is going to be automatically logged in after a successful registration.
authService.preregisterUser(email: email, firstname: firstname) { result in
switch result {
case .success:
//Continue
case .failure(let error):
switch error.type {
case .genericError:
// handle errors 400..499
case .networkError:
// handle network error
case .noUserError:
// handle no user error
case .invalidRefreshTokenError:
// handle error when access token could not be refreshed
default:
// handle unknown error
}
}
}
authService.finalizeRegistration(email: email, code: validationCode, password: password, autoLogin: autoLogin) { result in
switch result {
case .success(let registerUserResponse):
//Continue
case .failure(let error):
switch error.type {
case .genericError:
// handle errors 400..499
case .networkError:
// handle network error
case .noUserError:
// handle no user error
case .invalidRefreshTokenError:
// handle error when access token could not be refreshed
default:
// handle unknown error
}
}
}
Convert Anonymous User
Converting an anonymous user to an anybill User. Anonymous users have restricted access to the anybill functions (e.g. restricted amount of receipts). If the app user wants to take use of all the anybill functions he has to create an anybill account. To retain the data of an anonymous user the anybill sdk provides a function to convert an anonymous user to an anybill user.
authService.convertAnonymousUser(email: email, password: password, firstname: firstname, gender: gender) { result in
switch result {
case .success:
//Continue
case .failure(let error):
switch error.type {
case .genericError:
// handle errors 400..499
case .networkError:
// handle network error
case .noUserError:
// handle no user error
case .invalidRefreshTokenError:
// handle error when access token could not be refreshed
default:
// handle unknown error
}
}
}
Afterwards the user can login into his anybill account using his login credentials.
# Logout
Logging out an user deletes all of user's app data including cached receipts, authentication information and app settings (of the anybill sdk).
authService.logoutUser { result in
switch result {
case .success:
//Continue
case .failure(let error):
switch error.type {
case .genericError:
// handle errors 400..499
case .networkError:
// handle network error
case .noUserError:
// handle no user error
case .invalidRefreshTokenError:
// handle error when access token could not be refreshed
default:
// handle unknown error
}
}
}
# Delete Account
Deleting an anybill account is irreversibly. The account can not be retrieved afterwards.
authService.deleteCurrentUser { result in
switch result {
case .success:
//Continue
case .failure(let error):
switch error.type {
case .genericError:
// handle errors 400..499
case .networkError:
// handle network error
case .noUserError:
// handle no user error
case .invalidRefreshTokenError:
// handle error when access token could not be refreshed
default:
// handle unknown error
}
}
}
# Get QR Code Data
Certain points of sale allow anybill users to receive receipts by scanning a QR code on the user's phone. To get the jsonObject which should be included in the QR Code use the getQRCodeData
method.
Following code shows an example how to generate a QR Code and display it in your view:
authService.getQRCodeData{ result in
switch result {
case .success(let qrCodeData):
let data = try? JSONEncoder().encode(qrCodeData)
if let filter = CIFilter(name: "CIQRCodeGenerator") {
filter.setValue(data, forKey: "inputMessage")
let transform = CGAffineTransform(scaleX: 3, y: 3)
if let output = filter.outputImage?.transformed(by: transform) {
self.yourImageView.image = UIImage(ciImage: output)
}
}
case .failure(let error):
switch error.type {
case .genericError:
// handle errors 400..499
case .networkError:
// handle network error
case .noUserError:
// handle no user error
case .invalidRefreshTokenError:
// handle error when access token could not be refreshed
default:
// handle unknown error
}
}
}
More information about QR Code data can be found in the anybill app documentation.
# TokenProvider
The TokenProvider is used for internal authentication operations. The public methods should not be accessed from the app itself. It might cause problem during the anybill authentication process.
# Receipt
The AnybillBase module enables the user to manage his receipts.
The methods are accessable through the ReceiptProvider singleton.
let receiptService = ReceiptProvider.shared
[DEPRECATED]
let billService = BillProvider.shared
# Get receipt list
Get receipts
The getReceiptsFromApi(take: Int?, continuationToken: String?)
method to used retrieve user receipts. The result contains the receipts and the continuation token, which can be used to retrieve the next batch of receipts.
billService.getReceiptsFromApi(take: 100, continuationToken: token) { result in
switch result {
case .success:
//Access receipts through the observableReceipts observable in the ReceiptProvider
case .failure(let error):
switch error.type {
case .genericError:
// handle errors 400..499
case .networkError:
// handle network error
case .noUserError:
// handle no user error
case .invalidRefreshTokenError:
// handle error when access token could not be refreshed
default:
// handle unknown error
}
}
}
Get initial receipts (recommended)
Use the getInitialReceipts(take: Int?)
method to initially retrieve the first user receipts. With the getNextReceipts(take: Int?)
method the next batch of receipts can be retrieved. When called the receipts are cached in a local database.
billService.getInitialReceipts(take: 100) { result in
switch result {
case .success:
//Access receipts through the observableReceipts observable in the ReceiptProvider
case .failure(let error):
switch error.type {
case .genericError:
// handle errors 400..499
case .networkError:
// handle network error
case .noUserError:
// handle no user error
case .invalidRefreshTokenError:
// handle error when access token could not be refreshed
default:
// handle unknown error
}
}
}
Get next receipts (recommended)
After calling the getInitialReceipts(take: Int?)
method to retrieve the first user receipts, the getNextReceipts(take: Int?)
method can be used to retrieve the next batch of receipts. When called the receipts are cached in a local database.
billService.getNextReceipts(take: 100) { result in
switch result {
case .success:
//Access receipts through the observableReceipts observable in the ReceiptProvider
case .failure(let error):
switch error.type {
case .genericError:
// handle errors 400..499
case .networkError:
// handle network error
case .noUserError:
// handle no user error
case .invalidRefreshTokenError:
// handle error when access token could not be refreshed
default:
// handle unknown error
}
}
}
[DEPRECATED]
Get all user bills
Use the getAllBillsFromApi()
method to retrieve the complete list of user bills. When called the bills are cached in a local database.
billService.getAllBillsFromApi { result in
switch result {
case .success(let bills):
//Continue
case .failure(let error):
switch error.type {
case .genericError:
// handle errors 400..499
case .networkError:
// handle network error
case .noUserError:
// handle no user error
case .invalidRefreshTokenError:
// handle error when access token could not be refreshed
default:
// handle unknown error
}
}
}
Get changed user bills (recommended)
Use the updateBills()
method to fill an observable object, containing the list of user bills. The method first fills the observable object with the cached bills and then updates the list with the newly modified/added bills as soon as the API operation completed.
//Observe changes on the observable object
private let billService = BillProvider.shared
private var cancellables = Set<AnyCancellable>()
billService.$observableBills
.sink { [weak self] in
self?.setBills(bills: $0)
}
.store(in: &cancellables)
//Call this to update the categories using the caching process
billService.updateBills { result in
switch result {
case .success(let _):
// Continue
case .failure(let error):
switch error.type {
case .genericError:
// handle errors 400..499
case .networkError:
// handle network error
case .noUserError:
// handle no user error
case .invalidRefreshTokenError:
// handle error when access token could not be refreshed
default:
// handle unknown error
}
}
}
# Add receipt with qr code
Anybill allows adding receipts to the user's receipt list by scanning a QR Code on the screen of a point of sale. Using an in app QR Code Scanner (recommended), you can retrieve the new receipt ID and add it to the user's receipt list using the anybill sdk. The user can also scan the QR Code using the QR Code Scanner of his device. This will redirect him to a web page with deep links into your app, for which you will have to to use the AnybillAppLink feature.
//Depending on the QR Code Scanner you are going to receive a String or URL object.
//
//Format:
// https://getmy.anybill.de/#/bill/b45f4b77-9c3c-454a-8b87-08d8bbd02f7e
//
//You can choose to parse and validate the URL before using the sdk or call one of the following methods:
receiptService.addReceiptByUrl(url: "https://getmy.anybill.de/#/bill/b45f4b77-9c3c-454a-8b87-08d8bbd02f7e"){ result in
switch result {
case .success(let receiptId):
// Continue
case .failure(let error):
switch error.type {
case .genericError:
// handle errors 400..499
case .networkError:
// handle network error
case .noUserError:
// handle no user error
case .invalidRefreshTokenError:
// handle error when access token could not be refreshed
default:
// handle unknown error
}
}
}
billService.addReceiptByUrl(url: URL){ result in
switch result {
case .success(let receiptId):
// Continue
case .failure(let error):
switch error.type {
case .genericError:
// handle errors 400..499
case .networkError:
// handle network error
case .noUserError:
// handle no user error
case .invalidRefreshTokenError:
// handle error when access token could not be refreshed
default:
// handle unknown error
}
}
}
billService.addReceiptById(receiptId: "b45f4b77-9c3c-454a-8b87-08d8bbd02f7e"){ result in
switch result {
case .success(let receiptId):
// Continue
case .failure(let error):
switch error.type {
case .genericError:
// handle errors 400..499
case .networkError:
// handle network error
case .noUserError:
// handle no user error
case .invalidRefreshTokenError:
// handle error when access token could not be refreshed
default:
// handle unknown error
}
}
}
# Export receipts
Export receipt as PDF
To export or save a receipt as PDF call exportReceiptAsPDF()
. This will generate a Byte Array (UInt8
) including a PDF of the given receipt. After converting the Array to a File you can save the file on the device or share to other apps.
receiptsService.exportReceiptAsPDF(receipt: Receipt) {result in
switch result {
case .success(let UInt8Array):
// Convert to File
case .failure(let error):
switch error.type {
case .genericError:
// handle errors 400..499
case .networkError:
// handle network error
case .noUserError:
// handle no user error
case .invalidRefreshTokenError:
// handle error when access token could not be refreshed
default:
// handle unknown error
}
}
}
Export all receipts as zip
Anybill also provides the possibility to combine all receipts of a user in a .zip file and send it to the registered email address (e.g. if the user would like to delete his account but keep his receipt information). You can either specify receipt Ids or export all receipts. Anonymous AppUsers can not use this feature.
receiptsService.exportUserReceipts(receiptIds: [String]?) {result in
switch result {
case .success(let UInt8Array):
// Convert to File
case .failure(let error):
switch error.type {
case .genericError:
// handle errors 400..499
case .networkError:
// handle network error
case .noUserError:
// handle no user error
case .invalidRefreshTokenError:
// handle error when access token could not be refreshed
default:
// handle unknown error
}
}
}
[DEPRECATED]
# Category
The AnybillBase module provides categories, to categorize bills.
The category methods are accessable through the CategoryProvider singleton.
let categoryService = CategoryProvider.shared
# Get category list
Get all categories
Use the getAllCategoriesFromApi()
method to retrieve the complete list of categories. When called the categories are cached in a local database.
billService.getAllCategoriesFromApi { result in
switch result {
case .success(let categories):
//Continue
case .failure(let error):
switch error.type {
case .genericError:
// handle errors 400..499
case .networkError:
// handle network error
case .noUserError:
// handle no user error
case .invalidRefreshTokenError:
// handle error when access token could not be refreshed
default:
// handle unknown error
}
}
}
Get changed categories (recommended)
Use the updateCategories()
method to fill an observable object, containing the list of categories. The method first fills the observable object with the cached categories and then updates the list with the newly modified/added categoires as soon as the API operation completed.
//Observe changes on the observable object
private let categoryService = CategoryProvider.shared
private var cancellables = Set<AnyCancellable>()
categoryService.$observableCategories
.sink { [weak self] in
self?.setCategories(bills: $0)
}
.store(in: &cancellables)
//Call this to update the categories using the caching process
categoryService.updateCategories { result in
switch result {
case .success(let _):
// Continue
case .failure(let error):
switch error.type {
case .genericError:
// handle errors 400..499
case .networkError:
// handle network error
case .noUserError:
// handle no user error
case .invalidRefreshTokenError:
// handle error when access token could not be refreshed
default:
// handle unknown error
}
}
}
# Get specific category
Get category by Id
Retrieving a single Category object by its ID can be done with the getCategoryById()
method.
categoryService.getCategoryById(categoryId: "f4a97778-4607-49a9-b57c-d798dc3fb586"){ result in
switch result {
case .success(let category):
// Continue
case .failure(let error):
switch error.type {
case .genericError:
// handle errors 400..499
case .networkError:
// handle network error
case .noUserError:
// handle no user error
case .invalidRefreshTokenError:
// handle error when access token could not be refreshed
default:
// handle unknown error
}
}
}
Get category by name
Retrieving a category by the name of a store, that belongs to the desired category can be done with the getCategoriesByName()
method. For instance it can be used when creating a ScanBill, where the name of the vendor can be set manually. Using getCategoriesByName()
the vendor name can be matched with a BillCategory/List of BillCategory which then can be displayed to the user as a suggestion
categoryService.getCategoriesByName(name: "Aldi") { result in
switch result {
case .success(let categories):
// Continue
case .failure(let error):
switch error.type {
case .genericError:
// handle errors 400..499
case .networkError:
// handle network error
case .noUserError:
// handle no user error
case .invalidRefreshTokenError:
// handle error when access token could not be refreshed
default:
// handle unknown error
}
}
}
# Log
The AnybillBase module is used by other modules to log internal errors of the anybill SDK to Datadog. No information of your users or your app are being logged. For initialization instructions take a look at the Getting started section
# AnybillContentArea
The AnybillContentArea module enables the user to fetch a specific content area object.
The methods are accessable through the ContentAreaProvider singleton.
let contentAreaService = ContentAreaProvider.shared
# Get content area
Update content area (recommended)
This is the recommended way of fetching a content area object. First you need call updateContentArea()
with the bill id. If the content area is cached you will get the cached content area first and then the updated one as soon as the api call finished. The fetched content areas are stored in an observable dictionary with the billIds as keys.
private let contentAreaService = ContentAreaProvider.shared
private var cancellables = Set<AnyCancellable>()
contentAreaService.$contentAreaDict
.sink { [weak self] in
if let contentArea = $0[billId] {
self?.handleContentArea(contentArea: contentArea)
}
}
.store(in: &cancellables)
contentAreaService.updateContentArea(contentAreaId: id){ _ in
// can be used to handle the failure of the api call
}
Get content area
Use the getContentArea()
method to retrieve a content area object. When called the content area is cached in a local database.
contentAreaService.getContentArea(billId: String) { result in
switch result {
case .success(let contentArea):
//Continue
case .failure(let error):
switch error.type {
case .genericError:
// handle errors 400..499
case .networkError:
// handle network error
case .noUserError:
// handle no user error
case .invalidRefreshTokenError:
// handle error when access token could not be refreshed
default:
// handle unknown error
}
}
}
[DEPRECATED]
# AnybillStore
The AnybillStore module enables the user to fetch shops and vendors which are associated with anybill.
The methods are accessable through the StoreProvider singleton.
let storeService = StoreProvider.shared
# Get stores
Get all stores
Use the getAllStoresFromApi()
method to retrieve the complete list of stores. When called the stores are cached in a local database.
storeService.getAllStoresFromApi { result in
switch result {
case .success(let stores):
//Continue
case .failure(let error):
switch error.type {
case .genericError:
// handle errors 400..499
case .networkError:
// handle network error
case .noUserError:
// handle no user error
case .invalidRefreshTokenError:
// handle error when access token could not be refreshed
default:
// handle unknown error
}
}
}
Get changed stores (recommended)
Use the updateStores()
method to fill an observable object, containing the list of stores. The method first fills the observable object with the cached stores and then updates the list with the newly modified/added stores as soon as the API operation completed.
//Observe changes on the store observable object
private let storeService = StoreProvider.shared
private var cancellables = Set<AnyCancellable>()
storeService.$observableStores
.sink { [weak self] in
self?.setStores(bills: $0)
}
.store(in: &cancellables)
//Call this to update the stores using the caching process
storeService.updateStores { result in
switch result {
case .success(let _):
// Continue
case .failure(let error):
switch error.type {
case .genericError:
// handle errors 400..499
case .networkError:
// handle network error
case .noUserError:
// handle no user error
case .invalidRefreshTokenError:
// handle error when access token could not be refreshed
default:
// handle unknown error
}
}
}
# Get store details
Get details about a specific store
Use the getStoreDetails()
method to fetch information about a specific store.
storeService.getStoreDetails(storeId: String) { result in
switch result {
case .success(let storeDetails):
// Continue
case .failure(let error):
switch error.type {
case .genericError:
// handle errors 400..499
case .networkError:
// handle network error
case .noUserError:
// handle no user error
case .invalidRefreshTokenError:
// handle error when access token could not be refreshed
default:
// handle unknown error
}
}
}