Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update Android activities and adding the billing address in the result #165

Open
wants to merge 27 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
bf07f7e
Builds on Android
muccy Oct 13, 2023
46a9bd6
implementation 'com.braintreepayments.api:drop-in:6.13.0'
muccy Oct 13, 2023
699a722
Using MainActivity in manifest
muccy Oct 13, 2023
b3e141c
Proper configuration of activity to come back from BT
muccy Oct 14, 2023
c4c7c5d
Transparent activity
muccy Oct 14, 2023
e04e610
Handling every type of cancellation
muccy Oct 14, 2023
a9e5636
Better cancellation managements
muccy Oct 14, 2023
2acf8b3
Updating readme
muccy Oct 14, 2023
a781070
Updating version
muccy Oct 14, 2023
eda1a33
Revert Android SDK version
muccy Oct 14, 2023
024a2fd
Revert example app gradle
muccy Oct 14, 2023
d368fc5
Merge branch 'release/4.0.0-dev.2'
muccy Oct 14, 2023
923ac5a
Android: sending PayPal request with complete values
muccy Oct 17, 2023
5d7099b
Merge branch 'release/4.0.0-dev.2'
muccy Oct 17, 2023
8d69045
Added support to retrieve the billing address when the nonce is gener…
fhdeodato Jun 5, 2024
d237f29
updated the example to display the billing address
fhdeodato Jun 5, 2024
36dcd62
Merge remote-tracking branch 'repo-magno/main'
fhdeodato Jun 5, 2024
df6052c
added billing address for the android plugin
fhdeodato Jun 5, 2024
a9de172
change function name and version bump
fhdeodato Jun 5, 2024
4237c1c
added flag to request billing address
fhdeodato Jun 5, 2024
d7cceb4
version bump
fhdeodato Jun 5, 2024
db3f297
added first name, last name and email to the return
fhdeodato Jun 6, 2024
c7ee312
version bump
fhdeodato Jun 6, 2024
562c521
added first name, last name and email to the return for iOS
fhdeodato Jun 6, 2024
6893cf4
added new parameters to the intent
fhdeodato Jun 6, 2024
05f590a
version bump
fhdeodato Jun 6, 2024
1bc42ab
updating pod specs and code style
fhdeodato Jun 7, 2024
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
3 changes: 3 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"editor.formatOnSave": true
}
26 changes: 26 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ dependencies {

#### PayPal / Venmo / 3D Secure

##### With Drop-In

In order for this plugin to support PayPal, Venmo or 3D Secure payments, you must allow for the
browser switch by adding an intent filter to your `AndroidManifest.xml` (inside the `<application>` body):

Expand All @@ -53,6 +55,30 @@ browser switch by adding an intent filter to your `AndroidManifest.xml` (inside
**Important:** Your app's URL scheme must begin with your app's package ID and end with `.braintree`. For example, if the Package ID is `com.your-company.your-app`, then your URL scheme should be `com.your-company.your-app.braintree`. `${applicationId}` is automatically applied with your app's package when using Gradle.
**Note:** The scheme you define must use all lowercase letters. If your package contains underscores, the underscores should be removed when specifying the scheme in your Android Manifest.

##### Without Drop-In

If you want to use PayPal without Drop-In, you need to declare another intent filter to your `AndroidManifest.xml` (inside the `<application>` body):

```xml
<activity android:name="com.example.flutter_braintree.FlutterBraintreeCustom"
android:theme="@style/bt_transparent_activity" android:exported="true"
android:launchMode="singleInstance">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="${applicationId}.return.from.braintree" />
</intent-filter>
</activity>
<activity android:name="com.braintreepayments.api.ThreeDSecureActivity" android:theme="@style/Theme.AppCompat.Light" android:exported="true">
</activity>
```

Please note that intent filter scheme is different from drop-in's one.

**Important:** Your app's URL scheme must begin with your app's package ID and end with `.return.from.braintree`. For example, if the Package ID is `com.your-company.your-app`, then your URL scheme should be `com.your-company.your-app.return.from.braintree`. `${applicationId}` is automatically applied with your app's package when using Gradle.
**Note:** The scheme you define must use all lowercase letters. If your package contains underscores, the underscores should be removed when specifying the scheme in your Android Manifest.

#### Google Pay

Add the wallet enabled meta-data tag to your `AndroidManifest.xml` (inside the `<application>` body):
Expand Down
3 changes: 1 addition & 2 deletions android/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.flutter_braintree">
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

<application>
<activity android:name=".DropInActivity"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
package com.example.flutter_braintree;
import static com.braintreepayments.api.PayPalCheckoutRequest.USER_ACTION_COMMIT;
import static com.braintreepayments.api.PayPalCheckoutRequest.USER_ACTION_DEFAULT;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;

import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import org.json.JSONException;
import org.json.JSONObject;

import com.braintreepayments.api.BraintreeClient;
import com.braintreepayments.api.Card;
Expand All @@ -14,33 +21,43 @@
import com.braintreepayments.api.PayPalCheckoutRequest;
import com.braintreepayments.api.PayPalClient;
import com.braintreepayments.api.PayPalListener;
import com.braintreepayments.api.PayPalPaymentIntent;
import com.braintreepayments.api.PayPalRequest;
import com.braintreepayments.api.PayPalVaultRequest;
import com.braintreepayments.api.PaymentMethodNonce;
import com.braintreepayments.api.PostalAddress;
import com.braintreepayments.api.UserCanceledException;


import java.util.HashMap;
import java.util.Objects;

public class FlutterBraintreeCustom extends AppCompatActivity implements PayPalListener {
private BraintreeClient braintreeClient;
private PayPalClient payPalClient;
private Boolean started = false;
private long creationTimestamp = -1;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

creationTimestamp = System.currentTimeMillis();

setContentView(R.layout.activity_flutter_braintree_custom);
try {
Intent intent = getIntent();
braintreeClient = new BraintreeClient(this, intent.getStringExtra("authorization"));
String returnUrlScheme = (getPackageName() + ".return.from.braintree").replace("_", "").toLowerCase();
braintreeClient = new BraintreeClient(this, intent.getStringExtra("authorization"), returnUrlScheme);

String type = intent.getStringExtra("type");
if (type.equals("tokenizeCreditCard")) {
tokenizeCreditCard();
} else if (type.equals("requestPaypalNonce")) {
payPalClient = new PayPalClient(this, braintreeClient);
payPalClient.setListener(this);
requestPaypalNonce();

} else {
throw new Exception("Invalid request type: " + type);
}
Expand All @@ -59,16 +76,15 @@ protected void onNewIntent(Intent newIntent) {
setIntent(newIntent);
}

@Override
protected void onStart() {
super.onStart();
// @Override
// protected void onStart() {
// super.onStart();
// }

}

@Override
protected void onResume() {
super.onResume();
}
// @Override
// protected void onResume() {
// super.onResume();
// }

protected void tokenizeCreditCard() {
Intent intent = getIntent();
Expand Down Expand Up @@ -104,6 +120,53 @@ protected void requestPaypalNonce() {
// Checkout flow
PayPalCheckoutRequest checkOutRequest = new PayPalCheckoutRequest(intent.getStringExtra("amount"));
checkOutRequest.setCurrencyCode(intent.getStringExtra("currencyCode"));
checkOutRequest.setDisplayName(intent.getStringExtra("displayName"));
checkOutRequest.setBillingAgreementDescription(intent.getStringExtra("billingAgreementDescription"));
checkOutRequest.setShouldRequestBillingAgreement(Objects.requireNonNull(intent.getExtras()).getBoolean("requestBillingAgreement"));
checkOutRequest.setShippingAddressEditable(Objects.requireNonNull(intent.getExtras()).getBoolean("shippingAddressEditable"));
checkOutRequest.setShippingAddressRequired(Objects.requireNonNull(intent.getExtras()).getBoolean("shippingAddressRequired"));

// handles the shipping address override
if (intent.getStringExtra("shippingAddressOverride") != null) {
try {
JSONObject obj = new JSONObject(intent.getStringExtra("shippingAddressOverride"));
PostalAddress shippingAddressOverride = new PostalAddress();
shippingAddressOverride.setRecipientName(obj.getString("recipientName"));
shippingAddressOverride.setStreetAddress(obj.getString("streetAddress"));
shippingAddressOverride.setExtendedAddress(obj.getString("extendedAddress"));
shippingAddressOverride.setLocality(obj.getString("locality"));
shippingAddressOverride.setCountryCodeAlpha2(obj.getString("countryCodeAlpha2"));
shippingAddressOverride.setPostalCode(obj.getString("postalCode"));
shippingAddressOverride.setRegion(obj.getString("region"));
checkOutRequest.setShippingAddressOverride(shippingAddressOverride);
} catch (JSONException e) {
throw new RuntimeException(e);
}
}

String userAction;
switch (intent.getStringExtra("payPalPaymentUserAction")) {
case "commit":
userAction = USER_ACTION_COMMIT;
break;
default:
userAction = USER_ACTION_DEFAULT;
}
checkOutRequest.setUserAction(userAction);

String paymentIntent;
switch (intent.getStringExtra("payPalPaymentIntent")) {
case "order":
paymentIntent = PayPalPaymentIntent.ORDER;
break;
case "sale":
paymentIntent = PayPalPaymentIntent.SALE;
break;
default:
paymentIntent = PayPalPaymentIntent.AUTHORIZE;
}
checkOutRequest.setIntent(paymentIntent);

payPalClient.tokenizePayPalAccount(this, checkOutRequest);
}
}
Expand All @@ -117,6 +180,14 @@ public void onPaymentMethodNonceCreated(PaymentMethodNonce paymentMethodNonce) {
nonceMap.put("paypalPayerId", paypalAccountNonce.getPayerId());
nonceMap.put("typeLabel", "PayPal");
nonceMap.put("description", paypalAccountNonce.getEmail());

nonceMap.put("firstName", paypalAccountNonce.getFirstName());
nonceMap.put("lastName", paypalAccountNonce.getLastName());
nonceMap.put("email", paypalAccountNonce.getEmail());

HashMap<String, Object> billingAddressMap = getResultBillingAddress(paypalAccountNonce);

nonceMap.put("billingAddress", billingAddressMap);
}else if(paymentMethodNonce instanceof CardNonce){
CardNonce cardNonce = (CardNonce) paymentMethodNonce;
nonceMap.put("typeLabel", cardNonce.getCardType());
Expand All @@ -129,6 +200,20 @@ public void onPaymentMethodNonceCreated(PaymentMethodNonce paymentMethodNonce) {
finish();
}

@NonNull
private static HashMap<String, Object> getResultBillingAddress(PayPalAccountNonce paypalAccountNonce) {
PostalAddress btBillingAddress = paypalAccountNonce.getBillingAddress();
HashMap<String, Object> billingAddressMap = new HashMap<String, Object>();
billingAddressMap.put("recipientName",btBillingAddress.getRecipientName());
billingAddressMap.put("streetAddress",btBillingAddress.getStreetAddress());
billingAddressMap.put("extendedAddress",btBillingAddress.getExtendedAddress());
billingAddressMap.put("locality",btBillingAddress.getLocality());
billingAddressMap.put("countryCodeAlpha2",btBillingAddress.getCountryCodeAlpha2());
billingAddressMap.put("postalCode",btBillingAddress.getPostalCode());
billingAddressMap.put("region",btBillingAddress.getRegion());
return billingAddressMap;
}

public void onCancel() {
setResult(RESULT_CANCELED);
finish();
Expand All @@ -149,12 +234,14 @@ public void onPayPalSuccess(@NonNull PayPalAccountNonce payPalAccountNonce) {
@Override
public void onPayPalFailure(@NonNull Exception error) {
if (error instanceof UserCanceledException) {
if(((UserCanceledException) error).isExplicitCancelation()){
if(((UserCanceledException) error).isExplicitCancelation() || System.currentTimeMillis() - creationTimestamp > 500)
{
// PayPal sometimes sends a UserCanceledException early for no reason: filter it out
// Otherwise take every cancellation event
onCancel();
}
} else {
onError(error);
}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
import android.content.Intent;


import org.json.JSONException;
import org.json.JSONObject;

import java.util.Map;

Expand Down Expand Up @@ -108,6 +110,30 @@ public void onMethodCall(MethodCall call, Result result) {
intent.putExtra("payPalPaymentIntent", (String) request.get("payPalPaymentIntent"));
intent.putExtra("payPalPaymentUserAction", (String) request.get("payPalPaymentUserAction"));
intent.putExtra("billingAgreementDescription", (String) request.get("billingAgreementDescription"));

if(request.containsKey("shippingAddressOverride")){
Map shippingAddress = (Map) request.get("shippingAddressOverride");
JSONObject shippingAddressJson;
shippingAddressJson = new JSONObject();
assert shippingAddress != null;
try {
shippingAddressJson.put("recipientName",shippingAddress.get("recipientName"));
shippingAddressJson.put("streetAddress",shippingAddress.get("streetAddress"));
shippingAddressJson.put("extendedAddress",shippingAddress.get("extendedAddress"));
shippingAddressJson.put("locality",shippingAddress.get("locality"));
shippingAddressJson.put("countryCodeAlpha2",shippingAddress.get("countryCodeAlpha2"));
shippingAddressJson.put("postalCode",shippingAddress.get("postalCode"));
shippingAddressJson.put("region",shippingAddress.get("region"));
} catch (JSONException e) {
throw new RuntimeException(e);
}
intent.putExtra("shippingAddressOverride", (String) shippingAddressJson.toString());
}

intent.putExtra("requestBillingAgreement", (Boolean) request.get("requestBillingAgreement"));
intent.putExtra("shippingAddressEditable", (Boolean) request.get("shippingAddressEditable"));
intent.putExtra("shippingAddressRequired", (Boolean) request.get("shippingAddressRequired"));

activity.startActivityForResult(intent, CUSTOM_ACTIVITY_REQUEST_CODE);
} else {
result.notImplemented();
Expand Down
25 changes: 25 additions & 0 deletions example/.vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "example",
"request": "launch",
"type": "dart"
},
{
"name": "example (profile mode)",
"request": "launch",
"type": "dart",
"flutterMode": "profile"
},
{
"name": "example (release mode)",
"request": "launch",
"type": "dart",
"flutterMode": "release"
}
]
}
2 changes: 1 addition & 1 deletion example/android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ apply plugin: 'com.android.application'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"

android {
compileSdkVersion 31
compileSdkVersion 33

defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
Expand Down
27 changes: 19 additions & 8 deletions example/android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,13 @@
<application
android:icon="@mipmap/ic_launcher">
<activity
android:name="io.flutter.embedding.android.FlutterActivity"
android:name=".MainActivity"
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
android:windowSoftInputMode="adjustResize"
android:exported="true"
android:launchMode="singleInstance">
<!-- This keeps the window background of the activity showing
until Flutter renders its first frame. It can be removed if
there is no splash screen (such as the default splash screen
Expand All @@ -22,8 +24,8 @@
android:name="io.flutter.app.android.SplashScreenUntilFirstFrame"
android:value="true" />
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>

Expand All @@ -36,15 +38,24 @@
<data android:scheme="com.example.flutterbraintreeexample.braintree" />
</intent-filter>
</activity>
<activity android:name="com.braintreepayments.api.ThreeDSecureActivity" android:exported="true">
<activity android:name="com.braintreepayments.api.ThreeDSecureActivity"
android:exported="true">
</activity>
<activity android:name="com.example.flutter_braintree.FlutterBraintreeCustom" android:theme="@style/Theme.AppCompat.Light" android:exported="true">
<activity android:name="com.example.flutter_braintree.FlutterBraintreeCustom"
android:theme="@style/bt_transparent_activity" android:exported="true"
android:launchMode="singleInstance">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="com.example.flutterbraintreeexample.return.from.braintree" />
</intent-filter>
</activity>

<meta-data android:name="com.google.android.gms.wallet.api.enabled" android:value="true"/>
<meta-data android:name="com.google.android.gms.wallet.api.enabled" android:value="true" />

<meta-data
android:name="flutterEmbedding"
android:value="2" />
</application>
</manifest>
</manifest>
4 changes: 2 additions & 2 deletions example/android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ buildscript {
}

dependencies {
classpath 'com.android.tools.build:gradle:4.1.0'
classpath 'com.android.tools.build:gradle:7.4.2'
}
}

Expand All @@ -24,6 +24,6 @@ subprojects {
project.evaluationDependsOn(':app')
}

task clean(type: Delete) {
tasks.register("clean", Delete) {
delete rootProject.buildDir
}
Loading