Updated 10 min read By Marvin Frankenfeld

Integrate Photo Payment on Android with Kotlin

This guide shows how to add a production-ready invoice-to-transfer flow with Kotlin and Docutain SDK 1.9.0.0.

Users can scan a paper invoice, import a PDF or image, or read a GiroCode. Docutain returns the payment data as JSON, including only valid IBANs, so that the host app can apply its business rules and prefill its own transfer screen.

Recognition and IBAN validation run locally on the device. The banking app remains in control of user confirmation, its payment rules, authentication and the final SEPA credit transfer.

SDK version
Android 1.9.0.0
Minimum version
Android 6.0 / API 23
Inputs
Camera, PDF, image, GiroCode
Processing
100% local and offline
Jump to the Kotlin setup

The payment workflow you are building

A robust photo-payment integration separates data capture from payment authorization:

  1. The user opens the payment flow from the banking app.
  2. Docutain scans or imports the invoice and checks for a GiroCode.
  3. If no usable GiroCode is available, the SDK extracts payment data from the visible invoice text.
  4. The SDK returns structured JSON to the host app.
  5. The app applies its business rules and displays the data for review before its established authorization flow starts.

This design gives users one entry point for QR-equipped and conventional invoices. It also keeps payment execution outside the SDK and within the bank's existing controls.

What changed with Docutain SDK 1.9

Since version 1.9, Photo Payment can extract GiroCode data directly. The option allowGiroCode is enabled by default, so a separate barcode flow is no longer required for this use case.

  • One user journey: GiroCode and invoice OCR are handled by the same Photo Payment flow.
  • Share and open support: on Android, an incoming PDF or image can be forwarded through openWithIntent.
  • Purpose-built guidance: Photo-Payment-specific onboarding and scan-tip defaults also cover GiroCode capture.
  • Local processing: both QR data and payment information from the invoice are evaluated on the device.

Android prerequisites

ComponentRequirement for SDK 1.9.0.0
LanguageKotlin in this guide; Java is also supported
Minimum Android versionAndroid 6.0 / API level 23
Compile SDKAPI level 34 or newer
Android Gradle Plugin8.0.2 or newer
LicenseA key that matches the app's applicationId
Test hardwareA physical device with a rear-facing camera

Add and initialize Docutain SDK 1.9.0.0

1. Add the two Photo Payment modules

Make sure Maven Central is configured, then add the UI and DataExtraction modules to the app module:

def docutainSdkVersion = '1.9.0.0'
implementation("de.docutain:Docutain-SDK-UI:$docutainSdkVersion")
implementation("de.docutain:Docutain-SDK-DataExtraction:$docutainSdkVersion")

2. Initialize the SDK

Initialize Docutain before exposing SDK functionality. The placeholder below must be replaced by the license key issued for the app's exact applicationId.

import de.docutain.sdk.DocutainSDK;

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        if(!DocutainSDK.initSDK(this.application, "<YOUR-LICENSE-KEY>")){
            //init of Docutain SDK failed, get the last error message
            val error = DocutainSDK.getLastError()
            //your logic to deactivate access to SDK functionality
        }
    }
}

Do not allow the scan action to continue after initSDK() returns false. Log or surface getLastError() according to the app's support concept without exposing sensitive information.

Launch Photo Payment

Register the Activity Result contract once and launch it from the app's payment action:

import de.docutain.sdk.ui.PhotoPaymentConfiguration
import de.docutain.sdk.ui.PhotoPaymentResult

val photoPaymentResult = registerForActivityResult(PhotoPaymentResult()) { data ->
    if(data != null){
        if(data.isNotEmpty()){
            // data 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 canceled scan process
    }
}

myButton.setOnClickListener {   
    val paymentConfig = PhotoPaymentConfiguration()
    photoPaymentResult.launch(paymentConfig)
}

The default configuration provides the complete capture, review and extraction flow. Customize UI, onboarding and Scan Tips only where this improves the bank's existing design and user guidance.

Process the JSON result

A successful result is returned as a JSON string. The default structure can contain the following fields:

{
    "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": "2024-10-14",
    "Amount": "244.00",
    "InvoiceId": "2023174086",
    "Reference": "RNr:2023174086 vom 14.10.2024",
    "SEPACreditor": "DB Vertrieb GmbH"
}

Map only the fields needed by the payment form and normalize their display formats in the host app. Docutain validates the IBAN and returns it only when it is valid. The host app still applies its own requirements to recipient, amount and reference. Extracted data should prefill a draft; it must not trigger a transfer automatically.

Combine GiroCode and OCR in one flow

A GiroCode is an EPC QR code containing data for a SEPA credit transfer. In SDK 1.9, PhotoPaymentConfiguration.allowGiroCode defaults to true, so no additional configuration is required for the combined flow.

Invoice inputPrimary extraction pathApplication behavior
Valid GiroCodeDecode structured EPC payment dataApply business rules and prefill the transfer screen
No GiroCodeRead payment data from invoice textApply business rules and prefill the same transfer screen
Unreadable or incomplete resultShow the SDK's retry guidance or app fallbackAllow correction or manual entry
Shared PDF or imagePass the incoming Android intent via openWithIntentContinue in the same Photo Payment flow

For background and user-facing terminology, see how GiroCode payments work. For the broader product and platform scope, visit the Photo Payment SDK page.

Security and test checklist

On-device recognition reduces the amount of invoice data that needs to leave the device, but the complete banking workflow still needs application-level controls.

  • Confirm the valid IBAN belongs to the intended recipient and apply the app's rules for amount, recipient and supported character sets.
  • Always show extracted data before the existing authorization step.
  • Handle cancellation, empty results, malformed files and incomplete GiroCodes.
  • Test paper invoices, folded documents, screen-displayed invoices, PDFs and images received through Android share/open actions.
  • Verify process recreation, camera permissions, memory pressure and accessibility in the host app.
  • Test against representative real invoices with approved or anonymized data.

Official resources

Technical sources last checked on July 30, 2026.

FREQUENTLY ASKED QUESTIONS

Integrating Photo Payment on Android





Contact us and receive your quote

Our pricing is tailored to your use case. Let our colleague Harry Beck know how we can help and receive your quote.




Information about how we process your details is available in our Privacy Policy.