-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Make Balance and Capture use sync endpoints #288
Make Balance and Capture use sync endpoints #288
Conversation
This stack of pull requests is managed by Graphite. Learn more about stacking. Join @devinmorgan and the rest of your teammates on Graphite |
@@ -49,7 +49,7 @@ internal class BasisTheoryPinSubmitter( | |||
val bt = buildVaultProvider() | |||
|
|||
val proxyRequest: ProxyRequest = ProxyRequest().apply { | |||
headers = vaultProxyRequest.headers | |||
headers = vaultProxyRequest.headers + (ForageConstants.Headers.API_VERSION to "2024-01-08") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Acknowledging that this is ugly. I have plans to clean this up in the future
596fa5c
to
a93d1f2
Compare
a93d1f2
to
5a2a00f
Compare
5a2a00f
to
72c3b74
Compare
8a3a0ba
to
f8e4aa9
Compare
72c3b74
to
8869b6f
Compare
// If the API_VERSION header has already been appended, don't override it! | ||
chain.request().headers[ForageConstants.Headers.API_VERSION]?.let { | ||
this | ||
.apply { | ||
if (chain.request().headers[ForageConstants.Headers.API_VERSION] == null) { | ||
addHeader(ForageConstants.Headers.API_VERSION, apiVersion) | ||
} | ||
// Otherwise, set the default API_VERSION header | ||
?: addHeader( | ||
ForageConstants.Headers.API_VERSION, | ||
"default" | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Opportunity to make the code easier to understand
@@ -19,7 +19,8 @@ internal object OkHttpClientBuilder { | |||
sessionToken: String, | |||
merchantId: String? = null, | |||
idempotencyKey: String? = null, | |||
traceId: String? = null | |||
traceId: String? = null, | |||
apiVersion: String = "default" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
provideOkHttpClient
get's used by a lot of callees. Declaring the apiVersion
with a param that defaults to default
will decouple these callers from needing to work directly with OkHttpClient.addHeader
and the ForageConstants.Headers
. Less coupling makes for more maintainable code!
@@ -44,7 +44,7 @@ internal abstract class ErrorPayload( | |||
return RosettaErrorResponse(jsonErrorResponse) | |||
} | |||
else -> { | |||
throw UnexpectedResponseError(jsonErrorResponse.toString()) | |||
throw UnknownForageFailureResponse(jsonErrorResponse.toString()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The new name more precisely describes that this exception is thrown when we there is a response that we believe is Forage-defined that we are unable to parse
f8e4aa9
to
c57c042
Compare
8869b6f
to
ae3f7cb
Compare
return try { | ||
// In AbstractVaultSubmitter we track the forageError | ||
// and the forage status code of the UnknownErrorApiResponse | ||
// via the VaultProxyResponseMonitor. This keeps us informed | ||
// of *when* erroroneous BT response happen. Unfortunately, | ||
// we do not currently track the specifics of the proxy_error | ||
// that that BT returned to us. | ||
// TODO: log the specific error that BT responds with when | ||
// resRegExp.containsProxyError == true | ||
if (resRegExp.containsProxyError) UnknownErrorApiResponse else null | ||
} catch (_: Exception) { | ||
null | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The try catch block is not necessary here because regexp matching won't throw so I remove it in this PR. To elaborate on why it won't throw:
- if the regexp don't find a match they return null and the compiler enforces that we use null-safety accessors (
?.
). - if the regexp does match,
.get(0)
always refers to the entire match and.get(1)
refers to the first matched group
Point being that there won't ever be an index-out-of-bounds scenario by doing .get(1)
or any other error from not finding a regexp match. I added unit tests in BTResponseParserText.kt
and BTFailureResponseRegExp.kt
to confirm
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A learning I took from implementing these changes in iOS and JS is that we should really force all error scenarios through the BT codepath to ensure we are happy with the parsing. The error scenarios are:
- EBT network error code (eg.
ebt_error_55
) - Forage API error (eg.
invalid_merchant_header
) - BT error (eg. IDK COULD BE ANYTHING)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great point! I'll incorporate this into the bug bash on Friday. Thanks for surfacing this!!
return try { | ||
ForageApiResponse.Failure(resRegExp.statusCode, resRegExp.bodyText) | ||
} catch (_: Exception) { | ||
private fun parseForageError(resRegExp: BtFailureResponseRegExp): ForageApiResponse.Failure? = | ||
if (resRegExp.bodyText == null || | ||
resRegExp.statusCode == null || | ||
// if there's a proxy_error, don't try to parse as a Forage error | ||
resRegExp.containsProxyError |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I replace the try/catch with an additional clause in the if-statement that confirms resRegExp.containsProxyError == false
before attempting to parse as a ForageError. It is true that attempting to parse an error that is not a Forage error with ForageApiResponse.Failure(..)
will throw an exception. If the error is not a proxy_error
, then it can only be a ForageError and if we are unable to parse the FroageError, that is an exceptional circumstance and we need to throw.
NOTE: the callee of BTResponseParser
will catch the exception, log it, and gracefully return UnknownServerError
val jsonResponse = when { | ||
(result == null) -> JSONObject() | ||
(result == "") -> JSONObject() | ||
(result is Map<*, *>) -> JSONObject(result) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For whatever reason, using API-VERSION: 2024-01-08 caused BT to start returning a Gson representation of the JSON response. While this is a bad oversight of BT to leak a third-party dependency in their public API, fortunately, the Gson representation can be cast as a Map<*, *>
and the built-int JSONObject
constructor correctly parses.
(result == null) -> JSONObject() | ||
(result == "") -> JSONObject() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These are to gracefully handle the deferred pin-submission flows since they technically have an empty body and were throwing with the new API-VERSION: 2024-01-08
return try { | ||
vaultToForageResponse(BTResponseParser(vaultResponse)) | ||
} catch (e: UnknownBTSuccessResponse) { | ||
logger.e("Unknown success response from BasisTheory Vault.", e) | ||
UnknownErrorApiResponse | ||
} catch (e: UnknownForageFailureResponse) { | ||
logger.e("Unknown error from Payments API.", e) | ||
UnknownErrorApiResponse | ||
} catch (e: Exception) { | ||
logger.e("Request to Basis Theory failed.", e) | ||
UnknownErrorApiResponse | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
and this is where we catch the various exceptions that could arise from parsing BT responses
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
remove tests that tested whether polling was explicitly used or not, as polling no longer happens with sync endpoints. This is a separate endeavor from dropping all the polling code, which is done in the next PR
@@ -52,7 +52,7 @@ class BasisTheoryPinSubmitterTest() : MockServerSuite() { | |||
mockBasisTheoryTextElement = mock(TextElement::class.java) | |||
|
|||
// ensure we don't make any live requests! | |||
mockBasisTheoryResponse(Result.success("success")) | |||
mockBasisTheoryResponse(Result.success(mapOf("mySuccessfulKey" to "mySuccessfulValue"))) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updating because BT starts returning Gson representation of JSON instead of just the JSON string
d0a288c
to
5c38867
Compare
.../src/main/java/com/joinforage/forage/android/core/services/vault/CapturePaymentRepository.kt
Outdated
Show resolved
Hide resolved
...id/src/main/java/com/joinforage/forage/android/core/services/vault/CheckBalanceRepository.kt
Outdated
Show resolved
Hide resolved
return try { | ||
// In AbstractVaultSubmitter we track the forageError | ||
// and the forage status code of the UnknownErrorApiResponse | ||
// via the VaultProxyResponseMonitor. This keeps us informed | ||
// of *when* erroroneous BT response happen. Unfortunately, | ||
// we do not currently track the specifics of the proxy_error | ||
// that that BT returned to us. | ||
// TODO: log the specific error that BT responds with when | ||
// resRegExp.containsProxyError == true | ||
if (resRegExp.containsProxyError) UnknownErrorApiResponse else null | ||
} catch (_: Exception) { | ||
null | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A learning I took from implementing these changes in iOS and JS is that we should really force all error scenarios through the BT codepath to ensure we are happy with the parsing. The error scenarios are:
- EBT network error code (eg.
ebt_error_55
) - Forage API error (eg.
invalid_merchant_header
) - BT error (eg. IDK COULD BE ANYTHING)
f46d91d
to
1f1d6d9
Compare
Note that we are not deleting any polling code here - we're just inserting the sync endpoints into the flow. We are only deleting test cases testing internal details of how pin-submission works as SQS messages are simply not involved in the flow with sync-endpoints Signed-off-by: Devin Morgan <[email protected]>
BT returns different shaped responses with API-VESION: 2024-01-08. To ensure reliability, it's time to add unit test for these facillities.
Signed-off-by: Devin Morgan <[email protected]>
We have the ability now to differentiate between a failure to parse what should be a ForageError and a failure to parse what should be a successful response from BT. Signed-off-by: Devin Morgan <[email protected]>
It took me a very long time to understand why we go back and forth between ForageApiResponse.Success and EbtBalance so I'm adding this comments to clarify for other developers Signed-off-by: Devin Morgan <[email protected]>
Signed-off-by: Devin Morgan <[email protected]>
Signed-off-by: Devin Morgan <[email protected]>
Signed-off-by: Devin Morgan <[email protected]>
1f1d6d9
to
e135910
Compare
Merge activity
|
* Insert sync endpoints in BT and Rosetta Note that we are not deleting any polling code here - we're just inserting the sync endpoints into the flow. We are only deleting test cases testing internal details of how pin-submission works as SQS messages are simply not involved in the flow with sync-endpoints Signed-off-by: Devin Morgan <[email protected]> * Add BT parsing tests BT returns different shaped responses with API-VESION: 2024-01-08. To ensure reliability, it's time to add unit test for these facillities. * Apply spotless Signed-off-by: Devin Morgan <[email protected]> * Catch and log BT response parsing exceptions We have the ability now to differentiate between a failure to parse what should be a ForageError and a failure to parse what should be a successful response from BT. Signed-off-by: Devin Morgan <[email protected]> * Add comments for balance type back-and-forth It took me a very long time to understand why we go back and forth between ForageApiResponse.Success and EbtBalance so I'm adding this comments to clarify for other developers Signed-off-by: Devin Morgan <[email protected]> * Re-drop setting API-VERSION: default in AbstractVaultSubmitter Signed-off-by: Devin Morgan <[email protected]> * Get rid of unnecessary when statements and logs Signed-off-by: Devin Morgan <[email protected]> * Fix typo Signed-off-by: Devin Morgan <[email protected]> --------- Signed-off-by: Devin Morgan <[email protected]>
What
Swaps out polling usage in
CapturePaymentRepository
andCheckBalanceRepository
with sync endpoints by bumping theAPI-VERSION
header fromdefault
->2024-01-08
.Sources of complexity in this PR:
API-VERSION
and using"default"
as theAPI-VERSION
header when left unspecified. Since BT uses its own network facilities, specifyingAPI-VERSION: 2024-01-08
was less involved.gson
representations of JSON instead of ferrying the JSON as a string along so we had to navigate this gracefullyWhy
We've wanted to do this for almost a quarter now in Android. It's already done in iOS and JS
Test Plan
Demo
CI tests pass!
How
Must be merged after previous PRs in the stack