This content was deleted by the author. You can see it from Blockchain History logs.

Integrating Paystack Api for payment in Android

Repository

https://github.com/Godwyyn/Paystack

What Will I Learn?

In this tutorial, you will learn the following:

  • How to integrate Paystack Api to Android
  • How to set Input validator
  • XML layout design

Requirements

For this tutorial, you will need the following:

  • A laptop with any operating system such as Windows OS, Mac OSX and Linux
  • Android studio
  • Knowledge of Java, XML

Difficulty

Intermediate

Tutorial Contents

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.

Step 1: Create a Paystack Account/Overview

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.

Step 2: Paystack SDK setup

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" />

Step 3: Setup sample charge card backend

  • In this tutorial you will need to deploy a sample backend to use in charging the card. You can click on the here to setup with heroku.
  • Copy the endpoints from the deployed backend to your MainActivity.java file.
  • Add your public key to your MainActivity.java file
   String backend_url = "https://carwashh.herokuapp.com";
    String paystack_public_key = "pk_test_6323b07017e47c659f3d9976e6503a938164504e";

Step 4: Initialize PaystackSdk and UI

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);

Step 5: Validate Input

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");
        }
    }

Step 6: Charge card

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");
        }
    }

Step 7: Fetch Access Code from Server

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;
        }
    }

Step 8: Verify Server

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.

User Interface

  • PaywithPaystack Activity
<?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>
  • Main Activity

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;
        }
    }
}

Testing

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 

Heres 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.

Proof of Work Done

The full code of the tutorial is found in the githup link below.
https://github.com/Godwyyn/Paystack