Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Send PayPal correlation_id in analytics payload #1108

Merged
merged 12 commits into from
Oct 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading