Skip to content
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

2.0.0-beta2 Migration Guide #309

Merged
merged 9 commits into from
Jan 13, 2025
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
Loading