First integration

This guide helps you make the first payment using ready-to-use payment forms.

Demo app

Get started with the demo application to make test transactions without setting up a server. A test integration server supports these transactions for convenience, but it runs for demo purposes.

The SDK includes the demo app starting from version 2.5.0.

📘

Before running the application, copy the SDK files to the demo app folder. Find more details in the readme.txt file.

The full document in markdown, formatted according to your preferences are:

iOS

Requirements

  • Xcode 16 and iOS 18 SDK or higher
  • iOS 13.0+ deployment target

Install the mSDK

  1. Drag OPPWAMobile.xcframework and ipworks3ds_sdk.xcframework into the Frameworks folder of the project. Check Copy items if needed.
  2. Under Frameworks, Libraries, and Embedded Content in the General settings tab, set the Embed drop-down to Embed and Sign.

Import the framework by adding the following import statement to use the SDK:

     #import <OPPWAMobile/OPPWAMobile.h>
import OPPWAMobile

The SDK includes two versions of ipworks3ds_sdk.xcframework:

  • Development version: For debugging and testing.
  • Production version: Includes stricter security, preventing debugging or simulator use. The production version includes _deploy in the filename.

If integrating the SDK into a Swift app, follow Apple's Importing Objective-C into Swift guide.

Set up the server

To use the SDK, expose two APIs on the backend for the iOS app to communicate with:

  • Endpoint 1: Create a checkout ID
  • Endpoint 2: Retrieve the payment result

Refer to the set up your server guide for detailed instructions.

Request a checkout ID

The app must request a checkout ID from the backend. The following example demonstrates how to request a checkout ID using a sample integration server. Adapt it for your API.

NSURLRequest *merchantServerRequest = [[NSURLRequest alloc] initWithURL:[NSURL URLWithString:@"https://YOUR_URL/?amount=100&currency=EUR&paymentType=DB"]];
[[[NSURLSession sharedSession] dataTaskWithRequest:merchantServerRequest completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
    // Ensure that you handle errors
    NSDictionary *JSON = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:nil];
    self.checkoutID = JSON[@"checkoutId"];
}] resume];
let merchantServerRequest = NSURLRequest(url: URL(string: "https://YOUR_URL/?amount=100&currency=EUR&paymentType=DB")!)
URLSession.shared.dataTask(with: merchantServerRequest as URLRequest) { (data, response, error) in
    // Ensure that you handle errors
    if let data = data, let json = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
        let checkoutID = json?["checkoutId"] as? String
    }
}.resume()

Present the checkout UI

1. Initialise the payment provider

Set up the payment provider in test mode:

self.provider = [OPPPaymentProvider paymentProviderWithMode:OPPProviderModeTest];
let provider = OPPPaymentProvider(mode: OPPProviderMode.test)

2. Configure the checkout settings

Define the settings for the checkout screen using OPPCheckoutSettings. Initialise OPPCheckoutSettings to control the information displayed to the shopper. For the first integration, enable synchronous payment methods. Other payment brands require further configuration.

OPPCheckoutSettings *checkoutSettings = [[OPPCheckoutSettings alloc] init];

// Set available payment brands for the shop
checkoutSettings.paymentBrands = @[@"VISA", @"DIRECTDEBIT_SEPA"];

// Set shopper result URL
checkoutSettings.shopperResultURL = @"com.companyname.appname.payments://result";
let checkoutSettings = OPPCheckoutSettings()

// Set available payment brands for your shop
checkoutSettings.paymentBrands = ["VISA", "DIRECTDEBIT_SEPA"]

// Set shopper result URL
checkoutSettings.shopperResultURL = "com.companyname.appname.payments://result"
📘

To support all payment methods, set shopperResultURL. See the Asynchronous payments guide for more details.

3. Present the checkout page

Start OPPCheckoutProvider with the received checkout ID and settings.

📘

Create the OPPCheckoutProvider object at the class level to ensure its not released during the payment process.

OPPCheckoutProvider *checkoutProvider = [OPPCheckoutProvider checkoutProviderWithPaymentProvider:provider
                                                                                      checkoutID:checkoutID
                                                                                        settings:checkoutSettings];
let checkoutProvider = OPPCheckoutProvider(paymentProvider: provider, checkoutID: checkoutID!, settings: checkoutSettings)

Open the payment page and handle callbacks:

// Since version 2.13.0
[checkoutProvider presentCheckoutForSubmittingTransactionCompletionHandler:^(OPPTransaction * _Nullable transaction, NSError * _Nullable error) {
   if (transaction.type == OPPTransactionTypeSynchronous)  {
       // Send request to your server to obtain the status of the synchronous transaction
       // You can use transaction.resourcePath or just checkout id to do it
       // Error is no more an decisive factor for transaction termination
       if (transaction.resourcePath) {
             // get the payment status using the resourcePath
       }
    } else {
        // The SDK opens transaction.redirectUrl in a browser
        // See 'Asynchronous Payments' guide for more details
    }
} cancelHandler:^{
    // Executed if the shopper closes the payment page prematurely
}];

For SDK versions 2.12.0 and earlier, use an alternative method to open the checkout page. The paymentBrandSelectedHandler callback lets you create and pass a new checkout ID to the SDK. Newer versions move this callback to OPPCheckoutProviderDelegate. Use this feature to send more parameters at the first step for specific payment brands. Otherwise, set the callback to nil.

[checkoutProvider presentCheckoutForSubmittingTransactionCompletionHandler:^(OPPTransaction * _Nullable transaction, NSError * _Nullable error) {
    // Handle transaction submitting result as shown in previous sample
} paymentBrandSelectedHandler:nil cancelHandler:^{
    // Executed if the shopper closes the payment page prematurely
}];
checkoutProvider?.presentCheckout(forSubmittingTransactionCompletionHandler: { (transaction, error) in
    // Handle transaction submitting result as shown in previous sample
}, paymentBrandSelectedHandler:nil, cancelHandler: { 
    // Executed if the shopper closes the payment page prematurely
})
📘

Close the payment page when the app moves to the background to protect the shopper's credit card data. Use dismissCheckoutAnimated:completion: of OPPCheckoutProvider.

4. [Optional] Enhance security by disabling third-party keyboards

A malicious third-party keyboard may log keystrokes and transfer sensitive data. Disabling third-party keyboards can enhance security but may impact user experience.

- (BOOL)application:(UIApplication *)application shouldAllowExtensionPointIdentifier:(UIApplicationExtensionPointIdentifier)extensionPointIdentifier {
    if (extensionPointIdentifier == UIApplicationKeyboardExtensionPointIdentifier) {
        return NO;
    }
    return YES;
}
func application(_ application: UIApplication, shouldAllowExtensionPointIdentifier extensionPointIdentifier: UIApplicationExtensionPointIdentifier) -> Bool {
    if (extensionPointIdentifier == UIApplicationExtensionPointIdentifier.keyboard) {
        return false
    }
    return true
}

Get the payment status

The app must request the payment status from the server for any result except cancelled.

NSString *URL = [NSString stringWithFormat:@"https://YOUR_URL/paymentStatus?resourcePath=%@", 
  [transaction.resourcePath stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
  
NSURLRequest *merchantServerRequest = [[NSURLRequest alloc] initWithURL:[NSURL URLWithString:URL]];

[[[NSURLSession sharedSession] dataTaskWithRequest:merchantServerRequest 
    completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
    // Handle error
    NSDictionary *result = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:nil];
    BOOL status = [result[@"paymentResult"] boolValue];
}] resume];
let url = String(format: "https://YOUR_URL/paymentStatus?resourcePath=%@", transaction.resourcePath!.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!)
let merchantServerRequest = NSURLRequest(url: URL(string: url)!)
URLSession.shared.dataTask(with: request) { data, response, error in
    if let data = data, let json = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
        let transactionStatus = json?["paymentResult"] as? Bool
    }
}.resume()
📘

The transaction status may not be available immediately. Refer to the Response codes section for all possible statuses.

Validate payment response against order details

Before confirming the order to the customer, validate that the payment response matches the original order details. This validation might include:

  • Order ID
  • Amount
  • Product ID or SKU
  • Currency

This validation ensures that the payment authorisation corresponds to the correct product and prevents mismatches due to potential manipulation or technical issues.

⚠️

If the validation fails, do not confirm the order. Instead, initiate an investigation or notify the customer.

Testing

The sandbox environment accepts test payment parameters.

Find test values for credit cards and bank accounts in the Testing guide.

Move to production

  1. Contact support to get live credentials.

  2. Set the server type to LIVE when initialising OPPPaymentProvider.

    self.provider = [OPPPaymentProvider paymentProviderWithMode:OPPProviderModeLive];
    let provider = OPPPaymentProvider(mode: OPPProviderMode.live)
  3. Update the backend to use the correct API endpoints and credentials.

Android

For Android devices the below guide provides information on using ready-to-use payment forms.

Requirements

  • Android 7.0 (API Level 24) and up
  • AndroidX libraries compatibility
  • Java 8 compatibility

Install the Mobile SDK

  1. Add the Mobile SDK for Android.

  2. Drag oppwa.mobile.aar & ipworks3ds_sdk.aar (use ipworks3ds_sdk_deploy.aar for production) to the libs folder of the module where you plan to integrate the Mobile SDK.

  3. In the build.gradle file of this module, add:

    implementation fileTree(dir: "libs", include: ["*.aar"])
  4. Add the required dependencies to your build.gradle. Refer to the dependencies.txt provided in the release. It contains the list of required dependencies for the Mobile SDK.

  5. Enable the viewBinding feature:

    buildFeatures {
        viewBinding true
    }

Set up your server

To start working with the SDK, expose two APIs on your backend for your Android app to communicate with:

  • Endpoint 1: Creating a checkout ID
  • Endpoint 2: Getting the result of payment

Refer to the set up your server guide for detailed instructions.

Request Checkout ID

Your app should request a checkout ID from your server. This example uses the sample integration server; adapt it to your own backend API.

public String requestCheckoutId() {
    URL url;
    String urlString;
    HttpURLConnection connection = null;
    String checkoutId = null;

    urlString = YOUR_URL + "?amount=48.99&currency=EUR&paymentType=DB";

    try {
        url = new URL(urlString);
        connection = (HttpURLConnection) url.openConnection();
        
        JsonReader reader = new JsonReader(new InputStreamReader(connection.getInputStream(), "UTF-8"));
        reader.beginObject();

        while (reader.hasNext()) {
            if (reader.nextName().equals("checkoutId")) {
                checkoutId = reader.nextString();
                break;
            }
        }

        reader.endObject();
        reader.close();
    } catch (Exception e) {
        // error occurred
    } finally {
        if (connection != null) {
            connection.disconnect();
        }
    }

    return checkoutId;
}
fun requestCheckoutId(): String? { 
    val url: URL 
    var connection: HttpURLConnection? = null 
    var checkoutId: String? = null 

    val urlString = YOUR_URL.toString() + "?amount=48.99&currency=EUR&paymentType=DB" 
      
    try { 
        url = URL(urlString) 
        connection = url.openConnection() as HttpURLConnection 

        val reader = JsonReader(InputStreamReader(connection.inputStream, "UTF-8"))
        reader.beginObject() 

        while (reader.hasNext()) { 
            if (reader.nextName() == "checkoutId") { 
                checkoutId = reader.nextString() 

                break 
            } 
        } 

        reader.endObject() 
        reader.close() 
    } catch (e: Exception) { 
        /* error occurred */ 
    } finally { 
        connection?.disconnect() 
    } 

    return checkoutId 
}

Present Checkout UI

1. Configure the checkout settings

Define the settings for the checkout screen. Initialise CheckoutSettings with the received checkout ID. It controls the information displayed to the shopper. For the first integration, enable synchronous payment methods. Other payment brands need extra configuration.

Set<String> paymentBrands = new LinkedHashSet<String>();

paymentBrands.add("VISA");
paymentBrands.add("MASTER");
paymentBrands.add("DIRECTDEBIT_SEPA");

CheckoutSettings checkoutSettings = new CheckoutSettings(checkoutId, paymentBrands, Connect.ProviderMode.TEST);
// since mSDK version 6.0.0, the shopper result URL is not required
checkoutSettings.setShopperResultUrl("companyname://result");
val paymentBrands = hashSetOf("VISA", "MASTER", "DIRECTDEBIT_SEPA") 
 
val checkoutSettings = CheckoutSettings(checkoutId, paymentBrands, Connect.ProviderMode.TEST)
// since mSDK version 6.0.0 the shopper result URL is not required 
checkoutSettings.shopperResultUrl = "companyname://result"

2. Present the checkout page

Start the checkout activity and handle the result.

public class MainActivity extends AppCompatActivity {

    private final ActivityResultLauncher checkoutLauncher = registerForActivityResult(
        new CheckoutActivityResultContract(),
        this::handleCheckoutResult
    );
    
    private void startCheckout() {
        checkoutLauncher.launch(checkoutSettings);
    }
    
    private void handleCheckoutResult(@NonNull CheckoutActivityResult result) {
        if (result.isCanceled()) {
            // shopper cancelled the checkout process
            return;
        }

        String resourcePath = result.getResourcePath();

        if (resourcePath != null) {
            // get the payment status using the resourcePath
        }
    }
}
class MainActivity : AppCompatActivity() {

    private val checkoutLauncher = registerForActivityResult(CheckoutActivityResultContract()) {
        result: CheckoutActivityResult -> handleCheckoutResult(result)
    }

    private fun startCheckout() {
        checkoutLauncher.launch(checkoutSettings)
    }

    private fun handleCheckoutResult(result: CheckoutActivityResult) {
        if (result.isCanceled) {
            // shopper cancelled the checkout process
            return
        }

        val resourcePath = result.resourcePath

        if (resourcePath != null) {
            // get the payment status using the resourcePath
        }
    }
}
📘

You can use the getErrorInfo() method of PaymentError to get more details about the error.

Get the payment status

Your app should request the payment status from your server for any result except cancelled.

public String requestPaymentStatus() {
    URL url;
    String urlString;
    HttpURLConnection connection = null;
    String paymentStatus = null;

    urlString = YOUR_URL + "/paymentStatus?resourcePath=" + URLEncoder.encode(RESOURCE_PATH, "UTF-8");

    try {
        url = new URL(urlString);
        connection = (HttpURLConnection) url.openConnection();

        JsonReader jsonReader = new JsonReader(new InputStreamReader(connection.getInputStream(), "UTF-8"));
        jsonReader.beginObject();

        while (jsonReader.hasNext()) {
            if (jsonReader.nextName().equals("paymentResult")) {
                paymentStatus = jsonReader.nextString();
                break;
            }
        }

        jsonReader.endObject();
        jsonReader.close();
    } catch (Exception e) {
        // error occurred
    } finally {
        if (connection != null) {
            connection.disconnect();
        }
    }

    return paymentStatus;
}
fun requestPaymentStatus(): String? { 
    val url: URL 
    var connection: HttpURLConnection? = null 
    var paymentStatus: String? = null 

    val urlString = YOUR_URL.toString() + "/paymentStatus?resourcePath=" + URLEncoder.encode(RESOURCE_PATH, "UTF-8") 

    try { 
    url = URL(urlString) 
        connection = url.openConnection() as HttpURLConnection 

        val jsonReader = JsonReader(InputStreamReader(connection.inputStream, "UTF-8"))     
        jsonReader.beginObject() 

        while (jsonReader.hasNext()) { 
            if (jsonReader.nextName() == "paymentResult") { 
                paymentStatus = jsonReader.nextString() 

                break 
            } 
        } 

        jsonReader.endObject() 
        jsonReader.close() 
    } catch (e: Exception) { 
        /* error occurred */ 
    } finally { 
        connection?.disconnect() 
    } 

    return paymentStatus 
}
📘

The transaction status may not be available immediately. To support all statuses, refer to the response codes documentation.

Validate payment response against order details

Before confirming the order to the customer, validate that the payment response matches the original order details. This validation might include:

  • Order ID
  • Amount
  • Product ID or SKU
  • Currency

This validation ensures that the payment authorisation corresponds to the correct product and prevents mismatches due to potential manipulation or technical issues.

⚠️

If the validation fails, do not confirm the order. Instead, initiate an investigation or notify the customer.

Testing

The sandbox environment accepts test payment parameters. You can find test values for credit cards and bank accounts in the testing guide.

Go to production

  1. Contact support to get the live credentials.

  2. Adjust the server type to LIVE in your initialisation of CheckoutSettings.

    CheckoutSettings checkoutSettings = new CheckoutSettings(checkoutId, paymentBrands, Connect.ProviderMode.LIVE);
    val checkoutSettings = CheckoutSettings(checkoutId, paymentBrand, Connect.ProviderMode.LIVE)
  3. Change your backend to use the correct API endpoints and credentials.