Accept Payments on your Android App

Before you begin

You should create a free Paystack account which will give you access to your unique test key to test your integration.

Accepting payments in your Android app is easier than ever before with Paystack! With our Android SDK, you can easily start accepting payments from your customers. Integration is a two step process:

  • Initiate the transaction on your server
  • Complete the transaction on the SDK

In this tutorial, we'll be building a simple food ordering app that shows how to use Paystack Android SDK to accept payments in an android app. The app consists of two activities as shown below:

Main Activity containing the picture of a cheese burger and it's description Checkout Activity containing a form to collect card details

I grabbed the picture of the burger from Pexels. You can get it here or use a picture of a meal you'd prefer to eat. To keep my app's size lean, I opted to use the smaller version of the image.

Android Proficiency

This guide assumes you understand the fundamentals of Android development. The aim of this guide is to show you how to integrate payment in your android app via Paystack.

Project Setup

This guide was written using Android Studio 2023.2.1, Android Gradle plugin 7.4.2, and Gradle 7.5. Earlier versions might behave differently but the end result should be the same. You should also ensure you have a stable internet connection to install the project dependencies. The app code is available in Java and Kotlin.

Show me the code

If you are only interested in integrating the SDK you can skip to the Initialize Payment section.

Create Food Ordering App

Let's open up Android Studio and create a new project with the following parameters:

AttributeValue
Project TemplateEmpty activity
Application NameFood Checkout
Package namecom.yourdomain.appname
LanguageJava or Kotlin
Minimum SDKAPI 21: Android 5.0 (Lollipop)

Click the Finish button to confirm the project configuration and wait for the project dependencies to install completely. There are a couple of assets we'll be using for our application, so it's best we set them up before proceeding with other parts of the app.

Add the Burger Image

If you haven't downloaded the burger image yet, you should navigate to Pexels and download it. I used the Free Download drop-down to select a custom size (320 x 480) for my image. When the image downloads successfully, rename it to burger.jpg and drag to your application's drawable folder:

Screenshot of Android Studio with the updated burger image drawable

Update the Color File

The default colors created for us by Android Studio don't match our app's design. Open the colors.xml file in the values folder and update it with the following code:

1<?xml version="1.0" encoding="utf-8"?>
2<resources xmlns:tools="http://schemas.android.com/tools">
3 <color name="white">#FFFFFFFF</color>
4 <color name="silver_400">#CBC9C9</color>
5 <color name="azac_500">#DEB334</color>
6 <color name="cod_gray_900">#171516</color>
7 // this is used to modify the default border color of a text input
8 <color name="mtrl_textinput_default_box_stroke_color" tools:override="true">#CBC9C9</color>
9</resources>

Update the App's Theme

Since we've updated our color.xml file, our theme.xml will have a couple of errors because we have removed the previous color references it was using. Before updating our theme, we should delete the night version of the theme as we'll not be making use of it. We can then update our theme.xml file as follows:

1<resources xmlns:tools="http://schemas.android.com/tools">
2 <!-- Base application theme. -->
3 <style name="Theme.FoodCheckout" parent="Theme.MaterialComponents.DayNight.NoActionBar">
4 <!-- Primary brand color. -->
5 <item name="colorPrimary">@color/cod_gray_900</item>
6<!-- <item name="colorPrimaryVariant">@color/purple_700</item>-->
7 <item name="colorPrimaryVariant">#191919</item>
8 <item name="colorOnPrimary">@color/white</item>
9 <!-- Secondary brand color. -->
10 <item name="colorSecondary">@color/azac_500</item>
11 <item name="colorSecondaryVariant">@color/azac_500</item>
12 <item name="colorOnSecondary">@color/cod_gray_900</item>
13 <!-- Status bar color. -->
14 <item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
15 </style>
16</resources>

Create a File for all Dimensions

All text sizes, margins and dimensions used in this app were referenced from a dimens.xml file. To create one, right click the values folder, then New > Values Resource File:

Screenshot of Android Studio when creating a dimensions file

Click OK to confirm the file creation and add the following inside the newly created file:

1<?xml version="1.0" encoding="utf-8"?>
2<resources>
3 <dimen name="text_14">14sp</dimen>
4 <dimen name="text_16">16sp</dimen>
5 <dimen name="text_18">18sp</dimen>
6 <dimen name="text_24">24sp</dimen>
7 <dimen name="text_32">32sp</dimen>
8 <dimen name="dimen_8">8dp</dimen>
9 <dimen name="dimen_10">10dp</dimen>
10 <dimen name="dimen_16">16dp</dimen>
11 <dimen name="dimen_20">20dp</dimen>
12 <dimen name="dimen_24">24dp</dimen>
13 <dimen name="dimen_32">32dp</dimen>
14 <dimen name="image_height">420dp</dimen>
15</resources>

Add String Resource

Finally, let's add all the string resources we'll be using in the app. Open your strings.xml file and add the following:

1<resources>
2 <string name="app_name">Food Checkout</string>
3 <string name="meal_name">Cheese Burger</string>
4 <string name="meal_description">
5 A properly seasoned patty topped with a slice of fresh tomato, cheese and lettuce enclosed in a
6 fluffy bun. It’s accompanied with a side of fries with a sweet and spicy dip.
7 </string>
8 <!-- Hardcoding the price is for demo purpose only.
9 You shouldn't do this in prod -->
10 <string name="meal_price">Make Payment</string>
11</resources>

Implement App UI

At this point we've successfully:

  • Created the base application
  • Added all of our required assets

We can now go ahead to implement the app's main interface. We'll be modifying the MainActivity and activity_main.xml for the food details UI. Open the activity_main.xml file and add this snippet into it:

1<?xml version="1.0" encoding="utf-8"?>
2<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
3 xmlns:app="http://schemas.android.com/apk/res-auto"
4 xmlns:tools="http://schemas.android.com/tools"
5 android:layout_width="match_parent"
6 android:layout_height="match_parent"
7 android:background="@color/cod_gray_900"
8 tools:context=".MainActivity">
9
10 <ImageView
11 android:id="@+id/iv_meal_image"
12 android:layout_width="match_parent"
13 android:layout_height="@dimen/image_height"
14 android:contentDescription="@string/meal_name"
15 android:scaleType="centerCrop"
16 android:src="@drawable/burger"
17 app:layout_constraintLeft_toLeftOf="parent"
18 app:layout_constraintRight_toRightOf="parent"
19 app:layout_constraintTop_toTopOf="parent" />
20
21 <TextView
22 android:id="@+id/iv_meal_name"
23 android:layout_width="wrap_content"
24 android:layout_height="wrap_content"
25 android:layout_marginStart="20dp"
26 android:layout_marginTop="@dimen/dimen_20"
27 android:layout_marginEnd="@dimen/dimen_20"
28 android:text="@string/meal_name"
29 android:textColor="@color/azac_500"
30 android:textSize="@dimen/text_24"
31 app:layout_constraintLeft_toLeftOf="parent"
32 app:layout_constraintTop_toBottomOf="@id/iv_meal_image" />
33
34 <TextView
35 android:id="@+id/iv_meal_description"
36 android:layout_width="match_parent"
37 android:layout_height="wrap_content"
38 android:layout_marginTop="@dimen/dimen_10"
39 android:layout_marginStart="@dimen/dimen_20"
40 android:layout_marginEnd="@dimen/dimen_20"
41 android:text="@string/meal_description"
42 android:textColor="@color/white"
43 android:textSize="@dimen/text_14"
44 android:lineSpacingMultiplier="1.2"
45 app:layout_constraintLeft_toLeftOf="parent"
46 app:layout_constraintRight_toRightOf="parent"
47 app:layout_constraintTop_toBottomOf="@id/iv_meal_name" />
48
49 <Button
50 android:id="@+id/btn_checkout"
51 android:layout_width="match_parent"
52 android:layout_height="wrap_content"
53 android:layout_marginStart="@dimen/dimen_20"
54 android:layout_marginEnd="@dimen/dimen_20"
55 android:layout_marginBottom="@dimen/dimen_20"
56 android:backgroundTint="@color/azac_500"
57 android:textColor="@color/cod_gray_900"
58 android:text="@string/meal_price"
59 android:textStyle="bold"
60 app:layout_constraintBottom_toBottomOf="parent"
61 app:layout_constraintLeft_toLeftOf="parent"
62 app:layout_constraintRight_toRightOf="parent" />
63
64</androidx.constraintlayout.widget.ConstraintLayout>

This interface consists of four elements:

  • ImageView: This is used to render out sumptuous burger image
  • TextView: This is used to render the meal name and description respectively.
  • Button: This is used to trigger the payment sheet.

Initialize Payment

With the SDK, you need to start your transaction on your server and complete it on the SDK. To initialize a transaction, make a POST request on your server to the Initialize TransactionAPI endpoint:

Show Response
1curl https://api.paystack.co/transaction/initialize
2-H "Authorization: Bearer YOUR_SECRET_KEY"
3-H "Content-Type: application/json"
4-d '{ "email": "[email protected]",
5 "amount": "500000"
6 }'
7-X POST
1{
2 "status": true,
3 "message": "Authorization URL created",
4 "data": {
5 "authorization_url": "https://checkout.paystack.com/nkdks46nymizns7",
6 "access_code": "nkdks46nymizns7",
7 "reference": "nms6uvr1pl"
8 }
9}

On a successful initialization of the transaction, you get a response that contains an access_code. You need to return this access_code back to your mobile app.

Secret key safeguarding

Do not make an API request to the Initialize Transaction endpoint directly on your mobile app because it requires your secret key. Your secret key should only be used on your server where stronger security measures can be put in place.

With the access_code in place, you can now use the SDKs to complete the transaction.

Install SDK

The Android SDK provide methods and interfaces to accept payment. To use the SDK, add the paystack-ui in your app's build.gradle file:

Latest dependency version

You should check Maven Central to get the latest version before installation.

1dependencies {
2 implementation 'com.paystack.android:paystack-ui:0.0.9'
3}

Since we've made some changes to our build.gradle file, we'll need to sync the project to install the required dependency.

Integrate Paystack SDK

We'll now flesh out the MainActivity to handle the completion of the previously initiated payment. This is a 3-step process:

  1. Initialize the SDK
  2. Implement a Callback
  3. Create the PaymentSheet

Initialize the SDK

The SDK uses a builder pattern to handle foundational configuration before the UI can be loaded. This is where you add your public key as shown below:

1Paystack.builder()
2 .setPublicKey("pk_domain_xxxxxxxx")
3 .setLoggingEnabled(true)
4 .build()

Implement a Callback

A transaction can be in different states depending on the user's action or processor's response. In order to properly reconcile a transaction, you need to set up a callback:

1private fun paymentComplete(paymentSheetResult: PaymentSheetResult) {
2 val message = when (paymentSheetResult) {
3 PaymentSheetResult.Cancelled -> "Cancelled"
4 is PaymentSheetResult.Failed -> {
5 Log.e("Something went wrong", paymentSheetResult.error.message.orEmpty(), paymentSheetResult.error)
6 paymentSheetResult.error.message ?: "Failed"
7 }
8
9 is PaymentSheetResult.Completed -> {
10 // Returns the transaction reference PaymentCompletionDetails(reference={TransactionRef})
11 Log.d("Payment successful", paymentSheetResult.paymentCompletionDetails.toString())
12 "Successful"
13 }
14 }
15
16 Toast.makeText(this, "Payment $message", Toast.LENGTH_SHORT).show()
17}

Create the PaymentSheet

The PaymentSheet is needed to launch the user interface in the app's Activity. It takes two arguments, the activity it’ll be rendered in and the callback function that’s called when the transaction completes:

1private lateinit var paymentSheet: PaymentSheet
2
3override fun onCreate(savedInstanceState: Bundle?) {
4 super.onCreate(savedInstanceState)
5 setContentView(R.layout.activity_main)
6
7 // library initialization code snippets and others go here
8
9 paymentSheet = PaymentSheet(this, ::paymentComplete)
10
11 // more snippet
12}

The PaymentSheet comes with a launch method that allows you trigger the display of the component. The launch method takes the access_code from the previously initialized transaction.

1fun makePayment() {
2 // Pass access_code from transaction initialize call on the server
3 paymentSheet.launch("br6cgmvflhn3qtd")
4}

The makePayment method can be added in the click listener of the button to trigger the PaymentSheet UI.

Test Integration

We can now test our app to ensure it works as expected. We have a couple of test cards that can be used to simulate different payment scenarios.

Confirm Payment

Note

You should always confirm a payment before delivering services to your customer.

This section assumes that you have a backend server that your mobile app rides on. You need to listen for events using your webhook URL.

Webhooks allow Paystack to send you events that occur on your integration in real-time. When a payment is successful, we'll send a charge.success event to your webhook URL that will contain information about the successful transaction. It's recommended that you listen for this event to confirm a payment. This method of transaction confirmation is more resilient when a customer has connectivity issues and when there's a delay from payment processors.

Alternatively, you can send the transaction reference from the app to your backend server. Then, from your server, you can call our verify transactionAPI endpoint with the transaction reference. The response body will contain a status field that indicates whether or not the transaction was successful.

Recurring Payments

When a transaction is confirmed, the response body will contain an authorization object:

1{
2 ...
3 "data": {
4 ...
5 "authorization": {
6 "authorization_code":"AUTH_8dfhjjdt",
7 "card_type":"visa",
8 "last4":"1381",
9 "exp_month":"08",
10 "exp_year":"2018",
11 "bin":"412345",
12 "bank":"TEST BANK",
13 "channel":"card",
14 "signature": "SIG_idyuhgd87dUYSHO92D",
15 "reusable":true,
16 "country_code":"NG"
17 },
18 ...
19 }
20}

When an authorization has a "reusable": true value, the customer's card can be used for recurring payment. You can store the authorization_code and use it for subsequent payment.

Conclusion

The source code for this guide can be found in the FoodCheckout repository. The main branch contains the Java code while the kotlin branch contains the Kotlin code. Cheers!