blob: 093b86fa67c1f5057cedd838420edee7d5e7f41d [file] [log] [blame]
/*
* Copyright (C) 2015-2017 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 "ApplePaySession.h"
#if ENABLE(APPLE_PAY)
#include "ApplePayLineItem.h"
#include "ApplePayPaymentAuthorizationResult.h"
#include "ApplePayPaymentAuthorizedEvent.h"
#include "ApplePayPaymentMethodSelectedEvent.h"
#include "ApplePayPaymentMethodUpdate.h"
#include "ApplePayPaymentRequest.h"
#include "ApplePayShippingContactSelectedEvent.h"
#include "ApplePayShippingContactUpdate.h"
#include "ApplePayShippingMethod.h"
#include "ApplePayShippingMethodSelectedEvent.h"
#include "ApplePayShippingMethodUpdate.h"
#include "ApplePayValidateMerchantEvent.h"
#include "DOMWindow.h"
#include "Document.h"
#include "DocumentLoader.h"
#include "EventNames.h"
#include "Frame.h"
#include "JSDOMPromiseDeferred.h"
#include "LinkIconCollector.h"
#include "LinkIconType.h"
#include "Page.h"
#include "PageConsoleClient.h"
#include "PaymentAuthorizationStatus.h"
#include "PaymentContact.h"
#include "PaymentCoordinator.h"
#include "PaymentMerchantSession.h"
#include "PaymentMethod.h"
#include "PaymentRequestValidator.h"
#include "SecurityOrigin.h"
#include "Settings.h"
#include "UserGestureIndicator.h"
namespace WebCore {
// The amount follows the regular expression -?[0-9]+(\.[0-9][0-9])?.
static bool validateAmount(const String& amountString)
{
enum class State {
Start,
Sign,
Digit,
Dot,
DotDigit,
End,
};
State state = State::Start;
for (unsigned i = 0; i < amountString.length(); ++i) {
UChar c = amountString[i];
switch (state) {
case State::Start:
if (c == '-') {
state = State::Sign;
break;
}
if (!isASCIIDigit(c))
return false;
state = State::Digit;
break;
case State::Sign:
if (!isASCIIDigit(c))
return false;
state = State::Digit;
break;
case State::Digit:
if (c == '.') {
state = State::Dot;
break;
}
if (!isASCIIDigit(c))
return false;
break;
case State::Dot:
if (!isASCIIDigit(c))
return false;
state = State::DotDigit;
break;
case State::DotDigit:
if (!isASCIIDigit(c))
return false;
state = State::End;
break;
case State::End:
return false;
}
}
return state == State::Digit || state == State::DotDigit || state == State::End;
}
static ExceptionOr<ApplePaySessionPaymentRequest::LineItem> convertAndValidateTotal(ApplePayLineItem&& lineItem)
{
if (!validateAmount(lineItem.amount))
return Exception { TypeError, makeString("\"" + lineItem.amount, "\" is not a valid amount.") };
ApplePaySessionPaymentRequest::LineItem result;
result.amount = lineItem.amount;
result.type = lineItem.type;
result.label = lineItem.label;
return WTFMove(result);
}
static ExceptionOr<ApplePaySessionPaymentRequest::LineItem> convertAndValidate(ApplePayLineItem&& lineItem)
{
ApplePaySessionPaymentRequest::LineItem result;
// It is OK for pending types to not have an amount.
if (lineItem.type != ApplePaySessionPaymentRequest::LineItem::Type::Pending) {
if (!validateAmount(lineItem.amount))
return Exception { TypeError, makeString("\"" + lineItem.amount, "\" is not a valid amount.") };
result.amount = lineItem.amount;
}
result.type = lineItem.type;
result.label = lineItem.label;
return WTFMove(result);
}
static ExceptionOr<Vector<ApplePaySessionPaymentRequest::LineItem>> convertAndValidate(std::optional<Vector<ApplePayLineItem>>&& lineItems)
{
Vector<ApplePaySessionPaymentRequest::LineItem> result;
if (!lineItems)
return WTFMove(result);
result.reserveInitialCapacity(lineItems->size());
for (auto lineItem : lineItems.value()) {
auto convertedLineItem = convertAndValidate(WTFMove(lineItem));
if (convertedLineItem.hasException())
return convertedLineItem.releaseException();
result.uncheckedAppend(convertedLineItem.releaseReturnValue());
}
return WTFMove(result);
}
static ExceptionOr<ApplePaySessionPaymentRequest::ShippingMethod> convertAndValidate(ApplePayShippingMethod&& shippingMethod)
{
if (!validateAmount(shippingMethod.amount))
return Exception { TypeError, makeString("\"" + shippingMethod.amount, "\" is not a valid amount.") };
ApplePaySessionPaymentRequest::ShippingMethod result;
result.amount = shippingMethod.amount;
result.label = shippingMethod.label;
result.detail = shippingMethod.detail;
result.identifier = shippingMethod.identifier;
return WTFMove(result);
}
static ExceptionOr<Vector<ApplePaySessionPaymentRequest::ShippingMethod>> convertAndValidate(Vector<ApplePayShippingMethod>&& shippingMethods)
{
Vector<ApplePaySessionPaymentRequest::ShippingMethod> result;
result.reserveInitialCapacity(shippingMethods.size());
for (auto& shippingMethod : shippingMethods) {
auto convertedShippingMethod = convertAndValidate(WTFMove(shippingMethod));
if (convertedShippingMethod.hasException())
return convertedShippingMethod.releaseException();
result.uncheckedAppend(convertedShippingMethod.releaseReturnValue());
}
return WTFMove(result);
}
static ExceptionOr<ApplePaySessionPaymentRequest> convertAndValidate(unsigned version, ApplePayPaymentRequest&& paymentRequest, const PaymentCoordinator& paymentCoordinator)
{
auto convertedRequest = convertAndValidate(version, paymentRequest, paymentCoordinator);
if (convertedRequest.hasException())
return convertedRequest.releaseException();
auto result = convertedRequest.releaseReturnValue();
result.setRequester(ApplePaySessionPaymentRequest::Requester::ApplePayJS);
result.setCurrencyCode(paymentRequest.currencyCode);
auto total = convertAndValidateTotal(WTFMove(paymentRequest.total));
if (total.hasException())
return total.releaseException();
result.setTotal(total.releaseReturnValue());
auto lineItems = convertAndValidate(WTFMove(paymentRequest.lineItems));
if (lineItems.hasException())
return lineItems.releaseException();
result.setLineItems(lineItems.releaseReturnValue());
if (paymentRequest.requiredShippingContactFields) {
auto requiredShippingContactFields = convertAndValidate(version, WTFMove(*paymentRequest.requiredShippingContactFields));
if (requiredShippingContactFields.hasException())
return requiredShippingContactFields.releaseException();
result.setRequiredShippingContactFields(requiredShippingContactFields.releaseReturnValue());
}
result.setShippingType(paymentRequest.shippingType);
if (paymentRequest.shippingMethods) {
auto shippingMethods = convertAndValidate(WTFMove(*paymentRequest.shippingMethods));
if (shippingMethods.hasException())
return shippingMethods.releaseException();
result.setShippingMethods(shippingMethods.releaseReturnValue());
}
// FIXME: Merge this validation into the validation we are doing above.
auto validatedPaymentRequest = PaymentRequestValidator::validate(result);
if (validatedPaymentRequest.hasException())
return validatedPaymentRequest.releaseException();
return WTFMove(result);
}
static Vector<PaymentError> convert(const Vector<RefPtr<ApplePayError>>& errors)
{
Vector<PaymentError> convertedErrors;
for (auto& error : errors) {
PaymentError convertedError;
convertedError.code = error->code();
convertedError.message = error->message();
convertedError.contactField = error->contactField();
convertedErrors.append(convertedError);
}
return convertedErrors;
}
static ExceptionOr<PaymentAuthorizationResult> convertAndValidate(ApplePayPaymentAuthorizationResult&& result)
{
PaymentAuthorizationResult convertedResult;
switch (result.status) {
case ApplePaySession::STATUS_SUCCESS:
convertedResult.status = PaymentAuthorizationStatus::Success;
break;
case ApplePaySession::STATUS_FAILURE:
convertedResult.status = PaymentAuthorizationStatus::Failure;
break;
case ApplePaySession::STATUS_INVALID_BILLING_POSTAL_ADDRESS:
convertedResult.status = PaymentAuthorizationStatus::Failure;
convertedResult.errors.append({ PaymentError::Code::BillingContactInvalid, { }, std::nullopt });
break;
case ApplePaySession::STATUS_INVALID_SHIPPING_POSTAL_ADDRESS:
convertedResult.status = PaymentAuthorizationStatus::Failure;
convertedResult.errors.append({ PaymentError::Code::ShippingContactInvalid, { }, PaymentError::ContactField::PostalAddress });
break;
case ApplePaySession::STATUS_INVALID_SHIPPING_CONTACT:
convertedResult.status = PaymentAuthorizationStatus::Failure;
convertedResult.errors.append({ PaymentError::Code::ShippingContactInvalid, { }, std::nullopt });
break;
case ApplePaySession::STATUS_PIN_REQUIRED:
convertedResult.status = PaymentAuthorizationStatus::PINRequired;
break;
case ApplePaySession::STATUS_PIN_INCORRECT:
convertedResult.status = PaymentAuthorizationStatus::PINIncorrect;
break;
case ApplePaySession::STATUS_PIN_LOCKOUT:
convertedResult.status = PaymentAuthorizationStatus::PINLockout;
break;
default:
return Exception { InvalidAccessError };
}
convertedResult.errors.appendVector(convert(result.errors));
return WTFMove(convertedResult);
}
static ExceptionOr<PaymentMethodUpdate> convertAndValidate(ApplePayPaymentMethodUpdate&& update)
{
PaymentMethodUpdate convertedUpdate;
auto convertedNewTotal = convertAndValidateTotal(WTFMove(update.newTotal));
if (convertedNewTotal.hasException())
return convertedNewTotal.releaseException();
convertedUpdate.newTotalAndLineItems.total = convertedNewTotal.releaseReturnValue();
// FIXME: Merge this validation into the validation we are doing above.
auto validatedTotal = PaymentRequestValidator::validateTotal(convertedUpdate.newTotalAndLineItems.total);
if (validatedTotal.hasException())
return validatedTotal.releaseException();
auto convertedNewLineItems = convertAndValidate(WTFMove(update.newLineItems));
if (convertedNewLineItems.hasException())
return convertedNewLineItems.releaseException();
convertedUpdate.newTotalAndLineItems.lineItems = convertedNewLineItems.releaseReturnValue();
return WTFMove(convertedUpdate);
}
static ExceptionOr<ShippingContactUpdate> convertAndValidate(ApplePayShippingContactUpdate&& update)
{
ShippingContactUpdate convertedUpdate;
convertedUpdate.errors = convert(update.errors);
auto convertedNewShippingMethods = convertAndValidate(WTFMove(update.newShippingMethods));
if (convertedNewShippingMethods.hasException())
return convertedNewShippingMethods.releaseException();
convertedUpdate.newShippingMethods = convertedNewShippingMethods.releaseReturnValue();
auto convertedNewTotal = convertAndValidateTotal(WTFMove(update.newTotal));
if (convertedNewTotal.hasException())
return convertedNewTotal.releaseException();
convertedUpdate.newTotalAndLineItems.total = convertedNewTotal.releaseReturnValue();
// FIXME: Merge this validation into the validation we are doing above.
auto validatedTotal = PaymentRequestValidator::validateTotal(convertedUpdate.newTotalAndLineItems.total);
if (validatedTotal.hasException())
return validatedTotal.releaseException();
auto convertedNewLineItems = convertAndValidate(WTFMove(update.newLineItems));
if (convertedNewLineItems.hasException())
return convertedNewLineItems.releaseException();
convertedUpdate.newTotalAndLineItems.lineItems = convertedNewLineItems.releaseReturnValue();
return WTFMove(convertedUpdate);
}
static ExceptionOr<ShippingMethodUpdate> convertAndValidate(ApplePayShippingMethodUpdate&& update)
{
ShippingMethodUpdate convertedUpdate;
auto convertedNewTotal = convertAndValidateTotal(WTFMove(update.newTotal));
if (convertedNewTotal.hasException())
return convertedNewTotal.releaseException();
convertedUpdate.newTotalAndLineItems.total = convertedNewTotal.releaseReturnValue();
// FIXME: Merge this validation into the validation we are doing above.
auto validatedTotal = PaymentRequestValidator::validateTotal(convertedUpdate.newTotalAndLineItems.total);
if (validatedTotal.hasException())
return validatedTotal.releaseException();
auto convertedNewLineItems = convertAndValidate(WTFMove(update.newLineItems));
if (convertedNewLineItems.hasException())
return convertedNewLineItems.releaseException();
convertedUpdate.newTotalAndLineItems.lineItems = convertedNewLineItems.releaseReturnValue();
return WTFMove(convertedUpdate);
}
ExceptionOr<Ref<ApplePaySession>> ApplePaySession::create(Document& document, unsigned version, ApplePayPaymentRequest&& paymentRequest)
{
auto canCall = canCreateSession(document);
if (canCall.hasException())
return canCall.releaseException();
if (!UserGestureIndicator::processingUserGesture())
return Exception { InvalidAccessError, "Must create a new ApplePaySession from a user gesture handler." };
if (!document.page())
return Exception { InvalidAccessError, "Frame is detached" };
auto& paymentCoordinator = document.page()->paymentCoordinator();
if (!version || !paymentCoordinator.supportsVersion(version))
return Exception { InvalidAccessError, makeString("\"" + String::number(version), "\" is not a supported version.") };
auto convertedPaymentRequest = convertAndValidate(version, WTFMove(paymentRequest), paymentCoordinator);
if (convertedPaymentRequest.hasException())
return convertedPaymentRequest.releaseException();
return adoptRef(*new ApplePaySession(document, version, convertedPaymentRequest.releaseReturnValue()));
}
ApplePaySession::ApplePaySession(Document& document, unsigned version, ApplePaySessionPaymentRequest&& paymentRequest)
: ActiveDOMObject { &document }
, m_paymentRequest { WTFMove(paymentRequest) }
, m_version { version }
{
ASSERT(document.page()->paymentCoordinator().supportsVersion(version));
suspendIfNeeded();
}
ApplePaySession::~ApplePaySession() = default;
ExceptionOr<bool> ApplePaySession::supportsVersion(ScriptExecutionContext& scriptExecutionContext, unsigned version)
{
if (!version)
return Exception { InvalidAccessError };
auto& document = downcast<Document>(scriptExecutionContext);
auto canCall = canCreateSession(document);
if (canCall.hasException())
return canCall.releaseException();
auto* page = document.page();
if (!page)
return Exception { InvalidAccessError };
return page->paymentCoordinator().supportsVersion(version);
}
static bool shouldDiscloseApplePayCapability(Document& document)
{
auto* page = document.page();
if (!page || page->usesEphemeralSession())
return false;
return document.settings().applePayCapabilityDisclosureAllowed();
}
ExceptionOr<bool> ApplePaySession::canMakePayments(ScriptExecutionContext& scriptExecutionContext)
{
auto& document = downcast<Document>(scriptExecutionContext);
auto canCall = canCreateSession(document);
if (canCall.hasException())
return canCall.releaseException();
auto* page = document.page();
if (!page)
return Exception { InvalidAccessError };
return page->paymentCoordinator().canMakePayments();
}
ExceptionOr<void> ApplePaySession::canMakePaymentsWithActiveCard(ScriptExecutionContext& scriptExecutionContext, const String& merchantIdentifier, Ref<DeferredPromise>&& passedPromise)
{
auto& document = downcast<Document>(scriptExecutionContext);
auto canCall = canCreateSession(document);
if (canCall.hasException())
return canCall.releaseException();
RefPtr<DeferredPromise> promise(WTFMove(passedPromise));
if (!shouldDiscloseApplePayCapability(document)) {
auto* page = document.page();
if (!page)
return Exception { InvalidAccessError };
auto& paymentCoordinator = page->paymentCoordinator();
bool canMakePayments = paymentCoordinator.canMakePayments();
RunLoop::main().dispatch([promise, canMakePayments]() mutable {
promise->resolve<IDLBoolean>(canMakePayments);
});
return { };
}
auto* page = document.page();
if (!page)
return Exception { InvalidAccessError };
auto& paymentCoordinator = page->paymentCoordinator();
paymentCoordinator.canMakePaymentsWithActiveCard(merchantIdentifier, document.domain(), [promise](bool canMakePayments) mutable {
promise->resolve<IDLBoolean>(canMakePayments);
});
return { };
}
ExceptionOr<void> ApplePaySession::openPaymentSetup(ScriptExecutionContext& scriptExecutionContext, const String& merchantIdentifier, Ref<DeferredPromise>&& passedPromise)
{
auto& document = downcast<Document>(scriptExecutionContext);
auto canCall = canCreateSession(document);
if (canCall.hasException())
return canCall.releaseException();
if (!UserGestureIndicator::processingUserGesture())
return Exception { InvalidAccessError, "Must call ApplePaySession.openPaymentSetup from a user gesture handler." };
auto* page = document.page();
if (!page)
return Exception { InvalidAccessError };
auto& paymentCoordinator = page->paymentCoordinator();
RefPtr<DeferredPromise> promise(WTFMove(passedPromise));
paymentCoordinator.openPaymentSetup(merchantIdentifier, document.domain(), [promise](bool result) mutable {
promise->resolve<IDLBoolean>(result);
});
return { };
}
ExceptionOr<void> ApplePaySession::begin()
{
if (!canBegin())
return Exception { InvalidAccessError, "Payment session is already active." };
if (paymentCoordinator().hasActiveSession())
return Exception { InvalidAccessError, "Page already has an active payment session." };
auto& document = *downcast<Document>(scriptExecutionContext());
Vector<URL> linkIconURLs;
for (auto& icon : LinkIconCollector { document }.iconsOfTypes({ LinkIconType::TouchIcon, LinkIconType::TouchPrecomposedIcon }))
linkIconURLs.append(icon.url);
if (!paymentCoordinator().beginPaymentSession(*this, document.url(), linkIconURLs, m_paymentRequest))
return Exception { InvalidAccessError, "There is already has an active payment session." };
m_state = State::Active;
setPendingActivity(this);
return { };
}
ExceptionOr<void> ApplePaySession::abort()
{
if (!canAbort())
return Exception { InvalidAccessError };
m_state = State::Aborted;
paymentCoordinator().abortPaymentSession();
didReachFinalState();
return { };
}
ExceptionOr<void> ApplePaySession::completeMerchantValidation(JSC::ExecState& state, JSC::JSValue merchantSessionValue)
{
if (!canCompleteMerchantValidation())
return Exception { InvalidAccessError };
if (!merchantSessionValue.isObject())
return Exception { TypeError };
auto& document = *downcast<Document>(scriptExecutionContext());
auto& window = *document.domWindow();
String errorMessage;
auto merchantSession = PaymentMerchantSession::fromJS(state, asObject(merchantSessionValue), errorMessage);
if (!merchantSession) {
window.printErrorMessage(errorMessage);
return Exception { InvalidAccessError };
}
m_merchantValidationState = MerchantValidationState::ValidationComplete;
paymentCoordinator().completeMerchantValidation(*merchantSession);
return { };
}
ExceptionOr<void> ApplePaySession::completeShippingMethodSelection(ApplePayShippingMethodUpdate&& update)
{
if (!canCompleteShippingMethodSelection())
return Exception { InvalidAccessError };
auto convertedUpdate = convertAndValidate(WTFMove(update));
if (convertedUpdate.hasException())
return convertedUpdate.releaseException();
m_state = State::Active;
paymentCoordinator().completeShippingMethodSelection(convertedUpdate.releaseReturnValue());
return { };
}
ExceptionOr<void> ApplePaySession::completeShippingContactSelection(ApplePayShippingContactUpdate&& update)
{
if (!canCompleteShippingContactSelection())
return Exception { InvalidAccessError };
auto convertedUpdate = convertAndValidate(WTFMove(update));
if (convertedUpdate.hasException())
return convertedUpdate.releaseException();
m_state = State::Active;
paymentCoordinator().completeShippingContactSelection(convertedUpdate.releaseReturnValue());
return { };
}
ExceptionOr<void> ApplePaySession::completePaymentMethodSelection(ApplePayPaymentMethodUpdate&& update)
{
if (!canCompletePaymentMethodSelection())
return Exception { InvalidAccessError };
auto convertedUpdate = convertAndValidate(WTFMove(update));
if (convertedUpdate.hasException())
return convertedUpdate.releaseException();
m_state = State::Active;
paymentCoordinator().completePaymentMethodSelection(convertedUpdate.releaseReturnValue());
return { };
}
ExceptionOr<void> ApplePaySession::completePayment(ApplePayPaymentAuthorizationResult&& result)
{
if (!canCompletePayment())
return Exception { InvalidAccessError };
auto convertedResultOrException = convertAndValidate(WTFMove(result));
if (convertedResultOrException.hasException())
return convertedResultOrException.releaseException();
auto&& convertedResult = convertedResultOrException.releaseReturnValue();
bool isFinalState = isFinalStateResult(convertedResult);
paymentCoordinator().completePaymentSession(WTFMove(convertedResult));
if (!isFinalState) {
m_state = State::Active;
return { };
}
m_state = State::Completed;
unsetPendingActivity(this);
return { };
}
ExceptionOr<void> ApplePaySession::completeShippingMethodSelection(unsigned short status, ApplePayLineItem&& newTotal, Vector<ApplePayLineItem>&& newLineItems)
{
ApplePayShippingMethodUpdate update;
switch (status) {
case ApplePaySession::STATUS_SUCCESS:
break;
case ApplePaySession::STATUS_FAILURE:
case ApplePaySession::STATUS_INVALID_BILLING_POSTAL_ADDRESS:
case ApplePaySession::STATUS_INVALID_SHIPPING_POSTAL_ADDRESS:
case ApplePaySession::STATUS_INVALID_SHIPPING_CONTACT:
case ApplePaySession::STATUS_PIN_REQUIRED:
case ApplePaySession::STATUS_PIN_INCORRECT:
case ApplePaySession::STATUS_PIN_LOCKOUT:
// This is a fatal error. Cancel the request.
m_state = State::CancelRequested;
paymentCoordinator().cancelPaymentSession();
return { };
default:
return Exception { InvalidAccessError };
}
update.newTotal = WTFMove(newTotal);
update.newLineItems = WTFMove(newLineItems);
return completeShippingMethodSelection(WTFMove(update));
}
ExceptionOr<void> ApplePaySession::completeShippingContactSelection(unsigned short status, Vector<ApplePayShippingMethod>&& newShippingMethods, ApplePayLineItem&& newTotal, Vector<ApplePayLineItem>&& newLineItems)
{
ApplePayShippingContactUpdate update;
std::optional<ApplePayError::Code> errorCode;
std::optional<ApplePayError::ContactField> contactField;
switch (status) {
case ApplePaySession::STATUS_SUCCESS:
break;
case ApplePaySession::STATUS_FAILURE:
case ApplePaySession::STATUS_PIN_REQUIRED:
case ApplePaySession::STATUS_PIN_INCORRECT:
case ApplePaySession::STATUS_PIN_LOCKOUT:
errorCode = ApplePayError::Code::Unknown;
break;
case ApplePaySession::STATUS_INVALID_BILLING_POSTAL_ADDRESS:
errorCode = ApplePayError::Code::BillingContactInvalid;
break;
case ApplePaySession::STATUS_INVALID_SHIPPING_POSTAL_ADDRESS:
errorCode = ApplePayError::Code::ShippingContactInvalid;
contactField = ApplePayError::ContactField::PostalAddress;
break;
case ApplePaySession::STATUS_INVALID_SHIPPING_CONTACT:
errorCode = ApplePayError::Code::ShippingContactInvalid;
break;
default:
return Exception { InvalidAccessError };
}
if (errorCode)
update.errors = { ApplePayError::create(*errorCode, contactField, { }) };
update.newShippingMethods = WTFMove(newShippingMethods);
update.newTotal = WTFMove(newTotal);
update.newLineItems = WTFMove(newLineItems);
return completeShippingContactSelection(WTFMove(update));
}
ExceptionOr<void> ApplePaySession::completePaymentMethodSelection(ApplePayLineItem&& newTotal, Vector<ApplePayLineItem>&& newLineItems)
{
ApplePayPaymentMethodUpdate update;
update.newTotal = WTFMove(newTotal);
update.newLineItems = WTFMove(newLineItems);
return completePaymentMethodSelection(WTFMove(update));
}
ExceptionOr<void> ApplePaySession::completePayment(unsigned short status)
{
ApplePayPaymentAuthorizationResult result;
result.status = status;
return completePayment(WTFMove(result));
}
unsigned ApplePaySession::version() const
{
return m_version;
}
void ApplePaySession::validateMerchant(const URL& validationURL)
{
if (m_state == State::Aborted) {
// ApplePaySession::abort has been called.
return;
}
ASSERT(m_merchantValidationState == MerchantValidationState::Idle);
ASSERT(m_state == State::Active);
if (validationURL.isNull()) {
// Something went wrong when getting the validation URL.
// FIXME: Maybe we should send an error event here instead?
return;
}
m_merchantValidationState = MerchantValidationState::ValidatingMerchant;
auto event = ApplePayValidateMerchantEvent::create(eventNames().validatemerchantEvent, validationURL);
dispatchEvent(event.get());
}
void ApplePaySession::didAuthorizePayment(const Payment& payment)
{
ASSERT(m_state == State::Active);
m_state = State::Authorized;
auto event = ApplePayPaymentAuthorizedEvent::create(eventNames().paymentauthorizedEvent, version(), payment);
dispatchEvent(event.get());
}
void ApplePaySession::didSelectShippingMethod(const ApplePaySessionPaymentRequest::ShippingMethod& shippingMethod)
{
ASSERT(m_state == State::Active);
if (!hasEventListeners(eventNames().shippingmethodselectedEvent)) {
paymentCoordinator().completeShippingMethodSelection({ });
return;
}
m_state = State::ShippingMethodSelected;
auto event = ApplePayShippingMethodSelectedEvent::create(eventNames().shippingmethodselectedEvent, shippingMethod);
dispatchEvent(event.get());
}
void ApplePaySession::didSelectShippingContact(const PaymentContact& shippingContact)
{
ASSERT(m_state == State::Active);
if (!hasEventListeners(eventNames().shippingcontactselectedEvent)) {
paymentCoordinator().completeShippingContactSelection({ });
return;
}
m_state = State::ShippingContactSelected;
auto event = ApplePayShippingContactSelectedEvent::create(eventNames().shippingcontactselectedEvent, version(), shippingContact);
dispatchEvent(event.get());
}
void ApplePaySession::didSelectPaymentMethod(const PaymentMethod& paymentMethod)
{
ASSERT(m_state == State::Active);
if (!hasEventListeners(eventNames().paymentmethodselectedEvent)) {
paymentCoordinator().completePaymentMethodSelection({ });
return;
}
m_state = State::PaymentMethodSelected;
auto event = ApplePayPaymentMethodSelectedEvent::create(eventNames().paymentmethodselectedEvent, paymentMethod);
dispatchEvent(event.get());
}
void ApplePaySession::didCancelPaymentSession()
{
ASSERT(canCancel());
m_state = State::Canceled;
auto event = Event::create(eventNames().cancelEvent, false, false);
dispatchEvent(event.get());
didReachFinalState();
}
const char* ApplePaySession::activeDOMObjectName() const
{
return "ApplePaySession";
}
bool ApplePaySession::canSuspendForDocumentSuspension() const
{
switch (m_state) {
case State::Idle:
case State::Aborted:
case State::Completed:
case State::Canceled:
return true;
case State::Active:
case State::Authorized:
case State::ShippingMethodSelected:
case State::ShippingContactSelected:
case State::PaymentMethodSelected:
case State::CancelRequested:
return false;
}
}
void ApplePaySession::stop()
{
if (!canAbort())
return;
m_state = State::Aborted;
paymentCoordinator().abortPaymentSession();
didReachFinalState();
}
PaymentCoordinator& ApplePaySession::paymentCoordinator() const
{
return downcast<Document>(*scriptExecutionContext()).page()->paymentCoordinator();
}
bool ApplePaySession::canBegin() const
{
switch (m_state) {
case State::Idle:
return true;
case State::Active:
case State::Aborted:
case State::Authorized:
case State::Completed:
case State::Canceled:
case State::ShippingMethodSelected:
case State::ShippingContactSelected:
case State::PaymentMethodSelected:
case State::CancelRequested:
return false;
}
}
bool ApplePaySession::canAbort() const
{
switch (m_state) {
case State::Idle:
case State::Aborted:
case State::Completed:
case State::Canceled:
return false;
case State::Active:
case State::Authorized:
case State::ShippingMethodSelected:
case State::ShippingContactSelected:
case State::PaymentMethodSelected:
case State::CancelRequested:
return true;
}
}
bool ApplePaySession::canCancel() const
{
switch (m_state) {
case State::Idle:
case State::Aborted:
case State::Completed:
case State::Canceled:
return false;
case State::Active:
case State::Authorized:
case State::ShippingMethodSelected:
case State::ShippingContactSelected:
case State::PaymentMethodSelected:
case State::CancelRequested:
return true;
}
}
bool ApplePaySession::canCompleteMerchantValidation() const
{
if (m_state != State::Active)
return false;
if (m_merchantValidationState != MerchantValidationState::ValidatingMerchant)
return false;
return true;
}
bool ApplePaySession::canCompleteShippingMethodSelection() const
{
switch (m_state) {
case State::Idle:
case State::Aborted:
case State::Active:
case State::Completed:
case State::Canceled:
case State::Authorized:
case State::PaymentMethodSelected:
case State::ShippingContactSelected:
case State::CancelRequested:
return false;
case State::ShippingMethodSelected:
return true;
}
}
bool ApplePaySession::canCompleteShippingContactSelection() const
{
switch (m_state) {
case State::Idle:
case State::Aborted:
case State::Active:
case State::Completed:
case State::Canceled:
case State::Authorized:
case State::PaymentMethodSelected:
case State::ShippingMethodSelected:
case State::CancelRequested:
return false;
case State::ShippingContactSelected:
return true;
}
}
bool ApplePaySession::canCompletePaymentMethodSelection() const
{
switch (m_state) {
case State::Idle:
case State::Aborted:
case State::Active:
case State::Completed:
case State::Canceled:
case State::Authorized:
case State::ShippingMethodSelected:
case State::ShippingContactSelected:
case State::CancelRequested:
return false;
case State::PaymentMethodSelected:
return true;
}
}
bool ApplePaySession::canCompletePayment() const
{
switch (m_state) {
case State::Idle:
case State::Aborted:
case State::Active:
case State::Completed:
case State::Canceled:
case State::ShippingMethodSelected:
case State::ShippingContactSelected:
case State::PaymentMethodSelected:
case State::CancelRequested:
return false;
case State::Authorized:
return true;
}
}
bool ApplePaySession::isFinalState() const
{
switch (m_state) {
case State::Idle:
case State::Active:
case State::ShippingMethodSelected:
case State::ShippingContactSelected:
case State::PaymentMethodSelected:
case State::Authorized:
case State::CancelRequested:
return false;
case State::Completed:
case State::Aborted:
case State::Canceled:
return true;
}
}
void ApplePaySession::didReachFinalState()
{
ASSERT(isFinalState());
unsetPendingActivity(this);
}
}
#endif