Skip to content
Merged
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
2 changes: 2 additions & 0 deletions INTEGRATION_3.X_EXPO.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ In Your `app.config.ts` or `app.config.json` or `app.config.js` please add expo-
pathPrefix: "/braintree-payments" // Optional,
// Depending on which payment do you really need in the project initialize only required one
initialize3DSecure: "true",
initializeGooglePay: "true",
addFallbackUrlScheme: "true",
appDelegateLanguage?: "swift"; // Optional if you are still using AppDelegate.mm / AppDelegate.m
},
Expand All @@ -31,6 +32,7 @@ In Your `app.config.ts` or `app.config.json` or `app.config.js` please add expo-

`pathPrefix` - Path prefix, in case of you want to separate path only to handle the context switch (Optional)
`initialize3DSecure` - Boolean that determines if 3D Secure is used/needed (Values "true" | "false")
`initializeGooglePay` - Boolean that determines if Google Pay is used/needed (Values "true" | "false")
`addFallbackUrlScheme` - Boolean that determines if we should add a scheme for a fallback url used in venmo
`appDelegateLanguage` - Indicator that tell's the plugin logic if you are still using Objective C file for AppDelegate (Optional)

Expand Down
24 changes: 24 additions & 0 deletions INTEGRATION_3.X_REACT_NATIVE_CLI.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,30 @@ override fun onCreate() {

---

### C. For:

- `requestGooglePayPayment`

Add the following instead:

```kotlin
import com.expobraintree.ExpoBraintreeModule

override fun onCreate() {
...
ExpoBraintreeModule.initGooglePay(this)
...
}
```

---

### D. If you use **all methods**

You must add **all initialization methods**.

---

# 3. Update `build.gradle`

If you use **3D Secure**, add the following repository to:
Expand Down
16 changes: 13 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ A high-performance, native implementation of the [Braintree SDK](https://develop

| Package Version | Braintree Android | Braintree iOS | Min Android SDK | Min iOS |
| :---------------- | :---------------: | :-----------: | :-------------: | :-----: |
| **3.4.0** | v5.19.0 | v6.41.0 | 23 | 15.1 |
| **3.3.0** | v5.19.0 | v6.41.0 | 23 | 15.1 |
| **3.2.0 - 3.2.2** | v5.19.0 | v6.41.0 | 23 | 15.1 |
| **3.1.0** | v5.9.x | v6.31.0 | 23 | 14.0 |
Expand All @@ -38,6 +39,15 @@ A high-performance, native implementation of the [Braintree SDK](https://develop

---

### Feature List

| Package Version | Supported Expo SDK |
| :-------------- | :----------------------- |
| **3.4.0** | Google Pay Feature Added |
| **3.3.0** | 3D Secure Feature Added |

---

## 🛠️ Demos

![iOS](assets/ios_demo_with_3d_secure.gif)
Expand Down Expand Up @@ -143,6 +153,6 @@ You can find implementation details in the [Example App](example/src/App.tsx) or
## Roadmap

- [x] Venmo Integration
- [x] 3D-Secure (Alpha)
- [ ] Apple Pay
- [ ] Google Pay
- [x] 3D-Secure (Implemented in 3.3.0)
- [x] Google Pay (Implemented in 3.4.0)
- [ ] Apple Pay (TBD)
1 change: 1 addition & 0 deletions android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -104,4 +104,5 @@ dependencies {
implementation 'com.braintreepayments.api:card:5.19.0'
implementation 'com.braintreepayments.api:venmo:5.19.0'
implementation 'com.braintreepayments.api:three-d-secure:5.19.0'
implementation "com.braintreepayments.api:google-pay:5.19.0"
}
5 changes: 5 additions & 0 deletions android/src/main/java/com/expobraintree/BrainTreeEnums.kt
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,8 @@ enum class PAYPAL_ERROR_TYPES(val value: String) {
enum class VENMO_ERROR_TYPES(val value: String) {
VENMO_DISABLED_IN_CONFIGURATION("VENMO_DISABLED_IN_CONFIGURATION_ERROR")
}

enum class GOOGLE_PAY_ERROR_TYPES(val value: String) {
GOOGLE_PAY_NOT_AVAILABLE("GOOGLE_PAY_NOT_AVAILABLE"),
GOOGLE_PAY_FAILED("GOOGLE_PAY_FAILED")
}
38 changes: 22 additions & 16 deletions android/src/main/java/com/expobraintree/CardDataConverter.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,20 @@ class CardDataConverter {

companion object {

/**
* Converts a basic CardNonce into a WritableMap for React Native.
*/
fun createTokenizeCardDataNonce(cardNonce: CardNonce): WritableMap {
val result: WritableMap = Arguments.createMap()
result.putString("nonce", cardNonce.string)

// Handling unknown card types for consistent JS reporting
if (cardNonce.cardType == "Unknown") {
result.putString("cardNetwork", "")
} else {
result.putString("cardNetwork", cardNonce.cardType)
}

result.putString("lastFour", cardNonce.lastFour)
result.putString("lastTwo", cardNonce.lastTwo)
result.putString("expirationMonth", cardNonce.expirationMonth)
Expand All @@ -32,7 +38,7 @@ class CardDataConverter {

/**
* Converts the 3D Secure result nonce into a WritableMap to be sent back to JavaScript.
* This includes card details and the critical threeDSecureInfo object.
* Includes liability shift details required for security checks.
*/
fun createThreeDSecureDataNonce(cardNonce: ThreeDSecureNonce): WritableMap {
val result: WritableMap = Arguments.createMap()
Expand All @@ -53,18 +59,20 @@ class CardDataConverter {
val infoMap: WritableMap = Arguments.createMap()
val info = cardNonce.threeDSecureInfo

if (info != null) {
infoMap.putBoolean("liabilityShifted", info.liabilityShifted)
infoMap.putBoolean("liabilityShiftPossible", info.liabilityShiftPossible)
infoMap.putString("status", info.status)
infoMap.putBoolean("wasVerified", info.wasVerified)
}
// REMOVED: if (info != null) check because threeDSecureInfo is NonNull in SDK v5+
infoMap.putBoolean("liabilityShifted", info.liabilityShifted)
infoMap.putBoolean("liabilityShiftPossible", info.liabilityShiftPossible)
infoMap.putString("status", info.status)
infoMap.putBoolean("wasVerified", info.wasVerified)

result.putMap("threeDSecureInfo", infoMap)

return result
}

/**
* Creates a Card object from JS options for basic tokenization.
*/
fun createTokenizeCardRequest(options: ReadableMap): Card {
val card: Card = Card()
if (options.hasKey("number")) {
Expand All @@ -85,29 +93,30 @@ class CardDataConverter {
return card
}

/**
* Maps JS options to a ThreeDSecureRequest.
* Includes address mapping to satisfy 3DS 2.0 risk assessment requirements.
*/
fun create3DSecureRequest(options: ReadableMap): ThreeDSecureRequest {
val address = ThreeDSecurePostalAddress()

// Map personal names - Note: Braintree v6 uses givenName and surname
// Personal details mapping
if (options.hasKey("givenName")) {
address.givenName = options.getString("givenName")
}
if (options.hasKey("surName")) {
address.surname = options.getString("surName")
}

// Map contact details
if (options.hasKey("phoneNumber")) {
address.phoneNumber = options.getString("phoneNumber")
}

// CRITICAL: countryCodeAlpha2 MUST be provided if 'region' is present.
// This prevents the "The region cannot be provided without a corresponding country code" (422) error.
// Required by Braintree to avoid 422 errors if region is provided
if (options.hasKey("countryCodeAlpha2")) {
address.countryCodeAlpha2 = options.getString("countryCodeAlpha2")
}

// Map geographic location details
if (options.hasKey("city")) {
address.locality = options.getString("city")
}
Expand All @@ -124,21 +133,18 @@ class CardDataConverter {
address.extendedAddress = options.getString("streetAddress2")
}

// 3D Secure 2.0 requires additional info for better risk assessment
val additionalInformation = ThreeDSecureAdditionalInformation()
additionalInformation.shippingAddress = address

val threeDSecureRequest = ThreeDSecureRequest()
// Use elvis operator to ensure non-null values for the SDK
threeDSecureRequest.nonce = options.getString("nonce") ?: ""
threeDSecureRequest.email = options.getString("email") ?: ""
threeDSecureRequest.amount = options.getString("amount") ?: "0.00"

// Attach the objects to the main request
threeDSecureRequest.billingAddress = address
threeDSecureRequest.additionalInformation = additionalInformation

return threeDSecureRequest
}
}
}
}
Loading
Loading