From 6b3450bbc5f733dad2a5732385f7a52d0b302750 Mon Sep 17 00:00:00 2001 From: Stephanie <127455800+stechiu@users.noreply.github.com> Date: Fri, 6 Dec 2024 10:33:07 -0800 Subject: [PATCH 01/14] [RP1][iOS] Expose PayPal and Venmo App Installed for Merchants (#1473) * Added `isPayPalAppInstalled`, `isVenmoAppInstalled` methods * Moved methods to Shopper Insights * Fixed failing unit tests. Added to CHANGELOG * Addressed PR comment * Update Sources/BraintreeShopperInsights/BTShopperInsightsClient.swift --- CHANGELOG.md | 2 ++ .../BTShopperInsightsClient.swift | 16 ++++++++- .../BTShopperInsightsClient_Tests.swift | 34 +++++++++++++++++++ 3 files changed, 51 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 156f06052..4ffda6028 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ * Add `BTPayPalRequest.userPhoneNumber` optional property * BraintreeVenmo * Send `url` in `event_params` for App Switch events to PayPal's analytics service (FPTI) +* BraintreeShopperInsights (BETA) + * Add `isPayPalAppInstalled()` and/or `isVenmoAppInstalled()` ## 6.24.0 (2024-10-15) * BraintreePayPal diff --git a/Sources/BraintreeShopperInsights/BTShopperInsightsClient.swift b/Sources/BraintreeShopperInsights/BTShopperInsightsClient.swift index e5dc83a6c..c40368229 100644 --- a/Sources/BraintreeShopperInsights/BTShopperInsightsClient.swift +++ b/Sources/BraintreeShopperInsights/BTShopperInsightsClient.swift @@ -25,6 +25,8 @@ public class BTShopperInsightsClient { self.apiClient = apiClient } + // MARK: - Public Methods + /// This method confirms if the customer is a user of PayPal services using their email and phone number. /// - Parameters: /// - request: Required: A `BTShopperInsightsRequest` containing the buyer's user information. @@ -121,7 +123,19 @@ public class BTShopperInsightsClient { public func sendVenmoSelectedEvent() { apiClient.sendAnalyticsEvent(BTShopperInsightsAnalytics.venmoSelected) } - + + /// Indicates whether the PayPal App is installed. + /// - Warning: This method is currently in beta and may change or be removed in future releases. + public func isPayPalAppInstalled() -> Bool { + application.isPayPalAppInstalled() + } + + /// Indicates whether the Venmo App is installed. + /// - Warning: This method is currently in beta and may change or be removed in future releases. + public func isVenmoAppInstalled() -> Bool { + application.isVenmoAppInstalled() + } + // MARK: - Analytics Helper Methods private func notifySuccess(with result: BTShopperInsightsResult, for experiment: String?) -> BTShopperInsightsResult { diff --git a/UnitTests/BraintreeShopperInsightsTests/BTShopperInsightsClient_Tests.swift b/UnitTests/BraintreeShopperInsightsTests/BTShopperInsightsClient_Tests.swift index 168e28213..a8019bbdf 100644 --- a/UnitTests/BraintreeShopperInsightsTests/BTShopperInsightsClient_Tests.swift +++ b/UnitTests/BraintreeShopperInsightsTests/BTShopperInsightsClient_Tests.swift @@ -227,4 +227,38 @@ class BTShopperInsightsClient_Tests: XCTestCase { sut.sendVenmoSelectedEvent() XCTAssertEqual(mockAPIClient.postedAnalyticsEvents.first, "shopper-insights:venmo-selected") } + + // MARK: - App Installed Methods + + func testIsPayPalAppInstalled_whenPayPalAppNotInstalled_returnsFalse() { + let fakeApplication = FakeApplication() + fakeApplication.cannedCanOpenURL = false + + XCTAssertFalse(sut.isPayPalAppInstalled()) + } + + func testIsPayPalAppInstalled_whenPayPalAppIsInstalled_returnsTrue() { + let fakeApplication = FakeApplication() + fakeApplication.cannedCanOpenURL = true + fakeApplication.canOpenURLWhitelist.append(URL(string: "paypal-app-switch-checkout://x-callback-url/path")!) + sut.application = fakeApplication + + XCTAssertTrue(sut.isPayPalAppInstalled()) + } + + func testIsVenmoAppInstalled_whenVenmoAppNotInstalled_returnsFalse() { + let fakeApplication = FakeApplication() + fakeApplication.cannedCanOpenURL = false + + XCTAssertFalse(sut.isVenmoAppInstalled()) + } + + func testIsVenmoAppInstalled_whenVenmoAppIsInstalled_returnsTrue() { + let fakeApplication = FakeApplication() + fakeApplication.cannedCanOpenURL = true + fakeApplication.canOpenURLWhitelist.append(URL(string: "com.venmo.touch.v2://x-callback-url/path")!) + sut.application = fakeApplication + + XCTAssertTrue(sut.isVenmoAppInstalled()) + } } From cabadbb217ff68541c26197b837db6007be9a6b9 Mon Sep 17 00:00:00 2001 From: Stephanie <127455800+stechiu@users.noreply.github.com> Date: Mon, 9 Dec 2024 09:23:55 -0800 Subject: [PATCH 02/14] Add Shopper Session ID to `BTShopperInsightsClient` [RP1] (#1479) * Added `sessionID` to BTShopperInsightClient. Added unit tests * Added unit tests. Updated demo app * Added to CHANGELOG * Removed `payPalSessionID` --- CHANGELOG.md | 1 + .../Features/ShopperInsightsViewController.swift | 2 +- .../BraintreeCore/Analytics/FPTIBatchData.swift | 1 + .../BTShopperInsightsClient.swift | 12 ++++++++++-- .../Analytics/FPTIBatchData_Tests.swift | 14 +++++++------- .../BTShopperInsightsClient_Tests.swift | 7 ++++++- 6 files changed, 26 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4ffda6028..ff8a0563d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ * BraintreeVenmo * Send `url` in `event_params` for App Switch events to PayPal's analytics service (FPTI) * BraintreeShopperInsights (BETA) + * Add `shopperSessionID` to `BTShopperInsightsClient` initializer * Add `isPayPalAppInstalled()` and/or `isVenmoAppInstalled()` ## 6.24.0 (2024-10-15) diff --git a/Demo/Application/Features/ShopperInsightsViewController.swift b/Demo/Application/Features/ShopperInsightsViewController.swift index 5e6a3787a..d46a14d53 100644 --- a/Demo/Application/Features/ShopperInsightsViewController.swift +++ b/Demo/Application/Features/ShopperInsightsViewController.swift @@ -6,7 +6,7 @@ import BraintreeShopperInsights class ShopperInsightsViewController: PaymentButtonBaseViewController { - lazy var shopperInsightsClient = BTShopperInsightsClient(apiClient: apiClient) + lazy var shopperInsightsClient = BTShopperInsightsClient(apiClient: apiClient, shopperSessionID: "123456") lazy var payPalClient = BTPayPalClient(apiClient: apiClient) lazy var venmoClient = BTVenmoClient(apiClient: apiClient) diff --git a/Sources/BraintreeCore/Analytics/FPTIBatchData.swift b/Sources/BraintreeCore/Analytics/FPTIBatchData.swift index 0284172c3..f986717b6 100644 --- a/Sources/BraintreeCore/Analytics/FPTIBatchData.swift +++ b/Sources/BraintreeCore/Analytics/FPTIBatchData.swift @@ -178,6 +178,7 @@ struct FPTIBatchData: Codable { let platform = "iOS" + /// Either a randomly generated session ID or the shopper session ID passed in by a merchant let sessionID: String let tokenizationKey: String? diff --git a/Sources/BraintreeShopperInsights/BTShopperInsightsClient.swift b/Sources/BraintreeShopperInsights/BTShopperInsightsClient.swift index c40368229..f2b81849e 100644 --- a/Sources/BraintreeShopperInsights/BTShopperInsightsClient.swift +++ b/Sources/BraintreeShopperInsights/BTShopperInsightsClient.swift @@ -17,12 +17,20 @@ public class BTShopperInsightsClient { // MARK: - Private Properties private let apiClient: BTAPIClient + private let shopperSessionID: String? /// Creates a `BTShopperInsightsClient` - /// - Parameter apiClient: A `BTAPIClient` instance. + /// - Parameters: + /// - apiClient: A `BTAPIClient` instance. + /// - shopperSessionID: This value should be the shopper session ID returned from your server SDK request /// - Warning: This features only works with a client token. - public init(apiClient: BTAPIClient) { + public init(apiClient: BTAPIClient, shopperSessionID: String? = nil) { self.apiClient = apiClient + self.shopperSessionID = shopperSessionID + + if let shopperSessionID { + apiClient.metadata.sessionID = shopperSessionID + } } // MARK: - Public Methods diff --git a/UnitTests/BraintreeCoreTests/Analytics/FPTIBatchData_Tests.swift b/UnitTests/BraintreeCoreTests/Analytics/FPTIBatchData_Tests.swift index 72b5f2b5d..bb8f201c4 100644 --- a/UnitTests/BraintreeCoreTests/Analytics/FPTIBatchData_Tests.swift +++ b/UnitTests/BraintreeCoreTests/Analytics/FPTIBatchData_Tests.swift @@ -3,9 +3,9 @@ import XCTest @testable import BraintreeCore final class FPTIBatchData_Tests: XCTestCase { - + var sut: FPTIBatchData! - + let batchMetadata = FPTIBatchData.Metadata( authorizationFingerprint: "fake-auth", environment: "fake-env", @@ -14,7 +14,7 @@ final class FPTIBatchData_Tests: XCTestCase { sessionID: "fake-session", tokenizationKey: "fake-auth" ) - + let eventParams = [ FPTIBatchData.Event( connectionStartTime: 123, @@ -45,13 +45,13 @@ final class FPTIBatchData_Tests: XCTestCase { startTime: nil ) ] - + override func setUp() { super.setUp() sut = FPTIBatchData(metadata: batchMetadata, events: eventParams) } - + func testInit_formatsJSONBody() throws { let jsonBody = try sut.toDictionary() @@ -59,12 +59,12 @@ final class FPTIBatchData_Tests: XCTestCase { XCTFail("JSON body missing top level `events` key.") return } - + guard let eventParams = events[0]["event_params"] as? [[String: Any]] else { XCTFail("JSON body missing `event_params` key.") return } - + guard let batchParams = events[0]["batch_params"] as? [String: Any] else { XCTFail("JSON body missing `batch_params` key.") return diff --git a/UnitTests/BraintreeShopperInsightsTests/BTShopperInsightsClient_Tests.swift b/UnitTests/BraintreeShopperInsightsTests/BTShopperInsightsClient_Tests.swift index a8019bbdf..47806902f 100644 --- a/UnitTests/BraintreeShopperInsightsTests/BTShopperInsightsClient_Tests.swift +++ b/UnitTests/BraintreeShopperInsightsTests/BTShopperInsightsClient_Tests.swift @@ -28,7 +28,7 @@ class BTShopperInsightsClient_Tests: XCTestCase { } ] """ - + override func setUp() { super.setUp() mockAPIClient = MockAPIClient(authorization: clientToken) @@ -228,6 +228,11 @@ class BTShopperInsightsClient_Tests: XCTestCase { XCTAssertEqual(mockAPIClient.postedAnalyticsEvents.first, "shopper-insights:venmo-selected") } + func testShopperInsightsClient_withSessionID_setSessionIDInMetadata() { + sut = BTShopperInsightsClient(apiClient: mockAPIClient, shopperSessionID: "123456") + XCTAssertEqual(mockAPIClient.metadata.sessionID, "123456") + } + // MARK: - App Installed Methods func testIsPayPalAppInstalled_whenPayPalAppNotInstalled_returnsFalse() { From 8ef01a76dea5bcb40bdf37c00888ef90a934bcae Mon Sep 17 00:00:00 2001 From: warmkesselj <150195168+warmkesselj@users.noreply.github.com> Date: Mon, 9 Dec 2024 13:56:29 -0800 Subject: [PATCH 03/14] Shopper insights rp1 feature include session (#1481) * Add shopperSessionID to BTPayPalRequest * Update parameters to Vault and Checkout request * Testing shopperSessionId * Demo app textField for shopper session Id * Added unit test * Updated CHANGELOG * Fixed lint error * Remove UI for demo app and adding shopper sessionId property to native checkout and web checkout. * Update comment for shopper session Id * Fixed lint errors * PR update --------- Co-authored-by: stechiu --- CHANGELOG.md | 1 + .../Features/ShopperInsightsViewController.swift | 3 +++ Sources/BraintreePayPal/BTPayPalRequest.swift | 11 ++++++++++- .../BraintreePayPalTests/BTPayPalRequest_Tests.swift | 2 ++ 4 files changed, 16 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ff8a0563d..a0b46587c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## unreleased * BraintreePayPal * Add `BTPayPalRequest.userPhoneNumber` optional property + * Add `shopperSessionID` to `BTPayPalCheckoutRequest` and `BTPayPalVaultRequest` * BraintreeVenmo * Send `url` in `event_params` for App Switch events to PayPal's analytics service (FPTI) * BraintreeShopperInsights (BETA) diff --git a/Demo/Application/Features/ShopperInsightsViewController.swift b/Demo/Application/Features/ShopperInsightsViewController.swift index d46a14d53..820f914ae 100644 --- a/Demo/Application/Features/ShopperInsightsViewController.swift +++ b/Demo/Application/Features/ShopperInsightsViewController.swift @@ -13,6 +13,8 @@ class ShopperInsightsViewController: PaymentButtonBaseViewController { lazy var payPalVaultButton = createButton(title: "PayPal Vault", action: #selector(payPalVaultButtonTapped)) lazy var venmoButton = createButton(title: "Venmo", action: #selector(venmoButtonTapped)) + private var shopperSessionID = "test-shopper-session-id" + lazy var emailView: TextFieldWithLabel = { let view = TextFieldWithLabel() view.label.text = "Email" @@ -119,6 +121,7 @@ class ShopperInsightsViewController: PaymentButtonBaseViewController { button.isEnabled = false let paypalRequest = BTPayPalVaultRequest() + paypalRequest.shopperSessionID = shopperSessionID paypalRequest.userAuthenticationEmail = emailView.textField.text payPalClient.tokenize(paypalRequest) { nonce, error in diff --git a/Sources/BraintreePayPal/BTPayPalRequest.swift b/Sources/BraintreePayPal/BTPayPalRequest.swift index c7d733d97..b4cd2cca1 100644 --- a/Sources/BraintreePayPal/BTPayPalRequest.swift +++ b/Sources/BraintreePayPal/BTPayPalRequest.swift @@ -100,6 +100,9 @@ import BraintreeCore /// Optional: A user's phone number to initiate a quicker authentication flow in the scenario where the user has a PayPal account /// identified with the same phone number. public var userPhoneNumber: BTPayPalPhoneNumber? + + /// Optional: The shopper session ID returned from your shopper insights server SDK integration. + public var shopperSessionID: String? // MARK: - Static Properties @@ -120,7 +123,8 @@ import BraintreeCore lineItems: [BTPayPalLineItem]? = nil, billingAgreementDescription: String? = nil, riskCorrelationId: String? = nil, - userPhoneNumber: BTPayPalPhoneNumber? = nil + userPhoneNumber: BTPayPalPhoneNumber? = nil, + shopperSessionID: String? = nil ) { self.hermesPath = hermesPath self.paymentType = paymentType @@ -135,6 +139,7 @@ import BraintreeCore self.billingAgreementDescription = billingAgreementDescription self.riskCorrelationID = riskCorrelationId self.userPhoneNumber = userPhoneNumber + self.shopperSessionID = shopperSessionID } // MARK: Public Methods @@ -179,6 +184,10 @@ import BraintreeCore if let userPhoneNumberDict = try? userPhoneNumber?.toDictionary() { parameters["phone_number"] = userPhoneNumberDict } + + if let shopperSessionID { + parameters["shopper_session_id"] = shopperSessionID + } parameters["return_url"] = BTCoreConstants.callbackURLScheme + "://\(BTPayPalRequest.callbackURLHostAndPath)success" parameters["cancel_url"] = BTCoreConstants.callbackURLScheme + "://\(BTPayPalRequest.callbackURLHostAndPath)cancel" diff --git a/UnitTests/BraintreePayPalTests/BTPayPalRequest_Tests.swift b/UnitTests/BraintreePayPalTests/BTPayPalRequest_Tests.swift index 304d349f6..c9caf02ea 100644 --- a/UnitTests/BraintreePayPalTests/BTPayPalRequest_Tests.swift +++ b/UnitTests/BraintreePayPalTests/BTPayPalRequest_Tests.swift @@ -46,6 +46,7 @@ class BTPayPalRequest_Tests: XCTestCase { request.riskCorrelationID = "123-correlation-id" request.merchantAccountID = "merchant-account-id" request.isShippingAddressEditable = true + request.shopperSessionID = "123456" let lineItem = BTPayPalLineItem(quantity: "1", unitAmount: "1", name: "item", kind: .credit) lineItem.imageURL = URL(string: "http://example/image.jpg") @@ -73,6 +74,7 @@ class BTPayPalRequest_Tests: XCTestCase { XCTAssertEqual(parameters["return_url"] as? String, "sdk.ios.braintree://onetouch/v1/success") XCTAssertEqual(parameters["cancel_url"] as? String, "sdk.ios.braintree://onetouch/v1/cancel") + XCTAssertEqual(parameters["shopper_session_id"] as? String, "123456") } func testParametersWithConfiguration_whenShippingAddressIsRequiredNotSet_returnsNoShippingTrue() { From 786d57e4440828b70ff26041cb3c80b4e8188e83 Mon Sep 17 00:00:00 2001 From: warmkesselj <150195168+warmkesselj@users.noreply.github.com> Date: Wed, 11 Dec 2024 11:00:40 -0800 Subject: [PATCH 04/14] =?UTF-8?q?If=20a=20shopperSessionID=20exists,=20ove?= =?UTF-8?q?rride=20the=20value=20with=20the=20one=20retriev=E2=80=A6=20(#1?= =?UTF-8?q?482)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * If a shopperSessionID exists, override the value with the one retrieved from Shopper Insights Server * Add unit tests * Update unit tests --- Sources/BraintreePayPal/BTPayPalClient.swift | 7 ++++++- .../BTPayPalClient_Tests.swift | 20 +++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/Sources/BraintreePayPal/BTPayPalClient.swift b/Sources/BraintreePayPal/BTPayPalClient.swift index 0362ed159..8abc24aed 100644 --- a/Sources/BraintreePayPal/BTPayPalClient.swift +++ b/Sources/BraintreePayPal/BTPayPalClient.swift @@ -312,7 +312,12 @@ 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) apiClient.fetchOrReturnRemoteConfiguration { configuration, error in if let error { diff --git a/UnitTests/BraintreePayPalTests/BTPayPalClient_Tests.swift b/UnitTests/BraintreePayPalTests/BTPayPalClient_Tests.swift index 7fff4ad48..eee41d65c 100644 --- a/UnitTests/BraintreePayPalTests/BTPayPalClient_Tests.swift +++ b/UnitTests/BraintreePayPalTests/BTPayPalClient_Tests.swift @@ -1028,4 +1028,24 @@ class BTPayPalClient_Tests: XCTestCase { XCTAssertFalse(mockAPIClient.postedIsVaultRequest) } + + func testTokenize_whenCheckoutRequest_setSessionID() async { + XCTAssertNotEqual(mockAPIClient.metadata.sessionID, "test-shopper-insights-id") + + 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") + + let checkoutRequest = BTPayPalVaultRequest() + checkoutRequest.shopperSessionID = "test-shopper-insights-id" + let _ = try? await payPalClient.tokenize(checkoutRequest) + + XCTAssertEqual(mockAPIClient.metadata.sessionID, "test-shopper-insights-id") + } } From 2a00f6ef5de33f21fa4b8a5e4ac8f7776f887d25 Mon Sep 17 00:00:00 2001 From: scannillo <35243507+scannillo@users.noreply.github.com> Date: Mon, 16 Dec 2024 12:28:45 -0600 Subject: [PATCH 05/14] ShopperInsights - send `shopper_session_id` to FPTI (#1485) --- .../Analytics/FPTIBatchData.swift | 5 +++ Sources/BraintreeCore/BTAPIClient.swift | 6 ++- Sources/BraintreePayPal/BTPayPalClient.swift | 44 ++++++++++++------- .../BTShopperInsightsClient.swift | 23 +++++----- .../BTPayPalClient_Tests.swift | 17 ++----- .../BTShopperInsightsClient_Tests.swift | 17 ++++--- .../BraintreeTestShared/MockAPIClient.swift | 7 ++- 7 files changed, 67 insertions(+), 52 deletions(-) diff --git a/Sources/BraintreeCore/Analytics/FPTIBatchData.swift b/Sources/BraintreeCore/Analytics/FPTIBatchData.swift index f986717b6..4ae467013 100644 --- a/Sources/BraintreeCore/Analytics/FPTIBatchData.swift +++ b/Sources/BraintreeCore/Analytics/FPTIBatchData.swift @@ -56,6 +56,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) @@ -76,6 +78,7 @@ struct FPTIBatchData: Codable { paymentMethodsDisplayed: String? = nil, payPalContextID: String? = nil, requestStartTime: Int? = nil, + shopperSessionID: String? = nil, startTime: Int? = nil ) { self.appSwitchURL = appSwitchURL?.absoluteString @@ -92,6 +95,7 @@ struct FPTIBatchData: Codable { self.paymentMethodsDisplayed = paymentMethodsDisplayed self.payPalContextID = payPalContextID self.requestStartTime = requestStartTime + self.shopperSessionID = shopperSessionID self.startTime = startTime } @@ -110,6 +114,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" diff --git a/Sources/BraintreeCore/BTAPIClient.swift b/Sources/BraintreeCore/BTAPIClient.swift index 1d3b04f4d..79c42c43f 100644 --- a/Sources/BraintreeCore/BTAPIClient.swift +++ b/Sources/BraintreeCore/BTAPIClient.swift @@ -311,7 +311,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( @@ -324,7 +325,8 @@ import Foundation linkType: linkType?.rawValue, merchantExperiment: merchantExperiment, paymentMethodsDisplayed: paymentMethodsDisplayed, - payPalContextID: payPalContextID + payPalContextID: payPalContextID, + shopperSessionID: shopperSessionID ) ) } diff --git a/Sources/BraintreePayPal/BTPayPalClient.swift b/Sources/BraintreePayPal/BTPayPalClient.swift index 8abc24aed..30e876e3d 100644 --- a/Sources/BraintreePayPal/BTPayPalClient.swift +++ b/Sources/BraintreePayPal/BTPayPalClient.swift @@ -186,6 +186,7 @@ import BraintreeDataCollector // MARK: - Internal Methods + // swiftlint:disable function_body_length func handleReturn( _ url: URL?, paymentType: BTPayPalPaymentType, @@ -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 { @@ -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) @@ -337,7 +340,6 @@ import BraintreeDataCollector return } - self.payPalRequest = request self.apiClient.post( request.hermesPath, parameters: request.parameters( @@ -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) @@ -419,7 +422,8 @@ import BraintreeDataCollector BTPayPalAnalytics.appSwitchSucceeded, isVaultRequest: isVaultRequest, linkType: linkType, - payPalContextID: payPalContextID + payPalContextID: payPalContextID, + shopperSessionID: payPalRequest?.shopperSessionID ) BTPayPalClient.payPalClient = self appSwitchCompletion = completion @@ -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) } @@ -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 @@ -515,7 +522,8 @@ import BraintreeDataCollector correlationID: clientMetadataID, isVaultRequest: isVaultRequest, linkType: linkType, - payPalContextID: payPalContextID + payPalContextID: payPalContextID, + shopperSessionID: payPalRequest?.shopperSessionID ) completion(result, nil) } @@ -527,7 +535,8 @@ import BraintreeDataCollector errorDescription: error.localizedDescription, isVaultRequest: isVaultRequest, linkType: linkType, - payPalContextID: payPalContextID + payPalContextID: payPalContextID, + shopperSessionID: payPalRequest?.shopperSessionID ) completion(nil, error) } @@ -538,7 +547,8 @@ import BraintreeDataCollector correlationID: clientMetadataID, isVaultRequest: isVaultRequest, linkType: linkType, - payPalContextID: payPalContextID + payPalContextID: payPalContextID, + shopperSessionID: payPalRequest?.shopperSessionID ) completion(nil, BTPayPalError.canceled) } diff --git a/Sources/BraintreeShopperInsights/BTShopperInsightsClient.swift b/Sources/BraintreeShopperInsights/BTShopperInsightsClient.swift index f2b81849e..302406c5f 100644 --- a/Sources/BraintreeShopperInsights/BTShopperInsightsClient.swift +++ b/Sources/BraintreeShopperInsights/BTShopperInsightsClient.swift @@ -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 @@ -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 { @@ -102,14 +99,15 @@ 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) + apiClient.sendAnalyticsEvent(BTShopperInsightsAnalytics.payPalSelected, shopperSessionID: shopperSessionID) } /// Call this method when the Venmo button has been successfully displayed to the buyer. @@ -122,14 +120,15 @@ public class BTShopperInsightsClient { apiClient.sendAnalyticsEvent( BTShopperInsightsAnalytics.venmoPresented, merchantExperiment: experiment, - paymentMethodsDisplayed: paymentMethodsDisplayedString + 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() { - apiClient.sendAnalyticsEvent(BTShopperInsightsAnalytics.venmoSelected) + apiClient.sendAnalyticsEvent(BTShopperInsightsAnalytics.venmoSelected, shopperSessionID: shopperSessionID) } /// Indicates whether the PayPal App is installed. @@ -149,7 +148,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 } @@ -158,7 +158,8 @@ public class BTShopperInsightsClient { apiClient.sendAnalyticsEvent( BTShopperInsightsAnalytics.recommendedPaymentsFailed, errorDescription: error.localizedDescription, - merchantExperiment: experiment + merchantExperiment: experiment, + shopperSessionID: shopperSessionID ) return error } diff --git a/UnitTests/BraintreePayPalTests/BTPayPalClient_Tests.swift b/UnitTests/BraintreePayPalTests/BTPayPalClient_Tests.swift index eee41d65c..14527dbb6 100644 --- a/UnitTests/BraintreePayPalTests/BTPayPalClient_Tests.swift +++ b/UnitTests/BraintreePayPalTests/BTPayPalClient_Tests.swift @@ -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") } } diff --git a/UnitTests/BraintreeShopperInsightsTests/BTShopperInsightsClient_Tests.swift b/UnitTests/BraintreeShopperInsightsTests/BTShopperInsightsClient_Tests.swift index 47806902f..0f5501c10 100644 --- a/UnitTests/BraintreeShopperInsightsTests/BTShopperInsightsClient_Tests.swift +++ b/UnitTests/BraintreeShopperInsightsTests/BTShopperInsightsClient_Tests.swift @@ -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() @@ -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") } } @@ -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.") } @@ -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.") } @@ -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.") } @@ -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.") } @@ -204,6 +209,7 @@ 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() { @@ -211,26 +217,25 @@ class BTShopperInsightsClient_Tests: XCTestCase { 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.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() { sut.sendVenmoSelectedEvent() XCTAssertEqual(mockAPIClient.postedAnalyticsEvents.first, "shopper-insights:venmo-selected") - } - - func testShopperInsightsClient_withSessionID_setSessionIDInMetadata() { - sut = BTShopperInsightsClient(apiClient: mockAPIClient, shopperSessionID: "123456") - XCTAssertEqual(mockAPIClient.metadata.sessionID, "123456") + XCTAssertEqual(mockAPIClient.postedShopperSessionID, "fake-shopper-session-id") } // MARK: - App Installed Methods diff --git a/UnitTests/BraintreeTestShared/MockAPIClient.swift b/UnitTests/BraintreeTestShared/MockAPIClient.swift index 77ff070ec..2fa732434 100644 --- a/UnitTests/BraintreeTestShared/MockAPIClient.swift +++ b/UnitTests/BraintreeTestShared/MockAPIClient.swift @@ -18,7 +18,8 @@ public class MockAPIClient: BTAPIClient { public var postedMerchantExperiment: String? = nil public var postedPaymentMethodsDisplayed: String? = nil public var postedAppSwitchURL: [String: String?] = [:] - + public var postedShopperSessionID: String? = nil + @objc public var cannedConfigurationResponseBody : BTJSON? = nil @objc public var cannedConfigurationResponseError : NSError? = nil @@ -101,7 +102,8 @@ public class MockAPIClient: BTAPIClient { linkType: LinkType? = nil, paymentMethodsDisplayed: String? = nil, payPalContextID: String? = nil, - appSwitchURL: URL? = nil + appSwitchURL: URL? = nil, + shopperSessionID: String? = nil ) { postedPayPalContextID = payPalContextID postedLinkType = linkType @@ -110,6 +112,7 @@ public class MockAPIClient: BTAPIClient { postedPaymentMethodsDisplayed = paymentMethodsDisplayed postedAppSwitchURL[name] = appSwitchURL?.absoluteString postedAnalyticsEvents.append(name) + postedShopperSessionID = shopperSessionID } func didFetchPaymentMethods(sorted: Bool) -> Bool { From b02bb4339b72899234b0a8721a9ea47f84643ea9 Mon Sep 17 00:00:00 2001 From: warmkesselj <150195168+warmkesselj@users.noreply.github.com> Date: Fri, 20 Dec 2024 08:59:16 -0800 Subject: [PATCH 06/14] Shopper insights rp1 feature fixes from main merge (#1490) * Changelog * Fix merge conflict * update invokedOpenURLSuccessfully with main * update CHANGELOG from merge confict --------- Co-authored-by: Jax DesMarais-Leder --- CHANGELOG.md | 9 ++-- Sources/BraintreePayPal/BTPayPalClient.swift | 48 ++++++++++---------- 2 files changed, 29 insertions(+), 28 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 410d1503f..aa1c62d67 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,14 +1,15 @@ # Braintree iOS SDK Release Notes -## 6.25.0 (2024-12-11) +## unreleased * BraintreePayPal - * Add `BTPayPalRequest.userPhoneNumber` optional property * Add `shopperSessionID` to `BTPayPalCheckoutRequest` and `BTPayPalVaultRequest` -* BraintreeVenmo - * Send `url` in `event_params` for App Switch events to PayPal's analytics service (FPTI) * BraintreeShopperInsights (BETA) * Add `shopperSessionID` to `BTShopperInsightsClient` initializer * Add `isPayPalAppInstalled()` and/or `isVenmoAppInstalled()` + +## 6.25.0 (2024-12-11) +* BraintreePayPal + * Add `BTPayPalRequest.userPhoneNumber` optional property * Send `url` in `event_params` for App Switch events to PayPal's analytics service (FPTI) * BraintreeVenmo * Send `url` in `event_params` for App Switch events to PayPal's analytics service (FPTI) diff --git a/Sources/BraintreePayPal/BTPayPalClient.swift b/Sources/BraintreePayPal/BTPayPalClient.swift index 30e876e3d..65824d32e 100644 --- a/Sources/BraintreePayPal/BTPayPalClient.swift +++ b/Sources/BraintreePayPal/BTPayPalClient.swift @@ -286,6 +286,29 @@ import BraintreeDataCollector performSwitchRequest(appSwitchURL: url, paymentType: paymentType, completion: completion) } + func invokedOpenURLSuccessfully(_ success: Bool, url: URL, completion: @escaping (BTPayPalAccountNonce?, Error?) -> Void) { + if success { + apiClient.sendAnalyticsEvent( + BTPayPalAnalytics.appSwitchSucceeded, + isVaultRequest: isVaultRequest, + linkType: linkType, + payPalContextID: payPalContextID, + appSwitchURL: url + ) + BTPayPalClient.payPalClient = self + appSwitchCompletion = completion + } else { + apiClient.sendAnalyticsEvent( + BTPayPalAnalytics.appSwitchFailed, + isVaultRequest: isVaultRequest, + linkType: linkType, + payPalContextID: payPalContextID, + appSwitchURL: url + ) + notifyFailure(with: BTPayPalError.appSwitchFailed, completion: completion) + } + } + // MARK: - App Switch Methods func handleReturnURL(_ url: URL) { @@ -412,30 +435,7 @@ import BraintreeDataCollector } application.open(redirectURL) { success in - self.invokedOpenURLSuccessfully(success, completion: completion) - } - } - - private func invokedOpenURLSuccessfully(_ success: Bool, completion: @escaping (BTPayPalAccountNonce?, Error?) -> Void) { - if success { - apiClient.sendAnalyticsEvent( - BTPayPalAnalytics.appSwitchSucceeded, - isVaultRequest: isVaultRequest, - linkType: linkType, - payPalContextID: payPalContextID, - shopperSessionID: payPalRequest?.shopperSessionID - ) - BTPayPalClient.payPalClient = self - appSwitchCompletion = completion - } else { - apiClient.sendAnalyticsEvent( - BTPayPalAnalytics.appSwitchFailed, - isVaultRequest: isVaultRequest, - linkType: linkType, - payPalContextID: payPalContextID, - shopperSessionID: payPalRequest?.shopperSessionID - ) - notifyFailure(with: BTPayPalError.appSwitchFailed, completion: completion) + self.invokedOpenURLSuccessfully(success, url: redirectURL, completion: completion) } } From 365a059673c5581372cc2413fda9d841bd367416 Mon Sep 17 00:00:00 2001 From: Stephanie <127455800+stechiu@users.noreply.github.com> Date: Fri, 20 Dec 2024 11:53:05 -0800 Subject: [PATCH 07/14] Shopper Insights - Added Presentment Details Object and Update Presented Events (#1484) * Added presentment details and events * Addressed PR comments * Added doc strings * Updated CHANGELOG and unit tests * Addressed PR comments * Addressed PR comments * Fixed failing tests * Removed test from bad merge * Fixed failing unit test * Added unit tests * Added missing unit tests * Updated `buttonOrder` and doc strings * Removed `experimentType` * Updated doc strings * Fixed failing tests * Remove experimentType * Create functions to toggle the paypal or venmo buttons and send presentment analytics at the correct time. * Update docs * Re add functions that were deleted. Chnage accessor for func used in unit test * Reverse changes to PayPalClient * Remove trailing white space * Update Sources/BraintreeShopperInsights/BTPresentmentDetails.swift * Update * Update CHANGELOG.md --------- Co-authored-by: Justin Warmkessel Co-authored-by: Jax DesMarais-Leder --- Braintree.xcodeproj/project.pbxproj | 20 +++++ CHANGELOG.md | 5 ++ .../ShopperInsightsViewController.swift | 50 ++++++++--- .../Analytics/FPTIBatchData.swift | 17 +++- Sources/BraintreeCore/BTAPIClient.swift | 8 +- Sources/BraintreePayPal/BTPayPalClient.swift | 8 +- .../BTButtonOrder.swift | 33 ++++++++ .../BTButtonType.swift | 15 ++++ .../BTExperimentType.swift | 21 +++++ .../BraintreeShopperInsights/BTPageType.swift | 45 ++++++++++ .../BTPresentmentDetails.swift | 25 ++++++ .../BTShopperInsightsAnalytics.swift | 6 +- .../BTShopperInsightsClient.swift | 36 +++----- Sources/BraintreeVenmo/BTVenmoClient.swift | 8 +- .../BTPayPalClient_Tests.swift | 2 +- .../BTShopperInsightsAnalytics_Tests.swift | 4 +- .../BTShopperInsightsClient_Tests.swift | 82 +++++++++++++++---- .../BraintreeTestShared/MockAPIClient.swift | 23 ++++-- 18 files changed, 333 insertions(+), 75 deletions(-) create mode 100644 Sources/BraintreeShopperInsights/BTButtonOrder.swift create mode 100644 Sources/BraintreeShopperInsights/BTButtonType.swift create mode 100644 Sources/BraintreeShopperInsights/BTExperimentType.swift create mode 100644 Sources/BraintreeShopperInsights/BTPageType.swift create mode 100644 Sources/BraintreeShopperInsights/BTPresentmentDetails.swift 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 } From d4c7a21037fe812551a3892f4ad2401c5d0420a2 Mon Sep 17 00:00:00 2001 From: Stephanie <127455800+stechiu@users.noreply.github.com> Date: Fri, 20 Dec 2024 12:39:36 -0800 Subject: [PATCH 08/14] Shopper Insights - Update `sendSelected` Events (#1486) * Added BTButtonType enum and `sendSelectedEvent()` * Updated unit tests * Addressed PR comments * Fixed failing unit test * Removed unnecessary unit tests * add parameters to docstring for presented event --------- Co-authored-by: Jax DesMarais-Leder --- CHANGELOG.md | 1 + .../ShopperInsightsViewController.swift | 4 ++-- .../BTShopperInsightsAnalytics.swift | 8 +++----- .../BTShopperInsightsClient.swift | 19 +++++++++---------- .../BTShopperInsightsAnalytics_Tests.swift | 4 +--- .../BTShopperInsightsClient_Tests.swift | 14 ++++++++------ .../BraintreeTestShared/MockAPIClient.swift | 2 +- 7 files changed, 25 insertions(+), 27 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 92c1cda7a..52c0d6e5f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ * `experimentType` * `pageType` * `buttonOrder` + * Replace `sendPayPalSelectedEvent()` and `sendPayPalSelectedEvent()` with `sendSelectedEvent(for:)` ## 6.25.0 (2024-12-11) * BraintreePayPal diff --git a/Demo/Application/Features/ShopperInsightsViewController.swift b/Demo/Application/Features/ShopperInsightsViewController.swift index 47c8ace2b..2e9778fbb 100644 --- a/Demo/Application/Features/ShopperInsightsViewController.swift +++ b/Demo/Application/Features/ShopperInsightsViewController.swift @@ -140,7 +140,7 @@ class ShopperInsightsViewController: PaymentButtonBaseViewController { @objc func payPalVaultButtonTapped(_ button: UIButton) { progressBlock("Tapped PayPal Vault") - shopperInsightsClient.sendPayPalSelectedEvent() + shopperInsightsClient.sendSelectedEvent(for: .payPal) button.setTitle("Processing...", for: .disabled) button.isEnabled = false @@ -157,7 +157,7 @@ class ShopperInsightsViewController: PaymentButtonBaseViewController { @objc func venmoButtonTapped(_ button: UIButton) { progressBlock("Tapped Venmo") - shopperInsightsClient.sendVenmoSelectedEvent() + shopperInsightsClient.sendSelectedEvent(for: .venmo) button.setTitle("Processing...", for: .disabled) button.isEnabled = false diff --git a/Sources/BraintreeShopperInsights/BTShopperInsightsAnalytics.swift b/Sources/BraintreeShopperInsights/BTShopperInsightsAnalytics.swift index e00f0fbfd..4ef6d8f58 100644 --- a/Sources/BraintreeShopperInsights/BTShopperInsightsAnalytics.swift +++ b/Sources/BraintreeShopperInsights/BTShopperInsightsAnalytics.swift @@ -3,12 +3,10 @@ import Foundation enum BTShopperInsightsAnalytics { // MARK: - Merchant Triggered Events - - static let payPalSelected = "shopper-insights:paypal-selected" - static let venmoSelected = "shopper-insights:venmo-selected" - + static let buttonPresented = "shopper-insights:button-presented" - + static let buttonSelected = "shopper-insights:button-selected" + // MARK: - SDK Triggered Events static let recommendedPaymentsStarted = "shopper-insights:get-recommended-payments:started" diff --git a/Sources/BraintreeShopperInsights/BTShopperInsightsClient.swift b/Sources/BraintreeShopperInsights/BTShopperInsightsClient.swift index 2dc4de8d0..23564fe1e 100644 --- a/Sources/BraintreeShopperInsights/BTShopperInsightsClient.swift +++ b/Sources/BraintreeShopperInsights/BTShopperInsightsClient.swift @@ -106,17 +106,16 @@ public class BTShopperInsightsClient { 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 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) + /// Call this method when a button has been selected/tapped by the buyer. + /// This method sends analytics to help improve the Shopper Insights feature experience. + /// - Parameter buttonType: Type of button presented - PayPal, Venmo, or Other + public func sendSelectedEvent(for buttonType: BTButtonType) { + apiClient.sendAnalyticsEvent( + BTShopperInsightsAnalytics.buttonSelected, + buttonType: buttonType.rawValue, + shopperSessionID: shopperSessionID + ) } /// Indicates whether the PayPal App is installed. diff --git a/UnitTests/BraintreeShopperInsightsTests/BTShopperInsightsAnalytics_Tests.swift b/UnitTests/BraintreeShopperInsightsTests/BTShopperInsightsAnalytics_Tests.swift index 54578a829..713762c7c 100644 --- a/UnitTests/BraintreeShopperInsightsTests/BTShopperInsightsAnalytics_Tests.swift +++ b/UnitTests/BraintreeShopperInsightsTests/BTShopperInsightsAnalytics_Tests.swift @@ -5,9 +5,7 @@ final class BTShopperInsightsAnalytics_Tests: XCTestCase { func test_recommendedPaymentAnalyticEvents_sendExpectedEventNames() { XCTAssertEqual(BTShopperInsightsAnalytics.buttonPresented, "shopper-insights:button-presented") - XCTAssertEqual(BTShopperInsightsAnalytics.payPalSelected, "shopper-insights:paypal-selected") - XCTAssertEqual(BTShopperInsightsAnalytics.buttonPresented, "shopper-insights:button-presented") - XCTAssertEqual(BTShopperInsightsAnalytics.venmoSelected, "shopper-insights:venmo-selected") + XCTAssertEqual(BTShopperInsightsAnalytics.buttonSelected, "shopper-insights:button-selected") XCTAssertEqual(BTShopperInsightsAnalytics.recommendedPaymentsStarted, "shopper-insights:get-recommended-payments:started") XCTAssertEqual(BTShopperInsightsAnalytics.recommendedPaymentsSucceeded, "shopper-insights:get-recommended-payments:succeeded") XCTAssertEqual(BTShopperInsightsAnalytics.recommendedPaymentsFailed, "shopper-insights:get-recommended-payments:failed") diff --git a/UnitTests/BraintreeShopperInsightsTests/BTShopperInsightsClient_Tests.swift b/UnitTests/BraintreeShopperInsightsTests/BTShopperInsightsClient_Tests.swift index 8440ac76f..27ac9c74f 100644 --- a/UnitTests/BraintreeShopperInsightsTests/BTShopperInsightsClient_Tests.swift +++ b/UnitTests/BraintreeShopperInsightsTests/BTShopperInsightsClient_Tests.swift @@ -192,7 +192,7 @@ class BTShopperInsightsClient_Tests: XCTestCase { } func testGetRecommendedPaymentMethods_withTokenizationKey_returnsError() async { - var apiClient = BTAPIClient(authorization: "sandbox_merchant_1234567890abc")! + let apiClient = BTAPIClient(authorization: "sandbox_merchant_1234567890abc")! let shopperInsightsClient = BTShopperInsightsClient(apiClient: apiClient) do { @@ -243,11 +243,12 @@ class BTShopperInsightsClient_Tests: XCTestCase { } func testSendPayPalSelectedEvent_sendsAnalytic() { - sut.sendPayPalSelectedEvent() - XCTAssertEqual(mockAPIClient.postedAnalyticsEvents.first, "shopper-insights:paypal-selected") + sut.sendSelectedEvent(for: .payPal) + XCTAssertEqual(mockAPIClient.postedAnalyticsEvents.first, "shopper-insights:button-selected") XCTAssertEqual(mockAPIClient.postedShopperSessionID, "fake-shopper-session-id") + XCTAssertEqual(mockAPIClient.postedButtonType, "PayPal") } - + func testSendVenmoPresentedEvent_sendsAnalytic() { let presentmentDetails = BTPresentmentDetails( buttonOrder: .first, @@ -285,8 +286,9 @@ class BTShopperInsightsClient_Tests: XCTestCase { } func testSendVenmoSelectedEvent_sendsAnalytic() { - sut.sendVenmoSelectedEvent() - XCTAssertEqual(mockAPIClient.postedAnalyticsEvents.first, "shopper-insights:venmo-selected") + sut.sendSelectedEvent(for: .venmo) + XCTAssertEqual(mockAPIClient.postedAnalyticsEvents.first, "shopper-insights:button-selected") + XCTAssertEqual(mockAPIClient.postedButtonType, "Venmo") XCTAssertEqual(mockAPIClient.postedShopperSessionID, "fake-shopper-session-id") } diff --git a/UnitTests/BraintreeTestShared/MockAPIClient.swift b/UnitTests/BraintreeTestShared/MockAPIClient.swift index ba9b05d81..3fe91dd29 100644 --- a/UnitTests/BraintreeTestShared/MockAPIClient.swift +++ b/UnitTests/BraintreeTestShared/MockAPIClient.swift @@ -11,7 +11,7 @@ public class MockAPIClient: BTAPIClient { public var lastGETParameters = [:] as [String: Any]? public var lastGETAPIClientHTTPType: BTAPIClientHTTPService? - public var postedAnalyticsEvents : [String] = [] + public var postedAnalyticsEvents: [String] = [] public var postedAppSwitchURL: [String: String?] = [:] public var postedButtonOrder: String? = nil public var postedButtonType: String? = nil From f23a8813776bb133aeb5dd4017d8a00295559f4d Mon Sep 17 00:00:00 2001 From: Jax DesMarais-Leder Date: Thu, 9 Jan 2025 09:28:16 -0600 Subject: [PATCH 09/14] remove paymentMethodsDisplayed (#1494) --- Sources/BraintreeCore/Analytics/FPTIBatchData.swift | 5 ----- Sources/BraintreeCore/BTAPIClient.swift | 2 -- UnitTests/BraintreeTestShared/MockAPIClient.swift | 3 --- 3 files changed, 10 deletions(-) diff --git a/Sources/BraintreeCore/Analytics/FPTIBatchData.swift b/Sources/BraintreeCore/Analytics/FPTIBatchData.swift index 083c29fae..8db11a6e3 100644 --- a/Sources/BraintreeCore/Analytics/FPTIBatchData.swift +++ b/Sources/BraintreeCore/Analytics/FPTIBatchData.swift @@ -54,8 +54,6 @@ struct FPTIBatchData: Codable { 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 /// This value will be PayPal Order ID, Payment Token, EC token, Billing Agreement, or Venmo Context ID depending on the flow let payPalContextID: String? @@ -84,7 +82,6 @@ struct FPTIBatchData: Codable { linkType: String? = nil, merchantExperiment: String? = nil, pageType: String? = nil, - paymentMethodsDisplayed: String? = nil, payPalContextID: String? = nil, requestStartTime: Int? = nil, shopperSessionID: String? = nil, @@ -104,7 +101,6 @@ struct FPTIBatchData: Codable { self.linkType = linkType self.merchantExperiment = merchantExperiment self.pageType = pageType - self.paymentMethodsDisplayed = paymentMethodsDisplayed self.payPalContextID = payPalContextID self.requestStartTime = requestStartTime self.shopperSessionID = shopperSessionID @@ -124,7 +120,6 @@ struct FPTIBatchData: Codable { 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" case timestamp = "t" diff --git a/Sources/BraintreeCore/BTAPIClient.swift b/Sources/BraintreeCore/BTAPIClient.swift index 7bb81b48f..ddc6808d6 100644 --- a/Sources/BraintreeCore/BTAPIClient.swift +++ b/Sources/BraintreeCore/BTAPIClient.swift @@ -313,7 +313,6 @@ import Foundation isVaultRequest: Bool? = nil, linkType: LinkType? = nil, pageType: String? = nil, - paymentMethodsDisplayed: String? = nil, payPalContextID: String? = nil, shopperSessionID: String? = nil ) { @@ -330,7 +329,6 @@ import Foundation linkType: linkType?.rawValue, merchantExperiment: merchantExperiment, pageType: pageType, - paymentMethodsDisplayed: paymentMethodsDisplayed, payPalContextID: payPalContextID, shopperSessionID: shopperSessionID ) diff --git a/UnitTests/BraintreeTestShared/MockAPIClient.swift b/UnitTests/BraintreeTestShared/MockAPIClient.swift index 3fe91dd29..46bbaf504 100644 --- a/UnitTests/BraintreeTestShared/MockAPIClient.swift +++ b/UnitTests/BraintreeTestShared/MockAPIClient.swift @@ -19,7 +19,6 @@ public class MockAPIClient: BTAPIClient { public var postedLinkType: LinkType? = nil public var postedMerchantExperiment: String? = nil public var postedPageType: String? = nil - public var postedPaymentMethodsDisplayed: String? = nil public var postedPayPalContextID: String? = nil public var postedShopperSessionID: String? = nil @@ -107,7 +106,6 @@ public class MockAPIClient: BTAPIClient { isVaultRequest: Bool? = nil, linkType: LinkType? = nil, pageType: String? = nil, - paymentMethodsDisplayed: String? = nil, payPalContextID: String? = nil, shopperSessionID: String? = nil ) { @@ -118,7 +116,6 @@ public class MockAPIClient: BTAPIClient { postedLinkType = linkType postedIsVaultRequest = isVaultRequest ?? false postedMerchantExperiment = experiment - postedPaymentMethodsDisplayed = paymentMethodsDisplayed postedAppSwitchURL[eventName] = appSwitchURL?.absoluteString postedAnalyticsEvents.append(eventName) postedShopperSessionID = shopperSessionID From a89c63891d550c629ed97b2520d932c13ee3314a Mon Sep 17 00:00:00 2001 From: Justin Warmkessel Date: Wed, 15 Jan 2025 10:08:52 -0800 Subject: [PATCH 10/14] PR comments --- Sources/BraintreeShopperInsights/BTPresentmentDetails.swift | 2 ++ Sources/BraintreeShopperInsights/BTShopperInsightsClient.swift | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/Sources/BraintreeShopperInsights/BTPresentmentDetails.swift b/Sources/BraintreeShopperInsights/BTPresentmentDetails.swift index 1b33abc04..998ed3224 100644 --- a/Sources/BraintreeShopperInsights/BTPresentmentDetails.swift +++ b/Sources/BraintreeShopperInsights/BTPresentmentDetails.swift @@ -1,5 +1,7 @@ import Foundation +/// `BTPresentmentDetails` Configure detailed information about the presented button. +/// - Warning: This class is in beta. public class BTPresentmentDetails { var buttonOrder: BTButtonOrder diff --git a/Sources/BraintreeShopperInsights/BTShopperInsightsClient.swift b/Sources/BraintreeShopperInsights/BTShopperInsightsClient.swift index 23564fe1e..0a5fe0279 100644 --- a/Sources/BraintreeShopperInsights/BTShopperInsightsClient.swift +++ b/Sources/BraintreeShopperInsights/BTShopperInsightsClient.swift @@ -23,7 +23,7 @@ public class BTShopperInsightsClient { /// - Parameters: /// - apiClient: A `BTAPIClient` instance. /// - shopperSessionID: This value should be the shopper session ID returned from your server SDK request - /// - Warning: This features only works with a client token. + /// - Warning: This init is beta. And this feature only works with a client token. public init(apiClient: BTAPIClient, shopperSessionID: String? = nil) { self.apiClient = apiClient self.shopperSessionID = shopperSessionID @@ -110,6 +110,7 @@ public class BTShopperInsightsClient { /// Call this method when a button has been selected/tapped by the buyer. /// This method sends analytics to help improve the Shopper Insights feature experience. /// - Parameter buttonType: Type of button presented - PayPal, Venmo, or Other + /// - Warning: This function is in beta. public func sendSelectedEvent(for buttonType: BTButtonType) { apiClient.sendAnalyticsEvent( BTShopperInsightsAnalytics.buttonSelected, From aecc4955b4e8458ee16b1959de3ddbe9ac91eed5 Mon Sep 17 00:00:00 2001 From: warmkesselj <150195168+warmkesselj@users.noreply.github.com> Date: Wed, 22 Jan 2025 10:29:36 -0800 Subject: [PATCH 11/14] Update Sources/BraintreeShopperInsights/BTPresentmentDetails.swift Co-authored-by: Jax DesMarais-Leder --- Sources/BraintreeShopperInsights/BTPresentmentDetails.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/BraintreeShopperInsights/BTPresentmentDetails.swift b/Sources/BraintreeShopperInsights/BTPresentmentDetails.swift index 998ed3224..d4c31d7fa 100644 --- a/Sources/BraintreeShopperInsights/BTPresentmentDetails.swift +++ b/Sources/BraintreeShopperInsights/BTPresentmentDetails.swift @@ -1,7 +1,7 @@ import Foundation /// `BTPresentmentDetails` Configure detailed information about the presented button. -/// - Warning: This class is in beta. +/// - Warning: This class is in beta. It's public API may change or be removed in future releases. public class BTPresentmentDetails { var buttonOrder: BTButtonOrder From f6c5710fca685e68affa3b28e265c687bc11606a Mon Sep 17 00:00:00 2001 From: warmkesselj <150195168+warmkesselj@users.noreply.github.com> Date: Wed, 22 Jan 2025 10:29:52 -0800 Subject: [PATCH 12/14] Update Sources/BraintreeShopperInsights/BTShopperInsightsClient.swift Co-authored-by: Jax DesMarais-Leder --- Sources/BraintreeShopperInsights/BTShopperInsightsClient.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/BraintreeShopperInsights/BTShopperInsightsClient.swift b/Sources/BraintreeShopperInsights/BTShopperInsightsClient.swift index 0a5fe0279..98c907f04 100644 --- a/Sources/BraintreeShopperInsights/BTShopperInsightsClient.swift +++ b/Sources/BraintreeShopperInsights/BTShopperInsightsClient.swift @@ -110,7 +110,7 @@ public class BTShopperInsightsClient { /// Call this method when a button has been selected/tapped by the buyer. /// This method sends analytics to help improve the Shopper Insights feature experience. /// - Parameter buttonType: Type of button presented - PayPal, Venmo, or Other - /// - Warning: This function is in beta. + /// - Warning: This function is in beta. It's public API may change or be removed in future releases. public func sendSelectedEvent(for buttonType: BTButtonType) { apiClient.sendAnalyticsEvent( BTShopperInsightsAnalytics.buttonSelected, From a35ef44c831bb71572a40c11cc0d86fd614894cb Mon Sep 17 00:00:00 2001 From: warmkesselj <150195168+warmkesselj@users.noreply.github.com> Date: Wed, 22 Jan 2025 10:30:17 -0800 Subject: [PATCH 13/14] Update Sources/BraintreeShopperInsights/BTShopperInsightsClient.swift Co-authored-by: Jax DesMarais-Leder --- Sources/BraintreeShopperInsights/BTShopperInsightsClient.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/BraintreeShopperInsights/BTShopperInsightsClient.swift b/Sources/BraintreeShopperInsights/BTShopperInsightsClient.swift index 98c907f04..b2a7131d0 100644 --- a/Sources/BraintreeShopperInsights/BTShopperInsightsClient.swift +++ b/Sources/BraintreeShopperInsights/BTShopperInsightsClient.swift @@ -23,7 +23,7 @@ public class BTShopperInsightsClient { /// - Parameters: /// - apiClient: A `BTAPIClient` instance. /// - shopperSessionID: This value should be the shopper session ID returned from your server SDK request - /// - Warning: This init is beta. And this feature only works with a client token. + /// - Warning: This init is beta. It's public API may change or be removed in future releases. This feature only works with a client token. public init(apiClient: BTAPIClient, shopperSessionID: String? = nil) { self.apiClient = apiClient self.shopperSessionID = shopperSessionID From e8c9b985c649b2e32af98c44a328d4ff5db74b0b Mon Sep 17 00:00:00 2001 From: Justin Warmkessel Date: Wed, 22 Jan 2025 10:41:50 -0800 Subject: [PATCH 14/14] Update comments --- Sources/BraintreeShopperInsights/BTPresentmentDetails.swift | 2 +- Sources/BraintreeShopperInsights/BTShopperInsightsClient.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/BraintreeShopperInsights/BTPresentmentDetails.swift b/Sources/BraintreeShopperInsights/BTPresentmentDetails.swift index 998ed3224..d4c31d7fa 100644 --- a/Sources/BraintreeShopperInsights/BTPresentmentDetails.swift +++ b/Sources/BraintreeShopperInsights/BTPresentmentDetails.swift @@ -1,7 +1,7 @@ import Foundation /// `BTPresentmentDetails` Configure detailed information about the presented button. -/// - Warning: This class is in beta. +/// - Warning: This class is in beta. It's public API may change or be removed in future releases. public class BTPresentmentDetails { var buttonOrder: BTButtonOrder diff --git a/Sources/BraintreeShopperInsights/BTShopperInsightsClient.swift b/Sources/BraintreeShopperInsights/BTShopperInsightsClient.swift index 0a5fe0279..98c907f04 100644 --- a/Sources/BraintreeShopperInsights/BTShopperInsightsClient.swift +++ b/Sources/BraintreeShopperInsights/BTShopperInsightsClient.swift @@ -110,7 +110,7 @@ public class BTShopperInsightsClient { /// Call this method when a button has been selected/tapped by the buyer. /// This method sends analytics to help improve the Shopper Insights feature experience. /// - Parameter buttonType: Type of button presented - PayPal, Venmo, or Other - /// - Warning: This function is in beta. + /// - Warning: This function is in beta. It's public API may change or be removed in future releases. public func sendSelectedEvent(for buttonType: BTButtonType) { apiClient.sendAnalyticsEvent( BTShopperInsightsAnalytics.buttonSelected,