Skip to content

[Woo POS] Report the refund calculation flow in analytics - #16419

Open
samiuelson wants to merge 5 commits into
trunkfrom
woomob-3750-android-refund-flow-telemetry
Open

[Woo POS] Report the refund calculation flow in analytics#16419
samiuelson wants to merge 5 commits into
trunkfrom
woomob-3750-android-refund-flow-telemetry

Conversation

@samiuelson

@samiuelson samiuelson commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

WOOMOB-3750

Description

The server-computed create and the classic create emit identical analytics, and the route-availability fallback emits nothing at all. During the woo_pos_server_refunds rollout that leaves us blind: no measure of server-flow adoption, no way to compare failure rates between the two flows, and no telemetry to attribute a "the totals look wrong" report to either one.

Mirrors the iOS change in woocommerce-ios#17716; the property name and values match so the funnels stay comparable across platforms.

Three additions:

  • refund_flow on the refund processing events. RefundProcessingStarted, RefundProcessingSuccess and RefundProcessingFailed become data classes carrying refund_flow (local / server_computed), with the values in a new WooPosAnalyticsEventConstant.RefundFlow enum. The value comes from isServerComputedRefundConfirmed(), extracted from buildSubmissionRequest so the reported flow is decided by the same predicate the submission path branches on and cannot drift from the path actually taken. Success and failure derive it from request.serverLineItems, the documented discriminator on the request.
  • api_error_code on RefundProcessingFailed. WooPosRefundSubmissionState.Failure gains an apiErrorCode field, populated from WooError.apiErrorCode where the backend refund result is handled. The message alone cannot do this — it is localized to the store and varies by wording, so deterministic server rejections were indistinguishable from transport failures. The property is omitted rather than sent empty when there is no code.
  • refund_server_flow_unavailable. Emitted from WooPosRefundPreview where the availability cache is marked unavailable, carrying the store's woocommerce_version (already in scope via flow.wooVersion). The version is what tells us whether the 11.1.0 gate is behaving or a store is genuinely too old. Because the cache short-circuits the resolver on later refunds, this counts stores that fell back rather than refunds.

RefundFlowStarted is deliberately left alone. It fires on the item-selection step, before the preview probe runs, so only eligibility is known there — a store that later 404s would report server_computed at start and local afterwards. Reporting a value that can be contradicted later is worse than reporting none, and the three processing events already cover the comparison we need. Worth revisiting if a start-to-outcome funnel split turns out to be needed.

Test Steps

./gradlew :WooCommerce:testVanillaDebugUnitTest --tests "com.woocommerce.android.ui.woopos.orders.details.refund.*" — 181 tests pass, including four new ones:

  • the processing events report server_computed on the server-computed path,
  • refund_flow is local on the classic path (the three pre-existing assertions, updated),
  • the API error code is reported on a rejection,
  • the fallback event fires with the store's WooCommerce version on a 404, and does not fire when the preview succeeds.

To check on device with woo_pos_server_refunds enabled: issue a POS refund on a WooCommerce 11.1.0+ store and confirm refund_processing_started carries refund_flow: server_computed; repeat on a pre-11.1.0 store and confirm refund_flow: local plus one refund_server_flow_unavailable for that store.

Note on detekt: the per-module ./gradlew :WooCommerce:detekt task fails locally with "Run failed with 1 invalid config property" on trunk as well as here, so it is not a usable local check. ./gradlew detektAll — the task CI runs — works, and did flag a real SpacingBetweenDeclarationsWithComments violation in this branch, now fixed.

Images/gif

N/A — no UI changes.


  • I have considered if this change warrants user-facing release notes and have added them to RELEASE-NOTES.txt if necessary.

The server-computed create and the classic create emitted identical
events, and the route-availability fallback emitted nothing, so a rollout
of woo_pos_server_refunds could not be measured: no adoption figure, no
way to compare failure rates between the flows, and no telemetry to
attribute a "totals look wrong" report to either.

Add a refund_flow property (local / server_computed) to the refund
processing events, carry the store's REST error code on failures so
deterministic rejections separate from transport errors, and emit a new
event when a preview probe finds the route missing, carrying the store's
WooCommerce version so a misjudged version gate is visible.

The reported flow is decided by the same predicate the submission path
branches on, extracted as isServerComputedRefundConfirmed(), so it cannot
drift from the path actually taken.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@samiuelson samiuelson added type: task An internally driven task. feature: POS category: tracks Related to analytics, including Tracks Events. status: feature-flagged Behind a feature flag. Milestone is not strongly held. labels Aug 14, 2026
@samiuelson samiuelson added this to the 25.5 milestone Aug 14, 2026
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@dangermattic

Copy link
Copy Markdown
Collaborator
1 Warning
⚠️ View files have been modified, but no screenshot or video is included in the pull request. Consider adding some for clarity.

Generated by 🚫 Danger

@claude claude Bot 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.

AI Code Review - No issues found. The changes look good.

The PR adds analytics instrumentation to the POS refund flow (a refund_flow local/server-computed dimension, the api_error_code on failures, and a new refund_server_flow_unavailable fallback event). Verified: error.apiErrorCode resolves correctly (error = result.error), AnalyticsTracker.KEY_API_ERROR_CODE exists, the raw "woocommerce_version" key matches existing events in the same file, and isServerComputedRefundConfirmed() / refundFlowFor(request) cannot drift because forComputedCreate returns a non-null list on the server branch. Unit tests cover both the local and server-computed flows plus the API-error-code failure path.

PR housekeeping
  • Applied repo-specific POS analytics guidance from AGENTS.md (WooPos* events use WooPosAnalyticsTracker / WooPosAnalyticsEvent, plain ViewModel).

Automatic review · claude-opus-4-8 · Workflow run

How to reply to a finding

Reply on this review (or inline at the line the finding refers to) with one of:

  • @claude addressed - I made the change. Bot verifies against the next diff before marking resolved.
  • @claude rejected: <reason> - Will not fix; reason gets quoted on the next review.
  • @claude not-applicable - Finding does not apply (wrong file, already covered elsewhere, etc.).

The bot honours these on the next review pass.

@wpmobilebot

wpmobilebot commented Aug 14, 2026

Copy link
Copy Markdown
Collaborator

App Icon📲 You can test the changes from this Pull Request in WooCommerce Android by scanning the QR code below to install the corresponding build.

App NameWooCommerce Android
Platform📱 Mobile
FlavorJalapeno
Build TypeDebug
Build Number778
Version25.4-rc-1
Application IDcom.woocommerce.android.prealpha
Commit63e2a5e
Installation URL1evshk3nf4jng
Automatticians: You can use our internal self-serve MC tool to give yourself access to those builds if needed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@samiuelson

Copy link
Copy Markdown
Contributor Author

Heads-up on overlap with #16338, which is approved and will likely merge first.

Both PRs add an apiErrorCode: String? to WooPosRefundSubmissionState.Failure, with the same name and the same semantics — this PR to report it in analytics, #16338 to suppress Retry on deterministic rejections. That is convergent rather than conflicting: one field serves both, and #16338 keeps the code raw rather than pre-mapping it to WooPosRefundApiError precisely so unrecognised codes still reach analytics.

Git will still flag it as a conflict in WooPosRefundSubmissionState.kt and WooPosRefundSubmissionProcessor.kt. Resolution is to keep one copy of the field; no behaviour to reconcile. I will rebase this branch once #16338 lands.

samiuelson and others added 2 commits August 14, 2026 16:32
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Reverses the previous commit, which stripped the Given/When/Then markers
from the new tests. The intent was the opposite: production code carries
no comments here, tests keep their structure markers.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

category: tracks Related to analytics, including Tracks Events. feature: POS status: feature-flagged Behind a feature flag. Milestone is not strongly held. type: task An internally driven task.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants