blob: 6bce4f0e3d05f717cdd827d747a6f5cebfeb29db [file] [log] [blame]
/*
* 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)