/*
 * Copyright (C) 2017-2019 Apple Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
 * THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "config.h"
#include "ApplePayPaymentHandler.h"

#if ENABLE(APPLE_PAY) && ENABLE(PAYMENT_REQUEST)

#include "AddressErrors.h"
#include "ApplePayContactField.h"
#include "ApplePayCouponCodeDetails.h"
#include "ApplePayCouponCodeUpdate.h"
#include "ApplePayError.h"
#include "ApplePayErrorCode.h"
#include "ApplePayErrorContactField.h"
#include "ApplePayLineItem.h"
#include "ApplePayMerchantCapability.h"
#include "ApplePayModifier.h"
#include "ApplePayPayment.h"
#include "ApplePayPaymentAuthorizationResult.h"
#include "ApplePayPaymentCompleteDetails.h"
#include "ApplePayPaymentMethodUpdate.h"
#include "ApplePaySessionPaymentRequest.h"
#include "ApplePayShippingContactUpdate.h"
#include "ApplePayShippingMethod.h"
#include "ApplePayShippingMethodUpdate.h"
#include "Document.h"
#include "EventNames.h"
#include "Frame.h"
#include "JSApplePayCouponCodeDetails.h"
#include "JSApplePayError.h"
#include "JSApplePayPayment.h"
#include "JSApplePayPaymentMethod.h"
#include "JSApplePayRequest.h"
#include "JSDOMConvert.h"
#include "LinkIconCollector.h"
#include "MerchantValidationEvent.h"
#include "Page.h"
#include "PayerErrorFields.h"
#include "Payment.h"
#include "PaymentComplete.h"
#include "PaymentContact.h"
#include "PaymentCoordinator.h"
#include "PaymentDetailsModifier.h"
#include "PaymentMerchantSession.h"
#include "PaymentMethod.h"
#include "PaymentRequestUtilities.h"
#include "PaymentRequestValidator.h"
#include "PaymentResponse.h"
#include "PaymentValidationErrors.h"
#include "Settings.h"
#include <JavaScriptCore/JSONObject.h>

namespace WebCore {

static inline PaymentCoordinator& paymentCoordinator(Document& document)
{
    ASSERT(document.page());
    return document.page()->paymentCoordinator();
}

static ExceptionOr<ApplePayRequest> convertAndValidateApplePayRequest(Document& document, JSC::JSValue data)
{
    if (data.isEmpty())
        return Exception { TypeError, "Missing payment method data."_s };

    auto throwScope = DECLARE_THROW_SCOPE(document.vm());
    auto applePayRequest = convertDictionary<ApplePayRequest>(*document.globalObject(), data);
    if (throwScope.exception())
        return Exception { ExistingExceptionError };

    auto validatedRequest = convertAndValidate(document, applePayRequest.version, applePayRequest, paymentCoordinator(document));
    if (validatedRequest.hasException())
        return validatedRequest.releaseException();

    constexpr OptionSet fieldsToValidate = {
        PaymentRequestValidator::Field::MerchantCapabilities,
        PaymentRequestValidator::Field::SupportedNetworks,
        PaymentRequestValidator::Field::CountryCode,
    };
    auto exception = PaymentRequestValidator::validate(validatedRequest.releaseReturnValue(), fieldsToValidate);
    if (exception.hasException())
        return exception.releaseException();

    return WTFMove(applePayRequest);
}

ExceptionOr<void> ApplePayPaymentHandler::validateData(Document& document, JSC::JSValue data)
{
    auto requestOrException = convertAndValidateApplePayRequest(document, data);
    if (requestOrException.hasException())
        return requestOrException.releaseException();

    return { };
}

bool ApplePayPaymentHandler::handlesIdentifier(const PaymentRequest::MethodIdentifier& identifier)
{
    if (!std::holds_alternative<URL>(identifier))
        return false;

    auto& url = std::get<URL>(identifier);
    return url.host() == "apple.com"_s && url.path() == "/apple-pay"_s;
}

bool ApplePayPaymentHandler::hasActiveSession(Document& document)
{
    return WebCore::paymentCoordinator(document).hasActiveSession();
}

ApplePayPaymentHandler::ApplePayPaymentHandler(Document& document, const PaymentRequest::MethodIdentifier& identifier, PaymentRequest& paymentRequest)
    : ContextDestructionObserver { &document }
    , m_identifier { identifier }
    , m_paymentRequest { paymentRequest }
{
    ASSERT(handlesIdentifier(m_identifier));
}

Document& ApplePayPaymentHandler::document() const
{
    return downcast<Document>(*scriptExecutionContext());
}

PaymentCoordinator& ApplePayPaymentHandler::paymentCoordinator() const
{
    return WebCore::paymentCoordinator(document());
}

static ExceptionOr<void> validate(const PaymentCurrencyAmount& amount, const String& expectedCurrency)
{
    if (amount.currency != expectedCurrency)
        return Exception { TypeError, makeString("\"", amount.currency, "\" does not match the expected currency of \"", expectedCurrency, "\". Apple Pay requires all PaymentCurrencyAmounts to use the same currency code.") };
    return { };
}

static ExceptionOr<ApplePayLineItem> convertAndValidate(const PaymentItem& item, const String& expectedCurrency)
{
    auto exception = validate(item.amount, expectedCurrency);
    if (exception.hasException())
        return exception.releaseException();


    ApplePayLineItem lineItem;
    lineItem.amount = item.amount.value;
    lineItem.type = item.pending ? ApplePayLineItem::Type::Pending : ApplePayLineItem::Type::Final;
    lineItem.label = item.label;
    return { WTFMove(lineItem) };
}

static ExceptionOr<Vector<ApplePayLineItem>> convertAndValidate(const Vector<PaymentItem>& lineItems, const String& expectedCurrency)
{
    Vector<ApplePayLineItem> result;
    result.reserveInitialCapacity(lineItems.size());
    for (auto& lineItem : lineItems) {
        auto convertedLineItem = convertAndValidate(lineItem, expectedCurrency);
        if (convertedLineItem.hasException())
            return convertedLineItem.releaseException();
        result.uncheckedAppend(convertedLineItem.releaseReturnValue());
    }
    return { WTFMove(result) };
}

static ApplePaySessionPaymentRequest::ShippingType convert(PaymentShippingType type)
{
    switch (type) {
    case PaymentShippingType::Shipping:
        return ApplePaySessionPaymentRequest::ShippingType::Shipping;
    case PaymentShippingType::Delivery:
        return ApplePaySessionPaymentRequest::ShippingType::Delivery;
    case PaymentShippingType::Pickup:
        return ApplePaySessionPaymentRequest::ShippingType::StorePickup;
    }

    ASSERT_NOT_REACHED();
    return ApplePaySessionPaymentRequest::ShippingType::Shipping;
}

static ExceptionOr<ApplePayShippingMethod> convertAndValidate(const PaymentShippingOption& shippingOption, const String& expectedCurrency)
{
    auto exception = validate(shippingOption.amount, expectedCurrency);
    if (exception.hasException())
        return exception.releaseException();

    ApplePayShippingMethod result;
    result.amount = shippingOption.amount.value;
    result.label = shippingOption.label;
    result.identifier = shippingOption.id;
    return { WTFMove(result) };
}

ExceptionOr<void> ApplePayPaymentHandler::convertData(Document& document, JSC::JSValue data)
{
    auto requestOrException = convertAndValidateApplePayRequest(document, data);
    if (requestOrException.hasException())
        return requestOrException.releaseException();

    m_applePayRequest = requestOrException.releaseReturnValue();
    return { };
}

static void mergePaymentOptions(const PaymentOptions& options, ApplePaySessionPaymentRequest& request)
{
    auto requiredShippingContactFields = request.requiredShippingContactFields();
    requiredShippingContactFields.email |= options.requestPayerEmail;
    requiredShippingContactFields.name |= options.requestPayerName;
    requiredShippingContactFields.phone |= options.requestPayerPhone;
    requiredShippingContactFields.postalAddress |= options.requestShipping;
    request.setRequiredShippingContactFields(requiredShippingContactFields);

    auto requiredBillingContactFields = request.requiredBillingContactFields();
    requiredBillingContactFields.postalAddress |= options.requestBillingAddress;
    request.setRequiredBillingContactFields(requiredBillingContactFields);
    
    if (options.requestShipping)
        request.setShippingType(convert(options.shippingType));
}

ExceptionOr<void> ApplePayPaymentHandler::show(Document& document)
{
    auto validatedRequest = convertAndValidate(document, m_applePayRequest->version, *m_applePayRequest, paymentCoordinator());
    if (validatedRequest.hasException())
        return validatedRequest.releaseException();

    ApplePaySessionPaymentRequest request = validatedRequest.releaseReturnValue();
    request.setRequester(ApplePaySessionPaymentRequest::Requester::PaymentRequest);

    auto& details = m_paymentRequest->paymentDetails();

    String expectedCurrency = details.total.amount.currency;
    request.setCurrencyCode(expectedCurrency);

    auto total = convertAndValidate(details.total, expectedCurrency);
    ASSERT(!total.hasException());
    request.setTotal(total.releaseReturnValue());

    if (details.displayItems) {
        auto convertedLineItems = convertAndValidate(*details.displayItems, expectedCurrency);
        if (convertedLineItems.hasException())
            return convertedLineItems.releaseException();
        request.setLineItems(convertedLineItems.releaseReturnValue());
    }

    mergePaymentOptions(m_paymentRequest->paymentOptions(), request);

    auto shippingMethods = computeShippingMethods();
    if (shippingMethods.hasException())
        return shippingMethods.releaseException();
    request.setShippingMethods(shippingMethods.releaseReturnValue());

    auto modifierException = firstApplicableModifier();
    if (modifierException.hasException())
        return modifierException.releaseException();
    if (auto modifierData = modifierException.releaseReturnValue()) {
        auto applePayModifier = WTFMove(std::get<1>(*modifierData));
        UNUSED_VARIABLE(applePayModifier);

#if ENABLE(APPLE_PAY_RECURRING_PAYMENTS)
        request.setRecurringPaymentRequest(WTFMove(applePayModifier.recurringPaymentRequest));
#endif

#if ENABLE(APPLE_PAY_AUTOMATIC_RELOAD_PAYMENTS)
        request.setAutomaticReloadPaymentRequest(WTFMove(applePayModifier.automaticReloadPaymentRequest));
#endif

#if ENABLE(APPLE_PAY_MULTI_MERCHANT_PAYMENTS)
        request.setMultiTokenContexts(WTFMove(applePayModifier.multiTokenContexts));
#endif
    }

    constexpr OptionSet fieldsToValidate = {
        PaymentRequestValidator::Field::CurrencyCode,
        PaymentRequestValidator::Field::Total,
        PaymentRequestValidator::Field::ShippingMethods,
    };
    auto exception = PaymentRequestValidator::validate(request, fieldsToValidate);
    if (exception.hasException())
        return exception.releaseException();

    if (!paymentCoordinator().beginPaymentSession(document, *this, request))
        return Exception { AbortError };

    return { };
}

void ApplePayPaymentHandler::hide()
{
    paymentCoordinator().abortPaymentSession();
}

void ApplePayPaymentHandler::canMakePayment(Document&, Function<void(bool)>&& completionHandler)
{
    completionHandler(paymentCoordinator().canMakePayments());
}

ExceptionOr<Vector<ApplePayShippingMethod>> ApplePayPaymentHandler::computeShippingMethods() const
{
    auto& details = m_paymentRequest->paymentDetails();

    Vector<ApplePayShippingMethod> shippingOptions;
    if (details.shippingOptions) {
        auto& currency = details.total.amount.currency;

        shippingOptions.reserveInitialCapacity(details.shippingOptions->size());
        for (auto& shippingOption : *details.shippingOptions) {
            auto shippingMethodOrException = convertAndValidate(shippingOption, currency);
            if (shippingMethodOrException.hasException())
                return shippingMethodOrException.releaseException();

            auto shippingMethod = shippingMethodOrException.releaseReturnValue();

#if ENABLE(APPLE_PAY_SELECTED_SHIPPING_METHOD)
            if (shippingMethod.identifier == m_paymentRequest->shippingOption())
                shippingMethod.selected = true;
#endif

            shippingOptions.uncheckedAppend(WTFMove(shippingMethod));
        }
    }

#if ENABLE(APPLE_PAY_UPDATE_SHIPPING_METHODS_WHEN_CHANGING_LINE_ITEMS)
    auto modifierException = firstApplicableModifier();
    if (modifierException.hasException())
        return modifierException.releaseException();
    if (auto modifierData = modifierException.releaseReturnValue()) {
        auto applePayModifier = WTFMove(std::get<1>(*modifierData));

        shippingOptions.appendVector(WTFMove(applePayModifier.additionalShippingMethods));
    }
#endif

    return WTFMove(shippingOptions);
}

ExceptionOr<std::tuple<ApplePayLineItem, Vector<ApplePayLineItem>>> ApplePayPaymentHandler::computeTotalAndLineItems() const
{
    auto& details = m_paymentRequest->paymentDetails();
    auto& currency = details.total.amount.currency;

    auto convertedTotal = convertAndValidate(details.total, currency);
    if (convertedTotal.hasException())
        return convertedTotal.releaseException();
    auto total = convertedTotal.releaseReturnValue();

    Vector<ApplePayLineItem> lineItems;
    if (details.displayItems) {
        auto convertedLineItems = convertAndValidate(*details.displayItems, currency);
        if (convertedLineItems.hasException())
            return convertedLineItems.releaseException();
        lineItems = convertedLineItems.releaseReturnValue();
    }

    auto modifierException = firstApplicableModifier();
    if (modifierException.hasException())
        return modifierException.releaseException();
    if (auto modifierData = modifierException.releaseReturnValue()) {
        auto& [modifier, applePayModifier] = *modifierData;

        if (modifier.total) {
            auto totalOverride = convertAndValidate(*modifier.total, currency);
            if (totalOverride.hasException())
                return totalOverride.releaseException();
            total = totalOverride.releaseReturnValue();
        }

        auto additionalDisplayItems = convertAndValidate(modifier.additionalDisplayItems, currency);
        if (additionalDisplayItems.hasException())
            return additionalDisplayItems.releaseException();
        lineItems.appendVector(additionalDisplayItems.releaseReturnValue());

        if (applePayModifier.total)
            total = *applePayModifier.total;

        lineItems.appendVector(applePayModifier.additionalLineItems);
    }

    return {{ WTFMove(total), WTFMove(lineItems) }};
}

static inline void appendShippingContactInvalidError(String&& message, std::optional<ApplePayErrorContactField> contactField, Vector<RefPtr<ApplePayError>>& errors)
{
    if (!message.isNull())
        errors.append(ApplePayError::create(ApplePayErrorCode::ShippingContactInvalid, WTFMove(contactField), WTFMove(message)));
}

Vector<RefPtr<ApplePayError>> ApplePayPaymentHandler::computeErrors(String&& error, AddressErrors&& addressErrors, PayerErrorFields&& payerErrors, JSC::JSObject* paymentMethodErrors) const
{
    Vector<RefPtr<ApplePayError>> errors;

    auto& details = m_paymentRequest->paymentDetails();

    if (!details.shippingOptions || details.shippingOptions->isEmpty())
        computeAddressErrors(WTFMove(error), WTFMove(addressErrors), errors);

    computePayerErrors(WTFMove(payerErrors), errors);

    auto scope = DECLARE_CATCH_SCOPE(scriptExecutionContext()->vm());
    auto exception = computePaymentMethodErrors(paymentMethodErrors, errors);
    if (exception.hasException()) {
        ASSERT(scope.exception());
        scope.clearException();
    }

    return errors;
}

Vector<RefPtr<ApplePayError>> ApplePayPaymentHandler::computeErrors(JSC::JSObject* paymentMethodErrors) const
{
    Vector<RefPtr<ApplePayError>> errors;

    auto scope = DECLARE_CATCH_SCOPE(scriptExecutionContext()->vm());
    auto exception = computePaymentMethodErrors(paymentMethodErrors, errors);
    if (exception.hasException()) {
        ASSERT(scope.exception());
        scope.clearException();
    }

    return errors;
}

void ApplePayPaymentHandler::computeAddressErrors(String&& error, AddressErrors&& addressErrors, Vector<RefPtr<ApplePayError>>& errors) const
{
    if (!m_paymentRequest->paymentOptions().requestShipping)
        return;

    appendShippingContactInvalidError(WTFMove(error), std::nullopt, errors);
    appendShippingContactInvalidError(WTFMove(addressErrors.addressLine), ApplePayErrorContactField::AddressLines, errors);
    appendShippingContactInvalidError(WTFMove(addressErrors.city), ApplePayErrorContactField::Locality, errors);
    appendShippingContactInvalidError(WTFMove(addressErrors.country), ApplePayErrorContactField::Country, errors);
    appendShippingContactInvalidError(WTFMove(addressErrors.dependentLocality), ApplePayErrorContactField::SubLocality, errors);
    appendShippingContactInvalidError(WTFMove(addressErrors.phone), ApplePayErrorContactField::PhoneNumber, errors);
    appendShippingContactInvalidError(WTFMove(addressErrors.postalCode), ApplePayErrorContactField::PostalCode, errors);
    appendShippingContactInvalidError(WTFMove(addressErrors.recipient), ApplePayErrorContactField::Name, errors);
    appendShippingContactInvalidError(WTFMove(addressErrors.region), ApplePayErrorContactField::AdministrativeArea, errors);
}

void ApplePayPaymentHandler::computePayerErrors(PayerErrorFields&& payerErrors, Vector<RefPtr<ApplePayError>>& errors) const
{
    auto& options = m_paymentRequest->paymentOptions();

    if (options.requestPayerName)
        appendShippingContactInvalidError(WTFMove(payerErrors.name), ApplePayErrorContactField::Name, errors);

    if (options.requestPayerEmail)
        appendShippingContactInvalidError(WTFMove(payerErrors.email), ApplePayErrorContactField::EmailAddress, errors);

    if (options.requestPayerPhone)
        appendShippingContactInvalidError(WTFMove(payerErrors.phone), ApplePayErrorContactField::PhoneNumber, errors);
}

ExceptionOr<void> ApplePayPaymentHandler::computePaymentMethodErrors(JSC::JSObject* paymentMethodErrors, Vector<RefPtr<ApplePayError>>& errors) const
{
    if (!paymentMethodErrors)
        return { };

    auto& context = *scriptExecutionContext();
    auto throwScope = DECLARE_THROW_SCOPE(context.vm());
    auto applePayErrors = convert<IDLSequence<IDLInterface<ApplePayError>>>(*context.globalObject(), paymentMethodErrors);
    if (throwScope.exception())
        return Exception { ExistingExceptionError };

    for (auto&& applePayError : WTFMove(applePayErrors)) {
        if (applePayError)
            errors.append(WTFMove(applePayError));
    }

    return { };
}

static ExceptionOr<void> validate(const ApplePayModifier& applePayModifier)
{
#if ENABLE(APPLE_PAY_RECURRING_PAYMENTS)
    if (auto& recurringPaymentRequest = applePayModifier.recurringPaymentRequest) {
        auto& regularBilling = recurringPaymentRequest->regularBilling;
        if (regularBilling.paymentTiming != ApplePayPaymentTiming::Recurring)
            return Exception(TypeError, "'regularBilling' must be a 'recurring' line item."_s);
        if (!regularBilling.label)
            return Exception(TypeError, "Missing label for 'regularBilling'."_s);
        if (!isValidDecimalMonetaryValue(regularBilling.amount) && regularBilling.type != ApplePayLineItem::Type::Pending)
            return Exception(TypeError, makeString('"', regularBilling.amount, "\" is not a valid amount."));

        if (auto& trialBilling = recurringPaymentRequest->trialBilling) {
            if (trialBilling->paymentTiming != ApplePayPaymentTiming::Recurring)
                return Exception(TypeError, "'trialBilling' must be a 'recurring' line item."_s);
            if (!trialBilling->label)
                return Exception(TypeError, "Missing label for 'trialBilling'."_s);
            if (!isValidDecimalMonetaryValue(trialBilling->amount) && trialBilling->type != ApplePayLineItem::Type::Pending)
                return Exception(TypeError, makeString('"', trialBilling->amount, "\" is not a valid amount."));
        }

        if (auto& managementURL = recurringPaymentRequest->managementURL; !URL { managementURL }.isValid())
            return Exception(TypeError, makeString('"', managementURL, "\" is not a valid URL."));

        if (auto& tokenNotificationURL = recurringPaymentRequest->tokenNotificationURL; !tokenNotificationURL.isNull() && !URL { tokenNotificationURL }.isValid())
            return Exception(TypeError, makeString('"', tokenNotificationURL, "\" is not a valid URL."));
    }
#endif

#if ENABLE(APPLE_PAY_AUTOMATIC_RELOAD_PAYMENTS)
    if (auto& automaticReloadPaymentRequest = applePayModifier.automaticReloadPaymentRequest) {
        auto& automaticReloadBilling = automaticReloadPaymentRequest->automaticReloadBilling;
        if (automaticReloadBilling.paymentTiming != ApplePayPaymentTiming::AutomaticReload)
            return Exception(TypeError, "'automaticReloadBilling' must be an 'automaticReload' line item."_s);
        if (!automaticReloadBilling.label)
            return Exception(TypeError, "Missing label for 'automaticReloadBilling'."_s);
        if (!isValidDecimalMonetaryValue(automaticReloadBilling.amount) && automaticReloadBilling.type != ApplePayLineItem::Type::Pending)
            return Exception(TypeError, makeString('"', automaticReloadBilling.amount, "\" is not a valid amount."));
        if (!isValidDecimalMonetaryValue(automaticReloadBilling.automaticReloadPaymentThresholdAmount))
            return Exception(TypeError, makeString('"', automaticReloadBilling.automaticReloadPaymentThresholdAmount, "\" is not a valid automaticReloadPaymentThresholdAmount."));

        if (auto& managementURL = automaticReloadPaymentRequest->managementURL; !URL { managementURL }.isValid())
            return Exception(TypeError, makeString('"', managementURL, "\" is not a valid URL."));

        if (auto& tokenNotificationURL = automaticReloadPaymentRequest->tokenNotificationURL; !tokenNotificationURL.isNull() && !URL { tokenNotificationURL }.isValid())
            return Exception(TypeError, makeString('"', tokenNotificationURL, "\" is not a valid URL."));
    }
#endif

#if ENABLE(APPLE_PAY_MULTI_MERCHANT_PAYMENTS)
    if (auto& multiTokenContexts = applePayModifier.multiTokenContexts) {
        for (auto& tokenContext : *multiTokenContexts) {
            if (!isValidDecimalMonetaryValue(tokenContext.amount))
                return Exception(TypeError, makeString('"', tokenContext.amount, "\" is not a valid amount."));
        }
    }
#endif

    UNUSED_PARAM(applePayModifier);
    return { };
}

ExceptionOr<std::optional<std::tuple<PaymentDetailsModifier, ApplePayModifier>>> ApplePayPaymentHandler::firstApplicableModifier() const
{
    auto& details = m_paymentRequest->paymentDetails();
    if (!details.modifiers)
        return { std::nullopt };

    auto& lexicalGlobalObject = *document().globalObject();

    auto& serializedModifierData = m_paymentRequest->serializedModifierData();
    ASSERT(details.modifiers->size() == serializedModifierData.size());
    for (size_t i = 0; i < details.modifiers->size(); ++i) {
        auto& modifier = details.modifiers->at(i);

        auto convertedIdentifier = convertAndValidatePaymentMethodIdentifier(modifier.supportedMethods);
        if (!convertedIdentifier || !handlesIdentifier(*convertedIdentifier))
            continue;

        if (serializedModifierData[i].isEmpty())
            continue;

        auto scope = DECLARE_THROW_SCOPE(lexicalGlobalObject.vm());
        JSC::JSValue data;
        {
            JSC::JSLockHolder lock(&lexicalGlobalObject);
            data = JSONParse(&lexicalGlobalObject, serializedModifierData[i]);
            if (scope.exception())
                return Exception(ExistingExceptionError);
        }

        auto applePayModifier = convertDictionary<ApplePayModifier>(lexicalGlobalObject, WTFMove(data));
        if (scope.exception())
            return Exception(ExistingExceptionError);

        auto validateApplePayModifierResult = validate(applePayModifier);
        if (validateApplePayModifierResult.hasException())
            return validateApplePayModifierResult.releaseException();

        if (applePayModifier.paymentMethodType && *applePayModifier.paymentMethodType != m_selectedPaymentMethodType)
            continue;

        return { { { modifier, WTFMove(applePayModifier) } } };
    }

    return { std::nullopt };
}

ExceptionOr<void> ApplePayPaymentHandler::detailsUpdated(PaymentRequest::UpdateReason reason, String&& error, AddressErrors&& addressErrors, PayerErrorFields&& payerErrors, JSC::JSObject* paymentMethodErrors)
{
    using Reason = PaymentRequest::UpdateReason;
    switch (reason) {
    case Reason::ShowDetailsResolved:
        return { };
    case Reason::ShippingAddressChanged:
        return shippingAddressUpdated(computeErrors(WTFMove(error), WTFMove(addressErrors), WTFMove(payerErrors), paymentMethodErrors));
    case Reason::ShippingOptionChanged:
        return shippingOptionUpdated();
    case Reason::PaymentMethodChanged:
        return paymentMethodUpdated(computeErrors(WTFMove(error), WTFMove(addressErrors), WTFMove(payerErrors), paymentMethodErrors));
    }

    ASSERT_NOT_REACHED();
    return { };
}

ExceptionOr<void> ApplePayPaymentHandler::merchantValidationCompleted(JSC::JSValue&& merchantSessionValue)
{
    if (!paymentCoordinator().hasActiveSession())
        return Exception { InvalidStateError };

    if (!merchantSessionValue.isObject())
        return Exception { TypeError };

    String errorMessage;
    auto merchantSession = PaymentMerchantSession::fromJS(*document().globalObject(), asObject(merchantSessionValue), errorMessage);
    if (!merchantSession)
        return Exception { TypeError, WTFMove(errorMessage) };

    paymentCoordinator().completeMerchantValidation(*merchantSession);
    return { };
}

ExceptionOr<void> ApplePayPaymentHandler::shippingAddressUpdated(Vector<RefPtr<ApplePayError>>&& errors)
{
    ASSERT(m_updateState == UpdateState::ShippingAddress);
    m_updateState = UpdateState::None;

    ApplePayShippingContactUpdate update;
    update.errors = WTFMove(errors);

    auto newShippingMethods = computeShippingMethods();
    if (newShippingMethods.hasException())
        return newShippingMethods.releaseException();
    update.newShippingMethods = newShippingMethods.releaseReturnValue();

    auto newTotalAndLineItems = computeTotalAndLineItems();
    if (newTotalAndLineItems.hasException())
        return newTotalAndLineItems.releaseException();
    std::tie(update.newTotal, update.newLineItems) = newTotalAndLineItems.releaseReturnValue();

    auto modifierException = firstApplicableModifier();
    if (modifierException.hasException())
        return modifierException.releaseException();
    if (auto modifierData = modifierException.releaseReturnValue()) {
        auto applePayModifier = WTFMove(std::get<1>(*modifierData));
        UNUSED_VARIABLE(applePayModifier);

#if ENABLE(APPLE_PAY_RECURRING_PAYMENTS)
        update.newRecurringPaymentRequest = WTFMove(applePayModifier.recurringPaymentRequest);
#endif

#if ENABLE(APPLE_PAY_AUTOMATIC_RELOAD_PAYMENTS)
        update.newAutomaticReloadPaymentRequest = WTFMove(applePayModifier.automaticReloadPaymentRequest);
#endif

#if ENABLE(APPLE_PAY_MULTI_MERCHANT_PAYMENTS)
        update.newMultiTokenContexts = WTFMove(applePayModifier.multiTokenContexts);
#endif
    }

    paymentCoordinator().completeShippingContactSelection(WTFMove(update));
    return { };
}

ExceptionOr<void> ApplePayPaymentHandler::shippingOptionUpdated()
{
    ASSERT(m_updateState == UpdateState::ShippingOption);
    m_updateState = UpdateState::None;

    ApplePayShippingMethodUpdate update;

#if ENABLE(APPLE_PAY_UPDATE_SHIPPING_METHODS_WHEN_CHANGING_LINE_ITEMS)
    auto newShippingMethods = computeShippingMethods();
    if (newShippingMethods.hasException())
        return newShippingMethods.releaseException();
    update.newShippingMethods = newShippingMethods.releaseReturnValue();
#endif

    auto newTotalAndLineItems = computeTotalAndLineItems();
    if (newTotalAndLineItems.hasException())
        return newTotalAndLineItems.releaseException();
    std::tie(update.newTotal, update.newLineItems) = newTotalAndLineItems.releaseReturnValue();

    auto modifierException = firstApplicableModifier();
    if (modifierException.hasException())
        return modifierException.releaseException();
    if (auto modifierData = modifierException.releaseReturnValue()) {
        auto applePayModifier = WTFMove(std::get<1>(*modifierData));
        UNUSED_VARIABLE(applePayModifier);

#if ENABLE(APPLE_PAY_RECURRING_PAYMENTS)
        update.newRecurringPaymentRequest = WTFMove(applePayModifier.recurringPaymentRequest);
#endif

#if ENABLE(APPLE_PAY_AUTOMATIC_RELOAD_PAYMENTS)
        update.newAutomaticReloadPaymentRequest = WTFMove(applePayModifier.automaticReloadPaymentRequest);
#endif

#if ENABLE(APPLE_PAY_MULTI_MERCHANT_PAYMENTS)
        update.newMultiTokenContexts = WTFMove(applePayModifier.multiTokenContexts);
#endif
    }

    paymentCoordinator().completeShippingMethodSelection(WTFMove(update));
    return { };
}

ExceptionOr<void> ApplePayPaymentHandler::paymentMethodUpdated(Vector<RefPtr<ApplePayError>>&& errors)
{
#if ENABLE(APPLE_PAY_COUPON_CODE)
    if (m_updateState == UpdateState::CouponCode) {
        m_updateState = UpdateState::None;

        ApplePayCouponCodeUpdate update;
        update.errors = WTFMove(errors);

        auto newShippingMethods = computeShippingMethods();
        if (newShippingMethods.hasException())
            return newShippingMethods.releaseException();
        update.newShippingMethods = newShippingMethods.releaseReturnValue();

        auto newTotalAndLineItems = computeTotalAndLineItems();
        if (newTotalAndLineItems.hasException())
            return newTotalAndLineItems.releaseException();
        std::tie(update.newTotal, update.newLineItems) = newTotalAndLineItems.releaseReturnValue();

        auto modifierException = firstApplicableModifier();
        if (modifierException.hasException())
            return modifierException.releaseException();
        if (auto modifierData = modifierException.releaseReturnValue()) {
            auto applePayModifier = WTFMove(std::get<1>(*modifierData));
            UNUSED_VARIABLE(applePayModifier);

#if ENABLE(APPLE_PAY_RECURRING_PAYMENTS)
            update.newRecurringPaymentRequest = WTFMove(applePayModifier.recurringPaymentRequest);
#endif

#if ENABLE(APPLE_PAY_AUTOMATIC_RELOAD_PAYMENTS)
            update.newAutomaticReloadPaymentRequest = WTFMove(applePayModifier.automaticReloadPaymentRequest);
#endif

#if ENABLE(APPLE_PAY_MULTI_MERCHANT_PAYMENTS)
            update.newMultiTokenContexts = WTFMove(applePayModifier.multiTokenContexts);
#endif
        }

        paymentCoordinator().completeCouponCodeChange(WTFMove(update));
        return { };
    }
#endif // ENABLE(APPLE_PAY_COUPON_CODE)

    ASSERT(m_updateState == UpdateState::PaymentMethod);
    m_updateState = UpdateState::None;

    ApplePayPaymentMethodUpdate update;

#if ENABLE(APPLE_PAY_UPDATE_SHIPPING_METHODS_WHEN_CHANGING_LINE_ITEMS)
    update.errors = WTFMove(errors);

    auto newShippingMethods = computeShippingMethods();
    if (newShippingMethods.hasException())
        return newShippingMethods.releaseException();
    update.newShippingMethods = newShippingMethods.releaseReturnValue();
#else
    UNUSED_PARAM(errors);
#endif

    auto newTotalAndLineItems = computeTotalAndLineItems();
    if (newTotalAndLineItems.hasException())
        return newTotalAndLineItems.releaseException();
    std::tie(update.newTotal, update.newLineItems) = newTotalAndLineItems.releaseReturnValue();

    auto modifierException = firstApplicableModifier();
    if (modifierException.hasException())
        return modifierException.releaseException();
    if (auto modifierData = modifierException.releaseReturnValue()) {
        auto applePayModifier = WTFMove(std::get<1>(*modifierData));
        UNUSED_VARIABLE(applePayModifier);

#if ENABLE(APPLE_PAY_RECURRING_PAYMENTS)
        update.newRecurringPaymentRequest = WTFMove(applePayModifier.recurringPaymentRequest);
#endif

#if ENABLE(APPLE_PAY_AUTOMATIC_RELOAD_PAYMENTS)
        update.newAutomaticReloadPaymentRequest = WTFMove(applePayModifier.automaticReloadPaymentRequest);
#endif

#if ENABLE(APPLE_PAY_MULTI_MERCHANT_PAYMENTS)
        update.newMultiTokenContexts = WTFMove(applePayModifier.multiTokenContexts);
#endif
    }

    paymentCoordinator().completePaymentMethodSelection(WTFMove(update));
    return { };
}

#if ENABLE(APPLE_PAY_PAYMENT_ORDER_DETAILS)

static ExceptionOr<ApplePayPaymentOrderDetails> convertAndValidate(ApplePayPaymentOrderDetails&& orderDetails)
{
    if (auto& webServiceURL = orderDetails.webServiceURL; !URL { webServiceURL }.isValid())
        return Exception(TypeError, makeString('"', webServiceURL, "\" is not a valid URL."));

    return WTFMove(orderDetails);
}

#endif // ENABLE(APPLE_PAY_PAYMENT_ORDER_DETAILS)

static ExceptionOr<ApplePayPaymentCompleteDetails> convertAndValidate(ApplePayPaymentCompleteDetails&& details)
{
#if ENABLE(APPLE_PAY_PAYMENT_ORDER_DETAILS)
    if (auto orderDetails = WTFMove(details.orderDetails)) {
        auto convertedOrderDetails = convertAndValidate(WTFMove(*orderDetails));
        if (convertedOrderDetails.hasException())
            return convertedOrderDetails.releaseException();
        details.orderDetails = convertedOrderDetails.releaseReturnValue();
    }
#endif

    return WTFMove(details);
}

ExceptionOr<void> ApplePayPaymentHandler::complete(Document& document, std::optional<PaymentComplete>&& result, String&& serializedData)
{
    ApplePayPaymentAuthorizationResult authorizationResult;

    switch (result.value_or(PaymentComplete::Success)) {
    case PaymentComplete::Fail:
    case PaymentComplete::Unknown:
        authorizationResult.status = ApplePayPaymentAuthorizationResult::Failure;
        break;

    case PaymentComplete::Success:
        authorizationResult.status = ApplePayPaymentAuthorizationResult::Success;
        break;
    }

    if (!serializedData.isEmpty()) {
        auto throwScope = DECLARE_THROW_SCOPE(document.vm());

        auto parsedData = JSONParse(document.globalObject(), WTFMove(serializedData));
        if (throwScope.exception())
            return Exception { ExistingExceptionError };

        auto details = convertDictionary<ApplePayPaymentCompleteDetails>(*document.globalObject(), WTFMove(parsedData));
        if (throwScope.exception())
            return Exception { ExistingExceptionError };

        auto convertedDetails = convertAndValidate(WTFMove(details));
        if (convertedDetails.hasException())
            return convertedDetails.releaseException();

        details = convertedDetails.releaseReturnValue();

#if ENABLE(APPLE_PAY_PAYMENT_ORDER_DETAILS)
        authorizationResult.orderDetails = details.orderDetails;
#endif
    }

    ASSERT(authorizationResult.isFinalState());
    paymentCoordinator().completePaymentSession(WTFMove(authorizationResult));
    return { };
}

ExceptionOr<void> ApplePayPaymentHandler::retry(PaymentValidationErrors&& validationErrors)
{
    Vector<RefPtr<ApplePayError>> errors;

    computeAddressErrors(WTFMove(validationErrors.error), WTFMove(validationErrors.shippingAddress), errors);
    computePayerErrors(WTFMove(validationErrors.payer), errors);

    auto exception = computePaymentMethodErrors(validationErrors.paymentMethod.get(), errors);
    if (exception.hasException())
        return exception.releaseException();

    // Ensure there is always at least one error to avoid having a final result.
    if (errors.isEmpty())
        errors.append(ApplePayError::create(ApplePayErrorCode::Unknown, std::nullopt, nullString()));

    ApplePayPaymentAuthorizationResult authorizationResult { ApplePayPaymentAuthorizationResult::Failure, WTFMove(errors) };
    ASSERT(!authorizationResult.isFinalState());
    paymentCoordinator().completePaymentSession(WTFMove(authorizationResult));
    return { };
}

unsigned ApplePayPaymentHandler::version() const
{
    return m_applePayRequest ? m_applePayRequest->version : 0;
}

void ApplePayPaymentHandler::validateMerchant(URL&& validationURL)
{
    if (validationURL.isValid())
        m_paymentRequest->dispatchEvent(MerchantValidationEvent::create(eventNames().merchantvalidationEvent, std::get<URL>(m_identifier).string(), WTFMove(validationURL)).get());
}

static Ref<PaymentAddress> convert(const ApplePayPaymentContact& contact)
{
    return PaymentAddress::create(contact.countryCode, valueOrDefault(contact.addressLines), contact.administrativeArea, contact.locality, contact.subLocality, contact.postalCode, String(), String(), contact.localizedName, contact.phoneNumber);
}

template<typename T>
static JSC::Strong<JSC::JSObject> toJSDictionary(JSC::JSGlobalObject& lexicalGlobalObject, const T& value)
{
    JSC::JSLockHolder lock { &lexicalGlobalObject };
    return { lexicalGlobalObject.vm(), asObject(toJS<IDLDictionary<T>>(lexicalGlobalObject, *JSC::jsCast<JSDOMGlobalObject*>(&lexicalGlobalObject), value)) };
}

void ApplePayPaymentHandler::didAuthorizePayment(const Payment& payment)
{
    ASSERT(m_updateState == UpdateState::None);

    auto applePayPayment = payment.toApplePayPayment(version());
    auto shippingContact = valueOrDefault(applePayPayment.shippingContact);
    auto detailsFunction = [applePayPayment = WTFMove(applePayPayment)](JSC::JSGlobalObject& lexicalGlobalObject) {
        return toJSDictionary(lexicalGlobalObject, applePayPayment);
    };

    m_paymentRequest->accept(std::get<URL>(m_identifier).string(), WTFMove(detailsFunction), convert(shippingContact), shippingContact.localizedName, shippingContact.emailAddress, shippingContact.phoneNumber);
}

void ApplePayPaymentHandler::didSelectShippingMethod(const ApplePayShippingMethod& shippingMethod)
{
    ASSERT(m_updateState == UpdateState::None);
    m_updateState = UpdateState::ShippingOption;

    m_paymentRequest->shippingOptionChanged(shippingMethod.identifier);
}

void ApplePayPaymentHandler::didSelectShippingContact(const PaymentContact& shippingContact)
{
    ASSERT(m_updateState == UpdateState::None);
    m_updateState = UpdateState::ShippingAddress;

    m_paymentRequest->shippingAddressChanged(convert(shippingContact.toApplePayPaymentContact(version())));
}

void ApplePayPaymentHandler::didSelectPaymentMethod(const PaymentMethod& paymentMethod)
{
    ASSERT(m_updateState == UpdateState::None);
    m_updateState = UpdateState::PaymentMethod;

    auto applePayPaymentMethod = paymentMethod.toApplePayPaymentMethod();
    m_selectedPaymentMethodType = applePayPaymentMethod.type;
    m_paymentRequest->paymentMethodChanged(std::get<URL>(m_identifier).string(), [applePayPaymentMethod = WTFMove(applePayPaymentMethod)](JSC::JSGlobalObject& lexicalGlobalObject) {
        return toJSDictionary(lexicalGlobalObject, applePayPaymentMethod);
    });
}

#if ENABLE(APPLE_PAY_COUPON_CODE)

void ApplePayPaymentHandler::didChangeCouponCode(String&& couponCode)
{
    ASSERT(m_updateState == UpdateState::None);
    m_updateState = UpdateState::CouponCode;

    ApplePayCouponCodeDetails applePayCouponCodeDetails { WTFMove(couponCode) };
    m_paymentRequest->paymentMethodChanged(std::get<URL>(m_identifier).string(), [applePayCouponCodeDetails = WTFMove(applePayCouponCodeDetails)] (JSC::JSGlobalObject& lexicalGlobalObject) {
        return toJSDictionary(lexicalGlobalObject, applePayCouponCodeDetails);
    });
}

#endif // ENABLE(APPLE_PAY_COUPON_CODE)

void ApplePayPaymentHandler::didCancelPaymentSession(PaymentSessionError&&)
{
    m_paymentRequest->cancel();
}

} // namespace WebCore

#endif // ENABLE(APPLE_PAY) && ENABLE(PAYMENT_REQUEST)
