Overview
Collecting payments in your Android app involves creating an object to collect card information, tokenizing card and payment details, and submitting the payment to Paysafe for processing.
Setup
For information about API keys, and how to integrate and set up the Android SDK, please refer to the Android SDK Overview.
Using the Demo app
To test the card payments integration using the Demo app, you need to modify some constants representing the API key and the account id associated with the card payment method:
- open example/src/main/java/com/paysafe/example/util/Consts.kt file.
- modify API_KEY string to contain your API key.
- modify CARDS_ACCOUNT_ID string to contain the account id associated with the card payment method.
Compose support
The custom fields used to capture card information are built using the Compose UI library. Make sure that the library is properly imported in your build.gradle.kts file:
implementation("androidx.compose.ui:ui:1.6.0")
If you want to include just the card payments module, specify it as shown in the following example:
dependencies {
implementation("com.github.paysafegroup.paysafe_sdk_android_payments_api:card-payments:x.y.z")
}
In this example, we exclude paysafe-cardinal from being used when importing the card-payments module:
implementation("com.github.paysafegroup.paysafe_sdk_android_payments_api:card-payments:0.0.43") {
exclude(
"com.github.paysafegroup.paysafe_sdk_android_payments_api",
"paysafe-cardinal"
)
}
To see what the latest version is, check the Github releases.
Create checkout page
Securely collect card information from the client using PSCardFormController, a pre-defined cards component provided by the SDK that collects the card number, expiration date, and CVV/CVC.
STEP 1: Add the hosted field views to your Layout XML. In the following example, app_padding is 16dp.
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/creditCardLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingBottom="@dimen/app_padding">
<com.paysafe.android.hostedfields.cardnumber.PSCardNumberView
android:id="@+id/creditCardNumberField"
android:layout_width="@dimen/no_size"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/app_padding"
android:layout_marginTop="24dp"
android:layout_marginEnd="@dimen/app_padding"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/creditCardTitleLabel" />
<com.paysafe.android.hostedfields.holdername.PSCardholderNameView
android:id="@+id/creditCardHolderNameField"
android:layout_width="@dimen/no_size"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/app_padding"
android:layout_marginTop="12dp"
android:layout_marginEnd="@dimen/app_padding"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/creditCardNumberField" />
<com.paysafe.android.hostedfields.expirydate.PSExpiryDatePickerView
android:id="@+id/creditCardExpiryDateField"
android:layout_width="@dimen/no_size"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/app_padding"
android:layout_marginTop="12dp"
android:layout_marginEnd="@dimen/app_padding"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/creditCardHolderNameField" />
<com.paysafe.android.hostedfields.cvv.PSCvvView
android:id="@+id/creditCardCvvField"
android:layout_width="@dimen/no_size"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/app_padding"
android:layout_marginTop="12dp"
android:layout_marginEnd="@dimen/app_padding"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/creditCardExpiryDateField" />
</androidx.constraintlayout.widget.ConstraintLayout>
STEP 2: Initialize hosted fields in your Activity/Fragment (View binding example).
private lateinit var binding: YourActivityBinding // or YourFragmentBinding
private var cardController: PSCardFormController? = null
// Your Activity code:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = YourActivityBinding.inflate(layoutInflater)
setContentView(binding.root)
}
// Your Fragment code:
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
binding = YourFragmentBinding.inflate(inflater, container, false)
// ... rest of your method
return binding.root
}
STEP 3: Initialize PSCardFormController with card number, expiry date and CVV fields.
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
// ... rest of your method
PSCardFormController.initialize(
cardFormConfig = PSCardFormConfig(
currencyCode = "USD",
accountId = "<account-id>"
),
cardNumberView = binding.creditCardNumberField,
cardHolderNameView = binding.creditCardHolderNameField,
cardExpiryDateView = binding.creditCardExpiryDateField,
cardCvvView = binding.creditCardCvvField,
callback = object : PSCallback<PSCardFormController> {
override fun onSuccess(value: PSCardFormController) {
cardController = value
}
override fun onFailure(exception: Exception) {
// handle exception
}
}
)
return binding.root
}
STEP 4: Configure options for creating the card payment handle token.
/// Payment amount in minor units
val amount = totalPrice * 100
val cardTokenizeOptions = PSCardTokenizeOptions(
amount = amount,
currencyCode = "USD",
transactionType = TransactionType.PAYMENT,
merchantRefNum = "<merchant-ref-num>",
billingDetails = BillingDetails(
nickName = "Jax Billing Address",
street = "5335 Gate Pkwy",
city = "Jacksonville",
state = "FL",
country = "US",
zip = "32256"
),
profile = Profile(
firstName = "firstName",
lastName = "lastName",
locale = ProfileLocale.EN_GB,
),
accountId = "<account-id>",
merchantDescriptor = MerchantDescriptor(
dynamicDescriptor = "dynamicDescriptor",
phone = "18003270093"
),
shippingDetails = ShippingDetails(
shipMethod = ShippingMethod.NEXT_DAY_OR_OVERNIGHT,
street = "5335 Gate Pkwy",
street2 = "5335 Gate Pkwy",
city = "Jacksonville",
state = "FL",
countryCode = "US",
zip = "32256",
),
renderType = RenderType.BOTH,
threeDS = ThreeDS(
merchantUrl = "https://merchant.com/url/path",
"deviceChannel": "BROWSER",
"messageCategory": "PAYMENT",
"authenticationPurpose": "PAYMENT_TRANSACTION"
)
)
STEP 5: Call PsCardFormController.tokenize when the customer clicks on the Pay button. This returns a single-use payment handle token (the token representing the card data entered by the customer is stored in the Payment API). Single-use tokens are valid for only 5 minutes.
Paysafe Android SDK provides multiple methods for handling async flows when tokenizing:
cardController?.tokenize(this, cardTokenizeOptions, object : PSResultCallback<String> {
override fun onSuccess(value: String) {
// Send paymentHandle to backend and make /payments endpoint to complete the payments flow
}
override fun onFailure(exception: Exception) {
// do something with exception
}
})
val paymentHandleResult = cardController?.tokenize(cardTokenizeOptions)
when (paymentHandleResult) {
is PSResult.Success -> TODO("Complete payment with payment handle token value: ${paymentHandleResult.value}")
is PSResult.Failure -> TODO("Do Something with Exception: ${paymentHandleResult.exception}")
}
lifecycleScope.launchCatching(Dispatchers.IO) {
val paymentHandleToken = cardController?.tokenize(cardTokenizeOptions)?.value()
withContext(Dispatchers.Main) {
// handle paymentHandle
print(paymentHandleToken)
}
}.onFailure {
// handle exception
print(it)
}
STEP 6: Complete payment with the PaymentHandleToken.
Send the payment handle token received from PSCardFormController.tokenize() to your merchant server, where you can use the standard Payments API payment endpoint to process the payment.
curl -location -request POST 'https://api.test.paysafe.com/paymenthub/v1/payments' \
- u apiKeyId:apiKeyPassword
}'
- header 'Content-Type: application/json' \
- data-raw '{
"merchantRefNum": "{{paymentHandleToken}}",
"amount": 5000,
"currencyCode": "USD",
"settleWithAuth": true,
"paymentHandleToken": "CARDS PAYMENT TOKEN RECEIVED FROM CLIENT BROWSER"
}'
Test the integration
By this point you should have a basic card integration that collects card details and makes a payment.
There are several test cards you can use in test mode to make sure this integration is ready. Use them with any CVC, postal code, and future expiration date.
Card number | Description |
---|---|
4835641100110000 | Succeeds and immediately processes the payment. |
5100400000000000 | Requires authentication. Paysafe will trigger a modal asking for the customer to authenticate. |
5573560100022200 | Always fails with a decline code of insufficient_funds. |
For the full list of test cards, see our guide on testing.
Enhance the card form (hosted fields)
To enhance the card form, you can:
- customize the appearance of the hosted fields using the various styling options provided by the SDK.
- utilize the SDK to subscribe to events within the hosted fields that relate to customer actions, such as entering an invalid card number or selecting a field (focus).