diff options
Diffstat (limited to 'dom/payments/PaymentRequestService.cpp')
-rw-r--r-- | dom/payments/PaymentRequestService.cpp | 606 |
1 files changed, 606 insertions, 0 deletions
diff --git a/dom/payments/PaymentRequestService.cpp b/dom/payments/PaymentRequestService.cpp new file mode 100644 index 0000000000..3de2cba226 --- /dev/null +++ b/dom/payments/PaymentRequestService.cpp @@ -0,0 +1,606 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "BasicCardPayment.h" +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/dom/BasicCardPaymentBinding.h" +#include "mozilla/dom/PaymentRequestParent.h" +#include "nsArrayUtils.h" +#include "nsComponentManagerUtils.h" +#include "nsCOMPtr.h" +#include "nsIMutableArray.h" +#include "nsServiceManagerUtils.h" +#include "nsSimpleEnumerator.h" +#include "PaymentRequestService.h" + +namespace mozilla::dom { + +StaticRefPtr<PaymentRequestService> gPaymentService; + +namespace { + +class PaymentRequestEnumerator final : public nsSimpleEnumerator { + public: + NS_DECL_NSISIMPLEENUMERATOR + + PaymentRequestEnumerator() : mIndex(0) {} + + const nsID& DefaultInterface() override { + return NS_GET_IID(nsIPaymentRequest); + } + + private: + ~PaymentRequestEnumerator() override = default; + uint32_t mIndex; +}; + +NS_IMETHODIMP +PaymentRequestEnumerator::HasMoreElements(bool* aReturn) { + NS_ENSURE_ARG_POINTER(aReturn); + *aReturn = false; + if (NS_WARN_IF(!gPaymentService)) { + return NS_ERROR_FAILURE; + } + RefPtr<PaymentRequestService> service = gPaymentService; + *aReturn = mIndex < service->NumPayments(); + return NS_OK; +} + +NS_IMETHODIMP +PaymentRequestEnumerator::GetNext(nsISupports** aItem) { + NS_ENSURE_ARG_POINTER(aItem); + if (NS_WARN_IF(!gPaymentService)) { + return NS_ERROR_FAILURE; + } + RefPtr<payments::PaymentRequest> rowRequest = + gPaymentService->GetPaymentRequestByIndex(mIndex); + if (!rowRequest) { + return NS_ERROR_FAILURE; + } + mIndex++; + rowRequest.forget(aItem); + return NS_OK; +} + +} // end of anonymous namespace + +/* PaymentRequestService */ + +NS_IMPL_ISUPPORTS(PaymentRequestService, nsIPaymentRequestService) + +already_AddRefed<PaymentRequestService> PaymentRequestService::GetSingleton() { + MOZ_ASSERT(NS_IsMainThread()); + if (!gPaymentService) { + gPaymentService = new PaymentRequestService(); + ClearOnShutdown(&gPaymentService); + } + RefPtr<PaymentRequestService> service = gPaymentService; + return service.forget(); +} + +uint32_t PaymentRequestService::NumPayments() const { + return mRequestQueue.Length(); +} + +already_AddRefed<payments::PaymentRequest> +PaymentRequestService::GetPaymentRequestByIndex(const uint32_t aIndex) { + if (aIndex >= mRequestQueue.Length()) { + return nullptr; + } + RefPtr<payments::PaymentRequest> request = mRequestQueue[aIndex]; + MOZ_ASSERT(request); + return request.forget(); +} + +NS_IMETHODIMP +PaymentRequestService::GetPaymentRequestById(const nsAString& aRequestId, + nsIPaymentRequest** aRequest) { + NS_ENSURE_ARG_POINTER(aRequest); + *aRequest = nullptr; + RefPtr<payments::PaymentRequest> rowRequest; + nsresult rv = GetPaymentRequestById(aRequestId, getter_AddRefs(rowRequest)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + rowRequest.forget(aRequest); + return NS_OK; +} + +nsresult PaymentRequestService::GetPaymentRequestById( + const nsAString& aRequestId, payments::PaymentRequest** aRequest) { + NS_ENSURE_ARG_POINTER(aRequest); + *aRequest = nullptr; + uint32_t numRequests = mRequestQueue.Length(); + for (uint32_t index = 0; index < numRequests; ++index) { + RefPtr<payments::PaymentRequest> request = mRequestQueue[index]; + MOZ_ASSERT(request); + nsAutoString requestId; + nsresult rv = request->GetRequestId(requestId); + NS_ENSURE_SUCCESS(rv, rv); + if (requestId == aRequestId) { + request.forget(aRequest); + break; + } + } + return NS_OK; +} + +NS_IMETHODIMP +PaymentRequestService::Enumerate(nsISimpleEnumerator** aEnumerator) { + NS_ENSURE_ARG_POINTER(aEnumerator); + nsCOMPtr<nsISimpleEnumerator> enumerator = new PaymentRequestEnumerator(); + enumerator.forget(aEnumerator); + return NS_OK; +} + +NS_IMETHODIMP +PaymentRequestService::Cleanup() { + mRequestQueue.Clear(); + return NS_OK; +} + +NS_IMETHODIMP +PaymentRequestService::SetTestingUIService(nsIPaymentUIService* aUIService) { + // aUIService can be nullptr + mTestingUIService = aUIService; + return NS_OK; +} + +nsresult PaymentRequestService::LaunchUIAction(const nsAString& aRequestId, + uint32_t aActionType) { + nsCOMPtr<nsIPaymentUIService> uiService; + nsresult rv; + if (mTestingUIService) { + uiService = mTestingUIService; + } else { + uiService = do_GetService(NS_PAYMENT_UI_SERVICE_CONTRACT_ID, &rv); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + switch (aActionType) { + case IPCPaymentActionRequest::TIPCPaymentShowActionRequest: { + rv = uiService->ShowPayment(aRequestId); + break; + } + case IPCPaymentActionRequest::TIPCPaymentAbortActionRequest: { + rv = uiService->AbortPayment(aRequestId); + break; + } + case IPCPaymentActionRequest::TIPCPaymentCompleteActionRequest: { + rv = uiService->CompletePayment(aRequestId); + break; + } + case IPCPaymentActionRequest::TIPCPaymentUpdateActionRequest: { + rv = uiService->UpdatePayment(aRequestId); + break; + } + case IPCPaymentActionRequest::TIPCPaymentCloseActionRequest: { + rv = uiService->ClosePayment(aRequestId); + break; + } + default: { + return NS_ERROR_FAILURE; + } + } + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + return NS_OK; +} + +nsresult PaymentRequestService::RequestPayment( + const nsAString& aRequestId, const IPCPaymentActionRequest& aAction, + PaymentRequestParent* aIPC) { + NS_ENSURE_ARG_POINTER(aIPC); + + nsresult rv = NS_OK; + uint32_t type = aAction.type(); + + RefPtr<payments::PaymentRequest> request; + if (type != IPCPaymentActionRequest::TIPCPaymentCreateActionRequest) { + rv = GetPaymentRequestById(aRequestId, getter_AddRefs(request)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + if (!request && + type != IPCPaymentActionRequest::TIPCPaymentCloseActionRequest) { + return NS_ERROR_FAILURE; + } + if (request) { + request->SetIPC(aIPC); + } + } + + switch (type) { + case IPCPaymentActionRequest::TIPCPaymentCreateActionRequest: { + MOZ_ASSERT(!request); + const IPCPaymentCreateActionRequest& action = aAction; + nsCOMPtr<nsIMutableArray> methodData = + do_CreateInstance(NS_ARRAY_CONTRACTID); + MOZ_ASSERT(methodData); + for (IPCPaymentMethodData data : action.methodData()) { + nsCOMPtr<nsIPaymentMethodData> method; + rv = payments::PaymentMethodData::Create(data, getter_AddRefs(method)); + NS_ENSURE_SUCCESS(rv, rv); + rv = methodData->AppendElement(method); + NS_ENSURE_SUCCESS(rv, rv); + } + nsCOMPtr<nsIPaymentDetails> details; + rv = payments::PaymentDetails::Create(action.details(), + getter_AddRefs(details)); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr<nsIPaymentOptions> options; + rv = payments::PaymentOptions::Create(action.options(), + getter_AddRefs(options)); + NS_ENSURE_SUCCESS(rv, rv); + RefPtr<payments::PaymentRequest> request = new payments::PaymentRequest( + action.topOuterWindowId(), aRequestId, action.topLevelPrincipal(), + methodData, details, options, action.shippingOption()); + + if (!mRequestQueue.AppendElement(request, mozilla::fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + break; + } + case IPCPaymentActionRequest::TIPCPaymentCanMakeActionRequest: { + nsCOMPtr<nsIPaymentCanMakeActionResponse> canMakeResponse = + do_CreateInstance(NS_PAYMENT_CANMAKE_ACTION_RESPONSE_CONTRACT_ID); + MOZ_ASSERT(canMakeResponse); + rv = canMakeResponse->Init(aRequestId, CanMakePayment(aRequestId)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + rv = RespondPayment(canMakeResponse.get()); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + break; + } + case IPCPaymentActionRequest::TIPCPaymentShowActionRequest: { + const IPCPaymentShowActionRequest& action = aAction; + rv = ShowPayment(aRequestId, action.isUpdating()); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + break; + } + case IPCPaymentActionRequest::TIPCPaymentAbortActionRequest: { + MOZ_ASSERT(request); + request->SetState(payments::PaymentRequest::eInteractive); + rv = LaunchUIAction(aRequestId, type); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + break; + } + case IPCPaymentActionRequest::TIPCPaymentCompleteActionRequest: { + MOZ_ASSERT(request); + const IPCPaymentCompleteActionRequest& action = aAction; + request->SetCompleteStatus(action.completeStatus()); + rv = LaunchUIAction(aRequestId, type); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + break; + } + case IPCPaymentActionRequest::TIPCPaymentUpdateActionRequest: { + const IPCPaymentUpdateActionRequest& action = aAction; + nsCOMPtr<nsIPaymentDetails> details; + rv = payments::PaymentDetails::Create(action.details(), + getter_AddRefs(details)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + MOZ_ASSERT(request); + rv = request->UpdatePaymentDetails(details, action.shippingOption()); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + nsAutoString completeStatus; + rv = request->GetCompleteStatus(completeStatus); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + if (completeStatus.Equals(u"initial"_ns)) { + request->SetCompleteStatus(u""_ns); + } + MOZ_ASSERT(mShowingRequest && mShowingRequest == request); + rv = LaunchUIAction(aRequestId, type); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + break; + } + case IPCPaymentActionRequest::TIPCPaymentCloseActionRequest: { + rv = LaunchUIAction(aRequestId, type); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + if (mShowingRequest == request) { + mShowingRequest = nullptr; + } + mRequestQueue.RemoveElement(request); + break; + } + case IPCPaymentActionRequest::TIPCPaymentRetryActionRequest: { + const IPCPaymentRetryActionRequest& action = aAction; + MOZ_ASSERT(request); + request->UpdateErrors(action.error(), action.payerErrors(), + action.paymentMethodErrors(), + action.shippingAddressErrors()); + request->SetState(payments::PaymentRequest::eInteractive); + MOZ_ASSERT(mShowingRequest == request); + rv = LaunchUIAction( + aRequestId, IPCPaymentActionRequest::TIPCPaymentUpdateActionRequest); + break; + } + default: { + return NS_ERROR_FAILURE; + } + } + return NS_OK; +} + +NS_IMETHODIMP +PaymentRequestService::RespondPayment(nsIPaymentActionResponse* aResponse) { + NS_ENSURE_ARG_POINTER(aResponse); + nsAutoString requestId; + nsresult rv = aResponse->GetRequestId(requestId); + NS_ENSURE_SUCCESS(rv, rv); + + RefPtr<payments::PaymentRequest> request; + rv = GetPaymentRequestById(requestId, getter_AddRefs(request)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + if (!request) { + return NS_ERROR_FAILURE; + } + uint32_t type; + rv = aResponse->GetType(&type); + NS_ENSURE_SUCCESS(rv, rv); + + // PaymentRequest can only be responded when + // 1. the state is eInteractive + // 2. the state is eClosed and response type is COMPLETE_ACTION + // 3. the state is eCreated and response type is CANMAKE_ACTION + payments::PaymentRequest::eState state = request->GetState(); + bool canBeResponded = (state == payments::PaymentRequest::eInteractive) || + (state == payments::PaymentRequest::eClosed && + type == nsIPaymentActionResponse::COMPLETE_ACTION) || + (state == payments::PaymentRequest::eCreated && + type == nsIPaymentActionResponse::CANMAKE_ACTION); + if (!canBeResponded) { + return NS_ERROR_FAILURE; + } + + if (!request->GetIPC()) { + return NS_ERROR_FAILURE; + } + rv = request->GetIPC()->RespondPayment(aResponse); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + // Remove PaymentRequest from mRequestQueue while receive succeeded abort + // response or complete response + switch (type) { + case nsIPaymentActionResponse::ABORT_ACTION: { + nsCOMPtr<nsIPaymentAbortActionResponse> response = + do_QueryInterface(aResponse); + MOZ_ASSERT(response); + bool isSucceeded; + rv = response->IsSucceeded(&isSucceeded); + NS_ENSURE_SUCCESS(rv, rv); + mShowingRequest = nullptr; + if (isSucceeded) { + mRequestQueue.RemoveElement(request); + request->SetState(payments::PaymentRequest::eClosed); + } + break; + } + case nsIPaymentActionResponse::SHOW_ACTION: { + request->SetState(payments::PaymentRequest::eClosed); + nsCOMPtr<nsIPaymentShowActionResponse> response = + do_QueryInterface(aResponse); + MOZ_ASSERT(response); + uint32_t acceptStatus; + rv = response->GetAcceptStatus(&acceptStatus); + NS_ENSURE_SUCCESS(rv, rv); + if (acceptStatus != nsIPaymentActionResponse::PAYMENT_ACCEPTED) { + // Check if rejecting the showing PaymentRequest. + // If yes, set mShowingRequest as nullptr. + if (mShowingRequest == request) { + mShowingRequest = nullptr; + } + mRequestQueue.RemoveElement(request); + } + break; + } + case nsIPaymentActionResponse::COMPLETE_ACTION: { + mShowingRequest = nullptr; + mRequestQueue.RemoveElement(request); + break; + } + default: { + break; + } + } + return NS_OK; +} + +NS_IMETHODIMP +PaymentRequestService::ChangeShippingAddress(const nsAString& aRequestId, + nsIPaymentAddress* aAddress) { + RefPtr<payments::PaymentRequest> request; + nsresult rv = GetPaymentRequestById(aRequestId, getter_AddRefs(request)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + if (!request) { + return NS_ERROR_FAILURE; + } + if (request->GetState() != payments::PaymentRequest::eInteractive) { + return NS_ERROR_FAILURE; + } + if (!request->GetIPC()) { + return NS_ERROR_FAILURE; + } + rv = request->GetIPC()->ChangeShippingAddress(aRequestId, aAddress); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + return NS_OK; +} + +NS_IMETHODIMP +PaymentRequestService::ChangeShippingOption(const nsAString& aRequestId, + const nsAString& aOption) { + RefPtr<payments::PaymentRequest> request; + nsresult rv = GetPaymentRequestById(aRequestId, getter_AddRefs(request)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + if (!request) { + return NS_ERROR_FAILURE; + } + if (request->GetState() != payments::PaymentRequest::eInteractive) { + return NS_ERROR_FAILURE; + } + if (!request->GetIPC()) { + return NS_ERROR_FAILURE; + } + rv = request->GetIPC()->ChangeShippingOption(aRequestId, aOption); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + return NS_OK; +} + +NS_IMETHODIMP +PaymentRequestService::ChangePayerDetail(const nsAString& aRequestId, + const nsAString& aPayerName, + const nsAString& aPayerEmail, + const nsAString& aPayerPhone) { + RefPtr<payments::PaymentRequest> request; + nsresult rv = GetPaymentRequestById(aRequestId, getter_AddRefs(request)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + MOZ_ASSERT(request); + if (!request->GetIPC()) { + return NS_ERROR_FAILURE; + } + rv = request->GetIPC()->ChangePayerDetail(aRequestId, aPayerName, aPayerEmail, + aPayerPhone); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + return NS_OK; +} + +NS_IMETHODIMP +PaymentRequestService::ChangePaymentMethod( + const nsAString& aRequestId, const nsAString& aMethodName, + nsIMethodChangeDetails* aMethodDetails) { + RefPtr<payments::PaymentRequest> request; + nsresult rv = GetPaymentRequestById(aRequestId, getter_AddRefs(request)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + if (!request) { + return NS_ERROR_FAILURE; + } + if (request->GetState() != payments::PaymentRequest::eInteractive) { + return NS_ERROR_FAILURE; + } + if (!request->GetIPC()) { + return NS_ERROR_FAILURE; + } + rv = request->GetIPC()->ChangePaymentMethod(aRequestId, aMethodName, + aMethodDetails); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + return NS_OK; +} + +bool PaymentRequestService::CanMakePayment(const nsAString& aRequestId) { + /* + * TODO: Check third party payment app support by traversing all + * registered third party payment apps. + */ + return IsBasicCardPayment(aRequestId); +} + +nsresult PaymentRequestService::ShowPayment(const nsAString& aRequestId, + bool aIsUpdating) { + nsresult rv; + RefPtr<payments::PaymentRequest> request; + rv = GetPaymentRequestById(aRequestId, getter_AddRefs(request)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + MOZ_ASSERT(request); + request->SetState(payments::PaymentRequest::eInteractive); + if (aIsUpdating) { + request->SetCompleteStatus(u"initial"_ns); + } + + if (mShowingRequest || !CanMakePayment(aRequestId)) { + uint32_t responseStatus; + if (mShowingRequest) { + responseStatus = nsIPaymentActionResponse::PAYMENT_REJECTED; + } else { + responseStatus = nsIPaymentActionResponse::PAYMENT_NOTSUPPORTED; + } + nsCOMPtr<nsIPaymentShowActionResponse> showResponse = + do_CreateInstance(NS_PAYMENT_SHOW_ACTION_RESPONSE_CONTRACT_ID); + MOZ_ASSERT(showResponse); + rv = showResponse->Init(aRequestId, responseStatus, u""_ns, nullptr, u""_ns, + u""_ns, u""_ns); + rv = RespondPayment(showResponse.get()); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } else { + mShowingRequest = request; + rv = LaunchUIAction(aRequestId, + IPCPaymentActionRequest::TIPCPaymentShowActionRequest); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + return NS_OK; +} + +bool PaymentRequestService::IsBasicCardPayment(const nsAString& aRequestId) { + RefPtr<payments::PaymentRequest> request; + nsresult rv = GetPaymentRequestById(aRequestId, getter_AddRefs(request)); + NS_ENSURE_SUCCESS(rv, false); + nsCOMPtr<nsIArray> methods; + rv = request->GetPaymentMethods(getter_AddRefs(methods)); + NS_ENSURE_SUCCESS(rv, false); + uint32_t length; + rv = methods->GetLength(&length); + NS_ENSURE_SUCCESS(rv, false); + RefPtr<BasicCardService> service = BasicCardService::GetService(); + MOZ_ASSERT(service); + for (uint32_t index = 0; index < length; ++index) { + nsCOMPtr<nsIPaymentMethodData> method = do_QueryElementAt(methods, index); + MOZ_ASSERT(method); + nsAutoString supportedMethods; + rv = method->GetSupportedMethods(supportedMethods); + NS_ENSURE_SUCCESS(rv, false); + if (service->IsBasicCardPayment(supportedMethods)) { + return true; + } + } + return false; +} + +} // namespace mozilla::dom |