# iOS Integration

# General Information

Getting Started
App Link
Push Notifications

# Base Modules

AnybillBase
AnybillCore (deprecated)
AnybillAuth (deprecated)
AnybillBill (deprecated)
AnybillCategory (deprecated)
AnybillLog (deprecated)

# Additional Modules

AnybillContentArea
AnybillStore

# UI Modules

AnybillUI

# Getting Started

Breaking changes when updating to new iOS SDK versions (2.x.x and AnybillBase)

Note: With the latest update AnybillCore, AnybillAuth, AnybillBill, AnybillLog and AnybillCategory were merged together to the new AnybillBase module. The specific providers and models remain the same.

Updates to the following module versions are affected:

  • AnybillAuth 2.0.0
  • AnybillBill 4.0.0
  • AnybillCore 2.0.0
  • AnybillLog 2.0.0
  • AnybillStore 2.0.0
  • AnybillCategory 2.0.0
  • AnybillContentArea 2.0.0
  • AnybillBase 1.0.0

The iOS SDK modules minimum deployment target was increased to iOS 13. In this course, we improved the networking services, error handling and the observables.

The specific changes are:

  • New instantiation of module providers:
/// Old instantiation
let billService = BillServiceFactory.shared.create() 

/// New instantiation
let billService = BillProvider.shared
  • Slightly adjusted error handling:
/// Old error
case .failure(let error):
    if let anybillError = error as? AnybillError {
        switch anybillError.type {
        case .GenericError:
            // handle generic error
        case .NetworkError:
            // handle network error
        }
    }

/// Adjusted error
case .failure(let error):
    // Handle error
    // For example:
switch error.type {
    case .genericError:
        // handle errors 400..499
        if error.code == 400 {
        // handle 400 error
        }
    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
}
  • New observables:

/// Old observable
NotificationCenter.default.addObserver(self, selector: #selector(self.setBills(_:)), name: Notification.Name(rawValue: ApiConfig.BILL_LIVE_DATA_OBSERVER), object: nil)

@objc func setBills(_ notification: Notification) {
    if let bills = notification.userInfo?[BillConfig.BILL_OBSERVABLE_TAG] as? [BillBase] {
    // use bills
    }
}

/// New observable
BillProvider.shared.$observableBills
    .sink { [weak self] in
        self?.setBills(bills: $0)
    }
    .store(in: &cancellables)

# 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:

  1. Install cocoapods-art plugin
gem install cocoapods-art
  1. 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"
  1. 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

  1. First of all you need to specify the usage of dynamic frameworks in your target/project with use_frameworks!, if you did not already.

  2. 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

# Instantiate the log module in your AppDelegate file

In your AppDelegate file import AnybillBase and add LoggerDD.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 AnybillErrorobject 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
                }
        }
    }

Back to top

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.

To automatically add a bill 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 bill. If that is the case simply navigate to the anybill ui module or use the returning 'AnybillURLData' object to manually add the bill to the users account.

# Get the bill if the app is not installed yet

If the user tries to add the bill 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 bill after push notification

To open a bill 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 bill 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 bill 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)
        }
}

Back to top

# 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
                }
        }
    }

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.

  1. Register notification observer:
        NotificationCenter.default.addObserver(self, selector: #selector(getNewToken(_:)), name: Notification.Name(rawValue: ApiConfig.REFRESH_TOKEN_FAILED_OBSERVER), object: nil)
    
  2. Write function that gets a new token from the anybill api and log in the user with the new token

# 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
                }
        }
    }

[DEPRECATED]

Register Anybill User (deprecated)

To register a new anybill user use the registerUser() method. It requires a valid email, a password which fulfills anybill's password policy (at least 8 characters with 1 lowercase, 1 capital letters and 1 number) and the first name of the user. Other information of the user is optional. By setting autoLogin to true the user is going to be automatically logged in after a successful registration (default = false).

    authService.registerUser(email: email, firstname: firstname, gender: gender, password: password, lastname: lastName, birthday: birthday, autoLogin: autoLogin) { result in
        switch result {
            case .success(let anybillUser):
                //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 bills). 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 bills, 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 bills by scanning a QR code on the user's phone. To get the jsonObject which should be included in the QR Code use the getQRCodeDatamethod.

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.

Back to top

# Bill

The AnybillBase module enables the user to manage his bills.

The methods are accessable through the BillProvider singleton.

  let billService = BillProvider.shared

After version 3.0.0

Every method in the BillProvider, that returns a list or an individual bill object, actually returns a BillBase protocol object, which can be parsed to the actual bill object with the type parameter of the BillBase protocol. The BillBase type parameter can have three values: .bill, .pregenerated and .scan.

  • BillBase with type .bill are parsed as NoScanBill
    A NoScanBill is issued by anybill.

  • BillBase with type .pregenerated are parsed as PregeneratedBill
    A PregeneratedBill is added to the user account before the bill is available and then converted to a normal bill as soon as the related information is existing.

  • BillBase with type .scan are parsed as ScanBill
    A ScanBill is manually created by the user using the anybill app.

# Get bill list

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 bill with qr code

Anybill allows adding bills to the user's bill 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 bill ID and add it to the user's bill 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:

    billService.addBillByUrl(url: "https://getmy.anybill.de/#/bill/b45f4b77-9c3c-454a-8b87-08d8bbd02f7e"){ result in
        switch result {
            case .success(let billId):
                // 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.addBillByUrl(url: URL){ result in
        switch result {
            case .success(let billId):
                // 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.addBillById(billId: "b45f4b77-9c3c-454a-8b87-08d8bbd02f7e"){ result in
        switch result {
            case .success(let billId):
                // 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 bills

Export bill as PDF

To export or save a bill as PDF call exportBillAsPDF(). This will generate a Byte Array (UInt8) including a PDF of the given bill. After converting the Array to a File you can save the file on the device or share to other apps.

    billsService.exportBillAsPDF(bill: Bill) {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 bills as zip

Anybill also provides the possibility to combine all bills 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 bill information). You can either specify Bill Ids or export all Bills. Anonymous AppUsers can not use this feature.

    billsService.exportUserBills(billIds: [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
                }
        }     
    }

Back to top

# 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
                }
        }
    }

Back to top

# 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

# AnybillCore

The functionality of the base modules were merged together into the new AnybillBase module

Back to top

# AnybillAuth

The functionality of the base modules were merged together into the new AnybillBase module

Back to top

# AnybillBill

The functionality of the base modules were merged together into the new AnybillBase module

Back to top

# AnybillCategory

The functionality of the base modules were merged together into the new AnybillBase module

Back to top

# AnybillLog

The functionality of the base modules were merged together into the new AnybillBase module

Back to top

# 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
                }
        }
    }

Back to top

# 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
                }
        }
    }

Back to top

# AnybillUI

The AnybillUI module is an easy integratable ui extension to your app, which allows your users to manage their anybill bills.

Minimum iOS Version: 13.0

Used dependencies:

  • pod 'AnybillBase'
  • pod 'AnybillContentArea'
  • pod 'AnybillShops'
  • pod 'SVGKit'
  • pod 'RSBarcodes_Swift
  • pod 'BottomSheetSwiftUI'

# Integrate ui module

To integrate the anybill ui module to your app, simply add the AnybillUIModule View to the desired location.

Example integration to a SwiftUI TabView:

    TabView(selection: $tab) {
        Home()
            .tabItem {
                Label("Home", systemImage: "homekit")
            }
        AnybillUIModule(color: .red, style: .AcceptancePartner)
            .tabItem {
                Label("UI", systemImage: "pc")
            }
    }

To integrate the anybill ui module in an UIKit app, simply wrap the AnybillUIModule View in an UIHostingController and present it like any other ViewController.

        let vc = UIHostingController(rootView: AnybillUIModule(color: .red, style: .AcceptancePartner))
        vc.modalPresentationStyle = .fullScreen
        self.present(vc, animated: false)

Note: The ui module needs multiple permissions to work properly. Therefore the following permissions have to be added to your application's plist.info file:

# Personalize the anybill ui module

The anybill ui module is customizable, by adjusting the parameters of the initialize function.

  • The main color of the ui module can be changed with the color parameter (default = UIColor.black).

  • Different features can be hidden, by setting the specific flags (per default all features are enabled).

  • The style parameter specifies the look of the bill- and shop list items.

    • AcceptancePartner:

      • Bill list item with icon, name and date
      • Shop list item with icon, name, address and distance (if permission is granted)
        Acceptance Partner bill list Acceptance Partner shop list
    • Vendor:

      • Bill list item with address and date
      • Shop list item with address and distance (if permission is granted)
        Merchant bill list Merchant shop list
public init(color: UIColor = UIColor.black,
                shopsEnabled: Bool = true,
                profileEnabled: Bool = true,
                qrCodeEnabled: Bool = true,
                billScanEnabled: Bool = true,
                contentAreaEnabled: Bool = true,
                style: AnybillUIModuleStyle = .AcceptancePartner
                )

# Update data on appstart

Optionally you can add SDKData().updateAllData() to your application(_:didFinishLaunchingWithOptions:) method in the AppDelegate file to update the anybill data on the appstart.