Skip to content

Commit

Permalink
Merge branch 'main' into shipping-callback-feature
Browse files Browse the repository at this point in the history
  • Loading branch information
richherrera committed Oct 31, 2024
2 parents 506daa9 + 0dd7b58 commit 4188670
Show file tree
Hide file tree
Showing 1,189 changed files with 11,028 additions and 2,089 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,9 @@ internal class AnalyticsClient(
.putOpt(FPTI_KEY_START_TIME, event.startTime)
.putOpt(FPTI_KEY_END_TIME, event.endTime)
.putOpt(FPTI_KEY_ENDPOINT, event.endpoint)
.putOpt(FPTI_KEY_MERCHANT_EXPERIMENT, event.experiment)
.putOpt(FPTI_KEY_MERCHANT_PAYMENT_METHODS_DISPLAYED,
event.paymentMethodsDisplayed.ifEmpty { null })
return json.toString()
}

Expand Down Expand Up @@ -265,6 +268,8 @@ internal class AnalyticsClient(
private const val FPTI_KEY_START_TIME = "start_time"
private const val FPTI_KEY_END_TIME = "end_time"
private const val FPTI_KEY_ENDPOINT = "endpoint"
private const val FPTI_KEY_MERCHANT_EXPERIMENT = "experiment"
private const val FPTI_KEY_MERCHANT_PAYMENT_METHODS_DISPLAYED = "payment_methods_displayed"

private const val FPTI_BATCH_KEY_VENMO_INSTALLED = "venmo_installed"
private const val FPTI_BATCH_KEY_PAYPAL_INSTALLED = "paypal_installed"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
package com.braintreepayments.api.core

/**
* DTO for analytics events. See also: [AnalyticsEventParams]
* This class is internal to core module and is used in [AnalyticsClient] to construct the analytics
* payload to be sent to the backend.
*/
internal data class AnalyticsEvent(
val name: String,
val timestamp: Long,
Expand All @@ -8,5 +13,7 @@ internal data class AnalyticsEvent(
val isVaultRequest: Boolean = false,
val startTime: Long? = null,
val endTime: Long? = null,
val endpoint: String? = null
val endpoint: String? = null,
val experiment: String? = null,
val paymentMethodsDisplayed: List<String> = emptyList()
)
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,30 @@ package com.braintreepayments.api.core

import androidx.annotation.RestrictTo

/**
* DTO for analytics events. See also: [AnalyticsEvent]
* It is a catch-all data class for any parameters any of the modules wants to send. As such, not
* all parameters are required at each call site.
*
* @property payPalContextId Used for linking events from the client to server side request.
* @property linkType Indicates whether a deeplink or an app link was used to launch the app. Also see [LinkType].
* @property isVaultRequest Indicates whether the request was a BillingAgreement(BA)/Vault request.
* @property startTime [HttpResponseTiming] start time.
* @property endTime [HttpResponseTiming] end time.
* @property endpoint The endpoint being called.
* @property experiment Currently a ShopperInsights module specific event that indicates
* the experiment, as a JSON string, that the merchant sent to the us.
* @property paymentMethodsDisplayed A ShopperInsights module specific event that indicates the
* order of payment methods displayed to the shopper by the merchant.
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
data class AnalyticsEventParams @JvmOverloads constructor(
var payPalContextId: String? = null,
var linkType: String? = null,
var isVaultRequest: Boolean = false,
var startTime: Long? = null,
var endTime: Long? = null,
var endpoint: String? = null
var endpoint: String? = null,
val experiment: String? = null,
val paymentMethodsDisplayed: List<String> = emptyList()
)
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,8 @@ class BraintreeClient @VisibleForTesting internal constructor(
startTime = params.startTime,
endTime = params.endTime,
endpoint = params.endpoint,
experiment = params.experiment,
paymentMethodsDisplayed = params.paymentMethodsDisplayed
)
sendAnalyticsEvent(event, configuration, authorization)
}
Expand Down
16 changes: 15 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,21 @@

* PayPal
* Add `shippingCallbackURL` to `PayPalRequest`


## 5.2.0 (2024-10-30)

* GooglePay
* Fix a crash being caused on API 33 devices. It is recommended that merchants not use 5.1.0 for GooglePay.
* Shopper Insights (BETA)
* For analytics, send `experiment` as a parameter to `getRecommendedPaymentMethods` method
* For analytics, send `experiment` and `paymentMethodsDisplayed` analytic metrics to FPTI via the button presented event methods

## 5.1.0 (2024-10-15)

* PayPal
* Add `PayPalRecurringBillingDetails` and `PayPalRecurringBillingPlanType` opt-in request objects. Including these details will provide transparency to users on their billing schedule, dates, and amounts, as well as launch a modernized checkout UI.
* Add `userPhoneNumber` property to `PayPalVaultRequest` and `PayPalCheckoutRequest`

## 5.0.0 (2024-09-30)

* PayPal
Expand Down
46 changes: 38 additions & 8 deletions Demo/src/main/java/com/braintreepayments/demo/PayPalFragment.java
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,18 @@ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup c
@Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_paypal, container, false);
TextInputEditText buyerEmailEditText = view.findViewById(R.id.buyer_email_edit_text);
TextInputEditText buyerPhoneCountryCodeEditText = view.findViewById(R.id.buyer_phone_country_code_edit_text);
TextInputEditText buyerPhoneNationalNumberEditText = view.findViewById(R.id.buyer_phone_national_number_edit_text);
Button billingAgreementButton = view.findViewById(R.id.paypal_billing_agreement_button);
Button singlePaymentButton = view.findViewById(R.id.paypal_single_payment_button);

singlePaymentButton.setOnClickListener(v -> {
launchPayPal(false, buyerEmailEditText.getText().toString());
launchPayPal(
false,
buyerEmailEditText.getText().toString(),
buyerPhoneCountryCodeEditText.getText().toString(),
buyerPhoneNationalNumberEditText.getText().toString()
);
});
billingAgreementButton.setOnClickListener(v -> {
FragmentActivity activity = getActivity();
Expand All @@ -59,7 +66,12 @@ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup c
return;
}

launchPayPal(true, buyerEmailEditText.getText().toString());
launchPayPal(
true,
buyerEmailEditText.getText().toString(),
buyerPhoneCountryCodeEditText.getText().toString(),
buyerPhoneNationalNumberEditText.getText().toString()
);
});

payPalClient = new PayPalClient(
Expand Down Expand Up @@ -99,7 +111,12 @@ private void clearPendingRequest() {
PendingRequestStore.getInstance().clearPayPalPendingRequest(requireContext());
}

private void launchPayPal(boolean isBillingAgreement, String buyerEmailAddress) {
private void launchPayPal(
boolean isBillingAgreement,
String buyerEmailAddress,
String buyerPhoneCountryCode,
String buyerPhoneNationalNumber
) {
FragmentActivity activity = getActivity();
activity.setProgressBarIndeterminateVisibility(true);

Expand All @@ -110,24 +127,37 @@ private void launchPayPal(boolean isBillingAgreement, String buyerEmailAddress)
if (dataCollectorResult instanceof DataCollectorResult.Success) {
deviceData = ((DataCollectorResult.Success) dataCollectorResult).getDeviceData();
}
launchPayPal(activity, isBillingAgreement, amount, buyerEmailAddress);
launchPayPal(activity, isBillingAgreement, amount, buyerEmailAddress, buyerPhoneCountryCode, buyerPhoneNationalNumber);
});
} else {
launchPayPal(activity, isBillingAgreement, amount, buyerEmailAddress);
launchPayPal(activity, isBillingAgreement, amount, buyerEmailAddress, buyerPhoneCountryCode, buyerPhoneNationalNumber);
}
}

private void launchPayPal(
FragmentActivity activity,
boolean isBillingAgreement,
String amount,
String buyerEmailAddress
String buyerEmailAddress,
String buyerPhoneCountryCode,
String buyerPhoneNationalNumber
) {
PayPalRequest payPalRequest;
if (isBillingAgreement) {
payPalRequest = createPayPalVaultRequest(activity, buyerEmailAddress);
payPalRequest = createPayPalVaultRequest(
activity,
buyerEmailAddress,
buyerPhoneCountryCode,
buyerPhoneNationalNumber
);
} else {
payPalRequest = createPayPalCheckoutRequest(activity, amount, buyerEmailAddress);
payPalRequest = createPayPalCheckoutRequest(
activity,
amount,
buyerEmailAddress,
buyerPhoneCountryCode,
buyerPhoneNationalNumber
);
}
payPalClient.createPaymentAuthRequest(requireContext(), payPalRequest,
(paymentAuthRequest) -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,29 @@
import android.content.Context;

import com.braintreepayments.api.core.PostalAddress;
import com.braintreepayments.api.paypal.PayPalBillingCycle;
import com.braintreepayments.api.paypal.PayPalBillingInterval;
import com.braintreepayments.api.paypal.PayPalBillingPricing;
import com.braintreepayments.api.paypal.PayPalCheckoutRequest;
import com.braintreepayments.api.paypal.PayPalLandingPageType;
import com.braintreepayments.api.paypal.PayPalPaymentIntent;
import com.braintreepayments.api.paypal.PayPalPaymentUserAction;
import com.braintreepayments.api.paypal.PayPalRequest;
import com.braintreepayments.api.paypal.PayPalPricingModel;
import com.braintreepayments.api.paypal.PayPalRecurringBillingDetails;
import com.braintreepayments.api.paypal.PayPalRecurringBillingPlanType;
import com.braintreepayments.api.paypal.PayPalPhoneNumber;
import com.braintreepayments.api.paypal.PayPalVaultRequest;

import java.util.ArrayList;
import java.util.List;

public class PayPalRequestFactory {

public static PayPalVaultRequest createPayPalVaultRequest(
Context context,
String buyerEmailAddress
String buyerEmailAddress,
String buyerPhoneCountryCode,
String buyerPhoneNationalNumber
) {

PayPalVaultRequest request = new PayPalVaultRequest(true);
Expand All @@ -23,6 +34,10 @@ public static PayPalVaultRequest createPayPalVaultRequest(
request.setUserAuthenticationEmail(buyerEmailAddress);
}

if (!buyerPhoneCountryCode.isEmpty() && !buyerPhoneNationalNumber.isEmpty()) {
request.setUserPhoneNumber(new PayPalPhoneNumber(buyerPhoneCountryCode, buyerPhoneNationalNumber));
}

if (Settings.isPayPalAppSwithEnabled(context)) {
request.setEnablePayPalAppSwitch(true);
}
Expand Down Expand Up @@ -52,20 +67,62 @@ public static PayPalVaultRequest createPayPalVaultRequest(
request.setShippingAddressOverride(postalAddress);
}

if (Settings.isRbaMetadataEnabled(context)) {
PayPalBillingPricing billingPricing = new PayPalBillingPricing(
PayPalPricingModel.FIXED,
"9.99",
"99.99"
);

PayPalBillingCycle billingCycle = new PayPalBillingCycle(
false,
1,
PayPalBillingInterval.MONTH,
1,
1,
"2024-08-01",
billingPricing
);

List<PayPalBillingCycle> billingCycles = new ArrayList<>();
billingCycles.add(billingCycle);
PayPalRecurringBillingDetails payPalRecurringBillingDetails = new PayPalRecurringBillingDetails(
billingCycles,
"32.56",
"USD",
"Vogue Magazine Subscription",
"9.99",
"Home delivery to Chicago, IL",
"19.99",
1,
"1.99",
"0.59"
);

request.setRecurringBillingDetails(payPalRecurringBillingDetails);
request.setRecurringBillingPlanType(PayPalRecurringBillingPlanType.SUBSCRIPTION);
}

return request;
}

public static PayPalCheckoutRequest createPayPalCheckoutRequest(
Context context,
String amount,
String buyerEmailAddress
String buyerEmailAddress,
String buyerPhoneCountryCode,
String buyerPhoneNationalNumber
) {
PayPalCheckoutRequest request = new PayPalCheckoutRequest(amount, true);

if (!buyerEmailAddress.isEmpty()) {
request.setUserAuthenticationEmail(buyerEmailAddress);
}

if (!buyerPhoneCountryCode.isEmpty() && !buyerPhoneNationalNumber.isEmpty()) {
request.setUserPhoneNumber(new PayPalPhoneNumber(buyerPhoneCountryCode, buyerPhoneNationalNumber));
}

request.setDisplayName(Settings.getPayPalDisplayName(context));

String landingPageType = Settings.getPayPalLandingPageType(context);
Expand Down
4 changes: 4 additions & 0 deletions Demo/src/main/java/com/braintreepayments/demo/Settings.java
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,10 @@ public static boolean usePayPalAddressOverride(Context context) {
return getPreferences(context).getBoolean("paypal_address_override", true);
}

public static boolean isRbaMetadataEnabled(Context context) {
return getPreferences(context).getBoolean("paypal_rba_metadata", false);
}

public static boolean isThreeDSecureEnabled(Context context) {
return getPreferences(context).getBoolean("enable_three_d_secure", false);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -184,18 +184,25 @@ class ShopperInsightsFragment : BaseFragment() {
}

shopperInsightsClient.getRecommendedPaymentMethods(
request
request,
"dummy_experiment"
) { result ->
when (result) {
is ShopperInsightsResult.Success -> {
if (result.response.isPayPalRecommended) {
payPalVaultButton.isEnabled = true
shopperInsightsClient.sendPayPalPresentedEvent()
shopperInsightsClient.sendPayPalPresentedEvent(
"dummy_paypal_presented_experiment",
listOf("PayPal, Apple Pay, Google Pay")
)
}

if (result.response.isVenmoRecommended) {
venmoButton.isEnabled = true
shopperInsightsClient.sendVenmoPresentedEvent()
shopperInsightsClient.sendVenmoPresentedEvent(
"dummy_venmo_presented_experiment",
listOf("Apple Pay, Venmo, Google Pay")
)
}

responseTextView.text =
Expand All @@ -220,7 +227,9 @@ class ShopperInsightsFragment : BaseFragment() {
requireContext(),
PayPalRequestFactory.createPayPalVaultRequest(
activity,
emailInput.editText?.text.toString()
emailInput.editText?.text.toString(),
countryCodeInput.editText?.text.toString(),
nationalNumberInput.editText?.text.toString()
)
) { authRequest ->
when (authRequest) {
Expand Down
28 changes: 28 additions & 0 deletions Demo/src/main/res/layout/fragment_paypal.xml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,34 @@

</com.google.android.material.textfield.TextInputLayout>

<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"

android:hint="@string/paypal_phone_country_code">

<com.google.android.material.textfield.TextInputEditText
android:id="@+id/buyer_phone_country_code_edit_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="phone" />

</com.google.android.material.textfield.TextInputLayout>

<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="20dp"
android:hint="@string/paypal_phone_national_number">

<com.google.android.material.textfield.TextInputEditText
android:id="@+id/buyer_phone_national_number_edit_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="phone" />

</com.google.android.material.textfield.TextInputLayout>

<Button
android:id="@+id/paypal_single_payment_button"
android:layout_width="match_parent"
Expand Down
Loading

0 comments on commit 4188670

Please sign in to comment.