Skip to content

Commit

Permalink
Shopper Insights - Added Presentment Details Object and Update Presen…
Browse files Browse the repository at this point in the history
…ted 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 <Jwarmkessel@paypal.com>
Co-authored-by: Jax DesMarais-Leder <jdesmarais@paypal.com>
  • Loading branch information
3 people authored Dec 20, 2024
1 parent b02bb43 commit 365a059
Show file tree
Hide file tree
Showing 18 changed files with 333 additions and 75 deletions.
20 changes: 20 additions & 0 deletions Braintree.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -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 */; };
Expand Down Expand Up @@ -729,6 +734,11 @@
035A59D91EA5DE97002960C8 /* BTLocalPaymentClient_UnitTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BTLocalPaymentClient_UnitTests.swift; sourceTree = "<group>"; };
039A8BD91F9E993500D607E7 /* BTAmericanExpressRewardsBalance_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BTAmericanExpressRewardsBalance_Tests.swift; sourceTree = "<group>"; };
03F921C1200EBB200076CD80 /* BTThreeDSecurePostalAddress_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BTThreeDSecurePostalAddress_Tests.swift; sourceTree = "<group>"; };
04AA31172D0797460043ACAB /* BTButtonType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BTButtonType.swift; sourceTree = "<group>"; };
04AA31192D0797510043ACAB /* BTPresentmentDetails.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BTPresentmentDetails.swift; sourceTree = "<group>"; };
04AA311D2D0798F70043ACAB /* BTPageType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BTPageType.swift; sourceTree = "<group>"; };
04AA311F2D07990A0043ACAB /* BTButtonOrder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BTButtonOrder.swift; sourceTree = "<group>"; };
04B0010F2D0CF46900C0060D /* BTExperimentType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BTExperimentType.swift; sourceTree = "<group>"; };
09357DCA2A2FBEC10096D449 /* BTVenmoLineItem_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BTVenmoLineItem_Tests.swift; sourceTree = "<group>"; };
096C6B2529CCDCEB00912863 /* BTVenmoLineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BTVenmoLineItem.swift; sourceTree = "<group>"; };
162174E1192D9220008DC35D /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; };
Expand Down Expand Up @@ -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 */,
Expand Down Expand Up @@ -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 */,
);
Expand Down
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
50 changes: 37 additions & 13 deletions Demo/Application/Features/ShopperInsightsViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand All @@ -131,7 +156,6 @@ class ShopperInsightsViewController: PaymentButtonBaseViewController {
}

@objc func venmoButtonTapped(_ button: UIButton) {
shopperInsightsClient.sendVenmoPresentedEvent()
progressBlock("Tapped Venmo")
shopperInsightsClient.sendVenmoSelectedEvent()

Expand Down
17 changes: 16 additions & 1 deletion Sources/BraintreeCore/Analytics/FPTIBatchData.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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?
Expand All @@ -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
Expand All @@ -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,
Expand All @@ -75,13 +83,16 @@ struct FPTIBatchData: Codable {
isVaultRequest: Bool? = nil,
linkType: String? = nil,
merchantExperiment: String? = nil,
pageType: String? = nil,
paymentMethodsDisplayed: String? = nil,
payPalContextID: String? = nil,
requestStartTime: Int? = nil,
shopperSessionID: String? = nil,
startTime: Int? = nil
) {
self.appSwitchURL = appSwitchURL?.absoluteString
self.buttonOrder = buttonOrder
self.buttonType = buttonType
self.connectionStartTime = connectionStartTime
self.correlationID = correlationID
self.endpoint = endpoint
Expand All @@ -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
Expand All @@ -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"
Expand All @@ -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"
Expand Down
8 changes: 7 additions & 1 deletion Sources/BraintreeCore/BTAPIClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -303,27 +303,33 @@ 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,
isConfigFromCache: isConfigFromCache,
isVaultRequest: isVaultRequest,
linkType: linkType?.rawValue,
merchantExperiment: merchantExperiment,
pageType: pageType,
paymentMethodsDisplayed: paymentMethodsDisplayed,
payPalContextID: payPalContextID,
shopperSessionID: shopperSessionID
Expand Down
8 changes: 4 additions & 4 deletions Sources/BraintreePayPal/BTPayPalClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
33 changes: 33 additions & 0 deletions Sources/BraintreeShopperInsights/BTButtonOrder.swift
Original file line number Diff line number Diff line change
@@ -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"
}
15 changes: 15 additions & 0 deletions Sources/BraintreeShopperInsights/BTButtonType.swift
Original file line number Diff line number Diff line change
@@ -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"
}
21 changes: 21 additions & 0 deletions Sources/BraintreeShopperInsights/BTExperimentType.swift
Original file line number Diff line number Diff line change
@@ -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)" }
]
"""
}
}
45 changes: 45 additions & 0 deletions Sources/BraintreeShopperInsights/BTPageType.swift
Original file line number Diff line number Diff line change
@@ -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"
}
Loading

0 comments on commit 365a059

Please sign in to comment.