Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Separate Web & Native Link Activity Contract #9934

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,9 @@ interface ErrorReporter : FraudDetectionErrorReporter {
LINK_ATTACH_CARD_WITH_NULL_ACCOUNT(
partialEventName = "link.create_new_card.missing_link_account"
),
LINK_WEB_FAILED_TO_PARSE_RESULT_URI(
partialEventName = "link.web.result.parsing_failed"
),
PAYMENT_SHEET_AUTHENTICATORS_NOT_FOUND(
partialEventName = "paymentsheet.authenticators.not_found"
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,50 +3,29 @@ package com.stripe.android.link
import android.content.Context
import android.content.Intent
import androidx.activity.result.contract.ActivityResultContract
import com.stripe.android.PaymentConfiguration
import com.stripe.android.core.utils.FeatureFlags
import com.stripe.android.link.serialization.PopupPayload
import com.stripe.android.networking.StripeRepository
import javax.inject.Inject

internal class LinkActivityContract @Inject internal constructor(
private val stripeRepository: StripeRepository,
private val nativeLinkActivityContract: NativeLinkActivityContract,
private val webLinkActivityContract: WebLinkActivityContract
) : ActivityResultContract<LinkActivityContract.Args, LinkActivityResult>() {

override fun createIntent(context: Context, input: Args): Intent {
return if (FeatureFlags.nativeLinkEnabled.isEnabled) {
nativeIntent(context, input)
nativeLinkActivityContract.createIntent(context, input)
} else {
webIntent(context, input)
webLinkActivityContract.createIntent(context, input)
}
}

override fun parseResult(resultCode: Int, intent: Intent?): LinkActivityResult {
return createLinkActivityResult(resultCode, intent)
}

private fun webIntent(context: Context, input: Args): Intent {
val paymentConfiguration = PaymentConfiguration.getInstance(context)
val payload = PopupPayload.create(
configuration = input.configuration,
context = context,
publishableKey = paymentConfiguration.publishableKey,
stripeAccount = paymentConfiguration.stripeAccountId,
paymentUserAgent = stripeRepository.buildPaymentUserAgent(),
)
return LinkForegroundActivity.createIntent(context, payload.toUrl())
}

private fun nativeIntent(context: Context, input: Args): Intent {
val paymentConfiguration = PaymentConfiguration.getInstance(context)
return LinkActivity.createIntent(
context = context,
args = NativeLinkArgs(
configuration = input.configuration,
stripeAccountId = paymentConfiguration.stripeAccountId,
publishableKey = paymentConfiguration.publishableKey
)
)
val redirectUri = intent?.data
return if (redirectUri != null) {
webLinkActivityContract.parseResult(resultCode, intent)
} else {
nativeLinkActivityContract.parseResult(resultCode, intent)
}
}

data class Args internal constructor(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,8 @@
package com.stripe.android.link

import android.app.Activity
import android.content.Intent
import android.os.Parcelable
import android.util.Base64
import androidx.core.os.BundleCompat
import com.stripe.android.model.PaymentMethod
import com.stripe.android.model.parsers.PaymentMethodJsonParser
import kotlinx.parcelize.Parcelize
import org.json.JSONObject

internal sealed class LinkActivityResult : Parcelable {
/**
Expand Down Expand Up @@ -47,73 +41,3 @@ internal sealed class LinkActivityResult : Parcelable {
val error: Throwable
) : LinkActivityResult()
}

internal fun createLinkActivityResult(resultCode: Int, intent: Intent?): LinkActivityResult {
return when (resultCode) {
Activity.RESULT_CANCELED -> {
LinkActivityResult.Canceled()
}

LinkForegroundActivity.RESULT_FAILURE -> {
val exception = intent?.extras?.let {
BundleCompat.getSerializable(
it,
LinkForegroundActivity.EXTRA_FAILURE,
Exception::class.java
)
}
if (exception != null) {
LinkActivityResult.Failed(exception)
} else {
LinkActivityResult.Canceled()
}
}

LinkForegroundActivity.RESULT_COMPLETE -> {
val redirectUri = intent?.data ?: return LinkActivityResult.Canceled()
when (redirectUri.getQueryParameter("link_status")) {
"complete" -> {
val paymentMethod = redirectUri.getQueryParameter("pm")
?.parsePaymentMethod()
if (paymentMethod == null) {
LinkActivityResult.Canceled()
} else {
LinkActivityResult.PaymentMethodObtained(paymentMethod)
}
}

"logout" -> {
LinkActivityResult.Canceled(LinkActivityResult.Canceled.Reason.LoggedOut)
}

else -> {
LinkActivityResult.Canceled()
}
}
}

LinkActivity.RESULT_COMPLETE -> {
handleNativeLinkResult(intent)
}

else -> {
LinkActivityResult.Canceled()
}
}
}

private fun handleNativeLinkResult(intent: Intent?): LinkActivityResult {
val result = intent?.extras?.let {
BundleCompat.getParcelable(it, LinkActivityContract.EXTRA_RESULT, LinkActivityResult::class.java)
}
return result ?: LinkActivityResult.Canceled()
}

private fun String.parsePaymentMethod(): PaymentMethod? = try {
val decodedPaymentMethod = String(Base64.decode(this, 0), Charsets.UTF_8)
val paymentMethod = PaymentMethodJsonParser()
.parse(JSONObject(decodedPaymentMethod))
paymentMethod
} catch (e: Exception) {
null
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package com.stripe.android.link

import android.app.Activity
import android.content.Context
import android.content.Intent
import androidx.activity.result.contract.ActivityResultContract
import androidx.core.os.BundleCompat
import com.stripe.android.PaymentConfiguration
import javax.inject.Inject

internal class NativeLinkActivityContract @Inject constructor() :
ActivityResultContract<LinkActivityContract.Args, LinkActivityResult>() {
override fun createIntent(context: Context, input: LinkActivityContract.Args): Intent {
val paymentConfiguration = PaymentConfiguration.getInstance(context)
return LinkActivity.createIntent(
context = context,
args = NativeLinkArgs(
configuration = input.configuration,
stripeAccountId = paymentConfiguration.stripeAccountId,
publishableKey = paymentConfiguration.publishableKey
)
)
}

override fun parseResult(resultCode: Int, intent: Intent?): LinkActivityResult {
return when (resultCode) {
Activity.RESULT_CANCELED -> {
LinkActivityResult.Canceled()
}

LinkActivity.RESULT_COMPLETE -> {
val result = intent?.extras?.let {
BundleCompat.getParcelable(it, LinkActivityContract.EXTRA_RESULT, LinkActivityResult::class.java)
}
return result ?: LinkActivityResult.Canceled()
}

else -> {
LinkActivityResult.Canceled()
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package com.stripe.android.link

import android.app.Activity
import android.content.Context
import android.content.Intent
import android.util.Base64
import androidx.activity.result.contract.ActivityResultContract
import androidx.core.os.BundleCompat
import com.stripe.android.PaymentConfiguration
import com.stripe.android.core.exception.StripeException
import com.stripe.android.link.serialization.PopupPayload
import com.stripe.android.model.PaymentMethod
import com.stripe.android.model.parsers.PaymentMethodJsonParser
import com.stripe.android.networking.StripeRepository
import com.stripe.android.payments.core.analytics.ErrorReporter
import org.json.JSONObject
import javax.inject.Inject

internal class WebLinkActivityContract @Inject internal constructor(
private val stripeRepository: StripeRepository,
private val errorReporter: ErrorReporter
) : ActivityResultContract<LinkActivityContract.Args, LinkActivityResult>() {

override fun createIntent(context: Context, input: LinkActivityContract.Args): Intent {
val paymentConfiguration = PaymentConfiguration.getInstance(context)
val payload = PopupPayload.create(
configuration = input.configuration,
context = context,
publishableKey = paymentConfiguration.publishableKey,
stripeAccount = paymentConfiguration.stripeAccountId,
paymentUserAgent = stripeRepository.buildPaymentUserAgent(),
)
return LinkForegroundActivity.createIntent(context, payload.toUrl())
}

override fun parseResult(resultCode: Int, intent: Intent?): LinkActivityResult {
return when (resultCode) {
LinkForegroundActivity.RESULT_FAILURE -> {
val exception = intent?.extras?.let {
BundleCompat.getSerializable(
it,
LinkForegroundActivity.EXTRA_FAILURE,
Exception::class.java
)
}
if (exception != null) {
LinkActivityResult.Failed(exception)
} else {
LinkActivityResult.Canceled()
}
}

LinkForegroundActivity.RESULT_COMPLETE -> {
val redirectUri = intent?.data ?: return LinkActivityResult.Canceled()
when (redirectUri.getQueryParameter("link_status")) {
"complete" -> {
val paymentMethod = redirectUri.getQueryParameter("pm")
?.parsePaymentMethod()
if (paymentMethod == null) {
LinkActivityResult.Canceled()
} else {
LinkActivityResult.PaymentMethodObtained(paymentMethod)
}
}

"logout" -> {
LinkActivityResult.Canceled(LinkActivityResult.Canceled.Reason.LoggedOut)
}

else -> {
LinkActivityResult.Canceled()
}
}
}

Activity.RESULT_CANCELED -> {
LinkActivityResult.Canceled()
}
else -> {
LinkActivityResult.Canceled()
}
}
}

@SuppressWarnings("TooGenericExceptionCaught")
private fun String.parsePaymentMethod(): PaymentMethod? = try {
val decodedPaymentMethod = String(Base64.decode(this, 0), Charsets.UTF_8)
val paymentMethod = PaymentMethodJsonParser()
.parse(JSONObject(decodedPaymentMethod))
paymentMethod
} catch (e: Throwable) {
errorReporter.report(
errorEvent = ErrorReporter.UnexpectedErrorEvent.LINK_WEB_FAILED_TO_PARSE_RESULT_URI,
stripeException = object : StripeException(cause = e) {}
)
null
}
}
Loading
Loading