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

// Copyright 2020 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.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.os.Handler;

import androidx.annotation.Nullable;

import org.jni_zero.CalledByNative;
import org.jni_zero.JNINamespace;
import org.jni_zero.NativeMethods;

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.nio.ByteBuffer;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

/** Wrapper around a C++ payment app. */
@JNINamespace("payments")
public class JniPaymentApp extends PaymentApp {
    private final Handler mHandler = new Handler();
    private final @PaymentAppType int mPaymentAppType;

    // The Java object owns the C++ payment app and frees it in dismissInstrument().
    private long mNativeObject;

    private AbortCallback mAbortCallback;
    private InstrumentDetailsCallback mInvokeCallback;

    @CalledByNative
    private JniPaymentApp(
            String id,
            String label,
            String sublabel,
            Bitmap icon,
            @PaymentAppType int paymentAppType,
            long nativeObject) {
        super(id, label, sublabel, new BitmapDrawable(icon));
        mPaymentAppType = paymentAppType;
        mNativeObject = nativeObject;
    }

    @CalledByNative
    public void onAbortResult(boolean aborted) {
        mHandler.post(
                () -> {
                    if (mAbortCallback == null) return;
                    mAbortCallback.onInstrumentAbortResult(aborted);
                    mAbortCallback = null;
                });
    }

    @CalledByNative
    public void onInvokeResult(String methodName, String stringifiedDetails, PayerData payerData) {
        mHandler.post(
                () -> {
                    if (mInvokeCallback == null) return;
                    mInvokeCallback.onInstrumentDetailsReady(
                            methodName, stringifiedDetails, payerData);
                    mInvokeCallback = null;
                });
    }

    @CalledByNative
    public void onInvokeError(String errorMessage) {
        mHandler.post(
                () -> {
                    if (mInvokeCallback == null) return;
                    mInvokeCallback.onInstrumentDetailsError(errorMessage);
                    mInvokeCallback = null;
                });
    }

    @CalledByNative
    private static PayerData createPayerData(
            String payerName,
            String payerPhone,
            String payerEmail,
            Address shippingAddress,
            String selectedShippingOptionId) {
        return new PayerData(
                payerName, payerPhone, payerEmail, shippingAddress, selectedShippingOptionId);
    }

    @CalledByNative
    private static Address createShippingAddress(
            String country,
            String[] addressLine,
            String region,
            String city,
            String dependentLocality,
            String postalCode,
            String sortingCode,
            String organization,
            String recipient,
            String phone) {
        return new Address(
                country,
                addressLine,
                region,
                city,
                dependentLocality,
                postalCode,
                sortingCode,
                organization,
                recipient,
                phone);
    }

    @Override
    public Set<String> getInstrumentMethodNames() {
        return new HashSet<>(
                Arrays.asList(JniPaymentAppJni.get().getInstrumentMethodNames(mNativeObject)));
    }

    @Override
    public boolean isValidForPaymentMethodData(String method, @Nullable PaymentMethodData data) {
        return JniPaymentAppJni.get()
                .isValidForPaymentMethodData(
                        mNativeObject, method, data != null ? data.serialize() : null);
    }

    @Override
    public boolean handlesShippingAddress() {
        return JniPaymentAppJni.get().handlesShippingAddress(mNativeObject);
    }

    @Override
    public boolean handlesPayerName() {
        return JniPaymentAppJni.get().handlesPayerName(mNativeObject);
    }

    @Override
    public boolean handlesPayerEmail() {
        return JniPaymentAppJni.get().handlesPayerEmail(mNativeObject);
    }

    @Override
    public boolean handlesPayerPhone() {
        return JniPaymentAppJni.get().handlesPayerPhone(mNativeObject);
    }

    @Override
    public boolean hasEnrolledInstrument() {
        return JniPaymentAppJni.get().hasEnrolledInstrument(mNativeObject);
    }

    @Override
    public boolean canPreselect() {
        return JniPaymentAppJni.get().canPreselect(mNativeObject);
    }

    @Override
    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) {
        mInvokeCallback = callback;
        JniPaymentAppJni.get().invokePaymentApp(mNativeObject, /* callback= */ this);
    }

    @Override
    public void updateWith(PaymentRequestDetailsUpdate response) {
        JniPaymentAppJni.get().updateWith(mNativeObject, response.serialize());
    }

    @Override
    public void onPaymentDetailsNotUpdated() {
        JniPaymentAppJni.get().onPaymentDetailsNotUpdated(mNativeObject);
    }

    @Override
    public boolean isWaitingForPaymentDetailsUpdate() {
        return JniPaymentAppJni.get().isWaitingForPaymentDetailsUpdate(mNativeObject);
    }

    @Override
    public void abortPaymentApp(AbortCallback callback) {
        mAbortCallback = callback;
        JniPaymentAppJni.get().abortPaymentApp(mNativeObject, this);
    }

    @Override
    @Nullable
    public String getApplicationIdentifierToHide() {
        return JniPaymentAppJni.get().getApplicationIdentifierToHide(mNativeObject);
    }

    @Override
    @Nullable
    public Set<String> getApplicationIdentifiersThatHideThisApp() {
        return new HashSet<>(
                Arrays.asList(
                        JniPaymentAppJni.get()
                                .getApplicationIdentifiersThatHideThisApp(mNativeObject)));
    }

    @Override
    public long getUkmSourceId() {
        return JniPaymentAppJni.get().getUkmSourceId(mNativeObject);
    }

    @Override
    public void setPaymentHandlerHost(PaymentHandlerHost host) {
        JniPaymentAppJni.get().setPaymentHandlerHost(mNativeObject, host);
    }

    @Override
    public void dismissInstrument() {
        if (mNativeObject == 0) return;
        JniPaymentAppJni.get().freeNativeObject(mNativeObject);
        mNativeObject = 0;
    }

    @Override
    public void finalize() throws Throwable {
        dismissInstrument();
        super.finalize();
    }

    @Override
    public @PaymentAppType int getPaymentAppType() {
        return mPaymentAppType;
    }

    @Override
    public PaymentResponse setAppSpecificResponseFields(PaymentResponse response) {
        byte[] byteResult =
                JniPaymentAppJni.get()
                        .setAppSpecificResponseFields(mNativeObject, response.serialize());
        return PaymentResponse.deserialize(ByteBuffer.wrap(byteResult));
    }

    @NativeMethods
    interface Natives {
        String[] getInstrumentMethodNames(long nativeJniPaymentApp);

        boolean isValidForPaymentMethodData(
                long nativeJniPaymentApp, String method, ByteBuffer dataByteBuffer);

        boolean handlesShippingAddress(long nativeJniPaymentApp);

        boolean handlesPayerName(long nativeJniPaymentApp);

        boolean handlesPayerEmail(long nativeJniPaymentApp);

        boolean handlesPayerPhone(long nativeJniPaymentApp);

        boolean hasEnrolledInstrument(long nativeJniPaymentApp);

        boolean canPreselect(long nativeJniPaymentApp);

        void invokePaymentApp(long nativeJniPaymentApp, JniPaymentApp callback);

        void updateWith(long nativeJniPaymentApp, ByteBuffer responseByteBuffer);

        void onPaymentDetailsNotUpdated(long nativeJniPaymentApp);

        boolean isWaitingForPaymentDetailsUpdate(long nativeJniPaymentApp);

        void abortPaymentApp(long nativeJniPaymentApp, JniPaymentApp callback);

        String getApplicationIdentifierToHide(long nativeJniPaymentApp);

        String[] getApplicationIdentifiersThatHideThisApp(long nativeJniPaymentApp);

        long getUkmSourceId(long nativeJniPaymentApp);

        void setPaymentHandlerHost(long nativeJniPaymentApp, PaymentHandlerHost paymentHandlerHost);

        void freeNativeObject(long nativeJniPaymentApp);

        byte[] setAppSpecificResponseFields(long nativeJniPaymentApp, ByteBuffer paymentResponse);
    }
}