Android Connect

The PortOne Android SDK simplifies the integration of the PortOne Payment Gateway into your Android app, offering a secure, reliable, and efficient way to accept payments. With this SDK, you can seamlessly connect your app to a variety of payment channels, providing a smooth payment experience for users.


Sample App

Check the sample app to integrate on GitHub


Prerequisites

  • Create an account on PortOne:
    Before proceeding with the integration, ensure you have created an account on PortOne to access their services and functionalities.
  • Enable Payment Channels and Methods:
    Customize and enable the specific payment channels and methods that align with your business requirements and preferences.
  • Android application for the integration:
    You will need an existing Android application in which you intend to integrate the PortOne Android SDK for payment processing capabilities.
  • authKey to access the SDK:
    Obtain an authorization key (authKey) from the PortOne Team, as it is required to securely access and utilize the features of the PortOne SDK in your Android application. authKey will be issued by the PortOne Team by sending us email on this [email protected]

Integration

Steps to integrate your Android application with PortOne Android SDK.

  1. Install PortOne Android SDK and Authorise it.
  2. Set the Intent Filters in the Manifests
  3. Set Intent Receivers for Payment Status
  4. Setup to Obtain JWT Token from the Server
  5. Generate a Signature Hash for Payload

1. Install PortOne Android SDK and Authorise it.

To begin, add the PortOne Android SDK to your project by adding the dependency to your build.gradle file.

  • In the build.gradle (:app) file, add:
implementation 'com.github.iamport-intl:portone-android-native-sdk:V3.0.48'
  • Next, add the authKey to your gradle.properties file:.
authKey= XXXXXXXXXXXXXXXXXXXXXX
  • Authorize the SDK by adding the provided authKey to your gradle.properties file, which you can request by emailing the PortOne team at [email protected].

Then, configure your build.gradle (:Project) or settings.gradle to reference the key:

  1. Build.gradle (:Project) Setup:

    repositories {
            maven { url '<https://maven.google.com/>' }
            maven{
                url '<https://jitpack.io>'
                credentials { username authKey }
            }
        }
    
  2. Settings.gradle Setup:

    dependencyResolutionManagement {
        repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
        repositories {
            google()
            maven {
                url 'https://maven.google.com/'
            }
            maven {
                url 'https://jitpack.io'
                credentials { username authKey } // Add your generated authKey here
            }
        }
    }
    

Include the necessary code in your build.gradle () file, specifying the project dependency and Kotlin version.

buildscript {
    ext.kotlin_version = "1.5.10" // Specify the Kotlin version here
    
    repositories {
        google()
        jcenter()
    }
    dependencies {
        classpath "com.android.tools.build:gradle:4.2.2" // Add the Android Gradle plugin version
        
        // Add any other dependencies needed for your project setup
    }
}

2. Set the Intent Filters in the Manifests

  • Next, you need to add intent filters in your AndroidManifest.xml to handle payment status updates and navigate users back to your app after payment.

    <activity android:name=".CheckoutActivity">
        <intent-filter>
            <action android:name="android.intent.action.VIEW" />
            <category android:name="android.intent.category.DEFAULT" />
            <category android:name="android.intent.category.BROWSABLE" />
            <data
                android:scheme="portone"
                android:host="checkout" />
        </intent-filter>
    </activity>
    
    

This setup ensures your app can handle the redirection URL after payment completion. For detailed instructions, refer to the deep linking guide here.

3. Set Intent Receivers for Payment Status:

You’ll need to handle the payment status response using the onActivityResult method. Here’s an example:.

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)

        if (resultCode == RESULT_CODE && data != null) {
            when (requestCode) {
                PAYOUT_REQUEST_CODE, PAYMENT_STATUS_REQUEST_CODE -> {
                    val paymentStatus: CheckoutConnectDto.UpdatedPaymentStatusResponse? =
                        (data.getSerializableExtra(PAYMENT_STATUS)
                            ?: "Empty") as CheckoutConnectDto.UpdatedPaymentStatusResponse
                }

            }

        }
    }

This method receives the payment status and updates the user interface with the payment result.

4. Have a setup to get JWT token from the server

The PortOne SDK requires a JWT token for authentication. You need to implement a server-side process to generate this token and retrieve it in your Android app.

Steps:

  1. Implement server logic to generate a JWT token.
  2. In your Android app, fetch the token to process the checkout.

The process for generating a JWT token can be found in detail here

5. Generate Signture Hash

To generate a signature hash, create it on the server side using a secret key that will be included in the payload. This ensures secure processing of transactions.

Note: Generating a signature hash is optional if you have whitelisted your app while initializing the checkout. Detailed instructions on this are provided in the checkout setup section.

Steps:

  1. Implement server logic to generate a signature hash.
  2. In your Android app, fetch the signature hash to process the checkout.

The process for generating a Signature Hash can be found in detail here


Android Connect

The Android Connect SDK helps merchants add payment features to their Android apps. Unlike other SDKs that come with ready-made user interfaces, the Connect SDK gives you full control to design your own checkout screen to match your brand’s look and feel.

This SDK focuses only on the important payment functions, allowing you to create a unique user experience for your customers. It provides all the tools you need to handle payments efficiently while giving you the flexibility to customize how everything looks and works in your app.

Overview

  1. Initialize the SDK
  2. Fetch Enabled Payment Methods and Channels
  3. Checkout
  4. Checkout Using a New Card
  5. Checkout Using Saved Credit/Debit Cards
    1. Retrieve Saved Cards Using Phone Number
      1. Fetch OTP for Authentication
      2. Fetch Saved Cards Using Phone Number and OTP
    2. Retrieve Saved Cards Using Auth Token
  6. Merchant-Centric Card Vault
    1. Create a Customer
    2. Save a Card for Customer
    3. Get Customer Information
    4. Fetch a List of Cards for a Customer
    5. Delete Card for a Customer
  7. PreAuth and Capture
  8. Failover Routing

Methods and Applications

1. Initialize the SDK

To begin using the Android Connect SDK, create an instance of the PortOne SDK in the activity where the payment checkout process will take place. Initialize the SDK by specifying the environment—either sandbox for testing or live for production.

Steps

  1. Create an instance of the PortOne SDK in your activity.
  2. Set the environment to either sandbox for testing or live for production based on your use case.
class CheckoutActivity : AppCompatActivity() {

    // Step 1: Set the environment to "sandbox" for testing or "live" for production
    private var environment = "sandbox" // Set environment to "sandbox" for testing
    // For live environment uncomment the line below and comment the sandbox line
    // private var environment = "live" // Set environment to "live" for production

    // Step 2: Declare the PortOne instance
    private lateinit var portOne: PortOne

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_checkout)

        // Step 3: Initialize the PortOne SDK with the selected environment
        portOne = PortOneImpl(
            context = this,
            environment = environment,
            applicationId = BuildConfig.APPLICATION_ID)
    }
}


2. Fetch Enabled Payment Methods and Channels

This method retrieves the available payment channels and methods that can be used for the checkout process.

portOne.getPaymentMethods(
    portoneKey = portoneKey,           // Required: Merchant key
    subMerchantKey = subMerchantKey,   // Optional: Sub-merchant key (if applicable)
    currency = currency,               // Required: Currency (e.g., "USD", "KRW")
    object : ApiCallbackInterface<PaymentDto.PaymentMethodResponse> {
        override fun onSucceed(response: PaymentDto.PaymentMethodResponse) {
            LoggerUtil.info("Payment methods retrieved successfully")
        }
        override fun onFailure(errorCode: Int?, status: String?, errorMessage: String, throwable: Throwable) {
            LoggerUtil.info("Failed to retrieve payment methods: $errorMessage")
        }
    }
)
ParameterData TypeDescription
portoneKeyStringThe merchant's unique identifier. (mandatory)
subMerchantKeyStringThe sub-merchant's identifier. (optional)
currencyStringThe currency for the transaction. (mandatory)

3. Checkout

Checkout with All Payment Methods

The Checkout with All Payment Methods allows you to process payments using various methods, including:

  • Wallets
  • Saved Cards
  • Installments
  • Bank Transfers
  • Direct Bank Transfers

This method supports multiple payment channels and methods, including customer details (billing/shipping), order details, and redirection URLs for handling success or failure of transactions.


val checkoutRequest = CheckoutConnectDto.CheckoutRequest(
    portoneKey = "your_portone_key",
    paymentChannel = "paymentChannel", 
    paymentMethod = "paymentMethod",  
    merchantOrderId = "merchant_12345",
    amount = 100.0,
    currency = "USD",
    signatureHash = "example_signature_hash",
    billingDetails = billingDetails,
    shippingDetails = shippingDetails,
    orderDetails = orderDetails,
    successUrl = "https://yourapp.com/payment/success",
    failureUrl = "https://yourapp.com/payment/failure",
    redirectUrl = "https://yourapp.com/redirect",
    environment = "sandbox",
    source = "mobile",
    bankDetails = bankDetails,
    directBankTransferDetails = dbtDetails,
    transactionType = "payment",
    cardDetails = cardDetails,
    description = "Test transaction for order 12345",
    isRoutingEnabled = true,
    routingParams = routingParams
)

portOne.checkout(checkoutRequest)

ParameterData TypeDescription
portoneKeyStringMerchant’s unique identifier. (mandatory)
paymentChannelStringPayment channel, e.g., MOMOPAY, STRIPE.
paymentMethodStringSpecific payment method, e.g., MOMOPAY_WALLET.
merchantOrderIdStringUnique order identifier from the merchant. (mandatory)
amountDoubleTotal transaction amount. (mandatory)
currencyStringCurrency for the transaction, e.g., USD, KRW.
signatureHashStringSecurity hash for transaction verification. (optional)
billingDetailsBillingDetailsCustomer’s billing information. (optional)
shippingDetailsShippingDetailsShipping information. (optional)
orderDetailsListList of items in the order. (optional)
successUrlString(optional)
failureUrlString(optional)
redirectUrlStringURL for final redirection after payment completion.
environmentStringTransaction environment: "sandbox" or "live".
sourceStringIn the case of SDK its always mobile
bankDetailsBankDetailsInformation about the bank for installment payments.
directBankTransferDetailsDBTDetailsDetails for direct bank transfer.
transactionTypeStringType of transaction, e.g., payment, preauth.
cardDetailsCardDetailsTokenized card details if using a saved card.
descriptionStringDescription of the transaction or order.
isRoutingEnabledBooleanEnable failover routing. (optional)
routingParamsRoutingParamsParameters for routing in case of a failover scenario.

Checkout Using a New Credit Card

The Checkout Using a New Credit Card method is specifically designed for transactions where the customer enters new credit or debit card details.

kotlin
Copy code
val newCard = CheckoutConnectDto.NewCard(
    cardNumber = "4111111111111111",    // Example card number
    cardType = "VISA",                  // Card type, e.g., VISA, MASTERCARD
    cardholderName = "John Doe",        // Cardholder's name
    expiryMonth = "12",                 // Expiration month
    expiryYear = "2025",                // Expiration year
    serviceCode = "123",                // CVV/CVC code
    environment = "sandbox",            // Set environment: sandbox or live
    portoneKey = "your_portone_key"     // Merchant’s unique identifier
)

val checkoutRequest = CheckoutConnectDto.CheckoutRequest()

portOne.checkoutUsingNewCard(
    paymentDetails = checkoutRequest,
    newCard = newCard,
    subMerchantKey = "your_sub_merchant_key",
    token = "your_auth_token"
)

ParameterData TypeDescription
paymentDetailsCheckoutRequestMain checkout request details including amount, currency, etc.
newCardNewCardNew card details for the payment.
subMerchantKeyStringOptional sub-merchant key used by master merchants.
tokenStringAuthentication token for secure processing.

4. Making a Payment Using Saved Cards

To make a payment using saved cards for the first time, this process retrieves cards linked to the user’s phone number. OTP authentication is required, making it a two-step process for initial access. After the initial retrieval, future access can use an authentication token for convenience.


4.1 Retrieving Saved Cards for the First Time

This method retrieves saved cards by first sending an OTP to the registered phone number, followed by using the OTP for authentication to access the saved cards.


Step 1: Send OTP to Registered Phone Number

portOne.getOTP(
    phoneNo = phoneNo,
    object : ApiCallbackInterface<PaymentDto.OtpResponse> {
        override fun onSucceed(response: PaymentDto.OtpResponse) {
            LoggerUtil.info("OTP sent successfully")
        }
        override fun onFailure(errorCode: Int?, status: String?, errorMessage: String, throwable: Throwable) {
            LoggerUtil.info("Failed to send OTP: $errorMessage")
        }
    }
)

ParameterData TypeDescription
phoneNoStringUser’s phone number to receive OTP. (mandatory)

Step 2: Retrieve Saved Cards Using OTP

portOne.getSavedCards(
    token = null,
    portoneKey = portoneKey,
    phoneNo = phoneNo,
    otp = otp,
    object : ApiCallbackInterface<PaymentDto.CreditCardDetailsResponse> {
        override fun onSucceed(response: PaymentDto.CreditCardDetailsResponse) {
            LoggerUtil.info("Successfully retrieved saved cards")
        }
        override fun onFailure(errorCode: Int?, status: String?, errorMessage: String, throwable: Throwable) {
            LoggerUtil.info("Failed to retrieve saved cards: $errorMessage")
        }
    }
)

ParameterData TypeDescription
tokenStringAuthentication token, not required for first-time retrieval.
portoneKeyStringMerchant’s unique identifier.
phoneNoStringUser’s phone number for retrieving saved cards.
otpStringOTP sent to the phone for verification.

4.2 Retrieving Saved Cards for Subsequent Access

After the initial retrieval, a token is returned in the response. This token can be used instead of an OTP for future card retrievals, allowing a more streamlined authentication process.

portOne.getSavedCards(
    token = token,
    portoneKey = portoneKey,
    phoneNo = phoneNo,
    otp = null,
    object : ApiCallbackInterface<PaymentDto.CreditCardDetailsResponse> {
        override fun onSucceed(response: PaymentDto.CreditCardDetailsResponse) {
            LoggerUtil.info("Successfully retrieved saved cards using token")
        }
        override fun onFailure(errorCode: Int?, status: String?, errorMessage: String, throwable: Throwable) {
            LoggerUtil.info("Failed to retrieve saved cards: $errorMessage")
        }
    }
)

ParameterData TypeDescription
tokenStringAuthentication token obtained after initial retrieval.
portoneKeyStringMerchant’s unique identifier.
phoneNoStringUser’s phone number for retrieving saved cards.
otpStringNot required for subsequent retrievals; set to null.

These methods also fall under the checkout flow but require extra parameters and steps.

Direct Bank Transfer and Instalments

To process these checkout methods, specific additional parameters and steps in the request process are required.

ParametersData Type
customerNameStringmandatory
transactionTimeStringmandatory
amountPaidDoublemandatory

Merchant-Centric Card Vault

The Merchant-Centric Card Vault operates by enrolling merchants initially, followed by adding customers specific to each merchant. Cards are then saved based on individual customers, ensuring a personalized and secure card storage system. Several methods are available to facilitate various operations within this card vault setup.

1. Add Customer

This method is used to add a customer to the Merchant-Centric Card Vault.

portOne.addCustomer(
    token = token,
    portOneKey = portoneKey,
    subMerchantKey = subMerchantKey,
    request = PaymentDto.AddCustomerRequest(
        name = name,
        phoneNo = phoneNo,
        email = email,
        customerRef = customerRef
    ),
    object : ApiCallbackInterface<PaymentDto.AddCustomerResponse> {
        override fun onSucceed(response: PaymentDto.AddCustomerResponse) {
            val gson = Gson()
            val json = gson.toJson(response)
            LoggerUtil.info("Successful")
        }

        override fun onFailure(
            errorCode: Int?,
            status: String?,
            errorMessage: String,
            throwable: Throwable
        ) {
            LoggerUtil.info("Failed")
        }
    })

ParameterData TypeDescription
tokenStringMandatory
portoneKeyStringMandatory
subMerchantKeyStringOptional
nameStringMandatory
phoneNoStringMandatory
emailStringMandatory
customerRefStringMandatory

2. Save a Card for a Customer

This method is used to save a specific card for a particular customer.

portOne.addCardForCustomer(
    customerUUID = customerUUID,
    token = token,
    portoneKey = portoneKey,
    subMerchantKey = subMerchantKey,
    request = PaymentDto.NewCard(),
    object : ApiCallbackInterface<PaymentDto.AddCardsResponse> {
        override fun onSucceed(response: PaymentDto.AddCardsResponse) {
            LoggerUtil.info("Successful")
        }

        override fun onFailure(
            errorCode: Int?,
            status: String?,
            errorMessage: String,
            throwable: Throwable
        ) {
            LoggerUtil.info("Failed")
        }
    })

ParameterData TypeDescription
customerUUIDStringMandatory
tokenStringMandatory
portoneKeyStringMandatory
subMerchantKeyStringOptional

PaymentDto.NewCard

ParameterData TypeDescription
cardNumberStringMandatory
cardTypeStringMandatory
cardholderNameStringMandatory
serviceCodeStringMandatory
expiryYearStringMandatory
expiryMonthStringMandatory
environmentStringMandatory
portoneKeyStringMandatory

3. Retrieve Stored Cards for a Customer

This method retrieves the list of cards stored for a specific customer.

portOne.listCardsForCustomer(
    customerUUID = customerUUID,
    token = token,
    portoneKey = portoneKey,
    subMerchantKey = subMerchantKey,
    object : ApiCallbackInterface<PaymentDto.ListCardsForCustomerResponse> {
        override fun onSucceed(response: PaymentDto.ListCardsForCustomerResponse) {
            LoggerUtil.info("Successful")
        }

        override fun onFailure(
            errorCode: Int?,
            status: String?,
            errorMessage: String,
            throwable: Throwable
        ) {
            LoggerUtil.info("Failed")
        }
    })

ParameterData TypeDescription
customerUUIDStringMandatory
tokenStringMandatory
portoneKeyStringMandatory
subMerchantKeyStringOptional

4. Delete a Card for a Customer

This method deletes a specific card for an individual customer.

portOne.deleteCardForCustomer(
    customerUUID = customerUUID,
    token = token,
    portoneKey = portoneKey,
    subMerchantKey = subMerchantKey,
    request = PaymentDto.DeleteCardRequest(token = cardToken),
    object : ApiCallbackInterface<PaymentDto.GenericResponse> {
        override fun onSucceed(response: PaymentDto.GenericResponse) {
            LoggerUtil.info("Successful")
        }

        override fun onFailure(
            errorCode: Int?,
            status: String?,
            errorMessage: String,
            throwable: Throwable
        ) {
            LoggerUtil.info("Failed")
        }
    })

ParameterData TypeDescription
customerUUIDStringMandatory
tokenStringMandatory
portoneKeyStringMandatory
subMerchantKeyStringOptional
cardTokenStringMandatory

5. Get Customer Information

This method retrieves information about a specific customer.

portOne.getCustomer(
    token = token,
    portOneKey = portoneKey,
    customerUUID = customerUUID,
    getCustomerCallback = object : ApiCallbackInterface<CheckoutConnectDto.GetCustomerDataResponse> {
        override fun onSucceed(response: CheckoutConnectDto.GetCustomerDataResponse) {
            LoggerUtil.info("Successfully retrieved customer data")
        }

        override fun onFailure(
            errorCode: Int?,
            status: String?,
            errorMessage: String,
            throwable: Throwable
        ) {
            LoggerUtil.info("Failed to retrieve customer data: $errorMessage")
        }
    }
)

ParameterData TypeDescription
tokenStringMandatory
portOneKeyStringMandatory
customerUUIDStringUnique identifier for the customer

Pre-authorization and Payment Capture

During Pre-authorization, the transaction is initially approved, allowing funds to be reserved. At a later time or within specified days, the payment can be finalized using the Capture API.

To designate a transaction for pre-authorization, configure the transactionType parameter in the payload as shown:

transactionType = "PREAUTH"

kotlin
Copy code
val paymentDetails = PaymentDto.CheckoutUsingTokenizationRequest()
paymentDetails.transactionType = "PREAUTH"

💡

Note: Pre-authorization is applicable only for credit card payments and is designed to provide a smooth workflow.


Capturing a Pre-authorized Transaction

Once a transaction has been pre-authorized, the following method is used to capture (finalize) the transaction:

portOne.captureTransaction(
    orderReference = orderReference,
    portoneKey = portoneKey,
    token = token,
    object : ApiCallbackInterface<PaymentDto.GenericResponse> {
        override fun onSucceed(response: PaymentDto.GenericResponse) {
            LoggerUtil.info("Successful")
        }

        override fun onFailure(
            errorCode: Int?,
            status: String?,
            errorMessage: String,
            throwable: Throwable
        ) {
            LoggerUtil.info("Failed")
        }
    }
)

ParameterData TypeDescription
orderReferenceStringMandatory
portoneKeyStringMandatory
tokenStringMandatory

Failover Routing

Failover routing is designed to ensure seamless credit card payment processing. This feature allows the configuration of primary and secondary payment channels through the admin portal. If a payment attempt using the primary channel fails, the system automatically redirects the user to the secondary payment channel.

Fetching Routes

Use the following method to retrieve routes created in the admin portal:

portOne.getRoutesList(
    token = token,
    portoneKey = portoneKey,
    object : ApiCallbackInterface<PaymentDto.RoutesListResponse> {
        override fun onSucceed(response: PaymentDto.RoutesListResponse) {
            val gson = Gson()
            val json = gson.toJson(response)
            LoggerUtil.info("Successful")
        }

        override fun onFailure(
            errorCode: Int?,
            status: String?,
            errorMessage: String,
            throwable: Throwable
        ) {
            LoggerUtil.info("Failed")
        }
    }
)

Configuring Failover Routing

This method provides the routeRef that should be included in the payload as outlined below:

  1. Enable routing by setting isRoutingEnabled to true.
  2. Specify the Routing Param type as "failover".
  3. Include the Routing Ref, which is configured in the merchant portal.

val paymentDetails = PaymentDto.CheckoutUsingTokenizationRequest()
paymentDetails.isRoutingEnabled = true     // true or false
paymentDetails.routingParams = 
PaymentDto.RoutingParams(type = "failover", routeRef)

Possible Error Scenarios:

INVALID_UNAUTHORIZED_JWT_TOKEN_ERROR

  1. Ensure that the PortOne Key and Secret Key belong to the same account.
  2. Confirm that the Secret Key has not been altered.
  3. Verify that the Bearer keyword precedes the generated token with a space. Example: Bearer $jwtToken.
  4. Check if the token expiration time is after the current time.

INVALID_UNAUTHORIZED_TRANSACTION_SIGNATURE_ERROR

  1. Validate if all parameters align with the payload/request.
  2. Ensure that the PortOne key matches with the payload and the account.

INVALID_UNAUTHORIZED_TRANSACTION_IAMPORTKEY_ERROR

  1. Confirm that the PortOne key matches with the payload and the account.

INVALID_PAYMENT_CHANNEL

  1. Validate that the payment channels and methods included in the payload are enabled in the PortOne portal.

INVALID_ENVIRONMENT

  1. Verify that an environment (sandbox or live) has been specified.

Summation of order value, tax, duties, shipping, and discount should equal the total amount

  1. If items are provided, ensure that the values match the total amount calculation formula: sum(items price * items quantity) + shipping charge - discount = amount.
  2. Mandatory parameters in the payload:
    • price
    • promo_discount (0 accepted)
    • shipping_charges (0 accepted)