chore(telemetry): add payment funnel spans for Apple Pay and Google Wallet#53
chore(telemetry): add payment funnel spans for Apple Pay and Google Wallet#53
Conversation
✅ Snyk checks have passed. No issues have been found so far.
💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse. |
There was a problem hiding this comment.
Pull request overview
Adds React Native SDK payment-funnel observability via new OpenTelemetry spans across Apple Pay (native + WebView) and Google Wallet, and fixes an iOS native Apple Pay cancellation edge case so JS promises don’t remain unresolved. Also augments telemetry resource attributes to better segment RN traffic and adjusts release tooling/changelog behavior.
Changes:
- Add granular payment funnel spans (button press, tokenize success/failure, cancellation) for Apple Pay and Google Wallet.
- Fix iOS native Apple Pay module to reject the JS promise on user dismissal/cancellation.
- Add
bolt.platform='react-native'(and log full publishable key) on the OTel resource; hidechoreentries in release-it changelog sections.
Reviewed changes
Copilot reviewed 7 out of 8 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| src/telemetry/setup.ts | Adds bolt.platform resource attribute and switches publishable key attribute to full value. |
| src/telemetry/attributes.ts | Introduces new telemetry attribute keys (bolt.platform, payment.cancelled). |
| src/payments/GoogleWallet.tsx | Adds spans for button press/tokenize success/failure and a new cancellation branch. |
| src/payments/ApplePay.tsx | Adds spans for button press/tokenize success/failure and cancellation handling for native flow. |
| src/payments/ApplePayWebView.tsx | Emits a cancellation span for WebView Apple Pay cancellation events. |
| ios/ApplePayModule.swift | Adds promise settlement tracking and rejects on sheet dismissal when not otherwise settled. |
| example/ios/Podfile.lock | Updates example app pod lock to the new SDK version checksum. |
| .release-it.json | Hides chore entries from generated changelog sections. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
18ff9fe to
1891c71
Compare
1891c71 to
f9a6f97
Compare
iOS ApplePayModule:
- Move pendingResolve/pendingReject/promiseSettled writes onto the main
queue so the single-thread invariant the docstring promises actually
holds. Previously the initial reset ran on the RN bridge queue,
creating a race with main-queue PassKit delegate callbacks.
- Use present(completion:) and reject with PRESENT_FAILED when PassKit
can't present the sheet (invalid merchantId, missing entitlement,
etc.) — otherwise neither didAuthorize nor didFinish ever fires and
the JS promise hangs forever.
- Move the synthesized CANCELLED settleReject outside the dismiss
completion block. Apple's dismiss completion is not reliably
invoked in every path, so keeping the settlement inside it is a
leak risk.
- Log when settleReject runs with no pendingReject instead of
silently flipping the promiseSettled guard.
- Trim redundant comments.
JS:
- Cancellation is now silent across all three payment surfaces
(Apple Pay native, Apple Pay WebView, Google Pay) — no onError
call, to match the WebView's pre-existing behavior and avoid
surfacing "User cancelled" to merchant error handlers.
- Collapse zero-duration tokenize_success / tokenize_failure /
cancelled child spans into parentSpan.addEvent(...) on the
request_payment span so funnel markers correlate with the parent.
- Add a shared recordEvent() helper in tracer.ts for standalone
funnel markers (button_pressed) that precede the parent span.
Tests:
- Upgrade the startSpan mock to a jest.fn that captures span names
so tests can differentiate the spans being emitted.
- Add coverage for the native CANCELLED-code path on both Apple Pay
and Google Wallet — the prior test used Error('User cancelled')
without a .code property, which hit the fall-through tokenize_
failure path rather than the new CANCELLED branch.
- Add coverage asserting button_pressed event and tokenize_success
span-event emission.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
SanthoshCharanBolt
left a comment
There was a problem hiding this comment.
Where are we setting the session attributes? 👀
Description
Closes the payment funnel observability gap identified in the analytics & metrics analysis. Adds granular OTel funnel markers (button press, tokenize success/failure, user cancellation) across Apple Pay (WebView + native) and Google Wallet, bringing RN SDK span coverage in line with the web SDK's Counter/EventTracker funnel. Funnel markers inside an operation are attached to the parent
request_paymentspan viaspan.addEvent(...)so they correlate naturally in Grafana; standalone pre-operation events (button_pressed) go through a sharedrecordEvent(name, attrs)helper intelemetry/tracer.ts. Also addsbolt.platform='react-native'to the OTel resource so RN traffic can be filtered separately from the web SDK.Along the way, fixes correctness issues in the native iOS
ApplePayModule: the JS promise could previously leak on user cancel, on PassKit presentation failure, and on dismiss-completion drops. All mutation ofpendingResolve/pendingReject/promiseSettledis now serialized on the main queue (including the initial reset, which previously ran on the RN bridge method queue and raced with PassKit delegate callbacks);present(completion:)is used and rejectsPRESENT_FAILEDon!presented; the synthesizedCANCELLEDsettlement inpaymentAuthorizationControllerDidFinishis emitted outside the (unreliable)dismiss { }completion block; andsettleRejectnow logs when it fires with a nilpendingRejectrather than silently flipping the guard. Cancel is now silent across all three payment surfaces (noonError,SpanStatusCode.UNSETon the parent span) — matching the WebView's pre-existing behavior and bringing Google Wallet (which previously conflated cancel with tokenize failure) into alignment.Testing
All 162 Jest tests pass (155 pre-existing + 7 new). iOS Swift typechecks clean (
swiftc -typecheck). Coverage added in this PR:src/__tests__/ApplePay.test.tsx— silent CANCELLED branch (native rejection withcode: 'CANCELLED'→ noonError, notokenizecall, noreportAuthorizationResultcall,bolt.apple_pay.cancelledevent on parent span);button_pressedevent emission;tokenize_successspan-event emission on the happy path. The pre-existing "User cancelled" test (which usednew Error('User cancelled')without a.code) is retained and renamed to cover the generic-native-rejection fall-through path.src/__tests__/GoogleWallet.test.tsx— mirror CANCELLED coverage and funnel-marker assertions, plus the renamed generic-rejection test.startSpan: () => mockSpan) tojest.fn((name, attrs) => mockSpan)so tests can differentiate spans by name and assert onmockSpan.addEvent(name, attrs)instead of the prior lossy "any span fired" shape.Native iOS Swift coverage remains a gap — no iOS test target exists in the repo. The new state machine's invariants (at-most-once settlement, main-queue serialization, presentation-failure rejection, dismiss-drop resilience) are only validated through manual iOS simulator runs against the PassKit sandbox.
Security Review
Important
A security review is required for every PR in this repository to comply with PCI requirements.
Security Impact Summary
The publishable-key prefix logged to OTel is widened from 8 characters +
...to 12 characters +...insrc/telemetry/setup.ts. The publishable key is a non-secret, merchant-facing identifier (equivalent to a Stripe publishable key) and is already sent in plaintext in every API request and WebView URL — logging a slightly longer prefix to Grafana Cloud does not increase exposure or change the risk posture. The value remains truncated; the full key is not logged. No changes to authentication, authorization, tokenization logic, payment-data handling, or PCI-scoped storage. The native Apple Pay changes are correctness fixes to the promise state machine — no change to which fields ofPKPaymentare serialized or how the tokenizer API is called.