iOS SDK
The Omise iOS SDK provides a secure and convenient way to integrate payment processing into your iOS applications. Built for Swift and Objective-C, it handles tokenization of sensitive payment data and supports all major payment methods available in Southeast Asia.
Overviewโ
The iOS SDK enables you to:
- Tokenize credit cards securely without card data touching your servers
- Create payment sources for alternative payment methods
- Implement 3D Secure authentication flows
- Build custom payment forms with native iOS UI components
- Support biometric authentication with Touch ID and Face ID
- Handle errors gracefully with comprehensive error types
Key Featuresโ
- Native Swift API with async/await support
- Objective-C compatibility for legacy projects
- Pre-built UI components for rapid integration
- Comprehensive input validation
- Network resilience and automatic retries
- Full type safety with Swift generics
- SwiftUI and UIKit support
- Combine framework integration
Requirementsโ
- iOS 12.0 or later
- Xcode 13.0 or later
- Swift 5.5+ or Objective-C
- CocoaPods 1.10+, Swift Package Manager, or Carthage
Installationโ
Swift Package Manager (Recommended)โ
Add the iOS SDK to your project using Swift Package Manager:
- In Xcode, go to File > Add Packages
- Enter the repository URL:
https://github.com/omise/omise-ios - Select version 5.0.0 or later
- Add to your target
Or add it to your Package.swift:
dependencies: [
.package(url: "https://github.com/omise/omise-ios", from: "5.0.0")
]
CocoaPodsโ
Add the SDK to your Podfile:
platform :ios, '12.0'
use_frameworks!
target 'YourApp' do
pod 'OmiseSDK', '~> 5.0'
end
Then run:
pod install
Carthageโ
Add to your Cartfile:
github "omise/omise-ios" ~> 5.0
Run:
carthage update --use-xcframeworks
Then drag the built OmiseSDK.xcframework into your project.
Quick Startโ
1. Import the SDKโ
import OmiseSDK
// Objective-C
@import OmiseSDK;
2. Configure the Clientโ
Initialize the SDK with your public key:
import OmiseSDK
class PaymentService {
let client: OmiseClient
init() {
// Initialize with public key
client = OmiseClient(publicKey: "pkey_test_5xyzyx5xyzyx5xyzyx5")
}
}
// Objective-C
@interface PaymentService : NSObject
@property (nonatomic, strong) OMSClient *client;
@end
@implementation PaymentService
- (instancetype)init {
self = [super init];
if (self) {
_client = [[OMSClient alloc] initWithPublicKey:@"pkey_test_5xyzyx5xyzyx5xyzyx5"];
}
return self;
}
@end
3. Create a Tokenโ
Using Async/Await (Swift 5.5+)โ
func createToken() async throws -> Token {
let request = Token.CreateRequest(
name: "John Doe",
number: "4242424242424242",
expirationMonth: 12,
expirationYear: 2025,
securityCode: "123"
)
let token = try await client.send(request)
print("Token created: \(token.id)")
return token
}
// Usage
Task {
do {
let token = try await createToken()
// Send token to your server
await sendTokenToServer(token.id)
} catch {
print("Error: \(error.localizedDescription)")
}
}
Using Completion Handlersโ
func createToken(completion: @escaping (Result<Token, Error>) -> Void) {
let request = Token.CreateRequest(
name: "John Doe",
number: "4242424242424242",
expirationMonth: 12,
expirationYear: 2025,
securityCode: "123"
)
client.send(request) { result in
DispatchQueue.main.async {
completion(result)
}
}
}
// Usage
createToken { result in
switch result {
case .success(let token):
print("Token created: \(token.id)")
self.sendTokenToServer(token.id)
case .failure(let error):
print("Error: \(error.localizedDescription)")
}
}
Objective-Cโ
OMSTokenRequest *request = [[OMSTokenRequest alloc] init];
request.name = @"John Doe";
request.number = @"4242424242424242";
request.expirationMonth = 12;
request.expirationYear = 2025;
request.securityCode = @"123";
[self.client send:request callback:^(OMSToken * _Nullable token, NSError * _Nullable error) {
if (error) {
NSLog(@"Error: %@", error.localizedDescription);
return;
}
NSLog(@"Token created: %@", token.tokenId);
[self sendTokenToServer:token.tokenId];
}];
Configurationโ
Client Configurationโ
// Basic configuration
let client = OmiseClient(publicKey: "pkey_test_5xyzyx5xyzyx5xyzyx5")
// Advanced configuration
var config = OmiseClient.Configuration(publicKey: "pkey_test_5xyzyx5xyzyx5xyzyx5")
config.apiVersion = "2019-05-29"
config.timeout = 60.0
config.sessionConfiguration = .default
let client = OmiseClient(configuration: config)
Network Configurationโ
// Custom URLSession configuration
let sessionConfig = URLSessionConfiguration.default
sessionConfig.timeoutIntervalForRequest = 30
sessionConfig.timeoutIntervalForResource = 60
sessionConfig.requestCachePolicy = .reloadIgnoringLocalCacheData
var config = OmiseClient.Configuration(publicKey: "pkey_test_5xyzyx5xyzyx5xyzyx5")
config.sessionConfiguration = sessionConfig
let client = OmiseClient(configuration: config)
Creating Tokensโ
Basic Card Tokenizationโ
func tokenizeCard(
name: String,
number: String,
expirationMonth: Int,
expirationYear: Int,
securityCode: String
) async throws -> Token {
let request = Token.CreateRequest(
name: name,
number: number,
expirationMonth: expirationMonth,
expirationYear: expirationYear,
securityCode: securityCode
)
return try await client.send(request)
}
With Billing Addressโ
func tokenizeCardWithAddress() async throws -> Token {
let address = Token.Address(
street1: "123 Wireless Road",
street2: "Lumpini",
city: "Pathum Wan",
state: "Bangkok",
postalCode: "10330",
country: "TH"
)
let request = Token.CreateRequest(
name: "John Doe",
number: "4242424242424242",
expirationMonth: 12,
expirationYear: 2025,
securityCode: "123",
billingAddress: address
)
return try await client.send(request)
}
Validation Before Tokenizationโ
func validateAndTokenize(
number: String,
expirationMonth: Int,
expirationYear: Int,
securityCode: String
) async throws -> Token {
// Validate card number
guard CardValidator.isValid(number: number) else {
throw ValidationError.invalidCardNumber
}
// Validate expiry
guard CardValidator.isValid(expirationMonth: expirationMonth, year: expirationYear) else {
throw ValidationError.invalidExpiry
}
// Validate CVV
guard CardValidator.isValid(securityCode: securityCode) else {
throw ValidationError.invalidCVV
}
let request = Token.CreateRequest(
name: "John Doe",
number: number,
expirationMonth: expirationMonth,
expirationYear: expirationYear,
securityCode: securityCode
)
return try await client.send(request)
}
enum ValidationError: LocalizedError {
case invalidCardNumber
case invalidExpiry
case invalidCVV
var errorDescription: String? {
switch self {
case .invalidCardNumber:
return "Invalid card number"
case .invalidExpiry:
return "Invalid expiration date"
case .invalidCVV:
return "Invalid security code"
}
}
}
Creating Payment Sourcesโ
Internet Bankingโ
func createInternetBankingSource(amount: Int64, bank: InternetBanking) async throws -> Source {
let request = Source.CreateRequest(
amount: amount,
currency: .thb,
type: .internetBanking(bank)
)
return try await client.send(request)
}
// Usage
let source = try await createInternetBankingSource(
amount: 100000, // 1,000.00 THB
bank: .bay
)
// Redirect user to authorize payment
if let authorizeURL = source.authorizeURL {
UIApplication.shared.open(authorizeURL)
}
Mobile Bankingโ
func createMobileBankingSource(amount: Int64, bank: MobileBanking) async throws -> Source {
let request = Source.CreateRequest(
amount: amount,
currency: .thb,
type: .mobileBanking(bank)
)
return try await client.send(request)
}
// Usage
let source = try await createMobileBankingSource(
amount: 50000, // 500.00 THB
bank: .scb
)
PromptPayโ
func createPromptPaySource(amount: Int64) async throws -> Source {
let request = Source.CreateRequest(
amount: amount,
currency: .thb,
type: .promptPay
)
return try await client.send(request)
}
// Usage
let source = try await createPromptPaySource(amount: 100000)
// Display QR code to user
if let qrCodeURL = source.scanQRCodeURL {
displayQRCode(url: qrCodeURL)
}
TrueMoney Walletโ
func createTrueMoneySource(amount: Int64, phoneNumber: String) async throws -> Source {
let request = Source.CreateRequest(
amount: amount,
currency: .thb,
type: .trueMoney,
phoneNumber: phoneNumber
)
return try await client.send(request)
}
// Usage
let source = try await createTrueMoneySource(
amount: 100000,
phoneNumber: "0812345678"
)
Alipayโ
func createAlipaySource(amount: Int64) async throws -> Source {
let request = Source.CreateRequest(
amount: amount,
currency: .thb,
type: .alipay
)
return try await client.send(request)
}
UI Componentsโ
Built-in Credit Card Formโ
The SDK provides a pre-built credit card form:
import OmiseSDK
class PaymentViewController: UIViewController {
func presentCreditCardForm() {
let publicKey = "pkey_test_5xyzyx5xyzyx5xyzyx5"
let creditCardFormController = CreditCardFormController.makeCreditCardFormViewController(
withPublicKey: publicKey
)
creditCardFormController.delegate = self
creditCardFormController.handleErrors = true
present(creditCardFormController, animated: true)
}
}
extension PaymentViewController: CreditCardFormDelegate {
func creditCardForm(_ controller: CreditCardFormController, didSucceedWithToken token: Token) {
dismiss(animated: true) {
print("Token created: \(token.id)")
self.sendTokenToServer(token.id)
}
}
func creditCardForm(_ controller: CreditCardFormController, didFailWithError error: Error) {
dismiss(animated: true) {
self.showError(error.localizedDescription)
}
}
}
Custom Payment Source Chooserโ
func presentPaymentMethodChooser() {
let capability = try await client.retrieveCapability()
let chooser = PaymentSourceChooserController.makePaymentChooserNavigationController(
publicKey: "pkey_test_5xyzyx5xyzyx5xyzyx5",
amount: 100000,
currency: .thb,
capability: capability
)
chooser.delegate = self
present(chooser, animated: true)
}
extension PaymentViewController: PaymentSourceChooserDelegate {
func paymentSourceChooser(
_ controller: PaymentSourceChooserController,
didCompleteWith token: Token
) {
dismiss(animated: true) {
self.sendTokenToServer(token.id)
}
}
func paymentSourceChooser(
_ controller: PaymentSourceChooserController,
didCompleteWith source: Source
) {
dismiss(animated: true) {
self.handlePaymentSource(source)
}
}
func paymentSourceChooser(
_ controller: PaymentSourceChooserController,
didFailWithError error: Error
) {
dismiss(animated: true) {
self.showError(error.localizedDescription)
}
}
}
Input Validationโ
Card Number Validationโ
import OmiseSDK
// Validate card number format
let cardNumber = "4242424242424242"
let isValid = CardValidator.isValid(number: cardNumber)
// Detect card brand
let brand = CardBrand.detect(from: cardNumber)
switch brand {
case .visa:
print("Visa card")
case .masterCard:
print("Mastercard")
case .jcb:
print("JCB card")
default:
print("Unknown brand")
}
// Format card number with spaces
let formatted = CardNumber.format(cardNumber) // "4242 4242 4242 4242"
Expiry Date Validationโ
// Validate expiry date
let month = 12
let year = 2025
let isValid = CardValidator.isValid(expirationMonth: month, year: year)
// Check if card is expired
let isExpired = CardValidator.isExpired(month: month, year: year)
CVV Validationโ
// Validate CVV for card brand
let cvv = "123"
let cardBrand = CardBrand.visa
let isValid = CardValidator.isValid(securityCode: cvv, for: cardBrand)
Custom Validationโ
struct CardFormValidator {
static func validate(
number: String,
expirationMonth: Int,
expirationYear: Int,
securityCode: String,
name: String
) -> ValidationResult {
var errors: [String] = []
// Validate card number
if !CardValidator.isValid(number: number) {
errors.append("Invalid card number")
}
// Validate expiry
if !CardValidator.isValid(expirationMonth: expirationMonth, year: expirationYear) {
errors.append("Invalid or expired card")
}
// Validate CVV
let brand = CardBrand.detect(from: number)
if !CardValidator.isValid(securityCode: securityCode, for: brand) {
errors.append("Invalid security code")
}
// Validate name
if name.trimmingCharacters(in: .whitespaces).isEmpty {
errors.append("Cardholder name is required")
}
return errors.isEmpty ? .valid : .invalid(errors: errors)
}
}
enum ValidationResult {
case valid
case invalid(errors: [String])
}
3D Secure Authenticationโ
Handling 3D Secure Flowโ
import OmiseSDK
import SafariServices
class PaymentViewController: UIViewController {
func processPaymentWithToken(_ tokenId: String) async {
do {
// Create charge on your server
let charge = try await createChargeOnServer(tokenId: tokenId)
// Check if 3D Secure is required
if let authorizeURL = charge.authorizeURL {
await handle3DSecure(url: authorizeURL)
} else {
handleSuccessfulPayment(charge)
}
} catch {
handleError(error)
}
}
func handle3DSecure(url: URL) async {
await withCheckedContinuation { continuation in
let safariVC = SFSafariViewController(url: url)
safariVC.delegate = self
present(safariVC, animated: true) {
self.threeDSecureContinuation = continuation
}
}
}
private var threeDSecureContinuation: CheckedContinuation<Void, Never>?
}
extension PaymentViewController: SFSafariViewControllerDelegate {
func safariViewController(
_ controller: SFSafariViewController,
didCompleteInitialLoad didLoadSuccessfully: Bool
) {
guard didLoadSuccessfully else {
controller.dismiss(animated: true)
threeDSecureContinuation?.resume()
handleError(NSError(domain: "3DS", code: -1, userInfo: nil))
return
}
}
func safariViewControllerDidFinish(_ controller: SFSafariViewController) {
threeDSecureContinuation?.resume()
// Verify charge status on your server
Task {
await verifyChargeStatus()
}
}
}
Error Handlingโ
Error Typesโ
do {
let token = try await client.send(request)
} catch let error as OmiseError {
switch error {
case .api(let apiError):
// API error from server
print("API Error: \(apiError.message)")
handleAPIError(apiError)
case .network(let networkError):
// Network connectivity error
print("Network Error: \(networkError.localizedDescription)")
showNetworkError()
case .invalidRequest(let message):
// Invalid request parameters
print("Invalid Request: \(message)")
case .authenticationFailure:
// Invalid public key
print("Authentication failed - check your public key")
default:
print("Unknown error: \(error)")
}
} catch {
print("Unexpected error: \(error)")
}
API Error Handlingโ
func handleAPIError(_ error: APIError) {
switch error.code {
case "invalid_card":
showAlert(title: "Invalid Card", message: "Please check your card details")
case "insufficient_fund":
showAlert(title: "Insufficient Funds", message: "Your card has insufficient funds")
case "failed_processing":
showAlert(title: "Payment Failed", message: "Unable to process your payment")
case "invalid_security_code":
showAlert(title: "Invalid CVV", message: "Please check your security code")
default:
showAlert(title: "Error", message: error.message)
}
}
Retry Logicโ
func createTokenWithRetry(maxAttempts: Int = 3) async throws -> Token {
var lastError: Error?
for attempt in 1...maxAttempts {
do {
return try await createToken()
} catch {
lastError = error
// Don't retry on validation errors
if case OmiseError.invalidRequest = error {
throw error
}
// Wait before retrying
if attempt < maxAttempts {
try await Task.sleep(nanoseconds: UInt64(attempt) * 1_000_000_000)
}
}
}
throw lastError ?? OmiseError.unknown
}
Best Practicesโ
Securityโ
// โ
DO: Use test key for development
let testKey = "pkey_test_5xyzyx5xyzyx5xyzyx5"
// โ DON'T: Hardcode production keys
// let productionKey = "pkey_5xyzyx5xyzyx5xyzyx5"
// โ
DO: Load keys from configuration
let publicKey = Bundle.main.object(forInfoPlistKey: "OmisePublicKey") as? String
// โ DON'T: Log sensitive data
// print("Card number: \(cardNumber)")
// โ
DO: Use sanitized logging
print("Token created: \(token.id)")
Memory Managementโ
class PaymentManager {
private weak var client: OmiseClient?
private var currentTask: Task<Token, Error>?
func tokenizeCard() async throws -> Token {
// Cancel previous request
currentTask?.cancel()
let task = Task {
try await client?.send(request)
}
currentTask = task
return try await task.value
}
func cancelPendingRequests() {
currentTask?.cancel()
}
}
User Experienceโ
class PaymentViewController: UIViewController {
private let activityIndicator = UIActivityIndicatorView(style: .large)
func processPayment() {
showLoadingIndicator()
Task {
do {
let token = try await createToken()
hideLoadingIndicator()
await sendToServer(token)
} catch {
hideLoadingIndicator()
showError(error)
}
}
}
private func showLoadingIndicator() {
activityIndicator.startAnimating()
view.isUserInteractionEnabled = false
}
private func hideLoadingIndicator() {
activityIndicator.stopAnimating()
view.isUserInteractionEnabled = true
}
}
Testingโ
Test Modeโ
// Use test public key
let client = OmiseClient(publicKey: "pkey_test_5xyzyx5xyzyx5xyzyx5")
// Test card numbers
let testCards = [
"4242424242424242", // Successful
"4111111111111111", // Failed
"4000000000000002", // 3D Secure required
]
Unit Testingโ
import XCTest
@testable import YourApp
class PaymentTests: XCTestCase {
var client: OmiseClient!
override func setUp() {
super.setUp()
client = OmiseClient(publicKey: "pkey_test_5xyzyx5xyzyx5xyzyx5")
}
func testTokenCreation() async throws {
let request = Token.CreateRequest(
name: "Test User",
number: "4242424242424242",
expirationMonth: 12,
expirationYear: 2025,
securityCode: "123"
)
let token = try await client.send(request)
XCTAssertNotNil(token.id)
XCTAssertTrue(token.id.hasPrefix("tokn_test_"))
}
func testInvalidCardNumber() async {
let request = Token.CreateRequest(
name: "Test User",
number: "1234567890123456",
expirationMonth: 12,
expirationYear: 2025,
securityCode: "123"
)
do {
_ = try await client.send(request)
XCTFail("Should throw error for invalid card")
} catch {
XCTAssertNotNil(error)
}
}
}
UI Testingโ
class PaymentUITests: XCTestCase {
var app: XCUIApplication!
override func setUp() {
super.setUp()
app = XCUIApplication()
app.launch()
}
func testPaymentFlow() {
// Navigate to payment screen
app.buttons["Pay Now"].tap()
// Fill in card details
let cardNumberField = app.textFields["Card Number"]
cardNumberField.tap()
cardNumberField.typeText("4242424242424242")
let expiryField = app.textFields["Expiry"]
expiryField.tap()
expiryField.typeText("12/25")
let cvvField = app.secureTextFields["CVV"]
cvvField.tap()
cvvField.typeText("123")
// Submit
app.buttons["Submit"].tap()
// Verify success
XCTAssertTrue(app.alerts["Payment Successful"].exists)
}
}
Troubleshootingโ
Common Issuesโ
Issue: "Invalid public key" error
// Solution: Check your public key format
// Test keys: pkey_test_*
// Live keys: pkey_*
let client = OmiseClient(publicKey: "pkey_test_5xyzyx5xyzyx5xyzyx5")
Issue: Network timeout errors
// Solution: Increase timeout
var config = OmiseClient.Configuration(publicKey: "pkey_test_...")
config.timeout = 60.0
let client = OmiseClient(configuration: config)
Issue: Token creation fails silently
// Solution: Add proper error handling
do {
let token = try await client.send(request)
print("Success: \(token.id)")
} catch {
print("Error: \(error)")
// Add breakpoint here to debug
}
Issue: 3D Secure redirect not working
// Solution: Ensure Info.plist includes LSApplicationQueriesSchemes
// Add to Info.plist:
// <key>LSApplicationQueriesSchemes</key>
// <array>
// <string>https</string>
// </array>
Issue: Build errors after installation
# Solution: Clean build folder
# Xcode > Product > Clean Build Folder
# Or use command line:
rm -rf ~/Library/Developer/Xcode/DerivedData
Debug Modeโ
// Enable debug logging
#if DEBUG
client.enableDebugLogging = true
#endif
// This will log all network requests and responses
Migration Guideโ
Migrating from v4.x to v5.xโ
The v5.x release introduces async/await support and removes some deprecated APIs:
Before (v4.x):
client.send(request) { result in
switch result {
case .success(let token):
print(token.id)
case .failure(let error):
print(error)
}
}
After (v5.x):
// Option 1: Use async/await
let token = try await client.send(request)
// Option 2: Continue using completion handlers (still supported)
client.send(request) { result in
// Same as before
}
Breaking Changes:
- Minimum iOS version is now 12.0 (was 10.0)
- Minimum Swift version is 5.5 (was 5.0)
OmiseSDKConfigurationrenamed toOmiseClient.Configuration- Some deprecated payment method types removed
Frequently Asked Questionsโ
Can I use this SDK with SwiftUI?
Yes, the SDK works perfectly with SwiftUI. You can use the async/await APIs directly in your SwiftUI views and view models.
struct PaymentView: View {
@StateObject private var viewModel = PaymentViewModel()
var body: some View {
Button("Pay Now") {
Task {
await viewModel.processPayment()
}
}
}
}
Does the SDK support Combine?
While the SDK doesn't provide native Combine publishers, you can easily wrap the async/await APIs:
extension OmiseClient {
func sendPublisher(_ request: Token.CreateRequest) -> AnyPublisher<Token, Error> {
Future { promise in
Task {
do {
let token = try await self.send(request)
promise(.success(token))
} catch {
promise(.failure(error))
}
}
}
.eraseToAnyPublisher()
}
}
Can I customize the built-in UI components?
Yes, you can customize colors, fonts, and other appearance properties:
let theme = CreditCardFormTheme()
theme.primaryColor = .systemBlue
theme.errorColor = .systemRed
theme.font = UIFont.systemFont(ofSize: 16)
creditCardFormController.theme = theme
How do I handle App Store submission?
The SDK is App Store compliant. Make sure to:
- Use test keys during development
- Switch to production keys for release
- Don't log sensitive data in production
- Include privacy policy for payment processing
Can I save card details for future use?
The SDK creates tokens that can be saved to your server. Use the Customers API on your backend to attach tokens to customers for future charges.
Does the SDK support Apple Pay?
The SDK focuses on card tokenization. For Apple Pay, use PassKit framework and convert the payment token on your server.
What's the difference between a token and a source?
- Tokens represent credit/debit cards for immediate charging
- Sources represent alternative payment methods that require additional steps
How long are tokens valid?
Tokens expire after 30 minutes if not used. Once used to create a charge or attach to a customer, they're stored permanently.
Can I test with real card numbers?
No, always use test card numbers in test mode. Real cards will be declined in test mode.
Does the SDK work with Catalyst?
Yes, the SDK supports Mac Catalyst for iPad apps running on macOS.
Related Resourcesโ
- Mobile SDKs Overview - Compare all mobile SDKs
- Android SDK - Android development
- Charges API - Create charges from tokens
- Customers API - Save cards for future use
- GitHub Repository - Source code and examples
- API Reference - Complete API documentation
Next Stepsโ
- Set up your account to get your API keys
- Integrate on your server to create charges from tokens
- Test your integration with test cards
- Go live with production keys
Supportโ
Need help with the iOS SDK?
- GitHub Issues: github.com/omise/omise-ios/issues
- Email: support@omise.co
- Documentation: docs.omise.co