Skip to main content

Payment Gateway in the app

Integration of a payment gateway into a mobile or desktop application is very similar to integration into a web interface. In this case as well, a standard API protocol is utilized. The only difference is that on the web, it is possible to either opt for a redirect to the payment gateway or display the gateway within an iframe, whereas in the application, it is necessary to always initiate the payment within a component designated for displaying web pages, typically a WebView.

XML code

<?xml version="1.0" encoding="utf-8"?>

<androidx.constraintlayout.widget.ConstraintLayout
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=".MainActivity">

// <TextView...>

<WebView
android:id="@+id/comgatePaymentFrame"
android:layout_width="406dp" android:layout_height="760dp"
tools:ignore="MissingConstraints"
tools:layout_editor_absoluteX="2dp"
tools:layout_editor_absoluteY="59dp" />

// <Button...>

</androidx.constraintlayout.widget.ConstraintLayout>

For Android, .Net a Xamarin, the WebView component is available, while Apple utilizes WKWebView in Swift. The WebView interface should be displayed on as much of the screen as possible to provide ample space for the payment gateway and maximize user comfort.

Payment processing flow:

  1. The application contacts its backend (server), which requests the initiation of a payment.

  2. The server executes an HTTP request to the Comgate API (/v.1.0/create) and creates the payment. Comgate responds to this request with a response containing payment information in a query format:

    • code - return code of the response
    • message - status of payment creation
    • transId - Comgate transaction ID
    • redirect - URL for display in the WebView
  3. The server application responds to the request in step 1 and returns the payment parameters obtained from Comgate.

  4. The application parses the payment parameters (how to parse the query string in Kotlin and Java), creates a WebView component covering as much of the screen as possible, and loads the URL of the payment gateway (redirect parameter) into it.

  5. The payer is presented with the option to make the payment.

  6. While the payer attempts to make the payment, it is necessary to start a status watcher. This watcher attempts to synchronize the payment status from its server backend to the application as quickly as possible. Available methods:

    • Polling - short repeated requests to the server (approximately every 3 seconds).
    • Long Polling - the server keeps requests open until data is available or a timeout occurs. If the status is still unknown, the request is repeated.
    • WebSockets - provide a full-duplex communication channel, allowing the server to send real-time information about the payment status to the application.
    • Others...
  7. The Comgate server immediately sends push notifications to the defined URL of the application's backend upon a change in the payment status.

  8. The payer completes the payment.

  9. The gateway displayed in the WebView detects the payment and redirects the payer to the URL defined in the store link.

  10. Hide and remove the WebView.

  11. The application contacts the backend to verify the payment status.

  12. The application displays the payer's payment status screen.

Setting up return URLs

In the client portal, it's necessary to define 4 special URL addresses for each store link:

  1. Success URL - for successful payments
  2. URL - for cancelled and expired payments
  3. Pending URL - for ongoing payments
  4. URL for delivering payment result - URL for delivering the payment status (push notification)

For the 1st to 3rd URL, the payer is redirected directly from the payment gateway. The addresses can be the same or differ in parameters for the payment status. The payer can also be redirected to an empty URL of the server API. In this case, automatic detection of this redirection and hiding of the WebView must be ensured.

The 4th URL address is intended for server-to-server communication. When the payment status is known (paid or cancelled/expired), Comgate immediately sends information about this to this address. It's necessary to verify this with a follow-up request back to the Comgate API for the payment status.

How to correctly detect when it's necessary to hide the WebView

With WebView, it's possible to detect a specific URL and automatically perform further actions related to the payment status on it.

WebViewClient webViewClient = new WebViewClient() {

@Override
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
if(url.equals("your link...")){
finish();
}
}
};

Interesting links:

Simplified implementation

How to display payment in a WebView for Android Studio in Kotlin

package com.comgate.webviewtest

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.View
import android.webkit.WebView
import java.net.URLDecoder
import java.nio.charset.StandardCharsets

class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)

// TODO create payment
// ask mobile app cloud backend to create payment at Comgate API

// cloud return structured payment data
val cloudResponseFromComgate = "code=0&message=OK&transId=XXXX-XXXX-XXXX&redirect=https%3A%2F%2Fpay1.comgate.cz%2Finit%3Fid%3DXXXX-XXXX-XXXX";

// parse response
val paymentData = this.parseQuery(cloudResponseFromComgate);
val comgateMessage = paymentData["message"];
val paymentUrl = paymentData["redirect"];
val transId = paymentData["transId"];

if(comgateMessage == "OK" && paymentUrl?.isEmpty() == false && transId?.isEmpty() == false) {
val paymentView: WebView = findViewById(R.id.comgatePaymentFrame);
paymentView.visibility = View.VISIBLE;

// URL can change over time, so always use the URL that the Comgate API returns
paymentView.loadUrl(paymentUrl);

// Run status watcher
runStatusWatcher(transId);
} else {
// TODO: payment error
}
}

private fun runStatusWatcher(transId: String) {
// TODO: create payment status watcher
// ask mobile app cloud backend to payment status, you can use:
// - Polling: repeated request every 3 seconds
// - Long polling: server holds the request open until new data is available
// - WebSockets: provides a full-duplex communication channel
// - others
}

/**
* Parse query string to Map (Key->Value)
*/
private fun parseQuery(input: String): Map<String, String> {
val result = mutableMapOf<String, String>()
input.split("&").forEach { pair ->
val (key, value) = pair.split("=").map {
URLDecoder.decode(it.trim(), StandardCharsets.UTF_8.toString())
}
result[key] = value
}
return result
}
}