chromium/components/payments/content/android/java/src/org/chromium/components/payments/PaymentApp.java

// Copyright 2016 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.components.payments;

import android.graphics.drawable.Drawable;

import androidx.annotation.Nullable;

import org.chromium.base.task.PostTask;
import org.chromium.base.task.TaskTraits;
import org.chromium.components.autofill.EditableOption;
import org.chromium.payments.mojom.PaymentDetailsModifier;
import org.chromium.payments.mojom.PaymentItem;
import org.chromium.payments.mojom.PaymentMethodData;
import org.chromium.payments.mojom.PaymentOptions;
import org.chromium.payments.mojom.PaymentRequestDetailsUpdate;
import org.chromium.payments.mojom.PaymentResponse;
import org.chromium.payments.mojom.PaymentShippingOption;

import java.util.List;
import java.util.Map;
import java.util.Set;

/** The base class for a single payment app, e.g., a payment handler. */
public abstract class PaymentApp extends EditableOption {
    /** Arbitrarily chosen maximum length of a payment app name. */
    private static final int APP_NAME_ELIDE_LENGTH = 64;

    /**
     * Whether complete and valid autofill data for merchant's request is available, e.g., if
     * merchant specifies `requestPayerEmail: true`, then this variable is true only if the autofill
     * data contains a valid email address. May be used in canMakePayment() for some types of
     * app, such as AutofillPaymentInstrument.
     */
    protected boolean mHaveRequestedAutofillData;

    /** The interface for the requester of payment details from the app. */
    public interface InstrumentDetailsCallback {
        /**
         * Called after retrieving payment details.
         *
         * @param methodName         Method name. For example, "visa".
         * @param stringifiedDetails JSON-serialized object. For example, {"card": "123"}.
         * @param payerData          Payer's shipping address and contact information.
         */
        void onInstrumentDetailsReady(
                String methodName, String stringifiedDetails, PayerData payerData);

        /**
         * Called if unable to retrieve payment details.
         * @param errorMessage Developer-facing error message to be used when rejecting the promise
         *                     returned from PaymentRequest.show().
         */
        void onInstrumentDetailsError(String errorMessage);
    }

    /** The interface for the requester to abort payment. */
    public interface AbortCallback {
        /**
         * Called after aborting payment is finished.
         *
         * @param abortSucceeded Indicates whether abort is succeed.
         */
        void onInstrumentAbortResult(boolean abortSucceeded);
    }

    protected PaymentApp(String id, String label, String sublabel, Drawable icon) {
        super(id, maybeElide(removeLineTerminators(label)), sublabel, icon);
    }

    protected PaymentApp(
            String id, String label, String sublabel, String tertiarylabel, Drawable icon) {
        super(id, maybeElide(removeLineTerminators(label)), sublabel, tertiarylabel, icon);
    }

    private static String removeLineTerminators(String text) {
        // '\n' - A newline (line feed) character.
        // '\f' - A form feed character.
        // '\r' - A carriage-return character.
        // '\u0085' - A next-line character.
        // '\u2028' - A line-separator character.
        // '\u2029' - A paragraph-separator character.
        // [abc] - a, b, or c (simple character class).
        // X+ - X, one or more times, a greedy quantifier.
        // See: https://docs.oracle.com/javase/7/docs/api/java/util/regex/Pattern.html
        return text.replaceAll("[\n\f\r\u0085\u2028\u2029]+", "");
    }

    private static String maybeElide(String text) {
        // 2026 is the unicode horizontal ellipsis.
        // See https://util.unicode.org/UnicodeJsps/character.jsp?a=2026.
        return text.length() <= APP_NAME_ELIDE_LENGTH
                ? text
                : text.substring(0, APP_NAME_ELIDE_LENGTH) + "\u2026";
    }

    /**
     * Sets the modified total for this payment app.
     *
     * @param modifiedTotal The new modified total to use.
     */
    public void setModifiedTotal(@Nullable String modifiedTotal) {
        updatePromoMessage(modifiedTotal);
    }

    /**
     * Returns a set of payment method names for this app, e.g., "basic-card".
     *
     * @return The method names for this app.
     */
    public abstract Set<String> getInstrumentMethodNames();

    /**
     * @return Whether this is a replacement for all server autofill apps. If at least one of
     *         the displayed apps returns true here, then all apps that return true in
     *         isServerAutofillInstrument() should be hidden.
     */
    public boolean isServerAutofillInstrumentReplacement() {
        return false;
    }

    /**
     * @return Whether the app supports the payment method with the method data. For example,
     *         supported card types and networks in the data should be verified for 'basic-card'
     *         payment method.
     */
    public boolean isValidForPaymentMethodData(String method, @Nullable PaymentMethodData data) {
        return getInstrumentMethodNames().contains(method);
    }

    /** @return Whether the app can collect and return shipping address. */
    public boolean handlesShippingAddress() {
        return false;
    }

    /** @return Whether the app can collect and return payer's name. */
    public boolean handlesPayerName() {
        return false;
    }

    /** @return Whether the app can collect and return payer's email. */
    public boolean handlesPayerEmail() {
        return false;
    }

    /** @return Whether the app can collect and return payer's phone. */
    public boolean handlesPayerPhone() {
        return false;
    }

    /**
     * @param haveRequestedAutofillData Whether complete and valid autofill data for merchant's
     *                                  request is available.
     */
    public void setHaveRequestedAutofillData(boolean haveRequestedAutofillData) {
        mHaveRequestedAutofillData = haveRequestedAutofillData;
    }

    /**
     * @return Whether this payment app should cause PaymentRequest.hasEnrolledInstrument() to
     *         return true.
     */
    public boolean hasEnrolledInstrument() {
        return true;
    }

    /** @return Whether this payment app can be pre-selected for immediate payment. */
    public boolean canPreselect() {
        return true;
    }

    /**
     * Invoke the payment app to retrieve the payment details.
     *
     * The callback will be invoked with the resulting payment details or error.
     *
     * @param id               The unique identifier of the PaymentRequest.
     * @param merchantName     The name of the merchant.
     * @param origin           The origin of this merchant.
     * @param iframeOrigin     The origin of the iframe that invoked PaymentRequest.
     * @param certificateChain The site certificate chain of the merchant. Can be null for localhost
     *                         or local file, which are secure contexts without SSL.
     * @param methodDataMap    The payment-method specific data for all applicable payment methods,
     *                         e.g., whether the app should be invoked in test or production, a
     *                         merchant identifier, or a public key.
     * @param total            The total amount.
     * @param displayItems     The shopping cart items.
     * @param modifiers        The relevant payment details modifiers.
     * @param paymentOptions   The payment options of the PaymentRequest.
     * @param shippingOptions  The shipping options of the PaymentRequest.
     * @param callback         The object that will receive the payment details.
     */
    public void invokePaymentApp(
            String id,
            String merchantName,
            String origin,
            String iframeOrigin,
            @Nullable byte[][] certificateChain,
            Map<String, PaymentMethodData> methodDataMap,
            PaymentItem total,
            List<PaymentItem> displayItems,
            Map<String, PaymentDetailsModifier> modifiers,
            PaymentOptions paymentOptions,
            List<PaymentShippingOption> shippingOptions,
            InstrumentDetailsCallback callback) {}

    /**
     * Update the payment information in response to payment method, shipping address, or shipping
     * option change events.
     *
     * @param response The merchant's response to the payment method, shipping address, or shipping
     *         option change events.
     */
    public void updateWith(PaymentRequestDetailsUpdate response) {}

    /**
     * Called when the merchant ignored the payment method, shipping address or shipping option
     * change event.
     */
    public void onPaymentDetailsNotUpdated() {}

    /**
     * @return True after changePaymentMethodFromInvokedApp(), changeShippingOptionFromInvokedApp(),
     *         or changeShippingAddressFromInvokedApp() and before update updateWith() or
     *         onPaymentDetailsNotUpdated().
     */
    public boolean isWaitingForPaymentDetailsUpdate() {
        return false;
    }

    /**
     * Abort invocation of the payment app.
     * @param callback The callback to return abort result.
     */
    public void abortPaymentApp(AbortCallback callback) {
        PostTask.postTask(
                TaskTraits.UI_DEFAULT,
                new Runnable() {
                    @Override
                    public void run() {
                        callback.onInstrumentAbortResult(false);
                    }
                });
    }

    /** Cleans up any resources held by the payment app. For example, closes server connections. */
    public abstract void dismissInstrument();

    /**
     * @return The identifier for another payment app that should be hidden when this payment app is
     * present.
     */
    @Nullable
    public String getApplicationIdentifierToHide() {
        return null;
    }

    /**
     * @return The set of identifier of other apps that would cause this app to be hidden, if any of
     * them are present, e.g., ["com.bobpay.production", "com.bobpay.beta"].
     */
    @Nullable
    public Set<String> getApplicationIdentifiersThatHideThisApp() {
        return null;
    }

    /** @return The ukm source id assigned to the payment app. */
    public long getUkmSourceId() {
        return 0;
    }

    /**
     * Sets the endpoint for payment handler communication. Must be called before invoking this
     * payment app. Used only by payment apps that are backed by a payment handler.
     * @param host The endpoint for payment handler communication. Should not be null.
     */
    public void setPaymentHandlerHost(PaymentHandlerHost host) {}

    /** @return The type of payment app. */
    public @PaymentAppType int getPaymentAppType() {
        return PaymentAppType.UNDEFINED;
    }

    /**
     * @return Whether this app should be chosen over other available payment apps. For example,
     * when the Play Billing payment app is available in a TWA.
     */
    public boolean isPreferred() {
        return false;
    }

    /**
     * Updates the response IPC structure with the fields that are unique to this type of payment
     * app. Used when JSON serialization of payment method specific data is not being used. The
     * payment apps who need to set the fields should override this method.
     * @param response The PaymentResponse to whom the fields are set.
     * @return The PaymentResponse whose fields has been set.
     */
    public PaymentResponse setAppSpecificResponseFields(PaymentResponse response) {
        return response;
    }
}