From 6656c4f6353b46ab9c2df428a8aebb25f6be2dff Mon Sep 17 00:00:00 2001 From: Pedro Lima Date: Thu, 3 Jun 2021 11:19:56 -0700 Subject: [PATCH 1/2] Adding GPay basic integration with Braintree's GPay Plugin --- android/build.gradle | 13 +- .../com/pw/droplet/braintree/Braintree.java | 114 +++++++++++++++++- index.android.ts | 26 ++++ 3 files changed, 143 insertions(+), 10 deletions(-) diff --git a/android/build.gradle b/android/build.gradle index 805b561..ca244db 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -27,12 +27,12 @@ repositories { } android { - compileSdkVersion 28 + compileSdkVersion 29 buildToolsVersion "28.0.3" defaultConfig { - minSdkVersion 23 - targetSdkVersion 28 + minSdkVersion 24 + targetSdkVersion 29 versionCode 1 versionName "1.0" } @@ -46,10 +46,13 @@ android { } dependencies { - implementation 'com.facebook.react:react-native:0.59.10' - api 'com.braintreepayments.api:braintree:3.9.0' + implementation 'com.facebook.react:react-native:0.61.5' + api 'com.braintreepayments.api:braintree:3.17.4' api 'com.braintreepayments.api:data-collector:3.9.0' api 'com.squareup.okhttp3:okhttp:3.14.4' implementation 'com.braintreepayments.api:three-d-secure:3.8.0' implementation group: 'com.google.code.gson', name: 'gson', version: '2.8.6' + + implementation 'com.braintreepayments.api:google-payment:3.5.0' + api 'com.google.android.gms:play-services-wallet:18.1.3' } 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 bc99d11..063c583 100644 --- a/android/src/main/java/com/pw/droplet/braintree/Braintree.java +++ b/android/src/main/java/com/pw/droplet/braintree/Braintree.java @@ -1,6 +1,5 @@ package com.pw.droplet.braintree; -import android.content.Intent; import android.util.Log; import androidx.appcompat.app.AppCompatActivity; @@ -8,14 +7,20 @@ import com.braintreepayments.api.BraintreeFragment; import com.braintreepayments.api.Card; import com.braintreepayments.api.DataCollector; +import com.braintreepayments.api.GooglePayment; import com.braintreepayments.api.PayPal; import com.braintreepayments.api.exceptions.BraintreeError; import com.braintreepayments.api.exceptions.ErrorWithResponse; import com.braintreepayments.api.interfaces.BraintreeCancelListener; import com.braintreepayments.api.interfaces.BraintreeErrorListener; import com.braintreepayments.api.interfaces.BraintreeResponseListener; +import com.braintreepayments.api.interfaces.ConfigurationListener; import com.braintreepayments.api.interfaces.PaymentMethodNonceCreatedListener; import com.braintreepayments.api.models.CardBuilder; +import com.braintreepayments.api.models.Configuration; +import com.braintreepayments.api.models.GooglePaymentCardNonce; +import com.braintreepayments.api.models.GooglePaymentConfiguration; +import com.braintreepayments.api.models.GooglePaymentRequest; import com.braintreepayments.api.models.PayPalAccountNonce; import com.braintreepayments.api.models.PayPalRequest; import com.braintreepayments.api.models.PaymentMethodNonce; @@ -25,8 +30,9 @@ import com.facebook.react.bridge.ReactContextBaseJavaModule; import com.facebook.react.bridge.ReactMethod; import com.facebook.react.bridge.ReadableMap; -import com.facebook.react.bridge.WritableMap; import com.facebook.react.bridge.WritableNativeMap; +import com.google.android.gms.wallet.TransactionInfo; +import com.google.android.gms.wallet.WalletConstants; import com.google.gson.Gson; import com.google.gson.GsonBuilder; @@ -86,6 +92,7 @@ public void setup(final String token, final Callback successCallback, final Call } if (this.mBraintreeFragment != null) { + this.mBraintreeFragment.addListener(mConfigurationListener); this.mBraintreeFragment.addListener(mCancelListener); this.mBraintreeFragment.addListener(mPaymentNonceCreatedListener); this.mBraintreeFragment.addListener(mErrorListener); @@ -160,6 +167,80 @@ public void getCardNonce(final ReadableMap parameters, final Callback successCal Card.tokenize(this.mBraintreeFragment, cardBuilder); } + @ReactMethod + public void googlePayIsReadyToPay(final Callback successCallback, final Callback errorCallback) { + try { + GooglePayment.isReadyToPay(mBraintreeFragment, new BraintreeResponseListener() { + @Override + public void onResponse(Boolean isReadyToPay) { + successCallback.invoke(isReadyToPay); + } + }); + } catch (Exception e) { + Log.e(TAG, "Error calling GooglePayment.isReadyToPay", e); + errorCallback.invoke(e.getMessage()); + } + } + + @ReactMethod + public void googlePayRequestPayment(final String googlePayMerchantId, final String amount, final String currencyCode, final boolean billingAddressRequired, final Callback successCallback, final Callback errorCallback) { + this.successCallback = successCallback; + this.errorCallback = errorCallback; + + GooglePaymentRequest request = new GooglePaymentRequest() + .transactionInfo(TransactionInfo.newBuilder() + .setTotalPrice(amount) // format: 0.00 + .setTotalPriceStatus(WalletConstants.TOTAL_PRICE_STATUS_FINAL) + .setCurrencyCode(currencyCode) // ISO 4217 currency code + .build()); + + request.billingAddressRequired(billingAddressRequired); + //request.setAllowedCardNetworks(); TODO: pass down array of allowed card networks + //request.allowPrepaidCards(); TODO: add as parameter + //request.environment(); TODO: add as parameter (PRODUCTION or TEST) + + if (googlePayMerchantId != null && googlePayMerchantId.length() > 0) { + // Optional in sandbox; if set in sandbox, this value must be a valid production Google Merchant ID + request.googleMerchantId(googlePayMerchantId); + } + + GooglePayment.requestPayment(mBraintreeFragment, request); + } + + private void googlePayNonceCallback(GooglePaymentCardNonce googlePayNonce) { + if (googlePayNonce == null || googlePayNonce.getNonce() == null) { + this.errorCallback.invoke("GooglePay nonce is null"); + return; + } + + WritableNativeMap map = new WritableNativeMap(); + map.putString("nonce", googlePayNonce.getNonce()); + + if (googlePayNonce.getCardType() == null) { + this.errorCallback.invoke("GooglePay cardType is null"); + return; + } + map.putString("cardType", googlePayNonce.getCardType()); + + if (googlePayNonce.getLastFour() != null) { + map.putString("lastFour", googlePayNonce.getLastFour()); + } + + if (googlePayNonce.getEmail() != null) { + map.putString("email", googlePayNonce.getEmail()); + } + + if (googlePayNonce.getBillingAddress() != null) { + map.putMap("billingAddress", getAddressMap(googlePayNonce.getBillingAddress())); + } + + if (googlePayNonce.getShippingAddress() != null) { + map.putMap("shippingAddress", getAddressMap(googlePayNonce.getShippingAddress())); + } + + this.successCallback.invoke(map); + } + @ReactMethod public void payPalRequestOneTimePayment(final String amount, final String currencyCode, final Callback successCallback, final Callback errorCallback) { PayPal.requestOneTimePayment(this.mBraintreeFragment, getPayPalRequest(amount, currencyCode, successCallback, errorCallback)); @@ -205,11 +286,11 @@ private void payPalNonceCallback(PayPalAccountNonce payPalAccountNonce) { map.putString("lastName", payPalAccountNonce.getLastName()); if (payPalAccountNonce.getBillingAddress() != null) { - map.putMap("billingAddress", getPayPalAddressMap(payPalAccountNonce.getBillingAddress())); + map.putMap("billingAddress", getAddressMap(payPalAccountNonce.getBillingAddress())); } if (payPalAccountNonce.getShippingAddress() != null) { - map.putMap("shippingAddress", getPayPalAddressMap(payPalAccountNonce.getShippingAddress())); + map.putMap("shippingAddress", getAddressMap(payPalAccountNonce.getShippingAddress())); } this.successCallback.invoke(map); @@ -242,7 +323,7 @@ private PayPalRequest getPayPalRequest(final @Nullable String amount, final Stri .intent(PayPalRequest.INTENT_AUTHORIZE); } - private WritableMap getPayPalAddressMap(PostalAddress address) { + private ReadableMap getAddressMap(PostalAddress address) { WritableNativeMap map = new WritableNativeMap(); map.putString("recipientName", address.getRecipientName()); map.putString("streetAddress", address.getStreetAddress()); @@ -254,6 +335,27 @@ private WritableMap getPayPalAddressMap(PostalAddress address) { return map; } + private ConfigurationListener mConfigurationListener = new ConfigurationListener() { + @Override + public void onConfigurationFetched(Configuration configuration) { + GooglePaymentConfiguration gPayConfig = configuration.getGooglePayment(); + if (gPayConfig != null) { + // TODO remove all these logs before going live + Log.d(TAG, "GooglePay environment: " + gPayConfig.getEnvironment()); + Log.d(TAG, "GooglePay display name: " + gPayConfig.getDisplayName()); + Log.d(TAG, "GooglePay authorization fingerprint: " + gPayConfig.getGoogleAuthorizationFingerprint()); + if (gPayConfig.getSupportedNetworks() != null) { + for (String network : gPayConfig.getSupportedNetworks()) { + Log.d(TAG, "GooglePay supported network: " + network); + } + } + } + else { + Log.w(TAG, "GooglePay configuration is null"); + } + } + }; + private BraintreeCancelListener mCancelListener = new BraintreeCancelListener() { @Override public void onCancel(int requestCode) { @@ -266,6 +368,8 @@ public void onCancel(int requestCode) { public void onPaymentMethodNonceCreated(PaymentMethodNonce paymentMethodNonce) { if (paymentMethodNonce instanceof PayPalAccountNonce) { payPalNonceCallback((PayPalAccountNonce)paymentMethodNonce); + } else if (paymentMethodNonce instanceof GooglePaymentCardNonce) { + googlePayNonceCallback((GooglePaymentCardNonce)paymentMethodNonce); } else { nonceCallback(paymentMethodNonce.getNonce()); } diff --git a/index.android.ts b/index.android.ts index 3e5a08c..02241aa 100644 --- a/index.android.ts +++ b/index.android.ts @@ -14,6 +14,15 @@ export type PayPalSuccess = { shippingAddress: Address | null, }; +export type GooglePaySuccess = { + nonce: string, + cardType: string, + email: string | null, + lastFour: string | null, + billingAddress: Address | null, + shippingAddress: Address | null, +}; + export type Address = { recipientName: string | null, streetAddress: string | null, @@ -36,6 +45,23 @@ class Braintree { }); } + checkIsGooglePayReadyToPay(): Promise { + return new Promise((resolve: (result: boolean)=>void, reject: (reason: string)=>void) => { + NativeBraintree.googlePayIsReadyToPay( + (isReadyToPay: boolean) => resolve(isReadyToPay), + (error: string) => reject(error)) + }); + } + + getGooglePayNonce(googlePayMerchantId: string, amount: number, currencyCode: string, isBillingAddressRequired: boolean): Promise { + return new Promise((resolve: (result: GooglePaySuccess)=>void, reject: (reason: string)=>void) => { + NativeBraintree.googlePayRequestPayment(googlePayMerchantId, amount, currencyCode, isBillingAddressRequired, + (googlePaySuccess: GooglePaySuccess) => resolve(googlePaySuccess), + (error: string) => reject(error) + ); + }); + } + getPayPalOneTimePaymentNonce(amount: number, currencyCode: string): Promise { return new Promise((resolve: (result: PayPalSuccess)=>void, reject: (reason: string)=>void) => { NativeBraintree.payPalRequestOneTimePayment(amount, currencyCode, From b216b87e14c3aa629ac4f7c0e8bfd1bb531da5cf Mon Sep 17 00:00:00 2001 From: Pedro Lima Date: Thu, 3 Jun 2021 11:24:20 -0700 Subject: [PATCH 2/2] clearing callbacks - can be refactored to look better --- .../src/main/java/com/pw/droplet/braintree/Braintree.java | 6 ++++++ 1 file changed, 6 insertions(+) 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 063c583..1cca9fc 100644 --- a/android/src/main/java/com/pw/droplet/braintree/Braintree.java +++ b/android/src/main/java/com/pw/droplet/braintree/Braintree.java @@ -210,6 +210,8 @@ public void googlePayRequestPayment(final String googlePayMerchantId, final Stri private void googlePayNonceCallback(GooglePaymentCardNonce googlePayNonce) { if (googlePayNonce == null || googlePayNonce.getNonce() == null) { this.errorCallback.invoke("GooglePay nonce is null"); + this.errorCallback = null; + this.successCallback = null; return; } @@ -218,6 +220,8 @@ private void googlePayNonceCallback(GooglePaymentCardNonce googlePayNonce) { if (googlePayNonce.getCardType() == null) { this.errorCallback.invoke("GooglePay cardType is null"); + this.errorCallback = null; + this.successCallback = null; return; } map.putString("cardType", googlePayNonce.getCardType()); @@ -239,6 +243,8 @@ private void googlePayNonceCallback(GooglePaymentCardNonce googlePayNonce) { } this.successCallback.invoke(map); + this.errorCallback = null; + this.successCallback = null; } @ReactMethod