diff --git a/android/build.gradle b/android/build.gradle index 2cec4dc..5daa2f7 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -4,7 +4,8 @@ buildscript { mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:3.6.4' + // AGP 7.4.2 is compatible with Gradle 8.3 (used by SDK) + classpath 'com.android.tools.build:gradle:7.4.2' } } @@ -15,6 +16,7 @@ repositories { mavenCentral() maven { url 'http://nexus.skillz.com/content/groups/public' + allowInsecureProtocol = true } maven { url "https://cardinalcommerce.bintray.com/android" @@ -26,8 +28,9 @@ repositories { } android { + namespace 'com.pw.droplet.braintree' compileSdkVersion 28 - buildToolsVersion "28.0.3" + // buildToolsVersion removed - AGP 7.4.2 automatically uses compatible version defaultConfig { minSdkVersion 23 @@ -42,15 +45,33 @@ android { proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro" } } + + // CompileOptions for Java 8 compatibility + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } } //s dependencies { - implementation 'com.facebook.react:react-native:0.61.5' - implementation 'com.braintreepayments.api:card:4.11.0' - implementation 'com.braintreepayments.api:data-collector:4.11.0' - implementation 'com.braintreepayments.api:paypal:4.11.0' - implementation 'com.braintreepayments.api:venmo:4.11.0' + compileOnly 'com.facebook.react:react-native:0.61.5' + implementation 'com.braintreepayments.api:card:4.45.0' + implementation 'com.braintreepayments.api:data-collector:4.45.0' + implementation 'com.braintreepayments.api:paypal:4.45.0' + implementation 'com.braintreepayments.api:venmo:4.45.0' api 'com.squareup.okhttp3:okhttp:3.14.4' implementation group: 'com.google.code.gson', name: 'gson', version: '2.8.6' } + +// Task to create JAR file for distribution +task createJar(type: Jar) { + archiveBaseName = 'react-native-braintree-custom-ui' + archiveVersion = '1.0.18' // Match package.json version + from('build/intermediates/javac/release/classes') + exclude('**/BuildConfig.class') + exclude('**/R.class') + exclude('**/R$*.class') +} + +createJar.dependsOn('assembleRelease') diff --git a/android/src/main/java/com/pw/droplet/braintree/Braintree.java b/android/src/main/java/com/pw/droplet/braintree/Braintree.java index 3badd21..a17c4f0 100644 --- a/android/src/main/java/com/pw/droplet/braintree/Braintree.java +++ b/android/src/main/java/com/pw/droplet/braintree/Braintree.java @@ -70,11 +70,14 @@ public Braintree(ReactApplicationContext reactContext, VenmoClient venmoClient) } private void setVenmoClient(VenmoClient venmoClient) { - this.venmoClient = venmoClient; - if (this.venmoClient != null) { + // Only set if not already initialized in setup() method + // In 4.45.0+, VenmoClient must be initialized with BraintreeClient in setup() + if (this.venmoClient == null && venmoClient != null) { + this.venmoClient = venmoClient; this.venmoClient.setListener(new VenmoListener() { @Override public void onVenmoSuccess(@NonNull VenmoAccountNonce venmoAccountNonce) { + // Success is handled via tokenizeVenmoAccount callback } @Override @@ -134,8 +137,26 @@ public void onResult(@Nullable VenmoAccountNonce venmoAccountNonce, @Nullable Ex public void setup(final String token, final Callback successCallback, final Callback errorCallback) { try { this.token = token; - this.braintreeClient = new BraintreeClient(Objects.requireNonNull(getCurrentActivity()), this.token); + // Braintree SDK v4.9.0+ requires FragmentActivity for lifecycle-aware operations + FragmentActivity activity = (FragmentActivity) Objects.requireNonNull(getCurrentActivity()); + this.braintreeClient = new BraintreeClient(activity, this.token); this.dataCollector = new DataCollector(this.braintreeClient); + + // Initialize VenmoClient after BraintreeClient is ready (required in 4.45.0+) + // VenmoClient constructor now requires non-null BraintreeClient in SDK 4.45.0+ + this.venmoClient = new VenmoClient(this.braintreeClient); + this.venmoClient.setListener(new VenmoListener() { + @Override + public void onVenmoSuccess(@NonNull VenmoAccountNonce venmoAccountNonce) { + // Success is handled via tokenizeVenmoAccount callback + } + + @Override + public void onVenmoFailure(@NonNull Exception error) { + invokeVenmoErrorCallback(error); + } + }); + this.braintreeClient.getConfiguration(new ConfigurationCallback() { @Override public void onResult(@androidx.annotation.Nullable Configuration configuration, @androidx.annotation.Nullable Exception error) { @@ -252,7 +273,8 @@ public void payPalRequestBillingAgreement(final String billingAgreementDescripti @ReactMethod public void getDeviceData(final ReadableMap options, final Callback successCallback, final Callback errorCallback) { try { - this.dataCollector.collectDeviceData(Objects.requireNonNull(getCurrentActivity()), new DataCollectorCallback() { + FragmentActivity activity = (FragmentActivity) Objects.requireNonNull(getCurrentActivity()); + this.dataCollector.collectDeviceData(activity, new DataCollectorCallback() { @Override public void onResult(@androidx.annotation.Nullable String deviceData, @androidx.annotation.Nullable Exception error) { if (error != null) { @@ -278,17 +300,29 @@ public void venmoRequestMultiUseAgreement(final String profileId, final boolean } request.setShouldVault(false); - getCurrentActivity().runOnUiThread(new Runnable() { - @Override - public void run() { - tokenizeVenmoAccount(request); - } - }); + Activity activity = getCurrentActivity(); + if (activity != null) { + activity.runOnUiThread(new Runnable() { + @Override + public void run() { + tokenizeVenmoAccount(request); + } + }); + } else { + errorCallback.invoke("Activity is null"); + } } private void tokenizeVenmoAccount(VenmoRequest request) { - try{ + try { AppCompatActivity activity = (AppCompatActivity) Objects.requireNonNull(getCurrentActivity()); + + // Create venmoClient if it doesn't exist (minimal change for 4.45.0 compatibility) + if (this.venmoClient == null && this.braintreeClient != null) { + this.venmoClient = new VenmoClient(this.braintreeClient); + } + + // Keep original reflection pattern to copy internal state VenmoClient tempClient = new VenmoClient(braintreeClient); Field fieldBraintreeClient = tempClient.getClass().getDeclaredField("braintreeClient"); @@ -302,7 +336,7 @@ private void tokenizeVenmoAccount(VenmoRequest request) { fieldVenmoApi.set(this.venmoClient, venmoApi); this.venmoClient.tokenizeVenmoAccount(activity, request); - } catch (Exception error){ + } catch (Exception error) { invokeVenmoErrorCallback(error); } } diff --git a/ios/RCTBraintree/RCTBraintree.m b/ios/RCTBraintree/RCTBraintree.m index 225c265..1319d9c 100644 --- a/ios/RCTBraintree/RCTBraintree.m +++ b/ios/RCTBraintree/RCTBraintree.m @@ -35,7 +35,8 @@ + (instancetype)sharedInstance { - (instancetype)init { if (self = [super init]) { - self.dataCollector = [[BTDataCollector alloc] initWithAPIClient:self.braintreeClient]; + // Don't initialize dataCollector here - braintreeClient is nil at init time + // DataCollector will be initialized in setupWithClientToken after braintreeClient is ready } return self; } @@ -50,11 +51,13 @@ - (instancetype)init self.braintreeClient = [[BTAPIClient alloc] initWithAuthorization:clientToken]; - if (self.braintreeClient == nil) { - callback(@[@(NO)]); + // Initialize DataCollector after braintreeClient is ready (required for 4.45.0+) + if (self.braintreeClient != nil) { + self.dataCollector = [[BTDataCollector alloc] initWithAPIClient:self.braintreeClient]; + callback(@[@(YES)]); } else { - callback(@[@(YES)]); + callback(@[@(NO)]); } } @@ -237,35 +240,31 @@ - (BTCard*)createCardWithParameters:(NSMutableDictionary*)parameters { dispatch_async(dispatch_get_main_queue(), ^{ NSError *error = nil; - NSString *deviceData = nil; NSString *dataSelector = options[@"dataCollector"]; - //Initialize the data collector in V5 - self.dataCollector = [[BTDataCollector alloc] initWithAPIClient: self.braintreeClient]; + // Ensure dataCollector is initialized (should already be done in setupWithClientToken) + if (self.dataCollector == nil && self.braintreeClient != nil) { + self.dataCollector = [[BTDataCollector alloc] initWithAPIClient:self.braintreeClient]; + } //Data collection methods if ([dataSelector isEqualToString:@"card"] || [dataSelector isEqualToString:@"both"]) { - [self.dataCollector collectDeviceData:^(NSString * _Nonnull deviceData) { - deviceData = deviceData; + // collectDeviceData is async - callback is called in completion block + [self.dataCollector collectDeviceData:^(NSString * _Nonnull collectedData) { + callback(@[[NSNull null], collectedData]); }]; + return; // Early return - callback is called in completion block } else if ([dataSelector isEqualToString:@"paypal"] || [dataSelector isEqualToString:@"venmo"]) { - deviceData = [PPDataCollector collectPayPalDeviceData]; + NSString *deviceData = [PPDataCollector collectPayPalDeviceData]; + callback(@[[NSNull null], deviceData]); } else { NSMutableDictionary* details = [NSMutableDictionary dictionary]; [details setValue:@"Invalid data collector" forKey:NSLocalizedDescriptionKey]; error = [NSError errorWithDomain:@"RCTBraintree" code:255 userInfo:details]; SKZLog(@"Invalid data collector: %@. Use one of: `card`, `paypal`, or `both`", dataSelector); + callback(@[error.description, [NSNull null]]); } - - NSArray *args = @[]; - if (error == nil) { - args = @[[NSNull null], deviceData]; - } else { - args = @[error.description, [NSNull null]]; - } - - callback(args); }); } diff --git a/package.json b/package.json index 0050515..89780ad 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-native-braintree-custom-ui", - "version": "1.0.16", + "version": "1.0.18", "description": "Cross platform Braintree module", "main": "index", "author": {