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

Visa Checkout Single Result Objects #861

Merged
merged 6 commits into from
Dec 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,10 @@
modify parameters
* Replace `SEPADirectDebitClient#tokenize` with`SEPADirectDebitClient#createPaymentAuthRequest`
and modify parameters
* Visa Checkout
* Change parameters of `VisaCheckoutCreateProfileBuilderCallback` and
`VisaCheckoutTokenizeCallback`
* Add `VisaCheckoutProfileBuilderResult` and `VisaCheckoutTokenizeResult`
* American Express
* Change parameters of `AmericanExpressGetRewardsBalanceCallback`
* Add `AmericanExpressResult`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@

import com.braintreepayments.api.PaymentMethodNonce;
import com.braintreepayments.api.VisaCheckoutClient;
import com.braintreepayments.api.VisaCheckoutProfileBuilderResult;
import com.braintreepayments.api.VisaCheckoutResult;
import com.visa.checkout.CheckoutButton;
import com.visa.checkout.Profile;
import com.visa.checkout.PurchaseInfo;
Expand All @@ -31,11 +33,11 @@ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup c
checkoutButton = view.findViewById(R.id.visa_checkout_button);

visaCheckoutClient = new VisaCheckoutClient(requireContext(), super.getAuthStringArg());
visaCheckoutClient.createProfileBuilder((profileBuilder, error) -> {
if (profileBuilder != null) {
setupVisaCheckoutButton(profileBuilder);
} else {
handleError(error);
visaCheckoutClient.createProfileBuilder((profileBuilderResult) -> {
if (profileBuilderResult instanceof VisaCheckoutProfileBuilderResult.Failure) {
handleError(((VisaCheckoutProfileBuilderResult.Failure) profileBuilderResult).getError());
} else if (profileBuilderResult instanceof VisaCheckoutProfileBuilderResult.Success) {
setupVisaCheckoutButton(((VisaCheckoutProfileBuilderResult.Success) profileBuilderResult).getProfileBuilder());
}
});
return view;
Expand All @@ -54,11 +56,11 @@ public void onButtonClick(LaunchReadyHandler launchReadyHandler) {

@Override
public void onResult(VisaPaymentSummary visaPaymentSummary) {
visaCheckoutClient.tokenize(visaPaymentSummary, (paymentMethodNonce, error) -> {
if (paymentMethodNonce != null) {
handlePaymentMethodNonceCreated(paymentMethodNonce);
} else {
handleError(error);
visaCheckoutClient.tokenize(visaPaymentSummary, (visaCheckoutResult) -> {
if (visaCheckoutResult instanceof VisaCheckoutResult.Failure) {
handleError(((VisaCheckoutResult.Failure) visaCheckoutResult).getError());
} else if (visaCheckoutResult instanceof VisaCheckoutResult.Success) {
handlePaymentMethodNonceCreated(((VisaCheckoutResult.Success) visaCheckoutResult).getNonce());
}
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,13 @@
import android.content.Context;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;

import com.visa.checkout.Environment;
import com.visa.checkout.Profile;
import com.visa.checkout.VisaPaymentSummary;

import org.json.JSONException;
import org.json.JSONObject;

import java.util.List;

Expand Down Expand Up @@ -75,8 +73,7 @@ public void createProfileBuilder(
isVisaCheckoutSDKAvailable() && configuration.isVisaCheckoutEnabled();

if (!enabledAndSdkAvailable) {
callback.onResult(null,
new ConfigurationException("Visa Checkout is not enabled."));
callback.onVisaCheckoutProfileBuilderResult(new VisaCheckoutProfileBuilderResult.Failure(new ConfigurationException("Visa Checkout is not enabled.")));
return;
}

Expand All @@ -95,7 +92,7 @@ public void createProfileBuilder(
profileBuilder.setDataLevel(Profile.DataLevel.FULL);
profileBuilder.setExternalClientId(configuration.getVisaCheckoutExternalClientId());

callback.onResult(profileBuilder, null);
callback.onVisaCheckoutProfileBuilderResult(new VisaCheckoutProfileBuilderResult.Success(profileBuilder));
});
}

Expand All @@ -122,13 +119,13 @@ public void tokenize(@NonNull VisaPaymentSummary visaPaymentSummary,
try {
VisaCheckoutNonce visaCheckoutNonce =
VisaCheckoutNonce.fromJSON(tokenizationResponse);
callback.onResult(visaCheckoutNonce, null);
callback.onVisaCheckoutResult(new VisaCheckoutResult.Success(visaCheckoutNonce));
braintreeClient.sendAnalyticsEvent("visacheckout.tokenize.succeeded");
} catch (JSONException e) {
callback.onResult(null, e);
callback.onVisaCheckoutResult(new VisaCheckoutResult.Failure(e));
}
} else {
callback.onResult(null, exception);
} else if (exception != null) {
callback.onVisaCheckoutResult(new VisaCheckoutResult.Failure(exception));
braintreeClient.sendAnalyticsEvent("visacheckout.tokenize.failed");
}
});
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.braintreepayments.api

/**
* Callback for receiving result of[VisaCheckoutClient.createProfileBuilder].
*/
fun interface VisaCheckoutCreateProfileBuilderCallback {
/**
* @param profileBuilderResult a [VisaCheckoutProfileBuilderResult]
*/
fun onVisaCheckoutProfileBuilderResult(profileBuilderResult: VisaCheckoutProfileBuilderResult)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.braintreepayments.api

import com.visa.checkout.Profile

/**
* Result of creating a Visa Checkout profile builder
*/
sealed class VisaCheckoutProfileBuilderResult {

/**
* The [profileBuilder] was successfully created
*/
class Success(val profileBuilder: Profile.ProfileBuilder) : VisaCheckoutProfileBuilderResult()

/**
* There was an [error] creating the profile builder
*/
class Failure(val error: Exception) : VisaCheckoutProfileBuilderResult()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.braintreepayments.api

/**
* Result of tokenizing a Visa Checkout account
*/
sealed class VisaCheckoutResult {

/**
* The Visa Checkout flow completed successfully. This [nonce] should be sent to your server.
*/
class Success(val nonce: VisaCheckoutNonce) : VisaCheckoutResult()

/**
* There was an [error] in the Visa Checkout flow.
*/
class Failure(val error: Exception) : VisaCheckoutResult()
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.braintreepayments.api

/**
* Callback for receiving result of [VisaCheckoutClient.tokenize].
*/
fun interface VisaCheckoutTokenizeCallback {
/**
* @param visaCheckoutResult a [VisaCheckoutResult]
*/
fun onVisaCheckoutResult(visaCheckoutResult: VisaCheckoutResult)
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@ import junit.framework.TestCase.assertEquals
import junit.framework.TestCase.assertNotNull
import org.json.JSONException
import org.json.JSONObject
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import java.util.Arrays
import java.util.concurrent.CountDownLatch

@RunWith(RobolectricTestRunner::class)
Expand Down Expand Up @@ -47,11 +47,13 @@ class VisaCheckoutClientUnitTest {
val listener = mockk<VisaCheckoutCreateProfileBuilderCallback>(relaxed = true)
sut.createProfileBuilder(listener)

val configurationExceptionSlot = slot<ConfigurationException>()
verify(exactly = 1) { listener.onResult(null, capture(configurationExceptionSlot)) }
val configurationExceptionSlot = slot<VisaCheckoutProfileBuilderResult>()
verify(exactly = 1) { listener.onVisaCheckoutProfileBuilderResult(capture(configurationExceptionSlot)) }

val configurationException = configurationExceptionSlot.captured
assertEquals("Visa Checkout is not enabled.", configurationException.message)
val profileBuilderResult = configurationExceptionSlot.captured
assertTrue(profileBuilderResult is VisaCheckoutProfileBuilderResult.Failure)
val exception = (profileBuilderResult as VisaCheckoutProfileBuilderResult.Failure).error
assertEquals("Visa Checkout is not enabled.", exception.message)
}

@Test
Expand All @@ -72,10 +74,14 @@ class VisaCheckoutClientUnitTest {
.configurationSuccess(fromJson(configString))
.build()
val sut = VisaCheckoutClient(braintreeClient, apiClient)
sut.createProfileBuilder { profileBuilder, error ->
val expectedCardBrands = Arrays.asList(CardBrand.VISA, CardBrand.MASTERCARD)
val profile = profileBuilder!!.build()
sut.createProfileBuilder { profileBuilderResult ->
assertTrue(profileBuilderResult is VisaCheckoutProfileBuilderResult.Success)
val profileBuilder = (profileBuilderResult as VisaCheckoutProfileBuilderResult
.Success).profileBuilder
val profile = profileBuilder.build()
assertNotNull(profile)
assertTrue(profile.acceptedCardBrands.contains(CardBrand.VISA))
assertTrue(profile.acceptedCardBrands.contains(CardBrand.MASTERCARD))
lock.countDown()
}
lock.await()
Expand All @@ -99,10 +105,13 @@ class VisaCheckoutClientUnitTest {
.configurationSuccess(fromJson(configString))
.build()
val sut = VisaCheckoutClient(braintreeClient, apiClient)
sut.createProfileBuilder { profileBuilder, error ->
val expectedCardBrands = Arrays.asList(CardBrand.VISA, CardBrand.MASTERCARD)
val profile = profileBuilder!!.build()
sut.createProfileBuilder { profileBuilderResult ->
val profileBuilder = (profileBuilderResult as VisaCheckoutProfileBuilderResult
.Success).profileBuilder
val profile = profileBuilder.build()
assertNotNull(profile)
assertTrue(profile.acceptedCardBrands.contains(CardBrand.VISA))
assertTrue(profile.acceptedCardBrands.contains(CardBrand.MASTERCARD))
lock.countDown()
}
lock.await()
Expand All @@ -118,9 +127,11 @@ class VisaCheckoutClientUnitTest {
.configurationSuccess(configurationWithVisaCheckout)
.build()
val sut = VisaCheckoutClient(braintreeClient, apiClient)
val listener = mockk<VisaCheckoutTokenizeCallback>(relaxed = true)
sut.tokenize(visaPaymentSummary, listener)
verify { listener.onResult(any(), null) }
sut.tokenize(visaPaymentSummary) { visaCheckoutResult ->
assertTrue(visaCheckoutResult is VisaCheckoutResult.Success)
val nonce = (visaCheckoutResult as VisaCheckoutResult.Success).nonce
assertNotNull(nonce)
}
}

@Test
Expand Down Expand Up @@ -148,9 +159,11 @@ class VisaCheckoutClientUnitTest {
.configurationSuccess(configurationWithVisaCheckout)
.build()
val sut = VisaCheckoutClient(braintreeClient, apiClient)
val listener = mockk<VisaCheckoutTokenizeCallback>(relaxed = true)
sut.tokenize(visaPaymentSummary, listener)
verify { listener.onResult(null, tokenizeError) }
sut.tokenize(visaPaymentSummary) { visaCheckoutResult ->
assertTrue(visaCheckoutResult is VisaCheckoutResult.Failure)
val error = (visaCheckoutResult as VisaCheckoutResult.Failure).error
assertEquals(tokenizeError, error)
}
}

@Test
Expand Down
35 changes: 34 additions & 1 deletion v5_MIGRATION_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ basics for updating your Braintree integration from v4 to v5.
1. [PayPal](#paypal)
1. [Local Payment](#local-payment)
1. [SEPA Direct Debit](#sepa-direct-debit)
1. [Visa Checkout](#visa-checkout)

## Android API

Expand Down Expand Up @@ -508,4 +509,36 @@ class MyActivity : FragmentActivity() {
- override fun onSEPADirectDebitFailure(error: java.lang.Exception) {
- // handle error
- }
}
}
```

## Visa Checkout

The Visa Checkout integration has been updated to improve result handling.

```diff
class MyActivity : FragmentActivity() {

- private lateinit var braintreeClient: BraintreeClient
private lateinit var visaCheckoutClient: VisaCheckoutClient

fun initializeClients() {
- braintreeClient = BraintreeClient(context, "TOKENIZATION_KEY_OR_CLIENT_TOKEN")
- visaCheckoutClient = VisaCheckoutClient(braintreeClient)
+ visaCheckoutClient = visaCheckoutClient(context, "TOKENIZATION_KEY_OR_CLIENT_TOKEN")
}

fun onPaymentButtonClick() {
- visaCheckoutClient.tokenize(request)
+ sepaDirectDebitClient.tokenize(request) { visaCheckoutResult ->
+ when (visaCheckoutResult) {
+ is VisaCheckoutResult.Failure -> {
+ // handle visaCheckoutResult.error
+ is VisaCheckoutResult.Success -> {
+ // handle visaCheckoutResult.nonce
+ }
+ }
+ }
}
}
```
Loading