Skip to content

Commit

Permalink
Fropay for Russia Users [Android & Desktop] (#1046)
Browse files Browse the repository at this point in the history
* Organise the code and make it more readable.

* Added api changes for fropay.

* Updated flashlight and added plans-v4 API on desktop.

* Update paymentMethodFromJson method

* Added caching for payment methods and plans.

* fixed issue with monthly cost not showing up.

* Added support for plans-v4 support on android

* Parse fropay url.

* Reuse same method for multiple provider.

* Reuse same method on desktop and complete fropay support.

* Remove commented code.

* fetch plan only when user is generated.

* check if current state is null or not.

* Fixed crash when user choose freekassa.

* Removed unused code and added padding.

* update flashlight to v7.6.72

* updated safe guard.

* Fixed crash when user install app first time desktop.

* Fixed issue on protoc to use json.

* Remove oneMonthCost already set by internalsdk.

* If local is changed update payment methods.

* Update plans if language updates [android].

* Updates plans method.

* Update network config to support fropay.

* Change url variable name to redirectUrl.

* Use system browser for mac due to human verification issue.

---------

Co-authored-by: atavism <[email protected]>
  • Loading branch information
jigar-f and atavism authored Apr 24, 2024
1 parent e0b17b5 commit 927bec8
Show file tree
Hide file tree
Showing 20 changed files with 704 additions and 400 deletions.
2 changes: 1 addition & 1 deletion android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@

<receiver
android:name="org.getlantern.lantern.notification.NotificationReceiver"
android:exported="true">
android:exported="false">
<intent-filter>
<action android:name="org.getlantern.lantern.intent.VPN_DISCONNECTED" />
</intent-filter>
Expand Down
34 changes: 21 additions & 13 deletions android/app/src/main/kotlin/io/lantern/model/SessionModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -151,12 +151,6 @@ class SessionModel(
result.success(null)
}

"submitBitcoinPayment" -> paymentsUtil.submitBitcoinPayment(
call.argument("planID")!!,
call.argument("email")!!,
call.argument("refCode")!!,
result,
)

"submitGooglePlayPayment" -> paymentsUtil.submitGooglePlayPayment(
call.argument("email")!!,
Expand All @@ -173,8 +167,19 @@ class SessionModel(
result,
)

"generatePaymentRedirectUrl" -> paymentsUtil.generatePaymentRedirectUrl(
call.argument("planID")!!,
call.argument("email")!!,
call.argument("provider")!!,
result,
)

"userStatus" -> userStatus(result)
"updatePaymentPlans" -> updatePaymentMethods(result)
"setLanguage" -> {
LanternApp.getSession().setLanguage(call.argument("lang"))
fetchPaymentMethods(result)
}
else -> super.doOnMethodCall(call, result)
}
}
Expand All @@ -183,11 +188,14 @@ class SessionModel(
return when (call.method) {
"openWebview" -> {
val url = call.argument("url") ?: ""
url.isNotEmpty().let {
if (url.isNotEmpty()) {
val intent = Intent(activity, WebViewActivity_::class.java)
intent.putExtra("url", url)
intent.putExtra("url", url.trim())
activity.startActivity(intent)
} else {
throw IllegalArgumentException("No URL provided for webview")
}

}

"trackUserAction" -> {
Expand All @@ -201,10 +209,6 @@ class SessionModel(
LanternApp.getSession().acceptTerms()
}

"setLanguage" -> {
LanternApp.getSession().setLanguage(call.argument("lang"))
}

"setPaymentTestMode" -> {
LanternApp.getSession().setPaymentTestMode(call.argument("on") ?: false)
activity.restartApp()
Expand Down Expand Up @@ -303,10 +307,14 @@ class SessionModel(
}

private fun fetchPaymentMethods(result: MethodChannel.Result?) {
lanternClient.plansV3(object : LanternHttpClient.PlansV3Callback {
lanternClient.plansV4(object : LanternHttpClient.PlansV3Callback {
override fun onSuccess(
proPlans: Map<String, ProPlan>, paymentMethods: List<PaymentMethods>
) {
Logger.debug(
TAG,
"Successfully payment proplan $proPlans and methods $paymentMethods"
)
processPaymentMethods(proPlans, paymentMethods)
result?.success("Payment method successfully updated")
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -325,7 +325,7 @@ class MainActivity : FlutterActivity(), MethodChannel.MethodCallHandler,
}

private fun updatePaymentMethods() {
lanternClient.plansV3(
lanternClient.plansV4(
object : PlansV3Callback {
override fun onFailure(
throwable: Throwable?,
Expand All @@ -339,7 +339,7 @@ class MainActivity : FlutterActivity(), MethodChannel.MethodCallHandler,
paymentMethods: List<PaymentMethods>,

) {
Logger.debug(TAG, "Successfully fetched payment methods")
Logger.debug(TAG, "Successfully fetched payment methods with payment methods: $paymentMethods and plans $proPlans")
processPaymentMethods(proPlans, paymentMethods)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import android.content.Intent
import android.view.View
import android.webkit.WebView
import android.webkit.WebViewClient
import android.widget.ImageView
import android.widget.ProgressBar
import com.google.gson.JsonObject
import okhttp3.FormBody
Expand Down Expand Up @@ -45,6 +46,11 @@ open class FreeKassaActivity : BaseFragmentActivity() {
@JvmField
protected var webView: WebView? = null

@ViewById
@JvmField
protected var closeButton: ImageView? = null


@ViewById
@JvmField
protected var progressBar: ProgressBar? = null
Expand All @@ -64,6 +70,9 @@ open class FreeKassaActivity : BaseFragmentActivity() {
@AfterViews
fun afterViews() {
assertIntentExtras()
closeButton?.setOnClickListener(View.OnClickListener {
finish()
})
makeRequestToPrepayHandler(
{ transactionID: String -> displayWebView(transactionID) },
{ error: String -> showErrorDialog(error) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,10 @@ enum class PaymentMethod(val method: String) {
Freekassa("freekassa"),

@SerializedName("paymentwall")
PaymentWall("paymentwall")
PaymentWall("paymentwall"),

@SerializedName("fropay")
FroPay("fropay")
}

data class PaymentMethods(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,10 @@ enum class PaymentProvider(val provider: String) {
ResellerCode("reseller-code"),

@SerializedName("paymentwall")
PaymentWall("paymentwall")
PaymentWall("paymentwall"),

@SerializedName("fropay")
Fropay("fropay")
}

data class ProviderInfo(
Expand All @@ -28,3 +31,16 @@ data class ProviderInfo(
var logoUrl: List<String>
)

fun String.toPaymentProvider(): PaymentProvider? {
return when (this) {
"stripe" -> PaymentProvider.Stripe
"freekassa" -> PaymentProvider.Freekassa
"googleplay" -> PaymentProvider.GooglePlay
"btcpay" -> PaymentProvider.BTCPay
"reseller-code" -> PaymentProvider.ResellerCode
"paymentwall" -> PaymentProvider.PaymentWall
"fropay" -> PaymentProvider.Fropay
else -> null
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,61 @@ open class LanternHttpClient : HttpClient() {
}


fun plansV4(
cb: PlansV3Callback,
) {
val params =
mapOf(
"locale" to LanternApp.getSession().language,
"countrycode" to LanternApp.getSession().getCountryCode(),
)
get(
createProUrl("/plans-v4", params),
object : ProCallback {
override fun onFailure(
throwable: Throwable?,
error: ProError?,
) {
Logger.error(TAG, "Unable to fetch plans", throwable)
cb.onFailure(throwable, error)
}

override fun onSuccess(
response: Response?,
result: JsonObject?,
) {
Logger.d(TAG, "Plans v3 Response body $result")
val methods =
parseData<Map<String, List<PaymentMethods>>>(
result?.get("providers").toString(),
)
val icons = parseData<Icons>(result?.get("icons").toString())
Logger.d(TAG, "Plans v3 Icons Response body $icons")
val providers = methods.get("android")
// Due to API limitations
// We need loop all the provider and info since we can multiple provider with multiple methods
providers?.let {
providers.forEach { it ->
it.providers.forEach { provider ->
val icons = result?.get("icons")?.asJsonObject
val logoJson = icons?.get(
provider.name.toString().lowercase()
)!!.asJsonArray
val logoUrlsList: List<String> =
logoJson?.map { it.asString } ?: emptyList()
provider.logoUrl = logoUrlsList
}
}
}
val fetched = parseData<List<ProPlan>>(result?.get("plans").toString())
val plans = plansMap(fetched)
if (providers != null) cb.onSuccess(plans, providers)
}
},
)
}

@Deprecated("plansV3 has been deprecated Use plansV4 instead")
fun plansV3(
cb: PlansV3Callback,
) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import org.getlantern.lantern.model.LanternSessionManager
import org.getlantern.lantern.model.PaymentProvider
import org.getlantern.lantern.model.ProError
import org.getlantern.lantern.model.ProUser
import org.getlantern.lantern.model.toPaymentProvider
import org.getlantern.mobilesdk.Logger

class PaymentsUtil(private val activity: Activity) {
Expand Down Expand Up @@ -97,58 +98,90 @@ class PaymentsUtil(private val activity: Activity) {
}
}

fun submitBitcoinPayment(

fun generatePaymentRedirectUrl(
planID: String,
email: String,
refCode: String,
provider: String,
methodCallResult: MethodChannel.Result,
) {
try {
val provider = PaymentProvider.BTCPay.toString().lowercase()
val provider = provider.toPaymentProvider().toString().lowercase()
if (provider == null) {
methodCallResult.error(
"unknownError",
"$provider is unavailable", // This error message is localized Flutter-side
null,
)
return
}
val params =
mutableMapOf<String, String>(
mutableMapOf(
"email" to email,
"plan" to planID,
"provider" to provider,
"deviceName" to session.deviceName(),
)
lanternClient.get(
LanternHttpClient.createProUrl("/payment-redirect", params),
object : ProCallback {
override fun onFailure(
throwable: Throwable?,
error: ProError?,
) {
Logger.error(TAG, "BTCPay is unavailable", throwable)
methodCallResult.error(
"unknownError",
"BTCPay is unavailable", // This error message is localized Flutter-side
null,
)
return
}

override fun onSuccess(
response: Response?,
result: JsonObject?,
) {
Logger.debug(
TAG,
"Email successfully validated $email",
)
methodCallResult.success(result.toString())
}
},
)
sendPaymentRedirectRequest(params, object : ProCallback {
override fun onFailure(
throwable: Throwable?,
error: ProError?,
) {
Logger.error(TAG, "$provider is unavailable ", throwable)
methodCallResult.error(
"unknownError",
"$provider is unavailable", // This error message is localized Flutter-side
null,
)
return
}

override fun onSuccess(
response: Response?,
result: JsonObject?,
) {
val providerUrl = result!!.get("redirect").asString
Logger.debug(
TAG,
"$provider url is $providerUrl",
)

methodCallResult.success(providerUrl)
}
})
} catch (t: Throwable) {
methodCallResult.error(
"unknownError",
"BTCPay is unavailable", // This error message is localized Flutter-side
"$provider is unavailable", // This error message is localized Flutter-side
null,
)
}
}

private fun sendPaymentRedirectRequest(params: Map<String, String>, proCallback: ProCallback) {
lanternClient.get(
LanternHttpClient.createProUrl("/payment-redirect", params),
object : ProCallback {
override fun onFailure(
throwable: Throwable?,
error: ProError?,
) {
proCallback.onFailure(throwable, error)
return
}

override fun onSuccess(
response: Response?,
result: JsonObject?,
) {
proCallback.onSuccess(response, result)

}
},
)
}

// getPlanYear splits the given plan ID by hyphen and returns the year the given startas with
private fun getPlanYear(planID: String): String {
var plan = planID
Expand Down
19 changes: 16 additions & 3 deletions android/app/src/main/res/layout/freekassa_layout.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,27 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical">

<fragment xmlns:lantern="http://schemas.android.com/apk/res-auto"
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
class="org.getlantern.lantern.fragment.TitleBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
lantern:titleText="@string/lantern_pro_checkout" />
app:title="@string/lantern_pro_checkout"
>

<ImageView
android:id="@+id/closeButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@android:drawable/ic_menu_close_clear_cancel"
app:tint="@color/black"
android:layout_marginEnd="8dp"
android:layout_gravity="end"
/>

</androidx.appcompat.widget.Toolbar>

<RelativeLayout
android:layout_width="match_parent"
Expand Down
Loading

0 comments on commit 927bec8

Please sign in to comment.