Skip to content

Commit

Permalink
Merge branch 'shopper-insights-rp2-feature' into shopper-insights-rp2…
Browse files Browse the repository at this point in the history
…-sendSelected
  • Loading branch information
stechiu authored Dec 16, 2024
2 parents 7f41732 + 2a00f6e commit c2bb71f
Show file tree
Hide file tree
Showing 7 changed files with 83 additions and 45 deletions.
5 changes: 5 additions & 0 deletions Sources/BraintreeCore/Analytics/FPTIBatchData.swift
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ struct FPTIBatchData: Codable {

/// UTC millisecond timestamp when a networking task started requesting a resource. See [Apple's docs](https://developer.apple.com/documentation/foundation/urlsessiontasktransactionmetrics#3162615).
let requestStartTime: Int?
/// The Shopper Insights customer session ID created by a merchant's server SDK or graphQL integration.
let shopperSessionID: String?
/// UTC millisecond timestamp when a networking task initiated.
let startTime: Int?
let timestamp = String(Date().utcTimestampMilliseconds)
Expand All @@ -79,6 +81,7 @@ struct FPTIBatchData: Codable {
paymentMethodsDisplayed: String? = nil,
payPalContextID: String? = nil,
requestStartTime: Int? = nil,
shopperSessionID: String? = nil,
startTime: Int? = nil
) {
self.appSwitchURL = appSwitchURL?.absoluteString
Expand All @@ -96,6 +99,7 @@ struct FPTIBatchData: Codable {
self.paymentMethodsDisplayed = paymentMethodsDisplayed
self.payPalContextID = payPalContextID
self.requestStartTime = requestStartTime
self.shopperSessionID = shopperSessionID
self.startTime = startTime
}

Expand All @@ -115,6 +119,7 @@ struct FPTIBatchData: Codable {
case requestStartTime = "request_start_time"
case timestamp = "t"
case tenantName = "tenant_name"
case shopperSessionID = "shopper_session_id"
case startTime = "start_time"
case endTime = "end_time"
case endpoint = "endpoint"
Expand Down
6 changes: 4 additions & 2 deletions Sources/BraintreeCore/BTAPIClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,8 @@ import Foundation
linkType: LinkType? = nil,
paymentMethodsDisplayed: String? = nil,
payPalContextID: String? = nil,
appSwitchURL: URL? = nil
appSwitchURL: URL? = nil,
shopperSessionID: String? = nil
) {
analyticsService.sendAnalyticsEvent(
FPTIBatchData.Event(
Expand All @@ -326,7 +327,8 @@ import Foundation
linkType: linkType?.rawValue,
merchantExperiment: merchantExperiment,
paymentMethodsDisplayed: paymentMethodsDisplayed,
payPalContextID: payPalContextID
payPalContextID: payPalContextID,
shopperSessionID: shopperSessionID
)
)
}
Expand Down
44 changes: 27 additions & 17 deletions Sources/BraintreePayPal/BTPayPalClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,7 @@ import BraintreeDataCollector

// MARK: - Internal Methods

// swiftlint:disable function_body_length
func handleReturn(
_ url: URL?,
paymentType: BTPayPalPaymentType,
Expand All @@ -196,7 +197,8 @@ import BraintreeDataCollector
correlationID: clientMetadataID,
isVaultRequest: isVaultRequest,
linkType: linkType,
payPalContextID: payPalContextID
payPalContextID: payPalContextID,
shopperSessionID: payPalRequest?.shopperSessionID
)

guard let url, BTPayPalReturnURL.isValidURLAction(url: url, linkType: linkType) else {
Expand Down Expand Up @@ -312,13 +314,14 @@ import BraintreeDataCollector
completion: @escaping (BTPayPalAccountNonce?, Error?) -> Void
) {
linkType = (request as? BTPayPalVaultRequest)?.enablePayPalAppSwitch == true ? .universal : .deeplink

// The shopper insights server SDK integration
if let shopperID = request.shopperSessionID {
apiClient.metadata.sessionID = shopperID
}

apiClient.sendAnalyticsEvent(BTPayPalAnalytics.tokenizeStarted, isVaultRequest: isVaultRequest, linkType: linkType)
self.payPalRequest = request

apiClient.sendAnalyticsEvent(
BTPayPalAnalytics.tokenizeStarted,
isVaultRequest: isVaultRequest,
linkType: linkType,
shopperSessionID: payPalRequest?.shopperSessionID
)
apiClient.fetchOrReturnRemoteConfiguration { configuration, error in
if let error {
self.notifyFailure(with: error, completion: completion)
Expand All @@ -337,7 +340,6 @@ import BraintreeDataCollector
return
}

self.payPalRequest = request
self.apiClient.post(
request.hermesPath,
parameters: request.parameters(
Expand Down Expand Up @@ -393,7 +395,8 @@ import BraintreeDataCollector
BTPayPalAnalytics.appSwitchStarted,
isVaultRequest: isVaultRequest,
linkType: linkType,
payPalContextID: payPalContextID
payPalContextID: payPalContextID,
shopperSessionID: payPalRequest?.shopperSessionID
)

var urlComponents = URLComponents(url: payPalAppRedirectURL, resolvingAgainstBaseURL: true)
Expand All @@ -419,7 +422,8 @@ import BraintreeDataCollector
BTPayPalAnalytics.appSwitchSucceeded,
isVaultRequest: isVaultRequest,
linkType: linkType,
payPalContextID: payPalContextID
payPalContextID: payPalContextID,
shopperSessionID: payPalRequest?.shopperSessionID
)
BTPayPalClient.payPalClient = self
appSwitchCompletion = completion
Expand All @@ -428,7 +432,8 @@ import BraintreeDataCollector
BTPayPalAnalytics.appSwitchFailed,
isVaultRequest: isVaultRequest,
linkType: linkType,
payPalContextID: payPalContextID
payPalContextID: payPalContextID,
shopperSessionID: payPalRequest?.shopperSessionID
)
notifyFailure(with: BTPayPalError.appSwitchFailed, completion: completion)
}
Expand Down Expand Up @@ -476,14 +481,16 @@ import BraintreeDataCollector
isConfigFromCache: isConfigFromCache,
isVaultRequest: isVaultRequest,
linkType: linkType,
payPalContextID: payPalContextID
payPalContextID: payPalContextID,
shopperSessionID: payPalRequest?.shopperSessionID
)
} else {
apiClient.sendAnalyticsEvent(
BTPayPalAnalytics.browserPresentationFailed,
isVaultRequest: isVaultRequest,
linkType: linkType,
payPalContextID: payPalContextID
payPalContextID: payPalContextID,
shopperSessionID: payPalRequest?.shopperSessionID
)
}
} sessionDidCancel: { [self] in
Expand Down Expand Up @@ -515,7 +522,8 @@ import BraintreeDataCollector
correlationID: clientMetadataID,
isVaultRequest: isVaultRequest,
linkType: linkType,
payPalContextID: payPalContextID
payPalContextID: payPalContextID,
shopperSessionID: payPalRequest?.shopperSessionID
)
completion(result, nil)
}
Expand All @@ -527,7 +535,8 @@ import BraintreeDataCollector
errorDescription: error.localizedDescription,
isVaultRequest: isVaultRequest,
linkType: linkType,
payPalContextID: payPalContextID
payPalContextID: payPalContextID,
shopperSessionID: payPalRequest?.shopperSessionID
)
completion(nil, error)
}
Expand All @@ -538,7 +547,8 @@ import BraintreeDataCollector
correlationID: clientMetadataID,
isVaultRequest: isVaultRequest,
linkType: linkType,
payPalContextID: payPalContextID
payPalContextID: payPalContextID,
shopperSessionID: payPalRequest?.shopperSessionID
)
completion(nil, BTPayPalError.canceled)
}
Expand Down
31 changes: 22 additions & 9 deletions Sources/BraintreeShopperInsights/BTShopperInsightsClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,6 @@ public class BTShopperInsightsClient {
public init(apiClient: BTAPIClient, shopperSessionID: String? = nil) {
self.apiClient = apiClient
self.shopperSessionID = shopperSessionID

if let shopperSessionID {
apiClient.metadata.sessionID = shopperSessionID
}
}

// MARK: - Public Methods
Expand All @@ -49,7 +45,8 @@ public class BTShopperInsightsClient {
) async throws -> BTShopperInsightsResult {
apiClient.sendAnalyticsEvent(
BTShopperInsightsAnalytics.recommendedPaymentsStarted,
merchantExperiment: experiment
merchantExperiment: experiment,
shopperSessionID: shopperSessionID
)

if apiClient.authorization.type != .clientToken {
Expand Down Expand Up @@ -102,10 +99,17 @@ public class BTShopperInsightsClient {
apiClient.sendAnalyticsEvent(
BTShopperInsightsAnalytics.payPalPresented,
merchantExperiment: experiment,
paymentMethodsDisplayed: paymentMethodsDisplayedString
paymentMethodsDisplayed: paymentMethodsDisplayedString,
shopperSessionID: shopperSessionID
)
}

/// Call this method when the PayPal button has been selected/tapped by the buyer.
/// This method sends analytics to help improve the Shopper Insights feature experience
public func sendPayPalSelectedEvent() {
apiClient.sendAnalyticsEvent(BTShopperInsightsAnalytics.payPalSelected, shopperSessionID: shopperSessionID)
}

/// Call this method when the Venmo button has been successfully displayed to the buyer.
/// This method sends analytics to help improve the Shopper Insights feature experience.
/// - Parameters:
Expand All @@ -116,7 +120,8 @@ public class BTShopperInsightsClient {
apiClient.sendAnalyticsEvent(
BTShopperInsightsAnalytics.venmoPresented,
merchantExperiment: experiment,
paymentMethodsDisplayed: paymentMethodsDisplayedString
paymentMethodsDisplayed: paymentMethodsDisplayedString,
shopperSessionID: shopperSessionID
)
}

Expand All @@ -125,6 +130,12 @@ public class BTShopperInsightsClient {
public func sendSelectedEvent(for buttonType: BTButtonType) {
apiClient.sendAnalyticsEvent(buttonType.rawValue)
}

/// Call this method when the Venmo button has been selected/tapped by the buyer.
/// This method sends analytics to help improve the Shopper Insights feature experience
public func sendVenmoSelectedEvent() {
apiClient.sendAnalyticsEvent(BTShopperInsightsAnalytics.venmoSelected, shopperSessionID: shopperSessionID)
}

/// Indicates whether the PayPal App is installed.
/// - Warning: This method is currently in beta and may change or be removed in future releases.
Expand All @@ -143,7 +154,8 @@ public class BTShopperInsightsClient {
private func notifySuccess(with result: BTShopperInsightsResult, for experiment: String?) -> BTShopperInsightsResult {
apiClient.sendAnalyticsEvent(
BTShopperInsightsAnalytics.recommendedPaymentsSucceeded,
merchantExperiment: experiment
merchantExperiment: experiment,
shopperSessionID: shopperSessionID
)
return result
}
Expand All @@ -152,7 +164,8 @@ public class BTShopperInsightsClient {
apiClient.sendAnalyticsEvent(
BTShopperInsightsAnalytics.recommendedPaymentsFailed,
errorDescription: error.localizedDescription,
merchantExperiment: experiment
merchantExperiment: experiment,
shopperSessionID: shopperSessionID
)
return error
}
Expand Down
17 changes: 3 additions & 14 deletions UnitTests/BraintreePayPalTests/BTPayPalClient_Tests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1029,23 +1029,12 @@ class BTPayPalClient_Tests: XCTestCase {
XCTAssertFalse(mockAPIClient.postedIsVaultRequest)
}

func testTokenize_whenCheckoutRequest_setSessionID() async {
XCTAssertNotEqual(mockAPIClient.metadata.sessionID, "test-shopper-insights-id")

func testTokenize_whenShopperSessionIDSetOnRequest_includesInAnalytics() async {
let checkoutRequest = BTPayPalCheckoutRequest(amount: "2.00")
checkoutRequest.shopperSessionID = "test-shopper-insights-id"
let _ = try? await payPalClient.tokenize(checkoutRequest)

XCTAssertEqual(mockAPIClient.metadata.sessionID, "test-shopper-insights-id")
}

func testTokenize_whenVaultRequest_setSessionID() async {
XCTAssertNotEqual(mockAPIClient.metadata.sessionID, "test-shopper-insights-id")
checkoutRequest.shopperSessionID = "fake-shopper-session-id"

let checkoutRequest = BTPayPalVaultRequest()
checkoutRequest.shopperSessionID = "test-shopper-insights-id"
let _ = try? await payPalClient.tokenize(checkoutRequest)

XCTAssertEqual(mockAPIClient.metadata.sessionID, "test-shopper-insights-id")
XCTAssertEqual(mockAPIClient.postedShopperSessionID, "fake-shopper-session-id")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ class BTShopperInsightsClient_Tests: XCTestCase {
override func setUp() {
super.setUp()
mockAPIClient = MockAPIClient(authorization: clientToken)
sut = BTShopperInsightsClient(apiClient: mockAPIClient!)
sut = BTShopperInsightsClient(apiClient: mockAPIClient!, shopperSessionID: "fake-shopper-session-id")
}

// MARK: - getRecommendedPaymentMethods()
Expand Down Expand Up @@ -78,6 +78,7 @@ class BTShopperInsightsClient_Tests: XCTestCase {
XCTAssertEqual(error.domain, "fake-error-domain")

XCTAssertEqual(mockAPIClient.postedAnalyticsEvents.last, "shopper-insights:get-recommended-payments:failed")
XCTAssertEqual(mockAPIClient.postedShopperSessionID, "fake-shopper-session-id")
}
}

Expand All @@ -101,6 +102,7 @@ class BTShopperInsightsClient_Tests: XCTestCase {
XCTAssertTrue(result.isVenmoRecommended)
XCTAssertFalse(result.isPayPalRecommended)
XCTAssertEqual(mockAPIClient.postedAnalyticsEvents.last, "shopper-insights:get-recommended-payments:succeeded")
XCTAssertEqual(mockAPIClient.postedShopperSessionID, "fake-shopper-session-id")
} catch let error as NSError {
XCTFail("An error was not expected.")
}
Expand All @@ -127,6 +129,7 @@ class BTShopperInsightsClient_Tests: XCTestCase {
XCTAssertTrue(result.isEligibleInPayPalNetwork)
XCTAssertEqual(mockAPIClient.postedAnalyticsEvents.last, "shopper-insights:get-recommended-payments:succeeded")
XCTAssertEqual(mockAPIClient.postedMerchantExperiment, sampleExperiment)
XCTAssertEqual(mockAPIClient.postedShopperSessionID, "fake-shopper-session-id")
} catch {
XCTFail("An error was not expected.")
}
Expand All @@ -152,6 +155,7 @@ class BTShopperInsightsClient_Tests: XCTestCase {
XCTAssertTrue(result.isVenmoRecommended)
XCTAssertTrue(result.isEligibleInPayPalNetwork)
XCTAssertEqual(mockAPIClient.postedAnalyticsEvents.last, "shopper-insights:get-recommended-payments:succeeded")
XCTAssertEqual(mockAPIClient.postedShopperSessionID, "fake-shopper-session-id")
} catch {
XCTFail("An error was not expected.")
}
Expand Down Expand Up @@ -181,6 +185,7 @@ class BTShopperInsightsClient_Tests: XCTestCase {
XCTAssertFalse(result.isVenmoRecommended)
XCTAssertFalse(result.isEligibleInPayPalNetwork)
XCTAssertEqual(mockAPIClient.postedAnalyticsEvents.last, "shopper-insights:get-recommended-payments:succeeded")
XCTAssertEqual(mockAPIClient.postedShopperSessionID, "fake-shopper-session-id")
} catch {
XCTFail("An error was not expected.")
}
Expand All @@ -204,23 +209,30 @@ class BTShopperInsightsClient_Tests: XCTestCase {
func testSendPayPalPresentedEvent_sendsAnalytic() {
sut.sendPayPalPresentedEvent()
XCTAssertEqual(mockAPIClient.postedAnalyticsEvents.first, "shopper-insights:paypal-presented")
XCTAssertEqual(mockAPIClient.postedShopperSessionID, "fake-shopper-session-id")
}

func testSendPayPalPresentedEvent_whenPaymentMethodsDisplayedNotNil_sendsAnalytic() {
let paymentMethods = ["Apple Pay", "Card", "PayPal"]
sut.sendPayPalPresentedEvent(paymentMethodsDisplayed: paymentMethods)
XCTAssertEqual(mockAPIClient.postedPaymentMethodsDisplayed, paymentMethods.joined(separator: ", "))
XCTAssertEqual(mockAPIClient.postedAnalyticsEvents.first, "shopper-insights:paypal-presented")
XCTAssertEqual(mockAPIClient.postedShopperSessionID, "fake-shopper-session-id")
}

func testSendPayPalSelectedEvent_sendsAnalytic() {
sut.sendSelectedEvent(for: .payPal)
XCTAssertEqual(mockAPIClient.postedAnalyticsEvents.first, "PayPal")

sut.sendPayPalSelectedEvent()
XCTAssertEqual(mockAPIClient.postedAnalyticsEvents.first, "shopper-insights:paypal-selected")
XCTAssertEqual(mockAPIClient.postedShopperSessionID, "fake-shopper-session-id")
}

func testSendVenmoPresentedEvent_sendsAnalytic() {
sut.sendVenmoPresentedEvent()
XCTAssertEqual(mockAPIClient.postedAnalyticsEvents.first, "shopper-insights:venmo-presented")
XCTAssertEqual(mockAPIClient.postedShopperSessionID, "fake-shopper-session-id")
}

func testSendVenmoSelectedEvent_sendsAnalytic() {
Expand All @@ -231,6 +243,10 @@ class BTShopperInsightsClient_Tests: XCTestCase {
func testShopperInsightsClient_withSessionID_setSessionIDInMetadata() {
sut = BTShopperInsightsClient(apiClient: mockAPIClient, shopperSessionID: "123456")
XCTAssertEqual(mockAPIClient.metadata.sessionID, "123456")

sut.sendVenmoSelectedEvent()
XCTAssertEqual(mockAPIClient.postedAnalyticsEvents.first, "shopper-insights:venmo-selected")
XCTAssertEqual(mockAPIClient.postedShopperSessionID, "fake-shopper-session-id")
}

// MARK: - App Installed Methods
Expand Down
Loading

0 comments on commit c2bb71f

Please sign in to comment.