Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/dom/payments/PaymentResponse.cpp
Line
Count
Source (jump to first uncovered line)
1
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
3
/* This Source Code Form is subject to the terms of the Mozilla Public
4
 * License, v. 2.0. If a copy of the MPL was not distributed with this
5
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7
#include "mozilla/StaticPrefs.h"
8
#include "mozilla/dom/PaymentResponse.h"
9
#include "mozilla/dom/BasicCardPaymentBinding.h"
10
#include "mozilla/dom/PaymentRequestUpdateEvent.h"
11
#include "BasicCardPayment.h"
12
#include "PaymentAddress.h"
13
#include "PaymentRequestUtils.h"
14
#include "mozilla/EventStateManager.h"
15
16
namespace mozilla {
17
namespace dom {
18
19
NS_IMPL_CYCLE_COLLECTION_CLASS(PaymentResponse)
20
21
0
NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(PaymentResponse,
22
0
                                               DOMEventTargetHelper)
23
0
  // Don't need NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER because
24
0
  // DOMEventTargetHelper does it for us.
25
0
NS_IMPL_CYCLE_COLLECTION_TRACE_END
26
27
0
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(PaymentResponse,
28
0
                                                  DOMEventTargetHelper)
29
0
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mShippingAddress)
30
0
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPromise)
31
0
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTimer)
32
0
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
33
34
0
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(PaymentResponse,
35
0
                                                DOMEventTargetHelper)
36
0
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mShippingAddress)
37
0
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mPromise)
38
0
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mTimer)
39
0
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
40
41
0
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PaymentResponse)
42
0
  NS_INTERFACE_MAP_ENTRY(nsITimerCallback)
43
0
NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
44
45
NS_IMPL_ADDREF_INHERITED(PaymentResponse, DOMEventTargetHelper)
46
NS_IMPL_RELEASE_INHERITED(PaymentResponse, DOMEventTargetHelper)
47
48
PaymentResponse::PaymentResponse(nsPIDOMWindowInner* aWindow,
49
                                 PaymentRequest* aRequest,
50
                                 const nsAString& aRequestId,
51
                                 const nsAString& aMethodName,
52
                                 const nsAString& aShippingOption,
53
                                 PaymentAddress* aShippingAddress,
54
                                 const nsAString& aDetails,
55
                                 const nsAString& aPayerName,
56
                                 const nsAString& aPayerEmail,
57
                                 const nsAString& aPayerPhone)
58
  : DOMEventTargetHelper(aWindow)
59
  , mCompleteCalled(false)
60
  , mRequest(aRequest)
61
  , mRequestId(aRequestId)
62
  , mMethodName(aMethodName)
63
  , mDetails(aDetails)
64
  , mShippingOption(aShippingOption)
65
  , mPayerName(aPayerName)
66
  , mPayerEmail(aPayerEmail)
67
  , mPayerPhone(aPayerPhone)
68
  , mShippingAddress(aShippingAddress)
69
0
{
70
0
71
0
  // TODO: from https://github.com/w3c/browser-payment-api/issues/480
72
0
  // Add payerGivenName + payerFamilyName to PaymentAddress
73
0
  NS_NewTimerWithCallback(getter_AddRefs(mTimer),
74
0
                          this,
75
0
                          StaticPrefs::dom_payments_response_timeout(),
76
0
                          nsITimer::TYPE_ONE_SHOT,
77
0
                          aWindow->EventTargetFor(TaskCategory::Other));
78
0
}
79
80
0
PaymentResponse::~PaymentResponse() = default;
81
82
JSObject*
83
PaymentResponse::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
84
0
{
85
0
  return PaymentResponse_Binding::Wrap(aCx, this, aGivenProto);
86
0
}
87
88
void
89
PaymentResponse::GetRequestId(nsString& aRetVal) const
90
0
{
91
0
  aRetVal = mRequestId;
92
0
}
93
94
void
95
PaymentResponse::GetMethodName(nsString& aRetVal) const
96
0
{
97
0
  aRetVal = mMethodName;
98
0
}
99
100
void
101
PaymentResponse::GetDetails(JSContext* aCx,
102
                            JS::MutableHandle<JSObject*> aRetVal) const
103
0
{
104
0
  RefPtr<BasicCardService> service = BasicCardService::GetService();
105
0
  MOZ_ASSERT(service);
106
0
  if (!service->IsBasicCardPayment(mMethodName)) {
107
0
    DeserializeToJSObject(mDetails, aCx, aRetVal);
108
0
  } else {
109
0
    BasicCardResponse response;
110
0
    nsresult rv = service->DecodeBasicCardData(mDetails, GetOwner(), response);
111
0
    if (NS_WARN_IF(NS_FAILED(rv))) {
112
0
      return;
113
0
    }
114
0
115
0
    MOZ_ASSERT(aCx);
116
0
    JS::RootedValue value(aCx);
117
0
    if (NS_WARN_IF(!response.ToObjectInternal(aCx, &value))) {
118
0
      return;
119
0
    }
120
0
    aRetVal.set(&value.toObject());
121
0
  }
122
0
}
123
124
void
125
PaymentResponse::GetShippingOption(nsString& aRetVal) const
126
0
{
127
0
  aRetVal = mShippingOption;
128
0
}
129
130
void
131
PaymentResponse::GetPayerName(nsString& aRetVal) const
132
0
{
133
0
  aRetVal = mPayerName;
134
0
}
135
136
void
137
PaymentResponse::GetPayerEmail(nsString& aRetVal) const
138
0
{
139
0
  aRetVal = mPayerEmail;
140
0
}
141
142
void
143
PaymentResponse::GetPayerPhone(nsString& aRetVal) const
144
0
{
145
0
  aRetVal = mPayerPhone;
146
0
}
147
148
// TODO:
149
// Return a raw pointer here to avoid refcounting, but make sure it's safe
150
// (the object should be kept alive by the callee).
151
already_AddRefed<PaymentAddress>
152
PaymentResponse::GetShippingAddress() const
153
0
{
154
0
  RefPtr<PaymentAddress> address = mShippingAddress;
155
0
  return address.forget();
156
0
}
157
158
already_AddRefed<Promise>
159
PaymentResponse::Complete(PaymentComplete result, ErrorResult& aRv)
160
0
{
161
0
  if (mCompleteCalled) {
162
0
    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
163
0
    return nullptr;
164
0
  }
165
0
166
0
  mCompleteCalled = true;
167
0
168
0
  if (mTimer) {
169
0
    mTimer->Cancel();
170
0
    mTimer = nullptr;
171
0
  }
172
0
173
0
  RefPtr<PaymentRequestManager> manager = PaymentRequestManager::GetSingleton();
174
0
  if (NS_WARN_IF(!manager)) {
175
0
    aRv.Throw(NS_ERROR_FAILURE);
176
0
    return nullptr;
177
0
  }
178
0
  nsresult rv = manager->CompletePayment(mRequest, result);
179
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
180
0
    aRv.Throw(NS_ERROR_FAILURE);
181
0
    return nullptr;
182
0
  }
183
0
184
0
  if (NS_WARN_IF(!GetOwner())) {
185
0
    aRv.Throw(NS_ERROR_FAILURE);
186
0
    return nullptr;
187
0
  }
188
0
189
0
  nsIGlobalObject* global = GetOwner()->AsGlobal();
190
0
  ErrorResult errResult;
191
0
  RefPtr<Promise> promise = Promise::Create(global, errResult);
192
0
  if (errResult.Failed()) {
193
0
    aRv.Throw(NS_ERROR_FAILURE);
194
0
    return nullptr;
195
0
  }
196
0
197
0
  mPromise = promise;
198
0
  return promise.forget();
199
0
}
200
201
void
202
PaymentResponse::RespondComplete()
203
0
{
204
0
  // mPromise may be null when timing out
205
0
  if (mPromise) {
206
0
    mPromise->MaybeResolve(JS::UndefinedHandleValue);
207
0
    mPromise = nullptr;
208
0
  }
209
0
}
210
211
already_AddRefed<Promise>
212
PaymentResponse::Retry(JSContext* aCx,
213
                       const PaymentValidationErrors& aErrors,
214
                       ErrorResult& aRv)
215
0
{
216
0
  nsIGlobalObject* global = GetOwner()->AsGlobal();
217
0
  ErrorResult errResult;
218
0
  RefPtr<Promise> promise = Promise::Create(global, errResult);
219
0
  if (errResult.Failed()) {
220
0
    aRv.Throw(NS_ERROR_FAILURE);
221
0
    return nullptr;
222
0
  }
223
0
224
0
  if (mTimer) {
225
0
    mTimer->Cancel();
226
0
    mTimer = nullptr;
227
0
  }
228
0
229
0
  if (NS_WARN_IF(!GetOwner())) {
230
0
    aRv.Throw(NS_ERROR_FAILURE);
231
0
    return nullptr;
232
0
  }
233
0
234
0
  nsIDocument* doc = GetOwner()->GetExtantDoc();
235
0
  if (!doc || !doc->IsCurrentActiveDocument()) {
236
0
    promise->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
237
0
    return promise.forget();
238
0
  }
239
0
240
0
  if (mCompleteCalled || mRetryPromise) {
241
0
    promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
242
0
    return promise.forget();
243
0
  }
244
0
245
0
  nsresult rv = ValidatePaymentValidationErrors(aErrors);
246
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
247
0
    promise->MaybeReject(rv);
248
0
    return promise.forget();
249
0
  }
250
0
251
0
  MOZ_ASSERT(mRequest);
252
0
  rv = mRequest->RetryPayment(aCx, aErrors);
253
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
254
0
    promise->MaybeReject(rv);
255
0
    return promise.forget();
256
0
  }
257
0
258
0
  mRetryPromise = promise;
259
0
  return promise.forget();
260
0
}
261
262
void
263
PaymentResponse::RespondRetry(const nsAString& aMethodName,
264
                              const nsAString& aShippingOption,
265
                              PaymentAddress* aShippingAddress,
266
                              const nsAString& aDetails,
267
                              const nsAString& aPayerName,
268
                              const nsAString& aPayerEmail,
269
                              const nsAString& aPayerPhone)
270
0
{
271
0
  mMethodName = aMethodName;
272
0
  mShippingOption = aShippingOption;
273
0
  mShippingAddress = aShippingAddress;
274
0
  mDetails = aDetails;
275
0
  mPayerName = aPayerName;
276
0
  mPayerEmail = aPayerEmail;
277
0
  mPayerPhone = aPayerPhone;
278
0
279
0
  if (NS_WARN_IF(!GetOwner())) {
280
0
    return;
281
0
  }
282
0
283
0
  NS_NewTimerWithCallback(getter_AddRefs(mTimer),
284
0
                          this,
285
0
                          StaticPrefs::dom_payments_response_timeout(),
286
0
                          nsITimer::TYPE_ONE_SHOT,
287
0
                          GetOwner()->EventTargetFor(TaskCategory::Other));
288
0
  MOZ_ASSERT(mRetryPromise);
289
0
  mRetryPromise->MaybeResolve(JS::UndefinedHandleValue);
290
0
  mRetryPromise = nullptr;
291
0
}
292
293
void
294
PaymentResponse::RejectRetry(nsresult aRejectReason)
295
0
{
296
0
  MOZ_ASSERT(mRetryPromise);
297
0
  mRetryPromise->MaybeReject(aRejectReason);
298
0
  mRetryPromise = nullptr;
299
0
}
300
301
nsresult
302
PaymentResponse::ValidatePaymentValidationErrors(
303
  const PaymentValidationErrors& aErrors)
304
0
{
305
0
  // Should not be empty errors
306
0
  // check PaymentValidationErrors.error
307
0
  if (aErrors.mError.WasPassed() && !aErrors.mError.Value().IsEmpty()) {
308
0
    return NS_OK;
309
0
  }
310
0
  // check PaymentValidationErrors.payer
311
0
  PayerErrorFields payerErrors(aErrors.mPayer);
312
0
  if (payerErrors.mName.WasPassed() && !payerErrors.mName.Value().IsEmpty()) {
313
0
    return NS_OK;
314
0
  }
315
0
  if (payerErrors.mEmail.WasPassed() && !payerErrors.mEmail.Value().IsEmpty()) {
316
0
    return NS_OK;
317
0
  }
318
0
  if (payerErrors.mPhone.WasPassed() && !payerErrors.mPhone.Value().IsEmpty()) {
319
0
    return NS_OK;
320
0
  }
321
0
  // check PaymentValidationErrors.paymentMethod
322
0
  if (aErrors.mPaymentMethod.WasPassed()) {
323
0
    return NS_OK;
324
0
  }
325
0
  // check PaymentValidationErrors.shippingAddress
326
0
  AddressErrors addErrors(aErrors.mShippingAddress);
327
0
  if (addErrors.mAddressLine.WasPassed() &&
328
0
      !addErrors.mAddressLine.Value().IsEmpty()) {
329
0
    return NS_OK;
330
0
  }
331
0
  if (addErrors.mCity.WasPassed() && !addErrors.mCity.Value().IsEmpty()) {
332
0
    return NS_OK;
333
0
  }
334
0
  if (addErrors.mCountry.WasPassed() && !addErrors.mCountry.Value().IsEmpty()) {
335
0
    return NS_OK;
336
0
  }
337
0
  if (addErrors.mDependentLocality.WasPassed() &&
338
0
      !addErrors.mDependentLocality.Value().IsEmpty()) {
339
0
    return NS_OK;
340
0
  }
341
0
  if (addErrors.mOrganization.WasPassed() &&
342
0
      !addErrors.mOrganization.Value().IsEmpty()) {
343
0
    return NS_OK;
344
0
  }
345
0
  if (addErrors.mPhone.WasPassed() && !addErrors.mPhone.Value().IsEmpty()) {
346
0
    return NS_OK;
347
0
  }
348
0
  if (addErrors.mPostalCode.WasPassed() &&
349
0
      !addErrors.mPostalCode.Value().IsEmpty()) {
350
0
    return NS_OK;
351
0
  }
352
0
  if (addErrors.mRecipient.WasPassed() &&
353
0
      !addErrors.mRecipient.Value().IsEmpty()) {
354
0
    return NS_OK;
355
0
  }
356
0
  if (addErrors.mRegion.WasPassed() && !addErrors.mRegion.Value().IsEmpty()) {
357
0
    return NS_OK;
358
0
  }
359
0
  if (addErrors.mRegionCode.WasPassed() &&
360
0
      !addErrors.mRegionCode.Value().IsEmpty()) {
361
0
    return NS_OK;
362
0
  }
363
0
  if (addErrors.mSortingCode.WasPassed() &&
364
0
      !addErrors.mSortingCode.Value().IsEmpty()) {
365
0
    return NS_OK;
366
0
  }
367
0
  return NS_ERROR_DOM_ABORT_ERR;
368
0
}
369
370
NS_IMETHODIMP
371
PaymentResponse::Notify(nsITimer* timer)
372
0
{
373
0
  mTimer = nullptr;
374
0
  if (mCompleteCalled) {
375
0
    return NS_OK;
376
0
  }
377
0
378
0
  mCompleteCalled = true;
379
0
380
0
  RefPtr<PaymentRequestManager> manager = PaymentRequestManager::GetSingleton();
381
0
  if (NS_WARN_IF(!manager)) {
382
0
    return NS_ERROR_FAILURE;
383
0
  }
384
0
385
0
  return manager->CompletePayment(mRequest, PaymentComplete::Unknown, true);
386
0
}
387
388
nsresult
389
PaymentResponse::UpdatePayerDetail(const nsAString& aPayerName,
390
                                   const nsAString& aPayerEmail,
391
                                   const nsAString& aPayerPhone)
392
0
{
393
0
  MOZ_ASSERT(mRequest->ReadyForUpdate());
394
0
  PaymentOptions options;
395
0
  mRequest->GetOptions(options);
396
0
  if (options.mRequestPayerName) {
397
0
    mPayerName = aPayerName;
398
0
  }
399
0
  if (options.mRequestPayerEmail) {
400
0
    mPayerEmail = aPayerEmail;
401
0
  }
402
0
  if (options.mRequestPayerPhone) {
403
0
    mPayerPhone = aPayerPhone;
404
0
  }
405
0
  return DispatchUpdateEvent(NS_LITERAL_STRING("payerdetailchange"));
406
0
}
407
408
nsresult
409
PaymentResponse::DispatchUpdateEvent(const nsAString& aType)
410
0
{
411
0
  PaymentRequestUpdateEventInit init;
412
0
  RefPtr<PaymentRequestUpdateEvent> event =
413
0
    PaymentRequestUpdateEvent::Constructor(this, aType, init);
414
0
  event->SetTrusted(true);
415
0
  event->SetRequest(mRequest);
416
0
417
0
  ErrorResult rv;
418
0
  DispatchEvent(*event, rv);
419
0
  return rv.StealNSResult();
420
0
}
421
422
} // namespace dom
423
} // namespace mozilla