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, controlId 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 Pay app; merchant app must deliver to the Samsung Pay 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 and ShippingAddressControl are shown below.

BillingAddressControl


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

ZipCode Billing Address control

Shippling Address control

Applying a PlainTextControl

This control is used for displaying a title with a 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]](https://d3unf4s5rp9dfh.cloudfront.net/SamsungPay_doc/7_1_3_PlainTextControl.png)

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.
PlainTextControl flow

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.
AmountBoxControl flow

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.

Sample Merchant

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.

Sample Issuer application

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