From 5cc27c198727ad6719bb082d26ef6fc0919baa12 Mon Sep 17 00:00:00 2001 From: Max Hastings <128730898+MaxHastingsPP@users.noreply.github.com> Date: Tue, 12 Dec 2023 15:41:19 -0600 Subject: [PATCH 01/57] Add Payment Insights Client public API shell (#844) * Added Shopper Insights client, request, result, stubs and docs * Added Shopper Insights client unit test Added Shopper Insights client unit test * Added insight callback updated unit test * adding unit test documentation * remove coroutines * add new line * remove open * fix detekt * Update BraintreeCore/src/main/java/com/braintreepayments/api/BraintreeShopperInsightsClient.kt Co-authored-by: Sarah Koop * rename ShopperInsightsClient * lighten ShopperInsight docs * change ShopperInsightRequest from val to var * remove special char e.g. * rename response to info * Add Email and Phone request sub classes with unit tests * rename methods * updated unit tests * Added Email and Phone data class * Add docs and make interface a functional interface * simplify syntax * updating dependency * Rename parameters * fix lint issues * Addressing PR comment: making email a simple string * fix tests * Refactoring tests * linter fixes * refactoring * Renaming class * formatting * compact code * add docs * add kdoc * more kdocs * use constants for key strings * fix suppress params * Update BraintreeCore/src/test/java/com/braintreepayments/api/ShopperInsightsClientUnitTest.kt Co-authored-by: sshropshire <58225613+sshropshire@users.noreply.github.com> * Rename classes * Address PR comments. Move the object to json conversion to API class * move class to its own file * Change the ShopperInsightsRequest class signature * Moving the validation logic * adding kdoc --------- Co-authored-by: Sarah Koop Co-authored-by: saperi Co-authored-by: saperi22 <104481964+saperi22@users.noreply.github.com> Co-authored-by: sshropshire <58225613+sshropshire@users.noreply.github.com> --- BraintreeCore/build.gradle | 1 + .../com/braintreepayments/api/BuyerPhone.kt | 14 ++++ .../braintreepayments/api/PaymentReadyApi.kt | 30 +++++++ .../api/ShopperInsightsCallback.kt | 8 ++ .../api/ShopperInsightsClient.kt | 50 +++++++++++ .../api/ShopperInsightsInfo.kt | 20 +++++ .../api/ShopperInsightsRequest.kt | 16 ++++ .../api/ShopperInsightsResult.kt | 17 ++++ .../api/PaymentReadyApiTest.kt | 82 +++++++++++++++++++ .../api/ShopperInsightsClientUnitTest.kt | 57 +++++++++++++ 10 files changed, 295 insertions(+) create mode 100644 BraintreeCore/src/main/java/com/braintreepayments/api/BuyerPhone.kt create mode 100644 BraintreeCore/src/main/java/com/braintreepayments/api/PaymentReadyApi.kt create mode 100644 BraintreeCore/src/main/java/com/braintreepayments/api/ShopperInsightsCallback.kt create mode 100644 BraintreeCore/src/main/java/com/braintreepayments/api/ShopperInsightsClient.kt create mode 100644 BraintreeCore/src/main/java/com/braintreepayments/api/ShopperInsightsInfo.kt create mode 100644 BraintreeCore/src/main/java/com/braintreepayments/api/ShopperInsightsRequest.kt create mode 100644 BraintreeCore/src/main/java/com/braintreepayments/api/ShopperInsightsResult.kt create mode 100644 BraintreeCore/src/test/java/com/braintreepayments/api/PaymentReadyApiTest.kt create mode 100644 BraintreeCore/src/test/java/com/braintreepayments/api/ShopperInsightsClientUnitTest.kt diff --git a/BraintreeCore/build.gradle b/BraintreeCore/build.gradle index ee878d912d..cbd9b56c3a 100644 --- a/BraintreeCore/build.gradle +++ b/BraintreeCore/build.gradle @@ -78,6 +78,7 @@ dependencies { testImplementation deps.mockitoCore testImplementation deps.jsonAssert testImplementation deps.mockk + testImplementation deps.kotlinTest testImplementation project(':PayPal') testImplementation project(':TestUtils') testImplementation project(':UnionPay') diff --git a/BraintreeCore/src/main/java/com/braintreepayments/api/BuyerPhone.kt b/BraintreeCore/src/main/java/com/braintreepayments/api/BuyerPhone.kt new file mode 100644 index 0000000000..4f1d7e20f3 --- /dev/null +++ b/BraintreeCore/src/main/java/com/braintreepayments/api/BuyerPhone.kt @@ -0,0 +1,14 @@ +package com.braintreepayments.api + +/** + * Representation of a user phone number. + * @property countryCode The international country code for the shopper's phone number + * (e.g., "1" for the United States). + * @property nationalNumber The national segment of the shopper's phone number + * (excluding the country code). + */ + +data class BuyerPhone( + var countryCode: String, + var nationalNumber: String +) diff --git a/BraintreeCore/src/main/java/com/braintreepayments/api/PaymentReadyApi.kt b/BraintreeCore/src/main/java/com/braintreepayments/api/PaymentReadyApi.kt new file mode 100644 index 0000000000..9767f4a690 --- /dev/null +++ b/BraintreeCore/src/main/java/com/braintreepayments/api/PaymentReadyApi.kt @@ -0,0 +1,30 @@ +package com.braintreepayments.api + +import org.json.JSONObject + +// TODO: Implementation, documentation and interface. +internal class PaymentReadyApi { + fun processRequest(request: ShopperInsightsRequest): String = request.toJson() + + private fun ShopperInsightsRequest.toJson(): String { + return JSONObject().apply { + put(KEY_CUSTOMER, JSONObject().apply { + putOpt(KEY_EMAIL, email) + phone?.let { + put(KEY_PHONE, JSONObject().apply { + put(KEY_COUNTRY_CODE, it.countryCode) + put(KEY_NATIONAL_NUMBER, it.nationalNumber) + }) + } + }) + }.toString() + } + + companion object { + internal const val KEY_COUNTRY_CODE = "countryCode" + internal const val KEY_NATIONAL_NUMBER = "nationalNumber" + internal const val KEY_CUSTOMER = "customer" + internal const val KEY_EMAIL = "email" + internal const val KEY_PHONE = "phone" + } +} diff --git a/BraintreeCore/src/main/java/com/braintreepayments/api/ShopperInsightsCallback.kt b/BraintreeCore/src/main/java/com/braintreepayments/api/ShopperInsightsCallback.kt new file mode 100644 index 0000000000..ed9a57b1b8 --- /dev/null +++ b/BraintreeCore/src/main/java/com/braintreepayments/api/ShopperInsightsCallback.kt @@ -0,0 +1,8 @@ +package com.braintreepayments.api + +/** + * A callback that returns information on whether someone is a PayPal or a Venmo shopper. + */ +fun interface ShopperInsightsCallback { + fun onResult(result: ShopperInsightsResult) +} diff --git a/BraintreeCore/src/main/java/com/braintreepayments/api/ShopperInsightsClient.kt b/BraintreeCore/src/main/java/com/braintreepayments/api/ShopperInsightsClient.kt new file mode 100644 index 0000000000..e56b0df214 --- /dev/null +++ b/BraintreeCore/src/main/java/com/braintreepayments/api/ShopperInsightsClient.kt @@ -0,0 +1,50 @@ +package com.braintreepayments.api + +import androidx.annotation.VisibleForTesting + +/** + * Use [ShopperInsightsClient] to optimize your checkout experience + * by prioritizing the customer’s preferred payment methods in your UI. + * By customizing each customer’s checkout experience, + * you can improve conversion, increase sales/repeat buys and boost user retention/loyalty. + * + * Note: **This feature is in beta. It's public API may change in future releases.** + */ +class ShopperInsightsClient @VisibleForTesting internal constructor( + private val paymentReadyAPI: PaymentReadyApi +) { + /** + * Retrieves recommended payment methods based on the provided shopper insights request. + * + * @param request The [ShopperInsightsRequest] containing information about the shopper. + * @return A [ShopperInsightsResult] object indicating the recommended payment methods. + */ + fun getRecommendedPaymentMethods( + request: ShopperInsightsRequest, + callback: ShopperInsightsCallback + ) { + if (request.email == null && request.phone == null) { + callback.onResult( + ShopperInsightsResult.Failure( + IllegalArgumentException( + "One of ShopperInsightsRequest.email or " + + "ShopperInsightsRequest.phone must be non-null." + ) + ) + ) + return + } + + // TODO: - Add isAppInstalled checks for PP & Venmo. DTBTSDK-3176 + paymentReadyAPI.processRequest(request) + // Hardcoded result + callback.onResult( + ShopperInsightsResult.Success( + ShopperInsightsInfo( + isPayPalRecommended = false, + isVenmoRecommended = false + ) + ) + ) + } +} diff --git a/BraintreeCore/src/main/java/com/braintreepayments/api/ShopperInsightsInfo.kt b/BraintreeCore/src/main/java/com/braintreepayments/api/ShopperInsightsInfo.kt new file mode 100644 index 0000000000..e250de163a --- /dev/null +++ b/BraintreeCore/src/main/java/com/braintreepayments/api/ShopperInsightsInfo.kt @@ -0,0 +1,20 @@ +package com.braintreepayments.api + +/** + * Data class encapsulating the result of a shopper insight api request. + * + * This class holds information about the recommended payment methods for a shopper + * The recommendations include flags for whether payment methods like PayPal or Venmo + * should be displayed with high priority in the user interface. + * + * @property isPayPalRecommended If true, indicates that the PayPal payment option + * should be given high priority in the checkout UI. + * @property isVenmoRecommended If true, indicates that the Venmo payment option + * should be given high priority in the checkout UI. + * + * Note: **This feature is in beta. It's public API may change in future releases.** + */ +data class ShopperInsightsInfo( + val isPayPalRecommended: Boolean, + val isVenmoRecommended: Boolean +) diff --git a/BraintreeCore/src/main/java/com/braintreepayments/api/ShopperInsightsRequest.kt b/BraintreeCore/src/main/java/com/braintreepayments/api/ShopperInsightsRequest.kt new file mode 100644 index 0000000000..b2b4a8f91c --- /dev/null +++ b/BraintreeCore/src/main/java/com/braintreepayments/api/ShopperInsightsRequest.kt @@ -0,0 +1,16 @@ +package com.braintreepayments.api + +/** + * Data class representing a request for shopper insights. + * + * @property email The shopper's email address + * @property phone The shopper's phone number + * + * One of [email] or [phone] must be provided to get shopper insights. + * + * Note: **This feature is in beta. It's public API may change in future releases.** + */ +data class ShopperInsightsRequest( + var email: String?, + var phone: BuyerPhone? +) diff --git a/BraintreeCore/src/main/java/com/braintreepayments/api/ShopperInsightsResult.kt b/BraintreeCore/src/main/java/com/braintreepayments/api/ShopperInsightsResult.kt new file mode 100644 index 0000000000..c6ed736f3a --- /dev/null +++ b/BraintreeCore/src/main/java/com/braintreepayments/api/ShopperInsightsResult.kt @@ -0,0 +1,17 @@ +package com.braintreepayments.api + +/** + * The result object returned when insights about a shopper is requested. + */ +sealed class ShopperInsightsResult { + + /** + * @property response The response object describing the shopper's insights. + */ + class Success(val response: ShopperInsightsInfo) : ShopperInsightsResult() + + /** + * @property error An object that describes the error that occurred. + */ + class Failure(val error: Exception) : ShopperInsightsResult() +} diff --git a/BraintreeCore/src/test/java/com/braintreepayments/api/PaymentReadyApiTest.kt b/BraintreeCore/src/test/java/com/braintreepayments/api/PaymentReadyApiTest.kt new file mode 100644 index 0000000000..b9e1a5bb43 --- /dev/null +++ b/BraintreeCore/src/test/java/com/braintreepayments/api/PaymentReadyApiTest.kt @@ -0,0 +1,82 @@ +package com.braintreepayments.api + +import org.junit.Test +import org.skyscreamer.jsonassert.JSONAssert + +class PaymentReadyApiTest { + + private val paymentReadyApi = PaymentReadyApi() + + @Test + fun `test phone to json string conversion`() { + val testCountryCode = "1" + val testNationalNumber = "123456789" + val request = ShopperInsightsRequest( + null, + BuyerPhone( + countryCode = testCountryCode, + nationalNumber = testNationalNumber + ) + ) + + val observedJsonString = paymentReadyApi.processRequest(request) + val expectedJsonString = """ + { + "customer": { + "phone": { + "countryCode": "$testCountryCode", + "nationalNumber": "$testNationalNumber" + } + } + } + """.trimIndent() + + JSONAssert.assertEquals(expectedJsonString, observedJsonString, true) + } + + @Test + fun `test email to json string conversion`() { + val email = "fake-email@email-provider.com" + val request = ShopperInsightsRequest(email, null) + + val observedJsonString = paymentReadyApi.processRequest(request) + val expectedJsonString = """ + { + "customer": { + "email": "$email" + } + } + """.trimIndent() + + JSONAssert.assertEquals(expectedJsonString, observedJsonString, true) + } + + @Test + fun `test email and phone to json string conversion`() { + val email = "fake-email@email-provider.com" + val testCountryCode = "1" + val testNationalNumber = "123456789" + val request = ShopperInsightsRequest( + email, + BuyerPhone( + countryCode = testCountryCode, + nationalNumber = testNationalNumber + ) + ) + + val observedJsonString = paymentReadyApi.processRequest(request) + val expectedJsonString = """ + { + "customer": { + "phone": { + "countryCode": "$testCountryCode", + "nationalNumber": "$testNationalNumber" + }, + "email": "$email" + } + } + """.trimIndent() + + JSONAssert.assertEquals(expectedJsonString, observedJsonString, true) + } +} diff --git a/BraintreeCore/src/test/java/com/braintreepayments/api/ShopperInsightsClientUnitTest.kt b/BraintreeCore/src/test/java/com/braintreepayments/api/ShopperInsightsClientUnitTest.kt new file mode 100644 index 0000000000..c1b317ae99 --- /dev/null +++ b/BraintreeCore/src/test/java/com/braintreepayments/api/ShopperInsightsClientUnitTest.kt @@ -0,0 +1,57 @@ +package com.braintreepayments.api + +import io.mockk.mockk +import kotlin.test.assertEquals +import kotlin.test.assertIs +import org.junit.Assert.assertNotNull +import org.junit.Before +import org.junit.Test + +/** + * Unit tests for BraintreeShopperInsightsClient. + * + * This class contains tests for the shopper insights functionality within the Braintree SDK. + * It focuses on testing how the client handles different scenarios when fetching recommended + * payment methods. + */ +class ShopperInsightsClientUnitTest { + + private lateinit var sut: ShopperInsightsClient + private lateinit var paymentApi: PaymentReadyApi + + @Before + fun beforeEach() { + paymentApi = mockk(relaxed = true) + sut = ShopperInsightsClient(paymentApi) + } + + /** + * Tests if the getRecommendedPaymentMethods method returns paypal and venmo recommendations + * when providing a shopping insight request. + */ + @Test + fun testGetRecommendedPaymentMethods_returnsDefaultRecommendations() { + val request = ShopperInsightsRequest("fake-email", null) + sut.getRecommendedPaymentMethods(request) { result -> + assertNotNull(result) + val successResult = assertIs(result) + assertNotNull(successResult.response.isPayPalRecommended) + assertNotNull(successResult.response.isVenmoRecommended) + } + } + + @Test + fun `testGetRecommendedPaymentMethods - request object has null properties`() { + val request = ShopperInsightsRequest(null, null) + sut.getRecommendedPaymentMethods(request) { result -> + assertNotNull(result) + val error = assertIs(result) + val iae = assertIs(error.error) + assertEquals( + "One of ShopperInsightsRequest.email or " + + "ShopperInsightsRequest.phone must be non-null.", + iae.message + ) + } + } +} From ae80418d19be9546787986012b727d844cbe757f Mon Sep 17 00:00:00 2001 From: saperi22 <104481964+saperi22@users.noreply.github.com> Date: Thu, 14 Dec 2023 15:54:53 -0600 Subject: [PATCH 02/57] Create ShopperInsights module and move files there. (#853) * Create ShopperInsights module and move files there. * Add EOF line * Add README.md to the module * clean up dependencies * Refactor classes to standard package name * remove unused files * Modify release.yml to include newly created ShopperInsights module * Modify release_snapshot.yml to include newly created ShopperInsights module * remove example instrumented test * update dependencies * add dokka plugin to ShopperInsights module * removing readme --- .github/workflows/release.yml | 13 +++++ .github/workflows/release_snapshot.yml | 13 +++++ ShopperInsights/build.gradle | 54 +++++++++++++++++++ ShopperInsights/src/main/AndroidManifest.xml | 4 ++ .../com/braintreepayments/api/BuyerPhone.kt | 0 .../braintreepayments/api/PaymentReadyApi.kt | 0 .../api/ShopperInsightsCallback.kt | 0 .../api/ShopperInsightsClient.kt | 0 .../api/ShopperInsightsInfo.kt | 0 .../api/ShopperInsightsRequest.kt | 0 .../api/ShopperInsightsResult.kt | 0 .../api/PaymentReadyApiTest.kt | 0 .../api/ShopperInsightsClientUnitTest.kt | 0 build.gradle | 1 + settings.gradle | 1 + 15 files changed, 86 insertions(+) create mode 100644 ShopperInsights/build.gradle create mode 100644 ShopperInsights/src/main/AndroidManifest.xml rename {BraintreeCore => ShopperInsights}/src/main/java/com/braintreepayments/api/BuyerPhone.kt (100%) rename {BraintreeCore => ShopperInsights}/src/main/java/com/braintreepayments/api/PaymentReadyApi.kt (100%) rename {BraintreeCore => ShopperInsights}/src/main/java/com/braintreepayments/api/ShopperInsightsCallback.kt (100%) rename {BraintreeCore => ShopperInsights}/src/main/java/com/braintreepayments/api/ShopperInsightsClient.kt (100%) rename {BraintreeCore => ShopperInsights}/src/main/java/com/braintreepayments/api/ShopperInsightsInfo.kt (100%) rename {BraintreeCore => ShopperInsights}/src/main/java/com/braintreepayments/api/ShopperInsightsRequest.kt (100%) rename {BraintreeCore => ShopperInsights}/src/main/java/com/braintreepayments/api/ShopperInsightsResult.kt (100%) rename {BraintreeCore => ShopperInsights}/src/test/java/com/braintreepayments/api/PaymentReadyApiTest.kt (100%) rename {BraintreeCore => ShopperInsights}/src/test/java/com/braintreepayments/api/ShopperInsightsClientUnitTest.kt (100%) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 156ecc768e..2616e62cba 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -138,6 +138,18 @@ jobs: uses: ./.github/actions/unit_test_module with: module: SamsungPay + unit_test_shopper_insights: + name: Shopper Insights Unit Tests + runs-on: ubuntu-latest + steps: + - name: Checkout Repository + uses: actions/checkout@v2 + - name: Setup Java 8 + uses: ./.github/actions/setup + - name: Run Unit Tests + uses: ./.github/actions/unit_test_module + with: + module: ShopperInsights unit_test_three_d_secure: name: ThreeDSecure Unit Tests runs-on: ubuntu-latest @@ -223,6 +235,7 @@ jobs: unit_test_local_payment, unit_test_paypal, unit_test_samsung_pay, + unit_test_shopper_insights, unit_test_three_d_secure, unit_test_union_pay, unit_test_venmo, diff --git a/.github/workflows/release_snapshot.yml b/.github/workflows/release_snapshot.yml index 8f2d149ac5..10468eac58 100644 --- a/.github/workflows/release_snapshot.yml +++ b/.github/workflows/release_snapshot.yml @@ -137,6 +137,18 @@ jobs: uses: ./.github/actions/unit_test_module with: module: SamsungPay + unit_test_shopper_insights: + name: Shopper Insights Unit Tests + runs-on: ubuntu-latest + steps: + - name: Checkout Repository + uses: actions/checkout@v2 + - name: Setup Java 8 + uses: ./.github/actions/setup + - name: Run Unit Tests + uses: ./.github/actions/unit_test_module + with: + module: ShopperInsights unit_test_three_d_secure: name: ThreeDSecure Unit Tests runs-on: ubuntu-latest @@ -222,6 +234,7 @@ jobs: unit_test_local_payment, unit_test_paypal, unit_test_samsung_pay, + unit_test_shopper_insights, unit_test_three_d_secure, unit_test_union_pay, unit_test_venmo, diff --git a/ShopperInsights/build.gradle b/ShopperInsights/build.gradle new file mode 100644 index 0000000000..6e582d41fb --- /dev/null +++ b/ShopperInsights/build.gradle @@ -0,0 +1,54 @@ +plugins { + id 'com.android.library' + id 'org.jetbrains.kotlin.android' + id 'org.jetbrains.dokka' +} + +android { + namespace 'com.braintreepayments.api.shopperinsights' + compileSdk rootProject.compileSdkVersion + + defaultConfig { + minSdk rootProject.minSdkVersion + targetSdkVersion rootProject.targetSdkVersion + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + + testOptions { + unitTests { + includeAndroidResources = true + all { + jvmArgs '-noverify' + } + } + } + + compileOptions { + sourceCompatibility versions.javaSourceCompatibility + targetCompatibility versions.javaTargetCompatibility + } + kotlinOptions { + jvmTarget = '1.8' + } +} + +dependencies { + implementation deps.coreKtx + + testImplementation deps.jsonAssert + testImplementation deps.kotlinTest + testImplementation deps.mockk +} + +// region signing and publishing + +project.ext.name = "shopper-insights" +project.ext.pom_name = "shopper-insights" +project.ext.group_id = "com.braintreepayments.api" +project.ext.version = rootProject.version +project.ext.pom_desc = "Shopper Insights for Braintree Android." + +apply from: rootProject.file("gradle/gradle-publish.gradle") + +// endregion diff --git a/ShopperInsights/src/main/AndroidManifest.xml b/ShopperInsights/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..8bdb7e14b3 --- /dev/null +++ b/ShopperInsights/src/main/AndroidManifest.xml @@ -0,0 +1,4 @@ + + + + diff --git a/BraintreeCore/src/main/java/com/braintreepayments/api/BuyerPhone.kt b/ShopperInsights/src/main/java/com/braintreepayments/api/BuyerPhone.kt similarity index 100% rename from BraintreeCore/src/main/java/com/braintreepayments/api/BuyerPhone.kt rename to ShopperInsights/src/main/java/com/braintreepayments/api/BuyerPhone.kt diff --git a/BraintreeCore/src/main/java/com/braintreepayments/api/PaymentReadyApi.kt b/ShopperInsights/src/main/java/com/braintreepayments/api/PaymentReadyApi.kt similarity index 100% rename from BraintreeCore/src/main/java/com/braintreepayments/api/PaymentReadyApi.kt rename to ShopperInsights/src/main/java/com/braintreepayments/api/PaymentReadyApi.kt diff --git a/BraintreeCore/src/main/java/com/braintreepayments/api/ShopperInsightsCallback.kt b/ShopperInsights/src/main/java/com/braintreepayments/api/ShopperInsightsCallback.kt similarity index 100% rename from BraintreeCore/src/main/java/com/braintreepayments/api/ShopperInsightsCallback.kt rename to ShopperInsights/src/main/java/com/braintreepayments/api/ShopperInsightsCallback.kt diff --git a/BraintreeCore/src/main/java/com/braintreepayments/api/ShopperInsightsClient.kt b/ShopperInsights/src/main/java/com/braintreepayments/api/ShopperInsightsClient.kt similarity index 100% rename from BraintreeCore/src/main/java/com/braintreepayments/api/ShopperInsightsClient.kt rename to ShopperInsights/src/main/java/com/braintreepayments/api/ShopperInsightsClient.kt diff --git a/BraintreeCore/src/main/java/com/braintreepayments/api/ShopperInsightsInfo.kt b/ShopperInsights/src/main/java/com/braintreepayments/api/ShopperInsightsInfo.kt similarity index 100% rename from BraintreeCore/src/main/java/com/braintreepayments/api/ShopperInsightsInfo.kt rename to ShopperInsights/src/main/java/com/braintreepayments/api/ShopperInsightsInfo.kt diff --git a/BraintreeCore/src/main/java/com/braintreepayments/api/ShopperInsightsRequest.kt b/ShopperInsights/src/main/java/com/braintreepayments/api/ShopperInsightsRequest.kt similarity index 100% rename from BraintreeCore/src/main/java/com/braintreepayments/api/ShopperInsightsRequest.kt rename to ShopperInsights/src/main/java/com/braintreepayments/api/ShopperInsightsRequest.kt diff --git a/BraintreeCore/src/main/java/com/braintreepayments/api/ShopperInsightsResult.kt b/ShopperInsights/src/main/java/com/braintreepayments/api/ShopperInsightsResult.kt similarity index 100% rename from BraintreeCore/src/main/java/com/braintreepayments/api/ShopperInsightsResult.kt rename to ShopperInsights/src/main/java/com/braintreepayments/api/ShopperInsightsResult.kt diff --git a/BraintreeCore/src/test/java/com/braintreepayments/api/PaymentReadyApiTest.kt b/ShopperInsights/src/test/java/com/braintreepayments/api/PaymentReadyApiTest.kt similarity index 100% rename from BraintreeCore/src/test/java/com/braintreepayments/api/PaymentReadyApiTest.kt rename to ShopperInsights/src/test/java/com/braintreepayments/api/PaymentReadyApiTest.kt diff --git a/BraintreeCore/src/test/java/com/braintreepayments/api/ShopperInsightsClientUnitTest.kt b/ShopperInsights/src/test/java/com/braintreepayments/api/ShopperInsightsClientUnitTest.kt similarity index 100% rename from BraintreeCore/src/test/java/com/braintreepayments/api/ShopperInsightsClientUnitTest.kt rename to ShopperInsights/src/test/java/com/braintreepayments/api/ShopperInsightsClientUnitTest.kt diff --git a/build.gradle b/build.gradle index fd8d00c66f..35a5e60d15 100644 --- a/build.gradle +++ b/build.gradle @@ -147,6 +147,7 @@ detekt { "TestUtils/src", "ThreeDSecure/src", "Venmo/src", + "ShopperInsights/src" ) autoCorrect = project.hasProperty('detektAutoCorrect') reports { diff --git a/settings.gradle b/settings.gradle index 69ddff8106..a1f6427087 100644 --- a/settings.gradle +++ b/settings.gradle @@ -16,3 +16,4 @@ include ':TestUtils' include ':Demo' include ':SEPADirectDebit' include ':PayPalDataCollector' +include ':ShopperInsights' From 31f10d9ead6d3fa7228e897d7b341412215a407c Mon Sep 17 00:00:00 2001 From: Max Hastings <128730898+MaxHastingsPP@users.noreply.github.com> Date: Wed, 20 Dec 2023 13:20:19 -0600 Subject: [PATCH 03/57] Add Shopping Insights Demo App Integration (#854) * Add Shopping Insights Fragment * move insight row * added shopping insight vm, ui and integration * fix linting * Add email and phone null switches * remove unused import * import ShopperInsights module --------- Co-authored-by: Sai --- Demo/build.gradle | 5 +- .../braintreepayments/demo/MainFragment.java | 8 ++ .../demo/ShoppingInsightViewModel.kt | 42 ++++++ .../demo/ShoppingInsightsFragment.kt | 66 +++++++++ Demo/src/main/res/layout/fragment_main.xml | 9 ++ .../res/layout/fragment_shopping_insights.xml | 135 ++++++++++++++++++ Demo/src/main/res/navigation/nav_graph.xml | 9 +- Demo/src/main/res/values/strings.xml | 8 ++ 8 files changed, 280 insertions(+), 2 deletions(-) create mode 100644 Demo/src/main/java/com/braintreepayments/demo/ShoppingInsightViewModel.kt create mode 100644 Demo/src/main/java/com/braintreepayments/demo/ShoppingInsightsFragment.kt create mode 100644 Demo/src/main/res/layout/fragment_shopping_insights.xml diff --git a/Demo/build.gradle b/Demo/build.gradle index 53ae2c7872..bff413a11c 100644 --- a/Demo/build.gradle +++ b/Demo/build.gradle @@ -1,6 +1,7 @@ plugins { id 'com.android.application' id 'androidx.navigation.safeargs' + id 'kotlin-android' } android { @@ -75,7 +76,7 @@ android { dependencies { implementation 'androidx.preference:preference:1.1.1' - + implementation deps.kotlinStdLib implementation('com.squareup.retrofit:retrofit:1.9.0') { exclude module: 'com.google.gson' } @@ -88,6 +89,7 @@ dependencies { implementation project(':PayPal') implementation project(':PayPalNativeCheckout') implementation project(':SamsungPay') + implementation project(':ShopperInsights') implementation project(':SEPADirectDebit') implementation project(':ThreeDSecure') implementation project(':UnionPay') @@ -112,6 +114,7 @@ dependencies { implementation 'androidx.navigation:navigation-ui:2.3.5' implementation 'androidx.legacy:legacy-support-v4:1.0.0' implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0' + implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0' debugImplementation 'com.facebook.stetho:stetho:1.5.0' debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.12' diff --git a/Demo/src/main/java/com/braintreepayments/demo/MainFragment.java b/Demo/src/main/java/com/braintreepayments/demo/MainFragment.java index 06dbcb1e67..4a8efb6060 100644 --- a/Demo/src/main/java/com/braintreepayments/demo/MainFragment.java +++ b/Demo/src/main/java/com/braintreepayments/demo/MainFragment.java @@ -37,6 +37,7 @@ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup c Button samsungButton = view.findViewById(R.id.samsung_pay); Button sepaDirectDebitButton = view.findViewById(R.id.sepa_debit); Button payPalNativeCheckoutButton = view.findViewById(R.id.paypal_native_checkout); + Button shoppingInsightsButton = view.findViewById(R.id.shopping_insight); cardsButton.setOnClickListener(this::launchCards); payPalButton.setOnClickListener(this::launchPayPal); @@ -47,6 +48,7 @@ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup c samsungButton.setOnClickListener(this::launchSamsungPay); payPalNativeCheckoutButton.setOnClickListener(this::launchPayPalNativeCheckout); sepaDirectDebitButton.setOnClickListener(this::launchSEPADirectDebit); + shoppingInsightsButton.setOnClickListener(this::launchShoppingInsights); return view; } @@ -126,4 +128,10 @@ public void launchSEPADirectDebit(View v) { NavDirections action = MainFragmentDirections.actionMainFragmentToSepaDirectDebitFragment(); Navigation.findNavController(v).navigate(action); } + + public void launchShoppingInsights(View v) { + NavDirections action = + MainFragmentDirections.actionMainFragmentToShoppingInsightsFragment(); + Navigation.findNavController(v).navigate(action); + } } diff --git a/Demo/src/main/java/com/braintreepayments/demo/ShoppingInsightViewModel.kt b/Demo/src/main/java/com/braintreepayments/demo/ShoppingInsightViewModel.kt new file mode 100644 index 0000000000..f536afa1bb --- /dev/null +++ b/Demo/src/main/java/com/braintreepayments/demo/ShoppingInsightViewModel.kt @@ -0,0 +1,42 @@ +package com.braintreepayments.demo + +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import com.braintreepayments.api.BuyerPhone +import com.braintreepayments.api.ShopperInsightsRequest +import com.braintreepayments.api.ShopperInsightsResult + +/** + * ViewModel for handling shopping insights. + */ +class ShoppingInsightViewModel : ViewModel() { + + private val shopperInsightsLiveData = MutableLiveData() + + /** + * Fetches recommended payment methods using the provided buyer details. + * + * @param email The email address of the buyer. + * @param countryCode The country code for the buyer's phone number. + * @param nationalNumber The national number part of the buyer's phone number. + * @return MutableLiveData containing ShopperInsightsResult. + */ + fun getRecommendedPaymentMethods( + email: String?, + countryCode: String?, + nationalNumber: String? + ): MutableLiveData { + @Suppress("UnusedPrivateMember") + val request = if (countryCode != null && nationalNumber != null) { + ShopperInsightsRequest(email, BuyerPhone(countryCode, nationalNumber)) + } else { + ShopperInsightsRequest(email, null) + } + // TODO: Call Shopping Insight Client + // shopperInsightsClient.getRecommendedPaymentMethods(request) { result -> + // shopperInsightsLiveData.postValue(result) + // } + + return shopperInsightsLiveData + } +} diff --git a/Demo/src/main/java/com/braintreepayments/demo/ShoppingInsightsFragment.kt b/Demo/src/main/java/com/braintreepayments/demo/ShoppingInsightsFragment.kt new file mode 100644 index 0000000000..76473d0356 --- /dev/null +++ b/Demo/src/main/java/com/braintreepayments/demo/ShoppingInsightsFragment.kt @@ -0,0 +1,66 @@ +package com.braintreepayments.demo + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.Button +import android.widget.TextView +import androidx.fragment.app.Fragment +import androidx.lifecycle.ViewModelProvider +import com.google.android.material.switchmaterial.SwitchMaterial +import com.google.android.material.textfield.TextInputLayout + +/** + * Fragment for handling shopping insights. + */ +class ShoppingInsightsFragment : Fragment() { + + private lateinit var responseTextView: TextView + private lateinit var actionButton: Button + private lateinit var emailInput: TextInputLayout + private lateinit var countryCodeInput: TextInputLayout + private lateinit var nationalNumberInput: TextInputLayout + private lateinit var emailNullSwitch: SwitchMaterial + private lateinit var phoneNullSwitch: SwitchMaterial + + private val viewModel: ShoppingInsightViewModel by lazy { + ViewModelProvider(this)[ShoppingInsightViewModel::class.java] + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + return inflater.inflate(R.layout.fragment_shopping_insights, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + initializeViews(view) + setupActionButton() + } + + private fun initializeViews(view: View) { + responseTextView = view.findViewById(R.id.responseTextView) + actionButton = view.findViewById(R.id.actionButton) + emailInput = view.findViewById(R.id.emailInput) + countryCodeInput = view.findViewById(R.id.countryCodeInput) + nationalNumberInput = view.findViewById(R.id.nationalNumberInput) + emailNullSwitch = view.findViewById(R.id.emailNullSwitch) + phoneNullSwitch = view.findViewById(R.id.phoneNullSwitch) + } + + private fun setupActionButton() { + actionButton.setOnClickListener { + val email = if (emailNullSwitch.isChecked) null else emailInput.editText?.text.toString() + val countryCode = if (phoneNullSwitch.isChecked) null else countryCodeInput.editText?.text.toString() + val nationalNumber = if (phoneNullSwitch.isChecked) null else nationalNumberInput.editText?.text.toString() + viewModel.getRecommendedPaymentMethods(email, countryCode, nationalNumber) + .observe(viewLifecycleOwner) { + responseTextView.text = it.toString() + } + } + } +} diff --git a/Demo/src/main/res/layout/fragment_main.xml b/Demo/src/main/res/layout/fragment_main.xml index 23f8e388bd..57c9f6d1c1 100644 --- a/Demo/src/main/res/layout/fragment_main.xml +++ b/Demo/src/main/res/layout/fragment_main.xml @@ -23,6 +23,14 @@ android:text="@string/cards" android:textSize="12sp" /> +