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..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 @@ -10,8 +10,9 @@ 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.CAPTURE_ERROR +import org.wordpress.android.fluxc.model.payments.inperson.WCCapturePaymentErrorType 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 @@ -123,6 +124,24 @@ 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 + ) + + assertTrue(result.error!!.type == CAPTURE_ERROR) + 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 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..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,7 +23,8 @@ class WCCapturePaymentResponsePayload( class WCCapturePaymentError( val type: WCCapturePaymentErrorType = GENERIC_ERROR, - val message: String = "" + val message: String = "", + val extraData: Map? = null ) : OnChangedError enum class WCCapturePaymentErrorType { 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..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 @@ -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,23 @@ 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() + ) + } + + @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: Throwable) { + AppLog.e(AppLog.T.API, "Error parsing volley error $jsonString", e) + null + } } private fun mapToStoreLocationForSiteError(error: WPAPINetworkError?, message: String):