Skip to content

Commit e921c43

Browse files
anibalb2500Anibal Bojorquezjaxdesmarais
authored
Funding Source Query Parameter and Instrumentation (#1511)
* added funding_source to universal link & updated instrumentation * adding unit tests * updated changelog * added kdoc and updated visibility * set funding_source in AnalyticsParamRepository * linting * Update CHANGELOG.md --------- Co-authored-by: Anibal Bojorquez <[email protected]> Co-authored-by: Jax DesMarais-Leder <[email protected]>
1 parent 2fdca24 commit e921c43

File tree

14 files changed

+138
-9
lines changed

14 files changed

+138
-9
lines changed

BraintreeCore/src/main/java/com/braintreepayments/api/core/AnalyticsApi.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ internal class AnalyticsApi(
8989
.putOpt(FPTI_KEY_ERROR_DESC, event.errorDescription)
9090
.putOpt(FPTI_KEY_CONTEXT_TYPE, if (event.isVaultRequest) "BA-TOKEN" else "EC-TOKEN")
9191
.putOpt(FPTI_KEY_PAYPAL_ATTEMPTED_APP_SWITCH, event.didSdkAttemptAppSwitch)
92+
.putOpt(FPTI_KEY_FUNDING_SOURCE, event.fundingSource)
9293
}
9394

9495
@Throws(JSONException::class)
@@ -146,6 +147,7 @@ internal class AnalyticsApi(
146147
private const val FPTI_KEY_PAYPAL_ATTEMPTED_APP_SWITCH = "attempted_app_switch"
147148
private const val FPTI_KEY_ERROR_DESC = "error_desc"
148149
private const val FPTI_KEY_CONTEXT_TYPE = "context_type"
150+
private const val FPTI_KEY_FUNDING_SOURCE = "funding_source"
149151

150152
private const val FPTI_BATCH_KEY_VENMO_INSTALLED = "venmo_installed"
151153
private const val FPTI_BATCH_KEY_PAYPAL_INSTALLED = "paypal_installed"

BraintreeCore/src/main/java/com/braintreepayments/api/core/AnalyticsClient.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ class AnalyticsClient internal constructor(
3535
buttonOrder = analyticsEventParams.buttonOrder,
3636
pageType = analyticsEventParams.pageType,
3737
errorDescription = analyticsEventParams.errorDescription,
38+
fundingSource = analyticsParamRepository.fundingSource,
3839
didEnablePayPalAppSwitch = analyticsParamRepository.didEnablePayPalAppSwitch,
3940
didPayPalServerAttemptAppSwitch = analyticsParamRepository.didPayPalServerAttemptAppSwitch,
4041
didSdkAttemptAppSwitch = analyticsParamRepository.didSdkAttemptAppSwitch,

BraintreeCore/src/main/java/com/braintreepayments/api/core/AnalyticsEvent.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,5 @@ internal data class AnalyticsEvent(
2424
val didEnablePayPalAppSwitch: Boolean? = null,
2525
val didPayPalServerAttemptAppSwitch: Boolean? = null,
2626
val didSdkAttemptAppSwitch: Boolean? = null,
27+
val fundingSource: String? = null
2728
)

BraintreeCore/src/main/java/com/braintreepayments/api/core/AnalyticsParamRepository.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,11 @@ class AnalyticsParamRepository(
3131
*/
3232
var didSdkAttemptAppSwitch: Boolean? = null
3333

34+
/**
35+
* Value to determine whether PayPal, PayLater or PayPal Credit was clicked.
36+
*/
37+
var fundingSource: String? = null
38+
3439
/**
3540
* Session ID to tie analytics events together which is used for reporting conversion funnels.
3641
*/
@@ -48,6 +53,7 @@ class AnalyticsParamRepository(
4853
didEnablePayPalAppSwitch = null
4954
didPayPalServerAttemptAppSwitch = null
5055
didSdkAttemptAppSwitch = null
56+
fundingSource = null
5157
}
5258

5359
companion object {

BraintreeCore/src/test/java/com/braintreepayments/api/core/AnalyticsApiUnitTest.kt

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,8 @@ class AnalyticsApiUnitTest {
5959
didEnablePayPalAppSwitch = true,
6060
didPayPalServerAttemptAppSwitch = true,
6161
didSdkAttemptAppSwitch = true,
62-
errorDescription = "error-description"
62+
errorDescription = "error-description",
63+
fundingSource = "funding-source"
6364
)
6465

6566
private val tokenizationKeyEvent = AnalyticsEvent(
@@ -80,7 +81,8 @@ class AnalyticsApiUnitTest {
8081
didEnablePayPalAppSwitch = true,
8182
didPayPalServerAttemptAppSwitch = true,
8283
didSdkAttemptAppSwitch = true,
83-
errorDescription = "error-description"
84+
errorDescription = "error-description",
85+
fundingSource = "funding-source"
8486
)
8587

8688
@Before
@@ -200,6 +202,7 @@ class AnalyticsApiUnitTest {
200202
"experiment": "${tokenizationKeyEvent.experiment}",
201203
"shopper_session_id": "${tokenizationKeyEvent.shopperSessionId}",
202204
"error_desc": "${tokenizationKeyEvent.errorDescription}",
205+
"funding_source": "${tokenizationKeyEvent.fundingSource}",
203206
"event_name": "${tokenizationKeyEvent.name}",
204207
"button_position": "${tokenizationKeyEvent.buttonOrder}",
205208
"context_type": "EC-TOKEN",
@@ -258,6 +261,7 @@ class AnalyticsApiUnitTest {
258261
"experiment": "${clientTokenEvent.experiment}",
259262
"shopper_session_id": "${clientTokenEvent.shopperSessionId}",
260263
"error_desc": "${clientTokenEvent.errorDescription}",
264+
"funding_source": "${clientTokenEvent.fundingSource}",
261265
"event_name": "${clientTokenEvent.name}",
262266
"button_position": "${clientTokenEvent.buttonOrder}",
263267
"context_type": "BA-TOKEN",

BraintreeCore/src/test/java/com/braintreepayments/api/core/AnalyticsClientUnitTest.kt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ class AnalyticsClientUnitTest {
2626
private val eventName = "sample-event-name"
2727
private val timestamp = 123L
2828
private val linkType = LinkType.APP_LINK
29+
private val fundingSource = "paypal"
2930

3031
private lateinit var sut: AnalyticsClient
3132

@@ -40,7 +41,7 @@ class AnalyticsClientUnitTest {
4041
shopperSessionId = "shopper-session-id",
4142
buttonType = "button-type",
4243
buttonOrder = "button-order",
43-
pageType = "page-type",
44+
pageType = "page-type"
4445
)
4546

4647
private val expectedAnalyticsEvent = AnalyticsEvent(
@@ -61,6 +62,7 @@ class AnalyticsClientUnitTest {
6162
didEnablePayPalAppSwitch = true,
6263
didPayPalServerAttemptAppSwitch = true,
6364
didSdkAttemptAppSwitch = true,
65+
fundingSource = fundingSource
6466
)
6567

6668
@Before
@@ -71,6 +73,7 @@ class AnalyticsClientUnitTest {
7173
every { analyticsParamRepository.didEnablePayPalAppSwitch } returns true
7274
every { analyticsParamRepository.didPayPalServerAttemptAppSwitch } returns true
7375
every { analyticsParamRepository.didSdkAttemptAppSwitch } returns true
76+
every { analyticsParamRepository.fundingSource } returns fundingSource
7477

7578
configurationLoader = MockkConfigurationLoaderBuilder()
7679
.configuration(configuration)

BraintreeCore/src/test/java/com/braintreepayments/api/core/AnalyticsParamRepositoryUnitTest.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ class AnalyticsParamRepositoryUnitTest {
1414

1515
private val uuid = "test-uuid"
1616
private val newUuid = "new-uuid"
17+
private val expectedFundingSource = "funding-source"
1718

1819
@Before
1920
fun setUp() {
@@ -24,6 +25,7 @@ class AnalyticsParamRepositoryUnitTest {
2425
sut.didPayPalServerAttemptAppSwitch = true
2526
sut.didSdkAttemptAppSwitch = true
2627
sut.didEnablePayPalAppSwitch = true
28+
sut.fundingSource = expectedFundingSource
2729
}
2830

2931
@Test
@@ -43,12 +45,14 @@ class AnalyticsParamRepositoryUnitTest {
4345
assertEquals(true, sut.didPayPalServerAttemptAppSwitch)
4446
assertEquals(true, sut.didEnablePayPalAppSwitch)
4547
assertEquals(true, sut.didSdkAttemptAppSwitch)
48+
assertEquals(expectedFundingSource, sut.fundingSource)
4649

4750
sut.reset()
4851

4952
assertEquals(uuid, sut.sessionId)
5053
assertNull(sut.didPayPalServerAttemptAppSwitch)
5154
assertNull(sut.didEnablePayPalAppSwitch)
5255
assertNull(sut.didSdkAttemptAppSwitch)
56+
assertNull(sut.fundingSource)
5357
}
5458
}

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
# Braintree Android SDK Release Notes
22

3+
## unreleased
4+
5+
* PayPal
6+
* Pass `fundingSource` to the app switch url link and to analytics events.
7+
38
## 5.21.0 (2026-01-05)
49

510
* UIComponents
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package com.braintreepayments.api.paypal
2+
3+
/**
4+
* Funding sources available when creating a PayPal payment.
5+
*/
6+
internal enum class PayPalFundingSource(val value: String) {
7+
/**
8+
* Standard PayPal balance or linked bank account funding.
9+
*/
10+
PAYPAL("paypal"),
11+
12+
/**
13+
* PayPal Pay Later BNPL products for short-term installments (e.g. Pay in 4,
14+
* Pay Monthly in the US).
15+
*/
16+
PAY_LATER("paylater"),
17+
18+
/**
19+
* PayPal Credit revolving line of credit.
20+
*/
21+
CREDIT("credit"),
22+
}
23+
24+
/**
25+
* Returns the [PayPalFundingSource] of a PayPalRequest based on its type and parameters
26+
*/
27+
internal fun PayPalRequest.getFundingSource(): PayPalFundingSource = when {
28+
this is PayPalCheckoutRequest && shouldOfferPayLater -> PayPalFundingSource.PAY_LATER
29+
shouldOfferCredit -> PayPalFundingSource.CREDIT
30+
else -> PayPalFundingSource.PAYPAL
31+
}

PayPal/src/main/java/com/braintreepayments/api/paypal/PayPalInternalClient.kt

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,6 @@ internal class PayPalInternalClient(
141141
}
142142
dataCollector.getClientMetadataId(context, dataCollectorRequest, configuration)
143143
}
144-
145144
val returnLink: String = when (val returnLinkResult = getReturnLinkUseCase(parsedRedirectUri)) {
146145
is GetReturnLinkUseCase.ReturnLinkResult.AppLink -> returnLinkResult.appLinkReturnUri.toString()
147146
is GetReturnLinkUseCase.ReturnLinkResult.DeepLink -> returnLinkResult.deepLinkFallbackUrlScheme
@@ -159,8 +158,8 @@ internal class PayPalInternalClient(
159158
)
160159
if (getAppSwitchUseCase()) {
161160
if (!contextId.isNullOrEmpty()) {
162-
val flowType = if (payPalRequest.isBillingAgreement()) "va" else "ecs"
163-
val uri = createAppSwitchUri(parsedRedirectUri, configuration.merchantId, flowType)
161+
val merchantId = configuration.merchantId
162+
val uri = createAppSwitchUri(parsedRedirectUri, merchantId, payPalRequest)
164163
paymentAuthRequest.approvalUrl = uri.toString()
165164
} else {
166165
callback.onResult(null, BraintreeException("Missing Token for PayPal App Switch."))
@@ -183,15 +182,23 @@ internal class PayPalInternalClient(
183182
* These parameters support observability for BT SDK app switch integrations.
184183
*
185184
* @param uri The base [Uri] to build upon.
186-
* @param flowType The checkout flow type.
187185
* @param merchantId The merchant ID for the integration.
186+
* @param payPalRequest The original [PayPalRequest] associated to the request.
188187
*/
189-
private fun createAppSwitchUri(uri: Uri, merchantId: String, flowType: String): Uri {
188+
private fun createAppSwitchUri(
189+
uri: Uri,
190+
merchantId: String,
191+
payPalRequest: PayPalRequest
192+
): Uri {
193+
val flowType = if (payPalRequest.isBillingAgreement()) "va" else "ecs"
194+
val fundingSource = payPalRequest.getFundingSource()
195+
190196
return uri.buildUpon()
191197
.appendQueryParameter("source", "braintree_sdk")
192198
.appendQueryParameter("switch_initiated_time", System.currentTimeMillis().toString())
193199
.appendQueryParameter("merchant", merchantId)
194200
.appendQueryParameter("flow_type", flowType)
201+
.appendQueryParameter("fundingSource", fundingSource.value)
195202
.build()
196203
}
197204

0 commit comments

Comments
 (0)