From 0a5f24ab4170903a2e47a363729b3b79154b2af0 Mon Sep 17 00:00:00 2001 From: Andrey Date: Tue, 28 Jan 2025 09:36:07 +0100 Subject: [PATCH 1/5] Added a test --- .../MockedStack_WCInPersonPaymentsTest.kt | 17 +++++++++++++++++ ...inal-payment-response-amount-too-small.json | 12 ++++++++++++ .../WCCapturePaymentResponsePayload.kt | 18 +++++++++++------- 3 files changed, 40 insertions(+), 7 deletions(-) create mode 100644 example/src/androidTest/resources/wc-pay-capture-terminal-payment-response-amount-too-small.json diff --git a/example/src/androidTest/java/org/wordpress/android/fluxc/mocked/MockedStack_WCInPersonPaymentsTest.kt b/example/src/androidTest/java/org/wordpress/android/fluxc/mocked/MockedStack_WCInPersonPaymentsTest.kt index 3001a13d73..833c081e9c 100644 --- a/example/src/androidTest/java/org/wordpress/android/fluxc/mocked/MockedStack_WCInPersonPaymentsTest.kt +++ b/example/src/androidTest/java/org/wordpress/android/fluxc/mocked/MockedStack_WCInPersonPaymentsTest.kt @@ -10,6 +10,7 @@ import org.junit.Assert.assertThat import org.junit.Assert.assertTrue import org.junit.Test import org.wordpress.android.fluxc.model.SiteModel +import org.wordpress.android.fluxc.model.payments.inperson.WCCapturePaymentErrorType import org.wordpress.android.fluxc.model.payments.inperson.WCCapturePaymentErrorType.CAPTURE_ERROR import org.wordpress.android.fluxc.model.payments.inperson.WCCapturePaymentErrorType.MISSING_ORDER import org.wordpress.android.fluxc.model.payments.inperson.WCCapturePaymentErrorType.PAYMENT_ALREADY_CAPTURED @@ -123,6 +124,22 @@ class MockedStack_InPersonPaymentsTest : MockedStack_Base() { assertTrue(result.error?.type == SERVER_ERROR) } + @Test + fun whenAmountTooSmallErrorThenAmountTooSmallErrorReturned() = runBlocking { + interceptor.respondWithError("wc-pay-capture-terminal-payment-response-amount-too-small.json", 400) + + val result = restClient.capturePayment( + WOOCOMMERCE_PAYMENTS, + testSite, + DUMMY_PAYMENT_ID, + -10L + ) + + val type = result.error?.type as WCCapturePaymentErrorType.AMOUNT_TOO_SMALL + assertTrue(type.currency == "USD") + assertTrue(type.minAllowedAmountInStripeMinorUnit == 50L) + } + @Test fun whenLoadAccountInvalidStatusThenFallbacksToUnknown() = runBlocking { interceptor.respondWith("wc-pay-load-account-response-new-status.json") diff --git a/example/src/androidTest/resources/wc-pay-capture-terminal-payment-response-amount-too-small.json b/example/src/androidTest/resources/wc-pay-capture-terminal-payment-response-amount-too-small.json new file mode 100644 index 0000000000..13863c43b1 --- /dev/null +++ b/example/src/androidTest/resources/wc-pay-capture-terminal-payment-response-amount-too-small.json @@ -0,0 +1,12 @@ +{ + "code": "wcpay_capture_error", + "message": "Payment capture failed to complete with the following message: Minimum required amount was not reached. The minimum amount to capture is 0.5 USD.", + "data": { + "status": 400, + "extra_details": { + "minimum_amount": 50, + "minimum_amount_currency": "USD" + }, + "error_type": "amount_too_small" + } +} \ No newline at end of file diff --git a/plugins/woocommerce/src/main/kotlin/org/wordpress/android/fluxc/model/payments/inperson/WCCapturePaymentResponsePayload.kt b/plugins/woocommerce/src/main/kotlin/org/wordpress/android/fluxc/model/payments/inperson/WCCapturePaymentResponsePayload.kt index 3ef672faf0..dcbfc4e57e 100644 --- a/plugins/woocommerce/src/main/kotlin/org/wordpress/android/fluxc/model/payments/inperson/WCCapturePaymentResponsePayload.kt +++ b/plugins/woocommerce/src/main/kotlin/org/wordpress/android/fluxc/model/payments/inperson/WCCapturePaymentResponsePayload.kt @@ -26,11 +26,15 @@ class WCCapturePaymentError( val message: String = "" ) : OnChangedError -enum class WCCapturePaymentErrorType { - GENERIC_ERROR, - PAYMENT_ALREADY_CAPTURED, - MISSING_ORDER, - CAPTURE_ERROR, - SERVER_ERROR, - NETWORK_ERROR +sealed class WCCapturePaymentErrorType { + data object GENERIC_ERROR : WCCapturePaymentErrorType() + data object PAYMENT_ALREADY_CAPTURED : WCCapturePaymentErrorType() + data object MISSING_ORDER : WCCapturePaymentErrorType() + data object CAPTURE_ERROR : WCCapturePaymentErrorType() + data object SERVER_ERROR : WCCapturePaymentErrorType() + data object NETWORK_ERROR : WCCapturePaymentErrorType() + data class AMOUNT_TOO_SMALL( + val minAllowedAmountInStripeMinorUnit: Long, + val currency: String + ) : WCCapturePaymentErrorType() } From 407b58ea29bb2b663735d42e998d54912f8ddce7 Mon Sep 17 00:00:00 2001 From: Andrey Date: Tue, 28 Jan 2025 09:49:34 +0100 Subject: [PATCH 2/5] Changed type and the tests --- .../MockedStack_WCInPersonPaymentsTest.kt | 14 +++++++++---- .../WCCapturePaymentResponsePayload.kt | 21 ++++++++----------- 2 files changed, 19 insertions(+), 16 deletions(-) diff --git a/example/src/androidTest/java/org/wordpress/android/fluxc/mocked/MockedStack_WCInPersonPaymentsTest.kt b/example/src/androidTest/java/org/wordpress/android/fluxc/mocked/MockedStack_WCInPersonPaymentsTest.kt index 833c081e9c..1753e6a519 100644 --- a/example/src/androidTest/java/org/wordpress/android/fluxc/mocked/MockedStack_WCInPersonPaymentsTest.kt +++ b/example/src/androidTest/java/org/wordpress/android/fluxc/mocked/MockedStack_WCInPersonPaymentsTest.kt @@ -11,8 +11,8 @@ import org.junit.Assert.assertTrue import org.junit.Test import org.wordpress.android.fluxc.model.SiteModel import org.wordpress.android.fluxc.model.payments.inperson.WCCapturePaymentErrorType -import org.wordpress.android.fluxc.model.payments.inperson.WCCapturePaymentErrorType.CAPTURE_ERROR import org.wordpress.android.fluxc.model.payments.inperson.WCCapturePaymentErrorType.MISSING_ORDER +import org.wordpress.android.fluxc.model.payments.inperson.WCCapturePaymentErrorType.CAPTURE_ERROR import org.wordpress.android.fluxc.model.payments.inperson.WCCapturePaymentErrorType.PAYMENT_ALREADY_CAPTURED import org.wordpress.android.fluxc.model.payments.inperson.WCCapturePaymentErrorType.SERVER_ERROR import org.wordpress.android.fluxc.model.payments.inperson.WCPaymentAccountResult.WCPaymentAccountStatus @@ -135,9 +135,15 @@ class MockedStack_InPersonPaymentsTest : MockedStack_Base() { -10L ) - val type = result.error?.type as WCCapturePaymentErrorType.AMOUNT_TOO_SMALL - assertTrue(type.currency == "USD") - assertTrue(type.minAllowedAmountInStripeMinorUnit == 50L) + assertTrue(result.error!!.type == CAPTURE_ERROR) + assertTrue(result.error!!.extraData!!["error_type"] == "amount_too_small") + assertTrue( + result.error!!.extraData!!["extra_details"] as Map<*, *> == + mapOf( + "minimum_amount" to 50L, + "minimum_amount_currency" to "USD" + ) + ) } @Test diff --git a/plugins/woocommerce/src/main/kotlin/org/wordpress/android/fluxc/model/payments/inperson/WCCapturePaymentResponsePayload.kt b/plugins/woocommerce/src/main/kotlin/org/wordpress/android/fluxc/model/payments/inperson/WCCapturePaymentResponsePayload.kt index dcbfc4e57e..3221f35de4 100644 --- a/plugins/woocommerce/src/main/kotlin/org/wordpress/android/fluxc/model/payments/inperson/WCCapturePaymentResponsePayload.kt +++ b/plugins/woocommerce/src/main/kotlin/org/wordpress/android/fluxc/model/payments/inperson/WCCapturePaymentResponsePayload.kt @@ -23,18 +23,15 @@ class WCCapturePaymentResponsePayload( class WCCapturePaymentError( val type: WCCapturePaymentErrorType = GENERIC_ERROR, - val message: String = "" + val message: String = "", + val extraData: Map? = null ) : OnChangedError -sealed class WCCapturePaymentErrorType { - data object GENERIC_ERROR : WCCapturePaymentErrorType() - data object PAYMENT_ALREADY_CAPTURED : WCCapturePaymentErrorType() - data object MISSING_ORDER : WCCapturePaymentErrorType() - data object CAPTURE_ERROR : WCCapturePaymentErrorType() - data object SERVER_ERROR : WCCapturePaymentErrorType() - data object NETWORK_ERROR : WCCapturePaymentErrorType() - data class AMOUNT_TOO_SMALL( - val minAllowedAmountInStripeMinorUnit: Long, - val currency: String - ) : WCCapturePaymentErrorType() +enum class WCCapturePaymentErrorType { + GENERIC_ERROR, + PAYMENT_ALREADY_CAPTURED, + MISSING_ORDER, + CAPTURE_ERROR, + SERVER_ERROR, + NETWORK_ERROR } From e42acec3109c951b2db8fa27d23de144163857bf Mon Sep 17 00:00:00 2001 From: Andrey Date: Tue, 28 Jan 2025 11:09:03 +0100 Subject: [PATCH 3/5] Added parsing --- .../inperson/InPersonPaymentsRestClient.kt | 48 ++++++++++++++++--- 1 file changed, 42 insertions(+), 6 deletions(-) diff --git a/plugins/woocommerce/src/main/kotlin/org/wordpress/android/fluxc/network/rest/wpcom/wc/payments/inperson/InPersonPaymentsRestClient.kt b/plugins/woocommerce/src/main/kotlin/org/wordpress/android/fluxc/network/rest/wpcom/wc/payments/inperson/InPersonPaymentsRestClient.kt index d9e9798803..ece388d3dc 100644 --- a/plugins/woocommerce/src/main/kotlin/org/wordpress/android/fluxc/network/rest/wpcom/wc/payments/inperson/InPersonPaymentsRestClient.kt +++ b/plugins/woocommerce/src/main/kotlin/org/wordpress/android/fluxc/network/rest/wpcom/wc/payments/inperson/InPersonPaymentsRestClient.kt @@ -1,5 +1,8 @@ package org.wordpress.android.fluxc.network.rest.wpcom.wc.payments.inperson +import com.android.volley.VolleyError +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken import org.wordpress.android.fluxc.generated.endpoint.WOOCOMMERCE import org.wordpress.android.fluxc.model.SiteModel import org.wordpress.android.fluxc.model.payments.inperson.WCCapturePaymentError @@ -26,9 +29,13 @@ import org.wordpress.android.fluxc.store.WCInPersonPaymentsStore.InPersonPayment import org.wordpress.android.fluxc.store.WCInPersonPaymentsStore.InPersonPaymentsPluginType.STRIPE import org.wordpress.android.fluxc.store.WCInPersonPaymentsStore.InPersonPaymentsPluginType.WOOCOMMERCE_PAYMENTS import org.wordpress.android.fluxc.utils.toWooPayload +import org.wordpress.android.util.AppLog import javax.inject.Inject -class InPersonPaymentsRestClient @Inject constructor(private val wooNetwork: WooNetwork) { +class InPersonPaymentsRestClient @Inject constructor( + private val wooNetwork: WooNetwork, + private val gson: Gson, +) { suspend fun fetchConnectionToken( activePlugin: InPersonPaymentsPluginType, site: SiteModel @@ -71,15 +78,22 @@ class InPersonPaymentsRestClient @Inject constructor(private val wooNetwork: Woo response.data?.let { data -> WCCapturePaymentResponsePayload(site, paymentId, orderId, data.status) } ?: WCCapturePaymentResponsePayload( - mapToCapturePaymentError(error = null, message = "status field is null, but isError == false"), + mapToCapturePaymentError( + error = null, + message = "status field is null, but isError == false" + ), site, paymentId, orderId ) } + is WPAPIResponse.Error -> { WCCapturePaymentResponsePayload( - mapToCapturePaymentError(response.error, response.error.message ?: "Unexpected error"), + mapToCapturePaymentError( + response.error, + response.error.message ?: "Unexpected error" + ), site, paymentId, orderId @@ -146,9 +160,13 @@ class InPersonPaymentsRestClient @Inject constructor(private val wooNetwork: Woo ) ) } + is WPAPIResponse.Error -> { WCTerminalStoreLocationResult( - mapToStoreLocationForSiteError(response.error, response.error.message ?: "Unexpected error") + mapToStoreLocationForSiteError( + response.error, + response.error.message ?: "Unexpected error" + ) ) } } @@ -194,7 +212,10 @@ class InPersonPaymentsRestClient @Inject constructor(private val wooNetwork: Woo return response.toWooPayload() } - private fun mapToCapturePaymentError(error: WPAPINetworkError?, message: String): WCCapturePaymentError { + private fun mapToCapturePaymentError( + error: WPAPINetworkError?, + message: String + ): WCCapturePaymentError { val type = when { error == null -> GENERIC_ERROR error.errorCode == "wcpay_missing_order" -> MISSING_ORDER @@ -206,7 +227,22 @@ class InPersonPaymentsRestClient @Inject constructor(private val wooNetwork: Woo error.type == GenericErrorType.NETWORK_ERROR -> NETWORK_ERROR else -> GENERIC_ERROR } - return WCCapturePaymentError(type, message) + return WCCapturePaymentError( + type = type, + message = message, + extraData = error?.volleyError?.getExtraData() + ) + } + + fun VolleyError.getExtraData(): Map? { + val jsonString = this.networkResponse?.data?.toString(Charsets.UTF_8) + return try { + val mapType = object : TypeToken>() {}.type + return gson.fromJson>(jsonString, mapType)["data"] as Map? + } catch (e: Exception) { + AppLog.e(AppLog.T.API, "Error parsing volley error $jsonString", e) + null + } } private fun mapToStoreLocationForSiteError(error: WPAPINetworkError?, message: String): From c3ba2751582aff0dd843f692de43de1e128deeb7 Mon Sep 17 00:00:00 2001 From: Andrey Date: Tue, 28 Jan 2025 11:09:41 +0100 Subject: [PATCH 4/5] Changed test --- .../mocked/MockedStack_WCInPersonPaymentsTest.kt | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/example/src/androidTest/java/org/wordpress/android/fluxc/mocked/MockedStack_WCInPersonPaymentsTest.kt b/example/src/androidTest/java/org/wordpress/android/fluxc/mocked/MockedStack_WCInPersonPaymentsTest.kt index 1753e6a519..7f676fa6a1 100644 --- a/example/src/androidTest/java/org/wordpress/android/fluxc/mocked/MockedStack_WCInPersonPaymentsTest.kt +++ b/example/src/androidTest/java/org/wordpress/android/fluxc/mocked/MockedStack_WCInPersonPaymentsTest.kt @@ -136,14 +136,10 @@ class MockedStack_InPersonPaymentsTest : MockedStack_Base() { ) assertTrue(result.error!!.type == CAPTURE_ERROR) - assertTrue(result.error!!.extraData!!["error_type"] == "amount_too_small") - assertTrue( - result.error!!.extraData!!["extra_details"] as Map<*, *> == - mapOf( - "minimum_amount" to 50L, - "minimum_amount_currency" to "USD" - ) - ) + assertEquals("amount_too_small", result.error!!.extraData!!["error_type"]) + val extraDetails = result.error!!.extraData!!["extra_details"] as Map<*, *> + assertEquals(50.0, extraDetails["minimum_amount"]) + assertEquals("USD", extraDetails["minimum_amount_currency"]) } @Test From b04c5ae3d05301829a9d6be0bb89f9fb6f428309 Mon Sep 17 00:00:00 2001 From: Andrey Date: Tue, 28 Jan 2025 14:23:23 +0100 Subject: [PATCH 5/5] Supressed detekt complain --- .../wpcom/wc/payments/inperson/InPersonPaymentsRestClient.kt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/plugins/woocommerce/src/main/kotlin/org/wordpress/android/fluxc/network/rest/wpcom/wc/payments/inperson/InPersonPaymentsRestClient.kt b/plugins/woocommerce/src/main/kotlin/org/wordpress/android/fluxc/network/rest/wpcom/wc/payments/inperson/InPersonPaymentsRestClient.kt index ece388d3dc..abedf76a31 100644 --- a/plugins/woocommerce/src/main/kotlin/org/wordpress/android/fluxc/network/rest/wpcom/wc/payments/inperson/InPersonPaymentsRestClient.kt +++ b/plugins/woocommerce/src/main/kotlin/org/wordpress/android/fluxc/network/rest/wpcom/wc/payments/inperson/InPersonPaymentsRestClient.kt @@ -234,12 +234,13 @@ class InPersonPaymentsRestClient @Inject constructor( ) } - fun VolleyError.getExtraData(): Map? { + @Suppress("TooGenericExceptionCaught") + private fun VolleyError.getExtraData(): Map? { val jsonString = this.networkResponse?.data?.toString(Charsets.UTF_8) return try { val mapType = object : TypeToken>() {}.type return gson.fromJson>(jsonString, mapType)["data"] as Map? - } catch (e: Exception) { + } catch (e: Throwable) { AppLog.e(AppLog.T.API, "Error parsing volley error $jsonString", e) null }