14

Android Integrating PayTM Payment Gateway - ECommerce App

 2 years ago
source link: https://www.androidhive.info/2019/02/android-integrating-paytm-payment-gateway-ecommerce-app/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Android Integrating PayTM Payment Gateway

PayTM is the largest mobile commerce platform and digital wallet company. It has large user base of around 300 million and average transactions of 5 million per day. All digital payments can be made through paytm like mobile recharges, electricity bills, credit cards etc. Almost everything under the earth is covered in paytm 🙂 Paytm is fast, reliable and more accessible payment solution.

PayTM also provides payment gateway solution that can be integrated into web and mobile apps with UPI, Debit/ Credit Card, NetBanking and PayTM Wallet payment options.

DEMO

Today, in this article we are going to see how to integrate the PayTM gateway in a simple (bit complex actually 😛 ) e-commerce app. As writing an e-commerce app needs a bit of coding and architecture, everything can’t be covered in a single article. So, I have prepared a sample project (both web and mobile) and provided the code on Github. I also tried making it simple as much as possible so that every beginner can understand it.

1. Mart9 e-Commerce App

The example app I have created is very minimal with very limited screens. It has database (Realm), network layer (Retrofit) and payment options.

  • Login, Register screens to login or create a new user.
  • Home – To list down the available products along with name, thumbnail and price.
  • Cart – A BottomSheet view to maintain the cart items.
  • Payment screen – To make the necessary calls to backend server before redirecting user to payment gateway.
  • Transactions – To show the list of transactions made by a user.

Here are the screenshots from the app.

2. Overview of PayTM Payment Lifecycle

Completing the checkout involves number of calls between app, backend and PayTM server. Below is lifecycle of a transaction from initiation to payment completion.

1. Preparing Order: Once customer selects the items, the cart items will be sent to backend server. This will insert a new order row or update existing row in db and generate unique Order ID. This order ID has to be unique each time user redirected to PayTM payment screen, otherwise PayTM throws duplicate order id error.

2. Order Id & Checksum: The next step is to generate checksum considering the order ID. The checksum has to be generated considering the mandatory fields and Merchant ID.

3. Redirecting to PayTM: Once the checksum is generated, user will be redirected to PayTM payment screen showing multiple payment options like Debit / Credit card, Wallet, Net Banking etc., In this step, if the generated checksum is wrong, user will redirected back to app with error status.

4. Bank Transaction: User completes the payment by choosing the options provided. Here PayTM takes care of communicating with bank and completing the payment.

5. Verifying transaction: Once the transaction is completed, it has to be verified on backend also to avoid fraudulent transactions. Here the backend server makes CURL request to PayTM server to verify the transaction status.

6. Order status: The transaction status is received from backend and appropriate order status is shown to user.

The below diagram illustrates the communication flow between each party. Here you can see the transaction lifecycle in detailed manner.

3. Integrating PayTM gateway

In real life scenario, building an e-commerce app needs proper architecture and taking security precautions. This article aims to explain the flow as simple as possible. So I have built what is necessary to complete the payment gateway integration. Integrating PayTM (or any other gateway) involves the below steps.

3.1. Registering with PayTM and obtain necessary API keys.
3.2 Building backend app with necessary database schema and REST API. Integrating the PayTM server SDK.
3.3 Integrating PayTM mobile SDK in android / iOS app.

3.1 PayTM SandBox – Test API Details

To get started with PayTM, you can register a new account and get a test account to tryout the payment gateway. Follow the below steps to get your sandbox credentials.

1. Goto PayTM developer website and proceed with necessary steps to create a new account / login into existing account.

2. Once registered, navigate to API Keys in left navigation panel.

3. You can notice API details for Test and Production. Once the app is tested in sandbox, you can approach PayTM team to make your app live.

3.2 Building the backend – REST API

I have chosen Laravel (PHP) framework to build the backend module. In this, I have written necessary REST API required for this app. The admin panel is not built in this project.

This backend app / REST is already live and publicly available to tryout.

Base Url: https://demo.androidhive.info/paytm/public/api/

Postman dump: https://www.getpostman.com/collections/8b2e7763a8b7e0673918

Header Field Value Description

Authorization Bearer A492Kdleo3d83ba21699… Use the token received in /login or /register call

Content-Type application/json

Endpoint Method Description

/appConfig GET PayTM app config like Merchant ID and app environment (dev / production)

/register POST Registering a new user. This returns auth token needed to make further calls.

/login POST Login of an existing user. This returns auth token needed to make further calls.

/products GET Fetching all products along with name, thumbnail and price.

/prepareOrder POST Preparing a new order. This takes list of cart items and gives the unique Order ID that needs to be sent to PayTM

/getChecksum POST Generates the checksum needed while redirecting to PayTM payment screen.

/transactionStatus POST Verifies the transaction status once the payment is done. This involves our backend server making call to PayTM server and verifies the transaction

/transactions GET List of transactions made by a user

/orders/{id} GET Complete details of a single order including the total amount and list of items ordered

3.3 PayTM Android SDK Integration

Unlike my earlier tutorials, I am focusing only on important things in this article. The code of complete app can be found on Github page. Here, I am particularly interested in PayTMActivity.java as it has the core components of PayTM module.

The user will redirected to PayTM activity once the cart items are selected and clicked on Pay. This activity communicates with backend server multiple times to fulfil the order. I have written detailed comments under each method used.

Step 1: The list of cart items sent to server to create a new order. Here we call /prepareOrder endpoint to generate the unique order ID.

Step 2: Once the order ID is received, next we call /getChecksum by passing the Order ID along with other params to generate the checksum hash.

Step 3: Once the checksum is received, we call pgService.startPaymentTransaction() to redirect user to PayTM payment screen.

Step 4: Once the payment is completed, /transactionStatus is called to verify the transaction on server. Here we pass the same Order ID to verify the status.

Step 5: Once the transaction status is received, user will be shown success or failed screen by calling showOrderStatus() method.

PayTMActivity.java
package info.androidhive.paytmgateway.ui.paytm;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.content.ContextCompat;
import android.view.MenuItem;
import android.view.View;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
import com.paytm.pgsdk.PaytmOrder;
import com.paytm.pgsdk.PaytmPGService;
import com.paytm.pgsdk.PaytmPaymentTransactionCallback;
import com.wang.avi.AVLoadingIndicatorView;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;
import info.androidhive.paytmgateway.BuildConfig;
import info.androidhive.paytmgateway.R;
import info.androidhive.paytmgateway.app.Constants;
import info.androidhive.paytmgateway.db.AppDatabase;
import info.androidhive.paytmgateway.db.model.CartItem;
import info.androidhive.paytmgateway.db.model.User;
import info.androidhive.paytmgateway.networking.model.AppConfig;
import info.androidhive.paytmgateway.networking.model.ChecksumResponse;
import info.androidhive.paytmgateway.networking.model.Order;
import info.androidhive.paytmgateway.networking.model.OrderItem;
import info.androidhive.paytmgateway.networking.model.PrepareOrderRequest;
import info.androidhive.paytmgateway.networking.model.PrepareOrderResponse;
import info.androidhive.paytmgateway.ui.base.BaseActivity;
import info.androidhive.paytmgateway.ui.transactions.TransactionsActivity;
import io.realm.Realm;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
import timber.log.Timber;
public class PayTMActivity extends BaseActivity {
@BindView(R.id.lbl_status)
TextView lblStatus;
@BindView(R.id.loader)
AVLoadingIndicatorView loader;
@BindView(R.id.icon_status)
ImageView iconStatus;
@BindView(R.id.status_message)
TextView statusMessage;
@BindView(R.id.title_status)
TextView responseTitle;
@BindView(R.id.btn_check_orders)
TextView btnCheckOrders;
@BindView(R.id.layout_order_placed)
LinearLayout layoutOrderPlaced;
private Realm realm;
private AppConfig appConfig;
private User user;
/**
* Steps to process order:
* 1. Make server call to prepare the order. Which will create a new order in the db
* and returns the unique Order ID
* <p>
* 2. Once the order ID is received, send the PayTM params to server to calculate the
* Checksum Hash
* <p>
* 3. Send the PayTM params along with checksum hash to PayTM gateway
* <p>
* 4. Once the payment is done, send the Order Id back to server to verify the
* transaction status
*/
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_pay_tm);
ButterKnife.bind(this);
setToolbar();
enableToolbarUpNavigation();
getSupportActionBar().setTitle(getString(R.string.title_preparing_order));
changeStatusBarColor();
init();
}
@Override
public int getLayoutId() {
return R.layout.activity_pay_tm;
}
private void init() {
realm = Realm.getDefaultInstance();
realm.where(CartItem.class).findAllAsync()
.addChangeListener(cartItems -> {
});
user = AppDatabase.getUser();
appConfig = realm.where(AppConfig.class).findFirst();
prepareOrder();
}
private void setStatus(int message) {
lblStatus.setText(message);
}
/**
* STEP 1: Sending all the cart items to server and receiving the
* unique order id that needs to be sent to PayTM
*/
private void prepareOrder() {
setStatus(R.string.msg_preparing_order);
List<CartItem> cartItems = realm.where(CartItem.class).findAll();
PrepareOrderRequest request = new PrepareOrderRequest();
List<OrderItem> orderItems = new ArrayList<>();
for (CartItem cartItem : cartItems) {
OrderItem orderItem = new OrderItem();
orderItem.productId = cartItem.product.id;
orderItem.quantity = cartItem.quantity;
orderItems.add(orderItem);
}
request.orderItems = orderItems;
getApi().prepareOrder(request).enqueue(new Callback<PrepareOrderResponse>() {
@Override
public void onResponse(Call<PrepareOrderResponse> call, Response<PrepareOrderResponse> response) {
if (!response.isSuccessful()) {
handleUnknownError();
showOrderStatus(false);
return;
}
getChecksum(response.body());
}
@Override
public void onFailure(Call<PrepareOrderResponse> call, Throwable t) {
handleError(t);
showOrderStatus(false);
}
});
}
/**
* STEP 2:
* Sending the params to server to generate the Checksum
* that needs to be sent to PayTM
*/
void getChecksum(PrepareOrderResponse response) {
setStatus(R.string.msg_fetching_checksum);
if (appConfig == null) {
Timber.e("App config is null! Can't place the order. This usually shouldn\'t happen");
// navigating user to login screen
launchLogin(PayTMActivity.this);
finish();
return;
}
Map<String, String> paramMap = preparePayTmParams(response);
Timber.d("PayTm Params: %s", paramMap);
getApi().getCheckSum(paramMap).enqueue(new Callback<ChecksumResponse>() {
@Override
public void onResponse(Call<ChecksumResponse> call, Response<ChecksumResponse> response) {
if (!response.isSuccessful()) {
Timber.e("Network call failed");
handleUnknownError();
showOrderStatus(false);
return;
}
Timber.d("Checksum Received: " + response.body().checksum);
// Add the checksum to existing params list and send them to PayTM
paramMap.put("CHECKSUMHASH", response.body().checksum);
placeOrder(paramMap);
}
@Override
public void onFailure(Call<ChecksumResponse> call, Throwable t) {
handleError(t);
showOrderStatus(false);
}
});
}
public Map<String, String> preparePayTmParams(PrepareOrderResponse response) {
Map<String, String> paramMap = new HashMap<String, String>();
paramMap.put("CALLBACK_URL", String.format(BuildConfig.PAYTM_CALLBACK_URL, response.orderGatewayId));
paramMap.put("CHANNEL_ID", appConfig.getChannel());
paramMap.put("CUST_ID", "CUSTOMER_" + user.id);
paramMap.put("INDUSTRY_TYPE_ID", appConfig.getIndustryType());
paramMap.put("MID", appConfig.getMerchantId());
paramMap.put("WEBSITE", appConfig.getWebsite());
paramMap.put("ORDER_ID", response.orderGatewayId);
paramMap.put("TXN_AMOUNT", response.amount);
return paramMap;
}
/**
* STEP 3: Redirecting to PayTM gateway with necessary params along with checksum
* This will redirect to PayTM gateway and gives us the PayTM transaction response
*/
public void placeOrder(Map<String, String> params) {
setStatus(R.string.msg_redirecting_to_paytm);
// choosing between PayTM staging and production
PaytmPGService pgService = BuildConfig.IS_PATM_STAGIN ? PaytmPGService.getStagingService() : PaytmPGService.getProductionService();
PaytmOrder Order = new PaytmOrder(params);
pgService.initialize(Order, null);
pgService.startPaymentTransaction(this, true, true,
new PaytmPaymentTransactionCallback() {
@Override
public void someUIErrorOccurred(String inErrorMessage) {
Timber.e("someUIErrorOccurred: %s", inErrorMessage);
finish();
// Some UI Error Occurred in Payment Gateway Activity.
// // This may be due to initialization of views in
// Payment Gateway Activity or may be due to //
// initialization of webview. // Error Message details
// the error occurred.
}
@Override
public void onTransactionResponse(Bundle inResponse) {
Timber.d("PayTM Transaction Response: %s", inResponse.toString());
String orderId = inResponse.getString("ORDERID");
verifyTransactionStatus(orderId);
}
@Override
public void networkNotAvailable() { // If network is not
Timber.e("networkNotAvailable");
finish();
// available, then this
// method gets called.
}
@Override
public void clientAuthenticationFailed(String inErrorMessage) {
Timber.e("clientAuthenticationFailed: %s", inErrorMessage);
finish();
// This method gets called if client authentication
// failed. // Failure may be due to following reasons //
// 1. Server error or downtime. // 2. Server unable to
// generate checksum or checksum response is not in
// proper format. // 3. Server failed to authenticate
// that client. That is value of payt_STATUS is 2. //
// Error Message describes the reason for failure.
}
@Override
public void onErrorLoadingWebPage(int iniErrorCode,
String inErrorMessage, String inFailingUrl) {
Timber.e("onErrorLoadingWebPage: %d | %s | %s", iniErrorCode, inErrorMessage, inFailingUrl);
finish();
}
@Override
public void onBackPressedCancelTransaction() {
Toast.makeText(PayTMActivity.this, "Back pressed. Transaction cancelled", Toast.LENGTH_LONG).show();
finish();
}
@Override
public void onTransactionCancel(String inErrorMessage, Bundle inResponse) {
Timber.e("onTransactionCancel: %s | %s", inErrorMessage, inResponse);
finish();
}
});
}
/**
* STEP 4: Verifying the transaction status once PayTM transaction is over
* This makes server(own) -> server(PayTM) call to verify the transaction status
*/
private void verifyTransactionStatus(String orderId) {
setStatus(R.string.msg_verifying_status);
getApi().checkTransactionStatus(orderId).enqueue(new Callback<Order>() {
@Override
public void onResponse(Call<Order> call, Response<Order> response) {
if (!response.isSuccessful()) {
Timber.e("Network call failed");
handleUnknownError();
showOrderStatus(false);
return;
}
showOrderStatus(response.body().status.equalsIgnoreCase(Constants.ORDER_STATUS_COMPLETED));
}
@Override
public void onFailure(Call<Order> call, Throwable t) {
handleError(t);
showOrderStatus(false);
}
});
}
/*
* Displaying Order Status on UI. This toggles UI between success and failed cases
* */
private void showOrderStatus(boolean isSuccess) {
loader.setVisibility(View.GONE);
lblStatus.setVisibility(View.GONE);
if (isSuccess) {
iconStatus.setImageResource(R.drawable.baseline_check_black_48);
iconStatus.setColorFilter(ContextCompat.getColor(this, R.color.colorGreen));
responseTitle.setText(R.string.thank_you);
statusMessage.setText(R.string.msg_order_placed_successfully);
// as the order placed successfully, clear the cart
AppDatabase.clearCart();
} else {
iconStatus.setImageResource(R.drawable.baseline_close_black_48);
iconStatus.setColorFilter(ContextCompat.getColor(this, R.color.btn_remove_item));
responseTitle.setText(R.string.order_failed);
statusMessage.setText(R.string.msg_order_placed_failed);
}
layoutOrderPlaced.setVisibility(View.VISIBLE);
}
@OnClick(R.id.btn_check_orders)
void onOrdersClick() {
startActivity(new Intent(PayTMActivity.this, TransactionsActivity.class));
finish();
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == android.R.id.home) {
finish();
return true;
}
return super.onOptionsItemSelected(item);
}
@Override
protected void onDestroy() {
super.onDestroy();
if (realm != null) {
realm.removeAllChangeListeners();
realm.close();
}
}
}

4. Testing the App

Once you have completed the PayTM integration, you can test your app (or the app provided in this article), using the test mode credentials provided.

Mobile / username 7777777777

OTP 489871

5. References

Below are useful links you can keep handy while working on this project.
1. PayTM Android SDK documentation
2. PayTM REST API documentation
3. PayTM test mode credentials

I hope this article simplified the PayTM integration process. If you have any queries / suggestion, pls do post in the comment section below.

Happy Coding 🙂

Hi there! I am Founder at androidhive and programming enthusiast. My skills includes Android, iOS, PHP, Ruby on Rails and lot more. If you have any idea that you would want me to develop? Let’s talk: [email protected]


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK