Skip to content

Commit

Permalink
[1.x] Remove 3DS URL Parsing (#296)
Browse files Browse the repository at this point in the history
* Remove url parsing from 3DS deep link return URLs to match feature parity with iOS.

* Cleanup unit tests.

* Update CHANGELOG.

* Update static analysis script for 1.x releases.
  • Loading branch information
sshropshire authored Nov 21, 2024
1 parent 3407956 commit 9116b84
Show file tree
Hide file tree
Showing 4 changed files with 28 additions and 165 deletions.
26 changes: 13 additions & 13 deletions .github/workflows/static_analysis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,17 +43,17 @@ jobs:
java-version: '17'
distribution: 'microsoft'

# Assemble artifacts from main branch
- name: Checkout Main Branch
# Assemble artifacts from Release branch
- name: Checkout Release Branch
uses: actions/checkout@v4
with:
ref: main
- name: Assemble Release AAR on Main Branch
ref: 1.x
- name: Assemble Release AAR on Release Branch
run: ./gradlew clean assembleRelease -x :Demo:assembleRelease # we exclude Demo module in assemble
- name: Upload Main Branch Artifacts
- name: Upload Release Branch Artifacts
uses: actions/upload-artifact@v4
with:
name: main-aar
name: 1.x-aar
path: |
CorePayments/build/outputs/aar/CorePayments-release.aar
CardPayments/build/outputs/aar/CardPayments-release.aar
Expand All @@ -70,7 +70,7 @@ jobs:
- name: Upload Current Branch Artifacts
uses: actions/upload-artifact@v4
with:
name: current-aar
name: 1.x-current-aar
path: |
CorePayments/build/outputs/aar/CorePayments-release.aar
CardPayments/build/outputs/aar/CardPayments-release.aar
Expand All @@ -92,14 +92,14 @@ jobs:

# Run Diffuse analysis
- name: Run Diffuse Analysis - CardPayments
run: diffuse diff --aar main-aar/CardPayments/build/outputs/aar/CardPayments-release.aar current-aar/CardPayments/build/outputs/aar/CardPayments-release.aar
run: diffuse diff --aar 1.x-aar/CardPayments/build/outputs/aar/CardPayments-release.aar 1.x-current-aar/CardPayments/build/outputs/aar/CardPayments-release.aar
- name: Run Diffuse Analysis - CorePayments
run: diffuse diff --aar main-aar/CorePayments/build/outputs/aar/CorePayments-release.aar current-aar/CorePayments/build/outputs/aar/CorePayments-release.aar
run: diffuse diff --aar 1.x-aar/CorePayments/build/outputs/aar/CorePayments-release.aar 1.x-current-aar/CorePayments/build/outputs/aar/CorePayments-release.aar
- name: Run Diffuse Analysis - PayPalWebPayments
run: diffuse diff --aar main-aar/PayPalWebPayments/build/outputs/aar/PayPalWebPayments-release.aar current-aar/PayPalWebPayments/build/outputs/aar/PayPalWebPayments-release.aar
run: diffuse diff --aar 1.x-aar/PayPalWebPayments/build/outputs/aar/PayPalWebPayments-release.aar 1.x-current-aar/PayPalWebPayments/build/outputs/aar/PayPalWebPayments-release.aar
- name: Run Diffuse Analysis - PayPalNativePayments
run: diffuse diff --aar main-aar/PayPalNativePayments/build/outputs/aar/PayPalNativePayments-release.aar current-aar/PayPalNativePayments/build/outputs/aar/PayPalNativePayments-release.aar
run: diffuse diff --aar 1.x-aar/PayPalNativePayments/build/outputs/aar/PayPalNativePayments-release.aar 1.x-current-aar/PayPalNativePayments/build/outputs/aar/PayPalNativePayments-release.aar
- name: Run Diffuse Analysis - PaymentButtons
run: diffuse diff --aar main-aar/PaymentButtons/build/outputs/aar/PaymentButtons-release.aar current-aar/PaymentButtons/build/outputs/aar/PaymentButtons-release.aar
run: diffuse diff --aar 1.x-aar/PaymentButtons/build/outputs/aar/PaymentButtons-release.aar 1.x-current-aar/PaymentButtons/build/outputs/aar/PaymentButtons-release.aar
- name: Run Diffuse Analysis - FraudProtection
run: diffuse diff --aar main-aar/FraudProtection/build/outputs/aar/FraudProtection-release.aar current-aar/FraudProtection/build/outputs/aar/FraudProtection-release.aar
run: diffuse diff --aar 1.x-aar/FraudProtection/build/outputs/aar/FraudProtection-release.aar 1.x-current-aar/FraudProtection/build/outputs/aar/FraudProtection-release.aar
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
# PayPal Android SDK Release Notes

# unreleased
## unreleased
* Gradle
* Update Kotlin version to `1.9.24`
* Update Android Gradle Plugin (AGP) to version `8.7.1`
* Explicitly declare Java 17 version as the target JVM toolchain
* CardPayments
* Skip deep link URL parsing in `CardClient` for `approveOrder()` and `vault()` 3DS authentication flows

## 1.7.1 (2024-10-29)
* Gradle
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,11 +88,14 @@ internal class CardAuthLauncher(
private fun parseApproveOrderResult(browserSwitchResult: BrowserSwitchResult): CardStatus? {
val orderId = browserSwitchResult.requestMetadata?.optString(METADATA_KEY_ORDER_ID)
return if (orderId == null) {
CardStatus.ApproveOrderError(CardError.unknownError, orderId)
CardStatus.ApproveOrderError(CardError.unknownError, null)
} else {
when (browserSwitchResult.status) {
BrowserSwitchStatus.SUCCESS ->
parseApproveOrderSuccessResult(browserSwitchResult, orderId)
BrowserSwitchStatus.SUCCESS -> {
val result =
CardResult(orderId = orderId, didAttemptThreeDSecureAuthentication = true)
CardStatus.ApproveOrderSuccess(result)
}

BrowserSwitchStatus.CANCELED -> CardStatus.ApproveOrderCanceled(orderId)
else -> null
Expand All @@ -101,57 +104,15 @@ internal class CardAuthLauncher(
}

private fun parseVaultSuccessResult(browserSwitchResult: BrowserSwitchResult): CardStatus {
val deepLinkUrl = browserSwitchResult.deepLinkUrl
val requestMetadata = browserSwitchResult.requestMetadata

return if (deepLinkUrl == null || requestMetadata == null) {
val setupTokenId = requestMetadata?.optString(METADATA_KEY_SETUP_TOKEN_ID)
return if (setupTokenId == null) {
CardStatus.VaultError(CardError.unknownError)
} else {
// TODO: see if there's a way that we can require the merchant to make their
// return and cancel urls conform to a strict schema

// NOTE: this assumes that when the merchant created a setup token, they used a
// return_url with word "success" in it (or a cancel_url with the word "cancel" in it)
val setupTokenId =
browserSwitchResult.requestMetadata?.optString(METADATA_KEY_SETUP_TOKEN_ID)
val deepLinkUrlString = deepLinkUrl.toString()
val didSucceed = deepLinkUrlString.contains("success")
if (didSucceed) {
val result = CardVaultResult(setupTokenId!!, "SCA_COMPLETE")
CardStatus.VaultSuccess(result)
} else {
val didCancel = deepLinkUrlString.contains("cancel")
if (didCancel) {
CardStatus.VaultCanceled(setupTokenId)
} else {
CardStatus.VaultError(CardError.unknownError)
}
}
}
}

private fun parseApproveOrderSuccessResult(
browserSwitchResult: BrowserSwitchResult,
orderId: String
): CardStatus {
val deepLinkUrl = browserSwitchResult.deepLinkUrl

return if (deepLinkUrl == null || deepLinkUrl.getQueryParameter("error") != null) {
CardStatus.ApproveOrderError(CardError.threeDSVerificationError, orderId)
} else {
val state = deepLinkUrl.getQueryParameter("state")
val code = deepLinkUrl.getQueryParameter("code")
if (state == null || code == null) {
CardStatus.ApproveOrderError(CardError.malformedDeepLinkError, orderId)
} else {
val liabilityShift = deepLinkUrl.getQueryParameter("liability_shift")
val result = CardResult(
orderId = orderId,
liabilityShift = liabilityShift,
didAttemptThreeDSecureAuthentication = true
)
CardStatus.ApproveOrderSuccess(result)
}
val result = CardVaultResult(setupTokenId, "SCA_COMPLETE")
CardStatus.VaultSuccess(result)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ class CardAuthLauncherUnitTest {
}

@Test
fun `deliverBrowserSwitchResult() returns approve order success when liability shift available`() {
fun `deliverBrowserSwitchResult() returns approve order success`() {
sut = CardAuthLauncher(browserSwitchClient)

val scheme = "com.paypal.android.demo"
Expand All @@ -124,91 +124,10 @@ class CardAuthLauncherUnitTest {
val status = sut.deliverBrowserSwitchResult(activity) as CardStatus.ApproveOrderSuccess
val cardResult = status.result
assertEquals("fake-order-id", cardResult.orderId)
assertEquals("NO", cardResult.liabilityShift)
assertTrue(cardResult.didAttemptThreeDSecureAuthentication)
assertNull(cardResult.status)
}

@Test
fun `deliverBrowserSwitchResult() returns approve order error when deep link contains an error`() {
sut = CardAuthLauncher(browserSwitchClient)

val scheme = "com.paypal.android.demo"
val domain = "example.com"
val successDeepLink = "$scheme://$domain/return_url?error=error"

val browserSwitchResult = createBrowserSwitchResult(
BrowserSwitchStatus.SUCCESS,
approveOrderMetadata,
Uri.parse(successDeepLink)
)
every { browserSwitchClient.deliverResult(activity) } returns browserSwitchResult

val status = sut.deliverBrowserSwitchResult(activity) as CardStatus.ApproveOrderError
val error = status.error
assertEquals(0, error.code)
assertEquals("3DS Verification is returning an error.", error.errorDescription)
}

@Test
fun `deliverBrowserSwitchResult() returns approve order error when deep link is null`() {
sut = CardAuthLauncher(browserSwitchClient)

val browserSwitchResult = createBrowserSwitchResult(
BrowserSwitchStatus.SUCCESS,
approveOrderMetadata,
deepLinkUrl = null
)
every { browserSwitchClient.deliverResult(activity) } returns browserSwitchResult

val status = sut.deliverBrowserSwitchResult(activity) as CardStatus.ApproveOrderError
val error = status.error
assertEquals(0, error.code)
assertEquals("3DS Verification is returning an error.", error.errorDescription)
}

@Test
fun `deliverBrowserSwitchResult() returns approve order error when deep link is missing code parameter`() {
sut = CardAuthLauncher(browserSwitchClient)

val scheme = "com.paypal.android.demo"
val domain = "example.com"
val successDeepLink = "$scheme://$domain/return_url?state=undefined&liability_shift=NO"

val browserSwitchResult = createBrowserSwitchResult(
BrowserSwitchStatus.SUCCESS,
approveOrderMetadata,
Uri.parse(successDeepLink)
)
every { browserSwitchClient.deliverResult(activity) } returns browserSwitchResult

val status = sut.deliverBrowserSwitchResult(activity) as CardStatus.ApproveOrderError
val error = status.error
assertEquals(1, error.code)
assertEquals("Malformed deeplink URL.", error.errorDescription)
}

@Test
fun `deliverBrowserSwitchResult() returns approve order error when deep link is missing state parameter`() {
sut = CardAuthLauncher(browserSwitchClient)

val scheme = "com.paypal.android.demo"
val domain = "example.com"
val successDeepLink = "$scheme://$domain/return_url?code=undefined&liability_shift=NO"

val browserSwitchResult = createBrowserSwitchResult(
BrowserSwitchStatus.SUCCESS,
approveOrderMetadata,
Uri.parse(successDeepLink)
)
every { browserSwitchClient.deliverResult(activity) } returns browserSwitchResult

val status = sut.deliverBrowserSwitchResult(activity) as CardStatus.ApproveOrderError
val error = status.error
assertEquals(1, error.code)
assertEquals("Malformed deeplink URL.", error.errorDescription)
}

@Test
fun `deliverBrowserSwitchResult() returns approve order canceled when browser switch was canceled`() {
sut = CardAuthLauncher(browserSwitchClient)
Expand All @@ -222,7 +141,7 @@ class CardAuthLauncherUnitTest {
}

@Test
fun `deliverBrowserSwitchResult() returns vault success when deep link url contains the word success`() {
fun `deliverBrowserSwitchResult() returns vault success`() {
sut = CardAuthLauncher(browserSwitchClient)

val scheme = "com.paypal.android.demo"
Expand All @@ -242,25 +161,6 @@ class CardAuthLauncherUnitTest {
assertEquals("SCA_COMPLETE", vaultResult.status)
}

@Test
fun `deliverBrowserSwitchResult() returns vault canceled when deep link url contains the word cancel`() {
sut = CardAuthLauncher(browserSwitchClient)

val scheme = "com.paypal.android.demo"
val domain = "example.com"
val successDeepLink = "$scheme://$domain/canceled"

val browserSwitchResult = createBrowserSwitchResult(
BrowserSwitchStatus.SUCCESS,
vaultMetadata,
Uri.parse(successDeepLink)
)
every { browserSwitchClient.deliverResult(activity) } returns browserSwitchResult

val status = sut.deliverBrowserSwitchResult(activity) as CardStatus.VaultCanceled
assertEquals("fake-setup-token-id", status.setupTokenId)
}

private fun createBrowserSwitchResult(
@BrowserSwitchStatus status: Int,
metadata: JSONObject? = null,
Expand Down

0 comments on commit 9116b84

Please sign in to comment.