Skip to content

Commit

Permalink
Send PayPal correlation_id in analytics payload (#1108)
Browse files Browse the repository at this point in the history
  • Loading branch information
scannillo authored Oct 5, 2023
1 parent 61c2170 commit 431f1c9
Show file tree
Hide file tree
Showing 11 changed files with 45 additions and 21 deletions.
6 changes: 4 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
# Braintree iOS SDK Release Notes

## unreleased
* Send `tenant_name` in `event_params` to PayPal's analytics service (FPTI)
* Update `component` from `btmobilesdk` to `braintreeclientsdk` for PayPal's analytics service (FPTI)
* BraintreeCore
* Fix bug where `type` was always returned as `Unknown` in `fetchPaymentMethodNonces` (fixes #1099)
* Analytics
* Send `tenant_name` in `event_params` to PayPal's analytics service (FPTI)
* Update `component` from `btmobilesdk` to `braintreeclientsdk` for PayPal's analytics service (FPTI)
* Send `correlation_id`, when possible, in PayPal analytic events

## 6.6.0 (2023-08-22)
* BraintreePayPalNativeCheckout
Expand Down
10 changes: 6 additions & 4 deletions Sources/BraintreeCore/Analytics/BTAnalyticsService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,9 @@ class BTAnalyticsService: Equatable {
/// Events are queued and sent in batches to the analytics service, based on the status of the app and
/// the number of queued events. After exiting this method, there is no guarantee that the event has been sent.
/// - Parameter eventName: String representing the event name
func sendAnalyticsEvent(_ eventName: String, errorDescription: String? = nil) {
func sendAnalyticsEvent(_ eventName: String, errorDescription: String? = nil, correlationID: String? = nil) {
DispatchQueue.main.async {
self.enqueueEvent(eventName, errorDescription: errorDescription)
self.enqueueEvent(eventName, errorDescription: errorDescription, correlationID: correlationID)
self.flushIfAtThreshold()
}
}
Expand All @@ -59,10 +59,11 @@ class BTAnalyticsService: Equatable {
func sendAnalyticsEvent(
_ eventName: String,
errorDescription: String? = nil,
correlationID: String? = nil,
completion: @escaping (Error?) -> Void = { _ in }
) {
DispatchQueue.main.async {
self.enqueueEvent(eventName, errorDescription: errorDescription)
self.enqueueEvent(eventName, errorDescription: errorDescription, correlationID: correlationID)
self.flush(completion)
}
}
Expand Down Expand Up @@ -117,9 +118,10 @@ class BTAnalyticsService: Equatable {
// MARK: - Helpers

/// Adds an event to the queue
func enqueueEvent(_ eventName: String, errorDescription: String?) {
func enqueueEvent(_ eventName: String, errorDescription: String?, correlationID: String?) {
let timestampInMilliseconds = UInt64(Date().timeIntervalSince1970 * 1000)
let event = FPTIBatchData.Event(
correlationID: correlationID,
errorDescription: errorDescription,
eventName: eventName,
timestamp: String(timestampInMilliseconds)
Expand Down
2 changes: 2 additions & 0 deletions Sources/BraintreeCore/Analytics/FPTIBatchData.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,14 @@ struct FPTIBatchData: Codable {
/// Encapsulates a single event by it's name and timestamp.
struct Event: Codable {

let correlationID: String?
let errorDescription: String?
let eventName: String
let timestamp: String
let tenantName: String = "Braintree"

enum CodingKeys: String, CodingKey {
case correlationID = "correlation_id"
case errorDescription = "error_desc"
case eventName = "event_name"
case timestamp = "t"
Expand Down
3 changes: 2 additions & 1 deletion Sources/BraintreeCore/BTAPIClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -323,10 +323,11 @@ import Foundation

/// :nodoc: This method is exposed for internal Braintree use only. Do not use. It is not covered by Semantic Versioning and may change or be removed at any time.
@_documentation(visibility: private)
public func sendAnalyticsEvent(_ eventName: String, errorDescription: String? = nil) {
public func sendAnalyticsEvent(_ eventName: String, errorDescription: String? = nil, correlationID: String? = nil) {
analyticsService?.sendAnalyticsEvent(
eventName,
errorDescription: errorDescription,
correlationID: correlationID,
completion: { _ in }
)
}
Expand Down
10 changes: 6 additions & 4 deletions Sources/BraintreePayPal/BTPayPalClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ import BraintreeDataCollector
/// Exposed for testing the approvalURL construction
var approvalURL: URL? = nil

/// Exposed for testing the clientMetadataID associated with this request
/// Exposed for testing the clientMetadataID associated with this request.
/// Used in POST body for FPTI analytics & `/paypal_account` fetch.
var clientMetadataID: String? = nil

/// Exposed for testing the intent associated with this request
Expand Down Expand Up @@ -398,20 +399,21 @@ import BraintreeDataCollector
with result: BTPayPalAccountNonce,
completion: @escaping (BTPayPalAccountNonce?, Error?) -> Void
) {
apiClient.sendAnalyticsEvent(BTPayPalAnalytics.tokenizeSucceeded)
apiClient.sendAnalyticsEvent(BTPayPalAnalytics.tokenizeSucceeded, correlationID: clientMetadataID)
completion(result, nil)
}

private func notifyFailure(with error: Error, completion: @escaping (BTPayPalAccountNonce?, Error?) -> Void) {
apiClient.sendAnalyticsEvent(
BTPayPalAnalytics.tokenizeFailed,
errorDescription: error.localizedDescription
errorDescription: error.localizedDescription,
correlationID: clientMetadataID
)
completion(nil, error)
}

private func notifyCancel(completion: @escaping (BTPayPalAccountNonce?, Error?) -> Void) {
self.apiClient.sendAnalyticsEvent(BTPayPalAnalytics.browserLoginCanceled)
self.apiClient.sendAnalyticsEvent(BTPayPalAnalytics.browserLoginCanceled, correlationID: clientMetadataID)
completion(nil, BTPayPalError.canceled)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ import PayPalCheckout
@objc public class BTPayPalNativeCheckoutClient: NSObject {

private let apiClient: BTAPIClient

/// Used in POST body for FPTI analytics.
private var clientMetadataID: String? = nil

/// Initializes a PayPal Native client.
/// - Parameter apiClient: The Braintree API client
Expand Down Expand Up @@ -106,7 +109,9 @@ import PayPalCheckout
request: BTPayPalRequest,
completion: @escaping (BTPayPalNativeCheckoutAccountNonce?, Error?) -> Void
) {
clientMetadataID = request.riskCorrelationID ?? State.correlationIDs.riskCorrelationID
self.apiClient.sendAnalyticsEvent(BTPayPalNativeCheckoutAnalytics.tokenizeStarted)

let orderCreationClient = BTPayPalNativeOrderCreationClient(with: apiClient)
orderCreationClient.createOrder(with: request) { [weak self] result in
guard let self else {
Expand All @@ -131,22 +136,23 @@ import PayPalCheckout
action.set(billingAgreementToken: order.orderID)
@unknown default:
notifyFailure(with: BTPayPalNativeCheckoutError.invalidRequest, completion: completion)

}
},
onApprove: { [weak self] approval in
guard let self else {
completion(nil, BTPayPalNativeCheckoutError.deallocated)
return
}
self.clientMetadataID = approval.data.correlationIDs.riskCorrelationID

tokenize(approval: approval, request: request, completion: completion)
},
onCancel: {
self.notifyCancel(completion: completion)
},
onError: { error in
self.notifyFailure(with: BTPayPalNativeCheckoutError.checkoutSDKFailed, completion: completion)
self.clientMetadataID = error.correlationIDs.riskCorrelationID
self.notifyFailure(with: BTPayPalNativeCheckoutError.checkoutSDKFailed(error), completion: completion)
},
environment: order.environment
)
Expand Down Expand Up @@ -190,20 +196,21 @@ import PayPalCheckout
with result: BTPayPalNativeCheckoutAccountNonce,
completion: @escaping (BTPayPalNativeCheckoutAccountNonce?, Error?) -> Void
) {
apiClient.sendAnalyticsEvent(BTPayPalNativeCheckoutAnalytics.tokenizeSucceeded)
apiClient.sendAnalyticsEvent(BTPayPalNativeCheckoutAnalytics.tokenizeSucceeded, correlationID: clientMetadataID)
completion(result, nil)
}

private func notifyFailure(with error: Error, completion: @escaping (BTPayPalNativeCheckoutAccountNonce?, Error?) -> Void) {
apiClient.sendAnalyticsEvent(
BTPayPalNativeCheckoutAnalytics.tokenizeFailed,
errorDescription: error.localizedDescription
errorDescription: error.localizedDescription,
correlationID: clientMetadataID
)
completion(nil, error)
}

private func notifyCancel(completion: @escaping (BTPayPalNativeCheckoutAccountNonce?, Error?) -> Void) {
self.apiClient.sendAnalyticsEvent(BTPayPalNativeCheckoutAnalytics.tokenizeCanceled)
self.apiClient.sendAnalyticsEvent(BTPayPalNativeCheckoutAnalytics.tokenizeCanceled, correlationID: clientMetadataID)
completion(nil, BTPayPalNativeCheckoutError.canceled)
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import Foundation
import PayPalCheckout

/// Error returned from the native PayPal flow
enum BTPayPalNativeCheckoutError: Error, CustomNSError, LocalizedError, Equatable {
Expand Down Expand Up @@ -28,7 +29,7 @@ enum BTPayPalNativeCheckoutError: Error, CustomNSError, LocalizedError, Equatabl
case canceled

/// 7. PayPalCheckout SDK returned an error
case checkoutSDKFailed
case checkoutSDKFailed(PayPalCheckout.ErrorInfo)

/// 8. Tokenization with the Braintree Gateway failed
case tokenizationFailed(Error)
Expand Down Expand Up @@ -96,8 +97,8 @@ enum BTPayPalNativeCheckoutError: Error, CustomNSError, LocalizedError, Equatabl
return "Failed to create PayPal order: \(error.localizedDescription)"
case .canceled:
return "PayPal flow was canceled by the user."
case .checkoutSDKFailed:
return "PayPalCheckout SDK returned an error."
case .checkoutSDKFailed(let error):
return "PayPalCheckout SDK returned an error: \(error.description)"
case .tokenizationFailed(let error):
return "Tokenization with the Braintree Gateway failed: \(error.localizedDescription)"
case .parsingTokenizationResultFailed:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,13 @@ final class FPTIBatchData_Tests: XCTestCase {

let eventParams = [
FPTIBatchData.Event(
correlationID: "fake-correlation-id-1",
errorDescription: "fake-error-description-1",
eventName: "fake-event-1",
timestamp: "fake-time-1"
),
FPTIBatchData.Event(
correlationID: nil,
errorDescription: nil,
eventName: "fake-event-2",
timestamp: "fake-time-2"
Expand Down Expand Up @@ -81,5 +83,7 @@ final class FPTIBatchData_Tests: XCTestCase {
XCTAssertEqual(eventParams[1]["tenant_name"] as? String, "Braintree")
XCTAssertEqual(eventParams[0]["error_desc"] as? String, "fake-error-description-1")
XCTAssertNil(eventParams[1]["error_desc"])
XCTAssertEqual(eventParams[0]["correlation_id"] as? String, "fake-correlation-id-1")
XCTAssertNil(eventParams[1]["correlation_id"])
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,15 @@ class FakeAnalyticsService: BTAnalyticsService {
var lastEvent: String = ""
var didLastFlush: Bool = false

override func sendAnalyticsEvent(_ eventName: String, errorDescription: String? = nil) {
override func sendAnalyticsEvent(_ eventName: String, errorDescription: String? = nil, correlationID: String? = nil) {
self.lastEvent = eventName
self.didLastFlush = false
}

override func sendAnalyticsEvent(
_ eventName: String,
errorDescription: String? = nil,
correlationID: String? = nil,
completion: @escaping (Error?) -> Void = { _ in }
) {
self.lastEvent = eventName
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,6 @@ class BTPayPalNativeCheckoutClient_Tests: XCTestCase {
XCTAssertEqual(self.apiClient.postedAnalyticsEvents.last, BTPayPalNativeCheckoutAnalytics.tokenizeFailed)
}
}

// TODO: - Add remaining unit tests DTBTSDK-3076
}
2 changes: 1 addition & 1 deletion UnitTests/BraintreeTestShared/MockAPIClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ public class MockAPIClient: BTAPIClient {
completion([], nil)
}

public override func sendAnalyticsEvent(_ name: String, errorDescription: String? = nil) {
public override func sendAnalyticsEvent(_ name: String, errorDescription: String? = nil, correlationID: String? = nil) {
postedAnalyticsEvents.append(name)
}

Expand Down

0 comments on commit 431f1c9

Please sign in to comment.