diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..31f01ae
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,9 @@
+*.iml
+.gradle
+/local.properties
+/.idea/workspace.xml
+/.idea/libraries
+.DS_Store
+/build
+/captures
+.idea
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..e69de29
diff --git a/app/.gitignore b/app/.gitignore
new file mode 100644
index 0000000..796b96d
--- /dev/null
+++ b/app/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/app/build.gradle b/app/build.gradle
new file mode 100644
index 0000000..62c02c6
--- /dev/null
+++ b/app/build.gradle
@@ -0,0 +1,34 @@
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion rootProject.ext.compileSdkVersion
+ buildToolsVersion rootProject.ext.buildToolsVersion
+
+ defaultConfig {
+ applicationId "com.adyen.checkout"
+ minSdkVersion rootProject.ext.minSdkVersion
+ targetSdkVersion rootProject.ext.targetSdkVersion
+ versionCode rootProject.ext.versionCode
+ versionName rootProject.ext.versionName
+ testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+ }
+ buildTypes {
+ release {
+ debuggable rootProject.ext.release_debuggable
+ minifyEnabled rootProject.ext.release_minifyEnabled
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+ }
+ debug {
+ debuggable rootProject.ext.debug_debuggable
+ testCoverageEnabled = rootProject.ext.debug_testCoverageEnabled
+ }
+ }
+
+ testBuildType rootProject.ext.testBuildType
+}
+
+dependencies {
+ compile fileTree(include: ['*.jar'], dir: 'libs')
+
+ compile 'com.adyen.checkout:ui:1.0.5'
+}
\ No newline at end of file
diff --git a/app/libs/adyencse-1.0.0.aar b/app/libs/adyencse-1.0.0.aar
new file mode 100644
index 0000000..95d470b
Binary files /dev/null and b/app/libs/adyencse-1.0.0.aar differ
diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro
new file mode 100644
index 0000000..07d9606
--- /dev/null
+++ b/app/proguard-rules.pro
@@ -0,0 +1,17 @@
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in /Users/andrei/Library/Android/sdk/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the proguardFiles
+# directive in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
diff --git a/app/src/androidTest/AndroidManifest.xml b/app/src/androidTest/AndroidManifest.xml
new file mode 100644
index 0000000..03541db
--- /dev/null
+++ b/app/src/androidTest/AndroidManifest.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/androidTest/java/com/adyen/checkout/CardNumberService.java b/app/src/androidTest/java/com/adyen/checkout/CardNumberService.java
new file mode 100644
index 0000000..aff6385
--- /dev/null
+++ b/app/src/androidTest/java/com/adyen/checkout/CardNumberService.java
@@ -0,0 +1,129 @@
+package com.adyen.checkout;
+
+import java.util.ArrayList;
+import java.util.Collection;
+
+/**
+ * This class provides a list of creditcard numbers that can be used on Test.
+ * See: https://gist.github.com/j3j5/8b3e48ccad746b90a54a
+ */
+
+public final class CardNumberService {
+
+ private CardNumberService() {
+ // Private Constructor
+ }
+
+ public static Collection getMasterCardNumbers() {
+ ArrayList list = new ArrayList<>();
+ list.add("5100 0811 1222 3332");
+ list.add("5100 2900 2900 2909");
+ list.add("5577 0000 5577 0004");
+ list.add("5136 3333 3333 3335");
+ list.add("5585 5585 5585 5583");
+ list.add("5555 4444 3333 1111");
+ list.add("5555 5555 5555 4444");
+ list.add("5500 0000 0000 0004");
+ list.add("5424 0000 0000 0015");
+ return list;
+ }
+
+ public static Collection getVISANumbers() {
+ ArrayList list = new ArrayList<>();
+ list.add("4111 1111 1111 1111");
+ list.add("4988 4388 4388 4305");
+ list.add("4166 6766 6766 6746");
+ list.add("4646 4646 4646 4644");
+ list.add("4444 3333 2222 1111");
+ list.add("4400 0000 0000 0008");
+ list.add("4977 9494 9494 9497");
+ return list;
+ }
+
+ public static Collection getJCBNumbers() {
+ ArrayList list = new ArrayList<>();
+ list.add("3569 9900 1009 5841");
+ return list;
+ }
+
+ public static Collection getCarteBancaireNumbers() {
+ ArrayList list = new ArrayList<>();
+ list.add("4035 5010 0000 0008");
+ list.add("4360 0000 0100 0005");
+ return list;
+ }
+
+ public static Collection getAmexNumbers() {
+ ArrayList list = new ArrayList<>();
+ list.add("3700 0000 0000 002");
+ return list;
+ }
+
+ public static Collection getDinersNumbers() {
+ ArrayList list = new ArrayList<>();
+ list.add("3600 6666 3333 44");
+ return list;
+ }
+
+ public static Collection gerDiscoverNumbers() {
+ ArrayList list = new ArrayList<>();
+ list.add("6011 6011 6011 6611");
+ list.add("6445 6445 6445 6445");
+ return list;
+ }
+
+ public static Collection getMaestroNumbers() {
+ ArrayList list = new ArrayList<>();
+ list.add("6731 0123 4567 8906");
+ list.add("6759 6498 2643 8453");
+ return list;
+ }
+
+ public static Collection getMisterCashNumbers() {
+ ArrayList list = new ArrayList<>();
+ list.add("6703 4444 4444 4449");
+ return list;
+ }
+
+ public static Collection getHiperCardNumbers() {
+ ArrayList list = new ArrayList<>();
+ list.add("6062 8288 8866 6688");
+ return list;
+ }
+
+ public static Collection getBrazilianCompanyNumbers() {
+ ArrayList list = new ArrayList<>();
+ list.add("5066 9911 1111 1118");
+ return list;
+ }
+
+ public static Collection getDankortNumbers() {
+ ArrayList list = new ArrayList<>();
+ list.add("5019 5555 4444 5555");
+ return list;
+ }
+
+ public static Collection getUnionPayCreditCardNumbers() {
+ ArrayList list = new ArrayList<>();
+ list.add("6221 5588 1234 0000");
+ return list;
+ }
+
+ public static Collection get3DSecureNumbers() {
+ ArrayList list = new ArrayList<>();
+ list.add("5212 3456 7890 1234");
+ list.add("4212 3456 7890 1237");
+ list.add("3451 7792 5488 348");
+ list.add("3569 9900 1009 5833");
+ return list;
+ }
+
+ public static Collection removeWhiteSpaces(Collection collection) {
+ ArrayList result = new ArrayList<>();
+ for (String str : collection) {
+ result.add(str.replaceAll(" ", ""));
+ }
+ return result;
+ }
+}
+
diff --git a/app/src/androidTest/java/com/adyen/checkout/ExampleInstrumentedTest.java b/app/src/androidTest/java/com/adyen/checkout/ExampleInstrumentedTest.java
new file mode 100644
index 0000000..6c15ecf
--- /dev/null
+++ b/app/src/androidTest/java/com/adyen/checkout/ExampleInstrumentedTest.java
@@ -0,0 +1,26 @@
+package com.adyen.checkout;
+
+import android.content.Context;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Instrumentation test, which will execute on an Android device.
+ *
+ * @see Testing documentation
+ */
+@RunWith(AndroidJUnit4.class)
+public class ExampleInstrumentedTest {
+ @Test
+ public void useAppContext() throws Exception {
+ // Context of the app under test.
+ Context appContext = InstrumentationRegistry.getTargetContext();
+
+ assertEquals("com.adyen.checkout", appContext.getPackageName());
+ }
+}
diff --git a/app/src/androidTest/java/com/adyen/checkout/IbanNumberService.java b/app/src/androidTest/java/com/adyen/checkout/IbanNumberService.java
new file mode 100644
index 0000000..6cd310b
--- /dev/null
+++ b/app/src/androidTest/java/com/adyen/checkout/IbanNumberService.java
@@ -0,0 +1,49 @@
+package com.adyen.checkout;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+public final class IbanNumberService {
+
+ public static Collection getIbanNumbers() {
+ List list = new ArrayList<>();
+ list.add("NL13TEST0123456789");
+ list.add("NL36TEST0236169114");
+ list.add("NL26TEST0336169116");
+ list.add("NL16TEST0436169118");
+ list.add("NL81TEST0536169128");
+ list.add("NL27TEST0636169146");
+ list.add("NL39TEST0736169237");
+ list.add("NL82TEST0836169255");
+ list.add("NL72TEST0936169257");
+ list.add("NL46TEST0136169112");
+ list.add("NL70TEST0736160337");
+ list.add("NL18TEST0736162437");
+ list.add("NL92TEST0736163433");
+ list.add("DE87123456781234567890");
+ list.add("DE92123456789876543210");
+ list.add("DE14123456780023456789");
+ list.add("DE36444488881234567890");
+ list.add("DE41444488889876543210");
+ list.add("DE60444488880023456789");
+ list.add("DE89888888881234567890");
+ list.add("DE94888888889876543210");
+ list.add("DE16888888880023456789");
+ list.add("IT60X0542811101000000123456");
+ list.add("FR1420041010050500013M02606");
+ list.add("ES9121000418450200051332");
+ list.add("AT151234512345678901");
+ list.add("CH4912345123456789012");
+ list.add("DK8612341234567890");
+ list.add("GB85TEST12345612345678");
+ list.add("NO6012341234561");
+ list.add("PL20123123411234567890123456");
+ list.add("SE9412312345678901234561");
+ return list;
+ }
+
+ private IbanNumberService() {
+ // Private constructor
+ }
+}
diff --git a/app/src/androidTest/java/com/adyen/checkout/PaymentAppTest.java b/app/src/androidTest/java/com/adyen/checkout/PaymentAppTest.java
new file mode 100644
index 0000000..57c067f
--- /dev/null
+++ b/app/src/androidTest/java/com/adyen/checkout/PaymentAppTest.java
@@ -0,0 +1,438 @@
+package com.adyen.checkout;
+
+import android.support.test.espresso.Espresso;
+import android.support.test.espresso.NoActivityResumedException;
+import android.support.test.filters.LargeTest;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.test.uiautomator.UiDevice;
+
+import com.adyen.core.models.Payment;
+import com.adyen.testutils.EspressoTestUtils;
+import com.adyen.testutils.RetryTest;
+
+import org.junit.After;
+import org.junit.Ignore;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Collection;
+import java.util.Random;
+
+import static android.support.test.InstrumentationRegistry.getInstrumentation;
+import static android.support.test.espresso.Espresso.onView;
+import static android.support.test.espresso.action.ViewActions.clearText;
+import static android.support.test.espresso.action.ViewActions.click;
+import static android.support.test.espresso.action.ViewActions.closeSoftKeyboard;
+import static android.support.test.espresso.action.ViewActions.scrollTo;
+import static android.support.test.espresso.action.ViewActions.typeText;
+import static android.support.test.espresso.assertion.ViewAssertions.matches;
+import static android.support.test.espresso.matcher.ViewMatchers.isEnabled;
+import static android.support.test.espresso.matcher.ViewMatchers.withId;
+import static android.support.test.espresso.matcher.ViewMatchers.withText;
+import static com.adyen.testutils.EspressoTestUtils.closeAllActivities;
+import static com.adyen.testutils.EspressoTestUtils.waitForText;
+import static com.adyen.testutils.EspressoTestUtils.waitForView;
+import static org.hamcrest.Matchers.not;
+import static org.hamcrest.text.IsEqualIgnoringCase.equalToIgnoringCase;
+
+/**
+ * Functional tests for checking the SDK integration.
+ * The main goal is to test that SDK works fine in a merchant application.
+ */
+
+@RunWith(AndroidJUnit4.class)
+@LargeTest
+public class PaymentAppTest {
+
+ private static final String CARD_NUMBER = "4444333322221111";
+ private static final String CARD_EXP_DATE = "0818";
+ private static final String CARD_CVC_CODE = "737";
+ private static final String AMOUNT = "50";
+ private static final String EUR = "EUR";
+
+ private static final String IBAN = "NL13TEST0123456789";
+ private static final String ACCOUNT_OWNER_NAME = "A. Klassen";
+
+ @Rule
+ public ActivityTestRule mainActivityRule = new ActivityTestRule<>(MainActivity.class);
+
+ @Rule
+ public RetryTest retry = new RetryTest(5);
+
+ @After
+ public void tearDown() throws Exception {
+ closeAllActivities(getInstrumentation());
+ }
+
+ @Test
+ public void testRefusedVisaPayment() throws Exception {
+ checkCardPayment("13", "EUR", "Credit Card", "4444333322221111", "12/18", "722", Payment.PaymentStatus.REFUSED.toString());
+ }
+
+ @Test
+ public void testMonkeyTyping() throws Exception {
+ goToCreditCardFragment();
+
+ checkCreditCardPayButtonIsEnabled(false);
+
+ onView(withId(R.id.adyen_credit_card_no)).perform(clearText(), typeText(getRandomString(10)),
+ closeSoftKeyboard());
+ checkCreditCardPayButtonIsEnabled(false);
+
+ onView(withId(R.id.adyen_credit_card_exp_date)).perform(typeText(getRandomString(10)),
+ closeSoftKeyboard());
+ checkCreditCardPayButtonIsEnabled(false);
+
+ onView(withId(R.id.adyen_credit_card_cvc)).perform(typeText(getRandomString(10)),
+ closeSoftKeyboard());
+ checkCreditCardPayButtonIsEnabled(false);
+ cancelCreditCardPayment();
+ }
+
+ @Test
+ public void testCCNrInputField() throws Exception {
+ goToCreditCardFragment();
+
+ checkCreditCardPayButtonIsEnabled(false);
+
+ onView(withId(R.id.adyen_credit_card_exp_date)).perform(clearText(), typeText(CARD_EXP_DATE),
+ closeSoftKeyboard());
+ checkCreditCardPayButtonIsEnabled(false);
+ onView(withId(R.id.adyen_credit_card_cvc)).perform(typeText(CARD_CVC_CODE),
+ closeSoftKeyboard());
+ checkCreditCardPayButtonIsEnabled(false);
+
+ for (int i = 0; i < 7; i++) {
+ checkInputFieldEnablesButton(R.id.adyen_credit_card_no, R.id.collectCreditCardData, getRandomString(16), false);
+ }
+
+ checkInputFieldEnablesButton(R.id.adyen_credit_card_no, R.id.collectCreditCardData, CardNumberService.getMasterCardNumbers(), true);
+ checkInputFieldEnablesButton(R.id.adyen_credit_card_no, R.id.collectCreditCardData, CardNumberService.getVISANumbers(), true);
+ checkInputFieldEnablesButton(R.id.adyen_credit_card_no, R.id.collectCreditCardData, CardNumberService.getAmexNumbers(), true);
+ checkInputFieldEnablesButton(R.id.adyen_credit_card_no, R.id.collectCreditCardData, CardNumberService.getMaestroNumbers(), true);
+
+ checkInputFieldEnablesButton(R.id.adyen_credit_card_no, R.id.collectCreditCardData, CARD_NUMBER, true);
+ cancelCreditCardPayment();
+ }
+
+ @Test
+ public void testCardExpDate() throws Exception {
+ goToCreditCardFragment();
+
+ checkCreditCardPayButtonIsEnabled(false);
+
+ onView(withId(R.id.adyen_credit_card_no)).perform(clearText(), typeText(CARD_NUMBER),
+ closeSoftKeyboard());
+ checkCreditCardPayButtonIsEnabled(false);
+ onView(withId(R.id.adyen_credit_card_cvc)).perform(typeText(CARD_CVC_CODE),
+ closeSoftKeyboard());
+ checkCreditCardPayButtonIsEnabled(false);
+
+ checkInputFieldEnablesButton(R.id.adyen_credit_card_exp_date, R.id.collectCreditCardData, "02/18", true);
+ checkInputFieldEnablesButton(R.id.adyen_credit_card_exp_date, R.id.collectCreditCardData, "02/12", false);
+ checkInputFieldEnablesButton(R.id.adyen_credit_card_exp_date, R.id.collectCreditCardData, "222", true);
+ checkInputFieldEnablesButton(R.id.adyen_credit_card_exp_date, R.id.collectCreditCardData, "2/22", true);
+ checkInputFieldEnablesButton(R.id.adyen_credit_card_exp_date, R.id.collectCreditCardData, "02/22", true);
+
+ checkInputFieldEnablesButton(R.id.adyen_credit_card_exp_date, R.id.collectCreditCardData, CARD_EXP_DATE, true);
+ cancelCreditCardPayment();
+ }
+
+ @Test
+ public void testCVCField() throws Exception {
+ goToCreditCardFragment();
+
+ checkCreditCardPayButtonIsEnabled(false);
+
+ onView(withId(R.id.adyen_credit_card_no)).perform(clearText(), typeText(CARD_NUMBER),
+ closeSoftKeyboard());
+ checkCreditCardPayButtonIsEnabled(false);
+ onView(withId(R.id.adyen_credit_card_exp_date)).perform(clearText(), typeText(CARD_EXP_DATE),
+ closeSoftKeyboard());
+ checkCreditCardPayButtonIsEnabled(false);
+
+ checkInputFieldEnablesButton(R.id.adyen_credit_card_cvc, R.id.collectCreditCardData, "1", false);
+ checkInputFieldEnablesButton(R.id.adyen_credit_card_cvc, R.id.collectCreditCardData, "11", false);
+ checkInputFieldEnablesButton(R.id.adyen_credit_card_cvc, R.id.collectCreditCardData, "XXX", false);
+ checkInputFieldEnablesButton(R.id.adyen_credit_card_cvc, R.id.collectCreditCardData, getRandomString(3), false);
+
+ checkInputFieldEnablesButton(R.id.adyen_credit_card_cvc, R.id.collectCreditCardData, CARD_EXP_DATE, true);
+ cancelCreditCardPayment();
+ }
+
+ /**
+ * Test if the client side iban number validation works correctly.
+ * @throws Exception
+ */
+ @Test
+ public void testSepaUI() throws Exception {
+ goToSepaFragment();
+ checkButtonIsEnabled(R.id.collect_direct_debit_data, false);
+
+ onView(withId(R.id.adyen_bank_account_holder_name)).perform(typeText("TEST OWNER"),
+ closeSoftKeyboard());
+ onView(withId(R.id.consent_direct_debit_checkbox)).perform(click());
+
+ checkInputFieldEnablesButton(R.id.adyen_sepa_iban_edit_text, R.id.collect_direct_debit_data, IbanNumberService.getIbanNumbers(), true);
+ }
+
+ /**
+ * Makes sure tHat visa Payment succeeds for EURO payment.
+ */
+ @Test
+ public void testVISAPayment() throws Exception {
+ payWithVISACard("17", "EUR");
+ }
+
+ /**
+ * Makes sure tHat visa Payment succeeds for JPY payment.
+ */
+ @Test
+ public void testVISAPaymentJPY() throws Exception {
+ payWithVISACard("23000", "JPY");
+ }
+
+ /**
+ * Makes sure tHat visa Payment succeeds when the user enters the expiry date as "818" instead of "0818".
+ * Expiry date formatter should automatically add 0 at the beginning.
+ */
+ @Test
+ public void testSuccessfulPaymentWithExpiryDateFormatter() throws Exception {
+ payWithCard("23000", "IDR", "Credit Card", CARD_NUMBER, "818", CARD_CVC_CODE);
+ }
+
+ /**
+ * Makes sure tHat visa Payment fails if expiry date is wrong.
+ */
+ @Test
+ public void testPaymentFailure() throws Exception {
+ checkCardPayment("23000", "IDR", "Credit Card", CARD_NUMBER, "819", CARD_CVC_CODE, Payment.PaymentStatus.REFUSED.toString());
+ }
+
+ @Test
+ public void testOrientationChangeOnCreditCardScreen() throws Exception {
+ goToCreditCardFragment();
+ EspressoTestUtils.rotateScreen();
+ onView(withId(R.id.adyen_credit_card_no)).perform(clearText(), typeText(CARD_NUMBER),
+ closeSoftKeyboard());
+ EspressoTestUtils.rotateScreen();
+ onView(withId(R.id.adyen_credit_card_exp_date)).perform(typeText(CARD_EXP_DATE),
+ closeSoftKeyboard());
+ EspressoTestUtils.rotateScreen();
+ onView(withId(R.id.adyen_credit_card_cvc)).perform(typeText(CARD_CVC_CODE),
+ closeSoftKeyboard());
+ EspressoTestUtils.rotateScreen();
+ onView(withId(R.id.collectCreditCardData)).perform(click());
+ checkResultString(Payment.PaymentStatus.AUTHORISED.toString());
+ }
+
+ /**
+ * This test needs additional work. The following passes in Google Pixel device. But since
+ * it uses screen coordinates; this test cannot be relied on yet. Work in progress.
+ * @throws Exception
+ */
+ @Ignore @Test
+ public void testIdealPayment() throws Exception {
+ startIdealPayment();
+ waitForText("Test Issuer");
+ onView(withText(equalToIgnoringCase("Test Issuer"))).perform(click());
+ final UiDevice device = UiDevice.getInstance(getInstrumentation());
+ Thread.sleep(5000);
+ device.click(65, 535);
+
+ checkResultString(Payment.PaymentStatus.AUTHORISED.toString());
+ }
+
+ @Test
+ public void testSepaDirectDebitPayment() throws Exception {
+ checkSepaPayment(AMOUNT, EUR, IBAN, ACCOUNT_OWNER_NAME, Payment.PaymentStatus.RECEIVED.toString());
+ }
+
+ @Test
+ public void testOrientationChangeOnSEPADirectDebitScreen() throws Exception {
+ goToPaymentListFragment(AMOUNT, EUR);
+ onView(withText(equalToIgnoringCase("Sepa Direct Debit"))).perform(scrollTo(), click());
+ EspressoTestUtils.rotateScreen();
+ onView(withId(R.id.adyen_sepa_iban_edit_text)).perform(clearText(), typeText(IBAN),
+ closeSoftKeyboard());
+ EspressoTestUtils.rotateScreen();
+ onView(withId(R.id.adyen_bank_account_holder_name)).perform(typeText(ACCOUNT_OWNER_NAME),
+ closeSoftKeyboard());
+ EspressoTestUtils.rotateScreen();
+ onView(withId(R.id.consent_direct_debit_checkbox)).perform(click());
+ EspressoTestUtils.rotateScreen();
+ onView(withId(R.id.collect_direct_debit_data)).perform(click());
+ checkResultString(Payment.PaymentStatus.RECEIVED.toString());
+ }
+
+ @Test
+ public void testOrientationChangeOnPaymentMethodList() throws Exception {
+ goToPaymentListFragment(AMOUNT, EUR);
+ EspressoTestUtils.rotateScreen();
+ onView(withText(equalToIgnoringCase("Credit Card"))).perform(scrollTo(), click());
+ waitForView(R.id.adyen_credit_card_no);
+ Espresso.pressBack();
+ Espresso.pressBack();
+ EspressoTestUtils.rotateScreen();
+ onView(withText(equalToIgnoringCase("Credit Card"))).perform(scrollTo(), click());
+ waitForView(R.id.adyen_credit_card_no);
+ Espresso.pressBack();
+ cancelCreditCardPayment();
+ }
+
+ @Test
+ public void testLinearFlow() throws Exception {
+ goToPaymentListFragment(AMOUNT, EUR);
+ onView(withText(equalToIgnoringCase("Credit Card"))).perform(scrollTo(), click());
+ Espresso.pressBack(); // closes the keyboard
+ Espresso.pressBack();
+ onView(withText(equalToIgnoringCase("iDEAL"))).perform(scrollTo(), click());
+ Espresso.pressBack();
+ onView(withText(equalToIgnoringCase("Credit Card"))).perform(scrollTo(), click());
+ // TODO: move following to a method
+ onView(withId(R.id.adyen_credit_card_no)).perform(clearText(), typeText(CARD_NUMBER),
+ closeSoftKeyboard());
+ onView(withId(R.id.adyen_credit_card_exp_date)).perform(typeText(CARD_EXP_DATE),
+ closeSoftKeyboard());
+ onView(withId(R.id.adyen_credit_card_cvc)).perform(typeText(CARD_CVC_CODE),
+ closeSoftKeyboard());
+ onView(withId(R.id.collectCreditCardData)).perform(click());
+ checkResultString(Payment.PaymentStatus.AUTHORISED.toString());
+ }
+
+ // TODO: Add a test for returning from redirect and selecting another method.
+
+ private void payWithVISACard(final String amountValue, final String amountCurrency) throws Exception {
+ payWithCard(amountValue, amountCurrency, "Credit Card", CARD_NUMBER, CARD_EXP_DATE, CARD_CVC_CODE);
+ }
+
+ private void payWithCard(final String amountValue, final String amountCurrency, final String cardType,
+ final String cardNumber, final String cardExpiryDate, final String cardCVC)
+ throws Exception {
+ checkCardPayment(amountValue, amountCurrency, cardType, cardNumber, cardExpiryDate, cardCVC, Payment.PaymentStatus.AUTHORISED.toString());
+ }
+
+ private void checkCardPayment(final String amountValue, final String amountCurrency, final String cardType,
+ final String cardNumber, final String cardExpiryDate,
+ final String cardCVC, final String expectedResult) throws Exception {
+ goToPaymentListFragment(amountValue, amountCurrency);
+
+ onView(withText(equalToIgnoringCase(cardType))).perform(click());
+ onView(withId(R.id.adyen_credit_card_no)).perform(clearText(), typeText(cardNumber),
+ closeSoftKeyboard());
+ onView(withId(R.id.adyen_credit_card_exp_date)).perform(typeText(cardExpiryDate),
+ closeSoftKeyboard());
+ onView(withId(R.id.adyen_credit_card_cvc)).perform(typeText(cardCVC),
+ closeSoftKeyboard());
+ onView(withId(R.id.collectCreditCardData)).perform(click());
+ checkResultString(expectedResult);
+ }
+
+ private void checkSepaPayment(final String amountValue, final String amountCurrency,
+ final String ibanNumber, final String ownername, final String expectedResult)
+ throws Exception {
+ goToPaymentListFragment(amountValue, amountCurrency);
+
+ onView(withText(equalToIgnoringCase("Sepa Direct Debit"))).perform(scrollTo(), click());
+ onView(withId(R.id.adyen_sepa_iban_edit_text)).perform(clearText(), typeText(ibanNumber),
+ closeSoftKeyboard());
+ onView(withId(R.id.adyen_bank_account_holder_name)).perform(typeText(ownername),
+ closeSoftKeyboard());
+ onView(withId(R.id.consent_direct_debit_checkbox)).perform(click());
+ onView(withId(R.id.collect_direct_debit_data)).perform(click());
+ checkResultString(expectedResult);
+ }
+
+ private void goToPaymentListFragment(final String amount, final String currency) throws Exception {
+ EspressoTestUtils.waitForView(R.id.orderAmountEntry);
+ onView(withId(R.id.orderAmountEntry)).perform(clearText(), typeText(amount),
+ closeSoftKeyboard());
+ onView(withId(R.id.orderCurrencyEntry)).perform(clearText(), typeText(currency),
+ closeSoftKeyboard());
+ onView(withId(R.id.proceed_button)).perform(scrollTo(), click());
+
+ EspressoTestUtils.waitForView(com.adyen.ui.R.id.activity_payment_method_selection);
+ }
+
+ private void goToCreditCardFragment() throws Exception {
+ goToPaymentListFragment(AMOUNT, EUR);
+ onView(withText(equalToIgnoringCase("Credit Card"))).perform(click());
+ }
+
+ private void goToSepaFragment() throws Exception {
+ goToPaymentListFragment(AMOUNT, EUR);
+ onView(withText(equalToIgnoringCase("Sepa Direct Debit"))).perform(scrollTo(), click());
+ }
+
+ private void startIdealPayment() throws Exception {
+ goToPaymentListFragment(AMOUNT, EUR);
+ onView(withText(equalToIgnoringCase("iDeal"))).perform(click());
+ }
+
+ private void checkInputFieldEnablesButton(int fieldId, int buttonId, String input, boolean valid) {
+ onView(withId(fieldId)).perform(typeText(input),
+ closeSoftKeyboard());
+ checkButtonIsEnabled(buttonId, valid);
+ onView(withId(fieldId)).perform(clearText());
+ }
+
+ private void checkInputFieldEnablesButton(int fieldId, int buttonId, Collection input, boolean valid) {
+ for (String str : input) {
+ onView(withId(fieldId)).perform(typeText(str),
+ closeSoftKeyboard());
+ checkButtonIsEnabled(buttonId, valid);
+ onView(withId(fieldId)).perform(clearText());
+ checkButtonIsEnabled(buttonId, false);
+ }
+
+ }
+
+ private void checkCreditCardPayButtonIsEnabled(boolean shouldBeEnabled) {
+ if (shouldBeEnabled) {
+ onView(withId(R.id.collectCreditCardData)).check(matches(isEnabled()));
+ } else {
+ onView(withId(R.id.collectCreditCardData)).check(matches(not(isEnabled())));
+ }
+ }
+
+ private void checkButtonIsEnabled(final int buttonId, boolean shouldBeEnabled) {
+ if (shouldBeEnabled) {
+ onView(withId(buttonId)).check(matches(isEnabled()));
+ } else {
+ onView(withId(buttonId)).check(matches(not(isEnabled())));
+ }
+ }
+
+ private String getRandomString(int len) {
+ char[] chars1 = "ABCDEF012GHIJKL345MNOPQR678STUVWXYZ9".toCharArray();
+ StringBuilder sb1 = new StringBuilder();
+ Random random1 = new Random();
+ for (int i = 0; i < len; i++) {
+ char c1 = chars1[random1.nextInt(chars1.length)];
+ sb1.append(c1);
+ }
+ return sb1.toString();
+ }
+
+ private void cancelCreditCardPayment() throws Exception {
+ Espresso.pressBack();
+ EspressoTestUtils.waitForView(R.id.activity_payment_method_selection);
+ Espresso.pressBack();
+ EspressoTestUtils.waitForView(R.id.orderAmountTitle);
+ try {
+ // Actually this should not be required. However without pressing back one last time; the
+ // activity cannot be started in the next test. To avoid it; we kill the PaymentResultActivity as well.
+ Espresso.pressBack();
+ } catch (final NoActivityResumedException expected) {
+ // expected
+ }
+ }
+
+ private void checkResultString(final String expectedResult) throws Exception {
+ EspressoTestUtils.waitForView(R.id.verificationTextView);
+ onView(withId(R.id.verificationTextView)).check(matches(withText(expectedResult)));
+ }
+
+}
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..9447019
--- /dev/null
+++ b/app/src/main/AndroidManifest.xml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/com/adyen/checkout/MainActivity.java b/app/src/main/java/com/adyen/checkout/MainActivity.java
new file mode 100644
index 0000000..0af598f
--- /dev/null
+++ b/app/src/main/java/com/adyen/checkout/MainActivity.java
@@ -0,0 +1,186 @@
+package com.adyen.checkout;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.v4.app.FragmentActivity;
+import android.util.Log;
+import android.widget.Toast;
+
+import com.adyen.core.PaymentRequest;
+import com.adyen.core.interfaces.HttpResponseCallback;
+import com.adyen.core.interfaces.PaymentDataCallback;
+import com.adyen.core.interfaces.PaymentRequestListener;
+import com.adyen.core.models.Payment;
+import com.adyen.core.models.PaymentRequestResult;
+import com.adyen.core.utils.AsyncHttpClient;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.nio.charset.Charset;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Main activity for demonstrating how to use Checkout SDK. Client should implement an activity
+ * similar to this.
+ *
+ * In this example application, the payment UI is completely handled by SDK. This is the simplest way
+ * to integrate.
+ */
+public class MainActivity extends FragmentActivity implements PaymentDataEntryFragment.PaymentRequestListener {
+
+ private static final String TAG = MainActivity.class.getSimpleName();
+
+ private PaymentSetupRequest paymentSetupRequest;
+
+ private static final String SETUP = "setup";
+ private static final String VERIFY = "verify";
+
+ private static final String MERCHANT_SERVER_URL = "https://checkoutshopper-test.adyen.com/checkoutshopper/demo/easy-integration/merchantserver/";
+
+ private static final String MERCHANT_API_SECRET_KEY = "ADD_YOUR_API_KEY";
+ private static final String MERCHANT_APP_ID = "ADD_YOUR_APP_ID";
+
+ private PaymentRequest paymentRequest;
+ private final PaymentRequestListener paymentRequestListener = new PaymentRequestListener() {
+
+ @Override
+ public void onPaymentDataRequested(@NonNull final PaymentRequest request, @NonNull String token,
+ @NonNull final PaymentDataCallback callback) {
+ if (paymentRequest != request) {
+ Log.d(TAG, "onPaymentResult(): This is not the payment request that we created.");
+ return;
+ }
+ Log.d(TAG, "paymentRequestListener.provideSetupData()");
+ final Map headers = new HashMap<>();
+ headers.put("Content-Type", "application/json; charset=UTF-8");
+ headers.put("X-MerchantServer-App-SecretKey", MERCHANT_API_SECRET_KEY);
+ headers.put("X-MerchantServer-App-Id", MERCHANT_APP_ID);
+ AsyncHttpClient.post(MERCHANT_SERVER_URL + SETUP, headers, getSetupDataString(token), new HttpResponseCallback() {
+ @Override
+ public void onSuccess(final byte[] response) {
+ callback.completionWithPaymentData(response);
+ }
+
+ @Override
+ public void onFailure(final Throwable e) {
+ Log.e(TAG, "HTTP Response problem: ", e);
+ paymentRequest.cancel();
+ }
+ });
+ }
+
+ @Override
+ public void onPaymentResult(@NonNull PaymentRequest request,
+ @NonNull PaymentRequestResult paymentResult) {
+ if (paymentRequest != request) {
+ Log.d(TAG, "onPaymentResult(): This is not the payment request that we created.");
+ return;
+ }
+ Log.d(TAG, "paymentRequestListener.onPaymentResult() -> " + request);
+ String resultString;
+ if (paymentResult.isProcessed()) {
+ resultString = paymentResult.getPayment().getPaymentStatus().toString();
+ verifyPayment(paymentResult.getPayment());
+ } else {
+ resultString = paymentResult.getError().toString();
+ }
+
+ final Intent intent = new Intent(getApplicationContext(), PaymentResultActivity.class);
+ intent.putExtra("Result", resultString);
+ intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ startActivity(intent);
+ finish();
+ }
+
+ };
+
+ @SuppressWarnings("unchecked")
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ final PaymentDataEntryFragment paymentDataEntryFragment = new PaymentDataEntryFragment();
+ getSupportFragmentManager().beginTransaction().replace(android.R.id.content,
+ paymentDataEntryFragment).commitAllowingStateLoss();
+ }
+
+ @Override
+ public void onPaymentRequested(final PaymentSetupRequest paymentSetupRequest) {
+ Log.d(TAG, "onPaymentRequested");
+ this.paymentSetupRequest = paymentSetupRequest;
+ if (paymentRequest != null) {
+ paymentRequest.cancel();
+ }
+ paymentRequest = new PaymentRequest(this, paymentRequestListener);
+ paymentRequest.start();
+ }
+
+ private String getSetupDataString(final String token) {
+ final JSONObject jsonObject = new JSONObject();
+ try {
+ jsonObject.put("reference", "M+M Black dress & accessories");
+ jsonObject.put("merchantAccount", "TestMerchantCheckout");
+
+ jsonObject.put("shopperLocale", paymentSetupRequest.getShopperLocale());
+ jsonObject.put("sessionValidity", "2017-05-05T13:09:50");
+ jsonObject.put("token", token);
+
+ jsonObject.put("appUrlScheme", "app://checkout");
+ jsonObject.put("customerCountry", paymentSetupRequest.getCountryCode());
+ jsonObject.put("currency", paymentSetupRequest.getAmount().getCurrency());
+ jsonObject.put("quantity", paymentSetupRequest.getAmount().getValue());
+ jsonObject.put("platform", "android");
+ jsonObject.put("basketId", "M+M Black dress & accessories");
+ jsonObject.put("customerId", "emred");
+
+ } catch (final JSONException jsonException) {
+ Log.e("Unexpected error", "Setup failed");
+ }
+ return jsonObject.toString();
+ }
+
+ private void verifyPayment(final Payment payment) {
+ final JSONObject jsonObject = new JSONObject();
+ try {
+ jsonObject.put("payload", payment.getPayload());
+ } catch (JSONException e) {
+ e.printStackTrace();
+ Toast.makeText(this, "Failed to verify payment.", Toast.LENGTH_LONG).show();
+ return;
+ }
+ String verifyString = jsonObject.toString();
+
+ final Map headers = new HashMap<>();
+ headers.put("Content-Type", "application/json; charset=UTF-8");
+ headers.put("X-MerchantServer-App-SecretKey", MERCHANT_API_SECRET_KEY);
+ headers.put("X-MerchantServer-App-Id", MERCHANT_APP_ID);
+ AsyncHttpClient.post(MERCHANT_SERVER_URL + VERIFY, headers, verifyString, new HttpResponseCallback() {
+ String resultString = "";
+ @Override
+ public void onSuccess(final byte[] response) {
+ try {
+ JSONObject jsonVerifyResponse = new JSONObject(new String(response, Charset.forName("UTF-8")));
+ String authResponse = jsonVerifyResponse.getString("authResponse");
+ if (authResponse.equalsIgnoreCase(payment.getPaymentStatus().toString())) {
+ resultString = "Payment is " + payment.getPaymentStatus().toString().toLowerCase() + " and verified.";
+ } else {
+ resultString = "Failed to verify payment.";
+ }
+ } catch (JSONException e) {
+ e.printStackTrace();
+ resultString = "Failed to verify payment.";
+ }
+ Toast.makeText(MainActivity.this, resultString, Toast.LENGTH_LONG).show();
+ }
+
+ @Override
+ public void onFailure(final Throwable e) {
+ Toast.makeText(MainActivity.this, resultString, Toast.LENGTH_LONG).show();
+ }
+ });
+ }
+
+}
diff --git a/app/src/main/java/com/adyen/checkout/PaymentDataEntryFragment.java b/app/src/main/java/com/adyen/checkout/PaymentDataEntryFragment.java
new file mode 100644
index 0000000..952f3af
--- /dev/null
+++ b/app/src/main/java/com/adyen/checkout/PaymentDataEntryFragment.java
@@ -0,0 +1,94 @@
+package com.adyen.checkout;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.v4.app.Fragment;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.EditText;
+
+import com.adyen.core.models.Amount;
+import com.adyen.core.utils.AmountUtil;
+
+import java.text.ParseException;
+
+/**
+ * Fragment for collecting payment data from user.
+ *
+ */
+public class PaymentDataEntryFragment extends Fragment {
+
+ private static final String TAG = PaymentDataEntryFragment.class.getSimpleName();
+ private PaymentRequestListener paymentRequestListener;
+ private PaymentSetupRequest paymentSetupRequest;
+ private View fragmentView;
+
+ /**
+ * The listener interface for receiving payment request actions.
+ * Container Activity must implement this interface.
+ */
+ public interface PaymentRequestListener {
+ void onPaymentRequested(@NonNull PaymentSetupRequest paymentSetupRequest);
+ }
+
+ @Override
+ public void onAttach(final Context context) {
+ super.onAttach(context);
+ try {
+ this.paymentRequestListener = (PaymentRequestListener) context;
+ } catch (final ClassCastException classCastException) {
+ throw new ClassCastException(context.toString() + " is not a PaymentRequestListener");
+ }
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ // Inflate the layout for this fragment
+ fragmentView = inflater.inflate(R.layout.activity_main, container, false);
+
+ final Button proceedButton = (Button) fragmentView.findViewById(R.id.proceed_button);
+ proceedButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(final View view) {
+ try {
+ paymentSetupRequest = buildPaymentRequest(fragmentView);
+ paymentRequestListener.onPaymentRequested(paymentSetupRequest);
+ } catch (final ParseException parseException) {
+ Log.e(TAG, "Invalid amount string has been entered", parseException);
+ }
+ }
+ });
+
+ return fragmentView;
+ }
+
+ @NonNull
+ private PaymentSetupRequest buildPaymentRequest(final View view) throws ParseException {
+ Log.v(TAG, "buildPaymentRequest()");
+ PaymentSetupRequest paymentRequest = new PaymentSetupRequest();
+ final String amountValueString = ((EditText) view.findViewById(R.id.orderAmountEntry)).getText().toString();
+ final String amountCurrencyString = ((EditText) view.findViewById(R.id.orderCurrencyEntry))
+ .getText().toString();
+
+ paymentRequest.setAmount(new Amount(AmountUtil.parseMajorAmount(amountCurrencyString, amountValueString),
+ amountCurrencyString));
+ paymentRequest.setCountryCode(((EditText) view.findViewById(R.id.countryEntry)).getText().toString());
+ paymentRequest.setShopperLocale(((EditText) view.findViewById(R.id.shopperLocaleEntry)).getText().toString());
+ paymentRequest.setShopperIP(((EditText) view.findViewById(R.id.shopperIpEntry)).getText().toString());
+ paymentRequest.setMerchantAccount(((EditText) view.findViewById(R.id.merchantAccountEntry)).getText()
+ .toString());
+ paymentRequest.setMerchantReference(((EditText) view.findViewById(R.id.merchantReferenceEntry)).getText()
+ .toString());
+ paymentRequest.setPaymentDeadline(((EditText) view.findViewById(R.id.paymentDeadlineEntry)).getText()
+ .toString());
+ paymentRequest.setReturnURL(((EditText) view.findViewById(R.id.returnUrlEntry)).getText().toString());
+
+ return paymentRequest;
+ }
+
+}
diff --git a/app/src/main/java/com/adyen/checkout/PaymentResultActivity.java b/app/src/main/java/com/adyen/checkout/PaymentResultActivity.java
new file mode 100644
index 0000000..3883314
--- /dev/null
+++ b/app/src/main/java/com/adyen/checkout/PaymentResultActivity.java
@@ -0,0 +1,22 @@
+package com.adyen.checkout;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.widget.TextView;
+
+/**
+ * Activity for displaying the result.
+ */
+
+public class PaymentResultActivity extends Activity {
+
+ private String result;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.verification_activity);
+ result = getIntent().getStringExtra("Result");
+ ((TextView) findViewById(R.id.verificationTextView)).setText(result);
+ }
+}
diff --git a/app/src/main/java/com/adyen/checkout/PaymentSetupRequest.java b/app/src/main/java/com/adyen/checkout/PaymentSetupRequest.java
new file mode 100644
index 0000000..21f44cb
--- /dev/null
+++ b/app/src/main/java/com/adyen/checkout/PaymentSetupRequest.java
@@ -0,0 +1,125 @@
+package com.adyen.checkout;
+
+import android.support.annotation.NonNull;
+
+import com.adyen.core.models.Amount;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+public class PaymentSetupRequest {
+
+ private Amount amount;
+ private String merchantReference;
+ private String shopperIP;
+ private String shopperLocale;
+ private String merchantAccount;
+ private String countryCode;
+ private String paymentDeadline;
+ private String returnURL;
+ private String paymentToken;
+
+ public Amount getAmount() {
+ return amount;
+ }
+
+ public void setAmount(final Amount amount) {
+ this.amount = amount;
+ }
+
+ public String getMerchantReference() {
+ return merchantReference;
+ }
+
+ public void setMerchantReference(final String merchantReference) {
+ this.merchantReference = merchantReference;
+ }
+
+ public String getShopperIP() {
+ return shopperIP;
+ }
+
+ public void setShopperIP(final String shopperIP) {
+ this.shopperIP = shopperIP;
+ }
+
+ public String getShopperLocale() {
+ return shopperLocale;
+ }
+
+ public void setShopperLocale(final String shopperLocale) {
+ this.shopperLocale = shopperLocale;
+ }
+
+ public String getMerchantAccount() {
+ return merchantAccount;
+ }
+
+ public void setMerchantAccount(final String merchantAccount) {
+ this.merchantAccount = merchantAccount;
+ }
+
+ public String getCountryCode() {
+ return countryCode;
+ }
+
+ public void setCountryCode(final String countryCode) {
+ this.countryCode = countryCode;
+ }
+
+ public String getPaymentDeadline() {
+ return paymentDeadline;
+ }
+
+ public void setPaymentDeadline(final String paymentDeadline) {
+ this.paymentDeadline = paymentDeadline;
+ }
+
+ public String getReturnURL() {
+ return returnURL;
+ }
+
+ public void setReturnURL(final String returnURL) {
+ this.returnURL = returnURL;
+ }
+
+ public String getPaymentToken() {
+ return paymentToken;
+ }
+
+ public void setPaymentToken(final String paymentToken) {
+ this.paymentToken = paymentToken;
+ }
+
+ @NonNull
+ public String getSetupDataString() {
+ final JSONObject jsonObject = new JSONObject();
+ try {
+ jsonObject.put("reference", merchantReference);
+ jsonObject.put("merchantAccount", merchantAccount);
+ jsonObject.put("shopperLocale", shopperLocale);
+
+ jsonObject.put("appUrl", returnURL);
+ jsonObject.put("countryCode", countryCode);
+ jsonObject.put("sessionValidity", paymentDeadline);
+
+ final JSONObject paymentAmount = new JSONObject();
+ paymentAmount.put("currency", amount.getCurrency());
+ paymentAmount.put("value", amount.getValue());
+ jsonObject.put("amount", paymentAmount);
+
+ // HACK
+ jsonObject.put("shopperReference", "aap");
+
+ //Device fingerprint
+ jsonObject.put("sdkToken", paymentToken);
+
+
+ } catch (final JSONException jsonException) {
+ //TODO: What to do?
+ }
+
+ return jsonObject.toString();
+ }
+
+}
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
new file mode 100644
index 0000000..ed277c5
--- /dev/null
+++ b/app/src/main/res/layout/activity_main.xml
@@ -0,0 +1,230 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/verification_activity.xml b/app/src/main/res/layout/verification_activity.xml
new file mode 100644
index 0000000..96ed7a2
--- /dev/null
+++ b/app/src/main/res/layout/verification_activity.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..cde69bc
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.png b/app/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..c133a0c
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..bfa42f0
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..324e72c
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..aee44e1
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/values-w820dp/dimens.xml b/app/src/main/res/values-w820dp/dimens.xml
new file mode 100644
index 0000000..63fc816
--- /dev/null
+++ b/app/src/main/res/values-w820dp/dimens.xml
@@ -0,0 +1,6 @@
+
+
+ 64dp
+
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
new file mode 100644
index 0000000..5ea984b
--- /dev/null
+++ b/app/src/main/res/values/colors.xml
@@ -0,0 +1,22 @@
+
+
+ #3F51B5
+ #303F9F
+ #FF4081
+ #3d892e
+ #FFFFFF
+ #24D683
+ #25521C
+ #d3d3d3
+ #4E92DF
+ #DCDCDC
+ #B9B9B9
+ #979797
+ #2FB538
+ #FF0000
+ #000000
+ #CECECE
+ #F6F6F6
+ #969696
+ #252525
+
diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml
new file mode 100644
index 0000000..47c8224
--- /dev/null
+++ b/app/src/main/res/values/dimens.xml
@@ -0,0 +1,5 @@
+
+
+ 16dp
+ 16dp
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..45e30fd
--- /dev/null
+++ b/app/src/main/res/values/strings.xml
@@ -0,0 +1,3 @@
+
+ Checkout
+
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
new file mode 100644
index 0000000..5885930
--- /dev/null
+++ b/app/src/main/res/values/styles.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 0000000..9c7215d
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,50 @@
+buildscript {
+ repositories {
+ jcenter()
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:2.3.1'
+ }
+}
+
+allprojects {
+ repositories {
+ jcenter()
+ repositories {
+ flatDir {
+ dirs 'libs'
+ }
+ }
+ }
+}
+
+// This block encapsulates custom properties and makes them available to all
+// modules in the project.
+ext {
+ compileSdkVersion = 25
+ buildToolsVersion = "25.0.3"
+
+ supportLibVersion = "25.3.1"
+ supportAnnotationsVersion = "25.3.1"
+ jUnitVersion = "4.12"
+
+ minSdkVersion = 15
+ targetSdkVersion = 25
+ versionCode = 2
+ versionName = "1.0.3"
+
+ release_debuggable = false
+ release_minifyEnabled = false
+ release_useProguard = false
+ release_shrinkResources = false
+
+ debug_debuggable = true
+ debug_testCoverageEnabled = true
+
+ defaultPublishConfig = "release"
+ publishNonDefault = true
+
+ testBuildType = "debug"
+}
+
+apply plugin: 'java'
diff --git a/checkoutdemo/build.gradle b/checkoutdemo/build.gradle
new file mode 100644
index 0000000..e53040c
--- /dev/null
+++ b/checkoutdemo/build.gradle
@@ -0,0 +1,34 @@
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion rootProject.ext.compileSdkVersion
+ buildToolsVersion rootProject.ext.buildToolsVersion
+
+ defaultConfig {
+ applicationId "com.adyen.checkoutdemo"
+ minSdkVersion rootProject.ext.minSdkVersion
+ targetSdkVersion rootProject.ext.targetSdkVersion
+ versionCode rootProject.ext.versionCode
+ versionName rootProject.ext.versionName
+
+ testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+
+ }
+ buildTypes {
+ release {
+ debuggable rootProject.ext.release_debuggable
+ minifyEnabled rootProject.ext.release_minifyEnabled
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+ }
+ debug {
+ debuggable rootProject.ext.debug_debuggable
+ testCoverageEnabled = rootProject.ext.debug_testCoverageEnabled
+ }
+ }
+}
+
+dependencies {
+ compile fileTree(include: ['*.jar'], dir: 'libs')
+
+ compile 'com.adyen.checkout:ui:1.0.5'
+}
diff --git a/checkoutdemo/src/main/AndroidManifest.xml b/checkoutdemo/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..e55ead3
--- /dev/null
+++ b/checkoutdemo/src/main/AndroidManifest.xml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/checkoutdemo/src/main/java/com/adyen/checkoutdemo/FailureActivity.java b/checkoutdemo/src/main/java/com/adyen/checkoutdemo/FailureActivity.java
new file mode 100644
index 0000000..b7553d6
--- /dev/null
+++ b/checkoutdemo/src/main/java/com/adyen/checkoutdemo/FailureActivity.java
@@ -0,0 +1,57 @@
+package com.adyen.checkoutdemo;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.v4.app.FragmentActivity;
+import android.util.TypedValue;
+import android.view.View;
+import android.view.Window;
+import android.view.WindowManager;
+import android.widget.Button;
+
+public class FailureActivity extends FragmentActivity {
+
+ private Context context;
+ private Button tryAgainAction;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ this.requestWindowFeature(Window.FEATURE_NO_TITLE);
+ setContentView(R.layout.activity_failure);
+ context = this;
+
+ setStatusBarTranslucent(true);
+
+ tryAgainAction = (Button) findViewById(R.id.try_again_action);
+ tryAgainAction.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ Intent intent = new Intent(context, MainActivity.class);
+ startActivity(intent);
+ finish();
+ }
+ });
+ }
+
+ @TargetApi(19)
+ protected void setStatusBarTranslucent(boolean makeTranslucent) {
+ View v = findViewById(R.id.activity_failure);
+ if (v != null) {
+ int paddingTop = 0;
+ TypedValue tv = new TypedValue();
+ getTheme().resolveAttribute(0, tv, true);
+ paddingTop += TypedValue.complexToDimensionPixelSize(tv.data, getResources().getDisplayMetrics());
+ v.setPadding(0, makeTranslucent ? paddingTop : 0, 0, 0);
+ }
+
+ if (makeTranslucent) {
+ getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
+ } else {
+ getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
+ }
+ }
+
+}
diff --git a/checkoutdemo/src/main/java/com/adyen/checkoutdemo/MainActivity.java b/checkoutdemo/src/main/java/com/adyen/checkoutdemo/MainActivity.java
new file mode 100644
index 0000000..10553b2
--- /dev/null
+++ b/checkoutdemo/src/main/java/com/adyen/checkoutdemo/MainActivity.java
@@ -0,0 +1,192 @@
+package com.adyen.checkoutdemo;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.v4.app.FragmentActivity;
+import android.util.Log;
+import android.util.TypedValue;
+import android.view.View;
+import android.view.Window;
+import android.view.WindowManager;
+import android.widget.Button;
+import android.widget.Toast;
+
+import com.adyen.core.PaymentRequest;
+import com.adyen.core.interfaces.HttpResponseCallback;
+import com.adyen.core.interfaces.PaymentDataCallback;
+import com.adyen.core.interfaces.PaymentRequestListener;
+import com.adyen.core.models.Payment;
+import com.adyen.core.models.PaymentRequestResult;
+import com.adyen.core.utils.AsyncHttpClient;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.nio.charset.Charset;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+
+public class MainActivity extends FragmentActivity {
+
+ private static final String TAG = MainActivity.class.getSimpleName();
+
+ private static final String SETUP = "setup";
+ private static final String VERIFY = "verify";
+
+ private static final String MERCHANT_SERVER_URL = "https://checkoutshopper-test.adyen.com/checkoutshopper/demo/easy-integration/merchantserver/";
+
+ private static final String MERCHANT_API_SECRET_KEY = "ADD_YOUR_API_KEY";
+ private static final String MERCHANT_APP_ID = "ADD_YOUR_APP_ID";
+
+ private PaymentRequest paymentRequest;
+ private Context context;
+
+
+ private final PaymentRequestListener paymentRequestListener = new PaymentRequestListener() {
+ @Override
+ public void onPaymentDataRequested(@NonNull PaymentRequest paymentRequest, @NonNull String token,
+ @NonNull final PaymentDataCallback paymentDataCallback) {
+ final Map headers = new HashMap<>();
+ headers.put("Content-Type", "application/json; charset=UTF-8");
+ headers.put("X-MerchantServer-App-SecretKey", MERCHANT_API_SECRET_KEY);
+ headers.put("X-MerchantServer-App-Id", MERCHANT_APP_ID);
+
+ AsyncHttpClient.post(MERCHANT_SERVER_URL + SETUP, headers, getSetupDataString(token), new HttpResponseCallback() {
+ @Override
+ public void onSuccess(final byte[] response) {
+ paymentDataCallback.completionWithPaymentData(response);
+ }
+ @Override
+ public void onFailure(final Throwable e) {
+ Log.e(TAG, "HTTP Response problem: ", e);
+ }
+ });
+ }
+
+ @Override
+ public void onPaymentResult(@NonNull PaymentRequest paymentRequest,
+ @NonNull PaymentRequestResult paymentRequestResult) {
+ if (paymentRequestResult.isProcessed() && (
+ paymentRequestResult.getPayment().getPaymentStatus() == Payment.PaymentStatus.AUTHORISED
+ || paymentRequestResult.getPayment().getPaymentStatus()
+ == Payment.PaymentStatus.RECEIVED)) {
+ verifyPayment(paymentRequestResult.getPayment());
+ Intent intent = new Intent(context, SuccessActivity.class);
+ startActivity(intent);
+ finish();
+ } else {
+ Intent intent = new Intent(context, FailureActivity.class);
+ startActivity(intent);
+ finish();
+ }
+ }
+ };
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ this.requestWindowFeature(Window.FEATURE_NO_TITLE);
+ setContentView(R.layout.activity_main);
+ context = this;
+ setStatusBarTranslucent(true);
+
+ Button checkoutButton = (Button) findViewById(R.id.checkout_button);
+ checkoutButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ paymentRequest = new PaymentRequest(context, paymentRequestListener);
+ paymentRequest.start();
+ }
+ });
+ }
+
+ @Override
+ protected void onNewIntent(Intent intent) {
+ super.onNewIntent(intent);
+ }
+
+ private String getSetupDataString(final String token) {
+ final JSONObject jsonObject = new JSONObject();
+ try {
+ jsonObject.put("shopperLocale", "NL");
+ jsonObject.put("sessionValidity", "2017-05-28T17:18:59+00:00");
+ jsonObject.put("token", token);
+
+ jsonObject.put("appUrlScheme", "app://checkout");
+ jsonObject.put("customerCountry", "NL");
+ jsonObject.put("currency", "USD");
+ jsonObject.put("quantity", "17408");
+ jsonObject.put("platform", "android");
+ jsonObject.put("basketId", "M+M Black dress & accessories");
+ jsonObject.put("customerId", "emred");
+ } catch (final JSONException jsonException) {
+ Log.e("Unexpected error", "Setup failed");
+ }
+ return jsonObject.toString();
+ }
+
+ @TargetApi(19)
+ protected void setStatusBarTranslucent(boolean makeTranslucent) {
+ View v = findViewById(R.id.activity_main);
+ if (v != null) {
+ int paddingTop = 0;
+ TypedValue tv = new TypedValue();
+ getTheme().resolveAttribute(0, tv, true);
+ paddingTop += TypedValue.complexToDimensionPixelSize(tv.data, getResources().getDisplayMetrics());
+ v.setPadding(0, makeTranslucent ? paddingTop : 0, 0, 0);
+ }
+
+ if (makeTranslucent) {
+ getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
+ } else {
+ getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
+ }
+ }
+
+ private void verifyPayment(final Payment payment) {
+ final JSONObject jsonObject = new JSONObject();
+ try {
+ jsonObject.put("payload", payment.getPayload());
+ } catch (JSONException e) {
+ e.printStackTrace();
+ Toast.makeText(this, "Failed to verify payment.", Toast.LENGTH_LONG).show();
+ return;
+ }
+ String verifyString = jsonObject.toString();
+
+ final Map headers = new HashMap<>();
+ headers.put("Content-Type", "application/json; charset=UTF-8");
+ headers.put("X-MerchantServer-App-SecretKey", MERCHANT_API_SECRET_KEY);
+ headers.put("X-MerchantServer-App-Id", MERCHANT_APP_ID);
+ AsyncHttpClient.post(MERCHANT_SERVER_URL + VERIFY, headers, verifyString, new HttpResponseCallback() {
+ String resultString = "";
+ @Override
+ public void onSuccess(final byte[] response) {
+ try {
+ JSONObject jsonVerifyResponse = new JSONObject(new String(response, Charset.forName("UTF-8")));
+ String authResponse = jsonVerifyResponse.getString("authResponse");
+ if (authResponse.equalsIgnoreCase(payment.getPaymentStatus().toString())) {
+ resultString = "Payment is " + payment.getPaymentStatus().toString().toLowerCase(Locale.getDefault()) + " and verified.";
+ } else {
+ resultString = "Failed to verify payment.";
+ }
+ } catch (JSONException e) {
+ e.printStackTrace();
+ resultString = "Failed to verify payment.";
+ }
+ Toast.makeText(MainActivity.this, resultString, Toast.LENGTH_LONG).show();
+ }
+
+ @Override
+ public void onFailure(final Throwable e) {
+ Toast.makeText(MainActivity.this, resultString, Toast.LENGTH_LONG).show();
+ }
+ });
+ }
+
+
+}
diff --git a/checkoutdemo/src/main/java/com/adyen/checkoutdemo/SuccessActivity.java b/checkoutdemo/src/main/java/com/adyen/checkoutdemo/SuccessActivity.java
new file mode 100644
index 0000000..9960c49
--- /dev/null
+++ b/checkoutdemo/src/main/java/com/adyen/checkoutdemo/SuccessActivity.java
@@ -0,0 +1,57 @@
+package com.adyen.checkoutdemo;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.v4.app.FragmentActivity;
+import android.util.TypedValue;
+import android.view.View;
+import android.view.Window;
+import android.view.WindowManager;
+import android.widget.Button;
+
+public class SuccessActivity extends FragmentActivity {
+
+ private Context context;
+ private Button backToShopAction;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ this.requestWindowFeature(Window.FEATURE_NO_TITLE);
+ setContentView(R.layout.activity_success);
+ context = this;
+
+ setStatusBarTranslucent(true);
+
+ backToShopAction = (Button) findViewById(R.id.back_to_shop_action);
+ backToShopAction.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ Intent intent = new Intent(context, MainActivity.class);
+ startActivity(intent);
+ finish();
+ }
+ });
+ }
+
+ @TargetApi(19)
+ protected void setStatusBarTranslucent(boolean makeTranslucent) {
+ View v = findViewById(R.id.activity_success);
+ if (v != null) {
+ int paddingTop = 0;
+ TypedValue tv = new TypedValue();
+ getTheme().resolveAttribute(0, tv, true);
+ paddingTop += TypedValue.complexToDimensionPixelSize(tv.data, getResources().getDisplayMetrics());
+ v.setPadding(0, makeTranslucent ? paddingTop : 0, 0, 0);
+ }
+
+ if (makeTranslucent) {
+ getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
+ } else {
+ getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
+ }
+ }
+
+}
diff --git a/checkoutdemo/src/main/res/layout/activity_failure.xml b/checkoutdemo/src/main/res/layout/activity_failure.xml
new file mode 100644
index 0000000..88032b8
--- /dev/null
+++ b/checkoutdemo/src/main/res/layout/activity_failure.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/checkoutdemo/src/main/res/layout/activity_main.xml b/checkoutdemo/src/main/res/layout/activity_main.xml
new file mode 100644
index 0000000..a989687
--- /dev/null
+++ b/checkoutdemo/src/main/res/layout/activity_main.xml
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/checkoutdemo/src/main/res/layout/activity_success.xml b/checkoutdemo/src/main/res/layout/activity_success.xml
new file mode 100644
index 0000000..5736cc4
--- /dev/null
+++ b/checkoutdemo/src/main/res/layout/activity_success.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/checkoutdemo/src/main/res/mipmap-hdpi/back_to_shop.png b/checkoutdemo/src/main/res/mipmap-hdpi/back_to_shop.png
new file mode 100644
index 0000000..026255a
Binary files /dev/null and b/checkoutdemo/src/main/res/mipmap-hdpi/back_to_shop.png differ
diff --git a/checkoutdemo/src/main/res/mipmap-hdpi/background.png b/checkoutdemo/src/main/res/mipmap-hdpi/background.png
new file mode 100644
index 0000000..3bb59b6
Binary files /dev/null and b/checkoutdemo/src/main/res/mipmap-hdpi/background.png differ
diff --git a/checkoutdemo/src/main/res/mipmap-hdpi/btn_cta.png b/checkoutdemo/src/main/res/mipmap-hdpi/btn_cta.png
new file mode 100644
index 0000000..7bcd90a
Binary files /dev/null and b/checkoutdemo/src/main/res/mipmap-hdpi/btn_cta.png differ
diff --git a/checkoutdemo/src/main/res/mipmap-hdpi/checkout_window.png b/checkoutdemo/src/main/res/mipmap-hdpi/checkout_window.png
new file mode 100644
index 0000000..8c90199
Binary files /dev/null and b/checkoutdemo/src/main/res/mipmap-hdpi/checkout_window.png differ
diff --git a/checkoutdemo/src/main/res/mipmap-hdpi/failure.png b/checkoutdemo/src/main/res/mipmap-hdpi/failure.png
new file mode 100644
index 0000000..90b6165
Binary files /dev/null and b/checkoutdemo/src/main/res/mipmap-hdpi/failure.png differ
diff --git a/checkoutdemo/src/main/res/mipmap-hdpi/ic_launcher.png b/checkoutdemo/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..cde69bc
Binary files /dev/null and b/checkoutdemo/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/checkoutdemo/src/main/res/mipmap-hdpi/success.png b/checkoutdemo/src/main/res/mipmap-hdpi/success.png
new file mode 100644
index 0000000..54180bc
Binary files /dev/null and b/checkoutdemo/src/main/res/mipmap-hdpi/success.png differ
diff --git a/checkoutdemo/src/main/res/mipmap-hdpi/try_again.png b/checkoutdemo/src/main/res/mipmap-hdpi/try_again.png
new file mode 100644
index 0000000..ebd5af1
Binary files /dev/null and b/checkoutdemo/src/main/res/mipmap-hdpi/try_again.png differ
diff --git a/checkoutdemo/src/main/res/mipmap-mdpi/ic_launcher.png b/checkoutdemo/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..c133a0c
Binary files /dev/null and b/checkoutdemo/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/checkoutdemo/src/main/res/mipmap-xhdpi/back_to_shop.png b/checkoutdemo/src/main/res/mipmap-xhdpi/back_to_shop.png
new file mode 100644
index 0000000..572e571
Binary files /dev/null and b/checkoutdemo/src/main/res/mipmap-xhdpi/back_to_shop.png differ
diff --git a/checkoutdemo/src/main/res/mipmap-xhdpi/background.png b/checkoutdemo/src/main/res/mipmap-xhdpi/background.png
new file mode 100644
index 0000000..2836d62
Binary files /dev/null and b/checkoutdemo/src/main/res/mipmap-xhdpi/background.png differ
diff --git a/checkoutdemo/src/main/res/mipmap-xhdpi/btn_cta.png b/checkoutdemo/src/main/res/mipmap-xhdpi/btn_cta.png
new file mode 100644
index 0000000..3aec31d
Binary files /dev/null and b/checkoutdemo/src/main/res/mipmap-xhdpi/btn_cta.png differ
diff --git a/checkoutdemo/src/main/res/mipmap-xhdpi/checkout_window.png b/checkoutdemo/src/main/res/mipmap-xhdpi/checkout_window.png
new file mode 100644
index 0000000..3b87f4e
Binary files /dev/null and b/checkoutdemo/src/main/res/mipmap-xhdpi/checkout_window.png differ
diff --git a/checkoutdemo/src/main/res/mipmap-xhdpi/failure.png b/checkoutdemo/src/main/res/mipmap-xhdpi/failure.png
new file mode 100644
index 0000000..93aa1f7
Binary files /dev/null and b/checkoutdemo/src/main/res/mipmap-xhdpi/failure.png differ
diff --git a/checkoutdemo/src/main/res/mipmap-xhdpi/ic_launcher.png b/checkoutdemo/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..bfa42f0
Binary files /dev/null and b/checkoutdemo/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/checkoutdemo/src/main/res/mipmap-xhdpi/success.png b/checkoutdemo/src/main/res/mipmap-xhdpi/success.png
new file mode 100644
index 0000000..dcffcd8
Binary files /dev/null and b/checkoutdemo/src/main/res/mipmap-xhdpi/success.png differ
diff --git a/checkoutdemo/src/main/res/mipmap-xhdpi/try_again.png b/checkoutdemo/src/main/res/mipmap-xhdpi/try_again.png
new file mode 100644
index 0000000..8e2e517
Binary files /dev/null and b/checkoutdemo/src/main/res/mipmap-xhdpi/try_again.png differ
diff --git a/checkoutdemo/src/main/res/mipmap-xxhdpi/back_to_shop.png b/checkoutdemo/src/main/res/mipmap-xxhdpi/back_to_shop.png
new file mode 100644
index 0000000..2f92f03
Binary files /dev/null and b/checkoutdemo/src/main/res/mipmap-xxhdpi/back_to_shop.png differ
diff --git a/checkoutdemo/src/main/res/mipmap-xxhdpi/background.png b/checkoutdemo/src/main/res/mipmap-xxhdpi/background.png
new file mode 100644
index 0000000..963f12d
Binary files /dev/null and b/checkoutdemo/src/main/res/mipmap-xxhdpi/background.png differ
diff --git a/checkoutdemo/src/main/res/mipmap-xxhdpi/btn_cta.png b/checkoutdemo/src/main/res/mipmap-xxhdpi/btn_cta.png
new file mode 100644
index 0000000..80bd0e3
Binary files /dev/null and b/checkoutdemo/src/main/res/mipmap-xxhdpi/btn_cta.png differ
diff --git a/checkoutdemo/src/main/res/mipmap-xxhdpi/checkout_window.png b/checkoutdemo/src/main/res/mipmap-xxhdpi/checkout_window.png
new file mode 100644
index 0000000..4179f89
Binary files /dev/null and b/checkoutdemo/src/main/res/mipmap-xxhdpi/checkout_window.png differ
diff --git a/checkoutdemo/src/main/res/mipmap-xxhdpi/failure.png b/checkoutdemo/src/main/res/mipmap-xxhdpi/failure.png
new file mode 100644
index 0000000..1332510
Binary files /dev/null and b/checkoutdemo/src/main/res/mipmap-xxhdpi/failure.png differ
diff --git a/checkoutdemo/src/main/res/mipmap-xxhdpi/ic_launcher.png b/checkoutdemo/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..324e72c
Binary files /dev/null and b/checkoutdemo/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/checkoutdemo/src/main/res/mipmap-xxhdpi/success.png b/checkoutdemo/src/main/res/mipmap-xxhdpi/success.png
new file mode 100644
index 0000000..f4ce727
Binary files /dev/null and b/checkoutdemo/src/main/res/mipmap-xxhdpi/success.png differ
diff --git a/checkoutdemo/src/main/res/mipmap-xxhdpi/try_again.png b/checkoutdemo/src/main/res/mipmap-xxhdpi/try_again.png
new file mode 100644
index 0000000..cf4a641
Binary files /dev/null and b/checkoutdemo/src/main/res/mipmap-xxhdpi/try_again.png differ
diff --git a/checkoutdemo/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/checkoutdemo/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..aee44e1
Binary files /dev/null and b/checkoutdemo/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/checkoutdemo/src/main/res/values-w820dp/dimens.xml b/checkoutdemo/src/main/res/values-w820dp/dimens.xml
new file mode 100644
index 0000000..63fc816
--- /dev/null
+++ b/checkoutdemo/src/main/res/values-w820dp/dimens.xml
@@ -0,0 +1,6 @@
+
+
+ 64dp
+
diff --git a/checkoutdemo/src/main/res/values/colors.xml b/checkoutdemo/src/main/res/values/colors.xml
new file mode 100644
index 0000000..3ab3e9c
--- /dev/null
+++ b/checkoutdemo/src/main/res/values/colors.xml
@@ -0,0 +1,6 @@
+
+
+ #3F51B5
+ #303F9F
+ #FF4081
+
diff --git a/checkoutdemo/src/main/res/values/dimens.xml b/checkoutdemo/src/main/res/values/dimens.xml
new file mode 100644
index 0000000..47c8224
--- /dev/null
+++ b/checkoutdemo/src/main/res/values/dimens.xml
@@ -0,0 +1,5 @@
+
+
+ 16dp
+ 16dp
+
diff --git a/checkoutdemo/src/main/res/values/strings.xml b/checkoutdemo/src/main/res/values/strings.xml
new file mode 100644
index 0000000..46c89a4
--- /dev/null
+++ b/checkoutdemo/src/main/res/values/strings.xml
@@ -0,0 +1,3 @@
+
+ CheckoutDemo
+
diff --git a/checkoutdemo/src/main/res/values/styles.xml b/checkoutdemo/src/main/res/values/styles.xml
new file mode 100644
index 0000000..9b81f59
--- /dev/null
+++ b/checkoutdemo/src/main/res/values/styles.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml
new file mode 100644
index 0000000..5681036
--- /dev/null
+++ b/config/checkstyle/checkstyle.xml
@@ -0,0 +1,120 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/config/findbugs/findbugs-filter.xml b/config/findbugs/findbugs-filter.xml
new file mode 100644
index 0000000..69c23b6
--- /dev/null
+++ b/config/findbugs/findbugs-filter.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/customuiapplication/.gitignore b/customuiapplication/.gitignore
new file mode 100644
index 0000000..796b96d
--- /dev/null
+++ b/customuiapplication/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/customuiapplication/build.gradle b/customuiapplication/build.gradle
new file mode 100644
index 0000000..8eddd6b
--- /dev/null
+++ b/customuiapplication/build.gradle
@@ -0,0 +1,37 @@
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion rootProject.ext.compileSdkVersion
+ buildToolsVersion rootProject.ext.buildToolsVersion
+
+ defaultConfig {
+ applicationId "com.adyen.customuiapplication"
+ minSdkVersion rootProject.ext.minSdkVersion
+ targetSdkVersion rootProject.ext.targetSdkVersion
+ versionCode rootProject.ext.versionCode
+ versionName rootProject.ext.versionName
+
+ testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+
+ }
+ buildTypes {
+ release {
+ debuggable rootProject.ext.release_debuggable
+ minifyEnabled rootProject.ext.release_minifyEnabled
+ shrinkResources false
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+ }
+ debug {
+ debuggable rootProject.ext.debug_debuggable
+ testCoverageEnabled = rootProject.ext.debug_testCoverageEnabled
+ }
+ }
+ testBuildType rootProject.ext.testBuildType
+}
+
+dependencies {
+ compile fileTree(include: ['*.jar'], dir: 'libs')
+
+ compile 'com.adyen.checkout:core:1.0.5'
+
+}
diff --git a/customuiapplication/proguard-rules.pro b/customuiapplication/proguard-rules.pro
new file mode 100644
index 0000000..351f52f
--- /dev/null
+++ b/customuiapplication/proguard-rules.pro
@@ -0,0 +1,17 @@
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in /Users/emred/Library/Android/sdk/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the proguardFiles
+# directive in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
diff --git a/customuiapplication/src/androidTest/AndroidManifest.xml b/customuiapplication/src/androidTest/AndroidManifest.xml
new file mode 100644
index 0000000..7320a8b
--- /dev/null
+++ b/customuiapplication/src/androidTest/AndroidManifest.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/customuiapplication/src/androidTest/java/com/adyen/customuiapplication/ExampleInstrumentedTest.java b/customuiapplication/src/androidTest/java/com/adyen/customuiapplication/ExampleInstrumentedTest.java
new file mode 100644
index 0000000..56a6b37
--- /dev/null
+++ b/customuiapplication/src/androidTest/java/com/adyen/customuiapplication/ExampleInstrumentedTest.java
@@ -0,0 +1,26 @@
+package com.adyen.customuiapplication;
+
+import android.content.Context;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static junit.framework.Assert.assertEquals;
+
+/**
+ * Instrumentation test, which will execute on an Android device.
+ *
+ * @see Testing documentation
+ */
+@RunWith(AndroidJUnit4.class)
+public class ExampleInstrumentedTest {
+ @Test
+ public void useAppContext() throws Exception {
+ // Context of the app under test.
+ Context appContext = InstrumentationRegistry.getTargetContext();
+
+ assertEquals("com.adyen.customuiapplication", appContext.getPackageName());
+ }
+}
diff --git a/customuiapplication/src/androidTest/java/com/adyen/customuiapplication/PaymentAppTest.java b/customuiapplication/src/androidTest/java/com/adyen/customuiapplication/PaymentAppTest.java
new file mode 100644
index 0000000..c2acb05
--- /dev/null
+++ b/customuiapplication/src/androidTest/java/com/adyen/customuiapplication/PaymentAppTest.java
@@ -0,0 +1,101 @@
+package com.adyen.customuiapplication;
+
+import android.support.test.filters.LargeTest;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+
+import com.adyen.core.models.Payment;
+import com.adyen.testutils.EspressoTestUtils;
+import com.adyen.testutils.RetryTest;
+
+import org.junit.After;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static android.support.test.InstrumentationRegistry.getInstrumentation;
+import static android.support.test.espresso.Espresso.onView;
+import static android.support.test.espresso.action.ViewActions.clearText;
+import static android.support.test.espresso.action.ViewActions.click;
+import static android.support.test.espresso.action.ViewActions.closeSoftKeyboard;
+import static android.support.test.espresso.action.ViewActions.scrollTo;
+import static android.support.test.espresso.action.ViewActions.typeText;
+import static android.support.test.espresso.assertion.ViewAssertions.matches;
+import static android.support.test.espresso.matcher.ViewMatchers.withId;
+import static android.support.test.espresso.matcher.ViewMatchers.withText;
+import static com.adyen.testutils.EspressoTestUtils.closeAllActivities;
+import static org.hamcrest.text.IsEqualIgnoringCase.equalToIgnoringCase;
+
+/**
+ * Functional tests for checking the SDK integration.
+ * The main goal is to test that SDK works fine in a merchant application.
+ */
+
+@RunWith(AndroidJUnit4.class)
+@LargeTest
+public class PaymentAppTest {
+
+ private static final String CARD_NUMBER = "4444333322221111";
+ private static final String CARD_EXP_DATE = "0818";
+ private static final String CARD_CVC_CODE = "737";
+ private static final String AMOUNT = "50";
+ private static final String EUR = "EUR";
+
+ @Rule
+ public ActivityTestRule mMainActivityRule = new ActivityTestRule<>(MainActivity.class);
+
+ @Rule
+ public RetryTest retry = new RetryTest(5);
+
+ @After
+ public void tearDown() throws Exception {
+ closeAllActivities(getInstrumentation());
+ }
+
+ /**
+ * Makes sure tHat visa Payment succeeds for EURO payment.
+ */
+ @Test
+ public void testVISAPayment() throws Exception {
+ payWithVISACard(AMOUNT, EUR);
+ }
+
+ private void payWithVISACard(final String amountValue, final String amountCurrency) throws Exception {
+ payWithCard(amountValue, amountCurrency, "Credit Card", CARD_NUMBER, CARD_EXP_DATE, CARD_CVC_CODE);
+ }
+
+ private void payWithCard(final String amountValue, final String amountCurrency, final String cardType,
+ final String cardNumber, final String cardExpiryDate, final String cardCVC)
+ throws Exception {
+ checkCardPayment(amountValue, amountCurrency, cardType, cardNumber, cardExpiryDate, cardCVC, Payment.PaymentStatus.AUTHORISED.toString());
+ }
+
+ private void checkCardPayment(final String amountValue, final String amountCurrency, final String cardType,
+ final String cardNumber, final String cardExpiryDate,
+ final String cardCVC, final String expectedResult) throws Exception {
+ goToPaymentListFragment(amountValue, amountCurrency);
+
+ onView(withText(equalToIgnoringCase(cardType))).perform(click());
+ onView(withId(R.id.credit_card_no)).perform(clearText(), typeText(cardNumber),
+ closeSoftKeyboard());
+ onView(withId(R.id.credit_card_exp_date)).perform(typeText(cardExpiryDate),
+ closeSoftKeyboard());
+ onView(withId(R.id.credit_card_cvc)).perform(typeText(cardCVC),
+ closeSoftKeyboard());
+ onView(withId(R.id.collectCreditCardData)).perform(click());
+ EspressoTestUtils.waitForView(R.id.verificationTextView);
+ onView(withId(R.id.verificationTextView)).check(matches(withText(expectedResult)));
+ }
+
+ private void goToPaymentListFragment(final String amount, final String currency) throws Exception {
+ EspressoTestUtils.waitForView(R.id.orderAmountEntry);
+ onView(withId(R.id.orderAmountEntry)).perform(clearText(), typeText(amount),
+ closeSoftKeyboard());
+ onView(withId(R.id.orderCurrencyEntry)).perform(clearText(), typeText(currency),
+ closeSoftKeyboard());
+ onView(withId(R.id.proceed_button)).perform(scrollTo(), click());
+
+ EspressoTestUtils.waitForView(R.id.activity_payment_method_selection);
+ }
+
+}
diff --git a/customuiapplication/src/main/AndroidManifest.xml b/customuiapplication/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..b43a0ae
--- /dev/null
+++ b/customuiapplication/src/main/AndroidManifest.xml
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/customuiapplication/src/main/java/com/adyen/customuiapplication/CreditCardFragment.java b/customuiapplication/src/main/java/com/adyen/customuiapplication/CreditCardFragment.java
new file mode 100644
index 0000000..650b183
--- /dev/null
+++ b/customuiapplication/src/main/java/com/adyen/customuiapplication/CreditCardFragment.java
@@ -0,0 +1,116 @@
+package com.adyen.customuiapplication;
+
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.v4.app.Fragment;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.EditText;
+
+import com.adyen.core.models.paymentdetails.CVCOnlyPaymentDetails;
+import com.adyen.core.models.paymentdetails.CreditCardPaymentDetails;
+import com.adyen.core.models.paymentdetails.PaymentDetails;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import adyen.com.adyencse.encrypter.ClientSideEncrypter;
+import adyen.com.adyencse.encrypter.exception.EncrypterException;
+
+/**
+ * Fragment for collecting credit card info.
+ */
+
+public class CreditCardFragment extends Fragment {
+
+ private static final String TAG = CreditCardFragment.class.getSimpleName();
+ private CreditCardInfoListener creditCardInfoListener;
+ private boolean oneClick;
+ private String publicKey;
+ private String generationTime;
+
+ /**
+ * The listener interface for receiving payment method selection result.
+ * Container Activity must implement this interface.
+ */
+ public interface CreditCardInfoListener {
+ void onCreditCardInfoProvided(PaymentDetails paymentDetails);
+ }
+
+ public void setCreditCardInfoListener(@NonNull final CreditCardInfoListener creditCardInfoListener) {
+ this.creditCardInfoListener = creditCardInfoListener;
+ }
+
+ @Override
+ public void setArguments(final Bundle args) {
+ super.setArguments(args);
+ oneClick = args.getBoolean("oneClick");
+ publicKey = args.getString("public_key");
+ generationTime = args.getString("generation_time");
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ final View view;
+
+ if (oneClick) {
+ view = inflater.inflate(R.layout.credit_card_one_click_form, container, false);
+ final EditText cvcView = ((EditText) view.findViewById(R.id.credit_card_cvc));
+ view.findViewById(R.id.collectCreditCardData).setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(final View view) {
+ CVCOnlyPaymentDetails paymentDetails = new CVCOnlyPaymentDetails(cvcView.getText().toString());
+ if (creditCardInfoListener != null) {
+ creditCardInfoListener.onCreditCardInfoProvided(paymentDetails);
+ } else {
+ Log.w(TAG, "No listener provided.");
+ }
+ }
+ });
+
+ } else {
+ view = inflater.inflate(R.layout.credit_card_form, container, false);
+ final EditText creditCardNoView = ((EditText) view.findViewById(R.id.credit_card_no));
+ final EditText expiryDateView = ((EditText) view.findViewById(R.id.credit_card_exp_date));
+ final EditText cvcView = ((EditText) view.findViewById(R.id.credit_card_cvc));
+
+ view.findViewById(R.id.collectCreditCardData).setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(final View view) {
+ final JSONObject sensitiveData = new JSONObject();
+ try {
+ sensitiveData.put("number", creditCardNoView.getText());
+ sensitiveData.put("expiryMonth", expiryDateView.getText().subSequence(0, 2));
+ sensitiveData.put("expiryYear", "20" + expiryDateView.getText().subSequence(2, 4));
+ sensitiveData.put("cvc", cvcView.getText());
+ sensitiveData.put("holderName", "checkout shopper");
+ sensitiveData.put("generationtime", generationTime);
+
+ try {
+ ClientSideEncrypter encrypter = new ClientSideEncrypter(publicKey);
+ String encryptedData = encrypter.encrypt(sensitiveData.toString());
+
+ CreditCardPaymentDetails paymentDetails = new CreditCardPaymentDetails(encryptedData, true);
+ if (creditCardInfoListener != null) {
+ creditCardInfoListener.onCreditCardInfoProvided(paymentDetails);
+ } else {
+ Log.w(TAG, "No listener provided.");
+ }
+ } catch (EncrypterException e) {
+ e.printStackTrace();
+ }
+
+
+ } catch (final JSONException jsonException) {
+ Log.e(TAG, "Credit card information cannot be collected");
+ }
+ }
+ });
+ }
+ return view;
+ }
+
+}
diff --git a/customuiapplication/src/main/java/com/adyen/customuiapplication/IssuerListAdapter.java b/customuiapplication/src/main/java/com/adyen/customuiapplication/IssuerListAdapter.java
new file mode 100644
index 0000000..5be261e
--- /dev/null
+++ b/customuiapplication/src/main/java/com/adyen/customuiapplication/IssuerListAdapter.java
@@ -0,0 +1,69 @@
+package com.adyen.customuiapplication;
+
+import android.app.Activity;
+import android.content.Context;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.adyen.core.models.Issuer;
+import com.adyen.core.utils.AsyncImageDownloader;
+
+import java.util.List;
+
+/**
+ * A custom {@link ArrayAdapter} for displaying issuers.
+ */
+
+class IssuerListAdapter extends ArrayAdapter {
+
+ private static final String TAG = IssuerListAdapter.class.getSimpleName();
+
+ @NonNull
+ private final Activity context;
+ @NonNull
+ private final List issuers;
+
+ private static class ViewHolder {
+ private TextView paymentMethodNameView;
+ private ImageView imageView;
+ }
+
+ IssuerListAdapter(@NonNull Activity context, @NonNull List issuers) {
+ super(context, R.layout.payment_method_list, issuers);
+ Log.d(TAG, "IssuerListAdapter()");
+
+ this.context = context;
+ this.issuers = issuers;
+ }
+
+ @Override
+ @NonNull
+ public View getView(int position, @Nullable View view, @NonNull ViewGroup parent) {
+ ViewHolder viewHolder;
+ if (view == null) {
+ viewHolder = new ViewHolder();
+ LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+
+ view = inflater.inflate(R.layout.payment_method_list, parent, false);
+
+ viewHolder.paymentMethodNameView = (TextView) view.findViewById(R.id.paymentMethodName);
+ viewHolder.imageView = (ImageView) view.findViewById(R.id.paymentMethodLogo);
+ view.setTag(viewHolder);
+ } else {
+ viewHolder = (ViewHolder) view.getTag();
+ }
+
+ viewHolder.paymentMethodNameView.setText(issuers.get(position).getIssuerName());
+ AsyncImageDownloader.downloadImage(context, viewHolder.imageView, issuers.get(position).getIssuerLogoUrl(),
+ null);
+
+ return view;
+ }
+}
diff --git a/customuiapplication/src/main/java/com/adyen/customuiapplication/MainActivity.java b/customuiapplication/src/main/java/com/adyen/customuiapplication/MainActivity.java
new file mode 100644
index 0000000..133f2f1
--- /dev/null
+++ b/customuiapplication/src/main/java/com/adyen/customuiapplication/MainActivity.java
@@ -0,0 +1,322 @@
+package com.adyen.customuiapplication;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.customtabs.CustomTabsIntent;
+import android.support.v4.app.FragmentActivity;
+import android.util.Log;
+import android.widget.Toast;
+
+import com.adyen.core.PaymentRequest;
+import com.adyen.core.interfaces.HttpResponseCallback;
+import com.adyen.core.interfaces.PaymentDataCallback;
+import com.adyen.core.interfaces.PaymentDetailsCallback;
+import com.adyen.core.interfaces.PaymentMethodCallback;
+import com.adyen.core.interfaces.PaymentRequestDetailsListener;
+import com.adyen.core.interfaces.PaymentRequestListener;
+import com.adyen.core.interfaces.UriCallback;
+import com.adyen.core.models.Issuer;
+import com.adyen.core.models.Payment;
+import com.adyen.core.models.PaymentMethod;
+import com.adyen.core.models.PaymentRequestResult;
+import com.adyen.core.models.paymentdetails.IdealPaymentDetails;
+import com.adyen.core.models.paymentdetails.PaymentDetails;
+import com.adyen.core.utils.AsyncHttpClient;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.nio.charset.Charset;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+/**
+ * Main activity for demonstrating how to use Checkout SDK. Client should implement an activity
+ * similar to this.
+ * In this sample application, UI is completely custom made. This example application does not
+ * include wallet payment methods (samsungpay and android pay) in order to keep it simple.
+ */
+public class MainActivity extends FragmentActivity implements
+ PaymentDataEntryFragment.PaymentRequestListener,
+ PaymentMethodSelectionFragment.PaymentMethodSelectionListener {
+
+ private static final String TAG = MainActivity.class.getSimpleName();
+
+ private PaymentSetupRequest paymentSetupRequest;
+
+ private final List availablePaymentMethods = new CopyOnWriteArrayList<>();
+ private final List preferredPaymentMethods = new CopyOnWriteArrayList<>();
+
+ private static final String SETUP = "setup";
+ private static final String VERIFY = "verify";
+
+ private static final String MERCHANT_SERVER_URL = "https://checkoutshopper-test.adyen.com/checkoutshopper/demo/easy-integration/merchantserver/";
+
+ private static final String MERCHANT_API_SECRET_KEY = "ADD_YOUR_API_KEY";
+ private static final String MERCHANT_APP_ID = "ADD_YOUR_APP_ID";
+
+ private PaymentMethodCallback paymentMethodCallback;
+ private Context context;
+ private UriCallback uriCallback;
+
+ private PaymentRequest paymentRequest;
+
+ private PaymentRequestDetailsListener paymentRequestDetailsListener = new PaymentRequestDetailsListener() {
+ @Override
+ public void onPaymentMethodSelectionRequired(@NonNull final PaymentRequest paymentRequest,
+ final List recurringMethods,
+ @NonNull final List otherMethods,
+ @NonNull final PaymentMethodCallback callback) {
+ Log.d(TAG, "paymentRequestDetailsListener.onPaymentMethodSelectionRequired");
+ paymentMethodCallback = callback;
+ preferredPaymentMethods.clear();
+ preferredPaymentMethods.addAll(recurringMethods);
+ availablePaymentMethods.clear();
+ availablePaymentMethods.addAll(otherMethods);
+ final PaymentMethodSelectionFragment paymentMethodSelectionFragment
+ = new PaymentMethodSelectionFragment();
+ getSupportFragmentManager().beginTransaction().replace(android.R.id.content,
+ paymentMethodSelectionFragment).addToBackStack(null).commitAllowingStateLoss();
+ }
+
+ @Override
+ public void onRedirectRequired(@NonNull final PaymentRequest paymentRequest, final String redirectUrl,
+ @NonNull final UriCallback returnUriCallback) {
+ Log.d(TAG, "paymentRequestDetailsListener.onRedirectRequired(): " + redirectUrl);
+ uriCallback = returnUriCallback;
+ CustomTabsIntent.Builder builder = new CustomTabsIntent.Builder();
+ CustomTabsIntent customTabsIntent = builder.build();
+ customTabsIntent.launchUrl(context, Uri.parse(redirectUrl));
+ }
+
+ @Override
+ public void onPaymentDetailsRequired(@NonNull final PaymentRequest paymentRequest,
+ @NonNull final Map requiredFields,
+ @NonNull final PaymentDetailsCallback callback) {
+ Log.d(TAG, "paymentRequestDetailsListener.onPaymentDetailsRequired()");
+ final String paymentMethodType = paymentRequest.getPaymentMethod().getType();
+ if ("card".equals(paymentMethodType)) {
+ final CreditCardFragment creditCardFragment = new CreditCardFragment();
+ final Bundle bundle = new Bundle();
+ //ONE CLICK CHECK
+ if (requiredFields.containsKey("cardDetails.cvc")) {
+ bundle.putBoolean("oneClick", true);
+ }
+ creditCardFragment.setCreditCardInfoListener(new CreditCardFragment.CreditCardInfoListener() {
+ @Override
+ public void onCreditCardInfoProvided(PaymentDetails paymentDetails) {
+ callback.completionWithPaymentDetails(paymentDetails);
+ }
+ });
+ bundle.putString("public_key", paymentRequest.getPublicKey());
+ bundle.putString("generation_time", paymentRequest.getGenerationTime());
+ creditCardFragment.setArguments(bundle);
+
+ getSupportFragmentManager().beginTransaction().replace(android.R.id.content,
+ creditCardFragment).addToBackStack(null).commitAllowingStateLoss();
+ } else if ("ideal".equals(paymentMethodType)) {
+ final AlertDialog.Builder alertDialog = new AlertDialog.Builder(MainActivity.this);
+ final List issuers = paymentRequest.getPaymentMethod().getIssuers();
+ final IssuerListAdapter issuerListAdapter = new IssuerListAdapter(MainActivity.this, issuers);
+ alertDialog.setSingleChoiceItems(issuerListAdapter, -1, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(@NonNull final DialogInterface dialogInterface, final int i) {
+ final Issuer selectedIssuer = issuers.get(i);
+ dialogInterface.dismiss();
+ callback.completionWithPaymentDetails(new IdealPaymentDetails(selectedIssuer));
+ }
+ });
+ alertDialog.show();
+ } else {
+ Log.w(TAG, "UI for " + paymentMethodType + " has not been implemented.");
+ paymentRequest.cancel();
+ }
+ }
+ };
+
+ private final PaymentRequestListener paymentRequestListener = new PaymentRequestListener() {
+ @Override
+ public void onPaymentDataRequested(@NonNull final PaymentRequest paymentRequest, @NonNull String token,
+ @NonNull final PaymentDataCallback callback) {
+ Log.d(TAG, "paymentRequestListener.provideSetupData()");
+ final Map headers = new HashMap<>();
+ headers.put("Content-Type", "application/json; charset=UTF-8");
+ headers.put("X-MerchantServer-App-SecretKey", MERCHANT_API_SECRET_KEY);
+ headers.put("X-MerchantServer-App-Id", MERCHANT_APP_ID);
+
+ AsyncHttpClient.post(MERCHANT_SERVER_URL + SETUP, headers, getSetupDataString(token), new HttpResponseCallback() {
+ @Override
+ public void onSuccess(final byte[] response) {
+ callback.completionWithPaymentData(response);
+ }
+
+ @Override
+ public void onFailure(final Throwable e) {
+ Log.e(TAG, "HTTP Response problem: ", e);
+ paymentRequest.cancel();
+ }
+ });
+ }
+
+ @Override
+ public void onPaymentResult(@NonNull PaymentRequest paymentRequest,
+ @NonNull PaymentRequestResult paymentResult) {
+ Log.d(TAG, "paymentRequestListener.onPaymentResult()");
+ final String resultString;
+ if (paymentResult.isProcessed()) {
+ resultString = paymentResult.getPayment().getPaymentStatus().toString();
+ verifyPayment(paymentResult.getPayment());
+ } else {
+ resultString = paymentResult.getError().toString();
+ }
+
+ final Intent intent = new Intent(getApplicationContext(), PaymentResultActivity.class);
+ intent.putExtra("Result", resultString);
+ intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ startActivity(intent);
+ finish();
+ }
+
+ };
+
+ @SuppressWarnings("unchecked")
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ Log.d(TAG, "onCreate()");
+ context = this;
+ final Uri uri = getIntent().getData();
+ if (uri == null) {
+ setupInitScreen();
+ } else {
+ throw new IllegalStateException("Application was supposed to be declared singleTask");
+ }
+
+ }
+
+ @Override
+ protected void onNewIntent(final Intent intent) {
+ super.onNewIntent(intent);
+ Log.d(TAG, "onNewIntent: " + intent);
+ if (uriCallback != null) {
+ Log.d(TAG, "Notifying paymentRequest about return URI");
+ uriCallback.completionWithUri(intent.getData());
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ private void setupInitScreen() {
+ final PaymentDataEntryFragment paymentDataEntryFragment = new PaymentDataEntryFragment();
+ getSupportFragmentManager().beginTransaction().replace(android.R.id.content,
+ paymentDataEntryFragment).commitAllowingStateLoss();
+ }
+
+ public List getAvailablePaymentMethods() {
+ return availablePaymentMethods;
+ }
+
+ public List getPreferredPaymentMethods() {
+ return preferredPaymentMethods;
+ }
+
+ @Override
+ public void onPaymentRequested(final PaymentSetupRequest paymentSetupRequest) {
+ Log.d(TAG, "onPaymentRequested");
+ this.paymentSetupRequest = paymentSetupRequest;
+ if (paymentRequest != null) {
+ paymentRequest.cancel();
+ }
+ paymentRequest = new PaymentRequest(this, paymentRequestListener, paymentRequestDetailsListener);
+ paymentRequest.start();
+ }
+
+ private String getSetupDataString(final String token) {
+ final JSONObject jsonObject = new JSONObject();
+ try {
+ jsonObject.put("reference", "M+M Black dress & accessories");
+ jsonObject.put("shopperLocale", paymentSetupRequest.getShopperLocale());
+ jsonObject.put("shopperReference", "demo.shopper");
+ jsonObject.put("sessionValidity", "2017-05-10T13:09:50");
+ jsonObject.put("token", token);
+
+ jsonObject.put("appUrlScheme", "app://checkout");
+ jsonObject.put("customerCountry", paymentSetupRequest.getCountryCode());
+ jsonObject.put("currency", paymentSetupRequest.getAmount().getCurrency());
+ jsonObject.put("quantity", paymentSetupRequest.getAmount().getValue());
+ jsonObject.put("platform", "android");
+ jsonObject.put("basketId", "M+M Black dress & accessories");
+ jsonObject.put("customerId", "test");
+
+ } catch (final JSONException jsonException) {
+ Log.e("Unexpected error", "Setup failed");
+ }
+ return jsonObject.toString();
+ }
+
+ @Override
+ public void onPaymentMethodSelected(@NonNull final PaymentMethod paymentMethod) {
+ Log.d(TAG, "onPaymentMethodSelected(): " + paymentMethod.getType());
+ if (paymentMethodCallback != null) {
+ paymentMethodCallback.completionWithPaymentMethod(paymentMethod);
+ }
+ }
+
+ private void verifyPayment(final Payment payment) {
+ final JSONObject jsonObject = new JSONObject();
+ try {
+ jsonObject.put("payload", payment.getPayload());
+ } catch (JSONException e) {
+ e.printStackTrace();
+ Toast.makeText(this, "Failed to verify payment.", Toast.LENGTH_LONG).show();
+ return;
+ }
+ String verifyString = jsonObject.toString();
+
+ final Map headers = new HashMap<>();
+ headers.put("Content-Type", "application/json; charset=UTF-8");
+ headers.put("X-MerchantServer-App-SecretKey", MERCHANT_API_SECRET_KEY);
+ headers.put("X-MerchantServer-App-Id", MERCHANT_APP_ID);
+ AsyncHttpClient.post(MERCHANT_SERVER_URL + VERIFY, headers, verifyString, new HttpResponseCallback() {
+ String resultString = "";
+ @Override
+ public void onSuccess(final byte[] response) {
+ try {
+ JSONObject jsonVerifyResponse = new JSONObject(new String(response, Charset.forName("UTF-8")));
+ String authResponse = jsonVerifyResponse.getString("authResponse");
+ if (authResponse.equalsIgnoreCase(payment.getPaymentStatus().toString())) {
+ resultString = "Payment is " + payment.getPaymentStatus().toString().toLowerCase(Locale.getDefault()) + " and verified.";
+ } else {
+ resultString = "Failed to verify payment.";
+ }
+ } catch (JSONException e) {
+ e.printStackTrace();
+ resultString = "Failed to verify payment.";
+ }
+ Toast.makeText(MainActivity.this, resultString, Toast.LENGTH_LONG).show();
+ }
+
+ @Override
+ public void onFailure(final Throwable e) {
+ Toast.makeText(MainActivity.this, resultString, Toast.LENGTH_LONG).show();
+ }
+ });
+ }
+
+ @Override
+ public void onBackPressed() {
+ if (getSupportFragmentManager().getBackStackEntryCount() > 0) {
+ getSupportFragmentManager().popBackStackImmediate();
+ } else {
+ super.onBackPressed();
+ }
+ }
+}
diff --git a/customuiapplication/src/main/java/com/adyen/customuiapplication/PaymentDataEntryFragment.java b/customuiapplication/src/main/java/com/adyen/customuiapplication/PaymentDataEntryFragment.java
new file mode 100644
index 0000000..1d8e74f
--- /dev/null
+++ b/customuiapplication/src/main/java/com/adyen/customuiapplication/PaymentDataEntryFragment.java
@@ -0,0 +1,93 @@
+package com.adyen.customuiapplication;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.v4.app.Fragment;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.EditText;
+
+import com.adyen.core.models.Amount;
+import com.adyen.core.utils.AmountUtil;
+
+import java.text.ParseException;
+
+/**
+ * Fragment for collecting payment data from user.
+ */
+public class PaymentDataEntryFragment extends Fragment {
+
+ private static final String TAG = PaymentDataEntryFragment.class.getSimpleName();
+ private PaymentRequestListener paymentRequestListener;
+ private PaymentSetupRequest paymentSetupRequest;
+ private View fragmentView;
+
+ /**
+ * The listener interface for receiving payment request actions.
+ * Container Activity must implement this interface.
+ */
+ public interface PaymentRequestListener {
+ void onPaymentRequested(@NonNull PaymentSetupRequest paymentSetupRequest);
+ }
+
+ @Override
+ public void onAttach(final Context context) {
+ super.onAttach(context);
+ try {
+ this.paymentRequestListener = (PaymentRequestListener) context;
+ } catch (final ClassCastException classCastException) {
+ throw new ClassCastException(context.toString() + " is not a PaymentRequestListener");
+ }
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ // Inflate the layout for this fragment
+ fragmentView = inflater.inflate(R.layout.activity_main, container, false);
+
+ final Button proceedButton = (Button) fragmentView.findViewById(R.id.proceed_button);
+ proceedButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(final View view) {
+ try {
+ paymentSetupRequest = buildPaymentRequest(fragmentView);
+ paymentRequestListener.onPaymentRequested(paymentSetupRequest);
+ } catch (final ParseException parseException) {
+ Log.e(TAG, "Invalid amount string has been entered", parseException);
+ }
+ }
+ });
+
+ return fragmentView;
+ }
+
+ @NonNull
+ private PaymentSetupRequest buildPaymentRequest(final View view) throws ParseException {
+ Log.v(TAG, "buildPaymentRequest()");
+ PaymentSetupRequest paymentRequest = new PaymentSetupRequest();
+ final String amountValueString = ((EditText) view.findViewById(R.id.orderAmountEntry)).getText().toString();
+ final String amountCurrencyString = ((EditText) view.findViewById(R.id.orderCurrencyEntry))
+ .getText().toString();
+
+ paymentRequest.setAmount(new Amount(AmountUtil.parseMajorAmount(amountCurrencyString, amountValueString),
+ amountCurrencyString));
+ paymentRequest.setCountryCode(((EditText) view.findViewById(R.id.countryEntry)).getText().toString());
+ paymentRequest.setShopperLocale(((EditText) view.findViewById(R.id.shopperLocaleEntry)).getText().toString());
+ paymentRequest.setShopperIP(((EditText) view.findViewById(R.id.shopperIpEntry)).getText().toString());
+ paymentRequest.setMerchantAccount(((EditText) view.findViewById(R.id.merchantAccountEntry)).getText()
+ .toString());
+ paymentRequest.setMerchantReference(((EditText) view.findViewById(R.id.merchantReferenceEntry)).getText()
+ .toString());
+ paymentRequest.setPaymentDeadline(((EditText) view.findViewById(R.id.paymentDeadlineEntry)).getText()
+ .toString());
+ paymentRequest.setReturnURL(((EditText) view.findViewById(R.id.returnUrlEntry)).getText().toString());
+
+ return paymentRequest;
+ }
+
+}
diff --git a/customuiapplication/src/main/java/com/adyen/customuiapplication/PaymentListAdapter.java b/customuiapplication/src/main/java/com/adyen/customuiapplication/PaymentListAdapter.java
new file mode 100644
index 0000000..32ba3ac
--- /dev/null
+++ b/customuiapplication/src/main/java/com/adyen/customuiapplication/PaymentListAdapter.java
@@ -0,0 +1,72 @@
+package com.adyen.customuiapplication;
+
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.adyen.core.models.PaymentMethod;
+import com.adyen.core.utils.AsyncImageDownloader;
+
+import java.util.List;
+
+/**
+ * A custom {@link ArrayAdapter} for displaying payment methods.
+ */
+
+class PaymentListAdapter extends ArrayAdapter {
+
+ private static final String TAG = PaymentListAdapter.class.getSimpleName();
+
+ @NonNull private final Activity context;
+ @NonNull private final List paymentMethods;
+ private LayoutInflater layoutInflater;
+
+ private static class ViewHolder {
+ private TextView paymentMethodNameView;
+ private ImageView imageView;
+ }
+
+ PaymentListAdapter(@NonNull Activity context, @NonNull List paymentMethods) {
+ super(context, R.layout.payment_method_list, paymentMethods);
+ Log.d(TAG, "PaymentListAdapter()");
+
+ this.context = context;
+ this.paymentMethods = paymentMethods;
+ this.layoutInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ }
+
+ @Override
+ @NonNull
+ public View getView(int position, @Nullable View view, @NonNull ViewGroup parent) {
+ ViewHolder viewHolder;
+ if (view == null) {
+ viewHolder = new ViewHolder();
+ view = layoutInflater.inflate(R.layout.payment_method_list, parent, false);
+ viewHolder.paymentMethodNameView = (TextView) view.findViewById(R.id.paymentMethodName);
+ viewHolder.imageView = (ImageView) view.findViewById(R.id.paymentMethodLogo);
+ view.setTag(viewHolder);
+ } else {
+ viewHolder = (ViewHolder) view.getTag();
+ }
+ if (viewHolder != null) {
+ if (viewHolder.paymentMethodNameView != null && viewHolder.imageView != null) {
+ viewHolder.paymentMethodNameView.setText(paymentMethods.get(position).getName());
+ Bitmap defaultImage = null;
+ AsyncImageDownloader.downloadImage(context, viewHolder.imageView,
+ paymentMethods.get(position).getLogoUrl(), defaultImage);
+ }
+ }
+ return view;
+
+ }
+
+}
diff --git a/customuiapplication/src/main/java/com/adyen/customuiapplication/PaymentMethodSelectionFragment.java b/customuiapplication/src/main/java/com/adyen/customuiapplication/PaymentMethodSelectionFragment.java
new file mode 100644
index 0000000..bc9acad
--- /dev/null
+++ b/customuiapplication/src/main/java/com/adyen/customuiapplication/PaymentMethodSelectionFragment.java
@@ -0,0 +1,70 @@
+package com.adyen.customuiapplication;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.ListView;
+
+import com.adyen.core.models.PaymentMethod;
+
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+/**
+ * Fragment for displaying payment methods.
+ */
+public class PaymentMethodSelectionFragment extends Fragment {
+
+ private static final String TAG = PaymentMethodSelectionFragment.class.getSimpleName();
+ private PaymentMethodSelectionListener paymentMethodSelectionListener;
+ private final List paymentMethods = new CopyOnWriteArrayList<>();
+
+ /**
+ * The listener interface for receiving payment method selection result.
+ * Container Activity must implement this interface.
+ */
+ public interface PaymentMethodSelectionListener {
+ void onPaymentMethodSelected(PaymentMethod paymentMethod);
+ }
+
+ @Override
+ public void onAttach(final Context context) {
+ super.onAttach(context);
+ try {
+ this.paymentMethodSelectionListener = (PaymentMethodSelectionListener) context;
+ } catch (final ClassCastException classCastException) {
+ throw new ClassCastException(context.toString() + " is not a PaymentMethodSelectionListener");
+ }
+
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ final View view = inflater.inflate(R.layout.payment_method_selection_fragment, container, false);
+ final PaymentListAdapter paymentListAdapter = new PaymentListAdapter(getActivity(), paymentMethods);
+ final ListView listView = (ListView) view.findViewById(android.R.id.list);
+ listView.setAdapter(paymentListAdapter);
+ listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
+ @Override
+ public void onItemClick(final AdapterView> adapterView, final View view, final int i, final long l) {
+ final PaymentMethod selected = paymentMethods.get(i);
+ paymentMethodSelectionListener.onPaymentMethodSelected(selected);
+
+ }
+ });
+
+ paymentMethods.clear();
+ paymentMethods.addAll(((MainActivity) getActivity()).getPreferredPaymentMethods());
+ paymentMethods.addAll(((MainActivity) getActivity()).getAvailablePaymentMethods());
+ paymentListAdapter.notifyDataSetChanged();
+
+ // Inflate the layout for this fragment
+ return view;
+ }
+
+}
diff --git a/customuiapplication/src/main/java/com/adyen/customuiapplication/PaymentResultActivity.java b/customuiapplication/src/main/java/com/adyen/customuiapplication/PaymentResultActivity.java
new file mode 100644
index 0000000..3528862
--- /dev/null
+++ b/customuiapplication/src/main/java/com/adyen/customuiapplication/PaymentResultActivity.java
@@ -0,0 +1,22 @@
+package com.adyen.customuiapplication;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.widget.TextView;
+
+/**
+ * Activity for displaying the result.
+ */
+
+public class PaymentResultActivity extends Activity {
+
+ private String result;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.verification_activity);
+ result = getIntent().getStringExtra("Result");
+ ((TextView) findViewById(R.id.verificationTextView)).setText(result);
+ }
+}
diff --git a/customuiapplication/src/main/java/com/adyen/customuiapplication/PaymentSetupRequest.java b/customuiapplication/src/main/java/com/adyen/customuiapplication/PaymentSetupRequest.java
new file mode 100644
index 0000000..676b152
--- /dev/null
+++ b/customuiapplication/src/main/java/com/adyen/customuiapplication/PaymentSetupRequest.java
@@ -0,0 +1,125 @@
+package com.adyen.customuiapplication;
+
+import android.support.annotation.NonNull;
+
+import com.adyen.core.models.Amount;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+public class PaymentSetupRequest {
+
+ private Amount amount;
+ private String merchantReference;
+ private String shopperIP;
+ private String shopperLocale;
+ private String merchantAccount;
+ private String countryCode;
+ private String paymentDeadline;
+ private String returnURL;
+ private String paymentToken;
+
+ public Amount getAmount() {
+ return amount;
+ }
+
+ public void setAmount(final Amount amount) {
+ this.amount = amount;
+ }
+
+ public String getMerchantReference() {
+ return merchantReference;
+ }
+
+ public void setMerchantReference(final String merchantReference) {
+ this.merchantReference = merchantReference;
+ }
+
+ public String getShopperIP() {
+ return shopperIP;
+ }
+
+ public void setShopperIP(final String shopperIP) {
+ this.shopperIP = shopperIP;
+ }
+
+ public String getShopperLocale() {
+ return shopperLocale;
+ }
+
+ public void setShopperLocale(final String shopperLocale) {
+ this.shopperLocale = shopperLocale;
+ }
+
+ public String getMerchantAccount() {
+ return merchantAccount;
+ }
+
+ public void setMerchantAccount(final String merchantAccount) {
+ this.merchantAccount = merchantAccount;
+ }
+
+ public String getCountryCode() {
+ return countryCode;
+ }
+
+ public void setCountryCode(final String countryCode) {
+ this.countryCode = countryCode;
+ }
+
+ public String getPaymentDeadline() {
+ return paymentDeadline;
+ }
+
+ public void setPaymentDeadline(final String paymentDeadline) {
+ this.paymentDeadline = paymentDeadline;
+ }
+
+ public String getReturnURL() {
+ return returnURL;
+ }
+
+ public void setReturnURL(final String returnURL) {
+ this.returnURL = returnURL;
+ }
+
+ public String getPaymentToken() {
+ return paymentToken;
+ }
+
+ public void setPaymentToken(final String paymentToken) {
+ this.paymentToken = paymentToken;
+ }
+
+ @NonNull
+ public String getSetupDataString() {
+ final JSONObject jsonObject = new JSONObject();
+ try {
+ jsonObject.put("reference", merchantReference);
+ jsonObject.put("merchantAccount", merchantAccount);
+ jsonObject.put("shopperLocale", shopperLocale);
+
+ jsonObject.put("appUrl", returnURL);
+ jsonObject.put("countryCode", countryCode);
+ jsonObject.put("sessionValidity", paymentDeadline);
+
+ final JSONObject paymentAmount = new JSONObject();
+ paymentAmount.put("currency", amount.getCurrency());
+ paymentAmount.put("value", amount.getValue());
+ jsonObject.put("amount", paymentAmount);
+
+ // HACK
+ jsonObject.put("shopperReference", "aap");
+
+ //Device fingerprint
+ jsonObject.put("sdkToken", paymentToken);
+
+
+ } catch (final JSONException jsonException) {
+ //TODO: What to do?
+ }
+
+ return jsonObject.toString();
+ }
+
+}
diff --git a/customuiapplication/src/main/res/drawable-xhdpi/card_border.xml b/customuiapplication/src/main/res/drawable-xhdpi/card_border.xml
new file mode 100644
index 0000000..924c597
--- /dev/null
+++ b/customuiapplication/src/main/res/drawable-xhdpi/card_border.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/customuiapplication/src/main/res/layout/activity_main.xml b/customuiapplication/src/main/res/layout/activity_main.xml
new file mode 100644
index 0000000..d797bd1
--- /dev/null
+++ b/customuiapplication/src/main/res/layout/activity_main.xml
@@ -0,0 +1,230 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/customuiapplication/src/main/res/layout/credit_card_form.xml b/customuiapplication/src/main/res/layout/credit_card_form.xml
new file mode 100644
index 0000000..a3e2209
--- /dev/null
+++ b/customuiapplication/src/main/res/layout/credit_card_form.xml
@@ -0,0 +1,123 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/customuiapplication/src/main/res/layout/credit_card_one_click_form.xml b/customuiapplication/src/main/res/layout/credit_card_one_click_form.xml
new file mode 100644
index 0000000..ac818d3
--- /dev/null
+++ b/customuiapplication/src/main/res/layout/credit_card_one_click_form.xml
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/customuiapplication/src/main/res/layout/payment_method_list.xml b/customuiapplication/src/main/res/layout/payment_method_list.xml
new file mode 100644
index 0000000..a764539
--- /dev/null
+++ b/customuiapplication/src/main/res/layout/payment_method_list.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/customuiapplication/src/main/res/layout/payment_method_selection_fragment.xml b/customuiapplication/src/main/res/layout/payment_method_selection_fragment.xml
new file mode 100644
index 0000000..fde79e0
--- /dev/null
+++ b/customuiapplication/src/main/res/layout/payment_method_selection_fragment.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
diff --git a/customuiapplication/src/main/res/layout/verification_activity.xml b/customuiapplication/src/main/res/layout/verification_activity.xml
new file mode 100644
index 0000000..96ed7a2
--- /dev/null
+++ b/customuiapplication/src/main/res/layout/verification_activity.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/customuiapplication/src/main/res/mipmap-hdpi/ady_card_unknown.png b/customuiapplication/src/main/res/mipmap-hdpi/ady_card_unknown.png
new file mode 100644
index 0000000..af96a5c
Binary files /dev/null and b/customuiapplication/src/main/res/mipmap-hdpi/ady_card_unknown.png differ
diff --git a/customuiapplication/src/main/res/mipmap-hdpi/ic_launcher.png b/customuiapplication/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..cde69bc
Binary files /dev/null and b/customuiapplication/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/customuiapplication/src/main/res/mipmap-mdpi/ic_launcher.png b/customuiapplication/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..c133a0c
Binary files /dev/null and b/customuiapplication/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/customuiapplication/src/main/res/mipmap-xhdpi/ic_launcher.png b/customuiapplication/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..bfa42f0
Binary files /dev/null and b/customuiapplication/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/customuiapplication/src/main/res/mipmap-xxhdpi/ic_launcher.png b/customuiapplication/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..324e72c
Binary files /dev/null and b/customuiapplication/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/customuiapplication/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/customuiapplication/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..aee44e1
Binary files /dev/null and b/customuiapplication/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/customuiapplication/src/main/res/values-w820dp/colors.xml b/customuiapplication/src/main/res/values-w820dp/colors.xml
new file mode 100644
index 0000000..5ea984b
--- /dev/null
+++ b/customuiapplication/src/main/res/values-w820dp/colors.xml
@@ -0,0 +1,22 @@
+
+
+ #3F51B5
+ #303F9F
+ #FF4081
+ #3d892e
+ #FFFFFF
+ #24D683
+ #25521C
+ #d3d3d3
+ #4E92DF
+ #DCDCDC
+ #B9B9B9
+ #979797
+ #2FB538
+ #FF0000
+ #000000
+ #CECECE
+ #F6F6F6
+ #969696
+ #252525
+
diff --git a/customuiapplication/src/main/res/values-w820dp/dimens.xml b/customuiapplication/src/main/res/values-w820dp/dimens.xml
new file mode 100644
index 0000000..63fc816
--- /dev/null
+++ b/customuiapplication/src/main/res/values-w820dp/dimens.xml
@@ -0,0 +1,6 @@
+
+
+ 64dp
+
diff --git a/customuiapplication/src/main/res/values/colors.xml b/customuiapplication/src/main/res/values/colors.xml
new file mode 100644
index 0000000..5ea984b
--- /dev/null
+++ b/customuiapplication/src/main/res/values/colors.xml
@@ -0,0 +1,22 @@
+
+
+ #3F51B5
+ #303F9F
+ #FF4081
+ #3d892e
+ #FFFFFF
+ #24D683
+ #25521C
+ #d3d3d3
+ #4E92DF
+ #DCDCDC
+ #B9B9B9
+ #979797
+ #2FB538
+ #FF0000
+ #000000
+ #CECECE
+ #F6F6F6
+ #969696
+ #252525
+
diff --git a/customuiapplication/src/main/res/values/dimens.xml b/customuiapplication/src/main/res/values/dimens.xml
new file mode 100644
index 0000000..47c8224
--- /dev/null
+++ b/customuiapplication/src/main/res/values/dimens.xml
@@ -0,0 +1,5 @@
+
+
+ 16dp
+ 16dp
+
diff --git a/customuiapplication/src/main/res/values/strings.xml b/customuiapplication/src/main/res/values/strings.xml
new file mode 100644
index 0000000..8d09d76
--- /dev/null
+++ b/customuiapplication/src/main/res/values/strings.xml
@@ -0,0 +1,3 @@
+
+ CustomUIApplication
+
diff --git a/customuiapplication/src/main/res/values/styles.xml b/customuiapplication/src/main/res/values/styles.xml
new file mode 100644
index 0000000..5885930
--- /dev/null
+++ b/customuiapplication/src/main/res/values/styles.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
diff --git a/customwithcheckoutui/.gitignore b/customwithcheckoutui/.gitignore
new file mode 100644
index 0000000..796b96d
--- /dev/null
+++ b/customwithcheckoutui/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/customwithcheckoutui/build.gradle b/customwithcheckoutui/build.gradle
new file mode 100644
index 0000000..09c5c34
--- /dev/null
+++ b/customwithcheckoutui/build.gradle
@@ -0,0 +1,34 @@
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion rootProject.ext.compileSdkVersion
+ buildToolsVersion rootProject.ext.buildToolsVersion
+
+ defaultConfig {
+ applicationId "com.example.customwithadyenui"
+ minSdkVersion rootProject.ext.minSdkVersion
+ targetSdkVersion rootProject.ext.targetSdkVersion
+ versionCode rootProject.ext.versionCode
+ versionName rootProject.ext.versionName
+
+ testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+
+ }
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+ }
+ debug {
+ debuggable rootProject.ext.debug_debuggable
+ testCoverageEnabled = rootProject.ext.debug_testCoverageEnabled
+ }
+ }
+}
+
+dependencies {
+ compile fileTree(dir: 'libs', include: ['*.jar'])
+
+ compile 'com.adyen.checkout:ui:1.0.5'
+
+}
diff --git a/customwithcheckoutui/proguard-rules.pro b/customwithcheckoutui/proguard-rules.pro
new file mode 100644
index 0000000..6ed2897
--- /dev/null
+++ b/customwithcheckoutui/proguard-rules.pro
@@ -0,0 +1,25 @@
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in /Users/emmanuel/Library/Android/sdk/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the proguardFiles
+# directive in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
diff --git a/customwithcheckoutui/src/androidTest/java/com/adyen/customwithcheckoutui/ExampleInstrumentedTest.java b/customwithcheckoutui/src/androidTest/java/com/adyen/customwithcheckoutui/ExampleInstrumentedTest.java
new file mode 100644
index 0000000..aa9092e
--- /dev/null
+++ b/customwithcheckoutui/src/androidTest/java/com/adyen/customwithcheckoutui/ExampleInstrumentedTest.java
@@ -0,0 +1,26 @@
+package com.adyen.customwithcheckoutui;
+
+import android.content.Context;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Instrumentation test, which will execute on an Android device.
+ *
+ * @see Testing documentation
+ */
+@RunWith(AndroidJUnit4.class)
+public class ExampleInstrumentedTest {
+ @Test
+ public void useAppContext() throws Exception {
+ // Context of the app under test.
+ Context appContext = InstrumentationRegistry.getTargetContext();
+
+ assertEquals("com.example.customwithadyenui", appContext.getPackageName());
+ }
+}
diff --git a/customwithcheckoutui/src/main/AndroidManifest.xml b/customwithcheckoutui/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..4b28cd8
--- /dev/null
+++ b/customwithcheckoutui/src/main/AndroidManifest.xml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/customwithcheckoutui/src/main/java/com/adyen/customwithcheckoutui/MainActivity.java b/customwithcheckoutui/src/main/java/com/adyen/customwithcheckoutui/MainActivity.java
new file mode 100644
index 0000000..271d2b9
--- /dev/null
+++ b/customwithcheckoutui/src/main/java/com/adyen/customwithcheckoutui/MainActivity.java
@@ -0,0 +1,325 @@
+package com.adyen.customwithcheckoutui;
+
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.customtabs.CustomTabsIntent;
+import android.support.v4.app.FragmentActivity;
+import android.util.Log;
+import android.widget.Toast;
+
+import com.adyen.core.PaymentRequest;
+import com.adyen.core.interfaces.HttpResponseCallback;
+import com.adyen.core.interfaces.PaymentDataCallback;
+import com.adyen.core.interfaces.PaymentDetailsCallback;
+import com.adyen.core.interfaces.PaymentMethodCallback;
+import com.adyen.core.interfaces.PaymentRequestDetailsListener;
+import com.adyen.core.interfaces.PaymentRequestListener;
+import com.adyen.core.interfaces.UriCallback;
+import com.adyen.core.models.Payment;
+import com.adyen.core.models.PaymentMethod;
+import com.adyen.core.models.PaymentRequestResult;
+import com.adyen.core.models.paymentdetails.CreditCardPaymentDetails;
+import com.adyen.core.models.paymentdetails.IdealPaymentDetails;
+import com.adyen.core.models.paymentdetails.SepaDirectDebitPaymentDetails;
+import com.adyen.core.utils.AsyncHttpClient;
+import com.adyen.ui.fragments.CreditCardFragment;
+import com.adyen.ui.fragments.CreditCardFragmentBuilder;
+import com.adyen.ui.fragments.IssuerSelectionFragment;
+import com.adyen.ui.fragments.IssuerSelectionFragmentBuilder;
+import com.adyen.ui.fragments.PaymentMethodSelectionFragment;
+import com.adyen.ui.fragments.PaymentMethodSelectionFragmentBuilder;
+import com.adyen.ui.fragments.SepaDirectDebitFragment;
+import com.adyen.ui.fragments.SepaDirectDebitFragmentBuilder;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.nio.charset.Charset;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Main activity for demonstrating how to use Checkout SDK. Client should implement an activity
+ * similar to this.
+ * In this sample application, UI is completely custom made. This example application does not
+ * include wallet payment methods (samsungpay and android pay) in order to keep it simple.
+ */
+public class MainActivity extends FragmentActivity implements
+ PaymentDataEntryFragment.PaymentRequestListener {
+
+ private static final String TAG = MainActivity.class.getSimpleName();
+
+ private PaymentSetupRequest paymentSetupRequest;
+
+ private static final String SETUP = "setup";
+ private static final String VERIFY = "verify";
+
+ private static final String MERCHANT_SERVER_URL = "https://checkoutshopper-test.adyen.com/checkoutshopper/demo/easy-integration/merchantserver/";
+
+ private static final String MERCHANT_API_SECRET_KEY = "ADD_YOUR_API_KEY";
+ private static final String MERCHANT_APP_ID = "ADD_YOUR_APP_ID";
+ private Context context;
+ private UriCallback uriCallback;
+
+ private PaymentRequest paymentRequest;
+
+ private PaymentRequestDetailsListener paymentRequestDetailsListener = new PaymentRequestDetailsListener() {
+ @Override
+ public void onPaymentMethodSelectionRequired(@NonNull final PaymentRequest paymentRequest,
+ final List recurringMethods,
+ @NonNull final List otherMethods,
+ @NonNull final PaymentMethodCallback callback) {
+ Log.d(TAG, "paymentRequestDetailsListener.onPaymentMethodSelectionRequired");
+
+ final PaymentMethodSelectionFragment paymentMethodSelectionFragment
+ = new PaymentMethodSelectionFragmentBuilder()
+ .setPaymentMethods(otherMethods)
+ .setPreferredPaymentMethods(recurringMethods)
+ .setPaymentMethodSelectionListener(new PaymentMethodSelectionFragment.PaymentMethodSelectionListener() {
+ @Override
+ public void onPaymentMethodSelected(PaymentMethod paymentMethod) {
+ callback.completionWithPaymentMethod(paymentMethod);
+ }
+ })
+ .build();
+
+
+ getSupportFragmentManager().beginTransaction().replace(android.R.id.content,
+ paymentMethodSelectionFragment).addToBackStack(null).commitAllowingStateLoss();
+ }
+
+ @Override
+ public void onRedirectRequired(@NonNull final PaymentRequest paymentRequest, final String redirectUrl,
+ @NonNull final UriCallback returnUriCallback) {
+ Log.d(TAG, "paymentRequestDetailsListener.onRedirectRequired(): " + redirectUrl);
+ uriCallback = returnUriCallback;
+ CustomTabsIntent.Builder builder = new CustomTabsIntent.Builder();
+ CustomTabsIntent customTabsIntent = builder.build();
+ customTabsIntent.launchUrl(context, Uri.parse(redirectUrl));
+ }
+
+ @Override
+ public void onPaymentDetailsRequired(@NonNull final PaymentRequest paymentRequest,
+ @NonNull final Map requiredFields,
+ @NonNull final PaymentDetailsCallback callback) {
+ Log.d(TAG, "paymentRequestDetailsListener.onPaymentDetailsRequired()");
+ final String paymentMethodType = paymentRequest.getPaymentMethod().getType();
+
+ if (PaymentMethod.Type.CARD.equals(paymentMethodType)) {
+ CreditCardFragment creditCardFragment = new CreditCardFragmentBuilder()
+ .setPaymentMethod(paymentRequest.getPaymentMethod())
+ .setPublicKey(paymentRequest.getPublicKey())
+ .setGenerationtime(paymentRequest.getGenerationTime())
+ .setAmount(paymentRequest.getAmount())
+ .setShopperReference(paymentRequest.getShopperReference())
+ .setCreditCardInfoListener(new CreditCardFragment.CreditCardInfoListener() {
+ @Override
+ public void onCreditCardInfoProvided(CreditCardPaymentDetails paymentDetails) {
+ callback.completionWithPaymentDetails(paymentDetails);
+ }
+ })
+ .build();
+
+ getSupportFragmentManager().beginTransaction()
+ .replace(android.R.id.content, creditCardFragment).addToBackStack(null)
+ .commitAllowingStateLoss();
+ } else if (PaymentMethod.Type.IDEAL.equalsIgnoreCase(paymentMethodType)) {
+ IssuerSelectionFragment issuerSelectionFragment = new IssuerSelectionFragmentBuilder()
+ .setPaymentMethod(paymentRequest.getPaymentMethod())
+ .setIssuerSelectionListener(new IssuerSelectionFragment.IssuerSelectionListener() {
+ @Override
+ public void onIssuerSelected(IdealPaymentDetails issuerSelectionPaymentDetails) {
+ callback.completionWithPaymentDetails(issuerSelectionPaymentDetails);
+ }
+ })
+ .build();
+
+ getSupportFragmentManager().beginTransaction()
+ .replace(android.R.id.content, issuerSelectionFragment).addToBackStack(null)
+ .commitAllowingStateLoss();
+ } else if (PaymentMethod.Type.SEPA_DIRECT_DEBIT.equals(paymentMethodType)) {
+ SepaDirectDebitFragment sepaDirectDebitFragment = new SepaDirectDebitFragmentBuilder()
+ .setAmount(paymentRequest.getAmount())
+ .setSEPADirectDebitPaymentDetailsListener(new SepaDirectDebitFragment.SEPADirectDebitPaymentDetailsListener() {
+ @Override
+ public void onPaymentDetails(SepaDirectDebitPaymentDetails paymentDetails) {
+ callback.completionWithPaymentDetails(paymentDetails);
+ }
+ })
+ .build();
+
+ getSupportFragmentManager().beginTransaction()
+ .replace(android.R.id.content, sepaDirectDebitFragment).addToBackStack(null)
+ .commitAllowingStateLoss();
+
+ } else {
+ Log.w(TAG, "UI for " + paymentMethodType + " has not been implemented.");
+ Toast.makeText(MainActivity.this, "UI for " + paymentMethodType + " has not been implemented.", Toast.LENGTH_LONG).show();
+ paymentRequest.cancel();
+ }
+ }
+ };
+
+ private final PaymentRequestListener paymentRequestListener = new PaymentRequestListener() {
+ @Override
+ public void onPaymentDataRequested(@NonNull final PaymentRequest paymentRequest, @NonNull String token,
+ @NonNull final PaymentDataCallback callback) {
+ Log.d(TAG, "paymentRequestListener.provideSetupData()");
+ final Map headers = new HashMap<>();
+ headers.put("Content-Type", "application/json; charset=UTF-8");
+ headers.put("X-MerchantServer-App-SecretKey", MERCHANT_API_SECRET_KEY);
+ headers.put("X-MerchantServer-App-Id", MERCHANT_APP_ID);
+
+ AsyncHttpClient.post(MERCHANT_SERVER_URL + SETUP, headers, getSetupDataString(token), new HttpResponseCallback() {
+ @Override
+ public void onSuccess(final byte[] response) {
+ callback.completionWithPaymentData(response);
+ }
+
+ @Override
+ public void onFailure(final Throwable e) {
+ Log.e(TAG, "HTTP Response problem: ", e);
+ paymentRequest.cancel();
+ }
+ });
+ }
+
+ @Override
+ public void onPaymentResult(@NonNull PaymentRequest paymentRequest,
+ @NonNull PaymentRequestResult paymentResult) {
+ Log.d(TAG, "paymentRequestListener.onPaymentResult()");
+ final String resultString;
+ if (paymentResult.isProcessed()) {
+ resultString = paymentResult.getPayment().getPaymentStatus().toString();
+ verifyPayment(paymentResult.getPayment());
+ } else {
+ resultString = paymentResult.getError().toString();
+ }
+
+ final Intent intent = new Intent(getApplicationContext(), PaymentResultActivity.class);
+ intent.putExtra("Result", resultString);
+ intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ startActivity(intent);
+ finish();
+ }
+
+ };
+
+ @SuppressWarnings("unchecked")
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ Log.d(TAG, "onCreate()");
+ context = this;
+ final Uri uri = getIntent().getData();
+ if (uri == null) {
+ setupInitScreen();
+ } else {
+ throw new IllegalStateException("Application was supposed to be declared singleTask");
+ }
+
+ }
+
+ @Override
+ protected void onNewIntent(final Intent intent) {
+ super.onNewIntent(intent);
+ Log.d(TAG, "onNewIntent: " + intent);
+ if (uriCallback != null) {
+ Log.d(TAG, "Notifying paymentRequest about return URI");
+ uriCallback.completionWithUri(intent.getData());
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ private void setupInitScreen() {
+ final PaymentDataEntryFragment paymentDataEntryFragment = new PaymentDataEntryFragment();
+ getSupportFragmentManager().beginTransaction().replace(android.R.id.content,
+ paymentDataEntryFragment).commitAllowingStateLoss();
+ }
+
+ @Override
+ public void onPaymentRequested(final PaymentSetupRequest paymentSetupRequest) {
+ Log.d(TAG, "onPaymentRequested");
+ this.paymentSetupRequest = paymentSetupRequest;
+ if (paymentRequest != null) {
+ paymentRequest.cancel();
+ }
+ paymentRequest = new PaymentRequest(this, paymentRequestListener, paymentRequestDetailsListener);
+ paymentRequest.start();
+ }
+
+ private String getSetupDataString(final String token) {
+ final JSONObject jsonObject = new JSONObject();
+ try {
+
+ jsonObject.put("shopperLocale", paymentSetupRequest.getShopperLocale());
+ jsonObject.put("sessionValidity", "2017-03-28T17:18:59+00:00");
+ jsonObject.put("token", token);
+
+ jsonObject.put("appUrlScheme", "app://checkout");
+ jsonObject.put("customerCountry", paymentSetupRequest.getCountryCode());
+ jsonObject.put("currency", paymentSetupRequest.getAmount().getCurrency());
+ jsonObject.put("quantity", paymentSetupRequest.getAmount().getValue());
+ jsonObject.put("platform", "android");
+ jsonObject.put("basketId", "M+M Black dress & accessories");
+
+ } catch (final JSONException jsonException) {
+ Log.e("Unexpected error", "Setup failed");
+ }
+ return jsonObject.toString();
+ }
+
+ private void verifyPayment(final Payment payment) {
+ final JSONObject jsonObject = new JSONObject();
+ try {
+ jsonObject.put("payload", payment.getPayload());
+ } catch (JSONException e) {
+ e.printStackTrace();
+ Toast.makeText(this, "Failed to verify payment.", Toast.LENGTH_LONG).show();
+ return;
+ }
+ String verifyString = jsonObject.toString();
+
+ final Map headers = new HashMap<>();
+ headers.put("Content-Type", "application/json; charset=UTF-8");
+ headers.put("X-MerchantServer-App-SecretKey", MERCHANT_API_SECRET_KEY);
+ AsyncHttpClient.post(MERCHANT_SERVER_URL + VERIFY, headers, verifyString, new HttpResponseCallback() {
+ String resultString = "";
+ @Override
+ public void onSuccess(final byte[] response) {
+ try {
+ JSONObject jsonVerifyResponse = new JSONObject(new String(response, Charset.forName("UTF-8")));
+ String authResponse = jsonVerifyResponse.getString("authResponse");
+ if (authResponse.equalsIgnoreCase(payment.getPaymentStatus().toString())) {
+ resultString = "Payment is " + payment.getPaymentStatus().toString().toLowerCase() + " and verified.";
+ } else {
+ resultString = "Failed to verify payment.";
+ }
+ } catch (JSONException e) {
+ e.printStackTrace();
+ resultString = "Failed to verify payment.";
+ }
+ Toast.makeText(MainActivity.this, resultString, Toast.LENGTH_LONG).show();
+ }
+
+ @Override
+ public void onFailure(final Throwable e) {
+ Toast.makeText(MainActivity.this, resultString, Toast.LENGTH_LONG).show();
+ }
+ });
+ }
+
+ @Override
+ public void onBackPressed() {
+ if (getSupportFragmentManager().getBackStackEntryCount() > 0) {
+ getSupportFragmentManager().popBackStackImmediate();
+ } else {
+ super.onBackPressed();
+ }
+ }
+}
diff --git a/customwithcheckoutui/src/main/java/com/adyen/customwithcheckoutui/PaymentDataEntryFragment.java b/customwithcheckoutui/src/main/java/com/adyen/customwithcheckoutui/PaymentDataEntryFragment.java
new file mode 100644
index 0000000..df477d6
--- /dev/null
+++ b/customwithcheckoutui/src/main/java/com/adyen/customwithcheckoutui/PaymentDataEntryFragment.java
@@ -0,0 +1,93 @@
+package com.adyen.customwithcheckoutui;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.v4.app.Fragment;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.EditText;
+
+import com.adyen.core.models.Amount;
+import com.adyen.core.utils.AmountUtil;
+
+import java.text.ParseException;
+
+/**
+ * Fragment for collecting payment data from user.
+ */
+public class PaymentDataEntryFragment extends Fragment {
+
+ private static final String TAG = PaymentDataEntryFragment.class.getSimpleName();
+ private PaymentRequestListener paymentRequestListener;
+ private PaymentSetupRequest paymentSetupRequest;
+ private View fragmentView;
+
+ /**
+ * The listener interface for receiving payment request actions.
+ * Container Activity must implement this interface.
+ */
+ public interface PaymentRequestListener {
+ void onPaymentRequested(@NonNull PaymentSetupRequest paymentSetupRequest);
+ }
+
+ @Override
+ public void onAttach(final Context context) {
+ super.onAttach(context);
+ try {
+ this.paymentRequestListener = (PaymentRequestListener) context;
+ } catch (final ClassCastException classCastException) {
+ throw new ClassCastException(context.toString() + " is not a PaymentRequestListener");
+ }
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ // Inflate the layout for this fragment
+ fragmentView = inflater.inflate(R.layout.activity_main, container, false);
+
+ final Button proceedButton = (Button) fragmentView.findViewById(R.id.proceed_button);
+ proceedButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(final View view) {
+ try {
+ paymentSetupRequest = buildPaymentRequest(fragmentView);
+ paymentRequestListener.onPaymentRequested(paymentSetupRequest);
+ } catch (final ParseException parseException) {
+ Log.e(TAG, "Invalid amount string has been entered", parseException);
+ }
+ }
+ });
+
+ return fragmentView;
+ }
+
+ @NonNull
+ private PaymentSetupRequest buildPaymentRequest(final View view) throws ParseException {
+ Log.v(TAG, "buildPaymentRequest()");
+ PaymentSetupRequest paymentRequest = new PaymentSetupRequest();
+ final String amountValueString = ((EditText) view.findViewById(R.id.orderAmountEntry)).getText().toString();
+ final String amountCurrencyString = ((EditText) view.findViewById(R.id.orderCurrencyEntry))
+ .getText().toString();
+
+ paymentRequest.setAmount(new Amount(AmountUtil.parseMajorAmount(amountCurrencyString, amountValueString),
+ amountCurrencyString));
+ paymentRequest.setCountryCode(((EditText) view.findViewById(R.id.countryEntry)).getText().toString());
+ paymentRequest.setShopperLocale(((EditText) view.findViewById(R.id.shopperLocaleEntry)).getText().toString());
+ paymentRequest.setShopperIP(((EditText) view.findViewById(R.id.shopperIpEntry)).getText().toString());
+ paymentRequest.setMerchantAccount(((EditText) view.findViewById(R.id.merchantAccountEntry)).getText()
+ .toString());
+ paymentRequest.setMerchantReference(((EditText) view.findViewById(R.id.merchantReferenceEntry)).getText()
+ .toString());
+ paymentRequest.setPaymentDeadline(((EditText) view.findViewById(R.id.paymentDeadlineEntry)).getText()
+ .toString());
+ paymentRequest.setReturnURL(((EditText) view.findViewById(R.id.returnUrlEntry)).getText().toString());
+
+ return paymentRequest;
+ }
+
+}
diff --git a/customwithcheckoutui/src/main/java/com/adyen/customwithcheckoutui/PaymentResultActivity.java b/customwithcheckoutui/src/main/java/com/adyen/customwithcheckoutui/PaymentResultActivity.java
new file mode 100644
index 0000000..90b58c4
--- /dev/null
+++ b/customwithcheckoutui/src/main/java/com/adyen/customwithcheckoutui/PaymentResultActivity.java
@@ -0,0 +1,22 @@
+package com.adyen.customwithcheckoutui;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.widget.TextView;
+
+/**
+ * Activity for displaying the result.
+ */
+
+public class PaymentResultActivity extends Activity {
+
+ private String result;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.verification_activity);
+ result = getIntent().getStringExtra("Result");
+ ((TextView) findViewById(R.id.verificationTextView)).setText(result);
+ }
+}
diff --git a/customwithcheckoutui/src/main/java/com/adyen/customwithcheckoutui/PaymentSetupRequest.java b/customwithcheckoutui/src/main/java/com/adyen/customwithcheckoutui/PaymentSetupRequest.java
new file mode 100644
index 0000000..6de3cbb
--- /dev/null
+++ b/customwithcheckoutui/src/main/java/com/adyen/customwithcheckoutui/PaymentSetupRequest.java
@@ -0,0 +1,125 @@
+package com.adyen.customwithcheckoutui;
+
+import android.support.annotation.NonNull;
+
+import com.adyen.core.models.Amount;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+public class PaymentSetupRequest {
+
+ private Amount amount;
+ private String merchantReference;
+ private String shopperIP;
+ private String shopperLocale;
+ private String merchantAccount;
+ private String countryCode;
+ private String paymentDeadline;
+ private String returnURL;
+ private String paymentToken;
+
+ public Amount getAmount() {
+ return amount;
+ }
+
+ public void setAmount(final Amount amount) {
+ this.amount = amount;
+ }
+
+ public String getMerchantReference() {
+ return merchantReference;
+ }
+
+ public void setMerchantReference(final String merchantReference) {
+ this.merchantReference = merchantReference;
+ }
+
+ public String getShopperIP() {
+ return shopperIP;
+ }
+
+ public void setShopperIP(final String shopperIP) {
+ this.shopperIP = shopperIP;
+ }
+
+ public String getShopperLocale() {
+ return shopperLocale;
+ }
+
+ public void setShopperLocale(final String shopperLocale) {
+ this.shopperLocale = shopperLocale;
+ }
+
+ public String getMerchantAccount() {
+ return merchantAccount;
+ }
+
+ public void setMerchantAccount(final String merchantAccount) {
+ this.merchantAccount = merchantAccount;
+ }
+
+ public String getCountryCode() {
+ return countryCode;
+ }
+
+ public void setCountryCode(final String countryCode) {
+ this.countryCode = countryCode;
+ }
+
+ public String getPaymentDeadline() {
+ return paymentDeadline;
+ }
+
+ public void setPaymentDeadline(final String paymentDeadline) {
+ this.paymentDeadline = paymentDeadline;
+ }
+
+ public String getReturnURL() {
+ return returnURL;
+ }
+
+ public void setReturnURL(final String returnURL) {
+ this.returnURL = returnURL;
+ }
+
+ public String getPaymentToken() {
+ return paymentToken;
+ }
+
+ public void setPaymentToken(final String paymentToken) {
+ this.paymentToken = paymentToken;
+ }
+
+ @NonNull
+ public String getSetupDataString() {
+ final JSONObject jsonObject = new JSONObject();
+ try {
+ jsonObject.put("reference", merchantReference);
+ jsonObject.put("merchantAccount", merchantAccount);
+ jsonObject.put("shopperLocale", shopperLocale);
+
+ jsonObject.put("appUrl", returnURL);
+ jsonObject.put("countryCode", countryCode);
+ jsonObject.put("sessionValidity", paymentDeadline);
+
+ final JSONObject paymentAmount = new JSONObject();
+ paymentAmount.put("currency", amount.getCurrency());
+ paymentAmount.put("value", amount.getValue());
+ jsonObject.put("amount", paymentAmount);
+
+ // HACK
+ jsonObject.put("shopperReference", "aap");
+
+ //Device fingerprint
+ jsonObject.put("sdkToken", paymentToken);
+
+
+ } catch (final JSONException jsonException) {
+ //TODO: What to do?
+ }
+
+ return jsonObject.toString();
+ }
+
+}
diff --git a/customwithcheckoutui/src/main/res/layout/activity_main.xml b/customwithcheckoutui/src/main/res/layout/activity_main.xml
new file mode 100644
index 0000000..9afd9fc
--- /dev/null
+++ b/customwithcheckoutui/src/main/res/layout/activity_main.xml
@@ -0,0 +1,228 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/customwithcheckoutui/src/main/res/layout/verification_activity.xml b/customwithcheckoutui/src/main/res/layout/verification_activity.xml
new file mode 100644
index 0000000..96ed7a2
--- /dev/null
+++ b/customwithcheckoutui/src/main/res/layout/verification_activity.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/customwithcheckoutui/src/main/res/mipmap-hdpi/ic_launcher.png b/customwithcheckoutui/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..cde69bc
Binary files /dev/null and b/customwithcheckoutui/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/customwithcheckoutui/src/main/res/mipmap-hdpi/ic_launcher_round.png b/customwithcheckoutui/src/main/res/mipmap-hdpi/ic_launcher_round.png
new file mode 100644
index 0000000..9a078e3
Binary files /dev/null and b/customwithcheckoutui/src/main/res/mipmap-hdpi/ic_launcher_round.png differ
diff --git a/customwithcheckoutui/src/main/res/mipmap-mdpi/ic_launcher.png b/customwithcheckoutui/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..c133a0c
Binary files /dev/null and b/customwithcheckoutui/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/customwithcheckoutui/src/main/res/mipmap-mdpi/ic_launcher_round.png b/customwithcheckoutui/src/main/res/mipmap-mdpi/ic_launcher_round.png
new file mode 100644
index 0000000..efc028a
Binary files /dev/null and b/customwithcheckoutui/src/main/res/mipmap-mdpi/ic_launcher_round.png differ
diff --git a/customwithcheckoutui/src/main/res/mipmap-xhdpi/ic_launcher.png b/customwithcheckoutui/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..bfa42f0
Binary files /dev/null and b/customwithcheckoutui/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/customwithcheckoutui/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/customwithcheckoutui/src/main/res/mipmap-xhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..3af2608
Binary files /dev/null and b/customwithcheckoutui/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ
diff --git a/customwithcheckoutui/src/main/res/mipmap-xxhdpi/ic_launcher.png b/customwithcheckoutui/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..324e72c
Binary files /dev/null and b/customwithcheckoutui/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/customwithcheckoutui/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/customwithcheckoutui/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..9bec2e6
Binary files /dev/null and b/customwithcheckoutui/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ
diff --git a/customwithcheckoutui/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/customwithcheckoutui/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..aee44e1
Binary files /dev/null and b/customwithcheckoutui/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/customwithcheckoutui/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/customwithcheckoutui/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..34947cd
Binary files /dev/null and b/customwithcheckoutui/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ
diff --git a/customwithcheckoutui/src/main/res/values/colors.xml b/customwithcheckoutui/src/main/res/values/colors.xml
new file mode 100644
index 0000000..3ab3e9c
--- /dev/null
+++ b/customwithcheckoutui/src/main/res/values/colors.xml
@@ -0,0 +1,6 @@
+
+
+ #3F51B5
+ #303F9F
+ #FF4081
+
diff --git a/customwithcheckoutui/src/main/res/values/strings.xml b/customwithcheckoutui/src/main/res/values/strings.xml
new file mode 100644
index 0000000..6e4e4d7
--- /dev/null
+++ b/customwithcheckoutui/src/main/res/values/strings.xml
@@ -0,0 +1,3 @@
+
+ customwithadyenui
+
diff --git a/customwithcheckoutui/src/main/res/values/styles.xml b/customwithcheckoutui/src/main/res/values/styles.xml
new file mode 100644
index 0000000..5885930
--- /dev/null
+++ b/customwithcheckoutui/src/main/res/values/styles.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
diff --git a/gradle.properties b/gradle.properties
new file mode 100644
index 0000000..0bfc027
--- /dev/null
+++ b/gradle.properties
@@ -0,0 +1,21 @@
+# Project-wide Gradle settings.
+
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+# Default value: -Xmx10248m -XX:MaxPermSize=256m
+org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
+
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+org.gradle.parallel=true
+
+org.gradle.daemon = true
+org.gradle.configureondemand = true
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..05ef575
Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..272ff1d
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Thu Apr 06 17:30:15 CEST 2017
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip
diff --git a/gradlew b/gradlew
new file mode 100755
index 0000000..9d82f78
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,160 @@
+#!/usr/bin/env bash
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+ echo "$*"
+}
+
+die ( ) {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+esac
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+ JAVACMD=`cygpath --unix "$JAVACMD"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=$((i+1))
+ done
+ case $i in
+ (0) set -- ;;
+ (1) set -- "$args0" ;;
+ (2) set -- "$args0" "$args1" ;;
+ (3) set -- "$args0" "$args1" "$args2" ;;
+ (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
+function splitJvmOpts() {
+ JVM_OPTS=("$@")
+}
+eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
+JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+
+exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 0000000..aec9973
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,90 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windowz variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+if "%@eval[2+2]" == "4" goto 4NT_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+goto execute
+
+:4NT_args
+@rem Get arguments from the 4NT Shell from JP Software
+set CMD_LINE_ARGS=%$
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/settings.gradle b/settings.gradle
new file mode 100644
index 0000000..de2677f
--- /dev/null
+++ b/settings.gradle
@@ -0,0 +1 @@
+include ':app', ':customuiapplication', ':checkoutdemo', ':customwithcheckoutui'