Objective-C (iOS)

Requirements

  • Device must be iOS 8 or newer. iPad and iPhone will both work. 
  • Device cannot be rooted.
  • Need a mac to build sample app and your application. 
  • As we are unable to build our frameworks with bitcode enabled, you will be unable to build your application with bitcode enabled.

Building the Sample Application

  1. Install xCode version 8.1 or newer on your mac from the mac app store (You may be able to use an earlier version but the steps listed here are for 8.1 or newer). Find more information at https://developer.apple.com/support/xcode/.
  2. Copy the debug commerce sample app zip file (CommerceConverge-SampleAppDebug.zip) and unzip on the command line using “unzip CommerceConverge-SampleAppDebug.zip -d outputFolder” where you replace outputFolder with the folder you would like all the files to located. Do not unzip the zip file by double clicking on it as your default unzip application may cause the files to lose symbolic links that are needed inside the uncompressed folders. Do not release your application using the debug build of commerce frameworks that are included with the sample app or from CommerceConverge-FrameworkDebug.zip.
  3. Open xCode and open “CommerceSample.xcodeproj” using xCode’s File/Open menu.
  4. Connect your iOS device to your mac.
  5. On the toolbar where it shows what device you are building for click on it and choose your device you just connected. You can build the sample app for the simulator but you will be unable to use card reader for transactions (You can do cash transactions on the simulator).
  6. Now we need to change the Team and provisioning profile you are using to build the application so that it can run on phones of your team. In the project navigator, click on “CommerceSample”. Under “Targets” click on “CommerceConvergeSample”. Make sure the “General” settings is clicked near the top of xCode. Click on the team combobox and select your development team you are part of through Apple’s Developer Program or you can choose your personal team (Should have your team with personal team in parenthesis). You may need to click “Fix Issue” in order to download a code signing certificate if you have not previously. The provisioning profile is set to “Automatic” so xCode should be able to create one for you when building.  
  7. To build press Command+B.
  8. To run press Command+R.
  9. Once your application is running, you need to set the Converge Credentials you want to use. Click “Edit Account Settings” and set all the fields to your Converge Merchant Credentials. The only externals servers available are “Demo” and “Prod”. Set the server to the correct value for your credentials.
  10. Click “Get Account Info” and if everything is set up correctly, your account information will be retrieved from the server and displayed in the progress window. Or an error will show if unable to retrieve the information.
  11. If you want to run the sample app with the release build of the frameworks, run “unzip CommerceConverge-FrameworkRelease.zip”, and copy the frameworks using “cp -a” over the debug frameworks including in the sample app zip file. You will be unable to debug when you use the release builds of the framework.

Integrating Commerce SDK Into an iOS Application

Setting up your xCode project to use Commerce SDK

  1. There are 3 frameworks you need set your application to use: “Commerce-Converge.framework”, “CommerceDataTypes.framework”, and “ElavonCommon.framework”. Any time you copy these files from one location to another you need to use “cp -a from to” as the “-a” maintains the symbolic links in the framework. Copy all 3 into a folder under your application project location.

  2. Click on your project file in the project navigator. Click on the project name under “Project”. Click on “Build Settings”. Find “Framework Search Paths” and double click the value to show the popup. Click the “+” and add the path to where you copied the frameworks (if under the project it is best to use “\$(SRCROOT” + subfolderpath). If you want to be able to build with both release and debug frameworks, you could copy the frameworks to a release and debug folder, and set the specific folder for each by expanding “Framework Search Paths” and setting the correct folder for each build type.

  3. Under “Build Options”, make sure “Enable Bitcode” is set to “No”.

  4. Under “Other Linker Flags”, click ‘+’ and add “-ObjC”.

  5. Under “Target” click on your target name. Click on “Build Phases”. Under “Link Binary With Libraries” click the “+”. In the dialog, click the “Add Other” button and choose the 3 frameworks. You also need to add “libxml2”, “ExternalAccessory.framework”, “libstdc++”, “libresolv”, and “CoreBuetooth.framework”.

  6. For iOS 9.2, Apple added some Transport Security Requirements. In order to connect to the converge servers we need to override one of the requirements. You need to edit your PLIST file. Control click on your PLIST file and choose Open As->Source Code. You will need to add the following “NSAppTransportSecurity” key to the file under the main “<dict>” tag:

     <key>NSAppTransportSecurity</key>
     <dict>
         <key>NSExceptionDomains</key>
         <dict>
             <key>www.myvirtualmerchant.com</key>
             <dict>
                  <key>NSExceptionRequiresForwardSecrecy</key>
                  <false/>
                  <key>NSIncludesSubdomains</key>
                  <true/>
             </dict>
             <key>demo.myvirtualmerchant.com</key>
             <dict>
                 <key>NSExceptionRequiresForwardSecrecy</key>
                 <false/>
                 <key>NSIncludesSubdomains</key>
                 <true/>
             </dict>
         </dict>
     </dict>
    
  7. In order to communicate via bluetooth to card readers and printers, you need to set properties in your project. Under “Targets” click on your target name. Click on the “Capabilities” tab and turn on “Background Modes”. Check “Uses Bluetooth LE accessories” and “External accessory communication”. Also turn on “Wireless Accessory Configuration” if not already turned on and click “Fix issues” if needed. Open your “PLIST” file (most likely it is named “info.plist”) and click the + button next to “Information Property List”. Select “Supported external accessory protocols”. Expand the new array and for “item 0” click in the “Value” column and change the value to “com.ingenico.easypayemv.spm-transaction” so your app can communicate with the Ingenico card readers. Click the “+” next to “Supported external accessory protocols” to add another item and click in the “Value” column. Change the value to “jp.star-m.starpro” to allow your app to talk to Star Micronics printers.

  8. As a security precaution you should not allow custom keyboards to prevent keys from being recorded. To not allow them you should add the following to your implementation the UIApplicationDelegate:

    -(BOOL)application:(UIApplication *)application shouldAllowExtensionPointIdentifier:(NSString *)extensionPointIdentifier {
        // Disable custom keyboards on iOS 8
    return NO;
    }
    
  9. When saving the Converge merchant credentials or any other sensitive data, do not save them to NSUserDefaults. You should save them in the keychain. Look in our sample application for the “KeychainWrapper” class that saves to the keychain. Also you need to have the “data protection entitlement” on for your app. Click on the target for your app in xCode and then select the “Capabilities” tab. Look for “Data Protection” and turn the setting on.

  10. Press Command + B to make sure you can build.

Referencing and Initializing Commerce SDK

Prior to accepting payments and taking full advantage of Commerce functionality, a few steps should must be performed and items considered when coding.

1. Initialize Commerce Framework

Code to initialize and load the account information may look like the following:

// Initialize Commerce Framework
NSError *error = [ECLCommerce initializeCommerce];
if (error != nil) {
    return error;  // Ensure no errors when initializing Commerce Framework
}

2. Implement the ECLConvergeAccountDelegate protocol.

Code to implement could look like this:

// In your header: 
#import <Commerce-Converge/ECLConvergeAccountDelegate.h>
@interface ConvergeAccountDelegate : NSObject <ECLConvergeAccountDelegate>
 
// In your m file:
#import "ConvergeAccountDelegate.h"
#import <Commerce-Converge/ECLCommerce.h>
#import <Commerce-Converge/ECLAccountProtocol.h>
#import <ElavonCommon/ECCSensitiveData.h>

@implementation ConvergeAccountDelegate
 
- (ECLServerType)serverType:(id<ECLAccountProtocol>)account {
    return ECLServerType_Demo; // other choice is ECLServerType_Production
}
- (ECCSensitiveData *)merchantId:(id<ECLAccountProtocol>)account {
    return [[ECCSensitiveData alloc] init:@"merchantid"]; // replace merchantid with your converge account's merchant ID
}
- (ECCSensitiveData *)userId:(id<ECLAccountProtocol>)account {
    return [[ECCSensitiveData alloc] init:@"userid"]; // replace userid with your converge account's user ID
}
- (ECCSensitiveData *)pin:(id<ECLAccountProtocol>)account {
    return [[ECCSensitiveData alloc] init:@"pin"]; // replace pin with your converge account's PIN
}
- (ECCSensitiveData *)vendorId:(id<ECLAccountProtocol>)account {
    return [[ECCSensitiveData alloc] init:@"vendorid"]; // replace vendorid with your vendor ID
}
- (ECCSensitiveData *)vendorAppName:(id<ECLAccountProtocol>)account {
    return [[ECCSensitiveData alloc] init:@"vendorappname"]; // replace vendorappname with your application name
}
- (ECCSensitiveData *)vendorAppVersion:(id<ECLAccountProtocol>)account {
    return [[ECCSensitiveData alloc] init:@"vendorappversion"]; // replace vendorappversion with your application version
}

// Notify that the account has been initialized and can be used for transactions, etc.
- (void)accountDidInitialize:(id<ECLAccountProtocol>)account {
 
    // SAVE OFF YOUR ACCOUNT TO USE
    yourInstanceOfECLAccountProtocol = account;
    NSLog(@"accountDidInitialize");
}
// Notify that the account failed to initialize.
- (void)accountDidFailToInitialize:(id<ECLAccountProtocol>)account error:(NSError *)error {
    NSLog(@"accountDidFailToInitialize");
}
// Notify the currency supported for the account has changed
// Commerce will automatically retrieve this from the Converge server when the account is created and notify this method when it is retrieved
- (void)defaultCurrencyDidChange:(id<ECLAccountProtocol>)account defaultCurrencyCode:(ECLCurrencyCode)defaultCurrencyCode {
}
// This applicationId is used to identify the transactions done with your application. 8 character max.
- (NSString *)applicationId:(id<ECLAccountProtocol>)account {
    return @"myAppId";
}

Now you have an ECLConvergeAccountDelegate that you can pass to create an account in the next step

3. Create an Account

Code to initialize and load the account information may look like the following:

ConvergeAccountDelegate *convergeAccountDelegate = [[ConvergeAccountDelegate alloc] init];
// Load the Account Information into the Commerce Framework Instance
// The first parameter is the instance of a implementation of the ECLConvergeAccountDelegate. See the sample app for an example. See documentation for ECLConvergeAccountDelegate for what methods need to be implemented.
// The queue parameter is the dispatch_queue_t that all callbacks will be done on. If you use a queue other than the main queue you should be aware that you can only update the UI on the main queue.
[ECLCommerce createAccount:convergeAccountDelegate queue:dispatch_get_main_queue()];

At this point, barring any errors, Commerce Framework and the gateway account is initialized, ready to being processing transactions. Note: You have to wait for accountToBeInitialized on your ECLConvergeAccountDelegate implementation before you will have an account instance to use for transactions.

**4. Setup a Simple Sale/Auth Transaction **(See class  TransactionViewController  in the sample application )

Various options, such as gratuity-on-the-PIN Pad, tax, partial approval, pre-auth, and more are available when performing a sale/auth transaction (and included as examples).  For the purpose of this example, a basic sale/auth is setup to process as a card (i.e. credit/debit) transaction.

#import <Commerce-Converge/ECLCurrencyTransactionProtocol.h>
#import <Commerce-Converge/ECLCardTenderProtocol.h>
#import <CommerceDataTypes/CommerceDataTypes.h>
 
// Define the base Transaction AMOUNT (using Currency Code and Amount in minor units.
// For this example, the amount $19.95 is used.
ECLMoney *baseTransactionAmount = [[ECLMoney alloc] initWithMinorUnits:1995 withCurrencyCode:ECLCurrencyCode_USD];
 
// Retrieve an Instance of ECLCardTenderInterface (implementation used for performing card-based operations)
id<ECLCardTenderProtocol> cardTenderProtocol = [yourInstanceOfECLAccountProtocol.transactionProcessor createCardTender];
 
// Create an Instance of ECLCurrencyTransactionInterface (implementation used for card-based operations where money is changing hands)
// For reference a non-currency transaction is similar to a balance inquiry or loyalty program operation.
id<ECLCurrencyTransactionProtocol> currencyTransactionProtocol = [yourInstanceOfECLAccountProtocol.transactionProcessor createSaleTransactionWithSubtotal:baseTransactionAmount];

Performing the above steps defines the base set of information needed to process a transaction.  Using the above information, transaction processing can start.

5. Implement ECLTransactionProcessingDelegate (See class  MainViewController  in the sample application)

// Create an implementation of ECLTransactionProcessingDelegate (handle all callbacks and any requests for information during transaction processing)

// in your header file
#import <Commerce-Converge/ECLTransactionProcessingDelegate.h>
@interface TransactionProcessingDelegate : NSObject<ECLTransactionProcessingDelegate>
 
// In your .m file
#import "TransactionProcessingDelegate.h"
#import <Commerce-Converge/ECLAccountProtocol.h>
#import <Commerce-Converge/ECLTransactionProcessorProtocol.h>
#import <Commerce-Converge/ECLCurrencyTransactionProtocol.h>
#import <Commerce-Converge/ECLTransactionRequirementsProtocol.h>
#import <Commerce-Converge/ECLCardTenderProtocol.h>
#import <Commerce-Converge/ECLEmvCardTransactionOutcome.h>
#import <CommerceDataTypes/CommerceDataTypes.h>
#import <Commerce-Converge/ECLTenderRequirementsProtocol.h>
#import <Commerce-Converge/ECLTransactionProgress.h>
#import <Commerce-Converge/ECLDebugDescriptions.h>
@implementation TransactionProcessingDelegate
- (void)transactionDidComplete:(id<ECLTransactionProtocol>)transaction using:(id<ECLTenderProtocol>)tender outcome:(ECLTransactionOutcome *)outcome {
 
    if (outcome == nil) {
        NSLog(@"nil outcome");
        return;
    } 
 
    if (outcome.error != nil) {
        NSLog(@"transactionDidComplete with error: %@", outcome.error.debugDescription);
    }
 
    // can also check signatureError to see if we failed to send signature
 
    if (outcome.isApproved == YES) {
        // Transaction was APPROVED!!!  Use the "outcome" instance for transaction details (approval code, etc.)
    } else {
        // Transaction was DECLINED!!!  Use the "outcome" instance for transaction details (approval code, etc.)
    }
    if ([outcome isKindOfClass:[ECLCardTransactionOutcome class]]) {
        // Outcome is for a Card Transaction so cast to ECLCardTransactionOutcome to get more info 
        if ([outcome isKindOfClass:[ECLEmvCardTransactionOutcome class]]) {
            // Outcome is for a EMV Card Transaction so cast to ECLEmvCardTransactionOutcome to get more info 
            // including checking if we failed to reverse a transaction from reverseError property
            // or we failed to update a transaction on the server from updateError property
        }
    }
}
- (void)transactionDidCancel:(id<ECLTransactionProtocol>)transactionParam using:(id<ECLTenderProtocol>)tenderParam {
    // Transaction was canceled
}
- (void)transactionDidFail:(id<ECLTransactionProtocol>)transactionParam using:(id<ECLTenderProtocol>)tenderParam errors:(NSArray *)arrayOfNSErrors {
    // Transaction failed. Check the array of NSErrors to see why.
    // Use nsError.debugDescription to log more information
}

- (void)shouldProvideInformation:(id<ECLTransactionProtocol>)transactionParam tender:(id<ECLTenderProtocol>)tenderParam transactionRequires:(id<ECLTransactionRequirementsProtocol>)transactionRequires tenderRequires:(id<ECLTenderRequirementsProtocol>)tenderRequires {
    // We need more information to process the transaction!        
    // During transaction processing, this callback may be executed one or more times for the purpose
    // of collecting more information to continue processing the transaction.    
    // SEE SAMPLE APP FOR OTHER REQUIREMENTS YOU SHOULD PROVIDE WHEN THIS METHOD IS CALLED. Listed here is only sample of those.

    BOOL continueTransaction = NO;

    if (transactionRequires.requiresGratuity) {
        // Transaction requires gratuity to be set but if we don't want to set any we can set it to nil.
        // Lets make sure it is a ECLCurrencyTransactionProtocol
        if ([transactionParam conformsToProtocol:@protocol(ECLCurrencyTransactionProtocol)]) {
            id<ECLCurrencyTransactionProtocol> currencyTransaction = (id<ECLCurrencyTransactionProtocol>)transactionParam;
            [currencyTransaction setGratuity:nil];
            continueTransaction = YES;
        }
    }
    
    if (tenderRequires.requiresVoiceReferral != ECLVoiceReferral_NotRequired) {
        // Voice Referral Required!                
        // Some transactions may require calling the issuer to obtain an approval code.
        // Should this be required, pass the approval code to Commerce SDK once the code has been provided.        
        // Lets make sure it is a ECLCardTenderProtocol
        if ([tenderParam conformsToProtocol:@protocol(ECLCardTenderProtocol)]) {
            id<ECLCardTenderProtocol> cardTenderProtocol = (id<ECLCardTenderProtocol>)tenderParam;
            [cardTenderProtocol setVoiceReferralHandledAndApproved:@"321"]; // we should show UI to get Authorization Code 
                                                                            // and call continueTransaction after we get it but here we just set it
            continueTransaction = YES;
        }
    }

    if (tenderRequires.requiresSignatureVerification != ECLSignatureVerification_NotRequired) {
        // Signature Verification Required!                
        // In some cases, processing may ask to verify the cardholder's signature.  Once verified, the transaction processing may continue.
        // Lets make sure it is a ECLCardTenderProtocol
        if ([tenderParam conformsToProtocol:@protocol(ECLCardTenderProtocol)]) {
            id<ECLCardTenderProtocol> cardTenderProtocol = (id<ECLCardTenderProtocol>)tenderParam;
            [cardTenderProtocol setSignatureVerificationHandledAndVerified]; // we should show UI to ask for verification
                                                                            // and call continueTransaction after we get it but here we just set it
            continueTransaction = YES;
        }
    }
    
    if (tenderRequires.requiresDigitalSignature){
        // We should launch signature capture UI and setDigitalSignature on ECLCardTenderProtocol and call continueTransaction
        // See sample app how to capture signature. Search for the line below
        // [myViewController performSegueWithIdentifier:@"signatureScreenSegue" sender:self];
        // return;
 
        // but for now lets cancel the signature and that will print a line on the receipt for customer to sign
        // Lets make sure it is a ECLCardTenderProtocol
        if ([tenderParam conformsToProtocol:@protocol(ECLCardTenderProtocol)]) {
            id<ECLCardTenderProtocol> cardTenderProtocol = (id<ECLCardTenderProtocol>)tenderParam;
            [cardTenderProtocol cancelSignature];
            continueTransaction = YES;
        }
    }
 
    if (continueTransaction) {
        // NOTE: You should always do a dispatch_async when calling from a delegate method back into Commerce to process a transaction or before showing UI
        dispatch_async(dispatch_get_main_queue(), ^() {
            // continue the transaction because we updated required information
            [yourInstanceOfECLAccountProtocol.transactionProcessor continueProcessingTransaction:transactionParam using:tenderParam delegate:self];
        });
    } else {
        // we failed to provide the information. Should cancel transaction. Check log for what was required
    }
}
 
- (void)shouldSetCardReaderToUse:(id<ECLTransactionProtocol>)transactionParam tender:(id<ECLTenderProtocol>)tenderParam cardReaders:(NSArray *)cardReadersReadyForUse {
    // Commerce detected more than one card reader so you need to let the user choose which one from the list of NSStrings in cardReadersReadyForUse
    // Should this scenario arise, provide a mechanism to select the appropriate card reader.

    // NOTE: You should always do a dispatch_async when calling from a delegate method back into Commerce to process a transaction or before showing UI
    dispatch_async(dispatch_get_main_queue(), ^() {
        // Show your UI here
        // Then call yourInstanceOfECLAccountProtocol.cardReaders setDeviceToUse to set the card reader chosen
        // Then call yourInstanceOfECLAccountProtocol.transactionProcessor continueProcessingTransaction
    });
    return;
}
- (void)transactionProgress:(ECLTransactionProgress)progress transaction:(id<ECLTransactionProtocol>)transactionParam using:(id<ECLTenderProtocol>)tenderParam {
    // Always good to know what is happening while the transaction is processing!        
    // Use this callback to provide status information (i.e. "progress" messages)
    NSLog(@"transactionProgress %@", [ECLDebugDescriptions descriptionOfTransactionProgress:progress]);
}

6. Now you have everything needed to process the transaction

ECLTransactionProcessingDelegate *transactionProcessingDelegate = [[ECLTransactionProcessingDelegate alloc] init];
 
// Now use all the instances you have created in the previous steps to start the transaction!
yourInstanceOfECLAccountProtocol.transactionProcessor processTransaction:currencyTransactionProtocol using:cardTenderProtocol delegate:transactionProcessingDelegate];

Best Practices

Disable iOS auto-lock timer during transaction processing

Some iOS users may have configured their device’s auto-lock timer with a low value in order to preserve battery life. Since transactions can last for an undetermined amount of time due to factors involving cardholder responsiveness, it is a good idea to prevent the device from going to sleep in the middle of a transaction. In the sample app you will notice that this technique is used after starting a transaction. Then the auto-lock functionality is restored upon completion of a transaction. Here is an example:

// disable the auto-lock timer
[UIApplication sharedApplication].idleTimerDisabled = YES;

// (do important tasks)

// re-enable the auto-lock timer
[UIApplication sharedApplication].idleTimerDisabled = NO;