Commit 0b252310 authored by Sahel Sharify's avatar Sahel Sharify Committed by Commit Bot

[Payments]PaymentRequestSpec.java retrieves payment details from c++.

Before:
PaymentRequestSpec.java stores duplicates of payment details to be used
in its getters. This is error prone since the variables on c++ side and
their duplicates on java side need to kept synced.

After:
PaymentRequestSpec.java always retrieves most updated payment details
from c++ implementation.

Bug: 1129708
Change-Id: Ia106446bc08e91a6fcdcfc3875f078d2fd6ed6c6
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2417543Reviewed-by: default avatarRouslan Solomakhin <rouslan@chromium.org>
Reviewed-by: default avatarLiquan (Max) Gu <maxlg@chromium.org>
Commit-Queue: Sahel Sharify <sahel@chromium.org>
Cr-Commit-Position: refs/heads/master@{#809326}
parent 5280b272
...@@ -279,15 +279,15 @@ public class PaymentRequestImpl ...@@ -279,15 +279,15 @@ public class PaymentRequestImpl
mQueryForQuota.put("basic-card-payment-options", paymentMethodData); mQueryForQuota.put("basic-card-payment-options", paymentMethodData);
} }
mSpec = PaymentRequestSpec.createAndValidate( if (!PaymentValidator.validatePaymentDetails(details)
details, mPaymentOptions, methodData, LocaleUtils.getDefaultLocaleString()); || !parseAndValidateDetailsForSkipToGPayHelper(details)) {
if (mSpec == null
|| !parseAndValidateDetailsForSkipToGPayHelper(mSpec.getPaymentDetails())) {
mJourneyLogger.setAborted(AbortReason.INVALID_DATA_FROM_RENDERER); mJourneyLogger.setAborted(AbortReason.INVALID_DATA_FROM_RENDERER);
disconnectFromClientWithDebugMessage(ErrorStrings.INVALID_PAYMENT_DETAILS); disconnectFromClientWithDebugMessage(ErrorStrings.INVALID_PAYMENT_DETAILS);
return false; return false;
} }
mSpec = new PaymentRequestSpec(mPaymentOptions, details, methodData.values(),
LocaleUtils.getDefaultLocaleString());
if (mSpec.getRawTotal() == null) { if (mSpec.getRawTotal() == null) {
mJourneyLogger.setAborted(AbortReason.INVALID_DATA_FROM_RENDERER); mJourneyLogger.setAborted(AbortReason.INVALID_DATA_FROM_RENDERER);
disconnectFromClientWithDebugMessage(ErrorStrings.TOTAL_REQUIRED); disconnectFromClientWithDebugMessage(ErrorStrings.TOTAL_REQUIRED);
...@@ -754,7 +754,7 @@ public class PaymentRequestImpl ...@@ -754,7 +754,7 @@ public class PaymentRequestImpl
return; return;
} }
if (!mSpec.parseAndValidateDetails(details) if (!PaymentValidator.validatePaymentDetails(details)
|| !parseAndValidateDetailsForSkipToGPayHelper(details)) { || !parseAndValidateDetailsForSkipToGPayHelper(details)) {
mJourneyLogger.setAborted(AbortReason.INVALID_DATA_FROM_RENDERER); mJourneyLogger.setAborted(AbortReason.INVALID_DATA_FROM_RENDERER);
disconnectFromClientWithDebugMessage(ErrorStrings.INVALID_PAYMENT_DETAILS); disconnectFromClientWithDebugMessage(ErrorStrings.INVALID_PAYMENT_DETAILS);
...@@ -802,7 +802,7 @@ public class PaymentRequestImpl ...@@ -802,7 +802,7 @@ public class PaymentRequestImpl
return; return;
} }
if (!mSpec.parseAndValidateDetails(details) if (!PaymentValidator.validatePaymentDetails(details)
|| !parseAndValidateDetailsForSkipToGPayHelper(details)) { || !parseAndValidateDetailsForSkipToGPayHelper(details)) {
mJourneyLogger.setAborted(AbortReason.INVALID_DATA_FROM_RENDERER); mJourneyLogger.setAborted(AbortReason.INVALID_DATA_FROM_RENDERER);
disconnectFromClientWithDebugMessage(ErrorStrings.INVALID_PAYMENT_DETAILS); disconnectFromClientWithDebugMessage(ErrorStrings.INVALID_PAYMENT_DETAILS);
...@@ -1307,7 +1307,11 @@ public class PaymentRequestImpl ...@@ -1307,7 +1307,11 @@ public class PaymentRequestImpl
// PaymentAppFactoryParams implementation. // PaymentAppFactoryParams implementation.
@Override @Override
public Map<String, PaymentMethodData> getMethodData() { public Map<String, PaymentMethodData> getMethodData() {
return mSpec.getMethodData(); // TODO(crbug.com/1130343): This should only get called while mSpec is not destroyed.
// uncomment the following assertion after fixing the issue.
// assert !mSpec.isDestroyed();
return mSpec.isDestroyed() ? new ArrayMap<String, PaymentMethodData>()
: mSpec.getMethodData();
} }
// PaymentAppFactoryParams implementation. // PaymentAppFactoryParams implementation.
...@@ -1586,7 +1590,6 @@ public class PaymentRequestImpl ...@@ -1586,7 +1590,6 @@ public class PaymentRequestImpl
if (ComponentPaymentRequestImpl.getNativeObserverForTest() != null) { if (ComponentPaymentRequestImpl.getNativeObserverForTest() != null) {
ComponentPaymentRequestImpl.getNativeObserverForTest().onNotSupportedError(); ComponentPaymentRequestImpl.getNativeObserverForTest().onNotSupportedError();
} }
if (TextUtils.isEmpty(mRejectShowErrorMessage) && !isInTwa() if (TextUtils.isEmpty(mRejectShowErrorMessage) && !isInTwa()
&& mSpec.getMethodData().get(MethodStrings.GOOGLE_PLAY_BILLING) != null) { && mSpec.getMethodData().get(MethodStrings.GOOGLE_PLAY_BILLING) != null) {
mRejectShowErrorMessage = ErrorStrings.APP_STORE_METHOD_ONLY_SUPPORTED_IN_TWA; mRejectShowErrorMessage = ErrorStrings.APP_STORE_METHOD_ONLY_SUPPORTED_IN_TWA;
......
...@@ -9,6 +9,7 @@ ...@@ -9,6 +9,7 @@
#include <stdint.h> #include <stdint.h>
#include <vector> #include <vector>
#include "base/android/jni_array.h"
#include "base/android/scoped_java_ref.h" #include "base/android/scoped_java_ref.h"
#include "base/check.h" #include "base/check.h"
#include "mojo/public/cpp/bindings/struct_ptr.h" #include "mojo/public/cpp/bindings/struct_ptr.h"
...@@ -58,6 +59,18 @@ bool DeserializeFromJavaByteBufferArray( ...@@ -58,6 +59,18 @@ bool DeserializeFromJavaByteBufferArray(
return true; return true;
} }
// Serializes a vector of native Mojo objects into a Java byte[][].
template <typename T>
base::android::ScopedJavaLocalRef<jobjectArray>
SerializeToJavaArrayOfByteArrays(JNIEnv* env,
const std::vector<mojo::StructPtr<T>>& input) {
std::vector<std::vector<uint8_t>> serialized_elements(input.size());
for (size_t i = 0; i < input.size(); i++) {
serialized_elements[i] = T::Serialize(&input[i]);
}
return base::android::ToJavaArrayOfByteArray(env, serialized_elements);
}
} // namespace android } // namespace android
} // namespace payments } // namespace payments
......
...@@ -21,6 +21,7 @@ import org.chromium.payments.mojom.PaymentValidationErrors; ...@@ -21,6 +21,7 @@ import org.chromium.payments.mojom.PaymentValidationErrors;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
...@@ -32,46 +33,21 @@ import java.util.Map; ...@@ -32,46 +33,21 @@ import java.util.Map;
*/ */
@JNINamespace("payments::android") @JNINamespace("payments::android")
public class PaymentRequestSpec { public class PaymentRequestSpec {
// TODO(crbug.com/1124917): these parameters duplicate with those in payment_request_spec from
// the blink side. We need to de-dup them.
private final Map<String, PaymentMethodData> mMethodData;
private final String mId;
private Map<String, PaymentDetailsModifier> mModifiers = new ArrayMap<>();
private List<PaymentShippingOption> mRawShippingOptions;
private List<PaymentItem> mRawLineItems;
private PaymentItem mRawTotal;
private long mNativePointer; private long mNativePointer;
private PaymentDetails mPaymentDetails;
/** /**
* Creates a valid instance of PaymentRequestSpec with payment parameters, and saves the * Stores the information received from the renderer that invoked the Payment Request API.
* parameters in the instance. * Creates an instance of native payment_request_spec.cc with the given parameters.
* @param details The payment details, e.g., the total amount.
* @param options The payment options, e.g., whether shipping is requested. * @param options The payment options, e.g., whether shipping is requested.
* @param methodData The map of supported payment method identifiers and corresponding payment * @param details The payment details, e.g., the total amount.
* @param methodData The list of supported payment method identifiers and corresponding payment
* method specific data.
* @param appLocale The current application locale. * @param appLocale The current application locale.
* @return The created instance if valid; null otherwise.
*/ */
@Nullable public PaymentRequestSpec(PaymentOptions options, PaymentDetails details,
public static PaymentRequestSpec createAndValidate(PaymentDetails details, Collection<PaymentMethodData> methodData, String appLocale) {
PaymentOptions options, Map<String, PaymentMethodData> methodData, String appLocale) { mNativePointer = PaymentRequestSpecJni.get().create(options.serialize(),
PaymentRequestSpec spec = new PaymentRequestSpec(details, methodData); details.serialize(), MojoStructCollection.serialize(methodData), appLocale);
if (!spec.parseAndValidateDetails(details)) return null;
spec.createNative(options, appLocale);
return spec;
}
private PaymentRequestSpec(PaymentDetails details, Map<String, PaymentMethodData> methodData) {
mPaymentDetails = details;
mId = mPaymentDetails.id;
mMethodData = methodData;
}
private void createNative(PaymentOptions options, String appLocale) {
assert mNativePointer == 0;
mNativePointer =
PaymentRequestSpecJni.get().create(options.serialize(), mPaymentDetails.serialize(),
MojoStructCollection.serialize(mMethodData.values()), appLocale);
} }
/** @return Whether destroy() has been called. */ /** @return Whether destroy() has been called. */
...@@ -84,7 +60,15 @@ public class PaymentRequestSpec { ...@@ -84,7 +60,15 @@ public class PaymentRequestSpec {
* method specific data. This value is still available after the instance is destroyed. * method specific data. This value is still available after the instance is destroyed.
*/ */
public Map<String, PaymentMethodData> getMethodData() { public Map<String, PaymentMethodData> getMethodData() {
return mMethodData; Map<String, PaymentMethodData> methodDataMap = new ArrayMap<>();
byte[][] methodDataByteArrays = PaymentRequestSpecJni.get().getMethodData(mNativePointer);
for (int i = 0; i < methodDataByteArrays.length; i++) {
PaymentMethodData methodData =
PaymentMethodData.deserialize(ByteBuffer.wrap(methodDataByteArrays[i]));
String method = methodData.supportedMethod;
methodDataMap.put(method, methodData);
}
return methodDataMap;
} }
/** /**
...@@ -94,7 +78,16 @@ public class PaymentRequestSpec { ...@@ -94,7 +78,16 @@ public class PaymentRequestSpec {
* instance is destroyed. * instance is destroyed.
*/ */
public Map<String, PaymentDetailsModifier> getModifiers() { public Map<String, PaymentDetailsModifier> getModifiers() {
return mModifiers; Map<String, PaymentDetailsModifier> modifiers = new ArrayMap<>();
PaymentDetails details = getPaymentDetails();
if (details.modifiers != null) {
for (int i = 0; i < details.modifiers.length; i++) {
PaymentDetailsModifier modifier = details.modifiers[i];
String method = modifier.methodData.supportedMethod;
modifiers.put(method, modifier);
}
}
return modifiers;
} }
/** /**
...@@ -102,7 +95,7 @@ public class PaymentRequestSpec { ...@@ -102,7 +95,7 @@ public class PaymentRequestSpec {
* the instance is destroyed. * the instance is destroyed.
*/ */
public String getId() { public String getId() {
return mId; return getPaymentDetails().id;
} }
/** /**
...@@ -111,7 +104,10 @@ public class PaymentRequestSpec { ...@@ -111,7 +104,10 @@ public class PaymentRequestSpec {
* available after the instance is destroyed. * available after the instance is destroyed.
*/ */
public List<PaymentShippingOption> getRawShippingOptions() { public List<PaymentShippingOption> getRawShippingOptions() {
return mRawShippingOptions; PaymentDetails details = getPaymentDetails();
return Collections.unmodifiableList(details.shippingOptions != null
? Arrays.asList(details.shippingOptions)
: new ArrayList<>());
} }
/** /**
...@@ -119,7 +115,10 @@ public class PaymentRequestSpec { ...@@ -119,7 +115,10 @@ public class PaymentRequestSpec {
* passed to the payment app. This value is still available after the instance is destroyed. * passed to the payment app. This value is still available after the instance is destroyed.
*/ */
public List<PaymentItem> getRawLineItems() { public List<PaymentItem> getRawLineItems() {
return mRawLineItems; PaymentDetails details = getPaymentDetails();
return Collections.unmodifiableList(details.displayItems != null
? Arrays.asList(details.displayItems)
: new ArrayList<>());
} }
/** /**
...@@ -127,57 +126,13 @@ public class PaymentRequestSpec { ...@@ -127,57 +126,13 @@ public class PaymentRequestSpec {
* to the payment app. This value is still available after the instance is destroyed. * to the payment app. This value is still available after the instance is destroyed.
*/ */
public PaymentItem getRawTotal() { public PaymentItem getRawTotal() {
return mRawTotal; return getPaymentDetails().total;
} }
/** @return The payment details specified in the payment request. */ /** @return The payment details specified in the payment request. */
public PaymentDetails getPaymentDetails() { public PaymentDetails getPaymentDetails() {
return mPaymentDetails; return PaymentDetails.deserialize(
} ByteBuffer.wrap(PaymentRequestSpecJni.get().getPaymentDetails(mNativePointer)));
/**
* Sets the total, display line items, and shipping options based on input and returns the
* status boolean. That status is true for valid data, false for invalid data. If the input is
* invalid, disconnects from the client. Both raw and UI versions of data are updated.
*
* @param details The total, line items, and shipping options to parse, validate, and save in
* member variables.
* @return True if the data is valid. False if the data is invalid.
*/
public boolean parseAndValidateDetails(PaymentDetails details) {
if (!PaymentValidator.validatePaymentDetails(details)) return false;
if (details.total != null) {
mRawTotal = details.total;
}
if (mRawLineItems == null || details.displayItems != null) {
mRawLineItems = Collections.unmodifiableList(details.displayItems != null
? Arrays.asList(details.displayItems)
: new ArrayList<>());
}
if (details.modifiers != null) {
if (details.modifiers.length == 0) mModifiers.clear();
for (int i = 0; i < details.modifiers.length; i++) {
PaymentDetailsModifier modifier = details.modifiers[i];
String method = modifier.methodData.supportedMethod;
mModifiers.put(method, modifier);
}
}
if (details.shippingOptions != null) {
mRawShippingOptions =
Collections.unmodifiableList(Arrays.asList(details.shippingOptions));
} else if (mRawShippingOptions == null) {
mRawShippingOptions = Collections.unmodifiableList(new ArrayList<>());
}
assert mRawTotal != null;
assert mRawLineItems != null;
return true;
} }
/** /**
...@@ -186,7 +141,6 @@ public class PaymentRequestSpec { ...@@ -186,7 +141,6 @@ public class PaymentRequestSpec {
* @param details The updated payment details, e.g., the updated total amount. * @param details The updated payment details, e.g., the updated total amount.
*/ */
public void updateWith(PaymentDetails details) { public void updateWith(PaymentDetails details) {
mPaymentDetails = details;
PaymentRequestSpecJni.get().updateWith(mNativePointer, details.serialize()); PaymentRequestSpecJni.get().updateWith(mNativePointer, details.serialize());
} }
...@@ -236,5 +190,7 @@ public class PaymentRequestSpec { ...@@ -236,5 +190,7 @@ public class PaymentRequestSpec {
void recomputeSpecForDetails(long nativePaymentRequestSpec); void recomputeSpecForDetails(long nativePaymentRequestSpec);
String selectedShippingOptionError(long nativePaymentRequestSpec); String selectedShippingOptionError(long nativePaymentRequestSpec);
void destroy(long nativePaymentRequestSpec); void destroy(long nativePaymentRequestSpec);
byte[] getPaymentDetails(long nativePaymentRequestSpec);
byte[][] getMethodData(long nativePaymentRequestSpec);
} }
} }
...@@ -90,6 +90,17 @@ PaymentRequestSpec::SelectedShippingOptionError(JNIEnv* env) { ...@@ -90,6 +90,17 @@ PaymentRequestSpec::SelectedShippingOptionError(JNIEnv* env) {
env, spec_->selected_shipping_option_error()); env, spec_->selected_shipping_option_error());
} }
base::android::ScopedJavaLocalRef<jbyteArray>
PaymentRequestSpec::GetPaymentDetails(JNIEnv* env) {
return base::android::ToJavaByteArray(
env, mojom::PaymentDetails::Serialize(&spec_->details_ptr()));
}
base::android::ScopedJavaLocalRef<jobjectArray>
PaymentRequestSpec::GetMethodData(JNIEnv* env) {
return SerializeToJavaArrayOfByteArrays(env, spec_->method_data());
}
void PaymentRequestSpec::Destroy(JNIEnv* env) { void PaymentRequestSpec::Destroy(JNIEnv* env) {
delete this; delete this;
} }
......
...@@ -58,6 +58,12 @@ class PaymentRequestSpec { ...@@ -58,6 +58,12 @@ class PaymentRequestSpec {
base::android::ScopedJavaLocalRef<jstring> SelectedShippingOptionError( base::android::ScopedJavaLocalRef<jstring> SelectedShippingOptionError(
JNIEnv* env); JNIEnv* env);
// Returns the payment details.
base::android::ScopedJavaLocalRef<jbyteArray> GetPaymentDetails(JNIEnv* env);
// Returns the method data.
base::android::ScopedJavaLocalRef<jobjectArray> GetMethodData(JNIEnv* env);
// Destroys this bridge. // Destroys this bridge.
void Destroy(JNIEnv* env); void Destroy(JNIEnv* env);
......
...@@ -198,6 +198,8 @@ class PaymentRequestSpec : public PaymentOptionsProvider, ...@@ -198,6 +198,8 @@ class PaymentRequestSpec : public PaymentOptionsProvider,
const; const;
const mojom::PaymentDetails& details() const { return *details_.get(); } const mojom::PaymentDetails& details() const { return *details_.get(); }
const mojom::PaymentDetailsPtr& details_ptr() const { return details_; }
const std::vector<mojom::PaymentMethodDataPtr>& method_data() const { const std::vector<mojom::PaymentMethodDataPtr>& method_data() const {
return method_data_; return method_data_;
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment