Integrating a check processing SDK should be straightforward, but poor documentation, complex dependencies, and hidden integration requirements often turn what should be a one-week task into a multi-month project. This guide provides the technical roadmap for efficient integration.
Whether youβre evaluating SDKs or already mid-implementation, these practices will help you avoid common pitfalls and deliver a robust mobile deposit feature.
Pre-Integration Planning
SDK Evaluation Criteria
const sdkEvaluation = {
technical: {
platforms: ['iOS', 'Android', 'React Native', 'Flutter'],
minimumOSVersions: { iOS: '12.0', Android: 'API 21' },
dependencies: 'minimal external dependencies',
buildSize: '< 10MB addition to app',
documentation: 'comprehensive with code examples'
},
integration: {
setupTime: '< 1 day for basic integration',
customizationOptions: 'extensive UI/UX customization',
apiDesign: 'modern, async/await or Promise-based',
errorHandling: 'comprehensive error codes and recovery',
testingSupport: 'mocking capabilities for unit tests'
},
business: {
licensing: 'transparent pricing model',
support: '24/7 technical support availability',
compliance: 'SOC2, PCI-DSS certifications included',
updates: 'regular updates with backward compatibility'
}
};
Architecture Planning
// Define your integration architecture before starting
interface CheckProcessingArchitecture {
// Core SDK integration layer
sdkWrapper: {
initialization: 'singleton pattern with configuration';
session_management: 'handle authentication and tokens';
error_handling: 'centralized error processing';
logging: 'structured logging for debugging';
};
// Business logic layer
businessLogic: {
validation: 'pre-processing validation rules';
workflow: 'deposit workflow state management';
compliance: 'regulatory requirements handling';
analytics: 'usage tracking and metrics';
};
// UI/UX layer
userInterface: {
components: 'reusable UI components';
theming: 'consistent with app design system';
accessibility: 'WCAG 2.1 AA compliance';
responsive: 'cross-device compatibility';
};
// Backend integration
backend: {
api_gateway: 'secure communication with core banking';
data_validation: 'server-side verification';
fraud_detection: 'risk assessment integration';
audit_logging: 'compliance and monitoring';
};
}
iOS Integration Guide
Swift Implementation
import CheckProcessingSDK
class CheckDepositManager: NSObject {
private let sdk: CheckProcessingSDK
private var currentSession: CheckSession?
override init() {
// Initialize SDK with configuration
let config = SDKConfiguration(
apiKey: Configuration.checkProcessingAPIKey,
environment: Configuration.isProduction ? .production : .sandbox,
customSettings: [
"enableDebugLogging": !Configuration.isProduction,
"maxRetryAttempts": 3,
"timeoutSeconds": 30
]
)
self.sdk = CheckProcessingSDK(configuration: config)
super.init()
setupSDKDelegates()
}
func startCheckCapture(from viewController: UIViewController) async throws -> CheckResult {
// Create capture session
let session = try await sdk.createSession(
customerID: UserManager.shared.currentUserID,
accountID: AccountManager.shared.selectedAccountID
)
self.currentSession = session
// Configure capture options
let captureOptions = CaptureOptions(
allowedCheckTypes: [.personal, .business, .payroll],
imageQuality: .high,
processingMode: .realTime,
customUI: createCustomUIConfig()
)
// Present capture interface
return try await session.captureCheck(
from: viewController,
options: captureOptions
)
}
private func createCustomUIConfig() -> CustomUIConfig {
return CustomUIConfig(
theme: AppTheme.checkDeposit,
strings: LocalizedStrings.checkDeposit,
accessibility: AccessibilityConfig(
enableVoiceOver: true,
enableAudioGuidance: UserDefaults.isAudioGuidanceEnabled
)
)
}
private func setupSDKDelegates() {
sdk.delegate = self
sdk.analyticsDelegate = AnalyticsManager.shared
sdk.errorDelegate = ErrorManager.shared
}
}
// MARK: - CheckProcessingSDKDelegate
extension CheckDepositManager: CheckProcessingSDKDelegate {
func checkProcessingDidComplete(_ result: CheckResult) {
// Handle successful processing
Task {
await processSuccessfulDeposit(result)
}
}
func checkProcessingDidFail(_ error: CheckProcessingError) {
// Centralized error handling
ErrorManager.shared.handleCheckProcessingError(error)
}
func checkProcessingDidCancel() {
// Handle user cancellation
AnalyticsManager.shared.track(.checkDepositCancelled)
}
}
// MARK: - Error Handling
extension CheckDepositManager {
private func processSuccessfulDeposit(_ result: CheckResult) async {
do {
// Validate result
try validateCheckResult(result)
// Submit to backend
let depositResponse = try await BackendAPI.submitDeposit(
checkData: result.checkData,
images: result.images,
metadata: result.metadata
)
// Update UI
await MainActor.run {
NotificationCenter.default.post(
name: .checkDepositCompleted,
object: depositResponse
)
}
} catch {
await MainActor.run {
ErrorManager.shared.handleError(error)
}
}
}
private func validateCheckResult(_ result: CheckResult) throws {
guard result.confidence > 0.8 else {
throw CheckValidationError.lowConfidence(result.confidence)
}
guard result.checkData.amount > 0 else {
throw CheckValidationError.invalidAmount
}
// Additional business rule validation
try BusinessRules.validateDeposit(result.checkData)
}
}
CocoaPods Integration
# Podfile
platform :ios, '12.0'
use_frameworks!
target 'YourBankingApp' do
pod 'CheckProcessingSDK', '~> 2.1'
# Required dependencies
pod 'Alamofire', '~> 5.6'
pod 'KeychainAccess', '~> 4.2'
# Optional: Enhanced image processing
pod 'GPUImage2', '~> 3.0'
target 'YourBankingAppTests' do
inherit! :search_paths
pod 'CheckProcessingSDK/Testing', '~> 2.1'
end
end
post_install do |installer|
installer.pods_project.targets.each do |target|
target.build_configurations.each do |config|
config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '12.0'
end
end
end
Android Integration Guide
Kotlin Implementation
import com.chequesnap.sdk.CheckProcessingSDK
import com.chequesnap.sdk.model.*
import kotlinx.coroutines.*
class CheckDepositManager(private val context: Context) {
private val sdk: CheckProcessingSDK by lazy {
CheckProcessingSDK.Builder(context)
.setApiKey(BuildConfig.CHECK_PROCESSING_API_KEY)
.setEnvironment(if (BuildConfig.DEBUG) Environment.SANDBOX else Environment.PRODUCTION)
.setConfiguration(createSDKConfiguration())
.build()
}
private val scope = CoroutineScope(Dispatchers.Main + SupervisorJob())
suspend fun startCheckCapture(activity: Activity): Result<CheckDepositResult> {
return withContext(Dispatchers.IO) {
try {
// Initialize session
val session = sdk.createSession(
customerID = UserManager.getCurrentUserID(),
accountID = AccountManager.getSelectedAccountID()
)
// Configure capture parameters
val config = CaptureConfiguration.Builder()
.setImageQuality(ImageQuality.HIGH)
.setProcessingMode(ProcessingMode.REAL_TIME)
.setAllowedCheckTypes(
CheckType.PERSONAL,
CheckType.BUSINESS,
CheckType.PAYROLL
)
.setCustomTheme(createCustomTheme())
.setAccessibilityOptions(createAccessibilityOptions())
.build()
// Start capture flow
val result = session.captureCheck(activity, config)
// Process result
processCheckResult(result)
Result.success(result)
} catch (e: CheckProcessingException) {
handleSDKError(e)
Result.failure(e)
}
}
}
private fun createSDKConfiguration(): SDKConfiguration {
return SDKConfiguration.Builder()
.setDebugLogging(BuildConfig.DEBUG)
.setMaxRetryAttempts(3)
.setTimeoutSeconds(30)
.setAnalyticsEnabled(true)
.setCustomEndpoints(
if (BuildConfig.DEBUG) debugEndpoints else productionEndpoints
)
.build()
}
private fun createCustomTheme(): CustomTheme {
return CustomTheme.Builder()
.setPrimaryColor(ContextCompat.getColor(context, R.color.brand_primary))
.setSecondaryColor(ContextCompat.getColor(context, R.color.brand_secondary))
.setFontFamily(ResourcesCompat.getFont(context, R.font.app_font))
.setCornerRadius(context.resources.getDimensionPixelSize(R.dimen.corner_radius))
.build()
}
private suspend fun processCheckResult(result: CheckDepositResult) {
// Validate on background thread
withContext(Dispatchers.Default) {
validateCheckResult(result)
}
// Submit to backend
val response = BackendAPI.submitDeposit(
checkData = result.extractedData,
frontImage = result.frontImage,
backImage = result.backImage,
metadata = result.metadata
)
// Update UI on main thread
withContext(Dispatchers.Main) {
EventBus.post(CheckDepositCompletedEvent(response))
}
}
@Throws(ValidationException::class)
private fun validateCheckResult(result: CheckDepositResult) {
if (result.confidence < 0.8) {
throw ValidationException("Low confidence score: ${result.confidence}")
}
if (result.extractedData.amount <= 0) {
throw ValidationException("Invalid amount: ${result.extractedData.amount}")
}
// Business rule validation
BusinessRules.validateDeposit(result.extractedData)
}
private fun handleSDKError(error: CheckProcessingException) {
when (error.errorCode) {
ErrorCode.CAMERA_PERMISSION_DENIED -> {
// Handle permission error
PermissionManager.requestCameraPermission()
}
ErrorCode.NETWORK_ERROR -> {
// Handle network error
NetworkErrorHandler.handle(error)
}
ErrorCode.INVALID_CONFIGURATION -> {
// Handle configuration error
CrashReporting.logError(error)
}
else -> {
// Generic error handling
ErrorManager.handleError(error)
}
}
}
fun cleanup() {
scope.cancel()
sdk.cleanup()
}
}
Gradle Configuration
// app/build.gradle
android {
compileSdk 34
defaultConfig {
minSdk 21
targetSdk 34
// SDK configuration
buildConfigField "String", "CHECK_PROCESSING_API_KEY", "\"${project.findProperty('checkProcessingApiKey') ?: ''}\""
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
packagingOptions {
resources {
excludes += '/META-INF/{AL2.0,LGPL2.1}'
}
}
}
dependencies {
implementation 'com.chequesnap:check-processing-sdk:2.1.0'
// Required dependencies
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4'
implementation 'androidx.camera:camera-camera2:1.3.0'
implementation 'androidx.camera:camera-lifecycle:1.3.0'
implementation 'androidx.camera:camera-view:1.3.0'
// Optional: Enhanced image processing
implementation 'org.opencv:opencv-android:4.8.0'
// Testing
testImplementation 'com.chequesnap:check-processing-sdk-testing:2.1.0'
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
}
React Native Integration
JavaScript/TypeScript Implementation
import CheckProcessingSDK from '@chequesnap/react-native-sdk';
import { CheckResult, CaptureOptions, SDKConfiguration } from '@chequesnap/react-native-sdk/types';
class CheckDepositService {
private sdk: CheckProcessingSDK;
private isInitialized = false;
constructor() {
this.initializeSDK();
}
private async initializeSDK(): Promise<void> {
try {
const config: SDKConfiguration = {
apiKey: Config.CHECK_PROCESSING_API_KEY,
environment: __DEV__ ? 'sandbox' : 'production',
customSettings: {
enableDebugLogging: __DEV__,
maxRetryAttempts: 3,
timeoutSeconds: 30,
},
};
this.sdk = new CheckProcessingSDK(config);
await this.sdk.initialize();
this.setupEventListeners();
this.isInitialized = true;
} catch (error) {
console.error('Failed to initialize CheckProcessingSDK:', error);
throw error;
}
}
async captureCheck(): Promise<CheckResult> {
if (!this.isInitialized) {
throw new Error('SDK not initialized');
}
try {
// Request camera permissions
await this.requestPermissions();
// Configure capture options
const options: CaptureOptions = {
imageQuality: 'high',
processingMode: 'realTime',
allowedCheckTypes: ['personal', 'business', 'payroll'],
customUI: {
theme: this.createCustomTheme(),
strings: await this.getLocalizedStrings(),
accessibility: {
enableScreenReader: true,
enableAudioGuidance: await this.getAudioGuidancePreference(),
},
},
};
// Start capture
const result = await this.sdk.captureCheck(options);
// Validate and process result
await this.processCheckResult(result);
return result;
} catch (error) {
this.handleError(error);
throw error;
}
}
private async requestPermissions(): Promise<void> {
const { request, PERMISSIONS, RESULTS } = require('react-native-permissions');
const cameraPermission = Platform.select({
ios: PERMISSIONS.IOS.CAMERA,
android: PERMISSIONS.ANDROID.CAMERA,
});
const result = await request(cameraPermission);
if (result !== RESULTS.GRANTED) {
throw new Error('Camera permission required for check capture');
}
}
private createCustomTheme() {
return {
primaryColor: Colors.primary,
secondaryColor: Colors.secondary,
backgroundColor: Colors.background,
textColor: Colors.text,
cornerRadius: 8,
fontFamily: Fonts.primary,
};
}
private async processCheckResult(result: CheckResult): Promise<void> {
// Client-side validation
this.validateCheckResult(result);
// Submit to backend
const response = await BackendAPI.submitDeposit({
checkData: result.extractedData,
frontImage: result.images.front,
backImage: result.images.back,
metadata: {
...result.metadata,
deviceInfo: await DeviceInfo.getDeviceInfo(),
timestamp: new Date().toISOString(),
},
});
// Analytics tracking
Analytics.track('check_deposit_completed', {
confidence: result.confidence,
processingTime: result.processingTime,
checkType: result.extractedData.checkType,
});
// Navigate to success screen
NavigationService.navigate('CheckDepositSuccess', { response });
}
private validateCheckResult(result: CheckResult): void {
if (result.confidence < 0.8) {
throw new ValidationError('Low confidence score', result.confidence);
}
if (!result.extractedData.amount || result.extractedData.amount <= 0) {
throw new ValidationError('Invalid amount');
}
// Additional business rules
BusinessRules.validateDeposit(result.extractedData);
}
private setupEventListeners(): void {
this.sdk.on('captureStarted', () => {
Analytics.track('check_capture_started');
});
this.sdk.on('imageQualityUpdate', (quality) => {
// Handle real-time quality feedback
this.handleImageQualityUpdate(quality);
});
this.sdk.on('error', (error) => {
console.error('SDK Error:', error);
this.handleError(error);
});
}
private handleError(error: any): void {
// Centralized error handling
ErrorService.handleError(error);
// Show user-friendly error message
if (error.code === 'CAMERA_UNAVAILABLE') {
Alert.alert('Camera Error', 'Unable to access camera. Please check permissions.');
} else if (error.code === 'NETWORK_ERROR') {
Alert.alert('Network Error', 'Please check your internet connection.');
} else {
Alert.alert('Error', 'An unexpected error occurred. Please try again.');
}
}
}
export default new CheckDepositService();
React Native Component
import React, { useState, useEffect } from 'react';
import { View, TouchableOpacity, Text, Alert } from 'react-native';
import CheckDepositService from '../services/CheckDepositService';
const CheckDepositScreen: React.FC = () => {
const [isCapturing, setIsCapturing] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const handleCaptureCheck = async (): Promise<void> => {
try {
setIsCapturing(true);
setIsLoading(true);
const result = await CheckDepositService.captureCheck();
// Handle successful capture
console.log('Check captured successfully:', result);
} catch (error) {
console.error('Check capture failed:', error);
} finally {
setIsCapturing(false);
setIsLoading(false);
}
};
return (
<View style={styles.container}>
<TouchableOpacity
style={[styles.captureButton, isLoading && styles.disabled]}
onPress={handleCaptureCheck}
disabled={isLoading}
accessibilityLabel="Start check capture"
accessibilityRole="button"
>
<Text style={styles.buttonText}>
{isLoading ? 'Processing...' : 'Capture Check'}
</Text>
</TouchableOpacity>
</View>
);
};
Testing and Quality Assurance
Unit Testing Setup
// iOS Unit Tests
import XCTest
@testable import YourBankingApp
@testable import CheckProcessingSDK
class CheckDepositManagerTests: XCTestCase {
var manager: CheckDepositManager!
var mockSDK: MockCheckProcessingSDK!
override func setUp() {
super.setUp()
mockSDK = MockCheckProcessingSDK()
manager = CheckDepositManager(sdk: mockSDK)
}
func testSuccessfulCheckCapture() async throws {
// Arrange
let expectedResult = CheckResult.mock(confidence: 0.95)
mockSDK.captureResult = .success(expectedResult)
// Act
let result = try await manager.startCheckCapture(from: MockViewController())
// Assert
XCTAssertEqual(result.confidence, 0.95)
XCTAssertTrue(mockSDK.captureCheckCalled)
}
func testLowConfidenceHandling() async {
// Arrange
let lowConfidenceResult = CheckResult.mock(confidence: 0.6)
mockSDK.captureResult = .success(lowConfidenceResult)
// Act & Assert
do {
_ = try await manager.startCheckCapture(from: MockViewController())
XCTFail("Should have thrown low confidence error")
} catch CheckValidationError.lowConfidence(let confidence) {
XCTAssertEqual(confidence, 0.6)
}
}
}
Integration Testing
// Android Integration Tests
@RunWith(AndroidJUnit4::class)
class CheckDepositIntegrationTest {
@get:Rule
val activityRule = ActivityScenarioRule(MainActivity::class.java)
private lateinit var mockWebServer: MockWebServer
private lateinit var checkDepositManager: CheckDepositManager
@Before
fun setUp() {
mockWebServer = MockWebServer()
mockWebServer.start()
// Configure SDK to use mock server
val config = TestSDKConfiguration.Builder()
.setBaseUrl(mockWebServer.url("/").toString())
.build()
checkDepositManager = CheckDepositManager(
InstrumentationRegistry.getInstrumentation().targetContext,
config
)
}
@Test
fun testCompleteCheckDepositFlow() = runBlocking {
// Mock successful API responses
mockWebServer.enqueue(MockResponse().setBody(successfulSessionResponse))
mockWebServer.enqueue(MockResponse().setBody(successfulDepositResponse))
// Execute test
activityRule.scenario.onActivity { activity ->
val result = runBlocking {
checkDepositManager.startCheckCapture(activity)
}
// Verify results
assertTrue(result.isSuccess)
assertEquals(2, mockWebServer.requestCount)
}
}
@After
fun tearDown() {
mockWebServer.shutdown()
}
}
Performance Optimization
Image Processing Optimization
// iOS: Optimize image processing
extension CheckDepositManager {
func optimizeImageForProcessing(_ image: UIImage) -> UIImage {
// Resize to optimal dimensions for OCR
let optimalSize = CGSize(width: 1600, height: 1200)
let resized = image.resized(to: optimalSize)
// Enhance for OCR accuracy
let enhanced = resized
.withContrast(1.2)
.withBrightness(0.1)
.withSharpness(1.1)
return enhanced
}
func compressImageForUpload(_ image: UIImage) -> Data? {
// Progressive JPEG compression
var compressionQuality: CGFloat = 0.9
var imageData = image.jpegData(compressionQuality: compressionQuality)
// Target max size: 2MB
let maxSize = 2 * 1024 * 1024
while let data = imageData, data.count > maxSize && compressionQuality > 0.1 {
compressionQuality -= 0.1
imageData = image.jpegData(compressionQuality: compressionQuality)
}
return imageData
}
}
Memory Management
// Android: Memory optimization
class ImageProcessor {
companion object {
private const val MAX_IMAGE_SIZE = 2048
private const val JPEG_QUALITY = 85
}
fun optimizeForProcessing(bitmap: Bitmap): Bitmap {
// Scale down large images
val scaledBitmap = if (bitmap.width > MAX_IMAGE_SIZE || bitmap.height > MAX_IMAGE_SIZE) {
val scale = maxOf(
bitmap.width.toFloat() / MAX_IMAGE_SIZE,
bitmap.height.toFloat() / MAX_IMAGE_SIZE
)
Bitmap.createScaledBitmap(
bitmap,
(bitmap.width / scale).toInt(),
(bitmap.height / scale).toInt(),
true
).also {
bitmap.recycle() // Free original bitmap
}
} else {
bitmap
}
return enhanceForOCR(scaledBitmap)
}
private fun enhanceForOCR(bitmap: Bitmap): Bitmap {
// Apply image enhancements using native code for performance
return nativeEnhanceImage(bitmap)
}
external fun nativeEnhanceImage(bitmap: Bitmap): Bitmap
}
Common Integration Pitfalls
1. Insufficient Error Handling
// β Poor error handling
try {
const result = await sdk.captureCheck();
processResult(result);
} catch (error) {
console.error(error); // Generic handling
}
// β
Comprehensive error handling
try {
const result = await sdk.captureCheck();
await processResult(result);
} catch (error) {
if (error instanceof NetworkError) {
handleNetworkError(error);
} else if (error instanceof PermissionError) {
handlePermissionError(error);
} else if (error instanceof ValidationError) {
handleValidationError(error);
} else {
handleUnexpectedError(error);
}
}
2. Memory Leaks
// β Potential memory leak
class CheckDepositViewController: UIViewController {
var checkManager: CheckDepositManager!
override func viewDidLoad() {
checkManager = CheckDepositManager()
checkManager.delegate = self // Strong reference cycle
}
}
// β
Proper memory management
class CheckDepositViewController: UIViewController {
var checkManager: CheckDepositManager!
override func viewDidLoad() {
checkManager = CheckDepositManager()
checkManager.delegate = self
}
deinit {
checkManager.delegate = nil
checkManager.cleanup()
}
}
3. Thread Safety Issues
// β UI updates on background thread
GlobalScope.launch {
val result = sdk.captureCheck()
// This will crash!
statusText.text = "Processing complete"
}
// β
Proper thread management
GlobalScope.launch {
val result = sdk.captureCheck()
withContext(Dispatchers.Main) {
statusText.text = "Processing complete"
}
}
Deployment Best Practices
Configuration Management
# config/check_processing.yml
development:
api_key: <%= ENV['CHECK_PROCESSING_DEV_KEY'] %>
environment: sandbox
debug_logging: true
timeout_seconds: 60
production:
api_key: <%= ENV['CHECK_PROCESSING_PROD_KEY'] %>
environment: production
debug_logging: false
timeout_seconds: 30
Monitoring and Analytics
// Comprehensive monitoring setup
const monitoringConfig = {
performance: {
trackInitializationTime: true,
trackCaptureTime: true,
trackProcessingTime: true,
trackMemoryUsage: true
},
business: {
trackSuccessRates: true,
trackUserFlows: true,
trackErrorRates: true,
trackAbandonmentPoints: true
},
technical: {
trackAPILatency: true,
trackCrashRates: true,
trackNetworkErrors: true,
trackDeviceCompatibility: true
}
};
Modern check processing SDKs should provide streamlined integration experiences with comprehensive documentation, robust error handling, and extensive testing capabilities. The investment in proper integration pays dividends in reduced maintenance burden and improved user experience.
Need technical guidance for your specific integration requirements? Our developer success team provides architecture reviews and integration support.