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

Add MerchantRepository for holding BraintreeClient properties #1202

Merged
merged 3 commits into from
Nov 5, 2024
Merged
Show file tree
Hide file tree
Changes from 2 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 @@ -17,35 +17,18 @@ import org.json.JSONObject
@Suppress("LargeClass", "LongParameterList", "TooManyFunctions")
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
class BraintreeClient internal constructor(

/**
* @suppress
*/
val applicationContext: Context,

/**
* @suppress
*/
val integrationType: IntegrationType,

/**
* @suppress
*/
val authorization: Authorization,

private val returnUrlScheme: String,

/**
* @suppress
*/
val appLinkReturnUri: Uri?,

applicationContext: Context,
integrationType: IntegrationType,
authorization: Authorization,
returnUrlScheme: String,
appLinkReturnUri: Uri?,
private val analyticsClient: AnalyticsClient = AnalyticsClient(applicationContext),
private val httpClient: BraintreeHttpClient = BraintreeHttpClient(),
private val graphQLClient: BraintreeGraphQLClient = BraintreeGraphQLClient(),
private val configurationLoader: ConfigurationLoader = ConfigurationLoader(applicationContext, httpClient),
private val manifestValidator: ManifestValidator = ManifestValidator(),
private val time: Time = Time(),
private val merchantRepository: MerchantRepository = MerchantRepository.instance
) {

private val crashReporter: CrashReporter
Expand Down Expand Up @@ -80,6 +63,16 @@ class BraintreeClient internal constructor(
// statistics access via the sdk console
crashReporter = CrashReporter(this)
crashReporter.start()

merchantRepository.let {
it.applicationContext = applicationContext
it.integrationType = integrationType
it.authorization = authorization
it.returnUrlScheme = returnUrlScheme
if (appLinkReturnUri != null) {
it.appLinkReturnUri = appLinkReturnUri
}
}
}

/**
Expand All @@ -88,11 +81,11 @@ class BraintreeClient internal constructor(
* @param callback [ConfigurationCallback]
*/
fun getConfiguration(callback: ConfigurationCallback) {
if (authorization is InvalidAuthorization) {
if (merchantRepository.authorization is InvalidAuthorization) {
callback.onResult(null, createAuthError())
return
}
configurationLoader.loadConfiguration(authorization) { configuration, configError, timing ->
configurationLoader.loadConfiguration(merchantRepository.authorization) { configuration, configError, timing ->
if (configuration != null) {
callback.onResult(configuration, null)
} else {
Expand Down Expand Up @@ -123,7 +116,7 @@ class BraintreeClient internal constructor(
experiment = params.experiment,
paymentMethodsDisplayed = params.paymentMethodsDisplayed
)
sendAnalyticsEvent(event, configuration, authorization)
sendAnalyticsEvent(event, configuration, merchantRepository.authorization)
}
}

Expand All @@ -136,7 +129,7 @@ class BraintreeClient internal constructor(
analyticsClient.sendEvent(
it,
event,
integrationType,
merchantRepository.integrationType,
authorization
)
}
Expand All @@ -146,13 +139,13 @@ class BraintreeClient internal constructor(
* @suppress
*/
fun sendGET(url: String, responseCallback: HttpResponseCallback) {
if (authorization is InvalidAuthorization) {
if (merchantRepository.authorization is InvalidAuthorization) {
responseCallback.onResult(null, createAuthError())
return
}
getConfiguration { configuration, configError ->
if (configuration != null) {
httpClient.get(url, configuration, authorization) { response, httpError ->
httpClient.get(url, configuration, merchantRepository.authorization) { response, httpError ->
response?.let {
try {
sendAnalyticsTimingEvent(url, response.timing)
Expand Down Expand Up @@ -180,7 +173,7 @@ class BraintreeClient internal constructor(
additionalHeaders: Map<String, String> = emptyMap(),
responseCallback: HttpResponseCallback,
) {
if (authorization is InvalidAuthorization) {
if (merchantRepository.authorization is InvalidAuthorization) {
responseCallback.onResult(null, createAuthError())
return
}
Expand All @@ -190,7 +183,7 @@ class BraintreeClient internal constructor(
path = url,
data = data,
configuration = configuration,
authorization = authorization,
authorization = merchantRepository.authorization,
additionalHeaders = additionalHeaders
) { response, httpError ->
response?.let {
Expand All @@ -214,7 +207,7 @@ class BraintreeClient internal constructor(
* @suppress
*/
fun sendGraphQLPOST(json: JSONObject?, responseCallback: HttpResponseCallback) {
if (authorization is InvalidAuthorization) {
if (merchantRepository.authorization is InvalidAuthorization) {
responseCallback.onResult(null, createAuthError())
return
}
Expand All @@ -223,7 +216,7 @@ class BraintreeClient internal constructor(
graphQLClient.post(
json?.toString(),
configuration,
authorization
merchantRepository.authorization
) { response, httpError ->
response?.let {
try {
Expand Down Expand Up @@ -262,7 +255,7 @@ class BraintreeClient internal constructor(
return if (launchesBrowserSwitchAsNewTask) {
braintreeDeepLinkReturnUrlScheme
} else {
returnUrlScheme
merchantRepository.returnUrlScheme
}
}

Expand All @@ -271,7 +264,7 @@ class BraintreeClient internal constructor(
*/
fun <T> isUrlSchemeDeclaredInAndroidManifest(urlScheme: String?, klass: Class<T>?): Boolean {
return manifestValidator.isUrlSchemeDeclaredInAndroidManifest(
applicationContext,
merchantRepository.applicationContext,
urlScheme,
klass
)
Expand All @@ -281,7 +274,7 @@ class BraintreeClient internal constructor(
* @suppress
*/
fun <T> getManifestActivityInfo(klass: Class<T>?): ActivityInfo? {
return manifestValidator.getActivityInfo(applicationContext, klass)
return manifestValidator.getActivityInfo(merchantRepository.applicationContext, klass)
}

/**
Expand All @@ -290,10 +283,10 @@ class BraintreeClient internal constructor(
internal fun reportCrash() =
getConfiguration { configuration, _ ->
analyticsClient.reportCrash(
applicationContext,
merchantRepository.applicationContext,
configuration,
integrationType,
authorization
merchantRepository.integrationType,
merchantRepository.authorization
)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.braintreepayments.api.core

import android.content.Context
import android.net.Uri
import androidx.annotation.RestrictTo

@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
class MerchantRepository {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if there's a clearer name for this class.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe a doc string for our internal understanding of what this class is for?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

➕ 1 I think some docstrings would help, I don't have any issues with the name since this naming seems like a common pattern on Android.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does the @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) prevent dokka from publishing to the generated docs?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like those internal classes do get published :(

https://braintree.github.io/braintree_android/BraintreeCore/com.braintreepayments.api.core/index.html

I'll look to see if there's a way to prevent this.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does the @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) prevent dokka from publishing to the generated docs?

Not sure if it does that, but you could add @suppress in the docs to suppress it.
Also see: Kotlin/dokka#2404 for a custom implementation we can do to suppress all @RestrictTo annotated methods.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dang, I thought there'd be support directly in Dokka for that. I've added some docs for this class.

I'll create a tech debt ticket for possibly adding a custom implementation to suppress based on the annotation.


lateinit var applicationContext: Context
lateinit var integrationType: IntegrationType
lateinit var authorization: Authorization
lateinit var returnUrlScheme: String
var appLinkReturnUri: Uri? = null

companion object {
val instance: MerchantRepository by lazy { MerchantRepository() }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ class BraintreeClientUnitTest {
private lateinit var manifestValidator: ManifestValidator
private lateinit var browserSwitchClient: BrowserSwitchClient
private lateinit var expectedAuthException: BraintreeException
private lateinit var merchantRepository: MerchantRepository

@Before
fun beforeEach() {
Expand All @@ -48,6 +49,7 @@ class BraintreeClientUnitTest {
analyticsClient = mockk(relaxed = true)
manifestValidator = mockk(relaxed = true)
browserSwitchClient = mockk(relaxed = true)
merchantRepository = mockk(relaxed = true)

val clientSDKSetupURL =
"https://developer.paypal.com/braintree/docs/guides/client-sdk/setup/android/v4#initialization"
Expand Down Expand Up @@ -402,24 +404,6 @@ class BraintreeClientUnitTest {
)
}

@Test
fun integrationType_returnsCustomByDefault() {
val context = ApplicationProvider.getApplicationContext<Context>()
val sut = BraintreeClient(context, authorization.toString())
assertEquals("custom", sut.integrationType.stringValue)
}

@Test
fun integrationType_returnsIntegrationTypeDefinedInConstructor() {
val context = ApplicationProvider.getApplicationContext<Context>()
val sut = BraintreeClient(
context = context,
authorization = authorization.toString(),
integrationType = IntegrationType.DROP_IN
)
assertEquals("dropin", sut.integrationType.stringValue)
}

@Test
@Throws(JSONException::class)
fun reportCrash_reportsCrashViaAnalyticsClient() {
Expand Down Expand Up @@ -447,20 +431,39 @@ class BraintreeClientUnitTest {
}
}

@Test
fun `when BraintreeClient is initialized, merchantRepository properties are set`() {
createBraintreeClient(merchantRepository = merchantRepository)
verify { merchantRepository.returnUrlScheme = "sample-return-url-scheme" }
verify { merchantRepository.applicationContext = applicationContext }
verify { merchantRepository.authorization = authorization }
verify { merchantRepository.appLinkReturnUri = Uri.parse("https://example.com") }
verify { merchantRepository.integrationType = IntegrationType.CUSTOM }
}

@Test
fun `when BraintreeClient is initialized and appLinkReturnUri is null, it is not set on the MerchantRepository`() {
createBraintreeClient(appLinkReturnUri = null, merchantRepository = merchantRepository)
verify(exactly = 0) { merchantRepository.appLinkReturnUri = null }
}

private fun createBraintreeClient(
configurationLoader: ConfigurationLoader,
time: Time = Time()
configurationLoader: ConfigurationLoader = mockk(),
time: Time = Time(),
appLinkReturnUri: Uri? = Uri.parse("https://example.com"),
merchantRepository: MerchantRepository = MerchantRepository.instance
) = BraintreeClient(
applicationContext = applicationContext,
integrationType = IntegrationType.CUSTOM,
authorization = authorization,
returnUrlScheme = "sample-return-url-scheme",
appLinkReturnUri = Uri.parse("https://example.com"),
appLinkReturnUri = appLinkReturnUri,
httpClient = braintreeHttpClient,
graphQLClient = braintreeGraphQLClient,
analyticsClient = analyticsClient,
manifestValidator = manifestValidator,
configurationLoader = configurationLoader,
time = time
time = time,
merchantRepository = merchantRepository,
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import com.braintreepayments.api.core.BraintreeClient
import com.braintreepayments.api.core.BraintreeException
import com.braintreepayments.api.core.Configuration
import com.braintreepayments.api.core.ErrorWithResponse.Companion.fromJson
import com.braintreepayments.api.core.MerchantRepository
import com.braintreepayments.api.core.MetadataBuilder
import com.braintreepayments.api.core.TokenizationKey
import com.braintreepayments.api.core.UserCanceledException
Expand All @@ -31,7 +32,8 @@ import org.json.JSONObject
class GooglePayClient internal constructor(
private val braintreeClient: BraintreeClient,
private val internalGooglePayClient: GooglePayInternalClient = GooglePayInternalClient(),
private val analyticsParamRepository: AnalyticsParamRepository = AnalyticsParamRepository.instance
private val analyticsParamRepository: AnalyticsParamRepository = AnalyticsParamRepository.instance,
private val merchantRepository: MerchantRepository = MerchantRepository.instance,
) {
/**
* Initializes a new [GooglePayClient] instance
Expand Down Expand Up @@ -154,7 +156,7 @@ class GooglePayClient internal constructor(
if (configuration != null) {
callback.onTokenizationParametersResult(
GooglePayTokenizationParameters.Success(
getTokenizationParameters(configuration, braintreeClient.authorization),
getTokenizationParameters(configuration, merchantRepository.authorization),
getAllowedCardNetworks(configuration)
)
)
Expand Down Expand Up @@ -201,7 +203,7 @@ class GooglePayClient internal constructor(
braintreeClient.getConfiguration { configuration: Configuration?, configError: Exception? ->

if (configuration?.isGooglePayEnabled == true) {
setGooglePayRequestDefaults(configuration, braintreeClient.authorization, request)
setGooglePayRequestDefaults(configuration, merchantRepository.authorization, request)

val paymentDataRequest =
PaymentDataRequest.fromJson(request.toJson())
Expand Down Expand Up @@ -315,7 +317,7 @@ class GooglePayClient internal constructor(
): PaymentMethodTokenizationParameters {

val metadata =
MetadataBuilder().integration(braintreeClient.integrationType)
MetadataBuilder().integration(merchantRepository.integrationType)
.sessionId(analyticsParamRepository.sessionId).version().build()

val version = try {
Expand Down Expand Up @@ -467,7 +469,7 @@ class GooglePayClient internal constructor(
.put("braintree:merchantId", configuration.merchantId)
.put(
"braintree:metadata", JSONObject().put("source", "client")
.put("integration", braintreeClient.integrationType)
.put("integration", merchantRepository.integrationType)
.put("sessionId", analyticsParamRepository.sessionId)
.put("version", googlePayVersion)
.put("platform", "android").toString()
Expand Down Expand Up @@ -507,7 +509,7 @@ class GooglePayClient internal constructor(
)
.put(
"braintree:metadata", JSONObject().put("source", "client")
.put("integration", braintreeClient.integrationType)
.put("integration", merchantRepository.integrationType)
.put("sessionId", analyticsParamRepository.sessionId)
.put("version", googlePayVersion)
.put("platform", "android").toString()
Expand Down
Loading
Loading