A secure, production-ready iOS library for payment card tokenization using Datacap's tokenization API. This library provides a streamlined integration path for iOS applications requiring PCI-compliant payment processing.
- Overview
- Architecture
- Requirements
- Installation
- Quick Start
- API Reference
- Security Considerations
- Testing
- Migration Guide
- Support
The Datacap Mobile Token iOS Library enables secure tokenization of payment card data through Datacap's certified tokenization infrastructure. This library abstracts the complexity of PCI compliance while providing flexible integration options for iOS developers.
- PCI Compliant: Reduces PCI DSS scope by tokenizing sensitive card data
- Dual Environment Support: Seamless switching between certification and production environments
- Built-in UI Components: Pre-built, customizable card entry interface
- Validation Engine: Comprehensive card validation including Luhn algorithm and BIN detection
- Modern Swift Implementation: Leverages Swift 5.0+ features including async/await
- Comprehensive Error Handling: Detailed error types for precise error management
- Universal Platform Support: Optimized for iPhone and iPad
sequenceDiagram
participant App as iOS Application
participant Lib as Token Library
participant UI as Card Entry UI
participant API as Datacap API
participant Server as Merchant Server
App->>Lib: Initialize with API Key
App->>Lib: Request Token
Lib->>UI: Present Card Entry
UI->>UI: User Enters Card Data
UI->>Lib: Card Data Collected
Lib->>Lib: Validate Card Data
Lib->>API: POST /v1/otu
API->>API: Generate Token
API->>Lib: Return Token
Lib->>App: Token Response
App->>Server: Send Token
Server->>Server: Process Payment
graph TB
subgraph "Public API"
A[DatacapTokenService] -->|delegates to| B[DatacapTokenServiceDelegate]
A -->|presents| C[DatacapTokenViewController]
end
subgraph "Core Components"
C -->|uses| D[CardValidator]
C -->|uses| E[CardFormatter]
A -->|creates| F[DatacapToken]
A -->|throws| G[DatacapTokenError]
end
subgraph "Models"
H[CardData] -->|validated by| D
F -->|contains| I[Token Response]
end
subgraph "Network Layer"
A -->|communicates with| J[Datacap API]
end
The library follows these architectural principles:
- Separation of Concerns: Clear distinction between UI, business logic, and networking
- Protocol-Oriented Design: Extensive use of protocols for testability and flexibility
- Fail-Safe Defaults: Secure defaults with explicit opt-in for less secure options
- Minimal Dependencies: Zero external dependencies for maximum compatibility
- iOS Deployment Target: iOS 13.0+
- Swift Version: Swift 5.0+
- Xcode Version: Xcode 13.0+
- Platform Support: iOS (iPhone, iPad)
Add the following dependency to your Package.swift
:
dependencies: [
.package(
url: "https://github.com/datacapsystems/Datacap-MobileToken-iOS-Library-2025.git",
from: "1.0.0"
)
]
For Xcode integration:
- Navigate to File → Add Package Dependencies
- Enter the repository URL:
https://github.com/datacapsystems/Datacap-MobileToken-iOS-Library-2025.git
- Select version rule: Up to Next Major Version with 1.0.0
- Click Add Package
- Download the repository
- Copy the
Sources/DatacapTokenLibrary
directory to your project - Add all Swift files to your target
For a complete, production-ready implementation, check out our demo app on the App Store:
- Repository: Datacap-MobileToken-iOS-2025
- App Store: Datacap Token
import DatacapTokenLibrary
class PaymentViewController: UIViewController {
private let tokenService = DatacapTokenService(
publicKey: "YOUR_PUBLIC_KEY",
isCertification: true
)
override func viewDidLoad() {
super.viewDidLoad()
tokenService.delegate = self
}
@IBAction func collectPaymentTapped(_ sender: UIButton) {
tokenService.requestToken(from: self)
}
}
extension PaymentViewController: DatacapTokenServiceDelegate {
func tokenRequestDidSucceed(_ token: DatacapToken) {
// Token generated successfully
processPayment(with: token.token)
}
func tokenRequestDidFail(error: DatacapTokenError) {
// Handle tokenization error
presentErrorAlert(error: error)
}
func tokenRequestDidCancel() {
// User cancelled the operation
}
}
For applications requiring custom card entry interfaces:
class CustomCheckoutViewController: UIViewController {
private let tokenService = DatacapTokenService(
publicKey: "YOUR_PUBLIC_KEY",
isCertification: false // Production environment
)
func processPayment() async {
do {
let cardData = CardData(
cardNumber: sanitizedCardNumber,
expirationMonth: expirationMonth,
expirationYear: expirationYear,
cvv: cvvCode
)
let token = try await tokenService.generateTokenDirect(for: cardData)
await submitPaymentToServer(token: token.token)
} catch let error as DatacapTokenError {
await handleTokenizationError(error)
} catch {
await handleGenericError(error)
}
}
}
The primary interface for token generation.
public init(publicKey: String, isCertification: Bool = true)
Parameters:
publicKey
: Your Datacap-issued public API keyisCertification
: Environment selector (true for certification, false for production)
public func requestToken(from viewController: UIViewController)
Presents the built-in card entry interface.
Parameters:
viewController
: The presenting view controller
public func generateTokenDirect(for cardData: CardData) async throws -> DatacapToken
Generates a token without presenting UI.
Parameters:
cardData
: Card information for tokenization
Returns: DatacapToken
containing the payment token and metadata
Throws: DatacapTokenError
for validation or tokenization failures
public struct DatacapToken {
public let token: String
public let maskedCardNumber: String
public let cardType: String
public let expirationDate: String
public let responseCode: String
public let responseMessage: String
public let timestamp: Date
}
public struct CardData {
public let cardNumber: String
public let expirationMonth: String
public let expirationYear: String
public let cvv: String
}
public enum DatacapTokenError: LocalizedError {
case invalidPublicKey
case invalidCardNumber
case invalidExpirationDate
case invalidCVV
case networkError(String)
case tokenizationFailed(String)
case userCancelled
case missingAPIConfiguration
}
public enum CardValidator {
static func validateCardNumber(_ number: String) -> Bool
static func detectCardType(_ number: String) -> String
static func maxLengthForCardType(_ cardType: String) -> Int
static func cvvLengthForCardType(_ cardType: String) -> Int
}
public enum CardFormatter {
static func formatCardNumber(_ text: String, cardType: String?) -> String
static func maskCardNumber(_ number: String) -> String
}
This library is designed to minimize PCI DSS scope by:
- No Card Data Storage: The library never persists card data
- Secure Transmission: All API communication uses TLS 1.2+
- Token-Only Response: Raw card data is never returned after tokenization
- Memory Management: Sensitive data is cleared from memory after use
-
API Key Management
- Store API keys in secure keychain, not in code
- Use separate keys for certification and production
- Rotate keys according to your security policy
-
Environment Configuration
- Always use production environment for live transactions
- Implement environment detection to prevent certification mode in production builds
-
Error Handling
- Never log full card numbers or CVV codes
- Sanitize error messages before displaying to users
- Implement rate limiting for failed tokenization attempts
-
Network Security
- Implement certificate pinning for additional security
- Monitor for man-in-the-middle attacks
- Use network security configuration to restrict cleartext traffic
// Secure API key retrieval
private func getAPIKey() -> String {
#if DEBUG
return ProcessInfo.processInfo.environment["DATACAP_TEST_KEY"] ?? ""
#else
return KeychainService.shared.retrieve(key: "datacap_api_key") ?? ""
#endif
}
// Environment-aware initialization
private lazy var tokenService: DatacapTokenService = {
let isProduction = Bundle.main.object(forInfoDictionaryKey: "IS_PRODUCTION") as? Bool ?? false
return DatacapTokenService(
publicKey: getAPIKey(),
isCertification: !isProduction
)
}()
We provide a command-line tool to verify your API keys are working correctly:
# 1. Edit test-api.swift and add your API keys
# 2. Run the test
./test-api.swift
# Or run with Swift directly
swift test-api.swift
The test tool will:
- Verify both certification and production API keys
- Generate test tokens using a test card number
- Show detailed response information
- Help diagnose any configuration issues
Important: Never commit the test-api.swift file with real API keys!
The library includes comprehensive unit tests. To run tests in your project:
import XCTest
@testable import DatacapTokenLibrary
class PaymentTokenTests: XCTestCase {
func testCardValidation() {
XCTAssertTrue(CardValidator.validateCardNumber("4111111111111111"))
XCTAssertFalse(CardValidator.validateCardNumber("1234567890123456"))
}
func testTokenGeneration() async throws {
let service = DatacapTokenService(publicKey: "demo", isCertification: true)
let cardData = CardData(
cardNumber: "4111111111111111",
expirationMonth: "12",
expirationYear: "25",
cvv: "123"
)
let token = try await service.generateTokenDirect(for: cardData)
XCTAssertTrue(token.token.hasPrefix("tok_demo_"))
}
}
For development and testing, use the demo mode:
let testService = DatacapTokenService(publicKey: "demo", isCertification: true)
Demo mode generates tokens with the format tok_demo_XXXXXXXXXXXXXXXX
without making API calls.
This guide provides step-by-step instructions for migrating from the legacy DatacapMobileToken.xcframework to the new DatacapTokenLibrary. The new library offers improved performance, modern Swift APIs, and enhanced security features while maintaining backward compatibility with your existing Datacap integration.
graph LR
A[Legacy Framework] -->|Step 1| B[Assess Current Integration]
B -->|Step 2| C[Remove Legacy Framework]
C -->|Step 3| D[Install New Library]
D -->|Step 4| E[Update Code]
E -->|Step 5| F[Test & Verify]
F -->|Step 6| G[Deploy]
Before beginning migration, ensure you have:
- Current API keys for both certification and production environments
- Access to your existing implementation code
- A test environment for validation
- Backup of your current working implementation
- Understanding of your current error handling and UI customization
Review your existing implementation to identify:
- Custom UI modifications
- Error handling logic
- API key management approach
- Environment switching mechanism
- Any framework-specific dependencies
-
Remove from Xcode Project
1. Select DatacapMobileToken.xcframework in Project Navigator 2. Press Delete key and choose "Move to Trash" 3. Clean build folder (Cmd+Shift+K)
-
Remove from Build Phases
1. Select your target in project settings 2. Go to "Build Phases" → "Link Binary With Libraries" 3. Remove DatacapMobileToken.xcframework if present 4. Go to "Build Phases" → "Embed Frameworks" 5. Remove DatacapMobileToken.xcframework if present
-
Clean up Framework Search Paths
1. Go to Build Settings 2. Search for "Framework Search Paths" 3. Remove any paths referencing the old framework
Follow the Installation instructions above using Swift Package Manager or manual installation.
// Legacy
import DatacapMobileToken
// New
import DatacapTokenLibrary
Legacy API | New API | Notes |
---|---|---|
DatacapTokenizer() |
DatacapTokenService(publicKey:isCertification:) |
Now requires API key at initialization |
tokenizer.publicKey = "KEY" |
Set in initializer | API key is now immutable |
tokenizer.environment = .production |
isCertification: false |
Simplified environment selection |
tokenizer.tokenize(cardData) |
generateTokenDirect(for:) |
Now uses async/await |
tokenizerDidSucceed(_:token:) |
tokenRequestDidSucceed(_:) |
Returns full token object |
tokenizerDidFail(_:error:) |
tokenRequestDidFail(error:) |
Enhanced error types |
Basic Integration - Legacy:
class PaymentViewController: UIViewController, DatacapTokenizerDelegate {
let tokenizer = DatacapTokenizer()
override func viewDidLoad() {
super.viewDidLoad()
tokenizer.delegate = self
tokenizer.publicKey = "YOUR_KEY"
tokenizer.environment = .certification
}
func processPayment() {
tokenizer.presentCardEntry(from: self)
}
// Delegate methods
func tokenizerDidSucceed(_ tokenizer: DatacapTokenizer, token: String) {
// Process token
}
func tokenizerDidFail(_ tokenizer: DatacapTokenizer, error: Error) {
// Handle error
}
func tokenizerDidCancel(_ tokenizer: DatacapTokenizer) {
// Handle cancellation
}
}
Basic Integration - New:
class PaymentViewController: UIViewController, DatacapTokenServiceDelegate {
let tokenService = DatacapTokenService(
publicKey: "YOUR_KEY",
isCertification: true
)
override func viewDidLoad() {
super.viewDidLoad()
tokenService.delegate = self
}
func processPayment() {
tokenService.requestToken(from: self)
}
// Delegate methods
func tokenRequestDidSucceed(_ token: DatacapToken) {
// Process token - note: now receives full token object
let tokenString = token.token
let maskedCard = token.maskedCardNumber
}
func tokenRequestDidFail(error: DatacapTokenError) {
// Handle error - note: specific error type
switch error {
case .invalidCardNumber:
// Handle card validation error
case .networkError(let message):
// Handle network error
default:
// Handle other errors
}
}
func tokenRequestDidCancel() {
// Handle cancellation
}
}
Custom UI Integration - Legacy:
func customTokenization() {
let cardData = [
"cardNumber": "4111111111111111",
"expirationMonth": "12",
"expirationYear": "25",
"cvv": "123"
]
tokenizer.tokenizeCard(cardData) { success, token, error in
if success {
// Use token
} else {
// Handle error
}
}
}
Custom UI Integration - New:
func customTokenization() async {
let cardData = CardData(
cardNumber: "4111111111111111",
expirationMonth: "12",
expirationYear: "25",
cvv: "123"
)
do {
let token = try await tokenService.generateTokenDirect(for: cardData)
// Use token.token
} catch let error as DatacapTokenError {
// Handle specific error
} catch {
// Handle unexpected error
}
}
-
Certification Environment Testing
- Verify successful tokenization with test card (4111111111111111)
- Confirm error handling for invalid cards
- Test user cancellation flow
- Validate UI appearance and behavior
- Check masked card number format
-
Production Environment Testing
- Switch to production environment
- Test with live API keys (use test cards only)
- Verify environment switching works correctly
- Confirm error messages are appropriate
-
Integration Testing
- Test payment flow end-to-end
- Verify token format compatibility with backend
- Check error handling and recovery
- Test on all supported iOS versions
- Validate on both iPhone and iPad
-
Performance Testing
- Compare tokenization speed
- Check memory usage
- Verify no memory leaks
func verifyMigration() async {
// Test both environments
let environments: [(String, Bool)] = [
("Certification", true),
("Production", false)
]
for (name, isCert) in environments {
print("Testing \(name) environment...")
let service = DatacapTokenService(
publicKey: isCert ? "CERT_KEY" : "PROD_KEY",
isCertification: isCert
)
let testCard = CardData(
cardNumber: "4111111111111111",
expirationMonth: "12",
expirationYear: "30",
cvv: "123"
)
do {
let token = try await service.generateTokenDirect(for: testCard)
print("✓ \(name): Token generated - \(token.token)")
print(" Masked: \(token.maskedCardNumber)")
print(" Type: \(token.cardType)")
} catch {
print("✗ \(name): Failed - \(error)")
}
}
}
-
Gradual Rollout Recommended
- Deploy to internal testing first
- Monitor for any issues
- Roll out to beta users
- Full production deployment
-
Monitor After Deployment
- Track tokenization success rates
- Monitor error rates
- Compare performance metrics
- Gather user feedback
Issue: "Unresolved identifier 'DatacapTokenizer'"
- Cause: Old import statement still present
- Solution: Ensure all imports are updated to
import DatacapTokenLibrary
Issue: Delegate methods not being called
- Cause: Protocol name or method signatures changed
- Solution: Update to
DatacapTokenServiceDelegate
and new method signatures
Issue: Async/await compilation errors
- Cause: Using completion handlers with new async API
- Solution: Update to async/await pattern or use delegate methods
Issue: Different token format
- Cause: The new library returns a
DatacapToken
object, not just a string - Solution: Access the token string via
token.token
property
If you encounter issues during migration:
- Review the example app for reference implementation
- Check the API Reference section for detailed documentation
- Contact support@datacapsystems.com with:
- Your migration stage
- Specific error messages
- Code samples (sanitized)
- Library versions (old and new)
- Integration Support: support@datacapsystems.com
Report issues via GitHub Issues: https://github.com/datacapsystems/Datacap-MobileToken-iOS-Library-2025/issues
Include the following information:
- Library version
- iOS version
- Device model
- Steps to reproduce
- Error messages
- Code samples (sanitized)
- 1.0.0 (2025-01-01): Initial release
- Core tokenization functionality
- Built-in UI components
- Comprehensive validation
- Full test coverage
Issue: "No such module 'DatacapTokenLibrary'"
- Solution: Ensure the package is properly added via Swift Package Manager. Try File → Packages → Reset Package Caches in Xcode.
Issue: "Module compiled with Swift X.X cannot be imported by Swift Y.Y"
- Solution: Update to the latest version of the library that matches your Swift version.
Issue: "Invalid public key" error
- Solution:
- Verify your API key is correct and active
- Ensure you're using the correct environment (certification vs production)
- Check that the key hasn't been revoked or expired
Issue: Network timeout errors
- Solution:
- Check internet connectivity
- Verify firewall settings allow HTTPS to
*.dcap.com
- Implement retry logic with exponential backoff
Issue: "Tokenization failed" with valid card data
- Solution:
- Verify the test card (4111111111111111) works in certification mode
- Check API response for specific error messages
- Ensure expiration date is in the future
Issue: Card entry screen appears blank or crashes
- Solution:
- Verify iOS deployment target is 13.0+
- Check that the view controller is presented from main thread
- Ensure proper memory management of delegate references
Issue: Keyboard doesn't appear for card entry
- Solution:
- Check that the device/simulator has keyboard enabled
- Verify no other text field has first responder status
Enable verbose logging for troubleshooting:
// Add before initializing the service
UserDefaults.standard.set(true, forKey: "DatacapTokenLibraryDebugMode")
Copyright 2025 Datacap Systems, Inc. All rights reserved.
This library is distributed under the MIT License. See the LICENSE file for details.