diff --git a/Braintree.xcodeproj/project.pbxproj b/Braintree.xcodeproj/project.pbxproj index 38a990360..6784fbd04 100644 --- a/Braintree.xcodeproj/project.pbxproj +++ b/Braintree.xcodeproj/project.pbxproj @@ -7,6 +7,11 @@ objects = { /* Begin PBXBuildFile section */ + 04AA31182D07974D0043ACAB /* BTButtonType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04AA31172D0797460043ACAB /* BTButtonType.swift */; }; + 04AA311A2D0797570043ACAB /* BTPresentmentDetails.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04AA31192D0797510043ACAB /* BTPresentmentDetails.swift */; }; + 04AA311E2D0798FC0043ACAB /* BTPageType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04AA311D2D0798F70043ACAB /* BTPageType.swift */; }; + 04AA31202D07990E0043ACAB /* BTButtonOrder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04AA311F2D07990A0043ACAB /* BTButtonOrder.swift */; }; + 04B001102D0CF46E00C0060D /* BTExperimentType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04B0010F2D0CF46900C0060D /* BTExperimentType.swift */; }; 0917F6E42A27BDC700ACED2E /* BTVenmoLineItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 096C6B2529CCDCEB00912863 /* BTVenmoLineItem.swift */; }; 09357DCB2A2FBEC10096D449 /* BTVenmoLineItem_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09357DCA2A2FBEC10096D449 /* BTVenmoLineItem_Tests.swift */; }; 1FEB89E614CB6BF0B9858EE4 /* Pods_Tests_IntegrationTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 85BD589D380436A0C9D1DEC1 /* Pods_Tests_IntegrationTests.framework */; }; @@ -729,6 +734,11 @@ 035A59D91EA5DE97002960C8 /* BTLocalPaymentClient_UnitTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BTLocalPaymentClient_UnitTests.swift; sourceTree = ""; }; 039A8BD91F9E993500D607E7 /* BTAmericanExpressRewardsBalance_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BTAmericanExpressRewardsBalance_Tests.swift; sourceTree = ""; }; 03F921C1200EBB200076CD80 /* BTThreeDSecurePostalAddress_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BTThreeDSecurePostalAddress_Tests.swift; sourceTree = ""; }; + 04AA31172D0797460043ACAB /* BTButtonType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BTButtonType.swift; sourceTree = ""; }; + 04AA31192D0797510043ACAB /* BTPresentmentDetails.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BTPresentmentDetails.swift; sourceTree = ""; }; + 04AA311D2D0798F70043ACAB /* BTPageType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BTPageType.swift; sourceTree = ""; }; + 04AA311F2D07990A0043ACAB /* BTButtonOrder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BTButtonOrder.swift; sourceTree = ""; }; + 04B0010F2D0CF46900C0060D /* BTExperimentType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BTExperimentType.swift; sourceTree = ""; }; 09357DCA2A2FBEC10096D449 /* BTVenmoLineItem_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BTVenmoLineItem_Tests.swift; sourceTree = ""; }; 096C6B2529CCDCEB00912863 /* BTVenmoLineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BTVenmoLineItem.swift; sourceTree = ""; }; 162174E1192D9220008DC35D /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; @@ -1494,8 +1504,13 @@ 804698292B27C4D70090878E /* BraintreeShopperInsights */ = { isa = PBXGroup; children = ( + 04AA311F2D07990A0043ACAB /* BTButtonOrder.swift */, + 04AA31172D0797460043ACAB /* BTButtonType.swift */, 62EA90482B63071800DD79BC /* BTEligiblePaymentMethods.swift */, 800ED7822B4F5B66007D8A30 /* BTEligiblePaymentsRequest.swift */, + 04B0010F2D0CF46900C0060D /* BTExperimentType.swift */, + 04AA311D2D0798F70043ACAB /* BTPageType.swift */, + 04AA31192D0797510043ACAB /* BTPresentmentDetails.swift */, 8037BFAF2B2CCC130017072C /* BTShopperInsightsAnalytics.swift */, 8064F38E2B1E492F0059C4CB /* BTShopperInsightsClient.swift */, 624B27F62B6AE0C2000AC08A /* BTShopperInsightsError.swift */, @@ -3364,11 +3379,16 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 04AA311E2D0798FC0043ACAB /* BTPageType.swift in Sources */, 804698382B27C53B0090878E /* BTShopperInsightsRequest.swift in Sources */, + 04B001102D0CF46E00C0060D /* BTExperimentType.swift in Sources */, + 04AA31182D07974D0043ACAB /* BTButtonType.swift in Sources */, 624B27F72B6AE0C2000AC08A /* BTShopperInsightsError.swift in Sources */, 804698372B27C5390090878E /* BTShopperInsightsClient.swift in Sources */, 800ED7832B4F5B66007D8A30 /* BTEligiblePaymentsRequest.swift in Sources */, 8037BFB02B2CCC130017072C /* BTShopperInsightsAnalytics.swift in Sources */, + 04AA311A2D0797570043ACAB /* BTPresentmentDetails.swift in Sources */, + 04AA31202D07990E0043ACAB /* BTButtonOrder.swift in Sources */, 62EA90492B63071800DD79BC /* BTEligiblePaymentMethods.swift in Sources */, 804698392B27C53E0090878E /* BTShopperInsightsResult.swift in Sources */, ); diff --git a/CHANGELOG.md b/CHANGELOG.md index aa1c62d67..92c1cda7a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,11 @@ * BraintreeShopperInsights (BETA) * Add `shopperSessionID` to `BTShopperInsightsClient` initializer * Add `isPayPalAppInstalled()` and/or `isVenmoAppInstalled()` + * Replace `sendPayPalPresentedEvent()` and `sendPayPalPresentedEvent()` with `sendPresentedEvent(for:presentmentDetails:)` + * Add values to the following parameters to `presentmentDetails`: + * `experimentType` + * `pageType` + * `buttonOrder` ## 6.25.0 (2024-12-11) * BraintreePayPal diff --git a/Demo/Application/Features/ShopperInsightsViewController.swift b/Demo/Application/Features/ShopperInsightsViewController.swift index 820f914ae..47c8ace2b 100644 --- a/Demo/Application/Features/ShopperInsightsViewController.swift +++ b/Demo/Application/Features/ShopperInsightsViewController.swift @@ -95,25 +95,50 @@ class ShopperInsightsViewController: PaymentButtonBaseViewController { let result = try await shopperInsightsClient.getRecommendedPaymentMethods(request: request, experiment: sampleExperiment) // swiftlint:disable:next line_length progressBlock("PayPal Recommended: \(result.isPayPalRecommended)\nVenmo Recommended: \(result.isVenmoRecommended)\nEligible in PayPal Network: \(result.isEligibleInPayPalNetwork)") - payPalVaultButton.isEnabled = result.isPayPalRecommended - venmoButton.isEnabled = result.isVenmoRecommended + + togglePayPalVaultButton(enabled: result.isPayPalRecommended) + toggleVenmoButton(enabled: result.isVenmoRecommended) } catch { progressBlock("Error: \(error.localizedDescription)") } } } + private func togglePayPalVaultButton(enabled: Bool) { + payPalVaultButton.isEnabled = enabled + + guard enabled else { return } + + let presentmentDetails = BTPresentmentDetails( + buttonOrder: .first, + experimentType: .control, + pageType: .about + ) + + shopperInsightsClient.sendPresentedEvent( + for: .payPal, + presentmentDetails: presentmentDetails + ) + } + + private func toggleVenmoButton(enabled: Bool) { + venmoButton.isEnabled = enabled + + guard enabled else { return } + + let presentmentDetails = BTPresentmentDetails( + buttonOrder: .second, + experimentType: .control, + pageType: .about + ) + + shopperInsightsClient.sendPresentedEvent( + for: .venmo, + presentmentDetails: presentmentDetails + ) + } + @objc func payPalVaultButtonTapped(_ button: UIButton) { - let sampleExperiment = - """ - [ - { "experimentName" : "payment ready conversion experiment" }, - { "experimentID" : "a1b2c3" }, - { "treatmentName" : "treatment group 1" } - ] - """ - let paymentMethods = ["Apple Pay", "Card", "PayPal"] - shopperInsightsClient.sendPayPalPresentedEvent(paymentMethodsDisplayed: paymentMethods, experiment: sampleExperiment) progressBlock("Tapped PayPal Vault") shopperInsightsClient.sendPayPalSelectedEvent() @@ -131,7 +156,6 @@ class ShopperInsightsViewController: PaymentButtonBaseViewController { } @objc func venmoButtonTapped(_ button: UIButton) { - shopperInsightsClient.sendVenmoPresentedEvent() progressBlock("Tapped Venmo") shopperInsightsClient.sendVenmoSelectedEvent() diff --git a/Sources/BraintreeCore/Analytics/FPTIBatchData.swift b/Sources/BraintreeCore/Analytics/FPTIBatchData.swift index 4ae467013..083c29fae 100644 --- a/Sources/BraintreeCore/Analytics/FPTIBatchData.swift +++ b/Sources/BraintreeCore/Analytics/FPTIBatchData.swift @@ -25,11 +25,15 @@ struct FPTIBatchData: Codable { case fptiEvents = "event_params" } } - + /// Encapsulates a single event by it's name and timestamp. struct Event: Codable { let appSwitchURL: String? + /// The order or ranking in which payment buttons appear. + let buttonOrder: String? + /// The type of button displayed or presented + let buttonType: String? /// UTC millisecond timestamp when a networking task started establishing a TCP connection. See [Apple's docs](https://developer.apple.com/documentation/foundation/urlsessiontasktransactionmetrics#3162615). /// `nil` if a persistent connection is used. let connectionStartTime: Int? @@ -48,6 +52,8 @@ struct FPTIBatchData: Codable { let linkType: String? /// The experiment details associated with a shopper insights flow let merchantExperiment: String? + /// The type of page where the payment button is displayed or where an event occured. + let pageType: String? /// The list of payment methods displayed, in the same order in which they are rendered on the page, associated with the `BTShopperInsights` flow. let paymentMethodsDisplayed: String? /// Used for linking events from the client to server side request @@ -65,6 +71,8 @@ struct FPTIBatchData: Codable { init( appSwitchURL: URL? = nil, + buttonOrder: String? = nil, + buttonType: String? = nil, connectionStartTime: Int? = nil, correlationID: String? = nil, endpoint: String? = nil, @@ -75,6 +83,7 @@ struct FPTIBatchData: Codable { isVaultRequest: Bool? = nil, linkType: String? = nil, merchantExperiment: String? = nil, + pageType: String? = nil, paymentMethodsDisplayed: String? = nil, payPalContextID: String? = nil, requestStartTime: Int? = nil, @@ -82,6 +91,8 @@ struct FPTIBatchData: Codable { startTime: Int? = nil ) { self.appSwitchURL = appSwitchURL?.absoluteString + self.buttonOrder = buttonOrder + self.buttonType = buttonType self.connectionStartTime = connectionStartTime self.correlationID = correlationID self.endpoint = endpoint @@ -92,6 +103,7 @@ struct FPTIBatchData: Codable { self.isVaultRequest = isVaultRequest self.linkType = linkType self.merchantExperiment = merchantExperiment + self.pageType = pageType self.paymentMethodsDisplayed = paymentMethodsDisplayed self.payPalContextID = payPalContextID self.requestStartTime = requestStartTime @@ -101,6 +113,8 @@ struct FPTIBatchData: Codable { enum CodingKeys: String, CodingKey { case appSwitchURL = "url" + case buttonOrder = "button_position" + case buttonType = "button_type" case connectionStartTime = "connect_start_time" case correlationID = "correlation_id" case errorDescription = "error_desc" @@ -109,6 +123,7 @@ struct FPTIBatchData: Codable { case isVaultRequest = "is_vault" case linkType = "link_type" case merchantExperiment = "experiment" + case pageType = "page_type" case paymentMethodsDisplayed = "payment_methods_displayed" case payPalContextID = "paypal_context_id" case requestStartTime = "request_start_time" diff --git a/Sources/BraintreeCore/BTAPIClient.swift b/Sources/BraintreeCore/BTAPIClient.swift index 79c42c43f..7bb81b48f 100644 --- a/Sources/BraintreeCore/BTAPIClient.swift +++ b/Sources/BraintreeCore/BTAPIClient.swift @@ -303,20 +303,25 @@ import Foundation @_documentation(visibility: private) public func sendAnalyticsEvent( _ eventName: String, + appSwitchURL: URL? = nil, + buttonOrder: String? = nil, + buttonType: String? = nil, correlationID: String? = nil, errorDescription: String? = nil, merchantExperiment: String? = nil, isConfigFromCache: Bool? = nil, isVaultRequest: Bool? = nil, linkType: LinkType? = nil, + pageType: String? = nil, paymentMethodsDisplayed: String? = nil, payPalContextID: String? = nil, - appSwitchURL: URL? = nil, shopperSessionID: String? = nil ) { analyticsService.sendAnalyticsEvent( FPTIBatchData.Event( appSwitchURL: appSwitchURL, + buttonOrder: buttonOrder, + buttonType: buttonType, correlationID: correlationID, errorDescription: errorDescription, eventName: eventName, @@ -324,6 +329,7 @@ import Foundation isVaultRequest: isVaultRequest, linkType: linkType?.rawValue, merchantExperiment: merchantExperiment, + pageType: pageType, paymentMethodsDisplayed: paymentMethodsDisplayed, payPalContextID: payPalContextID, shopperSessionID: shopperSessionID diff --git a/Sources/BraintreePayPal/BTPayPalClient.swift b/Sources/BraintreePayPal/BTPayPalClient.swift index 65824d32e..3487e1166 100644 --- a/Sources/BraintreePayPal/BTPayPalClient.swift +++ b/Sources/BraintreePayPal/BTPayPalClient.swift @@ -290,20 +290,20 @@ import BraintreeDataCollector if success { apiClient.sendAnalyticsEvent( BTPayPalAnalytics.appSwitchSucceeded, + appSwitchURL: url, isVaultRequest: isVaultRequest, linkType: linkType, - payPalContextID: payPalContextID, - appSwitchURL: url + payPalContextID: payPalContextID ) BTPayPalClient.payPalClient = self appSwitchCompletion = completion } else { apiClient.sendAnalyticsEvent( BTPayPalAnalytics.appSwitchFailed, + appSwitchURL: url, isVaultRequest: isVaultRequest, linkType: linkType, - payPalContextID: payPalContextID, - appSwitchURL: url + payPalContextID: payPalContextID ) notifyFailure(with: BTPayPalError.appSwitchFailed, completion: completion) } diff --git a/Sources/BraintreeShopperInsights/BTButtonOrder.swift b/Sources/BraintreeShopperInsights/BTButtonOrder.swift new file mode 100644 index 000000000..0018ecc29 --- /dev/null +++ b/Sources/BraintreeShopperInsights/BTButtonOrder.swift @@ -0,0 +1,33 @@ +import Foundation + +/// The order or ranking in which payment buttons appear. +/// - Warning: This module is in beta. It's public API may change or be removed in future releases. +public enum BTButtonOrder: String { + + /// First place + case first = "1" + + /// Second place + case second = "2" + + /// Third place + case third = "3" + + /// Fourth place + case fourth = "4" + + /// Fifth place + case fifth = "5" + + /// Sixth place + case sixth = "6" + + /// Seventh place + case seventh = "7" + + /// Eighth place + case eighth = "8" + + /// Greater than Eighth place + case other = "other" +} diff --git a/Sources/BraintreeShopperInsights/BTButtonType.swift b/Sources/BraintreeShopperInsights/BTButtonType.swift new file mode 100644 index 000000000..8341aa7a1 --- /dev/null +++ b/Sources/BraintreeShopperInsights/BTButtonType.swift @@ -0,0 +1,15 @@ +import Foundation + +/// The type of button displayed or presented +/// Warning: This module is in beta. It's public API may change or be removed in future releases. +public enum BTButtonType: String { + + /// PayPal button + case payPal = "PayPal" + + /// Venmo button + case venmo = "Venmo" + + /// All button types other than PayPal or Venmo + case other = "Other" +} diff --git a/Sources/BraintreeShopperInsights/BTExperimentType.swift b/Sources/BraintreeShopperInsights/BTExperimentType.swift new file mode 100644 index 000000000..60cf4e2e9 --- /dev/null +++ b/Sources/BraintreeShopperInsights/BTExperimentType.swift @@ -0,0 +1,21 @@ +import Foundation + +/// The experiment type that is sent to analytics to help improve the Shopper Insights feature experience. +/// - Warning: This module is in beta. It's public API may change or be removed in future releases. +public enum BTExperimentType: String { + + /// The test experiment + case test + + /// The control experiment + case control + + public var formattedExperiment: String { + """ + [ + { "exp_name" : "PaymentReady" } + { "treatment_name" : "\(self.rawValue)" } + ] + """ + } +} diff --git a/Sources/BraintreeShopperInsights/BTPageType.swift b/Sources/BraintreeShopperInsights/BTPageType.swift new file mode 100644 index 000000000..a301d93c9 --- /dev/null +++ b/Sources/BraintreeShopperInsights/BTPageType.swift @@ -0,0 +1,45 @@ +import Foundation + +/// The type of page where the payment button is displayed or where an event occured. +/// - Warning: This module is in beta. It's public API may change or be removed in future releases. +public enum BTPageType: String { + + /// A home page is the primary landing page that a visitor will view when they navigate to a website. + case homepage = "homepage" + + /// An About page is a section on a website that provides information about a company, organization, or individual. + case about = "about" + + /// A contact page is a page on a website for visitors to contact the organization or individual providing the website. + case contact = "contact" + + /// An intermediary step that users pass through on their way to a product-listing page that doesn't provide a complete + /// list of products but may showcase a few products and provide links to product subcategories. + case productCategory = "product_category" + + /// A product detail page (PDP) is a web page that outlines everything customers and buyers need to know about a + /// particular product. + case productDetails = "product_details" + + /// The page a user sees after entering a search query. + case search = "search" + + /// A cart is a digital shopping cart that allows buyers to inspect and organize items they plan to buy. + case cart = "cart" + + /// A checkout page is the page related to payment and shipping/billing details on an eCommerce store. + case checkout = "checkout" + + /// An order review page gives the buyer an overview of the goods or services that they have selected and summarizes + /// the order that they are about to place. + case orderReview = "order_review" + + /// The order confirmation page summarizes an order after checkout completes. + case orderConfirmation = "order_confirmation" + + /// Popup cart displayed after “add to cart” click. + case miniCart = "mini_cart" + + /// Any other page available on a merchant’s site. + case other = "other" +} diff --git a/Sources/BraintreeShopperInsights/BTPresentmentDetails.swift b/Sources/BraintreeShopperInsights/BTPresentmentDetails.swift new file mode 100644 index 000000000..1b33abc04 --- /dev/null +++ b/Sources/BraintreeShopperInsights/BTPresentmentDetails.swift @@ -0,0 +1,25 @@ +import Foundation + +public class BTPresentmentDetails { + + var buttonOrder: BTButtonOrder + var experimentType: BTExperimentType + var pageType: BTPageType + + /// Detailed information, including button order, experiment type, and page type about the payment button that + /// is sent to analytics to help improve the Shopper Insights feature experience. + /// - Warning: This class is in beta. It's public API may change or be removed in future releases. + /// - Parameters: + /// - buttonOrder: The order or ranking in which payment buttons appear. + /// - experimentType: The experiment type that is sent to analytics to help improve the Shopper Insights feature experience. + /// - pageType: The type of page where the payment button is displayed or where an event occured. + public init( + buttonOrder: BTButtonOrder, + experimentType: BTExperimentType, + pageType: BTPageType + ) { + self.buttonOrder = buttonOrder + self.experimentType = experimentType + self.pageType = pageType + } +} diff --git a/Sources/BraintreeShopperInsights/BTShopperInsightsAnalytics.swift b/Sources/BraintreeShopperInsights/BTShopperInsightsAnalytics.swift index fefbcd5fa..e00f0fbfd 100644 --- a/Sources/BraintreeShopperInsights/BTShopperInsightsAnalytics.swift +++ b/Sources/BraintreeShopperInsights/BTShopperInsightsAnalytics.swift @@ -3,11 +3,11 @@ import Foundation enum BTShopperInsightsAnalytics { // MARK: - Merchant Triggered Events - - static let payPalPresented = "shopper-insights:paypal-presented" + static let payPalSelected = "shopper-insights:paypal-selected" - static let venmoPresented = "shopper-insights:venmo-presented" static let venmoSelected = "shopper-insights:venmo-selected" + + static let buttonPresented = "shopper-insights:button-presented" // MARK: - SDK Triggered Events diff --git a/Sources/BraintreeShopperInsights/BTShopperInsightsClient.swift b/Sources/BraintreeShopperInsights/BTShopperInsightsClient.swift index 302406c5f..2dc4de8d0 100644 --- a/Sources/BraintreeShopperInsights/BTShopperInsightsClient.swift +++ b/Sources/BraintreeShopperInsights/BTShopperInsightsClient.swift @@ -89,17 +89,20 @@ public class BTShopperInsightsClient { } } - /// Call this method when the PayPal button has been successfully displayed to the buyer. + /// Call this method when the PayPal or Venmo button has been successfully displayed to the buyer. /// This method sends analytics to help improve the Shopper Insights feature experience. /// - Parameters: - /// - paymentMethodsDisplayed: Optional: The list of available payment methods, rendered in the same order in which they are displayed i.e. ['Apple Pay', 'PayPal'] - /// - experiment: Optional: A `JSONObject` passed in as a string containing details of the merchant experiment. - public func sendPayPalPresentedEvent(paymentMethodsDisplayed: [String?] = [], experiment: String? = nil) { - let paymentMethodsDisplayedString = paymentMethodsDisplayed.compactMap { $0 }.joined(separator: ", ") + /// - buttonType: Type of button presented - PayPal, Venmo, or Other + /// - presentmentDetails: Detailed information, including button order, experiment type, and + /// page type about the payment button that is sent to analytics to help improve the Shopper Insights + /// feature experience. + public func sendPresentedEvent(for buttonType: BTButtonType, presentmentDetails: BTPresentmentDetails) { apiClient.sendAnalyticsEvent( - BTShopperInsightsAnalytics.payPalPresented, - merchantExperiment: experiment, - paymentMethodsDisplayed: paymentMethodsDisplayedString, + BTShopperInsightsAnalytics.buttonPresented, + buttonOrder: presentmentDetails.buttonOrder.rawValue, + buttonType: buttonType.rawValue, + merchantExperiment: presentmentDetails.experimentType.formattedExperiment, + pageType: presentmentDetails.pageType.rawValue, shopperSessionID: shopperSessionID ) } @@ -109,22 +112,7 @@ public class BTShopperInsightsClient { 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: - /// - paymentMethodsDisplayed: Optional: The list of available payment methods, rendered in the same order in which they are displayed. - /// - experiment: Optional: A `JSONObject` passed in as a string containing details of the merchant experiment. - public func sendVenmoPresentedEvent(paymentMethodsDisplayed: [String?] = [], experiment: String? = nil) { - let paymentMethodsDisplayedString = paymentMethodsDisplayed.compactMap { $0 }.joined(separator: ", ") - apiClient.sendAnalyticsEvent( - BTShopperInsightsAnalytics.venmoPresented, - merchantExperiment: experiment, - paymentMethodsDisplayed: paymentMethodsDisplayedString, - shopperSessionID: shopperSessionID - ) - } - + /// 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() { diff --git a/Sources/BraintreeVenmo/BTVenmoClient.swift b/Sources/BraintreeVenmo/BTVenmoClient.swift index 7193bb875..293c28d1a 100644 --- a/Sources/BraintreeVenmo/BTVenmoClient.swift +++ b/Sources/BraintreeVenmo/BTVenmoClient.swift @@ -406,20 +406,20 @@ import BraintreeCore if success { apiClient.sendAnalyticsEvent( BTVenmoAnalytics.appSwitchSucceeded, + appSwitchURL: appSwitchURL, isVaultRequest: shouldVault, linkType: linkType, - payPalContextID: payPalContextID, - appSwitchURL: appSwitchURL + payPalContextID: payPalContextID ) BTVenmoClient.venmoClient = self self.appSwitchCompletion = completion } else { apiClient.sendAnalyticsEvent( BTVenmoAnalytics.appSwitchFailed, + appSwitchURL: appSwitchURL, isVaultRequest: shouldVault, linkType: linkType, - payPalContextID: payPalContextID, - appSwitchURL: appSwitchURL + payPalContextID: payPalContextID ) notifyFailure(with: BTVenmoError.appSwitchFailed, completion: completion) } diff --git a/UnitTests/BraintreePayPalTests/BTPayPalClient_Tests.swift b/UnitTests/BraintreePayPalTests/BTPayPalClient_Tests.swift index 7d112d2d3..8b6403295 100644 --- a/UnitTests/BraintreePayPalTests/BTPayPalClient_Tests.swift +++ b/UnitTests/BraintreePayPalTests/BTPayPalClient_Tests.swift @@ -999,7 +999,7 @@ class BTPayPalClient_Tests: XCTestCase { XCTAssertEqual(mockAPIClient.postedAnalyticsEvents.first!, eventName) XCTAssertEqual(mockAPIClient.postedAppSwitchURL[eventName], fakeURL.absoluteString) } - + // MARK: - Analytics func testAPIClientMetadata_hasIntegrationSetToCustom() { diff --git a/UnitTests/BraintreeShopperInsightsTests/BTShopperInsightsAnalytics_Tests.swift b/UnitTests/BraintreeShopperInsightsTests/BTShopperInsightsAnalytics_Tests.swift index 991a240b4..54578a829 100644 --- a/UnitTests/BraintreeShopperInsightsTests/BTShopperInsightsAnalytics_Tests.swift +++ b/UnitTests/BraintreeShopperInsightsTests/BTShopperInsightsAnalytics_Tests.swift @@ -4,9 +4,9 @@ import XCTest final class BTShopperInsightsAnalytics_Tests: XCTestCase { func test_recommendedPaymentAnalyticEvents_sendExpectedEventNames() { - XCTAssertEqual(BTShopperInsightsAnalytics.payPalPresented, "shopper-insights:paypal-presented") + XCTAssertEqual(BTShopperInsightsAnalytics.buttonPresented, "shopper-insights:button-presented") XCTAssertEqual(BTShopperInsightsAnalytics.payPalSelected, "shopper-insights:paypal-selected") - XCTAssertEqual(BTShopperInsightsAnalytics.venmoPresented, "shopper-insights:venmo-presented") + XCTAssertEqual(BTShopperInsightsAnalytics.buttonPresented, "shopper-insights:button-presented") XCTAssertEqual(BTShopperInsightsAnalytics.venmoSelected, "shopper-insights:venmo-selected") XCTAssertEqual(BTShopperInsightsAnalytics.recommendedPaymentsStarted, "shopper-insights:get-recommended-payments:started") XCTAssertEqual(BTShopperInsightsAnalytics.recommendedPaymentsSucceeded, "shopper-insights:get-recommended-payments:succeeded") diff --git a/UnitTests/BraintreeShopperInsightsTests/BTShopperInsightsClient_Tests.swift b/UnitTests/BraintreeShopperInsightsTests/BTShopperInsightsClient_Tests.swift index 0f5501c10..8440ac76f 100644 --- a/UnitTests/BraintreeShopperInsightsTests/BTShopperInsightsClient_Tests.swift +++ b/UnitTests/BraintreeShopperInsightsTests/BTShopperInsightsClient_Tests.swift @@ -206,20 +206,42 @@ class BTShopperInsightsClient_Tests: XCTestCase { // MARK: - Analytics - func testSendPayPalPresentedEvent_sendsAnalytic() { - sut.sendPayPalPresentedEvent() - XCTAssertEqual(mockAPIClient.postedAnalyticsEvents.first, "shopper-insights:paypal-presented") - XCTAssertEqual(mockAPIClient.postedShopperSessionID, "fake-shopper-session-id") + func testSendPayPalPresentedEvent_whenExperimentTypeIsControl_sendsAnalytic() { + let presentmentDetails = BTPresentmentDetails( + buttonOrder: .first, + experimentType: .control, + pageType: .about + ) + sut.sendPresentedEvent(for: .payPal, presentmentDetails: presentmentDetails) + XCTAssertEqual(mockAPIClient.postedAnalyticsEvents.first, "shopper-insights:button-presented") + XCTAssertEqual(mockAPIClient.postedButtonOrder, "1") + XCTAssertEqual(mockAPIClient.postedButtonType, "PayPal") + XCTAssertEqual(mockAPIClient.postedMerchantExperiment, + """ + [ + { "exp_name" : "PaymentReady" } + { "treatment_name" : "control" } + ] + """) + XCTAssertEqual(mockAPIClient.postedPageType, "about") } - - 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 testSendPayPalPresentedEvent_whenExperimentTypeIsTest_sendsAnalytic() { + let presentmentDetails = BTPresentmentDetails( + buttonOrder: .first, + experimentType: .test, + pageType: .about + ) + sut.sendPresentedEvent(for: .payPal, presentmentDetails: presentmentDetails) + XCTAssertEqual(mockAPIClient.postedMerchantExperiment, + """ + [ + { "exp_name" : "PaymentReady" } + { "treatment_name" : "test" } + ] + """) } - + func testSendPayPalSelectedEvent_sendsAnalytic() { sut.sendPayPalSelectedEvent() XCTAssertEqual(mockAPIClient.postedAnalyticsEvents.first, "shopper-insights:paypal-selected") @@ -227,9 +249,39 @@ class BTShopperInsightsClient_Tests: XCTestCase { } func testSendVenmoPresentedEvent_sendsAnalytic() { - sut.sendVenmoPresentedEvent() - XCTAssertEqual(mockAPIClient.postedAnalyticsEvents.first, "shopper-insights:venmo-presented") - XCTAssertEqual(mockAPIClient.postedShopperSessionID, "fake-shopper-session-id") + let presentmentDetails = BTPresentmentDetails( + buttonOrder: .first, + experimentType: .control, + pageType: .about + ) + sut.sendPresentedEvent(for: .venmo, presentmentDetails: presentmentDetails) + XCTAssertEqual(mockAPIClient.postedAnalyticsEvents.first, "shopper-insights:button-presented") + XCTAssertEqual(mockAPIClient.postedButtonOrder, "1") + XCTAssertEqual(mockAPIClient.postedButtonType, "Venmo") + XCTAssertEqual(mockAPIClient.postedMerchantExperiment, + """ + [ + { "exp_name" : "PaymentReady" } + { "treatment_name" : "control" } + ] + """) + XCTAssertEqual(mockAPIClient.postedPageType, "about") + } + + func testSendVenmoPresentedEvent_whenExperimentTypeIsTest_sendsAnalytic() { + let presentmentDetails = BTPresentmentDetails( + buttonOrder: .first, + experimentType: .test, + pageType: .about + ) + sut.sendPresentedEvent(for: .venmo, presentmentDetails: presentmentDetails) + XCTAssertEqual(mockAPIClient.postedMerchantExperiment, + """ + [ + { "exp_name" : "PaymentReady" } + { "treatment_name" : "test" } + ] + """) } func testSendVenmoSelectedEvent_sendsAnalytic() { diff --git a/UnitTests/BraintreeTestShared/MockAPIClient.swift b/UnitTests/BraintreeTestShared/MockAPIClient.swift index 2fa732434..ba9b05d81 100644 --- a/UnitTests/BraintreeTestShared/MockAPIClient.swift +++ b/UnitTests/BraintreeTestShared/MockAPIClient.swift @@ -12,12 +12,15 @@ public class MockAPIClient: BTAPIClient { public var lastGETAPIClientHTTPType: BTAPIClientHTTPService? public var postedAnalyticsEvents : [String] = [] - public var postedPayPalContextID: String? = nil - public var postedLinkType: LinkType? = nil + public var postedAppSwitchURL: [String: String?] = [:] + public var postedButtonOrder: String? = nil + public var postedButtonType: String? = nil public var postedIsVaultRequest = false + public var postedLinkType: LinkType? = nil public var postedMerchantExperiment: String? = nil + public var postedPageType: String? = nil public var postedPaymentMethodsDisplayed: String? = nil - public var postedAppSwitchURL: [String: String?] = [:] + public var postedPayPalContextID: String? = nil public var postedShopperSessionID: String? = nil @objc public var cannedConfigurationResponseBody : BTJSON? = nil @@ -93,25 +96,31 @@ public class MockAPIClient: BTAPIClient { } public override func sendAnalyticsEvent( - _ name: String, + _ eventName: String, + appSwitchURL: URL? = nil, + buttonOrder: String? = nil, + buttonType: String? = nil, correlationID: String? = nil, errorDescription: String? = nil, merchantExperiment experiment: String? = nil, isConfigFromCache: Bool? = nil, isVaultRequest: Bool? = nil, linkType: LinkType? = nil, + pageType: String? = nil, paymentMethodsDisplayed: String? = nil, payPalContextID: String? = nil, - appSwitchURL: URL? = nil, shopperSessionID: String? = nil ) { + postedButtonType = buttonType + postedButtonOrder = buttonOrder + postedPageType = pageType postedPayPalContextID = payPalContextID postedLinkType = linkType postedIsVaultRequest = isVaultRequest ?? false postedMerchantExperiment = experiment postedPaymentMethodsDisplayed = paymentMethodsDisplayed - postedAppSwitchURL[name] = appSwitchURL?.absoluteString - postedAnalyticsEvents.append(name) + postedAppSwitchURL[eventName] = appSwitchURL?.absoluteString + postedAnalyticsEvents.append(eventName) postedShopperSessionID = shopperSessionID }