- Device Requirements
- Building the Sample Application
- Integrating Commerce SDK with an iOS Application
- Integration Steps
Device Requirements
- iPad and iPhone are supported.
- Device must be running iOS 12 or later.
- Device cannot be jailbroken.
Building the Sample Application
- Install Xcode on your Mac from the Mac App Store.
Refer to Xcode support page for more information.
- Copy the CommerceOpayo-SampleAppDebug.zip file and unzip on the command line using unzip CommerceOpayo-SampleAppDebug.zip -d outputFolder, where you replace outputFolder with the folder you would like all the files to be 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 CommerceOpayo-FrameworkDebug.zip.
- Open Xcode and open CommerceSample.xcodeproj using the File/Open menu.
- Connect your iOS device to your mac.
-
On the toolbar where it shows what device you are building for click on it and choose your device you just connected. Also make sure the build scheme is set to CommerceOpayoSample.
- 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 CommerceSample.
- Under Targets, click CommerceOpayoSample. Click the Signing & Capabilities tab.
- Click the team combobox and select the 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.
-
Press Command+B to build the sample application.
-
Press Command+R to run the sample application.
- Once your application is running, you need to set the Opayo credentials you want to use.
- Click Edit Account Settings and set all the fields to your Opayo merchant credentials.
- The only externals servers available are Demo and Prod. Set the server to the correct value for your credentials.
-
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. You can scroll down on the list of buttons in the sample app to reveal more functions.
- Separately you can also 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.
- If you want to run the sample app with the release build of the frameworks, run unzip CommerceOpayo-FrameworkRelease.zip, and copy the frameworks using cp -a over the debug frameworks included in the sample app zip file.
Note: You will be unable to debug when you use the release builds of the framework.
Integrating Commerce SDK Into a iOS Application
Setting up your xCode project to use Commerce SDK
- There are several required frameworks you need to link with your application:
- Commerce-Opayo.xcframework
- CommerceDataTypes.xcframework
- ElavonCommon.xcframework
- OpayoMPOS.xcframework
-
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 frameworks into a folder under your application project location.
- Click your project file in the project navigator.
Click the project name under Project.
Click Build Settings.
Find Framework Search Paths and double click the value to show the popup.
Click + 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.
-
Under Other Linker Flags, click + and add -ObjC.
- Under Target, click your target name. Make sure you are under the General tab. Under Frameworks, Libraries, and Embedded Content, click +. In the dialog, click the Add Other… button then Add Files… button, and choose all the frameworks from above.
Note: For all xcframeworks except Commerce-Opayo.xcframework, you need to choose Embed & Sign. For Commerce-Opayo.xcframework, you need to choose Do Not Embed (see sample app).
You also need to add libc++.tbd, libresolv.tbd, Foundation.framework, and ExternalAccessory.framework.
- In order to communicate via bluetooth to the card reader, you need to set properties in your project.
Under Targets, click your target name. Click the Signing & Capabilities tab.
Click on the + Capability button and add Background Modes.
Check External accessory communication and Uses Bluetooth LE accessories.
Click the Info tab (which corresponds to your PLIST file) and click the + button under Custom iOS Target Properties.
Add Privacy - Bluetooth Always Usage Description. Add a description such as "Required for bluetooth communication with card reader".
Add a new key LSApplicationQueriesSchemes of type array.
Expand the new array and add the following item:
- cydia
Add Supported external accessory protocols.
Expand the new array and add the following item:
- com.datecs.pinpad
- As a security precaution, you should set custom keyboards to prevent keys from being recorded. To not allow them, you should add the following to your implementation of UIApplicationDelegate:
- (BOOL)application:(UIApplication *)application shouldAllowExtensionPointIdentifier:(NSString *)extensionPointIdentifier { // Disable custom keyboards return NO; }
- When saving the Opayo merchant credentials or any other sensitive data, do not save these 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 switched on for your app.
Click on the target for your app in Xcode and select the Capabilities tab. Look for Data Protection and turn the setting on.
- Press Command+B to make sure you can build.
Integration Steps
Commerce SDK Initialization
- Commerce SDK initialization
First we need to initialize the Commerce SDK and check for errors. Please refer to <Commerce-Opayo/ECLCommerce.h> for details. The actual error should be of type ECLError.
NSError *error = [ECLCommerce initializeCommerce]; // if initialized successfully, error will be nil. Otherwise stop here and fix the error. // Possible errors are // ECLTransactionErrorCode.ECLUnsecure, // Running CSDK release build in debug mode, using jailbroken device, etc. // ECLTransactionErrorCode.ECLSensitiveDataLibraryNotInitialized, // Encryption library failed to initialize if ([error isKindOfClass:[ECLError class]]) { ... }
- Instantiation of Opayo account delegate and Opayo account
- We need to provide an implementation of protocol <Commerce-Opayo/ECLOpayoAccountDelegate.h> that Commerce SDK will use to retrieve the Opayo account details needed to create the account. Note that ECLOpayoAccountDelegate is an extension of <Commerce-Opayo/ECLAccountDelegate.h>, which provides callback methods for the result of the account creation.
@interface OpayoAccountDelegate : NSObject<ECLOpayoAccountDelegate> @end @implementation OpayoAccountDelegate // implement ECLOpayoAccountDelegate methods - (ECCSensitiveData *)merchantId:(id<ECLAccountProtocol>)account { return [[ECCSensitiveData alloc] init:@"myMerchantId"]; } - (ECCSensitiveData *)clientId:(id<ECLAccountProtocol>)account { return [[ECCSensitiveData alloc] init:@"myClientId"]; } - (ECCSensitiveData *)password:(id<ECLAccountProtocol>)account { // return Initial Password for account at first // after first transaction you will receive the hashed password, so return hashed password from that point on return [[ECCSensitiveData alloc] init:@"myPassword"]; } - (ECCSensitiveData *)operatorPin:(id<ECLAccountProtocol>)account { // return the optional operator PIN if set for the account; otherwise return nil return [[ECCSensitiveData alloc] init:@"myPin"]; } // implement ECLAccountDelegate methods - (ECLServerType)serverType:(id<ECLAccountProtocol>)account { // return ECLServerType_Demo if you have a test account return ECLServerType_Production; } - (void)accountDidInitialize:(id<ECLAccountProtocol>)account { } - (void)accountDidFailToInitialize:(id<ECLAccountProtocol>)account error:(NSError *)error { } ... @end
- Next we create the Opayo account by passing the implementation of ECLOpayoAccountDelegate as the first parameter. The second parameter is the dispatch_queue_t that Commerce SDK will use to send all callbacks 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.
OpayoAccountDelegate *delegate = [[OpayoAccountDelegate alloc] init]; [ECLCommerce createAccount:delegate queue:dispatch_get_main_queue()];
- If account creation is successful, you will receive the [ECLAccountDelegate accountDidInitialize:] callback. You should cache the <Commerce-Opayo/ECLAccountProtocol.h> instance returned by the callback for later use. If account creation is not successful, you will receive the [ECLAccountDelegate accountDidFailToInitialize:error:] callback. You should check the error parameter and resolve it before any further integration.
- (void)accountDidInitialize:(id<ECLAccountProtocol>)account { // account successfully initialized; cache the account for later use } - (void)accountDidFailToInitialize:(id<ECLAccountProtocol>)account error:(NSError *)error { // account failed to initialize, check error; check error }
Pair Card Reader
After successfully instantiating an account, we can pair the card reader to the iOS device if it hasn't been paired already. If POS system does not intend to support in-app pairing, please jump to the next section.
Pairing Card Reader
We can pair a card reader within the app by using the account we received from Commerce SDK.
First we create an implementation of <Commerce-Opayo/ECLDevicePairingDelegate.h> to receive callbacks related to device pairing.
@interface DevicePairingDelegate : NSObject<ECLDevicePairingDelegate> @end @implementation DevicePairingDelegate - (void)devicePairingSucceeded { } - (void)devicePairingFailed { } - (void)devicePairingNotSupported { } @end
We then use the account to pair a card reader by first casting the account to an <Commerce-Opayo/ECLOpayoAccountProtocol.h>.
DevicePairingDelegate *delegate = [[DevicePairingDelegate alloc] init]; // cachedAccount is the ECLAccountProtocol we received from Commerce SDK previously id<ECLOpayoAccountProtocol> opayoAccount = (id<ECLOpayoAccountProtocol>)cachedAccount; [opayoAccount pairWithPinPad:delegate];
You will see a dialog in the app with the name of the card reader, which you can select to establish the pairing.
TMS Update
The first time after installing the CommerceSDK integrated application on the iOS device, you must perform a FULL TMS (Terminal Management System) update to retrieve the most up-to-date configurations for your terminal and card reader. Note that card reader connection/initialization will not work unless you have performed a FULL TMS update first.
Check Required TMS Update Type
You can call a method to retrieve the current required TMS update type. It will return an update type of FULL when the application is first installed. Note that this method is provided as a convenience mainly to check if a FULL TMS update is needed. However, you can perform a TMS update at any time without calling this method.
The method is requiredTmsUpdateType on protocol <Commerce-Opayo/ECLOpayoAccountProtocol.h>. Please see <Commerce-Opayo/ECLOpayoTmsUpdateType.h> for all possible values.
// cachedAccount is the ECLAccountProtocol we received from Commerce SDK previously id<ECLOpayoAccountProtocol> opayoAccount = (id<ECLOpayoAccountProtocol>)cachedAccount; ECLOpayoTmsUpdateType requiredTmsUpdateType = [opayoAccount requiredTmsUpdateType];
Perform FULL TMS Update
You can use the same ECLOpayoAccountProtocol to perform the TMS Update. First you need to create an instance of <Commerce-Opayo/ECLTmsUpdateDelegate.h>.
@interface TmsUpdateDelegate : NSObject<ECLTmsUpdateDelegate> @end @implementation TmsUpdateDelegate - (void)shouldSetCardReaderToUse:(NSArray<NSString *> *)availableCardReaders { // if multiple card readers are available, select one and call to update again [cachedAccount.cardReaders selectDevice:availableCardReaders[0] withConnectionTypes:[ECLConnectionTypeUtil getAllConnectionTypes]]; id<ECLOpayoAccountProtocol> opayoAccount = (id<ECLOpayoAccountProtocol>)cachedAccount; [opayoAccount performTmsUpdate:updateType delegate:delegate]; } - (void)selectedCardReader:(NSString *)selectedCardReader { NSLog(@"TMS update selectedCardReader %@", selectedCardReader); } - (void)progress:(ECLTransactionProgress)progress { NSLog(@"TMS update progress %@", [ECLDebugDescriptions descriptionOfTransactionProgress:progress]); } - (void)completed:(BOOL)success error:(nullable NSError *)error { if (success) { // completed } else { // failed, check error } } @end
Now we can call method performTmsUpdate:delegate.
TmsUpdateDelegate *delegate = [[TmsUpdateDelegate alloc] init]; id<ECLOpayoAccountProtocol> opayoAccount = (id<ECLOpayoAccountProtocol>)cachedAccount; [opayoAccount performTmsUpdate:ECLOpayoTmsUpdateTypeFull delegate:delegate];
Perform NORMAL TMS Update
You can also perform a NORMAL TMS update at any time. This can be used to update various account related changes for your app such as gratuity and operator PIN support. It is also recommended to perform this update once a day to ensure the app is up-to-date with the account and terminal configuration on the server.
TmsUpdateDelegate *delegate = [[TmsUpdateDelegate alloc] init]; id<ECLOpayoAccountProtocol> opayoAccount = (id<ECLOpayoAccountProtocol>)cachedAccount; [opayoAccount performTmsUpdate:ECLOpayoTmsUpdateTypeNormal delegate:delegate];
Connect Card Reader (iOS)
We can now establish a connection to the card reader so that we can perform transactions. Note that connecting explicitly via this method is not required. You can choose to start a transaction and Commerce SDK will connect to the device internally if it is not already connected.
Connecting to Card Reader
If we have a card reader paired to the iOS device, we can then connect to it.
First we create an implementation of <Commerce-Opayo/ECLFindDevicesDelegate.h>.
@interface FindDevicesDelegate : NSObject<ECLFindDevicesDelegate> @end @implementation FindDevicesDelegate - (void)devicesSearchFound:(id<ECLDevicesProtocol>)devices name:(NSString *)name connectionType:(ECLConnectionType)connectionType { // called when an individual device available to connect is found } - (void)devicesSearchDone:(id<ECLDevicesProtocol>)devices searchResults:(NSArray<ECLFindDevicesSearchResult *> *)searchResults { // called when the device search is complete } @end
To find a paired device, you'll use method findDevices defined on <Commerce-Opayo/ECLAccountProtocol.h> to find all the paired devices.
FindDevicesDelegate *delegate = [[FindDevicesDelegate alloc] init]; // cachedAccount is the ECLAccountProtocol we received from Commerce SDK previously // The second parameter is set to NO, indicating that we'll not disconnect an already connected device // The third parameter is set to 30, i.e. searching timeout is 30 seconds. [cachedAccount findDevices:delegate forceToDisconnect:NO timeoutInSeconds:30];
When device search is finished, we get the device we want to connect to from the array of <Commerce-Opayo/ECLFindDevicesSearchResult.h> and select it using <Commerce-Opayo/ECLDevicesProtocol.h>. We cache the result as an instance of <Commerce-Opayo/ECLCardReaderProtocol.h>.
// callback method from ECLFindDevicesDelegate called when device search is finished - (void)devicesSearchDone:(id<ECLDevicesProtocol>)devices searchResults:(NSArray<ECLFindDevicesSearchResult *> *)deviceSearchResults { // simplified device selection code; you should allow selecting the device if there are multiple ECLFindDevicesSearchResult *result = [deviceSearchResults objectAtIndex:0]; id<ECLCardReaderProtocol> cardReader = (id<ECLCardReaderProtocol>) [devices selectDevice:result.name withConnectionTypes:result.connectionTypes]; }
We then implement an instance of <Commerce-Opayo/ECLCardReaderDelegate.h> to receive callbacks related to the card reader connection.
@interface CardReaderDelegate : NSObject<ECLCardReaderDelegate> @end @implementation CardReaderDelegate - (void)cardReaderInitialized:(id<ECLCardReaderProtocol>)cardReader { // when card reader is initialized, we can start using it } - (void)cardReaderDisconnected:(id<ECLCardReaderProtocol>)cardReader isStillTransacting:(BOOL)isStillTransacting reason:(ECLTransactionErrorCode)reason { // the connected card reader was disconnected } - (void)cardReader:(id<ECLCardReaderProtocol>)cardReader progress:(ECLTransactionProgress)progress { // progress messages during card reader connection } ... @end
Finally, we add the card reader delegate to the ECLCardReaderProtocol instance we received before and then call method connectAndInitialize.
CardReaderDelegate *delegate = [[CardReaderDelegate alloc] init]; [cardReader addConnectionDelegate:delegate]; // Connect and initialize the card reader, performing any updates if needed [cardReader connectAndInitialize:YES];
If card reader is successfully connected and initialized, method cardReaderInitialized on ECLCardReaderDelegate will be called.
Process Transaction
POS system could process transaction by creating a transaction object and send it to Commerce SDK (CSDK) without first connecting to card reader. However, it can sometimes take several minutes for Commerce SDK to successfully connect and initialize a card reader so it is strongly encouraged that the POS system calls Commerce SDK to connect and initialize card reader before running any transactions.
Commerce SDK supports three transaction types:
- SALE transaction (card holder makes a purchase of goods or service).
- Linked Refund transaction (merchant returns a portion or the whole amount for a SALE transaction back to card holder).
- Stand Alone Refund (SAR) transaction (merchant returns an amount back to card holder).
SALE and SAR transactions require interaction with card reader while Linked Refund transaction can be executed without a card reader.
SALE Transaction
The following steps lists how to create a SALE transaction and process it.
- In order for CSDK to process a SALE transaction we'll need to create an instance of ECLMoney to represent the base transaction amount.
// In this example we create a sale amount of £10.50. ECLMoney *saleAmount = [[ECLMoney alloc] initWithMinorUnits:1050 withCurrencyCode: ECLCurrencyCode_GBP];
- We then retrieve ECLTransactionProcessorProtocol instance from the cached account and use it to create an instance of ECLCurrencyTransactionProtocol with the amount just defined above.
// variable cachedAccount is the ECLAccountProtocol returned by CSDK when first initialized id<ECLCurrencyTransactionProtocol> saleTransaction = [[cachedAccount transactionProcessor] createSaleTransactionWithSubtotal:saleAmount];
- We will also create an instance of ECLCardTenderProtocol.
id<ECLCardTenderProtocol> cardTender = [[cachedAccount transactionProcessor] createCardTender];
- We then create an instance of ECLOpayoTransactionProcessingDelegate. Note that it is an extension of ECLTransactionProcessingDelegate.
Note: When processing a transaction for the very first time, your implementation of ECLOpayoAccountDelegate:password: from Commerce SDK Initialization must return the initial password for the account. During the execution of the first transaction, you will receive the passwordHashUpdated callback method with a hash of the password. You should securely store this password hash, and your ECLOpayoAccountDelegate:password: implementation must now return this password hash.
@interface OpayoTransactionProcessingDelegate : NSObject<ECLOpayoTransactionProcessingDelegate> @end @implementation OpayoTransactionProcessingDelegate // ECLOpayoTransactionProcessingDelegate method implementations // Some of the method implementations are omitted because they are not applicable to EU market at this point. // Note: addStatusString is a method defined to display a string in the app - (void)passwordHashUpdated:(id<ECLTransactionProtocol>)transaction using:(id<ECLTenderProtocol>)tender passwordHash:(ECCSensitiveData *)passwordHash { // you will receive this after running your first transaction with the initial account password. // you must now return the password hash in your ECLOpayoAccountDelegate:password: implementation } - (void)transactionDidComplete:(id<ECLTransactionProtocol>)transaction using:(id<ECLTenderProtocol>)tender outcome:(ECLTransactionOutcome *)outcome { // check if there was an error during transaction completion if (outcome.error != nil) { [self addStatusString:[NSString stringWithFormat:@"error: %@\n", [outcome.error debugDescription]]]; } // check transaction result, i.e. approved, declined, etc. [self addStatusString:[NSString stringWithFormat:@"result: %@\n", [ECLDebugDescriptions descriptionOfTransactionResult:outcome.result]]]; if ([outcome isKindOfClass:[ECLCardTransactionOutcome class]]) { // check attributes for card transaction } if ([outcome isKindOfClass:[ECLEmvCardTransactionOutcome class]]) { // check attributes for emv card transaction } } // This method will be called by CSDK if transaction is failed to be processed, for example card holder presses cancel button // on card reader when being prompted to insert/tap/swipe card. Errors are returned along with the original transaction request and tender. - (void)transactionDidFail:(id<ECLTransactionProtocol>)transaction using:(id<ECLTenderProtocol>)tender errors:(NSArray *)arrayOfNSErrors { [self addStatusString:[NSString stringWithFormat:@"transactionDidFail: %@\n", [arrayOfNSErrors[0] debugDescription]]]; // you can also check the remaining errors if there are multiple in the array } // CSDK will ask POS system to provide specific information by calling this method if there is any further information needed to process a transaction. - (void)shouldProvideInformation:(id<ECLTransactionProtocol>)transaction tender:(id<ECLTenderProtocol>)tender transactionRequires:(id<ECLTransactionRequirementsProtocol>)transactionRequires tenderRequires:(id<ECLTenderRequirementsProtocol>)tenderRequires { BOOL continueTransaction = NO; if (tenderRequires.requiresVoiceReferral != ECLVoiceReferral_NotRequired) { // you should use the phone number provided by ECLTenderRequirementsProtocol.voiceReferralPhoneNumber to call and receive voice auth code [(id<ECLCardTenderProtocol>)tender setVoiceReferralHandledAndApproved:@"123"]; continueTransaction = YES; } if (tenderRequires.requiresSignatureVerification != ECLSignatureVerification_NotRequired) { [(id<ECLCardTenderProtocol>)tender setSignatureVerificationHandledAndVerified]; continueTransaction = YES; } if (tenderRequires.requiresSpecifyingCardPresence) { ((id<ECLCardTenderProtocol>)tender).cardPresent = ECCTriState_Yes; // can also be ECCTriState_No if card not present continueTransaction = YES; } if (transactionRequires.requiresGratuity) { [(id<ECLCurrencyTransactionProtocol>)transaction setGratuity:nil]; continueTransaction = YES; } // After providing required information, POS system should ask CSDK to continue to process transaction by making the following call if (continueTransaction) { dispatch_async(dispatch_get_main_queue(), ^() { [[cachedAccount transactionProcessor] continueProcessingTransaction:transaction using:tender delegate:delegate]; }); } } // Called when there are multiple card readers available for use during a transaction // Tell CSDK which one to use so the transaction can proceed - (void)shouldSetCardReaderToUse:(id<ECLTransactionProtocol>)transaction tender:(id<ECLTenderProtocol>)tender cardReaders:(NSArray *)cardReadersReadyForUse { [[cachedAccount cardReaders] selectDevice:cardReadersReadyForUse[0] withConnectionTypes:[ECLConnectionTypeUtil getAllConnectionTypes]]; [[cachedAccount transactionProcessor] continueProcessingTransaction:transaction using:tender delegate:delegate]; } - (void)transactionProgress:(ECLTransactionProgress)progress transaction:(id<ECLTransactionProtocol>)transaction using:(id<ECLTenderProtocol>)tender { // receive any progress messages during the transaction [self addStatusString:[NSString stringWithFormat:@"progress: %@\n", [ECLDebugDescriptions descriptionOfTransactionProgress:progress]]]; } @end
- Afterwards we can ask CSDK to process the transaction by making the following call. This is an asynchronous call and CSDK will later notify the POS system about the transaction result through the transaction delegate defined above. Please refer to <Commerce-Opayo/ECLTransactionOutcome.h> for general transaction details, <Commerce-Opayo/ECLCardTransactionOutcome.h> for card based transaction details, and <Commerce-Opayo/ECLEmvCardTransactionOutcome.h> for EMV card based transaction details.
OpayoTransactionProcessingDelegate *delegate = [[OpayoTransactionProcessingDelegate alloc] init]; [[cachedAccount transactionProcessor] processTransaction:saleTransaction using:cardTender delegate:delegate];
Linked Refund Transaction
The same steps for processing SALE transaction should be followed to handle linked refund transaction. The only difference is that an instance of ECLLinkedRefundTransactionProtocol should be created in step 2. You will need the unique identifier of the original SALE transaction that was returned to you as part of the ECLTransactionOutcome.
// originalSaleTransactionUniqueId is the unique id from the original SALE transaction outcome. // amount is money value to be refunded. Please note that the value should be equal to or less than the refundable amount from the original SALE transaction. // Variable cachedAccount is the one cached after CSDK is successfully initialized. id<ECLLinkedRefundTransactionProtocol> linkedRefundTransaction = [[cachedAccount transactionProcessor] createLinkedRefundTransactionWithTotal:amount transactionID:originalSaleTransactionUniqueId]; // process the transaction, see SALE transaction page for details on how to create the other parameters [[cachedAccount transactionProcessor] processTransaction:linkedRefundTransaction using:cardTender delegate:delegate];
Stand Alone Refund Transaction
The same steps for processing SALE transaction should be followed to handle standalone refund transaction. The only difference is that an instance of ECLStandaloneRefundTransactionProtocol should be created in step 2.
// Variable cachedAccount is the one cached after CSDK is successfully initialized. // Variable amount is the total amount to be refunded. id<ECLStandaloneRefundTransactionProtocol> standaloneRefundTransaction = [[cachedAccount transactionProcessor] createStandaloneRefundTransactionWithTotal:money]; // process the transaction, see Sale transaction page for details on how to create the other parameters [[cachedAccount transactionProcessor] processTransaction:standaloneRefundTransaction using:cardTender delegate:delegate];