diff --git a/CHANGELOG.md b/CHANGELOG.md index af602afee7..370fe72000 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -149,6 +149,7 @@ * Move `ThreeDSecureInfo` to `three-d-secure` module * Add `CardResult` object * Change `CardTokenizeCallback` parameters + * Change `BinType` from String to enum * SEPA Direct Debit * Update package name to `com.braintreepayments.api.sepadirectdebit` * Remove `SEPADirectDebitLifecycleObserver` and `SEPADirectDebitListener` diff --git a/DataCollector/src/test/java/com/braintreepayments/api/datacollector/DataCollectorUnitTest.kt b/DataCollector/src/test/java/com/braintreepayments/api/datacollector/DataCollectorUnitTest.kt index 4dea0d4354..64ac6ae4de 100644 --- a/DataCollector/src/test/java/com/braintreepayments/api/datacollector/DataCollectorUnitTest.kt +++ b/DataCollector/src/test/java/com/braintreepayments/api/datacollector/DataCollectorUnitTest.kt @@ -12,7 +12,6 @@ import io.mockk.every import io.mockk.impl.annotations.MockK import io.mockk.slot import io.mockk.verify -import org.json.JSONException import org.json.JSONObject import org.junit.Assert import org.junit.Before @@ -26,7 +25,8 @@ class DataCollectorUnitTest { @MockK lateinit var context: Context - @MockK lateinit var configuration: Configuration + @MockK + lateinit var configuration: Configuration @MockK lateinit var uuidHelper: UUIDHelper @@ -48,7 +48,6 @@ class DataCollectorUnitTest { .build() @Before - @Throws(JSONException::class) fun beforeEach() { MockKAnnotations.init(this, relaxed = true) diff --git a/DataCollector/src/test/java/com/braintreepayments/api/datacollector/MagnesInternalClientUnitTest.kt b/DataCollector/src/test/java/com/braintreepayments/api/datacollector/MagnesInternalClientUnitTest.kt index b47524eef3..38803dc868 100644 --- a/DataCollector/src/test/java/com/braintreepayments/api/datacollector/MagnesInternalClientUnitTest.kt +++ b/DataCollector/src/test/java/com/braintreepayments/api/datacollector/MagnesInternalClientUnitTest.kt @@ -73,14 +73,14 @@ class MagnesInternalClientUnitTest { @Test fun getClientMetaDataId_returnsEmptyStringWhenContextIsNull() { - val sut = MagnesInternalClient(magnesSDK) - val result = sut.getClientMetadataId( - null, sandboxConfiguration, - dataCollectorInternalRequest - ) + val sut = MagnesInternalClient(magnesSDK) + val result = sut.getClientMetadataId( + null, sandboxConfiguration, + dataCollectorInternalRequest + ) - Assert.assertEquals("", result) - } + Assert.assertTrue(result.isEmpty()) + } @Throws(InvalidInputException::class) @Test diff --git a/SEPADirectDebit/src/main/java/com/braintreepayments/api/sepadirectdebit/CreateMandateResult.java b/SEPADirectDebit/src/main/java/com/braintreepayments/api/sepadirectdebit/CreateMandateResult.java deleted file mode 100644 index f258220dca..0000000000 --- a/SEPADirectDebit/src/main/java/com/braintreepayments/api/sepadirectdebit/CreateMandateResult.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.braintreepayments.api.sepadirectdebit; - -class CreateMandateResult { - - private final String approvalUrl; - private final String ibanLastFour; - private final String customerId; - private final String bankReferenceToken; - private final SEPADirectDebitMandateType mandateType; - - CreateMandateResult(String approvalUrl, String ibanLastFour, String customerId, - String bankReferenceToken, String mandateType) { - this.approvalUrl = approvalUrl; - this.ibanLastFour = ibanLastFour; - this.customerId = customerId; - this.bankReferenceToken = bankReferenceToken; - this.mandateType = SEPADirectDebitMandateType.fromString(mandateType); - } - - String getApprovalUrl() { - return approvalUrl; - } - - String getIbanLastFour() { - return ibanLastFour; - } - - String getCustomerId() { - return customerId; - } - - String getBankReferenceToken() { - return bankReferenceToken; - } - - SEPADirectDebitMandateType getMandateType() { - return mandateType; - } - -} diff --git a/SEPADirectDebit/src/main/java/com/braintreepayments/api/sepadirectdebit/CreateMandateResult.kt b/SEPADirectDebit/src/main/java/com/braintreepayments/api/sepadirectdebit/CreateMandateResult.kt new file mode 100644 index 0000000000..32b5fab4bc --- /dev/null +++ b/SEPADirectDebit/src/main/java/com/braintreepayments/api/sepadirectdebit/CreateMandateResult.kt @@ -0,0 +1,9 @@ +package com.braintreepayments.api.sepadirectdebit + +data class CreateMandateResult internal constructor( + val approvalUrl: String, + val ibanLastFour: String, + val customerId: String, + val bankReferenceToken: String, + val mandateType: SEPADirectDebitMandateType +) diff --git a/SEPADirectDebit/src/main/java/com/braintreepayments/api/sepadirectdebit/SEPADirectDebitApi.java b/SEPADirectDebit/src/main/java/com/braintreepayments/api/sepadirectdebit/SEPADirectDebitApi.java deleted file mode 100644 index 26fe6e7eab..0000000000 --- a/SEPADirectDebit/src/main/java/com/braintreepayments/api/sepadirectdebit/SEPADirectDebitApi.java +++ /dev/null @@ -1,145 +0,0 @@ -package com.braintreepayments.api.sepadirectdebit; - -import com.braintreepayments.api.core.BraintreeClient; -import com.braintreepayments.api.sharedutils.HttpResponseCallback; - -import org.json.JSONException; -import org.json.JSONObject; - -class SEPADirectDebitApi { - - private final BraintreeClient braintreeClient; - - SEPADirectDebitApi(BraintreeClient braintreeClient) { - this.braintreeClient = braintreeClient; - } - - void createMandate(SEPADirectDebitRequest sepaDirectDebitRequest, String returnUrlScheme, - final CreateMandateCallback callback) { - try { - JSONObject jsonObject = - buildCreateMandateRequest(sepaDirectDebitRequest, returnUrlScheme); - String url = "v1/sepa_debit"; - braintreeClient.sendPOST(url, jsonObject.toString(), (responseBody, httpError) -> { - if (responseBody != null) { - try { - CreateMandateResult result = parseCreateMandateResponse(responseBody); - callback.onResult(result, null); - } catch (JSONException e) { - callback.onResult(null, e); - } - } else if (httpError != null) { - callback.onResult(null, httpError); - } - }); - } catch (JSONException e) { - callback.onResult(null, e); - } - } - - void tokenize(String ibanLastFour, String customerId, String bankReferenceToken, - String mandateType, final SEPADirectDebitInternalTokenizeCallback callback) { - try { - JSONObject jsonObject = - buildTokenizeRequest(ibanLastFour, customerId, bankReferenceToken, mandateType); - String url = "v1/payment_methods/sepa_debit_accounts"; - braintreeClient.sendPOST(url, jsonObject.toString(), new HttpResponseCallback() { - - @Override - public void onResult(String responseBody, Exception httpError) { - if (responseBody != null) { - try { - SEPADirectDebitNonce nonce = parseTokenizeResponse(responseBody); - callback.onResult(nonce, null); - } catch (JSONException jsonException) { - callback.onResult(null, jsonException); - } - } else if (httpError != null) { - callback.onResult(null, httpError); - } - } - }); - } catch (JSONException e) { - callback.onResult(null, e); - } - } - - private SEPADirectDebitNonce parseTokenizeResponse(String responseBody) throws JSONException { - JSONObject jsonResponse = new JSONObject(responseBody); - return SEPADirectDebitNonce.fromJSON(jsonResponse); - } - - private JSONObject buildTokenizeRequest(String ibanLastFour, String customerId, - String bankReferenceToken, String mandateType) - throws JSONException { - JSONObject accountData = new JSONObject() - .put("last_4", ibanLastFour) - .put("merchant_or_partner_customer_id", customerId) - .put("bank_reference_token", bankReferenceToken) - .put("mandate_type", mandateType); - JSONObject requestData = new JSONObject() - .put("sepa_debit_account", accountData); - - return requestData; - } - - private CreateMandateResult parseCreateMandateResponse(String responseBody) - throws JSONException { - JSONObject json = new JSONObject(responseBody); - JSONObject sepaDebitAccount = json.getJSONObject("message").getJSONObject("body") - .getJSONObject("sepaDebitAccount"); - String approvalUrl = sepaDebitAccount.getString("approvalUrl"); - String ibanLastFour = sepaDebitAccount.getString("last4"); - String customerId = sepaDebitAccount.getString("merchantOrPartnerCustomerId"); - String bankReferenceToken = sepaDebitAccount.getString("bankReferenceToken"); - String mandateType = sepaDebitAccount.getString("mandateType"); - - return new CreateMandateResult(approvalUrl, ibanLastFour, customerId, bankReferenceToken, - mandateType); - } - - private JSONObject buildCreateMandateRequest(SEPADirectDebitRequest sepaDirectDebitRequest, - String returnUrlScheme) throws JSONException { - JSONObject sepaDebitData = new JSONObject() - .putOpt("account_holder_name", sepaDirectDebitRequest.getAccountHolderName()) - .putOpt("merchant_or_partner_customer_id", sepaDirectDebitRequest.getCustomerId()) - .putOpt("iban", sepaDirectDebitRequest.getIban()) - .putOpt("mandate_type", sepaDirectDebitRequest.getMandateType().toString()); - - if (sepaDirectDebitRequest.getBillingAddress() != null) { - JSONObject billingAddress = new JSONObject() - .putOpt("address_line_1", - sepaDirectDebitRequest.getBillingAddress().getStreetAddress()) - .putOpt("address_line_2", - sepaDirectDebitRequest.getBillingAddress().getExtendedAddress()) - .putOpt("admin_area_1", - sepaDirectDebitRequest.getBillingAddress().getLocality()) - .putOpt("admin_area_2", sepaDirectDebitRequest.getBillingAddress().getRegion()) - .putOpt("postal_code", - sepaDirectDebitRequest.getBillingAddress().getPostalCode()) - .putOpt("country_code", - sepaDirectDebitRequest.getBillingAddress().getCountryCodeAlpha2()); - - sepaDebitData.put("billing_address", billingAddress); - } - - String cancelUrl = String.format("%s://sepa/cancel", returnUrlScheme); - String successUrl = String.format("%s://sepa/success", returnUrlScheme); - - JSONObject requestData = new JSONObject() - .put("sepa_debit", sepaDebitData) - .put("cancel_url", cancelUrl) - .put("return_url", successUrl); - - if (sepaDirectDebitRequest.getMerchantAccountId() != null) { - requestData.putOpt("merchant_account_id", - sepaDirectDebitRequest.getMerchantAccountId()); - } - - if (sepaDirectDebitRequest.getLocale() != null) { - requestData.putOpt("locale", sepaDirectDebitRequest.getLocale()); - } - - return requestData; - } -} diff --git a/SEPADirectDebit/src/main/java/com/braintreepayments/api/sepadirectdebit/SEPADirectDebitApi.kt b/SEPADirectDebit/src/main/java/com/braintreepayments/api/sepadirectdebit/SEPADirectDebitApi.kt new file mode 100644 index 0000000000..e7a6c3d6d8 --- /dev/null +++ b/SEPADirectDebit/src/main/java/com/braintreepayments/api/sepadirectdebit/SEPADirectDebitApi.kt @@ -0,0 +1,174 @@ +package com.braintreepayments.api.sepadirectdebit + +import com.braintreepayments.api.core.BraintreeClient +import com.braintreepayments.api.sepadirectdebit.SEPADirectDebitNonce.Companion.fromJSON +import org.json.JSONException +import org.json.JSONObject + +internal class SEPADirectDebitApi(private val braintreeClient: BraintreeClient) { + + fun createMandate( + sepaDirectDebitRequest: SEPADirectDebitRequest, + returnUrlScheme: String, + callback: CreateMandateCallback + ) { + try { + val jsonObject = + buildCreateMandateRequest(sepaDirectDebitRequest, returnUrlScheme) + val url = "v1/sepa_debit" + braintreeClient.sendPOST( + url, + jsonObject.toString() + ) { responseBody, httpError -> + if (responseBody != null) { + try { + val result = parseCreateMandateResponse(responseBody) + callback.onResult(result, null) + } catch (e: JSONException) { + callback.onResult(null, e) + } + } else if (httpError != null) { + callback.onResult(null, httpError) + } + } + } catch (e: JSONException) { + callback.onResult(null, e) + } + } + + fun tokenize( + ibanLastFour: String, + customerId: String, + bankReferenceToken: String, + mandateType: String, + callback: SEPADirectDebitInternalTokenizeCallback + ) { + try { + val jsonObject = + buildTokenizeRequest(ibanLastFour, customerId, bankReferenceToken, mandateType) + val url = "v1/payment_methods/sepa_debit_accounts" + braintreeClient.sendPOST( + url, + jsonObject.toString() + ) { responseBody, httpError -> + if (responseBody != null) { + try { + val nonce = parseTokenizeResponse(responseBody) + callback.onResult(nonce, null) + } catch (jsonException: JSONException) { + callback.onResult(null, jsonException) + } + } else if (httpError != null) { + callback.onResult(null, httpError) + } + } + } catch (e: JSONException) { + callback.onResult(null, e) + } + } + + @Throws(JSONException::class) + private fun parseTokenizeResponse(responseBody: String): SEPADirectDebitNonce { + val jsonResponse = JSONObject(responseBody) + return fromJSON(jsonResponse) + } + + @Throws(JSONException::class) + private fun buildTokenizeRequest( + ibanLastFour: String, customerId: String, + bankReferenceToken: String, mandateType: String + ): JSONObject { + val accountData = JSONObject() + .put("last_4", ibanLastFour) + .put("merchant_or_partner_customer_id", customerId) + .put("bank_reference_token", bankReferenceToken) + .put("mandate_type", mandateType) + val requestData = JSONObject() + .put("sepa_debit_account", accountData) + + return requestData + } + + @Throws(JSONException::class) + private fun parseCreateMandateResponse(responseBody: String): CreateMandateResult { + val json = JSONObject(responseBody) + val sepaDebitAccount = json.getJSONObject("message").getJSONObject("body") + .getJSONObject("sepaDebitAccount") + val approvalUrl = sepaDebitAccount.getString("approvalUrl") + val ibanLastFour = sepaDebitAccount.getString("last4") + val customerId = sepaDebitAccount.getString("merchantOrPartnerCustomerId") + val bankReferenceToken = sepaDebitAccount.getString("bankReferenceToken") + val mandateType = sepaDebitAccount.getString("mandateType") + + return CreateMandateResult( + approvalUrl, + ibanLastFour, + customerId, + bankReferenceToken, + SEPADirectDebitMandateType.valueOf(mandateType) + ) + } + + @Throws(JSONException::class) + private fun buildCreateMandateRequest( + sepaDirectDebitRequest: SEPADirectDebitRequest, + returnUrlScheme: String + ): JSONObject { + val sepaDebitData = JSONObject() + .putOpt("account_holder_name", sepaDirectDebitRequest.accountHolderName) + .putOpt("merchant_or_partner_customer_id", sepaDirectDebitRequest.customerId) + .putOpt("iban", sepaDirectDebitRequest.iban) + .putOpt("mandate_type", sepaDirectDebitRequest.mandateType.toString()) + + val requestBillingAddress = sepaDirectDebitRequest.billingAddress + if (requestBillingAddress != null) { + val billingAddress = JSONObject() + .putOpt( + "address_line_1", + requestBillingAddress.streetAddress + ) + .putOpt( + "address_line_2", + requestBillingAddress.extendedAddress + ) + .putOpt( + "admin_area_1", + requestBillingAddress.locality + ) + .putOpt("admin_area_2", + requestBillingAddress.region + ) + .putOpt( + "postal_code", + requestBillingAddress.postalCode + ) + .putOpt( + "country_code", + requestBillingAddress.countryCodeAlpha2 + ) + + sepaDebitData.put("billing_address", billingAddress) + } + + val cancelUrl = "$returnUrlScheme://sepa/cancel" + val successUrl = "$returnUrlScheme://sepa/success" + + val requestData = JSONObject() + .put("sepa_debit", sepaDebitData) + .put("cancel_url", cancelUrl) + .put("return_url", successUrl) + + if (sepaDirectDebitRequest.merchantAccountId != null) { + requestData.putOpt( + "merchant_account_id", + sepaDirectDebitRequest.merchantAccountId + ) + } + + if (sepaDirectDebitRequest.locale != null) { + requestData.putOpt("locale", sepaDirectDebitRequest.locale) + } + + return requestData + } +} diff --git a/SEPADirectDebit/src/main/java/com/braintreepayments/api/sepadirectdebit/SEPADirectDebitClient.java b/SEPADirectDebit/src/main/java/com/braintreepayments/api/sepadirectdebit/SEPADirectDebitClient.java deleted file mode 100644 index 78ccbab259..0000000000 --- a/SEPADirectDebit/src/main/java/com/braintreepayments/api/sepadirectdebit/SEPADirectDebitClient.java +++ /dev/null @@ -1,197 +0,0 @@ -package com.braintreepayments.api.sepadirectdebit; - -import android.content.Context; -import android.content.Intent; -import android.net.Uri; -import android.webkit.URLUtil; - -import androidx.activity.ComponentActivity; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.annotation.VisibleForTesting; - -import com.braintreepayments.api.BrowserSwitchFinalResult; -import com.braintreepayments.api.BrowserSwitchOptions; -import com.braintreepayments.api.core.BraintreeClient; -import com.braintreepayments.api.core.BraintreeException; -import com.braintreepayments.api.core.BraintreeRequestCodes; - -import org.json.JSONException; -import org.json.JSONObject; - -/** - * Used to integrate with SEPA Direct Debit. - */ -public class SEPADirectDebitClient { - - private static final String IBAN_LAST_FOUR_KEY = "ibanLastFour"; - private static final String CUSTOMER_ID_KEY = "customerId"; - private static final String BANK_REFERENCE_TOKEN_KEY = "bankReferenceToken"; - private static final String MANDATE_TYPE_KEY = "mandateType"; - - private final SEPADirectDebitApi sepaDirectDebitApi; - private final BraintreeClient braintreeClient; - - /** - * Initializes a new {@link SEPADirectDebitClient} instance - * - * @param context an Android Context - * @param authorization a Tokenization Key or Client Token used to authenticate - * @param returnUrlScheme a custom return url to use for browser and app switching - */ - public SEPADirectDebitClient( - @NonNull Context context, - @NonNull String authorization, - @Nullable String returnUrlScheme - ) { - this(new BraintreeClient(context, authorization, returnUrlScheme)); - } - - @VisibleForTesting - SEPADirectDebitClient(@NonNull BraintreeClient braintreeClient) { - this(braintreeClient, new SEPADirectDebitApi(braintreeClient)); - } - - @VisibleForTesting - SEPADirectDebitClient(BraintreeClient braintreeClient, SEPADirectDebitApi sepaDirectDebitApi) { - this.braintreeClient = braintreeClient; - this.sepaDirectDebitApi = sepaDirectDebitApi; - } - - /** - * Starts the SEPA tokenization process by creating a {@link SEPADirectDebitPaymentAuthRequestParams} to be used - * to launch the SEPA mandate flow in - * {@link SEPADirectDebitLauncher#launch(ComponentActivity, SEPADirectDebitPaymentAuthRequest.ReadyToLaunch)} - * - * @param sepaDirectDebitRequest {@link SEPADirectDebitRequest} - * @param callback {@link SEPADirectDebitPaymentAuthRequestCallback} - */ - public void createPaymentAuthRequest(@NonNull final SEPADirectDebitRequest sepaDirectDebitRequest, - @NonNull final SEPADirectDebitPaymentAuthRequestCallback callback) { - braintreeClient.sendAnalyticsEvent(SEPADirectDebitAnalytics.TOKENIZE_STARTED); - sepaDirectDebitApi.createMandate(sepaDirectDebitRequest, - braintreeClient.getReturnUrlScheme(), - (result, createMandateError) -> { - if (result != null) { - if (URLUtil.isValidUrl(result.getApprovalUrl())) { - braintreeClient.sendAnalyticsEvent(SEPADirectDebitAnalytics.CREATE_MANDATE_CHALLENGE_REQUIRED); - try { - SEPADirectDebitPaymentAuthRequestParams params = - new SEPADirectDebitPaymentAuthRequestParams(buildBrowserSwitchOptions(result)); - braintreeClient.sendAnalyticsEvent(SEPADirectDebitAnalytics.CREATE_MANDATE_SUCCEEDED); - callback.onSEPADirectDebitPaymentAuthResult(new SEPADirectDebitPaymentAuthRequest.ReadyToLaunch(params)); - } catch (JSONException exception) { - braintreeClient.sendAnalyticsEvent(SEPADirectDebitAnalytics.CREATE_MANDATE_FAILED); - callbackCreatePaymentAuthFailure(callback, new SEPADirectDebitPaymentAuthRequest.Failure(exception)); - } - } else if (result.getApprovalUrl().equals("null")) { - braintreeClient.sendAnalyticsEvent(SEPADirectDebitAnalytics.CREATE_MANDATE_SUCCEEDED); - // Mandate has already been approved - sepaDirectDebitApi.tokenize(result.getIbanLastFour(), - result.getCustomerId(), result.getBankReferenceToken(), - result.getMandateType().toString(), - (sepaDirectDebitNonce, tokenizeError) -> { - if (sepaDirectDebitNonce != null) { - callbackCreatePaymentAuthChallengeNotRequiredSuccess(callback, new SEPADirectDebitPaymentAuthRequest.LaunchNotRequired(sepaDirectDebitNonce)); - } else if (tokenizeError != null) { - callbackCreatePaymentAuthFailure(callback, new SEPADirectDebitPaymentAuthRequest.Failure(tokenizeError)); - } - }); - } else { - braintreeClient.sendAnalyticsEvent(SEPADirectDebitAnalytics.CREATE_MANDATE_FAILED); - callbackCreatePaymentAuthFailure(callback, new SEPADirectDebitPaymentAuthRequest.Failure(new BraintreeException("An unexpected error occurred."))); - } - } else if (createMandateError != null) { - braintreeClient.sendAnalyticsEvent(SEPADirectDebitAnalytics.CREATE_MANDATE_FAILED); - callbackCreatePaymentAuthFailure(callback, - new SEPADirectDebitPaymentAuthRequest.Failure(createMandateError)); - } - }); - } - - // TODO: - The wording in this docstring is confusing to me. Let's improve & align across all clients. - /** - * After receiving a result from the SEPA mandate web flow via - * {@link SEPADirectDebitLauncher#handleReturnToAppFromBrowser(SEPADirectDebitPendingRequest.Started, Intent)} , pass the - * {@link SEPADirectDebitPaymentAuthResult.Success} returned to this method to tokenize the SEPA - * account and receive a {@link SEPADirectDebitNonce} on success. - * - * @param paymentAuthResult a {@link SEPADirectDebitPaymentAuthResult.Success} received from - * {@link SEPADirectDebitLauncher#handleReturnToAppFromBrowser(SEPADirectDebitPendingRequest.Started, Intent)} - * @param callback {@link SEPADirectDebitInternalTokenizeCallback} - */ - public void tokenize(@NonNull SEPADirectDebitPaymentAuthResult.Success paymentAuthResult, - @NonNull final SEPADirectDebitTokenizeCallback callback) { - BrowserSwitchFinalResult.Success browserSwitchResult = - paymentAuthResult.getPaymentAuthInfo().getBrowserSwitchSuccess(); - - Uri deepLinkUri = browserSwitchResult.getReturnUrl(); - if (deepLinkUri != null) { - if (deepLinkUri.getPath().contains("success") && - deepLinkUri.getQueryParameter("success").equals("true")) { - braintreeClient.sendAnalyticsEvent(SEPADirectDebitAnalytics.CHALLENGE_SUCCEEDED); - JSONObject metadata = browserSwitchResult.getRequestMetadata(); - String ibanLastFour = metadata.optString(IBAN_LAST_FOUR_KEY); - String customerId = metadata.optString(CUSTOMER_ID_KEY); - String bankReferenceToken = metadata.optString(BANK_REFERENCE_TOKEN_KEY); - String mandateType = metadata.optString(MANDATE_TYPE_KEY); - - sepaDirectDebitApi.tokenize(ibanLastFour, customerId, bankReferenceToken, - mandateType, - (sepaDirectDebitNonce, error) -> { - if (sepaDirectDebitNonce != null) { - callbackTokenizeSuccess(callback, new SEPADirectDebitResult.Success(sepaDirectDebitNonce)); - } else if (error != null) { - callbackTokenizeFailure(callback, new SEPADirectDebitResult.Failure(error)); - } - }); - } else if (deepLinkUri.getPath().contains("cancel")) { - callbackTokenizeCancel(callback); - } - } else { - braintreeClient.sendAnalyticsEvent(SEPADirectDebitAnalytics.CHALLENGE_FAILED); - callbackTokenizeFailure(callback, new SEPADirectDebitResult.Failure(new BraintreeException("Unknown error"))); - } - } - - private void callbackCreatePaymentAuthFailure(SEPADirectDebitPaymentAuthRequestCallback callback, SEPADirectDebitPaymentAuthRequest.Failure result) { - braintreeClient.sendAnalyticsEvent(SEPADirectDebitAnalytics.TOKENIZE_FAILED); - callback.onSEPADirectDebitPaymentAuthResult(result); - } - - private void callbackCreatePaymentAuthChallengeNotRequiredSuccess(SEPADirectDebitPaymentAuthRequestCallback callback, SEPADirectDebitPaymentAuthRequest.LaunchNotRequired result) { - braintreeClient.sendAnalyticsEvent(SEPADirectDebitAnalytics.TOKENIZE_SUCCEEDED); - callback.onSEPADirectDebitPaymentAuthResult(result); - } - - private void callbackTokenizeCancel(SEPADirectDebitTokenizeCallback callback) { - braintreeClient.sendAnalyticsEvent(SEPADirectDebitAnalytics.CHALLENGE_CANCELED); - callback.onSEPADirectDebitResult(SEPADirectDebitResult.Cancel.INSTANCE); - } - - private void callbackTokenizeFailure(SEPADirectDebitTokenizeCallback callback, SEPADirectDebitResult.Failure result) { - braintreeClient.sendAnalyticsEvent(SEPADirectDebitAnalytics.TOKENIZE_FAILED); - callback.onSEPADirectDebitResult(result); - } - - private void callbackTokenizeSuccess(SEPADirectDebitTokenizeCallback callback, SEPADirectDebitResult.Success result) { - braintreeClient.sendAnalyticsEvent(SEPADirectDebitAnalytics.TOKENIZE_SUCCEEDED); - callback.onSEPADirectDebitResult(result); - } - - private BrowserSwitchOptions buildBrowserSwitchOptions(CreateMandateResult createMandateResult) throws JSONException { - JSONObject metadata = new JSONObject() - .put(IBAN_LAST_FOUR_KEY, createMandateResult.getIbanLastFour()) - .put(CUSTOMER_ID_KEY, createMandateResult.getCustomerId()) - .put(BANK_REFERENCE_TOKEN_KEY, createMandateResult.getBankReferenceToken()) - .put(MANDATE_TYPE_KEY, createMandateResult.getMandateType().toString()); - - BrowserSwitchOptions browserSwitchOptions = new BrowserSwitchOptions() - .requestCode(BraintreeRequestCodes.SEPA_DEBIT.getCode()) - .url(Uri.parse(createMandateResult.getApprovalUrl())) - .metadata(metadata) - .returnUrlScheme(braintreeClient.getReturnUrlScheme()); - - return browserSwitchOptions; - } -} diff --git a/SEPADirectDebit/src/main/java/com/braintreepayments/api/sepadirectdebit/SEPADirectDebitClient.kt b/SEPADirectDebit/src/main/java/com/braintreepayments/api/sepadirectdebit/SEPADirectDebitClient.kt new file mode 100644 index 0000000000..cbf3c42562 --- /dev/null +++ b/SEPADirectDebit/src/main/java/com/braintreepayments/api/sepadirectdebit/SEPADirectDebitClient.kt @@ -0,0 +1,223 @@ +package com.braintreepayments.api.sepadirectdebit + +import android.content.Context +import android.net.Uri +import android.webkit.URLUtil +import androidx.annotation.VisibleForTesting +import com.braintreepayments.api.BrowserSwitchFinalResult +import com.braintreepayments.api.BrowserSwitchOptions +import com.braintreepayments.api.core.BraintreeClient +import com.braintreepayments.api.core.BraintreeException +import com.braintreepayments.api.core.BraintreeRequestCodes +import org.json.JSONException +import org.json.JSONObject + +/** + * Used to integrate with SEPA Direct Debit. + */ +class SEPADirectDebitClient @VisibleForTesting internal constructor( + private val braintreeClient: BraintreeClient, + private val sepaDirectDebitApi: SEPADirectDebitApi = SEPADirectDebitApi(braintreeClient) +) { + /** + * Initializes a new [SEPADirectDebitClient] instance + * + * @param context an Android Context + * @param authorization a Tokenization Key or Client Token used to authenticate + * @param returnUrlScheme a custom return url to use for browser and app switching + */ + constructor( + context: Context, + authorization: String, + returnUrlScheme: String? + ) : this(BraintreeClient(context, authorization, returnUrlScheme)) + + /** + * Starts the SEPA tokenization process by creating a [SEPADirectDebitPaymentAuthRequestParams] to be used + * to launch the SEPA mandate flow in + * [SEPADirectDebitLauncher.launch] + * + * @param sepaDirectDebitRequest [SEPADirectDebitRequest] + * @param callback [SEPADirectDebitPaymentAuthRequestCallback] + */ + fun createPaymentAuthRequest( + sepaDirectDebitRequest: SEPADirectDebitRequest, + callback: SEPADirectDebitPaymentAuthRequestCallback + ) { + braintreeClient.sendAnalyticsEvent(SEPADirectDebitAnalytics.TOKENIZE_STARTED) + sepaDirectDebitApi.createMandate( + sepaDirectDebitRequest, + braintreeClient.getReturnUrlScheme() + ) { result, createMandateError -> + if (result != null) { + if (URLUtil.isValidUrl(result.approvalUrl)) { + braintreeClient.sendAnalyticsEvent(SEPADirectDebitAnalytics.CREATE_MANDATE_CHALLENGE_REQUIRED) + try { + val params = SEPADirectDebitPaymentAuthRequestParams(buildBrowserSwitchOptions(result)) + braintreeClient.sendAnalyticsEvent(SEPADirectDebitAnalytics.CREATE_MANDATE_SUCCEEDED) + callback.onSEPADirectDebitPaymentAuthResult( + SEPADirectDebitPaymentAuthRequest.ReadyToLaunch(params) + ) + } catch (exception: JSONException) { + braintreeClient.sendAnalyticsEvent(SEPADirectDebitAnalytics.CREATE_MANDATE_FAILED) + callbackCreatePaymentAuthFailure( + callback, + SEPADirectDebitPaymentAuthRequest.Failure(exception) + ) + } + } else if (result.approvalUrl == "null") { + braintreeClient.sendAnalyticsEvent(SEPADirectDebitAnalytics.CREATE_MANDATE_SUCCEEDED) + // Mandate has already been approved + sepaDirectDebitApi.tokenize( + ibanLastFour = result.ibanLastFour, + customerId = result.customerId, + bankReferenceToken = result.bankReferenceToken, + mandateType = result.mandateType.toString() + ) { sepaDirectDebitNonce, error -> + if (sepaDirectDebitNonce != null) { + callbackCreatePaymentAuthChallengeNotRequiredSuccess( + callback, + SEPADirectDebitPaymentAuthRequest.LaunchNotRequired(sepaDirectDebitNonce) + ) + } else if (error != null) { + callbackCreatePaymentAuthFailure( + callback, + SEPADirectDebitPaymentAuthRequest.Failure(error) + ) + } + } + } else { + braintreeClient.sendAnalyticsEvent(SEPADirectDebitAnalytics.CREATE_MANDATE_FAILED) + callbackCreatePaymentAuthFailure( + callback, + SEPADirectDebitPaymentAuthRequest.Failure(BraintreeException("An unexpected error occurred.")) + ) + } + } else if (createMandateError != null) { + braintreeClient.sendAnalyticsEvent(SEPADirectDebitAnalytics.CREATE_MANDATE_FAILED) + callbackCreatePaymentAuthFailure( + callback, + SEPADirectDebitPaymentAuthRequest.Failure(createMandateError) + ) + } + } + } + + // TODO: - The wording in this docstring is confusing to me. Let's improve & align across all clients. + /** + * After receiving a result from the SEPA mandate web flow via + * [SEPADirectDebitLauncher.handleReturnToAppFromBrowser] , pass the + * [SEPADirectDebitPaymentAuthResult.Success] returned to this method to tokenize the SEPA + * account and receive a [SEPADirectDebitNonce] on success. + * + * @param paymentAuthResult a [SEPADirectDebitPaymentAuthResult.Success] received from + * [SEPADirectDebitLauncher.handleReturnToAppFromBrowser] + * @param callback [SEPADirectDebitInternalTokenizeCallback] + */ + fun tokenize( + paymentAuthResult: SEPADirectDebitPaymentAuthResult.Success, + callback: SEPADirectDebitTokenizeCallback + ) { + val browserSwitchResult: BrowserSwitchFinalResult.Success = + paymentAuthResult.paymentAuthInfo.browserSwitchSuccess + + val deepLinkUri: Uri = browserSwitchResult.returnUrl + if (deepLinkUri != null) { + if (deepLinkUri.path?.contains("success") == true && deepLinkUri.getQueryParameter("success") == "true") { + braintreeClient.sendAnalyticsEvent(SEPADirectDebitAnalytics.CHALLENGE_SUCCEEDED) + val metadata: JSONObject? = browserSwitchResult.requestMetadata + if (metadata != null) { + val ibanLastFour = metadata.optString(IBAN_LAST_FOUR_KEY) + val customerId = metadata.optString(CUSTOMER_ID_KEY) + val bankReferenceToken = metadata.optString(BANK_REFERENCE_TOKEN_KEY) + val mandateType = metadata.optString(MANDATE_TYPE_KEY) + + sepaDirectDebitApi.tokenize( + ibanLastFour = ibanLastFour, + customerId = customerId, + bankReferenceToken = bankReferenceToken, + mandateType = mandateType + ) { sepaDirectDebitNonce, error -> + if (sepaDirectDebitNonce != null) { + callbackTokenizeSuccess( + callback, + SEPADirectDebitResult.Success(sepaDirectDebitNonce) + ) + } else if (error != null) { + callbackTokenizeFailure(callback, SEPADirectDebitResult.Failure(error)) + } + } + } + } else if (deepLinkUri.path!!.contains("cancel")) { + callbackTokenizeCancel(callback) + } + } else { + braintreeClient.sendAnalyticsEvent(SEPADirectDebitAnalytics.CHALLENGE_FAILED) + callbackTokenizeFailure( + callback, + SEPADirectDebitResult.Failure(BraintreeException("Unknown error")) + ) + } + } + + private fun callbackCreatePaymentAuthFailure( + callback: SEPADirectDebitPaymentAuthRequestCallback, + result: SEPADirectDebitPaymentAuthRequest.Failure + ) { + braintreeClient.sendAnalyticsEvent(SEPADirectDebitAnalytics.TOKENIZE_FAILED) + callback.onSEPADirectDebitPaymentAuthResult(result) + } + + private fun callbackCreatePaymentAuthChallengeNotRequiredSuccess( + callback: SEPADirectDebitPaymentAuthRequestCallback, + result: SEPADirectDebitPaymentAuthRequest.LaunchNotRequired + ) { + braintreeClient.sendAnalyticsEvent(SEPADirectDebitAnalytics.TOKENIZE_SUCCEEDED) + callback.onSEPADirectDebitPaymentAuthResult(result) + } + + private fun callbackTokenizeCancel(callback: SEPADirectDebitTokenizeCallback) { + braintreeClient.sendAnalyticsEvent(SEPADirectDebitAnalytics.CHALLENGE_CANCELED) + callback.onSEPADirectDebitResult(SEPADirectDebitResult.Cancel) + } + + private fun callbackTokenizeFailure( + callback: SEPADirectDebitTokenizeCallback, + result: SEPADirectDebitResult.Failure + ) { + braintreeClient.sendAnalyticsEvent(SEPADirectDebitAnalytics.TOKENIZE_FAILED) + callback.onSEPADirectDebitResult(result) + } + + private fun callbackTokenizeSuccess( + callback: SEPADirectDebitTokenizeCallback, + result: SEPADirectDebitResult.Success + ) { + braintreeClient.sendAnalyticsEvent(SEPADirectDebitAnalytics.TOKENIZE_SUCCEEDED) + callback.onSEPADirectDebitResult(result) + } + + @Throws(JSONException::class) + private fun buildBrowserSwitchOptions(createMandateResult: CreateMandateResult): BrowserSwitchOptions { + val metadata = JSONObject() + .put(IBAN_LAST_FOUR_KEY, createMandateResult.ibanLastFour) + .put(CUSTOMER_ID_KEY, createMandateResult.customerId) + .put(BANK_REFERENCE_TOKEN_KEY, createMandateResult.bankReferenceToken) + .put(MANDATE_TYPE_KEY, createMandateResult.mandateType.toString()) + + val browserSwitchOptions = BrowserSwitchOptions() + .requestCode(BraintreeRequestCodes.SEPA_DEBIT.code) + .url(Uri.parse(createMandateResult.approvalUrl)) + .metadata(metadata) + .returnUrlScheme(braintreeClient.getReturnUrlScheme()) + + return browserSwitchOptions + } + + companion object { + private const val IBAN_LAST_FOUR_KEY = "ibanLastFour" + private const val CUSTOMER_ID_KEY = "customerId" + private const val BANK_REFERENCE_TOKEN_KEY = "bankReferenceToken" + private const val MANDATE_TYPE_KEY = "mandateType" + } +} diff --git a/SEPADirectDebit/src/main/java/com/braintreepayments/api/sepadirectdebit/SEPADirectDebitInternalTokenizeCallback.kt b/SEPADirectDebit/src/main/java/com/braintreepayments/api/sepadirectdebit/SEPADirectDebitInternalTokenizeCallback.kt index 91272f7241..783e4e2744 100644 --- a/SEPADirectDebit/src/main/java/com/braintreepayments/api/sepadirectdebit/SEPADirectDebitInternalTokenizeCallback.kt +++ b/SEPADirectDebit/src/main/java/com/braintreepayments/api/sepadirectdebit/SEPADirectDebitInternalTokenizeCallback.kt @@ -1,5 +1,5 @@ package com.braintreepayments.api.sepadirectdebit -internal interface SEPADirectDebitInternalTokenizeCallback { +internal fun interface SEPADirectDebitInternalTokenizeCallback { fun onResult(sepaDirectDebitNonce: SEPADirectDebitNonce?, error: Exception?) } diff --git a/SEPADirectDebit/src/main/java/com/braintreepayments/api/sepadirectdebit/SEPADirectDebitMandateType.java b/SEPADirectDebit/src/main/java/com/braintreepayments/api/sepadirectdebit/SEPADirectDebitMandateType.java deleted file mode 100644 index 350aa1fd4f..0000000000 --- a/SEPADirectDebit/src/main/java/com/braintreepayments/api/sepadirectdebit/SEPADirectDebitMandateType.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.braintreepayments.api.sepadirectdebit; - -import androidx.annotation.NonNull; - -/** - * Mandate type for the SEPA Direct Debit request. - */ -public enum SEPADirectDebitMandateType { - RECURRENT("RECURRENT"), - ONE_OFF("ONE_OFF") - ; - - private final String mandateType; - - SEPADirectDebitMandateType(final String mandateType) { - this.mandateType = mandateType; - } - - static SEPADirectDebitMandateType fromString(String mandateType) { - switch (mandateType) { - case "RECURRENT": - return SEPADirectDebitMandateType.RECURRENT; - case "ONE_OFF": - return SEPADirectDebitMandateType.ONE_OFF; - default: - return null; - } - } - - @NonNull - @Override - public String toString() { - return mandateType; - } -} \ No newline at end of file diff --git a/SEPADirectDebit/src/main/java/com/braintreepayments/api/sepadirectdebit/SEPADirectDebitMandateType.kt b/SEPADirectDebit/src/main/java/com/braintreepayments/api/sepadirectdebit/SEPADirectDebitMandateType.kt new file mode 100644 index 0000000000..bd0f767829 --- /dev/null +++ b/SEPADirectDebit/src/main/java/com/braintreepayments/api/sepadirectdebit/SEPADirectDebitMandateType.kt @@ -0,0 +1,9 @@ +package com.braintreepayments.api.sepadirectdebit + +/** + * Mandate type for the SEPA Direct Debit request. + */ +enum class SEPADirectDebitMandateType { + RECURRENT, + ONE_OFF +} diff --git a/SEPADirectDebit/src/main/java/com/braintreepayments/api/sepadirectdebit/SEPADirectDebitNonce.kt b/SEPADirectDebit/src/main/java/com/braintreepayments/api/sepadirectdebit/SEPADirectDebitNonce.kt index 30af2064f7..dab826d0a5 100644 --- a/SEPADirectDebit/src/main/java/com/braintreepayments/api/sepadirectdebit/SEPADirectDebitNonce.kt +++ b/SEPADirectDebit/src/main/java/com/braintreepayments/api/sepadirectdebit/SEPADirectDebitNonce.kt @@ -44,7 +44,7 @@ class SEPADirectDebitNonce internal constructor( if (details != null) { ibanLastFour = details.optString(IBAN_LAST_FOUR_KEY) customerId = details.optString(CUSTOMER_ID_KEY) - mandateType = SEPADirectDebitMandateType.fromString( + mandateType = SEPADirectDebitMandateType.valueOf( details.optString(MANDATE_TYPE_KEY) ) } diff --git a/SEPADirectDebit/src/main/java/com/braintreepayments/api/sepadirectdebit/SEPADirectDebitPaymentAuthRequestParams.java b/SEPADirectDebit/src/main/java/com/braintreepayments/api/sepadirectdebit/SEPADirectDebitPaymentAuthRequestParams.java deleted file mode 100644 index 71f2a5cd80..0000000000 --- a/SEPADirectDebit/src/main/java/com/braintreepayments/api/sepadirectdebit/SEPADirectDebitPaymentAuthRequestParams.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.braintreepayments.api.sepadirectdebit; - -import androidx.fragment.app.FragmentActivity; - -import com.braintreepayments.api.BrowserSwitchOptions; - -/** - * Returned via the {@link SEPADirectDebitPaymentAuthRequestCallback} after calling - * {@link SEPADirectDebitClient#createPaymentAuthRequest(SEPADirectDebitRequest, SEPADirectDebitPaymentAuthRequestCallback)}. - * - * Inspect the {@link SEPADirectDebitNonce} property to determine if tokenization is complete, or - * if you must continue the SEPA mandate web flow via - * {@link SEPADirectDebitLauncher#launch(FragmentActivity, SEPADirectDebitPaymentAuthRequest.ReadyToLaunch)} - */ -public class SEPADirectDebitPaymentAuthRequestParams { - - private BrowserSwitchOptions browserSwitchOptions; - - SEPADirectDebitPaymentAuthRequestParams(BrowserSwitchOptions browserSwitchOptions) { - this.browserSwitchOptions = browserSwitchOptions; - } - - BrowserSwitchOptions getBrowserSwitchOptions() { - return browserSwitchOptions; - } - -} diff --git a/SEPADirectDebit/src/main/java/com/braintreepayments/api/sepadirectdebit/SEPADirectDebitPaymentAuthRequestParams.kt b/SEPADirectDebit/src/main/java/com/braintreepayments/api/sepadirectdebit/SEPADirectDebitPaymentAuthRequestParams.kt new file mode 100644 index 0000000000..01c0b794ff --- /dev/null +++ b/SEPADirectDebit/src/main/java/com/braintreepayments/api/sepadirectdebit/SEPADirectDebitPaymentAuthRequestParams.kt @@ -0,0 +1,13 @@ +package com.braintreepayments.api.sepadirectdebit + +import com.braintreepayments.api.BrowserSwitchOptions + +/** + * Returned via the [SEPADirectDebitPaymentAuthRequestCallback] after calling + * [SEPADirectDebitClient.createPaymentAuthRequest]. + * + * Inspect the [SEPADirectDebitNonce] property to determine if tokenization is complete, or + * if you must continue the SEPA mandate web flow via + * [SEPADirectDebitLauncher.launch] + */ +class SEPADirectDebitPaymentAuthRequestParams internal constructor(val browserSwitchOptions: BrowserSwitchOptions) diff --git a/SEPADirectDebit/src/main/java/com/braintreepayments/api/sepadirectdebit/SEPADirectDebitPaymentAuthResultInfo.java b/SEPADirectDebit/src/main/java/com/braintreepayments/api/sepadirectdebit/SEPADirectDebitPaymentAuthResultInfo.java deleted file mode 100644 index 42ace64de4..0000000000 --- a/SEPADirectDebit/src/main/java/com/braintreepayments/api/sepadirectdebit/SEPADirectDebitPaymentAuthResultInfo.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.braintreepayments.api.sepadirectdebit; - -import com.braintreepayments.api.BrowserSwitchFinalResult; - -/** - * Details of a {@link SEPADirectDebitPaymentAuthResult.Success} - */ -public class SEPADirectDebitPaymentAuthResultInfo { - - private BrowserSwitchFinalResult.Success browserSwitchSuccess; - - SEPADirectDebitPaymentAuthResultInfo(BrowserSwitchFinalResult.Success browserSwitchSuccess) { - this.browserSwitchSuccess = browserSwitchSuccess; - } - - BrowserSwitchFinalResult.Success getBrowserSwitchSuccess() { - return browserSwitchSuccess; - } - -} diff --git a/SEPADirectDebit/src/main/java/com/braintreepayments/api/sepadirectdebit/SEPADirectDebitPaymentAuthResultInfo.kt b/SEPADirectDebit/src/main/java/com/braintreepayments/api/sepadirectdebit/SEPADirectDebitPaymentAuthResultInfo.kt new file mode 100644 index 0000000000..c224b663a3 --- /dev/null +++ b/SEPADirectDebit/src/main/java/com/braintreepayments/api/sepadirectdebit/SEPADirectDebitPaymentAuthResultInfo.kt @@ -0,0 +1,10 @@ +package com.braintreepayments.api.sepadirectdebit + +import com.braintreepayments.api.BrowserSwitchFinalResult + +/** + * Details of a [SEPADirectDebitPaymentAuthResult.Success] + */ +data class SEPADirectDebitPaymentAuthResultInfo( + val browserSwitchSuccess: BrowserSwitchFinalResult.Success +) diff --git a/SEPADirectDebit/src/main/java/com/braintreepayments/api/sepadirectdebit/SEPADirectDebitRequest.java b/SEPADirectDebit/src/main/java/com/braintreepayments/api/sepadirectdebit/SEPADirectDebitRequest.java deleted file mode 100644 index ad47800ca6..0000000000 --- a/SEPADirectDebit/src/main/java/com/braintreepayments/api/sepadirectdebit/SEPADirectDebitRequest.java +++ /dev/null @@ -1,135 +0,0 @@ -package com.braintreepayments.api.sepadirectdebit; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.braintreepayments.api.core.PostalAddress; - -/** - * Parameters for creating a SEPA Direct Debit tokenization request. - */ -public class SEPADirectDebitRequest { - - private String accountHolderName; - private String iban; - private String customerId; - private SEPADirectDebitMandateType mandateType = SEPADirectDebitMandateType.ONE_OFF; - private PostalAddress billingAddress; - private String merchantAccountId; - private String locale; - - /** - * @return The account holder name - */ - @Nullable - public String getAccountHolderName() { - return accountHolderName; - } - - /** - * Required. - * @param accountHolderName The account holder name. - */ - public void setAccountHolderName(@Nullable String accountHolderName) { - this.accountHolderName = accountHolderName; - } - - /** - * @return The full IBAN. - */ - @Nullable - public String getIban() { - return iban; - } - - /** - * Required. - * @param iban The full IBAN. - */ - public void setIban(@Nullable String iban) { - this.iban = iban; - } - - /** - * @return The customer ID. - */ - @Nullable - public String getCustomerId() { - return customerId; - } - - /** - * Required. - * @param customerId The customer ID. - */ - public void setCustomerId(@Nullable String customerId) { - this.customerId = customerId; - } - - /** - * @return The {@link SEPADirectDebitMandateType}. - */ - @NonNull - public SEPADirectDebitMandateType getMandateType() { - return mandateType; - } - - /** - * Optional. If not set, defaults to ONE_OFF. - * @param mandateType The {@link SEPADirectDebitMandateType}. - */ - public void setMandateType(@NonNull SEPADirectDebitMandateType mandateType) { - this.mandateType = mandateType; - } - - /** - * @return The user's billing address. - */ - @Nullable - public PostalAddress getBillingAddress() { - return billingAddress; - } - - /** - * Required. - * @param billingAddress The user's billing address. - */ - public void setBillingAddress(@Nullable PostalAddress billingAddress) { - this.billingAddress = billingAddress; - } - - /** - * @return A non-default merchant account to use for tokenization. - */ - @Nullable - public String getMerchantAccountId() { - return merchantAccountId; - } - - /** - * Optional. - * @param merchantAccountId A non-default merchant account to use for tokenization. - */ - public void setMerchantAccountId(@Nullable String merchantAccountId) { - this.merchantAccountId = merchantAccountId; - } - - /** - * @return A locale code to use for creating a mandate. - */ - @Nullable - public String getLocale() { - return locale; - } - - /** - * Optional. - * @param locale A locale code to use for creating a mandate. - * - * @see Documentation - * for possible values. Locale code should be supplied as a BCP-47 formatted locale code. - */ - public void setLocale(@Nullable String locale) { - this.locale = locale; - } -} diff --git a/SEPADirectDebit/src/main/java/com/braintreepayments/api/sepadirectdebit/SEPADirectDebitRequest.kt b/SEPADirectDebit/src/main/java/com/braintreepayments/api/sepadirectdebit/SEPADirectDebitRequest.kt new file mode 100644 index 0000000000..2a801662f4 --- /dev/null +++ b/SEPADirectDebit/src/main/java/com/braintreepayments/api/sepadirectdebit/SEPADirectDebitRequest.kt @@ -0,0 +1,27 @@ +package com.braintreepayments.api.sepadirectdebit + +import com.braintreepayments.api.core.PostalAddress + +/** + * Parameters for creating a SEPA Direct Debit tokenization request. + * + * @property accountHolderName The account holder name. + * @property iban The full IBAN. + * @property customerId The customer ID. + * @property mandateType The [SEPADirectDebitMandateType]. + * @property billingAddress The user's billing address. + * @property merchantAccountId A non-default merchant account to use for tokenization. + * Optional. + * @property locale A locale code to use for creating a mandate. + * @see [Documentation](https://developer.paypal.com/reference/locale-codes/) + * for possible values. Locale code should be supplied as a BCP-47 formatted locale code. + */ +data class SEPADirectDebitRequest internal constructor( + var accountHolderName: String? = null, + var iban: String? = null, + var customerId: String? = null, + var mandateType: SEPADirectDebitMandateType = SEPADirectDebitMandateType.ONE_OFF, + var billingAddress: PostalAddress? = null, + var merchantAccountId: String? = null, + var locale: String? = null +) diff --git a/SEPADirectDebit/src/test/java/com/braintreepayments/api/sepadirectdebit/SEPADirectDebitApiUnitTest.java b/SEPADirectDebit/src/test/java/com/braintreepayments/api/sepadirectdebit/SEPADirectDebitApiUnitTest.java index ef1a362a6d..798d3e4556 100644 --- a/SEPADirectDebit/src/test/java/com/braintreepayments/api/sepadirectdebit/SEPADirectDebitApiUnitTest.java +++ b/SEPADirectDebit/src/test/java/com/braintreepayments/api/sepadirectdebit/SEPADirectDebitApiUnitTest.java @@ -183,8 +183,12 @@ public void createMandate_properlyFormatsPOSTBody() throws JSONException { sut.createMandate(request, returnUrl, createMandateCallback); ArgumentCaptor captor = ArgumentCaptor.forClass(String.class); - verify(mockBraintreeClient).sendPOST(eq("v1/sepa_debit"), String.valueOf(captor.capture()), - any(HttpResponseCallback.class)); + verify(mockBraintreeClient).sendPOST( + eq("v1/sepa_debit"), + captor.capture(), + any(), + any(HttpResponseCallback.class) + ); String result = captor.getValue(); JSONObject json = new JSONObject(result); diff --git a/SEPADirectDebit/src/test/java/com/braintreepayments/api/sepadirectdebit/SEPADirectDebitClientUnitTest.java b/SEPADirectDebit/src/test/java/com/braintreepayments/api/sepadirectdebit/SEPADirectDebitClientUnitTest.java index 8e3cb9a25a..a403dc1149 100644 --- a/SEPADirectDebit/src/test/java/com/braintreepayments/api/sepadirectdebit/SEPADirectDebitClientUnitTest.java +++ b/SEPADirectDebit/src/test/java/com/braintreepayments/api/sepadirectdebit/SEPADirectDebitClientUnitTest.java @@ -46,7 +46,7 @@ public void beforeEach() { "1234", "fake-customer-id", "fake-bank-reference-token", - "ONE_OFF" + SEPADirectDebitMandateType.valueOf("ONE_OFF") ); sepaDirectDebitRequest = new SEPADirectDebitRequest(); @@ -65,7 +65,7 @@ public void createPaymentAuthRequest_onCreateMandateRequestSuccess_callsBackSEPA new SEPADirectDebitClient(braintreeClient, sepaDirectDebitApi); sut.createPaymentAuthRequest(sepaDirectDebitRequest, paymentAuthRequestCallback); - verify(braintreeClient).sendAnalyticsEvent(SEPADirectDebitAnalytics.TOKENIZE_STARTED); + verify(braintreeClient).sendAnalyticsEvent(eq(SEPADirectDebitAnalytics.TOKENIZE_STARTED), any()); ArgumentCaptor captor = ArgumentCaptor.forClass(SEPADirectDebitPaymentAuthRequest.class); @@ -75,8 +75,8 @@ public void createPaymentAuthRequest_onCreateMandateRequestSuccess_callsBackSEPA assertTrue(paymentAuthRequest instanceof SEPADirectDebitPaymentAuthRequest.ReadyToLaunch); SEPADirectDebitPaymentAuthRequestParams params = ((SEPADirectDebitPaymentAuthRequest.ReadyToLaunch) paymentAuthRequest).getRequestParams(); - verify(braintreeClient).sendAnalyticsEvent(SEPADirectDebitAnalytics.CREATE_MANDATE_SUCCEEDED); - verify(braintreeClient).sendAnalyticsEvent(SEPADirectDebitAnalytics.CREATE_MANDATE_CHALLENGE_REQUIRED); + verify(braintreeClient).sendAnalyticsEvent(eq(SEPADirectDebitAnalytics.CREATE_MANDATE_SUCCEEDED), any()); + verify(braintreeClient).sendAnalyticsEvent(eq(SEPADirectDebitAnalytics.CREATE_MANDATE_CHALLENGE_REQUIRED), any()); BrowserSwitchOptions browserSwitchOptions = params.getBrowserSwitchOptions(); assertEquals(Uri.parse("http://www.example.com"), browserSwitchOptions.getUrl()); @@ -98,7 +98,7 @@ public void createPaymentAuthRequest_onCreateMandateRequestSuccess_whenMandateAl "1234", "fake-customer-id", "fake-bank-reference-token", - "ONE_OFF" + SEPADirectDebitMandateType.valueOf("ONE_OFF") ); SEPADirectDebitNonce nonce = SEPADirectDebitNonce.fromJSON( @@ -120,7 +120,7 @@ public void createPaymentAuthRequest_onCreateMandateRequestSuccess_whenMandateAl SEPADirectDebitPaymentAuthRequest paymentAuthRequest = captor.getValue(); assertTrue(paymentAuthRequest instanceof SEPADirectDebitPaymentAuthRequest.LaunchNotRequired); assertEquals(((SEPADirectDebitPaymentAuthRequest.LaunchNotRequired) paymentAuthRequest).getNonce(), nonce); - verify(braintreeClient).sendAnalyticsEvent(SEPADirectDebitAnalytics.TOKENIZE_SUCCEEDED); + verify(braintreeClient).sendAnalyticsEvent(eq(SEPADirectDebitAnalytics.TOKENIZE_SUCCEEDED), any()); } @Test @@ -130,7 +130,7 @@ public void createPaymentAuthRequest_onCreateMandateRequestSuccess_whenApprovalU "1234", "fake-customer-id", "fake-bank-reference-token", - "ONE_OFF" + SEPADirectDebitMandateType.valueOf("ONE_OFF") ); SEPADirectDebitApi sepaDirectDebitApi = new MockSEPADirectDebitApiBuilder() @@ -150,8 +150,8 @@ public void createPaymentAuthRequest_onCreateMandateRequestSuccess_whenApprovalU Exception error = ((SEPADirectDebitPaymentAuthRequest.Failure) paymentAuthRequest).getError(); assertTrue(error instanceof BraintreeException); assertEquals("An unexpected error occurred.", error.getMessage()); - verify(braintreeClient).sendAnalyticsEvent(SEPADirectDebitAnalytics.CREATE_MANDATE_FAILED); - verify(braintreeClient).sendAnalyticsEvent(SEPADirectDebitAnalytics.TOKENIZE_FAILED); + verify(braintreeClient).sendAnalyticsEvent(eq(SEPADirectDebitAnalytics.CREATE_MANDATE_FAILED), any()); + verify(braintreeClient).sendAnalyticsEvent(eq(SEPADirectDebitAnalytics.TOKENIZE_FAILED), any()); } @Test @@ -161,7 +161,7 @@ public void createPaymentAuthRequest_onCreateMandateRequestSuccess_whenApprovalU "1234", "fake-customer-id", "fake-bank-reference-token", - "ONE_OFF" + SEPADirectDebitMandateType.valueOf("ONE_OFF") ); SEPADirectDebitApi sepaDirectDebitApi = new MockSEPADirectDebitApiBuilder() @@ -172,8 +172,8 @@ public void createPaymentAuthRequest_onCreateMandateRequestSuccess_whenApprovalU new SEPADirectDebitClient(braintreeClient, sepaDirectDebitApi); sut.createPaymentAuthRequest(sepaDirectDebitRequest, paymentAuthRequestCallback); - verify(braintreeClient).sendAnalyticsEvent(SEPADirectDebitAnalytics.TOKENIZE_STARTED); - verify(braintreeClient).sendAnalyticsEvent(SEPADirectDebitAnalytics.CREATE_MANDATE_SUCCEEDED); + verify(braintreeClient).sendAnalyticsEvent(eq(SEPADirectDebitAnalytics.TOKENIZE_STARTED), any()); + verify(braintreeClient).sendAnalyticsEvent(eq(SEPADirectDebitAnalytics.CREATE_MANDATE_SUCCEEDED), any()); verify(sepaDirectDebitApi).tokenize(eq("1234"), eq("fake-customer-id"), eq("fake-bank-reference-token"), eq("ONE_OFF"), any(SEPADirectDebitInternalTokenizeCallback.class)); @@ -197,8 +197,8 @@ public void createPaymentAuthRequest_onCreateMandateError_returnsErrorToListener assertTrue(paymentAuthRequest instanceof SEPADirectDebitPaymentAuthRequest.Failure); Exception actualError = ((SEPADirectDebitPaymentAuthRequest.Failure) paymentAuthRequest).getError(); assertEquals(error, actualError); - verify(braintreeClient).sendAnalyticsEvent(SEPADirectDebitAnalytics.CREATE_MANDATE_FAILED); - verify(braintreeClient).sendAnalyticsEvent(SEPADirectDebitAnalytics.TOKENIZE_FAILED); + verify(braintreeClient).sendAnalyticsEvent(eq(SEPADirectDebitAnalytics.CREATE_MANDATE_FAILED), any()); + verify(braintreeClient).sendAnalyticsEvent(eq(SEPADirectDebitAnalytics.TOKENIZE_FAILED), any()); } @@ -232,7 +232,7 @@ public void tokenize_whenDeepLinkContainsSuccess_callsTokenize_andSendsAnalytics verify(sepaDirectDebitApi).tokenize(eq("1234"), eq("customer-id"), eq("bank-reference-token"), eq("ONE_OFF"), any(SEPADirectDebitInternalTokenizeCallback.class)); - verify(braintreeClient).sendAnalyticsEvent(SEPADirectDebitAnalytics.CHALLENGE_SUCCEEDED); + verify(braintreeClient).sendAnalyticsEvent(eq(SEPADirectDebitAnalytics.CHALLENGE_SUCCEEDED), any()); } @Test @@ -272,7 +272,7 @@ public void tokenize_onTokenizeSuccess_callsBackNonce_andSendsAnalytics() SEPADirectDebitResult result = captor.getValue(); assertTrue(result instanceof SEPADirectDebitResult.Success); assertEquals(nonce, ((SEPADirectDebitResult.Success) result).getNonce()); - verify(braintreeClient).sendAnalyticsEvent(SEPADirectDebitAnalytics.TOKENIZE_SUCCEEDED); + verify(braintreeClient).sendAnalyticsEvent(eq(SEPADirectDebitAnalytics.TOKENIZE_SUCCEEDED), any()); } @Test @@ -311,7 +311,7 @@ public void tokenize_onTokenizeFailure_callsBackError_andSendsAnalytics() SEPADirectDebitResult result = captor.getValue(); assertTrue(result instanceof SEPADirectDebitResult.Failure); assertEquals(exception, ((SEPADirectDebitResult.Failure) result).getError()); - verify(braintreeClient).sendAnalyticsEvent(SEPADirectDebitAnalytics.TOKENIZE_FAILED); + verify(braintreeClient).sendAnalyticsEvent(eq(SEPADirectDebitAnalytics.TOKENIZE_FAILED), any()); verify(sepaDirectDebitApi).tokenize(eq("1234"), eq("customer-id"), eq("bank-reference-token"), eq("ONE_OFF"), any(SEPADirectDebitInternalTokenizeCallback.class)); @@ -380,7 +380,7 @@ public void tokenize_whenDeepLinkContainsCancel_callsBackError_andSendsAnalytics SEPADirectDebitResult result = captor.getValue(); assertTrue(result instanceof SEPADirectDebitResult.Cancel); - verify(braintreeClient).sendAnalyticsEvent(SEPADirectDebitAnalytics.CHALLENGE_CANCELED); + verify(braintreeClient).sendAnalyticsEvent(eq(SEPADirectDebitAnalytics.CHALLENGE_CANCELED), any()); } @Test @@ -409,6 +409,6 @@ public void tokenize_whenDeepLinkURLIsNull_returnsErrorToListener() { Exception exception = ((SEPADirectDebitResult.Failure) result).getError(); assertTrue(exception instanceof BraintreeException); assertEquals("Unknown error", exception.getMessage()); - verify(braintreeClient).sendAnalyticsEvent(SEPADirectDebitAnalytics.TOKENIZE_FAILED); + verify(braintreeClient).sendAnalyticsEvent(eq(SEPADirectDebitAnalytics.TOKENIZE_FAILED), any()); } }