diff --git a/Liqpay/build.gradle b/Liqpay/build.gradle index bfc3a45..5230134 100644 --- a/Liqpay/build.gradle +++ b/Liqpay/build.gradle @@ -8,12 +8,8 @@ android { defaultConfig { minSdkVersion 21 - targetSdkVersion 30 versionCode 1 - versionName "1.0" - - testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - consumerProguardFiles "consumer-rules.pro" + versionName "0.1.0" } buildTypes { @@ -30,8 +26,5 @@ dependencies { implementation 'androidx.core:core-ktx:1.5.0' implementation 'androidx.appcompat:appcompat:1.3.0' implementation 'com.google.android.material:material:1.3.0' - testImplementation 'junit:junit:4.12' - androidTestImplementation 'androidx.test.ext:junit:1.1.2' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' } \ No newline at end of file diff --git a/Liqpay/src/androidTest/java/ua/liqpay/ExampleInstrumentedTest.kt b/Liqpay/src/androidTest/java/ua/liqpay/ExampleInstrumentedTest.kt deleted file mode 100644 index d52e322..0000000 --- a/Liqpay/src/androidTest/java/ua/liqpay/ExampleInstrumentedTest.kt +++ /dev/null @@ -1,24 +0,0 @@ -package ua.liqpay - -import androidx.test.platform.app.InstrumentationRegistry -import androidx.test.ext.junit.runners.AndroidJUnit4 - -import org.junit.Test -import org.junit.runner.RunWith - -import org.junit.Assert.* - -/** - * Instrumented test, which will execute on an Android device. - * - * See [testing documentation](http://d.android.com/tools/testing). - */ -@RunWith(AndroidJUnit4::class) -class ExampleInstrumentedTest { - @Test - fun useAppContext() { - // Context of the app under test. - val appContext = InstrumentationRegistry.getInstrumentation().targetContext - assertEquals("ua.liqpay.test", appContext.packageName) - } -} \ No newline at end of file diff --git a/Liqpay/src/main/java/ua/liqpay/Config.kt b/Liqpay/src/main/java/ua/liqpay/Config.kt index f4f3539..aa4d68b 100644 --- a/Liqpay/src/main/java/ua/liqpay/Config.kt +++ b/Liqpay/src/main/java/ua/liqpay/Config.kt @@ -5,6 +5,11 @@ package ua.liqpay */ internal const val LIQPAY_URL_CHECKOUT = "https://www.liqpay.ua/api/3/checkout" +/** + * Current Liqpay API version. + */ +internal const val LIQPAY_API_VERSION = "3" + /** * Privat24 application package name. */ diff --git a/Liqpay/src/main/java/ua/liqpay/LiqPay.kt b/Liqpay/src/main/java/ua/liqpay/LiqPay.kt index 8161d56..f2a5b48 100644 --- a/Liqpay/src/main/java/ua/liqpay/LiqPay.kt +++ b/Liqpay/src/main/java/ua/liqpay/LiqPay.kt @@ -4,97 +4,107 @@ import android.content.BroadcastReceiver import android.content.Context import android.content.Intent import android.content.IntentFilter -import android.os.Looper import org.json.JSONObject -import ua.liqpay.request.ErrorCode -import ua.liqpay.request.HttpRequest -import ua.liqpay.request.LIQPAY_API_URL_REQUEST import ua.liqpay.utils.base64 -import ua.liqpay.utils.getDeviceId -import ua.liqpay.utils.isNetworkAvailable import ua.liqpay.utils.signature -import ua.liqpay.view.BUNDLE_LIQPAY_DATA +import ua.liqpay.view.BUNDLE_DATA import ua.liqpay.view.LiqpayActivity -import java.io.IOException -import java.net.URLEncoder -import java.util.* -import kotlin.collections.HashMap -const val BROADCAST_RECEIVER_ACTION = "ua.liqpay.action" +internal const val LIQPAY_BROADCAST_RECEIVER_ACTION = "ua.liqpay.action" +internal const val LIQPAY_DATA_KEY = "data" +internal const val LIQPAY_SIGNATURE_KEY = "signature" -class LiqPay(private val context: Context, - private val callback: LiqpayCallback) { +class LiqPay( + private val context: Context, + private val callback: LiqpayCallback +) { /** - * Start liqpay checkout + * Start checkout page. + * + * @param privateKey Secret access key for API + * @param publicKey Unique ID of your company in LiqPay system + * @param action Transaction type. Possible values: pay - payment, + * hold - amount of hold on sender's account, subscribe - regular payment, + * paydonate - donation, auth - card preauth + * @param amount Payment amount. For example: 5, 7.34 + * @param currency Payment currency. Possible values: USD, EUR, RUB, UAH, BYN, KZT + * @param description Payment description + * @param orderId Unique purchase ID in your shop. Maximum length is 255 symbols. + * @param language Customer's language ru, uk, en + * + * More info: https://www.liqpay.ua/documentation/en/api/aquiring/checkout/doc * - * @param privateKey Liqpay private key - * @param params Other params */ - fun checkout(privateKey: String, params: Map = hashMapOf()) { - val base64Data = JSONObject(params).toString().base64() - val signature = signature(base64Data, privateKey) - checkout(base64Data, signature) - } - fun checkout( - base64Data: String, - signature: String + privateKey: String, + publicKey: String, + action: String = "pay", + amount: Double, + currency: String = "UAH", + description: String, + orderId: String, + language: String = "uk" ) { - if (!isNetworkAvailable(context)) { - callback.onError(ErrorCode.FAIL_INTERNET_CONNECTION) - } else { - val liqPay = LiqPay(context, callback) - context.registerReceiver( - liqPay.eventReceiver, - IntentFilter(BROADCAST_RECEIVER_ACTION) - ) - LiqpayActivity.start(context, base64Data, signature) - } + val params = hashMapOf( + "action" to action, + "amount" to amount, + "currency" to currency, + "description" to description, + "order_id" to orderId, + "language" to language + ) + checkout(privateKey, publicKey, params) } - fun api( - context: Context, - path: String, - params: HashMap = hashMapOf(), - privateKey: String - ) { - val base64Data = JSONObject(params as Map<*, *>).toString().base64() + /** + * Start checkout page. + * + * @param privateKey Secret access key for API + * @param publicKey Unique ID of your company in LiqPay system + * @param params Custom API params. Info: https://www.liqpay.ua/documentation/en/api/aquiring/checkout/doc + * + */ + fun checkout(privateKey: String, publicKey: String, params: Map = hashMapOf()) { + val tempMap = params.toMutableMap().apply { + put("public_key", publicKey) + if (!containsKey("version")) { + put("version", LIQPAY_API_VERSION) + } + } + val base64Data = JSONObject(tempMap).toString().base64() val signature = signature(base64Data, privateKey) - api(context, path, base64Data, signature) + checkout(base64Data, signature) } - private fun api( - context: Context, - path: String, - base64Data: String?, - signature: String? + /** + * Start checkout page. + * + * @param base64Data Checkout data encoded by the base64 + * @param signature The unique signature of each request base64_encode(sha1(private_key + data + private_key)) + * + */ + fun checkout( + base64Data: String, + signature: String ) { - if (!isNetworkAvailable(context)) { - callback.onError(ErrorCode.FAIL_INTERNET_CONNECTION) - } else if (Looper.myLooper() == Looper.getMainLooper()) { - callback.onError(ErrorCode.NEED_NON_UI_THREAD) - } else { - val postParams = HashMap() - postParams["data"] = base64Data - postParams["signature"] = signature - try { - val request = HttpRequest() - val resp = request.post(LIQPAY_API_URL_REQUEST + path, postParams) - callback.onSuccess(resp) - } catch (e: IOException) { - e.printStackTrace() - callback.onError(ErrorCode.OTHER) - } - } + val liqPay = LiqPay(context, callback) + context.registerReceiver( + liqPay.eventReceiver, + IntentFilter(LIQPAY_BROADCAST_RECEIVER_ACTION) + ) + LiqpayActivity.start(context, base64Data, signature) } + /** + * Result event receiver. + */ private val eventReceiver = object : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { - if (intent.action == BROADCAST_RECEIVER_ACTION) { - val response = intent.getStringExtra("data") + if (intent.action == LIQPAY_BROADCAST_RECEIVER_ACTION) { + val response = intent.getStringExtra(BUNDLE_DATA) if (response.isNullOrEmpty()) { - callback.onError(ErrorCode.CHECKOUT_CANCELED) + callback.onCancel() } else { callback.onSuccess(response) } diff --git a/Liqpay/src/main/java/ua/liqpay/LiqpayCallback.kt b/Liqpay/src/main/java/ua/liqpay/LiqpayCallback.kt index 77b2482..a468e28 100644 --- a/Liqpay/src/main/java/ua/liqpay/LiqpayCallback.kt +++ b/Liqpay/src/main/java/ua/liqpay/LiqpayCallback.kt @@ -1,11 +1,14 @@ package ua.liqpay -import ua.liqpay.request.ErrorCode - +/** + * Liqpay result callback. + */ interface LiqpayCallback { fun onSuccess(response: String?) - fun onError(error: ErrorCode) + fun onError() + + fun onCancel() } \ No newline at end of file diff --git a/Liqpay/src/main/java/ua/liqpay/request/Api.kt b/Liqpay/src/main/java/ua/liqpay/request/Api.kt deleted file mode 100644 index 4c1cf02..0000000 --- a/Liqpay/src/main/java/ua/liqpay/request/Api.kt +++ /dev/null @@ -1,4 +0,0 @@ -package ua.liqpay.request - -internal const val LIQPAY_API_URL_CHECKOUT = "https://www.liqpay.ua/api/3/checkout" -internal const val LIQPAY_API_URL_REQUEST = "https://www.liqpay.ua/api/request/" \ No newline at end of file diff --git a/Liqpay/src/main/java/ua/liqpay/request/ErrorCode.kt b/Liqpay/src/main/java/ua/liqpay/request/ErrorCode.kt deleted file mode 100644 index 29726a1..0000000 --- a/Liqpay/src/main/java/ua/liqpay/request/ErrorCode.kt +++ /dev/null @@ -1,8 +0,0 @@ -package ua.liqpay.request - -enum class ErrorCode { - FAIL_INTERNET_CONNECTION, - NEED_NON_UI_THREAD, - CHECKOUT_CANCELED, - OTHER -} \ No newline at end of file diff --git a/Liqpay/src/main/java/ua/liqpay/request/HttpRequest.kt b/Liqpay/src/main/java/ua/liqpay/request/HttpRequest.kt deleted file mode 100644 index 46ce812..0000000 --- a/Liqpay/src/main/java/ua/liqpay/request/HttpRequest.kt +++ /dev/null @@ -1,103 +0,0 @@ -package ua.liqpay.request - -import ua.liqpay.utils.encodeUTF8 -import java.io.BufferedInputStream -import java.io.BufferedReader -import java.io.DataOutputStream -import java.io.InputStreamReader -import java.net.HttpURLConnection -import java.net.URL - -class HttpRequest : Request { - - override fun post(url: String, params: Map): String? { - return perform(url, Method.POST, params) - } - - override fun get(url: String, query: Map): String? { - return perform(url, Method.GET, query) - } - - /** - * Perform http request - * - * @param url URL to connect to - * @param method Request type method (POST, GET, etc) - * @param params Query or form data params - * - * @return Response data - */ - private fun perform( - url: String, - method: Method, - params: Map = hashMapOf() - ): String? { - val httpClient = getHttpClient(url) - httpClient.requestMethod = method.name - when (method) { - Method.POST -> { - httpClient.doOutput = true - val wr = DataOutputStream(httpClient.outputStream) - wr.writeBytes(formatted(params)) - wr.flush() - wr.close() - } - Method.GET -> { - } - } - return try { - val stream = BufferedInputStream(httpClient.inputStream) - readStreamData(inputStream = stream) - } catch (error: Exception) { - null - } finally { - httpClient.disconnect() - } - } - - /** - * Formatted query or form data params - * - * @param params Input params - * - * @return [String] - */ - private fun formatted(params: Map): String { - val stringBuilder = StringBuilder() - params.filter { it.value != null }.forEach { - stringBuilder - .append(it.key) - .append("=") - .append(it.value?.encodeUTF8()) - .append("&") - } - return stringBuilder.toString() - } - - /** - * Read data from buffer stream - * - * @param inputStream [BufferedInputStream] - * - * @return [String] - */ - private fun readStreamData(inputStream: BufferedInputStream): String { - val bufferedReader = BufferedReader(InputStreamReader(inputStream)) - val stringBuilder = StringBuilder() - bufferedReader.forEachLine { stringBuilder.append(it) } - return stringBuilder.toString() - } - - /** - * Initialization [HttpURLConnection] - * - * @param url URL to connect to - * - * @return [HttpURLConnection] - */ - private fun getHttpClient(url: String): HttpURLConnection { - val client = URL(url).openConnection() as HttpURLConnection - client.defaultUseCaches = false - return client - } -} \ No newline at end of file diff --git a/Liqpay/src/main/java/ua/liqpay/request/Method.kt b/Liqpay/src/main/java/ua/liqpay/request/Method.kt deleted file mode 100644 index 795b0d2..0000000 --- a/Liqpay/src/main/java/ua/liqpay/request/Method.kt +++ /dev/null @@ -1,6 +0,0 @@ -package ua.liqpay.request - -enum class Method { - POST, - GET -} \ No newline at end of file diff --git a/Liqpay/src/main/java/ua/liqpay/request/Request.kt b/Liqpay/src/main/java/ua/liqpay/request/Request.kt deleted file mode 100644 index cf52cb2..0000000 --- a/Liqpay/src/main/java/ua/liqpay/request/Request.kt +++ /dev/null @@ -1,8 +0,0 @@ -package ua.liqpay.request - -internal interface Request { - - fun post(url: String, params: Map = hashMapOf()): String? - - fun get(url: String, query: Map = hashMapOf()): String? -} \ No newline at end of file diff --git a/Liqpay/src/main/java/ua/liqpay/utils/AppUtil.kt b/Liqpay/src/main/java/ua/liqpay/utils/AppUtil.kt index ec47c58..d6cb824 100644 --- a/Liqpay/src/main/java/ua/liqpay/utils/AppUtil.kt +++ b/Liqpay/src/main/java/ua/liqpay/utils/AppUtil.kt @@ -6,9 +6,10 @@ import android.content.Intent import android.content.pm.PackageManager import android.net.Uri - - -fun isInstallApp(context: Context, uri: String): Boolean { +/** + * Check application is install. + */ +internal fun isInstallApp(context: Context, uri: String): Boolean { val pm = context.packageManager try { pm.getPackageInfo(uri, PackageManager.GET_ACTIVITIES) @@ -18,8 +19,10 @@ fun isInstallApp(context: Context, uri: String): Boolean { return false } - -fun handleAppLink(context: Context, url: String, app: String) { +/** + * Open or download application. + */ +internal fun handleAppLink(context: Context, url: String, app: String) { if (isInstallApp(context, app)) { context.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(url))) return diff --git a/Liqpay/src/main/java/ua/liqpay/utils/JsonUtil.kt b/Liqpay/src/main/java/ua/liqpay/utils/JsonUtil.kt index 2c18675..e052c79 100644 --- a/Liqpay/src/main/java/ua/liqpay/utils/JsonUtil.kt +++ b/Liqpay/src/main/java/ua/liqpay/utils/JsonUtil.kt @@ -10,7 +10,7 @@ import org.json.JSONObject * * @param object2 [JSONObject] */ -fun merge(object1: JSONObject, object2: JSONObject): JSONObject { +internal fun merge(object1: JSONObject, object2: JSONObject): JSONObject { val iter = object2.keys() as Iterator while (iter.hasNext()) { val key = iter.next() diff --git a/Liqpay/src/main/java/ua/liqpay/utils/Logger.kt b/Liqpay/src/main/java/ua/liqpay/utils/Logger.kt index ad6e9db..e1b8937 100644 --- a/Liqpay/src/main/java/ua/liqpay/utils/Logger.kt +++ b/Liqpay/src/main/java/ua/liqpay/utils/Logger.kt @@ -10,7 +10,7 @@ private const val IS_ENABLE = true * * @param message Error message */ -fun logE(message: Any?) { +internal fun logE(message: Any?) { if (IS_ENABLE) Log.e(LOG_TAG, message.toString()) } @@ -20,18 +20,18 @@ fun logE(message: Any?) { * * @param message Info message */ -fun logI(message: Any?) { +internal fun logI(message: Any?) { if (IS_ENABLE) Log.i(LOG_TAG, message.toString()) } -fun Throwable?.logE(){ +internal fun Throwable?.logE(){ this?.localizedMessage?.let { logE(it) } } -fun Exception?.logE(){ +internal fun Exception?.logE(){ this?.localizedMessage?.let { logE(it) } diff --git a/Liqpay/src/main/java/ua/liqpay/utils/NetworkUtil.kt b/Liqpay/src/main/java/ua/liqpay/utils/NetworkUtil.kt deleted file mode 100644 index 635cc54..0000000 --- a/Liqpay/src/main/java/ua/liqpay/utils/NetworkUtil.kt +++ /dev/null @@ -1,30 +0,0 @@ -package ua.liqpay.utils - -import android.content.Context -import android.net.ConnectivityManager -import android.net.NetworkCapabilities -import android.os.Build - -/** - * Check network state - * - * @param context [Context] - */ -fun isNetworkAvailable(context: Context?): Boolean { - if (context == null) return false - val connectivityManager = - context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - val capabilities = - connectivityManager.getNetworkCapabilities(connectivityManager.activeNetwork) - return capabilities != null && capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) or - capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) or - capabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET) - } else { - val activeNetworkInfo = connectivityManager.activeNetworkInfo - if (activeNetworkInfo != null && activeNetworkInfo.isConnected) { - return true - } - } - return false -} \ No newline at end of file diff --git a/Liqpay/src/main/java/ua/liqpay/utils/PermissionUtil.kt b/Liqpay/src/main/java/ua/liqpay/utils/PermissionUtil.kt deleted file mode 100644 index 6dc0a6f..0000000 --- a/Liqpay/src/main/java/ua/liqpay/utils/PermissionUtil.kt +++ /dev/null @@ -1,19 +0,0 @@ -package ua.liqpay.utils - -import android.content.Context -import android.content.pm.PackageManager - -/** - * Check all permission is granted - * - * @param context [Context] - * @param permissions Array of permission - */ -fun grantedPermissions(context: Context, vararg permissions: String): Boolean { - permissions.forEach { - if (context.checkCallingOrSelfPermission(it) != PackageManager.PERMISSION_GRANTED) { - return false - } - } - return true -} \ No newline at end of file diff --git a/Liqpay/src/main/java/ua/liqpay/utils/PreferenceUtil.kt b/Liqpay/src/main/java/ua/liqpay/utils/PreferenceUtil.kt deleted file mode 100644 index 833b8b5..0000000 --- a/Liqpay/src/main/java/ua/liqpay/utils/PreferenceUtil.kt +++ /dev/null @@ -1,23 +0,0 @@ -package ua.liqpay.utils - -import android.content.Context -import java.util.* - -const val PREF_APP_NAME = "pref_liqpay" -const val PREF_DEVICE_ID = "pref_device_id" - -/** - * Generate and save device ID - * - * @param context [Context] - */ -fun getDeviceId(context: Context): String { - val preferences = context.getSharedPreferences(PREF_APP_NAME, Context.MODE_PRIVATE) - var hashDevice = preferences.getString(PREF_DEVICE_ID, null) - if (hashDevice == null) { - val deviceUuid = UUID.randomUUID().toString() - hashDevice = deviceUuid - preferences.edit().putString(PREF_DEVICE_ID, hashDevice).apply() - } - return hashDevice -} diff --git a/Liqpay/src/main/java/ua/liqpay/utils/SignatureUtil.kt b/Liqpay/src/main/java/ua/liqpay/utils/SignatureUtil.kt index 7f2b779..25ce43f 100644 --- a/Liqpay/src/main/java/ua/liqpay/utils/SignatureUtil.kt +++ b/Liqpay/src/main/java/ua/liqpay/utils/SignatureUtil.kt @@ -7,6 +7,6 @@ package ua.liqpay.utils * @param privateKey Liqpay private key * */ -fun signature(data: String, privateKey: String): String { +internal fun signature(data: String, privateKey: String): String { return ((privateKey + data + privateKey).sha1()).base64() } diff --git a/Liqpay/src/main/java/ua/liqpay/utils/URLEncodeUtil.kt b/Liqpay/src/main/java/ua/liqpay/utils/URLEncodeUtil.kt index 0b7c9c5..b3a865b 100644 --- a/Liqpay/src/main/java/ua/liqpay/utils/URLEncodeUtil.kt +++ b/Liqpay/src/main/java/ua/liqpay/utils/URLEncodeUtil.kt @@ -9,7 +9,7 @@ import java.util.* * A collection of utilities for encoding URLs. * */ -object URLEncodeUtil { +internal object URLEncodeUtil { private const val DEFAULT_CONTENT_CHARSET = "UTF-8" private const val PARAMETER_SEPARATOR = "&" diff --git a/Liqpay/src/main/java/ua/liqpay/view/LiqpayActivity.kt b/Liqpay/src/main/java/ua/liqpay/view/LiqpayActivity.kt index 664572b..70ad0ba 100644 --- a/Liqpay/src/main/java/ua/liqpay/view/LiqpayActivity.kt +++ b/Liqpay/src/main/java/ua/liqpay/view/LiqpayActivity.kt @@ -2,34 +2,52 @@ package ua.liqpay.view import android.annotation.SuppressLint import android.app.Activity -import android.app.ProgressDialog +import android.content.BroadcastReceiver import android.content.Context import android.content.Intent +import android.content.IntentFilter import android.content.pm.ActivityInfo import android.os.Bundle +import ua.liqpay.LIQPAY_BROADCAST_RECEIVER_ACTION +import ua.liqpay.LIQPAY_DATA_KEY +import ua.liqpay.LIQPAY_SIGNATURE_KEY import ua.liqpay.LIQPAY_URL_CHECKOUT import java.net.URLEncoder -const val BUNDLE_LIQPAY_DATA = "bundle_liqpay_data" +private const val BUNDLE_LIQPAY_DATA = "bundle_liqpay_data" +/** + * Liapay payment activity. + */ internal class LiqpayActivity : Activity() { - private lateinit var progressDialog: ProgressDialog + private lateinit var loadingDialog: LoaderViewDialog + private lateinit var eventReceiver: BroadcastReceiver override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT - progressDialog = ProgressDialog(this).apply { - setMessage("Loading...") - show() - } - progressDialog.dismiss() + loadingDialog = LoaderViewDialog(this) + initCancelPaymentReceiver() setContentView(createLiqpayView()) } override fun onDestroy() { super.onDestroy() - progressDialog.dismiss() + loadingDialog.dismiss() + } + + override fun onStart() { + super.onStart() + registerReceiver( + eventReceiver, + IntentFilter(LIQPAY_BROADCAST_RECEIVER_ACTION) + ) + } + + override fun onStop() { + super.onStop() + unregisterReceiver(eventReceiver) } /** @@ -42,6 +60,15 @@ internal class LiqpayActivity : Activity() { webChromeClient = WindowChromeClient { setContentView(createLiqpayView()) } + loadingListener = object : LoadingListener{ + override fun showLoading() { + loadingDialog.show() + } + + override fun hideLoading() { + loadingDialog.dismiss() + } + } } } @@ -52,6 +79,19 @@ internal class LiqpayActivity : Activity() { return intent.getStringExtra(BUNDLE_LIQPAY_DATA)?.toByteArray() ?: byteArrayOf() } + private fun initCancelPaymentReceiver(){ + eventReceiver = object : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + if (intent.action == LIQPAY_BROADCAST_RECEIVER_ACTION) { + val response = intent.getStringExtra(BUNDLE_DATA) + if (response.isNullOrEmpty()) { + finish() + } + } + } + } + } + companion object { /** @@ -66,7 +106,8 @@ internal class LiqpayActivity : Activity() { @JvmStatic internal fun start(context: Context, data: String, signature: String) { val intent = Intent(context, LiqpayActivity::class.java).apply { - putExtra(BUNDLE_LIQPAY_DATA, "data=" + URLEncoder.encode(data) + "&signature=" + signature) + putExtra(BUNDLE_LIQPAY_DATA, "${LIQPAY_DATA_KEY}=" + URLEncoder.encode(data) + + "&${LIQPAY_SIGNATURE_KEY}=" + signature) addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) } context.startActivity(intent) diff --git a/Liqpay/src/main/java/ua/liqpay/view/LiqpayView.kt b/Liqpay/src/main/java/ua/liqpay/view/LiqpayView.kt index 56409ce..3d2ed1a 100644 --- a/Liqpay/src/main/java/ua/liqpay/view/LiqpayView.kt +++ b/Liqpay/src/main/java/ua/liqpay/view/LiqpayView.kt @@ -3,7 +3,6 @@ package ua.liqpay.view import android.annotation.SuppressLint import android.content.Context import android.content.Intent -import android.os.Build import android.util.AttributeSet import android.util.Base64 import android.util.Base64.NO_WRAP @@ -12,7 +11,8 @@ import android.webkit.WebResourceRequest import android.webkit.WebView import android.webkit.WebViewClient import org.json.JSONObject -import ua.liqpay.BROADCAST_RECEIVER_ACTION +import ua.liqpay.LIQPAY_BROADCAST_RECEIVER_ACTION +import ua.liqpay.LIQPAY_DATA_KEY import ua.liqpay.PRIVAT24_APP_PACKAGE import ua.liqpay.PRIVAT24_APP_URI_SCHEME import ua.liqpay.utils.URLEncodeUtil @@ -22,16 +22,19 @@ import java.net.URI import java.net.URL private const val SUCCESS_URL_QUERY = "status=success" -private const val CANCEL_URL_PATH = "checkout/cancel" -private const val CALLBACK_PATH = "api/mob/callback" -private const val DATA_KEY = "data" +private const val CANCEL_URL_PATH = "/cancel" +private const val LIQPAY_HOST = "liqpay.ua" +internal const val BUNDLE_DATA = "data" @SuppressLint("SetJavaScriptEnabled") class LiqpayView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 ) : WebView(context, attrs, defStyleAttr) { + internal var loadingListener: LoadingListener? = null + init { + loadingListener?.showLoading() val cookieManager = CookieManager.getInstance() cookieManager.setAcceptCookie(true) cookieManager.setAcceptThirdPartyCookies(this, true) @@ -61,24 +64,51 @@ class LiqpayView @JvmOverloads constructor( override fun onPageFinished(view: WebView, url: String) { - + loadingListener?.hideLoading() super.onPageFinished(view, url) } override fun onLoadResource(view: WebView, link: String) { super.onLoadResource(view, link) val url = URL(link) - if ((url.path.contains(CALLBACK_PATH) && - url.query.contains(SUCCESS_URL_QUERY)) || - link.contains(CANCEL_URL_PATH) - ) { - val data = parseUrl(link) - sendEvent(data.toString()) + if (!url.host.contains(LIQPAY_HOST)) { + return + } + when { + url.query?.contains(SUCCESS_URL_QUERY) ?: false -> handleSuccessEvent(link) + link.contains(CANCEL_URL_PATH) -> handleCancelEvent() + else -> handleUnknownEvent(link) } } } } - + + /** + * Handle success payment status. + * + * @param link Redirect web link + */ + private fun handleSuccessEvent(link: String) { + val data = parseUrl(link) + sendEvent(data.toString()) + } + + /** + * Handle cancel payment status. + */ + private fun handleCancelEvent() { + sendEvent(null) + } + + /** + * Handle unknown payment status. + * + * @param link Redirect web link + */ + private fun handleUnknownEvent(link: String) { + + } + /** * Parse url data @@ -91,7 +121,7 @@ class LiqpayView @JvmOverloads constructor( val data = JSONObject() URLEncodeUtil.parse(URI(url), "UTF-8").forEach { try { - if (DATA_KEY == it.first) { + if (LIQPAY_DATA_KEY == it.first) { val item = JSONObject(String(Base64.decode(it.second, NO_WRAP))) merge(data, item) } else { @@ -110,9 +140,9 @@ class LiqpayView @JvmOverloads constructor( * @param data Shared data */ private fun sendEvent(data: String?) { - Intent(BROADCAST_RECEIVER_ACTION).apply { + Intent(LIQPAY_BROADCAST_RECEIVER_ACTION).apply { setPackage(context.packageName) - data?.let { putExtra(DATA_KEY, it) } + data?.let { putExtra(BUNDLE_DATA, it) } context.sendBroadcast(this) } } diff --git a/Liqpay/src/main/java/ua/liqpay/view/LoaderViewDialog.kt b/Liqpay/src/main/java/ua/liqpay/view/LoaderViewDialog.kt new file mode 100644 index 0000000..7bdfeab --- /dev/null +++ b/Liqpay/src/main/java/ua/liqpay/view/LoaderViewDialog.kt @@ -0,0 +1,29 @@ +package ua.liqpay.view + +import android.app.Dialog +import android.content.Context +import android.view.Gravity +import android.view.LayoutInflater +import android.widget.LinearLayout +import ua.liqpay.R + + +class LoaderViewDialog (context: Context) : Dialog(context, android.R.style.Theme_Translucent_NoTitleBar){ + + init { + val layoutInflater = + context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater + val params = LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT) + val winManager = window?.attributes + winManager?.gravity = Gravity.CENTER + window?.attributes = winManager + setTitle(null) + setCancelable(false) + setOnCancelListener(null) + val view = layoutInflater.inflate(R.layout.dialog_loading, null) + window?.setBackgroundDrawableResource(R.color.dialog_loading_background) + window?.attributes?.windowAnimations = R.style.DialogLoading + addContentView(view, params) + } + +} \ No newline at end of file diff --git a/Liqpay/src/main/java/ua/liqpay/view/LoadingListener.kt b/Liqpay/src/main/java/ua/liqpay/view/LoadingListener.kt new file mode 100644 index 0000000..eb1d9b1 --- /dev/null +++ b/Liqpay/src/main/java/ua/liqpay/view/LoadingListener.kt @@ -0,0 +1,9 @@ +package ua.liqpay.view + + +internal interface LoadingListener { + + fun showLoading() + + fun hideLoading() +} \ No newline at end of file diff --git a/Liqpay/src/main/res/anim/dialog_enter_animation.xml b/Liqpay/src/main/res/anim/dialog_enter_animation.xml new file mode 100644 index 0000000..f42dab9 --- /dev/null +++ b/Liqpay/src/main/res/anim/dialog_enter_animation.xml @@ -0,0 +1,8 @@ + + + + + \ No newline at end of file diff --git a/Liqpay/src/main/res/anim/dialog_exit_animation.xml b/Liqpay/src/main/res/anim/dialog_exit_animation.xml new file mode 100644 index 0000000..ec4199d --- /dev/null +++ b/Liqpay/src/main/res/anim/dialog_exit_animation.xml @@ -0,0 +1,11 @@ + + + + + + \ No newline at end of file diff --git a/Liqpay/src/main/res/layout/dialog_loading.xml b/Liqpay/src/main/res/layout/dialog_loading.xml new file mode 100644 index 0000000..5ca9972 --- /dev/null +++ b/Liqpay/src/main/res/layout/dialog_loading.xml @@ -0,0 +1,13 @@ + + + + + + \ No newline at end of file diff --git a/Liqpay/src/main/res/values/colors.xml b/Liqpay/src/main/res/values/colors.xml new file mode 100644 index 0000000..d3a5eaf --- /dev/null +++ b/Liqpay/src/main/res/values/colors.xml @@ -0,0 +1,4 @@ + + + #9AFFFFFF + \ No newline at end of file diff --git a/Liqpay/src/main/res/values/style.xml b/Liqpay/src/main/res/values/style.xml new file mode 100644 index 0000000..180b043 --- /dev/null +++ b/Liqpay/src/main/res/values/style.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/Liqpay/src/test/java/ua/liqpay/ExampleUnitTest.kt b/Liqpay/src/test/java/ua/liqpay/ExampleUnitTest.kt deleted file mode 100644 index ab23ec7..0000000 --- a/Liqpay/src/test/java/ua/liqpay/ExampleUnitTest.kt +++ /dev/null @@ -1,17 +0,0 @@ -package ua.liqpay - -import org.junit.Test - -import org.junit.Assert.* - -/** - * Example local unit test, which will execute on the development machine (host). - * - * See [testing documentation](http://d.android.com/tools/testing). - */ -class ExampleUnitTest { - @Test - fun addition_isCorrect() { - assertEquals(4, 2 + 2) - } -} \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index 4220203..2d2aa84 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -3,17 +3,15 @@ apply plugin: 'kotlin-android' apply plugin: 'kotlin-android-extensions' android { - compileSdkVersion 29 + compileSdkVersion 30 buildToolsVersion "29.0.3" defaultConfig { applicationId "ua.liqpaysystem" minSdkVersion 21 - targetSdkVersion 29 + targetSdkVersion 30 versionCode 1 versionName "1.0" - - testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } buildTypes { @@ -31,8 +29,5 @@ dependencies { implementation 'androidx.appcompat:appcompat:1.2.0' implementation 'androidx.constraintlayout:constraintlayout:2.0.1' implementation project(path: ':Liqpay') - testImplementation 'junit:junit:4.12' - androidTestImplementation 'androidx.test.ext:junit:1.1.2' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' } \ No newline at end of file diff --git a/app/src/androidTest/java/ua/liqpaysystem/ExampleInstrumentedTest.kt b/app/src/androidTest/java/ua/liqpaysystem/ExampleInstrumentedTest.kt deleted file mode 100644 index 514ae20..0000000 --- a/app/src/androidTest/java/ua/liqpaysystem/ExampleInstrumentedTest.kt +++ /dev/null @@ -1,24 +0,0 @@ -package ua.liqpaysystem - -import androidx.test.platform.app.InstrumentationRegistry -import androidx.test.ext.junit.runners.AndroidJUnit4 - -import org.junit.Test -import org.junit.runner.RunWith - -import org.junit.Assert.* - -/** - * Instrumented test, which will execute on an Android device. - * - * See [testing documentation](http://d.android.com/tools/testing). - */ -@RunWith(AndroidJUnit4::class) -class ExampleInstrumentedTest { - @Test - fun useAppContext() { - // Context of the app under test. - val appContext = InstrumentationRegistry.getInstrumentation().targetContext - assertEquals("ua.liqpaysystem", appContext.packageName) - } -} \ No newline at end of file diff --git a/app/src/main/java/ua/liqpaysystem/MainActivity.kt b/app/src/main/java/ua/liqpaysystem/MainActivity.kt index 9166aa2..b805d11 100644 --- a/app/src/main/java/ua/liqpaysystem/MainActivity.kt +++ b/app/src/main/java/ua/liqpaysystem/MainActivity.kt @@ -1,27 +1,41 @@ package ua.liqpaysystem -import androidx.appcompat.app.AppCompatActivity import android.os.Bundle +import android.util.Log +import androidx.appcompat.app.AppCompatActivity import ua.liqpay.LiqPay import ua.liqpay.LiqpayCallback -import ua.liqpay.request.ErrorCode -class MainActivity : AppCompatActivity() { +private const val LIQPAY_PRIVATE_KEY = "" +private const val LIQPAY_PUBLIC_KEY = "" + +class MainActivity : AppCompatActivity(), LiqpayCallback { + + private val TAG = MainActivity::class.java.simpleName + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) - LiqPay(this, object: LiqpayCallback{ - override fun onSuccess(response: String?) { - - } + val liqpay = LiqPay(context = this, callback = this) + liqpay.checkout( + privateKey = LIQPAY_PRIVATE_KEY, + publicKey = LIQPAY_PUBLIC_KEY, + amount = 1.0, + description = "test", + orderId = "1231423423" + ) + } - override fun onError(error: ErrorCode) { + override fun onSuccess(response: String?) { + Log.i(TAG, "Success payment. Response: $response") + } - } + override fun onError() { + Log.i(TAG, "An error occurred.") + } - }).checkout( - signature = "FmbJe0TaQueUjkXLGs1GLlnebW0=", - base64Data = "eyJ2ZXJzaW9uIjozLCJwdWJsaWNfa2V5Ijoic2FuZGJveF9pNzkwOTExMzgyOTIiLCJhY3Rpb24iOiJwYXkiLCJhbW91bnQiOiIyLjAiLCJjdXJyZW5jeSI6IlVBSCIsImRlc2NyaXB0aW9uIjoiXHUwNDFmXHUwNDNlXHUwNDNmXHUwNDNlXHUwNDNiXHUwNDNkXHUwNDM1XHUwNDNkXHUwNDM4XHUwNDM1IFx1MDQ0MVx1MDQ0N1x1MDQzNVx1MDQ0Mlx1MDQzMCBcdTA0MzIgXHUwNDNmXHUwNDQwXHUwNDM4XHUwNDNiXHUwNDNlXHUwNDM2XHUwNDM1XHUwNDNkXHUwNDM4XHUwNDM4IEhlbHAmSm9iIiwib3JkZXJfaWQiOiI3NzkzNzE3Ni1iZDQzLTQ4MTAtYTBmYi01ODU2YmQ3ZjQ4OGIiLCJzZXJ2ZXJfdXJsIjoiaHR0cHM6XC9cL2hlbHBuam9iLmlkZWlsLmNvbVwvYXBpXC9saXFwYXlcL2NhbGxiYWNrIn0=") + override fun onCancel() { + Log.i(TAG, "User canceled paymentю") } } \ No newline at end of file diff --git a/app/src/test/java/ua/liqpaysystem/ExampleUnitTest.kt b/app/src/test/java/ua/liqpaysystem/ExampleUnitTest.kt deleted file mode 100644 index 829403b..0000000 --- a/app/src/test/java/ua/liqpaysystem/ExampleUnitTest.kt +++ /dev/null @@ -1,17 +0,0 @@ -package ua.liqpaysystem - -import org.junit.Test - -import org.junit.Assert.* - -/** - * Example local unit test, which will execute on the development machine (host). - * - * See [testing documentation](http://d.android.com/tools/testing). - */ -class ExampleUnitTest { - @Test - fun addition_isCorrect() { - assertEquals(4, 2 + 2) - } -} \ No newline at end of file diff --git a/build.gradle b/build.gradle index 5454b25..3ef8fd5 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,6 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { - ext.kotlin_version = "1.3.72" + ext.kotlin_version = "1.4.31" repositories { google() jcenter() @@ -8,9 +8,6 @@ buildscript { dependencies { classpath 'com.android.tools.build:gradle:4.1.1' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - - // NOTE: Do not place your application dependencies here; they belong - // in the individual module build.gradle files } }