https://github.com/Godwyyn/Paystack
In this tutorial, you will learn the following:
For this tutorial, you will need the following:
Intermediate
The Paystack API gives you access to a simple, much easy and flexible means of accepting payment in android. It strives to be RESTful and is organized around the main resources you would be interacting with. This tutorial will cover how to fully integrate the paystack Api into your android application.How to make Api calls, Authenticate your API calls by including the secret key in the Authorization header of every request you make, validate input details and as well provide the test card and test account accepted by paystack.
Before you can start integrating Paystack, you will need a Paystack account. Create a free account by clicking here
Paystack provide both public and secret keys. Public keys are meant to be used from your front-end when integrating using Paystack Inline and in our Mobile SDKs only. By design, public keys cannot modify any part of your account besides initiating transactions to you. The secret keys however, are to be kept secret. If for any reason you believe your secret key has been compromised or you wish to reset them, you can do so from the dashboard.
To begin this tutorial, you need to setup up your Android studio by downloading from the link provided above and installing, or you can just run the IDE if you have it on your computer. After installation, create a new Android studio project or open an existing project where you want to integrate the Paystack payment.
After setting up your paystack account and probably taking a look at the dashboard, the next thing is to go your Android studio project and set up your dependency.
implementation 'co.paystack.android:paystack:3.10'
}
Add internet permission to your app AndroidManifest
permission
<uses-permission android:name="android.permission.INTERNET" />
String backend_url = "https://carwashh.herokuapp.com";
String paystack_public_key = "pk_test_6323b07017e47c659f3d9976e6503a938164504e";
Initialize the paystack sdk and also try to catch any error in the case where the server Url or the public is null.
PaystackSdk.initialize(getApplicationContext());
PaystackSdk.setPublicKey(paystack_public_key);
if (BuildConfig.DEBUG && (backend_url.equals(""))) {
throw new AssertionError("Please set a backend url before running the sample");
}
if (BuildConfig.DEBUG && (paystack_public_key.equals(""))) {
throw new AssertionError("Please set a public key before running the sample");
}
mEditCardNum = (EditText) findViewById(R.id.edit_card_number);
mEditCVC = (EditText) findViewById(R.id.edit_cvc);
mEditExpiryMonth = (EditText) findViewById(R.id.edit_expiry_month);
mEditExpiryYear = (EditText) findViewById(R.id.edit_expiry_year);
Button mButtonPerformTransaction = (Button) findViewById(R.id.button_perform_transaction);
mTextError = (TextView) findViewById(R.id.textview_error);
mTextBackendMessage = (TextView) findViewById(R.id.textview_backend_message);
mTextReference = (TextView) findViewById(R.id.textview_reference);
Create a method to Validate the card input to ensure that the details entered by the user is valid. The validateCardForm method does that.
private void validateCardForm() {
//validate fields
String cardNum = mEditCardNum.getText().toString().trim();
if (isEmpty(cardNum)) {
mEditCardNum.setError("Empty card number");
return;
}
//build card object with ONLY the number, update the other fields later
card = new Card.Builder(cardNum, 0, 0, "").build();
if (!card.validNumber()) {
mEditCardNum.setError("Invalid card number");
return;
}
//validate cvc
String cvc = mEditCVC.getText().toString().trim();
if (isEmpty(cvc)) {
mEditCVC.setError("Empty cvc");
return;
}
//update the cvc field of the card
card.setCvc(cvc);
//check that it's valid
if (!card.validCVC()) {
mEditCVC.setError("Invalid cvc");
return;
}
//validate expiry month;
String sMonth = mEditExpiryMonth.getText().toString().trim();
int month = -1;
try {
month = Integer.parseInt(sMonth);
} catch (Exception ignored) {
}
if (month < 1) {
mEditExpiryMonth.setError("Invalid month");
return;
}
card.setExpiryMonth(month);
String sYear = mEditExpiryYear.getText().toString().trim();
int year = -1;
try {
year = Integer.parseInt(sYear);
} catch (Exception ignored) {
}
if (year < 1) {
mEditExpiryYear.setError("invalid year");
return;
}
card.setExpiryYear(year);
//validate expiry
if (!card.validExpiryDate()) {
mEditExpiryMonth.setError("Invalid expiry");
mEditExpiryYear.setError("Invalid expiry");
}
}
The next thing after validating the card details inputted is to charge the card. This means to initiate a fresh transaction on Paystack and this is done by calling the chargeCard method. Code is below:
private void chargeCard() {
transaction = null;
PaystackSdk.chargeCard(PayWithPaystack.this, charge, new Paystack.TransactionCallback() {
// This is called only after transaction is successful
@Override
public void onSuccess(Transaction transaction) {
dismissDialog();
PayWithPaystack.this.transaction = transaction;
mTextError.setText(" ");
Toast.makeText(PayWithPaystack.this, transaction.getReference(), Toast.LENGTH_LONG).show();
updateTextViews();
new verifyOnServer().execute(transaction.getReference());
}
// This is called only before requesting OTP
// Save reference so you may send to server if
// error occurs with OTP
// No need to dismiss dialog
@Override
public void beforeValidate(Transaction transaction) {
PayWithPaystack.this.transaction = transaction;
Toast.makeText(PayWithPaystack.this, transaction.getReference(), Toast.LENGTH_LONG).show();
updateTextViews();
}
@Override
public void onError(Throwable error, Transaction transaction) {
// If an access code has expired, simply ask your server for a new one
// and restart the charge instead of displaying error
PayWithPaystack.this.transaction = transaction;
if (error instanceof ExpiredAccessCodeException) {
PayWithPaystack.this.startAFreshCharge();
PayWithPaystack.this.chargeCard();
return;
}
dismissDialog();
if (transaction.getReference() != null) {
Toast.makeText(PayWithPaystack.this, transaction.getReference() + " concluded with error: " + error.getMessage(), Toast.LENGTH_LONG).show();
mTextError.setText(String.format("%s concluded with error: %s %s", transaction.getReference(), error.getClass().getSimpleName(), error.getMessage()));
new verifyOnServer().execute(transaction.getReference());
} else {
Toast.makeText(PayWithPaystack.this, error.getMessage(), Toast.LENGTH_LONG).show();
mTextError.setText(String.format("Error: %s %s", error.getClass().getSimpleName(), error.getMessage()));
}
updateTextViews();
}
});
}
private void dismissDialog() {
if ((dialog != null) && dialog.isShowing()) {
dialog.dismiss();
}
}
private void updateTextViews() {
if (transaction.getReference() != null) {
mTextReference.setText(String.format("Reference: %s", transaction.getReference()));
} else {
mTextReference.setText("No transaction");
}
}
After charging the card, this method fetches the access code from the sever. It returns the payment reference.
private class fetchAccessCodeFromServer extends AsyncTask<String, Void, String> {
private String error;
@Override
protected void onPostExecute(String result) {
super.onPostExecute(result);
if (result != null) {
charge.setAccessCode(result);
charge.setCard(card);
chargeCard();
} else {
PayWithPaystack.this.mTextBackendMessage.setText(String.format("There was a problem getting a new access code form the backend: %s", error));
dismissDialog();
}
}
@Override
protected String doInBackground(String... ac_url) {
try {
URL url = new URL(ac_url[0]);
BufferedReader in = new BufferedReader(
new InputStreamReader(
url.openStream()));
String inputLine;
inputLine = in.readLine();
in.close();
return inputLine;
} catch (Exception e) {
error = e.getClass().getSimpleName() + ": " + e.getMessage();
}
return null;
}
}
This class handles the gateway response, Checking if the server ia responding.
private class verifyOnServer extends AsyncTask<String, Void, String> {
private String reference;
private String error;
@Override
protected void onPostExecute(String result) {
super.onPostExecute(result);
if (result != null) {
PayWithPaystack.this.mTextBackendMessage.setText(String.format("Gateway response: %s", result));
} else {
PayWithPaystack.this.mTextBackendMessage.setText(String.format("There was a problem verifying %s on the backend: %s ", this.reference, error));
dismissDialog();
}
}
@Override
protected String doInBackground(String... reference) {
try {
this.reference = reference[0];
URL url = new URL(backend_url + "/verify/" + this.reference);
BufferedReader in = new BufferedReader(
new InputStreamReader(
url.openStream()));
String inputLine;
inputLine = in.readLine();
in.close();
return inputLine;
} catch (Exception e) {
error = e.getClass().getSimpleName() + ": " + e.getMessage();
}
return null;
}
}
If all procedures and steps are followed, a payment reference will be returned meaning the transaction is successful.
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".PayWithPaystack">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<RelativeLayout
android:id="@+id/layout_custom_form"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#ffffff"
android:gravity="center_vertical"
android:minHeight="150dp">
<LinearLayout
android:id="@+id/layout_card_num"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:gravity="center_horizontal"
android:orientation="horizontal">
<EditText
android:id="@+id/edit_card_number"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:hint="@string/card_number"
android:inputType="number"
android:minEms="8"
android:text="" />
<EditText
android:id="@+id/edit_cvc"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:hint="@string/cvv"
android:inputType="number"
android:maxLength="4"
android:minEms="4"
android:text="" />
</LinearLayout>
<View
android:id="@+id/divider_horizontal"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_centerHorizontal="true" />
<EditText
android:id="@+id/edit_expiry_month"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/layout_card_num"
android:layout_toLeftOf="@id/divider_horizontal"
android:gravity="right"
android:hint="@string/mm"
android:inputType="number"
android:maxEms="3"
android:maxLength="2"
android:text="" />
<EditText
android:id="@+id/edit_expiry_year"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/layout_card_num"
android:layout_toRightOf="@id/divider_horizontal"
android:gravity="left"
android:hint="@string/yyyy"
android:inputType="number"
android:maxEms="4"
android:maxLength="4"
android:text="" />
<Button
android:id="@+id/button_perform_transaction"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/edit_expiry_year"
android:layout_centerHorizontal="true"
android:padding="8dp"
android:text="Charge card" />
</RelativeLayout>
<RelativeLayout
android:id="@+id/layout_transaction_response"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/layout_custom_form"
android:background="#1C3A4B"
android:gravity="center_vertical"
android:minHeight="70dp">
<TextView
android:id="@+id/textview_reference"
style="@style/TextAppearance.AppCompat.Title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="@dimen/activity_horizontal_margin"
android:padding="8dp"
android:text="@string/no_transaction_yet"
android:textColor="#ffffff" />
</RelativeLayout>
<RelativeLayout
android:id="@+id/layout_token_response"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/layout_transaction_response"
android:background="#1C3A4B"
android:gravity="center_vertical"
android:minHeight="70dp">
<TextView
android:id="@+id/textview_error"
style="@style/TextAppearance.AppCompat.Title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="@dimen/activity_horizontal_margin"
android:padding="8dp"
android:text=" "
android:textColor="#ffffff" />
<TextView
android:id="@+id/textview_backend_message"
style="@style/TextAppearance.AppCompat.Title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/textview_error"
android:layout_margin="@dimen/activity_vertical_margin"
android:padding="8dp"
android:text=" "
android:textColor="#ffffff" />
</RelativeLayout>
</RelativeLayout>
</ScrollView>
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
Button payWithPaystack = (Button) findViewById(R.id.button_pay_with_paystack);
payWithPaystack.setOnClickListener(this);
}
/**
* Handle click listener
* @param view view that was clicked
*/
@Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.button_pay_with_paystack:
//start actiity
startActivity(new Intent(MainActivity.this, PayWithPaystackActivity.class));
break;
}
}
}
Here’s are some cards that work on Paystack test environment.
No validation
Card Number: 408 408 408 408 408 1
Expiry Date: any date in the future
CVV: 408
PIN+OTP validation
(nonreusable)
Card Number: 5060 6666 6666 6666 666 (Verve)
Expiry Date: any date in the future
CVV: 123
PIN: 1234
OTP: 123456
PIN only validation
(reusable)
Card Number: 5078 5078 5078 5078 12 (Verve)
Expiry Date: any date in the future
CVV: 081
PIN: 1111
PIN+PHONE+OTP validation
Card Number: 5078 5078 5078 5078 4 (Verve)
Expiry Date: any date in the future
CVV: 884
PIN: 0000
Phone: If less than 10 numeric characters, Transaction will fail.
OTP: 123456
Bank authorization Simulation
Card Number: 408 408 0000000 409
Expiry Date: any date in the future
CVV: 000
Here’s a bank account that works on Paystack test environment.
Bank: Zenith Bank
Account number: 0000000000
Birthday: any date whatsoever
All OTP: 123456
I hope this helps, thank you.
The full code of the tutorial is found in the githup link below.
https://github.com/Godwyyn/Paystack