diff --git a/packages/react-native-payments/android/build.gradle b/packages/react-native-payments/android/build.gradle
index 2dac59c2..75b387bc 100644
--- a/packages/react-native-payments/android/build.gradle
+++ b/packages/react-native-payments/android/build.gradle
@@ -1,12 +1,12 @@
apply plugin: 'com.android.library'
android {
- compileSdkVersion 23
- buildToolsVersion "23.0.1"
+ compileSdkVersion 28
+ buildToolsVersion "28.0.3"
defaultConfig {
minSdkVersion 16
- targetSdkVersion 22
+ targetSdkVersion 28
versionCode 1
versionName "1.0"
ndk {
@@ -19,7 +19,7 @@ android {
}
dependencies {
- compile 'com.facebook.react:react-native:+'
- compile 'com.google.android.gms:play-services-wallet:11.0.4'
- compile 'com.android.support:support-v4:23.0.1'
-}
+ implementation 'com.facebook.react:react-native:+'
+ implementation 'com.google.android.gms:play-services-wallet:17.0.0'
+ implementation 'androidx.appcompat:appcompat:1.0.2'
+}
\ No newline at end of file
diff --git a/packages/react-native-payments/android/src/main/AndroidManifest.xml b/packages/react-native-payments/android/src/main/AndroidManifest.xml
index 3bc7e98e..0a14dbfd 100644
--- a/packages/react-native-payments/android/src/main/AndroidManifest.xml
+++ b/packages/react-native-payments/android/src/main/AndroidManifest.xml
@@ -2,4 +2,9 @@
+
+
+
diff --git a/packages/react-native-payments/android/src/main/java/com/reactnativepayments/ReactNativePaymentsModule.java b/packages/react-native-payments/android/src/main/java/com/reactnativepayments/ReactNativePaymentsModule.java
index a47b50d4..7ff9fff3 100644
--- a/packages/react-native-payments/android/src/main/java/com/reactnativepayments/ReactNativePaymentsModule.java
+++ b/packages/react-native-payments/android/src/main/java/com/reactnativepayments/ReactNativePaymentsModule.java
@@ -5,11 +5,11 @@
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
-import android.support.annotation.Nullable;
-import android.support.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.NonNull;
import android.app.Fragment;
import android.app.FragmentManager;
-import android.support.annotation.RequiresPermission;
+import androidx.annotation.RequiresPermission;
import android.util.Log;
import com.facebook.react.bridge.Callback;
@@ -22,6 +22,10 @@
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.identity.intents.model.UserAddress;
import com.google.android.gms.wallet.*;
+import com.google.android.gms.common.api.ApiException;
+import com.google.android.gms.common.api.Status;
+import com.google.android.gms.tasks.OnCompleteListener;
+import com.google.android.gms.tasks.Task;
import com.facebook.react.bridge.ActivityEventListener;
import com.facebook.react.bridge.BaseActivityEventListener;
@@ -38,19 +42,19 @@
import java.util.List;
import java.util.Map;
-public class ReactNativePaymentsModule extends ReactContextBaseJavaModule implements GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener {
+import org.json.JSONException;
+import org.json.JSONObject;
+
+public class ReactNativePaymentsModule extends ReactContextBaseJavaModule {
private static final int LOAD_MASKED_WALLET_REQUEST_CODE = 88;
- private static final int LOAD_FULL_WALLET_REQUEST_CODE = 89;
- // Google API Client
- private GoogleApiClient mGoogleApiClient = null;
+ // Payments Client
+ private PaymentsClient mPaymentsClient;
// Callbacks
private static Callback mShowSuccessCallback = null;
private static Callback mShowErrorCallback = null;
- private static Callback mGetFullWalletSuccessCallback= null;
- private static Callback mGetFullWalletErrorCallback = null;
public static final String REACT_CLASS = "ReactNativePayments";
@@ -69,13 +73,12 @@ public void onActivityResult(Activity activity, int requestCode, int resultCode,
switch (resultCode) {
case Activity.RESULT_OK:
if (data != null) {
- MaskedWallet maskedWallet =
- data.getParcelableExtra(WalletConstants.EXTRA_MASKED_WALLET);
- Log.i(REACT_CLASS, "ANDROID PAY SUCCESS" + maskedWallet.getEmail());
- Log.i(REACT_CLASS, "ANDROID PAY SUCCESS" + buildAddressFromUserAddress(maskedWallet.getBuyerBillingAddress()));
+ PaymentData paymentData = PaymentData.getFromIntent(data);
+
+ Log.i(REACT_CLASS, "ANDROID PAY SUCCESS" + buildAddressFromUserAddress(paymentData.getCardInfo().getBillingAddress()));
- UserAddress userAddress = maskedWallet.getBuyerShippingAddress();
+ UserAddress userAddress = paymentData.getShippingAddress();
WritableNativeMap shippingAddress = userAddress != null
? buildAddressFromUserAddress(userAddress)
: null;
@@ -83,12 +86,33 @@ public void onActivityResult(Activity activity, int requestCode, int resultCode,
// TODO: Move into function
WritableNativeMap paymentDetails = new WritableNativeMap();
- paymentDetails.putString("paymentDescription", maskedWallet.getPaymentDescriptions()[0]);
- paymentDetails.putString("payerEmail", maskedWallet.getEmail());
+ paymentDetails.putString("payerEmail", paymentData.getEmail());
paymentDetails.putMap("shippingAddress", shippingAddress);
- paymentDetails.putString("googleTransactionId", maskedWallet.getGoogleTransactionId());
+ paymentDetails.putString("googleTransactionId", paymentData.getGoogleTransactionId());
+
+ WritableNativeMap cardInfo = buildCardInfo(paymentData.getCardInfo());
+ paymentDetails.putMap("cardInfo", cardInfo);
+
+ String serializedPaymentToken = paymentData.getPaymentMethodToken().getToken();
+ try {
+ JSONObject paymentTokenJson = new JSONObject(serializedPaymentToken);
+ String protocolVersion = paymentTokenJson.getString("protocolVersion");
+ String signature = paymentTokenJson.getString("signature");
+ String signedMessage = paymentTokenJson.getString("signedMessage");
+
+ WritableNativeMap paymentToken = new WritableNativeMap();
+ paymentToken.putString("protocolVersion", protocolVersion);
+ paymentToken.putString("signature", signature);
+ paymentToken.putString("signedMessage", signedMessage);
- sendEvent(reactContext, "NativePayments:onuseraccept", paymentDetails);
+ paymentDetails.putMap("paymentToken", paymentToken);
+
+ sendEvent(reactContext, "NativePayments:onuseraccept", paymentDetails);
+
+ } catch (JSONException e) {
+ Log.e(REACT_CLASS, "ANDROID PAY JSON ERROR", e);
+ mShowErrorCallback.invoke(errorCode);
+ }
}
break;
case Activity.RESULT_CANCELED:
@@ -102,17 +126,6 @@ public void onActivityResult(Activity activity, int requestCode, int resultCode,
break;
}
break;
- case LOAD_FULL_WALLET_REQUEST_CODE:
- if (resultCode == Activity.RESULT_OK && data != null) {
- FullWallet fullWallet = data.getParcelableExtra(WalletConstants.EXTRA_FULL_WALLET);
- String tokenJSON = fullWallet.getPaymentMethodToken().getToken();
- Log.i(REACT_CLASS, "FULL WALLET SUCCESS" + tokenJSON);
-
- mGetFullWalletSuccessCallback.invoke(tokenJSON);
- } else {
- Log.i(REACT_CLASS, "FULL WALLET FAILURE");
- mGetFullWalletErrorCallback.invoke();
- }
case WalletConstants.RESULT_ERROR:activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_SECURE);
// handleError(errorCode);
break;
@@ -151,25 +164,42 @@ public void getSupportedGateways(Callback errorCallback, Callback successCallbac
}
@ReactMethod
- public void canMakePayments(ReadableMap paymentMethodData, Callback errorCallback, Callback successCallback) {
- final Callback callback = successCallback;
- IsReadyToPayRequest req = IsReadyToPayRequest.newBuilder()
- .addAllowedCardNetwork(WalletConstants.CardNetwork.MASTERCARD)
- .addAllowedCardNetwork(WalletConstants.CardNetwork.VISA)
- .build();
+ public void canMakePayments(ReadableMap paymentMethodData, final Callback errorCallback, final Callback successCallback) {
+ IsReadyToPayRequest.Builder builder = IsReadyToPayRequest.newBuilder();
+
+ ReadableArray allowedCardNetworks = paymentMethodData.getArray("supportedNetworks");
+ if (allowedCardNetworks != null) {
+ builder.addAllowedCardNetworks(buildAllowedCardNetworks(allowedCardNetworks));
+ }
+
+ ReadableArray allowedPaymentMethods = paymentMethodData.getArray("allowedPaymentMethods");
+ if (allowedPaymentMethods != null) {
+ builder.addAllowedPaymentMethods(buildAllowedPaymentMethods(allowedPaymentMethods));
+ }
+
+ IsReadyToPayRequest request = builder.build();
+
int environment = getEnvironmentFromPaymentMethodData(paymentMethodData);
- if (mGoogleApiClient == null) {
- buildGoogleApiClient(getCurrentActivity(), environment);
+ if (mPaymentsClient == null) {
+ buildPaymentsClient(getCurrentActivity(), environment);
}
- Wallet.Payments.isReadyToPay(mGoogleApiClient, req)
- .setResultCallback(new ResultCallback() {
- @Override
- public void onResult(@NonNull BooleanResult booleanResult) {
- callback.invoke(booleanResult.getValue());
+ Task task = mPaymentsClient.isReadyToPay(request);
+ task.addOnCompleteListener(
+ new OnCompleteListener() {
+ @Override
+ public void onComplete(@NonNull Task task) {
+ try {
+ boolean result = task.getResult(ApiException.class);
+ if (result) {
+ successCallback.invoke(result);
+ }
+ } catch (ApiException e) {
+ errorCallback.invoke(e.getMessage());
}
- });
+ }
+ });
}
@ReactMethod
@@ -198,53 +228,40 @@ public void show(
final PaymentMethodTokenizationParameters parameters = buildTokenizationParametersFromPaymentMethodData(paymentMethodData);
- // TODO: clean up MaskedWalletRequest
ReadableMap total = details.getMap("total").getMap("amount");
- final MaskedWalletRequest maskedWalletRequest = MaskedWalletRequest.newBuilder()
- .setPaymentMethodTokenizationParameters(parameters)
- .setPhoneNumberRequired(shouldRequestPayerPhone)
- .setShippingAddressRequired(shouldRequestShipping)
- .setEstimatedTotalPrice(total.getString("value"))
- .setCurrencyCode(total.getString("currency"))
- .build();
- int environment = getEnvironmentFromPaymentMethodData(paymentMethodData);
- if (mGoogleApiClient == null) {
- buildGoogleApiClient(getCurrentActivity(), environment);
- }
- Wallet.Payments.loadMaskedWallet(mGoogleApiClient, maskedWalletRequest, LOAD_MASKED_WALLET_REQUEST_CODE);
- }
+ PaymentDataRequest.Builder builder = PaymentDataRequest.newBuilder()
+ .setTransactionInfo(TransactionInfo.newBuilder()
+ .setTotalPriceStatus(WalletConstants.TOTAL_PRICE_STATUS_FINAL)
+ .setTotalPrice(total.getString("value"))
+ .setCurrencyCode(total.getString("currency"))
+ .build())
+ .setPhoneNumberRequired(shouldRequestPayerPhone)
+ .setShippingAddressRequired(shouldRequestShipping)
+ .setPaymentMethodTokenizationParameters(parameters);
- @ReactMethod
- public void getFullWalletAndroid(
- String googleTransactionId,
- ReadableMap paymentMethodData,
- ReadableMap details,
- Callback errorCallback,
- Callback successCallback
- ) {
- mGetFullWalletSuccessCallback = successCallback;
- mGetFullWalletErrorCallback = errorCallback;
- ReadableMap total = details.getMap("total").getMap("amount");
- Log.i(REACT_CLASS, "ANDROID PAY getFullWalletAndroid" + details.getMap("total").getMap("amount"));
+ ReadableArray allowedCardNetworks = paymentMethodData.getArray("supportedNetworks");
+ if (allowedCardNetworks != null) {
+ builder.setCardRequirements(CardRequirements.newBuilder()
+ .addAllowedCardNetworks(buildAllowedCardNetworks(allowedCardNetworks)).build()
+ );
+ }
+
+ ReadableArray allowedPaymentMethods = paymentMethodData.getArray("allowedPaymentMethods");
+ if (allowedPaymentMethods != null) {
+ builder.addAllowedPaymentMethods(buildAllowedPaymentMethods(allowedPaymentMethods));
+ }
- FullWalletRequest fullWalletRequest = FullWalletRequest.newBuilder()
- .setGoogleTransactionId(googleTransactionId)
- .setCart(Cart.newBuilder()
- .setCurrencyCode(total.getString("currency"))
- .setTotalPrice(total.getString("value"))
- .setLineItems(buildLineItems(details.getArray("displayItems")))
- .build())
- .build();
+ PaymentDataRequest request = builder.build();
int environment = getEnvironmentFromPaymentMethodData(paymentMethodData);
- if (mGoogleApiClient == null) {
- buildGoogleApiClient(getCurrentActivity(), environment);
- }
- Wallet.Payments.loadFullWallet(mGoogleApiClient, fullWalletRequest, LOAD_FULL_WALLET_REQUEST_CODE);
+ if (mPaymentsClient == null) buildPaymentsClient(getCurrentActivity(), environment);
+
+ AutoResolveHelper.resolveTask(
+ mPaymentsClient.loadPaymentData(request), getCurrentActivity(), LOAD_MASKED_WALLET_REQUEST_CODE);
}
// Private Method
@@ -280,6 +297,64 @@ private static PaymentMethodTokenizationParameters buildTokenizationParametersFr
}
}
+ protected static List buildAllowedPaymentMethods(ReadableArray allowedPaymentMethods) {
+ List result = new ArrayList();
+ int size = allowedPaymentMethods.size();
+ for (int i = 0; i < size; ++i) {
+ int allowedPaymentMethod = allowedPaymentMethods.getInt(i);
+ result.add(allowedPaymentMethod);
+ }
+
+ return result;
+ }
+
+ protected static List buildAllowedCardNetworks(ReadableArray allowedCardNetworks) {
+ List result = new ArrayList();
+ int size = allowedCardNetworks.size();
+ for (int i = 0; i < size; ++i) {
+ String allowedCardNetwork = allowedCardNetworks.getString(i);
+ switch (allowedCardNetwork) {
+ case "visa":
+ result.add(WalletConstants.CARD_NETWORK_VISA);
+ break;
+ case "mastercard":
+ result.add(WalletConstants.CARD_NETWORK_MASTERCARD);
+ break;
+ case "amex":
+ result.add(WalletConstants.CARD_NETWORK_AMEX);
+ break;
+ case "discover":
+ result.add(WalletConstants.CARD_NETWORK_DISCOVER);
+ break;
+ case "interac":
+ result.add(WalletConstants.CARD_NETWORK_INTERAC);
+ break;
+ case "jcb":
+ result.add(WalletConstants.CARD_NETWORK_JCB);
+ break;
+ default:
+ result.add(WalletConstants.CARD_NETWORK_OTHER);
+ }
+ }
+
+ return result;
+ }
+
+ protected static WritableNativeMap buildCardInfo(CardInfo cardInfo) {
+
+ if (cardInfo == null) return null;
+
+
+ WritableNativeMap result = new WritableNativeMap();
+
+ result.putInt("cardClass", cardInfo.getCardClass());
+ result.putString("cardDescription", cardInfo.getCardDescription());
+ result.putString("cardDetails", cardInfo.getCardDetails());
+ result.putString("cardNetwork", cardInfo.getCardNetwork());
+
+ return result;
+ }
+
private static List buildLineItems(ReadableArray displayItems) {
List list = new ArrayList();
@@ -305,6 +380,8 @@ private static List buildLineItems(ReadableArray displayItems) {
private static WritableNativeMap buildAddressFromUserAddress(UserAddress userAddress) {
WritableNativeMap address = new WritableNativeMap();
+ if (userAddress == null) return address;
+
address.putString("recipient", userAddress.getName());
address.putString("organization", userAddress.getCompanyName());
address.putString("addressLine", userAddress.getAddress1());
@@ -336,36 +413,12 @@ private int getEnvironmentFromPaymentMethodData(ReadableMap paymentMethodData) {
: WalletConstants.ENVIRONMENT_PRODUCTION;
}
- // Google API Client
- // ---------------------------------------------------------------------------------------------
- private void buildGoogleApiClient(Activity currentActivity, int environment) {
- mGoogleApiClient = new GoogleApiClient.Builder(currentActivity)
- .addConnectionCallbacks(this)
- .addOnConnectionFailedListener(this)
- .addApi(Wallet.API, new Wallet.WalletOptions.Builder()
- .setEnvironment(environment)
- .setTheme(WalletConstants.THEME_LIGHT)
- .build())
- .build();
- mGoogleApiClient.connect();
- }
-
- @Override
- public void onConnected(Bundle connectionHint) {
-// mLastLocation = LocationServices.FusedLocationApi.getLastLocation(mGoogleApiClient);
- }
-
-
- @Override
- public void onConnectionFailed(ConnectionResult result) {
- // Refer to Google Play documentation for what errors can be logged
- Log.i(REACT_CLASS, "Connection failed: ConnectionResult.getErrorCode() = " + result.getErrorCode());
- }
-
- @Override
- public void onConnectionSuspended(int cause) {
- // Attempts to reconnect if a disconnect occurs
- Log.i(REACT_CLASS, "Connection suspended");
- mGoogleApiClient.connect();
+ protected void buildPaymentsClient(Activity currentActivity, int environment) {
+ mPaymentsClient = Wallet.getPaymentsClient(
+ currentActivity,
+ new Wallet.WalletOptions.Builder()
+ .setEnvironment(environment)
+ .build()
+ );
}
}
diff --git a/packages/react-native-payments/android/src/main/res/values/strings.xml b/packages/react-native-payments/android/src/main/res/values/strings.xml
new file mode 100644
index 00000000..439eefd2
--- /dev/null
+++ b/packages/react-native-payments/android/src/main/res/values/strings.xml
@@ -0,0 +1,3 @@
+
+ react-native-payments
+
\ No newline at end of file
diff --git a/packages/react-native-payments/lib/js/NativePayments.js b/packages/react-native-payments/lib/js/NativePayments.js
index 2485d601..22ff4b40 100644
--- a/packages/react-native-payments/lib/js/NativePayments.js
+++ b/packages/react-native-payments/lib/js/NativePayments.js
@@ -1,6 +1,6 @@
// @flow
-import type { PaymentDetailsBase, PaymentComplete } from './types';
+import type { CanMakePayments, PaymentDetailsBase, PaymentComplete } from './types';
import { NativeModules, Platform } from 'react-native';
const { ReactNativePayments } = NativeModules;
@@ -8,26 +8,25 @@ const { ReactNativePayments } = NativeModules;
const IS_ANDROID = Platform.OS === 'android';
const NativePayments: {
- canMakePayments: boolean,
+ canMakePayments: CanMakePayments => Promise,
supportedGateways: Array,
createPaymentRequest: PaymentDetailsBase => Promise,
handleDetailsUpdate: PaymentDetailsBase => Promise,
show: () => Promise,
abort: () => Promise,
- complete: PaymentComplete => Promise,
- getFullWalletAndroid: string => Promise
+ complete: PaymentComplete => Promise
} = {
supportedGateways: IS_ANDROID
- ? ['stripe', 'braintree'] // On Android, Payment Gateways are supported out of the gate.
+ ? [] // On Android, Payment Gateways are supported out of the gate.
: ReactNativePayments ? ReactNativePayments.supportedGateways : [],
- canMakePayments(methodData: object) {
+ canMakePayments(methodData?: CanMakePayments) {
return new Promise((resolve, reject) => {
if (IS_ANDROID) {
ReactNativePayments.canMakePayments(
methodData,
(err) => reject(err),
- (canMakePayments) => resolve(true)
+ () => resolve(true)
);
return;
@@ -133,30 +132,6 @@ const NativePayments: {
resolve(true);
});
});
- },
-
- getFullWalletAndroid(googleTransactionId: string, paymentMethodData: object, details: object): Promise {
- return new Promise((resolve, reject) => {
- if (!IS_ANDROID) {
- reject(new Error('This method is only available on Android.'));
-
- return;
- }
-
- ReactNativePayments.getFullWalletAndroid(
- googleTransactionId,
- paymentMethodData,
- details,
- (err) => reject(err),
- (serializedPaymentToken) => resolve({
- serializedPaymentToken,
- paymentToken: JSON.parse(serializedPaymentToken),
- /** Leave previous typo in order not to create a breaking change **/
- serializedPaymenToken: serializedPaymentToken,
- paymenToken: JSON.parse(serializedPaymentToken)
- })
- );
- });
}
};
diff --git a/packages/react-native-payments/lib/js/PaymentRequest.js b/packages/react-native-payments/lib/js/PaymentRequest.js
index 44c4ad85..90f82829 100644
--- a/packages/react-native-payments/lib/js/PaymentRequest.js
+++ b/packages/react-native-payments/lib/js/PaymentRequest.js
@@ -211,7 +211,7 @@ export default class PaymentRequest {
const normalizedDetails = convertDetailAmountsToString(details);
// Validate gateway config if present
- if (hasGatewayConfig(platformMethodData)) {
+ if (IS_IOS && hasGatewayConfig(platformMethodData)) {
validateGateway(
getGatewayName(platformMethodData),
NativePayments.supportedGateways
@@ -312,35 +312,33 @@ export default class PaymentRequest {
}
_getPlatformDetailsAndroid(details: {
+ cardInfo: Object,
googleTransactionId: string,
payerEmail: string,
- paymentDescription: string,
- shippingAddress: Object,
+ paymentToken: Object,
+ shippingAddress?: Object,
}) {
const {
+ cardInfo,
googleTransactionId,
- paymentDescription
+ paymentToken,
} = details;
return {
+ cardInfo,
googleTransactionId,
- paymentDescription,
- // On Android, the recommended flow is to have user's confirm prior to
- // retrieving the full wallet.
- getPaymentToken: () => NativePayments.getFullWalletAndroid(
- googleTransactionId,
- getPlatformMethodData(JSON.parse(this._serializedMethodData, Platform.OS)),
- convertDetailAmountsToString(this._details)
- )
+ paymentToken,
};
}
_handleUserAccept(details: {
- transactionIdentifier: string,
- paymentData: string,
- shippingAddress: Object,
- payerEmail: string,
- paymentToken?: string,
+ cardInfo?: Object,
+ googleTransactionId?: string,
+ transactionIdentifier?: string,
+ paymentData?: Object,
+ shippingAddress?: Object,
+ payerEmail?: string,
+ paymentToken: Object | string,
}) {
// On Android, we don't have `onShippingAddressChange` events, so we
// set the shipping address when the user accepts.
@@ -469,12 +467,5 @@ export default class PaymentRequest {
});
});
}
-
- // https://www.w3.org/TR/payment-request/#canmakepayment-method
- canMakePayments(): Promise {
- return NativePayments.canMakePayments(
- getPlatformMethodData(JSON.parse(this._serializedMethodData), Platform.OS)
- );
- }
}
diff --git a/packages/react-native-payments/lib/js/index.js b/packages/react-native-payments/lib/js/index.js
index 3fa6f02d..97b90486 100644
--- a/packages/react-native-payments/lib/js/index.js
+++ b/packages/react-native-payments/lib/js/index.js
@@ -1,7 +1,9 @@
// @flow
import _PaymentRequest from './PaymentRequest';
+import _NativePayments from './NativePayments';
import { PKPaymentButton } from './PKPaymentButton';
export const ApplePayButton = PKPaymentButton;
export const PaymentRequest = _PaymentRequest;
+export const canMakePayments = _NativePayments.canMakePayments;
\ No newline at end of file
diff --git a/packages/react-native-payments/lib/js/types.js b/packages/react-native-payments/lib/js/types.js
index 8608a66f..eabbca32 100644
--- a/packages/react-native-payments/lib/js/types.js
+++ b/packages/react-native-payments/lib/js/types.js
@@ -97,3 +97,9 @@ export type PaymentDetailsIOSRaw = {
paymentToken?: string,
transactionIdentifier: string,
};
+
+export type CanMakePayments = {
+ supportedNetworks: Array,
+ allowedPaymentMethods: Array,
+ environment: string
+};