Přeskočit na hlavní obsah

Platební brána v aplikaci

Integrace platební brány do mobilní, či desktopové aplikace (dále již jen jako aplikace) je velmi podobná jako integrace do webového rozhraní. I v tomto případě je využíváno standardní API protokol. Jediným rozdílem je, že na webu je možné si zvolit buď redirect na platební bránu, nebo zobrazení brány v iframe, zatímco v aplikaci je potřeba platbu vždy otevřít v komponentě určené pro zobrazení webové stránky, typicky WebView.

XML kód

<?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>

Pro Android, .Net a Xamarin je dostupná komponenta WebView, zatímco Apple používá ve svém Swiftu WKWebView. Rozhraní WebView by mělo být zobrazeno na co největší části obrazovky tak, aby měla platební brána dostatek prostoru pro zobrazení a maximalizovala tak uživatelský komfort.

Průběh zpracování platby:

  1. Aplikace kontaktuje svůj backend (server), který požádá o založení platby
  2. Server provede HTTP request na Comgate API (/v.1.0/create) a založí platbu. Comgate na tuto žádost vrátí odpověď obsahující informace o platbě ve formátu query:
    • code - návratový kód odpovědi
    • message - stav vytvoření platby
    • transId - Comgate ID transakce
    • redirect - url pro zobrazení ve WebView
  3. Server aplikace na požadavek v bodě 1 odpoví a vrátí parametry platby získané od Comgate.
  4. Aplikace parametry platby rozparsuje (jak parsovat query string v Kotlinu a Javě), vytvoří přes co největší část displeje WebView komponentu a v ní načte URL platební brány (parametr redirect).
  5. Plátci se zobrazí možnost platby
  6. Během toho, co se plátce pokouší zaplatit, je nutné spustit status watcher. Ten se snaží co nejrychleji synchronizovat stav platby ze svého serverového backendu do aplikace. Dostupné metody:
    • Polling - krátké opakované requesty na server (cca každé 3 sekundy)
    • Long Polling - server drží requesty otevřené dokud nejsou data dostupná nebo nedojde k timeout. Pokud ještě není znám status, request se opakuje.
    • WebSockets - poskytují full-duplex komunikační kanál, kdy je server schopen aplikaci v reálném čase poslat informaci o stavu platby.
    • další…
  7. Server Comgate zasílá bezprostředně po změně stavu platby push notifikace na definou URL backendu aplikace.
  8. Plátce zaplatí objednávku
  9. Brána zobrazená ve WebView zjistí, že došlo k zaplacení a přesměruje plátce na URL definovanou v propojení obchodu.
  10. Skrytí a odstranění WebView
  11. Aplikace kontaktuje backend

Nastavení návratových URL

V klientském portálu je pro každé propojení obchodu potřeba definovat 4 speciální URL adresy, jedná se o:

  1. url zaplacený - zaplacené platby
  2. url zrušený - zrušené a expirované platby
  3. url nevyřízený - stále probíhající platby
  4. url pro předání výsledku platby - URL pro předání stavu platby (push notifikace)

Na 1. - 3. adresu je plátce směrován přímo z platební brány. Adresy mohou být stejné, mohou se lišit v parametrech pro stav platby. Plátce je také možné přesměrovat na nějakou prázdnou URL serverového API. V tomto případě je nutné zajistit automatické zjištění tohoto přesměrování a skrytí WebView.

  1. adresa je určena pro server–server komunikaci. Ve chvíli, kdy je stav platby známý (zaplacená nebo zrušená/expirovaná), comgate neprodleně na tuto adresu předá o této skutečnosti informaci. Tu je nutné ověřit doprovodným requestem zpět na Comgate API pro stav platby.

Jak správně poznat okamžik, kdy je potřeba skrýt WebView

U WebView je možné detekovat konkrétní URL a na té automaticky provést další akce související se stavem platby.

Java kód

WebViewClient webViewClient = new WebViewClient() {

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

Zajímavé odkazy:

Zjednodušená implementace:

Jak zobrazit platbu ve WebView pro Android studio v Kotlinu:

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