Skip to content
This repository has been archived by the owner on Feb 4, 2025. It is now read-only.

Commit

Permalink
Merge pull request #3128 from wordpress-mobile/ipp-capture-payment-wi…
Browse files Browse the repository at this point in the history
…th-min-amount

[In Person Payments] Capture payment with min amount error handling
  • Loading branch information
samiuelson authored Jan 28, 2025
2 parents 3061745 + b04c5ae commit a1ee78c
Show file tree
Hide file tree
Showing 4 changed files with 77 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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")
Expand Down
Original file line number Diff line number Diff line change
@@ -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"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ class WCCapturePaymentResponsePayload(

class WCCapturePaymentError(
val type: WCCapturePaymentErrorType = GENERIC_ERROR,
val message: String = ""
val message: String = "",
val extraData: Map<String, Any>? = null
) : OnChangedError

enum class WCCapturePaymentErrorType {
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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"
)
)
}
}
Expand Down Expand Up @@ -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
Expand All @@ -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<String, Any>? {
val jsonString = this.networkResponse?.data?.toString(Charsets.UTF_8)
return try {
val mapType = object : TypeToken<Map<String, Any>>() {}.type
return gson.fromJson<Map<String, Any>>(jsonString, mapType)["data"] as Map<String, Any>?
} catch (e: Throwable) {
AppLog.e(AppLog.T.API, "Error parsing volley error $jsonString", e)
null
}
}

private fun mapToStoreLocationForSiteError(error: WPAPINetworkError?, message: String):
Expand Down

0 comments on commit a1ee78c

Please sign in to comment.