Payment gateway in an application
The integration of a payment gateway into a mobile or desktop application (hereafter referred to as the Application) is very similar to integration into a web interface. In this case, a standard API protocol is also utilized. The only difference is that on the web, it is possible to choose either a redirect to the payment gateway or displaying the gateway in an iframe, whereas in an application, the payment must be opened in a component designed for displaying web pages, typically WebView, or by using redirection via Android Intent or Apple URL Schemes and Universal Links.
WebView
For Android, .Net, and Xamarin, the WebView component is available, while Apple uses WKWebView in its Swift.
The WebView interface should take up as much of the screen as possible to provide the payment gateway with sufficient space for display and to maximize user comfort.
Payment Processing Workflow
- The Application contacts its Server and creates an order.
- The Server sends an HTTP request to the Comgate API (/create), initiating the payment. The Comgate API responds with payment information.
- The Server stores the information received from the Comgate API and forwards it to the Application (how to parse the query string in PHP, JavaScript, Kotlin, Java, and Swift).
- The Application creates a WebView component spanning as much of the display as possible and loads the payment gateway URL (redirect parameter) into it.
- The payer accesses the payment gateway within the WebView.
- While the payer attempts to make the payment, a status watcher must be activated. This ensures the payment status is synchronized as quickly as possible from the Server to the Application. Available methods:
- Polling - Short, repeated requests to the server (approximately every 3 seconds).
- Long Polling - The server keeps the request open until data is available or a timeout occurs. If the status is still unknown, the request is repeated.
- WebSocket - Provides a full-duplex communication channel where the server can send real-time updates about the payment status to the Application.
- The payer completes the order payment.
- Once the payment is completed, the Server is notified of the new payment status via Push Notification. The Server stores this information.
- The Server verifies the payment status by requesting the Comgate API (/status) to confirm the payment.
- If the payment is not confirmed, it retries approximately every 3 seconds. After 10 retries (about 30 seconds), the retry attempts should stop, and the Server should be requested in the background every 30 minutes.
Extra attention
In some cases, payment processing may take until 12:00 PM on the next business day.
- Retry attempts can continue based on the payment statuses.
- If the payment is not confirmed, it retries approximately every 3 seconds. After 10 retries (about 30 seconds), the retry attempts should stop, and the Server should be requested in the background every 30 minutes.
- The gateway displayed in the WebView detects that the payment has been completed:
- Just before redirection, it sends a post message that can be intercepted to hide the WebView.
- Alternatively, the payer is redirected to a URL defined in the store configuration, which the Application should detect to hide the WebView.
- The Application contacts its Server to verify the payment status.
- The Application grants access to the purchased service.
Sequence diagram
How to Determine When to Hide the WebView
As outlined in step 10, there are essentially two ways to determine that the payment has been completed and the WebView needs to be hidden:
- Intercepting the sent post message,
- Redirecting the payer to a URL defined in the store configuration.
Post message
Before redirecting the payer away from the gateway, a post message is sent to provide information about the payment status. This message can be intercepted to hide the WebView.
Structure of the post message:
{
"value":{
"id": "XXXX-XXXX-XXXX",
"status": "PAID", // PAID, CANCELLED, AUTHORIZED, PENDING
"refId": "hodnota-refId"
},
"scope": "comgate-to-eshop",
"action": "status"
}
When receiving a message, it is necessary to verify that scope
is "comgate-to-eshop"
and action
is "status"
. Other post messages should be ignored.
Additionally, special attention must be given to verifying the payment status. Statuses obtained via post messages are not reliable and must always be confirmed through the Comgate API.
Payer Redirection
The payer can also be redirected to a URL on your server. In this case, the Application must automatically detect this redirection and hide the WebView.
URLs can be configured in the client portal (or via API when creating the payment):
- Paid URL (
url_paid
) - for completed payments (PAID
,AUTHORIZED
), - Cancelled URL (
url_cancelled
) - for cancelled or expired payments (CANCELLED
), - Pending URL (
url_pending
) - for payments still in progress (PENDING
), - URL for payment result delivery - must be configured only in the client portal in the store settings.
Detecting URL Changes in WebView
Java source
WebViewClient webViewClient = new WebViewClient() {
@Override
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
if(url.equals("your link...")){
finish();
}
}
};
Useful Links:
- https://stackoverflow.com/questions/6908369/how-to-exit-a-webview-android
- https://stackoverflow.com/questions/3149216/how-to-listen-for-a-webview-finishing-loading-a-url
- https://medium.com/nerd-for-tech/androids-webview-common-challenges-and-it-s-solution-part-1-9b970b95a482
- https://stackoverflow.com/questions/20305612/closing-webview-in-android
Simplified Implementation in Kotlin
Kotlin source
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 a payment
// Request the mobile app server to create a payment using the Comgate API
// Server returns 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 the response
val paymentData = this.parseQuery(cloudResponseFromComgate)
val comgateMessage = paymentData["message"]
val paymentUrl = paymentData["redirect"]
val transId = paymentData["transId"]
if (comgateMessage == "OK" && paymentUrl?.isNotEmpty() == true && transId?.isNotEmpty() == true) {
val paymentView: WebView = findViewById(R.id.comgatePaymentFrame)
paymentView.visibility = View.VISIBLE
// The URL can change over time, so always use the URL returned by the Comgate API
paymentView.loadUrl(paymentUrl)
// Start the status watcher
runStatusWatcher(transId)
} else {
// TODO: Handle payment error
}
}
private fun runStatusWatcher(transId: String) {
// TODO: Implement a payment status watcher
// Options for implementation:
// - Polling: Send repeated requests every 3 seconds
// - Long polling: Keep the request open until new data is available
// - WebSockets: Establish a full-duplex communication channel
// - Other methods
}
/**
* Parses a query string into a Map (Key -> Value)
*/
private fun parseQuery(input: String): Map<String, String> {
return input.split("&")
.map { pair ->
pair.split("=").let {
URLDecoder.decode(it[0].trim(), StandardCharsets.UTF_8.toString()) to
URLDecoder.decode(it.getOrElse(1) { "" }.trim(), StandardCharsets.UTF_8.toString())
}
}
.toMap()
}
}
Android Intent
Comgate also provides the integration of returns from the payment gateway using Intents. The advantage is that you do not need to implement WebView, and the default browser is used to display the payment, which then returns focus to the application.
Payment Processing Workflow
- The Application contacts its Server and creates an order.
- The Server sends an HTTP request to the Comgate API (/create), initiating the payment. The Comgate API responds with payment information.
- The Server stores the information received from the Comgate API and forwards it to the Application (how to parse the query string in PHP, JavaScript, Kotlin, Java, and Swift).
- The Application opens the payment in the payer's default browser (using the payment gateway URL from the "redirect" parameter), causing the Application to lose focus.
- The payer accesses the payment gateway in the default browser.
- The payer completes the payment.
- Once the payment is completed, the Server is notified of the new payment status via Push Notification. The Server stores this information.
- The Server verifies the payment status by requesting the Comgate API (/status) to confirm the payment.
- If the payment is not confirmed, it retries approximately every 3 seconds. After 10 retries (about 30 seconds), the retry attempts should stop, and the Server should be requested in the background every 30 minutes.
Extra attention
In some cases, payment processing may take until 12:00 PM on the next business day.
- Retry attempts can continue based on the payment statuses.
- If the payment is not confirmed, it retries approximately every 3 seconds. After 10 retries (about 30 seconds), the retry attempts should stop, and the Server should be requested in the background every 30 minutes.
- The payer is redirected back to the Application, which regains "focus".
- The Application contacts its Server to verify the payment status.
- The Application grants access to the paid service.
Sequence Diagram
Configuring Return URIs for Intents
Currently, Android Intents can only be used as parameters when creating a payment through the Comgate API (/create). These parameters include:
url_paid
- URI to redirect the payer after the payment is completed,url_cancelled
- URI to redirect the payer after the payment is cancelled,url_pending
- URI to redirect the payer if the payment is still pending.
The payment gateway will then attempt to use this Intent in the default browser to redirect the payer back to your application.
Android Intents cannot currently be configured in the client portal.
Android Intent Format
Any Intent format can be used. Very often, we encounter the general notation in the form of:
intent://host/path?transactionId=XXXX-XXXX-XXXX&status=PAID#Intent;package=com.example.package;end
More specific Intents can also be used, such as:
myapp://payment/success?transactionId=XXXX-XXXX-XXXX&status=PAID
In both cases, Intents are closely tied to a specific application. Their functionality must be implemented within an application itself.
Apple URL Schemes and Universal Links
Apple provides mechanisms on its devices that allow applications to efficiently handle redirections. These mechanisms include URL Schemes and Universal Links.
URL Schemes
Custom URL schemes enable applications to register unique links that the payment gateway can use to redirect users back to the application.
myapp://payment/success?transactionId=XXXX-XXXX-XXXX&status=PAID
Universal Links
Universal Links are standard HTTP/HTTPS links that automatically open the application. This mechanism is preferred due to better security and user-friendliness.
https://example.com/payment/success?transactionId=XXXX-XXXX-XXXX&status=PAID
Return URLs
When setting up payments via the Comgate API, it is possible to define return URLs that determine where the user will be redirected in various scenarios:
url_paid
: Redirect after a successful payment (PAID
,AUTHORIZED
)url_cancelled
: Redirect after a cancelled or expired payment (CANCELLED
)url_pending
: Redirect for pending payments (PENDING
)
Return URLs in the Universal Links format can be defined either through the Comgate API or in the client portal.
URL Schemes can currently only be configured via the Comgate API.