From 9600171eb5fcebcafe54dfd5e694a72a6ed4bb4f Mon Sep 17 00:00:00 2001 From: sshropshire Date: Mon, 31 Mar 2025 11:58:59 -0500 Subject: [PATCH 01/10] Add GraphQL query for updating client config to project. --- .../graphql_query_update_client_config.graphql | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 PayPalWebPayments/src/main/res/raw/graphql_query_update_client_config.graphql diff --git a/PayPalWebPayments/src/main/res/raw/graphql_query_update_client_config.graphql b/PayPalWebPayments/src/main/res/raw/graphql_query_update_client_config.graphql new file mode 100644 index 000000000..5c3de8736 --- /dev/null +++ b/PayPalWebPayments/src/main/res/raw/graphql_query_update_client_config.graphql @@ -0,0 +1,17 @@ +mutation UpdateClientConfig( + $orderID: String!, + $fundingSource: ButtonFundingSourceType!, + $integrationArtifact: IntegrationArtifactType!, + $userExperienceFlow: UserExperienceFlowType!, + $productFlow: ProductFlowType!, + $buttonSessionID: String +) { + updateClientConfig( + token: $orderID + fundingSource: $fundingSource + integrationArtifact: $integrationArtifact, + userExperienceFlow: $userExperienceFlow, + productFlow: $productFlow, + buttonSessionID: $buttonSessionID + ) +} From 776d8d56ac047c9aabd5b7fb5510841e6265be7e Mon Sep 17 00:00:00 2001 From: sshropshire Date: Mon, 31 Mar 2025 12:06:47 -0500 Subject: [PATCH 02/10] Stub out UpdateClientConfigAPI. --- .../UpdateClientConfigAPI.kt | 42 +++++++++++++++++++ .../UpdateClientConfigResult.kt | 10 +++++ 2 files changed, 52 insertions(+) create mode 100644 PayPalWebPayments/src/main/java/com/paypal/android/paypalwebpayments/UpdateClientConfigAPI.kt create mode 100644 PayPalWebPayments/src/main/java/com/paypal/android/paypalwebpayments/UpdateClientConfigResult.kt diff --git a/PayPalWebPayments/src/main/java/com/paypal/android/paypalwebpayments/UpdateClientConfigAPI.kt b/PayPalWebPayments/src/main/java/com/paypal/android/paypalwebpayments/UpdateClientConfigAPI.kt new file mode 100644 index 000000000..65999f333 --- /dev/null +++ b/PayPalWebPayments/src/main/java/com/paypal/android/paypalwebpayments/UpdateClientConfigAPI.kt @@ -0,0 +1,42 @@ +package com.paypal.android.paypalwebpayments + +import android.content.Context +import androidx.annotation.RawRes +import com.paypal.android.corepayments.CoreConfig +import com.paypal.android.corepayments.LoadRawResourceResult +import com.paypal.android.corepayments.PayPalSDKError +import com.paypal.android.corepayments.ResourceLoader +import com.paypal.android.corepayments.graphql.GraphQLClient + +internal class UpdateClientConfigAPI( + private val coreConfig: CoreConfig, + private val applicationContext: Context, + private val graphQLClient: GraphQLClient, + private val resourceLoader: ResourceLoader +) { + + constructor(context: Context, coreConfig: CoreConfig) : this( + coreConfig, + context.applicationContext, + GraphQLClient(coreConfig), + ResourceLoader() + ) + + suspend fun updateClientConfig(): UpdateClientConfigResult { + @RawRes val resId = R.raw.graphql_query_update_client_config + return when (val result = resourceLoader.loadRawResource(applicationContext, resId)) { + is LoadRawResourceResult.Success -> + sendUpdateClientConfigGraphQLRequest(result.value) + + is LoadRawResourceResult.Failure -> UpdateClientConfigResult.Failure( + PayPalSDKError(123, "TODO: implement") + ) + } + } + + private suspend fun sendUpdateClientConfigGraphQLRequest( + query: String, + ): UpdateClientConfigResult { + return UpdateClientConfigResult.Failure(PayPalSDKError(123, "TODO: implement")) + } +} \ No newline at end of file diff --git a/PayPalWebPayments/src/main/java/com/paypal/android/paypalwebpayments/UpdateClientConfigResult.kt b/PayPalWebPayments/src/main/java/com/paypal/android/paypalwebpayments/UpdateClientConfigResult.kt new file mode 100644 index 000000000..86fb9e3f6 --- /dev/null +++ b/PayPalWebPayments/src/main/java/com/paypal/android/paypalwebpayments/UpdateClientConfigResult.kt @@ -0,0 +1,10 @@ +package com.paypal.android.paypalwebpayments + +import com.paypal.android.corepayments.PayPalSDKError + +sealed class UpdateClientConfigResult { + + data class Success(val clientConfig: String): UpdateClientConfigResult() + data class Failure(val error: PayPalSDKError): UpdateClientConfigResult() +} + From 92936ec68de4a84e8c314d9f8aba233012ef605b Mon Sep 17 00:00:00 2001 From: sshropshire Date: Tue, 1 Apr 2025 14:36:55 -0500 Subject: [PATCH 03/10] Add CARD FundingSource type. --- Demo/src/main/res/values/strings.xml | 1 + .../android/paypalwebpayments/PayPalWebCheckoutClient.kt | 4 ++++ .../paypalwebpayments/PayPalWebCheckoutFundingSource.kt | 7 ++++++- 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/Demo/src/main/res/values/strings.xml b/Demo/src/main/res/values/strings.xml index 884afaa07..574747473 100644 --- a/Demo/src/main/res/values/strings.xml +++ b/Demo/src/main/res/values/strings.xml @@ -45,6 +45,7 @@ PAYPAL_CREDIT PAY_LATER PAYPAL + CARD diff --git a/PayPalWebPayments/src/main/java/com/paypal/android/paypalwebpayments/PayPalWebCheckoutClient.kt b/PayPalWebPayments/src/main/java/com/paypal/android/paypalwebpayments/PayPalWebCheckoutClient.kt index adaa99798..c4843db69 100644 --- a/PayPalWebPayments/src/main/java/com/paypal/android/paypalwebpayments/PayPalWebCheckoutClient.kt +++ b/PayPalWebPayments/src/main/java/com/paypal/android/paypalwebpayments/PayPalWebCheckoutClient.kt @@ -48,6 +48,10 @@ class PayPalWebCheckoutClient internal constructor( checkoutOrderId = request.orderId analytics.notify(CheckoutEvent.STARTED, checkoutOrderId) + if (request.fundingSource == PayPalWebCheckoutFundingSource.CARD) { + // TODO: update client config + } + val result = payPalWebLauncher.launchPayPalWebCheckout(activity, request) when (result) { is PayPalPresentAuthChallengeResult.Success -> analytics.notify( diff --git a/PayPalWebPayments/src/main/java/com/paypal/android/paypalwebpayments/PayPalWebCheckoutFundingSource.kt b/PayPalWebPayments/src/main/java/com/paypal/android/paypalwebpayments/PayPalWebCheckoutFundingSource.kt index 783150165..ea8451953 100644 --- a/PayPalWebPayments/src/main/java/com/paypal/android/paypalwebpayments/PayPalWebCheckoutFundingSource.kt +++ b/PayPalWebPayments/src/main/java/com/paypal/android/paypalwebpayments/PayPalWebCheckoutFundingSource.kt @@ -20,5 +20,10 @@ enum class PayPalWebCheckoutFundingSource(val value: String) { /** * PAYPAL will launch the web checkout for a one-time PayPal Checkout flow */ - PAYPAL("paypal") + PAYPAL("paypal"), + + /** + * TODO: add docstring + */ + CARD("card") } From 5f67a43488fcd757a69a34b11ef15e66124ac088 Mon Sep 17 00:00:00 2001 From: sshropshire Date: Tue, 1 Apr 2025 16:46:58 -0500 Subject: [PATCH 04/10] Add additional details for GraphQL call to update user context. --- .../android/corepayments/HttpRequest.kt | 2 +- .../corepayments/graphql/GraphQLClient.kt | 16 +++-- .../android/corepayments/HttpUnitTest.kt | 4 +- .../PayPalWebCheckoutClient.kt | 59 ++++++++++++++++- .../PayPalWebStartCallback.kt | 12 ++++ .../UpdateClientConfigAPI.kt | 65 ++++++++++++++++++- 6 files changed, 146 insertions(+), 12 deletions(-) create mode 100644 PayPalWebPayments/src/main/java/com/paypal/android/paypalwebpayments/PayPalWebStartCallback.kt diff --git a/CorePayments/src/main/java/com/paypal/android/corepayments/HttpRequest.kt b/CorePayments/src/main/java/com/paypal/android/corepayments/HttpRequest.kt index 6f8d991ad..0737b0295 100644 --- a/CorePayments/src/main/java/com/paypal/android/corepayments/HttpRequest.kt +++ b/CorePayments/src/main/java/com/paypal/android/corepayments/HttpRequest.kt @@ -6,5 +6,5 @@ internal data class HttpRequest( val url: URL, val method: HttpMethod = HttpMethod.GET, val body: String? = null, - val headers: MutableMap = mutableMapOf(), + val headers: Map = emptyMap(), ) diff --git a/CorePayments/src/main/java/com/paypal/android/corepayments/graphql/GraphQLClient.kt b/CorePayments/src/main/java/com/paypal/android/corepayments/graphql/GraphQLClient.kt index d5ad6c970..fea7642b8 100644 --- a/CorePayments/src/main/java/com/paypal/android/corepayments/graphql/GraphQLClient.kt +++ b/CorePayments/src/main/java/com/paypal/android/corepayments/graphql/GraphQLClient.kt @@ -29,17 +29,22 @@ class GraphQLClient internal constructor( private val graphQLEndpoint = coreConfig.environment.graphQLEndpoint private val graphQLURL = "$graphQLEndpoint/graphql" - private val httpRequestHeaders = mutableMapOf( + private val httpRequestHeaders = mapOf( "Content-Type" to "application/json", "Accept" to "application/json", "x-app-name" to "nativecheckout", "Origin" to coreConfig.environment.graphQLEndpoint ) - suspend fun send(graphQLRequestBody: JSONObject, queryName: String? = null): GraphQLResult { + suspend fun send( + graphQLRequestBody: JSONObject, + queryName: String? = null, + additionalHeaders: Map = emptyMap() + ): GraphQLResult { val body = graphQLRequestBody.toString() val urlString = if (queryName != null) "$graphQLURL?$queryName" else graphQLURL - val httpRequest = HttpRequest(URL(urlString), HttpMethod.POST, body, httpRequestHeaders) + val allHeaders = httpRequestHeaders + additionalHeaders + val httpRequest = HttpRequest(URL(urlString), HttpMethod.POST, body, allHeaders) val httpResponse = http.send(httpRequest) val correlationId: String? = httpResponse.headers[PAYPAL_DEBUG_ID] @@ -51,7 +56,10 @@ class GraphQLClient internal constructor( } else { try { val responseAsJSON = JSONObject(httpResponse.body) - GraphQLResult.Success(responseAsJSON.getJSONObject("data"), correlationId = correlationId) + GraphQLResult.Success( + responseAsJSON.getJSONObject("data"), + correlationId = correlationId + ) } catch (jsonParseError: JSONException) { val error = APIClientError.graphQLJSONParseError(correlationId, jsonParseError) GraphQLResult.Failure(error) diff --git a/CorePayments/src/test/java/com/paypal/android/corepayments/HttpUnitTest.kt b/CorePayments/src/test/java/com/paypal/android/corepayments/HttpUnitTest.kt index 1e89cd842..75859dee2 100644 --- a/CorePayments/src/test/java/com/paypal/android/corepayments/HttpUnitTest.kt +++ b/CorePayments/src/test/java/com/paypal/android/corepayments/HttpUnitTest.kt @@ -63,8 +63,8 @@ class HttpUnitTest { @Test fun `send sets request headers on url connection`() = runTest { - val httpRequest = HttpRequest(url, HttpMethod.GET) - httpRequest.headers["Sample-Header"] = "sample-value" + val headers = mapOf("Sample-Header" to "sample-value") + val httpRequest = HttpRequest(url, HttpMethod.GET, headers = headers) val sut = createHttp(testScheduler) sut.send(httpRequest) diff --git a/PayPalWebPayments/src/main/java/com/paypal/android/paypalwebpayments/PayPalWebCheckoutClient.kt b/PayPalWebPayments/src/main/java/com/paypal/android/paypalwebpayments/PayPalWebCheckoutClient.kt index c4843db69..9441c7af4 100644 --- a/PayPalWebPayments/src/main/java/com/paypal/android/paypalwebpayments/PayPalWebCheckoutClient.kt +++ b/PayPalWebPayments/src/main/java/com/paypal/android/paypalwebpayments/PayPalWebCheckoutClient.kt @@ -8,6 +8,10 @@ import com.paypal.android.corepayments.analytics.AnalyticsService import com.paypal.android.paypalwebpayments.analytics.CheckoutEvent import com.paypal.android.paypalwebpayments.analytics.PayPalWebAnalytics import com.paypal.android.paypalwebpayments.analytics.VaultEvent +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch // NEXT MAJOR VERSION: consider renaming this module to PayPalWebClient since // it now offers both checkout and vaulting @@ -17,7 +21,9 @@ import com.paypal.android.paypalwebpayments.analytics.VaultEvent */ class PayPalWebCheckoutClient internal constructor( private val analytics: PayPalWebAnalytics, - private val payPalWebLauncher: PayPalWebLauncher + private val updateClientConfigAPI: UpdateClientConfigAPI, + private val payPalWebLauncher: PayPalWebLauncher, + private val dispatcher: CoroutineDispatcher ) { // for analytics tracking @@ -33,7 +39,9 @@ class PayPalWebCheckoutClient internal constructor( */ constructor(context: Context, configuration: CoreConfig, urlScheme: String) : this( PayPalWebAnalytics(AnalyticsService(context.applicationContext, configuration)), + UpdateClientConfigAPI(context, configuration), PayPalWebLauncher(urlScheme, configuration), + dispatcher = Dispatchers.Main ) /** @@ -49,7 +57,7 @@ class PayPalWebCheckoutClient internal constructor( analytics.notify(CheckoutEvent.STARTED, checkoutOrderId) if (request.fundingSource == PayPalWebCheckoutFundingSource.CARD) { - // TODO: update client config + // TODO: consider returning an error immediately } val result = payPalWebLauncher.launchPayPalWebCheckout(activity, request) @@ -65,6 +73,53 @@ class PayPalWebCheckoutClient internal constructor( return result } + /** + * Confirm PayPal payment source for an order. + * + * @param request [PayPalWebCheckoutRequest] for requesting an order approval + */ + fun start( + activity: ComponentActivity, + request: PayPalWebCheckoutRequest, + callback: PayPalWebStartCallback + ) { + CoroutineScope(dispatcher).launch { + checkoutOrderId = request.orderId + analytics.notify(CheckoutEvent.STARTED, checkoutOrderId) + + if (request.fundingSource == PayPalWebCheckoutFundingSource.CARD) { + val updateConfigResult = request.run { + updateClientConfigAPI.updateClientConfig( + orderId = orderId, + fundingSource = fundingSource + ) + } + if (updateConfigResult is UpdateClientConfigResult.Failure) { + // notify failure + callback.onPayPalWebStartResult( + PayPalPresentAuthChallengeResult.Failure(updateConfigResult.error) + ) + return@launch + } + } + + val result = payPalWebLauncher.launchPayPalWebCheckout(activity, request) + when (result) { + is PayPalPresentAuthChallengeResult.Success -> analytics.notify( + CheckoutEvent.AUTH_CHALLENGE_PRESENTATION_SUCCEEDED, + checkoutOrderId + ) + + is PayPalPresentAuthChallengeResult.Failure -> + analytics.notify( + CheckoutEvent.AUTH_CHALLENGE_PRESENTATION_FAILED, + checkoutOrderId + ) + } + callback.onPayPalWebStartResult(result) + } + } + /** * Vault PayPal as a payment method. * diff --git a/PayPalWebPayments/src/main/java/com/paypal/android/paypalwebpayments/PayPalWebStartCallback.kt b/PayPalWebPayments/src/main/java/com/paypal/android/paypalwebpayments/PayPalWebStartCallback.kt new file mode 100644 index 000000000..4362d16e7 --- /dev/null +++ b/PayPalWebPayments/src/main/java/com/paypal/android/paypalwebpayments/PayPalWebStartCallback.kt @@ -0,0 +1,12 @@ +package com.paypal.android.paypalwebpayments + +import androidx.annotation.MainThread + +fun interface PayPalWebStartCallback { + + /** + * Called when the result of a PayPal web launch is available. + */ + @MainThread + fun onPayPalWebStartResult(result: PayPalPresentAuthChallengeResult) +} \ No newline at end of file diff --git a/PayPalWebPayments/src/main/java/com/paypal/android/paypalwebpayments/UpdateClientConfigAPI.kt b/PayPalWebPayments/src/main/java/com/paypal/android/paypalwebpayments/UpdateClientConfigAPI.kt index 65999f333..34e843c5f 100644 --- a/PayPalWebPayments/src/main/java/com/paypal/android/paypalwebpayments/UpdateClientConfigAPI.kt +++ b/PayPalWebPayments/src/main/java/com/paypal/android/paypalwebpayments/UpdateClientConfigAPI.kt @@ -7,6 +7,9 @@ import com.paypal.android.corepayments.LoadRawResourceResult import com.paypal.android.corepayments.PayPalSDKError import com.paypal.android.corepayments.ResourceLoader import com.paypal.android.corepayments.graphql.GraphQLClient +import com.paypal.android.corepayments.graphql.GraphQLResult +import org.json.JSONException +import org.json.JSONObject internal class UpdateClientConfigAPI( private val coreConfig: CoreConfig, @@ -22,11 +25,18 @@ internal class UpdateClientConfigAPI( ResourceLoader() ) - suspend fun updateClientConfig(): UpdateClientConfigResult { + suspend fun updateClientConfig( + orderId: String, + fundingSource: PayPalWebCheckoutFundingSource + ): UpdateClientConfigResult { @RawRes val resId = R.raw.graphql_query_update_client_config return when (val result = resourceLoader.loadRawResource(applicationContext, resId)) { is LoadRawResourceResult.Success -> - sendUpdateClientConfigGraphQLRequest(result.value) + sendUpdateClientConfigGraphQLRequest( + query = result.value, + orderId = orderId, + fundingSource = fundingSource + ) is LoadRawResourceResult.Failure -> UpdateClientConfigResult.Failure( PayPalSDKError(123, "TODO: implement") @@ -36,7 +46,56 @@ internal class UpdateClientConfigAPI( private suspend fun sendUpdateClientConfigGraphQLRequest( query: String, + orderId: String, + fundingSource: PayPalWebCheckoutFundingSource ): UpdateClientConfigResult { - return UpdateClientConfigResult.Failure(PayPalSDKError(123, "TODO: implement")) + val variables = JSONObject() + .put("orderID", orderId) + .put("fundingSource", fundingSource.value) + .put("integrationArtifact", "PAYPAL_JS_SDK") + .put("userExperienceFlow", "INCONTEXT") + .put("productFlow", "SMART_PAYMENT_BUTTONS") + .put("buttonSessionId", JSONObject.NULL) + + val graphQLRequest = JSONObject() + .put("query", query) + .put("variables", variables) + + val clientId = coreConfig.clientId + val graphQLResponse = graphQLClient.send( + graphQLRequestBody = graphQLRequest, + queryName = "UpdateClientConfig", + additionalHeaders = mapOf("paypal-client-context" to clientId) + ) + return when (graphQLResponse) { + is GraphQLResult.Success -> { + val responseJSON = graphQLResponse.data + if (responseJSON == null) { + val error = graphQLResponse.run { + val errorDescription = "Error updating client config: $errors" + PayPalSDKError(0, errorDescription, correlationId) + } + UpdateClientConfigResult.Failure(error) + } else { + parseSuccessfulUpdateSuccessJSON(responseJSON, graphQLResponse.correlationId) + } + } + + is GraphQLResult.Failure -> UpdateClientConfigResult.Failure(graphQLResponse.error) + } + } + + private fun parseSuccessfulUpdateSuccessJSON( + responseBody: JSONObject, + correlationId: String? + ): UpdateClientConfigResult { + return try { + val clientConfig = responseBody.getString("updateClientConfig") + UpdateClientConfigResult.Success(clientConfig) + } catch (jsonError: JSONException) { + val message = "Update Client Config Failed: GraphQL JSON body was invalid." + val error = PayPalSDKError(0, message, correlationId, reason = jsonError) + UpdateClientConfigResult.Failure(error) + } } } \ No newline at end of file From 7631978e301ee41d6c82d1a0b44fe71df9e2c058 Mon Sep 17 00:00:00 2001 From: sshropshire Date: Tue, 1 Apr 2025 16:54:58 -0500 Subject: [PATCH 05/10] Update GraphQL parsing logic. --- .../paypal/android/paypalwebpayments/UpdateClientConfigAPI.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PayPalWebPayments/src/main/java/com/paypal/android/paypalwebpayments/UpdateClientConfigAPI.kt b/PayPalWebPayments/src/main/java/com/paypal/android/paypalwebpayments/UpdateClientConfigAPI.kt index 34e843c5f..10a03a3fe 100644 --- a/PayPalWebPayments/src/main/java/com/paypal/android/paypalwebpayments/UpdateClientConfigAPI.kt +++ b/PayPalWebPayments/src/main/java/com/paypal/android/paypalwebpayments/UpdateClientConfigAPI.kt @@ -90,7 +90,7 @@ internal class UpdateClientConfigAPI( correlationId: String? ): UpdateClientConfigResult { return try { - val clientConfig = responseBody.getString("updateClientConfig") + val clientConfig = responseBody.optString("updateClientConfig", "") UpdateClientConfigResult.Success(clientConfig) } catch (jsonError: JSONException) { val message = "Update Client Config Failed: GraphQL JSON body was invalid." From e44bfeed31d3738224647b052959d4b83a4facf9 Mon Sep 17 00:00:00 2001 From: sshropshire Date: Wed, 2 Apr 2025 11:04:54 -0500 Subject: [PATCH 06/10] Implement Card logo. --- .../components/ActionPaymentButtonColumn.kt | 110 ++++++++ .../uishared/enums/DemoPaymentButtonType.kt | 6 + .../android/paymentbuttons/CardButton.kt | 235 ++++++++++++++++++ .../src/main/res/drawable/card_black.xml | 12 + .../src/main/res/drawable/card_white.xml | 12 + 5 files changed, 375 insertions(+) create mode 100644 Demo/src/main/java/com/paypal/android/uishared/components/ActionPaymentButtonColumn.kt create mode 100644 Demo/src/main/java/com/paypal/android/uishared/enums/DemoPaymentButtonType.kt create mode 100644 PaymentButtons/src/main/java/com/paypal/android/paymentbuttons/CardButton.kt create mode 100644 PaymentButtons/src/main/res/drawable/card_black.xml create mode 100644 PaymentButtons/src/main/res/drawable/card_white.xml diff --git a/Demo/src/main/java/com/paypal/android/uishared/components/ActionPaymentButtonColumn.kt b/Demo/src/main/java/com/paypal/android/uishared/components/ActionPaymentButtonColumn.kt new file mode 100644 index 000000000..30dfb48b2 --- /dev/null +++ b/Demo/src/main/java/com/paypal/android/uishared/components/ActionPaymentButtonColumn.kt @@ -0,0 +1,110 @@ +package com.paypal.android.uishared.components + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Card +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.viewinterop.AndroidView +import com.paypal.android.paymentbuttons.CardButton +import com.paypal.android.paymentbuttons.CardButtonLabel +import com.paypal.android.paymentbuttons.PayPalButton +import com.paypal.android.paymentbuttons.PayPalButtonColor +import com.paypal.android.paymentbuttons.PayPalButtonLabel +import com.paypal.android.paymentbuttons.PaymentButtonSize +import com.paypal.android.uishared.enums.DemoPaymentButtonType +import com.paypal.android.uishared.state.ActionState +import com.paypal.android.uishared.state.CompletedActionState +import com.paypal.android.utils.UIConstants + +@Composable +fun ActionPaymentButtonColumn( + type: DemoPaymentButtonType, + state: ActionState, + onClick: () -> Unit, + modifier: Modifier = Modifier, + content: @Composable (CompletedActionState) -> Unit = {}, +) { + Card( + modifier = modifier + ) { + DemoPaymentButton( + type = type, + onClick = { + if (state is ActionState.Idle) { + onClick() + } + }, + modifier = Modifier.fillMaxWidth() + ) + + // optional content + val completedState = when (state) { + is ActionState.Success -> CompletedActionState.Success(state.value) + is ActionState.Failure -> CompletedActionState.Failure(state.value) + else -> null + } + completedState?.let { + content(completedState) + } + } +} + +@Composable +fun DemoPaymentButton( + type: DemoPaymentButtonType, + onClick: () -> Unit, + modifier: Modifier = Modifier +) { + when (type) { + DemoPaymentButtonType.PAYPAL -> AndroidView( + factory = { context -> + PayPalButton(context).apply { setOnClickListener { onClick() } } + }, + update = { button -> + button.color = PayPalButtonColor.BLUE + button.label = PayPalButtonLabel.PAY + button.size = PaymentButtonSize.LARGE + }, + modifier = modifier + ) + + DemoPaymentButtonType.CARD -> AndroidView( + factory = { context -> + CardButton(context).apply { setOnClickListener { onClick() } } + }, + update = { button -> + button.label = CardButtonLabel.PAY + }, + modifier = modifier + ) + } +} + +@Preview +@Composable +fun StatefulActionPaymentButtonPreview() { + MaterialTheme { + Surface(modifier = Modifier.fillMaxSize()) { + Column { + ActionPaymentButtonColumn( + type = DemoPaymentButtonType.CARD, + state = ActionState.Idle, + onClick = {}, + modifier = Modifier + .fillMaxWidth() + .padding(UIConstants.paddingMedium) + ) { state -> + Text(text = "Sample Text", modifier = Modifier.padding(64.dp)) + } + } + } + } +} diff --git a/Demo/src/main/java/com/paypal/android/uishared/enums/DemoPaymentButtonType.kt b/Demo/src/main/java/com/paypal/android/uishared/enums/DemoPaymentButtonType.kt new file mode 100644 index 000000000..df22c3155 --- /dev/null +++ b/Demo/src/main/java/com/paypal/android/uishared/enums/DemoPaymentButtonType.kt @@ -0,0 +1,6 @@ +package com.paypal.android.uishared.enums + +enum class DemoPaymentButtonType { + PAYPAL, + CARD +} \ No newline at end of file diff --git a/PaymentButtons/src/main/java/com/paypal/android/paymentbuttons/CardButton.kt b/PaymentButtons/src/main/java/com/paypal/android/paymentbuttons/CardButton.kt new file mode 100644 index 000000000..5818cf803 --- /dev/null +++ b/PaymentButtons/src/main/java/com/paypal/android/paymentbuttons/CardButton.kt @@ -0,0 +1,235 @@ +package com.paypal.android.paymentbuttons + +import android.content.Context +import android.content.res.TypedArray +import android.util.AttributeSet +import android.view.View +import androidx.core.content.res.use +import com.paypal.android.paymentbuttons.error.createFormattedIllegalArgumentException +import com.paypal.android.ui.R + +/** + * PayPalButton provides a PayPal button with the ability to modify the [color], [label], [shape], + * and [size]. + * + * Setting up PayPalButton within an XML layout: + * ``` + * + * ``` + * + * Optionally you can provide the following attributes: `paypal_color`, `paypal_label`, + * `payment_button_shape`, and `payment_button_size`. + * + */ +open class CardButton @JvmOverloads constructor( + context: Context, + attributeSet: AttributeSet? = null, + defStyleAttr: Int = 0 +) : PaymentButton(context, attributeSet, defStyleAttr) { + + /** + * Updates the color of the Payment Button with the provided [CardButtonColor]. + * + * This may update the PayPal wordmark to aid with visibility as well. When updated to GOLD or + * WHITE it will be updated to the traditional wordmark. When updated to BLUE or BLACK it will + * be updated to the monochrome wordmark. + */ + override var color: PaymentButtonColor = CardButtonColor.BLACK + set(value) { + field = value + updateShapeDrawableFillColor(field) + } + + /** + * Updates the label for Payment Button with the provided [CardButtonLabel]. + * + * This will default to [PAYPAL] if one is not provided which omits a label and only displays + * the wordmark. Note: this does not support [PAY_LATER], if you require a button with that + * label then use the specialized [PayLaterButton]. + */ + open var label: CardButtonLabel = CardButtonLabel.CHECKOUT + set(value) { + if (value != CardButtonLabel.PAY_LATER) { + field = value + updateLabel(field) + } + } + + override val wordmarkDarkLuminanceResId: Int = R.drawable.card_white + + override val wordmarkLightLuminanceResId: Int = R.drawable.card_black + + override val fundingType: PaymentButtonFundingType = PaymentButtonFundingType.PAYPAL + + init { + contentDescription = context.getString(R.string.paypal_payment_button_description) + analyticsService.sendAnalyticsEvent( + "payment-button:initialized", + orderId = null, + buttonType = PaymentButtonFundingType.PAYPAL.buttonType + ) + } + + private fun updateColorFrom(typedArray: TypedArray) { + val paypalColorAttribute = typedArray.getInt( + R.styleable.PayPalButton_paypal_color, + CardButtonColor.GOLD.value + ) + color = CardButtonColor(paypalColorAttribute) + } + + private fun updateLabelFrom(typedArray: TypedArray) { + val paypalLabelAttribute = typedArray.getInt( + R.styleable.PayPalButton_paypal_label, 0 + ) + label = CardButtonLabel(paypalLabelAttribute) + } + + protected fun updateLabel(updatedLabel: CardButtonLabel) { + // simulate payment label at the end + prefixTextVisibility = View.GONE + suffixTextVisibility = View.VISIBLE + suffixText = "Pay with Card" + } +} + +/** + * Defines the colors available for PayPal buttons. + * + * @see GOLD is the default color if one is not provided and is the recommended choice as research + * has shown it results in the best conversion. + * @see BLUE is the preferred alternative color if gold does not work for your experience. Research + * has shown that people know it is our brand color, which provides a halo of trust and security to + * your experience. + * @see WHITE is one of our secondary alternatives. This color is less capable of drawing people's + * attention. + * @see BLACK is one of our secondary alternatives. This color is less capable of drawing people's + * attention. + * @see SILVER is one of our secondary alternatives. This color is less capable of drawing people's + * attention. + */ +enum class CardButtonColor( + val value: Int, + override val colorResId: Int, + override val hasOutline: Boolean = false, + override val luminance: PaymentButtonColorLuminance +) : PaymentButtonColor { + GOLD(value = 0, colorResId = R.color.paypal_gold, luminance = PaymentButtonColorLuminance.LIGHT), + BLUE(value = 1, colorResId = R.color.paypal_blue, luminance = PaymentButtonColorLuminance.DARK), + WHITE( + value = 2, + colorResId = R.color.paypal_white, + hasOutline = true, + luminance = PaymentButtonColorLuminance.LIGHT + ), + BLACK(value = 3, colorResId = R.color.paypal_black, luminance = PaymentButtonColorLuminance.DARK), + SILVER(value = 4, colorResId = R.color.paypal_silver, luminance = PaymentButtonColorLuminance.LIGHT); + + companion object { + /** + * Given an [attributeIndex] this will provide the correct [CardButtonColor]. If an + * invalid [attributeIndex] is provided then it will throw an [IllegalStateException]. + * + * @throws [IllegalArgumentException] when an invalid index is provided. + */ + operator fun invoke(attributeIndex: Int): CardButtonColor { + return when (attributeIndex) { + GOLD.value -> GOLD + BLUE.value -> BLUE + WHITE.value -> WHITE + BLACK.value -> BLACK + SILVER.value -> SILVER + else -> throw createFormattedIllegalArgumentException("CardButtonColor", values().size) + } + } + } +} + +/** + * Defines the labels available for payment buttons. If no label is provided then it will + * default to [PAYPAL] which will not display a label and will only display the PayPal wordmark. For + * other labels they will have the label value itself along with a position either at the start or + * the end of the button. + * + */ +enum class CardButtonLabel( + val value: Int, + val position: Position? = null, + private val stringResId: Int? = null +) { + /** + * Label for PayPal text + */ + PAYPAL(value = 0), + + /** + * Label for Checkout text + */ + CHECKOUT( + value = 1, + position = Position.END, + stringResId = R.string.paypal_checkout_smart_payment_button_label_checkout + ), + + /** + * Label for Buy Now text + */ + BUY_NOW( + value = 2, + position = Position.END, + stringResId = R.string.paypal_checkout_smart_payment_button_label_buy_now + ), + + /** + * Label for Pay text + */ + PAY( + value = 3, + position = Position.START, + stringResId = R.string.paypal_checkout_smart_payment_button_label_pay + ), + + /** + * Label for Pay Later text + */ + PAY_LATER( + value = 4, + position = Position.END, + stringResId = R.string.paypal_checkout_smart_payment_button_label_pay_later + ); + + fun retrieveLabel(context: Context): String? { + return stringResId?.let { context.getString(it) } + } + + /** + * Defines at what position a label is displayed. A label can either be positioned at the start + * or end of a button. + */ + enum class Position { + START, + END; + } + + companion object { + /** + * Given an [attributeIndex] this will provide the correct [CardButtonLabel]. + * If an invalid [attributeIndex] is provided then it will throw an [IllegalArgumentException]. + * + * @throws [IllegalArgumentException] when an invalid index is provided. + */ + operator fun invoke(attributeIndex: Int): CardButtonLabel { + return when (attributeIndex) { + PAYPAL.value -> PAYPAL + CHECKOUT.value -> CHECKOUT + BUY_NOW.value -> BUY_NOW + PAY.value -> PAY + PAY_LATER.value -> PAY_LATER + else -> throw createFormattedIllegalArgumentException("PaymentButtonLabel", values().size) + } + } + } +} diff --git a/PaymentButtons/src/main/res/drawable/card_black.xml b/PaymentButtons/src/main/res/drawable/card_black.xml new file mode 100644 index 000000000..1ad9f4e5d --- /dev/null +++ b/PaymentButtons/src/main/res/drawable/card_black.xml @@ -0,0 +1,12 @@ + + + diff --git a/PaymentButtons/src/main/res/drawable/card_white.xml b/PaymentButtons/src/main/res/drawable/card_white.xml new file mode 100644 index 000000000..120c97881 --- /dev/null +++ b/PaymentButtons/src/main/res/drawable/card_white.xml @@ -0,0 +1,12 @@ + + + From 3a4c0f0fa722239859eac0acfcb6bb09ca5fbee8 Mon Sep 17 00:00:00 2001 From: sshropshire Date: Wed, 2 Apr 2025 11:11:18 -0500 Subject: [PATCH 07/10] Render Pay with Card button on PayPal view. --- .../android/ui/paypalweb/PayPalWebView.kt | 6 ++-- .../components/ActionPaymentButtonColumn.kt | 28 ++++++++++--------- .../uishared/enums/DemoPaymentButtonType.kt | 6 ---- 3 files changed, 18 insertions(+), 22 deletions(-) delete mode 100644 Demo/src/main/java/com/paypal/android/uishared/enums/DemoPaymentButtonType.kt diff --git a/Demo/src/main/java/com/paypal/android/ui/paypalweb/PayPalWebView.kt b/Demo/src/main/java/com/paypal/android/ui/paypalweb/PayPalWebView.kt index e805facfe..872bacc1a 100644 --- a/Demo/src/main/java/com/paypal/android/ui/paypalweb/PayPalWebView.kt +++ b/Demo/src/main/java/com/paypal/android/ui/paypalweb/PayPalWebView.kt @@ -19,6 +19,7 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.paypal.android.uishared.components.ActionButtonColumn +import com.paypal.android.uishared.components.ActionPaymentButtonColumn import com.paypal.android.uishared.components.CreateOrderForm import com.paypal.android.uishared.components.ErrorView import com.paypal.android.uishared.components.OrderView @@ -106,9 +107,8 @@ private fun Step2_StartPayPalWebCheckout(uiState: PayPalWebUiState, viewModel: P fundingSource = uiState.fundingSource, onFundingSourceChange = { value -> viewModel.fundingSource = value }, ) - ActionButtonColumn( - defaultTitle = "START CHECKOUT", - successTitle = "CHECKOUT COMPLETE", + ActionPaymentButtonColumn( + fundingSource = uiState.fundingSource, state = uiState.payPalWebCheckoutState, onClick = { context.getActivityOrNull()?.let { viewModel.startWebCheckout(it) } }, modifier = Modifier diff --git a/Demo/src/main/java/com/paypal/android/uishared/components/ActionPaymentButtonColumn.kt b/Demo/src/main/java/com/paypal/android/uishared/components/ActionPaymentButtonColumn.kt index 30dfb48b2..e1a8ad215 100644 --- a/Demo/src/main/java/com/paypal/android/uishared/components/ActionPaymentButtonColumn.kt +++ b/Demo/src/main/java/com/paypal/android/uishared/components/ActionPaymentButtonColumn.kt @@ -19,14 +19,14 @@ import com.paypal.android.paymentbuttons.PayPalButton import com.paypal.android.paymentbuttons.PayPalButtonColor import com.paypal.android.paymentbuttons.PayPalButtonLabel import com.paypal.android.paymentbuttons.PaymentButtonSize -import com.paypal.android.uishared.enums.DemoPaymentButtonType +import com.paypal.android.paypalwebpayments.PayPalWebCheckoutFundingSource import com.paypal.android.uishared.state.ActionState import com.paypal.android.uishared.state.CompletedActionState import com.paypal.android.utils.UIConstants @Composable fun ActionPaymentButtonColumn( - type: DemoPaymentButtonType, + fundingSource: PayPalWebCheckoutFundingSource, state: ActionState, onClick: () -> Unit, modifier: Modifier = Modifier, @@ -36,7 +36,7 @@ fun ActionPaymentButtonColumn( modifier = modifier ) { DemoPaymentButton( - type = type, + fundingSource = fundingSource, onClick = { if (state is ActionState.Idle) { onClick() @@ -59,29 +59,31 @@ fun ActionPaymentButtonColumn( @Composable fun DemoPaymentButton( - type: DemoPaymentButtonType, + fundingSource: PayPalWebCheckoutFundingSource, onClick: () -> Unit, modifier: Modifier = Modifier ) { - when (type) { - DemoPaymentButtonType.PAYPAL -> AndroidView( + when (fundingSource) { + + PayPalWebCheckoutFundingSource.CARD -> AndroidView( factory = { context -> - PayPalButton(context).apply { setOnClickListener { onClick() } } + CardButton(context).apply { setOnClickListener { onClick() } } }, update = { button -> - button.color = PayPalButtonColor.BLUE - button.label = PayPalButtonLabel.PAY + button.label = CardButtonLabel.PAY button.size = PaymentButtonSize.LARGE }, modifier = modifier ) - DemoPaymentButtonType.CARD -> AndroidView( + else -> AndroidView( factory = { context -> - CardButton(context).apply { setOnClickListener { onClick() } } + PayPalButton(context).apply { setOnClickListener { onClick() } } }, update = { button -> - button.label = CardButtonLabel.PAY + button.color = PayPalButtonColor.BLUE + button.label = PayPalButtonLabel.PAY + button.size = PaymentButtonSize.LARGE }, modifier = modifier ) @@ -95,7 +97,7 @@ fun StatefulActionPaymentButtonPreview() { Surface(modifier = Modifier.fillMaxSize()) { Column { ActionPaymentButtonColumn( - type = DemoPaymentButtonType.CARD, + fundingSource = PayPalWebCheckoutFundingSource.CARD, state = ActionState.Idle, onClick = {}, modifier = Modifier diff --git a/Demo/src/main/java/com/paypal/android/uishared/enums/DemoPaymentButtonType.kt b/Demo/src/main/java/com/paypal/android/uishared/enums/DemoPaymentButtonType.kt deleted file mode 100644 index df22c3155..000000000 --- a/Demo/src/main/java/com/paypal/android/uishared/enums/DemoPaymentButtonType.kt +++ /dev/null @@ -1,6 +0,0 @@ -package com.paypal.android.uishared.enums - -enum class DemoPaymentButtonType { - PAYPAL, - CARD -} \ No newline at end of file From 776bb748aefe6bb2763f20f884b349ffe3083be8 Mon Sep 17 00:00:00 2001 From: sshropshire Date: Wed, 9 Apr 2025 14:50:35 -0500 Subject: [PATCH 08/10] Clean up detekt errors. --- .../android/paypalwebpayments/PayPalWebStartCallback.kt | 2 +- .../android/paypalwebpayments/UpdateClientConfigAPI.kt | 4 ++-- .../paypalwebpayments/UpdateClientConfigResult.kt | 5 ++--- .../paypalwebpayments/PayPalWebCheckoutClientUnitTest.kt | 9 ++++++++- 4 files changed, 13 insertions(+), 7 deletions(-) diff --git a/PayPalWebPayments/src/main/java/com/paypal/android/paypalwebpayments/PayPalWebStartCallback.kt b/PayPalWebPayments/src/main/java/com/paypal/android/paypalwebpayments/PayPalWebStartCallback.kt index 4362d16e7..76df40d5a 100644 --- a/PayPalWebPayments/src/main/java/com/paypal/android/paypalwebpayments/PayPalWebStartCallback.kt +++ b/PayPalWebPayments/src/main/java/com/paypal/android/paypalwebpayments/PayPalWebStartCallback.kt @@ -9,4 +9,4 @@ fun interface PayPalWebStartCallback { */ @MainThread fun onPayPalWebStartResult(result: PayPalPresentAuthChallengeResult) -} \ No newline at end of file +} diff --git a/PayPalWebPayments/src/main/java/com/paypal/android/paypalwebpayments/UpdateClientConfigAPI.kt b/PayPalWebPayments/src/main/java/com/paypal/android/paypalwebpayments/UpdateClientConfigAPI.kt index 10a03a3fe..d33de6b3f 100644 --- a/PayPalWebPayments/src/main/java/com/paypal/android/paypalwebpayments/UpdateClientConfigAPI.kt +++ b/PayPalWebPayments/src/main/java/com/paypal/android/paypalwebpayments/UpdateClientConfigAPI.kt @@ -39,7 +39,7 @@ internal class UpdateClientConfigAPI( ) is LoadRawResourceResult.Failure -> UpdateClientConfigResult.Failure( - PayPalSDKError(123, "TODO: implement") + PayPalSDKError(0, "TODO: implement") ) } } @@ -98,4 +98,4 @@ internal class UpdateClientConfigAPI( UpdateClientConfigResult.Failure(error) } } -} \ No newline at end of file +} diff --git a/PayPalWebPayments/src/main/java/com/paypal/android/paypalwebpayments/UpdateClientConfigResult.kt b/PayPalWebPayments/src/main/java/com/paypal/android/paypalwebpayments/UpdateClientConfigResult.kt index 86fb9e3f6..521110fb6 100644 --- a/PayPalWebPayments/src/main/java/com/paypal/android/paypalwebpayments/UpdateClientConfigResult.kt +++ b/PayPalWebPayments/src/main/java/com/paypal/android/paypalwebpayments/UpdateClientConfigResult.kt @@ -4,7 +4,6 @@ import com.paypal.android.corepayments.PayPalSDKError sealed class UpdateClientConfigResult { - data class Success(val clientConfig: String): UpdateClientConfigResult() - data class Failure(val error: PayPalSDKError): UpdateClientConfigResult() + data class Success(val clientConfig: String) : UpdateClientConfigResult() + data class Failure(val error: PayPalSDKError) : UpdateClientConfigResult() } - diff --git a/PayPalWebPayments/src/test/java/com/paypal/android/paypalwebpayments/PayPalWebCheckoutClientUnitTest.kt b/PayPalWebPayments/src/test/java/com/paypal/android/paypalwebpayments/PayPalWebCheckoutClientUnitTest.kt index 6157f79a9..56effe635 100644 --- a/PayPalWebPayments/src/test/java/com/paypal/android/paypalwebpayments/PayPalWebCheckoutClientUnitTest.kt +++ b/PayPalWebPayments/src/test/java/com/paypal/android/paypalwebpayments/PayPalWebCheckoutClientUnitTest.kt @@ -9,6 +9,7 @@ import io.mockk.mockk import io.mockk.verify import junit.framework.TestCase.assertSame import junit.framework.TestCase.assertTrue +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi import org.junit.Before import org.junit.Test @@ -21,6 +22,7 @@ class PayPalWebCheckoutClientUnitTest { private val activity: FragmentActivity = mockk(relaxed = true) private val analytics = mockk(relaxed = true) + private val updateClientConfigAPI = mockk(relaxed = true) private val intent = Intent() @@ -30,7 +32,12 @@ class PayPalWebCheckoutClientUnitTest { @Before fun beforeEach() { payPalWebLauncher = mockk(relaxed = true) - sut = PayPalWebCheckoutClient(analytics, payPalWebLauncher) + sut = PayPalWebCheckoutClient( + analytics = analytics, + updateClientConfigAPI = updateClientConfigAPI, + payPalWebLauncher = payPalWebLauncher, + dispatcher = Dispatchers.Main + ) } @Test From 30b12929bd7f51f10b2167881d7af1ae1fad8143 Mon Sep 17 00:00:00 2001 From: sshropshire Date: Wed, 23 Apr 2025 10:38:29 -0500 Subject: [PATCH 09/10] Update UpdateClientConfigAPI call parameters to match mobile sdk. --- .../PayPalWebCheckoutClient.kt | 3 +-- .../paypalwebpayments/UpdateClientConfigAPI.kt | 17 ++++++----------- 2 files changed, 7 insertions(+), 13 deletions(-) diff --git a/PayPalWebPayments/src/main/java/com/paypal/android/paypalwebpayments/PayPalWebCheckoutClient.kt b/PayPalWebPayments/src/main/java/com/paypal/android/paypalwebpayments/PayPalWebCheckoutClient.kt index 9441c7af4..03cc44d88 100644 --- a/PayPalWebPayments/src/main/java/com/paypal/android/paypalwebpayments/PayPalWebCheckoutClient.kt +++ b/PayPalWebPayments/src/main/java/com/paypal/android/paypalwebpayments/PayPalWebCheckoutClient.kt @@ -90,8 +90,7 @@ class PayPalWebCheckoutClient internal constructor( if (request.fundingSource == PayPalWebCheckoutFundingSource.CARD) { val updateConfigResult = request.run { updateClientConfigAPI.updateClientConfig( - orderId = orderId, - fundingSource = fundingSource + orderId = orderId ) } if (updateConfigResult is UpdateClientConfigResult.Failure) { diff --git a/PayPalWebPayments/src/main/java/com/paypal/android/paypalwebpayments/UpdateClientConfigAPI.kt b/PayPalWebPayments/src/main/java/com/paypal/android/paypalwebpayments/UpdateClientConfigAPI.kt index d33de6b3f..b24771da8 100644 --- a/PayPalWebPayments/src/main/java/com/paypal/android/paypalwebpayments/UpdateClientConfigAPI.kt +++ b/PayPalWebPayments/src/main/java/com/paypal/android/paypalwebpayments/UpdateClientConfigAPI.kt @@ -25,17 +25,13 @@ internal class UpdateClientConfigAPI( ResourceLoader() ) - suspend fun updateClientConfig( - orderId: String, - fundingSource: PayPalWebCheckoutFundingSource - ): UpdateClientConfigResult { + suspend fun updateClientConfig(orderId: String): UpdateClientConfigResult { @RawRes val resId = R.raw.graphql_query_update_client_config return when (val result = resourceLoader.loadRawResource(applicationContext, resId)) { is LoadRawResourceResult.Success -> sendUpdateClientConfigGraphQLRequest( query = result.value, - orderId = orderId, - fundingSource = fundingSource + orderId = orderId ) is LoadRawResourceResult.Failure -> UpdateClientConfigResult.Failure( @@ -46,15 +42,14 @@ internal class UpdateClientConfigAPI( private suspend fun sendUpdateClientConfigGraphQLRequest( query: String, - orderId: String, - fundingSource: PayPalWebCheckoutFundingSource + orderId: String ): UpdateClientConfigResult { val variables = JSONObject() .put("orderID", orderId) - .put("fundingSource", fundingSource.value) - .put("integrationArtifact", "PAYPAL_JS_SDK") + .put("fundingSource", "card") + .put("integrationArtifact", "MOBILE_SDK") .put("userExperienceFlow", "INCONTEXT") - .put("productFlow", "SMART_PAYMENT_BUTTONS") + .put("productFlow", "MOBILE_SDK") .put("buttonSessionId", JSONObject.NULL) val graphQLRequest = JSONObject() From ec73f11d75c389153922bdba05397482bfd9f112 Mon Sep 17 00:00:00 2001 From: sshropshire Date: Wed, 23 Apr 2025 13:08:04 -0500 Subject: [PATCH 10/10] Revert "Update UpdateClientConfigAPI call parameters to match mobile sdk." This reverts commit d7d02aa3d934cdcd9b415950eca0c156c750d4b2. --- .../PayPalWebCheckoutClient.kt | 3 ++- .../paypalwebpayments/UpdateClientConfigAPI.kt | 17 +++++++++++------ 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/PayPalWebPayments/src/main/java/com/paypal/android/paypalwebpayments/PayPalWebCheckoutClient.kt b/PayPalWebPayments/src/main/java/com/paypal/android/paypalwebpayments/PayPalWebCheckoutClient.kt index 03cc44d88..9441c7af4 100644 --- a/PayPalWebPayments/src/main/java/com/paypal/android/paypalwebpayments/PayPalWebCheckoutClient.kt +++ b/PayPalWebPayments/src/main/java/com/paypal/android/paypalwebpayments/PayPalWebCheckoutClient.kt @@ -90,7 +90,8 @@ class PayPalWebCheckoutClient internal constructor( if (request.fundingSource == PayPalWebCheckoutFundingSource.CARD) { val updateConfigResult = request.run { updateClientConfigAPI.updateClientConfig( - orderId = orderId + orderId = orderId, + fundingSource = fundingSource ) } if (updateConfigResult is UpdateClientConfigResult.Failure) { diff --git a/PayPalWebPayments/src/main/java/com/paypal/android/paypalwebpayments/UpdateClientConfigAPI.kt b/PayPalWebPayments/src/main/java/com/paypal/android/paypalwebpayments/UpdateClientConfigAPI.kt index b24771da8..d33de6b3f 100644 --- a/PayPalWebPayments/src/main/java/com/paypal/android/paypalwebpayments/UpdateClientConfigAPI.kt +++ b/PayPalWebPayments/src/main/java/com/paypal/android/paypalwebpayments/UpdateClientConfigAPI.kt @@ -25,13 +25,17 @@ internal class UpdateClientConfigAPI( ResourceLoader() ) - suspend fun updateClientConfig(orderId: String): UpdateClientConfigResult { + suspend fun updateClientConfig( + orderId: String, + fundingSource: PayPalWebCheckoutFundingSource + ): UpdateClientConfigResult { @RawRes val resId = R.raw.graphql_query_update_client_config return when (val result = resourceLoader.loadRawResource(applicationContext, resId)) { is LoadRawResourceResult.Success -> sendUpdateClientConfigGraphQLRequest( query = result.value, - orderId = orderId + orderId = orderId, + fundingSource = fundingSource ) is LoadRawResourceResult.Failure -> UpdateClientConfigResult.Failure( @@ -42,14 +46,15 @@ internal class UpdateClientConfigAPI( private suspend fun sendUpdateClientConfigGraphQLRequest( query: String, - orderId: String + orderId: String, + fundingSource: PayPalWebCheckoutFundingSource ): UpdateClientConfigResult { val variables = JSONObject() .put("orderID", orderId) - .put("fundingSource", "card") - .put("integrationArtifact", "MOBILE_SDK") + .put("fundingSource", fundingSource.value) + .put("integrationArtifact", "PAYPAL_JS_SDK") .put("userExperienceFlow", "INCONTEXT") - .put("productFlow", "MOBILE_SDK") + .put("productFlow", "SMART_PAYMENT_BUTTONS") .put("buttonSessionId", JSONObject.NULL) val graphQLRequest = JSONObject()