This repository provides step by step documentation for the integration of JPS's native iOS SDK to drive our proprietary YT-Touch terminal to accept credit and debit card payments (incl. VISA, MasterCard, American Express and more).
For the Android version of the SDK, please refer to the Android documentation
The interactions between the mobile device and the card terminal is a Master-Slave relation in which the mobile device drives the card terminal by calling the various terminal commands. The SDK provides payment, update, and log APIs. The main purpose of the SDK is to send RPC commands to the card terminal to sequence operations.
The SDK includes the following modules: Connection, RPC, MDM, Payment, Log.
- The connection module provides an 'IconnectionManager' interface, so you can use your implementation, and provides a Bluetooth Low Energy (BLE) implementaion.
- The RPC module uses the IconnectionManager implementation to send RPC commands and receive responses from the card terminal. It provides an implementation of all the device RPCs.
- The MDM module is an implementation of all JPS's TMS services. The TMS server is mainly used to manage the firmware updates and ICC / NFC parameter configurations of the terminal. The SDK allows transparent update of the card terminal when using our TMS. This module is not required if you choose to use your own TMS.
- The payment module implements the transaction processing for contact and contactless payments. For each transaction, a UCubePaymentRequest instance must be provided as input to configure the current operation. A callback is called at every progress step of the transaction, and a PaymentContext instance is returned when the transaction is complete and contains all the necessary data to record the transaction.
- The SDK provides an ILogger interface and a default implementation to manage logs. Your application can choose between using the default implementation, which prints the logs in an ainternal storage log file that can be then extract or sent online. Of course you can use your Ilogger implementation.
All SDK features are gathered in a single Class: UCubeAPI. This class provides public static methods that your application can use to setup ConnectionManager, setup Logger, perform a payment, perform an update using our TMS...etc.
The SDK does not save any connection or transaction or update data.
For more information about JPS products, please refer to our www.youtransactor.com.
This documentation describes the iOS SDK implementation. The iOS SDK manages the Payment Transaction (EMV Contact, EMV CLess) by driving the YT-Touch. It provides all required EMV data to the payment application in order to do the authorization and transaction completion. The SDK provides remote update APIs to download and install the Firmware and the EMV configuration. The connected TMS server is JPS's TMS backend.
This document presents the iOS SDK architecture, describes the transaction flow, details how the SDK can be integrated to an iOS payment application, and provides sample codes.
The YT-Touch is a lightweight and compact payment dongle. It can turn a tablet or a mobile device, Android or iOS, into a point of sale via a BLE connection to enable acceptance of contact and contactless payment cards.
The mobile device can be either Android or iOS and typically hosts applications related to payment. It links the card terminal to the rest of the system.
The mobile device application consists of 2 modules:
- Business module
- Application that meets the business needs of the end customer. This is for example a cashier application in the case of a restaurant, or a control application in the case of transports.
- Payment Module
- Drives the transaction
- Responsible for device software/configurations updates
The business module on the mobile device is developed by you. It uses the mobile device's user interface to fulfill the business needs of the customer.
The Payment module integrates our SDK, delivered as a library, to create the payment application.
The management system can be administered by JPS and offers the following services:
- Management of the uCube fleet
- Deployment of software updates
- Deployment of payment parameters
- Other services
The MDM module of SDK implements all our management system services and the UCubeAPI provides methods to call these features.
To be functional, in the scope of PCI PTS requirement, SRED & Pin keys shall be loaded securely in the device. During the personalisation process JPS tools inject the certification chain. Afterwards, SRED & Pin keys can be loaded locally Or remotely using JPS Tools.
The YT-Touch lights up by pressing the "ON / OFF" button or when a BLE connection is established. Once the device is on, the payment process can be initiated. The YT-Touch switches off either by pressing the "ON / OFF" button or after X minutes of inactivity (X = OFF timeout).
The terminal firmware can be updated remotely to add bug fixes, evolutions... Contact and contactless configuration can also be updated remotely.
The Terminal's documentation describe how these updates can be performed and what RPC commands to use.
If you choose to use our TMS, this can be done transparently by first calling the mdmCheckUpdate
method to get the TMS configuration, compare it with current device versions, and then use the mdmUpdate
to download & intall the binary update.
The SDK prints logs in logcat at runtime. The log module uses a default ILogger implementation that prints these logs in a file which can then be uploaded to a remote server.
Our TMS provides a Web Service that accepts a zip of log files. You can choose to setup the log module to use our default implementation or implement your own.
The diagram below describes the SDK's architecture. The Application has access to the Payment, MDM and connection modules using the uCubeAPI interface. The RPC module is public so the application has a direct access to it.
The Deployment Target
of the SDK is iOS 10.0
.
Your Xcode project deployment target must be iOS 10.0 or later.
UCube is available through CocoaPods. To install it, simply add the following line to your Podfile:
pod 'UCube', :git => 'https://github.com/YouTransactor/mPOS-SDK-IOS-Framework'
You can import it like this:
import UCube
The APIs provided by UCubeAPI are:
getVersionName()
setConnexionManager(_ connexionManager: ConnexionManager)
setupLogger(_ logger: Loggable)
pay(paymentRequest: UCubePaymentRequest,
didProgress: PaymentProgressClosure? = nil,
didFinish: PaymentFinishClosure)
setLocale(locale : String, didProgress: ProgressTaskClosure? = nil, didFinish: FinishTaskClosure? = nil)
geLocale(didProgress: ProgressTaskClosure? = nil, didFinish: FinishTaskClosure? = nil)
getSupportedLocaleList(didProgress: ProgressTaskClosure? = nil, didFinish: FinishTaskClosure? = nil)
sendData(commandID: UInt16,
outputSecurityMode: SecurityMode,
inputSecurityMode: SecurityMode,
payload: Data,
didProgress: SendDataProgressClosure? = nil,
didFinish: SendDataFinishClosure? = nil)
close()
/* TMS APIs */
mdmSetup()
mdmRegister(didProgress: ProgressClosure? = nil, didFinish: FinishClosure? = nil)
mdmRegister(didProgress: ProgressClosure? = nil, didFinish: FinishClosure? = nil)
mdmUnregister()
isMdmManagerReady() -> Bool
mdmCheckUpdate(forceUpdate: Bool,
checkOnlyFirmwareVersion: Bool,
didProgress: ProgressClosure? = nil,
didFinish: FinishClosure? = nil)
mdmUpdate(updates: [BinaryUpdate],
didProgress: ProgressClosure? = nil,
didFinish: FinishClosure? = nil)
mdmSendLogs(didProgress: ProgressClosure? = nil, didFinish: FinishClosure? = nil)
- You can use the sample app provided in this repository as a reference
The ConnectionManager protocol :
public protocol ConnectionManager {
var isConnected: Bool { get }
func setDevice(_ device: UCubeDevice)
func setDevice(identifier: String, completion: ((_ device: UCubeDevice?) -> Void)? = nil)
func getDevice() -> UCubeDevice?
func connect(completion: @escaping ConnectionCompletion)
func disconnect(completion: @escaping DisconnectionCompletion)
func send(_ data: Data, completion: @escaping SendCommandCompletion)
}
-
First you should set
Privacy - Bluetooth Always Usage Description
in Info.plist. This is required on iOS 13 or later in order to ask user permission for Bluetooth usage. -
Second you should select the device that you want to communicate with.
-
You can use the default connection manager
BLEConnectionManager.shared
-
BLEConnectionManager
provides astartScan()
&stopScan()
methods which allow you to start and stop BLE scan.
In the SampleApp an example of device selection using these methods is provided. -
BLEConnectionManager
also provides ascanDelegate
property in order to send scan events. You need to conform toScanDelegate
protocol.
BLEConnectionManager.shared.scanDelegate = self BLEConnectionManager.shared.startScan() [...] func scanDidDiscoverDevice(_ device: UCubeDevice) { LogManager.debug(message: "Discovered device \(device.name)") BLEConnectionManager.shared.setDevice(device) }
-
-
You can set your own instance of
BLEConnectionManager
or a different connection manager usingUCubeAPI.setConnectionManager
.
let bleConnectionManager = BLEConnectionManager()
UCubeAPI.setConnectionManager(bleConnectionManager)
let customConnectionManager = CustomConnectionManager()
UCubeAPI.setConnectionManager(customConnectionManager)
CustomConnectionManager
needs to conform to ConnectionManager
protocol.
You can retrieve a previously scanned device without performing a BLE scan.
You have to save the identifier of the device UCubeDevice.identifier
.
BLEConnectionManager.shared.setDevice(identifier: identifier) { device in
if device == nil {
LogManager.debug(message: "No device found with identifier: \(identifier)")
}
}
The Loggable protocol :
public protocol Loggable {
func debug(message: String, filePath: String, functionName: String)
func error(message: String, error: Error?, filePath: String, functionName: String)
}
To setup the log module you should put this instructions below in your AppDelegate or main ViewController.
// If you want to use your Loggable implementation
UCubeAPI.setupLogger(MyLogger())
The default Logger
is set by default.
purchase
withdrawal
refund
purchaseCashback
manualCash
inquiry
UCubeAPI.pay(
paymentRequest: paymentRequest,
didProgress: { state: ServiceState in
// your code here
},
didFinish: { (success: Bool, paymentContext: PaymentContext) in
// your code here
}
)
// non optional variables
var paymentRequest = UCubePaymentRequest(amount: amountValue, currency: currency, transactionType: transactionType, readers: readers, authorizationTask: AuthorizationTask(presenter: self), preferredLanguages: ["en"] )
// optional variables
paymentRequest.cardWaitTimeout = cardWaitTimeout
paymentRequest.forceDebug = false
paymentRequest.transactionDate = Date()
paymentRequest.forceAuthorization = forceAuthorizationSwitch.isOn
paymentRequest.forceOnlinePIN = forceOnlinePinSwitch.isOn
paymentRequest.authorizationPlainTags = [
RPC.EMVTag.TAG_4F_APPLICATION_IDENTIFIER,
RPC.EMVTag.TAG_50_APPLICATION_LABEL,
RPC.EMVTag.TAG_5F2A_TRANSACTION_CURRENCY_CODE,
RPC.EMVTag.TAG_5F34_APPLICATION_PRIMARY_ACCOUNT_NUMBER_SEQUENCE_NUMBE
]
paymentRequest.authorizationSecuredTags = [
RPC.EMVTag.TAG_SECURE_5A_APPLICATION_PRIMARY_ACCOUNT_NUMBER,
RPC.EMVTag.TAG_SECURE_57_TRACK_2_EQUIVALENT_DATA,
RPC.EMVTag.TAG_SECURE_56_TRACK_1_DATA,
RPC.EMVTag.TAG_SECURE_5F20_CARDHOLDER_NAME
]
paymentRequest.finalizationPlainTags = [
RPC.EMVTag.TAG_8E_CARDHOLDER_VERIFICATION_METHOD_LIST,
RPC.EMVTag.TAG_95_TERMINAL_VERIFICATION_RESULTS,
RPC.EMVTag.TAG_9B_TRANSACTION_STATUS_INFORMATION,
RPC.EMVTag.TAG_99_TRANSACTION_PERSONAL_IDENTIFICATION_NUMBER_DATA
]
paymentRequest.finalizationSecuredTags = [
RPC.EMVTag.TAG_SECURE_5F24_APPLICATION_EXPIRATION_DATE,
RPC.EMVTag.TAG_SECURE_5F30_SERVICE_CODE
]
paymentRequest.riskManagementTask = RiskManagementTask(presenter: self)
/* input common */
public var cardWaitTimeout : TimeInterval = 30
public var amount: UInt64 = 0
public var currency: Currency = Currency(label: "EUR", code: 978, exponent: 2)
public var transactionType: TransactionType?
public var transactionDate: Date?
public var applicationVersion: Int?
public var preferredLanguages: [String]?
public var forceOnlinePIN: Bool = false
private var forceAuthorization: Bool = false
public var onlinePinBlockFormat: UInt8 = RPC.PIN.blockISO9564Format0
public var readers: [CardEntryMode] = [.ICC, .NFC]
public var forceDebug: Bool = false
/* input NFC & ICC */
public var authorizationPlainTags: Set<Int>?
public var authorizationSecuredTags: Set<Int>?
public var finalizationPlainTags: Set<Int>?
public var finalizationSecuredTags: Set<Int>?
/* output common */
public var paymentStatus: PaymentStatus? // END status*/
public var uCubeInfo: Data?
public var sredKsn: Data?
public var pinKsn: Data?
public var cardEntryMode: CardEntryMode?
public var onlinePinBlock: Data?
public var finalizationPlainTagsValues: [Int: Data]?
public var authorizationPlainTagsValues: [Int: Data]?
public var finalizationSecuredTagsValues: Data?
public var authorizationSecuredTagsValues: Data?
public var authorizationResponse: Data? //0x8A
/* output nfc */
public var nfcOutcome: Data?
public var signatureRequired: Bool = false
/* output icc */
public var selectedApplication: EMVApplicationDescriptor?
private var tvr: Data = Data(repeating: 0, count: 5)
public var transactionFinalisationData: Data?
public var transactionInitData: Data?
public var transactionProcessData: Data?
/* output for debug */
public var tagCC: Data? // svpp logs level 2 tag CC
public var tagF4: Data? // svpp logs level 2 tag F4
public var tagF5: Data? // svpp logs level 2 tag F5
// Common states
startCancelAll
startExitSecureSession
getInfo
enterSecureSession
ksnAvailable
startTransaction
cardReadEnd
// Authorization
authorization
// End
getFinalizationSecuredTags
getFinalizationPlainTags
getCCL2Log
getF4L2Log
getF5L2Log
endExitSecureSession
// SMC states
smcBuildCandidateList
smcSelectApplication
smcUserSelectApplication
smcInitTransaction
smcRiskManagement
smcProcessTransaction
smcGetAuthorizationSecuredTags
smcGetAuthorizationPlainTags
smcFinalizeTransaction
smcRemoveCard
// NFC states
nfcGetAuthorizationSecuredTags
nfcGetAuthorizationPlainTags
nfcSimplifiedOnlinePin
nfcCompleteTransaction
The EMV payment state machine is sequence of executing commands and tasks. Bellow you will see the different tasks used at transaction
Durring the payment process the payment state machine will be interrupted to execute some tasks that you implement.
public class EMVApplicationSelectionTask: ApplicationSelectionTasking {
private var applications: [EMVApplicationDescriptor]?
private var candidates: [EMVApplicationDescriptor]?
private var paymentContext: PaymentContext?
public func setAvailableApplications(_ applications: [EMVApplicationDescriptor]) {
self.applications = applications;
}
public func setPaymentContext(_ paymentContext: PaymentContext) {
self.paymentContect = paymentContext;
}
public func getSelection() -> [EMVApplicationDescriptor] {
return candidates
}
public func getContext() -> PaymentContext? {
return context
}
public func setContext(_ context: PaymentContext) {
self.context = context
}
@Override
public func execute(monitor: TaskMonitoring) {
var candidates: [EMVApplicationDescriptor] = []
// Todo do AID selection
monitor.eventHandler(.success, []) // should call this to return to the payment state machine
}
@Override
public func cancel(completion: (Bool) -> Void) {
monitor?.eventHandler(.cancelled, [])
completion(true)
}
}
class RiskManagementTask: RiskManagementTasking {
private var tvr: Data = Data([0, 0, 0, 0, 0])
private var paymentContext: PaymentContext?
func getTVR() -> Data {
return tvr
}
func getContext() -> PaymentContext? {
return paymentContext
}
func setContext(_ context: PaymentContext) {
self.paymentContext = context
}
public func setPaymentContext(_ paymentContext: PaymentContext) {
self.paymentContext = paymentContext;
}
public func execute(monitor: TaskMonitoring) {
// TODO: perform risk management
monitor.eventHandler(.success); // should call this to return to the payment state machine
}
@Override
public func cancel(completion: (Bool) -> Void) {
monitor?.eventHandler(.cancelled, [])
completion(true)
}
}
class AuthorizationTask: AuthorizationTasking {
private var authorizationResponse: Data = Data([0x8A, 0x02, 0x30, 0x30]) // Approved
private var paymentContext: PaymentContext?
func getAuthorizationResponse() -> Data {
return authorizationResponse
}
func getContext() -> PaymentContext? {
return paymentContext
}
func setContext(_ context: PaymentContext) {
self.paymentContext = context
}
public func execute(monitor: TaskMonitoring) {
// TODO: perform authorization
monitor.eventHandler(.success); // should call this to return to the payment state machine
}
@Override
public func cancel(completion: (Bool) -> Void) {
monitor?.eventHandler(.cancelled, [])
completion(true)
}
}
approved, // Transaction has been approved by terminal
declined, // Transaction has been declined by terminal
/* Cancelled Status cases:
1/ GPO not read yet and application calls payment.cancel()
2/ one of commands returns -32 or -28 status
3/ NFC_Outcome[1] = 0x3A Transaction_cancelled
*/
cancelled,
cardWaitFailed,//Transaction has been failed because customer does not present a card and startNFCTransaction fail
unsupportedCard, ///Transaction has been failed: Error returned by terminal, at contact transaction, when no application match between card and terminal's configuration
nfcOutcomeTryOtherInterface, // Transaction has been failed: Error returned by terminal, at contactless transaction
nfcOutcomeEndApplication,// Transaction has been failed: Error returned by terminal, at contactless transaction
nfcOutcomeFailed,// Transaction has been failed: Error returned by terminal, at contactless transaction
error, // Transaction has been failed : when one of the tasks or commands has been fail
errorDisconnect,//Transaction has been failed : when there is a disconnect during the transaction
errorShuttingDown,//Transaction has been failed : when command fails with SHUTTING_DOWN error during the transaction
errorWrongActivatedReader, // Transaction has been failed : when terminal return wrong value in the tag DF70 at startNFCTransaction
errorMissingRequiredCryptogram,// Transaction has been failed :when the value of the tag 9f27 is wrong
errorWrongCryptogramValue, // Transaction has been failed : when in the response of the transaction process command the tag 9F27 is missing
errorWrongNfcOutcome, // Transaction has been failed : when terminal returns wrong values in the nfc outcome byte array
}
During the transaction, Customer may need to cancel payment. This is only possible before terminal reads card with success, in other words the GPO of card was successfully read. The cancel method returns a callback with status of cancellation. Here is a figure that resume the two kind of states, blue ones the cancellation is possoble the red ones the cancellation not possible. Note that at the end of startTransaction state, if the reader interface was NFC, so the card was successfully read. The startTransaction step do the wait card and the read card for contactless and only the wait card for contact.
emvPaystateMachine = UCubeAPI.pay(...);
....
emvPaystateMachine.cancel{ (status) in
//TODO : do stuff here
}
The main function of MDM module is the update of firmware and configurations of the terminal.
The terminal have to be registred on the TMS server using this code below:
UCubeAPI.mdmRegister(
didProgress: { state: ServiceState in
// your code here
},
didFinish: { (success: Bool, parameters: [Any]?) in
// your code here
}
)
At the register process the SDK send the public certificate of terminal to the TMS, so the server can verifie the JPS signature and then generate and return an SSL certificate unique by terminal. This SSL certificate is used to call the rest of web services. Note that the register should be done only once, at the selection of terminal. the SDK save the SSL certificate and to be removed you have to call this method below.
let success: Bool = UCubeAPI.mdmUnregister()
if(!success) {
LogManager.error(message: "FATAL Error! error to unregister current device")
}
To check if the SSL certificate exit, use this method :
UCubeAPI.isMDMManagerReady()
The update can be done in two steps, check the TMS configuration and compare it with current versions this is performed by the mdmCheckUpdate
method and then download the binary(ies) from TMS server and install them and this can be done by the mdmUpdate
method.
UCubeAPI.mdmCheckUpdate(
forceUpdate: false,
checkOnlyFirmwareVersion: false,
didProgress: { state: ServiceState in
// your code here
},
didFinish: { (success: Bool, parameters: [Any]?) in
if success {
let updates = parameters[0] as! [BinaryUpdate]
let configs = parameters[1] as! [Config]
if updates.count == 0 {
print("Terminal up to date")
} else {
// TODO: call mdmUpdate with in input a [BinaryUpdate]
}
}
}
)
UCubeAPI.mdmUpdate(
updates: selectedUpdateList,,
didProgress: { state: ServiceState in
// your code here
},
didFinish: { (success: Bool, parameters: [Any]?) in
// your code here
}
)
Sending Logs to the server is useful in case of debug. The TMS server provides a web service to receive these log files and the SDK implement the call of this ws.
UCubeAPI.mdmSendLogs(
didProgress: { state: ServiceState in
// your code here
},
didFinish: { (success: Bool, parameters: [Any]?) in
// your code here
}
)
Once the connectionManager set and the device selected. You can call any RPC commands implemented in the SDK. This is the list of RPC Commands class:
/* System & Drivers */
GetInfoCommand.swift
SetInfoFieldCommand.swift
WaitCardCommand.swift
WaitCardRemovalCommand.swift
DisplayChoiceCommand.swift
DisplayMessageCommand.swift
PowerOffCommand.swift
CancelCommand.swift
/* System kernel */
EnterSecureSessionCommand.swift
ExitSecureSessionCommand.swift
InstallForLoadCommand.swift
InstallForLoadKeyCommand.swift
LoadCommand.swift
SimplifiedOnlinePINCommand.swift
/* Payment kernel */
BankParametersDownloads.swift
GetEMVParametersCommand.swift
BuildCandidateListCommand.swift
StartNFCTransactionCommand.swift
CompleteNFCTransactionCommand.swift
GetPlainTagCommand.swift
GetSecuredTagCommand.swift
InitTransactionCommand.swift
TransactionFinalizationCommand.swift
TransactionProcessCommand.swift
All this commands are described in the terminal documentation.
- This is an example of command call:
let displayMessageCommand = DisplayMessageCommand(message: message)
displayMessageCommand.setCentered(centered)
displayMessageCommand.setYPosition(yPosition)
displayMessageCommand.setFont(font)
displayMessageCommand.execute(
monitor: TaskMonitor { (event: TaskEvent, parameters: [Any]) in
switch event {
case .failed:
// your code here
case .success:
// your code here
default:
break
}
}
)