Skip to content

Commit

Permalink
Shopper insights button analytics -- add new params (#1187)
Browse files Browse the repository at this point in the history
* Add provision to send in merchant provided data for analytics events

* update demo

* update changelog.md
  • Loading branch information
saperi22 authored Oct 17, 2024
1 parent 5309f50 commit bbcc87e
Show file tree
Hide file tree
Showing 8 changed files with 96 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,9 @@ internal class AnalyticsClient(
.putOpt(FPTI_KEY_START_TIME, event.startTime)
.putOpt(FPTI_KEY_END_TIME, event.endTime)
.putOpt(FPTI_KEY_ENDPOINT, event.endpoint)
.putOpt(FPTI_KEY_MERCHANT_EXPERIMENT, event.experiment)
.putOpt(FPTI_KEY_MERCHANT_PAYMENT_METHODS_DISPLAYED,
event.paymentMethodsDisplayed.ifEmpty { null })
return json.toString()
}

Expand Down Expand Up @@ -265,6 +268,8 @@ internal class AnalyticsClient(
private const val FPTI_KEY_START_TIME = "start_time"
private const val FPTI_KEY_END_TIME = "end_time"
private const val FPTI_KEY_ENDPOINT = "endpoint"
private const val FPTI_KEY_MERCHANT_EXPERIMENT = "experiment"
private const val FPTI_KEY_MERCHANT_PAYMENT_METHODS_DISPLAYED = "payment_methods_displayed"

private const val FPTI_BATCH_KEY_VENMO_INSTALLED = "venmo_installed"
private const val FPTI_BATCH_KEY_PAYPAL_INSTALLED = "paypal_installed"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
package com.braintreepayments.api.core

/**
* DTO for analytics events. See also: [AnalyticsEventParams]
* This class is internal to core module and is used in [AnalyticsClient] to construct the analytics
* payload to be sent to the backend.
*/
internal data class AnalyticsEvent(
val name: String,
val timestamp: Long,
Expand All @@ -8,5 +13,7 @@ internal data class AnalyticsEvent(
val isVaultRequest: Boolean = false,
val startTime: Long? = null,
val endTime: Long? = null,
val endpoint: String? = null
val endpoint: String? = null,
val experiment: String? = null,
val paymentMethodsDisplayed: List<String> = emptyList()
)
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,30 @@ package com.braintreepayments.api.core

import androidx.annotation.RestrictTo

/**
* DTO for analytics events. See also: [AnalyticsEvent]
* It is a catch-all data class for any parameters any of the modules wants to send. As such, not
* all parameters are required at each call site.
*
* @property payPalContextId Used for linking events from the client to server side request.
* @property linkType Indicates whether a deeplink or an app link was used to launch the app. Also see [LinkType].
* @property isVaultRequest Indicates whether the request was a BillingAgreement(BA)/Vault request.
* @property startTime [HttpResponseTiming] start time.
* @property endTime [HttpResponseTiming] end time.
* @property endpoint The endpoint being called.
* @property experiment Currently a ShopperInsights module specific event that indicates
* the experiment, as a JSON string, that the merchant sent to the us.
* @property paymentMethodsDisplayed A ShopperInsights module specific event that indicates the
* order of payment methods displayed to the shopper by the merchant.
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
data class AnalyticsEventParams @JvmOverloads constructor(
var payPalContextId: String? = null,
var linkType: String? = null,
var isVaultRequest: Boolean = false,
var startTime: Long? = null,
var endTime: Long? = null,
var endpoint: String? = null
var endpoint: String? = null,
val experiment: String? = null,
val paymentMethodsDisplayed: List<String> = emptyList()
)
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,8 @@ class BraintreeClient @VisibleForTesting internal constructor(
startTime = params.startTime,
endTime = params.endTime,
endpoint = params.endpoint,
experiment = params.experiment,
paymentMethodsDisplayed = params.paymentMethodsDisplayed
)
sendAnalyticsEvent(event, configuration, authorization)
}
Expand Down
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Braintree Android SDK Release Notes

## unreleased

* Shopper Insights (BETA)
* For analytics, send `experiment` as a parameter to `getRecommendedPaymentMethods` method
* For analytics, send `experiment` and `paymentMethodsDisplayed` analytic metrics to FPTI via the button presented event methods

## 5.1.0 (2024-10-15)

* PayPal
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -184,18 +184,25 @@ class ShopperInsightsFragment : BaseFragment() {
}

shopperInsightsClient.getRecommendedPaymentMethods(
request
request,
"dummy_experiment"
) { result ->
when (result) {
is ShopperInsightsResult.Success -> {
if (result.response.isPayPalRecommended) {
payPalVaultButton.isEnabled = true
shopperInsightsClient.sendPayPalPresentedEvent()
shopperInsightsClient.sendPayPalPresentedEvent(
"dummy_paypal_presented_experiment",
listOf("PayPal, Apple Pay, Google Pay")
)
}

if (result.response.isVenmoRecommended) {
venmoButton.isEnabled = true
shopperInsightsClient.sendVenmoPresentedEvent()
shopperInsightsClient.sendVenmoPresentedEvent(
"dummy_venmo_presented_experiment",
listOf("Apple Pay, Venmo, Google Pay")
)
}

responseTextView.text =
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.braintreepayments.api.shopperinsights

import android.content.Context
import com.braintreepayments.api.core.AnalyticsEventParams
import com.braintreepayments.api.core.AnalyticsParamRepository
import com.braintreepayments.api.core.BraintreeClient
import com.braintreepayments.api.core.BraintreeException
Expand Down Expand Up @@ -43,17 +44,22 @@ class ShopperInsightsClient internal constructor(
* Retrieves recommended payment methods based on the provided shopper insights request.
*
* @param request The [ShopperInsightsRequest] containing information about the shopper.
* @param experiment optional JSON string representing an experiment you want to run
* @return A [ShopperInsightsResult] object indicating the recommended payment methods.
* Note: This feature is in beta. Its public API may change or be removed in future releases
* PayPal recommendation is only available for US, AU, FR, DE, ITA, NED, ESP, Switzerland and
* UK merchants. Venmo recommendation is only available for US merchants.
*/
fun getRecommendedPaymentMethods(
request: ShopperInsightsRequest,
experiment: String? = null,
callback: ShopperInsightsCallback
) {
analyticsParamRepository.resetSessionId()
braintreeClient.sendAnalyticsEvent(GET_RECOMMENDED_PAYMENTS_STARTED)
braintreeClient.sendAnalyticsEvent(
GET_RECOMMENDED_PAYMENTS_STARTED,
AnalyticsEventParams(experiment = experiment)
)

if (request.email == null && request.phone == null) {
callbackFailure(
Expand Down Expand Up @@ -153,9 +159,22 @@ class ShopperInsightsClient internal constructor(
/**
* Call this method when the PayPal button has been successfully displayed to the buyer.
* This method sends analytics to help improve the Shopper Insights feature experience.
*
* @param experiment optional JSON string representing an experiment you want to run
* @param paymentMethodsDisplayed optional The list of available payment methods,
* rendered in the same order in which they are displayed
*/
fun sendPayPalPresentedEvent() {
braintreeClient.sendAnalyticsEvent(PAYPAL_PRESENTED)
fun sendPayPalPresentedEvent(
experiment: String? = null,
paymentMethodsDisplayed: List<String> = emptyList()
) {
braintreeClient.sendAnalyticsEvent(
PAYPAL_PRESENTED,
AnalyticsEventParams(
experiment = experiment,
paymentMethodsDisplayed = paymentMethodsDisplayed
)
)
}

/**
Expand All @@ -169,9 +188,22 @@ class ShopperInsightsClient internal constructor(
/**
* Call this method when the Venmo button has been successfully displayed to the buyer.
* This method sends analytics to help improve the Shopper Insights feature experience.
*
* @param experiment optional JSON string representing an experiment you want to run
* @param paymentMethodsDisplayed optional The list of available payment methods,
* rendered in the same order in which they are displayed
*/
fun sendVenmoPresentedEvent() {
braintreeClient.sendAnalyticsEvent(VENMO_PRESENTED)
fun sendVenmoPresentedEvent(
experiment: String? = null,
paymentMethodsDisplayed: List<String> = emptyList()
) {
braintreeClient.sendAnalyticsEvent(
VENMO_PRESENTED,
AnalyticsEventParams(
experiment = experiment,
paymentMethodsDisplayed = paymentMethodsDisplayed
)
)
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.braintreepayments.api.shopperinsights

import android.content.Context
import androidx.test.core.app.ApplicationProvider
import com.braintreepayments.api.core.AnalyticsEventParams
import com.braintreepayments.api.core.AnalyticsParamRepository
import com.braintreepayments.api.core.Authorization
import com.braintreepayments.api.core.BraintreeClient
Expand Down Expand Up @@ -51,23 +52,24 @@ class ShopperInsightsClientUnitTest {

@Test
fun `when getRecommendedPaymentMethods is called, session id is reset`() {
sut.getRecommendedPaymentMethods(mockk(relaxed = true), mockk(relaxed = true))
sut.getRecommendedPaymentMethods(mockk(relaxed = true), "some_experiment", mockk(relaxed = true))

verify { analyticsParamRepository.resetSessionId() }
}

@Test
fun `when getRecommendedPaymentMethods is called, started event is sent`() {
sut.getRecommendedPaymentMethods(mockk(relaxed = true), mockk(relaxed = true))
val experiment = "some_experiment"
sut.getRecommendedPaymentMethods(mockk(relaxed = true), experiment, mockk(relaxed = true))

verifyStartedAnalyticsEvent()
verifyStartedAnalyticsEvent(AnalyticsEventParams(experiment = experiment))
}

@Test
fun `when getRecommendedPaymentMethods is called, failed event is sent`() {
val request = ShopperInsightsRequest(null, null)

sut.getRecommendedPaymentMethods(request, mockk(relaxed = true))
sut.getRecommendedPaymentMethods(request, "some_experiment", mockk(relaxed = true))

verifyFailedAnalyticsEvent()
}
Expand Down Expand Up @@ -442,15 +444,15 @@ class ShopperInsightsClientUnitTest {
val apiCallbackSlot = slot<EligiblePaymentsCallback>()
every { api.findEligiblePayments(any(), capture(apiCallbackSlot)) } just runs

sut.getRecommendedPaymentMethods(request, callback)
sut.getRecommendedPaymentMethods(request, "some_experiment", callback)

apiCallbackSlot.captured.onResult(result = result, error = error)
}

private fun verifyStartedAnalyticsEvent() {
private fun verifyStartedAnalyticsEvent(params: AnalyticsEventParams = AnalyticsEventParams()) {
verify {
braintreeClient
.sendAnalyticsEvent("shopper-insights:get-recommended-payments:started")
.sendAnalyticsEvent("shopper-insights:get-recommended-payments:started", params)
}
}

Expand Down

0 comments on commit bbcc87e

Please sign in to comment.