Skip to content

Commit

Permalink
Add Payment Insights Client public API shell (#844)
Browse files Browse the repository at this point in the history
* 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 <[email protected]>

* 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 <[email protected]>

* 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 <[email protected]>
Co-authored-by: saperi <[email protected]>
Co-authored-by: saperi22 <[email protected]>
Co-authored-by: sshropshire <[email protected]>
  • Loading branch information
5 people authored Dec 12, 2023
1 parent ed09dab commit 5cc27c1
Show file tree
Hide file tree
Showing 10 changed files with 295 additions and 0 deletions.
1 change: 1 addition & 0 deletions BraintreeCore/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down
Original file line number Diff line number Diff line change
@@ -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
)
Original file line number Diff line number Diff line change
@@ -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"
}
}
Original file line number Diff line number Diff line change
@@ -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)
}
Original file line number Diff line number Diff line change
@@ -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
)
)
)
}
}
Original file line number Diff line number Diff line change
@@ -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
)
Original file line number Diff line number Diff line change
@@ -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?
)
Original file line number Diff line number Diff line change
@@ -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()
}
Original file line number Diff line number Diff line change
@@ -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 = "[email protected]"
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 = "[email protected]"
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)
}
}
Original file line number Diff line number Diff line change
@@ -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<ShopperInsightsResult.Success>(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<ShopperInsightsResult.Failure>(result)
val iae = assertIs<IllegalArgumentException>(error.error)
assertEquals(
"One of ShopperInsightsRequest.email or " +
"ShopperInsightsRequest.phone must be non-null.",
iae.message
)
}
}
}

0 comments on commit 5cc27c1

Please sign in to comment.