-
Notifications
You must be signed in to change notification settings - Fork 42
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
Card: Remove Approve Order Listener Pattern #305
Changes from all commits
648a584
1362c12
e396f74
0cb7358
3b32191
b141c34
9beadea
d8398ce
3e543ab
5ba2db7
f07654b
411096a
7993da0
60ac0fb
3366f00
31e72c2
e62b819
9c40315
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
package com.paypal.android.cardpayments | ||
|
||
import androidx.annotation.MainThread | ||
|
||
fun interface CardApproveOrderCallback { | ||
/** | ||
* Called when the order is approved. | ||
* | ||
* @param result [CardApproveOrderResult] result with details | ||
*/ | ||
@MainThread | ||
fun onCardApproveOrderResult(result: CardApproveOrderResult) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
package com.paypal.android.cardpayments | ||
|
||
import com.paypal.android.corepayments.PayPalSDKError | ||
|
||
sealed class CardApproveOrderResult { | ||
|
||
data class Success( | ||
val orderId: String, | ||
val status: String? = null, | ||
val didAttemptThreeDSecureAuthentication: Boolean = false | ||
) : CardApproveOrderResult() | ||
|
||
data class AuthorizationRequired(val authChallenge: CardAuthChallenge) : CardApproveOrderResult() | ||
data class Failure(val error: PayPalSDKError) : CardApproveOrderResult() | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -18,7 +18,6 @@ import kotlinx.coroutines.launch | |
/** | ||
* Use this client to approve an order with a [Card]. | ||
* | ||
* @property approveOrderListener listener to receive callbacks from [CardClient.approveOrder]. | ||
* @property cardVaultListener listener to receive callbacks form [CardClient.vault]. | ||
*/ | ||
class CardClient internal constructor( | ||
|
@@ -29,8 +28,6 @@ class CardClient internal constructor( | |
private val dispatcher: CoroutineDispatcher | ||
) { | ||
|
||
var approveOrderListener: ApproveOrderListener? = null | ||
|
||
// NEXT MAJOR VERSION: rename to vaultListener | ||
/** | ||
* @suppress | ||
|
@@ -54,12 +51,14 @@ class CardClient internal constructor( | |
) | ||
|
||
// NEXT MAJOR VERSION: Consider renaming approveOrder() to confirmPaymentSource() | ||
|
||
/** | ||
* Confirm [Card] payment source for an order. | ||
* | ||
* @param cardRequest [CardRequest] for requesting an order approval | ||
* @param callback [CardApproveOrderCallback] callback for receiving result asynchronously | ||
*/ | ||
fun approveOrder(cardRequest: CardRequest) { | ||
fun approveOrder(cardRequest: CardRequest, callback: CardApproveOrderCallback) { | ||
// TODO: deprecate this method and offer auth challenge integration pattern (similar to vault) | ||
approveOrderId = cardRequest.orderId | ||
analytics.notifyApproveOrderStarted(cardRequest.orderId) | ||
|
@@ -69,22 +68,27 @@ class CardClient internal constructor( | |
is ConfirmPaymentSourceResult.Success -> { | ||
if (response.payerActionHref == null) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this syntax looks really similar to completion handler. So callback in place of listener pattern is safer for memory management? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's similar. The main difference now is there's a callback for every async method. With the listener pattern, there is only one listener, and it needs to be set by the merchant before starting any of the payment flows. |
||
analytics.notifyApproveOrderSucceeded(response.orderId) | ||
val result = | ||
response.run { CardResult(orderId = orderId, status = status?.name) } | ||
approveOrderListener?.onApproveOrderSuccess(result) | ||
val result = response.run { | ||
CardApproveOrderResult.Success( | ||
orderId = orderId, | ||
status = status?.name | ||
) | ||
} | ||
callback.onCardApproveOrderResult(result) | ||
} else { | ||
analytics.notifyApproveOrderAuthChallengeReceived(cardRequest.orderId) | ||
approveOrderListener?.onApproveOrderThreeDSecureWillLaunch() | ||
|
||
val url = Uri.parse(response.payerActionHref) | ||
val authChallenge = CardAuthChallenge.ApproveOrder(url, cardRequest) | ||
approveOrderListener?.onApproveOrderAuthorizationRequired(authChallenge) | ||
val result = CardApproveOrderResult.AuthorizationRequired(authChallenge) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's interesting that you have this intermediate state on the same result type. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This method does have three possible results. Alternatively, I thought about bundling the Technically it isn't a failure. PPaaS just needs more information before the buyer can continue. |
||
callback.onCardApproveOrderResult(result) | ||
} | ||
} | ||
|
||
is ConfirmPaymentSourceResult.Failure -> { | ||
analytics.notifyApproveOrderFailed(cardRequest.orderId) | ||
approveOrderListener?.onApproveOrderFailure(response.error) | ||
val result = CardApproveOrderResult.Failure(response.error) | ||
callback.onCardApproveOrderResult(result) | ||
} | ||
} | ||
} | ||
|
@@ -134,6 +138,9 @@ class CardClient internal constructor( | |
|
||
/** | ||
* Present an auth challenge received from a [CardClient.approveOrder] or [CardClient.vault] result. | ||
* @param activity [ComponentActivity] activity reference used to present a Chrome Custom Tab. | ||
* @param authChallenge [CardAuthChallenge] auth challenge to present | ||
* (see [CardApproveOrderResult.AuthorizationRequired]) | ||
*/ | ||
fun presentAuthChallenge( | ||
activity: ComponentActivity, | ||
|
@@ -173,6 +180,25 @@ class CardClient internal constructor( | |
} | ||
} | ||
|
||
fun finishApproveOrder(intent: Intent, authState: String): CardFinishApproveOrderResult { | ||
val result = authChallengeLauncher.completeApproveOrderAuthRequest(intent, authState) | ||
when (result) { | ||
is CardFinishApproveOrderResult.Success -> | ||
analytics.notifyApproveOrderAuthChallengeSucceeded(result.orderId) | ||
|
||
is CardFinishApproveOrderResult.Failure -> | ||
analytics.notifyApproveOrderAuthChallengeFailed(null) | ||
|
||
CardFinishApproveOrderResult.Canceled -> | ||
analytics.notifyApproveOrderAuthChallengeCanceled(null) | ||
|
||
else -> { | ||
// no analytics tracking required at the moment | ||
} | ||
} | ||
return result | ||
} | ||
|
||
fun completeAuthChallenge(intent: Intent, authState: String): CardStatus { | ||
val status = authChallengeLauncher.completeAuthRequest(intent, authState) | ||
when (status) { | ||
|
@@ -194,26 +220,11 @@ class CardClient internal constructor( | |
cardVaultListener?.onVaultFailure(PayPalSDKError(1, "User Canceled")) | ||
} | ||
|
||
is CardStatus.ApproveOrderError -> { | ||
analytics.notifyApproveOrderAuthChallengeFailed(status.orderId) | ||
approveOrderListener?.onApproveOrderFailure(status.error) | ||
} | ||
|
||
is CardStatus.ApproveOrderSuccess -> { | ||
analytics.notifyApproveOrderAuthChallengeSucceeded(status.result.orderId) | ||
approveOrderListener?.onApproveOrderSuccess(status.result) | ||
} | ||
|
||
is CardStatus.ApproveOrderCanceled -> { | ||
analytics.notifyApproveOrderAuthChallengeCanceled(status.orderId) | ||
approveOrderListener?.onApproveOrderCanceled() | ||
} | ||
|
||
is CardStatus.UnknownError -> { | ||
Log.d("PayPalSDK", "An unknown error occurred: ${status.error.message}") | ||
} | ||
|
||
CardStatus.NoResult -> { | ||
else -> { | ||
// ignore | ||
} | ||
} | ||
|
@@ -224,7 +235,6 @@ class CardClient internal constructor( | |
* Call this method at the end of the card flow to clear out all observers and listeners | ||
*/ | ||
fun removeObservers() { | ||
approveOrderListener = null | ||
cardVaultListener = null | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
package com.paypal.android.cardpayments | ||
|
||
import com.paypal.android.corepayments.PayPalSDKError | ||
|
||
sealed class CardFinishApproveOrderResult { | ||
data class Success( | ||
val orderId: String, | ||
val status: String? = null, | ||
val didAttemptThreeDSecureAuthentication: Boolean = false | ||
) : CardFinishApproveOrderResult() | ||
|
||
data class Failure(val error: PayPalSDKError) : CardFinishApproveOrderResult() | ||
data object Canceled : CardFinishApproveOrderResult() | ||
data object NoResult : CardFinishApproveOrderResult() | ||
} |
This file was deleted.
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.
That's interesting. I don't think it's possible to do this in iOS with protocols.
Do you feel like this is something merchant would want us to handle?
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'm not sure if we need to deliver results on the main thread, but it is convenient when updating the UI after executing a transaction.
It's up to y'all if you want to do the same. I feel like the closest equivalent to this annotation in Swift would be
@MainActor
.