From 26b0a57c03efc319b56e195b6004521ebab87d26 Mon Sep 17 00:00:00 2001 From: Amit Levy Date: Wed, 15 Jan 2025 14:49:52 +0200 Subject: [PATCH] Adding StoreKit2 Adding to PCConfiguration storeKitVersion enum, adding logConsumableTransaction and transactionFetcher class to handle it. --- .gitignore | 2 + .../reactnative/PCAppsFlyerModule.java | 6 +++ index.d.ts | 7 +++ index.js | 9 +++- ios/PCAppsFlyer.m | 41 ++++++++++++++- ios/RNAppsFlyer-Bridging-Header.h | 15 ++++++ ios/RNAppsFlyer.xcodeproj/project.pbxproj | 51 +++++++++++++++++++ ios/TransactionFetcher.swift | 27 ++++++++++ 8 files changed, 156 insertions(+), 2 deletions(-) create mode 100644 ios/RNAppsFlyer-Bridging-Header.h mode change 100755 => 100644 ios/RNAppsFlyer.xcodeproj/project.pbxproj create mode 100644 ios/TransactionFetcher.swift diff --git a/.gitignore b/.gitignore index 7bd61fdf..dd6104b0 100644 --- a/.gitignore +++ b/.gitignore @@ -69,3 +69,5 @@ demos/appsflyer-expo-app/yarn.lock demos/appsflyer-react-native-app/ios/.xcode.env demos/appsflyer-react-native-app/ios/AppsFlyerExample.xcodeproj/project.pbxproj + +android/bin/ diff --git a/android/src/main/includeConnector/com/appsflyer/reactnative/PCAppsFlyerModule.java b/android/src/main/includeConnector/com/appsflyer/reactnative/PCAppsFlyerModule.java index 6ff2be26..6aca43ee 100644 --- a/android/src/main/includeConnector/com/appsflyer/reactnative/PCAppsFlyerModule.java +++ b/android/src/main/includeConnector/com/appsflyer/reactnative/PCAppsFlyerModule.java @@ -51,6 +51,12 @@ public void create(ReadableMap config) { boolean logInApps = config.getBoolean("logInApps"); boolean sandbox = config.getBoolean("sandbox"); + // Optional: Log that storeKitVersion is ignored on Android (for debugging purposes) + if (config.hasKey("storeKitVersion")) { + String storeKitVersion = config.getString("storeKitVersion"); + Log.d(TAG, "storeKitVersion (" + storeKitVersion + ") is ignored on Android."); + } + MappedValidationResultListener arsListener = this.arsListener; MappedValidationResultListener viapListener = this.viapListener; diff --git a/index.d.ts b/index.d.ts index a123d7c1..7bac9dd6 100644 --- a/index.d.ts +++ b/index.d.ts @@ -166,10 +166,16 @@ declare module "react-native-appsflyer" { /** * PurchaseConnector */ + export const StoreKitVersion = { + SK1: "SK1", + SK2: "SK2", + }; + export interface PurchaseConnectorConfig { logSubscriptions: boolean; logInApps: boolean; sandbox: boolean; + storeKitVersion?: keyof typeof StoreKitVersion; // Optional property } export const AppsFlyerPurchaseConnectorConfig: { @@ -180,6 +186,7 @@ declare module "react-native-appsflyer" { create(config: PurchaseConnectorConfig): void; startObservingTransactions(): void; stopObservingTransactions(): void; + logConsumableTransaction(transactionId: string): void; onSubscriptionValidationResultSuccess( callback: (data:OnResponse) => any ): () => void; diff --git a/index.js b/index.js index 2987af7d..17a6a755 100755 --- a/index.js +++ b/index.js @@ -197,12 +197,19 @@ AppsFlyerPurchaseConnector.OnReceivePurchaseRevenueValidationInfo = ( }; }; +function logConsumableTransaction(transactionId){ + PCAppsFlyer.logConsumableTransaction(transactionId); +}; + +AppsFlyerPurchaseConnector.logConsumableTransaction = logConsumableTransaction; + const AppsFlyerPurchaseConnectorConfig = { - setConfig: ({ logSubscriptions, logInApps, sandbox }) => { + setConfig: ({ logSubscriptions, logInApps, sandbox, storeKitVersion }) => { return { logSubscriptions, logInApps, sandbox, + storeKitVersion: storeKitVersion || StoreKitVersion.SK1, // Default to SK1 if not provided }; }, }; diff --git a/ios/PCAppsFlyer.m b/ios/PCAppsFlyer.m index e6ce11d6..e3a3654a 100644 --- a/ios/PCAppsFlyer.m +++ b/ios/PCAppsFlyer.m @@ -7,6 +7,7 @@ #if __has_include() #import +#import "RNAppsFlyer-Swift.h" @implementation PCAppsFlyer @synthesize bridge = _bridge; @@ -14,6 +15,7 @@ @implementation PCAppsFlyer static NSString *const logSubscriptionsKey = @"logSubscriptions"; static NSString *const logInAppsKey = @"logInApps"; static NSString *const sandboxKey = @"sandbox"; +static NSString *const storeKitKey = @"storeKitVersion"; static NSString *const connectorAlreadyConfiguredMessage = @"Connector already configured"; static NSString *const connectorNotConfiguredMessage = @"Connector not configured, did you call `create` first?"; @@ -41,9 +43,19 @@ @implementation PCAppsFlyer BOOL logSubscriptions = [config[logSubscriptionsKey] boolValue]; BOOL logInApps = [config[logInAppsKey] boolValue]; BOOL sandbox = [config[sandboxKey] boolValue]; - + NSString *storeKitVersion = config[storeKitKey]; + [connector setIsSandbox:sandbox]; + // Set the StoreKitVersion (default to SK1 if not provided or invalid) + if ([storeKitVersion isEqualToString:@"SK2"]) { + [connector setStoreKitVersion:AFSDKStoreKitVersionSK2]; + NSLog(@"%@Configure PurchaseConnector with StoreKit2 Version", TAG); + } else { + [connector setStoreKitVersion:AFSDKStoreKitVersionSK1]; + NSLog(@"%@Configure PurchaseConnector with StoreKit1 Version", TAG); + } + if (logSubscriptions && logInApps) { [connector setAutoLogPurchaseRevenue:AFSDKAutoLogPurchaseRevenueOptionsAutoRenewableSubscriptions | AFSDKAutoLogPurchaseRevenueOptionsInAppPurchases]; } @@ -58,6 +70,33 @@ @implementation PCAppsFlyer resolve(nil); } +RCT_EXPORT_METHOD(logConsumableTransaction:(NSString *)transactionId + resolver:(RCTPromiseResolveBlock)resolve + rejecter:(RCTPromiseRejectBlock)reject) { + NSLog(@"%@Logging consumable transaction with ID: %@", TAG, transactionId); + + if (connector == nil) { + reject(connectorNotConfiguredMessage, connectorNotConfiguredMessage, nil); + return; + } + + // Call the Swift method via the TransactionFetcher class + [TransactionFetcher fetchTransactionWithId:transactionId completion:^(AFSDKTransactionSK2 * _Nullable afTransaction) { + if (afTransaction) { + // Use the fetched transaction + [connector logConsumableTransaction:afTransaction]; + NSLog(@"Logged transaction: %@", transactionId); + resolve(nil); + } else { + // Handle the case where the transaction was not found + NSError *error = [NSError errorWithDomain:@"PCAppsFlyer" + code:404 + userInfo:@{NSLocalizedDescriptionKey: @"Transaction not found"}]; + reject(@"transaction_not_found", @"Transaction not found", error); + } + }]; +} + RCT_EXPORT_METHOD(startObservingTransactions:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) { NSLog(@"%@Starting to observe transactions.", TAG); diff --git a/ios/RNAppsFlyer-Bridging-Header.h b/ios/RNAppsFlyer-Bridging-Header.h new file mode 100644 index 00000000..cfb64c9b --- /dev/null +++ b/ios/RNAppsFlyer-Bridging-Header.h @@ -0,0 +1,15 @@ +// +// RNAppsFlyer-Bridging-Header.h +// RNAppsFlyer +// +// Created by Amit Levy on 15/01/2025. +// Copyright © 2025 Facebook. All rights reserved. +// + +#ifndef _RNAppsFlyer_Bridging_Header_h +#define _RNAppsFlyer_Bridging_Header_h + +#import +#import + +#endif /* _RNAppsFlyer_Bridging_Header_h */ diff --git a/ios/RNAppsFlyer.xcodeproj/project.pbxproj b/ios/RNAppsFlyer.xcodeproj/project.pbxproj old mode 100755 new mode 100644 index b594b417..7d710e59 --- a/ios/RNAppsFlyer.xcodeproj/project.pbxproj +++ b/ios/RNAppsFlyer.xcodeproj/project.pbxproj @@ -7,6 +7,10 @@ objects = { /* Begin PBXBuildFile section */ + 7E215B402D3683B60088EE50 /* PCAppsFlyer.m in Sources */ = {isa = PBXBuildFile; fileRef = 7E215B3E2D3683B60088EE50 /* PCAppsFlyer.m */; }; + 7E215B412D3683B60088EE50 /* TransactionFetcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E215B3F2D3683B60088EE50 /* TransactionFetcher.swift */; }; + 7E215B422D3683B60088EE50 /* AppsFlyerAttribution.m in Sources */ = {isa = PBXBuildFile; fileRef = 7E215B372D3683B60088EE50 /* AppsFlyerAttribution.m */; }; + 7E215B4B2D37AFF60088EE50 /* RNAppsFlyer-Bridging-Header.h in Headers */ = {isa = PBXBuildFile; fileRef = 7E215B492D37AEE10088EE50 /* RNAppsFlyer-Bridging-Header.h */; settings = {ATTRIBUTES = (Private, ); }; }; B3E7B58A1CC2AC0600A0062D /* RNAppsFlyer.m in Sources */ = {isa = PBXBuildFile; fileRef = B3E7B5891CC2AC0600A0062D /* RNAppsFlyer.m */; }; /* End PBXBuildFile section */ @@ -24,6 +28,18 @@ /* Begin PBXFileReference section */ 134814201AA4EA6300B7C361 /* libRNAppsFlyer.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRNAppsFlyer.a; sourceTree = BUILT_PRODUCTS_DIR; }; + 7E215B352D3683B60088EE50 /* AFAdRevenueData.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AFAdRevenueData.h; sourceTree = ""; }; + 7E215B362D3683B60088EE50 /* AppsFlyerAttribution.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppsFlyerAttribution.h; sourceTree = ""; }; + 7E215B372D3683B60088EE50 /* AppsFlyerAttribution.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppsFlyerAttribution.m; sourceTree = ""; }; + 7E215B382D3683B60088EE50 /* AppsFlyerConsent.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppsFlyerConsent.h; sourceTree = ""; }; + 7E215B392D3683B60088EE50 /* AppsFlyerDeepLink.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppsFlyerDeepLink.h; sourceTree = ""; }; + 7E215B3A2D3683B60088EE50 /* AppsFlyerDeepLinkObserver.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppsFlyerDeepLinkObserver.h; sourceTree = ""; }; + 7E215B3B2D3683B60088EE50 /* AppsFlyerDeepLinkResult.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppsFlyerDeepLinkResult.h; sourceTree = ""; }; + 7E215B3C2D3683B60088EE50 /* AppsFlyerLib.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppsFlyerLib.h; sourceTree = ""; }; + 7E215B3D2D3683B60088EE50 /* PCAppsFlyer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PCAppsFlyer.h; sourceTree = ""; }; + 7E215B3E2D3683B60088EE50 /* PCAppsFlyer.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PCAppsFlyer.m; sourceTree = ""; }; + 7E215B3F2D3683B60088EE50 /* TransactionFetcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionFetcher.swift; sourceTree = ""; }; + 7E215B492D37AEE10088EE50 /* RNAppsFlyer-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "RNAppsFlyer-Bridging-Header.h"; sourceTree = ""; }; 943704C6209F6734005B3A22 /* AppsFlyerShareInviteHelper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppsFlyerShareInviteHelper.h; sourceTree = ""; }; 943704C7209F6734005B3A22 /* AppsFlyerLinkGenerator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppsFlyerLinkGenerator.h; sourceTree = ""; }; 943704C9209F6735005B3A22 /* AppsFlyerCrossPromotionHelper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppsFlyerCrossPromotionHelper.h; sourceTree = ""; }; @@ -53,22 +69,54 @@ 58B511D21A9E6C8500147676 = { isa = PBXGroup; children = ( + 7E215B352D3683B60088EE50 /* AFAdRevenueData.h */, + 7E215B362D3683B60088EE50 /* AppsFlyerAttribution.h */, + 7E215B372D3683B60088EE50 /* AppsFlyerAttribution.m */, + 7E215B382D3683B60088EE50 /* AppsFlyerConsent.h */, + 7E215B392D3683B60088EE50 /* AppsFlyerDeepLink.h */, + 7E215B3A2D3683B60088EE50 /* AppsFlyerDeepLinkObserver.h */, + 7E215B3B2D3683B60088EE50 /* AppsFlyerDeepLinkResult.h */, + 7E215B3C2D3683B60088EE50 /* AppsFlyerLib.h */, + 7E215B3D2D3683B60088EE50 /* PCAppsFlyer.h */, + 7E215B3E2D3683B60088EE50 /* PCAppsFlyer.m */, + 7E215B3F2D3683B60088EE50 /* TransactionFetcher.swift */, 943704C9209F6735005B3A22 /* AppsFlyerCrossPromotionHelper.h */, 943704C7209F6734005B3A22 /* AppsFlyerLinkGenerator.h */, 943704C6209F6734005B3A22 /* AppsFlyerShareInviteHelper.h */, B3E7B5881CC2AC0600A0062D /* RNAppsFlyer.h */, B3E7B5891CC2AC0600A0062D /* RNAppsFlyer.m */, + 7E215B492D37AEE10088EE50 /* RNAppsFlyer-Bridging-Header.h */, 134814211AA4EA7D00B7C361 /* Products */, + 7E215B4C2D37B0770088EE50 /* Frameworks */, ); sourceTree = ""; }; + 7E215B4C2D37B0770088EE50 /* Frameworks */ = { + isa = PBXGroup; + children = ( + ); + name = Frameworks; + sourceTree = ""; + }; /* End PBXGroup section */ +/* Begin PBXHeadersBuildPhase section */ + 7E215B4A2D37AFE20088EE50 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 7E215B4B2D37AFF60088EE50 /* RNAppsFlyer-Bridging-Header.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + /* Begin PBXNativeTarget section */ 58B511DA1A9E6C8500147676 /* RNAppsFlyer */ = { isa = PBXNativeTarget; buildConfigurationList = 58B511EF1A9E6C8500147676 /* Build configuration list for PBXNativeTarget "RNAppsFlyer" */; buildPhases = ( + 7E215B4A2D37AFE20088EE50 /* Headers */, 58B511D71A9E6C8500147676 /* Sources */, 58B511D81A9E6C8500147676 /* Frameworks */, 58B511D91A9E6C8500147676 /* CopyFiles */, @@ -121,6 +169,9 @@ buildActionMask = 2147483647; files = ( B3E7B58A1CC2AC0600A0062D /* RNAppsFlyer.m in Sources */, + 7E215B402D3683B60088EE50 /* PCAppsFlyer.m in Sources */, + 7E215B412D3683B60088EE50 /* TransactionFetcher.swift in Sources */, + 7E215B422D3683B60088EE50 /* AppsFlyerAttribution.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/ios/TransactionFetcher.swift b/ios/TransactionFetcher.swift new file mode 100644 index 00000000..f116c5dc --- /dev/null +++ b/ios/TransactionFetcher.swift @@ -0,0 +1,27 @@ +import Foundation +import StoreKit +import PurchaseConnector + +@objc class TransactionFetcher: NSObject { + @objc static func fetchTransaction(withId transactionId: String, completion: @escaping (AFSDKTransactionSK2?) -> Void) { + Task { + if #available(iOS 15.0, *) { + do { + let allTransactions = try await Transaction.all + if let matchingTransaction = allTransactions.first(where: { $0.id == UInt64(transactionId) }) { + let afTransaction = AFSDKTransactionSK2(transaction: matchingTransaction) + completion(afTransaction) + } else { + completion(nil) + } + } catch { + print("Error fetching transactions: \(error)") + completion(nil) + } + } else { + print("StoreKit 2 is not available on this iOS version.") + completion(nil) + } + } + } +}