メインコンテンツへスキップ

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
  • サポート 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

要件

  • iOS 12.0 or later
  • Xcode 13.0 or later
  • Swift 5.5+ or Objective-C
  • CocoaPods 1.10+, Swift Package Manager, or Carthage

インストール

Add the iOS SDK to your project using Swift Package Manager:

  1. In Xcode, go to File > Add Packages
  2. Enter the repository URL:
    https://github.com/omise/omise-ios
  3. Select version 5.0.0 or later
  4. 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.

クイックスタート

1. Import the SDK

import OmiseSDK
// Objective-C
@import OmiseSDK;

2. Configure the Client

Initialize the SDK with your public key:

import OmiseSDK

class Paymentサービス {
let client: OmiseClient

init() {
// Initialize with public key
client = OmiseClient(publicKey: "pkey_test_5xyzyx5xyzyx5xyzyx5")
}
}
// Objective-C
@interface Paymentサービス : NSObject
@property (nonatomic, strong) OMSClient *client;
@end

@implementation Paymentサービス

- (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
}

// 使用方法
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)
}
}
}

// 使用方法
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];
}];

構成

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

インターネットバンキング

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)
}

// 使用方法
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)
}

// 使用方法
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)
}

// 使用方法
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)
}

// 使用方法
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 PaymentViewコントローラー: UIViewコントローラー {

func presentCreditCardForm() {
let publicKey = "pkey_test_5xyzyx5xyzyx5xyzyx5"

let creditCardFormコントローラー = CreditCardFormコントローラー.makeCreditCardFormViewコントローラー(
withPublicKey: publicKey
)

creditCardFormコントローラー.delegate = self
creditCardFormコントローラー.handleErrors = true

present(creditCardFormコントローラー, animated: true)
}
}

extension PaymentViewコントローラー: CreditCardFormDelegate {
func creditCardForm(_ controller: CreditCardFormコントローラー, didSucceedWithToken token: Token) {
dismiss(animated: true) {
print("Token created: \(token.id)")
self.sendTokenToServer(token.id)
}
}

func creditCardForm(_ controller: CreditCardFormコントローラー, didFailWithError error: Error) {
dismiss(animated: true) {
self.showError(error.localizedDescription)
}
}
}

Custom Payment Source Chooser

func presentPaymentMethodChooser() {
let capability = try await client.retrieveCapability()

let chooser = PaymentSourceChooserコントローラー.makePaymentChooserNavigationコントローラー(
publicKey: "pkey_test_5xyzyx5xyzyx5xyzyx5",
amount: 100000,
currency: .thb,
capability: capability
)

chooser.delegate = self
present(chooser, animated: true)
}

extension PaymentViewコントローラー: PaymentSourceChooserDelegate {
func paymentSourceChooser(
_ controller: PaymentSourceChooserコントローラー,
didCompleteWith token: Token
) {
dismiss(animated: true) {
self.sendTokenToServer(token.id)
}
}

func paymentSourceChooser(
_ controller: PaymentSourceChooserコントローラー,
didCompleteWith source: Source
) {
dismiss(animated: true) {
self.handlePaymentSource(source)
}
}

func paymentSourceChooser(
_ controller: PaymentSourceChooserコントローラー,
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 Safariサービスs

class PaymentViewコントローラー: UIViewコントローラー {

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 = SFSafariViewコントローラー(url: url)
safariVC.delegate = self
present(safariVC, animated: true) {
self.threeDSecureContinuation = continuation
}
}
}

private var threeDSecureContinuation: CheckedContinuation<Void, Never>?
}

extension PaymentViewコントローラー: SFSafariViewコントローラーDelegate {
func safariViewコントローラー(
_ controller: SFSafariViewコントローラー,
didCompleteInitialLoad didLoadSuccessfully: Bool
) {
guard didLoadSuccessfully else {
controller.dismiss(animated: true)
threeDSecureContinuation?.resume()
handleError(NSError(domain: "3DS", code: -1, userInfo: nil))
return
}
}

func safariViewコントローラーDidFinish(_ controller: SFSafariViewコントローラー) {
threeDSecureContinuation?.resume()
// Verify charge status on your server
Task {
await verifyChargeStatus()
}
}
}

エラーハンドリング

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 エラーハンドリング

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
}

ベストプラクティス

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 PaymentViewコントローラー: UIViewコントローラー {
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
}
}

テスト

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 テスト

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 テスト

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)
}
}

トラブルシューティング

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

デバッグモード

// 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:

  1. Minimum iOS version is now 12.0 (was 10.0)
  2. Minimum Swift version is 5.5 (was 5.0)
  3. OmiseSDKConfiguration renamed to OmiseClient.Configuration
  4. 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)

creditCardFormコントローラー.theme = theme

How do I handle App Store submission?

The SDK is App Store compliant. Make sure to:

  1. Use test keys during development
  2. Switch to production keys for release
  3. Don't log sensitive data in production
  4. 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.

関連リソース

次のステップ

  1. Set up your account to get your API keys
  2. Integrate on your server to create charges from tokens
  3. Test your integration with test cards
  4. Go live with production keys

サポート

Need help with the iOS SDK?