Skip to content

Improve Analytics#444

Open
anibalb2500 wants to merge 13 commits into
paypal:feature/app-switchfrom
anibalb2500:analytics_improvement
Open

Improve Analytics#444
anibalb2500 wants to merge 13 commits into
paypal:feature/app-switchfrom
anibalb2500:analytics_improvement

Conversation

@anibalb2500

@anibalb2500 anibalb2500 commented Jun 5, 2026

Copy link
Copy Markdown

Thank you for your contribution to PayPal.

Before submitting this PR, note that we cannot accept language translation PRs.
We have a dedicated localization team to provide the translations. If there is
an error in a specific translation, you may open an issue and we will escalate
it to the localization team.

Summary of changes

PaymentButtons — introduce typed analytics wrapper

PaymentButton previously fired analytics events inline using a bare AnalyticsService.
This PR introduces a thin typed wrapper layer consistent with how other modules handle analytics:

  • Add PaymentButtonEvent enum with typed INITIALIZED and TAPPED constants, following
    the pattern established by CheckoutEvent/VaultEvent (PayPalWebPayments) and
    ApproveOrderEvent/VaultEvent (CardPayments)
  • Add PaymentButtonAnalytics wrapper class delegating to AnalyticsService, consistent
    with CardAnalytics and PayPalWebAnalytics in other modules
  • Refactor setOnClickListener override to dispatch TAPPED through the new wrapper

CorePayments — generic analytics params and vault token fix

AnalyticsService.sendAnalyticsEvent previously leaked checkout and button concepts
(orderId, buttonType) into the core layer. These are now replaced with a generic
AnalyticsEventParams data class so AnalyticsService and AnalyticsEventData have no
knowledge of what the params mean.

  • sendAnalyticsEvent(name, orderId?, buttonType?)sendAnalyticsEvent(name, params: AnalyticsEventParams)
  • AnalyticsEventParams holds typed fields: orderId, vaultSetupToken, buttonType, appSwitchEnabled
  • TrackingEventsAPI maps known FPTI keys (order_id, vault_setup_token) from the params
    struct when building the serialized request — the wire format is unchanged
  • Fix: vault notify in PayPalWebAnalytics and CardAnalytics was passing setup token as
    orderId; now uses dedicated vaultSetupToken field
  • New vault_setup_token FPTI param wired through TrackingEventData

CorePayments — minor logging and documentation improvements

  • Extract "[PayPal SDK]" log tag into a named TAG constant to eliminate duplication
  • Correct log severity on analytics failure paths (Log.dLog.w / Log.e)
  • Add KDoc to CoreConfig documenting that FPTI analytics always route to the live endpoint
    regardless of the configured Environment

Evidence

Web checkout flow validated end-to-end using Order-ID 2DH291271Y007604B

Analytics.Improvement.Evidence.6-12.mov
image

Web vault flow validated end-to-end using vault_setup_token 5C810075PJ396333L

Vault.Evidence.mov
image

Buttons Regression Testing

Buttons.Regression.mov
image

Note I removed the following improvements from PaymentButton that were previously added, to stay in scope of our work

  • Click Listener not working when added via XML
  • Sending dynamic 'partner-client-id' analytics param as other modules.

Checklist

  • Added a changelog entry

Authors

  • @anbojorquez

@anibalb2500
anibalb2500 marked this pull request as ready for review June 5, 2026 17:48
* [com.paypal.android.paymentbuttons.analytics.PaymentButtonAnalytics].
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
interface AnalyticsServiceWrapper {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

q: What's the purpose of this interface?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

It enforces a consistent pattern across all analytics wrappers in the SDK. Every wrapper (CardAnalytics, PayPalWebAnalytics, PaymentButtonAnalytics) must hold an AnalyticsService instance.

Without the interface, that's just a convention with nothing enforcing it. It also makes the pattern self-documenting for anyone adding a new analytics wrapper in the future.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I'm still confused about this "wrapper" concept. The genericness of the Wrapper suffix feels like a code smell–if it had a legitimate purpose it would arguably have a better name explaining the role that it takes. We also have no declarations in this PR of a variable of type AnalyticsServiceWrapper meaning this interface isn't being used.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Removing the wrapper.

* Internal so subclasses can fire INITIALIZED in their own init blocks.
*/
internal val analytics: PaymentButtonAnalytics? =
coreConfig?.let { PaymentButtonAnalytics(AnalyticsService(context, it)) }

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

qq: CoreConfig may be null here and we won't get analytics. I feel like merchant's probably won't be instantiating these buttons programmatically (they'll probably be inflated via XML). Would merchants have to set CoreConfig programatically after the view is inflated?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

I intentionally removed XML attribute support for CoreConfig after discussion in the Slack thread.

Since Android Views are moving to maintenance mode, won't merchants be using compose?

I can re-add support for passing in clientId and env in XML attributes, if that's what's preferred Happy to do it.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

We'll still need support for XML since technically merchants will be able to use these views in XML layouts by virtual of them being Android Views. If we want to avoid this, we will have to author pure Composables (which isn't a bad idea, the buttons have a considerable amount of technical debt and there are new button designs in the wild that we have yet to adopt).

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Added analytics support for PaymentButtons added via XML

Comment thread PaymentButtons/src/main/java/com/paypal/android/paymentbuttons/PaymentButton.kt Outdated
verify(exactly = 1) {
analyticsService.sendAnalyticsEvent(
"payment-button:tapped",
params = mapOf(AnalyticsParams.BUTTON_TYPE to "Pay Later")

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Suggested change
params = mapOf(AnalyticsParams.BUTTON_TYPE to "Pay Later")
params = mapOf(AnalyticsParams.BUTTON_TYPE to "pay_later")

random q: Would snake_case or hyphen-case work better here for analytics? I'm not sure if there are any rules around the formatting about the values we send.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

I'm not sure, but that's something we can discuss separately and address as a follow up. Android and iOS both need to have consistent naming here.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Yeah we'll want these values to align across both iOS and Android (and possibly JS where possible).

@sshropshire

Copy link
Copy Markdown
Collaborator

@anibalb2500 we may need to figure out a branching strategy when @kgangineni gets back. This may belong in a feature branch (or at least go into develop).

@349446840464 349446840464 left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Can I ask about the sandbox new resperatory analizer , as new SQL that you create? With data files set?

Comment thread CHANGELOG.md Outdated
@anibalb2500
anibalb2500 changed the base branch from main to app-switch June 9, 2026 16:49
… architecture

PaymentButtons:
- Add PaymentButtonEvent enum (INITIALIZED, TAPPED) and PaymentButtonAnalytics
  wrapper, following the same pattern as CheckoutEvent/VaultEvent (PayPalWebPayments)
  and ApproveOrderEvent/VaultEvent (CardPayments)
- Replace AnalyticsService(clientId='N/A', environment=Environment.LIVE) in
  PaymentButton with an optional CoreConfig constructor param — analytics is
  created from the provided config; if none is given analytics is null and no
  events fire, eliminating fake-credential events hitting live FPTI
- INITIALIZED fires in each subclass init block; TAPPED fires via performClick()
  so it fires regardless of whether a click listener is set
- Make PaymentButton always clickable (isClickable = true) so performClick()
  is reachable on XML-inflated buttons with no click listener

CorePayments:
- sendAnalyticsEvent(name, orderId?, buttonType?) replaced with
  sendAnalyticsEvent(name, params: Map<String, String>) — AnalyticsService
  no longer knows about checkout or button concepts
- Add AnalyticsParams object with ORDER_ID, SETUP_TOKEN_ID, and BUTTON_TYPE
  constants accessible across all SDK modules
- Add AnalyticsServiceWrapper interface enforcing that every analytics wrapper
  holds an AnalyticsService; implemented by CardAnalytics, PayPalWebAnalytics,
  and PaymentButtonAnalytics
- Vault event paths in CardAnalytics and PayPalWebAnalytics now correctly use
  AnalyticsParams.SETUP_TOKEN_ID instead of ORDER_ID
- Extract log tag in AnalyticsService into a constant; correct log severity
  on failure paths (Log.d -> Log.w / Log.e)
- Add KDoc to CoreConfig documenting the intentional live-endpoint override

Tests:
- Add PaymentButtonAnalyticsTest and PaymentButtonTest with Robolectric
- Add test infrastructure (MockK, Robolectric, includeAndroidResources) to
  PaymentButtons module which had no prior test setup
- Update AnalyticsServiceTest and TrackingEventsAPIUnitTest for new signatures
@anibalb2500
anibalb2500 force-pushed the analytics_improvement branch from f7367dc to 0b0c696 Compare June 9, 2026 16:54
@anibalb2500
anibalb2500 force-pushed the analytics_improvement branch from 9017e6a to 529dacb Compare June 9, 2026 17:08
@anibalb2500
anibalb2500 force-pushed the analytics_improvement branch from 529dacb to 9fbd980 Compare June 9, 2026 17:22
@anibalb2500

Copy link
Copy Markdown
Author

@sshropshire I've rebased the PR and refactored the event params. Please take a look when you get a chance.

Comment thread Demo/src/main/res/layout/pay_later_button_test_layout.xml Outdated
Comment thread PaymentButtons/src/main/java/com/paypal/android/paymentbuttons/PaymentButton.kt Outdated
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants