Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# Mac
.build
.DS_Store
.LSOverride

Expand Down
41 changes: 41 additions & 0 deletions BranchSDK.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -488,6 +488,9 @@
E52E5B0A2CC79E5C00F553EE /* BranchFileLogger.m in Sources */ = {isa = PBXBuildFile; fileRef = E52E5B092CC79E5C00F553EE /* BranchFileLogger.m */; };
E52E5B0B2CC79E5C00F553EE /* BranchFileLogger.m in Sources */ = {isa = PBXBuildFile; fileRef = E52E5B092CC79E5C00F553EE /* BranchFileLogger.m */; };
E563942E2CC7A8E600E18E65 /* BranchFileLogger.h in Headers */ = {isa = PBXBuildFile; fileRef = E52E5B052CC79E4E00F553EE /* BranchFileLogger.h */; };
E710E5E72EB48AB90051AE51 /* BranchEvent+StoreKit2.swift in Sources */ = {isa = PBXBuildFile; fileRef = E710E5E52EB48AB90051AE51 /* BranchEvent+StoreKit2.swift */; };
E710E5E82EB48AB90051AE51 /* BranchEvent+StoreKit2.swift in Sources */ = {isa = PBXBuildFile; fileRef = E710E5E52EB48AB90051AE51 /* BranchEvent+StoreKit2.swift */; };
E710E5E92EB48AB90051AE51 /* BranchEvent+StoreKit2.swift in Sources */ = {isa = PBXBuildFile; fileRef = E710E5E52EB48AB90051AE51 /* BranchEvent+StoreKit2.swift */; };
E71E396F2DD3A92900110F59 /* BNCInAppBrowser.h in Headers */ = {isa = PBXBuildFile; fileRef = E71E396D2DD3A92900110F59 /* BNCInAppBrowser.h */; };
E71E39712DD3A92900110F59 /* BNCInAppBrowser.m in Sources */ = {isa = PBXBuildFile; fileRef = E71E396E2DD3A92900110F59 /* BNCInAppBrowser.m */; };
E71E39722DD3A92900110F59 /* BNCInAppBrowser.h in Headers */ = {isa = PBXBuildFile; fileRef = E71E396D2DD3A92900110F59 /* BNCInAppBrowser.h */; };
Expand Down Expand Up @@ -713,6 +716,7 @@
5FF2AFDF28E7C22100393216 /* BranchSDK.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BranchSDK.h; sourceTree = "<group>"; };
E52E5B052CC79E4E00F553EE /* BranchFileLogger.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BranchFileLogger.h; sourceTree = "<group>"; };
E52E5B092CC79E5C00F553EE /* BranchFileLogger.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BranchFileLogger.m; sourceTree = "<group>"; };
E710E5E52EB48AB90051AE51 /* BranchEvent+StoreKit2.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "BranchEvent+StoreKit2.swift"; sourceTree = "<group>"; };
E71E396D2DD3A92900110F59 /* BNCInAppBrowser.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BNCInAppBrowser.h; sourceTree = "<group>"; };
E71E396E2DD3A92900110F59 /* BNCInAppBrowser.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = BNCInAppBrowser.m; sourceTree = "<group>"; };
E73D027F2DEE8AE90076C3F1 /* BranchConfigurationController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BranchConfigurationController.h; sourceTree = "<group>"; };
Expand Down Expand Up @@ -773,6 +777,7 @@
isa = PBXGroup;
children = (
5FCDD36B2B7AC6A100EAF29F /* BranchSDK */,
E710E5E62EB48AB90051AE51 /* BranchSDK_Swift */,
5F2211702894A9C000C5B190 /* TestHost */,
5F2211872894A9C100C5B190 /* TestHostTests */,
5F2211912894A9C100C5B190 /* TestHostUITests */,
Expand Down Expand Up @@ -1022,6 +1027,15 @@
path = Framework;
sourceTree = "<group>";
};
E710E5E62EB48AB90051AE51 /* BranchSDK_Swift */ = {
isa = PBXGroup;
children = (
E710E5E52EB48AB90051AE51 /* BranchEvent+StoreKit2.swift */,
);
name = BranchSDK_Swift;
path = Sources/BranchSDK_Swift;
sourceTree = "<group>";
};
/* End PBXGroup section */

/* Begin PBXHeadersBuildPhase section */
Expand Down Expand Up @@ -1660,6 +1674,7 @@
5FCDD59A2B7AC6A400EAF29F /* BNCServerResponse.m in Sources */,
5FCDD5852B7AC6A400EAF29F /* BNCInitSessionResponse.m in Sources */,
5FCDD4412B7AC6A100EAF29F /* BNCPreferenceHelper.m in Sources */,
E710E5E92EB48AB90051AE51 /* BranchEvent+StoreKit2.swift in Sources */,
5FCDD5792B7AC6A400EAF29F /* BranchContentPathProperties.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand Down Expand Up @@ -1767,6 +1782,7 @@
5FCDD59B2B7AC6A400EAF29F /* BNCServerResponse.m in Sources */,
5FCDD5862B7AC6A400EAF29F /* BNCInitSessionResponse.m in Sources */,
5FCDD4422B7AC6A100EAF29F /* BNCPreferenceHelper.m in Sources */,
E710E5E82EB48AB90051AE51 /* BranchEvent+StoreKit2.swift in Sources */,
5FCDD57A2B7AC6A400EAF29F /* BranchContentPathProperties.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand All @@ -1786,6 +1802,7 @@
5FCDD4732B7AC6A100EAF29F /* BranchLATDRequest.m in Sources */,
5FCDD57E2B7AC6A400EAF29F /* BNCPasteboard.m in Sources */,
5F5FDA182B7DE2FE00F14A43 /* BranchLogger.m in Sources */,
E710E5E72EB48AB90051AE51 /* BranchEvent+StoreKit2.swift in Sources */,
5FCDD4612B7AC6A100EAF29F /* BNCRequestFactory.m in Sources */,
5FCDD41F2B7AC6A100EAF29F /* BNCDeepLinkViewControllerInstance.m in Sources */,
5FCDD58A2B7AC6A400EAF29F /* BNCLinkCache.m in Sources */,
Expand Down Expand Up @@ -1884,6 +1901,7 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
BUILD_LIBRARY_FOR_DISTRIBUTION = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
Expand Down Expand Up @@ -1936,6 +1954,7 @@
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
SWIFT_VERSION = 4.0;
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
};
Expand All @@ -1945,6 +1964,7 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
BUILD_LIBRARY_FOR_DISTRIBUTION = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
Expand Down Expand Up @@ -1990,6 +2010,7 @@
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
SDKROOT = iphoneos;
SWIFT_VERSION = 4.0;
VALIDATE_PRODUCT = YES;
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
Expand Down Expand Up @@ -2027,6 +2048,7 @@
"PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = "";
SKIP_INSTALL = YES;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 4.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
Expand Down Expand Up @@ -2062,27 +2084,32 @@
"PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = "";
SKIP_INSTALL = YES;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 4.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Release;
};
5F2211692894A90500C5B190 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
BUILD_LIBRARY_FOR_DISTRIBUTION = YES;
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = R63EM248DP;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 4.0;
};
name = Debug;
};
5F22116A2894A90500C5B190 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
BUILD_LIBRARY_FOR_DISTRIBUTION = YES;
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = R63EM248DP;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 4.0;
};
name = Release;
};
Expand Down Expand Up @@ -2344,6 +2371,7 @@
SDKROOT = appletvos;
SKIP_INSTALL = YES;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 4.0;
TARGETED_DEVICE_FAMILY = 3;
TVOS_DEPLOYMENT_TARGET = 12.0;
};
Expand Down Expand Up @@ -2379,6 +2407,7 @@
SDKROOT = appletvos;
SKIP_INSTALL = YES;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 4.0;
TARGETED_DEVICE_FAMILY = 3;
TVOS_DEPLOYMENT_TARGET = 12.0;
};
Expand All @@ -2387,60 +2416,72 @@
5FF9DEEE28EE7C0D00D62DE1 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
BUILD_LIBRARY_FOR_DISTRIBUTION = YES;
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = R63EM248DP;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 4.0;
};
name = Debug;
};
5FF9DEEF28EE7C0D00D62DE1 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
BUILD_LIBRARY_FOR_DISTRIBUTION = YES;
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = R63EM248DP;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 4.0;
};
name = Release;
};
5FF9DEF228EE7C2200D62DE1 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
BUILD_LIBRARY_FOR_DISTRIBUTION = YES;
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = R63EM248DP;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 4.0;
};
name = Debug;
};
5FF9DEF328EE7C2200D62DE1 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
BUILD_LIBRARY_FOR_DISTRIBUTION = YES;
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = R63EM248DP;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 4.0;
};
name = Release;
};
5FF9DEF628EE7C3600D62DE1 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
BUILD_LIBRARY_FOR_DISTRIBUTION = YES;
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = R63EM248DP;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 4.0;
};
name = Debug;
};
5FF9DEF728EE7C3600D62DE1 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
BUILD_LIBRARY_FOR_DISTRIBUTION = YES;
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = R63EM248DP;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 4.0;
};
name = Release;
};
Expand Down
7 changes: 6 additions & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ let package = Package(
products: [
.library(
name: "BranchSDK",
targets: ["BranchSDK"]),
targets: ["BranchSDK", "BranchSwiftSDK"]),
],
dependencies: [
],
Expand All @@ -37,5 +37,10 @@ let package = Package(
.linkedFramework("AdServices", .when(platforms: [.iOS]))
]
),
.target(
name: "BranchSwiftSDK",
dependencies: ["BranchSDK"],
path: "Sources/BranchSDK_Swift"
)
]
)
1 change: 0 additions & 1 deletion Sources/BranchSDK/BranchEvent.m
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,6 @@ + (BOOL)supportsSecureCoding {
#pragma mark - BranchEvent

@interface BranchEvent ()<SKRequestDelegate, SKProductsRequestDelegate>
@property (nonatomic, copy) NSString* eventName;
@property (strong, nonatomic) SKProductsRequest *request;
@end

Expand Down
2 changes: 1 addition & 1 deletion Sources/BranchSDK/Public/BranchEvent.h
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ typedef NS_ENUM(NSInteger, BranchEventAdType) {
@property (nonatomic, copy) NSString*_Nullable affiliation;
@property (nonatomic, copy) NSString*_Nullable eventDescription;
@property (nonatomic, copy) NSString*_Nullable searchQuery;

@property (nonatomic, copy) NSString*_Nullable eventName;
@property (nonatomic, assign) BranchEventAdType adType;

@property (nonatomic, strong) NSArray<BranchUniversalObject*>*_Nonnull contentItems;
Expand Down
117 changes: 117 additions & 0 deletions Sources/BranchSDK_Swift/BranchEvent+StoreKit2.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
//
// BranchEvent+StoreKit2.swift
// Branch-SDK
//
// Created by Nidhi Dixit on 09/30/25.
// Copyright 2024 Branch Metrics. All rights reserved.
//

import Foundation
import StoreKit
import BranchSDK

@available(iOS 15.0, tvOS 15.0, watchOS 8.0, *)
extension BranchEvent {

/// This method extracts detailed product and transaction information from a StoreKit 2 transaction
/// and logs a Branch PURCHASE event with all the extracted information.
/// - Parameter transaction: The StoreKit 2 transaction
public func logEventForTransaction( transaction: Transaction) {
Task {
await logEventAsync(with: transaction)
}
}

private func logEventAsync(with transaction: Transaction) async {
do {
let products = try await Product.products(for: [transaction.productID])
guard let product = products.first else {
BranchLogger.shared().logError("Could not load product for transaction: \(transaction.productID)", error: nil)
return
}
self.populateBUO(with: transaction, product: product)
try await self.logEvent()
BranchLogger.shared().logDebug("Created and logged StoreKit 2 event: \(self.description)", error: nil)
} catch {
BranchLogger.shared().logError("Failed to load product for StoreKit 2 transaction: \(error.localizedDescription)", error: error)
}
}

private func populateBUO(with transaction: Transaction, product: Product) {
let buo = BranchUniversalObject()
buo.canonicalIdentifier = product.id
buo.title = product.displayName
buo.contentDescription = product.description
buo.contentMetadata.quantity = Double(transaction.purchasedQuantity)
buo.contentMetadata.price = NSDecimalNumber(decimal: product.price)
buo.contentMetadata.currency = BNCCurrency(rawValue: product.priceFormatStyle.currencyCode)
buo.contentMetadata.productName = product.displayName

var customMetadata: [String: Any] = [
"logged_from_storekit2": true,
"product_type": product.type.rawValue,
"transaction_id": String(transaction.id),
"original_transaction_id": String(transaction.originalID),
"purchase_date": ISO8601DateFormatter().string(from: transaction.purchaseDate),
"purchased_quantity": transaction.purchasedQuantity
]

if let subscriptionInfo = product.subscription {
customMetadata["subscription_group_id"] = subscriptionInfo.subscriptionGroupID
customMetadata["subscription_period"] = formatSubscriptionPeriod(subscriptionInfo.subscriptionPeriod)

if let introductoryOffer = subscriptionInfo.introductoryOffer {
customMetadata["introductory_offer_type"] = introductoryOffer.type.rawValue
customMetadata["introductory_offer_period"] = formatSubscriptionPeriod(introductoryOffer.period)
}
}
customMetadata["ownership_type"] = transaction.ownershipType.rawValue

if let revocationDate = transaction.revocationDate {
customMetadata["revocation_date"] = ISO8601DateFormatter().string(from: revocationDate)
}
if let revocationReason = transaction.revocationReason {
customMetadata["revocation_reason"] = revocationReason.rawValue
}

buo.contentMetadata.customMetadata = NSMutableDictionary(dictionary: customMetadata)

self.contentItems = [buo]
self.eventName = "PURCHASE"
self.transactionID = String(transaction.id)
self.eventDescription = "StoreKit 2: \(product.displayName)"
self.currency = BNCCurrency(rawValue: product.priceFormatStyle.currencyCode)
self.revenue = NSDecimalNumber(decimal: product.price)

switch product.type {
case .autoRenewable, .nonRenewable:
self.alias = "Subscription"
case .consumable, .nonConsumable:
self.alias = "IAP"
default:
self.alias = "IAP"
}

var eventCustomData: [String: String] = [:]
eventCustomData["transaction_identifier"] = String(transaction.id)
eventCustomData["logged_from_storekit2"] = "true"
self.customData = eventCustomData
}

private func formatSubscriptionPeriod(_ period: Product.SubscriptionPeriod) -> String {
let unitString: String
switch period.unit {
case .day:
unitString = "day"
case .week:
unitString = "week"
case .month:
unitString = "month"
case .year:
unitString = "year"
@unknown default:
unitString = "unknown"
}
return "\(period.value) \(unitString)\(period.value > 1 ? "s" : "")"
}
}