Merge lp:~dobey/pay-service/merge-payui into lp:pay-service
- merge-payui
- Merge into trunk.15.10
Proposed by
dobey
Status: | Merged |
---|---|
Approved by: | dobey |
Approved revision: | 121 |
Merged at revision: | 96 |
Proposed branch: | lp:~dobey/pay-service/merge-payui |
Merge into: | lp:pay-service |
Diff against target: |
10389 lines (+9058/-472) 107 files modified
.bzrignore (+3/-2) CMakeLists.txt (+1/-2) HACKING (+85/-0) debian/control (+22/-4) debian/copyright (+2/-2) debian/pay-service.click-hook (+0/-4) debian/pay-service.install (+2/-2) debian/pay-ui.install (+3/-0) debian/rules (+1/-1) pay-ui/CMakeLists.txt (+21/-0) pay-ui/app/CMakeLists.txt (+6/-0) pay-ui/app/components/AlertDialog.qml (+31/-0) pay-ui/app/components/BeforeUnloadDialog.qml (+37/-0) pay-ui/app/components/CMakeLists.txt (+6/-0) pay-ui/app/components/ConfirmDialog.qml (+37/-0) pay-ui/app/components/ModalDialog.qml (+32/-0) pay-ui/app/components/PromptDialog.qml (+51/-0) pay-ui/app/components/SecurityCertificatePopover.qml (+145/-0) pay-ui/app/payui.qml (+459/-0) pay-ui/app/tests/unit/js/unit_test.js (+17/-0) pay-ui/app/tests/unit/tst_checkoutpage.qml (+73/-0) pay-ui/app/tests/unit/tst_purchasewebkit.qml (+57/-0) pay-ui/app/ui/CMakeLists.txt (+6/-0) pay-ui/app/ui/CheckoutPage.qml (+404/-0) pay-ui/app/ui/ErrorDialog.qml (+67/-0) pay-ui/app/ui/UbuntuPurchaseWebkit.qml (+107/-0) pay-ui/backend/CMakeLists.txt (+49/-0) pay-ui/backend/modules/payui/backend.cpp (+23/-0) pay-ui/backend/modules/payui/backend.h (+33/-0) pay-ui/backend/modules/payui/certificateadapter.cpp (+46/-0) pay-ui/backend/modules/payui/certificateadapter.h (+51/-0) pay-ui/backend/modules/payui/credentials_service.cpp (+81/-0) pay-ui/backend/modules/payui/credentials_service.h (+53/-0) pay-ui/backend/modules/payui/network.cpp (+512/-0) pay-ui/backend/modules/payui/network.h (+136/-0) pay-ui/backend/modules/payui/oxideconstants.cpp (+19/-0) pay-ui/backend/modules/payui/oxideconstants.h (+53/-0) pay-ui/backend/modules/payui/pay_info.cpp (+37/-0) pay-ui/backend/modules/payui/pay_info.h (+73/-0) pay-ui/backend/modules/payui/purchase.cpp (+154/-0) pay-ui/backend/modules/payui/purchase.h (+59/-0) pay-ui/backend/modules/payui/qmldir (+2/-0) pay-ui/backend/tests/CMakeLists.txt (+30/-0) pay-ui/backend/tests/mock_click_server.py (+162/-0) pay-ui/backend/tests/test_network.cpp (+317/-0) pay-ui/pay-ui.in (+19/-0) pay-ui/tests/autopilot/pay_ui/__init__.py (+106/-0) pay-ui/tests/autopilot/pay_ui/tests/__init__.py (+77/-0) pay-ui/tests/autopilot/pay_ui/tests/mock_server.py (+337/-0) pay-ui/tests/autopilot/pay_ui/tests/test_pay_ui.py (+186/-0) pay-ui/tests/autopilot/run_autopilot (+1/-0) po/CMakeLists.txt (+1/-1) po/POTFILES.in (+8/-0) po/aa.po (+106/-0) po/am.po (+106/-0) po/ast.po (+106/-0) po/az.po (+106/-0) po/br.po (+106/-0) po/ca.po (+106/-0) po/ca@valencia.po (+106/-0) po/cs.po (+106/-0) po/cy.po (+106/-0) po/de.po (+106/-0) po/el.po (+106/-0) po/en_AU.po (+106/-0) po/en_GB.po (+106/-0) po/es.po (+106/-0) po/eu.po (+106/-0) po/fa.po (+106/-0) po/fi.po (+106/-0) po/fr.po (+106/-0) po/gd.po (+107/-0) po/gl.po (+106/-0) po/he.po (+106/-0) po/hu.po (+106/-0) po/id.po (+106/-0) po/is.po (+106/-0) po/it.po (+106/-0) po/ja.po (+106/-0) po/ko.po (+106/-0) po/nb.po (+106/-0) po/nl.po (+106/-0) po/pay-service.pot (+82/-2) po/pl.po (+106/-0) po/pt.po (+106/-0) po/pt_BR.po (+106/-0) po/ro.po (+106/-0) po/ru.po (+106/-0) po/sl.po (+106/-0) po/sq.po (+106/-0) po/sr.po (+106/-0) po/sv.po (+107/-0) po/ug.po (+106/-0) po/uk.po (+106/-0) po/vi.po (+106/-0) po/zh_CN.po (+106/-0) po/zh_TW.po (+106/-0) service-ng/src/launchpad.net/go-mir/mir/fakes/prompt_session.go (+5/-1) service-ng/src/launchpad.net/go-mir/mir/mir_prompt_session_helper.c (+41/-0) service-ng/src/launchpad.net/go-mir/mir/mir_prompt_session_helper.h (+28/-0) service-ng/src/launchpad.net/go-mir/mir/prompt_session.go (+14/-1) service-ng/src/pay-service-2/service/pay_ui.go (+18/-83) service-ng/src/pay-service-2/service/pay_ui_test.go (+8/-199) service-ng/test-service.sh (+2/-2) tests/setup-staging.sh (+0/-8) ual-helper/CMakeLists.txt (+0/-12) ual-helper/exec-tool.c (+0/-146) |
To merge this branch: | bzr merge lp:~dobey/pay-service/merge-payui |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Charles Kerr (community) | Approve | ||
PS Jenkins bot (community) | continuous-integration | Needs Fixing | |
Review via email: mp+288514@code.launchpad.net |
Commit message
Merge pay-ui into pay-service.
Description of the change
To post a comment you must log in.
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : | # |
review:
Needs Fixing
(continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:119
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild:
http://
review:
Needs Fixing
(continuous-integration)
Revision history for this message
Charles Kerr (charlesk) wrote : | # |
LGTM.
Reviewed the glue code, eg the packaging changes, pay-ui launcher changes etc. I didn't review the code in pay-ui/ since it's not new & is just being merged over from a different repo.
review:
Approve
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file '.bzrignore' |
2 | --- .bzrignore 2015-12-11 19:49:20 +0000 |
3 | +++ .bzrignore 2016-03-11 14:49:24 +0000 |
4 | @@ -8,7 +8,8 @@ |
5 | *.so.* |
6 | *.mo |
7 | *.service |
8 | - |
9 | - |
10 | +pay-ui/pay-ui |
11 | + |
12 | +__pycache__ |
13 | build/ |
14 | builddir/ |
15 | |
16 | === modified file 'CMakeLists.txt' |
17 | --- CMakeLists.txt 2015-12-13 02:28:18 +0000 |
18 | +++ CMakeLists.txt 2016-03-11 14:49:24 +0000 |
19 | @@ -35,7 +35,6 @@ |
20 | |
21 | pkg_check_modules (SERVICE_DEPS REQUIRED |
22 | Qt5Core |
23 | - click-0.4 |
24 | dbus-cpp |
25 | dbustest-1 |
26 | gio-2.0 |
27 | @@ -87,12 +86,12 @@ |
28 | add_subdirectory(common) |
29 | add_subdirectory(libpay) |
30 | add_subdirectory(service-ng) |
31 | -add_subdirectory(ual-helper) |
32 | add_subdirectory(data) |
33 | if (${enable_tests}) |
34 | add_subdirectory(tests) |
35 | endif () |
36 | add_subdirectory(pay-test-app) |
37 | +add_subdirectory(pay-ui) |
38 | add_subdirectory(po) |
39 | |
40 | ## |
41 | |
42 | === added file 'HACKING' |
43 | --- HACKING 1970-01-01 00:00:00 +0000 |
44 | +++ HACKING 2016-03-11 14:49:24 +0000 |
45 | @@ -0,0 +1,85 @@ |
46 | +pay-service hacking guide |
47 | +=============================== |
48 | + |
49 | +Getting pay-service |
50 | +------------------------- |
51 | + |
52 | +To get the main branch of pay-service: |
53 | + |
54 | + $ bzr branch lp:pay-service |
55 | + |
56 | + |
57 | +Getting dependencies |
58 | +-------------------- |
59 | + |
60 | +To succesfully build pay-service extra packages are required: |
61 | + |
62 | + $ sudo apt-get build-dep pay-service |
63 | + |
64 | + |
65 | +Building pay-service |
66 | +------------------ |
67 | + |
68 | +This app is built using cmake. Here's an example on how to build it: |
69 | + |
70 | + $ mkdir build |
71 | + $ cd build |
72 | + $ cmake .. |
73 | + $ make -j 8 |
74 | + |
75 | + |
76 | +Running the unit tests |
77 | +---------------------- |
78 | + |
79 | + $ make test |
80 | + |
81 | + |
82 | +Running the autopilot tests |
83 | +--------------------------- |
84 | + |
85 | +To run the autopilot tests locally, you first need to build pay-service. |
86 | + |
87 | + $ make autopilot |
88 | + |
89 | +The autopilot tests can also be run against a built debian package, under |
90 | +qemu. To do so, you will need some additional packages. Most importantly, |
91 | +you will need the latest version of the autopkgtest package. You can download |
92 | +it at https://launchpad.net/ubuntu/+source/autopkgtest by selecting the most |
93 | +recent build for the most recent version of Ubuntu. |
94 | + |
95 | + $ sudo dpkg -i autopkgtest*.deb |
96 | + $ sudo apt-get install qemu |
97 | + |
98 | +After installing autopkgtest and qemu, you need to build an image for qemu |
99 | +to use. We use vivid here, as building an image to closely resemble the actual |
100 | +stable phone images is quite difficult. When this is easier in the future, we |
101 | +will switch to using stable phone images for this. The architecture argument |
102 | +should match the architecture of the debian package you are trying to test. |
103 | +We output the image to ~/ rather than the current directory, so it will be in |
104 | +a safer place to avoid rebuilding images every time. You can store it in any |
105 | +directory you wish. |
106 | + |
107 | + $ adt-buildvm-ubuntu-cloud -r vivid -a amd64 -o ~/ |
108 | + |
109 | +Then the tests may be run using adt-run with the qemu virtualization host. |
110 | +The output directory option here can be wherever you like, and is where the |
111 | +test artifacts will be placed. The ordering of the arguments to adt-run is |
112 | +important so try to keep them in this order. |
113 | + |
114 | + $ adt-run --click-source . \ |
115 | + --source ../pay-service*.dsc \ |
116 | + -o /tmp/adt-payui-test \ |
117 | + --setup-commands "add-apt-repository \ |
118 | + ppa:ci-train-ppa-service/stable-phone-overlay" \ |
119 | + --apt-pocket proposed \ |
120 | + --setup-commands "apt-get update" \ |
121 | + --setup-commands ubuntu-touch-session \ |
122 | + --- qemu ~/adt-vivid-amd64-cloud.img |
123 | + |
124 | +To examine the test results, which are in subunit format, additional tools are |
125 | +required. |
126 | + |
127 | + $ sudo add-apt-repository ppa:thomir/trv |
128 | + $ sudo apt-get update |
129 | + $ sudo apt-get install trv |
130 | + $ trv /tmp/adt-payui-test/artifacts/autopilot.subunit |
131 | |
132 | === modified file 'debian/control' |
133 | --- debian/control 2015-12-11 22:14:01 +0000 |
134 | +++ debian/control 2016-03-11 14:49:24 +0000 |
135 | @@ -2,8 +2,7 @@ |
136 | Section: gnome |
137 | Priority: optional |
138 | Maintainer: Ubuntu Developers <ubuntu-devel-discuss@lists.ubuntu.com> |
139 | -Build-Depends: click-dev, |
140 | - cmake, |
141 | +Build-Depends: cmake, |
142 | cmake-extras, |
143 | dbus, |
144 | dbus-test-runner, |
145 | @@ -15,7 +14,6 @@ |
146 | google-mock, |
147 | intltool, |
148 | lcov, |
149 | - libclick-0.4-dev, |
150 | libdbus-1-dev, |
151 | libdbus-cpp-dev, |
152 | libdbustest1-dev, |
153 | @@ -26,12 +24,15 @@ |
154 | libproperties-cpp-dev, |
155 | libtrust-store-dev, |
156 | libubuntu-app-launch2-dev (>= 0.5), |
157 | + libubuntuoneauth-2.0-dev, |
158 | pkg-config, |
159 | python3-dbusmock, |
160 | qt5-default, |
161 | qtbase5-dev, |
162 | qtdeclarative5-dev, |
163 | + qtdeclarative5-dev-tools, |
164 | sysvinit-utils, |
165 | + xvfb, |
166 | Standards-Version: 3.9.5 |
167 | Homepage: https://launchpad.net/pay-service |
168 | # If you aren't a member of ~indicator-applet-developers but need to upload |
169 | @@ -44,6 +45,7 @@ |
170 | Pre-Depends: ${misc:Pre-Depends}, |
171 | Depends: ${misc:Depends}, |
172 | ${shlibs:Depends}, |
173 | + pay-ui (= ${binary:Version}), |
174 | sysvinit-utils, |
175 | ubuntu-push-client (>= 0.68+15.04.20151009), |
176 | Recommends: gnupg, |
177 | @@ -54,7 +56,23 @@ |
178 | payment provider and give that money to the developer. This service |
179 | coordinates the payment interaction with the server on the device. |
180 | . |
181 | - This package provides a service for the Pay Service |
182 | + This package provides a service for the Pay Service. |
183 | + |
184 | +Package: pay-ui |
185 | +Architecture: any |
186 | +Depends: |
187 | + ${misc:Depends}, |
188 | + ${shlibs:Depends}, |
189 | + qtdeclarative5-ubuntu-web-plugin [amd64 armhf i386], |
190 | + qtdeclarative5-online-accounts-client0.1, |
191 | + qtdeclarative5-ubuntu-ui-toolkit-plugin, |
192 | +Description: service to allow requesting payment for an item - user interface |
193 | + Allows applications to request an item be paid for by the user. |
194 | + This requires interaction with the server backend to handle the |
195 | + payment provider and give that money to the developer. This service |
196 | + coordinates the payment interaction with the server on the device. |
197 | + . |
198 | + This package provides the user interface for the Pay Service. |
199 | |
200 | Package: libpay2 |
201 | Architecture: any |
202 | |
203 | === modified file 'debian/copyright' |
204 | --- debian/copyright 2015-12-10 22:04:32 +0000 |
205 | +++ debian/copyright 2016-03-11 14:49:24 +0000 |
206 | @@ -3,7 +3,7 @@ |
207 | Source: http://launchpad.net/pay-service |
208 | |
209 | Files: * |
210 | -Copyright: 2014-2015 Canonical, Ltd. |
211 | +Copyright: 2014-2016 Canonical, Ltd. |
212 | License: GPL-3 |
213 | |
214 | Files: libpay/* |
215 | @@ -27,7 +27,7 @@ |
216 | License: LGPL-3 |
217 | |
218 | Files: service-ng/src/launchpad.net/go-mir/* |
219 | -Copyright: 2015 Canonical, Ltd. |
220 | +Copyright: 2015-2016 Canonical, Ltd. |
221 | License: LGPL-3 |
222 | |
223 | Files: service-ng/src/launchpad.net/go-trust-store/* |
224 | |
225 | === removed file 'debian/pay-service.click-hook' |
226 | --- debian/pay-service.click-hook 2014-07-10 13:34:44 +0000 |
227 | +++ debian/pay-service.click-hook 1970-01-01 00:00:00 +0000 |
228 | @@ -1,4 +0,0 @@ |
229 | -Pattern: ${home}/.cache/pay-service/pay-ui/${id}.desktop |
230 | -Exec: /bin/true |
231 | -User-Level: yes |
232 | -Hook-Name: pay-ui |
233 | |
234 | === modified file 'debian/pay-service.install' |
235 | --- debian/pay-service.install 2015-12-09 18:18:45 +0000 |
236 | +++ debian/pay-service.install 2016-03-11 14:49:24 +0000 |
237 | @@ -1,5 +1,5 @@ |
238 | usr/lib/*/pay-service/pay-service-2 |
239 | usr/lib/*/pay-service/setup-staging.sh |
240 | -usr/lib/*/ubuntu-app-launch/pay-ui/* |
241 | +usr/share/locale/*/LC_MESSAGES/pay-service.mo |
242 | usr/share/dbus-1/services/*.service |
243 | -usr/share/upstart/sessions/*.conf |
244 | \ No newline at end of file |
245 | +usr/share/upstart/sessions/*.conf |
246 | |
247 | === added file 'debian/pay-ui.install' |
248 | --- debian/pay-ui.install 1970-01-01 00:00:00 +0000 |
249 | +++ debian/pay-ui.install 2016-03-11 14:49:24 +0000 |
250 | @@ -0,0 +1,3 @@ |
251 | +usr/lib/payui |
252 | +usr/lib/*/payui |
253 | +usr/share/payui |
254 | |
255 | === modified file 'debian/rules' |
256 | --- debian/rules 2015-10-14 20:53:54 +0000 |
257 | +++ debian/rules 2016-03-11 14:49:24 +0000 |
258 | @@ -10,4 +10,4 @@ |
259 | dh_auto_configure -- -DCMAKE_INSTALL_LIBEXECDIR=/usr/lib/$(DEB_HOST_MULTIARCH)/pay-service |
260 | |
261 | %: |
262 | - dh $@ --parallel --fail-missing --with click |
263 | + dh $@ --parallel --fail-missing |
264 | |
265 | === added directory 'pay-ui' |
266 | === added file 'pay-ui/CMakeLists.txt' |
267 | --- pay-ui/CMakeLists.txt 1970-01-01 00:00:00 +0000 |
268 | +++ pay-ui/CMakeLists.txt 2016-03-11 14:49:24 +0000 |
269 | @@ -0,0 +1,21 @@ |
270 | +# Need xvfb-run for some tests. |
271 | +set(XVFB_CMD xvfb-run -a -s "-screen 0 540x960x24") |
272 | + |
273 | +# Standard install paths |
274 | +set(APP_NAME payui) |
275 | + |
276 | +set(QT_IMPORTS_DIR "${CMAKE_INSTALL_FULL_LIBEXECDIR}/payui") |
277 | +set(PAYUI_DIR "${CMAKE_INSTALL_FULL_DATADIR}/payui/qml") |
278 | + |
279 | +add_subdirectory(app) |
280 | +add_subdirectory(backend) |
281 | + |
282 | +configure_file(pay-ui.in pay-ui) |
283 | +install(PROGRAMS ${CMAKE_CURRENT_BINARY_DIR}/pay-ui |
284 | + DESTINATION lib/payui |
285 | +) |
286 | + |
287 | +add_custom_target("autopilot" |
288 | + COMMAND PYTHONPATH=${CMAKE_CURRENT_SOURCE_DIR}/tests/autopilot BUILD_DIR=${CMAKE_CURRENT_BINARY_DIR} SOURCE_DIR=${CMAKE_CURRENT_SOURCE_DIR} U1_DEBUG=1 ${XVFB_CMD} ${CMAKE_CURRENT_SOURCE_DIR}/tests/autopilot/run_autopilot |
289 | + DEPENDS payuibackend payuibackend-qmldir |
290 | +) |
291 | |
292 | === added directory 'pay-ui/app' |
293 | === added file 'pay-ui/app/CMakeLists.txt' |
294 | --- pay-ui/app/CMakeLists.txt 1970-01-01 00:00:00 +0000 |
295 | +++ pay-ui/app/CMakeLists.txt 2016-03-11 14:49:24 +0000 |
296 | @@ -0,0 +1,6 @@ |
297 | +file(GLOB QML_JS_FILES *.qml *.js) |
298 | + |
299 | +install(FILES ${QML_JS_FILES} DESTINATION ${PAYUI_DIR}) |
300 | + |
301 | +add_subdirectory(components) |
302 | +add_subdirectory(ui) |
303 | |
304 | === added directory 'pay-ui/app/components' |
305 | === added file 'pay-ui/app/components/AlertDialog.qml' |
306 | --- pay-ui/app/components/AlertDialog.qml 1970-01-01 00:00:00 +0000 |
307 | +++ pay-ui/app/components/AlertDialog.qml 2016-03-11 14:49:24 +0000 |
308 | @@ -0,0 +1,31 @@ |
309 | +/* |
310 | + * Copyright 2013-2014 Canonical Ltd. |
311 | + * |
312 | + * This file is part of webbrowser-app. |
313 | + * |
314 | + * webbrowser-app is free software; you can redistribute it and/or modify |
315 | + * it under the terms of the GNU General Public License as published by |
316 | + * the Free Software Foundation; version 3. |
317 | + * |
318 | + * webbrowser-app is distributed in the hope that it will be useful, |
319 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
320 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
321 | + * GNU General Public License for more details. |
322 | + * |
323 | + * You should have received a copy of the GNU General Public License |
324 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
325 | + */ |
326 | + |
327 | +import QtQuick 2.0 |
328 | +import Ubuntu.Components 1.1 |
329 | + |
330 | +ModalDialog { |
331 | + objectName: "alertDialog" |
332 | + title: i18n.dtr("webbrowser-app", "JavaScript Alert") |
333 | + |
334 | + Button { |
335 | + objectName: "dialogOkButton" |
336 | + text: i18n.dtr("webbrowser-app", "OK") |
337 | + onClicked: model.accept() |
338 | + } |
339 | +} |
340 | |
341 | === added file 'pay-ui/app/components/BeforeUnloadDialog.qml' |
342 | --- pay-ui/app/components/BeforeUnloadDialog.qml 1970-01-01 00:00:00 +0000 |
343 | +++ pay-ui/app/components/BeforeUnloadDialog.qml 2016-03-11 14:49:24 +0000 |
344 | @@ -0,0 +1,37 @@ |
345 | +/* |
346 | + * Copyright 2014 Canonical Ltd. |
347 | + * |
348 | + * This file is part of webbrowser-app. |
349 | + * |
350 | + * webbrowser-app is free software; you can redistribute it and/or modify |
351 | + * it under the terms of the GNU General Public License as published by |
352 | + * the Free Software Foundation; version 3. |
353 | + * |
354 | + * webbrowser-app is distributed in the hope that it will be useful, |
355 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
356 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
357 | + * GNU General Public License for more details. |
358 | + * |
359 | + * You should have received a copy of the GNU General Public License |
360 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
361 | + */ |
362 | + |
363 | +import QtQuick 2.0 |
364 | +import Ubuntu.Components 1.1 |
365 | + |
366 | +ModalDialog { |
367 | + title: i18n.dtr("webbrowser-app", "Confirm Navigation") |
368 | + objectName: "beforeUnloadDialog" |
369 | + |
370 | + Button { |
371 | + objectName: "leaveButton" |
372 | + text: i18n.dtr("webbrowser-app", "Leave") |
373 | + onClicked: model.accept() |
374 | + } |
375 | + |
376 | + Button { |
377 | + objectName: "stayButton" |
378 | + text: i18n.dtr("webbrowser-app", "Stay") |
379 | + onClicked: model.reject() |
380 | + } |
381 | +} |
382 | |
383 | === added file 'pay-ui/app/components/CMakeLists.txt' |
384 | --- pay-ui/app/components/CMakeLists.txt 1970-01-01 00:00:00 +0000 |
385 | +++ pay-ui/app/components/CMakeLists.txt 2016-03-11 14:49:24 +0000 |
386 | @@ -0,0 +1,6 @@ |
387 | +file(GLOB COMPONENTS_QML_JS_FILES *.qml *.js) |
388 | + |
389 | +# make the files visible in the qtcreator tree |
390 | +add_custom_target(payui_components_QMlFiles ALL SOURCES ${COMPONENTS_QML_JS_FILES}) |
391 | + |
392 | +install(FILES ${COMPONENTS_QML_JS_FILES} DESTINATION ${PAYUI_DIR}/components) |
393 | |
394 | === added file 'pay-ui/app/components/ConfirmDialog.qml' |
395 | --- pay-ui/app/components/ConfirmDialog.qml 1970-01-01 00:00:00 +0000 |
396 | +++ pay-ui/app/components/ConfirmDialog.qml 2016-03-11 14:49:24 +0000 |
397 | @@ -0,0 +1,37 @@ |
398 | +/* |
399 | + * Copyright 2013-2014 Canonical Ltd. |
400 | + * |
401 | + * This file is part of webbrowser-app. |
402 | + * |
403 | + * webbrowser-app is free software; you can redistribute it and/or modify |
404 | + * it under the terms of the GNU General Public License as published by |
405 | + * the Free Software Foundation; version 3. |
406 | + * |
407 | + * webbrowser-app is distributed in the hope that it will be useful, |
408 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
409 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
410 | + * GNU General Public License for more details. |
411 | + * |
412 | + * You should have received a copy of the GNU General Public License |
413 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
414 | + */ |
415 | + |
416 | +import QtQuick 2.0 |
417 | +import Ubuntu.Components 1.1 |
418 | + |
419 | +ModalDialog { |
420 | + objectName: "confirmDialog" |
421 | + title: i18n.dtr("webbrowser-app", "JavaScript Confirmation") |
422 | + |
423 | + Button { |
424 | + objectName: "dialogOkButton" |
425 | + text: i18n.dtr("webbrowser-app", "OK") |
426 | + onClicked: model.accept() |
427 | + } |
428 | + |
429 | + Button { |
430 | + objectName: "dialogCancelButton" |
431 | + text: i18n.dtr("webbrowser-app", "Cancel") |
432 | + onClicked: model.reject() |
433 | + } |
434 | +} |
435 | |
436 | === added file 'pay-ui/app/components/ModalDialog.qml' |
437 | --- pay-ui/app/components/ModalDialog.qml 1970-01-01 00:00:00 +0000 |
438 | +++ pay-ui/app/components/ModalDialog.qml 2016-03-11 14:49:24 +0000 |
439 | @@ -0,0 +1,32 @@ |
440 | +/* |
441 | + * Copyright 2014 Canonical Ltd. |
442 | + * |
443 | + * This file is part of webbrowser-app. |
444 | + * |
445 | + * webbrowser-app is free software; you can redistribute it and/or modify |
446 | + * it under the terms of the GNU General Public License as published by |
447 | + * the Free Software Foundation; version 3. |
448 | + * |
449 | + * webbrowser-app is distributed in the hope that it will be useful, |
450 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
451 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
452 | + * GNU General Public License for more details. |
453 | + * |
454 | + * You should have received a copy of the GNU General Public License |
455 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
456 | + */ |
457 | + |
458 | +import QtQuick 2.0 |
459 | +import Ubuntu.Components 1.1 |
460 | +import Ubuntu.Components.Popups 1.0 as Popups |
461 | + |
462 | +Popups.Dialog { |
463 | + text: model.message |
464 | + |
465 | + // Set the parent at construction time, instead of letting show() |
466 | + // set it later on, which for some reason results in the size of |
467 | + // the dialog not being updated. |
468 | + parent: QuickUtils.rootItem(this) |
469 | + |
470 | + Component.onCompleted: show() |
471 | +} |
472 | |
473 | === added file 'pay-ui/app/components/PromptDialog.qml' |
474 | --- pay-ui/app/components/PromptDialog.qml 1970-01-01 00:00:00 +0000 |
475 | +++ pay-ui/app/components/PromptDialog.qml 2016-03-11 14:49:24 +0000 |
476 | @@ -0,0 +1,51 @@ |
477 | +/* |
478 | + * Copyright 2013-2014 Canonical Ltd. |
479 | + * |
480 | + * This file is part of webbrowser-app. |
481 | + * |
482 | + * webbrowser-app is free software; you can redistribute it and/or modify |
483 | + * it under the terms of the GNU General Public License as published by |
484 | + * the Free Software Foundation; version 3. |
485 | + * |
486 | + * webbrowser-app is distributed in the hope that it will be useful, |
487 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
488 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
489 | + * GNU General Public License for more details. |
490 | + * |
491 | + * You should have received a copy of the GNU General Public License |
492 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
493 | + */ |
494 | + |
495 | +import QtQuick 2.0 |
496 | +import Ubuntu.Components 1.1 |
497 | + |
498 | +ModalDialog { |
499 | + title: i18n.dtr("webbrowser-app", "JavaScript Prompt") |
500 | + |
501 | + TextField { |
502 | + objectName: "dialogInput" |
503 | + id: input |
504 | + text: model.defaultValue |
505 | + onAccepted: model.accept(input.text) |
506 | + } |
507 | + |
508 | + Button { |
509 | + objectName: "dialogOkButton" |
510 | + text: i18n.dtr("webbrowser-app", "OK") |
511 | + color: "green" |
512 | + onClicked: model.accept(input.text) |
513 | + } |
514 | + |
515 | + Button { |
516 | + objectName: "dialogCancelButton" |
517 | + text: i18n.dtr("webbrowser-app", "Cancel") |
518 | + color: UbuntuColors.coolGrey |
519 | + onClicked: model.reject() |
520 | + } |
521 | + |
522 | + Binding { |
523 | + target: model |
524 | + property: "currentValue" |
525 | + value: input.text |
526 | + } |
527 | +} |
528 | |
529 | === added file 'pay-ui/app/components/SecurityCertificatePopover.qml' |
530 | --- pay-ui/app/components/SecurityCertificatePopover.qml 1970-01-01 00:00:00 +0000 |
531 | +++ pay-ui/app/components/SecurityCertificatePopover.qml 2016-03-11 14:49:24 +0000 |
532 | @@ -0,0 +1,145 @@ |
533 | +/* |
534 | + * Copyright 2014 Canonical Ltd. |
535 | + * |
536 | + * This file is part of webbrowser-app. |
537 | + * |
538 | + * webbrowser-app is free software; you can redistribute it and/or modify |
539 | + * it under the terms of the GNU General Public License as published by |
540 | + * the Free Software Foundation; version 3. |
541 | + * |
542 | + * webbrowser-app is distributed in the hope that it will be useful, |
543 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
544 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
545 | + * GNU General Public License for more details. |
546 | + * |
547 | + * You should have received a copy of the GNU General Public License |
548 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
549 | + */ |
550 | + |
551 | +import QtQuick 2.0 |
552 | +import Ubuntu.Components 1.1 |
553 | +import Ubuntu.Components.ListItems 1.0 |
554 | +import Ubuntu.Components.Popups 1.0 |
555 | +import payui 0.1 as Oxide |
556 | + |
557 | +Popover { |
558 | + id: certificatePopover |
559 | + |
560 | + property var securityStatus |
561 | + |
562 | + Column { |
563 | + width: parent.width - units.gu(4) |
564 | + anchors.horizontalCenter: parent.horizontalCenter |
565 | + spacing: units.gu(0.5) |
566 | + |
567 | + Item { |
568 | + height: units.gu(1.5) |
569 | + width: parent.width |
570 | + } |
571 | + |
572 | + Column { |
573 | + width: parent.width |
574 | + visible: securityStatus.securityLevel == Oxide.SecurityStatus.SecurityLevelWarning |
575 | + spacing: units.gu(0.5) |
576 | + |
577 | + Row { |
578 | + width: parent.width |
579 | + spacing: units.gu(0.5) |
580 | + |
581 | + Icon { |
582 | + name: "security-alert" |
583 | + height: units.gu(2) |
584 | + width: height |
585 | + } |
586 | + |
587 | + Label { |
588 | + width: parent.width |
589 | + wrapMode: Text.WordWrap |
590 | + text: i18n.dtr("webbrowser-app", "This site has insecure content") |
591 | + fontSize: "x-small" |
592 | + } |
593 | + } |
594 | + |
595 | + ThinDivider { |
596 | + width: parent.width |
597 | + anchors.leftMargin: 0 |
598 | + anchors.rightMargin: 0 |
599 | + } |
600 | + } |
601 | + |
602 | + Label { |
603 | + width: parent.width |
604 | + wrapMode: Text.WordWrap |
605 | + text: i18n.dtr("webbrowser-app", "You are connected to") |
606 | + fontSize: "x-small" |
607 | + } |
608 | + |
609 | + Label { |
610 | + width: parent.width |
611 | + wrapMode: Text.WordWrap |
612 | + text: securityStatus.certificate.subjectDisplayName |
613 | + fontSize: "x-small" |
614 | + } |
615 | + |
616 | + ThinDivider { |
617 | + width: parent.width |
618 | + anchors.leftMargin: 0 |
619 | + anchors.rightMargin: 0 |
620 | + visible: orgName.visible || localityName.visible || stateName.visible || countryName.visible |
621 | + } |
622 | + |
623 | + Label { |
624 | + width: parent.width |
625 | + wrapMode: Text.WordWrap |
626 | + visible: orgName.visible |
627 | + text: i18n.dtr("webbrowser-app", "Which is run by") |
628 | + fontSize: "x-small" |
629 | + } |
630 | + |
631 | + Label { |
632 | + id: orgName |
633 | + width: parent.width |
634 | + wrapMode: Text.WordWrap |
635 | + visible: text.length > 0 |
636 | + text: securityStatus.certificate.getSubjectInfo(Oxide.SslCertificate.PrincipalAttrOrganizationName).join(", ") |
637 | + fontSize: "x-small" |
638 | + } |
639 | + |
640 | + Label { |
641 | + id: localityName |
642 | + width: parent.width |
643 | + wrapMode: Text.WordWrap |
644 | + visible: text.length > 0 |
645 | + text: securityStatus.certificate.getSubjectInfo(Oxide.SslCertificate.PrincipalAttrLocalityName).join(", ") |
646 | + fontSize: "x-small" |
647 | + } |
648 | + |
649 | + Label { |
650 | + id: stateName |
651 | + width: parent.width |
652 | + wrapMode: Text.WordWrap |
653 | + visible: text.length > 0 |
654 | + text: securityStatus.certificate.getSubjectInfo(Oxide.SslCertificate.PrincipalAttrStateOrProvinceName).join(", ") |
655 | + fontSize: "x-small" |
656 | + } |
657 | + |
658 | + Label { |
659 | + id: countryName |
660 | + width: parent.width |
661 | + wrapMode: Text.WordWrap |
662 | + visible: text.length > 0 |
663 | + text: securityStatus.certificate.getSubjectInfo(Oxide.SslCertificate.PrincipalAttrCountryName).join(", ") |
664 | + fontSize: "x-small" |
665 | + } |
666 | + |
667 | + Item { |
668 | + height: units.gu(1.5) |
669 | + width: parent.width |
670 | + } |
671 | + } |
672 | + |
673 | + MouseArea { |
674 | + anchors.fill: parent |
675 | + onClicked: PopupUtils.close(certificatePopover) |
676 | + } |
677 | +} |
678 | |
679 | === added file 'pay-ui/app/payui.qml' |
680 | --- pay-ui/app/payui.qml 1970-01-01 00:00:00 +0000 |
681 | +++ pay-ui/app/payui.qml 2016-03-11 14:49:24 +0000 |
682 | @@ -0,0 +1,459 @@ |
683 | +/* |
684 | + * Copyright 2014-2016 Canonical Ltd. |
685 | + * |
686 | + * This program is free software; you can redistribute it and/or modify |
687 | + * it under the terms of the GNU General Public License as published by |
688 | + * the Free Software Foundation; version 3. |
689 | + * |
690 | + * This program is distributed in the hope that it will be useful, |
691 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
692 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
693 | + * GNU General Public License for more details. |
694 | + * |
695 | + * You should have received a copy of the GNU General Public License |
696 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
697 | + */ |
698 | + |
699 | +import QtQuick 2.0 |
700 | +import QtQuick.LocalStorage 2.0 |
701 | +import Ubuntu.Components 1.1 |
702 | +import Ubuntu.Components.Popups 0.1 |
703 | +import Ubuntu.OnlineAccounts 0.1 |
704 | +import Ubuntu.OnlineAccounts.Client 0.1 |
705 | +import payui 0.1 |
706 | +import "ui" |
707 | + |
708 | +/*! |
709 | + states: |
710 | + - add-payment |
711 | + - buy-interaction |
712 | + - checkout |
713 | + - online-accounts |
714 | + - error |
715 | +*/ |
716 | + |
717 | +MainView { |
718 | + id: mainView |
719 | + // objectName for functional testing purposes (autopilot-qt5) |
720 | + objectName: "payui" |
721 | + |
722 | + // NOTE: Must match the gettext domain for translations. |
723 | + applicationName: "pay-service" |
724 | + |
725 | + /* |
726 | + This property enables the application to change orientation |
727 | + when the device is rotated. The default is false. |
728 | + */ |
729 | + // automaticOrientation: true |
730 | + |
731 | + width: units.gu(100) |
732 | + height: units.gu(75) |
733 | + |
734 | + useDeprecatedToolbar: false |
735 | + |
736 | + property bool loading: true |
737 | + property bool purchasing: false |
738 | + property bool recentLogin: false |
739 | + property bool cancellable: true |
740 | + property string suggestedCurrency: "USD" |
741 | + |
742 | + backgroundColor: "transparent" |
743 | + |
744 | + onStateChanged: { |
745 | + mainView.backgroundColor = "white"; |
746 | + } |
747 | + |
748 | + AccountServiceModel { |
749 | + id: accounts |
750 | + provider: "ubuntuone" |
751 | + } |
752 | + |
753 | + Setup { |
754 | + id: setup |
755 | + applicationId: "pay-service" |
756 | + providerId: "ubuntuone" |
757 | + |
758 | + onFinished: { |
759 | + mainView.recentLogin = true; |
760 | + mainView.showLoading(); |
761 | + purchase.checkCredentials(); |
762 | + } |
763 | + } |
764 | + |
765 | + Purchase { |
766 | + id: purchase |
767 | + |
768 | + onItemDetailsObtained: { |
769 | + suggestedCurrency = currency; |
770 | + checkout.itemIcon = icon; |
771 | + checkout.itemTitle = title; |
772 | + checkout.itemSubtitle = publisher; |
773 | + checkout.price = formatted_price; |
774 | + } |
775 | + |
776 | + onPaymentTypesObtained: { |
777 | + mainView.recentLogin = mainView.recentCredentials(); |
778 | + checkout.beforeTimeout = mainView.recentLogin; |
779 | + |
780 | + // Check for selected payment, and keep it selected if so. |
781 | + if (checkout.hasSelectedPayment) { |
782 | + for (var i=0; i < payments.length; i++) { |
783 | + if (payments[i].paymentId == checkout.paymentId && |
784 | + payments[i].backendId == checkout.backendId) { |
785 | + payments[i].preferred = true; |
786 | + } else { |
787 | + payments[i].preferred = false; |
788 | + } |
789 | + } |
790 | + } |
791 | + checkout.model = payments; |
792 | + checkout.hasPayments = payments.length != 0; |
793 | + checkout.setSelectedItem(); |
794 | + |
795 | + mainView.state = "checkout"; |
796 | + pageStack.push(checkout); |
797 | + |
798 | + hideLoading(); |
799 | + } |
800 | + |
801 | + onNoPreferredPaymentMethod: { |
802 | + checkout.hasPreferredPayment = false; |
803 | + var values = mainView.getLastPayment(); |
804 | + var backendid = values[0]; |
805 | + var paymentid = values[1]; |
806 | + if (backendid != "" && paymentid != "") { |
807 | + checkout.hasStoredPayment = true; |
808 | + } |
809 | + } |
810 | + |
811 | + onPasswordValid: { |
812 | + hideLoading(); |
813 | + // Reset password and otp to not keep them in memory. |
814 | + checkout.password = ""; |
815 | + checkout.otp = ""; |
816 | + } |
817 | + |
818 | + onBuyItemFailed: { |
819 | + hideLoading(); |
820 | + purchaseErrorDialog.open = true; |
821 | + } |
822 | + |
823 | + onBuyItemSucceeded: { |
824 | + purchase.quitSuccess(); |
825 | + } |
826 | + |
827 | + onBuyInterationRequired: { |
828 | + mainView.purchasing = false; |
829 | + webkit.title = i18n.tr("Finish Purchase"); |
830 | + webkit.url = url; |
831 | + mainView.state = "buy-interaction"; |
832 | + pageStack.push(webkit); |
833 | + } |
834 | + |
835 | + onError: { |
836 | + hideLoading(); |
837 | + serverErrorDialog.open = true; |
838 | + } |
839 | + |
840 | + onAuthenticationError: { |
841 | + mainView.recentLogin = false; |
842 | + if (pageStack.currentPage == checkout) { |
843 | + mainView.hideLoading(); |
844 | + checkout.showErrorMessage(i18n.tr("Incorrect Password, please try again.")); |
845 | + } else { |
846 | + mainView.state = "online-accounts"; |
847 | + setup.exec(); |
848 | + } |
849 | + } |
850 | + |
851 | + onCredentialsFound: { |
852 | + mainView.recentLogin = mainView.recentCredentials(); |
853 | + checkout.beforeTimeout = mainView.recentLogin; |
854 | + |
855 | + if (mainView.state == "online-accounts") { |
856 | + purchase.checkItemPurchased(); |
857 | + } else if (mainView.state != "checkout" && !mainView.purchasing && mainView.state != "buy-interaction") { |
858 | + purchase.getPaymentTypes(suggestedCurrency); |
859 | + } |
860 | + purchase.getItemDetails(); |
861 | + } |
862 | + |
863 | + onItemNotPurchased: { |
864 | + purchase.getPaymentTypes(suggestedCurrency); |
865 | + } |
866 | + |
867 | + onCredentialsNotFound: { |
868 | + mainView.recentLogin = false; |
869 | + if (mainView.state == "online-accounts") { |
870 | + purchase.quitCancel(); |
871 | + } else { |
872 | + mainView.state = "online-accounts"; |
873 | + setup.exec(); |
874 | + } |
875 | + } |
876 | + |
877 | + onLoginError: { |
878 | + mainView.recentLogin = false; |
879 | + mainView.hideLoading(); |
880 | + checkout.showErrorMessage(message); |
881 | + } |
882 | + |
883 | + onTwoFactorAuthRequired: { |
884 | + mainView.hideLoading(); |
885 | + checkout.showTwoFactor(); |
886 | + } |
887 | + |
888 | + onCertificateFound: { |
889 | + checkout.certificate = cert |
890 | + } |
891 | + } |
892 | + |
893 | + function showLoading() { |
894 | + mainView.loading = true; |
895 | + PopupUtils.open(loadingDialogContainer); |
896 | + } |
897 | + |
898 | + function hideLoading() { |
899 | + mainView.purchasing = false; |
900 | + mainView.loading = false; |
901 | + mainView.cancellable = true; |
902 | + } |
903 | + |
904 | + function createDB() { |
905 | + var db = LocalStorage.openDatabaseSync("PayUI", "1.0", "PayUI Credentials Date", 100); |
906 | + db.transaction( |
907 | + function(tx) { |
908 | + // Create the database if it doesn't already exist |
909 | + tx.executeSql('CREATE TABLE IF NOT EXISTS PayUIPayment(backendid TEXT, paymentid TEXT)'); |
910 | + } |
911 | + ) |
912 | + } |
913 | + |
914 | + function recentCredentials() { |
915 | + var valid = false; |
916 | + var date = purchase.getTokenUpdated(); |
917 | + var currentDate = new Date(); |
918 | + var msec = currentDate - date; |
919 | + var mm = Math.floor(msec / 1000 / 60); |
920 | + if (mm < 15) { |
921 | + valid = true; |
922 | + } |
923 | + return valid; |
924 | + } |
925 | + |
926 | + function getLastPayment() { |
927 | + var backendid = ""; |
928 | + var paymentid = ""; |
929 | + var db = LocalStorage.openDatabaseSync("PayUI", "1.0", "PayUI Credentials Date", 100); |
930 | + db.transaction( |
931 | + function(tx) { |
932 | + var rs = tx.executeSql('SELECT * FROM PayUIPayment'); |
933 | + if (rs.rows.length > 0) { |
934 | + backendid = rs.rows.item(0).backendid; |
935 | + paymentid = rs.rows.item(0).paymentid; |
936 | + } |
937 | + } |
938 | + ) |
939 | + return [backendid, paymentid]; |
940 | + } |
941 | + |
942 | + function updatePayment(backendid, paymentid) { |
943 | + var db = LocalStorage.openDatabaseSync("PayUI", "1.0", "PayUI Credentials Date", 100); |
944 | + db.transaction( |
945 | + function(tx) { |
946 | + var rs = tx.executeSql('SELECT * FROM PayUIPayment'); |
947 | + if (rs.rows.length > 0) { |
948 | + tx.executeSql('UPDATE PayUIPayment SET backendid = "' + backendid + '", paymentid = "' + paymentid + '"'); |
949 | + } else { |
950 | + tx.executeSql('INSERT INTO PayUIPayment VALUES(?, ?)', [backendid, paymentid]); |
951 | + } |
952 | + } |
953 | + ) |
954 | + } |
955 | + |
956 | + ErrorDialog { |
957 | + id: purchaseErrorDialog |
958 | + title: i18n.tr("Purchase failed") |
959 | + message: i18n.tr("The purchase couldn't be completed.") |
960 | + |
961 | + onRetry: { |
962 | + mainView.state = "error"; |
963 | + mainView.showLoading(); |
964 | + purchase.getItemDetails(); |
965 | + } |
966 | + |
967 | + onClose: { |
968 | + purchase.quitCancel(); |
969 | + } |
970 | + } |
971 | + |
972 | + ErrorDialog { |
973 | + id: serverErrorDialog |
974 | + title: i18n.tr("Error contacting the server") |
975 | + message: i18n.tr("Do you want to try again?") |
976 | + |
977 | + onRetry: { |
978 | + mainView.state = "error"; |
979 | + mainView.showLoading(); |
980 | + purchase.getItemDetails(); |
981 | + } |
982 | + |
983 | + onClose: { |
984 | + purchase.quitCancel(); |
985 | + } |
986 | + } |
987 | + |
988 | + ErrorDialog { |
989 | + id: creditCardErrorDialog |
990 | + title: i18n.tr("Adding Credit Card failed") |
991 | + message: i18n.tr("Do you want to try again?") |
992 | + |
993 | + onRetry: { |
994 | + mainView.state = "error"; |
995 | + mainView.showLoading(); |
996 | + purchase.getItemDetails(); |
997 | + } |
998 | + |
999 | + onClose: { |
1000 | + purchase.quitCancel(); |
1001 | + } |
1002 | + } |
1003 | + |
1004 | + Component { |
1005 | + id: loadingDialogContainer |
1006 | + |
1007 | + Dialog { |
1008 | + id: loadingDialog |
1009 | + title: mainView.purchasing ? i18n.tr("Processing Purchase") : i18n.tr("Loading") |
1010 | + text: i18n.tr("Please wait…") |
1011 | + ActivityIndicator { |
1012 | + running: mainView.loading ? true : false |
1013 | + width: parent.width |
1014 | + |
1015 | + onRunningChanged: { |
1016 | + if(!running) { |
1017 | + PopupUtils.close(loadingDialog); |
1018 | + } |
1019 | + } |
1020 | + } |
1021 | + |
1022 | + Button { |
1023 | + objectName: "buttonCancelLoading" |
1024 | + text: i18n.tr("Cancel") |
1025 | + color: UbuntuColors.orange |
1026 | + visible: mainView.cancellable |
1027 | + onClicked: { |
1028 | + PopupUtils.close(loadingDialog); |
1029 | + purchase.quitCancel(); |
1030 | + } |
1031 | + } |
1032 | + } |
1033 | + } |
1034 | + |
1035 | + PageStack { |
1036 | + id: pageStack |
1037 | + objectName: "pageStack" |
1038 | + |
1039 | + Component.onCompleted: { |
1040 | + showLoading(); |
1041 | + mainView.createDB(); |
1042 | + purchase.checkCredentials(); |
1043 | + } |
1044 | + |
1045 | + onCurrentPageChanged: { |
1046 | + if (pageStack.currentPage == checkout) { |
1047 | + mainView.state = "checkout"; |
1048 | + } |
1049 | + } |
1050 | + |
1051 | + CheckoutPage { |
1052 | + id: checkout |
1053 | + objectName: "pageCheckout" |
1054 | + visible: false |
1055 | + account: accounts |
1056 | + |
1057 | + onCancel: { |
1058 | + purchase.quitCancel(); |
1059 | + } |
1060 | + |
1061 | + onBuy: { |
1062 | + mainView.recentLogin = mainView.recentCredentials(); |
1063 | + checkout.beforeTimeout = mainView.recentLogin; |
1064 | + |
1065 | + // Pass it on. |
1066 | + checkout.hasSelectedPayment = true; |
1067 | + checkout.backendId = backendId; |
1068 | + checkout.paymentId = paymentId; |
1069 | + |
1070 | + mainView.purchasing = true; |
1071 | + mainView.cancellable = false; |
1072 | + showLoading(); |
1073 | + if (!checkout.hasPreferredPayment) { |
1074 | + mainView.updatePayment(backendId, paymentId); |
1075 | + } |
1076 | + |
1077 | + if (mainView.recentLogin) { |
1078 | + purchase.buyItem(email, "", "", suggestedCurrency, paymentId, backendId, mainView.recentLogin); |
1079 | + } else { |
1080 | + purchase.buyItem(email, password, otp, suggestedCurrency, paymentId, backendId, mainView.recentLogin); |
1081 | + } |
1082 | + } |
1083 | + |
1084 | + onAddCreditCard: { |
1085 | + webkit.title = i18n.tr("Add Payment"); |
1086 | + webkit.url = purchase.getAddPaymentUrl(suggestedCurrency); |
1087 | + mainView.state = "add-payment"; |
1088 | + pageStack.push(webkit); |
1089 | + } |
1090 | + } |
1091 | + |
1092 | + UbuntuPurchaseWebkit { |
1093 | + id: webkit |
1094 | + visible: false |
1095 | + |
1096 | + onPurchaseFailed: { |
1097 | + pageStack.pop(); |
1098 | + hideLoading(); |
1099 | + purchaseErrorDialog.open = true; |
1100 | + } |
1101 | + |
1102 | + onPurchaseCancelled: { |
1103 | + hideLoading(); |
1104 | + if (mainView.state == "add-payment") { |
1105 | + mainView.state = "checkout"; |
1106 | + pageStack.pop(); |
1107 | + } else { |
1108 | + purchase.quitCancel(); |
1109 | + } |
1110 | + } |
1111 | + |
1112 | + onPurchaseSucceeded: { |
1113 | + if (mainView.state == "add-payment") { |
1114 | + showLoading(); |
1115 | + purchase.getPaymentTypes(suggestedCurrency); |
1116 | + } else { |
1117 | + purchase.quitSuccess(); |
1118 | + } |
1119 | + } |
1120 | + |
1121 | + onLoading: { |
1122 | + if (value) { |
1123 | + showLoading(); |
1124 | + } else { |
1125 | + hideLoading(); |
1126 | + } |
1127 | + } |
1128 | + } |
1129 | + } |
1130 | + |
1131 | + Rectangle { |
1132 | + id: lockIconPlace |
1133 | + width: units.gu(7) |
1134 | + height: units.gu(7) |
1135 | + anchors { |
1136 | + right: parent.right |
1137 | + top: parent.top |
1138 | + } |
1139 | + visible: false |
1140 | + } |
1141 | +} |
1142 | |
1143 | === added directory 'pay-ui/app/tests' |
1144 | === added directory 'pay-ui/app/tests/unit' |
1145 | === added directory 'pay-ui/app/tests/unit/js' |
1146 | === added file 'pay-ui/app/tests/unit/js/unit_test.js' |
1147 | --- pay-ui/app/tests/unit/js/unit_test.js 1970-01-01 00:00:00 +0000 |
1148 | +++ pay-ui/app/tests/unit/js/unit_test.js 2016-03-11 14:49:24 +0000 |
1149 | @@ -0,0 +1,17 @@ |
1150 | +.pragma library |
1151 | + |
1152 | +// Find an object with the given name in the children tree of "obj" |
1153 | +function findChild(obj,objectName) { |
1154 | + var childs = new Array(0); |
1155 | + childs.push(obj) |
1156 | + while (childs.length > 0) { |
1157 | + if (childs[0].objectName == objectName) { |
1158 | + return childs[0] |
1159 | + } |
1160 | + for (var i in childs[0].children) { |
1161 | + childs.push(childs[0].children[i]) |
1162 | + } |
1163 | + childs.splice(0, 1); |
1164 | + } |
1165 | + return undefined; |
1166 | +} |
1167 | |
1168 | === added file 'pay-ui/app/tests/unit/tst_checkoutpage.qml' |
1169 | --- pay-ui/app/tests/unit/tst_checkoutpage.qml 1970-01-01 00:00:00 +0000 |
1170 | +++ pay-ui/app/tests/unit/tst_checkoutpage.qml 2016-03-11 14:49:24 +0000 |
1171 | @@ -0,0 +1,73 @@ |
1172 | +import QtQuick 2.0 |
1173 | +import QtTest 1.0 |
1174 | +import Ubuntu.Components 0.1 |
1175 | +import "../../ui" |
1176 | +import "js/unit_test.js" as UT |
1177 | + |
1178 | +// See more details @ http://qt-project.org/doc/qt-5.0/qtquick/qml-testcase.html |
1179 | + |
1180 | +// Execute tests with: |
1181 | +// qmltestrunner |
1182 | + |
1183 | +Item { |
1184 | + id: root |
1185 | + |
1186 | + property string title: "My App" |
1187 | + property string subtitle: "My App Subtitle" |
1188 | + property string price: "$ 1.99" |
1189 | + property string ubuntuid: "mail@mail.com" |
1190 | + property bool called: false |
1191 | + |
1192 | + // The objects |
1193 | + CheckoutPage { |
1194 | + id: checkoutPage |
1195 | + itemTitle: root.title |
1196 | + itemSubtitle: root.subtitle |
1197 | + price: root.price |
1198 | + ubuntuID: root.ubuntuid |
1199 | + |
1200 | + onCancel: { |
1201 | + root.called = true; |
1202 | + } |
1203 | + |
1204 | + onBuy: { |
1205 | + root.called = true; |
1206 | + } |
1207 | + } |
1208 | + |
1209 | + TestCase { |
1210 | + name: "CheckoutPage" |
1211 | + |
1212 | + function init() { |
1213 | + console.debug("Cleaning vars"); |
1214 | + root.called = false; |
1215 | + } |
1216 | + |
1217 | + function test_uiTexts() { |
1218 | + var titleLabel = UT.findChild(checkoutPage, "titleLabel"); |
1219 | + var subtitleLabel = UT.findChild(checkoutPage, "subtitleLabel"); |
1220 | + var priceLabel = UT.findChild(checkoutPage, "priceLabel"); |
1221 | + var ubuntuIdLabel = UT.findChild(checkoutPage, "ubuntuIdLabel"); |
1222 | + var ubuntuidExpected = "Ubuntu ID: " + root.ubuntuid |
1223 | + |
1224 | + compare(titleLabel.text, root.title); |
1225 | + compare(subtitleLabel.text, root.subtitle); |
1226 | + compare(priceLabel.text, root.price); |
1227 | + compare(ubuntuIdLabel.text, ubuntuidExpected); |
1228 | + } |
1229 | + |
1230 | + function test_cancelPressed() { |
1231 | + compare(root.called, false); |
1232 | + var cancelButton = UT.findChild(checkoutPage, "cancelButton"); |
1233 | + cancelButton.clicked(); |
1234 | + compare(root.called, true); |
1235 | + } |
1236 | + |
1237 | + function test_buyPressed() { |
1238 | + compare(root.called, false); |
1239 | + var buyButton = UT.findChild(checkoutPage, "buyButton"); |
1240 | + buyButton.clicked(); |
1241 | + compare(root.called, true); |
1242 | + } |
1243 | + } |
1244 | +} |
1245 | |
1246 | === added file 'pay-ui/app/tests/unit/tst_purchasewebkit.qml' |
1247 | --- pay-ui/app/tests/unit/tst_purchasewebkit.qml 1970-01-01 00:00:00 +0000 |
1248 | +++ pay-ui/app/tests/unit/tst_purchasewebkit.qml 2016-03-11 14:49:24 +0000 |
1249 | @@ -0,0 +1,57 @@ |
1250 | +import QtQuick 2.0 |
1251 | +import QtTest 1.0 |
1252 | +import Ubuntu.Components 0.1 |
1253 | +import "../../ui" |
1254 | + |
1255 | +// See more details @ http://qt-project.org/doc/qt-5.0/qtquick/qml-testcase.html |
1256 | + |
1257 | +// Execute tests with: |
1258 | +// qmltestrunner |
1259 | + |
1260 | +Item { |
1261 | + id: root |
1262 | + |
1263 | + property bool succeeded: false |
1264 | + property bool failed: false |
1265 | + |
1266 | + // The objects |
1267 | + UbuntuPurchaseWebkit { |
1268 | + id: purchaseWebkit |
1269 | + |
1270 | + onPurchaseCanceled: { |
1271 | + root.failed = true; |
1272 | + } |
1273 | + |
1274 | + onPurchaseSucceeded: { |
1275 | + root.succeeded = true; |
1276 | + } |
1277 | + } |
1278 | + |
1279 | + TestCase { |
1280 | + name: "UbuntuPurchaseWebkitPage" |
1281 | + |
1282 | + function init() { |
1283 | + console.debug("Cleaning vars"); |
1284 | + root.succeeded = false; |
1285 | + root.failed = false; |
1286 | + } |
1287 | + |
1288 | + function test_normalNavigation() { |
1289 | + purchaseWebkit.url = "http://fakepage.com"; |
1290 | + compare(root.failed, false); |
1291 | + compare(root.succeeded, false); |
1292 | + } |
1293 | + |
1294 | + function test_succeeded() { |
1295 | + purchaseWebkit.url = "https://sc.staging.ubuntu.com/click/succeeded"; |
1296 | + compare(root.failed, false); |
1297 | + compare(root.succeeded, true); |
1298 | + } |
1299 | + |
1300 | + function test_failed() { |
1301 | + purchaseWebkit.url = "https://sc.staging.ubuntu.com/click/succeeded"; |
1302 | + compare(root.failed, true); |
1303 | + compare(root.succeeded, false); |
1304 | + } |
1305 | + } |
1306 | +} |
1307 | |
1308 | === added directory 'pay-ui/app/ui' |
1309 | === added file 'pay-ui/app/ui/CMakeLists.txt' |
1310 | --- pay-ui/app/ui/CMakeLists.txt 1970-01-01 00:00:00 +0000 |
1311 | +++ pay-ui/app/ui/CMakeLists.txt 2016-03-11 14:49:24 +0000 |
1312 | @@ -0,0 +1,6 @@ |
1313 | +file(GLOB UI_QML_JS_FILES *.qml *.js) |
1314 | + |
1315 | +# make the files visible in the qtcreator tree |
1316 | +add_custom_target(payui_ui_QMlFiles ALL SOURCES ${UI_QML_JS_FILES}) |
1317 | + |
1318 | +install(FILES ${UI_QML_JS_FILES} DESTINATION ${PAYUI_DIR}/ui) |
1319 | |
1320 | === added file 'pay-ui/app/ui/CheckoutPage.qml' |
1321 | --- pay-ui/app/ui/CheckoutPage.qml 1970-01-01 00:00:00 +0000 |
1322 | +++ pay-ui/app/ui/CheckoutPage.qml 2016-03-11 14:49:24 +0000 |
1323 | @@ -0,0 +1,404 @@ |
1324 | +/* |
1325 | + * Copyright 2014 Canonical Ltd. |
1326 | + * |
1327 | + * This program is free software; you can redistribute it and/or modify |
1328 | + * it under the terms of the GNU General Public License as published by |
1329 | + * the Free Software Foundation; version 3. |
1330 | + * |
1331 | + * This program is distributed in the hope that it will be useful, |
1332 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
1333 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
1334 | + * GNU General Public License for more details. |
1335 | + * |
1336 | + * You should have received a copy of the GNU General Public License |
1337 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
1338 | + */ |
1339 | + |
1340 | +import QtQuick 2.0 |
1341 | +import Ubuntu.Components 1.1 |
1342 | +import Ubuntu.Components.ListItems 0.1 as ListItem |
1343 | +import Ubuntu.Components.Popups 0.1 |
1344 | +import payui 0.1 as Oxide |
1345 | +import "../components" |
1346 | + |
1347 | +Page { |
1348 | + id: pageCheckout |
1349 | + |
1350 | + title: i18n.tr("Payment") |
1351 | + |
1352 | + property int keyboardSize: Qt.inputMethod.visible ? Qt.inputMethod.keyboardRectangle.height : 0 |
1353 | + property alias selectedItem: paymentTypes.selectedIndex |
1354 | + |
1355 | + property alias itemIcon: iconImage.source |
1356 | + property alias itemTitle: titleLabel.text |
1357 | + property alias itemSubtitle: subtitleLabel.text |
1358 | + property alias price: priceLabel.text |
1359 | + property alias account: accountView.model |
1360 | + property alias model: paymentTypes.model |
1361 | + property alias password: passwordField.text |
1362 | + property alias otp: twoFactorField.text |
1363 | + property alias certificate: otherSecurityStatus.certificate |
1364 | + property alias securityStatus: otherSecurityStatus |
1365 | + |
1366 | + property bool hasPayments: false |
1367 | + property bool hasPreferredPayment: true |
1368 | + property bool hasStoredPayment: false |
1369 | + property bool beforeTimeout: false |
1370 | + |
1371 | + property bool hasSelectedPayment: false |
1372 | + property string backendId: "" |
1373 | + property string paymentId: "" |
1374 | + |
1375 | + signal cancel |
1376 | + signal buy(string email, string password, string otp, string paymentId, string backendId) |
1377 | + signal addCreditCard |
1378 | + |
1379 | + function launchPurchase() { |
1380 | + var pay = paymentTypes.model[pageCheckout.selectedItem]; |
1381 | + var email = accountView.currentItem.email; |
1382 | + pageCheckout.buy(email, password, otp, pay.paymentId, pay.backendId); |
1383 | + } |
1384 | + |
1385 | + function showErrorMessage(message) { |
1386 | + errorLabel.text = message; |
1387 | + errorLabel.visible = true; |
1388 | + } |
1389 | + |
1390 | + function showTwoFactor() { |
1391 | + errorLabel.visible = false; |
1392 | + twoFactorUI.visible = true; |
1393 | + } |
1394 | + |
1395 | + function setSelectedItem() { |
1396 | + for (var i=0; i < pageCheckout.model.length; i++) { |
1397 | + if (pageCheckout.model[i].preferred) { |
1398 | + selectedItem = i; |
1399 | + } |
1400 | + } |
1401 | + } |
1402 | + |
1403 | + QtObject { |
1404 | + id: otherSecurityStatus |
1405 | + property int securityLevel: certificate == null ? Oxide.SecurityStatus.SecurityLevelNone : Oxide.SecurityStatus.SecurityLevelSecure |
1406 | + property var certificate: null |
1407 | + } |
1408 | + |
1409 | + head.actions:[ |
1410 | + Action { |
1411 | + id: lockAction |
1412 | + iconName: pageCheckout.securityStatus.securityLevel == Oxide.SecurityStatus.SecurityLevelSecure ? "lock" : "security-alert" |
1413 | + onTriggered: { |
1414 | + PopupUtils.open(popoverComponent, lockIconPlace, {"securityStatus": pageCheckout.securityStatus}) |
1415 | + } |
1416 | + } |
1417 | + ] |
1418 | + |
1419 | + Component { |
1420 | + id: popoverComponent |
1421 | + |
1422 | + SecurityCertificatePopover { |
1423 | + id: certPopover |
1424 | + securityStatus: null |
1425 | + } |
1426 | + } |
1427 | + |
1428 | + Flickable { |
1429 | + id: checkoutFlickable |
1430 | + anchors { |
1431 | + left: parent.left |
1432 | + right: parent.right |
1433 | + top: parent.top |
1434 | + } |
1435 | + |
1436 | + contentHeight: contentItem.childrenRect.height + pageCheckout.keyboardSize |
1437 | + |
1438 | + Item { |
1439 | + id: header |
1440 | + height: units.gu(8) |
1441 | + anchors { |
1442 | + left: parent.left |
1443 | + right: parent.right |
1444 | + top: parent.top |
1445 | + topMargin: units.gu(1) |
1446 | + } |
1447 | + |
1448 | + UbuntuShape { |
1449 | + id: iconShape |
1450 | + objectName: "iconShape" |
1451 | + anchors { |
1452 | + top: parent.top |
1453 | + left: parent.left |
1454 | + margins: units.gu(1) |
1455 | + } |
1456 | + image: Image { |
1457 | + id: iconImage |
1458 | + objectName: "iconImage" |
1459 | + } |
1460 | + width: units.gu(6) |
1461 | + height: units.gu(6) |
1462 | + } |
1463 | + |
1464 | + Column { |
1465 | + id: col |
1466 | + spacing: units.gu(0.5) |
1467 | + anchors { |
1468 | + left: iconShape.right |
1469 | + top: parent.top |
1470 | + right: priceLabel.left |
1471 | + bottom: parent.bottom |
1472 | + margins: units.gu(1) |
1473 | + } |
1474 | + |
1475 | + Label { |
1476 | + id: titleLabel |
1477 | + objectName: "titleLabel" |
1478 | + fontSize: "medium" |
1479 | + anchors { |
1480 | + left: parent.left |
1481 | + right: parent.right |
1482 | + } |
1483 | + elide: Text.ElideRight |
1484 | + } |
1485 | + Label { |
1486 | + id: subtitleLabel |
1487 | + objectName: "subtitleLabel" |
1488 | + fontSize: "small" |
1489 | + anchors { |
1490 | + left: parent.left |
1491 | + right: parent.right |
1492 | + } |
1493 | + elide: Text.ElideRight |
1494 | + } |
1495 | + } |
1496 | + |
1497 | + Label { |
1498 | + id: priceLabel |
1499 | + objectName: "priceLabel" |
1500 | + font.bold: true |
1501 | + fontSize: "large" |
1502 | + verticalAlignment: Text.AlignVCenter |
1503 | + |
1504 | + anchors { |
1505 | + right: parent.right |
1506 | + top: parent.top |
1507 | + bottom: parent.bottom |
1508 | + rightMargin: units.gu(2) |
1509 | + } |
1510 | + } |
1511 | + } |
1512 | + |
1513 | + Rectangle { |
1514 | + id: separator |
1515 | + height: units.dp(1) |
1516 | + color: "#d5d5d5" |
1517 | + anchors { |
1518 | + left: parent.left |
1519 | + right: parent.right |
1520 | + top: header.bottom |
1521 | + rightMargin: units.gu(2) |
1522 | + leftMargin: units.gu(2) |
1523 | + topMargin: units.gu(1) |
1524 | + } |
1525 | + } |
1526 | + |
1527 | + ListView { |
1528 | + id: accountView |
1529 | + anchors { |
1530 | + left: parent.left |
1531 | + right: parent.right |
1532 | + top: separator.bottom |
1533 | + leftMargin: units.gu(2) |
1534 | + rightMargin: units.gu(2) |
1535 | + topMargin: units.gu(2) |
1536 | + } |
1537 | + height: units.gu(2) |
1538 | + enabled: false |
1539 | + delegate: Label { |
1540 | + id: ubuntuIdLabel |
1541 | + objectName: "ubuntuIdLabel" |
1542 | + text: model.displayName |
1543 | + elide: Text.ElideRight |
1544 | + property string email: model.displayName |
1545 | + } |
1546 | + } |
1547 | + |
1548 | + TextField { |
1549 | + id: passwordField |
1550 | + objectName: "passwordField" |
1551 | + placeholderText: i18n.tr("Enter your Ubuntu One password") |
1552 | + echoMode: TextInput.Password |
1553 | + visible: !pageCheckout.beforeTimeout |
1554 | + anchors { |
1555 | + left: parent.left |
1556 | + right: parent.right |
1557 | + top: accountView.bottom |
1558 | + margins: units.gu(2) |
1559 | + } |
1560 | + |
1561 | + Keys.onReturnPressed: launchPurchase(); |
1562 | + } |
1563 | + |
1564 | + Label { |
1565 | + id: errorLabel |
1566 | + objectName: "errorLabel" |
1567 | + color: "red" |
1568 | + text: "" |
1569 | + wrapMode: Text.WordWrap |
1570 | + visible: false |
1571 | + anchors { |
1572 | + left: parent.left |
1573 | + right: parent.right |
1574 | + top: passwordField.bottom |
1575 | + margins: units.gu(2) |
1576 | + } |
1577 | + } |
1578 | + |
1579 | + Column { |
1580 | + id: twoFactorUI |
1581 | + spacing: units.gu(2) |
1582 | + anchors { |
1583 | + left: parent.left |
1584 | + right: parent.right |
1585 | + top: errorLabel.visible ? errorLabel.bottom : passwordField.bottom |
1586 | + margins: units.gu(2) |
1587 | + } |
1588 | + visible: false |
1589 | + |
1590 | + Label { |
1591 | + id: twoFactorLabel |
1592 | + objectName: "twoFactorLabel" |
1593 | + color: "black" |
1594 | + text: i18n.tr("Type your verification code:") |
1595 | + wrapMode: Text.WordWrap |
1596 | + anchors { |
1597 | + left: parent.left |
1598 | + right: parent.right |
1599 | + } |
1600 | + } |
1601 | + |
1602 | + TextField { |
1603 | + id: twoFactorField |
1604 | + objectName: "twoFactorField" |
1605 | + placeholderText: i18n.tr("2-factor device code") |
1606 | + inputMethodHints: Qt.ImhDigitsOnly |
1607 | + anchors { |
1608 | + left: parent.left |
1609 | + right: parent.right |
1610 | + } |
1611 | + |
1612 | + Keys.onReturnPressed: launchPurchase(); |
1613 | + } |
1614 | + } |
1615 | + |
1616 | + Rectangle { |
1617 | + id: paymentSep |
1618 | + height: units.dp(1) |
1619 | + color: "#d5d5d5" |
1620 | + anchors { |
1621 | + left: parent.left |
1622 | + right: parent.right |
1623 | + top: twoFactorUI.visible ? twoFactorUI.bottom : (errorLabel.visible ? errorLabel.bottom : (passwordField.visible ? passwordField.bottom : accountView.bottom)) |
1624 | + rightMargin: units.gu(2) |
1625 | + leftMargin: units.gu(2) |
1626 | + topMargin: units.gu(2) |
1627 | + } |
1628 | + } |
1629 | + |
1630 | + OptionSelector { |
1631 | + id: paymentTypes |
1632 | + objectName: "paymentTypes" |
1633 | + anchors { |
1634 | + left: parent.left |
1635 | + right: parent.right |
1636 | + top: paymentSep.bottom |
1637 | + margins: units.gu(2) |
1638 | + } |
1639 | + containerHeight: units.gu(24) |
1640 | + expanded: false |
1641 | + delegate: OptionSelectorDelegate { |
1642 | + Item { |
1643 | + anchors.fill: parent |
1644 | + Column { |
1645 | + anchors { |
1646 | + fill: parent |
1647 | + leftMargin: units.gu(2) |
1648 | + topMargin: units.gu(2) |
1649 | + rightMargin: units.gu(5) |
1650 | + } |
1651 | + spacing: units.gu(0.25) |
1652 | + |
1653 | + Label { |
1654 | + text: modelData.name |
1655 | + elide: Text.ElideLeft |
1656 | + anchors { |
1657 | + left: parent.left |
1658 | + right: parent.right |
1659 | + } |
1660 | + fontSize: "small" |
1661 | + } |
1662 | + Label { |
1663 | + text: modelData.description |
1664 | + elide: Text.ElideLeft |
1665 | + anchors { |
1666 | + left: parent.left |
1667 | + right: parent.right |
1668 | + } |
1669 | + fontSize: "x-small" |
1670 | + } |
1671 | + } |
1672 | + } |
1673 | + |
1674 | + height: units.gu(8) |
1675 | + } |
1676 | + } |
1677 | + |
1678 | + Row { |
1679 | + id: rowButtons |
1680 | + anchors { |
1681 | + left: parent.left |
1682 | + right: parent.right |
1683 | + top: paymentTypes.bottom |
1684 | + margins: units.gu(4) |
1685 | + } |
1686 | + |
1687 | + spacing: units.gu(2) |
1688 | + property int buttonsWidth: (width / 2) - (spacing / 2) |
1689 | + |
1690 | + Button { |
1691 | + id: cancelButton |
1692 | + objectName: "cancelButton" |
1693 | + text: i18n.tr("Cancel") |
1694 | + width: parent.buttonsWidth |
1695 | + color: "#797979" |
1696 | + |
1697 | + onClicked: pageCheckout.cancel(); |
1698 | + } |
1699 | + Button { |
1700 | + id: buyButton |
1701 | + objectName: "buyButton" |
1702 | + text: i18n.tr("Buy Now") |
1703 | + color: UbuntuColors.orange |
1704 | + width: parent.buttonsWidth |
1705 | + |
1706 | + onClicked: { |
1707 | + launchPurchase(); |
1708 | + } |
1709 | + } |
1710 | + } |
1711 | + |
1712 | + Label { |
1713 | + id: addCreditCardLabel |
1714 | + objectName: "addCreditCardLabel" |
1715 | + textFormat: Text.RichText |
1716 | + text: '<a href="#"><span style="color: #797979;">%1</span></a>'.arg(i18n.tr("Add credit/debit card")) |
1717 | + anchors { |
1718 | + left: parent.left |
1719 | + right: parent.right |
1720 | + top: rowButtons.bottom |
1721 | + topMargin: units.gu(6) |
1722 | + } |
1723 | + horizontalAlignment: Text.AlignHCenter |
1724 | + onLinkActivated: pageCheckout.addCreditCard(); |
1725 | + } |
1726 | + } |
1727 | +} |
1728 | |
1729 | === added file 'pay-ui/app/ui/ErrorDialog.qml' |
1730 | --- pay-ui/app/ui/ErrorDialog.qml 1970-01-01 00:00:00 +0000 |
1731 | +++ pay-ui/app/ui/ErrorDialog.qml 2016-03-11 14:49:24 +0000 |
1732 | @@ -0,0 +1,67 @@ |
1733 | +/* |
1734 | + * Copyright 2014 Canonical Ltd. |
1735 | + * |
1736 | + * This program is free software; you can redistribute it and/or modify |
1737 | + * it under the terms of the GNU General Public License as published by |
1738 | + * the Free Software Foundation; version 3. |
1739 | + * |
1740 | + * This program is distributed in the hope that it will be useful, |
1741 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
1742 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
1743 | + * GNU General Public License for more details. |
1744 | + * |
1745 | + * You should have received a copy of the GNU General Public License |
1746 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
1747 | + */ |
1748 | + |
1749 | +import QtQuick 2.0 |
1750 | +import Ubuntu.Components 0.1 |
1751 | +import Ubuntu.Components.Popups 0.1 |
1752 | + |
1753 | +Item { |
1754 | + id: dialogErrorItem |
1755 | + property string title: "" |
1756 | + property string message: "" |
1757 | + property bool open: false |
1758 | + |
1759 | + signal retry |
1760 | + signal close |
1761 | + |
1762 | + onOpenChanged: { |
1763 | + if (dialogErrorItem.open) { |
1764 | + PopupUtils.open(errorDialogContainer); |
1765 | + } |
1766 | + } |
1767 | + |
1768 | + Component { |
1769 | + id: errorDialogContainer |
1770 | + |
1771 | + Dialog { |
1772 | + id: errorDialog |
1773 | + title: dialogErrorItem.title |
1774 | + text: dialogErrorItem.message |
1775 | + |
1776 | + Button { |
1777 | + text: i18n.tr("Retry") |
1778 | + objectName: "retryErrorButton" |
1779 | + color: UbuntuColors.orange |
1780 | + onClicked: { |
1781 | + dialogErrorItem.retry(); |
1782 | + dialogErrorItem.open = false; |
1783 | + PopupUtils.close(errorDialog); |
1784 | + } |
1785 | + } |
1786 | + Button { |
1787 | + text: i18n.tr("Close") |
1788 | + objectName: "closeErrorButton" |
1789 | + color: UbuntuColors.coolGrey |
1790 | + onClicked: { |
1791 | + dialogErrorItem.close(); |
1792 | + dialogErrorItem.open = false; |
1793 | + PopupUtils.close(errorDialog); |
1794 | + } |
1795 | + } |
1796 | + } |
1797 | + } |
1798 | + |
1799 | +} |
1800 | |
1801 | === added file 'pay-ui/app/ui/UbuntuPurchaseWebkit.qml' |
1802 | --- pay-ui/app/ui/UbuntuPurchaseWebkit.qml 1970-01-01 00:00:00 +0000 |
1803 | +++ pay-ui/app/ui/UbuntuPurchaseWebkit.qml 2016-03-11 14:49:24 +0000 |
1804 | @@ -0,0 +1,107 @@ |
1805 | +/* |
1806 | + * Copyright 2013-2014 Canonical Ltd. |
1807 | + * |
1808 | + * This program is free software; you can redistribute it and/or modify |
1809 | + * it under the terms of the GNU General Public License as published by |
1810 | + * the Free Software Foundation; version 3. |
1811 | + * |
1812 | + * This program is distributed in the hope that it will be useful, |
1813 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
1814 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
1815 | + * GNU General Public License for more details. |
1816 | + * |
1817 | + * You should have received a copy of the GNU General Public License |
1818 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
1819 | + */ |
1820 | + |
1821 | +import QtQuick 2.0 |
1822 | +import Ubuntu.Components 1.1 |
1823 | +import Ubuntu.Components.Popups 0.1 |
1824 | +import Ubuntu.Web 0.2 |
1825 | +import "../components" |
1826 | + |
1827 | + |
1828 | +Page { |
1829 | + id: pageWebkit |
1830 | + objectName: "pageWebkit" |
1831 | + |
1832 | + signal purchaseSucceeded() |
1833 | + signal purchaseFailed() |
1834 | + signal purchaseCancelled() |
1835 | + signal loading(bool value) |
1836 | + |
1837 | + property int keyboardSize: Qt.inputMethod.visible ? Qt.inputMethod.keyboardRectangle.height : 0 |
1838 | + property alias url: webView.url |
1839 | + property var securityStatus: webView.securityStatus |
1840 | + |
1841 | + function parseQuery(url) { |
1842 | + var argsParsed = {}; |
1843 | + var vars = url.split('?'); |
1844 | + if(vars.length == 1) { |
1845 | + return argsParsed; |
1846 | + } |
1847 | + |
1848 | + var args = vars[1].split('&'); |
1849 | + for (var i=0; i < args.length; i++) { |
1850 | + var arg = decodeURI(args[i]); |
1851 | + if (arg.indexOf('=') == -1) { |
1852 | + argsParsed[arg.trim()] = true; |
1853 | + } else { |
1854 | + var keyvalue = arg.split('='); |
1855 | + argsParsed[keyvalue[0].trim()] = keyvalue[1].trim(); |
1856 | + } |
1857 | + } |
1858 | + |
1859 | + return argsParsed; |
1860 | + } |
1861 | + |
1862 | + head.actions:[ |
1863 | + Action { |
1864 | + id: lockAction |
1865 | + iconName: pageWebkit.securityStatus.securityLevel ? "lock" : "security-alert" |
1866 | + onTriggered: { |
1867 | + PopupUtils.open(popoverComponent, lockIconPlace, {"securityStatus": pageWebkit.securityStatus}) |
1868 | + } |
1869 | + } |
1870 | + ] |
1871 | + |
1872 | + Component { |
1873 | + id: popoverComponent |
1874 | + |
1875 | + SecurityCertificatePopover { |
1876 | + id: certPopover |
1877 | + securityStatus: null |
1878 | + } |
1879 | + } |
1880 | + |
1881 | + WebView { |
1882 | + id: webView |
1883 | + objectName: "webView" |
1884 | + anchors.fill: parent |
1885 | + anchors.bottomMargin: pageWebkit.keyboardSize |
1886 | + |
1887 | + // We need to specify the dialogs to use for JS dialogs here. |
1888 | + alertDialog: AlertDialog {} |
1889 | + confirmDialog: ConfirmDialog {} |
1890 | + promptDialog: PromptDialog {} |
1891 | + beforeUnloadDialog: BeforeUnloadDialog {} |
1892 | + |
1893 | + onLoadingChanged: { |
1894 | + pageWebkit.loading(webView.loading); |
1895 | + } |
1896 | + |
1897 | + onUrlChanged: { |
1898 | + var re_succeeded = new RegExp("/click/succeeded"); |
1899 | + var re_failed = new RegExp("/click/failed"); |
1900 | + var re_cancelled = new RegExp("/click/cancelled"); |
1901 | + |
1902 | + if (re_succeeded.test(webView.url)) { |
1903 | + pageWebkit.purchaseSucceeded(); |
1904 | + } else if (re_failed.test(webView.url)) { |
1905 | + pageWebkit.purchaseFailed(); |
1906 | + } else if (re_cancelled.test(webView.url)) { |
1907 | + pageWebkit.purchaseCancelled(); |
1908 | + } |
1909 | + } |
1910 | + } |
1911 | +} |
1912 | |
1913 | === added directory 'pay-ui/backend' |
1914 | === added file 'pay-ui/backend/CMakeLists.txt' |
1915 | --- pay-ui/backend/CMakeLists.txt 1970-01-01 00:00:00 +0000 |
1916 | +++ pay-ui/backend/CMakeLists.txt 2016-03-11 14:49:24 +0000 |
1917 | @@ -0,0 +1,49 @@ |
1918 | +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wall") |
1919 | + |
1920 | +find_package(Qt5Core) |
1921 | +find_package(Qt5Qml) |
1922 | +find_package(Qt5Quick) |
1923 | + |
1924 | +include_directories( |
1925 | + ${CMAKE_CURRENT_SOURCE_DIR} |
1926 | +) |
1927 | + |
1928 | +pkg_check_modules(UBUNTUONEAUTH REQUIRED ubuntuoneauth-2.0) |
1929 | +add_definitions(${UBUNTUONEAUTH_CFLAGS} ${UBUNTUONEAUTH_CFLAGS_OTHER}) |
1930 | + |
1931 | +set( |
1932 | + payuibackend_SRCS |
1933 | + modules/payui/backend.cpp |
1934 | + modules/payui/purchase.cpp |
1935 | + modules/payui/network.cpp |
1936 | + modules/payui/pay_info.cpp |
1937 | + modules/payui/credentials_service.cpp |
1938 | + modules/payui/certificateadapter.cpp |
1939 | + modules/payui/oxideconstants.cpp |
1940 | +) |
1941 | +set(PAYUI_BACKEND payuibackend) |
1942 | + |
1943 | +add_library(${PAYUI_BACKEND} SHARED |
1944 | + ${payuibackend_SRCS} |
1945 | +) |
1946 | + |
1947 | +target_link_libraries(${PAYUI_BACKEND} |
1948 | + ${UBUNTUONEAUTH_LDFLAGS} |
1949 | +) |
1950 | + |
1951 | +set_target_properties(${PAYUI_BACKEND} PROPERTIES |
1952 | + LIBRARY_OUTPUT_DIRECTORY payui) |
1953 | + |
1954 | +qt5_use_modules(${PAYUI_BACKEND} Gui Qml Quick Network DBus) |
1955 | + |
1956 | +# Copy qmldir file to build dir for running in QtCreator |
1957 | +add_custom_target(payuibackend-qmldir ALL |
1958 | + COMMAND cp ${CMAKE_CURRENT_SOURCE_DIR}/modules/payui/qmldir ${CMAKE_CURRENT_BINARY_DIR}/payui |
1959 | + DEPENDS ${QMLFILES} |
1960 | +) |
1961 | + |
1962 | +# Install plugin file |
1963 | +install(TARGETS ${PAYUI_BACKEND} DESTINATION ${QT_IMPORTS_DIR}/payui/) |
1964 | +install(FILES modules/payui/qmldir DESTINATION ${QT_IMPORTS_DIR}/payui/) |
1965 | + |
1966 | +add_subdirectory (tests) |
1967 | |
1968 | === added directory 'pay-ui/backend/modules' |
1969 | === added directory 'pay-ui/backend/modules/payui' |
1970 | === added file 'pay-ui/backend/modules/payui/backend.cpp' |
1971 | --- pay-ui/backend/modules/payui/backend.cpp 1970-01-01 00:00:00 +0000 |
1972 | +++ pay-ui/backend/modules/payui/backend.cpp 2016-03-11 14:49:24 +0000 |
1973 | @@ -0,0 +1,23 @@ |
1974 | +#include <QtQml> |
1975 | +#include <QtQml/QQmlContext> |
1976 | +#include "backend.h" |
1977 | +#include "purchase.h" |
1978 | +#include "certificateadapter.h" |
1979 | +#include "oxideconstants.h" |
1980 | +#include "pay_info.h" |
1981 | + |
1982 | +void BackendPlugin::registerTypes(const char *uri) |
1983 | +{ |
1984 | + Q_ASSERT(uri == QLatin1String("payui")); |
1985 | + |
1986 | + qmlRegisterType<UbuntuPurchase::Purchase>(uri, 0, 1, "Purchase"); |
1987 | + qmlRegisterType<UbuntuPurchase::PayInfo>(uri, 0, 1, "PayInfo"); |
1988 | + qmlRegisterType<CertificateAdapter>(uri, 0, 1, "CertificateAdapter"); |
1989 | + qmlRegisterType<SecurityStatus>(uri, 0, 1, "SecurityStatus"); |
1990 | + qmlRegisterType<SslCertificate>(uri, 0, 1, "SslCertificate"); |
1991 | +} |
1992 | + |
1993 | +void BackendPlugin::initializeEngine(QQmlEngine *engine, const char *uri) |
1994 | +{ |
1995 | + QQmlExtensionPlugin::initializeEngine(engine, uri); |
1996 | +} |
1997 | |
1998 | === added file 'pay-ui/backend/modules/payui/backend.h' |
1999 | --- pay-ui/backend/modules/payui/backend.h 1970-01-01 00:00:00 +0000 |
2000 | +++ pay-ui/backend/modules/payui/backend.h 2016-03-11 14:49:24 +0000 |
2001 | @@ -0,0 +1,33 @@ |
2002 | +#ifndef BACKEND_PLUGIN_H |
2003 | +#define BACKEND_PLUGIN_H |
2004 | + |
2005 | +#include <QtQml/QQmlEngine> |
2006 | +#include <QtQml/QQmlExtensionPlugin> |
2007 | + |
2008 | +/* |
2009 | + ----8<----- |
2010 | + |
2011 | + import payui 0.1 |
2012 | + |
2013 | + Rectangle { |
2014 | + width: 200 |
2015 | + height: 200 |
2016 | + |
2017 | + Purchase { |
2018 | + id: purchase |
2019 | + } |
2020 | + } |
2021 | + |
2022 | + -----8<------ |
2023 | +*/ |
2024 | +class BackendPlugin : public QQmlExtensionPlugin |
2025 | +{ |
2026 | + Q_OBJECT |
2027 | + Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface") |
2028 | + |
2029 | +public: |
2030 | + void registerTypes(const char *uri); |
2031 | + void initializeEngine(QQmlEngine *engine, const char *uri); |
2032 | +}; |
2033 | +#endif // BACKEND_PLUGIN_H |
2034 | + |
2035 | |
2036 | === added file 'pay-ui/backend/modules/payui/certificateadapter.cpp' |
2037 | --- pay-ui/backend/modules/payui/certificateadapter.cpp 1970-01-01 00:00:00 +0000 |
2038 | +++ pay-ui/backend/modules/payui/certificateadapter.cpp 2016-03-11 14:49:24 +0000 |
2039 | @@ -0,0 +1,46 @@ |
2040 | +/* |
2041 | + * Copyright 2014 Canonical Ltd. |
2042 | + * |
2043 | + * This program is free software; you can redistribute it and/or |
2044 | + * modify it under the terms of version 3 of the GNU Lesser General Public |
2045 | + * License as published by the Free Software Foundation. |
2046 | + * |
2047 | + * This program is distributed in the hope that it will be useful, |
2048 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
2049 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
2050 | + * General Public License for more details. |
2051 | + * |
2052 | + * You should have received a copy of the GNU Lesser General Public |
2053 | + * License along with this library; if not, write to the |
2054 | + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
2055 | + * Boston, MA 02110-1301, USA. |
2056 | + */ |
2057 | + |
2058 | +#include <QStringList> |
2059 | +#include "certificateadapter.h" |
2060 | + |
2061 | +CertificateAdapter::CertificateAdapter() |
2062 | +{ |
2063 | +} |
2064 | + |
2065 | +CertificateAdapter::CertificateAdapter(const CertificateAdapter &other) |
2066 | +{ |
2067 | + m_cert = other.m_cert; |
2068 | +} |
2069 | + |
2070 | +CertificateAdapter::~CertificateAdapter() |
2071 | +{ |
2072 | +} |
2073 | + |
2074 | +CertificateAdapter::CertificateAdapter(QSslCertificate cert, QObject *parent) : |
2075 | + QObject(parent), m_cert(cert) |
2076 | +{ |
2077 | +} |
2078 | + |
2079 | +QString CertificateAdapter::subjectDisplayName() { |
2080 | + return m_cert.subjectInfo(QSslCertificate::CommonName).join(", "); |
2081 | +} |
2082 | + |
2083 | +QStringList CertificateAdapter::getSubjectInfo(int subject) { |
2084 | + return m_cert.subjectInfo((QSslCertificate::SubjectInfo)subject); |
2085 | +} |
2086 | |
2087 | === added file 'pay-ui/backend/modules/payui/certificateadapter.h' |
2088 | --- pay-ui/backend/modules/payui/certificateadapter.h 1970-01-01 00:00:00 +0000 |
2089 | +++ pay-ui/backend/modules/payui/certificateadapter.h 2016-03-11 14:49:24 +0000 |
2090 | @@ -0,0 +1,51 @@ |
2091 | +/* |
2092 | + * Copyright 2014 Canonical Ltd. |
2093 | + * |
2094 | + * This program is free software; you can redistribute it and/or |
2095 | + * modify it under the terms of version 3 of the GNU Lesser General Public |
2096 | + * License as published by the Free Software Foundation. |
2097 | + * |
2098 | + * This program is distributed in the hope that it will be useful, |
2099 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
2100 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
2101 | + * General Public License for more details. |
2102 | + * |
2103 | + * You should have received a copy of the GNU Lesser General Public |
2104 | + * License along with this library; if not, write to the |
2105 | + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
2106 | + * Boston, MA 02110-1301, USA. |
2107 | + */ |
2108 | + |
2109 | +#ifndef CERTIFICATEADAPTER_H |
2110 | +#define CERTIFICATEADAPTER_H |
2111 | + |
2112 | +#include <QObject> |
2113 | +#include <QSslCertificate> |
2114 | +#include <QMetaType> |
2115 | + |
2116 | +/* |
2117 | + * Adapt a QSSlCertificate to the interface that oxide uses for certs. |
2118 | + */ |
2119 | +class CertificateAdapter : public QObject |
2120 | +{ |
2121 | + Q_OBJECT |
2122 | + Q_PROPERTY(QString subjectDisplayName READ subjectDisplayName) |
2123 | +public: |
2124 | + CertificateAdapter(QSslCertificate cert, QObject *parent = 0); |
2125 | + CertificateAdapter(const CertificateAdapter& other); |
2126 | + CertificateAdapter(); |
2127 | + ~CertificateAdapter(); |
2128 | + |
2129 | +signals: |
2130 | + |
2131 | +public slots: |
2132 | + QStringList getSubjectInfo(int subject); |
2133 | + |
2134 | +protected: |
2135 | + QString subjectDisplayName(); |
2136 | + QSslCertificate m_cert; |
2137 | +}; |
2138 | + |
2139 | +Q_DECLARE_METATYPE(CertificateAdapter); |
2140 | + |
2141 | +#endif // CERTIFICATEADAPTER_H |
2142 | |
2143 | === added file 'pay-ui/backend/modules/payui/credentials_service.cpp' |
2144 | --- pay-ui/backend/modules/payui/credentials_service.cpp 1970-01-01 00:00:00 +0000 |
2145 | +++ pay-ui/backend/modules/payui/credentials_service.cpp 2016-03-11 14:49:24 +0000 |
2146 | @@ -0,0 +1,81 @@ |
2147 | +/* |
2148 | + * Copyright 2014 Canonical Ltd. |
2149 | + * |
2150 | + * This library is free software; you can redistribute it and/or |
2151 | + * modify it under the terms of version 3 of the GNU Lesser General Public |
2152 | + * License as published by the Free Software Foundation. |
2153 | + * |
2154 | + * This program is distributed in the hope that it will be useful, |
2155 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
2156 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
2157 | + * General Public License for more details. |
2158 | + * |
2159 | + * You should have received a copy of the GNU Lesser General Public |
2160 | + * License along with this library; if not, write to the |
2161 | + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
2162 | + * Boston, MA 02110-1301, USA. |
2163 | + */ |
2164 | + |
2165 | +#include "credentials_service.h" |
2166 | + |
2167 | +#include <errormessages.h> |
2168 | +#include <token.h> |
2169 | +#include <QProcessEnvironment> |
2170 | + |
2171 | + |
2172 | +namespace UbuntuPurchase { |
2173 | + |
2174 | +bool useFakeCredentials() |
2175 | +{ |
2176 | + QProcessEnvironment environment = QProcessEnvironment::systemEnvironment(); |
2177 | + QString getcreds = environment.value("GET_CREDENTIALS", "1"); |
2178 | + return getcreds != "1"; |
2179 | +} |
2180 | + |
2181 | +CredentialsService::CredentialsService(QObject *parent) : |
2182 | + SSOService(parent) |
2183 | +{ |
2184 | + connect(this, &SSOService::requestFailed, |
2185 | + this, &CredentialsService::handleRequestFailed); |
2186 | +} |
2187 | + |
2188 | +void CredentialsService::getCredentials() |
2189 | +{ |
2190 | + if (useFakeCredentials()) { |
2191 | + Token token("tokenkey", "tokensecret", "consumerkey", "consumersecret"); |
2192 | + Q_EMIT credentialsFound(token); |
2193 | + } else { |
2194 | + if (!m_token.isValid()) { |
2195 | + SSOService::getCredentials(); |
2196 | + } else { |
2197 | + Q_EMIT credentialsFound(m_token); |
2198 | + } |
2199 | + } |
2200 | +} |
2201 | + |
2202 | +void CredentialsService::setCredentials(Token token) |
2203 | +{ |
2204 | + m_token = token; |
2205 | +} |
2206 | + |
2207 | +void CredentialsService::login(const QString email, |
2208 | + const QString password, |
2209 | + const QString otp) |
2210 | +{ |
2211 | + if (m_token.isValid() || useFakeCredentials()) { |
2212 | + Q_EMIT SSOService::credentialsStored(); |
2213 | + } else { |
2214 | + SSOService::login(email, password, otp); |
2215 | + } |
2216 | +} |
2217 | + |
2218 | +void CredentialsService::handleRequestFailed(const ErrorResponse& error) |
2219 | +{ |
2220 | + if (error.httpStatus() == 0 || error.httpReason() == NO_HTTP_REASON) { |
2221 | + emit loginError("Network error, please try again."); |
2222 | + } else { |
2223 | + emit loginError(error.message()); |
2224 | + } |
2225 | +} |
2226 | + |
2227 | +} |
2228 | |
2229 | === added file 'pay-ui/backend/modules/payui/credentials_service.h' |
2230 | --- pay-ui/backend/modules/payui/credentials_service.h 1970-01-01 00:00:00 +0000 |
2231 | +++ pay-ui/backend/modules/payui/credentials_service.h 2016-03-11 14:49:24 +0000 |
2232 | @@ -0,0 +1,53 @@ |
2233 | +/* |
2234 | + * Copyright 2014 Canonical Ltd. |
2235 | + * |
2236 | + * This library is free software; you can redistribute it and/or |
2237 | + * modify it under the terms of version 3 of the GNU Lesser General Public |
2238 | + * License as published by the Free Software Foundation. |
2239 | + * |
2240 | + * This program is distributed in the hope that it will be useful, |
2241 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
2242 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
2243 | + * General Public License for more details. |
2244 | + * |
2245 | + * You should have received a copy of the GNU Lesser General Public |
2246 | + * License along with this library; if not, write to the |
2247 | + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
2248 | + * Boston, MA 02110-1301, USA. |
2249 | + */ |
2250 | + |
2251 | +#ifndef CREDENTIALS_SERVICE_H |
2252 | +#define CREDENTIALS_SERVICE_H |
2253 | + |
2254 | +#include <QObject> |
2255 | +#include <ssoservice.h> |
2256 | +#include <token.h> |
2257 | + |
2258 | +using namespace UbuntuOne; |
2259 | + |
2260 | +namespace UbuntuPurchase { |
2261 | + |
2262 | +class CredentialsService : public SSOService |
2263 | +{ |
2264 | + Q_OBJECT |
2265 | + |
2266 | +public: |
2267 | + explicit CredentialsService(QObject *parent = 0); |
2268 | + |
2269 | + void getCredentials(); |
2270 | + void setCredentials(Token token); |
2271 | + void login(const QString email, const QString password, |
2272 | + const QString otp = QString()); |
2273 | + |
2274 | +Q_SIGNALS: |
2275 | + void loginError(const QString& message); |
2276 | + |
2277 | +private Q_SLOTS: |
2278 | + void handleRequestFailed(const ErrorResponse& error); |
2279 | + |
2280 | +private: |
2281 | + Token m_token; |
2282 | +}; |
2283 | +} |
2284 | + |
2285 | +#endif // CREDENTIALS_SERVICE_H |
2286 | |
2287 | === added file 'pay-ui/backend/modules/payui/network.cpp' |
2288 | --- pay-ui/backend/modules/payui/network.cpp 1970-01-01 00:00:00 +0000 |
2289 | +++ pay-ui/backend/modules/payui/network.cpp 2016-03-11 14:49:24 +0000 |
2290 | @@ -0,0 +1,512 @@ |
2291 | +/* |
2292 | + * Copyright 2014-2016 Canonical Ltd. |
2293 | + * |
2294 | + * This library is free software; you can redistribute it and/or |
2295 | + * modify it under the terms of version 3 of the GNU Lesser General Public |
2296 | + * License as published by the Free Software Foundation. |
2297 | + * |
2298 | + * This program is distributed in the hope that it will be useful, |
2299 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
2300 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
2301 | + * General Public License for more details. |
2302 | + * |
2303 | + * You should have received a copy of the GNU Lesser General Public |
2304 | + * License along with this library; if not, write to the |
2305 | + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
2306 | + * Boston, MA 02110-1301, USA. |
2307 | + */ |
2308 | + |
2309 | +#include "network.h" |
2310 | +#include <QJsonDocument> |
2311 | +#include <QJsonObject> |
2312 | +#include <QJsonArray> |
2313 | +#include <QJsonValue> |
2314 | +#include <QByteArray> |
2315 | +#include <QUrl> |
2316 | +#include <QUrlQuery> |
2317 | +#include <QDebug> |
2318 | +#include <QFile> |
2319 | +#include <QProcessEnvironment> |
2320 | +#include <QDBusInterface> |
2321 | +#include <QDBusReply> |
2322 | +#include <QDBusConnection> |
2323 | + |
2324 | +#include "certificateadapter.h" |
2325 | + |
2326 | +#define PAY_PURCHASES_PATH "/purchases" |
2327 | +#define PAY_PAYMENTMETHODS_PATH "/paymentmethods" |
2328 | +#define PAY_PAYMENTMETHODS_ADD_PATH PAY_PAYMENTMETHODS_PATH + "/add" |
2329 | +#define SUGGESTED_CURRENCY_HEADER_NAME "X-Suggested-Currency" |
2330 | + |
2331 | +#define DEVICE_ID_HEADER "X-Device-Id" |
2332 | + |
2333 | +#define PARTNER_ID_HEADER "X-Partner-ID" |
2334 | +#define PARTNER_ID_FILE "/custom/partner-id" |
2335 | + |
2336 | +#define PREFERED_PAYMENT_TYPE "0" |
2337 | +#define PAYMENT_TYPES "1" |
2338 | +#define BUY_ITEM "2" |
2339 | +#define ITEM_INFO "3" |
2340 | +#define UPDATE_CREDENTIALS "4" |
2341 | +#define CHECK_PASSWORD "5" |
2342 | +#define CHECK_PURCHASED "6" |
2343 | + |
2344 | +#define BUY_COMPLETE "Complete" |
2345 | +#define BUY_IN_PROGRESS "InProgress" |
2346 | + |
2347 | +namespace UbuntuPurchase { |
2348 | + |
2349 | +Network::Network(QObject *parent) : |
2350 | + QObject(parent), |
2351 | + m_nam(this), |
2352 | + m_service(this), |
2353 | + m_preferred(nullptr) |
2354 | +{ |
2355 | + connect(&m_nam, SIGNAL(finished(QNetworkReply*)), |
2356 | + this, SLOT(onReply(QNetworkReply*))); |
2357 | + // SSO SERVICE |
2358 | + connect(&m_service, &CredentialsService::credentialsFound, |
2359 | + this, &Network::handleCredentialsFound); |
2360 | + connect(&m_service, &CredentialsService::credentialsNotFound, |
2361 | + this, &Network::credentialsNotFound); |
2362 | + connect(&m_service, &SSOService::credentialsStored, |
2363 | + this, &Network::handleCredentialsStored); |
2364 | + connect(&m_service, &CredentialsService::loginError, |
2365 | + this, &Network::loginError); |
2366 | + connect(&m_service, &SSOService::twoFactorAuthRequired, |
2367 | + this, &Network::twoFactorAuthRequired); |
2368 | +} |
2369 | + |
2370 | +void Network::getCredentials() |
2371 | +{ |
2372 | + qDebug() << "getting credentials"; |
2373 | + m_service.getCredentials(); |
2374 | +} |
2375 | + |
2376 | +void Network::setCredentials(Token token) |
2377 | +{ |
2378 | + m_service.setCredentials(token); |
2379 | +} |
2380 | + |
2381 | +QMap<QString, QString> buildCurrencyMap() |
2382 | +{ |
2383 | + /* NOTE: The list of currencies we need to handle mapping symbols of. |
2384 | + * Please keep this list in A-Z order. |
2385 | + */ |
2386 | + QMap<QString, QString> currencies_map { |
2387 | + { "CNY", "RMB"}, |
2388 | + { "EUR", "€"}, |
2389 | + { "GBP", "₤"}, |
2390 | + { "HKD", "HK$"}, |
2391 | + { "TWD", "TW$"}, |
2392 | + { "USD", "US$"}, |
2393 | + }; |
2394 | + return currencies_map; |
2395 | +} |
2396 | + |
2397 | +const QString DEFAULT_CURRENCY {"USD"}; |
2398 | + |
2399 | +QString Network::getSymbolForCurrency(const QString ¤cy_code) |
2400 | +{ |
2401 | + static QMap<QString, QString> currency_map = buildCurrencyMap(); |
2402 | + |
2403 | + if (currency_map.contains(currency_code)) { |
2404 | + return currency_map[currency_code]; |
2405 | + } else{ |
2406 | + return currency_code; |
2407 | + } |
2408 | +} |
2409 | + |
2410 | +bool Network::isSupportedCurrency(const QString& currency_code) |
2411 | +{ |
2412 | + static QMap<QString, QString> currency_map = buildCurrencyMap(); |
2413 | + |
2414 | + return currency_map.contains(currency_code); |
2415 | +} |
2416 | + |
2417 | +QString Network::sanitizeUrl(const QUrl& url) |
2418 | +{ |
2419 | + static QRegExp regexp("\\b\\/\\/+"); |
2420 | + return url.toString().replace(regexp, "/"); |
2421 | +} |
2422 | + |
2423 | +QString Network::encodeQuerySlashes(const QString& query) |
2424 | +{ |
2425 | + static QRegExp regexp("\\b\\/"); |
2426 | + QString temp(query); |
2427 | + return temp.replace(regexp, "%2F"); |
2428 | +} |
2429 | + |
2430 | +void Network::onReply(QNetworkReply *reply) |
2431 | +{ |
2432 | + QVariant statusAttr = reply->attribute( |
2433 | + QNetworkRequest::HttpStatusCodeAttribute); |
2434 | + if (!statusAttr.isValid()) { |
2435 | + QString message("Invalid reply status"); |
2436 | + qWarning() << message; |
2437 | + Q_EMIT error(message); |
2438 | + return; |
2439 | + } |
2440 | + |
2441 | + RequestObject* state = qobject_cast<RequestObject*>(reply->request().originatingObject()); |
2442 | + if (state->operation.isEmpty()) { |
2443 | + QString message("Reply received for non valid state."); |
2444 | + qWarning() << message; |
2445 | + Q_EMIT error(message); |
2446 | + return; |
2447 | + } |
2448 | + |
2449 | + int httpStatus = statusAttr.toInt(); |
2450 | + qDebug() << "Reply status:" << httpStatus; |
2451 | + if (httpStatus == 200 || httpStatus == 201) { |
2452 | + QByteArray payload = reply->readAll(); |
2453 | + qDebug() << payload; |
2454 | + QJsonDocument document = QJsonDocument::fromJson(payload); |
2455 | + |
2456 | + if (state->operation.contains(PAYMENT_TYPES) && document.isArray()) { |
2457 | + qDebug() << "Reply state: PAYMENT_TYPES"; |
2458 | + QVariantList listPays; |
2459 | + QJsonArray array = document.array(); |
2460 | + for (int i = 0; i < array.size(); i++) { |
2461 | + QJsonObject object = array.at(i).toObject(); |
2462 | + QString description = object.value("description").toString(); |
2463 | + QString backend = object.value("id").toString(); |
2464 | + bool preferredBackend = object.value("preferred").toBool(); |
2465 | + QJsonArray choices = object.value("choices").toArray(); |
2466 | + for (int j = 0; j < choices.size(); j++) { |
2467 | + QJsonObject choice = choices.at(j).toObject(); |
2468 | + QString name = choice.value("description").toString(); |
2469 | + QString paymentId = QString::number(choice.value("id").toInt()); |
2470 | + bool requiresInteracion = choice.value("requires_interaction").toBool(); |
2471 | + bool preferred = choice.value("preferred").toBool() && preferredBackend; |
2472 | + PayInfo* pay = new PayInfo(); |
2473 | + pay->setPayData(name, description, paymentId, backend, requiresInteracion, preferred); |
2474 | + listPays.append(qVariantFromValue((QObject*)pay)); |
2475 | + if (preferred) { |
2476 | + m_preferred = pay; |
2477 | + } |
2478 | + } |
2479 | + } |
2480 | + qDebug() << "Emit signal paymentTypesObtained"; |
2481 | + Q_EMIT paymentTypesObtained(listPays); |
2482 | + if (m_preferred == nullptr) { |
2483 | + Q_EMIT noPreferredPaymentMethod(); |
2484 | + } |
2485 | + qDebug() << "Emit signal certificateFound"; |
2486 | + CertificateAdapter* cert = new CertificateAdapter(reply->sslConfiguration().peerCertificate()); |
2487 | + Q_EMIT certificateFound(cert); |
2488 | + } else if (state->operation.contains(BUY_ITEM) && document.isObject()) { |
2489 | + qDebug() << "Reply state: BUY_ITEM"; |
2490 | + QJsonObject object = document.object(); |
2491 | + QString state = object.value("state").toString(); |
2492 | + |
2493 | + if (state == BUY_COMPLETE) { |
2494 | + qDebug() << "BUY STATE: complete"; |
2495 | + Q_EMIT buyItemSucceeded(); |
2496 | + } else if (state == BUY_IN_PROGRESS) { |
2497 | + QUrl url(getPayApiUrl(object.value("redirect_to").toString())); |
2498 | + qDebug() << "BUY STATE: in progress"; |
2499 | + qDebug() << "BUY Redirect URL:" << url.toString(); |
2500 | + QString sign = m_token.signUrl(url.toString(), "GET", true); |
2501 | + url.setQuery(encodeQuerySlashes(sign)); |
2502 | + Q_EMIT buyInteractionRequired(url.toString()); |
2503 | + } else { |
2504 | + qDebug() << "BUY STATE: failed"; |
2505 | + Q_EMIT buyItemFailed(); |
2506 | + } |
2507 | + } else if (state->operation.contains(ITEM_INFO) && document.isObject()) { |
2508 | + qDebug() << "Reply state: ITEM_INFO"; |
2509 | + QJsonObject object = document.object(); |
2510 | + QString icon; |
2511 | + QString publisher; |
2512 | + if (object.contains("publisher")) { |
2513 | + publisher = object.value("publisher").toString(); |
2514 | + } |
2515 | + if (object.contains("icon_url")) { |
2516 | + icon = object.value("icon_url").toString(); |
2517 | + } else if (object.contains("icon")) { |
2518 | + icon = object.value("icon").toString(); |
2519 | + } |
2520 | + |
2521 | + QString title = object.value("title").toString(); |
2522 | + |
2523 | + QJsonObject prices = object.value("prices").toObject(); |
2524 | + QString suggested_currency = DEFAULT_CURRENCY; |
2525 | + QString currency = DEFAULT_CURRENCY; |
2526 | + if (reply->hasRawHeader(SUGGESTED_CURRENCY_HEADER_NAME)) { |
2527 | + suggested_currency = reply->rawHeader(SUGGESTED_CURRENCY_HEADER_NAME); |
2528 | + } |
2529 | + const char* env_value = std::getenv(CURRENCY_ENVVAR); |
2530 | + if (env_value != NULL) { |
2531 | + suggested_currency = env_value; |
2532 | + } |
2533 | + |
2534 | + if (isSupportedCurrency(suggested_currency) && prices.contains(suggested_currency)) { |
2535 | + currency = suggested_currency; |
2536 | + } |
2537 | + double price = 0.00; |
2538 | + if (prices[currency].isDouble()) { |
2539 | + price = prices[currency].toDouble(); |
2540 | + } else if (prices[currency].isString()) { |
2541 | + price = prices[currency].toString().toDouble(); |
2542 | + } |
2543 | + QLocale locale; |
2544 | + QString formatted_price = locale.toCurrencyString(price, getSymbolForCurrency(currency)); |
2545 | + qDebug() << "Sending signal: itemDetailsObtained: " << title << " " << formatted_price; |
2546 | + Q_EMIT itemDetailsObtained(title, publisher, currency, formatted_price, icon); |
2547 | + } else if (state->operation.contains(CHECK_PURCHASED)) { |
2548 | + QJsonObject object = document.object(); |
2549 | + auto state = object.value("state").toString(); |
2550 | + if (state == "Complete" || state == "purchased "|| |
2551 | + state == "approved") { |
2552 | + Q_EMIT buyItemSucceeded(); |
2553 | + } else { |
2554 | + Q_EMIT itemNotPurchased(); |
2555 | + } |
2556 | + } else { |
2557 | + QString message("Reply received for non valid state."); |
2558 | + qWarning() << message; |
2559 | + Q_EMIT error(message); |
2560 | + } |
2561 | + |
2562 | + } else if (httpStatus == 401 || httpStatus == 403) { |
2563 | + qWarning() << "Credentials no longer valid. Invalidating."; |
2564 | + m_service.invalidateCredentials(); |
2565 | + Q_EMIT authenticationError(); |
2566 | + } else if (httpStatus == 404 && state->operation.contains(CHECK_PURCHASED)) { |
2567 | + Q_EMIT itemNotPurchased(); |
2568 | + } else { |
2569 | + QString message(QString::number(httpStatus)); |
2570 | + message += ": "; |
2571 | + message += reply->readAll().data(); |
2572 | + qWarning() << message; |
2573 | + Q_EMIT error(message); |
2574 | + } |
2575 | + reply->deleteLater(); |
2576 | +} |
2577 | + |
2578 | +void Network::requestPaymentTypes(const QString& currency) |
2579 | +{ |
2580 | + QNetworkRequest request; |
2581 | + request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); |
2582 | + QUrl url(getPayApiUrl(QString(PAY_API_ROOT) + PAY_PAYMENTMETHODS_PATH + "/")); |
2583 | + QUrlQuery query; |
2584 | + query.addQueryItem("currency", currency); |
2585 | + url.setQuery(query); |
2586 | + qDebug() << "Request Payment Types:" << url.toString(); |
2587 | + signRequestUrl(request, url.toString()); |
2588 | + request.setRawHeader("Accept", "application/json"); |
2589 | + request.setUrl(url); |
2590 | + RequestObject* reqObject = new RequestObject(QString(PAYMENT_TYPES)); |
2591 | + request.setOriginatingObject(reqObject); |
2592 | + m_nam.get(request); |
2593 | +} |
2594 | + |
2595 | +void Network::checkPassword(const QString& email, const QString& password, |
2596 | + const QString& otp, bool purchasing) |
2597 | +{ |
2598 | + m_startPurchase = purchasing; |
2599 | + m_service.login(email, password, otp); |
2600 | +} |
2601 | + |
2602 | +void Network::buyItemWithPreferredPaymentType(const QString& email, const QString& password, const QString& otp, |
2603 | + const QString& appid, const QString& itemid, const QString& currency, |
2604 | + bool recentLogin) |
2605 | +{ |
2606 | + m_selectedPaymentId = m_preferred->paymentId(); |
2607 | + m_selectedBackendId = m_preferred->backendId(); |
2608 | + m_selectedAppId = appid; |
2609 | + m_selectedItemId = itemid; |
2610 | + m_currency = currency; |
2611 | + if (recentLogin) { |
2612 | + purchaseProcess(); |
2613 | + } else { |
2614 | + checkPassword(email, password, otp, true); |
2615 | + } |
2616 | +} |
2617 | + |
2618 | +void Network::buyItem(const QString& email, const QString& password, |
2619 | + const QString& otp, |
2620 | + const QString& appid, const QString& itemid, const QString& currency, |
2621 | + const QString& paymentId, const QString& backendId, bool recentLogin) |
2622 | +{ |
2623 | + m_selectedPaymentId = paymentId; |
2624 | + m_selectedBackendId = backendId; |
2625 | + m_selectedAppId = appid; |
2626 | + m_selectedItemId = itemid; |
2627 | + m_currency = currency; |
2628 | + if (recentLogin) { |
2629 | + purchaseProcess(); |
2630 | + } else { |
2631 | + checkPassword(email, password, otp, true); |
2632 | + } |
2633 | +} |
2634 | + |
2635 | +void Network::purchaseProcess() |
2636 | +{ |
2637 | + QUrl url(getPayApiUrl(QString(PAY_API_ROOT) + PAY_PURCHASES_PATH + "/")); |
2638 | + qDebug() << "Request Purchase:" << url; |
2639 | + qDebug() << "Payment" << m_selectedAppId << m_selectedBackendId << m_selectedPaymentId; |
2640 | + QJsonObject serializer; |
2641 | + |
2642 | + serializer.insert("name", m_selectedAppId); |
2643 | + if (!m_selectedItemId.isEmpty()) { |
2644 | + serializer.insert("item_sku", m_selectedItemId); |
2645 | + } |
2646 | + serializer.insert("backend_id", m_selectedBackendId); |
2647 | + serializer.insert("method_id", m_selectedPaymentId); |
2648 | + serializer.insert("currency", m_currency); |
2649 | + QJsonDocument doc(serializer); |
2650 | + |
2651 | + QByteArray content = doc.toJson(); |
2652 | + |
2653 | + QNetworkRequest request; |
2654 | + request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); |
2655 | + request.setRawHeader(DEVICE_ID_HEADER, getDeviceId().toUtf8().data()); |
2656 | + |
2657 | + // Get the partner ID and add it to the request. |
2658 | + QByteArray partner_id = getPartnerId(); |
2659 | + if (!partner_id.isEmpty()) { |
2660 | + request.setRawHeader(PARTNER_ID_HEADER, partner_id); |
2661 | + } |
2662 | + request.setUrl(url); |
2663 | + signRequestUrl(request, url.toString(), QString("POST")); |
2664 | + RequestObject* reqObject = new RequestObject(QString(BUY_ITEM)); |
2665 | + request.setOriginatingObject(reqObject); |
2666 | + m_nam.post(request, content); |
2667 | +} |
2668 | + |
2669 | +QByteArray Network::getPartnerId() |
2670 | +{ |
2671 | + QByteArray id; |
2672 | + |
2673 | + if (QFile::exists(PARTNER_ID_FILE)) { |
2674 | + QFile pid_file(PARTNER_ID_FILE); |
2675 | + if (pid_file.open(QIODevice::ReadOnly | QIODevice::Text)) { |
2676 | + // Always use lowercase, and trim whitespace. |
2677 | + id = pid_file.readLine().toLower().trimmed(); |
2678 | + qDebug() << "Found partner ID:" << id; |
2679 | + } else { |
2680 | + qWarning() << "Failed to open partner ID file."; |
2681 | + } |
2682 | + } else { |
2683 | + qDebug() << "No partner ID file found."; |
2684 | + } |
2685 | + |
2686 | + return id; |
2687 | +} |
2688 | + |
2689 | +QString Network::getDeviceId() |
2690 | +{ |
2691 | + QDBusInterface iface("com.ubuntu.WhoopsiePreferences", |
2692 | + "/com/ubuntu/WhoopsiePreferences", |
2693 | + "com.ubuntu.WhoopsiePreferences", |
2694 | + QDBusConnection::systemBus(), 0); |
2695 | + QDBusReply<QString> reply = iface.call("GetIdentifier"); |
2696 | + return reply.value(); |
2697 | +} |
2698 | + |
2699 | +void Network::getItemInfo(const QString& packagename, const QString& sku) |
2700 | +{ |
2701 | + QUrl url; |
2702 | + |
2703 | + if (sku.isEmpty()) { |
2704 | + url = getSearchApiUrl(QString(SEARCH_API_ROOT) + "/package/" + packagename); |
2705 | + qDebug() << "Request Item Info:" << url; |
2706 | + QUrlQuery query; |
2707 | + query.addQueryItem("fields", "title,description,price,icon_url"); |
2708 | + url.setQuery(query); |
2709 | + } else { |
2710 | + url = getPayApiUrl(QString(IAP_API_ROOT) + "/packages/" + packagename + "/items/by-sku/" + sku); |
2711 | + } |
2712 | + qDebug() << "Request Item Info:" << url; |
2713 | + |
2714 | + QNetworkRequest request; |
2715 | + request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); |
2716 | + request.setUrl(url); |
2717 | + signRequestUrl(request, url.toString(), QStringLiteral("GET")); |
2718 | + RequestObject* reqObject = new RequestObject(QString(ITEM_INFO)); |
2719 | + request.setOriginatingObject(reqObject); |
2720 | + m_nam.get(request); |
2721 | +} |
2722 | + |
2723 | +QString Network::getEnvironmentValue(const QString& key, |
2724 | + const QString& defaultValue) |
2725 | +{ |
2726 | + QProcessEnvironment environment = QProcessEnvironment::systemEnvironment(); |
2727 | + QString result = environment.value(key, defaultValue); |
2728 | + return result; |
2729 | +} |
2730 | + |
2731 | +QString Network::getPayApiUrl(const QString& path) |
2732 | +{ |
2733 | + QUrl url(getEnvironmentValue(PAY_BASE_URL_ENVVAR, PAY_BASE_URL).append(path)); |
2734 | + return sanitizeUrl(url); |
2735 | +} |
2736 | + |
2737 | +QString Network::getSearchApiUrl(const QString& path) |
2738 | +{ |
2739 | + QUrl url(getEnvironmentValue(SEARCH_BASE_URL_ENVVAR, SEARCH_BASE_URL).append(path)); |
2740 | + return sanitizeUrl(url); |
2741 | +} |
2742 | + |
2743 | +void Network::signRequestUrl(QNetworkRequest& request, QString url, QString method) |
2744 | +{ |
2745 | + QString sign = m_token.signUrl(url, method); |
2746 | + request.setRawHeader("Authorization", sign.toUtf8()); |
2747 | +} |
2748 | + |
2749 | +QString Network::getAddPaymentUrl(const QString& currency) |
2750 | +{ |
2751 | + QUrl payUrl(getPayApiUrl(QString(PAY_API_ROOT) + PAY_PAYMENTMETHODS_ADD_PATH + "/")); |
2752 | + QUrlQuery query; |
2753 | + query.addQueryItem("currency", currency); |
2754 | + payUrl.setQuery(query); |
2755 | + qDebug() << "Get Add Payment URL:" << payUrl; |
2756 | + QString sign = m_token.signUrl(payUrl.toString(), |
2757 | + QStringLiteral("GET"), true); |
2758 | + payUrl.setQuery(encodeQuerySlashes(sign)); |
2759 | + return payUrl.toString(); |
2760 | +} |
2761 | + |
2762 | +QDateTime Network::getTokenUpdated() |
2763 | +{ |
2764 | + return m_token.updated(); |
2765 | +} |
2766 | + |
2767 | +void Network::checkItemPurchased(const QString& appid, const QString& sku) |
2768 | +{ |
2769 | + QUrl url; |
2770 | + if (sku.isEmpty()) { |
2771 | + url = getPayApiUrl(QString(PAY_API_ROOT) + PAY_PURCHASES_PATH + "/" + appid + "/"); |
2772 | + } else { |
2773 | + url = getPayApiUrl(QString(IAP_API_ROOT) + "/packages/" + appid + "/items/by-sku/" + sku); |
2774 | + } |
2775 | + |
2776 | + qDebug() << "Checking for previous purchase:" << url; |
2777 | + QNetworkRequest request; |
2778 | + request.setUrl(url); |
2779 | + signRequestUrl(request, url.toString(), QStringLiteral("GET")); |
2780 | + RequestObject* reqObject = new RequestObject(QString(CHECK_PURCHASED)); |
2781 | + request.setOriginatingObject(reqObject); |
2782 | + m_nam.get(request); |
2783 | +} |
2784 | + |
2785 | +void Network::handleCredentialsFound(Token token) |
2786 | +{ |
2787 | + m_token = token; |
2788 | + Q_EMIT credentialsFound(); |
2789 | +} |
2790 | + |
2791 | +void Network::handleCredentialsStored() |
2792 | +{ |
2793 | + // Get credentials again to update token object. |
2794 | + getCredentials(); |
2795 | + if (m_startPurchase) { |
2796 | + purchaseProcess(); |
2797 | + } else { |
2798 | + Q_EMIT passwordValid(); |
2799 | + } |
2800 | +} |
2801 | + |
2802 | +} |
2803 | |
2804 | === added file 'pay-ui/backend/modules/payui/network.h' |
2805 | --- pay-ui/backend/modules/payui/network.h 1970-01-01 00:00:00 +0000 |
2806 | +++ pay-ui/backend/modules/payui/network.h 2016-03-11 14:49:24 +0000 |
2807 | @@ -0,0 +1,136 @@ |
2808 | +/* |
2809 | + * Copyright 2014-2015 Canonical Ltd. |
2810 | + * |
2811 | + * This library is free software; you can redistribute it and/or |
2812 | + * modify it under the terms of version 3 of the GNU Lesser General Public |
2813 | + * License as published by the Free Software Foundation. |
2814 | + * |
2815 | + * This program is distributed in the hope that it will be useful, |
2816 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
2817 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
2818 | + * General Public License for more details. |
2819 | + * |
2820 | + * You should have received a copy of the GNU Lesser General Public |
2821 | + * License along with this library; if not, write to the |
2822 | + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
2823 | + * Boston, MA 02110-1301, USA. |
2824 | + */ |
2825 | + |
2826 | +#ifndef NETWORK_H |
2827 | +#define NETWORK_H |
2828 | + |
2829 | +#include <QDateTime> |
2830 | +#include <QObject> |
2831 | +#include <QtNetwork/QNetworkReply> |
2832 | +#include <QStringList> |
2833 | +#include <QVariantList> |
2834 | +#include <QUrl> |
2835 | +#include <QtNetwork/QNetworkAccessManager> |
2836 | +#include <token.h> |
2837 | +#include "credentials_service.h" |
2838 | + |
2839 | +#include "certificateadapter.h" |
2840 | +#include "pay_info.h" |
2841 | + |
2842 | +using namespace UbuntuOne; |
2843 | + |
2844 | +namespace UbuntuPurchase { |
2845 | + |
2846 | +constexpr static const char* PAY_BASE_URL_ENVVAR{"PAY_BASE_URL"}; |
2847 | +constexpr static const char* PAY_BASE_URL{"https://myapps.developer.ubuntu.com"}; |
2848 | +constexpr static const char* PAY_API_ROOT{"/api/2.0/click"}; |
2849 | +constexpr static const char* IAP_API_ROOT{"/inventory/api/v1"}; |
2850 | +constexpr static const char* CURRENCY_ENVVAR {"U1_SEARCH_CURRENCY"}; |
2851 | +constexpr static const char* SEARCH_BASE_URL_ENVVAR{"U1_SEARCH_BASE_URL"}; |
2852 | +constexpr static const char* SEARCH_BASE_URL{"https://search.apps.ubuntu.com"}; |
2853 | +constexpr static const char* SEARCH_API_ROOT{"/api/v1"}; |
2854 | + |
2855 | +class RequestObject : public QObject |
2856 | +{ |
2857 | + Q_OBJECT |
2858 | +public: |
2859 | + explicit RequestObject(QString oper, QObject *parent = 0) : |
2860 | + QObject(parent) |
2861 | + { |
2862 | + operation = oper; |
2863 | + } |
2864 | + |
2865 | + QString operation; |
2866 | +}; |
2867 | + |
2868 | +class Network : public QObject |
2869 | +{ |
2870 | + Q_OBJECT |
2871 | +public: |
2872 | + explicit Network(QObject *parent = 0); |
2873 | + |
2874 | + void requestPaymentTypes(const QString& currency); |
2875 | + void buyItem(const QString& email, const QString& password, |
2876 | + const QString& otp, const QString& appid, |
2877 | + const QString& itemid, const QString& currency, const QString& paymentId, |
2878 | + const QString& backendId, bool recentLogin); |
2879 | + void buyItemWithPreferredPaymentType(const QString& email, const QString& password, |
2880 | + const QString& otp, |
2881 | + const QString& appid, const QString& itemid, const QString& currency, |
2882 | + bool recentLogin); |
2883 | + void getItemInfo(const QString& packagename, const QString& sku); |
2884 | + void checkPassword(const QString& email, const QString& password, |
2885 | + const QString& otp, bool purchasing=false); |
2886 | + void getCredentials(); |
2887 | + void setCredentials(Token token); |
2888 | + QString getAddPaymentUrl(const QString& currency); |
2889 | + QDateTime getTokenUpdated(); |
2890 | + void checkItemPurchased(const QString& appid, const QString& sku); |
2891 | + static QString getSymbolForCurrency(const QString& currency_code); |
2892 | + static bool isSupportedCurrency(const QString& currency_code); |
2893 | + static QString sanitizeUrl(const QUrl& url); |
2894 | + static QString encodeQuerySlashes(const QString& query); |
2895 | + virtual QString getEnvironmentValue(const QString& key, |
2896 | + const QString& defaultValue); |
2897 | + virtual QString getPayApiUrl(const QString& path); |
2898 | + virtual QString getSearchApiUrl(const QString& path); |
2899 | + |
2900 | +Q_SIGNALS: |
2901 | + void itemDetailsObtained(QString title, QString publisher, QString currency, QString formatted_price, QString icon); |
2902 | + void paymentTypesObtained(QVariantList payments); |
2903 | + void buyItemSucceeded(); |
2904 | + void buyItemFailed(); |
2905 | + void buyInteractionRequired(QString url); |
2906 | + void error(QString message); |
2907 | + void authenticationError(); |
2908 | + void credentialsNotFound(); |
2909 | + void credentialsFound(); |
2910 | + void passwordValid(); |
2911 | + void noPreferredPaymentMethod(); |
2912 | + void loginError(const QString& message); |
2913 | + void twoFactorAuthRequired(); |
2914 | + void itemNotPurchased(); |
2915 | + void certificateFound(QObject* cert); |
2916 | + |
2917 | +private Q_SLOTS: |
2918 | + void onReply(QNetworkReply*); |
2919 | + void handleCredentialsFound(Token token); |
2920 | + void handleCredentialsStored(); |
2921 | + void purchaseProcess(); |
2922 | + |
2923 | +private: |
2924 | + QNetworkAccessManager m_nam; |
2925 | + QNetworkRequest m_request; |
2926 | + CredentialsService m_service; |
2927 | + Token m_token; |
2928 | + PayInfo* m_preferred; |
2929 | + QString m_selectedPaymentId; |
2930 | + QString m_selectedBackendId; |
2931 | + QString m_selectedAppId; |
2932 | + QString m_selectedItemId; |
2933 | + QString m_currency; |
2934 | + bool m_startPurchase = false; |
2935 | + |
2936 | + void signRequestUrl(QNetworkRequest& request, QString url, QString method="GET"); |
2937 | + QString getDeviceId(); |
2938 | + QByteArray getPartnerId(); |
2939 | +}; |
2940 | + |
2941 | +} |
2942 | + |
2943 | +#endif // NETWORK_H |
2944 | |
2945 | === added file 'pay-ui/backend/modules/payui/oxideconstants.cpp' |
2946 | --- pay-ui/backend/modules/payui/oxideconstants.cpp 1970-01-01 00:00:00 +0000 |
2947 | +++ pay-ui/backend/modules/payui/oxideconstants.cpp 2016-03-11 14:49:24 +0000 |
2948 | @@ -0,0 +1,19 @@ |
2949 | +/* |
2950 | + * Copyright 2014 Canonical Ltd. |
2951 | + * |
2952 | + * This program is free software; you can redistribute it and/or |
2953 | + * modify it under the terms of version 3 of the GNU Lesser General Public |
2954 | + * License as published by the Free Software Foundation. |
2955 | + * |
2956 | + * This program is distributed in the hope that it will be useful, |
2957 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
2958 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
2959 | + * General Public License for more details. |
2960 | + * |
2961 | + * You should have received a copy of the GNU Lesser General Public |
2962 | + * License along with this library; if not, write to the |
2963 | + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
2964 | + * Boston, MA 02110-1301, USA. |
2965 | + */ |
2966 | + |
2967 | +#include "oxideconstants.h" |
2968 | |
2969 | === added file 'pay-ui/backend/modules/payui/oxideconstants.h' |
2970 | --- pay-ui/backend/modules/payui/oxideconstants.h 1970-01-01 00:00:00 +0000 |
2971 | +++ pay-ui/backend/modules/payui/oxideconstants.h 2016-03-11 14:49:24 +0000 |
2972 | @@ -0,0 +1,53 @@ |
2973 | +/* |
2974 | + * Copyright 2014 Canonical Ltd. |
2975 | + * |
2976 | + * This program is free software; you can redistribute it and/or |
2977 | + * modify it under the terms of version 3 of the GNU Lesser General Public |
2978 | + * License as published by the Free Software Foundation. |
2979 | + * |
2980 | + * This program is distributed in the hope that it will be useful, |
2981 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
2982 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
2983 | + * General Public License for more details. |
2984 | + * |
2985 | + * You should have received a copy of the GNU Lesser General Public |
2986 | + * License along with this library; if not, write to the |
2987 | + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
2988 | + * Boston, MA 02110-1301, USA. |
2989 | + */ |
2990 | + |
2991 | +#ifndef OXIDECONSTANTS_H |
2992 | +#define OXIDECONSTANTS_H |
2993 | + |
2994 | +#include <QObject> |
2995 | + |
2996 | +class SecurityStatus : public QObject |
2997 | +{ |
2998 | + Q_OBJECT |
2999 | + Q_ENUMS(SecurityLevel) |
3000 | +public: |
3001 | + enum SecurityLevel { |
3002 | + SecurityLevelNone, |
3003 | + SecurityLevelSecure, |
3004 | + SecurityLevelSecureEV, |
3005 | + SecurityLevelWarning, |
3006 | + SecurityLevelError |
3007 | + }; |
3008 | +}; |
3009 | + |
3010 | +class SslCertificate : public QObject |
3011 | +{ |
3012 | + Q_OBJECT |
3013 | + Q_ENUMS(PrincipalAttr) |
3014 | +public: |
3015 | + enum PrincipalAttr { |
3016 | + PrincipalAttrOrganizationName, |
3017 | + PrincipalAttrCommonName, |
3018 | + PrincipalAttrLocalityName, |
3019 | + PrincipalAttrOrganizationUnitName, |
3020 | + PrincipalAttrCountryName, |
3021 | + PrincipalAttrStateOrProvinceName |
3022 | + }; |
3023 | +}; |
3024 | + |
3025 | +#endif // OXIDECONSTANTS_H |
3026 | |
3027 | === added file 'pay-ui/backend/modules/payui/pay_info.cpp' |
3028 | --- pay-ui/backend/modules/payui/pay_info.cpp 1970-01-01 00:00:00 +0000 |
3029 | +++ pay-ui/backend/modules/payui/pay_info.cpp 2016-03-11 14:49:24 +0000 |
3030 | @@ -0,0 +1,37 @@ |
3031 | +/* |
3032 | + * Copyright 2014 Canonical Ltd. |
3033 | + * |
3034 | + * This library is free software; you can redistribute it and/or |
3035 | + * modify it under the terms of version 3 of the GNU Lesser General Public |
3036 | + * License as published by the Free Software Foundation. |
3037 | + * |
3038 | + * This program is distributed in the hope that it will be useful, |
3039 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
3040 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
3041 | + * General Public License for more details. |
3042 | + * |
3043 | + * You should have received a copy of the GNU Lesser General Public |
3044 | + * License along with this library; if not, write to the |
3045 | + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
3046 | + * Boston, MA 02110-1301, USA. |
3047 | + */ |
3048 | + |
3049 | +#include "pay_info.h" |
3050 | +namespace UbuntuPurchase { |
3051 | + |
3052 | +PayInfo::PayInfo(QObject *parent) : |
3053 | + QObject(parent) |
3054 | +{ |
3055 | +} |
3056 | + |
3057 | +void PayInfo::setPayData(QString name, QString description, QString payment, QString backend, bool requiresInteracion, bool preferred) |
3058 | +{ |
3059 | + m_name = name; |
3060 | + m_paymentId = payment; |
3061 | + m_description = description; |
3062 | + m_backendId = backend; |
3063 | + m_requiresInteracion = requiresInteracion; |
3064 | + m_preferred = preferred; |
3065 | +} |
3066 | + |
3067 | +} |
3068 | |
3069 | === added file 'pay-ui/backend/modules/payui/pay_info.h' |
3070 | --- pay-ui/backend/modules/payui/pay_info.h 1970-01-01 00:00:00 +0000 |
3071 | +++ pay-ui/backend/modules/payui/pay_info.h 2016-03-11 14:49:24 +0000 |
3072 | @@ -0,0 +1,73 @@ |
3073 | +/* |
3074 | + * Copyright 2014 Canonical Ltd. |
3075 | + * |
3076 | + * This library is free software; you can redistribute it and/or |
3077 | + * modify it under the terms of version 3 of the GNU Lesser General Public |
3078 | + * License as published by the Free Software Foundation. |
3079 | + * |
3080 | + * This program is distributed in the hope that it will be useful, |
3081 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
3082 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
3083 | + * General Public License for more details. |
3084 | + * |
3085 | + * You should have received a copy of the GNU Lesser General Public |
3086 | + * License along with this library; if not, write to the |
3087 | + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
3088 | + * Boston, MA 02110-1301, USA. |
3089 | + */ |
3090 | + |
3091 | +#ifndef PAY_INFO_H |
3092 | +#define PAY_INFO_H |
3093 | + |
3094 | +#include <QObject> |
3095 | +#include <QString> |
3096 | + |
3097 | +namespace UbuntuPurchase { |
3098 | + |
3099 | +class PayInfo : public QObject |
3100 | +{ |
3101 | + Q_OBJECT |
3102 | + Q_PROPERTY(QString name READ name NOTIFY nameChanged) |
3103 | + Q_PROPERTY(QString paymentId READ paymentId NOTIFY paymentIdChanged) |
3104 | + Q_PROPERTY(QString backendId READ backendId NOTIFY backendIdChanged) |
3105 | + Q_PROPERTY(QString description READ description NOTIFY descriptionChanged) |
3106 | + Q_PROPERTY(bool requiresInteracion READ requiresInteracion NOTIFY requiresInteracionChanged) |
3107 | + Q_PROPERTY(bool preferred READ preferred WRITE setPreferred NOTIFY preferredChanged) |
3108 | + |
3109 | +public: |
3110 | + explicit PayInfo(QObject *parent = 0); |
3111 | + |
3112 | + QString name() { return m_name; } |
3113 | + QString paymentId() { return m_paymentId; } |
3114 | + QString backendId() { return m_backendId; } |
3115 | + QString description() { return m_description; } |
3116 | + bool requiresInteracion() { return m_requiresInteracion; } |
3117 | + bool preferred() { return m_preferred; } |
3118 | + |
3119 | + void setPayData(QString name, QString description, QString payment, QString backend, bool steps, bool preferred); |
3120 | + void setName(const QString& name) { m_name = name; Q_EMIT nameChanged(); } |
3121 | + void setDescription(const QString& description) { m_description = description; Q_EMIT descriptionChanged(); } |
3122 | + void setPaymentId(const QString& paymentId) { m_paymentId = paymentId; Q_EMIT paymentIdChanged(); } |
3123 | + void setbackendId(const QString& backendId) { m_backendId = backendId; Q_EMIT backendIdChanged(); } |
3124 | + void setRequiresInteracion(bool requiresInteracion) { m_requiresInteracion = requiresInteracion; Q_EMIT requiresInteracionChanged(); } |
3125 | + void setPreferred(bool preferred) { m_preferred = preferred; Q_EMIT preferredChanged(); } |
3126 | + |
3127 | +Q_SIGNALS: |
3128 | + void nameChanged(); |
3129 | + void paymentIdChanged(); |
3130 | + void backendIdChanged(); |
3131 | + void descriptionChanged(); |
3132 | + void requiresInteracionChanged(); |
3133 | + void preferredChanged(); |
3134 | + |
3135 | +private: |
3136 | + QString m_name; |
3137 | + QString m_paymentId; |
3138 | + QString m_backendId; |
3139 | + QString m_description; |
3140 | + bool m_requiresInteracion; |
3141 | + bool m_preferred; |
3142 | +}; |
3143 | +} |
3144 | + |
3145 | +#endif // PAY_INFO_H |
3146 | |
3147 | === added file 'pay-ui/backend/modules/payui/purchase.cpp' |
3148 | --- pay-ui/backend/modules/payui/purchase.cpp 1970-01-01 00:00:00 +0000 |
3149 | +++ pay-ui/backend/modules/payui/purchase.cpp 2016-03-11 14:49:24 +0000 |
3150 | @@ -0,0 +1,154 @@ |
3151 | +/* |
3152 | + * Copyright 2014-2016 Canonical Ltd. |
3153 | + * |
3154 | + * This library is free software; you can redistribute it and/or |
3155 | + * modify it under the terms of version 3 of the GNU Lesser General Public |
3156 | + * License as published by the Free Software Foundation. |
3157 | + * |
3158 | + * This program is distributed in the hope that it will be useful, |
3159 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
3160 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
3161 | + * General Public License for more details. |
3162 | + * |
3163 | + * You should have received a copy of the GNU Lesser General Public |
3164 | + * License along with this library; if not, write to the |
3165 | + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
3166 | + * Boston, MA 02110-1301, USA. |
3167 | + */ |
3168 | + |
3169 | +#include "purchase.h" |
3170 | +#include <QUrl> |
3171 | +#include <QUrlQuery> |
3172 | +#include <QCoreApplication> |
3173 | +#include <QDateTime> |
3174 | +#include <QtDebug> |
3175 | + |
3176 | +#include <logging.h> |
3177 | + |
3178 | +namespace UbuntuPurchase { |
3179 | + |
3180 | +Purchase::Purchase(QObject *parent) : |
3181 | + QObject(parent), |
3182 | + m_network(this) |
3183 | +{ |
3184 | + connect(&m_network, &Network::itemDetailsObtained, |
3185 | + this, &Purchase::itemDetailsObtained); |
3186 | + connect(&m_network, &Network::paymentTypesObtained, |
3187 | + this, &Purchase::paymentTypesObtained); |
3188 | + connect(&m_network, &Network::buyItemSucceeded, |
3189 | + this, &Purchase::buyItemSucceeded); |
3190 | + connect(&m_network, &Network::buyItemFailed, |
3191 | + this, &Purchase::buyItemFailed); |
3192 | + connect(&m_network, &Network::buyInteractionRequired, |
3193 | + this, &Purchase::buyInterationRequired); |
3194 | + connect(&m_network, &Network::error, |
3195 | + this, &Purchase::error); |
3196 | + connect(&m_network, &Network::authenticationError, |
3197 | + this, &Purchase::authenticationError); |
3198 | + connect(&m_network, &Network::credentialsNotFound, |
3199 | + this, &Purchase::credentialsNotFound); |
3200 | + connect(&m_network, &Network::credentialsFound, |
3201 | + this, &Purchase::credentialsFound); |
3202 | + connect(&m_network, &Network::passwordValid, |
3203 | + this, &Purchase::passwordValid); |
3204 | + connect(&m_network, &Network::noPreferredPaymentMethod, |
3205 | + this, &Purchase::noPreferredPaymentMethod); |
3206 | + connect(&m_network, &Network::loginError, |
3207 | + this, &Purchase::loginError); |
3208 | + connect(&m_network, &Network::twoFactorAuthRequired, |
3209 | + this, &Purchase::twoFactorAuthRequired); |
3210 | + connect(&m_network, &Network::itemNotPurchased, |
3211 | + this, &Purchase::itemNotPurchased); |
3212 | + connect(&m_network, &Network::certificateFound, |
3213 | + this, &Purchase::certificateFound); |
3214 | + |
3215 | + QCoreApplication* instance = QCoreApplication::instance(); |
3216 | + |
3217 | + // Set up logging |
3218 | + UbuntuOne::AuthLogger::setupLogging(); |
3219 | + const char* u1_debug = getenv("U1_DEBUG"); |
3220 | + if (u1_debug != NULL && strcmp(u1_debug, "") != 0) { |
3221 | + UbuntuOne::AuthLogger::setLogLevel(QtDebugMsg); |
3222 | + } |
3223 | + |
3224 | + // Handle the purchase: URI |
3225 | + for (int i = 0; i < instance->arguments().size(); i++) { |
3226 | + QString argument = instance->arguments().at(i); |
3227 | + if (argument.startsWith("purchase://")) { |
3228 | + QUrl data(argument); |
3229 | + m_appid = data.host(); |
3230 | + m_itemid = data.fileName(); |
3231 | + qDebug() << "Purchase requested for" << m_itemid << "by app" << m_appid; |
3232 | + break; |
3233 | + } |
3234 | + } |
3235 | +} |
3236 | + |
3237 | +Purchase::~Purchase() |
3238 | +{ |
3239 | + UbuntuOne::AuthLogger::stopLogging(); |
3240 | +} |
3241 | + |
3242 | +void Purchase::quitCancel() |
3243 | +{ |
3244 | + qDebug() << "Purchase Canceled: exit code 1"; |
3245 | + QCoreApplication::exit(1); |
3246 | +} |
3247 | + |
3248 | +void Purchase::quitSuccess() |
3249 | +{ |
3250 | + qDebug() << "Purchase Succeeded: exit code 0"; |
3251 | + QCoreApplication::exit(0); |
3252 | +} |
3253 | + |
3254 | +void Purchase::checkCredentials() |
3255 | +{ |
3256 | + m_network.getCredentials(); |
3257 | +} |
3258 | + |
3259 | +QString Purchase::getAddPaymentUrl(QString currency) |
3260 | +{ |
3261 | + return m_network.getAddPaymentUrl(currency); |
3262 | +} |
3263 | + |
3264 | +void Purchase::getItemDetails() |
3265 | +{ |
3266 | + if (m_appid.isEmpty() && m_itemid.isEmpty()) { |
3267 | + qCritical() << "AppId or ItemId not provided"; |
3268 | + quitCancel(); |
3269 | + } |
3270 | + qDebug() << "Getting Item Details"; |
3271 | + m_network.getItemInfo(m_appid, m_itemid); |
3272 | +} |
3273 | + |
3274 | +void Purchase::getPaymentTypes(const QString& currency) |
3275 | +{ |
3276 | + m_network.requestPaymentTypes(currency); |
3277 | +} |
3278 | + |
3279 | +void Purchase::checkWallet(QString email, QString password, QString otp) |
3280 | +{ |
3281 | + m_network.checkPassword(email, password, otp); |
3282 | +} |
3283 | + |
3284 | +void Purchase::buyItemWithPreferredPayment(QString email, QString password, QString otp, QString currency, bool recentLogin) |
3285 | +{ |
3286 | + m_network.buyItemWithPreferredPaymentType(email, password, otp, m_appid, m_itemid, currency, recentLogin); |
3287 | +} |
3288 | + |
3289 | +void Purchase::buyItem(QString email, QString password, QString otp, QString currency, QString paymentId, QString backendId, bool recentLogin) |
3290 | +{ |
3291 | + m_network.buyItem(email, password, otp, m_appid, m_itemid, currency, paymentId, backendId, recentLogin); |
3292 | +} |
3293 | + |
3294 | +QDateTime Purchase::getTokenUpdated() |
3295 | +{ |
3296 | + return m_network.getTokenUpdated(); |
3297 | +} |
3298 | + |
3299 | +void Purchase::checkItemPurchased() |
3300 | +{ |
3301 | + m_network.checkItemPurchased(m_appid, m_itemid); |
3302 | +} |
3303 | + |
3304 | +} |
3305 | |
3306 | === added file 'pay-ui/backend/modules/payui/purchase.h' |
3307 | --- pay-ui/backend/modules/payui/purchase.h 1970-01-01 00:00:00 +0000 |
3308 | +++ pay-ui/backend/modules/payui/purchase.h 2016-03-11 14:49:24 +0000 |
3309 | @@ -0,0 +1,59 @@ |
3310 | +#ifndef PURCHASE_H |
3311 | +#define PURCHASE_H |
3312 | + |
3313 | +#include <QDateTime> |
3314 | +#include <QObject> |
3315 | +#include <QString> |
3316 | + |
3317 | +#include "network.h" |
3318 | +#include "certificateadapter.h" |
3319 | + |
3320 | +namespace UbuntuPurchase { |
3321 | + |
3322 | +class Purchase : public QObject |
3323 | +{ |
3324 | + Q_OBJECT |
3325 | + |
3326 | +public: |
3327 | + explicit Purchase(QObject *parent = 0); |
3328 | + ~Purchase(); |
3329 | + |
3330 | + Q_INVOKABLE void getItemDetails(); |
3331 | + Q_INVOKABLE void getPaymentTypes(const QString ¤cy); |
3332 | + Q_INVOKABLE void buyItemWithPreferredPayment(QString email, QString password, QString otp, QString currency, bool recentLogin); |
3333 | + Q_INVOKABLE void buyItem(QString email, QString password, QString otp, QString currency, QString paymentId, QString backendId, bool recentLogin); |
3334 | + Q_INVOKABLE void checkCredentials(); |
3335 | + Q_INVOKABLE QString getAddPaymentUrl(QString currency); |
3336 | + Q_INVOKABLE void checkWallet(QString email, QString password, QString otp); |
3337 | + |
3338 | + Q_INVOKABLE void quitSuccess(); |
3339 | + Q_INVOKABLE void quitCancel(); |
3340 | + Q_INVOKABLE QDateTime getTokenUpdated(); |
3341 | + Q_INVOKABLE void checkItemPurchased(); |
3342 | + |
3343 | +Q_SIGNALS: |
3344 | + void itemDetailsObtained(QString title, QString publisher, QString currency, QString formatted_price, QString icon); |
3345 | + void paymentTypesObtained(QVariantList payments); |
3346 | + void buyItemSucceeded(); |
3347 | + void buyItemFailed(); |
3348 | + void buyInterationRequired(QString url); |
3349 | + void error(QString message); |
3350 | + void authenticationError(); |
3351 | + void credentialsNotFound(); |
3352 | + void credentialsFound(); |
3353 | + void passwordValid(); |
3354 | + void noPreferredPaymentMethod(); |
3355 | + void loginError(const QString& message); |
3356 | + void twoFactorAuthRequired(); |
3357 | + void itemNotPurchased(); |
3358 | + void certificateFound(QObject* cert); |
3359 | + |
3360 | +private: |
3361 | + Network m_network; |
3362 | + QString m_appid; |
3363 | + QString m_itemid; |
3364 | +}; |
3365 | + |
3366 | +} |
3367 | + |
3368 | +#endif // PURCHASE_H |
3369 | |
3370 | === added file 'pay-ui/backend/modules/payui/qmldir' |
3371 | --- pay-ui/backend/modules/payui/qmldir 1970-01-01 00:00:00 +0000 |
3372 | +++ pay-ui/backend/modules/payui/qmldir 2016-03-11 14:49:24 +0000 |
3373 | @@ -0,0 +1,2 @@ |
3374 | +module payui |
3375 | +plugin payuibackend |
3376 | |
3377 | === added directory 'pay-ui/backend/tests' |
3378 | === added file 'pay-ui/backend/tests/CMakeLists.txt' |
3379 | --- pay-ui/backend/tests/CMakeLists.txt 1970-01-01 00:00:00 +0000 |
3380 | +++ pay-ui/backend/tests/CMakeLists.txt 2016-03-11 14:49:24 +0000 |
3381 | @@ -0,0 +1,30 @@ |
3382 | + |
3383 | +include_directories (${CMAKE_CURRENT_SOURCE_DIR}/../payui) |
3384 | +set(CMAKE_INCLUDE_CURRENT_DIR ON) |
3385 | +set(CMAKE_AUTOMOC ON) |
3386 | +find_package(Qt5Core REQUIRED) |
3387 | + |
3388 | +set(PURCHASE_TESTS_TARGET test-purchase-ui) |
3389 | + |
3390 | +add_executable(${PURCHASE_TESTS_TARGET} |
3391 | + test_network.cpp |
3392 | +) |
3393 | +target_link_libraries(${PURCHASE_TESTS_TARGET} |
3394 | + -Wl,-rpath,${CMAKE_CURRENT_BINARY_DIR}/../payui |
3395 | + -L${CMAKE_CURRENT_BINARY_DIR}/../payui |
3396 | + ${PAYUI_BACKEND} |
3397 | +) |
3398 | +qt5_use_modules(${PURCHASE_TESTS_TARGET} Qml Quick Core DBus Xml Network Test) |
3399 | + |
3400 | +add_custom_target(payui-cppunit-tests |
3401 | + COMMAND env LANGUAGE=en_US.UTF-8 LANG=en_US.UTF-8 LC_ALL=en_US.UTF-8 ${XVFB_CMD} ${CMAKE_CURRENT_BINARY_DIR}/${PURCHASE_TESTS_TARGET} |
3402 | + DEPENDS ${PURCHASE_TESTS_TARGET} mock_click_server.py |
3403 | +) |
3404 | + |
3405 | +add_test(NAME payui-cppunit-tests |
3406 | + COMMAND make payui-cppunit-tests |
3407 | +) |
3408 | + |
3409 | +add_custom_target(mock_click_server.py |
3410 | + COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/mock_click_server.py ${CMAKE_CURRENT_BINARY_DIR}/mock_click_server.py |
3411 | +) |
3412 | |
3413 | === added file 'pay-ui/backend/tests/mock_click_server.py' |
3414 | --- pay-ui/backend/tests/mock_click_server.py 1970-01-01 00:00:00 +0000 |
3415 | +++ pay-ui/backend/tests/mock_click_server.py 2016-03-11 14:49:24 +0000 |
3416 | @@ -0,0 +1,162 @@ |
3417 | +import json |
3418 | +import threading |
3419 | +from http.server import BaseHTTPRequestHandler, HTTPServer |
3420 | + |
3421 | + |
3422 | +KEEP_ALIVE = True |
3423 | + |
3424 | + |
3425 | +class MyHandler(BaseHTTPRequestHandler): |
3426 | + |
3427 | + def do_HEAD(self): |
3428 | + self.send_response(200) |
3429 | + self.send_header("X-Click-Token", "X-Click-Token") |
3430 | + self.end_headers() |
3431 | + |
3432 | + def response_payment_types(self, fail=False): |
3433 | + types = [ |
3434 | + { |
3435 | + "description": "PayPal", |
3436 | + "id": "paypal", |
3437 | + "preferred": False, |
3438 | + "choices": [ |
3439 | + { |
3440 | + "currencies": [ |
3441 | + "USD", |
3442 | + "GBP", |
3443 | + "EUR" |
3444 | + ], |
3445 | + "id": 532, |
3446 | + "requires_interaction": False, |
3447 | + "preferred": False, |
3448 | + "description": "PayPal Preapproved Payment (exp. 2014-04-12)" |
3449 | + } |
3450 | + ] |
3451 | + }, |
3452 | + { |
3453 | + "description": "Credit or Debit Card", |
3454 | + "id": "credit_card", |
3455 | + "preferred": True, |
3456 | + "choices": [ |
3457 | + { |
3458 | + "currencies": [ |
3459 | + "USD" |
3460 | + ], |
3461 | + "id": 1767, |
3462 | + "requires_interaction": False, |
3463 | + "preferred": False, |
3464 | + "description": "**** **** **** 1111 (Visa, exp. 02/2015)" |
3465 | + }, |
3466 | + { |
3467 | + "currencies": [ |
3468 | + "USD" |
3469 | + ], |
3470 | + "id": 1726, |
3471 | + "requires_interaction": False, |
3472 | + "preferred": True, |
3473 | + "description": "**** **** **** 1111 (Visa, exp. 03/2015)" |
3474 | + } |
3475 | + ] |
3476 | + } |
3477 | + ] |
3478 | + if fail: |
3479 | + self.send_response(404) |
3480 | + else: |
3481 | + self.send_response(200) |
3482 | + self.send_header("Content-type", "application/json") |
3483 | + self.end_headers() |
3484 | + self.wfile.write(bytes(json.dumps(types), 'UTF-8')) |
3485 | + |
3486 | + def response_buy_item(self, fail=False, interaction=False): |
3487 | + state = "Complete" if not interaction else "InProgress" |
3488 | + response = { |
3489 | + "state": state, |
3490 | + } |
3491 | + if interaction: |
3492 | + # Real server returns path starting with / here. |
3493 | + response["redirect_to"] = "/redirect.url?currency=USD" |
3494 | + if fail: |
3495 | + response = {} |
3496 | + |
3497 | + if self.path.find("/notpurchased/") != -1: |
3498 | + self.send_response(404) |
3499 | + else: |
3500 | + self.send_response(200) |
3501 | + |
3502 | + self.send_header("Content-type", "application/json") |
3503 | + self.end_headers() |
3504 | + self.wfile.write(bytes(json.dumps(response), 'UTF-8')) |
3505 | + |
3506 | + def response_item_info(self, fail, eurozone, dotar): |
3507 | + response = { |
3508 | + "title": "title", |
3509 | + "publisher": "publisher", |
3510 | + "price": 9.99, |
3511 | + "prices": { |
3512 | + "USD": 1.99, |
3513 | + "EUR": 1.69, |
3514 | + "GBP": 1.29, |
3515 | + "ARS": 18.05, |
3516 | + }, |
3517 | + "icon_url": "icon_url", |
3518 | + } |
3519 | + if fail: |
3520 | + self.send_response(404) |
3521 | + else: |
3522 | + self.send_response(200) |
3523 | + self.send_header("Content-type", "application/json") |
3524 | + if eurozone: |
3525 | + self.send_header("X-Suggested-Currency", "EUR") |
3526 | + if dotar: |
3527 | + self.send_header("X-Suggested-Currency", "ARS") |
3528 | + self.end_headers() |
3529 | + self.wfile.write(bytes(json.dumps(response), 'UTF-8')) |
3530 | + |
3531 | + def response_auth_error(self): |
3532 | + self.send_response(401) |
3533 | + self.send_header("Content-type", "application/json") |
3534 | + self.end_headers() |
3535 | + self.wfile.write(bytes(json.dumps(dict()), 'UTF-8')) |
3536 | + |
3537 | + def do_POST(self): |
3538 | + """Respond to a POST request.""" |
3539 | + #content = self.rfile.read(int(self.headers.get('content-length'))) |
3540 | + #structure = json.loads(content.decode('utf-8')) |
3541 | + self.do_GET() |
3542 | + |
3543 | + def do_GET(self): |
3544 | + """Respond to a GET request.""" |
3545 | + if self.path.find("/authError/") != -1: |
3546 | + self.response_auth_error() |
3547 | + elif self.path.find("shutdown") != -1: |
3548 | + global KEEP_ALIVE |
3549 | + KEEP_ALIVE = False |
3550 | + elif self.path.find("paymentmethods/") != -1: |
3551 | + fail = self.path.find("/fail/") != -1 |
3552 | + self.response_payment_types(fail) |
3553 | + elif self.path.find("purchases/") != -1: |
3554 | + fail = self.path.find("/fail/") != -1 |
3555 | + interaction = self.path.find("/interaction/") != -1 |
3556 | + self.response_buy_item(fail=fail, interaction=interaction) |
3557 | + elif self.path.find("iteminfo/") != -1: |
3558 | + fail = self.path.find("/fail/") != -1 |
3559 | + eurozone = self.path.find("/eurozone/") != -1 |
3560 | + dotar = self.path.find("/dotar/") != -1 |
3561 | + self.response_item_info(fail, eurozone, dotar) |
3562 | + |
3563 | + |
3564 | +def run_click_server(): |
3565 | + server_address = ('', 8000) |
3566 | + httpd = HTTPServer(server_address, MyHandler) |
3567 | + global KEEP_ALIVE |
3568 | + print('start') |
3569 | + while KEEP_ALIVE: |
3570 | + httpd.handle_request() |
3571 | + |
3572 | + |
3573 | +def run_mocked_settings(): |
3574 | + t = threading.Thread(target=run_click_server) |
3575 | + t.start() |
3576 | + |
3577 | + |
3578 | +run_mocked_settings() |
3579 | |
3580 | === added file 'pay-ui/backend/tests/test_network.cpp' |
3581 | --- pay-ui/backend/tests/test_network.cpp 1970-01-01 00:00:00 +0000 |
3582 | +++ pay-ui/backend/tests/test_network.cpp 2016-03-11 14:49:24 +0000 |
3583 | @@ -0,0 +1,317 @@ |
3584 | +/* |
3585 | + * Copyright 2014-2016 Canonical Ltd. |
3586 | + * |
3587 | + * This library is free software; you can redistribute it and/or |
3588 | + * modify it under the terms of version 3 of the GNU Lesser General Public |
3589 | + * License as published by the Free Software Foundation. |
3590 | + * |
3591 | + * This program is distributed in the hope that it will be useful, |
3592 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
3593 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
3594 | + * General Public License for more details. |
3595 | + * |
3596 | + * You should have received a copy of the GNU Lesser General Public |
3597 | + * License along with this library; if not, write to the |
3598 | + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
3599 | + * Boston, MA 02110-1301, USA. |
3600 | + */ |
3601 | + |
3602 | +#include <stdlib.h> |
3603 | + |
3604 | +#include <QObject> |
3605 | +#include <QProcess> |
3606 | +#include <QProcessEnvironment> |
3607 | +#include <QDebug> |
3608 | +#include <QVariant> |
3609 | +#include <QUrlQuery> |
3610 | +#include <QString> |
3611 | +#include <QDir> |
3612 | +#include <QTest> |
3613 | +#include <QTimer> |
3614 | +#include <QSignalSpy> |
3615 | +#include <QVariant> |
3616 | +#include <QVariantList> |
3617 | + |
3618 | +#include <modules/payui/network.h> |
3619 | +#include <modules/payui/pay_info.h> |
3620 | + |
3621 | +using namespace UbuntuPurchase; |
3622 | + |
3623 | +class TestNetwork: public QObject |
3624 | +{ |
3625 | + Q_OBJECT |
3626 | + |
3627 | +private slots: |
3628 | + void initTestCase(); |
3629 | + void testNetworkAuthenticationError(); |
3630 | + void testNetworkGetPaymentTypes(); |
3631 | + void testNetworkGetPaymentTypesFail(); |
3632 | + void testNetworkBuyItem(); |
3633 | + void testNetworkBuyItemInProgress(); |
3634 | + void testNetworkBuyItemFail(); |
3635 | + void testNetworkButItemWithPaymentType(); |
3636 | + void testNetworkButItemWithPaymentTypeFail(); |
3637 | + void testNetworkButItemWithPaymentTypeInProgress(); |
3638 | + void testNetworkGetItemInfo(); |
3639 | + void testNetworkGetItemInfoEurozone(); |
3640 | + void testNetworkGetItemInfoOtherCoins(); |
3641 | + void testNetworkGetItemInfoOverride(); |
3642 | + void testNetworkGetItemInfoOverrideOther(); |
3643 | + void testNetworkGetItemInfoFail(); |
3644 | + void testUseExistingCredentials(); |
3645 | + void testCheckAlreadyPurchased(); |
3646 | + void testCheckAlreadyPurchasedFail(); |
3647 | + void testSanitizeUrl(); |
3648 | + void testEncodeQuerySlashes(); |
3649 | + void cleanupTestCase(); |
3650 | + |
3651 | +private: |
3652 | + UbuntuPurchase::Network network; |
3653 | + QProcess* process; |
3654 | +}; |
3655 | + |
3656 | +void TestNetwork::initTestCase() |
3657 | +{ |
3658 | + setenv("SSO_AUTH_BASE_URL", "http://localhost:8000/", 1); |
3659 | + qDebug() << "Starting Server"; |
3660 | + network.setCredentials(Token("token_key", "token_secret", "consumer_key", "consumer_secret")); |
3661 | + process = new QProcess(this); |
3662 | + QSignalSpy spy(process, SIGNAL(started())); |
3663 | + process->setWorkingDirectory(QDir::currentPath() + "/backend/tests/"); |
3664 | + qDebug() << process->workingDirectory(); |
3665 | + QString program = "python3"; |
3666 | + QString script = "mock_click_server.py"; |
3667 | + process->start(program, QStringList() << script); |
3668 | + QTRY_COMPARE(spy.count(), 1); |
3669 | + // Wait for server to start |
3670 | + QTimer timer; |
3671 | + QSignalSpy spy2(&timer, SIGNAL(timeout())); |
3672 | + timer.setInterval(2000); |
3673 | + timer.setSingleShot(true); |
3674 | + timer.start(); |
3675 | + QTRY_COMPARE(spy2.count(), 1); |
3676 | +} |
3677 | + |
3678 | +void TestNetwork::testNetworkAuthenticationError() |
3679 | +{ |
3680 | + setenv(PAY_BASE_URL_ENVVAR, "http://localhost:8000/authError/", 1); |
3681 | + QSignalSpy spy(&network, SIGNAL(authenticationError())); |
3682 | + network.buyItem("email", "password", "otp", "USD", "appid", "itemid", "paymentid", "backendid", false); |
3683 | + // WTF DOES THIS HAPPEN TWICE |
3684 | + QTRY_COMPARE(spy.count(), 2); |
3685 | +} |
3686 | + |
3687 | +void TestNetwork::testNetworkGetPaymentTypes() |
3688 | +{ |
3689 | + setenv(PAY_BASE_URL_ENVVAR, "http://localhost:8000/", 1); |
3690 | + QSignalSpy spy(&network, SIGNAL(paymentTypesObtained(QVariantList))); |
3691 | + network.requestPaymentTypes("USD"); |
3692 | + QTRY_COMPARE(spy.count(), 1); |
3693 | + QList<QVariant> arguments = spy.takeFirst(); |
3694 | + QVERIFY(arguments.at(0).toList().count() == 3); |
3695 | + QVariantList listPays = arguments.at(0).toList(); |
3696 | + for (int i = 0; i < listPays.count(); i++) { |
3697 | + QVariant var = listPays.at(i); |
3698 | + PayInfo* info = var.value<PayInfo*>(); |
3699 | + if (i == 2) { |
3700 | + QVERIFY(info->preferred() == true); |
3701 | + } else { |
3702 | + QVERIFY(info->preferred() == false); |
3703 | + } |
3704 | + } |
3705 | +} |
3706 | + |
3707 | +void TestNetwork::testNetworkGetPaymentTypesFail() |
3708 | +{ |
3709 | + setenv(PAY_BASE_URL_ENVVAR, "http://localhost:8000/fail/", 1); |
3710 | + QSignalSpy spy(&network, SIGNAL(error(QString))); |
3711 | + network.requestPaymentTypes("USD"); |
3712 | + // WTF DOES THIS HAPPEN TWICE |
3713 | + QTRY_COMPARE(spy.count(), 2); |
3714 | + QList<QVariant> arguments = spy.takeFirst(); |
3715 | + QVERIFY(arguments.at(0).toString().startsWith("404:")); |
3716 | +} |
3717 | + |
3718 | +void TestNetwork::testNetworkBuyItem() |
3719 | +{ |
3720 | + setenv(PAY_BASE_URL_ENVVAR, "http://localhost:8000/", 1); |
3721 | + QSignalSpy spy(&network, SIGNAL(buyItemSucceeded())); |
3722 | + network.buyItem("email", "password", "otp", "USD", "appid", "itemid", "paymentid", "backendid", false); |
3723 | + QTRY_COMPARE(spy.count(), 1); |
3724 | +} |
3725 | + |
3726 | +void TestNetwork::testNetworkBuyItemInProgress() |
3727 | +{ |
3728 | + setenv(PAY_BASE_URL_ENVVAR, "http://localhost:8000/interaction/", 1); |
3729 | + QSignalSpy spy(&network, SIGNAL(buyInteractionRequired(QString))); |
3730 | + network.buyItem("email", "password", "otp", "USD", "appid", "itemid", "paymentid", "backendid", false); |
3731 | + QTRY_COMPARE(spy.count(), 1); |
3732 | + QList<QVariant> arguments = spy.takeFirst(); |
3733 | + QString url(arguments.at(0).toString()); |
3734 | + QString expected("http://localhost:8000/interaction/redirect.url?currency=USD"); |
3735 | + QVERIFY(url.startsWith(expected)); |
3736 | +} |
3737 | + |
3738 | +void TestNetwork::testNetworkBuyItemFail() |
3739 | +{ |
3740 | + setenv(PAY_BASE_URL_ENVVAR, "http://localhost:8000/fail/", 1); |
3741 | + QSignalSpy spy(&network, SIGNAL(buyItemFailed())); |
3742 | + network.buyItem("email", "password", "otp", "USD", "appid", "itemid", "paymentid", "backendid", false); |
3743 | + QTRY_COMPARE(spy.count(), 1); |
3744 | +} |
3745 | + |
3746 | +void TestNetwork::testNetworkButItemWithPaymentType() |
3747 | +{ |
3748 | + setenv(PAY_BASE_URL_ENVVAR, "http://localhost:8000/", 1); |
3749 | + QSignalSpy spy(&network, SIGNAL(paymentTypesObtained(QVariantList))); |
3750 | + network.requestPaymentTypes("USD"); |
3751 | + QTRY_COMPARE(spy.count(), 1); |
3752 | + QSignalSpy spy2(&network, SIGNAL(buyItemSucceeded())); |
3753 | + network.buyItemWithPreferredPaymentType("email", "password", "otp", "USD", "appid", "itemid", false); |
3754 | + QTRY_COMPARE(spy2.count(), 1); |
3755 | +} |
3756 | + |
3757 | +void TestNetwork::testNetworkButItemWithPaymentTypeFail() |
3758 | +{ |
3759 | + setenv(PAY_BASE_URL_ENVVAR, "http://localhost:8000/", 1); |
3760 | + QSignalSpy spy(&network, SIGNAL(paymentTypesObtained(QVariantList))); |
3761 | + network.requestPaymentTypes("USD"); |
3762 | + QTRY_COMPARE(spy.count(), 1); |
3763 | + setenv(PAY_BASE_URL_ENVVAR, "http://localhost:8000/fail/", 1); |
3764 | + QSignalSpy spy2(&network, SIGNAL(buyItemFailed())); |
3765 | + network.buyItemWithPreferredPaymentType("email", "password", "otp", "USD", "appid", "itemid", false); |
3766 | + QTRY_COMPARE(spy2.count(), 1); |
3767 | +} |
3768 | + |
3769 | +void TestNetwork::testNetworkButItemWithPaymentTypeInProgress() |
3770 | +{ |
3771 | + setenv(PAY_BASE_URL_ENVVAR, "http://localhost:8000/", 1); |
3772 | + QSignalSpy spy(&network, SIGNAL(paymentTypesObtained(QVariantList))); |
3773 | + network.requestPaymentTypes("USD""USD"); |
3774 | + QTRY_COMPARE(spy.count(), 1); |
3775 | + setenv(PAY_BASE_URL_ENVVAR, "http://localhost:8000/interaction/", 1); |
3776 | + QSignalSpy spy2(&network, SIGNAL(buyInteractionRequired(QString))); |
3777 | + network.buyItemWithPreferredPaymentType("email", "password", "otp", "USD", "appid", "itemid", false); |
3778 | + QTRY_COMPARE(spy2.count(), 1); |
3779 | +} |
3780 | + |
3781 | +void TestNetwork::testNetworkGetItemInfo() |
3782 | +{ |
3783 | + setenv(SEARCH_BASE_URL_ENVVAR, "http://localhost:8000/iteminfo/", 1); |
3784 | + unsetenv(CURRENCY_ENVVAR); |
3785 | + QSignalSpy spy(&network, SIGNAL(itemDetailsObtained(QString,QString,QString,QString,QString))); |
3786 | + network.getItemInfo("packagename", ""); |
3787 | + QTRY_COMPARE(spy.count(), 1); |
3788 | + QList<QVariant> arguments = spy.takeFirst(); |
3789 | + QCOMPARE(arguments.at(2).toString(), QStringLiteral("USD")); |
3790 | + QCOMPARE(arguments.at(3).toString(), QStringLiteral("US$1.99")); |
3791 | +} |
3792 | + |
3793 | +void TestNetwork::testNetworkGetItemInfoEurozone() |
3794 | +{ |
3795 | + setenv(SEARCH_BASE_URL_ENVVAR, "http://localhost:8000/iteminfo/eurozone/", 1); |
3796 | + unsetenv(CURRENCY_ENVVAR); |
3797 | + QSignalSpy spy(&network, SIGNAL(itemDetailsObtained(QString,QString,QString,QString,QString))); |
3798 | + network.getItemInfo("packagename", ""); |
3799 | + QTRY_COMPARE(spy.count(), 1); |
3800 | + QList<QVariant> arguments = spy.takeFirst(); |
3801 | + QCOMPARE(arguments.at(2).toString(), QStringLiteral("EUR")); |
3802 | + QCOMPARE(arguments.at(3).toString(), QStringLiteral("€1.69")); |
3803 | +} |
3804 | + |
3805 | +void TestNetwork::testNetworkGetItemInfoOtherCoins() |
3806 | +{ |
3807 | + setenv(SEARCH_BASE_URL_ENVVAR, "http://localhost:8000/iteminfo/dotar/", 1); |
3808 | + unsetenv(CURRENCY_ENVVAR); |
3809 | + QSignalSpy spy(&network, SIGNAL(itemDetailsObtained(QString,QString,QString,QString,QString))); |
3810 | + network.getItemInfo("packagename", ""); |
3811 | + QTRY_COMPARE(spy.count(), 1); |
3812 | + QList<QVariant> arguments = spy.takeFirst(); |
3813 | + QCOMPARE(arguments.at(2).toString(), QStringLiteral("USD")); |
3814 | + QCOMPARE(arguments.at(3).toString(), QStringLiteral("US$1.99")); |
3815 | +} |
3816 | + |
3817 | +void TestNetwork::testNetworkGetItemInfoOverride() |
3818 | +{ |
3819 | + setenv(SEARCH_BASE_URL_ENVVAR, "http://localhost:8000/iteminfo/", 1); |
3820 | + setenv(CURRENCY_ENVVAR, "EUR", true); |
3821 | + QSignalSpy spy(&network, SIGNAL(itemDetailsObtained(QString,QString,QString,QString,QString))); |
3822 | + network.getItemInfo("packagename", ""); |
3823 | + QTRY_COMPARE(spy.count(), 1); |
3824 | + QList<QVariant> arguments = spy.takeFirst(); |
3825 | + QCOMPARE(arguments.at(2).toString(), QStringLiteral("EUR")); |
3826 | + QCOMPARE(arguments.at(3).toString(), QStringLiteral("€1.69")); |
3827 | + unsetenv(CURRENCY_ENVVAR); |
3828 | +} |
3829 | + |
3830 | +void TestNetwork::testNetworkGetItemInfoOverrideOther() |
3831 | +{ |
3832 | + setenv(SEARCH_BASE_URL_ENVVAR, "http://localhost:8000/iteminfo/dotar/", 1); |
3833 | + setenv(CURRENCY_ENVVAR, "EUR", true); |
3834 | + QSignalSpy spy(&network, SIGNAL(itemDetailsObtained(QString,QString,QString,QString,QString))); |
3835 | + network.getItemInfo("packagename", ""); |
3836 | + QTRY_COMPARE(spy.count(), 1); |
3837 | + QList<QVariant> arguments = spy.takeFirst(); |
3838 | + QCOMPARE(arguments.at(2).toString(), QStringLiteral("EUR")); |
3839 | + QCOMPARE(arguments.at(3).toString(), QStringLiteral("€1.69")); |
3840 | + unsetenv(CURRENCY_ENVVAR); |
3841 | +} |
3842 | + |
3843 | +void TestNetwork::testNetworkGetItemInfoFail() |
3844 | +{ |
3845 | + setenv(SEARCH_BASE_URL_ENVVAR, "http://localhost:8000/fail/iteminfo/", 1); |
3846 | + unsetenv(CURRENCY_ENVVAR); |
3847 | + QSignalSpy spy(&network, SIGNAL(error(QString))); |
3848 | + network.getItemInfo("packagename", ""); |
3849 | + // WTF DOES THIS HAPPEN TWICE |
3850 | + QTRY_COMPARE(spy.count(), 2); |
3851 | +} |
3852 | + |
3853 | +void TestNetwork::testUseExistingCredentials() |
3854 | +{ |
3855 | + setenv(PAY_BASE_URL_ENVVAR, "http://localhost:8000/", 1); |
3856 | + QSignalSpy spy(&network, SIGNAL(buyItemSucceeded())); |
3857 | + network.buyItem("email", "password", "otp", "USD", "appid", "itemid", "paymentid", "backendid", true); |
3858 | + QTRY_COMPARE(spy.count(), 1); |
3859 | +} |
3860 | + |
3861 | +void TestNetwork::testCheckAlreadyPurchased() |
3862 | +{ |
3863 | + setenv(PAY_BASE_URL_ENVVAR, "http://localhost:8000/", 1); |
3864 | + QSignalSpy spy(&network, SIGNAL(buyItemSucceeded())); |
3865 | + network.checkItemPurchased("com.example.fakeapp", ""); |
3866 | + QTRY_COMPARE(spy.count(), 1); |
3867 | +} |
3868 | + |
3869 | +void TestNetwork::testCheckAlreadyPurchasedFail() |
3870 | +{ |
3871 | + setenv(PAY_BASE_URL_ENVVAR, "http://localhost:8000/notpurchased/", 1); |
3872 | + QSignalSpy spy(&network, SIGNAL(itemNotPurchased())); |
3873 | + network.checkItemPurchased("com.example.fakeapp", ""); |
3874 | + // WTF DOES THIS HAPPEN TWICE |
3875 | + QTRY_COMPARE(spy.count(), 2); |
3876 | +} |
3877 | + |
3878 | +void TestNetwork::testSanitizeUrl() |
3879 | +{ |
3880 | + QUrl url("https://example.com//test/this/heavily///really%2f/"); |
3881 | + QUrl expected("https://example.com/test/this/heavily/really%2f/"); |
3882 | + QString result = network.sanitizeUrl(url); |
3883 | + QTRY_COMPARE(result, expected.toString()); |
3884 | +} |
3885 | + |
3886 | +void TestNetwork::testEncodeQuerySlashes() |
3887 | +{ |
3888 | + QString query("abcdef/01235%3D"); |
3889 | + QTRY_COMPARE(network.encodeQuerySlashes(query), |
3890 | + QStringLiteral("abcdef%2F01235%3D")); |
3891 | +} |
3892 | + |
3893 | +void TestNetwork::cleanupTestCase() |
3894 | +{ |
3895 | + process->close(); |
3896 | + process->deleteLater(); |
3897 | +} |
3898 | + |
3899 | +QTEST_MAIN(TestNetwork) |
3900 | +#include "test_network.moc" |
3901 | |
3902 | === added file 'pay-ui/pay-ui.in' |
3903 | --- pay-ui/pay-ui.in 1970-01-01 00:00:00 +0000 |
3904 | +++ pay-ui/pay-ui.in 2016-03-11 14:49:24 +0000 |
3905 | @@ -0,0 +1,19 @@ |
3906 | +#!/bin/sh |
3907 | +# |
3908 | +# Copyright © 2016 Canonical Ltd. |
3909 | +# |
3910 | +# This program is free software: you can redistribute it and/or modify it |
3911 | +# under the terms of the GNU General Public License version 3, as published |
3912 | +# by the Free Software Foundation. |
3913 | +# |
3914 | +# This program is distributed in the hope that it will be useful, but |
3915 | +# WITHOUT ANY WARRANTY; without even the implied warranties of |
3916 | +# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
3917 | +# PURPOSE. See the GNU General Public License for more details. |
3918 | +# |
3919 | +# You should have received a copy of the GNU General Public License along |
3920 | +# with this program. If not, see <http://www.gnu.org/licenses/>. |
3921 | + |
3922 | +set -e |
3923 | + |
3924 | +env QML2_IMPORT_PATH=${QML2_IMPORT_PATH}:@QT_IMPORTS_DIR@ qmlscene @PAYUI_DIR@/payui.qml $@ |
3925 | |
3926 | === added directory 'pay-ui/tests' |
3927 | === added directory 'pay-ui/tests/autopilot' |
3928 | === added directory 'pay-ui/tests/autopilot/pay_ui' |
3929 | === added file 'pay-ui/tests/autopilot/pay_ui/__init__.py' |
3930 | --- pay-ui/tests/autopilot/pay_ui/__init__.py 1970-01-01 00:00:00 +0000 |
3931 | +++ pay-ui/tests/autopilot/pay_ui/__init__.py 2016-03-11 14:49:24 +0000 |
3932 | @@ -0,0 +1,106 @@ |
3933 | +# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- |
3934 | +# |
3935 | +# Copyright (C) 2015 Canonical Ltd. |
3936 | +# |
3937 | +# This program is free software; you can redistribute it and/or modify |
3938 | +# it under the terms of the GNU General Public License version 3, as published |
3939 | +# by the Free Software Foundation. |
3940 | +# |
3941 | +# This program is distributed in the hope that it will be useful, |
3942 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
3943 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
3944 | +# GNU General Public License for more details. |
3945 | +# |
3946 | +# You should have received a copy of the GNU General Public License |
3947 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
3948 | + |
3949 | +import logging |
3950 | +import ubuntuuitoolkit as uitk |
3951 | + |
3952 | +from autopilot import logging as autopilot_logging |
3953 | + |
3954 | +logger = logging.getLogger(__name__) |
3955 | + |
3956 | + |
3957 | +class MainView(uitk.MainView): |
3958 | + |
3959 | + def _tap(self, objectName): |
3960 | + """Find a widget and tap on it.""" |
3961 | + item = self.wait_select_single(objectName=objectName) |
3962 | + if item.enabled: |
3963 | + self.pointing_device.click_object(item) |
3964 | + |
3965 | + def _type_text(self, objectName, text): |
3966 | + """Find a text widget and enter some text in it.""" |
3967 | + item = self.wait_select_single( |
3968 | + uitk.TextField, objectName=objectName) |
3969 | + item.write(text) |
3970 | + |
3971 | + @autopilot_logging.log_action(logger.info) |
3972 | + def cancel(self): |
3973 | + """Tap on the 'Cancel' button.""" |
3974 | + self._tap('cancelButton') |
3975 | + |
3976 | + @autopilot_logging.log_action(logger.info) |
3977 | + def buy(self): |
3978 | + """Tap on the 'Buy Now' button.""" |
3979 | + self._tap('buyButton') |
3980 | + |
3981 | + @autopilot_logging.log_action(logger.info) |
3982 | + def enter_password(self, password): |
3983 | + """Type the password into the entry field.""" |
3984 | + self._type_text('passwordField', password) |
3985 | + |
3986 | + @autopilot_logging.log_action(logger.info) |
3987 | + def tap_on_webview(self): |
3988 | + """Tap in the center of the web view.""" |
3989 | + self._tap('webView') |
3990 | + |
3991 | + @autopilot_logging.log_action(logger.info) |
3992 | + def open_add_card_page(self): |
3993 | + """Open the 'Add Credit/Debit Card' page. |
3994 | + |
3995 | + :return the Add Credit/Debit Card page. |
3996 | + """ |
3997 | + self._tap('addCreditCardLabel') |
3998 | + return self.wait_select_single(objectName='pageWebkit') |
3999 | + |
4000 | + @autopilot_logging.log_action(logger.info) |
4001 | + def get_payment_types(self): |
4002 | + """Get the payment types option selector widget. |
4003 | + |
4004 | + :return the Payment Types option selector widget. |
4005 | + """ |
4006 | + return self.wait_select_single(objectName='paymentTypes') |
4007 | + |
4008 | + @autopilot_logging.log_action(logger.info) |
4009 | + def get_checkout_page(self): |
4010 | + """Get the widget for the Checkout page. |
4011 | + |
4012 | + :return the Checkout page widget.""" |
4013 | + return self.wait_select_single(objectName='pageCheckout') |
4014 | + |
4015 | + @autopilot_logging.log_action(logger.info) |
4016 | + def tap_dialog_ok_button(self): |
4017 | + """Tap the 'OK' button in the dialog.""" |
4018 | + self._tap('dialogOkButton') |
4019 | + |
4020 | + @autopilot_logging.log_action(logger.info) |
4021 | + def tap_dialog_cancel_button(self): |
4022 | + """Tap the 'Cancel' button in the dialog.""" |
4023 | + self._tap('dialogCancelButton') |
4024 | + |
4025 | + @autopilot_logging.log_action(logger.info) |
4026 | + def tap_dialog_leave_button(self): |
4027 | + """Tap the 'Leave' button in the dialog.""" |
4028 | + self._tap('leaveButton') |
4029 | + |
4030 | + @autopilot_logging.log_action(logger.info) |
4031 | + def tap_dialog_stay_button(self): |
4032 | + """Tap the 'Stay' button in the dialog.""" |
4033 | + self._tap('stayButton') |
4034 | + |
4035 | + @autopilot_logging.log_action(logger.info) |
4036 | + def input_dialog_text(self, text): |
4037 | + """Type the text into the dialog text entry field.""" |
4038 | + self._type_text('dialogInput', text) |
4039 | |
4040 | === added directory 'pay-ui/tests/autopilot/pay_ui/tests' |
4041 | === added file 'pay-ui/tests/autopilot/pay_ui/tests/__init__.py' |
4042 | --- pay-ui/tests/autopilot/pay_ui/tests/__init__.py 1970-01-01 00:00:00 +0000 |
4043 | +++ pay-ui/tests/autopilot/pay_ui/tests/__init__.py 2016-03-11 14:49:24 +0000 |
4044 | @@ -0,0 +1,77 @@ |
4045 | +# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- |
4046 | +# |
4047 | +# Copyright (C) 2014-2016 Canonical Ltd. |
4048 | +# |
4049 | +# This program is free software; you can redistribute it and/or modify |
4050 | +# it under the terms of the GNU General Public License version 3, as published |
4051 | +# by the Free Software Foundation. |
4052 | +# |
4053 | +# This program is distributed in the hope that it will be useful, |
4054 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
4055 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
4056 | +# GNU General Public License for more details. |
4057 | +# |
4058 | +# You should have received a copy of the GNU General Public License |
4059 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
4060 | + |
4061 | +import fixtures |
4062 | +import json |
4063 | +import os |
4064 | +import shutil |
4065 | +import subprocess |
4066 | +import tempfile |
4067 | +import ubuntuuitoolkit as uitk |
4068 | + |
4069 | +import pay_ui |
4070 | + |
4071 | + |
4072 | +class BasePayUITestCase(uitk.base.UbuntuUIToolkitAppTestCase): |
4073 | + """Base Autopilot test case for the Pay UI project.""" |
4074 | + |
4075 | + dirpath = None |
4076 | + |
4077 | + def setUp(self): |
4078 | + super().setUp() |
4079 | + build_dir = os.environ.get('BUILD_DIR', None) |
4080 | + source_dir = os.environ.get('SOURCE_DIR', None) |
4081 | + self.app = self.launch_application(build_dir, source_dir) |
4082 | + |
4083 | + def create_config_dir(self): |
4084 | + self.dirpath = tempfile.mkdtemp() |
4085 | + self.useFixture(fixtures.EnvironmentVariable( |
4086 | + 'XDG_DATA_HOME', self.dirpath)) |
4087 | + |
4088 | + def clean_config_dir(self): |
4089 | + if self.dirpath: |
4090 | + shutil.rmtree(self.dirpath) |
4091 | + |
4092 | + def launch_application(self, build_dir=None, source_dir=None): |
4093 | + if build_dir is None: |
4094 | + return self.launch_installed_app() |
4095 | + else: |
4096 | + return self.launch_built_application(build_dir, source_dir) |
4097 | + |
4098 | + def launch_installed_app(self): |
4099 | + return self.launch_test_application( |
4100 | + '/usr/lib/payui/pay-ui', |
4101 | + app_type='qt', |
4102 | + emulator_base=uitk.UbuntuUIToolkitCustomProxyObjectBase) |
4103 | + |
4104 | + def launch_built_application(self, build_dir, source_dir): |
4105 | + built_import_path = os.path.join(build_dir, 'backend') |
4106 | + self.useFixture( |
4107 | + fixtures.EnvironmentVariable( |
4108 | + 'QML2_IMPORT_PATH', newvalue=built_import_path)) |
4109 | + main_qml_path = os.path.join(source_dir, 'app', 'payui.qml') |
4110 | + return self.launch_test_application( |
4111 | + uitk.base.get_qmlscene_launch_command(), |
4112 | + main_qml_path, |
4113 | + '--transparent', |
4114 | + 'purchase://com.example.testapp', |
4115 | + app_type='qt', |
4116 | + emulator_base=uitk.UbuntuUIToolkitCustomProxyObjectBase) |
4117 | + |
4118 | + @property |
4119 | + def main_view(self): |
4120 | + """Return main view""" |
4121 | + return self.app.select_single(pay_ui.MainView) |
4122 | |
4123 | === added file 'pay-ui/tests/autopilot/pay_ui/tests/mock_server.py' |
4124 | --- pay-ui/tests/autopilot/pay_ui/tests/mock_server.py 1970-01-01 00:00:00 +0000 |
4125 | +++ pay-ui/tests/autopilot/pay_ui/tests/mock_server.py 2016-03-11 14:49:24 +0000 |
4126 | @@ -0,0 +1,337 @@ |
4127 | +# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- |
4128 | +# |
4129 | +# Copyright (C) 2014 Canonical Ltd. |
4130 | +# |
4131 | +# This program is free software; you can redistribute it and/or modify |
4132 | +# it under the terms of the GNU General Public License version 3, as published |
4133 | +# by the Free Software Foundation. |
4134 | +# |
4135 | +# This program is distributed in the hope that it will be useful, |
4136 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
4137 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
4138 | +# GNU General Public License for more details. |
4139 | +# |
4140 | +# You should have received a copy of the GNU General Public License |
4141 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
4142 | + |
4143 | +import json |
4144 | +import socket |
4145 | +import threading |
4146 | +from http.server import BaseHTTPRequestHandler, HTTPServer |
4147 | + |
4148 | + |
4149 | +html_success = """ |
4150 | +<html> |
4151 | + <body bgcolor="green" onClick="window.location.assign('/paymentmethods/completeadd')"> |
4152 | + <h1>Placeholder for web interaction</h1> |
4153 | + <p>Click anywhere to proceed</p> |
4154 | + </body> |
4155 | +</html>" |
4156 | +""" |
4157 | + |
4158 | +html_cancel = """ |
4159 | +<html> |
4160 | + <body bgcolor="red" onClick="window.location.assign('/api/2.0/click/cancelled')"> |
4161 | + <h1>Placeholder for web interaction</h1> |
4162 | + <p>Click anywhere to cancel</p> |
4163 | + </body> |
4164 | +</html>" |
4165 | +""" |
4166 | + |
4167 | +html_completed = """ |
4168 | +<html> |
4169 | + <body onLoad="window.location.assign('/click/succeeded')"> |
4170 | + </body> |
4171 | +</html> |
4172 | +""" |
4173 | + |
4174 | +html_beforeunload = """ |
4175 | +<html> |
4176 | + <script language="javascript"> |
4177 | + window.onbeforeunload = function() { |
4178 | + return 'Really want to add your card?' |
4179 | + } |
4180 | + window.onload = function() { |
4181 | + window.location.assign('/click/cancelled') |
4182 | + } |
4183 | + window.onclick = function() { |
4184 | + window.onbeforeunload = null; |
4185 | + window.location.assign('/paymentmethods/completeadd') |
4186 | + } |
4187 | + </script> |
4188 | + <body bgcolor="yellow"> |
4189 | + <h1>Placeholder for web interaction</h1> |
4190 | + <p>Click 'Stay' and then anywhere to proceed.</p> |
4191 | + </body> |
4192 | +</html> |
4193 | +""" |
4194 | + |
4195 | +html_alert = """ |
4196 | +<html> |
4197 | + <body onClick="window.location.assign('/paymentmethods/completeadd')" |
4198 | + onLoad="alert('Click OK to add your card.')"> |
4199 | + <h1>Placeholder for web interaction</h1> |
4200 | + <p>Click anywhere to proceed</p> |
4201 | + </body> |
4202 | +</html> |
4203 | +""" |
4204 | + |
4205 | +html_confirm = """ |
4206 | +<html> |
4207 | + <script language="javascript"> |
4208 | + window.onload = function() { |
4209 | + if (window.confirm('Do you want to add your card?')) { |
4210 | + window.location.assign('/paymentmethods/completeadd') |
4211 | + } else { |
4212 | + window.location.assign('/click/cancelled') |
4213 | + } |
4214 | + } |
4215 | + </script> |
4216 | + <body bgcolor="pink"> |
4217 | + <h1>Placeholder for web interaction</h1> |
4218 | + <p>Click ok to add card, or cancel to not.</p> |
4219 | + </body> |
4220 | +</html> |
4221 | +""" |
4222 | + |
4223 | +html_prompt = """ |
4224 | +<html> |
4225 | + <script language="javascript"> |
4226 | + window.onload = function() { |
4227 | + if (window.prompt('Speak friend and enter') == 'friend') { |
4228 | + window.location.assign('/paymentmethods/completeadd') |
4229 | + } else { |
4230 | + window.location.assign('/click/cancelled') |
4231 | + } |
4232 | + } |
4233 | + </script> |
4234 | + <body bgcolor="pink"> |
4235 | + <h1>Placeholder for web interaction</h1> |
4236 | + <p>Type friend and ok to add card.</p> |
4237 | + </body> |
4238 | +</html> |
4239 | +""" |
4240 | + |
4241 | + |
4242 | +class MyHandler(BaseHTTPRequestHandler): |
4243 | + |
4244 | + def do_HEAD(self): |
4245 | + self.send_response(200) |
4246 | + self.send_header("X-Click-Token", "X-Click-Token") |
4247 | + self.end_headers() |
4248 | + |
4249 | + def response_payment_types(self, fail=False): |
4250 | + if fail: |
4251 | + self.send_response(404) |
4252 | + else: |
4253 | + self.send_response(200) |
4254 | + self.send_header("Content-type", "application/json") |
4255 | + self.end_headers() |
4256 | + self.wfile.write(bytes(json.dumps(self.server.payment_types), 'UTF-8')) |
4257 | + |
4258 | + def interaction_html(self): |
4259 | + return html_cancel if self.server.interaction_cancel else html_success |
4260 | + |
4261 | + def response_cc_interaction(self, fail=False): |
4262 | + if fail: |
4263 | + self.send_response(404) |
4264 | + else: |
4265 | + self.send_response(200) |
4266 | + self.send_header("Content-type", "text/html") |
4267 | + self.end_headers() |
4268 | + self.wfile.write(bytes(self.interaction_html(), 'UTF-8')) |
4269 | + |
4270 | + def response_payment_add(self, fail=False): |
4271 | + if fail: |
4272 | + self.send_response(404) |
4273 | + else: |
4274 | + self.send_response(200) |
4275 | + self.send_header("Content-type", "text/html") |
4276 | + self.end_headers() |
4277 | + test = self.path.split('/')[1] |
4278 | + if test.find('js_alert') != -1: |
4279 | + self.wrile.write(bytes(html_alert, 'UTF-8')) |
4280 | + elif test.find('js_beforeunload') != -1: |
4281 | + self.wfile.write(bytes(html_beforeunload, 'UTF-8')) |
4282 | + elif test.find('js_confirm') != -1: |
4283 | + self.wfile.write(bytes(html_confirm, 'UTF-8')) |
4284 | + elif test.find('js_prompt') != -1: |
4285 | + self.wfile.write(bytes(html_prompt, 'UTF-8')) |
4286 | + else: |
4287 | + self.wfile.write(bytes(self.interaction_html(), 'UTF-8')) |
4288 | + |
4289 | + def response_payment_add_complete(self): |
4290 | + """Add the new payment info to the list.""" |
4291 | + self.server.payment_types[1]["choices"].append({ |
4292 | + "currencies": ["USD"], |
4293 | + "id": 1999, |
4294 | + "requires_interaction": False, |
4295 | + "preferred": False, |
4296 | + "description": "Yet another payment method" |
4297 | + }) |
4298 | + self.send_response(200) |
4299 | + self.send_header("Content-type", "text/html") |
4300 | + self.end_headers() |
4301 | + self.wfile.write(bytes(html_completed, 'UTF-8')) |
4302 | + |
4303 | + def response_buy_item(self): |
4304 | + response = { |
4305 | + "state": "Complete", |
4306 | + } |
4307 | + if self.server.buy_cc_interaction: |
4308 | + response["state"] = "InProgress" |
4309 | + response["redirect_to"] = self.server.buy_cc_interaction |
4310 | + if self.server.fail: |
4311 | + response = {} |
4312 | + self.send_response(200) |
4313 | + self.send_header("Content-type", "application/json") |
4314 | + self.end_headers() |
4315 | + try: |
4316 | + self.wfile.write(bytes(json.dumps(response), 'UTF-8')) |
4317 | + except socket.error: |
4318 | + pass |
4319 | + |
4320 | + def response_update_credentials(self, fail=False): |
4321 | + response = { |
4322 | + "token_key": "token_key", |
4323 | + "token_secret": "token_secret", |
4324 | + "consumer_key": "consumer_key", |
4325 | + "consumer_secret": "consumer_secret", |
4326 | + } |
4327 | + if fail: |
4328 | + self.send_response(404) |
4329 | + else: |
4330 | + self.send_response(200) |
4331 | + self.send_header("Content-type", "application/json") |
4332 | + self.end_headers() |
4333 | + self.wfile.write(bytes(json.dumps(response), 'UTF-8')) |
4334 | + |
4335 | + def response_item_info(self, fail=False): |
4336 | + response = { |
4337 | + "title": "title", |
4338 | + "publisher": "publisher", |
4339 | + "icon_url": "icon_url", |
4340 | + "prices": { |
4341 | + "USD": 2.99, |
4342 | + "EUR": 2.49, |
4343 | + "GBP": 1.99, |
4344 | + }, |
4345 | + } |
4346 | + if fail: |
4347 | + self.send_response(404) |
4348 | + else: |
4349 | + self.send_response(200) |
4350 | + self.send_header("Content-type", "application/json") |
4351 | + self.end_headers() |
4352 | + self.wfile.write(bytes(json.dumps(response), 'UTF-8')) |
4353 | + |
4354 | + def response_auth_error(self): |
4355 | + self.send_response(401) |
4356 | + self.send_header("Content-type", "application/json") |
4357 | + self.end_headers() |
4358 | + self.wfile.write(bytes(json.dumps(dict()), 'UTF-8')) |
4359 | + |
4360 | + def do_POST(self): |
4361 | + """Respond to a POST request.""" |
4362 | + #content = self.rfile.read(int(self.headers.get('content-length'))) |
4363 | + #structure = json.loads(content.decode('utf-8')) |
4364 | + if self.path.find("purchases/") != -1: |
4365 | + self.response_buy_item() |
4366 | + |
4367 | + def do_GET(self): |
4368 | + """Respond to a GET request.""" |
4369 | + fail = self.path.find("/fail/") != -1 |
4370 | + if self.path.find("paymentmethods/add/") != -1: |
4371 | + self.response_payment_add() |
4372 | + elif self.path.find("paymentmethods/completeadd") != -1: |
4373 | + self.response_payment_add_complete() |
4374 | + elif self.path.find("paymentmethods/") != -1: |
4375 | + self.response_payment_types(fail) |
4376 | + elif self.path.find("creditcard_interaction/") != -1: |
4377 | + self.response_cc_interaction() |
4378 | + elif self.path.find("purchases/") != -1: |
4379 | + self.response_buy_item() |
4380 | + elif self.path.find("creds/") != -1 or self.path.find("wallet/") != -1: |
4381 | + self.response_update_credentials(fail) |
4382 | + elif self.path.find("iteminfo/") != -1: |
4383 | + self.response_item_info(fail) |
4384 | + elif self.path.find("/authError/") != -1: |
4385 | + self.response_auth_error() |
4386 | + |
4387 | + |
4388 | +def initial_payment_types(): |
4389 | + return [ |
4390 | + { |
4391 | + "description": "PayPal", |
4392 | + "id": "paypal", |
4393 | + "preferred": False, |
4394 | + "choices": [ |
4395 | + { |
4396 | + "currencies": [ |
4397 | + "USD", |
4398 | + "GBP", |
4399 | + "EUR" |
4400 | + ], |
4401 | + "id": 532, |
4402 | + "requires_interaction": False, |
4403 | + "preferred": False, |
4404 | + "description": ("PayPal Preapproved Payment " |
4405 | + "(exp. 2014-04-12)") |
4406 | + } |
4407 | + ] |
4408 | + }, |
4409 | + { |
4410 | + "description": "Credit or Debit Card", |
4411 | + "id": "credit_card", |
4412 | + "preferred": True, |
4413 | + "choices": [ |
4414 | + { |
4415 | + "currencies": [ |
4416 | + "USD" |
4417 | + ], |
4418 | + "id": 1767, |
4419 | + "requires_interaction": False, |
4420 | + "preferred": False, |
4421 | + "description": ("**** **** **** 1111 " |
4422 | + "(Visa, exp. 02/2015)") |
4423 | + }, |
4424 | + { |
4425 | + "currencies": [ |
4426 | + "USD" |
4427 | + ], |
4428 | + "id": 1726, |
4429 | + "requires_interaction": False, |
4430 | + "preferred": True, |
4431 | + "description": ("**** **** **** 1111 " |
4432 | + "(Visa, exp. 03/2015)") |
4433 | + } |
4434 | + ] |
4435 | + } |
4436 | + ] |
4437 | + |
4438 | + |
4439 | +class MockServer: |
4440 | + def __init__(self): |
4441 | + server_address = ('', 0) |
4442 | + self.server = HTTPServer(server_address, MyHandler) |
4443 | + tcp_port = self.server.socket.getsockname()[1] |
4444 | + self.base_url = "http://127.0.0.1:%d/" % tcp_port |
4445 | + server_thread = threading.Thread(target=self.server.serve_forever) |
4446 | + server_thread.start() |
4447 | + self.server.payment_types = initial_payment_types() |
4448 | + self.server.fail = False |
4449 | + self.server.buy_cc_interaction = None |
4450 | + self.server.interaction_cancel = False |
4451 | + |
4452 | + def set_purchase_needs_cc_interaction(self): |
4453 | + # Real server returns path starting with / here, not full URL. |
4454 | + self.server.buy_cc_interaction = "/creditcard_interaction" |
4455 | + |
4456 | + def set_interaction_result_cancelled(self): |
4457 | + self.server.interaction_cancel = True |
4458 | + |
4459 | + def url(self, tail=""): |
4460 | + return self.base_url + tail |
4461 | + |
4462 | + def shutdown(self): |
4463 | + self.server.shutdown() |
4464 | |
4465 | === added file 'pay-ui/tests/autopilot/pay_ui/tests/test_pay_ui.py' |
4466 | --- pay-ui/tests/autopilot/pay_ui/tests/test_pay_ui.py 1970-01-01 00:00:00 +0000 |
4467 | +++ pay-ui/tests/autopilot/pay_ui/tests/test_pay_ui.py 2016-03-11 14:49:24 +0000 |
4468 | @@ -0,0 +1,186 @@ |
4469 | +# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- |
4470 | +# |
4471 | +# Copyright (C) 2014-2016 Canonical Ltd. |
4472 | +# |
4473 | +# This program is free software; you can redistribute it and/or modify |
4474 | +# it under the terms of the GNU General Public License version 3, as published |
4475 | +# by the Free Software Foundation. |
4476 | +# |
4477 | +# This program is distributed in the hope that it will be useful, |
4478 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
4479 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
4480 | +# GNU General Public License for more details. |
4481 | +# |
4482 | +# You should have received a copy of the GNU General Public License |
4483 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
4484 | + |
4485 | +import fixtures |
4486 | +import testtools |
4487 | + |
4488 | +from testtools.matchers import Equals, NotEquals |
4489 | +from autopilot.matchers import Eventually |
4490 | + |
4491 | +from pay_ui import tests |
4492 | +from pay_ui.tests import mock_server |
4493 | + |
4494 | + |
4495 | +class PayUITestCase(tests.BasePayUITestCase): |
4496 | + |
4497 | + def setUp(self): |
4498 | + self.clean_config_dir() |
4499 | + self.mock_server = mock_server.MockServer() |
4500 | + self.addCleanup(self.mock_server.shutdown) |
4501 | + self.useFixture(fixtures.EnvironmentVariable( |
4502 | + 'PAY_BASE_URL', |
4503 | + self.mock_server.url() + '/' + self.id().split('.')[-1])) |
4504 | + self.useFixture(fixtures.EnvironmentVariable( |
4505 | + 'U1_SEARCH_BASE_URL', self.mock_server.url('iteminfo/'))) |
4506 | + self.useFixture(fixtures.EnvironmentVariable( |
4507 | + 'SSO_AUTH_BASE_URL', self.mock_server.url('login/'))) |
4508 | + self.useFixture(fixtures.EnvironmentVariable('GET_CREDENTIALS', '0')) |
4509 | + self.create_config_dir() |
4510 | + self.addCleanup(self.clean_config_dir) |
4511 | + super().setUp() |
4512 | + |
4513 | + def app_returncode(self): |
4514 | + return self.app.process.wait(timeout=30) |
4515 | + |
4516 | + def test_ui_initialized(self): |
4517 | + main = self.main_view |
4518 | + self.assertThat(main, NotEquals(None)) |
4519 | + |
4520 | + def test_cancel_purchase(self): |
4521 | + self.main_view.cancel() |
4522 | + self.assertThat(self.app_returncode(), Equals(1)) |
4523 | + |
4524 | + def test_basic_purchase(self): |
4525 | + self.skipTest('Mouse clicks on buyButton not registering.') |
4526 | + self.main_view.enter_password('password123') |
4527 | + self.main_view.buy() |
4528 | + self.assertThat(self.app_returncode(), Equals(0)) |
4529 | + |
4530 | + def test_add_credit_card_completed(self): |
4531 | + payment_types = self.main_view.get_payment_types() |
4532 | + self.assertThat(payment_types.get_option_count(), Equals(3)) |
4533 | + self.main_view.open_add_card_page() |
4534 | + self.take_screenshot('add_card_page') |
4535 | + self.main_view.tap_on_webview() |
4536 | + self.assertThat(payment_types.get_option_count, Eventually(Equals(4))) |
4537 | + |
4538 | + def test_add_credit_card_returns_on_cancel(self): |
4539 | + self.mock_server.set_interaction_result_cancelled() |
4540 | + payment_types = self.main_view.get_payment_types() |
4541 | + self.assertThat(payment_types.get_option_count(), Equals(3)) |
4542 | + self.main_view.open_add_card_page() |
4543 | + self.take_screenshot('add_card_page') |
4544 | + self.main_view.tap_on_webview() |
4545 | + checkout_page = self.main_view.get_checkout_page() |
4546 | + self.assertThat(checkout_page.get_properties()["active"], |
4547 | + Eventually(Equals(True))) |
4548 | + |
4549 | + def test_add_credit_card_cancelled(self): |
4550 | + self.mock_server.set_interaction_result_cancelled() |
4551 | + payment_types = self.main_view.get_payment_types() |
4552 | + self.assertThat(payment_types.get_option_count(), Equals(3)) |
4553 | + self.main_view.open_add_card_page() |
4554 | + self.take_screenshot('add_card_page') |
4555 | + self.main_view.tap_on_webview() |
4556 | + self.assertThat(payment_types.get_option_count, Eventually(Equals(3))) |
4557 | + |
4558 | + @testtools.skip('JS Alert dialog seems to not work.') |
4559 | + def test_add_credit_card_js_alert(self): |
4560 | + payment_types = self.main_view.get_payment_types() |
4561 | + self.assertThat(payment_types.get_option_count(), Equals(3)) |
4562 | + self.main_view.open_add_card_page() |
4563 | + self.take_screenshot('add_card_page') |
4564 | + self.main_view.tap_dialog_ok_button() |
4565 | + self.assertThat(payment_types.get_option_count, Eventually(Equals(4))) |
4566 | + |
4567 | + def test_add_credit_card_js_beforeunload(self): |
4568 | + payment_types = self.main_view.get_payment_types() |
4569 | + self.assertThat(payment_types.get_option_count(), Equals(3)) |
4570 | + self.main_view.open_add_card_page() |
4571 | + self.take_screenshot('add_card_page') |
4572 | + self.main_view.tap_dialog_stay_button() |
4573 | + self.main_view.tap_on_webview() |
4574 | + self.assertThat(payment_types.get_option_count, Eventually(Equals(4))) |
4575 | + |
4576 | + @testtools.skip('Clicking add card link second seems to not work.') |
4577 | + def test_add_credit_card_js_beforeunload_twice(self): |
4578 | + payment_types = self.main_view.get_payment_types() |
4579 | + self.assertThat(payment_types.get_option_count(), Equals(3)) |
4580 | + self.main_view.open_add_card_page() |
4581 | + self.take_screenshot('add_card_page') |
4582 | + self.main_view.tap_dialog_leave_button() |
4583 | + self.take_screenshot('beforeunload_left') |
4584 | + self.main_view.open_add_card_page() |
4585 | + self.take_screenshot('beforeunload_back') |
4586 | + self.main_view.tap_dialog_stay_button() |
4587 | + self.main_view.tap_on_web_view() |
4588 | + self.assertThat(payment_types.get_option_count, Eventually(Equals(4))) |
4589 | + |
4590 | + def test_add_credit_card_js_beforeunload_cancelled(self): |
4591 | + payment_types = self.main_view.get_payment_types() |
4592 | + self.assertThat(payment_types.get_option_count(), Equals(3)) |
4593 | + self.main_view.open_add_card_page() |
4594 | + self.take_screenshot('add_card_page') |
4595 | + self.main_view.tap_dialog_leave_button() |
4596 | + self.take_screenshot('beforeunload_cancelled') |
4597 | + self.assertThat(payment_types.get_option_count, Eventually(Equals(3))) |
4598 | + |
4599 | + def test_add_credit_card_js_confirm_ok(self): |
4600 | + payment_types = self.main_view.get_payment_types() |
4601 | + self.assertThat(payment_types.get_option_count(), Equals(3)) |
4602 | + self.main_view.open_add_card_page() |
4603 | + self.take_screenshot('add_card_page') |
4604 | + self.main_view.tap_dialog_ok_button() |
4605 | + self.assertThat(payment_types.get_option_count, Eventually(Equals(4))) |
4606 | + |
4607 | + def test_add_credit_card_js_confirm_cancel(self): |
4608 | + payment_types = self.main_view.get_payment_types() |
4609 | + self.assertThat(payment_types.get_option_count(), Equals(3)) |
4610 | + self.main_view.open_add_card_page() |
4611 | + self.take_screenshot('add_card_page') |
4612 | + self.main_view.tap_dialog_cancel_button() |
4613 | + self.assertThat(payment_types.get_option_count, Eventually(Equals(3))) |
4614 | + |
4615 | + def test_add_credit_card_js_prompt_ok(self): |
4616 | + payment_types = self.main_view.get_payment_types() |
4617 | + self.assertThat(payment_types.get_option_count(), Equals(3)) |
4618 | + self.main_view.open_add_card_page() |
4619 | + self.take_screenshot('add_card_page') |
4620 | + self.main_view.input_dialog_text('friend') |
4621 | + self.main_view.tap_dialog_ok_button() |
4622 | + self.assertThat(payment_types.get_option_count, Eventually(Equals(4))) |
4623 | + |
4624 | + def test_add_credit_card_js_prompt_ok_wrong_text(self): |
4625 | + payment_types = self.main_view.get_payment_types() |
4626 | + self.assertThat(payment_types.get_option_count(), Equals(3)) |
4627 | + self.main_view.open_add_card_page() |
4628 | + self.take_screenshot('add_card_page') |
4629 | + self.main_view.input_dialog_text('amigo') |
4630 | + self.main_view.tap_dialog_ok_button() |
4631 | + self.assertThat(payment_types.get_option_count, Eventually(Equals(3))) |
4632 | + |
4633 | + def test_add_credit_card_js_prompt_cancel(self): |
4634 | + payment_types = self.main_view.get_payment_types() |
4635 | + self.assertThat(payment_types.get_option_count(), Equals(3)) |
4636 | + self.main_view.open_add_card_page() |
4637 | + self.take_screenshot('add_card_page') |
4638 | + self.main_view.tap_dialog_cancel_button() |
4639 | + self.assertThat(payment_types.get_option_count, Eventually(Equals(3))) |
4640 | + |
4641 | + def test_purchase_with_web_interaction_completed(self): |
4642 | + self.skipTest('Mouse clicks on buyButton not registering.') |
4643 | + self.mock_server.set_purchase_needs_cc_interaction() |
4644 | + self.main_view.buy() |
4645 | + self.main_view.tap_on_webview() |
4646 | + self.assertThat(self.app_returncode(), Equals(0)) |
4647 | + |
4648 | + def test_purchase_with_web_interaction_cancelled(self): |
4649 | + self.skipTest('Mouse clicks on buyButton not registering.') |
4650 | + self.mock_server.set_purchase_needs_cc_interaction() |
4651 | + self.mock_server.set_interaction_result_cancelled() |
4652 | + self.main_view.buy() |
4653 | + self.main_view.tap_on_webview() |
4654 | + self.assertThat(self.app_returncode(), Equals(1)) |
4655 | |
4656 | === added file 'pay-ui/tests/autopilot/run_autopilot' |
4657 | --- pay-ui/tests/autopilot/run_autopilot 1970-01-01 00:00:00 +0000 |
4658 | +++ pay-ui/tests/autopilot/run_autopilot 2016-03-11 14:49:24 +0000 |
4659 | @@ -0,0 +1,1 @@ |
4660 | +GET_CREDENTIALS=0 autopilot3 run -v pay_ui |
4661 | |
4662 | === modified file 'po/CMakeLists.txt' |
4663 | --- po/CMakeLists.txt 2015-12-11 19:49:20 +0000 |
4664 | +++ po/CMakeLists.txt 2016-03-11 14:49:24 +0000 |
4665 | @@ -15,7 +15,7 @@ |
4666 | # Creates the .pot file containing the translations template |
4667 | set(INTLTOOL_ENV |
4668 | XGETTEXT="${GETTEXT_XGETTEXT_EXECUTABLE}" |
4669 | - XGETTEXT_ARGS="--keyword=Gettext;--keyword=NGettext:1,2" |
4670 | + XGETTEXT_ARGS="--keyword=Gettext;--keyword=NGettext:1,2;--keyword=tr;--keyword=tr:1,2;--keyword=N_" |
4671 | srcdir="${CMAKE_CURRENT_SOURCE_DIR}" |
4672 | ) |
4673 | add_custom_target(${POT_FILE} |
4674 | |
4675 | === modified file 'po/POTFILES.in' |
4676 | --- po/POTFILES.in 2015-12-11 19:49:20 +0000 |
4677 | +++ po/POTFILES.in 2016-03-11 14:49:24 +0000 |
4678 | @@ -1,1 +1,9 @@ |
4679 | +pay-ui/app/components/ConfirmDialog.qml |
4680 | +pay-ui/app/components/PromptDialog.qml |
4681 | +pay-ui/app/components/BeforeUnloadDialog.qml |
4682 | +pay-ui/app/components/SecurityCertificatePopover.qml |
4683 | +pay-ui/app/components/AlertDialog.qml |
4684 | +pay-ui/app/ui/CheckoutPage.qml |
4685 | +pay-ui/app/ui/ErrorDialog.qml |
4686 | +pay-ui/app/payui.qml |
4687 | service-ng/src/pay-service-2/service/pay_service.go |
4688 | |
4689 | === added file 'po/aa.po' |
4690 | --- po/aa.po 1970-01-01 00:00:00 +0000 |
4691 | +++ po/aa.po 2016-03-11 14:49:24 +0000 |
4692 | @@ -0,0 +1,106 @@ |
4693 | +# Afar translation for pay-ui |
4694 | +# Copyright (c) 2015 Rosetta Contributors and Canonical Ltd 2015 |
4695 | +# This file is distributed under the same license as the pay-ui package. |
4696 | +# FIRST AUTHOR <EMAIL@ADDRESS>, 2015. |
4697 | +# |
4698 | +msgid "" |
4699 | +msgstr "" |
4700 | +"Project-Id-Version: pay-service\n" |
4701 | +"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n" |
4702 | +"POT-Creation-Date: 2015-02-19 15:01-0500\n" |
4703 | +"PO-Revision-Date: 2015-04-26 13:56+0000\n" |
4704 | +"Last-Translator: Charif AYFARAH <ayfarah@ymail.com>\n" |
4705 | +"Language-Team: Afar <aa@li.org>\n" |
4706 | +"MIME-Version: 1.0\n" |
4707 | +"Content-Type: text/plain; charset=UTF-8\n" |
4708 | +"Content-Transfer-Encoding: 8bit\n" |
4709 | +"X-Launchpad-Export-Date: 2015-12-25 05:38+0000\n" |
4710 | +"X-Generator: Launchpad (build 17865)\n" |
4711 | + |
4712 | +#: ../app/payui.qml:148 |
4713 | +msgid "Finish Purchase" |
4714 | +msgstr "Limok gaba kal" |
4715 | + |
4716 | +#: ../app/payui.qml:163 |
4717 | +msgid "Incorrect Password, please try again." |
4718 | +msgstr "Cuumiti makot yan, maganak kaadu gabbat." |
4719 | + |
4720 | +#: ../app/payui.qml:276 |
4721 | +msgid "Purchase failed" |
4722 | +msgstr "Limo makkinna" |
4723 | + |
4724 | +#: ../app/payui.qml:277 |
4725 | +msgid "The purchase couldn't be completed." |
4726 | +msgstr "limo ma duuduminna." |
4727 | + |
4728 | +#: ../app/payui.qml:292 |
4729 | +msgid "Error contacting the server" |
4730 | +msgstr "Yayfaayi angaaraw soka le" |
4731 | + |
4732 | +#: ../app/payui.qml:293 ../app/payui.qml:309 |
4733 | +msgid "Do you want to try again?" |
4734 | +msgstr "Kaadu gabbattam maay faxxa?" |
4735 | + |
4736 | +#: ../app/payui.qml:308 |
4737 | +msgid "Adding Credit Card failed" |
4738 | +msgstr "Abuúd Karti edde osisaanam makkinna" |
4739 | + |
4740 | +#: ../app/payui.qml:327 |
4741 | +msgid "Processing Purchase" |
4742 | +msgstr "Limô gexso" |
4743 | + |
4744 | +#: ../app/payui.qml:327 |
4745 | +msgid "Loading" |
4746 | +msgstr "Qulluumah" |
4747 | + |
4748 | +#: ../app/payui.qml:328 |
4749 | +msgid "Please wait…" |
4750 | +msgstr "Maganak qambal..." |
4751 | + |
4752 | +#: ../app/payui.qml:342 ../app/ui/CheckoutPage.qml:370 |
4753 | +msgid "Cancel" |
4754 | +msgstr "Bayis" |
4755 | + |
4756 | +#: ../app/payui.qml:401 |
4757 | +msgid "Add Payment" |
4758 | +msgstr "Mekla edde osis" |
4759 | + |
4760 | +#: ../app/ui/CheckoutPage.qml:27 |
4761 | +msgid "Payment" |
4762 | +msgstr "Mekla" |
4763 | + |
4764 | +#: ../app/ui/CheckoutPage.qml:228 |
4765 | +msgid "Enter your Ubuntu One password" |
4766 | +msgstr "Ubuntu One cuumita culus" |
4767 | + |
4768 | +#: ../app/ui/CheckoutPage.qml:271 |
4769 | +msgid "Type your verification code:" |
4770 | +msgstr "Mudenti diggoyso culus:" |
4771 | + |
4772 | +#: ../app/ui/CheckoutPage.qml:282 |
4773 | +msgid "2-factor device code" |
4774 | +msgstr "2-afeetah aalatih mudenta" |
4775 | + |
4776 | +#: ../app/ui/CheckoutPage.qml:379 |
4777 | +msgid "Buy Now" |
4778 | +msgstr "Away xaamit" |
4779 | + |
4780 | +#: ../app/ui/CheckoutPage.qml:393 |
4781 | +msgid "Add credit/debit card" |
4782 | +msgstr "Abuú/Raci karti edde osis" |
4783 | + |
4784 | +#: ../app/ui/ErrorDialog.qml:45 |
4785 | +msgid "Retry" |
4786 | +msgstr "Qagis" |
4787 | + |
4788 | +#: ../app/ui/ErrorDialog.qml:55 |
4789 | +msgid "Close" |
4790 | +msgstr "Alif" |
4791 | + |
4792 | +#: payui_payui.desktop.in.in.h:1 |
4793 | +msgid "Pay UI" |
4794 | +msgstr "UI mekel" |
4795 | + |
4796 | +#: payui_payui.desktop.in.in.h:2 |
4797 | +msgid "The application for completing a purchase." |
4798 | +msgstr "Limô duddoh abnisso" |
4799 | |
4800 | === added file 'po/am.po' |
4801 | --- po/am.po 1970-01-01 00:00:00 +0000 |
4802 | +++ po/am.po 2016-03-11 14:49:24 +0000 |
4803 | @@ -0,0 +1,106 @@ |
4804 | +# Amharic translation for pay-ui |
4805 | +# Copyright (c) 2014 Rosetta Contributors and Canonical Ltd 2014 |
4806 | +# This file is distributed under the same license as the pay-ui package. |
4807 | +# FIRST AUTHOR <EMAIL@ADDRESS>, 2014. |
4808 | +# |
4809 | +msgid "" |
4810 | +msgstr "" |
4811 | +"Project-Id-Version: pay-service\n" |
4812 | +"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n" |
4813 | +"POT-Creation-Date: 2015-02-19 15:01-0500\n" |
4814 | +"PO-Revision-Date: 2015-03-03 14:18+0000\n" |
4815 | +"Last-Translator: samson <Unknown>\n" |
4816 | +"Language-Team: Amharic <am@li.org>\n" |
4817 | +"MIME-Version: 1.0\n" |
4818 | +"Content-Type: text/plain; charset=UTF-8\n" |
4819 | +"Content-Transfer-Encoding: 8bit\n" |
4820 | +"X-Launchpad-Export-Date: 2015-12-25 05:38+0000\n" |
4821 | +"X-Generator: Launchpad (build 17865)\n" |
4822 | + |
4823 | +#: ../app/payui.qml:148 |
4824 | +msgid "Finish Purchase" |
4825 | +msgstr "ግዢውን መጨረሻ" |
4826 | + |
4827 | +#: ../app/payui.qml:163 |
4828 | +msgid "Incorrect Password, please try again." |
4829 | +msgstr "የተሳሳተ የመግቢያ ቃል እባክዎን እንደገና ይሞክሩ" |
4830 | + |
4831 | +#: ../app/payui.qml:276 |
4832 | +msgid "Purchase failed" |
4833 | +msgstr "ግዢው አልተሳካም" |
4834 | + |
4835 | +#: ../app/payui.qml:277 |
4836 | +msgid "The purchase couldn't be completed." |
4837 | +msgstr "ግዢውን መፈጸም አልተቻለም" |
4838 | + |
4839 | +#: ../app/payui.qml:292 |
4840 | +msgid "Error contacting the server" |
4841 | +msgstr "ስህተት ተፈጥሯል ሰርቨሩን በመገናኘት ላይ እንዳለ" |
4842 | + |
4843 | +#: ../app/payui.qml:293 ../app/payui.qml:309 |
4844 | +msgid "Do you want to try again?" |
4845 | +msgstr "እንደገና መሞከር ይፈልጋሉ?" |
4846 | + |
4847 | +#: ../app/payui.qml:308 |
4848 | +msgid "Adding Credit Card failed" |
4849 | +msgstr "የ ክሬዲር ካርድ መጨመር አልተቻለም" |
4850 | + |
4851 | +#: ../app/payui.qml:327 |
4852 | +msgid "Processing Purchase" |
4853 | +msgstr "ግዢውን በማካሄድ ላይ" |
4854 | + |
4855 | +#: ../app/payui.qml:327 |
4856 | +msgid "Loading" |
4857 | +msgstr "በመጫን ላይ" |
4858 | + |
4859 | +#: ../app/payui.qml:328 |
4860 | +msgid "Please wait…" |
4861 | +msgstr "እባክዎን ይጠብቁ…" |
4862 | + |
4863 | +#: ../app/payui.qml:342 ../app/ui/CheckoutPage.qml:370 |
4864 | +msgid "Cancel" |
4865 | +msgstr "መሰረዣ" |
4866 | + |
4867 | +#: ../app/payui.qml:401 |
4868 | +msgid "Add Payment" |
4869 | +msgstr "ክፍያውን መጨመሪያ" |
4870 | + |
4871 | +#: ../app/ui/CheckoutPage.qml:27 |
4872 | +msgid "Payment" |
4873 | +msgstr "ክፍያ" |
4874 | + |
4875 | +#: ../app/ui/CheckoutPage.qml:228 |
4876 | +msgid "Enter your Ubuntu One password" |
4877 | +msgstr "የ እርስዎን የ ኡቡንቱ ዋን የ መግቢያ ቃል ያስገቡ" |
4878 | + |
4879 | +#: ../app/ui/CheckoutPage.qml:271 |
4880 | +msgid "Type your verification code:" |
4881 | +msgstr "የ እርስዎን ማረጋገጫ ኮድ ይጻፉ:" |
4882 | + |
4883 | +#: ../app/ui/CheckoutPage.qml:282 |
4884 | +msgid "2-factor device code" |
4885 | +msgstr "2-ፋክተር የ አካል ኮድ" |
4886 | + |
4887 | +#: ../app/ui/CheckoutPage.qml:379 |
4888 | +msgid "Buy Now" |
4889 | +msgstr "አሁን መግዣ" |
4890 | + |
4891 | +#: ../app/ui/CheckoutPage.qml:393 |
4892 | +msgid "Add credit/debit card" |
4893 | +msgstr "ክሬዲት/ዴቢት ካርድ መጨመሪያ" |
4894 | + |
4895 | +#: ../app/ui/ErrorDialog.qml:45 |
4896 | +msgid "Retry" |
4897 | +msgstr "እንደገና ይሞክሩ" |
4898 | + |
4899 | +#: ../app/ui/ErrorDialog.qml:55 |
4900 | +msgid "Close" |
4901 | +msgstr "መዝጊያ" |
4902 | + |
4903 | +#: payui_payui.desktop.in.in.h:1 |
4904 | +msgid "Pay UI" |
4905 | +msgstr "መክፈያ UI" |
4906 | + |
4907 | +#: payui_payui.desktop.in.in.h:2 |
4908 | +msgid "The application for completing a purchase." |
4909 | +msgstr "መተግበሪያ ግዢውን ለመፈጸም" |
4910 | |
4911 | === added file 'po/ast.po' |
4912 | --- po/ast.po 1970-01-01 00:00:00 +0000 |
4913 | +++ po/ast.po 2016-03-11 14:49:24 +0000 |
4914 | @@ -0,0 +1,106 @@ |
4915 | +# Asturian translation for pay-ui |
4916 | +# Copyright (c) 2014 Rosetta Contributors and Canonical Ltd 2014 |
4917 | +# This file is distributed under the same license as the pay-ui package. |
4918 | +# FIRST AUTHOR <EMAIL@ADDRESS>, 2014. |
4919 | +# |
4920 | +msgid "" |
4921 | +msgstr "" |
4922 | +"Project-Id-Version: pay-service\n" |
4923 | +"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n" |
4924 | +"POT-Creation-Date: 2015-02-19 15:01-0500\n" |
4925 | +"PO-Revision-Date: 2015-06-09 15:40+0000\n" |
4926 | +"Last-Translator: enolp <enolp@softastur.org>\n" |
4927 | +"Language-Team: Asturian <ast@li.org>\n" |
4928 | +"MIME-Version: 1.0\n" |
4929 | +"Content-Type: text/plain; charset=UTF-8\n" |
4930 | +"Content-Transfer-Encoding: 8bit\n" |
4931 | +"X-Launchpad-Export-Date: 2015-12-25 05:38+0000\n" |
4932 | +"X-Generator: Launchpad (build 17865)\n" |
4933 | + |
4934 | +#: ../app/payui.qml:148 |
4935 | +msgid "Finish Purchase" |
4936 | +msgstr "Acabar la compra" |
4937 | + |
4938 | +#: ../app/payui.qml:163 |
4939 | +msgid "Incorrect Password, please try again." |
4940 | +msgstr "La contraseña ye incorreuta. Inténtalo de nueves." |
4941 | + |
4942 | +#: ../app/payui.qml:276 |
4943 | +msgid "Purchase failed" |
4944 | +msgstr "Falló la compra" |
4945 | + |
4946 | +#: ../app/payui.qml:277 |
4947 | +msgid "The purchase couldn't be completed." |
4948 | +msgstr "Nun pudo completase la compra" |
4949 | + |
4950 | +#: ../app/payui.qml:292 |
4951 | +msgid "Error contacting the server" |
4952 | +msgstr "Fallu al contautar col sirvidor" |
4953 | + |
4954 | +#: ../app/payui.qml:293 ../app/payui.qml:309 |
4955 | +msgid "Do you want to try again?" |
4956 | +msgstr "¿Quies intentalo otra vegada?" |
4957 | + |
4958 | +#: ../app/payui.qml:308 |
4959 | +msgid "Adding Credit Card failed" |
4960 | +msgstr "Fallu al amestar tarxeta de creitu" |
4961 | + |
4962 | +#: ../app/payui.qml:327 |
4963 | +msgid "Processing Purchase" |
4964 | +msgstr "Procesando la compra" |
4965 | + |
4966 | +#: ../app/payui.qml:327 |
4967 | +msgid "Loading" |
4968 | +msgstr "Cargando" |
4969 | + |
4970 | +#: ../app/payui.qml:328 |
4971 | +msgid "Please wait…" |
4972 | +msgstr "Espera, por favor..." |
4973 | + |
4974 | +#: ../app/payui.qml:342 ../app/ui/CheckoutPage.qml:370 |
4975 | +msgid "Cancel" |
4976 | +msgstr "Encaboxar" |
4977 | + |
4978 | +#: ../app/payui.qml:401 |
4979 | +msgid "Add Payment" |
4980 | +msgstr "Amestar pagu" |
4981 | + |
4982 | +#: ../app/ui/CheckoutPage.qml:27 |
4983 | +msgid "Payment" |
4984 | +msgstr "Pagu" |
4985 | + |
4986 | +#: ../app/ui/CheckoutPage.qml:228 |
4987 | +msgid "Enter your Ubuntu One password" |
4988 | +msgstr "Introduz la to contraseña d'Ubuntu One" |
4989 | + |
4990 | +#: ../app/ui/CheckoutPage.qml:271 |
4991 | +msgid "Type your verification code:" |
4992 | +msgstr "Escribi'l códigu de comprobación:" |
4993 | + |
4994 | +#: ../app/ui/CheckoutPage.qml:282 |
4995 | +msgid "2-factor device code" |
4996 | +msgstr "códigu de preséu 2-factor" |
4997 | + |
4998 | +#: ../app/ui/CheckoutPage.qml:379 |
4999 | +msgid "Buy Now" |
5000 | +msgstr "Mercar Agora" |
The diff has been truncated for viewing.
FAILED: Continuous integration, rev:118 jenkins. qa.ubuntu. com/job/ pay-service- ci/146/ jenkins. qa.ubuntu. com/job/ pay-service- wily-amd64- ci/76/console jenkins. qa.ubuntu. com/job/ pay-service- wily-armhf- ci/76/console
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild: s-jenkins. ubuntu- ci:8080/ job/pay- service- ci/146/ rebuild
http://