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
35 changes: 28 additions & 7 deletions android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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'
}
}

Expand All @@ -15,6 +16,7 @@ repositories {
mavenCentral()
maven {
url 'http://nexus.skillz.com/content/groups/public'
allowInsecureProtocol = true
}
maven {
url "https://cardinalcommerce.bintray.com/android"
Expand All @@ -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
Expand All @@ -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'
}
Comment on lines 56 to 65
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify Braintree 4.45.0 artifacts exist on Maven Central
echo "Verifying Braintree v4 modular artifacts at version 4.45.0..."
for artifact in card data-collector paypal venmo; do
  url="https://repo1.maven.org/maven2/com/braintreepayments/api/${artifact}/4.45.0/${artifact}-4.45.0.pom"
  http_code=$(curl -s -o /dev/null -w "%{http_code}" "$url")
  echo "${artifact}: HTTP $http_code"
done

echo ""
echo "Verifying react-native 0.61.5 availability..."
react_url="https://repo1.maven.org/maven2/com/facebook/react/react-native/0.61.5/react-native-0.61.5.pom"
react_code=$(curl -s -o /dev/null -w "%{http_code}" "$react_url")
echo "react-native 0.61.5: HTTP $react_code"

Repository: skillz/react-native-braintree-custom-ui

Length of output: 294


React-native 0.61.5 does not exist on Maven Central and will cause build failures.

While the Braintree v4 modular artifacts (card, data-collector, paypal, venmo) at version 4.45.0 are correctly published and accessible, react-native:0.61.5 returns HTTP 404 and cannot be resolved. This is a blocking issue that will prevent the build from succeeding. Verify the correct available version for react-native or update to a version that exists on Maven Central.

The compileOnly configuration is appropriate for a library module, but it won't help if the dependency cannot be resolved in the first place.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@android/build.gradle` around lines 56 - 65, The build fails because the
dependency line compileOnly 'com.facebook.react:react-native:0.61.5' references
a React Native artifact/version not available on Maven Central; update the
react-native dependency on the compileOnly line (the exact symbol:
com.facebook.react:react-native:0.61.5) to a version that actually exists on
Maven Central or remove the artifact version and rely on the host app providing
React Native (i.e., keep compileOnly but point to a valid published version like
one verified on Maven Central or change to the project's correct provided
coordinate) so the dependency can be resolved during Gradle sync.


// 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')
58 changes: 46 additions & 12 deletions android/src/main/java/com/pw/droplet/braintree/Braintree.java
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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) {
Expand All @@ -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");
Expand All @@ -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);
}
}
Expand Down
37 changes: 18 additions & 19 deletions ios/RCTBraintree/RCTBraintree.m
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand All @@ -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)]);
}
}

Expand Down Expand Up @@ -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
Comment on lines +252 to +256
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🌐 Web query:

Braintree iOS SDK BTDataCollector collectDeviceData completion block signature documentation

💡 Result:

In the current Braintree iOS SDK, BTDataCollector exposes this completion-based API:

  • Swift

    public func collectDeviceData(_ completion: `@escaping` (String?, Error?) -> Void)

    The completion receives (deviceData: String?, error: Error?). [1]

  • Objective‑C (via @objc bridging)

    - (void)collectDeviceData:(void (^ _Nonnull)(NSString * _Nullable deviceData,
                                                NSError  * _Nullable error))completion;

    (Same semantics: either deviceData or error is set.) [1][2]

Sources: Braintree-generated reference docs for BTDataCollector.collectDeviceData(_:) [1] and the SDK implementation (BTDataCollector.swift) showing the exact Swift signature and closure parameters [2].


Fix collectDeviceData: completion block signature — currently incompatible with Braintree iOS SDK.

The completion block signature is incorrect. The Braintree iOS SDK's BTDataCollector defines:

- (void)collectDeviceData:(void (^ _Nonnull)(NSString * _Nullable deviceData, NSError * _Nullable error))completion;

The code only accepts one parameter (NSString * _Nonnull collectedData), but the API expects two: (NSString * _Nullable, NSError * _Nullable). This causes a compilation error or silent API mismatch.

Additionally, update the block to handle both the deviceData and error parameters. Currently, errors are completely ignored and the callback is invoked unconditionally without checking for errors.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ios/RCTBraintree/RCTBraintree.m` around lines 252 - 256, Update the
collectDeviceData completion block in RCTBraintree.m to match BTDataCollector's
signature and handle errors: change the block passed to [self.dataCollector
collectDeviceData:...] to accept (NSString * _Nullable deviceData, NSError *
_Nullable error), check error and return callback(@[errorObj]) or
callback(@[[NSNull null], deviceData]) accordingly, and ensure you create a
proper JS/React Native error object when invoking the existing callback so
errors are not ignored.

} else if ([dataSelector isEqualToString:@"paypal"] || [dataSelector isEqualToString:@"venmo"]) {
deviceData = [PPDataCollector collectPayPalDeviceData];
NSString *deviceData = [PPDataCollector collectPayPalDeviceData];
callback(@[[NSNull null], deviceData]);
Comment on lines 257 to +259
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

find . -type f \( -name "Podfile" -o -name "*.podspec" \) | head -20

Repository: skillz/react-native-braintree-custom-ui

Length of output: 65


🏁 Script executed:

rg "PPDataCollector|Braintree" -g "*.m" -g "*.h" -g "Podfile" -g "*.podspec" --max-count=50

Repository: skillz/react-native-braintree-custom-ui

Length of output: 1213


🏁 Script executed:

rg "import.*Braintree|#import.*Braintree" -g "*.m" -g "*.h" -A 2

Repository: skillz/react-native-braintree-custom-ui

Length of output: 617


🏁 Script executed:

find . -name "Podfile" -o -name "*.podspec" | xargs cat 2>/dev/null

Repository: skillz/react-native-braintree-custom-ui

Length of output: 65


🏁 Script executed:

rg "pod.*Braintree" -A 2 -B 2

Repository: skillz/react-native-braintree-custom-ui

Length of output: 65


🏁 Script executed:

ls -la

Repository: skillz/react-native-braintree-custom-ui

Length of output: 906


🏁 Script executed:

git log --oneline --all -- ios/RCTBraintree/ | head -20

Repository: skillz/react-native-braintree-custom-ui

Length of output: 1237


🌐 Web query:

PPDataCollector collectPayPalDeviceData Braintree iOS SDK

💡 Result:

PPDataCollector.collectPayPalDeviceData() is part of Braintree iOS SDK v5’s PayPalDataCollector module. It returns a device_data string you should forward to your server and include as the device_data parameter when creating/verifying a transaction (helps Premium Fraud Management Tools; required for PayPal Vault flow). [1]

v5 (has PPDataCollector)

Install

  • CocoaPods: pod 'Braintree/PayPalDataCollector' [1]

Collect

let deviceData = PPDataCollector.collectPayPalDeviceData()
// send deviceData to your server and pass as `device_data`

[1]

Sandbox note (v5.9.0+)
Use collectDeviceData(isSandbox: Bool) to collect the appropriate sandbox/production device data. [1]

v6+ (no PPDataCollector)

The PayPalDataCollector module was removed in favor of the unified BraintreeDataCollector module, so PPDataCollector.collectPayPalDeviceData() won’t exist on v6+. [2]

Sources: [1] PayPal/Braintree Premium Fraud Management Tools – iOS v5 client-side device data collection, [2] CocoaPods Braintree changelog (6.0.0-beta1 removal note.


🌐 Web query:

BTDataCollector Braintree iOS SDK PayPal device data

💡 Result:

In the Braintree iOS SDK, BTDataCollector is the client-side API you use to collect PayPal “device data” for fraud/risk evaluation, then send that string to your server as the device_data parameter with a transaction/verification request. (developer.paypal.com)

iOS SDK v6 (current pattern)

let apiClient = BTAPIClient(authorization: "<TOKENIZATION_KEY_OR_CLIENT_TOKEN>")
let dataCollector = BTDataCollector(apiClient: apiClient)

dataCollector.collectDeviceData { deviceData, error in
    guard let deviceData else { return /* handle error */ }
    // Send deviceData to your server (as `device_data`)
}

(developer.paypal.com)

Notes / version differences

  • v6 migration: PayPalDataCollector was removed “in favor of a single” collector module (BraintreeDataCollector / BTDataCollector). (developer.paypal.com)
  • v5 guidance: BTDataCollector.collectDeviceData() replaced the older collectCardFraudData() method; call it as early as practical (app launch or start of checkout). (developer.paypal.com)
  • For some PayPal integrations (e.g., Vault flow), PayPal’s docs explicitly call out that collecting device data is required. (developer.paypal.com)

Citations:


Resolve the API version mismatch: PPDataCollector (v5) vs BTDataCollector imports (v6).

The code uses PPDataCollector.collectPayPalDeviceData(), which is the correct PayPal data collection API for Braintree iOS SDK v5. However, the header imports BTDataCollector.h, which is the v6+ unified data collector. This mismatch indicates an incomplete SDK migration. If the target is Braintree iOS SDK v6+, PPDataCollector no longer exists—replace it with BTDataCollector. If the target is v5, remove the BTDataCollector.h import and add #import "BraintreePayPalDataCollector.h" instead. Clarify the SDK version target and complete the migration consistently.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ios/RCTBraintree/RCTBraintree.m` around lines 257 - 259, The code is using
PPDataCollector.collectPayPalDeviceData while importing BTDataCollector.h,
causing an SDK version mismatch; decide which Braintree SDK version to target
and make the imports/usage consistent: if targeting v6+, replace PPDataCollector
usage with the v6 BTDataCollector API (e.g., use BTDataCollector methods to
collect PayPal/Venmo device data and update any callback handling in the method
that checks dataSelector), otherwise if targeting v5 remove the
BTDataCollector.h import and add `#import` "BraintreePayPalDataCollector.h" and
keep PPDataCollector.collectPayPalDeviceData as used; update any related symbol
names (PPDataCollector, BTDataCollector, BraintreePayPalDataCollector)
accordingly so imports and method calls match the chosen SDK version.

} 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);
});
}

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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": {
Expand Down