/src/mozilla-central/dom/payments/PaymentRequest.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 "BasicCardPayment.h" |
8 | | #include "mozilla/dom/Element.h" |
9 | | #include "mozilla/dom/PaymentRequest.h" |
10 | | #include "mozilla/dom/PaymentRequestChild.h" |
11 | | #include "mozilla/dom/PaymentResponse.h" |
12 | | #include "mozilla/EventStateManager.h" |
13 | | #include "mozilla/StaticPrefs.h" |
14 | | #include "nsContentUtils.h" |
15 | | #include "nsIScriptError.h" |
16 | | #include "nsIURLParser.h" |
17 | | #include "nsNetCID.h" |
18 | | #include "PaymentRequestManager.h" |
19 | | #include "mozilla/dom/MerchantValidationEvent.h" |
20 | | |
21 | | namespace mozilla { |
22 | | namespace dom { |
23 | | |
24 | | NS_IMPL_CYCLE_COLLECTION_CLASS(PaymentRequest) |
25 | | |
26 | 0 | NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(PaymentRequest, |
27 | 0 | DOMEventTargetHelper) |
28 | 0 | // Don't need NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER because |
29 | 0 | // DOMEventTargetHelper does it for us. |
30 | 0 | NS_IMPL_CYCLE_COLLECTION_TRACE_END |
31 | | |
32 | 0 | NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(PaymentRequest, |
33 | 0 | DOMEventTargetHelper) |
34 | 0 | NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mResultPromise) |
35 | 0 | NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAcceptPromise) |
36 | 0 | NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAbortPromise) |
37 | 0 | NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mResponse) |
38 | 0 | NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mShippingAddress) |
39 | 0 | NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFullShippingAddress) |
40 | 0 | NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END |
41 | | |
42 | 0 | NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(PaymentRequest, |
43 | 0 | DOMEventTargetHelper) |
44 | 0 | NS_IMPL_CYCLE_COLLECTION_UNLINK(mResultPromise) |
45 | 0 | NS_IMPL_CYCLE_COLLECTION_UNLINK(mAcceptPromise) |
46 | 0 | NS_IMPL_CYCLE_COLLECTION_UNLINK(mAbortPromise) |
47 | 0 | NS_IMPL_CYCLE_COLLECTION_UNLINK(mResponse) |
48 | 0 | NS_IMPL_CYCLE_COLLECTION_UNLINK(mShippingAddress) |
49 | 0 | NS_IMPL_CYCLE_COLLECTION_UNLINK(mFullShippingAddress) |
50 | 0 | NS_IMPL_CYCLE_COLLECTION_UNLINK_END |
51 | | |
52 | 0 | NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PaymentRequest) |
53 | 0 | NS_INTERFACE_MAP_ENTRY(nsIDocumentActivity) |
54 | 0 | NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper) |
55 | | |
56 | | NS_IMPL_ADDREF_INHERITED(PaymentRequest, DOMEventTargetHelper) |
57 | | NS_IMPL_RELEASE_INHERITED(PaymentRequest, DOMEventTargetHelper) |
58 | | |
59 | | bool |
60 | | PaymentRequest::PrefEnabled(JSContext* aCx, JSObject* aObj) |
61 | 0 | { |
62 | 0 | #ifdef NIGHTLY_BUILD |
63 | 0 | return XRE_IsContentProcess() && |
64 | 0 | Preferences::GetBool("dom.payments.request.enabled"); |
65 | | #else |
66 | | return false; |
67 | | #endif |
68 | | } |
69 | | |
70 | | nsresult |
71 | | PaymentRequest::IsValidStandardizedPMI(const nsAString& aIdentifier, |
72 | | nsAString& aErrorMsg) |
73 | 0 | { |
74 | 0 | /* |
75 | 0 | * The syntax of a standardized payment method identifier is given by the |
76 | 0 | * following [ABNF]: |
77 | 0 | * |
78 | 0 | * stdpmi = part *( "-" part ) |
79 | 0 | * part = 1loweralpha *( DIGIT / loweralpha ) |
80 | 0 | * loweralpha = %x61-7A |
81 | 0 | */ |
82 | 0 | nsString::const_iterator start, end; |
83 | 0 | aIdentifier.BeginReading(start); |
84 | 0 | aIdentifier.EndReading(end); |
85 | 0 | while (start != end) { |
86 | 0 | // the first char must be in the range %x61-7A |
87 | 0 | if ((*start < 'a' || *start > 'z')) { |
88 | 0 | aErrorMsg.AssignLiteral("'"); |
89 | 0 | aErrorMsg.Append(aIdentifier); |
90 | 0 | aErrorMsg.AppendLiteral("' is not valid. The character '"); |
91 | 0 | aErrorMsg.Append(*start); |
92 | 0 | aErrorMsg.AppendLiteral("' at the beginning or after the '-' must be in the range [a-z]."); |
93 | 0 | return NS_ERROR_RANGE_ERR; |
94 | 0 | } |
95 | 0 | ++start; |
96 | 0 | // the rest can be in the range %x61-7A + DIGITs |
97 | 0 | while (start != end && *start != '-' && |
98 | 0 | ((*start >= 'a' && *start <= 'z') || (*start >= '0' && *start <= '9'))) { |
99 | 0 | ++start; |
100 | 0 | } |
101 | 0 | // if the char is not in the range %x61-7A + DIGITs, it must be '-' |
102 | 0 | if (start != end && *start != '-') { |
103 | 0 | aErrorMsg.AssignLiteral("'"); |
104 | 0 | aErrorMsg.Append(aIdentifier); |
105 | 0 | aErrorMsg.AppendLiteral("' is not valid. The character '"); |
106 | 0 | aErrorMsg.Append(*start); |
107 | 0 | aErrorMsg.AppendLiteral("' must be in the range [a-zA-z0-9-]."); |
108 | 0 | return NS_ERROR_RANGE_ERR; |
109 | 0 | } |
110 | 0 | if (*start == '-') { |
111 | 0 | ++start; |
112 | 0 | // the last char can not be '-' |
113 | 0 | if (start == end) { |
114 | 0 | aErrorMsg.AssignLiteral("'"); |
115 | 0 | aErrorMsg.Append(aIdentifier); |
116 | 0 | aErrorMsg.AppendLiteral("' is not valid. The last character '"); |
117 | 0 | aErrorMsg.Append(*start); |
118 | 0 | aErrorMsg.AppendLiteral("' must be in the range [a-z0-9]."); |
119 | 0 | return NS_ERROR_RANGE_ERR; |
120 | 0 | } |
121 | 0 | } |
122 | 0 | } |
123 | 0 | return NS_OK; |
124 | 0 | } |
125 | | |
126 | | nsresult |
127 | | PaymentRequest::IsValidPaymentMethodIdentifier(const nsAString& aIdentifier, |
128 | | nsAString& aErrorMsg) |
129 | 0 | { |
130 | 0 | if (aIdentifier.IsEmpty()) { |
131 | 0 | aErrorMsg.AssignLiteral("Payment method identifier is required."); |
132 | 0 | return NS_ERROR_TYPE_ERR; |
133 | 0 | } |
134 | 0 | /* |
135 | 0 | * URL-based payment method identifier |
136 | 0 | * |
137 | 0 | * 1. If url's scheme is not "https", return false. |
138 | 0 | * 2. If url's username or password is not the empty string, return false. |
139 | 0 | * 3. Otherwise, return true. |
140 | 0 | */ |
141 | 0 | nsCOMPtr<nsIURLParser> urlParser = do_GetService(NS_STDURLPARSER_CONTRACTID); |
142 | 0 | MOZ_ASSERT(urlParser); |
143 | 0 | uint32_t schemePos = 0; |
144 | 0 | int32_t schemeLen = 0; |
145 | 0 | uint32_t authorityPos = 0; |
146 | 0 | int32_t authorityLen = 0; |
147 | 0 | NS_ConvertUTF16toUTF8 url(aIdentifier); |
148 | 0 | nsresult rv = urlParser->ParseURL(url.get(), |
149 | 0 | url.Length(), |
150 | 0 | &schemePos, &schemeLen, |
151 | 0 | &authorityPos, &authorityLen, |
152 | 0 | nullptr, nullptr); |
153 | 0 | NS_ENSURE_SUCCESS(rv, NS_ERROR_RANGE_ERR); |
154 | 0 | if (schemeLen == -1) { |
155 | 0 | // The PMI is not a URL-based PMI, check if it is a standardized PMI |
156 | 0 | return IsValidStandardizedPMI(aIdentifier, aErrorMsg); |
157 | 0 | } |
158 | 0 | if (!Substring(aIdentifier, schemePos, schemeLen).EqualsASCII("https")) { |
159 | 0 | aErrorMsg.AssignLiteral("'"); |
160 | 0 | aErrorMsg.Append(aIdentifier); |
161 | 0 | aErrorMsg.AppendLiteral("' is not valid. The scheme must be 'https'."); |
162 | 0 | return NS_ERROR_RANGE_ERR; |
163 | 0 | } |
164 | 0 | if (Substring(aIdentifier, authorityPos, authorityLen).IsEmpty()) { |
165 | 0 | aErrorMsg.AssignLiteral("'"); |
166 | 0 | aErrorMsg.Append(aIdentifier); |
167 | 0 | aErrorMsg.AppendLiteral("' is not valid. hostname can not be empty."); |
168 | 0 | return NS_ERROR_RANGE_ERR; |
169 | 0 | } |
170 | 0 | |
171 | 0 | uint32_t usernamePos = 0; |
172 | 0 | int32_t usernameLen = 0; |
173 | 0 | uint32_t passwordPos = 0; |
174 | 0 | int32_t passwordLen = 0; |
175 | 0 | uint32_t hostnamePos = 0; |
176 | 0 | int32_t hostnameLen = 0; |
177 | 0 | int32_t port = 0; |
178 | 0 |
|
179 | 0 | NS_ConvertUTF16toUTF8 authority(Substring(aIdentifier, authorityPos, authorityLen)); |
180 | 0 | rv = urlParser->ParseAuthority(authority.get(), |
181 | 0 | authority.Length(), |
182 | 0 | &usernamePos, &usernameLen, |
183 | 0 | &passwordPos, &passwordLen, |
184 | 0 | &hostnamePos, &hostnameLen, |
185 | 0 | &port); |
186 | 0 | if (NS_FAILED(rv)) { |
187 | 0 | // Handle the special cases that URLParser treats it as an invalid URL, but |
188 | 0 | // are used in web-platform-test |
189 | 0 | // For exmaple: |
190 | 0 | // https://:@example.com // should be considered as valid |
191 | 0 | // https://:password@example.com. // should be considered as invalid |
192 | 0 | int32_t atPos = authority.FindChar('@'); |
193 | 0 | if (atPos >= 0) { |
194 | 0 | // only accept the case https://:@xxx |
195 | 0 | if (atPos == 1 && authority.CharAt(0) == ':') { |
196 | 0 | usernamePos = 0; |
197 | 0 | usernameLen = 0; |
198 | 0 | passwordPos = 0; |
199 | 0 | passwordLen = 0; |
200 | 0 | } else { |
201 | 0 | // for the fail cases, don't care about what the actual length is. |
202 | 0 | usernamePos = 0; |
203 | 0 | usernameLen = INT32_MAX; |
204 | 0 | passwordPos = 0; |
205 | 0 | passwordLen = INT32_MAX; |
206 | 0 | } |
207 | 0 | } else { |
208 | 0 | usernamePos = 0; |
209 | 0 | usernameLen = -1; |
210 | 0 | passwordPos = 0; |
211 | 0 | passwordLen = -1; |
212 | 0 | } |
213 | 0 | // Parse server information when both username and password are empty or do not |
214 | 0 | // exist. |
215 | 0 | if ((usernameLen <= 0) && (passwordLen <= 0)) { |
216 | 0 | if (authority.Length() - atPos - 1 == 0) { |
217 | 0 | aErrorMsg.AssignLiteral("'"); |
218 | 0 | aErrorMsg.Append(aIdentifier); |
219 | 0 | aErrorMsg.AppendLiteral("' is not valid. hostname can not be empty."); |
220 | 0 | return NS_ERROR_RANGE_ERR; |
221 | 0 | } |
222 | 0 | // Re-using nsIURLParser::ParseServerInfo to extract the hostname and port |
223 | 0 | // information. This can help us to handle complicated IPv6 cases. |
224 | 0 | nsAutoCString serverInfo(Substring(authority, |
225 | 0 | atPos + 1, |
226 | 0 | authority.Length() - atPos - 1)); |
227 | 0 | rv = urlParser->ParseServerInfo(serverInfo.get(), |
228 | 0 | serverInfo.Length(), |
229 | 0 | &hostnamePos, &hostnameLen, &port); |
230 | 0 | if (NS_FAILED(rv)) { |
231 | 0 | // ParseServerInfo returns NS_ERROR_MALFORMED_URI in all fail cases, we |
232 | 0 | // probably need a followup bug to figure out the fail reason. |
233 | 0 | return NS_ERROR_RANGE_ERR; |
234 | 0 | } |
235 | 0 | } |
236 | 0 | } |
237 | 0 | // PMI is valid when usernameLen/passwordLen equals to -1 or 0. |
238 | 0 | if (usernameLen > 0 || passwordLen > 0) { |
239 | 0 | aErrorMsg.AssignLiteral("'"); |
240 | 0 | aErrorMsg.Append(aIdentifier); |
241 | 0 | aErrorMsg.AssignLiteral("' is not valid. Username and password must be empty."); |
242 | 0 | return NS_ERROR_RANGE_ERR; |
243 | 0 | } |
244 | 0 | |
245 | 0 | // PMI is valid when hostnameLen is larger than 0 |
246 | 0 | if (hostnameLen <= 0) { |
247 | 0 | aErrorMsg.AssignLiteral("'"); |
248 | 0 | aErrorMsg.Append(aIdentifier); |
249 | 0 | aErrorMsg.AppendLiteral("' is not valid. hostname can not be empty."); |
250 | 0 | return NS_ERROR_RANGE_ERR; |
251 | 0 | } |
252 | 0 | return NS_OK; |
253 | 0 | } |
254 | | |
255 | | nsresult |
256 | | PaymentRequest::IsValidMethodData(JSContext* aCx, |
257 | | const Sequence<PaymentMethodData>& aMethodData, |
258 | | nsAString& aErrorMsg) |
259 | 0 | { |
260 | 0 | if (!aMethodData.Length()) { |
261 | 0 | aErrorMsg.AssignLiteral("At least one payment method is required."); |
262 | 0 | return NS_ERROR_TYPE_ERR; |
263 | 0 | } |
264 | 0 | |
265 | 0 | for (const PaymentMethodData& methodData : aMethodData) { |
266 | 0 | nsresult rv = IsValidPaymentMethodIdentifier(methodData.mSupportedMethods, |
267 | 0 | aErrorMsg); |
268 | 0 | if (NS_FAILED(rv)) { |
269 | 0 | return rv; |
270 | 0 | } |
271 | 0 | |
272 | 0 | RefPtr<BasicCardService> service = BasicCardService::GetService(); |
273 | 0 | MOZ_ASSERT(service); |
274 | 0 | if (service->IsBasicCardPayment(methodData.mSupportedMethods)) { |
275 | 0 | if (!methodData.mData.WasPassed()) { |
276 | 0 | continue; |
277 | 0 | } |
278 | 0 | MOZ_ASSERT(aCx); |
279 | 0 | if (!service->IsValidBasicCardRequest(aCx, |
280 | 0 | methodData.mData.Value(), |
281 | 0 | aErrorMsg)) { |
282 | 0 | return NS_ERROR_TYPE_ERR; |
283 | 0 | } |
284 | 0 | } |
285 | 0 | } |
286 | 0 |
|
287 | 0 | return NS_OK; |
288 | 0 | } |
289 | | |
290 | | nsresult |
291 | | PaymentRequest::IsValidNumber(const nsAString& aItem, |
292 | | const nsAString& aStr, |
293 | | nsAString& aErrorMsg) |
294 | 0 | { |
295 | 0 | nsresult error = NS_ERROR_FAILURE; |
296 | 0 |
|
297 | 0 | if (!aStr.IsEmpty()) { |
298 | 0 | nsAutoString aValue(aStr); |
299 | 0 |
|
300 | 0 | // If the beginning character is '-', we will check the second one. |
301 | 0 | int beginningIndex = (aValue.First() == '-') ? 1 : 0; |
302 | 0 |
|
303 | 0 | // Ensure |
304 | 0 | // - the beginning character is a digit in [0-9], and |
305 | 0 | // - the last character is not '.' |
306 | 0 | // to follow spec: |
307 | 0 | // https://w3c.github.io/browser-payment-api/#dfn-valid-decimal-monetary-value |
308 | 0 | // |
309 | 0 | // For example, ".1" is not valid for '.' is not in [0-9], |
310 | 0 | // and " 0.1" either for beginning with ' ' |
311 | 0 | if (aValue.Last() != '.' && |
312 | 0 | aValue.CharAt(beginningIndex) >= '0' && |
313 | 0 | aValue.CharAt(beginningIndex) <= '9') { |
314 | 0 | aValue.ToFloat(&error); |
315 | 0 | } |
316 | 0 | } |
317 | 0 |
|
318 | 0 | if (NS_FAILED(error)) { |
319 | 0 | aErrorMsg.AssignLiteral("The amount.value of \""); |
320 | 0 | aErrorMsg.Append(aItem); |
321 | 0 | aErrorMsg.AppendLiteral("\"("); |
322 | 0 | aErrorMsg.Append(aStr); |
323 | 0 | aErrorMsg.AppendLiteral(") must be a valid decimal monetary value."); |
324 | 0 | return NS_ERROR_TYPE_ERR; |
325 | 0 | } |
326 | 0 | return NS_OK; |
327 | 0 | } |
328 | | |
329 | | nsresult |
330 | | PaymentRequest::IsNonNegativeNumber(const nsAString& aItem, |
331 | | const nsAString& aStr, |
332 | | nsAString& aErrorMsg) |
333 | 0 | { |
334 | 0 | nsresult error = NS_ERROR_FAILURE; |
335 | 0 |
|
336 | 0 | if (!aStr.IsEmpty()) { |
337 | 0 | nsAutoString aValue(aStr); |
338 | 0 | // Ensure |
339 | 0 | // - the beginning character is a digit in [0-9], and |
340 | 0 | // - the last character is not '.' |
341 | 0 | if (aValue.Last() != '.' && |
342 | 0 | aValue.First() >= '0' && |
343 | 0 | aValue.First() <= '9') { |
344 | 0 | aValue.ToFloat(&error); |
345 | 0 | } |
346 | 0 | } |
347 | 0 |
|
348 | 0 | if (NS_FAILED(error)) { |
349 | 0 | aErrorMsg.AssignLiteral("The amount.value of \""); |
350 | 0 | aErrorMsg.Append(aItem); |
351 | 0 | aErrorMsg.AppendLiteral("\"("); |
352 | 0 | aErrorMsg.Append(aStr); |
353 | 0 | aErrorMsg.AppendLiteral(") must be a valid and non-negative decimal monetary value."); |
354 | 0 | return NS_ERROR_TYPE_ERR; |
355 | 0 | } |
356 | 0 | return NS_OK; |
357 | 0 | } |
358 | | |
359 | | nsresult |
360 | | PaymentRequest::IsValidCurrency(const nsAString& aItem, |
361 | | const nsAString& aCurrency, |
362 | | nsAString& aErrorMsg) |
363 | 0 | { |
364 | 0 | /* |
365 | 0 | * According to spec in https://w3c.github.io/payment-request/#validity-checkers, |
366 | 0 | * perform currency validation with following criteria |
367 | 0 | * 1. The currency length must be 3. |
368 | 0 | * 2. The currency contains any character that must be in the range "A" to "Z" |
369 | 0 | * (U+0041 to U+005A) or the range "a" to "z" (U+0061 to U+007A) |
370 | 0 | */ |
371 | 0 | if (aCurrency.Length() != 3) { |
372 | 0 | aErrorMsg.AssignLiteral("The length amount.currency of \""); |
373 | 0 | aErrorMsg.Append(aItem); |
374 | 0 | aErrorMsg.AppendLiteral("\"("); |
375 | 0 | aErrorMsg.Append(aCurrency); |
376 | 0 | aErrorMsg.AppendLiteral(") must be 3."); |
377 | 0 | return NS_ERROR_RANGE_ERR; |
378 | 0 | } |
379 | 0 | // Don't use nsUnicharUtils::ToUpperCase, it converts the invalid "ınr" PMI to |
380 | 0 | // to the valid one "INR". |
381 | 0 | for (uint32_t idx = 0; idx < aCurrency.Length(); ++idx) { |
382 | 0 | if ((aCurrency.CharAt(idx) >= 'A' && aCurrency.CharAt(idx) <= 'Z') || |
383 | 0 | (aCurrency.CharAt(idx) >= 'a' && aCurrency.CharAt(idx) <= 'z')) { |
384 | 0 | continue; |
385 | 0 | } |
386 | 0 | aErrorMsg.AssignLiteral("The character amount.currency of \""); |
387 | 0 | aErrorMsg.Append(aItem); |
388 | 0 | aErrorMsg.AppendLiteral("\"("); |
389 | 0 | aErrorMsg.Append(aCurrency); |
390 | 0 | aErrorMsg.AppendLiteral(") must be in the range 'A' to 'Z'(U+0041 to U+005A) or 'a' to 'z'(U+0061 to U+007A)."); |
391 | 0 | return NS_ERROR_RANGE_ERR; |
392 | 0 | } |
393 | 0 | return NS_OK; |
394 | 0 | } |
395 | | |
396 | | nsresult |
397 | | PaymentRequest::IsValidCurrencyAmount(const nsAString& aItem, |
398 | | const PaymentCurrencyAmount& aAmount, |
399 | | const bool aIsTotalItem, |
400 | | nsAString& aErrorMsg) |
401 | 0 | { |
402 | 0 | nsresult rv; |
403 | 0 | // currencySystem must equal urn:iso:std:iso:4217 |
404 | 0 | if (!aAmount.mCurrencySystem.EqualsASCII("urn:iso:std:iso:4217")) { |
405 | 0 | aErrorMsg.AssignLiteral("The amount.currencySystem of \""); |
406 | 0 | aErrorMsg.Append(aItem); |
407 | 0 | aErrorMsg.AppendLiteral("\"("); |
408 | 0 | aErrorMsg.Append(aAmount.mCurrencySystem); |
409 | 0 | aErrorMsg.AppendLiteral(") must equal urn:iso:std:iso:4217."); |
410 | 0 | return NS_ERROR_RANGE_ERR; |
411 | 0 | } |
412 | 0 | rv = IsValidCurrency(aItem, aAmount.mCurrency, aErrorMsg); |
413 | 0 | if (NS_FAILED(rv)) { |
414 | 0 | return rv; |
415 | 0 | } |
416 | 0 | if (aIsTotalItem) { |
417 | 0 | rv = IsNonNegativeNumber(aItem, aAmount.mValue, aErrorMsg); |
418 | 0 | if (NS_FAILED(rv)) { |
419 | 0 | return rv; |
420 | 0 | } |
421 | 0 | } else { |
422 | 0 | rv = IsValidNumber(aItem, aAmount.mValue, aErrorMsg); |
423 | 0 | if (NS_FAILED(rv)) { |
424 | 0 | return rv; |
425 | 0 | } |
426 | 0 | } |
427 | 0 | return NS_OK; |
428 | 0 | } |
429 | | |
430 | | nsresult |
431 | | PaymentRequest::IsValidDetailsInit(const PaymentDetailsInit& aDetails, |
432 | | const bool aRequestShipping, |
433 | | nsAString& aErrorMsg) |
434 | 0 | { |
435 | 0 | // Check the amount.value and amount.currency of detail.total |
436 | 0 | nsresult rv = IsValidCurrencyAmount(NS_LITERAL_STRING("details.total"), |
437 | 0 | aDetails.mTotal.mAmount, |
438 | 0 | true, // isTotalItem |
439 | 0 | aErrorMsg); |
440 | 0 | if (NS_FAILED(rv)) { |
441 | 0 | return rv; |
442 | 0 | } |
443 | 0 | return IsValidDetailsBase(aDetails, aRequestShipping, aErrorMsg); |
444 | 0 | } |
445 | | |
446 | | nsresult |
447 | | PaymentRequest::IsValidDetailsUpdate(const PaymentDetailsUpdate& aDetails, |
448 | | const bool aRequestShipping) |
449 | 0 | { |
450 | 0 | nsAutoString message; |
451 | 0 | // Check the amount.value and amount.currency of detail.total |
452 | 0 | nsresult rv = IsValidCurrencyAmount(NS_LITERAL_STRING("details.total"), |
453 | 0 | aDetails.mTotal.mAmount, |
454 | 0 | true, // isTotalItem |
455 | 0 | message); |
456 | 0 | if (NS_FAILED(rv)) { |
457 | 0 | return rv; |
458 | 0 | } |
459 | 0 | return IsValidDetailsBase(aDetails, aRequestShipping, message); |
460 | 0 | } |
461 | | |
462 | | nsresult |
463 | | PaymentRequest::IsValidDetailsBase(const PaymentDetailsBase& aDetails, |
464 | | const bool aRequestShipping, |
465 | | nsAString& aErrorMsg) |
466 | 0 | { |
467 | 0 | nsresult rv; |
468 | 0 | // Check the amount.value of each item in the display items |
469 | 0 | if (aDetails.mDisplayItems.WasPassed()) { |
470 | 0 | const Sequence<PaymentItem>& displayItems = aDetails.mDisplayItems.Value(); |
471 | 0 | for (const PaymentItem& displayItem : displayItems) { |
472 | 0 | rv = IsValidCurrencyAmount(displayItem.mLabel, |
473 | 0 | displayItem.mAmount, |
474 | 0 | false, // isTotalItem |
475 | 0 | aErrorMsg); |
476 | 0 | if (NS_FAILED(rv)) { |
477 | 0 | return rv; |
478 | 0 | } |
479 | 0 | } |
480 | 0 | } |
481 | 0 |
|
482 | 0 | // Check the shipping option |
483 | 0 | if (aDetails.mShippingOptions.WasPassed() && aRequestShipping) { |
484 | 0 | const Sequence<PaymentShippingOption>& shippingOptions = aDetails.mShippingOptions.Value(); |
485 | 0 | nsTArray<nsString> seenIDs; |
486 | 0 | for (const PaymentShippingOption& shippingOption : shippingOptions) { |
487 | 0 | rv = IsValidCurrencyAmount(NS_LITERAL_STRING("details.shippingOptions"), |
488 | 0 | shippingOption.mAmount, |
489 | 0 | false, // isTotalItem |
490 | 0 | aErrorMsg); |
491 | 0 | if (NS_FAILED(rv)) { |
492 | 0 | return rv; |
493 | 0 | } |
494 | 0 | if (seenIDs.Contains(shippingOption.mId)) { |
495 | 0 | aErrorMsg.AssignLiteral("Duplicate shippingOption id '"); |
496 | 0 | aErrorMsg.Append(shippingOption.mId); |
497 | 0 | aErrorMsg.AppendLiteral("'"); |
498 | 0 | return NS_ERROR_TYPE_ERR; |
499 | 0 | } |
500 | 0 | seenIDs.AppendElement(shippingOption.mId); |
501 | 0 | } |
502 | 0 | } |
503 | 0 |
|
504 | 0 | // Check payment details modifiers |
505 | 0 | if (aDetails.mModifiers.WasPassed()) { |
506 | 0 | const Sequence<PaymentDetailsModifier>& modifiers = aDetails.mModifiers.Value(); |
507 | 0 | for (const PaymentDetailsModifier& modifier : modifiers) { |
508 | 0 | rv = IsValidPaymentMethodIdentifier(modifier.mSupportedMethods, aErrorMsg); |
509 | 0 | if (NS_FAILED(rv)) { |
510 | 0 | return rv; |
511 | 0 | } |
512 | 0 | rv = IsValidCurrencyAmount(NS_LITERAL_STRING("details.modifiers.total"), |
513 | 0 | modifier.mTotal.mAmount, |
514 | 0 | true, // isTotalItem |
515 | 0 | aErrorMsg); |
516 | 0 | if (NS_FAILED(rv)) { |
517 | 0 | return rv; |
518 | 0 | } |
519 | 0 | if (modifier.mAdditionalDisplayItems.WasPassed()) { |
520 | 0 | const Sequence<PaymentItem>& displayItems = modifier.mAdditionalDisplayItems.Value(); |
521 | 0 | for (const PaymentItem& displayItem : displayItems) { |
522 | 0 | rv = IsValidCurrencyAmount(displayItem.mLabel, |
523 | 0 | displayItem.mAmount, |
524 | 0 | false, // isTotalItem |
525 | 0 | aErrorMsg); |
526 | 0 | if (NS_FAILED(rv)) { |
527 | 0 | return rv; |
528 | 0 | } |
529 | 0 | } |
530 | 0 | } |
531 | 0 | } |
532 | 0 | } |
533 | 0 |
|
534 | 0 | return NS_OK; |
535 | 0 | } |
536 | | |
537 | | already_AddRefed<PaymentRequest> |
538 | | PaymentRequest::Constructor(const GlobalObject& aGlobal, |
539 | | const Sequence<PaymentMethodData>& aMethodData, |
540 | | const PaymentDetailsInit& aDetails, |
541 | | const PaymentOptions& aOptions, |
542 | | ErrorResult& aRv) |
543 | 0 | { |
544 | 0 | nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(aGlobal.GetAsSupports()); |
545 | 0 | if (!window) { |
546 | 0 | aRv.Throw(NS_ERROR_UNEXPECTED); |
547 | 0 | return nullptr; |
548 | 0 | } |
549 | 0 | |
550 | 0 | // the feature can only be used in an active document |
551 | 0 | if (!window->IsCurrentInnerWindow()) { |
552 | 0 | aRv.Throw(NS_ERROR_DOM_SECURITY_ERR); |
553 | 0 | return nullptr; |
554 | 0 | } |
555 | 0 | |
556 | 0 | |
557 | 0 | nsCOMPtr<nsIDocument> doc = window->GetExtantDoc(); |
558 | 0 | if (!doc) { |
559 | 0 | aRv.Throw(NS_ERROR_UNEXPECTED); |
560 | 0 | return nullptr; |
561 | 0 | } |
562 | 0 | |
563 | 0 | // Check if AllowPaymentRequest on the owner document |
564 | 0 | if (!doc->AllowPaymentRequest()) { |
565 | 0 | aRv.Throw(NS_ERROR_DOM_SECURITY_ERR); |
566 | 0 | return nullptr; |
567 | 0 | } |
568 | 0 | |
569 | 0 | // Get the top level principal |
570 | 0 | nsCOMPtr<nsIDocument> topLevelDoc = doc->GetTopLevelContentDocument(); |
571 | 0 | MOZ_ASSERT(topLevelDoc); |
572 | 0 | nsCOMPtr<nsIPrincipal> topLevelPrincipal = topLevelDoc->NodePrincipal(); |
573 | 0 |
|
574 | 0 | // Check payment methods and details |
575 | 0 | nsAutoString message; |
576 | 0 | nsresult rv = IsValidMethodData(aGlobal.Context(), |
577 | 0 | aMethodData, |
578 | 0 | message); |
579 | 0 | if (NS_FAILED(rv)) { |
580 | 0 | if (rv == NS_ERROR_TYPE_ERR) { |
581 | 0 | aRv.ThrowTypeError<MSG_ILLEGAL_TYPE_PR_CONSTRUCTOR>(message); |
582 | 0 | } else if (rv == NS_ERROR_RANGE_ERR) { |
583 | 0 | aRv.ThrowRangeError<MSG_ILLEGAL_RANGE_PR_CONSTRUCTOR>(message); |
584 | 0 | } |
585 | 0 | return nullptr; |
586 | 0 | } |
587 | 0 | rv = IsValidDetailsInit(aDetails, aOptions.mRequestShipping, message); |
588 | 0 | if (NS_FAILED(rv)) { |
589 | 0 | if (rv == NS_ERROR_TYPE_ERR) { |
590 | 0 | aRv.ThrowTypeError<MSG_ILLEGAL_TYPE_PR_CONSTRUCTOR>(message); |
591 | 0 | } else if (rv == NS_ERROR_RANGE_ERR) { |
592 | 0 | aRv.ThrowRangeError<MSG_ILLEGAL_RANGE_PR_CONSTRUCTOR>(message); |
593 | 0 | } |
594 | 0 | return nullptr; |
595 | 0 | } |
596 | 0 |
|
597 | 0 | RefPtr<PaymentRequestManager> manager = PaymentRequestManager::GetSingleton(); |
598 | 0 | if (NS_WARN_IF(!manager)) { |
599 | 0 | return nullptr; |
600 | 0 | } |
601 | 0 | |
602 | 0 | // Create PaymentRequest and set its |mId| |
603 | 0 | RefPtr<PaymentRequest> request; |
604 | 0 | rv = manager->CreatePayment(aGlobal.Context(), window, topLevelPrincipal, aMethodData, |
605 | 0 | aDetails, aOptions, getter_AddRefs(request)); |
606 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
607 | 0 | aRv.Throw(NS_ERROR_DOM_TYPE_ERR); |
608 | 0 | return nullptr; |
609 | 0 | } |
610 | 0 | return request.forget(); |
611 | 0 | } |
612 | | |
613 | | already_AddRefed<PaymentRequest> |
614 | | PaymentRequest::CreatePaymentRequest(nsPIDOMWindowInner* aWindow, nsresult& aRv) |
615 | 0 | { |
616 | 0 | // Generate a unique id for identification |
617 | 0 | nsID uuid; |
618 | 0 | aRv = nsContentUtils::GenerateUUIDInPlace(uuid); |
619 | 0 | if (NS_WARN_IF(NS_FAILED(aRv))) { |
620 | 0 | return nullptr; |
621 | 0 | } |
622 | 0 | |
623 | 0 | // Build a string in {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx} format |
624 | 0 | char buffer[NSID_LENGTH]; |
625 | 0 | uuid.ToProvidedString(buffer); |
626 | 0 |
|
627 | 0 | // Remove {} and the null terminator |
628 | 0 | nsAutoString id; |
629 | 0 | id.AssignASCII(&buffer[1], NSID_LENGTH - 3); |
630 | 0 |
|
631 | 0 | // Create payment request with generated id |
632 | 0 | RefPtr<PaymentRequest> request = new PaymentRequest(aWindow, id); |
633 | 0 | return request.forget(); |
634 | 0 | } |
635 | | |
636 | | PaymentRequest::PaymentRequest(nsPIDOMWindowInner* aWindow, const nsAString& aInternalId) |
637 | | : DOMEventTargetHelper(aWindow) |
638 | | , mInternalId(aInternalId) |
639 | | , mShippingAddress(nullptr) |
640 | | , mUpdating(false) |
641 | | , mRequestShipping(false) |
642 | | , mDeferredShow(false) |
643 | | , mUpdateError(NS_OK) |
644 | | , mState(eCreated) |
645 | | , mIPC(nullptr) |
646 | 0 | { |
647 | 0 | MOZ_ASSERT(aWindow); |
648 | 0 | RegisterActivityObserver(); |
649 | 0 | } |
650 | | |
651 | | already_AddRefed<Promise> |
652 | | PaymentRequest::CanMakePayment(ErrorResult& aRv) |
653 | 0 | { |
654 | 0 | if (mState != eCreated) { |
655 | 0 | aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); |
656 | 0 | return nullptr; |
657 | 0 | } |
658 | 0 | |
659 | 0 | if (mResultPromise) { |
660 | 0 | // XXX This doesn't match the spec but does match Chromium. |
661 | 0 | aRv.Throw(NS_ERROR_DOM_NOT_ALLOWED_ERR); |
662 | 0 | return nullptr; |
663 | 0 | } |
664 | 0 | |
665 | 0 | nsIGlobalObject* global = GetOwnerGlobal(); |
666 | 0 | ErrorResult result; |
667 | 0 | RefPtr<Promise> promise = Promise::Create(global, result); |
668 | 0 | if (result.Failed()) { |
669 | 0 | aRv.Throw(NS_ERROR_FAILURE); |
670 | 0 | return nullptr; |
671 | 0 | } |
672 | 0 | |
673 | 0 | RefPtr<PaymentRequestManager> manager = PaymentRequestManager::GetSingleton(); |
674 | 0 | MOZ_ASSERT(manager); |
675 | 0 | nsresult rv = manager->CanMakePayment(this); |
676 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
677 | 0 | promise->MaybeReject(NS_ERROR_FAILURE); |
678 | 0 | return promise.forget(); |
679 | 0 | } |
680 | 0 | mResultPromise = promise; |
681 | 0 | return promise.forget(); |
682 | 0 | } |
683 | | |
684 | | void |
685 | | PaymentRequest::RespondCanMakePayment(bool aResult) |
686 | 0 | { |
687 | 0 | MOZ_ASSERT(mResultPromise); |
688 | 0 | mResultPromise->MaybeResolve(aResult); |
689 | 0 | mResultPromise = nullptr; |
690 | 0 | } |
691 | | |
692 | | already_AddRefed<Promise> |
693 | | PaymentRequest::Show(const Optional<OwningNonNull<Promise>>& aDetailsPromise, |
694 | | ErrorResult& aRv) |
695 | 0 | { |
696 | 0 | nsIGlobalObject* global = GetOwnerGlobal(); |
697 | 0 | nsCOMPtr<nsPIDOMWindowInner> win = do_QueryInterface(global); |
698 | 0 | MOZ_ASSERT(win); |
699 | 0 | nsIDocument* doc = win->GetExtantDoc(); |
700 | 0 |
|
701 | 0 | if (!EventStateManager::IsHandlingUserInput()) { |
702 | 0 | nsString msg = NS_LITERAL_STRING("User activation is now required to call PaymentRequest.show()"); |
703 | 0 | nsContentUtils::ReportToConsoleNonLocalized(msg, |
704 | 0 | nsIScriptError::warningFlag, |
705 | 0 | NS_LITERAL_CSTRING("Security"), |
706 | 0 | doc); |
707 | 0 | if (StaticPrefs::dom_payments_request_user_interaction_required()) { |
708 | 0 | aRv.Throw(NS_ERROR_DOM_SECURITY_ERR); |
709 | 0 | return nullptr; |
710 | 0 | } |
711 | 0 | } |
712 | 0 | |
713 | 0 | if (!doc || !doc->IsCurrentActiveDocument()) { |
714 | 0 | aRv.Throw(NS_ERROR_DOM_ABORT_ERR); |
715 | 0 | return nullptr; |
716 | 0 | } |
717 | 0 | |
718 | 0 | if (mState != eCreated) { |
719 | 0 | aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); |
720 | 0 | return nullptr; |
721 | 0 | } |
722 | 0 | |
723 | 0 | ErrorResult result; |
724 | 0 | RefPtr<Promise> promise = Promise::Create(global, result); |
725 | 0 | if (result.Failed()) { |
726 | 0 | mState = eClosed; |
727 | 0 | aRv.Throw(NS_ERROR_FAILURE); |
728 | 0 | return nullptr; |
729 | 0 | } |
730 | 0 | |
731 | 0 | if (aDetailsPromise.WasPassed()) { |
732 | 0 | aDetailsPromise.Value().AppendNativeHandler(this); |
733 | 0 | mUpdating = true; |
734 | 0 | mDeferredShow = true; |
735 | 0 | } |
736 | 0 |
|
737 | 0 | RefPtr<PaymentRequestManager> manager = PaymentRequestManager::GetSingleton(); |
738 | 0 | MOZ_ASSERT(manager); |
739 | 0 | nsresult rv = manager->ShowPayment(this); |
740 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
741 | 0 | if (rv == NS_ERROR_ABORT) { |
742 | 0 | promise->MaybeReject(NS_ERROR_DOM_ABORT_ERR); |
743 | 0 | } else { |
744 | 0 | promise->MaybeReject(NS_ERROR_DOM_NOT_ALLOWED_ERR); |
745 | 0 | } |
746 | 0 | mState = eClosed; |
747 | 0 | return promise.forget(); |
748 | 0 | } |
749 | 0 |
|
750 | 0 | mAcceptPromise = promise; |
751 | 0 | mState = eInteractive; |
752 | 0 | return promise.forget(); |
753 | 0 | } |
754 | | |
755 | | void |
756 | | PaymentRequest::RejectShowPayment(nsresult aRejectReason) |
757 | 0 | { |
758 | 0 | MOZ_ASSERT(mAcceptPromise || mResponse); |
759 | 0 | MOZ_ASSERT(mState == eInteractive); |
760 | 0 |
|
761 | 0 | if (mResponse) { |
762 | 0 | mResponse->RejectRetry(aRejectReason); |
763 | 0 | } else { |
764 | 0 | mAcceptPromise->MaybeReject(aRejectReason); |
765 | 0 | } |
766 | 0 | mState = eClosed; |
767 | 0 | mAcceptPromise = nullptr; |
768 | 0 | } |
769 | | |
770 | | void |
771 | | PaymentRequest::RespondShowPayment(const nsAString& aMethodName, |
772 | | const nsAString& aDetails, |
773 | | const nsAString& aPayerName, |
774 | | const nsAString& aPayerEmail, |
775 | | const nsAString& aPayerPhone, |
776 | | nsresult aRv) |
777 | 0 | { |
778 | 0 | MOZ_ASSERT(mAcceptPromise || mResponse); |
779 | 0 | MOZ_ASSERT(mState == eInteractive); |
780 | 0 |
|
781 | 0 | if (NS_FAILED(aRv)) { |
782 | 0 | RejectShowPayment(aRv); |
783 | 0 | return; |
784 | 0 | } |
785 | 0 | |
786 | 0 | // https://github.com/w3c/payment-request/issues/692 |
787 | 0 | mShippingAddress.swap(mFullShippingAddress); |
788 | 0 | mFullShippingAddress = nullptr; |
789 | 0 |
|
790 | 0 | if (mResponse) { |
791 | 0 | mResponse->RespondRetry(aMethodName, mShippingOption, mShippingAddress, |
792 | 0 | aDetails, aPayerName, aPayerEmail, aPayerPhone); |
793 | 0 | } else { |
794 | 0 | RefPtr<PaymentResponse> paymentResponse = |
795 | 0 | new PaymentResponse(GetOwner(), this, mId, aMethodName, |
796 | 0 | mShippingOption, mShippingAddress, aDetails, |
797 | 0 | aPayerName, aPayerEmail, aPayerPhone); |
798 | 0 | mResponse = paymentResponse; |
799 | 0 | mAcceptPromise->MaybeResolve(paymentResponse); |
800 | 0 | } |
801 | 0 |
|
802 | 0 | mState = eClosed; |
803 | 0 | mAcceptPromise = nullptr; |
804 | 0 | } |
805 | | |
806 | | void |
807 | | PaymentRequest::RespondComplete() |
808 | 0 | { |
809 | 0 | MOZ_ASSERT(mResponse); |
810 | 0 | mResponse->RespondComplete(); |
811 | 0 | } |
812 | | |
813 | | already_AddRefed<Promise> |
814 | | PaymentRequest::Abort(ErrorResult& aRv) |
815 | 0 | { |
816 | 0 | if (mState != eInteractive) { |
817 | 0 | aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); |
818 | 0 | return nullptr; |
819 | 0 | } |
820 | 0 | |
821 | 0 | if (mAbortPromise) { |
822 | 0 | aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); |
823 | 0 | return nullptr; |
824 | 0 | } |
825 | 0 | |
826 | 0 | nsIGlobalObject* global = GetOwnerGlobal(); |
827 | 0 | ErrorResult result; |
828 | 0 | RefPtr<Promise> promise = Promise::Create(global, result); |
829 | 0 | if (result.Failed()) { |
830 | 0 | aRv.Throw(NS_ERROR_FAILURE); |
831 | 0 | return nullptr; |
832 | 0 | } |
833 | 0 | |
834 | 0 | RefPtr<PaymentRequestManager> manager = PaymentRequestManager::GetSingleton(); |
835 | 0 | MOZ_ASSERT(manager); |
836 | 0 | // It's possible to be called between show and its promise resolving. |
837 | 0 | nsresult rv = manager->AbortPayment(this, mDeferredShow); |
838 | 0 | mDeferredShow = false; |
839 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
840 | 0 | aRv.Throw(NS_ERROR_FAILURE); |
841 | 0 | return nullptr; |
842 | 0 | } |
843 | 0 | |
844 | 0 | mAbortPromise = promise; |
845 | 0 | return promise.forget(); |
846 | 0 | } |
847 | | |
848 | | void |
849 | | PaymentRequest::RespondAbortPayment(bool aSuccess) |
850 | 0 | { |
851 | 0 | // Check whether we are aborting the update: |
852 | 0 | // |
853 | 0 | // - If |mUpdateError| is not NS_OK, we are aborting the update as |
854 | 0 | // |mUpdateError| was set in method |AbortUpdate|. |
855 | 0 | // => Reject |mAcceptPromise| and reset |mUpdateError| to complete |
856 | 0 | // the action, regardless of |aSuccess|. |
857 | 0 | // |
858 | 0 | // - Otherwise, we are handling |Abort| method call from merchant. |
859 | 0 | // => Resolve/Reject |mAbortPromise| based on |aSuccess|. |
860 | 0 | if (NS_FAILED(mUpdateError)) { |
861 | 0 | // Respond show with mUpdateError, set mUpdating to false. |
862 | 0 | mUpdating = false; |
863 | 0 | RespondShowPayment(EmptyString(), EmptyString(), EmptyString(), |
864 | 0 | EmptyString(), EmptyString(), mUpdateError); |
865 | 0 | mUpdateError = NS_OK; |
866 | 0 | return; |
867 | 0 | } |
868 | 0 | |
869 | 0 | MOZ_ASSERT(mAbortPromise); |
870 | 0 | MOZ_ASSERT(mState == eInteractive); |
871 | 0 |
|
872 | 0 | if (aSuccess) { |
873 | 0 | mAbortPromise->MaybeResolve(JS::UndefinedHandleValue); |
874 | 0 | mAbortPromise = nullptr; |
875 | 0 | RejectShowPayment(NS_ERROR_DOM_ABORT_ERR); |
876 | 0 | } else { |
877 | 0 | mAbortPromise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR); |
878 | 0 | mAbortPromise = nullptr; |
879 | 0 | } |
880 | 0 | } |
881 | | |
882 | | nsresult |
883 | | PaymentRequest::UpdatePayment(JSContext* aCx, const PaymentDetailsUpdate& aDetails, |
884 | | bool aDeferredShow) |
885 | 0 | { |
886 | 0 | NS_ENSURE_ARG_POINTER(aCx); |
887 | 0 | if (mState != eInteractive) { |
888 | 0 | return NS_ERROR_DOM_INVALID_STATE_ERR; |
889 | 0 | } |
890 | 0 | RefPtr<PaymentRequestManager> manager = PaymentRequestManager::GetSingleton(); |
891 | 0 | if (NS_WARN_IF(!manager)) { |
892 | 0 | return NS_ERROR_FAILURE; |
893 | 0 | } |
894 | 0 | nsresult rv = manager->UpdatePayment(aCx, this, aDetails, mRequestShipping, |
895 | 0 | aDeferredShow); |
896 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
897 | 0 | return rv; |
898 | 0 | } |
899 | 0 | return NS_OK; |
900 | 0 | } |
901 | | |
902 | | void |
903 | | PaymentRequest::AbortUpdate(nsresult aRv, bool aDeferredShow) |
904 | 0 | { |
905 | 0 | MOZ_ASSERT(NS_FAILED(aRv)); |
906 | 0 |
|
907 | 0 | if (mState != eInteractive) { |
908 | 0 | return; |
909 | 0 | } |
910 | 0 | // Close down any remaining user interface. |
911 | 0 | RefPtr<PaymentRequestManager> manager = PaymentRequestManager::GetSingleton(); |
912 | 0 | MOZ_ASSERT(manager); |
913 | 0 | nsresult rv = manager->AbortPayment(this, aDeferredShow); |
914 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
915 | 0 | return; |
916 | 0 | } |
917 | 0 | |
918 | 0 | // Remember update error |aRv| and do the following steps in RespondShowPayment. |
919 | 0 | // 1. Set target.state to closed |
920 | 0 | // 2. Reject the promise target.acceptPromise with exception "aRv" |
921 | 0 | // 3. Abort the algorithm with update error |
922 | 0 | mUpdateError = aRv; |
923 | 0 | } |
924 | | |
925 | | nsresult |
926 | | PaymentRequest::RetryPayment(JSContext* aCx, const PaymentValidationErrors& aErrors) |
927 | 0 | { |
928 | 0 | if (mState == eInteractive) { |
929 | 0 | return NS_ERROR_DOM_INVALID_STATE_ERR; |
930 | 0 | } |
931 | 0 | RefPtr<PaymentRequestManager> manager = PaymentRequestManager::GetSingleton(); |
932 | 0 | MOZ_ASSERT(manager); |
933 | 0 | nsresult rv = manager->RetryPayment(aCx, this, aErrors); |
934 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
935 | 0 | return rv; |
936 | 0 | } |
937 | 0 | mState = eInteractive; |
938 | 0 | return NS_OK; |
939 | 0 | } |
940 | | |
941 | | void |
942 | | PaymentRequest::GetId(nsAString& aRetVal) const |
943 | 0 | { |
944 | 0 | aRetVal = mId; |
945 | 0 | } |
946 | | |
947 | | void |
948 | | PaymentRequest::GetInternalId(nsAString& aRetVal) |
949 | 0 | { |
950 | 0 | aRetVal = mInternalId; |
951 | 0 | } |
952 | | |
953 | | void |
954 | | PaymentRequest::SetId(const nsAString& aId) |
955 | 0 | { |
956 | 0 | mId = aId; |
957 | 0 | } |
958 | | |
959 | | bool |
960 | | PaymentRequest::Equals(const nsAString& aInternalId) const |
961 | 0 | { |
962 | 0 | return mInternalId.Equals(aInternalId); |
963 | 0 | } |
964 | | |
965 | | bool |
966 | | PaymentRequest::ReadyForUpdate() |
967 | 0 | { |
968 | 0 | return mState == eInteractive && !mUpdating; |
969 | 0 | } |
970 | | |
971 | | void |
972 | | PaymentRequest::SetUpdating(bool aUpdating) |
973 | 0 | { |
974 | 0 | mUpdating = aUpdating; |
975 | 0 | } |
976 | | |
977 | | already_AddRefed<PaymentResponse> |
978 | | PaymentRequest::GetResponse() const |
979 | 0 | { |
980 | 0 | RefPtr<PaymentResponse> response = mResponse; |
981 | 0 | return response.forget(); |
982 | 0 | } |
983 | | |
984 | | nsresult |
985 | | PaymentRequest::DispatchUpdateEvent(const nsAString& aType) |
986 | 0 | { |
987 | 0 | MOZ_ASSERT(ReadyForUpdate()); |
988 | 0 |
|
989 | 0 | PaymentRequestUpdateEventInit init; |
990 | 0 | init.mBubbles = false; |
991 | 0 | init.mCancelable = false; |
992 | 0 |
|
993 | 0 | RefPtr<PaymentRequestUpdateEvent> event = |
994 | 0 | PaymentRequestUpdateEvent::Constructor(this, aType, init); |
995 | 0 | event->SetTrusted(true); |
996 | 0 | event->SetRequest(this); |
997 | 0 |
|
998 | 0 | ErrorResult rv; |
999 | 0 | DispatchEvent(*event, rv); |
1000 | 0 | return rv.StealNSResult(); |
1001 | 0 | } |
1002 | | |
1003 | | nsresult |
1004 | | PaymentRequest::DispatchMerchantValidationEvent(const nsAString& aType) |
1005 | 0 | { |
1006 | 0 | MOZ_ASSERT(ReadyForUpdate()); |
1007 | 0 |
|
1008 | 0 | MerchantValidationEventInit init; |
1009 | 0 | init.mBubbles = false; |
1010 | 0 | init.mCancelable = false; |
1011 | 0 | init.mValidationURL = EmptyString(); |
1012 | 0 |
|
1013 | 0 | ErrorResult rv; |
1014 | 0 | RefPtr<MerchantValidationEvent> event = |
1015 | 0 | MerchantValidationEvent::Constructor(this, aType, init, rv); |
1016 | 0 | if (rv.Failed()) { |
1017 | 0 | return rv.StealNSResult(); |
1018 | 0 | } |
1019 | 0 | event->SetTrusted(true); |
1020 | 0 | event->SetRequest(this); |
1021 | 0 |
|
1022 | 0 | DispatchEvent(*event, rv); |
1023 | 0 | return rv.StealNSResult(); |
1024 | 0 | } |
1025 | | |
1026 | | already_AddRefed<PaymentAddress> |
1027 | | PaymentRequest::GetShippingAddress() const |
1028 | 0 | { |
1029 | 0 | RefPtr<PaymentAddress> address = mShippingAddress; |
1030 | 0 | return address.forget(); |
1031 | 0 | } |
1032 | | |
1033 | | nsresult |
1034 | | PaymentRequest::UpdateShippingAddress(const nsAString& aCountry, |
1035 | | const nsTArray<nsString>& aAddressLine, |
1036 | | const nsAString& aRegion, |
1037 | | const nsAString& aCity, |
1038 | | const nsAString& aDependentLocality, |
1039 | | const nsAString& aPostalCode, |
1040 | | const nsAString& aSortingCode, |
1041 | | const nsAString& aOrganization, |
1042 | | const nsAString& aRecipient, |
1043 | | const nsAString& aPhone) |
1044 | 0 | { |
1045 | 0 | nsTArray<nsString> emptyArray; |
1046 | 0 | mShippingAddress = new PaymentAddress(GetOwner(), aCountry, emptyArray, |
1047 | 0 | aRegion, aCity, aDependentLocality, |
1048 | 0 | aPostalCode, aSortingCode, |
1049 | 0 | EmptyString(), EmptyString(), EmptyString()); |
1050 | 0 | mFullShippingAddress = new PaymentAddress(GetOwner(), aCountry, aAddressLine, |
1051 | 0 | aRegion, aCity, aDependentLocality, |
1052 | 0 | aPostalCode, aSortingCode, |
1053 | 0 | aOrganization, aRecipient, aPhone); |
1054 | 0 | // Fire shippingaddresschange event |
1055 | 0 | return DispatchUpdateEvent(NS_LITERAL_STRING("shippingaddresschange")); |
1056 | 0 | } |
1057 | | |
1058 | | void |
1059 | | PaymentRequest::SetShippingOption(const nsAString& aShippingOption) |
1060 | 0 | { |
1061 | 0 | mShippingOption = aShippingOption; |
1062 | 0 | } |
1063 | | |
1064 | | void |
1065 | | PaymentRequest::GetShippingOption(nsAString& aRetVal) const |
1066 | 0 | { |
1067 | 0 | aRetVal = mShippingOption; |
1068 | 0 | } |
1069 | | |
1070 | | nsresult |
1071 | | PaymentRequest::UpdateShippingOption(const nsAString& aShippingOption) |
1072 | 0 | { |
1073 | 0 | mShippingOption = aShippingOption; |
1074 | 0 |
|
1075 | 0 | // Fire shippingaddresschange event |
1076 | 0 | return DispatchUpdateEvent(NS_LITERAL_STRING("shippingoptionchange")); |
1077 | 0 | } |
1078 | | |
1079 | | void |
1080 | | PaymentRequest::SetShippingType(const Nullable<PaymentShippingType>& aShippingType) |
1081 | 0 | { |
1082 | 0 | mShippingType = aShippingType; |
1083 | 0 | } |
1084 | | |
1085 | | Nullable<PaymentShippingType> |
1086 | | PaymentRequest::GetShippingType() const |
1087 | 0 | { |
1088 | 0 | return mShippingType; |
1089 | 0 | } |
1090 | | |
1091 | | void PaymentRequest::GetOptions(PaymentOptions& aRetVal) const |
1092 | 0 | { |
1093 | 0 | aRetVal = mOptions; |
1094 | 0 | } |
1095 | | |
1096 | | void PaymentRequest::SetOptions(const PaymentOptions& aOptions) |
1097 | 0 | { |
1098 | 0 | mOptions = aOptions; |
1099 | 0 | } |
1100 | | |
1101 | | void |
1102 | | PaymentRequest::ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) |
1103 | 0 | { |
1104 | 0 | MOZ_ASSERT(aCx); |
1105 | 0 | mUpdating = false; |
1106 | 0 | if (NS_WARN_IF(!aValue.isObject())) { |
1107 | 0 | return; |
1108 | 0 | } |
1109 | 0 | |
1110 | 0 | // Converting value to a PaymentDetailsUpdate dictionary |
1111 | 0 | PaymentDetailsUpdate details; |
1112 | 0 | if (!details.Init(aCx, aValue)) { |
1113 | 0 | AbortUpdate(NS_ERROR_DOM_TYPE_ERR, mDeferredShow); |
1114 | 0 | JS_ClearPendingException(aCx); |
1115 | 0 | return; |
1116 | 0 | } |
1117 | 0 | |
1118 | 0 | nsresult rv = IsValidDetailsUpdate(details, mRequestShipping); |
1119 | 0 | if (NS_FAILED(rv)) { |
1120 | 0 | AbortUpdate(rv, mDeferredShow); |
1121 | 0 | return; |
1122 | 0 | } |
1123 | 0 | |
1124 | 0 | // Update the PaymentRequest with the new details |
1125 | 0 | if (NS_FAILED(UpdatePayment(aCx, details, mDeferredShow))) { |
1126 | 0 | AbortUpdate(NS_ERROR_DOM_ABORT_ERR, mDeferredShow); |
1127 | 0 | return; |
1128 | 0 | } |
1129 | 0 | |
1130 | 0 | mDeferredShow = false; |
1131 | 0 | } |
1132 | | |
1133 | | void |
1134 | | PaymentRequest::RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) |
1135 | 0 | { |
1136 | 0 | MOZ_ASSERT(mDeferredShow); |
1137 | 0 | mUpdating = false; |
1138 | 0 | AbortUpdate(NS_ERROR_DOM_ABORT_ERR, mDeferredShow); |
1139 | 0 | mDeferredShow = false; |
1140 | 0 | } |
1141 | | |
1142 | | void |
1143 | | PaymentRequest::RegisterActivityObserver() |
1144 | 0 | { |
1145 | 0 | if (nsPIDOMWindowInner* window = GetOwner()) { |
1146 | 0 | nsCOMPtr<nsIDocument> doc = window->GetExtantDoc(); |
1147 | 0 | if (doc) { |
1148 | 0 | doc->RegisterActivityObserver( |
1149 | 0 | NS_ISUPPORTS_CAST(nsIDocumentActivity*, this)); |
1150 | 0 | } |
1151 | 0 | } |
1152 | 0 | } |
1153 | | |
1154 | | void |
1155 | | PaymentRequest::UnregisterActivityObserver() |
1156 | 0 | { |
1157 | 0 | if (nsPIDOMWindowInner* window = GetOwner()) { |
1158 | 0 | nsCOMPtr<nsIDocument> doc = window->GetExtantDoc(); |
1159 | 0 | if (doc) { |
1160 | 0 | doc->UnregisterActivityObserver( |
1161 | 0 | NS_ISUPPORTS_CAST(nsIDocumentActivity*, this)); |
1162 | 0 | } |
1163 | 0 | } |
1164 | 0 | } |
1165 | | |
1166 | | void |
1167 | | PaymentRequest::NotifyOwnerDocumentActivityChanged() |
1168 | 0 | { |
1169 | 0 | nsPIDOMWindowInner* window = GetOwner(); |
1170 | 0 | NS_ENSURE_TRUE_VOID(window); |
1171 | 0 | nsIDocument* doc = window->GetExtantDoc(); |
1172 | 0 | NS_ENSURE_TRUE_VOID(doc); |
1173 | 0 |
|
1174 | 0 | if (!doc->IsCurrentActiveDocument()) { |
1175 | 0 | RefPtr<PaymentRequestManager> mgr = PaymentRequestManager::GetSingleton(); |
1176 | 0 | mgr->ClosePayment(this); |
1177 | 0 | } |
1178 | 0 | } |
1179 | | |
1180 | | PaymentRequest::~PaymentRequest() |
1181 | 0 | { |
1182 | 0 | if (mIPC) { |
1183 | 0 | // If we're being destroyed, the PaymentRequestManager isn't holding any |
1184 | 0 | // references to us and we can't be waiting for any replies. |
1185 | 0 | mIPC->MaybeDelete(false); |
1186 | 0 | } |
1187 | 0 | UnregisterActivityObserver(); |
1188 | 0 | } |
1189 | | |
1190 | | JSObject* |
1191 | | PaymentRequest::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) |
1192 | 0 | { |
1193 | 0 | return PaymentRequest_Binding::Wrap(aCx, this, aGivenProto); |
1194 | 0 | } |
1195 | | |
1196 | | } // namespace dom |
1197 | | } // namespace mozilla |