diff --git a/README.md b/README.md index ffb385f781..284bbc4fee 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ To give you as much flexibility as possible, our Android SDK can be integrated i Import the quick integration modules by adding these lines to your build.gradle file. ```groovy -final checkoutVersion = "2.4.1" +final checkoutVersion = "2.4.3" implementation "com.adyen.checkout:ui:${checkoutVersion}" implementation "com.adyen.checkout:nfc:${checkoutVersion}" // Optional; Integrates NFC card reader in card UI implementation "com.adyen.checkout:wechatpay:${checkoutVersion}" // Optional; Integrates support for WeChat Pay @@ -136,7 +136,7 @@ By default, we use the font that is declared in the theme that is used for check #### Installation Import the following modules by adding these line to your `build.gradle` file. ```groovy -final checkoutVersion = "2.4.1" +final checkoutVersion = "2.4.3" implementation "com.adyen.checkout:core:${checkoutVersion}" implementation "com.adyen.checkout:core-card:${checkoutVersion}" // Optional; Required for processing card payments. implementation "com.adyen.checkout:nfc:${checkoutVersion}" // Optional; Enables reading of card information with the device"s NFC chip. @@ -183,7 +183,7 @@ PaymentController.handlePaymentSessionResponse(/*Activity*/ this, encodedPayment With the `PaymentReference` you can retrieve an instance of a `PaymentHandler`. Here you can attach the desired Observers and Handlers in the scope of the current Activity (Observers and Handlers will automatically be removed when the `Activity` is destroyed): -> `PaymentReference` is `Parcelable`, so you can pass it along to another `Activity`. +> `PaymentReference` is `Parcelable`, so you can pass it along to another `Activity`. ```java @Override @@ -192,7 +192,7 @@ protected void onCreate(@Nullable Bundle savedInstanceState) { PaymentReference paymentReference = getIntent().getParcelableExtra("EXTRA_PAYMENT_REFERENCE"); mPaymentHandler = paymentReference.getPaymentHandler(/*Activity*/ this); - + // Observe data mPaymentHandler.getNetworkingStateObservable().observe(/*Activity*/ this, new Observer() { @Override @@ -212,7 +212,7 @@ protected void onCreate(@Nullable Bundle savedInstanceState) { // TODO: Handle PaymentResult. } }); - + // Handle data mPaymentHandler.setRedirectHandler(/*Activity*/ this, new RedirectHandler() { @Override @@ -252,6 +252,7 @@ If you are using ProGuard add the following options: -dontwarn com.adyen.checkout.nfc.** -dontwarn com.adyen.checkout.googlepay.** -dontwarn com.adyen.checkout.wechatpay.** +-dontwarn com.adyen.checkout.threeds.** ``` diff --git a/build.gradle b/build.gradle index 6a711e24ef..8463e5face 100644 --- a/build.gradle +++ b/build.gradle @@ -28,8 +28,8 @@ ext { "com.tencent.mm.opensdk:wechat-sdk-android-without-mta:9a15154c07c05eadba8351c110647c1754316e32d8f12f55e24679891b52739c:SHA-256", ] - versionCode = 211 - versionName = "2.4.2" + versionCode = 243 + versionName = "2.4.3" testCoverageEnabled = true } diff --git a/checkout-core-card/build.gradle b/checkout-core-card/build.gradle index 4e26b21ac3..e7604e0601 100644 --- a/checkout-core-card/build.gradle +++ b/checkout-core-card/build.gradle @@ -34,5 +34,5 @@ dependencies { implementation "com.android.support:support-annotations:${rootProject.supportLibVersion}" implementation "com.adyen.cse:adyen-cse:${rootProject.adyenCseVersion}" - implementation project(":checkout-base") + api project(":checkout-base") } diff --git a/checkout-core/build.gradle b/checkout-core/build.gradle index f235ca0f5c..09d1df5b78 100644 --- a/checkout-core/build.gradle +++ b/checkout-core/build.gradle @@ -38,5 +38,5 @@ dependencies { implementation "android.arch.persistence.room:runtime:${rootProject.roomVersion}" annotationProcessor "android.arch.persistence.room:compiler:${rootProject.roomVersion}" - implementation project(":checkout-base") + api project(":checkout-base") } diff --git a/checkout-googlepay/build.gradle b/checkout-googlepay/build.gradle index 190f429c72..ce559d6c0b 100644 --- a/checkout-googlepay/build.gradle +++ b/checkout-googlepay/build.gradle @@ -30,8 +30,8 @@ dependencies { implementation "com.android.support:support-annotations:${rootProject.supportLibVersion}" implementation "com.google.android.gms:play-services-wallet:${rootProject.playServicesWalletVersion}" - implementation project(":checkout-base") - implementation project(":checkout-core") - implementation project(":checkout-core-card") - implementation project(":checkout-util") + api project(":checkout-base") + api project(":checkout-core") + api project(":checkout-core-card") + api project(":checkout-util") } diff --git a/checkout-nfc/build.gradle b/checkout-nfc/build.gradle index a9763afccd..3b35ba6c45 100644 --- a/checkout-nfc/build.gradle +++ b/checkout-nfc/build.gradle @@ -31,5 +31,5 @@ android { dependencies { implementation "com.android.support:support-annotations:${rootProject.supportLibVersion}" - implementation project(":checkout-core-card") + api project(":checkout-core-card") } diff --git a/checkout-threeds/build.gradle b/checkout-threeds/build.gradle index 142e1cf146..4eaebddd41 100644 --- a/checkout-threeds/build.gradle +++ b/checkout-threeds/build.gradle @@ -30,7 +30,9 @@ android { dependencies { implementation "com.android.support:support-annotations:${rootProject.supportLibVersion}" - implementation "com.adyen.threeds:adyen-3ds2:0.9.4" + implementation("com.adyen.threeds:adyen-3ds2:0.9.5") { + exclude group: 'com.android.support', module: 'appcompat-v7' + } - implementation project(":checkout-base") + api project(":checkout-base") } diff --git a/checkout-threeds/src/main/java/com/adyen/checkout/threeds/Card3DS2Authenticator.java b/checkout-threeds/src/main/java/com/adyen/checkout/threeds/Card3DS2Authenticator.java index 442b0ba4f7..593bfd0460 100644 --- a/checkout-threeds/src/main/java/com/adyen/checkout/threeds/Card3DS2Authenticator.java +++ b/checkout-threeds/src/main/java/com/adyen/checkout/threeds/Card3DS2Authenticator.java @@ -40,21 +40,18 @@ public final class Card3DS2Authenticator { private Activity mActivity; - private ListenerDelegate mListenerDelegate; + private UiCustomization mUiCustomization; private Transaction mTransaction; - private UiCustomization mUiCustomization; - /** * Initializes the 3DS2 card authenticator. *

* * @param activity The current activity - * @param listener {@link AuthenticationListener} the 3DS2 authentication listener */ - public Card3DS2Authenticator(@NonNull Activity activity, @NonNull AuthenticationListener listener) { - this(activity, null, listener); + public Card3DS2Authenticator(@NonNull Activity activity) { + this(activity, null); } /** @@ -63,13 +60,11 @@ public Card3DS2Authenticator(@NonNull Activity activity, @NonNull Authentication * * @param activity The current activity. * @param uiCustomization (optional) The {@link UiCustomization}, UI configuration information that is used to specify the UI layout and theme. - * @param listener {@link AuthenticationListener} which will be invoked on authentication success with the challenge result or failure. */ @SuppressWarnings("WeakerAccess") - public Card3DS2Authenticator(@NonNull Activity activity, @Nullable UiCustomization uiCustomization, @NonNull AuthenticationListener listener) { + public Card3DS2Authenticator(@NonNull Activity activity, @Nullable UiCustomization uiCustomization) { mActivity = activity; mUiCustomization = uiCustomization; - mListenerDelegate = new ListenerDelegate(listener); } /** @@ -77,19 +72,17 @@ public Card3DS2Authenticator(@NonNull Activity activity, @Nullable UiCustomizati *

* * @param encodedFingerprintToken The fingerprint token received from the Checkout API. - * @return The encoded device fingerprint. + * @param listener {@link FingerprintListener} The fingerprint listener listens to fingerprint creation success or failure. */ @SuppressWarnings("WeakerAccess") @NonNull - public String createFingerprint(@NonNull String encodedFingerprintToken) throws ThreeDS2Exception { - FingerprintToken fingerprintToken; + public void createFingerprint(@NonNull String encodedFingerprintToken, @NonNull FingerprintListener listener) { try { - fingerprintToken = Base64Coder.decode(encodedFingerprintToken, FingerprintToken.class); + FingerprintToken fingerprintToken = Base64Coder.decode(encodedFingerprintToken, FingerprintToken.class); + createFingerprint(fingerprintToken.getDirectoryServerId(), fingerprintToken.getDirectoryServerPublicKey(), listener); } catch (JSONException e) { - throw ThreeDS2Exception.from("Fingerprint token decoding failure.", e); + listener.onFailure(ThreeDS2Exception.from("Fingerprint token decoding failure.", e)); } - - return createFingerprint(fingerprintToken.getDirectoryServerId(), fingerprintToken.getDirectoryServerPublicKey()); } /** @@ -98,32 +91,39 @@ public String createFingerprint(@NonNull String encodedFingerprintToken) throws * * @param directoryServerId The directory server identifier. * @param directoryServerPublicKey The directory server public key. - * @return The encoded device fingerprint. + * @param listener {@link FingerprintListener} The fingerprint listener listens to fingerprint creation success or failure. */ @SuppressWarnings("WeakerAccess") @NonNull - public String createFingerprint(@NonNull String directoryServerId, @NonNull String directoryServerPublicKey) throws ThreeDS2Exception { + public void createFingerprint( + @NonNull String directoryServerId, + @NonNull String directoryServerPublicKey, + @NonNull FingerprintListener listener + ) { ConfigParameters configParameters = new AdyenConfigParameters.Builder(directoryServerId, directoryServerPublicKey).build(); try { + // TODO: 15/04/2019 make this call async. ThreeDS2Service.INSTANCE.initialize(mActivity, configParameters, null, mUiCustomization); } catch (SDKAlreadyInitializedException e) { // Do nothing. } try { - mTransaction = ThreeDS2Service.INSTANCE.createTransaction(null, null); + closeTransaction(); + createTransaction(); } catch (SDKNotInitializedException e) { - throw ThreeDS2Exception.from("Transaction creation failure, 3DS service isn't initialized.", e); + listener.onFailure(ThreeDS2Exception.from("Transaction creation failure, 3DS service isn't initialized.", e)); } AuthenticationRequestParameters authenticationRequestParameters = mTransaction.getAuthenticationRequestParameters(); Fingerprint fingerprint = new Fingerprint(authenticationRequestParameters); try { - return Base64Coder.encode(fingerprint); + String encodedFingerprint = Base64Coder.encode(fingerprint); + listener.onSuccess(encodedFingerprint); } catch (JSONException e) { - throw ThreeDS2Exception.from("Fingerprint encoding failure.", e); + listener.onFailure(ThreeDS2Exception.from("Fingerprint encoding failure.", e)); } } @@ -132,9 +132,10 @@ public String createFingerprint(@NonNull String directoryServerId, @NonNull Stri *

* * @param encodedChallengeToken The challenge token, as received from the Checkout API. + * @param listener {@link ChallengeListener} The challenge listener listens to challenge authentication success or failure. */ - public void presentChallenge(@NonNull String encodedChallengeToken) throws ThreeDS2Exception { - presentChallenge(encodedChallengeToken, DEFAULT_CHALLENGE_TIME_OUT); + public void presentChallenge(@NonNull String encodedChallengeToken, @NonNull ChallengeListener listener) throws ThreeDS2Exception { + presentChallenge(encodedChallengeToken, DEFAULT_CHALLENGE_TIME_OUT, listener); } /** @@ -143,27 +144,29 @@ public void presentChallenge(@NonNull String encodedChallengeToken) throws Three * * @param encodedChallengeToken The challenge token, as received from the Checkout API. * @param challengeTimeOut The challenge timeout in minutes, the default is 10 minutes, minimum is 5 minutes. + * @param listener {@link ChallengeListener} The challenge listener listens to challenge authentication success or failure. */ @SuppressWarnings("WeakerAccess") - public void presentChallenge(@NonNull String encodedChallengeToken, int challengeTimeOut) throws ThreeDS2Exception { - Challenge challenge; + public void presentChallenge( + @NonNull String encodedChallengeToken, + int challengeTimeOut, + @NonNull ChallengeListener listener + ) throws ThreeDS2Exception { try { - challenge = Base64Coder.decode(encodedChallengeToken, Challenge.class); + Challenge challenge = Base64Coder.decode(encodedChallengeToken, Challenge.class); + ChallengeParameters challengeParameters = createChallengeParameters(challenge); + mTransaction.doChallenge(mActivity, challengeParameters, new ListenerDelegate(listener), challengeTimeOut); } catch (JSONException e) { throw ThreeDS2Exception.from("Challenge token decoding failure.", e); } - - ChallengeParameters challengeParameters = createChallengeParameters(challenge); - - mTransaction.doChallenge(mActivity, challengeParameters, mListenerDelegate, challengeTimeOut); } + /** + * Releases the resources been held by the {@link Card3DS2Authenticator}. + */ // TODO: 21/11/2018 replace the release with lifecycle aware logic. - public void release() { - if (mTransaction != null) { - mTransaction.close(); - mTransaction = null; - } + public synchronized void release() { + closeTransaction(); try { ThreeDS2Service.INSTANCE.cleanup(mActivity); @@ -172,7 +175,13 @@ public void release() { } mActivity = null; - mListenerDelegate = null; + } + + /** + * @return true if the {@link Card3DS2Authenticator} is released, otherwise false. + */ + public synchronized boolean isReleased() { + return mActivity == null; } @NonNull @@ -186,18 +195,92 @@ private ChallengeParameters createChallengeParameters(@NonNull Challenge challen return challengeParameters; } + private void createTransaction() throws SDKNotInitializedException { + mTransaction = ThreeDS2Service.INSTANCE.createTransaction(null, null); + } + + private void closeTransaction() { + if (mTransaction != null) { + mTransaction.close(); + mTransaction = null; + } + } + + public interface FingerprintListener { + /** + * Invoked on fingerprint creation without a failure. + *

+ * + * @param fingerprint contains the fingerprint data. + */ + void onSuccess(@NonNull String fingerprint); + + /** + * Invoked on fingerprint failure. + *

+ * + * @param e {@link ThreeDS2Exception} contains the failure metadata. + */ + void onFailure(@NonNull ThreeDS2Exception e); + } + + public interface ChallengeListener { + /** + * Invoked on challenge finish without a failure. + *

+ * + * @param challengeResult {@link ChallengeResult} contains the challenge authentication state and payload. + */ + void onSuccess(@NonNull ChallengeResult challengeResult); + + /** + * This method will be called when a user backs from a challenge. + */ + void onCancel(); + + /** + * This method will be called on challenge timeout.
+ * The default timeout is 10 minutes, the minimum timout is 5 minutes.
+ * It is possible to change the challenge timeout by passing desirable timeout to the following method + * {@link Card3DS2Authenticator#presentChallenge(String, int, ChallengeListener)} + */ + void onTimeout(); + + /** + * Invoked on challenge failure. + *

+ * + * @param e {@link ThreeDS2Exception} contains the failure metadata. + */ + void onFailure(@NonNull ThreeDS2Exception e); + } + + /** + * Simple implementation of {@link ChallengeListener} provides empty implementation of optional + * callback methods {@link ChallengeListener#onCancel()} and {@link ChallengeListener#onTimeout()}. + */ + public abstract static class SimpleChallengeListener implements ChallengeListener { + @Override + public void onCancel() { + } + + @Override + public void onTimeout() { + } + } + private final class ListenerDelegate implements ChallengeStatusReceiver { private static final String PROTOCOL_ERROR_FORMAT = "Error [code: %s, description: %s, details: %s]"; private static final String RUNTIME_ERROR_FORMAT = "Error [code: %s, message: %s]"; - private final AuthenticationListener mDelegate; + private final ChallengeListener mDelegate; /** * Initializes the ListenerDelegate. */ - ListenerDelegate(@NonNull AuthenticationListener listener) { + ListenerDelegate(@NonNull ChallengeListener listener) { mDelegate = listener; } @@ -216,12 +299,12 @@ public void completed(CompletionEvent completionEvent) { @Override public void cancelled() { - mDelegate.onFailure(ThreeDS2Exception.from("Challenge was canceled.")); + mDelegate.onCancel(); } @Override public void timedout() { - mDelegate.onFailure(ThreeDS2Exception.from("Challenge was timed out.")); + mDelegate.onTimeout(); } /** @@ -251,22 +334,4 @@ public void runtimeError(RuntimeErrorEvent runtimeErrorEvent) { mDelegate.onFailure(ThreeDS2Exception.from(message)); } } - - public interface AuthenticationListener { - /** - * Invoked on challenge finish without a failure. - *

- * - * @param challengeResult {@link ChallengeResult} contains the challlgen authentication state and payload. - */ - void onSuccess(@NonNull ChallengeResult challengeResult); - - /** - * Invoked on challenge failure. - *

- * - * @param e {@link ThreeDS2Exception} contains the failure metadata. - */ - void onFailure(@NonNull ThreeDS2Exception e); - } } diff --git a/checkout-ui/build.gradle b/checkout-ui/build.gradle index 135a1da179..6e6cf129de 100644 --- a/checkout-ui/build.gradle +++ b/checkout-ui/build.gradle @@ -37,10 +37,10 @@ dependencies { implementation "com.android.support:design:${rootProject.supportLibVersion}" implementation "android.arch.lifecycle:extensions:${rootProject.lifecycleVersion}" - implementation project(":checkout-base") - implementation project(":checkout-core") - implementation project(":checkout-core-card") - implementation project(":checkout-util") + api project(":checkout-base") + api project(":checkout-core") + api project(":checkout-core-card") + api project(":checkout-util") // Plugins compileOnly project(":checkout-threeds") diff --git a/checkout-ui/src/main/java/com/adyen/checkout/ui/internal/card/CardDetailsActivity.java b/checkout-ui/src/main/java/com/adyen/checkout/ui/internal/card/CardDetailsActivity.java index e9164c6407..06d103ab99 100644 --- a/checkout-ui/src/main/java/com/adyen/checkout/ui/internal/card/CardDetailsActivity.java +++ b/checkout-ui/src/main/java/com/adyen/checkout/ui/internal/card/CardDetailsActivity.java @@ -100,7 +100,7 @@ import java.util.concurrent.Callable; public class CardDetailsActivity extends CheckoutDetailsActivity - implements View.OnClickListener, NfcCardReaderTutorialFragment.Listener, DialogInterface.OnDismissListener { + implements View.OnClickListener, NfcCardReaderTutorialFragment.Listener, DialogInterface.OnDismissListener, AuthenticationHandler { private static final String EXTRA_TARGET_PAYMENT_METHOD = "EXTRA_TARGET_PAYMENT_METHOD"; private static final int CARD_NUMBER_BLOCK_LENGTH = 4; @@ -218,24 +218,6 @@ public boolean isValid() { mNfcCardReader = null; } - try { - mCard3DS2Authenticator = new Card3DS2Authenticator(this, new Card3DS2Authenticator.AuthenticationListener() { - @Override - public void onSuccess(@NonNull ChallengeResult challengeResult) { - mCard3DS2Authenticator.release(); - ChallengeDetails challengeDetails = new ChallengeDetails(challengeResult.getPayload()); - getPaymentHandler().submitAuthenticationDetails(challengeDetails); - } - - @Override - public void onFailure(@NonNull ThreeDS2Exception e) { - mCard3DS2Authenticator.release(); - } - }); - } catch (NoClassDefFoundError e) { - mCard3DS2Authenticator = null; - } - mConnectivityDelegate = new ConnectivityDelegate(this, new Observer() { @Override public void onChanged(@Nullable NetworkInfo networkInfo) { @@ -268,40 +250,11 @@ public void onChanged(@NonNull PaymentSession paymentSession) { } }); - if (mCard3DS2Authenticator != null) { - paymentHandler.setAuthenticationHandler(this, new AuthenticationHandler() { - @Override - public void onAuthenticationDetailsRequired(@NonNull AuthenticationDetails authenticationDetails) { - try { - switch (authenticationDetails.getResultCode()) { - case IDENTIFY_SHOPPER: { - FingerprintAuthentication authentication = authenticationDetails.getAuthentication(FingerprintAuthentication.class); - String encodedFingerprintToken = authentication.getFingerprintToken(); - String encodedFingerprint = mCard3DS2Authenticator.createFingerprint(encodedFingerprintToken); - FingerprintDetails fingerprintDetails = new FingerprintDetails(encodedFingerprint); - getPaymentHandler().submitAuthenticationDetails(fingerprintDetails); - break; - } - case CHALLENGE_SHOPPER: { - ChallengeAuthentication authentication = authenticationDetails.getAuthentication(ChallengeAuthentication.class); - String encodedChallengeToken = authentication.getChallengeToken(); - mCard3DS2Authenticator.presentChallenge(encodedChallengeToken); - break; - } - default: - ErrorDialogFragment - .newInstance(CardDetailsActivity.this, - new IllegalStateException("Unsupported result code: " + authenticationDetails.getResultCode())) - .showIfNotShown(getSupportFragmentManager()); - break; - } - } catch (CheckoutException | ThreeDS2Exception e) { - ErrorDialogFragment - .newInstance(CardDetailsActivity.this, e) - .showIfNotShown(getSupportFragmentManager()); - } - } - }); + try { + mCard3DS2Authenticator = new Card3DS2Authenticator(this); + paymentHandler.setAuthenticationHandler(this, this); + } catch (NoClassDefFoundError e) { + mCard3DS2Authenticator = null; } paymentHandler.setAdditionalDetailsHandler(this, new AdditionalDetailsHandler() { @@ -388,6 +341,69 @@ public boolean isNfcEnabledOnDevice() { return mNfcCardReader != null && mNfcCardReader.isNfcEnabledOnDevice(); } + @Override + public void onAuthenticationDetailsRequired(@NonNull AuthenticationDetails authenticationDetails) { + if (mCard3DS2Authenticator.isReleased()) { + mCard3DS2Authenticator = new Card3DS2Authenticator(this); + } + + try { + switch (authenticationDetails.getResultCode()) { + case IDENTIFY_SHOPPER: { + FingerprintAuthentication authentication = authenticationDetails.getAuthentication(FingerprintAuthentication.class); + String encodedFingerprintToken = authentication.getFingerprintToken(); + mCard3DS2Authenticator.createFingerprint(encodedFingerprintToken, new Card3DS2Authenticator.FingerprintListener() { + @Override + public void onSuccess(@NonNull String fingerprint) { + FingerprintDetails fingerprintDetails = new FingerprintDetails(fingerprint); + getPaymentHandler().submitAuthenticationDetails(fingerprintDetails); + } + + @Override + public void onFailure(@NonNull ThreeDS2Exception e) { + mCard3DS2Authenticator.release(); + ErrorDialogFragment + .newInstance(CardDetailsActivity.this, e) + .showIfNotShown(getSupportFragmentManager()); + } + }); + break; + } + case CHALLENGE_SHOPPER: { + ChallengeAuthentication authentication = authenticationDetails.getAuthentication(ChallengeAuthentication.class); + String encodedChallengeToken = authentication.getChallengeToken(); + mCard3DS2Authenticator.presentChallenge(encodedChallengeToken, new Card3DS2Authenticator.SimpleChallengeListener() { + @Override + public void onSuccess(@NonNull ChallengeResult challengeResult) { + mCard3DS2Authenticator.release(); + ChallengeDetails challengeDetails = new ChallengeDetails(challengeResult.getPayload()); + getPaymentHandler().submitAuthenticationDetails(challengeDetails); + } + + @Override + public void onFailure(@NonNull ThreeDS2Exception e) { + mCard3DS2Authenticator.release(); + ErrorDialogFragment + .newInstance(CardDetailsActivity.this, e) + .showIfNotShown(getSupportFragmentManager()); + } + }); + break; + } + default: + ErrorDialogFragment + .newInstance(CardDetailsActivity.this, + new IllegalStateException("Unsupported result code: " + authenticationDetails.getResultCode())) + .showIfNotShown(getSupportFragmentManager()); + break; + } + } catch (CheckoutException | ThreeDS2Exception e) { + ErrorDialogFragment + .newInstance(CardDetailsActivity.this, e) + .showIfNotShown(getSupportFragmentManager()); + } + } + private boolean setupHolderNameViews(boolean initialLaunch) { CustomTextInputLayout holderNameLayout = findViewById(R.id.customTextInputLayout_holderName); mHolderNameEditText.addTextChangedListener(new SimpleTextWatcher() { diff --git a/checkout-util/build.gradle b/checkout-util/build.gradle index f7c8b72625..6736ddb9dc 100644 --- a/checkout-util/build.gradle +++ b/checkout-util/build.gradle @@ -36,6 +36,6 @@ dependencies { implementation "com.android.support:support-annotations:${rootProject.supportLibVersion}" - implementation project(":checkout-base") - implementation project(":checkout-core") + api project(":checkout-base") + api project(":checkout-core") } diff --git a/checkout-util/src/main/java/com/adyen/checkout/util/internal/TextFormat.java b/checkout-util/src/main/java/com/adyen/checkout/util/internal/TextFormat.java index 03de0f981e..b59d2a0819 100644 --- a/checkout-util/src/main/java/com/adyen/checkout/util/internal/TextFormat.java +++ b/checkout-util/src/main/java/com/adyen/checkout/util/internal/TextFormat.java @@ -29,6 +29,9 @@ public static CharSequence format(@NonNull Context context, @StringRes int resId for (Object formatArg : formatArgs) { String stringArg = formatArg.toString(); int indexStart = string.indexOf(stringArg); + if (indexStart == -1) { + continue; + } // TODO: 05/11/2018 Improve replacing. int indexEnd = indexStart + (Character.isDigit(builder.charAt(indexStart + 1)) ? INDEXED_STRING_ARG_LENGTH : STRING_ARG_LENGTH); builder.replace(indexStart, indexEnd, formatArg instanceof CharSequence ? ((CharSequence) formatArg) : formatArg.toString()); diff --git a/checkout-wechatpay/build.gradle b/checkout-wechatpay/build.gradle index d4c1ef3ed3..5b388bdd49 100644 --- a/checkout-wechatpay/build.gradle +++ b/checkout-wechatpay/build.gradle @@ -30,7 +30,7 @@ dependencies { implementation "com.android.support:support-annotations:${rootProject.supportLibVersion}" implementation "com.tencent.mm.opensdk:wechat-sdk-android-without-mta:${rootProject.weChatPayVersion}" - implementation project(":checkout-base") - implementation project(":checkout-core") - implementation project(":checkout-util") + api project(":checkout-base") + api project(":checkout-core") + api project(":checkout-util") } diff --git a/example-app/build.gradle b/example-app/build.gradle index fed4388010..fb539e0024 100644 --- a/example-app/build.gradle +++ b/example-app/build.gradle @@ -48,9 +48,6 @@ dependencies { implementation "com.squareup.moshi:moshi:1.6.0" - implementation project(":checkout-base") - implementation project(":checkout-core") - implementation project(":checkout-util") implementation project(":checkout-ui") // Plugins diff --git a/example-app/src/androidTest/java/com/adyen/example/androidtest/auto/AutoPaymentAppTest.java b/example-app/src/androidTest/java/com/adyen/example/androidtest/auto/AutoPaymentAppTest.java index b3dc7949c1..7926521eef 100644 --- a/example-app/src/androidTest/java/com/adyen/example/androidtest/auto/AutoPaymentAppTest.java +++ b/example-app/src/androidTest/java/com/adyen/example/androidtest/auto/AutoPaymentAppTest.java @@ -33,7 +33,6 @@ import static android.support.test.espresso.action.ViewActions.typeText; import static android.support.test.espresso.matcher.ViewMatchers.isClickable; import static android.support.test.espresso.matcher.ViewMatchers.withId; -import static android.support.test.espresso.web.sugar.Web.onWebView; import static com.adyen.example.androidtest.PaymentAppTestUtils.changePaymentSetup; import static com.adyen.example.androidtest.PaymentAppTestUtils.confirmPaymentAndWaitForResult; import static com.adyen.example.androidtest.PaymentAppTestUtils.goToPaymentMethodsOverview; @@ -42,8 +41,10 @@ @RunWith(AndroidJUnit4.class) public class AutoPaymentAppTest { - - private String creditCardNumberThreeds = "4111111111111111"; + private static final String CARD_NUMBER = "5555444433331111"; + private static final String CARD_NUMBER_3DS2 = "4111111111111111"; + private static final String CARD_EXPIRY_DATE = "1020"; + private static final String CARD_CVC = "737"; @Rule public ActivityTestRule mMainActivityTestRule = new ActivityTestRule<>(MainActivity.class); @@ -55,34 +56,47 @@ public void tearDown() throws Exception { @Test public void testCardPayment() throws Exception { - mMainActivityTestRule.launchActivity(null); - this.performCreditCardPayment("5555444433331111"); + performCreditCardPayment(CARD_NUMBER); confirmPaymentAndWaitForResult(); } @Test - public void testCardPaymentThreedsText() throws Exception { - this.performCreditCardPayment(this.creditCardNumberThreeds); - Thread.sleep(500); + public void testCardPayment3DS2Text() throws Exception { + changeAmount(12100L); + performCreditCardPayment(CARD_NUMBER_3DS2); + Thread.sleep(1000); onView(withId(com.adyen.threeds2.R.id.editText_text)).perform(clearText(), typeText("1234")); onView(withId(com.adyen.threeds2.R.id.button_continue)).perform(click()); confirmPaymentAndWaitForResult(); } @Test - public void testCardPaymentThreedsSingleSelect() throws Exception { - changeAmount(12120L); - this.performCreditCardPayment(this.creditCardNumberThreeds); - Thread.sleep(500); + public void testCardPayment3DS2SingleSelect1() throws Exception { + changeAmount(12110L); + performCreditCardPayment(CARD_NUMBER_3DS2); + Thread.sleep(1000); + onData(anything()).inAdapterView(withId(com.adyen.threeds2.R.id.listView_selectInfoItems)) + .atPosition(0).perform(click()); onView(withId(com.adyen.threeds2.R.id.button_next)).perform(click()); confirmPaymentAndWaitForResult(); } @Test - public void testCardPaymentThreedsMultiSelect() throws Exception { + public void testCardPayment3DS2SingleSelect2() throws Exception { + changeAmount(12110L); + performCreditCardPayment(CARD_NUMBER_3DS2); + Thread.sleep(1000); + onData(anything()).inAdapterView(withId(com.adyen.threeds2.R.id.listView_selectInfoItems)) + .atPosition(1).perform(click()); + onView(withId(com.adyen.threeds2.R.id.button_next)).perform(click()); + confirmPaymentAndWaitForResult(); + } + + @Test + public void testCardPayment3DS2MultiSelect1() throws Exception { changeAmount(12120L); - this.performCreditCardPayment(this.creditCardNumberThreeds); - Thread.sleep(500); + performCreditCardPayment(CARD_NUMBER_3DS2); + Thread.sleep(1000); onData(anything()).inAdapterView(withId(com.adyen.threeds2.R.id.listView_selectInfoItems)) .atPosition(0).perform(click()); onView(withId(com.adyen.threeds2.R.id.button_next)).perform(click()); @@ -90,32 +104,22 @@ public void testCardPaymentThreedsMultiSelect() throws Exception { } @Test - public void testCardPaymentThreedsOutOfBand() throws Exception { - changeAmount(12130L); - this.performCreditCardPayment(this.creditCardNumberThreeds); - Thread.sleep(500); - onView(withId(com.adyen.threeds2.R.id.button_continue)).perform(click()); + public void testCardPayment3DS2MultiSelect2() throws Exception { + changeAmount(12120L); + performCreditCardPayment(CARD_NUMBER_3DS2); + Thread.sleep(2000); + onData(anything()).inAdapterView(withId(com.adyen.threeds2.R.id.listView_selectInfoItems)) + .atPosition(0).perform(click()); + onView(withId(com.adyen.threeds2.R.id.button_next)).perform(click()); confirmPaymentAndWaitForResult(); } - @Test - public void testCardPaymentWith3dth() throws Exception { - - mMainActivityTestRule.launchActivity(null); - - goToPaymentMethodsOverview(); - - selectPaymentMethodByName("Credit Card"); - - onView(withId(com.adyen.checkout.ui.R.id.editText_cardNumber)).perform(clearText(), typeText("5555444433331111")); - onView(withId(com.adyen.checkout.ui.R.id.editText_expiryDate)).perform(clearText(), typeText("1020")); - onView(withId(com.adyen.checkout.ui.R.id.editText_securityCode)).perform(clearText(), typeText("737")); - KeyboardUtil.hide(EspressoTestUtils.waitForActivity(CardDetailsActivity.class).findViewById(R.id.button_pay)); - Thread.sleep(500); - onView(withId(com.adyen.checkout.ui.R.id.button_pay)).check(ViewAssertions.matches(isClickable())); - onView(withId(com.adyen.checkout.ui.R.id.button_pay)).perform(click()); - + public void testCardPayment3DS2OutOfBand() throws Exception { + changeAmount(12130L); + performCreditCardPayment(CARD_NUMBER_3DS2); + Thread.sleep(1000); + onView(withId(com.adyen.threeds2.R.id.button_continue)).perform(click()); confirmPaymentAndWaitForResult(); } @@ -142,8 +146,8 @@ private void performCreditCardPayment(String creditCardNumber) throws Exception selectPaymentMethodByName("Credit Card"); onView(withId(com.adyen.checkout.ui.R.id.editText_cardNumber)).perform(clearText(), typeText(creditCardNumber)); - onView(withId(com.adyen.checkout.ui.R.id.editText_expiryDate)).perform(clearText(), typeText("1020")); - onView(withId(com.adyen.checkout.ui.R.id.editText_securityCode)).perform(clearText(), typeText("737")); + onView(withId(com.adyen.checkout.ui.R.id.editText_expiryDate)).perform(clearText(), typeText(CARD_EXPIRY_DATE)); + onView(withId(com.adyen.checkout.ui.R.id.editText_securityCode)).perform(clearText(), typeText(CARD_CVC)); KeyboardUtil.hide(EspressoTestUtils.waitForActivity(CardDetailsActivity.class).findViewById(R.id.button_pay)); Thread.sleep(500); onView(withId(com.adyen.checkout.ui.R.id.button_pay)).check(ViewAssertions.matches(isClickable())); diff --git a/release.gradle b/release.gradle index bf96813eee..607afed89d 100644 --- a/release.gradle +++ b/release.gradle @@ -1,3 +1,5 @@ +import com.sun.tools.classfile.Dependency + apply plugin: 'maven-publish' apply plugin: 'com.jfrog.bintray' // TODO: add support for signing of artifacts