The Tap Card iOS SDK makes it quick and easy to build an excellent payment experience in your iOS app. We provide powerful and customizable UI screens and elements that can be used out-of-the-box to collect your users' payment details. We also expose the low-level APIs that power those UIs so that you can build fully custom experiences.
Learn about our Tap Identity Authentication to verify the identity of your users on iOS.
Get started with our documentation guide and example projects
Table of contents
- Features
- Card Scanning
- Installation
- Data Configuration
- Passing Configurations
- Add Card View
- Card View Delegate
- Tokenize
- Full code example
Simplified security: We make it simple for you to collect sensitive data such as credit card numbers and remain PCI compliant. This means the sensitive data is sent directly to Tap instead of passing through your server.
-
Drag and drop UI for card form collection.Â
-
Hide/Show supported card brands.
-
Hide/Show card scanning capability.
-
Pass a default card holder name.
-
Enable/Disable collection of card holder name.
-
Accept only Credit or Debit cards.
-
Define which card brands to be allowed.
-
Choose Enligsh and Arabic layouts.
-
Auto detect the device's display style supporting light and dark ones.
We provide low level apis, that correspond to objects and methods in the Tap API. You can build your own entirely custom UI on top of this layer, while still taking advantage of utilities like TapCardValidatorKit-iOS to validate your user’s input.
let validation = CardValidator.validate(cardNumber: "4242424242424242", preferredBrands: [.mada])
We support card scanning on iOS 13 and higher. The card scanner will try as accurately as possible to collect the card data to ease the process on the buyer. The scanner supports the following with maximum possible accuracy:
-
Printed cards.
-
Imposed cards.
-
Vertical cards.
You should make sure your app is 13.0+ and you have added the Privacy - Camera Usage Description in the info.plist file, as follows:
Using Cocoapods
add pod 'TapCardCheckOutKit' to your podfile
The public keys providede to your business from Tap integration team. They are used to correctly identify your identity as a Tap merchant. You can always test with out testing keys, but to get your own please Sign up
var publicKey:SecretKey = .init(sandbox: "pk_test_YhUjg9PNT8oDlKJ1aE2fMRz7", production: "sk_live_V4UDhitI0r7sFwHCfNB6xMKp")
CheckoutSecretKey* publicKey = [[CheckoutSecretKey alloc]initWithSandbox:@"pk_test_YhUjg9PNT8oDlKJ1aE2fMRz7" production:@"sk_live_V4UDhitI0r7sFwHCfNB6xMKp"];
An enum to define the required scope of the tap card sdk. Default is to generate Tap Token for a card. Keep an eye, while we add more scopes to our sdks.
var scope: Scope = .TapToken
Scope* scope = ScopeTapToken
A model that represents the amount and the currency combined, for your targeted transaction. Default is 1 KWD
var transcation: Transaction = .init(amount:Double = 1, currency: TapCurrencyCode = .KWD)
Transaction transaction = [[Transaction alloc]initWithAmount:1 currency:TapCurrencyCodeKWD];
A model that represents the reference to Tap order if needed
var order: Order = .init(identifier: "")
CheckoutOrder* order = [[CheckoutOrder alloc]initWithIdentifier:@""];
A model that represents the details and configurations related to the merchant. Including your merchant id provided by Tap integration team.
var merchant: Merchant = .init(id:"ID")
Merchant* merchant = [[Merchant alloc]initWithId:@"ID"];
Represents the model for the customer attached to the transaction if any.
// If you know the customer id
var customer:TapCustomer = TapCustomer(identifier: "Customer ID",
nameOnCard: "Cardholder name",
editable: true)
// If you want to create a new customer object
var customer: try! TapCustomer(emailAddress: .init(emailAddressString: "tap@company.com"),
phoneNumber: .init(isdNumber: "965", phoneNumber: "22922822"),
firstName: "First name", middleName: "Not needed",
lastName: "Last Name",
address: nil,
nameOnCard: "Customer's card holder name",
editable: true)
TapCustomer* customer = [[TapCustomer alloc]initWithEmailAddress:[[TapEmailAddress alloc] initWithEmailAddressString:@"tap@company.com"]
phoneNumber:[[TapPhone alloc]initWithIsdNumber:@"" phoneNumber:@"" error:nil]
firstName:@"First Name"
middleName:@"Not Needed"
lastName:@"Last Name"
address:nil
nameOnCard:@"Customer's card holder name"
editable:YES];
Parameter | Description | Required | Sample |
---|---|---|---|
identifier | Customer's tap identifier. You can get it through tap apis. Usually, you will get when the customer makes a transaction. | Required if you are not passing email or phone. SO whether you provide email or phone or identifier. | "CustomerID" |
emailAddress | The email address for the new customer. | Required if you are not passing an identifier or a phone. | .init( emailAddressString : "tap@company.com") |
phoneNumber | The phone number for the new customer. | Required if you are not passing an identifier or an email. | .init( isdNumber : "965" , phoneNumber : "22922822" ) |
firstName | The new customer's first name. | Required if you are creating a new customer. | "Name" |
middleName | The new customer's middle name. | Optional, default is "" | "Name" |
lastName | The new customer's last name | Optional, default is "" | "Name" |
address | The customer's address. | Optional. | let country:Country = try! .init(isoCode: "KW")<br/> let adddress:Address = .init(type:.commercial,<br/> country: country,<br/> line1: "8 mall",<br/> line2: "floor 6",<br/> line3: "Salem Al Mubarak St",<br/> city: "Salmiya",<br/> state: "Hawally",<br/> zipCode: "30003"<br/>) |
nameOnCard | If you want to fill the card holder name field in the card form. | Optional, Default is "" | "Card holder name" |
editable | If you want to make the card holder name editable or not. | Optional, Default is TRUE | "true" |
A model that decides the enablement of some of teh Tap provided features related to UI/UX
/// A model that decides the enablement of some of teh Tap provided features related to UI/UX
/// - Parameter acceptanceBadge : Decides whether to show/hide the the supported card brands bar underneath the card input form. Default is true
var features: Features = .init(acceptanceBadge: true)
Features* features = [[Features alloc] initWithAcceptanceBadge:YES];
Represents the details of the acceptance details, like payment methods, transaction's environment, card types, etc. Default is to accept all allowed payment methods activiated to your business from Tap integration team.
var acceptance: Acceptance = .init(supportedBrands: [.mada,.masterCard],
supportedFundSource: .All,
supportedPaymentAuthentications:
[.ThreeDS, .EMV],
sdkMode: .sandbox)
Acceptance* acceptance = [[Acceptance alloc]
initWithSupportedBrands:@[@(CardBrandMada),@(CardBrandMasterCard)]
supportedFundSource:All
supportedPaymentAuthentications:@[@(SupportedPaymentAuthenticationsThreeDS),@(SupportedPaymentAuthenticationsEMV)]
sdkMode:Sandbox];
Parameter | Description | Required | Sample |
---|---|---|---|
supportedBrands | The supported brands / payment methods. Default is All | NO | [.americanExpress, .mada, .masterCard, .omanNet, .visa, .meeza] |
supportedFundSource | The supported funding source for the card whether debit or credit. Default is All | NO | .All  or .Credit  or .Debit |
supportedPaymentAuthentications | The supported authentications for th card. Default is 3ds | NO | A combination of ThreeDS  & EMV |
sdkMode | The SDK mode you want to try your transactions with. Default is sandbox | Yes | Any of .sanbox  or .production |
A model that decides the visibilty of the card fields. For now, only Card name is adjustable.
/// - Parameter cardHolder: Decides whether to show/hide the card holder name.
/// Default is false
var fields: Fields = .init(cardHolder: true)
Fields* fields = [[Fields alloc] initWithCardHolder:YES];
A model that decides the visibilty of some componens related to the card sdk. So the merchant can adjust the UX as much as possible to fit your UI.
var addons: Addons = .init(loader: true,
displayCardScanning: true)
Addons* addons = [[Addons alloc]initWithLoader:YES
displayCardScanning:YES];
Parameter | Description | Required | Sample |
---|---|---|---|
loader | Decides whether to show/hide the loader on topp of the card, whever the card is doing some action (e.g. tokennizing a card.) Default is true | NO | true |
displayCardScanning | Decides whether or not to show the card scanning functionality. Default is true | NO | true |
A model of parameters that controls a bit the look and feel of the card sdk.
var interface: Interface = .init(locale: "en",
direction: .Dynamic,
edges: .Curved,
tapScannerUICustomization: .init(tapFullScreenScanBorderColor: .green,
blurCardScannerBackground: true),
powered: true)
Interface* interface = [[Interface alloc]initWithLocale:@"en"
direction:CardDirectionDynamic
edges:CardEdgesCurved
tapScannerUICustomization: nil
powered: YES];
Parameter | Description | Required | Sample |
---|---|---|---|
locale | Defines the locale to display the card with. accepted values en,ar and default is en | NO | en |
direction | Defines the direction/text alignment of the card input fields. Default is dynamic to follow the locale's alignment | NO | .LTR Â or .Dynamic |
edges | Defines the shape aof the card’s edge. Default is curved | NO | .curved  or .straight |
tapScannerUICustomization | The ui customization to the full screen scanner borer color and to show a blur. Default is green  & blur . |
NO | .init( tapFullScreenScanBorderColor : .green , <br/>blurCardScannerBackground : true ) |
powered | Display the powered by tap logo. Default is true | NO | true  or false |
###Â Pass configurations to Card SDK (2)
After collecting/defining the configurations, now it is time to pass these configurations to the Card sdk. Please mind the following:
-
Always run the configuration interface before you show the screen that has the card element.
-
This ensures the card element will look and behave as expected.
-
This ensures you validate the configurations before proceeding further with your checkout screen.
// Pass the configuration to the static card configuration.
// Ask the card to configure itself and listen to the call backs
TapCardForumConfiguration.shared.configure(dataConfig: cardDataConfig) {
// This means, the card is correctly configured and the
// transaction data you passed are valid.
// Now you can proceed to the screen that contains your card view.
} onErrorOccured: { error in
// This means, an error happened in the configurations you passed.
// For example, passing a currency/payment method not enabled to you as a
// merchant.
}
// Pass the configuration to the static card configuration.
// Ask the card to configure itself and listen to the call backs
[TapCardForumConfiguration.shared configureWithDataConfig:cardDataConfig onCardSdkReady:^{
// This means, the card is correctly configured and the
// transaction data you passed are valid.
// Now you can proceed to the screen that contains your card view.
} onErrorOccured:^(NSError * error) {
// This means, an error happened in the configurations you passed.
// For example, passing a currency/payment method not enabled to you as a
// merchant.
}];
You can add the TapCardView
as a drag drop into your UIViewController
in the Storyboard
.
-
Add a
UIView
. -
Set it to have edge to edge
width
. -
Set its
height
to158
with alow
priority. -
Change the
UIView
class in the inspector toTapCardView
& make sure Module field isTapCardCheckOutKit
-
Add an
IBOutlet
to yourUIViewController
for further access. -
@IBOutlet weak var tapCardView: TapCardView!
```
Now, we will have to set the delegate to the TapCardView
. This will help you listening and being notified about different events, that happens at run time during customer's interaction with the card sdk.
/**
- Parameter presentScannerInViewController: The UIViewController that will display the scanner into
- Parameter tapCardInputDelegate: A delegate listens for needed actions and callbacks
*/
tapCardView.setupCardForm(presentScannerInViewController: self, tapCardInputDelegate: self)
/**
- Parameter presentScannerInViewController: The UIViewController that will display the scanner into
- Parameter tapCardInputDelegate: A delegate listens for needed actions and callbacks
*/
[_tapCardView setupCardFormWithPresentScannerInViewController:self tapCardInputDelegate:self];
We assigned the controller to be the delegate. Let us see the two methods provided insde the delegate
Before we look into listening to the delegate methods, it is important to get used to two different important enums.
This enum will be passed in the errorOccured
delegate method.
Possible values:
-
Network
: This means, for some reaon, TAP servers are not reachable. So you may want to tell your customer to check his connection and try again in a while. -
InvalidCardType
: This means, your customer is trying to pay with unallowed brand/type other than the ones you allowed in your configurations . For example, we detected he is paying with aCredit
card while you only resitriced acceptance forDebit
. So it is the time you can show an educational message to your customer.
Enum defining different events passed from card kit. Will help you in controlling your UI based on the different statuses of the card element at run time, based on the customer's interaction.
Possible values:
-
CardNotReady
: Will be fired, when the card has no valid data. Meaning, you cannot tokenize the current data. This will be helpful for you in showing warning messages or to disable the checkout button. -
CardReady
: Will be fired, when the card has valid data. Meaning, you can tokenize the current data. This will be helpful for you to enable your checkout button for example. -
TokenizeStarted
: Will be fired, once the tokenization process started. It will help you in showing your own loader for example. -
TokenizeEnded
: Will be fired, once the tokenization process ended. You may proceed to checkout screen or remove your custom loader, etc.
extension YourViewContoller: TapCardInputDelegate {
/**
Will be called whenever an error occured during processing the transaction.
- Parameter error: The error type whether it is related to network or to card.
- Parameter message: A descriptive message to indicate what happened during the error
*/
func errorOccured(with error: CommonDataModelsKit_iOS.CardKitErrorType, message: String) {
// Based on the error, you may want to inform your customer and educate him
}
/**
Be updated by listening to events fired from the card kit
- Parameter with event: The event just fired
*/
func eventHappened(with event: CommonDataModelsKit_iOS.CardKitEventType) {
// Based on the event, you can adjust your UI like enable/disable
// The checkout button
}
}
@interface YourViewContoller () <TapCardInputDelegate>
/**
Will be called whenever an error occured during processing the transaction.
- Parameter error: The error type whether it is related to network or to card.
- Parameter message: A descriptive message to indicate what happened during the error
*/
- (void)errorOccuredWith:(enum CardKitErrorType)error message:(NSString * _Nonnull)message {
// Based on the error, you may want to inform your customer and educate him
}
/**
Be updated by listening to events fired from the card kit
- Parameter with event: The event just fired
*/
- (void)eventHappenedWith:(enum CardKitEventType)event {
// Based on the event, you can adjust your UI like enable/disable
// The checkout button
}
Now, when you recieve CardReady
event in the delegate , you may use the public interface provided by the Card sdk to generate a Tap token
for the card data. You may then use this token
to create a Charge or an Authorize.
// In your code
func tokenizeClicked() {
// Always as a defensive coding, make sure the card can start tokenization
// by using our validation interface
guard tapCardView.canProcessCard() else { return }
// This means, we can start the tokenization process
/**
Handles tokenizing the current card data.
- Parameter onTokenReady: A callback to listen when a token is
successfully generated
- Parameter onErrorOccured: A callback to listen when tokenization
fails with error message and the validity of all the card
fields for your own interest
*/
tapCardView.tokenizeCard { [weak self] token in
print(token.card)
self?.showAlert(title: "Tokenized", message: token.identifier)
} onErrorOccured: { [weak self] error, cardFieldsValidity in
print(error)
self?.showAlert(title: "Error", message: "\(error.localizedDescription)\nAlso, tap card indicated the validity of the fields as follows :\nNumber: \(cardFieldsValidity.cardNumberValidationStatus)\nExpiry: \(cardFieldsValidity.cardExpiryValidationStatus)\nCVV: \(cardFieldsValidity.cardCVVValidationStatus)\nName: \(cardFieldsValidity.cardNameValidationStatus)")
}
}
// Always as a defensive coding, make sure the card can start tokenization
// by using our validation interface
if([_tapCardInput canProcessCard]) {
// This means, we can start the tokenization process
/**
Handles tokenizing the current card data.
- Parameter onTokenReady: A callback to listen when a token is
successfully generated
- Parameter onErrorOccured: A callback to listen when tokenization
fails with error message and the validity of all the card
fields for your own interest
*/
[_tapCardInput tokenizeCardOnTokenReady:^(CheckoutToken * token) {
NSLog(@"%@",[token identifier]);
} onErrorOccured:^(NSError * error, CardFieldsValidity * cardFieldsValidity) {
[self showAlert:@"Error" message:[NSString stringWithFormat:@"%@\nAlso, tap card indicated the validity of the fields as follows :\nNumber: %i\nExpiry: %i\nCVV: %i\nName:%i",error.localizedDescription,cardFieldsValidity.cardNumberValidationStatus,cardFieldsValidity.cardExpiryValidationStatus,cardFieldsValidity.cardCVVValidationStatus,cardFieldsValidity.cardNameValidationStatus]];
}];
//
// DirectViewController.swift
// TapCardCheckoutExample
//
// Created by Osama Rabie on 29/07/2023.
//
import UIKit
import TapCardCheckOutKit
import CommonDataModelsKit_iOS
import LocalisationManagerKit_iOS
class DirectViewController: UIViewController {
/// The card view reference
var tapCardView:TapCardView?
/// The tokenize button
var tokenizeButton:UIButton = .init()
override func viewDidLoad() {
super.viewDidLoad()
TapLocalisationManager.shared.localisationLocale = "en"
view.backgroundColor = .amber
// Do any additional setup after loading the view.
// Setup the positiong of the card view,
// Or you can just use drag and drop features provided by the story board
setupTokenizeButton()
// will create the configuration object and call the Tap apis to configure & validate the card data
configureCardSDK()
}
/// will create the configuration object and call the Tap apis to configure & validate the card data
func configureCardSDK() {
// The card data configuration
let cardDataConfig:TapCardDataConfiguration = .init(publicKey: sharedConfigurationSharedManager.publicKey, scope: sharedConfigurationSharedManager.scope, transcation: sharedConfigurationSharedManager.transcation, merchant: sharedConfigurationSharedManager.merchant, customer: sharedConfigurationSharedManager.customer, acceptance: sharedConfigurationSharedManager.acceptance, fields: sharedConfigurationSharedManager.fields, addons: sharedConfigurationSharedManager.addons, interface: sharedConfigurationSharedManager.interface)
// let us use the public configure interface
TapCardForumConfiguration.shared.configure(dataConfig: cardDataConfig) {
DispatchQueue.main.async { [weak self] in
// This means, all went good! time to setup the card view
self?.view.isUserInteractionEnabled = true
self?.setupTapCardConstraints()
self?.setupCardView()
}
} onErrorOccured: { error in
DispatchQueue.main.async { [weak self] in
// This means, an error happened! Please check your integration
self?.view.isUserInteractionEnabled = true
let uiAlertController:UIAlertController = .init(title: "Error from middleware", message: error?.localizedDescription ?? "", preferredStyle: .actionSheet)
let uiAlertAction:UIAlertAction = .init(title: "Retry", style: .destructive) { _ in
self?.configureCardSDK()
}
uiAlertController.addAction(uiAlertAction)
self?.present(uiAlertController, animated: true)
}
}
}
/// Do any additional setup after loading the view.
/// Setup the positiong of the card view,
/// Or you can just use drag and drop features provided by the story board
func setupTapCardConstraints() {
tapCardView = .init()
view.addSubview(tapCardView!)
tapCardView?.translatesAutoresizingMaskIntoConstraints = false
// Set it to be edge to edge to the container view
NSLayoutConstraint(item: tapCardView!, attribute: NSLayoutConstraint.Attribute.leading, relatedBy: NSLayoutConstraint.Relation.equal, toItem: view, attribute: NSLayoutConstraint.Attribute.leading, multiplier: 1.0, constant: 0).isActive = true
NSLayoutConstraint(item: tapCardView!, attribute: NSLayoutConstraint.Attribute.trailing, relatedBy: NSLayoutConstraint.Relation.equal, toItem: view, attribute: NSLayoutConstraint.Attribute.trailing, multiplier: 1.0, constant: 0).isActive = true
NSLayoutConstraint(item: tapCardView!, attribute: NSLayoutConstraint.Attribute.top, relatedBy: NSLayoutConstraint.Relation.equal, toItem: view, attribute: NSLayoutConstraint.Attribute.top, multiplier: 1.0, constant: 50).isActive = true
// Set a low highet constraint
let heightConstraint = NSLayoutConstraint(item: tapCardView!, attribute: NSLayoutConstraint.Attribute.height, relatedBy: NSLayoutConstraint.Relation.equal, toItem: nil, attribute: NSLayoutConstraint.Attribute.notAnAttribute, multiplier: 1, constant: 158)
heightConstraint.isActive = true
heightConstraint.priority = .defaultLow
tapCardView?.layoutIfNeeded()
tapCardView?.updateConstraints()
}
func setupTokenizeButton() {
tokenizeButton.setTitle("Tokenize", for: .normal)
tokenizeButton.backgroundColor = .systemGray2
tokenizeButton.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(tokenizeButton)
// Set button constraints
NSLayoutConstraint(item: tokenizeButton, attribute: NSLayoutConstraint.Attribute.centerX, relatedBy: NSLayoutConstraint.Relation.equal, toItem: view, attribute: NSLayoutConstraint.Attribute.centerX, multiplier: 1.0, constant: 0).isActive = true
NSLayoutConstraint(item: tokenizeButton, attribute: NSLayoutConstraint.Attribute.centerY, relatedBy: NSLayoutConstraint.Relation.equal, toItem: view, attribute: NSLayoutConstraint.Attribute.centerY, multiplier: 1.0, constant: 0).isActive = true
NSLayoutConstraint(item: tokenizeButton, attribute: NSLayoutConstraint.Attribute.height, relatedBy: NSLayoutConstraint.Relation.equal, toItem: nil, attribute: NSLayoutConstraint.Attribute.notAnAttribute, multiplier: 1, constant: 50).isActive = true
NSLayoutConstraint(item: tokenizeButton, attribute: NSLayoutConstraint.Attribute.width, relatedBy: NSLayoutConstraint.Relation.equal, toItem: nil, attribute: NSLayoutConstraint.Attribute.notAnAttribute, multiplier: 1, constant: 150).isActive = true
tokenizeButton.layoutIfNeeded()
tokenizeButton.updateConstraints()
tokenizeButton.addTarget(self, action: #selector(buttonAction), for: .touchUpInside)
}
@objc func buttonAction(sender: UIButton!) {
// Always as a defensive coding, make sure the card can start tokenization
// by using our validation interface
guard tapCardView?.canProcessCard() ?? false else { return }
// This means, we can start the tokenization process
/**
Handles tokenizing the current card data.
- Parameter onTokenReady: A callback to listen when a token is
successfully generated
- Parameter onErrorOccured: A callback to listen when tokenization
fails with error message and the validity of all the card
fields for your own interest
*/
tapCardView?.tokenizeCard { token in
print(token.card)
} onErrorOccured: { error, cardFieldsValidity in
print(error)
print("\(error.localizedDescription)\nAlso, tap card indicated the validity of the fields as follows :\nNumber: \(cardFieldsValidity.cardNumberValidationStatus)\nExpiry: \(cardFieldsValidity.cardExpiryValidationStatus)\nCVV: \(cardFieldsValidity.cardCVVValidationStatus)\nName: \(cardFieldsValidity.cardNameValidationStatus)")
}
}
/// Will assign the delegate of the card view
func setupCardView() {
tapCardView?.setupCardForm(presentScannerInViewController: self, tapCardInputDelegate: self)
// Only make it visible after successful configuration
tapCardView?.isHidden = false
}
}
extension DirectViewController: TapCardInputDelegate {
func eventHappened(with event: CardKitEventType) {
print(event.description)
if event == .CardNotReady {
tokenizeButton.alpha = 0.5
tokenizeButton.isEnabled = false
}else if event == .CardReady {
tokenizeButton.alpha = 1
tokenizeButton.isEnabled = true
}
}
func errorOccured(with error: CardKitErrorType, message:String) {
print("\(error.description) with \(message)")
}
}