Skip to content

Commit

Permalink
2.0.0-beta2 Migration Guide (#309)
Browse files Browse the repository at this point in the history
* Update v2_MIGRATION_GUIDE for CardClient without listeners.

* Clean up CardClient v2 migration guide.

* Remove private modifiers.

* Update MIGRATION GUIDE for PayPal payment flows.

* Update result type in MIGRATION_GUIDE.

* Update MIGRATION_GUIDE with a list of reasons for migrating away from the listener pattern.

* Add link to previous migration guide.

* Update migration guide link wording.

* Tweak note messaging.
  • Loading branch information
sshropshire authored Jan 13, 2025
1 parent 5e2e1ce commit 3de44e9
Showing 1 changed file with 205 additions and 96 deletions.
301 changes: 205 additions & 96 deletions v2_MIGRATION_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

This guide highlights how to migrate to the latest version of the PayPal SDK.

> For evolution of this guide, see the MIGRATION_GUIDE for [v2.0.0-beta1](https://github.com/paypal/paypal-android/blob/4afcf913ae8ae91c741faa4a7d49f8ba44765117/v2_MIGRATION_GUIDE.md).
## Table of Contents

1. [Card Payments](#card-payments)
Expand All @@ -13,93 +15,131 @@ This guide highlights how to migrate to the latest version of the PayPal SDK.
We refactored the `CardClient` API to improve the developer experience. Use this diff to guide your migration from `v1` to `v2`:

```diff
class SampleActivity: ComponentActivity(), ApproveOrderListener, CardVaultListener {
-class SampleActivity: ComponentActivity(), ApproveOrderListener, CardVaultListener {
+class SampleActivity: ComponentActivity() {

val config = CoreConfig("<CLIENT_ID>", environment = Environment.LIVE)
- val cardClient = CardClient(requireActivity(), config)
+ val cardClient = CardClient(requireContext(), config)
+ var authState: String? = null

init {
cardClient.approveOrderListener = this
cardClient.vaultListener = this
}
- init {
- cardClient.approveOrderListener = this
- cardClient.vaultListener = this
- }

+ override fun onResume() {
+ super.onResume()
+ // Manually attempt auth challenge completion (via deep link)
+ authState?.let { state -> cardClient.completeAuthChallenge(intent, state) }
+ // Manually attempt auth challenge completion (via host activity intent deep link)
+ checkForCardAuthCompletion(intent)
+ }

override fun onDestroy() {
super.onDestroy()
cardClient.removeObservers()
}
+ override fun onNewIntent(newIntent: Intent) {
+ super.onNewIntent(newIntent)
+ // Manually attempt auth challenge completion (via new intent deep link)
+ checkForCardAuthCompletion(newIntent)
+ }

private fun approveOrder() {
fun approveOrder() {
val cardRequest: CardRequest = TODO("Create a card request.")
- cardClient.approveOrder(this, cardRequest)
+ cardClient.approveOrder(cardRequest)
+ when (val approveOrderResult = cardClient.approveOrder(cardRequest)) {
+ is CardApproveOrderResult.Success -> TODO("Capture or authorize order on your server.")
+ is CardApproveOrderResult.Failure -> TODO("Handle approve order failure.")
+ is CardApproveOrderResult.AuthorizationRequired -> presentAuthChallenge(result.authChallenge)
+ }
}

private fun vaultCard() {
fun vaultCard() {
val cardVaultRequest: CardVaultRequest = TODO("Create a card vault request.")
- cardClient.vault(this, cardVaultRequest)
+ cardClient.vault(cardVaultRequest)
}

override fun onApproveOrderSuccess(result: CardResult) {
TODO("Capture or authorize order on your server.")
+ // Discard auth state when done
+ authState = null
}

override fun onApproveOrderFailure(error: PayPalSDKError) {
TODO("Handle approve order failure.")
+ // Discard auth state when done
+ authState = null
+ when (val vaultResult = cardClient.vault(cardVaultRequest)) {
+ is CardVaultResult.Success -> TODO("Create payment token on your server.")
+ is CardVaultResult.Failure -> TODO("Handle card vault failure.")
+ is CardVaultResult.AuthorizationRequired -> presentAuthChallenge(result.authChallenge)
+ }
}

+ override fun onApproveOrderAuthorizationRequired(authChallenge: CardAuthChallenge) {
+ fun presentAuthChallenge(authChallenge: CardAuthChallenge) {
+ // Manually present auth challenge
+ val result = cardClient.presentAuthChallenge(this, authChallenge)
+ when (result) {
+ when (val result = cardClient.presentAuthChallenge(this, authChallenge)) {
+ is CardPresentAuthChallengeResult.Success -> {
+ // Capture auth state for balancing call to completeAuthChallenge() in onResume()
+ // Capture auth state for balancing call to finishApproveOrder()/finishVault() when
+ // the merchant application re-enters the foreground
+ authState = result.authState
+ }
+ is CardPresentAuthChallengeResult.Failure -> TODO("Handle Present Auth Challenge Failure")
+ }
+ }

override fun onVaultSuccess(result: CardVaultResult) {
+ fun checkForCardAuthCompletion(intent: Intent) = authState?.let { state ->
+ // check for approve order completion
+ when (val approveOrderResult = cardClient.finishApproveOrder(intent, state)) {
+ is CardFinishApproveOrderResult.Success -> {
+ TODO("Capture or authorize order on your server.")
+ authState = null // Discard auth state when done
+ }
+
+ is CardFinishApproveOrderResult.Failure -> {
+ TODO("Handle approve order failure.")
+ authState = null // Discard auth state when done
+ }
+
+ CardFinishApproveOrderResult.Canceled -> {
+ TODO("Give user the option to restart the flow.")
+ authState = null // Discard auth state when done
+ }
+
+ CardFinishApproveOrderResult.NoResult -> {
+ // there isn't enough information to determine the state of the auth challenge for this payment method
+ }
+ }
+
+ // check for vault completion
+ when (val vaultResult = cardClient.finishVault(intent, state)) {
+ is CardFinishVaultResult.Success -> {
+ TODO("Create payment token on your server.")
+ authState = null // Discard auth state when done
+ }
+ is CardFinishVaultResult.Failure -> {
+ TODO("Handle card vault failure.")
+ authState = null // Discard auth state when done
+ }
+ CardFinishVaultResult.Canceled -> {
+ TODO("Give user the option to restart the flow.")
+ authState = null // Discard auth state when done
+ }
+ CardFinishVaultResult.NoResult -> {
+ // there isn't enough information to determine the state of the auth challenge for this payment method
+ }
+ }
+ }

- override fun onApproveOrderSuccess(result: CardResult) {
- TODO("Capture or authorize order on your server.")
- }

- override fun onApproveOrderFailure(error: PayPalSDKError) {
- TODO("Handle approve order failure.")
- }

- override fun onVaultSuccess(result: CardVaultResult) {
- val authChallenge = result.authChallenge
- if (authChallenge != null) {
- cardClient?.presentAuthChallenge(activity, authChallenge)
- } else {
TODO("Create payment token on your server.")
- TODO("Create payment token on your server.")
- }
+ // Discard auth state when done
+ authState = null
}
- }

override fun onVaultFailure(error: PayPalSDKError) {
TODO("Handle card vault failure.")
+ // Discard auth state when done
+ authState = null
}

+ override fun onVaultAuthorizationRequired(authChallenge: CardAuthChallenge) {
+ // Manually present auth challenge
+ val result = cardClient.presentAuthChallenge(this, authChallenge)
+ when (result) {
+ is CardPresentAuthChallengeResult.Success -> {
+ // Capture auth state for balancing call to completeAuthChallenge() in onResume()
+ authState = result.authState
+ }
+ is CardPresentAuthChallengeResult.Failure -> TODO("Handle Present Auth Challenge Failure")
+ }
+ }
- override fun onVaultFailure(error: PayPalSDKError) {
- TODO("Handle card vault failure.")
- }

- override fun onDestroy() {
- super.onDestroy()
- cardClient.removeObservers()
- }
}
```

Expand All @@ -120,83 +160,145 @@ Here are some detailed notes on the changes made to Card Payments in v2:
- In `v2` the host application is responsible for calling `CardClient#completeAuthChallenge()` to attempt completion of an auth challenge.
- The goal of this change is to make the SDK less opinionated and give host applications more control over the auth challenge user experience.

### Migration from Listener Patern to Result Types

- In `v1` the SDK would notify the host application of success, failure, etc. events using a registered listener
- In `v2` the host application will receive a sealed class `Result` type in response to each method invocation
- The goal of this change is to prevent having to retain listener references and to make auth challenge presentation more explicit
- Sealed class result types also have the added benefit of explicitly calling out all possible outcomes of a related method inovcation

</details>

## PayPal Web Payments

We refactored the `PayPalWebClient` API to improve the developer experience. Use this diff to guide your migration from `v1` to `v2`:

```diff
class SampleActivity: ComponentActivity(), PayPalWebCheckoutListener, PayPalWebVaultListener {
-class SampleActivity: ComponentActivity(), PayPalWebCheckoutListener, PayPalWebVaultListener {
+class SampleActivity: ComponentActivity() {

val config = CoreConfig("<CLIENT_ID>", environment = Environment.LIVE)
- val payPalClient = PayPalWebCheckoutClient(requireActivity(), config, "my-deep-link-url-scheme")
+ val payPalClient = PayPalWebCheckoutClient(requireContext(), config, "my-deep-link-url-scheme")
+ var authState: String? = null

init {
payPalClient.listener = this
payPalClient.vaultListener = this
}
- init {
- payPalClient.listener = this
- payPalClient.vaultListener = this
- }

+ override fun onResume() {
+ super.onResume()
+ // Manually attempt auth challenge completion (via deep link)
+ authState?.let { state -> payPalClient.completeAuthChallenge(intent, state) }
+ // Manually attempt auth challenge completion (via host activity intent deep link)
+ checkForPayPalAuthCompletion(intent)
+ }

override fun onDestroy() {
super.onDestroy()
payPalClient.removeObservers()
}
+ override fun onNewIntent(newIntent: Intent) {
+ super.onNewIntent(newIntent)
+ // Manually attempt auth challenge completion (via new intent deep link)
+ checkForPayPalAuthCompletion(newIntent)
+ }

- override fun onDestroy() {
- super.onDestroy()
- payPalClient.removeObservers()
- }

private fun launchPayPalCheckout() {
val checkoutRequest: PayPalWebCheckoutRequest = TODO("Create a PayPal checkout request.")
- payPalClient.start(checkoutRequest)
+ payPalClient.start(this, checkoutRequest)
+ when (val result = paypalClient.start(this, checkoutRequest)) {
+ is PayPalPresentAuthChallengeResult.Success -> {
+ // Capture auth state for balancing call to finishStart() when
+ // the merchant application re-enters the foreground
+ authState = result.authState
+ }
+ is PayPalPresentAuthChallengeResult.Failure -> TODO("Handle Present Auth Challenge Failure")
+ }
}

private fun launchPayPalVault() {
val vaultRequest: PayPalWebVaultRequest = TODO("Create a card vault request.")
- payPalClient.vault(vaultRequest)
+ payPalClient.vault(this, vaultRequest)
+ when (val result = paypalClient.vault(this, vaultRequest)) {
+ is PayPalPresentAuthChallengeResult.Success -> {
+ // Capture auth state for balancing call to finishVault() when
+ // the merchant application re-enters the foreground
+ authState = result.authState
+ }
+ is PayPalPresentAuthChallengeResult.Failure -> TODO("Handle Present Auth Challenge Failure")
+ }
}

+ fun checkForPayPalAuthCompletion(intent: Intent) = authState?.let { state ->
+ // check for checkout completion
+ when (val checkoutResult = cardClient.finishStart(intent, state)) {
+ is PayPalWebCheckoutFinishStartResult.Success -> {
+ TODO("Capture or authorize order on your server.")
+ authState = null // Discard auth state when done
+ }
+
+ is PayPalWebCheckoutFinishStartResult.Failure -> {
+ TODO("Handle approve order failure.")
+ authState = null // Discard auth state when done
+ }
+
+ is PayPalWebCheckoutFinishStartResult.Canceled -> {
+ TODO("Notify user PayPal checkout was canceled.")
+ authState = null // Discard auth state when done
+ }
+
+ PayPalWebCheckoutFinishStartResult.NoResult -> {
+ // there isn't enough information to determine the state of the auth challenge for this payment method
+ }
+ }
+
+ // check for vault completion
+ when (val vaultResult = cardClient.finishVault(intent, state)) {
+ is PayPalWebCheckoutFinishVaultResult.Success -> {
+ TODO("Create payment token on your server.")
+ authState = null // Discard auth state when done
+ }
+
+ is PayPalWebCheckoutFinishVaultResult.Failure -> {
+ TODO("Handle card vault failure.")
+ authState = null // Discard auth state when done
+ }
+
+ is PayPalWebCheckoutFinishVaultResult.Canceled -> {
+ TODO("Notify user PayPal vault was canceled.")
+ authState = null // Discard auth state when done
+ }
+
+ PayPalWebCheckoutFinishVaultResult.NoResult -> {
+ // there isn't enough information to determine the state of the auth challenge for this payment method
+ }
+ }
+ }

override fun onPayPalWebSuccess(result: PayPalWebCheckoutResult) {
TODO("Capture or authorize order on your server.")
+ // Discard auth state when done
+ authState = null
}
- override fun onPayPalWebSuccess(result: PayPalWebCheckoutResult) {
- TODO("Capture or authorize order on your server.")
- }

fun onPayPalWebFailure(error: PayPalSDKError) {
TODO("Handle approve order failure.")
+ // Discard auth state when done
+ authState = null
}
- override fun onPayPalWebFailure(error: PayPalSDKError) {
- TODO("Handle approve order failure.")
- }

fun onPayPalWebCanceled() {
TODO("Notify user PayPal checkout was canceled.")
+ // Discard auth state when done
+ authState = null
}
- override fun onPayPalWebCanceled() {
- TODO("Notify user PayPal checkout was canceled.")
- }

fun onPayPalWebVaultSuccess(result: PayPalWebVaultResult) {
TODO("Create payment token on your server.")
+ // Discard auth state when done
+ authState = null
}
- fun onPayPalWebVaultSuccess(result: PayPalWebVaultResult) {
- TODO("Create payment token on your server.")
- }

fun onPayPalWebVaultFailure(error: PayPalSDKError) {
TODO("Handle card vault failure.")
+ // Discard auth state when done
+ authState = null
}
- fun onPayPalWebVaultFailure(error: PayPalSDKError) {
- TODO("Handle card vault failure.")
- }

fun onPayPalWebVaultCanceled() {
TODO("Notify user PayPal vault was canceled.")
+ // Discard auth state when done
+ authState = null
}
- fun onPayPalWebVaultCanceled() {
- TODO("Notify user PayPal vault was canceled.")
- }
}
```

Expand All @@ -217,6 +319,13 @@ Here are some detailed notes on the changes made to PayPal Web Payments in v2:
- In `v2` the host application is responsible for calling `PayPalWebCheckoutClient#completeAuthChallenge()` to attempt completion of an auth challenge.
- The goal of this change is to make the SDK less opinionated and give host applications more control over the auth challenge user experience.

### Migration from Listener Patern to Result Types

- In `v1` the SDK would notify the host application of success, failure, etc. events using a registered listener
- In `v2` the host application will receive a sealed class `Result` type in response to each method invocation
- The goal of this change is to prevent having to retain listener references and to make auth challenge presentation more explicit
- Sealed class result types also have the added benefit of explicitly calling out all possible outcomes of a related method inovcation

</details>

## PayPal Native Payments
Expand Down

0 comments on commit 3de44e9

Please sign in to comment.