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:
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:
Attribute | Value |
---|---|
Project Template | Empty activity |
Application Name | Food Checkout |
Package name | com.yourdomain.appname |
Language | Java or Kotlin |
Minimum SDK | API 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:
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 input8 <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
:
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 a6 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">910 <ImageView11 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" />2021 <TextView22 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" />3334 <TextView35 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" />4849 <Button50 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" />6364</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:
1curl https://api.paystack.co/transaction/initialize2-H "Authorization: Bearer YOUR_SECRET_KEY"3-H "Content-Type: application/json"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.
- build.gradle
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:
- Initialize the SDK
- Implement a Callback
- 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:
- Kotlin
- Java
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:
- Kotlin
- Java
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 }89 is PaymentSheetResult.Completed -> {10 // Returns the transaction reference PaymentCompletionDetails(reference={TransactionRef})11 Log.d("Payment successful", paymentSheetResult.paymentCompletionDetails.toString())12 "Successful"13 }14 }1516 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:
- Kotlin
- Java
1private lateinit var paymentSheet: PaymentSheet23override fun onCreate(savedInstanceState: Bundle?) {4 super.onCreate(savedInstanceState)5 setContentView(R.layout.activity_main)67 // library initialization code snippets and others go here89 paymentSheet = PaymentSheet(this, ::paymentComplete)1011 // more snippet12}
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.
- Kotlin
- Java
1fun makePayment() {2 // Pass access_code from transaction initialize call on the server3 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!