chromium/chrome/android/java/src/org/chromium/chrome/browser/payments/ChromePaymentResponseHelper.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.chrome.browser.payments;

import androidx.annotation.Nullable;

import org.chromium.chrome.browser.autofill.AddressNormalizerFactory;
import org.chromium.chrome.browser.autofill.AutofillAddress;
import org.chromium.chrome.browser.autofill.PersonalDataManager;
import org.chromium.chrome.browser.autofill.PhoneNumberUtil;
import org.chromium.components.autofill.AddressNormalizer.NormalizedAddressRequestDelegate;
import org.chromium.components.autofill.AutofillProfile;
import org.chromium.components.autofill.EditableOption;
import org.chromium.components.payments.PayerData;
import org.chromium.components.payments.PaymentAddressTypeConverter;
import org.chromium.components.payments.PaymentApp;
import org.chromium.components.payments.PaymentResponseHelperInterface;
import org.chromium.payments.mojom.PayerDetail;
import org.chromium.payments.mojom.PaymentOptions;
import org.chromium.payments.mojom.PaymentResponse;

/** The helper class to create and prepare a PaymentResponse. */
public class ChromePaymentResponseHelper
        implements NormalizedAddressRequestDelegate, PaymentResponseHelperInterface {
    @Nullable private final AutofillContact mSelectedContact;
    private final PaymentApp mSelectedPaymentApp;
    private final PaymentOptions mPaymentOptions;
    private final PaymentResponse mPaymentResponse;
    private AutofillAddress mSelectedShippingAddress;
    private PaymentResponseResultCallback mResultCallback;
    private boolean mIsWaitingForShippingNormalization;
    private boolean mIsWaitingForPaymentsDetails = true;
    private PayerData mPayerDataFromPaymentApp;

    /**
     * Builds a helper to construct and fill a PaymentResponse.
     *
     * @param selectedShippingAddress The shipping address picked by the user.
     * @param selectedShippingOption The shipping option picked by the user.
     * @param selectedContact The contact info picked by the user, can be null.
     * @param selectedPaymentApp The payment app picked by the user.
     * @param paymentOptions The paymentOptions of the corresponding payment request.
     * @param personalDataManager The context appropriate PersonalDataManager reference.
     */
    public ChromePaymentResponseHelper(
            EditableOption selectedShippingAddress,
            EditableOption selectedShippingOption,
            @Nullable AutofillContact selectedContact,
            PaymentApp selectedPaymentApp,
            PaymentOptions paymentOptions,
            PersonalDataManager personalDataManager) {
        mPaymentResponse = new PaymentResponse();
        mPaymentResponse.payer = new PayerDetail();

        mSelectedPaymentApp = selectedPaymentApp;
        mPaymentOptions = paymentOptions;

        mSelectedContact = selectedContact;

        // Set up the shipping option section of the response when it comes from payment sheet.
        // (Shipping option comes from payment app when the app can handle shipping.)
        if (mPaymentOptions.requestShipping && !mSelectedPaymentApp.handlesShippingAddress()) {
            assert selectedShippingOption != null;
            assert selectedShippingOption.getIdentifier() != null;
            mPaymentResponse.shippingOption = selectedShippingOption.getIdentifier();
        }

        // Set up the shipping address section of the response when it comes from payment sheet.
        // (Shipping address comes from payment app when the app can handle shipping.)
        if (mPaymentOptions.requestShipping && !mSelectedPaymentApp.handlesShippingAddress()) {
            assert selectedShippingAddress != null;
            // Shipping addresses are created in ChromePaymentRequestService.init(). These should
            // all be instances of AutofillAddress.
            mSelectedShippingAddress = (AutofillAddress) selectedShippingAddress;

            // Addresses to be sent to the merchant should always be complete.
            assert mSelectedShippingAddress.isComplete();

            // Record the use of the profile.
            personalDataManager.recordAndLogProfileUse(
                    mSelectedShippingAddress.getProfile().getGUID());

            mPaymentResponse.shippingAddress = mSelectedShippingAddress.toPaymentAddress();

            // The shipping address needs to be normalized before sending the response to the
            // merchant.
            mIsWaitingForShippingNormalization = true;
            AddressNormalizerFactory.getInstance()
                    .normalizeAddress(mSelectedShippingAddress.getProfile(), this);
        }
    }

    @Override
    public void generatePaymentResponse(
            String methodName,
            String stringifiedDetails,
            PayerData payerData,
            PaymentResponseResultCallback resultCallback) {
        mResultCallback = resultCallback;
        mPaymentResponse.methodName = methodName;
        mPaymentResponse.stringifiedDetails = stringifiedDetails;
        mPayerDataFromPaymentApp = payerData;

        mIsWaitingForPaymentsDetails = false;

        // Wait for the shipping address normalization before sending the response.
        if (!mIsWaitingForShippingNormalization) onAllDataReady();
    }

    @Override
    public void onAddressNormalized(AutofillProfile profile) {
        // Check if a normalization is still required.
        if (!mIsWaitingForShippingNormalization) return;
        mIsWaitingForShippingNormalization = false;

        if (profile != null) {
            // The normalization finished first: use the normalized address.
            mSelectedShippingAddress.completeAddress(profile);
            mPaymentResponse.shippingAddress = mSelectedShippingAddress.toPaymentAddress();
        }

        // Wait for the payment details before sending the response.
        if (!mIsWaitingForPaymentsDetails) onAllDataReady();
    }

    @Override
    public void onCouldNotNormalize(AutofillProfile profile) {
        onAddressNormalized(profile);
    }

    private void onAllDataReady() {
        assert !mIsWaitingForPaymentsDetails;
        assert !mIsWaitingForShippingNormalization;

        // Set up the shipping section of the response when it comes from payment app.
        if (mPaymentOptions.requestShipping && mSelectedPaymentApp.handlesShippingAddress()) {
            mPaymentResponse.shippingAddress =
                    PaymentAddressTypeConverter.convertAddressToMojoPaymentAddress(
                            mPayerDataFromPaymentApp.shippingAddress);
            mPaymentResponse.shippingOption = mPayerDataFromPaymentApp.selectedShippingOptionId;
        }

        // Set up the contact section of the response.
        if (mPaymentOptions.requestPayerName) {
            if (mSelectedPaymentApp.handlesPayerName()) {
                mPaymentResponse.payer.name = mPayerDataFromPaymentApp.payerName;
            } else {
                assert mSelectedContact != null;
                mPaymentResponse.payer.name = mSelectedContact.getPayerName();
            }
        }
        if (mPaymentOptions.requestPayerPhone) {
            if (mSelectedPaymentApp.handlesPayerPhone()) {
                mPaymentResponse.payer.phone = mPayerDataFromPaymentApp.payerPhone;
            } else {
                assert mSelectedContact != null;
                mPaymentResponse.payer.phone = mSelectedContact.getPayerPhone();
            }
        }
        if (mPaymentOptions.requestPayerEmail) {
            if (mSelectedPaymentApp.handlesPayerEmail()) {
                mPaymentResponse.payer.email = mPayerDataFromPaymentApp.payerEmail;
            } else {
                assert mSelectedContact != null;
                mPaymentResponse.payer.email = mSelectedContact.getPayerEmail();
            }
        }

        // Normalize the phone number only if it's not null since this calls native code.
        if (mPaymentResponse.payer.phone != null) {
            mPaymentResponse.payer.phone =
                    PhoneNumberUtil.formatForResponse(mPaymentResponse.payer.phone);
        }

        mResultCallback.onPaymentResponseReady(mPaymentResponse);
    }
}