Skip to main content

Photo Payment

With Docutain's Photo Payment SDK you can integrate photo payment into your app within minutes. It includes ready-to-use UI components that can be altered to your needs and corporate design.

photoPayment

Data Protection​

Invoices contain lots of private information that need to be protected. By using the Docutain Photo Payment SDK, you have chosen the only available solution that provides 100% data protection.

This is because all functionality, especially analyzing the document content to find the relevant payment information happens on device, 100% offline. Meaning, no data at all is transfered to any external server or cloud service.

This eliminates any data protection risk and makes the Docutain SDK the first choice.

Reliability​

We all know that even the most reliable servers experience outages from time to time, which directly result in the inability to process photo payments for your users.

Since the Docutain SDK runs offline on the respective device, without any server connection, the functionality is 100% available.

Photo Payment​

Initialization​

Start Photo Payment​

To start the photo payment process you only have to call DocutainSdk.startPhotoPayment and wait for it to return.

You can pass options to change some behaviours and theming to adopt it to your needs. See Change default scan behaviour for possible custom settings.

import de.docutain.sdk.kmp.DocutainException
import de.docutain.sdk.kmp.DocutainSdk
import de.docutain.sdk.kmp.PhotoPaymentConfiguration

val scope = rememberCoroutineScope()
scope.launch {
try {
val photoPaymentConfig = PhotoPaymentConfiguration()
val result = DocutainSdk.startPhotoPayment(photoPaymentConfig)
if (result != null) {
if (result.isNotEmpty()) {
// payment information has been extracted
// result is a JSON string containing the information
// extract the fields you need and pass it on to your payment sheet
} else {
// no data was extracted at all
// Note: this case is only reachable if you disabled the Empty Result Screen
}
} else {
// user cancelled scan process
}
} catch (exception: DocutainException) {
// the SDK operation failed
}
}
info

See Error Handling for details about errors the SDK can throw.

Extract Payment Data​

The payment data will be returned as JSON string. By default, it will have the following structure:

{
"Address": {
"Name1": "DB Fernverkehr AG",
"Name2": "",
"Name3": "",
"Zipcode": "60643",
"City": "Frankfurt am Main",
"Street": "BahnCard-Service",
"Phone": "0302970",
"CustomerId": "",
"Bank": [{
"BIC": "PBNKDEFFXXX",
"IBAN": "DE02100100100152517108"
}]
},
"Date": "2026-09-17",
"Amount": "244.00",
"InvoiceId": "2023174086",
"Reference": "RNr:2023174086 vom 17.09.2026",
"SEPACreditor": "DB Vertrieb GmbH"
}

For a transfer form, use the extracted values as suggestions for these fields:

Transfer form fieldJSON fieldHow to use it
RecipientSEPACreditorThe SEPA creditor is the payment recipient and may differ from the document sender.
IBANAddress.Bank[].IBANThe list can contain multiple bank accounts. We recommend using the IBAN from the first entry. If BIC reading is disabled, Address.IBAN[] can contain multiple IBANs; use the first entry there as well. See below for how to disable BIC reading.
AmountAmountPrefill the transfer amount. If Amount is 0.00, no amount was detected; leave the amount field empty so the user can enter it.
Payment referenceReferencePrefill the transfer's purpose/reference field.
BIC, if neededAddress.Bank[].BICAlways use the BIC from the same Address.Bank[] entry as the IBAN used for the transfer. The IBAN and BIC in each bank account belong together.

If you don't need it, you can disable reading the BIC:

val photoPaymentConfig = PhotoPaymentConfiguration().apply {
analyzeConfig.readBIC = false
}
val scope = rememberCoroutineScope()
scope.launch {
val result = DocutainSdk.startPhotoPayment(photoPaymentConfig)
}

Instead of a Bank element, the data will then only have an IBAN element:

{
"Address": {
"Name1": "DB Fernverkehr AG",
"Name2": "",
"Name3": "",
"Zipcode": "60643",
"City": "Frankfurt am Main",
"Street": "BahnCard-Service",
"Phone": "0302970",
"CustomerId": "",
"IBAN": ["DE76570928000208303503", "DE41510500150710030316"]
},
"Date": "2026-09-17",
"Amount": "244.00",
"InvoiceId": "2023174086",
"Reference": "RNr:2023174086 vom 17.09.2026",
"SEPACreditor": "DB Vertrieb GmbH"
}

Optionally, you can enable reading the payment state, telling you whether the invoice has already been marked as paid.

val photoPaymentConfig = PhotoPaymentConfiguration().apply {
analyzeConfig.readPaymentState = true
}
val scope = rememberCoroutineScope()
scope.launch {
val result = DocutainSdk.startPhotoPayment(photoPaymentConfig)
}

The payment data will then have an additional value PaymentState which contains either Paid or ToBePaid:

{
"Address": {
"Name1": "DB Fernverkehr AG",
"Name2": "",
"Name3": "",
"Zipcode": "60643",
"City": "Frankfurt am Main",
"Street": "BahnCard-Service",
"Phone": "0302970",
"CustomerId": "",
"Bank": [{
"BIC": "PBNKDEFFXXX",
"IBAN": "DE02100100100152517108"
}]
},
"Date": "2026-09-17",
"Amount": "244.00",
"InvoiceId": "2023174086",
"Reference": "RNr:2023174086 vom 17.09.2026",
"PaymentState": "ToBePaid",
"SEPACreditor": "DB Vertrieb GmbH"
}

By default, the SEPACreditor is read as well. Some invoices specify a SEPA creditor that is not the same as the sender of the document, but the SEPA creditor is the recipient of the payment.

Warning

For photo payment, we recommend keeping readSEPACreditor enabled. Disabling it can remove the correct payment recipient from the extracted data when the SEPA creditor differs from the document sender.

If you do not need it for another use case, you can disable it:

val photoPaymentConfig = PhotoPaymentConfiguration().apply {
analyzeConfig.readSEPACreditor = false
}
val scope = rememberCoroutineScope()
scope.launch {
val result = DocutainSdk.startPhotoPayment(photoPaymentConfig)
}

The analyzeConfig of the PhotoPaymentConfiguration is a PaymentAnalyzeConfiguration and provides the following values:

PropertyTypeDefault ValueDescription
readBICBooleantrueIf true, the detected data will contain the BIC, if any available.
readPaymentStateBooleanfalseIf true, the detected data will contain a field showing the payment state. Either Paid or ToBePaid.
readSEPACreditorBooleantrueIf true, the detected data will contain the SEPA creditor, if any available.

GiroCode​

If the scanned invoice contains a valid GiroCode, the SDK extracts the payment data directly from it instead of performing a full document analysis. This can significantly reduce processing time, especially for larger documents. If no valid GiroCode is found, the SDK automatically falls back to the standard recognition process.

GiroCode extraction is enabled by default. If you do not want it, set allowGiroCode to false:

val photoPaymentConfig = PhotoPaymentConfiguration().apply {
allowGiroCode = false
}
val scope = rememberCoroutineScope()
scope.launch {
val result = DocutainSdk.startPhotoPayment(photoPaymentConfig)
}

Open with / Share​

To accept a PDF or image from another app via "open with" or "share", register your app as a target on each platform and pass the received URI strings to startPhotoPaymentWithExternalFiles. The SDK shows the extraction UI and returns the extracted payment data the same way as after a scan.

On Android, add intent filters for PDF and image files: ACTION_VIEW for "open with", and ACTION_SEND / ACTION_SEND_MULTIPLE for sharing one or more files. Read the URI from Intent.data or Intent.EXTRA_STREAM, respectively, and pass the readable content:// URIs as strings. Handle both the initial intent and onNewIntent so files shared while the app is already running are processed too.

On iOS, register PDFs and images in CFBundleDocumentTypes and receive the opened file through onOpenURL. A file from Files or iCloud Drive may only be readable while your app has access to its original URL. Copy it into an app-owned location while that access is active, then pass its file:// URL as a list containing exactly one entry.

In shared code, start photo payment once the SDK is initialized and a received file is pending. Consume the pending files once so recomposition does not start the same payment again. On Android, avoid processing the initial intent again when the activity is recreated.

// fileUris contains the URI strings handed over by the platform's share/open-with handler.
val scope = rememberCoroutineScope()
scope.launch {
val result = DocutainSdk.startPhotoPaymentWithExternalFiles(fileUris)
// Handle the JSON result, or null if the user cancelled.
}

For a complete Compose example of the Android and iOS open-with/share handling, see the Kotlin Multiplatform Photo Payment sample.

Onboarding​

The SDK provides 2 optional onboarding possibilities that will be shown to the user on first start with some default content. You can customize it according to your needs or disable it completely.

See Onboarding for details.

onboardingDataProtection onboardingLightingConditions

Scan Tips​

The SDK provides an optional toolbar item within the scanning screen, that when clicked, will open some tips on how to get the best scan result. By default it is activated and shows some default items. You can customize it according to your needs or disable it completely.

See Scan Tips for details.

scanTips

Empty Result Screen​

By default, when no payment data could be extracted (meaning, no IBAN, no SEPACreditor, no Amount), the SDK provides a screen with some tips and an option to cancel or to retry the scan. You can customize it according to your needs or disable it completely.

emptyResultScreen

Disable​

To disable the empty result screen, set the emptyResultScreen option to null:

val photoPaymentConfig = PhotoPaymentConfiguration().apply {
emptyResultScreen = null
}
val scope = rememberCoroutineScope()
scope.launch {
val result = DocutainSdk.startPhotoPayment(photoPaymentConfig)
}

Customize​

Button colors are configured by the global color configuration.

Use the items property to select and arrange the default items provided by the SDK.

The following provides an overview of the currently available options to alter the emptyResultScreen:

PropertyTypeDescription
itemsList<DocutainListItem>?The items you want to show as scan tips. If you don't provide any items, some default items will be displayed.
titleString?The title to be displayed in the top toolbar.
repeatButtonDocutainButtonThe button that restarts the scan process.
info

You can get the default items used by the SDK to use them for your own list.

val emptyResultScreenDefaultItems = DocutainSdk.emptyResultScreenDefaultItems()

The following sample alters the repeatButton:

val photoPaymentConfig = PhotoPaymentConfiguration().apply {
emptyResultScreen = EmptyResultScreen().apply {
repeatButton.title = "Repeat scan"
}
}
val scope = rememberCoroutineScope()
scope.launch {
val result = DocutainSdk.startPhotoPayment(photoPaymentConfig)
}

Change default scan behaviour​

PhotoPaymentConfiguration​

You can use the PhotoPaymentConfiguration to alter the default scan behaviour to your needs. Currently the following values can be set from shared code:

PropertyTypeDefault ValueDescription
allowCaptureModeSettingBooleanfalseIf true, the document scanner toolbar will display an item that allows the user to switch between automatic and manual camera triggering.
autoCaptureBooleantrueIf true, the camera will capture the image automatically at the right moment.
defaultScanFilterScanFilterILLUSTRATIONThe default scan filter that will be used after scan.
pageEditConfigPageEditConfigurationPageEditConfigurationConfiguration class used to alter the default page editing behaviour.
sourceSourceCAMERA_IMPORTThe source of the Document Scanner.
autoCropBooleanfalseIf true, the image gets automatically cropped if document was detected. This applies only when importing images.
multiPageBooleantrueIf true, scanning multi page documents is possible. Set this to false if you need to scan single page documents.
preCaptureFocusBooleantrueIf true, the camera will run a focus action right before taking the image. This improves the quality of the scanned images, but depending on the device, image capture might take a little bit longer. Available only on Android.
textConfigTextConfigurationTextConfigurationConfiguration class used to alter the default text behaviour.
buttonConfigButtonConfigurationButtonConfigurationConfiguration class used to alter the default buttons.
colorConfigColorConfigurationColorConfigurationConfiguration class used to alter the default color theming behaviour.
confirmPagesBooleanfalseIf true, a list of all pages (thumbnails) will be displayed before the scan process can be finished.
allowPageEditingBooleantrueIf true, after the scan screen is finished, an editing screen with the captured images will be displayed.
statusBarAppearanceStatusBarAppearance?nullOverrides the status bar appearance. This only applies to Android.
navigationBarAppearanceNavigationBarAppearance?nullOverrides the navigation bar appearance. This only applies to Android.
onboardingOnboarding?SDK defaultAn optional onboarding when the user opens the scanner for the first time. See Onboarding.
scanTipsScanTips?SDK defaultAn optional toolbar item that shows scan tips when clicked. See Scan Tips.
analyzeConfigPaymentAnalyzeConfigurationPaymentAnalyzeConfigurationA configuration class used to alter the default document analysis behaviour for payment. See Extract Payment Data.
vibrateOnCaptureBooleantrueIf true, when an image is captured, the device vibrates to signal successful capture.
emptyResultScreenEmptyResultScreen?SDK defaultA screen that will be displayed if no payment information could be extracted. See Empty Result Screen.
allowGiroCodeBooleantrueIf true, GiroCode data will be extracted. See GiroCode.
info

All parameters in PhotoPaymentConfiguration are optional.

tip

Set allowPageEditing to false to skip the editing screen and make the photo payment process even faster.

The following sample shows how to disable page editing completely:

val photoPaymentConfig = PhotoPaymentConfiguration().apply {
allowPageEditing = false
}
val scope = rememberCoroutineScope()
scope.launch {
val result = DocutainSdk.startPhotoPayment(photoPaymentConfig)
}

PageEditConfiguration​

You can use the PageEditConfiguration to alter the default page editing behaviour of the document scanner to your needs. Currently the following values can be set:

PropertyTypeDefault ValueDescription
allowPageFilterBooleanfalseIf false, the bottom toolbar will hide the filter page item.
allowPageRotationBooleanfalseIf false, the bottom toolbar will hide the rotate page item.
allowPageArrangementBooleanfalseIf false, the bottom toolbar will hide the arrange page item.
allowPageCroppingBooleantrueIf false, the bottom toolbar will hide the page cropping item.
allowPageRetakeBooleantrueIf true, the bottom toolbar will show a button allowing to retake the current page.
allowPageAddBooleantrueIf true, the bottom toolbar will show a button allowing to add a new page.
allowPageDeletionBooleantrueIf true, the menu item for deleting pages will be displayed in the toolbar.
pageArrangementShowDeleteButtonBooleanfalseIf true, each item of the page arrangement functionality will show a delete button.
pageArrangementShowPageNumberBooleantrueIf true, each item of the page arrangement functionality will show its page number.

The following sample shows how to disable the page retake button:

val photoPaymentConfig = PhotoPaymentConfiguration().apply {
pageEditConfig.allowPageRetake = false
}
val scope = rememberCoroutineScope()
scope.launch {
val result = DocutainSdk.startPhotoPayment(photoPaymentConfig)
}

TextConfiguration​

You can use the TextConfiguration to alter the default text behaviour of the document scanner to your needs. If a value does not get set explicitly, the default value provided by the SDK will be used. Currently the following values can be set:

PropertyTypeDescription
textSizeBottomToolbarFloat?The text size of elements residing in the bottom toolbar.
textSizeTopToolbarFloat?The text size of menu items residing in the top toolbar.
textSizeScanButtonsFloat?The text size of the buttons in the scan page, located at the lower part, like the torch button.
textSizeTitleFloat?The text size of the title in the top toolbar. By default, auto shrinking down till 9.0 is enabled. If you define your custom size, automatic shrinking will be disabled.
textTitleScanPageString?The title to be displayed in the scan page top toolbar.
textTitleEditPageString?The title to be displayed in the edit page top toolbar.
textTitleFilterPageString?The title to be displayed in the filter page top toolbar.
textTitleCroppingPageString?The title to be displayed in the cropping page top toolbar.
textTitleArrangementPageString?The title to be displayed in the page arrangement page top toolbar.
textTitleConfirmationPageString?The title to be displayed in the confirmation page top toolbar.
textDocumentTitleString?The title to show in the top toolbar on all pages. It overwrites page specific titles, if any are set.
textFocusHintString?The text to show when camera is focusing after capture got triggered.
textFirstPageHintString?The text to show when user swipes to the previous page but is already at the first page.
textLastPageHintString?The text to show when user swipes to the next page but is already at the last page.
textOnePageHintString?The text to show when user swipes but only one page is available.
textScanProgressString?The text to show in the progress popup while pages are still being processed.
textDeleteDialogCurrentPageStringThe text for the option to delete the current page.
textDeleteDialogAllPagesStringThe text for the option to delete all pages.
textDeleteDialogCancelString?The text for the cancel option.
textTitleScanTipsPageString?The title to be displayed in the scan tips page top toolbar.
info

All parameters in TextConfiguration are optional.

The following sample shows how to set a document title:

val photoPaymentConfig = PhotoPaymentConfiguration().apply {
textConfig.textDocumentTitle = "Document Title"
}
val scope = rememberCoroutineScope()
scope.launch {
val result = DocutainSdk.startPhotoPayment(photoPaymentConfig)
}

ButtonConfiguration​

You can use the ButtonConfiguration to alter the default buttons of the scanner. Each button is an object of DocutainButton; its title can be changed from shared code.

Currently the following buttons can be set:

ButtonDefault ValueDescription
buttonEditRotatebuttonEditRotateThe button that rotates the current page.
buttonEditCropbuttonEditCropThe button that opens the cropping functionality.
buttonEditFilterbuttonEditFilterThe button that opens the filter functionality.
buttonEditArrangebuttonEditArrangeThe button that opens the page arrangement functionality.
buttonEditRetakebuttonEditRetakeThe button that starts the process of replacing the current page with a new scan.
buttonEditAddPagebuttonEditAddPageThe button on the edit page that opens the scan screen to add a new page.
buttonEditDeletebuttonEditDeleteThe button that deletes the current page or opens a dialog with options if multiple pages are available.
buttonEditFinishbuttonEditFinishThe button that finishes the scan process.
buttonCropExpandbuttonCropExpandThe button that expands the cropping rectangle to the whole page.
buttonCropSnapbuttonCropSnapThe button that snaps the cropping rectangle to the detected document.
buttonCropFinishbuttonCropFinishThe button that finishes the manual cropping process.
buttonScanAutoCaptureOnbuttonScanAutoCaptureOnThe button shown when automatic capture is activated.
buttonScanAutoCaptureOffbuttonScanAutoCaptureOffThe button shown when automatic capture is deactivated.
buttonScanTorchbuttonScanTorchThe button that toggles the torch.
buttonScanCapturebuttonScanCaptureThe button that triggers a manual image capture.
buttonScanFinishbuttonScanFinishThe button that finishes the current scan process.
buttonScanImportbuttonScanImportThe button that opens a file importer.
buttonConfirmationFinishbuttonConfirmationFinishThe button that finishes the confirmation page.

On iOS, shared code can also configure two cancel buttons:

FunctionDescription
configureEditCancelImportConfigures the button that cancels importing from the edit page.
configureScanCancelConfigures the button that cancels the current scan.
import de.docutain.sdk.kmp.configureEditCancelImport
import de.docutain.sdk.kmp.configureScanCancel

val photoPaymentConfig = PhotoPaymentConfiguration().apply {
buttonConfig.configureEditCancelImport { title = "Cancel import" }
buttonConfig.configureScanCancel { title = "Cancel scan" }
}
val scope = rememberCoroutineScope()
scope.launch {
DocutainSdk.startPhotoPayment(photoPaymentConfig)
}
info

All parameters in ButtonConfiguration are optional.

The following sample shows how to customize buttonEditRetake:

val photoPaymentConfig = PhotoPaymentConfiguration().apply {
buttonConfig.buttonEditRetake.title = "Custom Title"
}
val scope = rememberCoroutineScope()
scope.launch {
val result = DocutainSdk.startPhotoPayment(photoPaymentConfig)
}

ColorConfiguration​

In order to fit the Docutain Photo Payment SDK into your corporate design, you have a bunch of options to alter the default color theming of the ready-to-use UI components. See color configuration for details.