Ready-to-use UI

Ready-to-use checkout screens speed up development. This removes the burden of designing and implementing checkout screens. These screens define a customisable checkout flow that integrates well into your application.

The table below helps choose the solution that works best.

Drop-In CheckoutDrop-in forms and payment buttons
Integration steps1. Set list of supported payment brands
2. Present checkout screens
Integration guide
1. Create payment button
2. Present checkout for specific payment brand
Integration guide
First presented screenList of payment brands and stored cards (optionally)Depends on specific payment brand:
- Payment form
- Web page
- Modal view
Benefits1. Ready screen with list of payment brands1. More flexible solution, use drop-in buttons or any other controls to display payment brands
2. The best way to add specific payment brand to your app

Payment methods

Synchronous payments

For synchronous payments, the app collects the shopper's payment details without redirects for checks or authentication.

To integrate the SDK with prebuilt checkout forms, follow the first integration guide.

Asynchronous payments

Some payment methods require shopper verification (3-D Secure) or account authentication. The shopper redirects from the payment form to the issuer page to verify identity.

See how to configure the app to return from app switches in the asynchronous payments guide.

Checkout screens customisation

UI customisation

The Checkout project allows defining appearance from the default theme by applying custom attributes.

Find the full reference in the customisation guide.

Internationalisation

The ready-to-use UI supports multiple languages. Labels are customisable, allowing translations for unsupported languages (including right-to-left).

Find the full list of supported languages and instructions in the internationalisation guide.

See more customisations in the advanced options guide.

Fast checkout via drop-in buttons

Drop-in buttons make it easy to integrate specific payment brands into your app with minimal effort. They offer a fast checkout flow, reducing steps and improving user experience.

You should use them for fast, brand-specific integrations without custom development.

Ideal for apps with a single payment brand or those adding new brands quickly. The best way to enable trusted payment options and boost conversion rates.

Before adding buttons, install the Mobile SDK and set up the server. Then follow this guide.

iOS

Create payment button

The easiest way to create a payment button is by adding it in your layout's xml. The payment button needs a payment brand to display the appropriate logo. You can set your own logo for the button and do all necessary customisations as well.

  1. Drag a standard button to the view. Set the class OPPPaymentButton in the Identity Inspector tab.

  2. Create an outlet for the button in the view controller.

    @property (nonatomic) IBOutlet OPPPaymentButton *paymentButton;
    @IBOutlet var paymentButton: OPPPaymentButton!
  3. Set the payment brand in viewDidLoad.

    - (void)viewDidLoad {
        [super viewDidLoad];
        self.paymentButton.paymentBrand = @"VISA";
    }
     override func viewDidLoad() {
         super.viewDidLoad()
         self.paymentButton.paymentBrand = "VISA"
     }
  4. Set a custom logo and background image.

    [self.paymentButton setImage:[UIImage imageNamed:@"logo"] forState:UIControlStateNormal];
    [self.paymentButton setBackgroundImage:[UIImage imageNamed:@"bg"] forState:UIControlStateNormal];
    Card payment button
    self.paymentButton.setImage(UIImage.init(named: "logo"), for: .normal)
    self.paymentButton.setBackgroundImage(UIImage.init(named: "bg"), for: .normal)
    ```
📘

Customise the image before setting a payment brand. Otherwise, the default brand logo might overwrite the custom image.

Card payment button

A payment button can work as a card payment button regardless of the card brand. The app detects the payment brand based on the card number. Set the payment brand as "CARD".

self.paymentButton.paymentBrand = @"CARD";
self.paymentButton.paymentBrand = "CARD"

Use button action method

Add an action method to the view controller.

- (IBAction)paymentButtonAction:(id)sender {
    OPPPaymentButton *button = (OPPPaymentButton *)sender;

    // Request checkout ID from the server
    // Configure OPPCheckoutProvider
    // Present checkout for the payment brand and implement callbacks
}
@IBAction func paymentButtonAction(_ sender: Any) {
    // button will be used to get payment brand
    let button = sender as! OPPPaymentButton
    
    // request checkout ID from your server
    // configure OPPCheckoutProvider
    // present checkout for specific payment brand and implement callbacks
}

Request checkout ID

To start a payment, create a checkout ID by requesting it from the server. This example uses a sample integration server. Adapt it to use a custom backend 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) {
    // 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 json = try? JSONSerialization.jsonObject(with: data!, options: []) as? [String: Any] {
        let checkoutID = json?["checkoutId"] as? String
    }
}.resume()

Create and configure checkout provider

You must start OPPCheckoutProvider with a payment provider, checkout ID, and checkout settings.

OPPPaymentProvider *provider = [OPPPaymentProvider paymentProviderWithMode:OPPProviderModeTest];

OPPCheckoutSettings *checkoutSettings = [[OPPCheckoutSettings alloc] init];
checkoutSettings.paymentBrands = @[@"VISA"];
checkoutSettings.shopperResultURL = @"com.companyname.appname.payments://result";

OPPCheckoutProvider *checkoutProvider = [OPPCheckoutProvider checkoutProviderWithPaymentProvider:provider
                                                                                      checkoutID:checkoutID
                                                                                        settings:checkoutSettings];
let provider = OPPPaymentProvider(mode: OPPProviderMode.test)

let checkoutSettings = OPPCheckoutSettings()
checkoutSettings.paymentBrands = ["VISA"]
checkoutSettings.shopperResultURL = "com.companyname.appname.payments://result"
let checkoutProvider = OPPCheckoutProvider(paymentProvider: provider, checkoutID: checkoutID!, settings: checkoutSettings)
📘

Some payment brands do not need extra user input in the app. The shopper redirects directly to the issuer's web page (example: PayPal). In this case, checkout settings are unnecessary.

Tokenisation

The checkout page can display stored tokens for a selected payment brand. Add an extra parameter to the checkout request (step 1). Your server should send the shopper's tokens along with other data such as amount, currency, and order type.

📘

The checkout page automatically displays the first token for the selected payment brand.

Present checkout and use callbacks

Present checkout for a payment brand using the OPPCheckoutProvider API.

The first screen depends on the payment brand:

  • Payment form (credit cards): If the shopper has stored tokens for the selected payment brand, the first token appears on the screen.
  • Web page (for asynchronous payments, see the asynchronous payments guide).
[checkoutProvider presentCheckoutWithPaymentBrand:button.paymentBrand 
  loadingHandler:^(BOOL inProgress) {
    // Executed whenever SDK sends request to the server or receives the response. 
    // You can start or stop loading animation based on inProgress parameter.
} completionHandler:^(OPPTransaction * _Nullable transaction, NSError * _Nullable error) {
   if (transaction.type == OPPTransactionTypeAsynchronous) {
        // Open transaction.redirectURL in Safari browser to complete the transaction
   }  else if (transaction.type == OPPTransactionTypeSynchronous) {
       // Send request to your server to obtain transaction status

       if (transaction.resourcePath) {
          // Get the payment status using the resourcePath.
       }
   }
} cancelHandler:^{
    // Executed if the shopper closes the payment page prematurely.
}];
checkoutProvider?.presentCheckout(withPaymentBrand: button.paymentBrand, 
   loadingHandler: { (inProgress) in
    // Executed whenever SDK sends request to the server or receives the answer. 
    // You can start or stop loading animation based on inProgress parameter.
}, completionHandler: { (transaction, error) in
    if transaction.type == .asynchronous {
         // Open transaction.redirectURL in Safari browser to complete the transaction
    } else if transaction.type == .synchronous {
        // Send request to your server to obtain transaction status
        if transaction.resourcePath {
           // Get the payment status using the resourcePath.

        }
    }
}, cancelHandler: { 
    // Executed if the shopper closes the payment page prematurely.
})

Get the payment status

Request the payment status from your server. Send either the checkout ID or resource path based on a chosen approach.

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

Testing

The sandbox environment accepts test payment parameters.

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

Go to production

  1. Contact support to get live credentials.

  2. Adjust 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.

Apple Pay button types

Apple Pay provides multiple button types and styles. Starting from iOS 14 and macOS 11, the system appearance can determine the button style. The checkoutSettings.applePayType property customises the button type on the checkout payment selection screen but not for direct payment button integration.

The Apple Pay button type continue is available for iOS 15 and above.

// Apple Pay button type set to Buy
checkoutSettings.applePayType = PKPaymentButtonTypeBuy;
//Apple Pay button type is set to Buy
checkoutSettings.applePayType = PKPaymentButtonType.buy
📘

The Peach Payments system sets PKPaymentButtonTypePlain by default. Change the button type by setting the checkoutSettings.applePayType property to any PKPaymentButtonType value. See the Objective-C and Swift Apple Pay button types documentation for more details.

Android

Create payment button

The easiest way to create a payment button is to add it in your layout's XML. Set a payment brand to display the appropriate logo automatically. You can also set your own logo and customise the button as needed.

  1. To add PaymentButtonFragment to your activity you can do this in two ways.

    Declare it inside the activity's layout file:

    <fragment
        android:name="com.oppwa.mobile.connect.checkout.dialog.PaymentButtonFragment"
        android:id="@+id/payment_button_fragment"
        android:layout_margin="10dp"
        android:layout_width="100dp"
        android:layout_height="65dp"/>
    PaymentButtonFragment paymentButtonFragment =
            (PaymentButtonFragment) getSupportFragmentManager().findFragmentById(R.id.payment_button_fragment);
    val paymentButtonFragment = supportFragmentManager.findFragmentById(
            R.id.payment_button_fragment) as PaymentButtonFragment

    Or, programmatically add the fragment to an existing ViewGroup:

    PaymentButtonFragment paymentButtonFragment = new PaymentButtonFragment();
    getSupportFragmentManager()
        .beginTransaction()
        .add(R.id.payment_button_fragment, paymentButtonFragment)
        .commit();
    val paymentButtonFragment = PaymentButtonFragment()
    supportFragmentManager.beginTransaction() 
            .add(R.id.payment_button_fragment, paymentButtonFragment) 
            .commit()
  2. Set payment brand and add OnClickListener:

    paymentButtonFragment.setPaymentBrand("PAYPAL");
    paymentButtonFragment.getPaymentButton().setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            /* request new checkout ID */
        }
    });
    paymentButtonFragment.paymentBrand = "PAYPAL" 
    paymentButtonFragment.paymentButton.setOnClickListener {  
        /* request new checkout id */ 
    }
    📘

    If you use Google Pay as a payment option, add this before setting the payment brand: paymentButtonFragment.setPaymentButtonIntegrationMode(PaymentButtonIntegrationMode.PAYMENT_OPTION);

  3. Customise the payment button if needed

    paymentButtonFragment.getPaymentButton().setImageResource(R.drawable.image_resource);
    paymentButtonFragment.getPaymentButton().setBackgroundResource(R.drawable.background_resource);
    paymentButtonFragment.paymentButton.apply {
        setImageResource(R.drawable.image_resource) 
        setBackgroundResource(R.drawable.button_base_background) 
    }

Card payment button

Use the payment button as a card payment button regardless of the card brands. It detects the payment brand based on the card number. Set the payment brand as CARD for the card payment button. Brand detection works for payment brands sent in CheckoutSettings.

paymentButtonFragment.setPaymentBrand("CARD");
paymentButtonFragment.paymentBrand = "CARD"

Tokenisation

The Mobile SDK checkout page can display a stored token for the selected payment brand. Add an extra parameter to the normal checkout request. Your server should send shoppers' tokens along with other configuration data such as amount, currency, order type, and so on.

📘

The Mobile SDK automatically displays the first token for the selected payment brand.

Request checkout ID

Your app should request a checkout ID from your server. This example uses a sample integration server. Adapt it to use your 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 
}

Submit transaction

Create CheckoutSettings. You can configure the payment brands the shopper can use for payment in addition to the payment brand set in setPaymentBrand:

Set<String> paymentBrands = new LinkedHashSet<String>();
paymentBrands.add("VISA");
paymentBrands.add("MASTER");

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") 
 
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"

Or leave it empty:

CheckoutSettings checkoutSettings = new CheckoutSettings(checkoutId, null, Connect.ProviderMode.TEST);

Submit the transaction and handle CheckoutActivityResult.

public class MainActivity extends AppCompatActivity {

    private final ActivityResultLauncher checkoutLauncher = registerForActivityResult(
        new CheckoutActivityResultContract(),
        this::handleCheckoutResult
    );

    private void submitTransaction() {
        try {
            // set the ActivityResultLauncher to handle CheckoutActivityResult
            paymentButtonFragment.setActivityResultLauncher(checkoutLauncher);
            paymentButtonFragment.submitTransaction(checkoutSettings);
        } catch (PaymentException e) {
            // error occurred
        }
    }

    private void handleCheckoutResult(@NonNull CheckoutActivityResult result) {
        if (result.isCanceled()) {
            // shopper cancelled the checkout process
            return;
        }
        
        String resourcePath = result.getResourcePath();

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

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

    private fun submitTransaction() {
        try { 
            // set the ActivityResultLauncher to handle CheckoutActivityResult
            paymentButtonFragment.setActivityResultLauncher(checkoutLauncher)
            paymentButtonFragment.submitTransaction(checkoutSettings)
        } catch (e: PaymentException) { 
             // error occurred
        }
    }

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

        val resourcePath = result.resourcePath

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

For async transactions, override the onNewIntent method of your activity:

@Override
protected void onNewIntent(Intent intent) {
    super.onNewIntent(intent);

    if (intent.getScheme().equals("YOUR_SCHEME")) {
        /* request payment status */
    }
}
override fun onNewIntent(intent: Intent) { 
    super.onNewIntent(intent) 
     
    if (intent.scheme == "YOUR_SCHEME") { 
        /* request payment status */ 
    } 
}

Receiving callbacks during checkout (optional)

Mobile SDK provides you with the callback before the payment is submitted. The callback section provides more details on how to implement callbacks.

Use the OnBeforeSubmitCallback interface to listen to callbacks:

public class CheckoutOnBeforeSubmitListener implements OnBeforeSubmitCallback {
    @Override
    public void onBeforeSubmit(@NonNull PaymentDetails paymentDetails, @NonNull Listener callback) {      
         
    }
}
class CheckoutOnBeforeSubmitListener : OnBeforeSubmitCallback {
    override fun onBeforeSubmit(paymentDetails: PaymentDetails, callback: Listener) {
        
    }
}

Request payment status

Lastly, your app should request the payment status from your server. Send either the checkout ID or resource path based on a chosen approach.

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

    urlString = YOUR_URL + "/paymentStatus/" + CHECKOUT_ID;

    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 
}

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 live credentials.

  2. Adjust the server type to LIVE:

    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.

Customise the checkout screens

Peach Payments has prepared two ready-to-use styles for checkout screens. Define your own appearance based on one of the default themes and apply the attributes you want to customise.

iOS

The Checkout project includes two ready-to-use styles:

  • Light theme: default (OPPCheckoutThemeStyleLight)
  • Dark theme (OPPCheckoutThemeStyleDark)

Set one of the default themes and override specific properties:

OPPCheckoutSettings *settings = [[OPPCheckoutSettings alloc] init];
settings.theme.style = OPPPaymentPageThemeStyleLight;
settings.theme.confirmationButtonColor = [UIColor greenColor];
let settings = OPPCheckoutSettings()
settings.theme.style = .light
settings.theme.confirmationButtonColor = UIColor.green

See below the list of properties you can customise and how they change the checkout UI:

Attribute nameTypeDescription
primaryBackgroundColorUIColorBackground colour for all views in this theme
primaryForegroundColorUIColorText colour for important labels in a view
textFieldBackroundColorUIColorBackground colour for text fields
textFieldTextColorUIColorInput field text colour
textFieldPlaceholderColorUIColorPlaceholder colour for text fields
confirmationButtonColorUIColorColour of the confirmation buttons
confirmationButtonTextColorUIColorText colour for confirmation buttons
sectionBackgroundColorUIColorBackground colour of sections
sectionTextColorUIColorText colour of section titles
cellHighlightedBackgroundColorUIColorHighlighted background colour for table view cells in the payment brands list
cellHighlightedTextColorUIColorHighlighted text colour for table view cells
cellTintColorUIColorTint colour for table view cells
separatorColorUIColorColour for separator lines
paymentBrandIconBackgroundColorUIColorBackground colour for payment brand icons
paymentBrandIconBorderColorUIColorBorder colour for payment brand icons
storedPaymentMethodIconBackgroundColorUIColorBackground colour for stored payment method icons
storedPaymentMethodIconBorderColorUIColorBorder colour for stored payment method icons
errorColorUIColorColour for rendering error messages or views
primaryFontUIFontText font for important labels in a view
secondaryFontUIFontText font for other labels in a view
confirmationButtonFontUIFontText font for the confirmation button
sectionFontUIFontText font for section titles
cellTextFontUIFontText font for table view cells
errorFontUIFontText font for error messages
activityIndicatorPrimaryStyleUIActivityIndicatorViewStyleIndicator view processing style
navigationBarTintColorUIColorNavigation bar tint colour
navigationBarBackgroundColorUIColorNavigation bar background colour
navigationBarTextAttributesNSDictionaryText attributes for the navigation bar title
navigationItemTextAttributesNSDictionaryText attributes for navigation bar item titles
cancelBarButtonTextNSStringCustom text for the right bar button in the navigation bar (default: 'Cancel')
cancelBarButtonImageUIImageCustom image for the right bar button in the navigation bar. If you do not set the image, the text button appears (cancelBarButtonText)
OPPPaymentPageSettings *settings = [[OPPPaymentPageSettings alloc] init];

// General colours of the checkout UI
settings.theme.primaryBackgroundColor = [UIColor whiteColor];
settings.theme.primaryForegroundColor = [UIColor blackColor];
settings.theme.confirmationButtonColor = [UIColor orangeColor];
settings.theme.confirmationButtonTextColor = [UIColor whiteColor];
settings.theme.errorColor = [UIColor redColor];
settings.theme.separatorColor = [UIColor lightGrayColor];

// Navigation bar customisation
settings.theme.navigationBarTintColor = [UIColor whiteColor];
settings.theme.navigationBarBackgroundColor = [UIColor orangeColor];
settings.theme.navigationBarTextAttributes = @{NSForegroundColorAttributeName: [UIColor whiteColor]};
settings.theme.navigationItemTextAttributes = @{NSForegroundColorAttributeName: [UIColor whiteColor]};
settings.theme.cancelBarButtonImage = [UIImage imageNamed:@"shopping_cart_black_icon"];

// Payment brands list customisation
settings.theme.cellHighlightedBackgroundColor = [UIColor orangeColor];
settings.theme.cellHighlightedTextColor = [UIColor whiteColor];

// Fonts customisation
settings.theme.primaryFont = [UIFont systemFontOfSize:14.0];
settings.theme.secondaryFont = [UIFont systemFontOfSize:12.0];
settings.theme.confirmationButtonFont = [UIFont systemFontOfSize:15.0];
settings.theme.errorFont = [UIFont systemFontOfSize:12.0];

let settings = OPPCheckoutSettings()

// General colors of the checkout UI
settings.theme.primaryBackgroundColor = UIColor.white
settings.theme.primaryForegroundColor = UIColor.black
settings.theme.confirmationButtonColor = UIColor.orange
settings.theme.confirmationButtonTextColor = UIColor.white
settings.theme.errorColor = UIColor.red
settings.theme.activityIndicatorStyle = .gray;
settings.theme.separatorColor = UIColor.lightGray

// Navigation bar customization
settings.theme.navigationBarTintColor = UIColor.white
settings.theme.navigationBarBackgroundColor = UIColor.orange
settings.theme.navigationBarTextAttributes = [NSForegroundColorAttributeName: UIColor.white]
settings.theme.navigationItemTextAttributes = [NSForegroundColorAttributeName: UIColor.white]
settings.theme.cancelBarButtonImage = UIImage(named: "shopping_cart_black_icon")

// Payment brands list customization
settings.theme.cellHighlightedBackgroundColor = UIColor.orange
settings.theme.cellHighlightedTextColor = UIColor.white

// Fonts customization
settings.theme.primaryFont = UIFont.systemFont(ofSize: 14.0)
settings.theme.secondaryFont = UIFont.systemFont(ofSize: 12.0)
settings.theme.confirmationButtonFont = UIFont.systemFont(ofSize: 15.0)
settings.theme.errorFont = UIFont.systemFont(ofSize: 12.0)

Android

The Checkout project uses styles to customise the UI. A style is a collection of properties that define the look and format of a View or window. A style can specify properties such as height, padding, font colour, font size, background colour, and more. Define a style in an XML resource separate from the XML that specifies the layout.

The Checkout project includes two ready-to-use styles:

  • Light theme (Theme.Checkout.Light)
  • Dark theme (Theme.Checkout.Dark)

Customise these styles or create a new style based on the Checkout style. To apply a style, declare it in the AndroidManifest.xml file within the Checkout activity tag:

<activity
    android:name="com.oppwa.mobile.connect.checkout.dialog.CheckoutActivity"
    tools:replace="android:theme"
    android:theme="@style/Theme.Checkout.Light"/>
📘

Set the tools:replace="android:theme" attribute for CheckoutActivity. This attribute belongs to the Android tools namespace, so declare this namespace in the <manifest> element: xmlns:tools="http://schemas.android.com/tools". If you use a custom style, extend the Theme.Checkout style.

Customise the checkout theme by overriding checkout_color_accent. Add it to your colour resources:

<resources>
    <color name="checkout_color_accent">#29b6f6</color>
</resources>

For deeper customisation, override attributes from the predefined styles. See below the list of properties you can customise and how they change the checkout UI:

Attribute nameTypeDescription
cancelButtonDrawablereferenceImage of the cancel button
listDisclosureDrawablereferenceImage of the list disclosure
listMarkDrawablereferenceMark of the selected list item
cameraDrawablereferenceImage of the camera button in the card payment information form
cancelButtonTintColorcolourColour of the cancel button drawable
listDisclosureTintColorcolourColour of the list disclosure drawable
listMarkTintColorcolourColour of the list item mark drawable
cameraTintColorcolourColour of the camera icon drawable
headerBackgroundcolourBackground of the header (colour or image)
windowBackgroundcolourBackground of the window (colour or image)
checkoutTextViewStylereferenceBase style of the checkout text view
checkoutHelperTextViewStylereferenceStyle of the helper text view
checkoutSectionTitleTextViewStylereferenceStyle of the payment list section title (For example, 'Stored Payment Methods')
checkoutProgressTextViewStylereferenceStyle of the progress text view
checkoutEditTextStylereferenceBase style of the checkout edit text
checkoutTokenImageViewStylereferenceStyle of the payment token icon image view
checkoutMethodImageViewStylereferenceStyle of the payment brand icon image view
checkoutButtonStylereferenceStyle of the pay button
checkoutListViewStylereferenceStyle of the list view
paymentMethodListItemSelectorreferenceSelector for the payment methods list
checkoutProgressBarStylereferenceStyle of the progress bar
typefacestringName of the .ttf font file in the assets/fonts directory (without the file extension)

Define a custom style based on Checkout.Light in the /res/values/styles.xml file:

<style name="NewCheckoutTheme" parent="Theme.Checkout.Light">
    <item name="headerBackground">@color/checkout_header_color</item>
    <item name="cancelButtonTintColor">@android:color/black</item>
    <item name="listMarkTintColor">@color/accent_color</item>
    <item name="cameraTintColor">@color/accent_color</item>
    <item name="checkboxButtonTintColor">@color/accent_color</item>
    <item name="paymentMethodListItemSelector">@drawable/checkout_list_item_selector</item>
    <item name="checkoutButtonStyle">@style/NewCheckoutTheme.Button</item>
</style>

<style name="NewCheckoutTheme.Button" parent="Checkout.Button.Light">
    <item name="android:background">@color/accent_color</item>
</style>

Internationalisation

iOS

The ready-to-use UI supports these languages:

  • English (en)
  • Arabic (ar)
  • Basque (eu-ES)
  • Catalan (ca)
  • Chinese Simplified (zh-Hans)
  • Croatian (hr)
  • Czech (cs)
  • Danish (da)
  • Dutch (nl)
  • Dutch Belgium (nl-BE)
  • Finnish (fi)
  • French (fr-FR)
  • French Belgium (fr-BE)
  • French Canada (fr-CA)
  • Galician (gl-ES)
  • German (de)
  • German Austria (de-AT)
  • Hungarian (hu)
  • Italian (it)
  • Italian Switzerland (it-CH)
  • Japanese (ja)
  • Korean (ko)
  • Norwegian Bokmål (nb)
  • Polish (pl)
  • Portuguese (pt)
  • Romanian (ro)
  • Russian (ru)
  • Serbian (sr)
  • Slovak (sk)
  • Slovenian (sl)
  • Spanish (es)
  • Swedish (sv)
  • Thai (th)

Set the checkout language

Checkout uses the user's preferred language if available. If not, it defaults to English.

To set a specific language, use OPPCheckoutSettings with the two-letter ISO 639-1 code. Do not set this property to use the default language.

OPPCheckoutSettings *settings = [[OPPCheckoutSettings alloc] init];
settings.language = @"de";
let settings = OPPCheckoutSettings()
settings.language = "de"

Change translations for supported languages

Since version 3.0.0 you can change any label of Ready-to-use UI by overriding them in your app.

📘

If you still use an older version, you should change string files in the OPPWAMobile-Resources.bundle.

  1. Create OPPCheckout.strings file in your project:

    1. In Xcode, select File > New > File....
    2. Choose Strings File from the iOS Resource category and click Next.
    3. Name the file OPPCheckout and click Create.
  2. Add new strings to this file with the specific keys, following the sample below (base language).

    "VIEW_CONTROLLER_TITLE_PAYMENT_METHOD_SELECTION" = "Select payment method";
    "VIEW_CONTROLLER_TITLE_PAYMENT_DETAILS" = "Payment details";
    "VIEW_CONTROLLER_TITLE_PROCESSING" = "Processing";

    See the full list of available keys at the end of this guide.

  3. To support multiple languages:

    If you need to support multiple locations, select your OPPCheckout.strings file and click Localise in the right pane. The Localisation options show all project languages. Select the missing language to create a new variant of OPPCheckout.strings. Add translations in the new file. The SDK strings files provide any missing labels.

📘

If you don't see required language in Localization options, make sure you added the language to the project.

Add a new language

To add a missing language, update the Localisation options for OPPCheckout.strings and provide translations. If a key is missing, the system uses English.

Localising payment method labels

To localise payment methods, add a translation with an appropriate key which is concatenation of prefix PAYMENT_METHOD_TYPE_ and brand constant, for example, MASTER.

"PAYMENT_METHOD_TYPE_MASTER" = "MasterCard";
"PAYMENT_METHOD_TYPE_AMEX" = "American Express";

Localisable strings

/// View controller titles
"VIEW_CONTROLLER_TITLE_PAYMENT_METHOD_SELECTION" = "Select Payment Method";
"VIEW_CONTROLLER_TITLE_PAYMENT_DETAILS" = "Payment Details";
"VIEW_CONTROLLER_TITLE_PROCESSING" = "Processing";
"VIEW_CONTROLLER_TITLE_ADDRESS" = "Billing Address";

/// Form titles
"FORM_TITLE_PAYMENT_INFO" = "Payment Info";

/// Labels
"LABEL_TOTAL_AMOUNT" = "Total Amount";
"LABEL_MBWAY_PUSH_NOTIFICATION" = "A push notification will be sent to your device.";
"LABEL_MBWAY_ACCEPT_PIN" = "Please accept the transaction with your PIN MB WAY.";
"LABEL_TERMS_AND_CONDITIONS" = "Terms and Conditions";
"LABEL_TERMS_AND_CONDITIONS_AGREEMENT" = "I have read and agree the Terms and Conditions";
"LABEL_SWEDISH" = "Swedish";
"LABEL_FINNISH" = "Finnish";
"LABEL_STC_PAY_SELECT_PAYMENT_METHOD" = "Please select your preferred payment method:";

/// Messages
"MESSAGE_PLEASE_WAIT" = "Please wait";

/// Device owner authentication
"DEVICE_AUTH_REASON" = "To confirm payment";

/// Button titles
"BUTTON_TITLE_PAY" = "Pay Now";
"BUTTON_TITLE_PAY_AMOUNT" = "Pay %@";
"BUTTON_TITLE_REGISTER" = "Register";
"BUTTON_TITLE_CANCEL" = "Cancel";
"BUTTON_TITLE_BACK" = "Change";
"BUTTON_TITLE_DONE" = "Done";

/// Text field label names
"FIELD_LABEL_CARD_HOLDER_NAME" = "Name of Card Holder";
"FIELD_LABEL_CREDIT_CARD_NUMBER" = "Credit Card Number";
"FIELD_LABEL_EXPIRY_DATE" = "Expiration Date";
"FIELD_LABEL_CVV" = "Security Code or CVV";
"FIELD_LABEL_MOBILE_PHONE_NUMBER" = "Mobile Phone Number";
"FIELD_LABEL_QRCODE" = "QR-Code";
"FIELD_LABEL_BANK_ACCOUNT_HOLDER_NAME" = "Account Holder";
"FIELD_LABEL_IBAN" = "IBAN";
"FIELD_LABEL_IBAN_OR_ACCOUNT_NUMBER" = "IBAN or Account Number";
"FIELD_LABEL_BIC_OR_BANK_CODE" = "BIC or Bank Code";
"FIELD_LABEL_SELECT_COUNTRY" = "Select Country";
"FIELD_LABEL_SELECT_BANK" = "Select Bank";
"FIELD_LABEL_STORE_PAYMENT_DETAILS" = "Store payment details for future use";
"FIELD_LABEL_STORE_CARD_TYPE" = "Choose Card Type";
"FIELD_LABEL_NUMBER_OF_INSTALLMENTS" = "Number of installments";
"FIELD_LABEL_EMAIL" = "Email";
"FIELD_LABEL_COUNTRY_CODE" = "Country Code";
"FIELD_LABEL_NATIONAL_IDENTIFIER" = "National Identifier";
"FIELD_LABEL_ACCOUNT_NUMBER" = "Account Number";
"FIELD_LABEL_ACCOUNT_VERIFICATION" = "Account Verification";
"FIELD_LABEL_DATE_OF_BIRTH" = "Date of birth";

/// Text field placeholders
"FIELD_PLACEHOLDER_CARD_HOLDER_NAME" = "Name as it appears on card";
"FIELD_PLACEHOLDER_CARD_NUMBER" = "Long number on the front of your card";
"FIELD_PLACEHOLDER_EXPIRY_DATE" = "MM/YY or MM/YYYY";
"FIELD_PLACEHOLDER_CVV_3_DIGITS" = "3-digit code";
"FIELD_PLACEHOLDER_CVV_4_DIGITS" = "4-digit code";
"FIELD_PLACEHOLDER_BANK_ACCOUNT_HOLDER_NAME" = "Account Holder Name";
"FIELD_PLACEHOLDER_IBAN" = "International Bank Account Number";
"FIELD_PLACEHOLDER_IBAN_OR_ACCOUNT_NUMBER" = "IBAN or Account Number";
"FIELD_PLACEHOLDER_BIC_OR_BANK_CODE" = "BIC or Bank Code";
"FIELD_PLACEHOLDER_COUNTRY_CODE" = "+xxx";
"FIELD_PLACEHOLDER_MOBILE_PHONE_NUMBER" = "xxxxxxxxx";
"FIELD_PLACEHOLDER_EMAIL" = "Email";
"FIELD_PLACEHOLDER_NATIONAL_IDENTIFIER" = "National Identifier";
"FIELD_PLACEHOLDER_ACCOUNT_NUMBER" = "Account Number";
"FIELD_PLACEHOLDER_ACCOUNT_VERIFICATION" = "Account Verification";
"FIELD_PLACEHOLDER_STREET1" = "Street";
"FIELD_PLACEHOLDER_STREET2" = "Address Line 2";
"FIELD_PLACEHOLDER_CITY" = "City";
"FIELD_PLACEHOLDER_STATE" = "State";
"FIELD_PLACEHOLDER_COUNTRY" = "Country";
"FIELD_PLACEHOLDER_POSTCODE" = "Post Code";
"FIELD_PLACEHOLDER_OPTIONAL" = "(Optional)";
"FIELD_PLACEHOLDER_DATE_OF_BIRTH" = "DD.MM.YYYY";

/// Text field errors
"FIELD_ERROR_CARD_HOLDER_NAME_INVALID" = "Card holder name is not valid";
"FIELD_ERROR_CARD_NUMBER_INVALID" = "Card number entered is not valid";
"FIELD_ERROR_CARD_EXPIRY_DATE_INVALID" = "Card expiration date is not valid, use MM/YY or MM/YYYY format";
"FIELD_ERROR_CARD_EXPIRED" = "Card is expired";
"FIELD_ERROR_CARD_CVV_3_DIGITS_INVALID" = "Security code must contain last 3 digits on the back of your card";
"FIELD_ERROR_CARD_CVV_4_DIGITS_INVALID" = "Security code must contain 4-digit number on the front of your card";
"FIELD_ERROR_BANK_ACCOUNT_HOLDER_INVALID" = "Account holder name is not valid";
"FIELD_ERROR_IBAN_INVALID" = "IBAN is not valid";
"FIELD_ERROR_IBAN_OR_ACCOUNT_NUMBER_INVALID" = "IBAN or Account Number is not valid";
"FIELD_ERROR_BIC_OR_BANK_CODE_INVALID" = "BIC or Bank Code is not valid";
"FIELD_ERROR_EMAIL_INVALID" = "Invalid email";
"FIELD_ERROR_MOBILE_PHONE_INVALID" = "Invalid mobile number";
"FIELD_ERROR_NATIONAL_IDENTIFIER_INVALID" = "Invalid National Identifier";
"FIELD_ERROR_ACCOUNT_NUMBER_INVALID" = "Invalid account number";
"FIELD_ERROR_ACCOUNT_EXPIRY_DATE_INVALID" = "Expiration date is not valid, use MM/YY or MM/YYYY format";
"FIELD_ERROR_ACCOUNT_EXPIRED" = "Account is expired";
"FIELD_ERROR_ADDRESS_INVALID" = "Click here for Billing Address";
"FIELD_ERROR_DATE_OF_BIRTH_INVALID" = "Invalid birth date. DD.MM.YYYY expected";

/// Payment method groups are displayed if tokens are received
"PAYMENT_METHOD_GROUP_STORED" = "Stored Payment Methods";
"PAYMENT_METHOD_GROUP_OTHER" = "Other Payment Methods";
"PAYMENT_METHOD_GROUP_CARDS" = "Credit / Debit Cards";

/// Copy And Pay widget localisable strings
"FIELD_PLACEHOLDER_EXPIRY_DATE_WEB" = "Use MM/YY format";
"FIELD_PLACEHOLDER_CVV_WEB" = "Use 3 or 4 digit code";
"FIELD_ERROR_CARD_EXPIRY_DATE_INVALID_WEB" = "Card expiration date is not valid, use MM/YY format";
"FIELD_ERROR_CARD_CVV_INVALID_WEB" = "Security code must contain 3 or 4 digits";

/// Card scanning
"ALERT_TITLE_CARD_SCANNING_FAILED" = "Scanning failed";
"LABEL_CARD_SCAN_HOLD_HERE" = "Hold here";
"BUTTON_TITLE_OK" = "OK";

/// Accessibility strings
"ACCESSIBILITY_PAY_WITH" = "Pay with";
"ACCESSIBILITY_CANCEL_CHECKOUT" = "Cancel checkout";
"ACCESSIBILITY_SCAN_CARD" = "Scan a card";
"ACCESSIBILITY_PAY_WITH_STORED_PAYMENT_METHOD" = "Pay with stored payment method";

Android

Ready-to-use UI languages:

  • English (en_US)
  • Arabic (ar_AR)
  • Basque (eu_ES)
  • Catalan (ca_ES)
  • Chinese Simplified (zh_CN)
  • Croatian (hr_HR)
  • Czech (cs_CZ)
  • Danish (da_DK)
  • Dutch (nl_NL)
  • Dutch Belgium (nl_BE)
  • Finnish (fi_FI)
  • French (fr_FR)
  • French Belgium (fr_BE)
  • French Canada (fr_CA)
  • Galician (gl_ES)
  • German (de_DE)
  • German Austria (de_AT)
  • Hungarian (hu_HU)
  • Italian (it_IT)
  • Italian Switzerland (it_CH)
  • Japanese (ja_JA)
  • Korean (ko_KR)
  • Norwegian Bokmål (nb_NO)
  • Polish (pl_PL)
  • Portuguese (pt_PT)
  • Romanian (ro_RO)
  • Russian (ru_RU)
  • Serbian (sr_RS)
  • Slovak (sk_SK)
  • Slovenian (sl_SL)
  • Spanish (es_ES)
  • Swedish (sv_SE)
  • Thai (th_TH)

Set the checkout language

Set the locale in CheckoutSettings and start CheckoutActivity. For example:

CheckoutSettings checkoutSettings = new CheckoutSettings(checkoutId, paymentBrands, providerMode);
checkoutSettings.setLocale("de_DE");

Intent intent = checkoutSettings.createCheckoutActivityIntent(this);
startActivityForResult(intent, CheckoutActivity.REQUEST_CODE_CHECKOUT);
val checkoutSettings = CheckoutSettings(checkoutId, paymentParams, providerMode) 
checkoutSettings.locale = "de_DE"
 
intent = checkoutSettings.createCheckoutActivityIntent(this) 
startActivityForResult(intent, CheckoutActivity.REQUEST_CODE_CHECKOUT)

Change the translation for supported languages

Override string resources by adding the string with the same key to your app's res/values/strings.xml file. Find full list of localisable strings at the end of this guide.

Add a new language

Create a new folder for translation values with the appropriate locale in the /res/ folder.

Example: French language folder /res/values-fr/.

Add the default strings.xml there. It is in the default folder /res/values/.

Localisation of payment method labels

Localise labels for any payment method. Add a translation with the appropriate key, which is the brand constant in lowercase.

<string name="master">MasterCard</string>
<string name="amex">American Express</string>

Localisable strings

<-- content -->
<string name="checkout_transaction_run">Please wait</string>
<string name="checkout_payment_details">Payment Details</string>
<string name="checkout_processing">Processing</string>
<string name="checkout_order_review">Order Review</string>
<string name="checkout_layout_text_payment_info">Payment Info</string>
<string name="checkout_layout_text_total_amount">Total Amount</string>
<string name="checkout_layout_text_select_payment_method">Payment Methods</string>
<string name="checkout_layout_text_stored_payment_methods">Stored Payment Methods</string>
<string name="checkout_layout_text_cards_payment_methods">Credit / Debit Cards</string>
<string name="checkout_layout_text_other_payment_methods">Other Payment Methods</string>
<string name="checkout_layout_text_select_country">Select Country</string>
<string name="checkout_layout_text_select_bank">Select Bank</string>
<string name="checkout_choose_card_type">Choose card type</string>
<string name="checkout_layout_text_pay">Pay Now</string>
<string name="checkout_layout_text_pay_amount">Pay %s</string>
<string name="checkout_layout_text_register">Register</string>
<string name="checkout_layout_text_header_amount" formatted="false">%.2f %s</string>
<string name="checkout_tokenization_checkbox">Store payment details for future use</string>
<string name="checkout_number_of_installments">Number of installments</string>
<string name="checkout_layout_text_push_notification_sent">A push notification will be sent to your device.</string>
<string name="checkout_layout_text_accept_mbway_pin">Please accept the transaction with your PIN MB WAY.</string>
<string name="checkout_layout_text_terms_and_conditions">Terms and Conditions</string>
<string name="checkout_layout_text_terms_and_conditions_agreement">I have read and agree the Terms and Conditions</string>
<string name="checkout_layout_text_swedish">Swedish</string>
<string name="checkout_layout_text_finnish">Finnish</string>
<string name="checkout_layout_text_pay_with">Pay with</string>
<string name="checkout_layout_text_ship_to">Ship to</string>
<string name="checkout_layout_text_order_summary">Order summary</string>
<string name="checkout_layout_text_order_total">TOTAL</string>
<string name="checkout_layout_text_stcpay_method_select">Please select your preferred payment method:</string>
<string name="checkout_layout_text_qr_code">QR-Code</string>

<-- hints -->
<string name="checkout_layout_hint_card_holder">Name of Card Holder</string>
<string name="checkout_layout_hint_card_number">Credit Card Number</string>
<string name="checkout_layout_hint_card_expiration_date">Expiration Date</string>
<string name="checkout_layout_hint_card_cvv">Security Code or CVV</string>
<string name="checkout_layout_hint_phone_number">Mobile Phone Number</string>
<string name="checkout_layout_hint_direct_debit_sepa_account_holder">Account Holder</string>
<string name="checkout_layout_hint_direct_debit_sepa_iban">IBAN</string>
<string name="checkout_layout_hint_email">Email</string>
<string name="checkout_layout_hint_country_code">Country Code</string>
<string name="checkout_layout_hint_national_identifier">National Identifier</string>
<string name="checkout_layout_hint_account_number">Account Number</string>
<string name="checkout_layout_hint_account_verification">Account Verification</string>
<string name="checkout_layout_hint_date_of_birth">Date of birth</string>

<-- helpers -->
<string name="checkout_helper_card_holder">Name as it appears on card</string>
<string name="checkout_helper_card_number">Long number on the front of your card</string>
<string name="checkout_helper_expiration_date">Use MM/YY or MM/YYYY format</string>
<string name="checkout_helper_expiry_date">Use MM/YY format</string>
<string name="checkout_helper_security_code">Last 3 digits on the back of your card</string>
<string name="checkout_helper_security_code_amex">4-digit code on the front of your card</string>
<string name="checkout_helper_cvv">Use 3 or 4 digit code</string>
<string name="checkout_helper_country_code">+xxx</string>
<string name="checkout_helper_phone_number">xxxxxxxxx</string>
<string name="checkout_helper_iban">The IBAN (International Bank Account Number) associated with the bank account</string>
<string name="checkout_helper_iban_account_number">IBAN or Account Number</string>
<string name="checkout_helper_bic_bank_code">BIC or Bank Code</string>
<string name="checkout_helper_national_identifier">National Identifier</string>
<string name="checkout_helper_account_number">Account Number</string>
<string name="checkout_helper_account_verification">Account Verification</string>
<string name="checkout_helper_birth_date">Use DD.MM.YYYY</string>

<-- errors -->
<string name="checkout_error_card_holder_invalid">Card holder name is not valid</string>
<string name="checkout_error_card_number_invalid">Card number entered is not valid</string>
<string name="checkout_error_expiration_date_invalid">Card expiration date is not valid, use MM/YY or MM/YYYY format</string>
<string name="checkout_error_expiry_date_invalid">Card expiration date is not valid, use MM/YY format</string>
<string name="checkout_error_expiration_date">Card is expired</string>
<string name="checkout_error_security_code_invalid">Security code must contain last 3 digits on the back of your card</string>
<string name="checkout_error_security_code_invalid_amex">Security code must contain 4-digit number on the front of your card</string>
<string name="checkout_error_cvv_invalid">Security code must contain 3 or 4 digits</string>
<string name="checkout_error_mobile_phone_number_invalid">Invalid mobile number</string>
<string name="checkout_error_account_holder_invalid">Account holder name is not valid</string>
<string name="checkout_error_iban_invalid">IBAN is not valid</string>
<string name="checkout_error_iban_account_number_invalid">IBAN or Account Number is not valid</string>
<string name="checkout_error_bic_bank_code_invalid">BIC or Bank Code is not valid</string>
<string name="checkout_error_email_invalid">Invalid email</string>
<string name="checkout_error_national_identifier_invalid">Invalid National Identifier</string>
<string name="checkout_error_account_number_invalid">Invalid account number</string>
<string name="checkout_error_account_expiration_date_invalid">Expiration date is not valid, use MM/YY or MM/YYYY format</string>
<string name="checkout_error_account_expired">Account is expired</string>
<string name="checkout_error_date_of_birth">Invalid birth date. DD.MM.YYYY expected</string>

<-- device auth -->
<string name="checkout_auth_confirm_payment">To confirm payment</string>
<string name="checkout_fingerprint_touch_sensor">Touch sensor</string>
<string name="checkout_fingerprint_success">Fingerprint recognized</string>
<string name="checkout_fingerprint_not_recognized">Fingerprint not recognized, try again</string>
<string name="checkout_fingerprint_enter_pin">Enter PIN</string>
<string name="checkout_fingerprint_cancel">Cancel</string>

<-- billing address -->
<string name="checkout_billing_address_fragment_title">Click here for Billing Address</string>
<string name="checkout_billing_address_title">Billing Address</string>
<string name="checkout_billing_address_optional">%s (Optional)</string>

<-- billing address hints -->
<string name="checkout_billing_address_country_spinner_hint">Select country</string>
<string name="checkout_billing_address_state_hint">State</string>
<string name="checkout_billing_address_state_spinner_hint">Select state</string>
<string name="checkout_billing_address_city_hint">City</string>
<string name="checkout_billing_address_post_code_hint">ZIP/Postal Code</string>
<string name="checkout_billing_address_street1_hint">Street Address Line 1</string>
<string name="checkout_billing_address_street2_hint">Street Address Line 2</string>

<-- billing address errors -->
<string name="checkout_billing_address_country_required_error">Country is required</string>
<string name="checkout_billing_address_state_required_error">State is required</string>
<string name="checkout_billing_address_city_required_error">City is required</string>
<string name="checkout_billing_address_post_code_required_error">ZIP/Postal Code is required</string>
<string name="checkout_billing_address_street_required_error">Street is required</string>

<-- nfc card reader -->
<string name="oppwa_nfc_card_reader_enable_nfc_button_label">Enable NFC</string>
<string name="oppwa_nfc_card_reader_enable_nfc_description">Enable NFC to read the card</string>
<string name="oppwa_nfc_card_reader_helper_description">Hold the card on the back of your phone to read it</string>
<string name="oppwa_nfc_card_reader_progress_description">Reading card</string>
<string name="oppwa_nfc_card_reader_error_generic">An error occurred</string>
<string name="oppwa_nfc_card_reader_error_card_removed_too_fast">Card was removed too fast</string>

<-- accessibility -->
<string name="checkout_layout_text_scan_fingerprint">Scan fingerprint</string>
<string name="checkout_layout_text_scan_card">Scan a card</string>
<string name="checkout_layout_text_cancel_checkout">Cancel checkout</string>
<string name="checkout_layout_text_back">Back</string>
<string name="checkout_layout_text_selected">selected</string>
<string name="checkout_layout_text_pay_with_stored_payment_method">Pay with stored payment method %s</string>

Advanced options

The Mobile SDK offers a range of advanced options to customise and optimise the checkout experience beyond the default settings of ready-to-use UI. These features allow developers to manage payment brand configurations, display total amounts, and personalise the UI with custom logos.

You can also control security-related behaviors such as skipping CVV checks, enabling billing address fields, and handling callbacks for dynamic payment flows. Additionally, the SDK supports implementing summary pages before finalising payments, giving you greater flexibility and control.

By leveraging these options, you can deliver a secure, branded, and user-friendly payment experience tailored to your business needs.

iOS

Payment methods

Brand management

Two ways to set brands for your checkout:

  • In your app (see configuring checkout settings)
  • In the merchant and administration portal: allowing updates to brands offered to shoppers without updating the mobile app

To configure brands in the portal, go to Administration > COPYandPAY > Categories > Brands. You can define which brands appear in the checkout by setting Allowed Brands on a channel or its parents.

To activate brand configuration through the portal, set Activate Brand Management to TRUE.

The Override shop brands setting determines how new brands propagate to the checkout:

  • TRUE: Override whatever you set in the shop
  • FALSE (default): Offer brands specified in both the BIP and checkout settings in the app

Display total amount

By default, the total amount does not appear in the ready-to-use UI. Since version 2.23.0, you can enable this option by setting the displayTotalAmount property of OPPCheckoutSettings to YES.

OPPCheckoutSettings *checkoutSettings = [[OPPCheckoutSettings alloc] init];
checkoutSettings.displayTotalAmount = YES;
let checkoutSettings = OPPCheckoutSettings()
checkoutSettings.displayTotalAmount = true

The total amount appears on the payment method selection screen and payment forms.

Custom logos

Since version 2.48.0, you can set custom logos for payment brands in the ready-to-use UI.

OPPCheckoutSettings *checkoutSettings = [[OPPCheckoutSettings alloc] init];
UIImage *privateLabelLogo = [UIImage imageNamed:@"logo.png"];
checkoutSettings.customLogos = @{@"PRIVATE_LABEL" : privateLabelLogo};
let checkoutSettings = OPPCheckoutSettings()
let privateLabelLogo = UIImage.init(named: "logo.png")
checkoutSettings.customLogos = ["PRIVATE_LABEL" : privateLabelLogo!]

Remove back button

By default, the back button appears in the UI. Since version 4.10.0, you can disable this option by setting backButtonAvailable to NO.

OPPCheckoutSettings *checkoutSettings = [[OPPCheckoutSettings alloc] init];
checkoutSettings.backButtonAvailable = NO;
let checkoutSettings = OPPCheckoutSettings()
checkoutSettings.backButtonAvailable = false

Credit card

Skipping CVV

The checkout project allows skipping CVV requests for all cards or stored ones. You can manage this by setting skipCVV in OPPCheckoutSettings:

  • OPPCheckoutSkipCVVModeNever: Always request CVV for card payments (default)
  • OPPCheckoutSkipCVVModeForStoredCards: Skip CVV check for stored cards
  • OPPCheckoutSkipCVVModeAlways: Always skip CVV check
OPPCheckoutSettings *checkoutSettings = [[OPPCheckoutSettings alloc] init];
checkoutSettings.skipCVV = OPPCheckoutSkipCVVModeForStoredCards;
let checkoutSettings = OPPCheckoutSettings()
checkoutSettings.skipCVV = .forStoredCards

Skipping cardholder name

To hide the cardholder name field, set cardHolderVisible to NO.

OPPCheckoutSettings *checkoutSettings = [[OPPCheckoutSettings alloc] init];
checkoutSettings.cardHolderVisible = NO;
let checkoutSettings = OPPCheckoutSettings()
checkoutSettings.isCardHolderVisible = false

Override cardholder validation

Since version 2.29.0, you can override cardholder validation using validateCardHolder in OPPCheckoutProviderDelegate.

// Adopt a OPPCheckoutProviderDelegate protocol
@interface CheckoutViewController () <OPPCheckoutProviderDelegate>
@end

@implementation CheckoutViewController
...
- (IBAction)checkoutButtonAction:(id)sender {
    // Set a delegate property for the OPPCheckoutProvider instance
    self.checkoutProvider.delegate = self;
    ...
}

// Implement a callback, it will be called after holder text field loses focus or Pay button is pressed
- (BOOL)checkoutProvider:(OPPCheckoutProvider *)checkoutProvider validateCardHolder:(nullable NSString *)cardHolder {
    // Implement your internal validation
    // return `YES` if the card holder is valid, otherwise `NO`
}

@end
// Adopt a OPPCheckoutProviderDelegate protocol
class CheckoutViewController: UIViewController, OPPCheckoutProviderDelegate {
    ...
    @IBAction func checkoutButtonAction(_ sender: UIButton) {
        // Set a delegate property for the OPPCheckoutProvider instance
        self.checkoutProvider.delegate = self
        ...
    }

    // Implement a callback, it will be called after holder text field loses focus or Pay button is pressed
    func checkoutProvider(_ checkoutProvider: OPPCheckoutProvider, validateCardHolder cardHolder: String?) -> Bool {
        // Implement your internal validation
        // return `true` if the card holder is valid, otherwise `false`
    }
}

Display instalment options

By default, the system has instalments deactivated. Since version 2.30.0, you can enable this option by configuring OPPCheckoutSettings in the following way:

  • Enable instalment payments.
  • Set your list of options if needed. Default is 1, 3, 5.
OPPCheckoutSettings *checkoutSettings = [[OPPCheckoutSettings alloc] init];
checkoutSettings.installmentEnabled = YES;
checkoutSettings.installmentOptions = @[@1, @2, @3];
let checkoutSettings = OPPCheckoutSettings()
checkoutSettings.isInstallmentEnabled = true
checkoutSettings.installmentOptions = [1, 2, 3]

Display billing address

Billing address fields can be pre-filled.

OPPCheckoutSettings *settings = [[OPPCheckoutSettings alloc] init];
OPPBillingAddress *address = [[[[[[[[[[[[[[OPPBillingAddressBuilder new]
                                          withCountry:@"US"]
                                          withCountryRequired:YES]
                                          withState:@"NY"]
                                          withStateRequired:YES]
                                          withCity:@"New York"]
                                          withCityRequired:YES]
                                          withPostCode:@"12345"]
                                          withPostCodeRequired:YES]
                                          withStreet1:@"Suite 1234"]
                                          withStreet1Required:YES]
                                          withStreet2:@"Some Road"]
                                          withStreet2Required:NO] build];
checkoutSettings.billingAddress = address;
let checkoutSettings = OPPCheckoutSettings()
let address = OPPBillingAddressBuilder()
                        .withCity("US")
                        .withCityRequired(true)
                        .withState("NY")
                        .withStateRequired(true)
                        .withCity("New York")
                        .withCityRequired(true)
                        .withPostCode("12345")
                        .withPostCodeRequired(true)
                        .withStreet1("Suite 1234")
                        .withStreet1Required(true)
                        .withStreet2("Some Road")
                        .withStreet2Required(false)
                        .build()
checkoutSettings.billingAddress = address

Card brand detection

The system automatically detects card brands while the shopper types the card number. You can configure detection using:

  • Regular expressions (OPPCheckoutBrandDetectionTypeRegex, default)
  • BIN list (OPPCheckoutBrandDetectionTypeBinList)

Then you should choose how to proceed if the system detects multiple card brands:

  • Set the order for detected brands
  • Decide whether you want either present multiple brands or hide them by default

Below you can see more detailed description for each configuration with user cases.

Brand detection type
  1. Regex

    Regular expressions detect card brands fast and reliably.

    This brand detection is the default, but you can specify the type in the code:

    OPPCheckoutSettings *checkoutSettings = [[OPPCheckoutSettings alloc] init];
    
    // Set configured payment brands
    checkoutSettings.paymentBrands = @[@"VISA", @"MASTER", @"MAESTRO", @"AMEX"];
    
    // Regex as a card brand detection type
    checkoutSettings.brandDetectionType = OPPCheckoutBrandDetectionTypeRegex;
    let checkoutSettings = OPPCheckoutSettings()
    
    // Set configured payment brands
    checkoutSettings.paymentBrands = ["VISA", "MASTER", "MAESTRO", "AMEX"];
    
    // Regex as a card brand detection type
    checkoutSettings.brandDetectionType = OPPCheckoutBrandDetectionType.regex;

    You must use Regex for the below cases:

    • Configured brands: VISA, MASTER, MAESTRO, AMEX
    • Shopper types: 5710 1000 0000 0008
    • Detected brands: MASTER, MAESTRO
    • Default selection: MASTER
  2. BIN list

    The BIN list library detects card brands more precisely but runs slightly slower. The library updates routinely, making it the most reliable source.

    To enable brand detection by BIN list, change the following option in CheckoutSettings:

    OPPCheckoutSettings *checkoutSettings = [[OPPCheckoutSettings alloc] init];
    
    // Set configured payment brands
    checkoutSettings.paymentBrands = @[@"VISA", @"MASTER", @"MAESTRO", @"AMEX"];
    
    // Set bin list as a card brand detection type
    checkoutSettings.brandDetectionType = OPPCheckoutBrandDetectionTypeBinList;
    let checkoutSettings = OPPCheckoutSettings()
    
    // Set configured payment brands
    checkoutSettings.paymentBrands = ["VISA", "MASTER", "MAESTRO", "AMEX"];
    
    // Set bin list as a card brand detection type
    checkoutSettings.brandDetectionType = OPPCheckoutBrandDetectionType.binList;

    You must use BIN list for the below cases:

    • Configured brands: VISA, MASTER, MAESTRO, AMEX
    • Bin list brand detection type: Enabled
    • Shopper types: 5710 1000 0000 0008
    • Detected brands (initially): MASTER, MAESTRO (regex fallback)
    • Final detection: After six digits, the BIN list confirms the card as MAESTRO, not MASTER.
  3. BIN list and type

    This brand detection type behaves similarly to BIN list but provides extra functionality such as to dynamically change the hint of the card number field based on the card type. For example, if the card type is debit card then it shows Debit Card Number or if the card type is credit card then it shows Credit Card Number. By default, it shows as Card Number.

    To enable brand detection by BIN list and type, change the following option in CheckoutSettings

    OPPCheckoutSettings *checkoutSettings = [[OPPCheckoutSettings alloc] init];
    
    // Set configured payment brands
    checkoutSettings.paymentBrands = @[@"VISA", @"MASTER", @"MAESTRO", @"AMEX"];
    
    // Set bin list and type as a card brand detection type              
    checkoutSettings.brandDetectionType = OPPCheckoutBrandDetectionTypeBinListAndType;
    let checkoutSettings = OPPCheckoutSettings()
    
    // Set configured payment brands
    checkoutSettings.paymentBrands = ["VISA", "MASTER", "MAESTRO", "AMEX"];
    
    // Set bin list and type as a card brand detection type
    checkoutSettings.brandDetectionType = OPPCheckoutBrandDetectionType.binListAndType;
Brands priority

When the system detects multiple brands, you might want to show a preferred brand first in the list. The parameter CheckoutSettings provides an option for this:

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

// Set configured payment brands
checkoutSettings.paymentBrands = @[@"VISA", @"MASTER", @"MAESTRO", @"AMEX"];

// Set the preferred order of the detected card brands
checkoutSettings.paymentBrands = @[@"MAESTRO", @"MASTER"];
let checkoutSettings = OPPCheckoutSettings()

// Set configured payment brands
checkoutSettings.paymentBrands = ["VISA", "MASTER", "MAESTRO", "AMEX"];

// Set the preferred order of the detected card brands
checkoutSettings.paymentBrands = ["MAESTRO", "MASTER"];

Use brands priority for the below cases:

  • Configured brands: VISA, MASTER, MAESTRO, AMEX
  • Priority for detected brands: MAESTRO, MASTER
  • Example scenario:
    • Shopper types 5710 1000 0000 0008
    • Detected brands: MAESTRO, MASTER
    • Default selection: MAESTRO

Brand detection appearance style

  1. Active

    The interface for detected card brands appears automatically. This behaviour is the default.

  2. Inactive

    The interface for detected card brands stays hidden. The user can make it visible by clicking the card icon in the card number text field.

    OPPCheckoutSettings *checkoutSettings = [[OPPCheckoutSettings alloc] init];
    
    // Set configured payment brands
    checkoutSettings.paymentBrands = @[@"VISA", @"MASTER", @"MAESTRO", @"AMEX"];
    
    // Hide the detected brands
    checkoutSettings.brandDetectionAppearanceStyle = OPPCheckoutBrandDetectionAppearanceStyleInactive;
    let checkoutSettings = OPPCheckoutSettings()
    
    // Set configured payment brands
    checkoutSettings.paymentBrands = ["VISA", "MASTER", "MAESTRO", "AMEX"]
    
    // Hide the detected brands
    checkoutSettings.brandDetectionAppearanceStyle = OPPCheckoutBrandDetectionAppearanceStyle.inactive

    Use inactive for the below:

    1. Configured brands: VISA, MASTER, MAESTRO, AMEX
    2. Brand detection appearance style: INACTIVE
    3. Shopper types 5710 1000 0000 0008
    4. The system detects MASTER and MAESTRO but does not show them under the card number field
    5. Default selection: MASTER

    The shopper can still choose another brand manually

  3. None

    The interface for detected card brands stays deactivated. The system sets the brand based on brand priority.

    OPPCheckoutSettings *checkoutSettings = [[OPPCheckoutSettings alloc] init];
    
    // Set configured payment brands
    checkoutSettings.paymentBrands = @[@"VISA", @"MASTER", @"MAESTRO", @"AMEX"];
    
    // Set the preferred order of the detected card brands
    checkoutSettings.brandDetectionPriority = @[@"MAESTRO", @"MASTER"];
    
    // Disable the detected brands showing
    checkoutSettings.brandDetectionAppearanceStyle = OPPCheckoutBrandDetectionAppearanceStyleNone;
    let checkoutSettings = OPPCheckoutSettings()
    
    // Set configured payment brands
    checkoutSettings.paymentBrands = ["VISA", "MASTER", "MAESTRO", "AMEX"]
    
    // Set the preferred order of the detected card brands
    checkoutSettings.brandDetectionPriority = ["MAESTRO", "MASTER"]
    
    // Hide the detected brands
    checkoutSettings.brandDetectionAppearanceStyle = OPPCheckoutBrandDetectionAppearanceStyle.none

    Use none for the below:

    1. Configured brands: VISA, MASTER, MAESTRO, AMEX
    2. Priority for detected brands: MAESTRO, MASTER
    3. Brand detection appearance style: NONE
    4. Shopper types 5710 1000 0000 0008
    5. The system detects MASTER and MAESTRO
    6. Default selection: MAESTRO
📘

If brand priority is not set, the system chooses the brand based on the payment brands order.

Security

Protect payments with Touch ID / Face ID

Enable biometric authentication to provide extra security for your customers.

To complete payment, the customer must authenticate with Touch ID or Face ID if enabled on their device. If unavailable, the device requests the passcode.

Enable this protection for tokens (stored cards and bank account information). Configure biometric authentication for any payment brands.

Biometric authentication can secure payments. The three modes are:

  • OPPSecurityPolicyModeDeviceAuthNotRequired: No authentication
  • OPPSecurityPolicyModeDeviceAuthRequiredIfAvailable: Require authentication if enabled on the device
  • OPPSecurityPolicyModeDeviceAuthRequired: Always require authentication - the payment brand is not available if Touch ID, Face ID, or passcode are not set

Create an OPPSecurityPolicy and add it to the securityPolicies option of OPPCheckoutSettings to configure device authentication.

OPPSecurityPolicy applies the specified mode for tokens or a list of payment brands.

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

OPPSecurityPolicy *securityPolicyForTokens = [OPPSecurityPolicy securityPolicyForTokensWithMode:OPPSecurityPolicyModeDeviceAuthRequired];
NSArray *paymentBrands = @[@"VISA", @"MASTER"];
OPPSecurityPolicy *securityPolicyForPaymentMethods = [OPPSecurityPolicy securityPolicyWithPaymentBrands:paymentBrands mode:OPPSecurityPolicyModeDeviceAuthRequiredIfAvailable];
        
checkoutSettings.securityPolicies = @[securityPolicyForPaymentMethods, securityPolicyForTokens];
let checkoutSettings = OPPCheckoutSettings()

let securityPolicyForTokens = OPPSecurityPolicy(forTokensWith: .deviceAuthRequired)
let paymentBrands = ["VISA", "MASTER"]
let securityPolicyForPaymentBrands = OPPSecurityPolicy(paymentBrands: paymentBrands, mode: .deviceAuthRequiredIfAvailable)

checkoutSettings.securityPolicies = [securityPolicyForPaymentBrands, securityPolicyForTokens]

Callbacks

Receiving callbacks during checkout process

You may need to review the transaction before submitting it to your server. The OPPCheckoutProviderDelegate protocol provides a method to receive this event and affect the checkout process.

The common scenarios to use the event are:

  • Review the transaction and abort it if an internal error occurs.
  • Recreate the checkout ID for specific payment brands, for example, if they require custom parameters at the first step.
  • You can access additional details through the OPPPaymentAddOnDetails instance.
// Adopt a OPPCheckoutProviderDelegate protocol
@interface CheckoutViewController () <OPPCheckoutProviderDelegate>
@end

@implementation CheckoutViewController
...
- (IBAction)checkoutButtonAction:(id)sender {
    // Set a delegate property for the OPPCheckoutProvider instance
    self.checkoutProvider.delegate = self;
    ...
}

// Implement a callback, it will be called right before submitting a transaction to the Server
- (void)checkoutProvider:(OPPCheckoutProvider *)checkoutProvider continueSubmitting:(OPPTransaction *)transaction completion:(void (^)(NSString * _Nullable checkoutID, BOOL abort))completion {
    // To continue submitting you should call completion block which expects 2 parameters:
    // checkoutID - you can create new checkoutID here or pass current one
    // abort - you can abort transaction here by passing 'true'
    completion(transaction.paymentParams.checkoutID, false);
}

@end
// Adopt a OPPCheckoutProviderDelegate protocol
class CheckoutViewController: UIViewController, OPPCheckoutProviderDelegate {
    ...
    @IBAction func checkoutButtonAction(_ sender: UIButton) {
        // Set a delegate property for the OPPCheckoutProvider instance
        self.checkoutProvider.delegate = self
        ...
    }

    // Implement a callback, it will be called right before submitting a transaction to the Server
    func checkoutProvider(_ checkoutProvider: OPPCheckoutProvider, continueSubmitting transaction: OPPTransaction, completion: @escaping (String?, Bool) -> Void) {
        // To continue submitting you should call completion block which expects 2 parameters:
        // checkoutID - you can create new checkoutID here or pass current one
        // abort - you can abort transaction here by passing 'true'
        completion(transaction.paymentParams.checkoutID, false)
    }
}
📘

The callback which provides the OPPPaymentAddOnDetails is designed to allow Mobile SDK to include additional information during the continue-submit process, if required. Mobile SDK can expand OPPPaymentAddOnDetails model in future based on the requirement to provide additional details.

Summary page

It is possible to use Mobile SDK to temporarily store the payment information after submission, rather than executing it straight away. This can be useful if you want to display an order summary page before committing the payment. For this purpose, you can implement your ViewController to display the payment summary. OPPCheckoutProviderDelegate protocol provides you a method to receive such an event with ability to affect the checkout process.

// Adopt a OPPCheckoutProviderDelegate protocol
@interface CheckoutViewController () <OPPCheckoutProviderDelegate>
@end

@implementation CheckoutViewController
...
- (IBAction)checkoutButtonAction:(id)sender {
    // Set a delegate property for the OPPCheckoutProvider instance
    self.checkoutProvider.delegate = self;
    ...
}

// Merchant have to implement one of the below method for getting the callback while submitting the payment depend on the usage.

// Implement a callback, it will be called right before submitting a transaction to the Server
- (void)checkoutProvider:(OPPCheckoutProvider *)checkoutProvider continueSubmitting:(OPPTransaction *)transaction completion:(void (^)(NSString * _Nullable checkoutID, BOOL abort))completion {
    // You could display new ViewController with the option to Continue or Abort in this callback.
    // And than call completion block with params
    // To continue submitting you should call completion block which expects 2 parameters:
    // checkoutID - you can create new checkoutID here or pass current one
    // abort - you can abort transaction here by passing 'true'
    completion(transaction.paymentParams.checkoutID, false);
}

// Implement a callback, it will be called right before submitting a transaction to the Server
- (void)checkoutProvider:(OPPCheckoutProvider *)checkoutProvider continueSubmitting:(OPPTransaction *)transaction andAddOnDetails:(OPPPaymentAddOnDetails * _Nullable)additionalDetails completion:(void (^)(NSString * _Nullable checkoutID, BOOL abort))completion {
    // To continue submitting you should call completion block which expects 2 parameters:
    // checkoutID - you can create new checkoutID here or pass current one
    // abort - you can abort transaction here by passing 'true'
    // additionalDetails - Merchant get an additional information from mSDK eg: in case of Visa Installment flow will get the selected plan information.
    completion(transaction.paymentParams.checkoutID, false);
}

@end
// Adopt a OPPCheckoutProviderDelegate protocol
class CheckoutViewController: UIViewController, OPPCheckoutProviderDelegate {
    ...
    @IBAction func checkoutButtonAction(_ sender: UIButton) {
        // Set a delegate property for the OPPCheckoutProvider instance
        self.checkoutProvider.delegate = self
        ...
    }

    // Merchant have to implement one of the below method for getting the callback while submitting the payment depend on the usage.

    // Implement a callback, it will be called right before submitting a transaction to the Server
    func checkoutProvider(_ checkoutProvider: OPPCheckoutProvider, continueSubmitting transaction: OPPTransaction, completion: @escaping (String?, Bool) -> Void) {
        // You could display new ViewController with the option to Continue or Abort in this callback.
        // And than call completion block with params
        // To continue submitting you should call completion block which expects 2 parameters:
        // checkoutID - you can create new checkoutID here or pass current one
        // abort - you can abort transaction here by passing 'true'
        completion(transaction.paymentParams.checkoutID, false)
    }

    // Implement a callback, it will be called right before submitting a transaction to the Server
    func checkoutProvider(_ checkoutProvider: OPPCheckoutProvider, continueSubmitting transaction: OPPTransaction, andAddOnDetails additionalDetails: OPPPaymentAddOnDetails?, completion: @escaping (String?, Bool) -> Void) {
        // To continue submitting you should call completion block which expects 2 parameters:
        // checkoutID - you can create new checkoutID here or pass current one
        // abort - you can abort transaction here by passing 'true'
        // additionalDetails - Merchant get an additional information from mSDK eg: in case of Visa Installment flow will get the selected plan information.
        completion(transaction.paymentParams.checkoutID, false)
    }   
}

The summary page should consist of at least two actions and return control back to the OPPCheckoutProvider.

  1. Confirm payment.
  2. Cancel payment.

COPYandPAY in Mobile SDK

In rare cases, you cannot integrate a payment method natively in the Mobile SDK, but you may still want to offer it. As an alternative, use COPYandPAY integration inside the mobile SDK. This should be a fallback solution, not the default integration. COPYandPAY integration inside the mobile SDK supports the ONEY brand.

Create an OPPWpwlOptions object to configure brand-specific options. Refer to the COPYandPAY API for more details.

Initialise OPPWpwlOptions with two dictionaries:

  • One for JSON-like options
  • One for JavaScript functions
NSDictionary *config = @{@"locale": @"fr-BE"};
NSDictionary *jsConfig = @{@"onReady": @"function() {/* function here */}"};
OPPWpwlOptions *wpwlOptions = [OPPWpwlOptions initWithConfiguration:config jsFunctions:jsConfig];
var config = [ "locale" : "fr-BE" ]
var jsConfig = [ "onReady" : "function() {/* function here */}" ] as [String : Any]
let wpwlOptions = OPPWpwlOptions.initWithConfiguration( config , jsFunctions: jsConfig)

Create a dictionary to map the brand to the OPPWpwlOptions object and set it in the OPPCheckoutSettings.wpwlOptions property.

OPPCheckoutSettings *checkoutSettings = [[OPPCheckoutSettings alloc] init];
checkoutSettings.wpwlOptions = @{@"ONEY": wpwlOptions};
let checkoutSettings = OPPCheckoutSettings()
checkoutSettings.wpwlOptions["ONEY"] = wpwlOptions;

Android

Payment methods

Brand management

Two possible ways to set brands for your checkout are:

  • In your app - see Configure the checkout settings.
  • In the merchant and administration portal - allowing you to update the brands offered to shoppers without requiring an update of the mobile app.

Located under Administration > COPYandPAY > Categories > Brands, you can define which brands appear in the checkout by configuring the Allowed Brands on a channel, or any of its parents/ancestors.

To activate brand configuration through the merchant and administration portal, set Activate Brand Management to TRUE.

The last setting, Override shop brands, decides how new brands propagate to the checkout:

  • TRUE: Override whatever you defined in the shop.
  • FALSE (default): Offer brands specified in both the BIP and the checkout settings in the app.

Display total amount

By default, the total amount is not shown in the ready-to-use UI. Since version 2.23.0, enable this option by setting the boolean property isTotalAmountRequired of the CheckoutSettings class.

CheckoutSettings checkoutSettings = new CheckoutSettings(checkoutId, paymentBrands, providerMode);
checkoutSettings.setTotalAmountRequired(true);
val checkoutSettings = CheckoutSettings(checkoutId, paymentBrands, providerMode) 
checkoutSettings.isTotalAmountRequired = true

The total amount appears on the payment method selection screen and payment forms.

Custom logos

Since version 2.48.0, the ready-to-use UI allows setting custom logos for payment brands.

CheckoutSettings checkoutSettings = new CheckoutSettings(checkoutId, paymentBrands, providerMode);
checkoutSettings.setCustomLogo("PRIVATE_LABEL", R.drawable.logo);
val checkoutSettings = CheckoutSettings(checkoutId, paymentBrands, providerMode) 
checkoutSettings.setCustomLogo("PRIVATE_LABEL", R.drawable.logo)

Remove back button

By default, the back button appears in the ready-to-use UI. Since version 4.10.0, enable or disable this option by setting the boolean property isBackButtonAvailable of the CheckoutSettings class.

CheckoutSettings checkoutSettings = new CheckoutSettings(checkoutId, paymentBrands, providerMode);
checkoutSettings.setBackButtonAvailable(false);
val checkoutSettings = CheckoutSettings(checkoutId, paymentBrands, providerMode) 
checkoutSettings.isBackButtonAvailable = false

The back button does not appear on the payment form.

Credit card

Skipping CVV

You can skip the CVV request for all cards or stored ones by updating the CheckoutSkipCVVMode of the CheckoutSettings class:

  • NEVER: Always request CVV (default).
  • FOR_STORED_CARDS: Skip CVV check for stored card payments.
  • ALWAYS: Always skip CVV check.
CheckoutSettings checkoutSettings = new CheckoutSettings(checkoutId, paymentBrands, providerMode);
checkoutSettings.setSkipCVVMode(CheckoutSkipCVVMode.FOR_STORED_CARDS);
val checkoutSettings = CheckoutSettings(checkoutId, paymentBrands, providerMode) 
checkoutSettings.skipCVVMode = CheckoutSkipCVVMode.FOR_STORED_CARDS

Skipping cardholder

To hide the cardholder name field in the payment form, update CheckoutSettings.

CheckoutSettings checkoutSettings = new CheckoutSettings(checkoutId, paymentBrands, providerMode);
checkoutSettings.setCardHolderVisible(false);
val checkoutSettings = CheckoutSettings(checkoutId, paymentBrands, providerMode) 
checkoutSettings.isCardHolderVisible = false

Override cardholder validation

Since version 2.29.0, override the holder validation by implementing the IPaymentFormListener listener. IPaymentFormListener should catch payment form events and pass it to the CheckoutSettings. The listener also should implement the Parcelable interface as it is passed through intent.

// Create your listener to override holder validation
public class CustomFormListener implements IPaymentFormListener {
    public CustomFormListener() {}
    
    @Override
    public CheckoutValidationResult onCardHolderValidate(String holder) {
        // Your internal validation here
        if (isHolderValid(holder)) {
            return CheckoutValidationResult.VALID;
        } else {
            return CheckoutValidationResult.NOT_VALID;
        }
    }

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel parcel, int i) {}
    
    private CustomFormListener(Parcel in) {}

    public static final Creator<CustomFormListener> CREATOR = new Creator<CustomFormListener>() {
        @Override
        public CustomFormListener createFromParcel(Parcel in) {
            return new CustomFormListener(in);
        }

        @Override
        public CustomFormListener[] newArray(int size) {
            return new CustomFormListener[size];
        }
    };
}
// Create your listener to override holder validation 
class CustomFormListener() : IPaymentFormListener { 
    constructor(parcel: Parcel) : this() { 
    } 

    override fun writeToParcel(parcel: Parcel, flags: Int) { 
    } 

    override fun onCardHolderValidate(holder: String?): CheckoutValidationResult { 
        // Your internal validation here 
        return if (isCardHolderValid(holder)) { 
            CheckoutValidationResult.VALID 
        } else { 
            CheckoutValidationResult.NOT_VALID 
        } 
    } 

    override fun describeContents(): Int { 
        return 0 
    } 

    companion object CREATOR : Parcelable.Creator<CustomFormListener> { 
        override fun createFromParcel(parcel: Parcel): CustomFormListener { 
            return CustomFormListener(parcel) 
        } 

        override fun newArray(size: Int): Array<CustomFormListener?> { 
            return arrayOfNulls(size) 
        }
    }
}

Pass created listener to the CheckoutSettings.

CheckoutSettings checkoutSettings = new CheckoutSettings(checkoutId, paymentBrands, providerMode);

checkoutSettings.setPaymentFormListener(new CustomFormListener());
val checkoutSettings = CheckoutSettings(checkoutId, paymentBrands, providerMode) 
 
checkoutSettings.paymentFormListener = CustomFormListener()

Display instalment options

To display instalment options configure CheckoutSettings in the following way:

  • Enable instalment payments
  • Set your list of options if needed. Default is 1, 3, 5.
CheckoutSettings checkoutSettings = new CheckoutSettings(checkoutId, paymentBrands, providerMode);
checkoutSettings.setInstallmentEnabled(true);
checkoutSettings.setInstallmentOptions(new Integer[] {1, 2, 3});
val checkoutSettings = CheckoutSettings(checkoutId, paymentBrands, providerMode) 
 
checkoutSettings.isInstallmentEnabled = true 
checkoutSettings.installmentOptions = arrayOf(1, 2, 3)

Display billing address

Enable and pre-fill billing address fields.

CheckoutSettings checkoutSettings = new CheckoutSettings(
        checkoutId, 
        paymentBrands, 
        providerMode
);

BillingAddress billingAddress = new BillingAddress.Builder()
        .setCountry("US")
        .setCountryRequired(true)
        .setState("NY")
        .setStateRequired(true)
        .setCity("New York")
        .setCityRequired(true)
        .setPostCode("12345")
        .setPostCodeRequired(true)
        .setStreet1("Suite 1234")
        .setStreet1Required(true)
        .setStreet2("Some Road")
        .setStreet2Required(false)
        .build();
        
checkoutSettings.setBillingAddress(billingAddress);
val checkoutSettings = CheckoutSettings(
        checkoutId,
        paymentBrands,
        providerMode
)

val billingAddress = BillingAddress.Builder()
        .setCountry("US")
        .setCountryRequired(true)
        .setState("NY")
        .setStateRequired(true)
        .setCity("New York")
        .setCityRequired(true)
        .setPostCode("12345")
        .setPostCodeRequired(true)
        .setStreet1("Suite 1234")
        .setStreet1Required(true)
        .setStreet2("Some Road")
        .setStreet2Required(false)
        .build()

checkoutSettings.billingAddress = billingAddress

Card brand detection

The system automatically detects the card brand while the shopper types the card number. You can configure detection using:

  • Regular expressions
  • BIN list

Then you should choose how to proceed if the system detects multiple card brands:

  • Set the order for detected brands
  • Decide whether you want either present multiple brands or hide them by default

Below you can see more detailed description for each configuration with user cases.

Brand detection type
  1. Regex

    Regular expressions define rules to detect card brands. This method is fast and reliable.

    By default, the system uses this detection method, but you can explicitly specify it in the code:

    Set<String> paymentBrands = new LinkedHashSet<>();
    paymentBrands.add("VISA");
    paymentBrands.add("MASTER");
    paymentBrands.add("MAESTRO");
    paymentBrands.add("AMEX");
    
    CheckoutSettings checkoutSettings = new CheckoutSettings(
        checkoutId, paymentBrands, providerMode);
    
    // Set regex as the card brand detection type
    checkoutSettings.setBrandDetectionType(CheckoutBrandDetectionType.REGEX);
    val paymentBrands = hashSetOf("VISA", "MASTER", "MAESTRO", "AMEX") 
    
    val checkoutSettings = CheckoutSettings(checkoutId, paymentBrands, providerMode) 
    
    // Regex as a card brand detection type             
    checkoutSettings.brandDetectionType = CheckoutBrandDetectionType.REGEX

    You must use Regex for the below cases:

    • Configured brands: VISA, MASTER, MAESTRO, AMEX
    • Shopper types: 5710 1000 0000 0008
    • Detected brands: MASTER, MAESTRO
    • Default selection: MASTER
  2. BIN list

    The BIN list method is more precise but slightly slower. The library is routinely updated, making it the most reliable source.

    To enable brand detection using the BIN list, update the CheckoutSettings:

    Set<String> paymentBrands = new LinkedHashSet<>();
    paymentBrands.add("VISA");
    paymentBrands.add("MASTER");
    paymentBrands.add("MAESTRO");
    paymentBrands.add("AMEX");
    
    CheckoutSettings checkoutSettings = new CheckoutSettings(checkoutId, paymentBrands, providerMode);
    
    // Set BIN list as the card brand detection type       
    checkoutSettings.setBrandDetectionType(CheckoutBrandDetectionType.BINLIST);
    val paymentBrands = hashSetOf("VISA", "MASTER", "MAESTRO", "AMEX") 
    
    val checkoutSettings = CheckoutSettings(checkoutId, paymentBrands, providerMode) 
    
    // Set bin list as a card brand detection type        
    checkoutSettings.brandDetectionType = CheckoutBrandDetectionType.BINLIST

    You must use Bin list for the below cases:

    • Configured brands: VISA, MASTER, MAESTRO, AMEX
    • Detection type: BIN list enabled
    • Shopper types: 5710 1000 0000 0008
    • Initial detection: MASTER, MAESTRO (regular expressions as fallback)
    • Final detection: After six digits, the BIN list identifies the card as MAESTRO
  3. BIN list and type

    This brand detection type behaves similarly to BIN list but provides extra functionality such as to dynamically change the hint of the card number field based on the card type. For example, if the card type is debit card then it shows Debit Card Number or if the card type is credit card then it shows Credit Card Number. By default, it shows as Card Number.

    To enable brand detection by BIN list and type, change the following option in CheckoutSettings:

    Set<String> paymentBrands = new LinkedHashSet<>();
    paymentBrands.add("VISA");
    paymentBrands.add("MASTER");
    paymentBrands.add("MAESTRO");
    paymentBrands.add("AMEX");
    
    CheckoutSettings checkoutSettings = new CheckoutSettings(checkoutId, paymentBrands, providerMode);
    
    // Set bin list and type as a card brand detection type
    checkoutSettings.setBrandDetectionType(CheckoutBrandDetectionType.BINLISTANDTYPE);
    val paymentBrands = hashSetOf("VISA", "MASTER", "MAESTRO", "AMEX") 
    
    val checkoutSettings = CheckoutSettings(checkoutId, paymentBrands, providerMode) 
    
    // Set bin list and type as a card brand detection type
    checkoutSettings.brandDetectionType = CheckoutBrandDetectionType.BINLISTANDTYPE
Brand priority

When the Mobile SDK detects multiple brands, you may want to rank a preferred brand in the list. The parameter, CheckoutSettings provides an option to set brand priority:

Set<String> paymentBrands = new LinkedHashSet<>();
paymentBrands.add("VISA");
paymentBrands.add("MASTER");
paymentBrands.add("MAESTRO");
paymentBrands.add("AMEX");

CheckoutSettings checkoutSettings = new CheckoutSettings(
    checkoutId, paymentBrands, providerMode);

List<String> brandsPriority = new ArrayList<>();
brandsPriority.add("MAESTRO");
brandsPriority.add("MASTER");

// Set the preferred order of the detected card brands
checkoutSettings.setBrandDetectionPriority(brandsPriority);
val paymentBrands = hashSetOf("VISA", "MASTER", "MAESTRO", "AMEX") 
 
val checkoutSettings = CheckoutSettings(checkoutId, paymentBrands, providerMode) 
 
val brandsPriority = arrayListOf("MAESTRO", "MASTER") 
 
// Set the preferred order of the detected card brands 
checkoutSettings.brandDetectionPriority = brandsPriority

Use brands priority for the below cases:

  • Configured brands: VISA, MASTER, MAESTRO, AMEX
  • Priority for detected brands: MAESTRO, MASTER
  • Shopper types: 5710 1000 0000 0008
  • Detected brands: MAESTRO, MASTER
  • Default selection: MAESTRO
Brand detection appearance style
  1. Active

    The interface for detected card brands appears automatically. This behaviour is the default.

  2. Inactive

    The interface for detected card brands stays hidden. The user can make it visible by clicking the card icon in the card number text field.

    Set<String> paymentBrands = new LinkedHashSet<>();
    paymentBrands.add("VISA");
    paymentBrands.add("MASTER");
    paymentBrands.add("MAESTRO");
    paymentBrands.add("AMEX");
    
    CheckoutSettings checkoutSettings = new CheckoutSettings(
            checkoutId, paymentBrands, providerMode);
    
    // Hide the detected brands        
    checkoutSettings.setBrandDetectionAppearanceStyle(CheckoutBrandDetectionAppearanceStyle.INACTIVE);
    val paymentBrands = hashSetOf("VISA", "MASTER", "MAESTRO", "AMEX") 
    
    
    val checkoutSettings = CheckoutSettings(checkoutId, paymentBrands, providerMode) 
    
    // Hide the detected brands   
    checkoutSettings.setBrandDetectionAppearanceStyle(CheckoutBrandDetectionAppearanceStyle.INACTIVE)

    Use inactive for the below:

    1. Configured brands: VISA, MASTER, MAESTRO, AMEX
    2. Brand detection appearance style: INACTIVE
    3. Shopper types 5710 1000 0000 0008
    4. The system detects MASTER and MAESTRO but does not show them under the card number field
    5. Default selection: MASTER

    The shopper can still choose another brand manually

  3. None

    The interface for detected card brands stays deactivated. The system sets the brand based on brand priority.

    Set<String> paymentBrands = new LinkedHashSet<>();
    paymentBrands.add("VISA");
    paymentBrands.add("MASTER");
    paymentBrands.add("MAESTRO");
    paymentBrands.add("AMEX");
    
    CheckoutSettings checkoutSettings = new CheckoutSettings(
            checkoutId, paymentBrands, providerMode);
    
    List<String> brandsPriority = new ArrayList<>();
    brandsPriority.add("MAESTRO");
    brandsPriority.add("MASTER");
    
    // Set the preferred order of the detected card brands
    checkoutSettings.setBrandDetectionPriority(brandsPriority);
    
    // Disable the detected brands showing
    checkoutSettings.setBrandDetectionAppearanceStyle(CheckoutBrandDetectionAppearanceStyle.NONE);
    val paymentBrands = hashSetOf("VISA", "MASTER", "MAESTRO", "AMEX") 
    
    
    val checkoutSettings = CheckoutSettings(checkoutId, paymentBrands, providerMode) 
    
    val brandsPriority = arrayListOf("MAESTRO", "MASTER") 
    
    // Set the preferred order of the detected card brands 
    checkoutSettings.brandDetectionPriority = brandsPriority
    
    // Disable the detected brands showing          
    checkoutSettings.setBrandDetectionAppearanceStyle(CheckoutBrandDetectionAppearanceStyle.NONE)

    Use none for the below:

    1. Configured brands: VISA, MASTER, MAESTRO, AMEX
    2. Priority for detected brands: MAESTRO, MASTER
    3. Brand detection appearance style: NONE
    4. Shopper types 5710 1000 0000 0008
    5. The system detects MASTER and MAESTRO
    6. Default selection: MAESTRO
📘

If brand priority is not set, the system chooses the brand based on the payment brands order.

Security

Protect payments with device authentication

You can improve security by enabling payment confirmation with device authentication.

To complete payment, the customer must provide their credentials (pattern, PIN, password, or fingerprint if available).

Enable this protection for tokens (stored cards and bank account information) to improve security. You can also configure device authentication for any payment brand.

Device authentication is available for Android 5.0 (API level 21) and higher. Three modes exist:

ModeDescription
CheckoutSecurityPolicyMode.DEVICE_AUTH_NOT_REQUIREDDevice authentication is disabled.
CheckoutSecurityPolicyMode.DEVICE_AUTH_REQUIRED_IF_AVAILABLEAuthentication required if set by user.
CheckoutSecurityPolicyMode.DEVICE_AUTH_REQUIREDAuthentication always required. Payment brand won't be available if the device is not secured.

You can configure device authentication per payment brand. For example:

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

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

CheckoutSettings checkoutSettings = new CheckoutSettings(checkoutId, paymentBrands, providerMode);

checkoutSettings.setSecurityPolicyModeForBrand("VISA", CheckoutSecurityPolicyMode.DEVICE_AUTH_REQUIRED);
checkoutSettings.setSecurityPolicyModeForBrand("MASTER", CheckoutSecurityPolicyMode.DEVICE_AUTH_REQUIRED_IF_AVAILABLE);
val paymentBrands = hashSetOf("VISA", "MASTER", "DIRECTDEBIT_SEPA") 
 
val checkoutSettings = CheckoutSettings(checkoutId, paymentBrands, providerMode) 
 
checkoutSettings.setSecurityPolicyModeForBrand("VISA", CheckoutSecurityPolicyMode.DEVICE_AUTH_REQUIRED) 
checkoutSettings.setSecurityPolicyModeForBrand("MASTER", CheckoutSecurityPolicyMode.DEVICE_AUTH_REQUIRED_IF_AVAILABLE)

Set authentication for tokens:

checkoutSettings.setSecurityPolicyModeForTokens(CheckoutSecurityPolicyMode.DEVICE_AUTH_REQUIRED);
checkoutSettings.securityPolicyModeForTokens = CheckoutSecurityPolicyMode.DEVICE_AUTH_REQUIRED

Callbacks

⚠️

Deprecation note: The BroadcastReceiver approach for receiving callbacks has been deprecated since version 6.8.0 in favor of the OnBeforeSubmitCallback interface. This change provides a more modern and type-safe approach to handling payment details before submission. The OnBeforeSubmitCallback interface allows developers to review and modify PaymentDetails prior to transaction submission, offering enhanced flexibility and security. Migrate to OnBeforeSubmitCallback to ensure compatibility with future SDK releases.

Receiving callbacks during checkout

The CheckoutActivity sends the callback before submitting the payment. Use it to review payment details, request a new checkout ID with extra parameters for a specific payment brand, or cancel the payment. Use BroadcastReceiver or OnBeforeSubmitCallback to receive callbacks before submitting payments.

Using OnBeforeSubmitCallback (since version 6.8.0)

Create a class that implements OnBeforeSubmitCallback.

public class CheckoutCallback implements OnBeforeSubmitCallback {

    public CheckoutCallback() {
        // do nothing
    }

    public CheckoutCallback(@NonNull Parcel in) {
        // do nothing
    }

    @Override
    public void onBeforeSubmit(@NonNull PaymentDetails paymentDetails,
                               @NonNull Listener listener) {
        // review and update the payment details if needed
        listener.onComplete(paymentDetails);
    }

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(@NonNull Parcel parcel, int flags) {
        // do nothing
    }

    public static final Creator CREATOR = new Creator() {
        @Override
        public CheckoutCallback createFromParcel(Parcel in) {
            return new CheckoutCallback(in);
        }

        @Override
        public CheckoutCallback[] newArray(int size) {
            return new CheckoutCallback[size];
        }
    };
}
class CheckoutCallback() : OnBeforeSubmitCallback {

    constructor(parcel: Parcel) : this() {
        // do nothing
    }

    override fun onBeforeSubmit(
        paymentDetails: PaymentDetails,
        listener: OnBeforeSubmitCallback.Listener
    ) {
        // review and update the payment details if needed,
        // pass the payment details object back to Mobile SDK using listener
        listener.onComplete(paymentDetails)
    }

    override fun describeContents(): Int {
        return 0
    }

    override fun writeToParcel(
        parcel: Parcel, 
        flags: Int
    ) {
        // do nothing
    }

    companion object CREATOR : Creator {
        override fun createFromParcel(parcel: Parcel): CheckoutCallback {
            return CheckoutCallback(parcel)
        }

        override fun newArray(size: Int): Array {
            return arrayOfNulls(size)
        }
    }
}

Set OnBeforeSubmitCallback in CheckoutSettings.

checkoutSettings.setOnBeforeSubmitCallback(new CheckoutCallback());
checkoutSettings.onBeforeSubmitCallback = CheckoutCallback()

Summary page

It is possible to use the Mobile SDK to temporarily store the payment information after submission, rather than executing it straight away. This can be useful if you want to display an order summary page before committing the payment. For this purpose, you can implement the OnBeforeSubmitCallback callback in which you get PaymentDetails to display on the summary page.

To handle on before submit event using OnBeforeSubmitCallback, create a new class which implements the OnBeforeSubmitCallback interface.

public class CheckoutOnBeforeSubmitListener extends OnBeforeSubmitCallback {
    @Override
    public void onBeforeSubmitUpdate(@NonNull PaymentDetails paymentDetails,
                                                             @NonNull Listener callback) {
        String currentCheckoutId = paymentDetails.getCheckoutId();
        String paymentBrand = paymentDetails.getPaymentBrand();
        /* You can use this callback to request a new checkout ID if selected payment brand requires 
               some specific parameters or just send back the same checkout id to continue checkout process */
        paymentDetails.setCheckoutId(newCheckoutId);
        
        /* You also can abort the transaction using this method */
        paymentDetails.setAborted(true);

        /* Once you are ready to continue the payment flow pass updated paymentDetails to the callback*/
        callback.onComplete(paymentDetails);
    }
}
class CheckoutOnBeforeSubmitListener() : OnBeforeSubmitCallback {
    override fun onBeforeSubmitUpdate(paymentDetails: PaymentDetails, callback: OnBeforeSubmitCallback.Listener) {
        val currentCheckoutId = paymentDetails.checkoutId
        val paymentBrand = paymentDetails.paymentBrand

        /* You can use this callback to request a new checkout ID if selected payment brand requires 
               some specific parameters or just send back the same checkout id to continue checkout process */
        paymentDetails.checkoutId = newCheckoutId

        /* Once you are ready to continue the payment flow pass updated paymentDetails to the callback*/
        callback.onComplete(paymentDetails)
    }
}

You can cancel a payment. In this case set true during paymentDetails.setCanceled() call. The checkout is closed with the ErrorCode.ERROR_CODE_TRANSACTION_ABORTED code.

paymentDetails.setCanceled(true);
callback.onComplete(paymentDetails);
paymentDetails.isCanceled = true
 callback.onComplete(paymentDetails)

COPYandPAY in Mobile SDK

In rare cases, a payment method is not integrated natively into the Mobile SDK. As an alternative, use the COPYandPAY integration for specific brands, such as ONEY.

To configure brand-specific options, create a WpwlOptions object. Refer to COPYandPAY API for more details.

With WpwlOptions, use addValue to add an option, and use addJSFunction to add a JavaScript function. The following snippet gives an example which sets the locale and the onReady callback.

WpwlOptions wpwlOption = new WpwlOptions().addValue("locale", "fr-BE");
wpwlOptions.addJSFunction("onReady", "function() {/* function here */}");
val wpwlOption = WpwlOptions().addValue("inlineFlow", "fr-BE")
wpwlOptions.addJSFunction("onReady","function() {/* function here */}")

Use a Map to associate a payment brand with WpwlOptions, then add the map to CheckoutSettings.

Map<String, WpwlOptions> wpwlOptionsMap = new HashMap<>();
wpwlOptionsMap.put("ONEY", wpwlOptions);

checkoutSettings.setWpwlOptions(wpwlOptionsMap);
val wpwlOptionsMap = mutableMapOf<String, WpwlOptions>()
wpwlOptionsMap["ONEY"] = wpwlOptions

checkoutSettings.setWpwlOptions(wpwlOptionsMap);

Asynchronous payments

In an asynchronous workflow, a redirection happens to allow the account holder to complete or verify the payment. After this, the account holder returns to the app, and you can query the payment status.

This section explains how to support communication between apps for asynchronous payment methods.

📘

This section assumes you have already followed the first integration tutorial.

iOS

Secure browsing

The issuer page opens in a secure web view using SFSafariViewController.

Register a custom URL scheme

  1. In Xcode, click on your project in the Project Navigator and go to App Target > Info > URL Types.
  2. Click [+] to add a new URL type.
  3. Under URL Schemes, enter your app switch return URL scheme. This scheme must start with your app's Bundle ID. For example, if the app bundle ID is com.companyname.appname, then your URL scheme could be com.companyname.appname.payments.
  4. Add the scheme URL to a allowlist in your app's Info.plist:
<key>LSApplicationQueriesSchemes</key>
<array>
    <string>com.companyname.appname.payments</string>
</array>
📘

If you have multiple app targets, add the return URL type for all targets.

Shopper result URL

As soon as an asynchronous payment processes, the shopper result URL opens. Set this URL in the OPPCheckoutSettings class to handle the payment result.

Sample URL: com.companyname.appname.payments://result.

The scheme must match the one registered in the previous step. The rest of the URL can be any valid string (but not empty).

OPPCheckoutSettings *checkoutSettings = [[OPPCheckoutSettings alloc] init];
checkoutSettings.shopperResultURL = @"com.companyname.appname.payments://result";
let checkoutSettings = OPPCheckoutSettings()
checkoutSettings.shopperResultURL = "com.companyname.appname.payments://result"

Do not set shopperResultURL if it is already sent in the first step (prepare checkout request). This causes an override error.

Handle URL request

Handle the specific callback in AppDelegate or SceneDelegate depending on the deployment target of your app. Ensure the URL scheme matches the registered one.

Important: You must close checkout by calling dismissCheckoutAnimated:completion: of the OPPCheckoutProvider class. If you cannot access OPPCheckoutProvider from the app delegate, use broadcast notifications to handle the result in the view controller.

For iOS 10-12, AppDelegate:

- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary *)options {
    if ([url.scheme caseInsensitiveCompare:@"com.companyname.appname.payments"] == NSOrderedSame) {
        [checkoutProvider dismissCheckoutAnimated:YES completion:^{
            // request payment status
        }];
        return YES;
    } else {
        return NO;
    }
}
func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any]) -> Bool {
    if url.scheme?.caseInsensitiveCompare("com.companyname.appname.payments") == .orderedSame {
        checkoutProvider.dismissCheckout(animated: true) {
            // request payment status
        }
        return true
    }
    return false
}

For iOS 13 and newer, SceneDelegate:

- (void)scene:(UIScene *)scene openURLContexts:(NSSet<UIOpenURLContext *> *)URLContexts {
    NSURL *url = [[URLContexts allObjects] firstObject].URL;
    if ([url.scheme caseInsensitiveCompare:@"com.companyname.appname.payments"] == NSOrderedSame) {
        [checkoutProvider dismissCheckoutAnimated:YES completion:^{
            // request payment status
        }];
    }
}
func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
    guard let url = URLContexts.first?.url else {
        return
    }
    
    if (url.scheme?.caseInsensitiveCompare("com.companyname.appname.payments") == .orderedSame) {
        checkoutProvider.dismissCheckout(animated: true) {
            // request payment status
        }
    }
}

Handle aborted transactions

Mobile SDK does not have a built-in option to handle an aborted asynchronous payment. However, you can use it in your application.

Follow these steps:

  1. Use checkoutProvider:checkoutProviderDidFinishSafariViewController. This callback runs when the user taps the Done button in SafariViewController.

    - (void)checkoutProviderDidFinishSafariViewController:(nonnull OPPCheckoutProvider *)checkoutProvider {
        // Save transaction aborted state here.
    }
    func checkoutProviderDidFinishSafariViewController(_ checkoutProvider: OPPCheckoutProvider) {
        // Save transaction aborted state here.
    }
  2. Follow the handle URL request section and check the transaction state in the dismissCheckoutAnimated:completion: method.

Testing

You can test your custom URL scheme by opening a URL that starts with it (example: com.companyname.appname.payments://result) in Mobile Safari on your iOS device or simulator.

Also, always test app switching on a real device.

Android

Secure browsing

To ensure a secure and seamless user experience, the issuer page will be opened using Chrome Custom Tabs within the application.

This approach provides:

Enhanced Security: Chrome Custom Tabs leverage the security features of the Chrome browser, including safe browsing and sandboxing. Consistent UI: Maintains the look and feel of your app while using the browser's capabilities. User Trust: Displays the verified URL and security indicators (like HTTPS lock icon).

Register a custom URL scheme

Define the name of your custom scheme (for example, companyname) and add an intent filter to your target activity in the AndroidManifest.xml.

<activity
    android:name="YOUR_ACTIVITY"
    android:launchMode="singleTask">

    <intent-filter>
        <data android:scheme="companyname"/>

        <action android:name="android.intent.action.VIEW"/>

        <category android:name="android.intent.category.DEFAULT"/>
        <category android:name="android.intent.category.BROWSABLE"/>
    </intent-filter>
</activity>
📘

Set launchMode to singleTask for the target activity.

Shopper result URL

When an asynchronous payment completes, the shopper result URL opens. Set it in the CheckoutSettings class to handle the payment result.

Sample URL: companyname://result

The scheme must match the one registered in the previous step. The remaining part of the URL can be any valid string (but not empty).

CheckoutSettings checkoutSettings = new CheckoutSettings(checkoutId, paymentBrands, providerMode)
    .setShopperResultUrl("companyname://result");
val checkoutSettings = CheckoutSettings(checkoutId, paymentBrands, providerMode) 
        .setShopperResultUrl("companyname://result")
📘

Do not set shopperResultUrl if it is already sent in the first step (prepare checkout request). Doing so causes an override error.

Handle the redirect intent in target activity

Override the onNewIntent method of your target activity to handle the redirect intent and request the payment status.

@Override
protected void onNewIntent(Intent intent) {
    super.onNewIntent(intent);

    if (intent.getScheme().equals("companyname")) {
        // request payment status if ActivityResultCallback is received
    }
}
override fun onNewIntent(intent: Intent) { 
    super.onNewIntent(intent) 

 
    if (intent.scheme == "companyname") { 
        // request payment status if ActivityResultCallback is received
    } 
}
📘
  • Before Android 10 (API level 29), onNewIntent() runs before onStart() and ActivityResultCallback. Handle this case this way.
  • The URL does not open for the BOLETO payment brand. Use your activity callback to detect when the shopper returns to the app, for example, onStart().

Handle aborted transactions

Mobile SDK does not have a built-in option to handle an aborted asynchronous payment. However, you can handle it in your application.

Your application must track the transaction state. Create a TransactionState enumeration:

enum TransactionState {
    NEW,
    PENDING,
    COMPLETED
}
enum class TransactionState {
    NEW,
    PENDING,
    COMPLETED
}

Mark a new transaction as NEW and save this value until checkout ends.

Handle CheckoutActivityResult

If the payment was asynchronous, change the transaction state to PENDING.

private void handleCheckoutResult(@NonNull CheckoutActivityResult result) {
    if (result.isCanceled()) {
        // shopper cancelled the checkout process
        return;
    }

    Transaction transaction = result.getTransaction();

    if (transaction != null) {
        if (transaction.getTransactionType() == TransactionType.SYNC) {
            transactionState = TransactionState.COMPLETED;
        } else {
            // `onNewIntent()` might have already run if the activity was destroyed in the background
            // ensure it does not overwrite the COMPLETED state
            if (!transactionState.equals(TransactionState.COMPLETED)) {
                transactionState = TransactionState.PENDING;
            }
        }
    }
}
private fun handleCheckoutResult(result: CheckoutActivityResult) {
    if (result.isCanceled) {
        // shopper cancelled the checkout process
         return
    }

    val transaction: Transaction? = result.transaction

    if (transaction != null) {
        if (transaction.transactionType === TransactionType.SYNC) {
            transactionState = TransactionState.COMPLETED;
        } else {
            // onNewIntent() might be already invoked if activity was destroyed in background,
            // make sure you don't overwrite COMPLETED state
            if (!transactionState.equals(TransactionState.COMPLETED)) {
                transactionState = TransactionState.PENDING;
            }
        }
    }
}

Override onNewIntent() to handle redirect intent

Update the transaction state to COMPLETED.

@Override
protected void onNewIntent(Intent intent) {
    super.onNewIntent(intent);

    if (intent.getScheme().equals("companyname")) {
        transactionState = TransactionState.COMPLETED;
    }
}
override fun onNewIntent(intent: Intent) {
    super.onNewIntent(intent)

    if (intent.scheme == "companyname") {
        transactionState = TransactionState.COMPLETED;
    }
}

Override onResume()

If the transaction state is not COMPLETED at this point, then the system aborted the transaction.

@Override
protected void onResume() {
    super.onResume();

    if (transactionState.equals(TransactionState.PENDING)) {
        requestPaymentStatus(resourcePath);
        // handle aborted transaction
    } else if (transactionState.equals(TransactionState.COMPLETED)) {
        requestPaymentStatus(resourcePath);
    }
}
override fun onResume(intent: Intent) {
    super.onNewIntent(intent)

    if (transactionState.equals(TransactionState.PENDING)) {
        // handle aborted transaction
    } else if (transactionState.equals(TransactionState.COMPLETED)) {
        requestPaymentStatus(resourcePath);
    }
}

Tokenisation

Tokenisation replaces sensitive payment data, such as credit card numbers, with a unique identifier or, token. After you receive a token for card data or a bank account, you can process transactions using these tokens without storing the original payment data.

Supported payment methods for storage

Card details

You can tokenise the following card details:

  • Cardholder name
  • Card number
  • Expiry date
📘

You cannot store the CVV with a token. Prompt the shopper to re-enter the CVV code for subsequent transactions, if required.

Bank accounts

For DIRECTDEBIT_SEPA, the system stores the following details:

  • Bank account holder
  • IBAN

Virtual accounts

You can use PAYPAL tokens for payments since version 2.66.0.

iOS

Storing payment data

The Mobile SDK offers two options for storing payment data:

Store data during a payment

When a shopper checks out for the first time, they provide complete payment data. Use this option to automatically store their data for future transactions.

  1. You have two options:

    1. Merchant-determined tokenisation

      During the checkout process, you can store the data by adding extra parameters to the normal prepare checkout request, as described in COPYandPAY merchant-determined tokenisation.

    2. Shopper-determined tokenisation

      Extend the payment form to display an option allowing shoppers to store payment details.

      Set the storePaymentDetailsMode of the OPPCheckoutSettings to one of the following:

      • OPPCheckoutStorePaymentDetailsModeNever: The system does not save payment details by default.
      • OPPCheckoutStorePaymentDetailsModePrompt: The shopper confirms whether to save payment information for future purchases; the payment form includes a switch.
      • OPPCheckoutStorePaymentDetailsModeAlways: The system saves the shopper's payment details for each new payment.
      OPPCheckoutSettings *settings = [[OPPCheckoutSettings alloc] init];
      settings.storePaymentDetails = OPPCheckoutStorePaymentDetailsModePrompt;
      …
      ```swift
      let settings = OPPCheckoutSettings()
      settings.storePaymentDetails = .prompt
      …
  2. Receive a token with payment status

    If your site offers an administrative area where shoppers can register their payment details independently of the checkout process, use this option.

    When storing payment details, the server generates a token for the provided data and returns it with the payment status. The registrationId parameter represents the generated token. The server handles and stores this token.

    Example of Payment Status response:

    {
    "registrationId": "8a82944a580a782101581f3a0b4b5ab9",
    "result": {
        "code": "000.100.110",
        "description": "Request successfully processed in 'Merchant in Integrator Test Mode'"
    }
    // ...
    }

Store data as a stand-alone

If your site provides shoppers with an administrative area where they can register their payment details independent of a checkout process, this option is for you.

To create a registration separate from any payment:

  1. Prepare checkout and change two parameters:

    • Set createRegistration=true.
    • Do not send paymentType.
    curl https://sandbox-card.peachpayments.com/v1/checkouts \
    -d "entityId=8a8294174e735d0c014e78cf26461790" \
    -d "notificationUrl=http://www.example.com/notify" \
    -d "createRegistration=true" \
    -H "Authorization: Bearer OGE4Mjk0MTc0ZTczNWQwYzAxNGU3OGNmMjY2YjE3OTR8SFV3I3JGQTQ9bWpxaWYrPz9OWVQ="
    public Dictionary<string, dynamic> Request() {
        Dictionary<string, dynamic> responseData;
        string data="entityId=8a8294174e735d0c014e78cf26461790" +
            "&notificationUrl=http://www.example.com/notify" +
            "&createRegistration=true";
        string url = "https://sandbox-card.peachpayments.com/v1/checkouts";
        byte[]  buffer = Encoding.ASCII.GetBytes(data);
        HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(url);
        request.Method = "POST";
            request.Headers["Authorization"] = "Bearer OGE4Mjk0MTc0ZTczNWQwYzAxNGU3OGNmMjY2YjE3OTR8SFV3I3JGQTQ9bWpxaWYrPz9OWVQ=";
        request.ContentType = "application/x-www-form-urlencoded";
        Stream PostData = request.GetRequestStream();
        PostData.Write(buffer, 0, buffer.Length);
        PostData.Close();
        using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
        {
            Stream dataStream = response.GetResponseStream();
            StreamReader reader = new StreamReader(dataStream);
            var s = new JavaScriptSerializer();
            responseData = s.Deserialize<Dictionary<string, dynamic>>(reader.ReadToEnd());
            reader.Close();
            dataStream.Close();
        }
        return responseData;
    }
    
    responseData = Request()["result"]["description"];
    import groovy.json.JsonSlurper
    
    public static String request() {
    def data = "entityId=8a8294174e735d0c014e78cf26461790" +
    "&notificationUrl=http://www.example.com/notify" +
    "&createRegistration=true"
    def url = "https://sandbox-card.peachpayments.com/v1/checkouts".toURL()
    def connection = url.openConnection()
    connection.setRequestMethod("POST")
    connection.setRequestProperty("Authorization","Bearer OGE4Mjk0MTc0ZTczNWQwYzAxNGU3OGNmMjY2YjE3OTR8SFV3I3JGQTQ9bWpxaWYrPz9OWVQ=")
    connection.doOutput = true
    connection.outputStream << data
    def json = new JsonSlurper().parseText(connection.inputStream.text)
    json
    }
    println request()
    private String request() throws IOException {
        URL url = new URL("https://sandbox-card.peachpayments.com/v1/checkouts");
    
        HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
        conn.setRequestMethod("POST");
            conn.setRequestProperty("Authorization", "Bearer OGE4Mjk0MTc0ZTczNWQwYzAxNGU3OGNmMjY2YjE3OTR8SFV3I3JGQTQ9bWpxaWYrPz9OWVQ=");
        conn.setDoInput(true);
        conn.setDoOutput(true);
    
        String data = ""
            + "entityId=8a8294174e735d0c014e78cf26461790"
            + "&notificationUrl=http://www.example.com/notify"
            + "&createRegistration=true";
    
        DataOutputStream wr = new DataOutputStream(conn.getOutputStream());
        wr.writeBytes(data);
        wr.flush();
        wr.close();
        int responseCode = conn.getResponseCode();
        InputStream is;
    
        if (responseCode >= 400) is = conn.getErrorStream();
        else is = conn.getInputStream();
    
        return IOUtils.toString(is);
    }
    const https = require('https');
    const querystring = require('querystring');
    
    const request = async () => {
        const path='/v1/checkouts';
        const data = querystring.stringify({
            'entityId':'8a8294174e735d0c014e78cf26461790',
            'notificationUrl':'http://www.example.com/notify',
            'createRegistration':'true'
        });
        const options = {
            port: 443,
            host: 'sandbox-card.peachpayments.com',
            path: path,
            method: 'POST',
            headers: {
                'Content-Type': 'application/x-www-form-urlencoded',
                'Content-Length': data.length,
                'Authorization':'Bearer OGE4Mjk0MTc0ZTczNWQwYzAxNGU3OGNmMjY2YjE3OTR8SFV3I3JGQTQ9bWpxaWYrPz9OWVQ='
            }
        };
        return new Promise((resolve, reject) => {
            const postRequest = https.request(options, function(res) {
                const buf = [];
                res.on('data', chunk => {
                    buf.push(Buffer.from(chunk));
                });
                res.on('end', () => {
                    const jsonString = Buffer.concat(buf).toString('utf8');
                    try {
                        resolve(JSON.parse(jsonString));
                    } catch (error) {
                        reject(error);
                    }
                });
            });
            postRequest.on('error', reject);
            postRequest.write(data);
            postRequest.end();
        });
    };
    
    request().then(console.log).catch(console.error);
    function request() {
        $url = "https://sandbox-card.peachpayments.com/v1/checkouts";
        $data = "entityId=8a8294174e735d0c014e78cf26461790" .
                    "&notificationUrl=http://www.example.com/notify" .
                    "&createRegistration=true";
    
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_HTTPHEADER, array(
                    'Authorization:Bearer OGE4Mjk0MTc0ZTczNWQwYzAxNGU3OGNmMjY2YjE3OTR8SFV3I3JGQTQ9bWpxaWYrPz9OWVQ='));
        curl_setopt($ch, CURLOPT_POST, 1);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);// this should be set to true in production
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        $responseData = curl_exec($ch);
        if(curl_errno($ch)) {
            return curl_error($ch);
        }
        curl_close($ch);
        return $responseData;
    }
    $responseData = request();
    try:
        from urllib.parse import urlencode
        from urllib.request import build_opener, Request, HTTPHandler
        from urllib.error import HTTPError, URLError
    except ImportError:
        from urllib import urlencode
        from urllib2 import build_opener, Request, HTTPHandler, HTTPError, URLError
    import json
    
    def request():
        url = "https://sandbox-card.peachpayments.com/v1/checkouts"
        data = {
            'entityId' : '8a8294174e735d0c014e78cf26461790',
            'notificationUrl' : 'http://www.example.com/notify',
            'createRegistration' : 'true'
        }
        try:
            opener = build_opener(HTTPHandler)
            request = Request(url, data=urlencode(data).encode('utf-8'))
            request.add_header('Authorization', 'Bearer OGE4Mjk0MTc0ZTczNWQwYzAxNGU3OGNmMjY2YjE3OTR8SFV3I3JGQTQ9bWpxaWYrPz9OWVQ=')
            request.get_method = lambda: 'POST'
            response = opener.open(request)
            return json.loads(response.read())
        except HTTPError as e:
            return json.loads(e.read())
        except URLError as e:
            return e.reason
    
    responseData = request()
    print(responseData)
    require 'net/https'
    require 'uri'
    require 'json'
    
    def request()
        uri = URI('https://sandbox-card.peachpayments.com/v1/checkouts')
        http = Net::HTTP.new(uri.host, uri.port)
        http.use_ssl = true
        req = Net::HTTP::Post.new(uri.path)
        req.set_form_data({
            'entityId' => '8a8294174e735d0c014e78cf26461790',
            'notificationUrl' => 'http://www.example.com/notify',
            'createRegistration' => 'true'
        })
        res = http.request(req)
        return JSON.parse(res.body)
    end
    
    puts request()
    def initialPayment : String = {
        val url = "https://sandbox-card.peachpayments.com/v1/checkouts"
        val data = (""
            + "entityId=8a8294174e735d0c014e78cf26461790"
            + "&notificationUrl=http://www.example.com/notify"
            + "&createRegistration=true"
        )
        val conn = new URL(url).openConnection()
    
        conn match {
            case secureConn: HttpsURLConnection  => secureConn.setRequestMethod("POST")
            case _ => throw new ClassCastException
        }
        conn.setDoInput(true)
        conn.setDoOutput(true)
        IOUtils.write(data, conn.getOutputStream())
        conn.setRequestProperty("Authorization", "Bearer OGE4Mjk0MTc0ZTczNWQwYzAxNGU3OGNmMjY2YjE3OTR8SFV3I3JGQTQ9bWpxaWYrPz9OWVQ=")
        conn.connect()
        if (conn.getResponseCode() >= 400) {
            return IOUtils.toString(conn.getErrorStream())
        }
        else {
            return IOUtils.toString(conn.getInputStream())
        }
    }
    Public Function Request() As Dictionary(Of String, Object)
        Dim url As String = "https://sandbox-card.peachpayments.com/v1/checkouts"
        Dim data As String = "" +
            "entityId=8a8294174e735d0c014e78cf26461790" +
            "&notificationUrl=http://www.example.com/notify" +
            "&createRegistration=true"
    
        Dim req As WebRequest = WebRequest.Create(url)
        req.Method = "POST"
            req.Headers.Add("Authorization", "Bearer OGE4Mjk0MTc0ZTczNWQwYzAxNGU3OGNmMjY2YjE3OTR8SFV3I3JGQTQ9bWpxaWYrPz9OWVQ=")
        req.ContentType = "application/x-www-form-urlencoded"
        Dim byteArray As Byte() = Encoding.UTF8.GetBytes(data)
        req.ContentLength = byteArray.Length
        Dim dataStream As Stream = req.GetRequestStream()
        dataStream.Write(byteArray, 0, byteArray.Length)
        dataStream.Close()
        Dim res As WebResponse = req.GetResponse()
        Dim resStream = res.GetResponseStream()
        Dim reader As New StreamReader(resStream)
        Dim response As String = reader.ReadToEnd()
        reader.Close()
        resStream.Close()
        res.Close()
        Dim jss As New System.Web.Script.Serialization.JavaScriptSerializer()
        Dim dict As Dictionary(Of String, Object) = jss.Deserialize(Of Dictionary(Of String, Object))(response)
    
        Return dict
    End Function
    
    responseData = Request()("result")("description")
    {
    "result":{
      "code":"000.200.100",
      "description":"successfully created checkout"
    },
    "buildNumber":"9092e7a6af8301accda2f9a3a38f743f907dadd5@2026-03-23 16:50:06 +0000",
    "timestamp":"2026-03-26 14:55:22+0000",
    "ndc":"ADE164F28B623382238F480F7E9BD1D2.uat01-vm-tx02",
    "id":"ADE164F28B623382238F480F7E9BD1D2.uat01-vm-tx02"
    }

    The Mobile SDK adapts the workflow to handle registration.

  2. Receive a token with registration status: After sending the transaction, you receive a resourcePath=/v1/checkouts/{checkoutId}/registration to query the registration result. Request the transaction status using this resource path.

    📘

    The resource path for registration-only transactions differs from the path for obtaining payment status.

Using payment data

Prepare checkout with tokens

To prepare checkout with tokens:

  • Add the shopper's tokens to the prepare checkout request. Your server should send these tokens along with other configuration data, such as amount, currency, and order type.

  • Send tokens in the registrations[n].id parameter, where n is a sequence number starting from zero for each shopper's token. For example, if the shopper has two tokens, send registrations[0].id = {first tokenID} and registrations[1].id = {second tokenID}.

Stored payment methods UI

Before presenting payment pages, the SDK fetches tokens related to the checkoutId from the server and displays the pre-filled payment methods to the shopper.

📘

Since you cannot store the CVV with a token, prompt the user to re-enter the CVV code for subsequent transactions, if required.

Skipping 3D-Secure for stored cards

To skip 3D-Secure for payments with stored cards, send the recurringType=REGISTRATION_BASED parameter during the checkout preparation.

Android

Storing payment data

The Mobile SDK offers two options for storing payment data:

Store data during a payment

When a shopper checks out for the first time, they provide complete payment data. Use this option to automatically store their data for future transactions.

  1. You have two options:

    1. Merchant-determined tokenisation

      During checkout, store data by adding parameters to the prepare checkout request, as described in the COPYandPAY merchant-determined tokenisation guide.

    2. Shopper-determined tokenisation

      Extend the payment form to display an option allowing shoppers to store their payment details.

      Set the storePaymentDetailsMode of the CheckoutSettings to one of the following:

      • CheckoutStorePaymentDetailsMode.NEVER: The system does not save payment details by default.
      • CheckoutStorePaymentDetailsMode.PROMPT: The shopper confirms whether to save payment information for future purchases; the payment form includes a switch.
      • CheckoutStorePaymentDetailsMode.ALWAYS: The system saves the shopper's payment details for each new payment.
      CheckoutSettings checkoutSettings = new CheckoutSettings(checkoutId, paymentBrands, providerMode);
      checkoutSettings.setStorePaymentDetailsMode(CheckoutStorePaymentDetailsMode.PROMPT);
      val checkoutSettings = CheckoutSettings(checkoutId, paymentBrands, providerMode) 
      checkoutSettings.storePaymentDetailsMode = CheckoutStorePaymentDetailsMode.PROMPT
  2. Receive a token with payment status

    When storing payment details, the server generates a token for the provided data and returns it with the payment status. The registrationId parameter is your generated token. Your server handles and stores this token.

    Example of payment status response:

    {
    "registrationId": "8a82944a580a782101581f3a0b4b5ab9",
    "result": {
        "code": "000.100.110",
        "description": "Request successfully processed in 'Merchant in Integrator Test Mode'"
    }
    // ...
    }

Store data as a stand-alone process

If your site offers an administrative area where shoppers can register their payment details independently of the checkout process, use this option.

To create a registration separate from any payment:

  1. Prepare checkout and change two parameters:

    • Set createRegistration=true.
    • Do not send paymentType.
    curl https://sandbox-card.peachpayments.com/v1/checkouts \
    -d "entityId=8a8294174e735d0c014e78cf26461790" \
    -d "notificationUrl=http://www.example.com/notify" \
    -d "createRegistration=true" \
    -H "Authorization: Bearer OGE4Mjk0MTc0ZTczNWQwYzAxNGU3OGNmMjY2YjE3OTR8SFV3I3JGQTQ9bWpxaWYrPz9OWVQ="
    public Dictionary<string, dynamic> Request() {
        Dictionary<string, dynamic> responseData;
        string data="entityId=8a8294174e735d0c014e78cf26461790" +
            "&notificationUrl=http://www.example.com/notify" +
            "&createRegistration=true";
        string url = "https://sandbox-card.peachpayments.com/v1/checkouts";
        byte[]  buffer = Encoding.ASCII.GetBytes(data);
        HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(url);
        request.Method = "POST";
            request.Headers["Authorization"] = "Bearer OGE4Mjk0MTc0ZTczNWQwYzAxNGU3OGNmMjY2YjE3OTR8SFV3I3JGQTQ9bWpxaWYrPz9OWVQ=";
        request.ContentType = "application/x-www-form-urlencoded";
        Stream PostData = request.GetRequestStream();
        PostData.Write(buffer, 0, buffer.Length);
        PostData.Close();
        using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
        {
            Stream dataStream = response.GetResponseStream();
            StreamReader reader = new StreamReader(dataStream);
            var s = new JavaScriptSerializer();
            responseData = s.Deserialize<Dictionary<string, dynamic>>(reader.ReadToEnd());
            reader.Close();
            dataStream.Close();
        }
        return responseData;
    }
    
    responseData = Request()["result"]["description"];
    import groovy.json.JsonSlurper
    
    public static String request() {
    def data = "entityId=8a8294174e735d0c014e78cf26461790" +
    "&notificationUrl=http://www.example.com/notify" +
    "&createRegistration=true"
    def url = "https://sandbox-card.peachpayments.com/v1/checkouts".toURL()
    def connection = url.openConnection()
    connection.setRequestMethod("POST")
    connection.setRequestProperty("Authorization","Bearer OGE4Mjk0MTc0ZTczNWQwYzAxNGU3OGNmMjY2YjE3OTR8SFV3I3JGQTQ9bWpxaWYrPz9OWVQ=")
    connection.doOutput = true
    connection.outputStream << data
    def json = new JsonSlurper().parseText(connection.inputStream.text)
    json
    }
    println request()
    private String request() throws IOException {
        URL url = new URL("https://sandbox-card.peachpayments.com/v1/checkouts");
    
        HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
        conn.setRequestMethod("POST");
            conn.setRequestProperty("Authorization", "Bearer OGE4Mjk0MTc0ZTczNWQwYzAxNGU3OGNmMjY2YjE3OTR8SFV3I3JGQTQ9bWpxaWYrPz9OWVQ=");
        conn.setDoInput(true);
        conn.setDoOutput(true);
    
        String data = ""
            + "entityId=8a8294174e735d0c014e78cf26461790"
            + "&notificationUrl=http://www.example.com/notify"
            + "&createRegistration=true";
    
        DataOutputStream wr = new DataOutputStream(conn.getOutputStream());
        wr.writeBytes(data);
        wr.flush();
        wr.close();
        int responseCode = conn.getResponseCode();
        InputStream is;
    
        if (responseCode >= 400) is = conn.getErrorStream();
        else is = conn.getInputStream();
    
        return IOUtils.toString(is);
    }
    const https = require('https');
    const querystring = require('querystring');
    
    const request = async () => {
        const path='/v1/checkouts';
        const data = querystring.stringify({
            'entityId':'8a8294174e735d0c014e78cf26461790',
            'notificationUrl':'http://www.example.com/notify',
            'createRegistration':'true'
        });
        const options = {
            port: 443,
            host: 'sandbox-card.peachpayments.com',
            path: path,
            method: 'POST',
            headers: {
                'Content-Type': 'application/x-www-form-urlencoded',
                'Content-Length': data.length,
                'Authorization':'Bearer OGE4Mjk0MTc0ZTczNWQwYzAxNGU3OGNmMjY2YjE3OTR8SFV3I3JGQTQ9bWpxaWYrPz9OWVQ='
            }
        };
        return new Promise((resolve, reject) => {
            const postRequest = https.request(options, function(res) {
                const buf = [];
                res.on('data', chunk => {
                    buf.push(Buffer.from(chunk));
                });
                res.on('end', () => {
                    const jsonString = Buffer.concat(buf).toString('utf8');
                    try {
                        resolve(JSON.parse(jsonString));
                    } catch (error) {
                        reject(error);
                    }
                });
            });
            postRequest.on('error', reject);
            postRequest.write(data);
            postRequest.end();
        });
    };
    
    request().then(console.log).catch(console.error);
    function request() {
        $url = "https://sandbox-card.peachpayments.com/v1/checkouts";
        $data = "entityId=8a8294174e735d0c014e78cf26461790" .
                    "&notificationUrl=http://www.example.com/notify" .
                    "&createRegistration=true";
    
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_HTTPHEADER, array(
                    'Authorization:Bearer OGE4Mjk0MTc0ZTczNWQwYzAxNGU3OGNmMjY2YjE3OTR8SFV3I3JGQTQ9bWpxaWYrPz9OWVQ='));
        curl_setopt($ch, CURLOPT_POST, 1);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);// this should be set to true in production
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        $responseData = curl_exec($ch);
        if(curl_errno($ch)) {
            return curl_error($ch);
        }
        curl_close($ch);
        return $responseData;
    }
    $responseData = request();
    try:
        from urllib.parse import urlencode
        from urllib.request import build_opener, Request, HTTPHandler
        from urllib.error import HTTPError, URLError
    except ImportError:
        from urllib import urlencode
        from urllib2 import build_opener, Request, HTTPHandler, HTTPError, URLError
    import json
    
    def request():
        url = "https://sandbox-card.peachpayments.com/v1/checkouts"
        data = {
            'entityId' : '8a8294174e735d0c014e78cf26461790',
            'notificationUrl' : 'http://www.example.com/notify',
            'createRegistration' : 'true'
        }
        try:
            opener = build_opener(HTTPHandler)
            request = Request(url, data=urlencode(data).encode('utf-8'))
            request.add_header('Authorization', 'Bearer OGE4Mjk0MTc0ZTczNWQwYzAxNGU3OGNmMjY2YjE3OTR8SFV3I3JGQTQ9bWpxaWYrPz9OWVQ=')
            request.get_method = lambda: 'POST'
            response = opener.open(request)
            return json.loads(response.read())
        except HTTPError as e:
            return json.loads(e.read())
        except URLError as e:
            return e.reason
    
    responseData = request()
    print(responseData)
    require 'net/https'
    require 'uri'
    require 'json'
    
    def request()
        uri = URI('https://sandbox-card.peachpayments.com/v1/checkouts')
        http = Net::HTTP.new(uri.host, uri.port)
        http.use_ssl = true
        req = Net::HTTP::Post.new(uri.path)
        req.set_form_data({
            'entityId' => '8a8294174e735d0c014e78cf26461790',
            'notificationUrl' => 'http://www.example.com/notify',
            'createRegistration' => 'true'
        })
        res = http.request(req)
        return JSON.parse(res.body)
    end
    
    puts request()
    def initialPayment : String = {
        val url = "https://sandbox-card.peachpayments.com/v1/checkouts"
        val data = (""
            + "entityId=8a8294174e735d0c014e78cf26461790"
            + "&notificationUrl=http://www.example.com/notify"
            + "&createRegistration=true"
        )
        val conn = new URL(url).openConnection()
    
        conn match {
            case secureConn: HttpsURLConnection  => secureConn.setRequestMethod("POST")
            case _ => throw new ClassCastException
        }
        conn.setDoInput(true)
        conn.setDoOutput(true)
        IOUtils.write(data, conn.getOutputStream())
        conn.setRequestProperty("Authorization", "Bearer OGE4Mjk0MTc0ZTczNWQwYzAxNGU3OGNmMjY2YjE3OTR8SFV3I3JGQTQ9bWpxaWYrPz9OWVQ=")
        conn.connect()
        if (conn.getResponseCode() >= 400) {
            return IOUtils.toString(conn.getErrorStream())
        }
        else {
            return IOUtils.toString(conn.getInputStream())
        }
    }
    Public Function Request() As Dictionary(Of String, Object)
        Dim url As String = "https://sandbox-card.peachpayments.com/v1/checkouts"
        Dim data As String = "" +
            "entityId=8a8294174e735d0c014e78cf26461790" +
            "&notificationUrl=http://www.example.com/notify" +
            "&createRegistration=true"
    
        Dim req As WebRequest = WebRequest.Create(url)
        req.Method = "POST"
            req.Headers.Add("Authorization", "Bearer OGE4Mjk0MTc0ZTczNWQwYzAxNGU3OGNmMjY2YjE3OTR8SFV3I3JGQTQ9bWpxaWYrPz9OWVQ=")
        req.ContentType = "application/x-www-form-urlencoded"
        Dim byteArray As Byte() = Encoding.UTF8.GetBytes(data)
        req.ContentLength = byteArray.Length
        Dim dataStream As Stream = req.GetRequestStream()
        dataStream.Write(byteArray, 0, byteArray.Length)
        dataStream.Close()
        Dim res As WebResponse = req.GetResponse()
        Dim resStream = res.GetResponseStream()
        Dim reader As New StreamReader(resStream)
        Dim response As String = reader.ReadToEnd()
        reader.Close()
        resStream.Close()
        res.Close()
        Dim jss As New System.Web.Script.Serialization.JavaScriptSerializer()
        Dim dict As Dictionary(Of String, Object) = jss.Deserialize(Of Dictionary(Of String, Object))(response)
    
        Return dict
    End Function
    
    responseData = Request()("result")("description")
    {
    "result":{
      "code":"000.200.100",
      "description":"successfully created checkout"
    },
    "buildNumber":"9092e7a6af8301accda2f9a3a38f743f907dadd5@2026-03-23 16:50:06 +0000",
    "timestamp":"2026-03-26 14:56:50+0000",
    "ndc":"B518F980A3EF5151E4FB3653A5730960.uat01-vm-tx04",
    "id":"B518F980A3EF5151E4FB3653A5730960.uat01-vm-tx04"
    }

    The Mobile SDK adapts the workflow to handle registration.

  2. Receive a token with registration status: After sending the transaction, you receive a resourcePath=/v1/checkouts/{checkoutId}/registration to query the registration result. Request the transaction status using this resource path.

    📘

    The resource path for registration-only transactions differs from the path for obtaining payment status.

Using payment data

Prepare checkout with tokens

To prepare checkout with tokens:

  • Add the shopper's tokens to the prepare checkout request. Your server should send these tokens along with other configuration data, such as amount, currency, and order type.

  • Send tokens in the registrations[n].id parameter, where n is a sequence number starting from zero for each shopper's token. For example, if the shopper has two tokens, send registrations[0].id = {first tokenID} and registrations[1].id = {second tokenID}.

Stored payment methods UI

Before presenting payment pages, the SDK fetches tokens related to the checkoutId from the server and displays the pre-filled payment methods to the shopper.

📘

Since you cannot store the CVV with a token, prompt the user to re-enter the CVV code for subsequent transactions, if required.

Skipping 3-D Secure for stored cards

To skip 3-D Secure for payments with stored cards, send the recurringType=REGISTRATION_BASED parameter during the checkout preparation.

Card scanning

Peach Payments' checkout UI supports camera-based card number recognition, allowing users to scan their card details for a faster and more convenient payment experience.

iOS

By default, the card scan feature is on in the ready-to-use UI. Since version 5.2.0, you can control this option by setting the cardScanButtonAvailable property of the OPPCheckoutSettings class.

OPPCheckoutSettings *checkoutSettings = [[OPPCheckoutSettings alloc] init];
checkoutSettings.cardScanButtonAvailable = NO; // Set to YES to turn it on
let checkoutSettings = OPPCheckoutSettings()
checkoutSettings.cardScanButtonAvailable = false

The card scanning feature allows users to capture their card number and expiry date using their device's camera, reducing the need for manual data entry.

  1. Start scanning: Tap the camera icon in the card number field.
  2. Grant permissions: On first use, the app asks for camera access.
  3. Position the card: Align the card in the on-screen frame. The app detects and scans the card number and expiry date.
  4. Enter other details: Type the cardholder's name and CVV code, as these are not captured during scanning.

Integration steps

To add the card scanning feature to your iOS app, follow these steps:

  1. Remove previous dependencies: If upgrading from an earlier version of the Mobile SDK, remove OPPWAMobile-CardIO.xcframework from the Frameworks folder.
  2. Update Info.plist: Add usage descriptions to your app's Info.plist file:
    • NSCameraUsageDescription: Add a message explaining why your app needs camera access (for example, "To scan credit cards."). This message appears when the app asks for camera permissions.
    • NSPhotoLibraryUsageDescription: While card.io does not ask for this key, required when uploading to the App Store. Set any non-empty string, such as duplicating the NSCameraUsageDescription.

Following these steps improves the user experience by enabling efficient and secure card scanning.

Android

Peach Payments' checkout UI supports card scanning using NFC.

The system enables that card scanning feature by default. Disable it in CheckoutSettings if needed.

checkoutSettings.setCardScanningEnabled(false);
checkoutSettings.isCardScanningEnabled = false

Integration steps

NFC allows storing payment details during payment. You can read the card number and expiry date from the contactless card.

  • Tap the contactless card icon in the card number input field.
  • Hold the contactless card near the device's NFC reader.
  • Follow the on-screen instructions to complete the scan.
  • The scanner fills in the card number and expiry date automatically.
📘
  • The NFC card scanner might not read the card data. In this case, the shopper can enter the card details manually.
  • This feature uses NFC to read the card number and expiration date from a contactless card and automatically populates them into the card payment form. It does not perform an EMV transaction or support card-present processing.