search

All-in-One SDK Integration in Flutter App (Deprecated)

This page has been deprecated. Please refer to the updated document at https://developer.paytm.com/docs/all-in-one-sdk/hybrid-apps/flutter/

 

To merchants who have built their app on Flutter platform, Paytm provides a bridge to conveniently integrate All-in-One SDK. On this page, we will highlight the steps required to integrate All-in-One SDK with Flutter platform for your app. This platform helps you to build a seamless and responsive checkout experience for your application.


This integration will support the following flows:

  • App Invoke Flow: In case the Paytm app is installed, it will be launched to complete the transaction and give the response back to your app.
  • Redirection Flow: In case the Paytm app is not installed, All-in-One SDK will open a web-view to process transaction and give the response back to your app.

Overview of payment processing in Flutter App

  1. On your mobile app, the user adds goods/services into the shopping/order cart and proceeds to checkout. You call the Initiate Transaction API from your backend to generate transaction token.
    Within the Initiate Transaction API, you also get an option to include single or multiple payment sources for the users, thus, allowing you to make your own payment page with multiple payment sources.

  2. Launch the Hybrid app bridge to invoke Paytm All-in-One SDK with the transaction token received in step 1.

  3. If Paytm app is installed on user's phone, the payment will be completed on Paytm app using the user's saved credentials else transaction will be processed via web view within the All-in-One SDK(Paytm hosted redirection flow).

  4. Paytm processes the transaction with the user’s bank and returns the transaction response to your app.

  5. You call the Transaction Status API to verify the transaction response.

  6. Notify the payment status to the user and proceed with the order/service fulfilment.

Pre-requisites

  1. Create an account on Paytm as a merchant. Click how to create an account.

  2. Get the merchant id and merchant key for the integration environment after creating the account.

  3. Go through the checksum logic to understand how to generate and validate the checksum.

  4. Get the staging android or iOS Paytm app for integration testing on the merchant staging environment.

  5. Go through All-in-One SDK documentation before proceeding with integration.

  6. Call the Initiate Transaction API from your backend to generate Transaction Token.

Follow the steps below to integrate All-in-One SDK in your flutter app:

 

Android Project of Flutter

  1. Add the line below to ‘repositories’ section of your project-level build.gradle file.

    maven {
       url "https://artifactory.paytm.in/libs-release-local"
    }
    
  2. Add the line below to ‘dependencies’ section of your App build.gradle.

    implementation "com.paytm.appinvokesdk:appinvokesdk:1.5.3"
  3. Add AllInOneSDKPlugin class in your project (see Appendix at the end of this document).

  4. In the pre-existing MainApplication class, make changes as shared in doc (see Appendix).

  5. Update setResult method in AllInOneSDKPlugin class as per your response requirement in the flutter app.

  6. Call startTransaction method from your Flutter App to invoke Paytm module.

For further queries visit - https://developer.paytm.com/docs/all-in-one-sdk/.

Note: Please change minSDKversion to ‘18’ in your project level build.gradle file.

Flutter Project

  1. Import services.dart package.
    import 'package:flutter/services.dart';
  2. Using MethodChannel create a link to AllInOneSDKPlugin of Android project.
    var CHANNEL = "samples.flutter.dev/allInOne"; //CHANNEL as set in MainActivity of android project. 
    static const platform = const MethodChannel('CHANNEL');
    
  3. Start the process by calling platform.invokeMethod with the appropriate arguments.
    var arguments = <String, dynamic>{
        "mid": mid,
        "orderId": orderId,
        "amount": amount,
        "txnToken": txnToken,
        "callbackUrl": callbackUrl, //send this if custom callback url is required
        "isStaging": isStaging
    };
    try {
        var result = await platform.invokeMethod("startTransaction", arguments);
        print(result.toString());
    } catch (err) {
        print(err.message);
    }
    
    Attributes Description Mandatory

    orderid

    String(50)

    Unique reference ID for a transaction which is generated by a merchant. Special characters allowed in Order ID are: "@" "-" "_" ".". Yes

    mid

    String(20)

    Unique identifier which is a part of your account credentials and is provided to every merchant by Paytm.It is different on staging and production environment. Yes

    txnToken

    String

    Transaction token received in response to the Initiate Transaction API request (Note - pass same order id in sdk which was used for initiateTransaction). Yes

    amount

    String

    Amount in INR payable by the customer. It should not include any separator like (",") and must contain digits up to two decimal points. Yes

    callbackurl

    String(255)

    On completion of transaction, Paytm Payment Gateway will send the response on this URL. This can be a static response URL as mentioned below:
    • Staging Environment: "https://securegw-stage.paytm.in/theia/paytmCallback?ORDER_ID=<order_id>"
    • Production Environment: "https://securegw.paytm.in/theia/paytmCallback?ORDER_ID=<order_id>"
    Yes

    IsStaging

    Boolean

    IsStaging is to define staging or production server (True for staging and False for production) Yes
  4. Callback will be received in the above “result” as below string in case of transaction success or failure else “error will be thrown” which will have a string message like onBackedPressed, networkError etc. You can handle this as per your requirement in the AllInOneSDKPlugin.setResult method which is in your flutter android project.
    1. In the case of App Invoke flow, the response will be received in onActivityResult.

    2. In the case of Redirection flow (WebView), the response will be received in PaytmPaymentTransactionCallback interface.

      Sample Response

      Bundle[
          {
              STATUS=TXN_SUCCESS, 
              ORDERID="Order Id", 
              CHARGEAMOUNT=0.00, 
              TXNAMOUNT=1.00, 
              TXNDATE=2020-07-21 19:00:05.0, 
              MID="Merchant Id", 
              TXNID="Transaction Value", 
              RESPCODE=01, 
              PAYMENTMODE=UPI, 
              BANKTXNID="Bank transaction Id", 
              CURRENCY=INR, 
              GATEWAYNAME=ICICI, 
              RESPMSG=Txn Success
          }
      ]
  5. Verifying Payment

    1. You should validate the transaction response via a server-side request using the Transaction Status API. This API requires checksumhash in request and response. You must verify the Order ID and Amount with your data. The status should be treated as the final status of the transaction in all cases.

    2. Paytm provides payment response on both Callback URL and Webhook URL. Please refer to the sample response for different payment sources here.

 

Appendix

MainActivity.kt

package
import android.content.Intent
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel
 
class MainActivity: FlutterActivity() {
   private val CHANNEL = "samples.flutter.dev/allInOne" //Change as per project
   private lateinit var plugin : AllInOneSDKPlugin
 
   override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
       super.configureFlutterEngine(flutterEngine)
       MethodChannel(flutterEngine.dartExecutor.binaryMessenger,CHANNEL).setMethodCallHandler { call, result ->
           plugin = AllInOneSDKPlugin(this,call,result)
       }
    }
 
   override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
       super.onActivityResult(requestCode, resultCode, data)
       plugin.onActivityResult(requestCode,resultCode,data)
    }
}

AllInOneSDKPlugin.kt

package 
import android.app.Activity
import android.content.Intent
import android.os.Bundle
import android.widget.Toast
import com.paytm.pgsdk.PaytmOrder
import com.paytm.pgsdk.PaytmPaymentTransactionCallback
import com.paytm.pgsdk.TransactionManager
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
 
class AllInOneSDKPlugin(var activity: Activity, var call: MethodCall, var result: MethodChannel.Result) {
    private val REQ_CODE = 0
    init {
            if (call.method == "startTransaction") {
                startTransaction()
            }
    }
    private fun startTransaction() {
        val arg = call.arguments as Map<*, *>?
        if (arg != null) {
            val mid = arg["mid"] as String?
            val orderId = arg["orderId"] as String?
            val amount = arg["amount"] as String?
            val txnToken = arg["txnToken"] as String?
            val callbackUrl = arg["callbackUrl"] as String?
            val isStaging = arg["isStaging"] as Boolean
            if (mid == null || orderId == null || amount == null || mid.isEmpty() || orderId.isEmpty() || amount.isEmpty()) {
                showToast("Please enter all field")
                return
            }
            if (txnToken == null || txnToken.isEmpty()) {
                showToast("Token error")
                return
            }
            initiateTransaction(mid, orderId, amount, txnToken, callbackUrl, isStaging)
        } else {
            showToast("Please send arguments")
        }
    }
    private fun initiateTransaction(mid: String, orderId: String, amount: String, txnToken: String, callbackUrl: String?, isStaging: Boolean) {
        var host = "https://securegw.paytm.in/"
        if (isStaging) {
            host = "https://securegw-stage.paytm.in/"
        }
        val callback = if (callbackUrl == null || callbackUrl.trim().isEmpty()) {
            host + "theia/paytmCallback?ORDER_ID=" + orderId
        } else {
            callbackUrl
        }
        val paytmOrder = PaytmOrder(orderId, mid, txnToken, amount, callback)
        val transactionManager = TransactionManager(paytmOrder, object : PaytmPaymentTransactionCallback {
            override fun onTransactionResponse(bundle: Bundle) {
        //Return in both cases if transaction is success or failure
                setResult("Payment Transaction response $bundle", true)
            }
            override fun networkNotAvailable() {
                setResult("networkNotAvailable", false)
            }
            override fun onErrorProceed(s: String) {
                setResult(s, false)
            }
            override fun clientAuthenticationFailed(s: String) {
                setResult(s, false)
            }
            override fun someUIErrorOccurred(s: String) {
                setResult(s, false)
            }
            override fun onErrorLoadingWebPage(iniErrorCode: Int, inErrorMessage: String, inFailingUrl: String) {
                setResult(inErrorMessage, false)
            }
            override fun onBackPressedCancelTransaction() {
                setResult("onBackPressedCancelTransaction", false)
            }
            override fun onTransactionCancel(s: String, bundle: Bundle) {
                setResult("$s $bundle", false)
            }
        })
        transactionManager.setShowPaymentUrl(host + "theia/api/v1/showPaymentPage")
        transactionManager.startTransaction(activity, REQ_CODE)
    }
    fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        if (requestCode == REQ_CODE && data != null) {
            val message = data.getStringExtra("nativeSdkForMerchantMessage")
            val response = data.getStringExtra("response")
    // data.getStringExtra("nativeSdkForMerchantMessage") this return message if transaction was stopped by users
    // data.getStringExtra("response") this returns the shared response if the transaction was successful or failure.
            if(response !=null &&  response.isNotEmpty()){
                setResult(response,true)
            }else{
                setResult(message,false)
            }
        }
    }
    private fun setResult(message: String, isSuccess: Boolean) {
        if (isSuccess) {
            result.success(message)
        } else {
            result.error("0", message, null)
        }
    }
    private fun showToast(message: String) {
        Toast.makeText(activity, message, Toast.LENGTH_LONG).show()
    }
}

 

Configuration in iOS App

Navigate to the ios folder and open the ios project as (Proj-Name -> ios -> xcodeproj/xcworkspace)

  1. Add SDK

    Add the latest Paytm All-in-one SDK to the ios app created by react-native(Drag and Drop).
  2. App Settings

    1. General -> Frameworks, Libraries and Embedded Content: verify that the SDK is added and change the Embed to “Embed & Sign”.

    2. Info: Add LSApplicationQueriesSchemes. Change the type to Array. Create a new item in it and set its value as “paytm”.

    3. Info -> URL Types: Add a new URL Type that you’ll be using as the callback from Paytm App (URL Scheme: “paytm”+”MID”). Example: paytmMid123.

  3. Plugin classes

    Add the Plugin class below to your project.

    AllInOneSDKSwiftWrapper.swift
    import Foundation
    import AppInvokeSDK 
    class AllInOneSDKSwiftWrapper: FlutterViewController{
        var viewController = UIApplication.shared.windows.first?.rootViewController
        let appinvoke = AIHandler()
        var flutterCallbackResult: FlutterResult?
        func startTransaction(parameters: [String:Any], callBack: @escaping FlutterResult) {
            self.flutterCallbackResult = callBack
            if let mid = parameters["mid"] as? String, let callbackUrl = parameters["callbackUrl"] as? String, let isStaging = parameters["isStaging"] as? Bool, let orderId = parameters["orderId"] as? String, let transactionToken = parameters["txnToken"] as? String, let amount = parameters["amount"] as? String {
                DispatchQueue.main.async {
                    var env:AIEnvironment = .production
                    if isStaging {
                        env = .staging
                    } else {
                        env = .production
                    }
                    self.appinvoke.openPaytm(merchantId: mid, orderId: orderId, txnToken: transactionToken, amount: amount, callbackUrl: callbackUrl, delegate: self, environment: env)
                }
            }
        }
     
    }
     
    extension AllInOneSDKSwiftWrapper: AIDelegate {
        func didFinish(with status: PaymentStatus, response: [String : Any]) {
            self.flutterCallbackResult?(response)
        }
        func openPaymentWebVC(_ controller: UIViewController?) {
            if let vc = controller {
                DispatchQueue.main.async {[weak self] in
                  self?.viewController?.present(vc, animated: true, completion: nil)
                }
            }
        }
     
    }

     

Flutter Project

  1. Import services.dart package.
    import 'package:flutter/services.dart';
  2. Using MethodChannel create a link to AllInOneSDKSwiftWrapper of ios project.
    var CHANNEL = "samples.flutter.dev/allInOne"; //CHANNEL as set in AppDelegate of iOS project.  
    static const platform = const MethodChannel('CHANNEL');
  3. Start the process by calling platform.invokeMethod with the appropriate arguments.
    var arguments = <String, dynamic>{
        "mid": mid,
        "orderId": orderId,
        "amount": amount,
        "txnToken": txnToken,
        "callbackUrl": callbackUrl, //send this if custom callback url is required
        "isStaging": isStaging
    };
    try {
        var result = await platform.invokeMethod("startTransaction", arguments);
        print(result.toString());
    } catch (err) {
        print(err.message);
    }

    Note: Callback will be received in the above "result" as below string in case of transaction success or failure else "error will be thrown" which will have a string message like onBackedPressed, networkError etc.

    Sample Response

    Bundle[
        {
            STATUS=TXN_SUCCESS, 
            ORDERID="Order Id", 
            CHARGEAMOUNT=0.00, 
            TXNAMOUNT=1.00, 
            TXNDATE=2020-07-21 19:00:05.0, 
            MID="Merchant Id", 
            TXNID="Transaction Value", 
            RESPCODE=01, 
            PAYMENTMODE=UPI, 
            BANKTXNID="Bank transaction Id", 
            CURRENCY=INR, 
            GATEWAYNAME=ICICI, 
            RESPMSG=Txn Success
        }
    ]

     

Integration in iOS App

  1. Start by opening the iOS host portion of your Flutter app in Xcode.
    1. Start Xcode.

    2. Select the menu item File > Open….

    3. Navigate to the directory holding your Flutter app, and select the ios folder inside it. Click OK.

  2. Add support for Swift in the standard template setup that uses Objective-C.
    1. Expand Runner > Runner in the Project navigator.

    2. Open the file AppDelegate.swift located under Runner > Runner in the Project navigator.

      Update AppDelegate.swift like this.

    3. Instantiate AllInOneSDKSwiftWrapper.

    4. Define FlutterResult callback property to be called later when the response is received from PG and return a result to the flutter app.

    5. Update the application:didFinishLaunchingWithOptions: function and create a FlutterMethodChannel tied to the channel name samples.flutter.dev/allInOne.

      Note: Channel name should be the same as channel name used in the Flutter app. 
      ​
      //MARK: Instantiation of PG Plugin
      let plugin = AllInOneSDKSwiftWrapper()
      
      // MARK: store FlutterResult callback for later use, when paytm app is invoked to get the result here in AppDelegate class
      var flutterCallbackResult: FlutterResult?
      
      override func application(
      _ application: UIApplication,
      didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
      ) -> Bool {
      
      let controller: FlutterViewController = window?.rootViewController as! FlutterViewController
      let channel = FlutterMethodChannel(name: "samples.flutter.dev/allInOne",binaryMessenger: controller.binaryMessenger)
      
      channel.setMethodCallHandler { (call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in
      
      // Note: this method is invoked on the UI thread.
          guard  call.method == "startTransaction" // Note: same method name as called by the flutter app.
          else {
              result(FlutterMethodNotImplemented)
              return
          }
          if let parameters = call.arguments as? [String: Any] {
              //print(parameters)
              self.flutterCallbackResult = result
              self.plugin.startTransaction(parameters: parameters, callBack: result)//MARK:calling method of AllInOneSwiftSDKWrapper with the callback result to be called from the wrapper 
          }
      }
      
      ​
  3. While Paytm app is invoked, the response is received in the AppDelegate and is received in the following:
    func application(_ app: UIApplication, open url: URL, options: 
    [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool

     Handle the response as described:

    override func application(_ app: UIApplication, open url: URL, options: 
    [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
        let dict = self.separateDeeplinkParamsIn(url: url.absoluteString, byRemovingParams: nil)
        self.flutterCallbackResult?(dict) // MARK: Called to send response to flutter app in result parameter.
        return true
    }

    Here the method separateDeeplinkParamsIn is called to parse response received in URL's query parameters into JSON.

    //MARK: response got in the URL can be segregated and converted into json from here.
    func separateDeeplinkParamsIn(url: String?, byRemovingParams rparams: [String]?)  -> [String: String] {
        guard let url = url else {
            return [String : String]()
        }
        /// This url gets mutated until the end. The approach is working fine in current scenario. May need a revisit.
        var urlString = stringByRemovingDeeplinkSymbolsIn(url: url)
        var paramList = [String : String]()
        let pList = urlString.components(separatedBy: CharacterSet.init(charactersIn: "&?//"))
        for keyvaluePair in pList {
            let info = keyvaluePair.components(separatedBy: CharacterSet.init(charactersIn: "="))
            if let fst = info.first , let lst = info.last, info.count == 2 {
                paramList[fst] = lst.removingPercentEncoding
                if let rparams = rparams, rparams.contains(info.first!) {
                    urlString = urlString.replacingOccurrences(of: keyvaluePair + "&", with: "")
                    //Please dont interchage the order
                    urlString = urlString.replacingOccurrences(of: keyvaluePair, with: "")
                }
            }
        }
        if let trimmedURL = pList.first {
            paramList["trimmedurl"] = trimmedURL
        }
        return paramList
    }
    func  stringByRemovingDeeplinkSymbolsIn(url: String) -> String {
        var urlString = url.replacingOccurrences(of: "$", with: "&")
        /// This may need a revisit. This is doing more than just removing the deeplink symbol.
        if let range = urlString.range(of: "&"), urlString.contains("?") == false{
            urlString = urlString.replacingCharacters(in: range, with: "?")
        }
        return urlString
    }
  4. When Paytm app is not present, the Redirection flow (WebView) will be opened and the response will be received in AllInOneSDKSwiftWrapper, in didFinish:with:response method of AIDelegate.
    import Foundation
    import AppInvokeSDK
    class AllInOneSDKSwiftWrapper: FlutterViewController{
        lazy var viewController = UIApplication.shared.windows.first?.rootViewController
        let appinvoke = AIHandler()
        var flutterCallbackResult: FlutterResult?
        func startTransaction(parameters: [String:Any], callBack: @escaping FlutterResult) {
            self.flutterCallbackResult = callBack
            if let mid = parameters["mid"] as? String, let callbackUrl = parameters["callbackUrl"] as? String, let isStaging = parameters["isStaging"] as? Bool, let orderId = parameters["orderId"] as? String, let transactionToken = parameters["txnToken"] as? String, let amount = parameters["amount"] as? String {
                DispatchQueue.main.async {
                  var env:AIEnvironment = .production
                  if isStaging {
                    env = .staging
                  } else {
                    env = .production
                  }
                  self.appinvoke.openPaytm(merchantId: mid, orderId: orderId, txnToken: transactionToken, amount: amount, callbackUrl: callbackUrl, delegate: self, environment: env)
                }
            }
        }
    }
    extension AllInOneSDKSwiftWrapper: AIDelegate {
        func didFinish(with status: PaymentStatus, response: [String : Any]) {
            self.flutterCallbackResult?(response)
        }
        func openPaymentWebVC(_ controller: UIViewController?) {
            if let vc = controller {
                DispatchQueue.main.async {[weak self] in
                  self?.viewController?.present(vc, animated: true, completion: nil)
                }
            }
        }
    }

    From there, the flutterCallbackResult will be called with the response from webView(Redirection Flow).

  5. Verifying Payment 

    1. You should validate the transaction response via a server-side request using the Transaction Status API. This API requires checksumhash in request and response. You must verify the Order ID and Amount with your data. The status should be treated as the final status of the transaction in all cases.

    2. Paytm provides the payment response on both the Callback URL and Webhook URL. Please refer to the sample response for different payment sources here.