3.1 Android SDK

3.1.1 Introduction

  • The Samsung Pay SDK allows Android-based partner apps—such as merchant apps and issuer banking apps—to securely integrate features of Integration Wallet, enabling in-app payments, push provisioning, and more.

The following major operations are supported:

  • In-App Payment - Gives customers the option of paying for products and services with Samsung Wallet.
  • Push Provisioning - Allows customers add a bank card to Samsung Wallet from the issuer app by providing the required card details.

To integrate your partner application with the Samsung Pay SDK, the following components are included your SDK download:

  • Samsungpay.jar - Contains classes and interfaces of the Samsung Pay SDK which need to be integrated to partner apps.
  • Javadoc - Provides descriptions of the APIs included in the Samsung Pay SDK, along with sample code showing how to use them.
  • Sample merchant app and sample issuer app showing how Samsung Pay APIs can be coded in a finished Android project. All major operations of Samsung Pay SDK are implemented for demonstration purposes.

3.1.2 Samsung Pay SDK Architecture

The following diagram shows a high-level architecture revealing the general interactions between the Samsung Pay SDK and a partner app.

Viewed at this level, the partner apps leverage the Samsung Pay SDK to perform the operations shown ― push provisioning and opening Favorite Cards for issuers; online payments for merchants ― with Samsung Pay.

The key components involved are:

  • Partner app - merchant- or issuer-developed app for making online/offline payments and provisioning payment cards through Samsung Wallet.
  • Samsung Pay SDK - SDK integrated into the partner app for direct communication with Samsung Wallet.
  • Samsung Wallet app - Wallet app that the Samsung Pay SDK communicates with.
  • Financial network - comprises the payment gateways, acquirers, card associations, and issuers that participate in transaction processing under agreement with the merchant.

The main classes comprising the Samsung Pay SDK include:

  • SamsungPay – used by the partner app to get the Samsung Pay SDK information and the status of Samsung Wallet app on the device.
  • PaymentManager – provides payment/transaction functionality.
  • CardManager – manages card list (get, add, update) functionality.
  • WatchManager – manages all functions related to Samsung Pay Watch.
  • CardInfoListener – interface for requestCardInfo result from Samsung Wallet.
  • CustomSheetTransactionInfoListener – interface for transaction success/failure callbacks from Samsung Wallet.

3.1.3 Uses Cases

In-App Payment

The most common In-App (online) payment use case take the following form:

  • Merchant app presents user with the option of making payment with Samsung Wallet.
  • Upon the user selecting the Samsung Pay option, the merchant app calls the APIs included in the Samsung Pay SDK to initiate a transaction with Samsung Wallet app.
  • Samsung Wallet app responds with the tokenized payment information necessary to complete the transaction.
  • Merchant app forwards this payment information to the designated Payment Gateway (PG), either directly through the merchant's web server, or indirectly via the Samsung-PG Interface server for normal transaction processing.

App-to-app Push Provisioning

The push provisioning use case ― adding payment cards to Samsung Wallet from the card issuer’s app ― typically takes this form:

  • The user logs into the issuer app.
  • The issuer app checks if Samsung Wallet is activated on the device and ready to use. If it is in the ready status, the issuer app displays an Add button for cards not currently registered/enrolled with the Samsung Wallet app.
  • If the add card option is selected, the issuer app calls an API to push the proper payload data to Samsung Wallet.
  • While the card is being provisioned, Samsung Wallet stays in background.

3.1.4 Setting up SDK development environment

The importance of maintaining a good development environment cannot be overstated. For integrating the Samsung Pay SDK with your partner app, the following prerequisites and recommendations help ensure a successful SDK implementation.

System requirements

The Samsung Pay SDK is designed exclusively for Samsung mobile devices supporting Samsung Pay and running Android Lollipop 5.1 (Android API level 22) or later versions of the Android OS. The SDK’s In-App Payments functionality requires Android 6.0 (M) (Android API level 23) or later versions of the Android OS.

Use the following snippet to determine the OS version running on a device and whether or not to display the Samsung Pay button in your partner app.

import android.os.Build; 
// In-App payment supported on Android M or above 
// Check Android version of the device
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) 
{    //Hide Samsung Pay button}

Service registration

To develop a Samsung Pay SDK service, merchants and issuers need to register for an account with Samsung Pay Developers in order to create the appropriate service type for their applications. Here are some helpful links inside the portal:

  1. Become a member: https://pay.samsung.com/developers/tour/memberguide
  2. Create services: https://pay.samsung.com/developers/tour/svcguide
  3. Register apps: https://pay.samsung.com/developers/tour/appsguide
  4. Manage services and apps: https://pay.samsung.com/developers/tour/svcnappsguide

Add Samsung pay SDK to your project

Be sure to do the following before attempting to use the SDK:

  1. If not already part of your environment, download and install an IDE. Android Studio is recommended.

  2. Download the Samsung Pay SDK
    The SDK package has the following directory structure:

    Folder

    Contents

    Docs

    Javadoc – API reference documentation

    Libs

    samsungpay.jar SDK Java Archive file – contains the Samsung Pay APIs to be used by your partner app

    Samples

    Sample apps

  3. Configure your IDE to integrate the Samsung Pay SDK with your partner app.

    a) Add samsungpay.jar to the libs folder of your Android project.

    b) Go to Gradle Scripts > build.gradle and enter the following dependency:

    dependencies {
        compile files('libs/samsungpay.jar')
    }
    

    c) Import the SDK package into your code

    import com.samsung.android.sdk.samsungpay.v2;
    

    d) ProGuard rules - If your app(s) have any issue with ProGuard, the following rules are recommended to enter in debug mode:

    dontwarn com.samsung.android.sdk.samsungpay.**
    -keep class com.samsung.android.sdk.** { *; }
    -keep interface com.samsung.android.sdk.** { *; }
    -keepresourcexmlelements manifest/application/meta-data@name=spay_sdk_api_level
    -keepresourcexmlelements manifest/application/meta-data@name=debug_mode
    -keepresourcexmlelements manifest/application/meta-data@name=spay_debug_api_key
    dontwarn com.samsung.android.sdk.samsungpay.**
    -keep class com.samsung.android.sdk.** { *; }
    -keep interface com.samsung.android.sdk.** { *; }
    -keepresourcexmlelements manifest/application/meta-data@name=spay_sdk_api_level
    -keepresourcexmlelements manifest/application/meta-data@name=debug_mode
    -keepresourcexmlelements manifest/application/meta-data@name=spay_debug_api_key
    

    e) When DexGuard is employed, the following additional rules apply:

    -keepresourcexmlelements manifest/application/meta-data@name=spay_sdk_api_level
    

Android R OS (targetSdkVersion 30)

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    package="xxx.xxx.xxx.xxx">
    <queries>
        <package android:name="com.samsung.android.spay" />
        <package android:name="com.samsung.android.samsungpay.gear" />
    </queries>

3.1.5 Configuring API Level

API level attributes

As of SDK version 1.4, enhanced version control management has been introduced to improve backward compatibility and handle API dependency from country and Service Type. For example, if a partner integrates the latest SDK―for instance, API level 2.22―but continues to use APIs based in level 1.4, the partner app remains compatible with Samsung Wallet apps supporting API level 1.4 without the necessity of upgrading the Samsung Pay app.

The chief characteristics and properties of the API level include:

  • Every API starting from version 1.4 has an API level assigned based on the SDK version number in which it is introduced.
  • The SDK’s Javadoc reference can be filtered by API level so you can determine the minimum API level you need to configure in the metadata section of your app’s AndroidManifest file. The earliest possible version is 1.4.
  • This lets you use the API level defined in your AndroidManifest without having to trigger an upgrade of the Samsung Wallet app on the user’s device.

Implement the following usage in your AndroidManifest:

<application
    ......
    <meta-data
        android:name="spay_sdk_api_level"
        android:value="2.22" /> // most recent SDK version is recommended to leverage the latest APIs but it need to be set to 2.17 for Russia
    ......
</application>

Partner app verification

In partner verification process Samsung Pay SDK verify your registered app, version in Samsung Pay portal and service. It also determines device and app compatibility. Your app needs to verify the presence of the Samsung Wallet app on the device, its status, and whether or not its version is sufficient to support your implementation of the SDK.

3.1.6 Common Terminology

Terminology

Description

AAVS

Automatic Add Value Service

AIDL

Android Interface Definition Language for communication

Merchant

A business entity engaged in retail e-commerce that provides an online checkout service for purchasing its products and/or services to registered end users

Issuer

Financial institution empowered to issue credit and/or debit payment cards to qualified consumers and businesses

Payment Gateway (PG)

E-commerce app service provider equipped to authorize credit card payments for e-businesses, online retailers, bricks and clicks, or traditional brick and mortar

Payment Token

Secure method for payment ensuring that a cardholder’s information is not exploited by unauthorized parties

Samsung Pay

Samsung’s proprietary mobile wallet app and payment system

Samsung Pay Service

The server and service components of Samsung Pay

Samsung Pay Watch

Samsung Pay app on Samsung Galaxy Watches to support payment system

Eligibility Check

A query by third-party apps to check whether or not Samsung Pay is supported/activated/ready-to-use on a Samsung device

MST

Magnetic Secure Transmission

TUI

Trusted User Interface

3.1.7 API Common Flow

Once setup is complete, you’re ready to add the SDK code within your partner app for calling the SDK’s APIs and receiving callbacks.

When a partner app calls one of the SDK’s APIs, the following interaction flow is processed to check whether the caller is authenticated and authorized before responding to the request. The steps of the interaction are enumerated below.

Step 1: The Partner App Calls a Samsung Pay SDK API

  • The partner app initiates a call to the Samsung Pay SDK.
  • This could be to request:
    • In-App Payment
    • Push Provisioning
    • Status checks, etc.

Step 2: SDK Validates the Preconditions

  • The SDK performs initial checks before proceeding:
    • Is Samsung Wallet installed on the device?
    • Is the integrity of the Samsung Pay system intact (e.g., no tampering or missing files)?

Step 3: SDK Initiates Communication with Samsung Wallet

  • If preconditions are met, the SDK uses AIDL (Android Interface Definition Language) to securely open a communication channel with the Samsung Wallet app.

Step 4: Samsung Wallet App Status Validation

  • Samsung Wallet app checks:
    • Has the Samsung Pay setup been completed on the device?
    • Does the app require a mandatory update?
    • Is the Samsung Pay SDK API level used by the partner app compatible?

Step 5: Partner App Eligibility Verification

  • SDK triggers a backend verification with the Samsung Pay Server to confirm:
    • The app’s package name, service ID, and CSR match what’s registered on the Samsung Pay Developers Portal.
    • The app is authorized to use the requested Samsung Pay functionality.

Step 6: Samsung Wallet Responds to SDK

  • Based on the above validations:
    • Samsung Wallet responds via AIDL to the SDK.
    • It sends status codes, eligibility results, or additional prompts if needed (e.g., update required).

Step 7: SDK Triggers Callback in the Partner App

  • The SDK invokes a callback function in the partner app.
  • This informs the app whether:
    • It can proceed with payment/provisioning.
    • An error or restriction has occurred.

Step 8: Partner App Executes Business Logic Based on SDK Callback

  • If successful:

    • Initiate Samsung Pay payment UI or push provisioning.
    • Complete transaction or card enrollment via the Wallet app.
  • If unsuccessful:

    • Inform the user.
    • Offer fallback payment options.
    • Provide guidance on enabling/updating Samsung Wallet.

3.1.8 Checking Samsung Pay Status

The first step in integrating the Samsung Pay SDK into your partner app is to create a Samsung Pay instance and check the status of Samsung Pay on the user's device. This check determines whether the device supports Samsung Pay and if the Samsung Pay button should be shown as a payment or provisioning option.

The Samsung Pay button serves two key purposes:

  • For merchant apps, it enables users to select Samsung Pay for in-app payments.
  • For issuer apps, it allows users to add a card directly to Samsung Pay via Push Provisioning.

Setting PartnerInfo for Verification

Before checking the status, the partner app must configure and pass a valid PartnerInfo object to the SamsungPay instance. This is essential for verifying the calling app and enabling further SDK functionality.

The PartnerInfo must include:

  • serviceId (SID): A unique identifier assigned by the Samsung Pay Developer Portal.
  • ServiceType: Defines the type of service (e.g., merchant or issuer). This value is also assigned during service registration.

Samsung Pay uses PartnerInfo for:

  • Validating the app’s identity and registration
  • Performing SDK version and API compatibility checks
  • Verifying allowlist/blocklist status

Once PartnerInfo is set correctly, you can proceed to call getSamsungPayStatus() to check if Samsung Pay is available and ready for use on the device.

val serviceId = "partner_app_service_id"
val bundle = Bundle()
bundle.putString(SamsungPay.PARTNER_SERVICE_TYPE, SamsungPay.ServiceType.INAPP_PAYMENT.toString())
val partnerInfo = PartnerInfo(serviceId, bundle)

After setting PartnerInfo, your partner app can now call getSamsungPayStatus().

This method of the SamsungPay class must be called before using any other feature in the Samsung Pay SDK.

fun getSamsungPayStatus(callback : StatusListener)

Copy

The result is delivered to StatusListener and provides the following events:

  • onSuccess() ‒ Called when the requested operation is successful. It provides the status of the request, as well as extra bundle data related to the request.

  • onFail() ‒ Called when the request operation fails. It returns the error code and extra bundle data related to the request.

The Samsung Pay status code returned is one of the following:

  • SPAY_NOT_SUPPORTED - indicates Samsung Wallet is not supported on this device; typically returned if the device is incompatible with Samsung Pay or if the Samsung Wallet app is not installed.

  • SPAY_NOT_READY - indicates Samsung Wallet is not completely activated; usually returned if the user did not complete a mandatory update or if the user has not signed in with a valid Samsung Account. In which case, the partner app can activate or update the Samsung Wallet app on the device according to the 'EXTRA_ERROR_REASON' bundle keys below.

    • ERROR_SPAY_SETUP_NOT_COMPLETE - tells the partner app to display a popup message asking if the user wishes to activate Samsung Pay. If the user agrees, the partner app calls activateSamsungPay() to activate the Samsung Wallet app.
    • ERROR_SPAY_APP_NEED_TO_UPDATE - tells the partner app to display a popup message asking if the user wishes to update Samsung Pay. If user agrees, the partner app calls goToUpdatePage() to open the app update page.
  • ERROR_PARTNER_INFO_INVALID - indicates that Partner app information is invalid; typically, the partner app is using a SDK version that is not allowed, an invalid service type, or the wrong API level.

    • ERROR_PARTNER_SDK_API_LEVEL - tells the partner app it is using the wrong API level. To resolve the error condition, the partner app must set a valid API level.
    • ERROR_PARTNER_SERVICE_TYPE - tells the partner app that it did not set a Service Type, or that the Service Type it did set is invalid. Service Type is set in PartnerInfo.
  • SPAY_READY - indicates that Samsung Pay is activated and ready to use; typically returned after the user completes all mandatory updates and signs in.

Extra bundle data can have the following values:

  • EXTRA_COUNTRY_CODE - for both onSuccess() and onFail(), this is the current device’s country code (ISO 3166-1 alpha-2) set by Samsung Pay. If the partner app is not supported in this particular country, the partner app can decide not to display Samsung Pay button.

  • EXTRA_ERROR_REASON - for onFailure(), this is the reason for failure set by Samsung Pay.

When the returned status code is SPAY_READY, the partner app can safely display the Samsung Pay button for user selection as a payment option, push provisioning, and so on.

The following sample code shows how to use the getSamsungPayStatus() API method:

val serviceId = "partner_app_service_id"
val bundle = Bundle()
bundle.putString(SamsungPay.PARTNER_SERVICE_TYPE, SamsungPay.ServiceType.INAPP_PAYMENT.toString())
val partnerInfo = PartnerInfo(serviceId, bundle)
val samsungPay = SamsungPay(context, partnerInfo)
/*
 * Method to get the Samsung Pay status on the device.
 * Partner (Issuers, Merchants) applications must call this Method to
 * check the current status of Samsung Pay before doing any operation.
 */
samsungPay.getSamsungPayStatus(object : StatusListener {
    override fun onSuccess(status: Int, bundle: Bundle) {
        when (status) {
            SamsungPay.SPAY_NOT_SUPPORTED ->
                // Samsung Pay is not supported
                samsungPayButton.setVisibility(View.INVISIBLE)

            SamsungPay.SPAY_NOT_READY -> {
                // Activate Samsung Pay or update Samsung Pay, if needed
                samsungPayButton.setVisibility(View.INVISIBLE)
                val errorReason = bundle.getInt(SamsungPay.EXTRA_ERROR_REASON)
                if (errorReason == SamsungPay.ERROR_SETUP_NOT_COMPLETED) {
                    // Display an appropriate popup message to the user
                    samsungPay.activateSamsungPay()
                } else if (errorReason == SamsungPay.ERROR_SPAY_APP_NEED_TO_UPDATE) {
                    // Display an appropriate popup message to the user
                    samsungPay.goToUpdatePage()
                } else {
                    Toast.makeText(context, "error reason: $errorReason", Toast.LENGTH_LONG).show()
                }
            }

            SamsungPay.SPAY_READY ->
                // Samsung Pay is ready
                samsungPayButton.setVisibility(View.VISIBLE)

            else ->
                // Not expected result
                samsungPayButton.setVisibility(View.INVISIBLE)
        }
    }

    override fun onFail(errorCode: Int, bundle: Bundle) {
        samsungPayButton.setVisibility(View.INVISIBLE)
        Log.d(TAG, "checkSamsungPayStatus onFail() : $errorCode")
    }
})

3.1.9 Activating the Samsung Wallet App

The SamsungPay class provides the following API method to activate the Samsung Wallet app on a device:

fun activateSamsungPay()

activateSamsungPay() is called to activate the Samsung Wallet app on the same device on which the partner app is running. First, however, the partner app must check the Samsung Pay status with a getSamsungPayStatus() call (see section 4.2 above). If the status is SPAY_NOT_READY and EXTRA_ERROR_REASON is ERROR_SPAY_SETUP_NOT_COMPLETE, the partner app needs to display an appropriate message to user, then call activateSamsungPay() to launch the Samsung Wallet app so the user can sign in.

Here’s an example of how to code this:

val serviceId = "partner_app_service_id"
val bundle = Bundle()
bundle.putString(SamsungPay.PARTNER_SERVICE_TYPE, SpaySdk.ServiceType.INAPP_PAYMENT.toString())
val partnerInfo = PartnerInfo(serviceId, bundle)
val samsungPay = SamsungPay(context, partnerInfo)
samsungPay.activateSamsungPay()

3.1.10 Updating the Samsung Wallet App

The SamsungPay class provides the following API method to update the Samsung Wallet app on the device:

fun goToUpdatePage()

goToUpdatePage() is called to update Samsung Wallet app on the same device on which the partner app is running.

As with all API calls, the partner app must first check the Samsung Pay status with getSamsungPayStatus(). If this returns SPAY_NOT_READY and EXTRA_ERROR_REASON is ERROR_SPAY_APP_NEED_TO_UPDATE, then the partner app needs to display an appropriate message to the user and call goToUpdatePage(), which launches the Samsung Pay update page.

The following code sample reflects how to update Samsung Pay:

val serviceId = "partner_app_service_id"
val bundle = Bundle()
bundle.putString(SamsungPay.PARTNER_SERVICE_TYPE, SpaySdk.ServiceType.INAPP_PAYMENT.toString())
val partnerInfo = PartnerInfo(serviceId, bundle)
val samsungPay = SamsungPay(context, partnerInfo)
samsungPay.goToUpdatePage()

3.1.11 In-App (Online)

The main classes and interfaces involved here are:

  • SamsungPay– Class for a merchant app to get Samsung Pay SDK information and the status of Samsung Pay on the device.

  • PaymentManager – Class to provide payment/transaction functionality.

  • CardInfoListener – Interface for requestCardInfo result callbacks from Samsung Wallet.

  • CustomSheetTransactionInfoListener – Interface for transaction success/failure callbacks from Samsung Wallet; payment information is provided with a success callback and must be used by the merchant app for processing the payment.

The flow pictured next captures the essential online payment API process between merchant apps integrated with the Samsung Pay SDK and Samsung Wallet and the merchant’s payment gateway (PG).

3.1.12 API flow for In-App payments

The API flow for Samsung Pay In-App Payments involves a series of operations that ensure secure, seamless, and verified payment processing between the merchant app, Samsung Wallet, and the Payment Gateway (PG). These steps are illustrated in the flow diagram above and described below:

  1. Check the ready status of Samsung Pay.
    Use SamsungPay.getSamsungPayStatus() to verify whether:
    • Samsung Pay is installed and supported on the device
    • Samsung Pay is activated and ready for transactions
    • The user has completed mandatory updates and account setup
  2. Start the Payment Manager to establish the service binding and verify the merchant app.
    • Establish a binding between your app and the Samsung Wallet app
    • Validate that the merchant app is authorized and registered
    • Prepare the SDK to handle payment operations
  3. Get payment card information and the payment amount, including updates. This step also includes:
    • Displaying eligible cards to the user
    • Allowing the user to select a card for the transaction
    • Collecting or updating the payment amount
  4. Get/update the user’s billing and shipping addresses, including an updated payment amount if shipping charges will be incurred. If required by the transaction:
    • Retrieve or update the user’s billing and shipping address
    • Recalculate and update the total payment amount (e.g., shipping cost impact)
  5. Authenticate the user. Trigger authentication via Trusted User Interface (TUI), using biometrics or other secure methods supported by Samsung Wallet.
  6. Submit payment information to PG. This ensures the user has authorized the transaction. Send the tokenized payment data received from Samsung Pay to your designated Payment Gateway (PG) for transaction processing. This can be done via:
    • A direct merchant server-to-PG connection, or
    • Samsung PG Interface server (if applicable)
  7. Verify transaction success or failure. Receive confirmation of transaction success or failure from the Payment Gateway. Communicate the result back to the user and update your app's UI and backend accordingly.

3.1.13 Token Modes (Network vs. Gateway)

To complete the payment, the merchant’s designated payment gateway (PG) handles one of two types of tokens: gateway tokens (indirect) or network tokens (direct). The Samsung Pay SDK supports both types.

The essential difference between the two types is who decrypts the token information. Network tokens require that the merchant app handles decryption of the token bundle or work with the PG to handle decryption, whereas Gateway token decryption is handled by the PG via the Samsung-PG Interface Server.

Network Token Mode (Direct)

  1. User selects Samsung Pay as the payment method at checkout in the merchant app and the Samsung Pay app requests partner verification from the Samsung Pay Online Payment Server.
  2. Encrypted payment information is passed from the Samsung Pay app to the PG through the merchant app via the PG SDK.
  3. Applying the merchant's private key, PG decrypts the payment information structure and processes the payment through the acquirer and payment network.
  4. Upon receiving authorization or rejection, PG notifies the merchant app through its PG SDK.

Gateway Token Mode (Indirect)

  1. User selects Samsung Pay as the payment method at checkout in the merchant app and the Samsung Pay app requests partner verification from the Samsung Pay Online Payment Server.
  2. Encrypted payment information and the Partner ID are passed to the Samsung-PG Interface Server.
  3. Samsung-PG Interface Server sends a transaction authorization request to the PG on behalf of the merchant; PG authenticates the Partner ID before generating a transaction Reference ID.
  4. Reference ID is passed to merchant app via SDK callback. Merchant app then passes the Reference ID to the PG for payment process execution.
  5. Samsung-PG Interface Server returns the payment token to the PG (i.e., gateway token it received from Samsung Pay app in Step 2).
  6. PG continues payment processing with the acquirer and payment network.
  7. The result (approved/declined) is returned to the merchant app on the device for display to the user.

Check with your PG to determine its specific requirements for payment processing.

Regardless of the PG model employed, direct or indirect, the goal is to offer Samsung Pay as a secure payment method within your merchant app. The most common use case involves the following general steps:

  1. To make a purchase, the user selects to “Buy” or got to checkout after adding items to a shopping cart.
  2. Now in checkout, the user selects a payment option; for example, either the merchant’s “standard” method or Samsung Pay.
  3. Upon selecting Samsung Pay, the user is presented with a payment sheet that allows for card selection and shipping address confirmation with the option to add/modify information for this order, whereupon the user:
    • Makes payment card selection from the list of enrolled cards.
    • Chooses to change or add the delivery address.
    • Enters required address information in the form presented and saves it.
    • Authenticates the payment method, amount, and delivery with a biometric verification (fingerprint, iris…) or PIN.

3.1.14 Checking registered/enrolled card information

Before displaying the Samsung Pay button, a partner app can query card brand information for the user’s currently enrolled payment cards in Samsung Wallet to determine if payment is supported with the enrolled card. For example, if a merchant app accepts one card brand exclusively but the user has not registered any cards matching this brand in Samsung Wallet, the merchant app needs to determine whether or not to display the Samsung Pay button for this purchase checkout.

To query the card brand, use the requestCardInfo() API method of the PaymentManager class. The requestFilter is optional bundle data reserved for future use. The merchant app does not need to set a value for it now.

However, before calling this method, CardInfoListener must be registered so its listener can provide the following events:

  • onResult - called when the Samsung Pay SDK returns card information from Samsung Wallet; returns information about enrolled cards or is empty if no card is registered.
  • onFailure - called when the query fails; for example, if SDK service in the Samsung Wallet app ends abnormally.

The following snippet shows how to retrieve the list of supported card brands from Samsung Pay:

val serviceId = "partner_app_service_id"
val bundle = Bundle()
bundle.putString(SamsungPay.PARTNER_SERVICE_TYPE, SpaySdk.ServiceType.INAPP_PAYMENT.toString())
val partnerInfo = PartnerInfo(serviceId, bundle)
val paymentManager = PaymentManager(context, partnerInfo)
paymentManager.requestCardInfo(Bundle(), cardInfoListener) // get Card Brand List
//CardInfoListener is for listening requestCardInfo() callback events

val cardInfoListener: CardInfoListener = object : CardInfoListener {
    // This callback is received when the card information is received successfully
    override fun onResult(cardResponse: List<CardInfo>) {
        var visaCount = 0
        var mcCount = 0
        var amexCount = 0
        var dsCount = 0
        var brandStrings = "Card Info : "
        var brand: SpaySdk.Brand?
        for (i in cardResponse.indices) {
            brand = cardResponse[i].brand
            when (brand) {
                SpaySdk.Brand.AMERICANEXPRESS -> amexCount++
                SpaySdk.Brand.MASTERCARD -> mcCount++
                SpaySdk.Brand.VISA -> visaCount++
                SpaySdk.Brand.DISCOVER -> dsCount++
                else -> { /* Other card brands */
                }
            }
        }
        brandStrings += "  VI = $visaCount,  MC = $mcCount,  AX = $amexCount,  DS = $dsCount"
        Log.d(TAG, "cardInfoListener onResult  : $brandStrings")
        Toast.makeText(context, "cardInfoListener onResult" + brandStrings, Toast.LENGTH_LONG).show()
    }
    /*
     * This callback is received when the card information cannot be retrieved.
     * For example, when SDK service in the Samsung Wallet app dies abnormally.
     */
    override fun onFailure(errorCode: Int, errorData: Bundle) {
        //Called when an error occurs during In-App cryptogram generation
        Toast.makeText(context, "cardInfoListener onFailure : " + errorCode,
            Toast.LENGTH_LONG).show()
    }
}

3.1.15 Creating a transaction request

Upon successful initialization of the SamsungPay class, the merchant app needs to create a transaction request with payment information.

Merchant app developers choosing to temporarily continue offering the normal sheet will need to configure their Android manifest to reflect the pre-2.0.00 version of the SDK used to implement their app’s existing normal sheet, although this is not recommended. In all cases, merchant app developers should update their apps with the latest version of the SDK as soon as possible to avoid timing out using an earlier version of the SDK when responding to Samsung Pay callbacks.

Using the custom payment sheet

To initiate a payment transaction with Samsung Pay’s custom payment sheet, your merchant app must populate the following mandatory fields in CustomSheetPaymentInfo:

  • Merchant Name - as it will appear in Samsung Pay’s payment sheet, as well as the user's card account statement.
  • Amount - the constituent transaction properties (currency, item price, shipping price, tax, total price) which together determine the total amount the user is agreeing to pay the merchant.

Optionally, the following fields can be added to the payment information:

  • Merchant ID- can be used for the merchant’s own designated purpose at its discretion unless the merchant uses an indirect PG like Stripe or Braintree. If an indirect PG is used, this field must be set to the merchant’s Payment Gateway ID fetched from the Samsung Pay Developers portal. Merchant ID is mandatory if a merchant requests with a MADA Token, this field should be included in the payload.

  • Order Number - usually created by the merchant app via interaction with a PG. This number is required for refunds and chargebacks. In the case of Visa cards, the value is mandatory. The allowed characters are [A-Z][a-z][0-9,-] and the length of the value can be up to 36 characters.

  • Address - the user’s billing and/or shipping address (see Applying an AddressControl for details).

  • Allowed Card Brands - specifies card brands accepted by the merchant. If no brand is specified, all brands are accepted by default. If at least one brand is specified, all other card brands not specified are set to "card not supported’ on the payment sheet.

Here’s the 'CustomSheetPaymentInfo' structure:

class CustomSheetPaymentInfo : Parcelable {
    private val version: String? = null
    private val merchantId: String? = null
    private val merchantName: String? = null
    private val orderNumber: String? = null
    private val addressInPaymentSheet: AddressInPaymentSheet = AddressInPaymentSheet.DO_NOT_SHOW
    private val allowedCardBrand: List<SpaySdk.Brand>? = null
    private val cardInfo: CardInfo? = null
    private val isCardHolderNameRequired = false
    private val isRecurring = false
    private val merchantCountryCode: String? = null
    private val customSheet: CustomSheet? = null
    private val extraPaymentInfo: Bundle? = null
}

Your merchant app sends this CustomSheetPaymentInfo to Samsung Wallet via the applicable Samsung Pay SDK API methods. Upon successful user authentication in direct mode, Samsung Wallet returns the above "Payment Info" structure and a result string. The result string is forwarded to the PG by your merchant app to complete the transaction. It will vary based on the PG you’re using.

The following example demonstrates how to populate customSheet in the CustomSheetPaymentInfo class.

See Sample merchant app using custom payment sheet below for example usage of each CustomSheet control.

/*
 * Make user's transaction details.
 * The merchant app should send CustomSheetPaymentInfo to Samsung Wallet via 
 * the applicable Samsung Pay SDK API method for the operation being invoked.
 */
private fun makeCustomSheetPaymentInfo(): CustomSheetPaymentInfo {
    val brandList = ArrayList<SpaySdk.Brand>()
    // If the supported brand is not specified, all card brands in Samsung Wallet are 
    // listed in the Payment Sheet.
    brandList.add(PaymentManager.Brand.VISA)
    brandList.add(PaymentManager.Brand.MASTERCARD)
    brandList.add(PaymentManager.Brand.AMERICANEXPRESS)
    /*
     * Make the SheetControls you want and add them to custom sheet.
     * Place each control in sequence with AmountBoxControl listed last.
     */
    val customSheet = CustomSheet()
    customSheet.addControl(makeBillingAddressControl())
    customSheet.addControl(makeShippingAddressControl())
    customSheet.addControl(makePlainTextControl())
    customSheet.addControl(makeShippingMethodSpinnerControl())
    customSheet.addControl(makeAmountControl())
    val extraPaymentInfo = Bundle()
    /*
     * You can add Transaction Type for MADA card brand
     * The supported values are: PURCHASE and PREAUTHORIZATION
     * If you don't set any value, the  default value is PURCHASE.
     */
    extraPaymentInfo.putString(SpaySdk.EXTRA_ONLINE_TRANSACTION_TYPE,
        SpaySdk.TransactionType.PREAUTHORIZATION.toString())
    
    val customSheetPaymentInfo = CustomSheetPaymentInfo.Builder()
        .setMerchantId("123456")
        .setMerchantName("Sample Merchant") 
        // Merchant requires billing address from Samsung Wallet and 
        // sends the shipping address to Samsung Wallet.
        // Show both billing and shipping address on the payment sheet.
        .setAddressInPaymentSheet(CustomSheetPaymentInfo.AddressInPaymentSheet.NEED_BILLING_SEND_SHIPPING)
        .setAllowedCardBrands(brandList)
        .setCardHolderNameEnabled(true)
        .setRecurringEnabled(false)
        .setCustomSheet(customSheet)
        .setExtraPaymentInfo(extraPaymentInfo)
        .build()
    return customSheetPaymentInfo
}

3.1.16 Requesting payment with a custom payment sheet

The startInAppPayWithCustomSheet() method of the PaymentManager class is applied to request payment using a custom payment sheet in Samsung Wallet. The two methods are defined as follows:

  • startInAppPayWithCustomSheet() - initiates the payment request with a custom payment sheet. The payment sheet persists for 5 minutes after the API is called. If the time limit expires, the transaction fails.

  • updateSheet() - must be called to update current payment sheet. As of API level 1.5, a merchant app can update the custom sheet with a custom error message. Refer to Updating sheet with custom error message.

When you call the startInAppPayWithCustomSheet() method, a custom payment sheet is displayed on the merchant app screen. From it, the user can select a registered card for payment and change the billing and shipping addresses, as necessary.

The result is delivered to CustomSheetTransactionInfoListener, which provides the following events:

  • onSuccess() - called when Samsung Pay confirms payment. It provides the CustomSheetPaymentInfo object and the paymentCredential JSON string.

  • CustomSheetPaymentInfo is used for the current transaction. It contains amount, shippingAddress, merchantId, merchantName, orderNumber. API methods exclusively available in the onSuccess() callback comprise:

    • getPaymentCardLast4DPAN() – returns the last 4 digits of the user's digitized personal/primary identification number (DPAN)
    • getPaymentCardLast4FPAN() – returns the last 4 digits of the user's funding personal/primary identification number (FPAN)
    • getPaymentCardBrand() – returns the brand of the card used for the transaction
    • getPaymentCurrencyCode() – returns the ISO currency code in which the transaction is valued
    • getPaymentShippingAddress() – returns the shipping/delivery address for the transaction
    • getPaymentShippingMethod() – returns the shipping method for the transaction.
      For PGs using the direct model (network tokens), the paymentCredential is a JSON object containing encrypted cryptogram which can be passed to the PG. PGs using the indirect model (gateway tokens) like Stripe, it is a JSON object containing reference (card reference – a token ID generated by the PG) and status (i.e., AUTHORIZED, PENDING, CHARGED, or REFUNDED). Refer to Payment credential sample for details.
  • onCardInfoUpdated() - called when the user changes the payment card. In this callback, updateSheet() method must be called to update current payment sheet.

  • onFailure() - called when the transaction fails; returns the error code and errorData bundle for the failure.

Here’s how to call the startInAppPayWithCustomSheet() method of the PaymentManager class:

/*
 * CustomSheetTransactionInfoListener is for listening callback events of In-App custom sheet payment.
 * This is invoked when card is changed by the user on the custom payment sheet,
 * and also with the success or failure of online (In-App) payment.
 */
private val transactionListener  = object :  CustomSheetTransactionInfoListener {
    // This callback is received when the user changes card on the custom payment sheet in Samsung Pay
    override fun onCardInfoUpdated(selectedCardInfo: CardInfo, customSheet: CustomSheet) {
       /*
        * Called when the user changes card in Samsung Wallet.
        * Newly selected cardInfo is passed so merchant app can update transaction amount
        * based on different card (if needed),
        */
        val amountBoxControl =
            customSheet.getSheetControl(AMOUNT_CONTROL_ID) as AmountBoxControl
        amountBoxControl.updateValue(PRODUCT_ITEM_ID, 1000.0) //item price
        amountBoxControl.updateValue(PRODUCT_TAX_ID, 50.0) // sales tax
        amountBoxControl.updateValue(PRODUCT_SHIPPING_ID, 10.0) // Shipping fee
        amountBoxControl.updateValue(PRODUCT_FUEL_ID, 0.0, "Pending") // additional item status
        amountBoxControl.setAmountTotal(
            1060.0,
            AmountConstants.FORMAT_TOTAL_PRICE_ONLY
        ) // grand total
        customSheet.updateControl(amountBoxControl)

        // Call updateSheet() with AmountBoxControl; mandatory.
        try {
            paymentManager.updateSheet(customSheet)
        } catch (e: java.lang.IllegalStateException) {
            e.printStackTrace()
        } catch (e: java.lang.NullPointerException) {
            e.printStackTrace()
        }
    }
   /*
    * This callback is received when the payment is approved by the user and the transaction payload
    * is generated. Payload can be an encrypted cryptogram (network token mode) or the PG's token
    * reference ID (gateway token mode).
    */
    override fun onSuccess(response: CustomSheetPaymentInfo, paymentCredential: String, extraPaymentData: Bundle) {
       /*
        * Called when Samsung Pay creates the transaction cryptogram, which merchant app then sends
        * to merchant server or PG to complete in-app payment.
        */
        try {
            val DPAN = response.cardInfo.cardMetaData.getString(SpaySdk.EXTRA_LAST4_DPAN, "")
            val FPAN = response.cardInfo.cardMetaData.getString(SpaySdk.EXTRA_LAST4_FPAN, "")
            Toast.makeText(context, "DPAN: " + DPAN + "FPAN: " + FPAN, Toast.LENGTH_LONG).show()
        } catch (e: java.lang.NullPointerException) {
            e.printStackTrace()
        }
        Toast.makeText(context, "Transaction : onSuccess", Toast.LENGTH_LONG).show()
    }

    override fun onFailure(errorCode: Int, errorData: Bundle) {
        // Called when an error occurs during cryptogram generation
        Toast.makeText(context, "Transaction : onFailure : $errorCode", Toast.LENGTH_LONG)
            .show()
    }
}
private fun startInAppPayWithCustomSheet() {
    // Show custom payment sheet
    try {
        val bundle = Bundle()
        bundle.putString(
            SamsungPay.PARTNER_SERVICE_TYPE,
            SpaySdk.ServiceType.INAPP_PAYMENT.toString()
        )
        val partnerInfo = PartnerInfo(serviceId, bundle)
        paymentManager = PaymentManager(context, partnerInfo)
        // Request payment using Samsung Wallet
        paymentManager.startInAppPayWithCustomSheet(
            makeCustomSheetPaymentInfo(),
            transactionListener
        )
    } catch (e: IllegalStateException) {
        e.printStackTrace()
    } catch (e: NumberFormatException) {
        e.printStackTrace()
    } catch (e: NullPointerException) {
        e.printStackTrace()
    } catch (e: IllegalArgumentException) {
        e.printStackTrace()
    }
}

When an address is provided by Samsung Wallet, onAddressUpdated()is called whenever address information is updated in the custom payment sheet. You can use the updateSheet() method to update the shipping fee or any other relevant information in the payment sheet. Set the errorCode to determine if the address provided by Samsung Wallet app is invalid, out of delivery, or does not exist.

For example, when the:

  • merchant does not support the product delivery to the designated location
  • billing address from Samsung Wallet is not valid for tax recalculation

For all such cases, the merchant app should call updateSheet() with one of the following error codes:

  • ERROR_SHIPPING_ADDRESS_INVALID
  • ERROR_SHIPPING_ADDRESS_UNABLE_TO_SHIP
  • ERROR_SHIPPING_ADDRESS_NOT_EXIST
  • ERROR_BILLING_ADDRESS_INVALID
  • ERROR_BILLING_ADDRESS_NOT_EXIST

The sample code included below under Applying the Address Control demonstrates how to use the updateSheet() method for 'addressControl' in the payment sheet.

3.1.17 Payment credential sample

The paymentCredential is the resulting output of the startInAppPayWithCustomSheet() method. The structure varies depending on the PG you’re using and the integration model—direct or indirect). The following paymentCredential is for a Visa card.

For PG using direct (network token) mode – e.g. First Data, Adyen, Visa (Cybersourse)

Sample paymentCredential JSON output (using JWE-only)

{
"billing_address":{"city":"BillingCity","country":"USA","state_province":"CA","street":"BillingAddr1","zip_postal_code":"123456"},
"card_last4digits":"1122",
"3DS":{"data":"eyJhbGciOiJSU0ExXzUiLCJraWQiOiJCak91a1h2aFV4WU5wOFIwVGs2Y25OaCtZWWFqZXhIeHRVZ0VFdHlhYy9NPSIsInR5cCI6IkpPU0UiLCJjaGFubmVsU2VjdXJpdHlDb250ZXh0IjoiUlNBX1BLSSIsImVuYyI6IkExMjhHQ00ifQ.Fg2OOUvHdGKkIVyBa2S5KtUrPWUeujKZEyxz7n6kALhQahszv3P5JaBaOJ-RoKcznFjDg3qierzjktU7zXST9gwv4Oclahpfdw64w0X6TtAxeYJiIVkJUG-edXXTWaJeyeIkgC68wEhF1CltSqG4zLWi6upVCAywdPpBN0Hl0C5WcF5Az4WABYtV_Fda5aHGuyPnE70kEQRTWdlacW9MzEJx2Xth7Msd9OHoulR8LUQ-7gha17jHoOBwgMoQ9q0hAoCNm0LjWiuhKoRyyu-Njulnbkk8FZus_AIuMgdv2YN9ygFqIlMculb0VWuF0YeKX6IsgAxi0ZQhLiUsJkCZ_w.AuZZxoG46lnrtk3Q.QE2llwS30VzH-ZduuE8b045CnfRm2p-RjZGBnZcHELS3v26N64cFg1AV5mtP5f-fSwbJ3ntP5x4V1NK8FmdY0uSPxzeMfvl5badGAC7w9FrXt6X5xV1Fqu6-q-ZkbxcB9bYgownt983BcKOE1bd5djxFBOdLrc4j68ikDjc5M3LEBDx6hV0aQzKmilCH-JeVL3AwQyKBny4Vj7m3Fizw7u1PRLI2ZfWUkXDfS4Vwv3bPm4QUDEMVnHXJ.qTYmdmn4ne93juljNmWkJg","type":"S","version":"100"},
"merchant_ref":"MerchantId",
"method":"3DS",
"recurring_payment":false
}

Decrypt using the merchant’s private key. Below is sample private key.

 -----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEA4LZYjQR+dqd/XLEOXct9jwTJXHD2PTJke9djtMIjKi0h2Oc2GHoW4uJHHY/1jvFt2+zCnjTOXuVLp+76/DWA3bCwFRj+fPP6x5KKYlPb+dJDYo1TTumltNqCWymJB3u7jBC+xR4vKfRzqjxkE7xhN/SBb82uE8c3sMzVKYnUJi<…>
-----END RSA PRIVATE KEY-----

The decrypted output will be similar to this:

{
   "amount":"1000",
   "currency_code":"USD",
   "utc":"1490266732173",
   "eci_indicator":"5",
   "tokenPAN":"1234567890123456",
   "tokenPanExpiration":"0420", //The format is **MMYY**
   "cryptogram":"AK+zkbPMCORcABCD3AGRAoACFA=="
}

Processing the payload

Depending on the structure of the payment processing API provided by your PG, your merchant app can send either of these directly to the PG:

  • Entire paymentCredential output
  • Extracted “3DS” part only

Consult your PG documentation for specific guidance.

When using indirect model (e.g. Stripe)

In indirect (gateway token) mode, paymentCredential is the PG’s token reference ID and its status.

Here’s a sample of the JSON output.

{
    "reference":"tok_18rje5E6SzUi23f2mEFAkeP7",
   "status":"AUTHORIZED"
}

For Stripe, your merchant app should be able to pass this token object directly to Charge or another appropriate payment processing API provided by the PG.

3.1.18 Push Provisioning

The following diagram illustrates the flows of the App-to-App APIs for payment card push provisioning.

The main classes involved are:

  • Samsung Pay – for fetching Samsung Wallet app status and wallet information on the device.
  • PaymentManager – for Push provisioning and invoking Favorite Cards payment functionalities.
  • CardManager – for payment card management.
  • WatchManager – for all functions related to Samsung Pay Watch.

3.1.19 Requesting registered card list in the Samsung Pay

The getAllCards() method of the CardManager class is used to request a list of all cards currently registered/enrolled in Samsung Wallet on the same device running the issuer’s app. To succeed, the issuer app must pass valid PartnerInfo to 'CardManager' for caller verification. 'cardFilter' narrows the card list returned by Samsung Wallet to the issuerName specified. Please be noted that getSamsungPayStatus() must be called before getAllCards(). getAllCards() could not return a cards list when getSamsungPayStatus() responds with a code other than SPAY_READY.

As of API level (SDK version) 1.4, cardFilter retrieves this information from the Samsung Pay Developers portal. Certain issuers may need to register multiple ISSUER NAME(s) with the portal, depending on their app and/or the requirements of their token service provider (TSP). The getAllCards() parameter cardFilter matches the ISSUER NAME(s) specified with those registered in the portal. Only complete matches are returned.

This method is typically called when your partner app wants to check the card status. It does not need to be called every time the partner app resumes. Therefore, you should create the card list with the 'onCreate()' method, rather than the 'onResume()' method.

The result of a getAllCards() call is delivered to GetCardListener, which provides the following events:

  • onSuccess() - called when the operation succeeds; provides the list of all filtered cards and their status. Card information includes cardId, cardStatus, and extra cardInfo data.
  • onFail() - called when the operation fails.

Here’s an example of how to use the 'getAllCards()' API method in your issuer app.

val cardFilter = Bundle()
// Since API level 1.4, cardFilter param is ignored. Partner does not need to use it here.
// It is retrieved from the Samsung Pay Developers portal
cardFilter.putString(CardManager.EXTRA_ISSUER_NAME, issuerName)
cardManager.getAllCards(null, object : GetCardListener{
    override fun onSuccess(cards: MutableList<Card>?) {
        // Getting card status is success
        if (cards == null || cards.isEmpty()) {
            Log.e(TAG, "No card is found")
            return
        } else {
            // Perform operation with card data
            for (s in cards) {
                Log.d(TAG, "CardId: " + s.cardId + "CardStatus " + s.cardStatus)
                // Get Extra card data
                if (s.cardInfo != null) {
                    val cardId = s.cardId // Since API level 2.13, ID from card network.
                    val last4FPan = s.cardInfo.getString(CardManager.EXTRA_LAST4_FPAN)
                    val last4DPan = s.cardInfo.getString(CardManager.EXTRA_LAST4_DPAN)
                    val cardType = s.cardInfo.getString(CardManager.EXTRA_CARD_TYPE)
                    val cardIssuerName =
                        s.cardInfo.getString(CardManager.EXTRA_ISSUER_NAME)
                    Log.d(
                        TAG,
                        "last4FPan:$last4FPan last4DPan:$last4DPan CardId: $cardId"
                    )
                }
            }
        }
    }

    override fun onFail(errorCode: Int, errorData: Bundle?) {
        // Getting card status is failed
    }
})

3.1.20 Getting wallet information

The SamsungPay class provides the getWalletInfo() API method, which is called to request wallet information from the Samsung Wallet app prior to calling the addCard() API, when you want to avoid duplicate provisioning. Your issuer app uses this information to uniquely identify the user and the Samsung Wallet app on a particular device (wallet device management ID, device ID, and wallet user ID).

fun getWalletInfo(List<String> keys, StatusListener callback)

The following example demonstrates how to use it.

// Set the serviceId assigned by the Samsung Pay Developers portal during service creation
val serviceId = "sampleServiceId"
val bundle = Bundle()
bundle.putString(SamsungPay.EXTRA_ISSUER_NAME, "issuer name")
bundle.putString(SamsungPay.PARTNER_SERVICE_TYPE, ServiceType.APP2APP.toString())

val pInfo = PartnerInfo(serviceId, bundle)
val samsungPay = SamsungPay(context, pInfo)

// Add bundle keys to get wallet information from Samsung Pay.
// This information can be delivered to the Partner Server for an eligibility check.
val keys = ArrayList<String>()
keys.add(SamsungPay.WALLET_USER_ID)
keys.add(SamsungPay.DEVICE_ID)

samsungPay.getWalletInfo(keys, object : StatusListener{
    override fun onSuccess(status: Int, walletData: Bundle) {
        // Log.d(TAG, "doWalletInfo onSuccess callback is called");

        // For VISA, deviceID can be set to "clientDeviceID" as defined by VISA
        val deviceId = walletData.getString(SamsungPay.DEVICE_ID)


        // For VISA, walletUserId can be set to "clientWalletAccountID" as defined by VISA
        val walletUserId = walletData.getString(SamsungPay.WALLET_USER_ID)
    }

    override fun onFail(errorCode: Int, errorData: Bundle?) {
        Log.e(TAG, "onFail callback is called, errorCode: " + errorCode);
        // Check the extra error codes in the errorData bundle for all the reasons in
        // SamsungPay.EXTRA_ERROR_REASON, when provided
    }
})

3.1.21 Adding a card to Samsung Pay

Your issuer app calls the 'addCard()' API method of CardManager to add a card to Samsung Wallet. By providing the required card details, your app can make it convenient and easy for users to add their bank-issued debit/credit cards to Samsung Wallet directly from your app without additional steps, like switching between apps.

For most issuers, getWalletInfo() suffices for requesting current wallet information. The response from Samsung Wallet tells the issuer app whether or not the user’s card has already been added to Samsung Wallet or is ineligible for provisioning. It is therefore recommended that you call getWalletInfo() before displaying the Add to Samsung Pay button. If the card is eligible, display the “add” button and, if the user taps it, call addCard().

The 'addCard()' result is delivered to AddCardListener, which provides the following events:

  • onSuccess() - called when the operation succeeds; provides information and status regarding the added card.
  • onFail() - called when the operation fails; returns the error code and extra bundle data such as EXTRA_ERROR_REASON or EXTRA_REQUEST_ID (if provided).
  • onProgress() - called to indicate the current progress of the 'addCard()' operation; can be used to show a progress bar to the user in the issuer app. This callback is supported for TSM solution issuers in China and Spain.

Here’s an example of how to use the addCard() API method in your issuer app.

val cardType = Card.CARD_TYPE_CREDIT
val tokenizationProvider: String = AddCardInfo.PROVIDER_ABCD
// Samsung Pay does not provide detailed payload information; generate the provisioning payload in
// accordance with your card network specifications
val testPayload = "ThisIsTestPayloadCardInfo1234567890"
//BIN (Bank Identification Number) can be set to issuerId. It is mandatory for some card network.
val issuerId = "123456"

val cardDetail = Bundle()
cardDetail.putString(EXTRA_PROVISION_PAYLOAD, testPayload)

cardDetail.putString(EXTRA_ISSUER_ID, issuerId)

val addCardInfo = AddCardInfo(cardType, tokenizationProvider, cardDetail)

cardManager.addCard(addCardInfo, object : AddCardListener {
    override fun onSuccess(status: Int, card: Card?) {
        Log.d(TAG, "onSuccess callback is called");
    }

    override fun onFail(errorCode: Int, errorData: Bundle?) {
        Log.d(TAG, "onFail callback is called");
//                   check some extra error codes in the errorData bundle
//                   such as SamsungPay.EXTRA_ERROR_REASON or SamsungPay.EXTRA_REQUEST_ID (if provided).
    }

    override fun onProgress(currentCount: Int, totalCount: Int, bundleData: Bundle?) {
        Log.d(TAG,"onProgress callback is called : " + currentCount + " / " +  totalCount);
    }

})

3.1.22 Adding a Co-badge card to Samsung Pay

Co-badge payment cards combine two payment brands/networks.
To add a Co-badge card through push provisioning, you must provide two different card network details: one for the primary card network and another for the secondary card network.

Issuer app calls the addCobadgeCard() API method of CardManager to add a Co-badge card to Samsung Pay. In most cases, calling getWalletInfo() will suffice to request current wallet information. The response from Samsung Pay indicates whether the user's Co-badge card has already been added to Samsung Pay or is ineligible for provisioning. Therefore, it is advisable to call getWalletInfo() before displaying the Add to Samsung Pay button. If the Co-badge card is eligible, display the "add" button and, upon user tapping, call addCobadgeCard().

Important
Please remember to refer to the relevant issuer implementation guide(s) and specifications provided by each card network and ensure that your partner app and server adhere to their specific requirements.

The addCobadgeCard() result is delivered to AddCardListener, which provides the following events:

  • onSuccess() - called when the operation succeeds; provides information and status regarding the added card.
  • onFail() - called when the operation fails; returns the error code and extra bundle data such as EXTRA_ERROR_REASON or EXTRA_REQUEST_ID (if provided).
  • onProgress() - called to indicate the current progress of the 'addCard()' operation; can be used to show a progress bar to the user in the issuer app. This callback is supported for TSM solution issuers in China and Spain.

Here’s an example of how to use the addCobadgeCard() API method in your issuer app.

String cardType = Card.CARD_TYPE_CREDIT;
String primaryTokenizationProvider = AddCardInfo.PROVIDER_ABCD;

//Provide your primary card network payload
String testPrimaryPayload = "ThisIsTestPrimaryPayloadCardInfo1234567890"; 

String secondaryTokenizationProvider = AddCardInfo.PROVIDER_EFGH;
//Provide your secondary card network payload
String testSecondaryPayload = "ThisIsTestSecondaryPayloadCardInfo1234567890"; 

Bundle primaryCardDetail = new Bundle();
primaryCardDetail.putString(AddCardInfo.EXTRA_PROVISION_PAYLOAD, testPrimaryPayload);
AddCardInfo primaryAddCardInfo = new AddCardInfo(cardType, primaryTokenizationProvider, primaryCardDetail);

Bundle secondaryCardDetail = new Bundle();
secondaryCardDetail.putString(AddCardInfo.EXTRA_PROVISION_PAYLOAD, testSecondaryPayload);
AddCardInfo secondaryAddCardInfo = new AddCardInfo(cardType, secondaryTokenizationProvider, secondaryCardDetail);

cardManager.addCobadgeCard(primaryAddCardInfo, secondaryAddCardInfo, new AddCardListener() {
    @Override
    public void onSuccess(int status, Card card) {
        Log.d(TAG, "onSuccess callback is called"); 
    }
    
    @Override
    public void onFail(int error, Bundle errorData ) {
        Log.d(TAG, "onFail callback is called"); 
        check some extra error codes in the errorData bundle
such as SamsungPay.EXTRA_ERROR_REASON or SamsungPay.EXTRA_REQUEST_ID (if provided).
    }
    
    @Override
    public void onProgress(int currentCount, int totalCount, Bundle bundleData) { 
        Log.d(TAG,"onProgress callback is called : " + currentCount + " / " +  totalCount); 
    } 
});

3.1.23 Sample Applications

Sample apps, use cases, and UX strategies are included here to aid you in understanding the SDK and implementing it in your application. Sample source code and APKs can be downloaded from Download section.

Sample merchant app

Included with the Samsung Pay SDK to demonstrate its features, the sample merchant app shows you how to implement the payment sheet’s dynamic controls to leverage additional customer order and payment data and/or create a more custom UI look and feel.

The following payment sheet controls are available:

  • AddressControl
  • PlainTextControl
  • AmountBoxControl
  • SpinnerControl

Controls are applied to suit a particular purpose or need. For example, displaying a promotion notice in the payment sheet using the PlainTextControl.

Applying an AddressControl

This control is used to display the billing or shipping address on the payment sheet based on Samsung Pay’s My info user profile or addresses provided by your merchant app during the transaction request.

When creating the control, controlld and SheetItemType are needed to distinguish the billing address from the shipping address. Otherwise, your merchant app sets the following properties:

  • Address title – displays a merchant-defined title on the payment sheet. If empty, the default title (such as “Billing address”) is displayed.

  • Address – provides various methods to retrieve address details.
    The merchant app can retrieve the phone number using the 'getPhoneNumber'() method of 'CustomSheetPaymentInfo' Address. Starting from API level 1.5, the addressee’s email address has also been added. Retrieve the email address using 'getEmail'(). You can also set a display option for the shipping address with 'setDisplayOption'(). For more information, see the Samsung Pay SDK-API reference Javadoc and the sample code included with the Samsung Pay SDK.

  • SheetUpdatedListener – used to capture the response from the Samsung Wallet app; merchant app must deliver to the Samsung Wallet app an AmountBoxControl to display payment information on a custom payment sheet. When the onResult() callback is called, the updateSheet() method must also be called to update the current payment sheet.

  • ErrorCode – used for containing error codes directly related to the address.

The workflows for BillingAddressControl:

The workflow for ShippingAddressControl:

The following sample code demonstrates use of AddressControl on the payment sheet.

fun makeBillingAddressControl(): AddressControl {
    val billingAddressControl = if (!isZipCodeOnly) {
        // For billing address
        AddressControl(BILLING_ADDRESS_ID, SheetItemType.BILLING_ADDRESS)
        billingAddressControl.addressTitle = "Billing Address"
    } else {
        /* 
         * For billing address with zip code only.
         * Since API level 2.19, SheetItemType.ZIP_ONLY_ADDRESS
         * For US country only
         */
        AddressControl(BILLING_ADDRESS_ID, SheetItemType.ZIP_ONLY_ADDRESS)
        billingAddressControl.addressTitle = "Zip Code"
    }
    //This callback is received when Controls are updated
    billingAddressControl.sheetUpdatedListener = sheetUpdatedListener()
    return billingAddressControl
}

//Listener for billing or zip code only billing address
fun sheetUpdatedListener(): SheetUpdatedListener {
    return SheetUpdatedListener { updatedControlId: String, customSheet: CustomSheet ->
        Log.d(
            TAG,
            "onResult billingAddressControl updatedControlId: $updatedControlId"
        )
        val addressControl =
            customSheet.getSheetControl(updatedControlId) as AddressControl
        val billAddress = addressControl.address
        //Validate only zipcode or billing address. And set errorCode if needed.
        if (addressControl.sheetItem.sheetItemType == SheetItemType.ZIP_ONLY_ADDRESS) {
            val errorCode: Int = validateZipcodeBillingAddress(billAddress)
            Log.d(TAG, "onResult updateSheetBilling  errorCode: $errorCode")
            addressControl.errorCode = errorCode
            customSheet.updateControl(addressControl)
        } else {
            val errorCode = validateBillingAddress(billAddress)
            Log.d(TAG, "onResult updateSheetBilling  errorCode: $errorCode")
            addressControl.errorCode = errorCode
            customSheet.updateControl(addressControl)
        }
        // update transaction values
        val amountBoxControl =
            CustomSheet.getSheetControl(AMOUNT_CONTROL_ID) as AmountBoxControl
        amountBoxControl.updateValue(PRODUCT_ITEM_ID, 1000.0)
        amountBoxControl.updateValue(PRODUCT_TAX_ID, 50.0)
        amountBoxControl.updateValue(PRODUCT_SHIPPING_ID, 10.0)
        amountBoxControl.updateValue(PRODUCT_FUEL_ID, 0.0, "Pending")
        amountBoxControl.setAmountTotal(1060.0, AmountConstants.FORMAT_TOTAL_PRICE_ONLY)
        customSheet.updateControl(amountBoxControl)
        try {
            // Call updateSheet for the full AmountBoxControl; mandatory
            paymentManager.updateSheet(customSheet)
        } catch (e: IllegalStateException) {
            e.printStackTrace()
        } catch (e: NullPointerException) {
            e.printStackTrace()
        }
    }
}

  
     
// For Shipping address
fun makeShippingAddressControl(): AddressControl {
    val shippingAddressControl = AddressControl(SHIPPING_ADDRESS_ID, SheetItemType.SHIPPING_ADDRESS)
    shippingAddressControl.addressTitle = "Shipping Address"
    val shippingAddress = CustomSheetPaymentInfo.Address.Builde()
        .setAddressee("name")
        .setAddressLine1("addLine1")
        .setAddressLine2("addLine2")
        .setCity("city")
        .setState("state")
        .setCountryCode("USA")
        .setPostalCode("zip")
        .setPhoneNumber("555-123-1234")
        .setEmail("user@samsung.com")
        .build()
    shippingAddressControl.address = shippingAddress
    /*
     * Set address display option on custom payment sheet.
     * If displayOption is not set, then default addressControl is displayed on custom payment sheet.
     * The possible values are combination of below constants:
     *     {DISPLAY_OPTION_ADDRESSEE}
     *     {DISPLAY_OPTION_ADDRESS}
     *     {DISPLAY_OPTION_PHONE_NUMBER}
     *     {DISPLAY_OPTION_EMAIL}
     */
    var displayOption_val = AddressConstants.DISPLAY_OPTION_ADDRESSEE // Addressee is mandatory
    displayOption_val += AddressConstants.DISPLAY_OPTION_ADDRESS
    displayOption_val += AddressConstants.DISPLAY_OPTION_PHONE_NUMBER
    displayOption_val += AddressConstants.DISPLAY_OPTION_EMAIL
    shippingAddressControl.displayOption = displayOption_val
    return shippingAddressControl
}

Here’s how these controls display on a custom payment sheet:

Billing Address control

Zip Code Billing Address control

Shipping Address control

Applying a PlainTextControl

This control is used for displaying a title with two lines of text or a single line of text without a title on the payment sheet. When allocating this control, a controlId is needed. The merchant app sets both the title, as applicable, and the text. Diagrammed below is the flow between your merchant app and Samsung Pay.

PlainTextControl flow

The merchant app code invoking this class would look something like the following:

fun makePlainTextControl(): PlainTextControl {
    val plainTextControl = PlainTextControl("ExamplePlainTextControlId")
    plainTextControl.setText("Plain Text [example]", "This is example of PlainTextControl")
    return plainTextControl
}

And this is how it displays on the custom payment sheet.

Applying an AmountBoxControl

AmountBoxControl is used for displaying purchase amount information on the payment sheet.

It requires a controlId and a currencyCode, and consists of Item(s) and AmountTotal, defined as follows and diagrammed on the next page:

  • Item – consists of id, title, price, and extraPrice. If there is an extraPrice in AmountBoxControl, its text is displayed on the payment sheet even though there is an actual (numerical) price value. If there is no extraPrice, then currencyCode with the price value is displayed.
  • AmountTotal – consists of price and displayOption. The displayOption allows predefined strings only. Your merchant app can set the text to “Estimated amount”, “Amount pending”, “Pending”, “Free”, and so forth. The UI format for the string is different for each option.

For details, see the Javadoc Samsung Pay SDK-API Reference, available in the Documentation folder of your downloaded SDK package.

Here’s a coding example to demonstrate the use of AmountBoxControl in a payment sheet.

fun makeAmountControl(): AmountBoxControl {
    val amountBoxControl = AmountBoxControl(AMOUNT_CONTROL_ID, "USD")
    amountBoxControl.addItem(PRODUCT_ITEM_ID, "Item", 1000.0, "")
    amountBoxControl.addItem(PRODUCT_TAX_ID, "Tax", 50.0, "")
    amountBoxControl.addItem(PRODUCT_SHIPPING_ID, "Shipping", 10.0, "")
    amountBoxControl.setAmountTotal(1060.0, AmountConstants.FORMAT_TOTAL_PRICE_ONLY)
    amountBoxControl.addItem(3, PRODUCT_FUEL_ID, "Fuel", 0.0, "Pending")
    return amountBoxControl
}

The merchant app can also add new items using the 'addItem'() method of 'AmountControlBox' during callback.

When the custom sheet is updated, the merchant can add new items to AmountBoxControl. For example, if the user selects a specific card in the payment sheet which the merchant offers, a discount item can be added via the updateSheet().

// Example for adding new item while updating values
val amount = sheet.getSheetControll("id_amount")

amount.updateValue("itemId", 900.0)
amount.updateValue("taxId", 50.0)
amount.updateValue("shippingId", 10.0)
amount.updateValue("fuelId", 0.0)


// Add “Discount” item
amount.addItem(4, "discountId", "Discount", -60.0, "")

amount.setAmountTotal(1000.0, AmountConstants.FORMAT_TOTAL_PRICE_ONLY)
sheet.updateControl(amount)


// Call updateSheet with AmountBoxControl; mandatory
try {
    paymentManager.updateSheet(sheet)
} catch (e: IllegalStateException) {
    e.printStackTrace()
} catch (e: NullPointerException) {
    e.printStackTrace()
}

Applying the SpinnerControl

This control is used for displaying Spinner options on a payment sheet. When creating the control, controlId, title, and SheetItemType are needed to distinguish between the types of spinner to be displayed. Your merchant app sets the following properties with SpinnerControl.

  • title – the merchant-defined Spinner title to appear the payment sheet.
  • SheetItemType – provides various types of Spinner.

A SHIPPING_METHOD_SPINNER and an INSTALLMENT_SPINNER are the two types of spinner available as of API level 1.6.

Here’s an example of constructing a SpinnerControl within your merchant app:

// Construct SpinnerControl for shipping method
val spinnerControl = SpinnerControl(
    SHIPPINGMETHOD_SPINNER_ID, "Shipping Method ",
    SheetItemType.SHIPPING_METHOD_SPINNER
)

// Let the user can select one shipping method option on the payment sheet
spinnerControl.addItem(
    "shipping_method_1",
    getString(android.R.string.standard_shipping_free)
)
spinnerControl.addItem("shipping_method_2", getString(android.R.string.twoday_shipping))
spinnerControl.addItem("shipping_method_3", getString(android.R.string.oneday_shipping))
spinnerControl.selectedItemId = "shipping_method_1" // Set default option

// Listen for SheetControl events
spinnerControl.setSheetUpdatedListener(SheetUpdatedListener { updatedControlId, customSheet ->
    val amountBoxControl =
        CustomSheet.getSheetControl(AMOUNT_CONTROL_ID) as AmountBoxControl
    val spinnerControl = CustomSheet.getSheetControl(updatedControlId) as SpinnerControl
    when (spinnerControl.selectedItemId) {
        "shipping_method_1" -> amountBoxControl.updateValue(PRODUCT_SHIPPING_ID, 10.0)
        "shipping_method_2" -> amountBoxControl.updateValue(PRODUCT_SHIPPING_ID, 10 + 0.1)
        "shipping_method_3" -> amountBoxControl.updateValue(PRODUCT_SHIPPING_ID, 10 + 0.2)
        else -> amountBoxControl.updateValue(PRODUCT_SHIPPING_ID, 10.0)
    }
    amountBoxControl.setAmountTotal(
        1000 + amountBoxControl.getValue(PRODUCT_SHIPPING_ID),
        AmountConstants.FORMAT_TOTAL_PRICE_ONLY
    )
    customSheet.updateControl(amountBoxControl)

    // Call updateSheet with AmountBoxControl; mandatory.
    try {
        paymentManager.updateSheet(customSheet)
    } catch (e: IllegalStateException) {
        e.printStackTrace()
    } catch (e: NullPointerException) {
        e.printStackTrace()
    }
})

// Construct SpinnerControl for installment plan
val spinnerControl = SpinnerControl(
    INSTALLMENT_SPINNER_ID, "Installment",
    SheetItemType.INSTALLMENT_SPINNER
)
spinnerControl.addItem("installment_1", "1 month without interest")
spinnerControl.addItem("installment_2", "2 months with 2% monthly interest")
spinnerControl.addItem("installment_3", "3 months with 2.2% monthly interest")
spinnerControl.selectedItemId = "installment_1" // Set default option

// Listen for SheetControl events
spinnerControl.setSheetUpdatedListener(SheetUpdatedListener { updatedControlId, customSheet ->
    val amountBoxControl: AmountBoxControl = 
    CustomSheet.getSheetControl(AMOUNT_CONTROL_ID) as AmountBoxControl
    val spinnerControl = CustomSheet.getSheetControl(updatedControlId) as SpinnerControl
    val totalInterest = 0.0
    when (spinnerControl.selectedItemId) {
        "installment1" -> amountBoxControl.updateValue(
            PRODUCT_TOTAL_INTEREST_ID,
            totalInterest
        )

        "installment2" ->                 // Calculate total interest again and updateValue
            amountBoxControl.updateValue(PRODUCT_TOTAL_INTEREST_ID, totalInterest)

        "installment3" ->                 // Calculate total interest again and updateValue
            amountBoxControl.updateValue(PRODUCT_TOTAL_INTEREST_ID, totalInterest)

        else -> amountBoxControl.updateValue(PRODUCT_TOTAL_INTEREST_ID, totalInterest)
    }
    amountBoxControl.setAmountTotal(
        1000 + amountBoxControl.getValue(PRODUCT_TOTAL_INTEREST_ID),
        AmountConstants.FORMAT_TOTAL_PRICE_ONLY
    )
    customSheet.updateControl(amountBoxControl)

    // Call updateSheet with AmountBoxControl; mandatory.
    try {
        paymentManager.updateSheet(customSheet)
    } catch (e: IllegalStateException) {
        e.printStackTrace()
    } catch (e: NullPointerException) {
        e.printStackTrace()
    }
})

Update sheet with custom error message

To display a custom error message on the payment sheet, use updateSheet with customErrorMessage.

fun updateSheet(sheet : CustomSheet, errorCode : Int, customErrorMessage : String)

This API method is an extended version of the existing updateSheet(sheet) method which gives the merchant the ability to display a custom error message in the payment sheet’s authentication area. It can be used to inform the user of any foreseen error scenarios encountered.

// Update sheet with CUSTOM_MESSSAGE error code
paymentManager.updateSheet(customSheet, PaymentManager.CUSTOM_MESSAGE,"Phone number entered is not valid. Please change your phone number.")

Sample issuer app

The Samsung Pay SDK also provides a sample issuer app to showcase Samsung Pay SDK features.




Issuer app can add card to Samsung Wallet by selecting specific token service provider (TSP) from the dropdown menu.




To add Cobadge card you need to select primary and secondary token service providers (TSP) from the dropdown menus.



For more information, refer to the Samsung Pay SDK API Reference and sample code.


3.1.24 API References