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