Merge lp:~dobey/pay-service/merge-payui into lp:pay-service

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
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.

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 :
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
=== modified file '.bzrignore'
--- .bzrignore 2015-12-11 19:49:20 +0000
+++ .bzrignore 2016-03-11 14:49:24 +0000
@@ -8,7 +8,8 @@
8*.so.*8*.so.*
9*.mo9*.mo
10*.service10*.service
1111pay-ui/pay-ui
1212
13__pycache__
13build/14build/
14builddir/15builddir/
1516
=== modified file 'CMakeLists.txt'
--- CMakeLists.txt 2015-12-13 02:28:18 +0000
+++ CMakeLists.txt 2016-03-11 14:49:24 +0000
@@ -35,7 +35,6 @@
3535
36pkg_check_modules (SERVICE_DEPS REQUIRED36pkg_check_modules (SERVICE_DEPS REQUIRED
37 Qt5Core37 Qt5Core
38 click-0.4
39 dbus-cpp38 dbus-cpp
40 dbustest-139 dbustest-1
41 gio-2.040 gio-2.0
@@ -87,12 +86,12 @@
87add_subdirectory(common)86add_subdirectory(common)
88add_subdirectory(libpay)87add_subdirectory(libpay)
89add_subdirectory(service-ng)88add_subdirectory(service-ng)
90add_subdirectory(ual-helper)
91add_subdirectory(data)89add_subdirectory(data)
92if (${enable_tests})90if (${enable_tests})
93 add_subdirectory(tests)91 add_subdirectory(tests)
94endif ()92endif ()
95add_subdirectory(pay-test-app)93add_subdirectory(pay-test-app)
94add_subdirectory(pay-ui)
96add_subdirectory(po)95add_subdirectory(po)
9796
98##97##
9998
=== added file 'HACKING'
--- HACKING 1970-01-01 00:00:00 +0000
+++ HACKING 2016-03-11 14:49:24 +0000
@@ -0,0 +1,85 @@
1pay-service hacking guide
2===============================
3
4Getting pay-service
5-------------------------
6
7To get the main branch of pay-service:
8
9 $ bzr branch lp:pay-service
10
11
12Getting dependencies
13--------------------
14
15To succesfully build pay-service extra packages are required:
16
17 $ sudo apt-get build-dep pay-service
18
19
20Building pay-service
21------------------
22
23This app is built using cmake. Here's an example on how to build it:
24
25 $ mkdir build
26 $ cd build
27 $ cmake ..
28 $ make -j 8
29
30
31Running the unit tests
32----------------------
33
34 $ make test
35
36
37Running the autopilot tests
38---------------------------
39
40To run the autopilot tests locally, you first need to build pay-service.
41
42 $ make autopilot
43
44The autopilot tests can also be run against a built debian package, under
45qemu. To do so, you will need some additional packages. Most importantly,
46you will need the latest version of the autopkgtest package. You can download
47it at https://launchpad.net/ubuntu/+source/autopkgtest by selecting the most
48recent build for the most recent version of Ubuntu.
49
50 $ sudo dpkg -i autopkgtest*.deb
51 $ sudo apt-get install qemu
52
53After installing autopkgtest and qemu, you need to build an image for qemu
54to use. We use vivid here, as building an image to closely resemble the actual
55stable phone images is quite difficult. When this is easier in the future, we
56will switch to using stable phone images for this. The architecture argument
57should match the architecture of the debian package you are trying to test.
58We output the image to ~/ rather than the current directory, so it will be in
59a safer place to avoid rebuilding images every time. You can store it in any
60directory you wish.
61
62 $ adt-buildvm-ubuntu-cloud -r vivid -a amd64 -o ~/
63
64Then the tests may be run using adt-run with the qemu virtualization host.
65The output directory option here can be wherever you like, and is where the
66test artifacts will be placed. The ordering of the arguments to adt-run is
67important so try to keep them in this order.
68
69 $ adt-run --click-source . \
70 --source ../pay-service*.dsc \
71 -o /tmp/adt-payui-test \
72 --setup-commands "add-apt-repository \
73 ppa:ci-train-ppa-service/stable-phone-overlay" \
74 --apt-pocket proposed \
75 --setup-commands "apt-get update" \
76 --setup-commands ubuntu-touch-session \
77 --- qemu ~/adt-vivid-amd64-cloud.img
78
79To examine the test results, which are in subunit format, additional tools are
80required.
81
82 $ sudo add-apt-repository ppa:thomir/trv
83 $ sudo apt-get update
84 $ sudo apt-get install trv
85 $ trv /tmp/adt-payui-test/artifacts/autopilot.subunit
086
=== modified file 'debian/control'
--- debian/control 2015-12-11 22:14:01 +0000
+++ debian/control 2016-03-11 14:49:24 +0000
@@ -2,8 +2,7 @@
2Section: gnome2Section: gnome
3Priority: optional3Priority: optional
4Maintainer: Ubuntu Developers <ubuntu-devel-discuss@lists.ubuntu.com>4Maintainer: Ubuntu Developers <ubuntu-devel-discuss@lists.ubuntu.com>
5Build-Depends: click-dev,5Build-Depends: cmake,
6 cmake,
7 cmake-extras,6 cmake-extras,
8 dbus,7 dbus,
9 dbus-test-runner,8 dbus-test-runner,
@@ -15,7 +14,6 @@
15 google-mock,14 google-mock,
16 intltool,15 intltool,
17 lcov,16 lcov,
18 libclick-0.4-dev,
19 libdbus-1-dev,17 libdbus-1-dev,
20 libdbus-cpp-dev,18 libdbus-cpp-dev,
21 libdbustest1-dev,19 libdbustest1-dev,
@@ -26,12 +24,15 @@
26 libproperties-cpp-dev,24 libproperties-cpp-dev,
27 libtrust-store-dev,25 libtrust-store-dev,
28 libubuntu-app-launch2-dev (>= 0.5),26 libubuntu-app-launch2-dev (>= 0.5),
27 libubuntuoneauth-2.0-dev,
29 pkg-config,28 pkg-config,
30 python3-dbusmock,29 python3-dbusmock,
31 qt5-default,30 qt5-default,
32 qtbase5-dev,31 qtbase5-dev,
33 qtdeclarative5-dev,32 qtdeclarative5-dev,
33 qtdeclarative5-dev-tools,
34 sysvinit-utils,34 sysvinit-utils,
35 xvfb,
35Standards-Version: 3.9.536Standards-Version: 3.9.5
36Homepage: https://launchpad.net/pay-service37Homepage: https://launchpad.net/pay-service
37# If you aren't a member of ~indicator-applet-developers but need to upload38# If you aren't a member of ~indicator-applet-developers but need to upload
@@ -44,6 +45,7 @@
44Pre-Depends: ${misc:Pre-Depends},45Pre-Depends: ${misc:Pre-Depends},
45Depends: ${misc:Depends},46Depends: ${misc:Depends},
46 ${shlibs:Depends},47 ${shlibs:Depends},
48 pay-ui (= ${binary:Version}),
47 sysvinit-utils,49 sysvinit-utils,
48 ubuntu-push-client (>= 0.68+15.04.20151009),50 ubuntu-push-client (>= 0.68+15.04.20151009),
49Recommends: gnupg,51Recommends: gnupg,
@@ -54,7 +56,23 @@
54 payment provider and give that money to the developer. This service56 payment provider and give that money to the developer. This service
55 coordinates the payment interaction with the server on the device.57 coordinates the payment interaction with the server on the device.
56 .58 .
57 This package provides a service for the Pay Service59 This package provides a service for the Pay Service.
60
61Package: pay-ui
62Architecture: any
63Depends:
64 ${misc:Depends},
65 ${shlibs:Depends},
66 qtdeclarative5-ubuntu-web-plugin [amd64 armhf i386],
67 qtdeclarative5-online-accounts-client0.1,
68 qtdeclarative5-ubuntu-ui-toolkit-plugin,
69Description: service to allow requesting payment for an item - user interface
70 Allows applications to request an item be paid for by the user.
71 This requires interaction with the server backend to handle the
72 payment provider and give that money to the developer. This service
73 coordinates the payment interaction with the server on the device.
74 .
75 This package provides the user interface for the Pay Service.
5876
59Package: libpay277Package: libpay2
60Architecture: any78Architecture: any
6179
=== modified file 'debian/copyright'
--- debian/copyright 2015-12-10 22:04:32 +0000
+++ debian/copyright 2016-03-11 14:49:24 +0000
@@ -3,7 +3,7 @@
3Source: http://launchpad.net/pay-service3Source: http://launchpad.net/pay-service
44
5Files: *5Files: *
6Copyright: 2014-2015 Canonical, Ltd.6Copyright: 2014-2016 Canonical, Ltd.
7License: GPL-37License: GPL-3
88
9Files: libpay/*9Files: libpay/*
@@ -27,7 +27,7 @@
27License: LGPL-327License: LGPL-3
2828
29Files: service-ng/src/launchpad.net/go-mir/*29Files: service-ng/src/launchpad.net/go-mir/*
30Copyright: 2015 Canonical, Ltd.30Copyright: 2015-2016 Canonical, Ltd.
31License: LGPL-331License: LGPL-3
3232
33Files: service-ng/src/launchpad.net/go-trust-store/*33Files: service-ng/src/launchpad.net/go-trust-store/*
3434
=== removed file 'debian/pay-service.click-hook'
--- debian/pay-service.click-hook 2014-07-10 13:34:44 +0000
+++ debian/pay-service.click-hook 1970-01-01 00:00:00 +0000
@@ -1,4 +0,0 @@
1Pattern: ${home}/.cache/pay-service/pay-ui/${id}.desktop
2Exec: /bin/true
3User-Level: yes
4Hook-Name: pay-ui
50
=== modified file 'debian/pay-service.install'
--- debian/pay-service.install 2015-12-09 18:18:45 +0000
+++ debian/pay-service.install 2016-03-11 14:49:24 +0000
@@ -1,5 +1,5 @@
1usr/lib/*/pay-service/pay-service-21usr/lib/*/pay-service/pay-service-2
2usr/lib/*/pay-service/setup-staging.sh2usr/lib/*/pay-service/setup-staging.sh
3usr/lib/*/ubuntu-app-launch/pay-ui/*3usr/share/locale/*/LC_MESSAGES/pay-service.mo
4usr/share/dbus-1/services/*.service4usr/share/dbus-1/services/*.service
5usr/share/upstart/sessions/*.conf
6\ No newline at end of file5\ No newline at end of file
6usr/share/upstart/sessions/*.conf
77
=== added file 'debian/pay-ui.install'
--- debian/pay-ui.install 1970-01-01 00:00:00 +0000
+++ debian/pay-ui.install 2016-03-11 14:49:24 +0000
@@ -0,0 +1,3 @@
1usr/lib/payui
2usr/lib/*/payui
3usr/share/payui
04
=== modified file 'debian/rules'
--- debian/rules 2015-10-14 20:53:54 +0000
+++ debian/rules 2016-03-11 14:49:24 +0000
@@ -10,4 +10,4 @@
10 dh_auto_configure -- -DCMAKE_INSTALL_LIBEXECDIR=/usr/lib/$(DEB_HOST_MULTIARCH)/pay-service10 dh_auto_configure -- -DCMAKE_INSTALL_LIBEXECDIR=/usr/lib/$(DEB_HOST_MULTIARCH)/pay-service
1111
12%:12%:
13 dh $@ --parallel --fail-missing --with click13 dh $@ --parallel --fail-missing
1414
=== added directory 'pay-ui'
=== added file 'pay-ui/CMakeLists.txt'
--- pay-ui/CMakeLists.txt 1970-01-01 00:00:00 +0000
+++ pay-ui/CMakeLists.txt 2016-03-11 14:49:24 +0000
@@ -0,0 +1,21 @@
1# Need xvfb-run for some tests.
2set(XVFB_CMD xvfb-run -a -s "-screen 0 540x960x24")
3
4# Standard install paths
5set(APP_NAME payui)
6
7set(QT_IMPORTS_DIR "${CMAKE_INSTALL_FULL_LIBEXECDIR}/payui")
8set(PAYUI_DIR "${CMAKE_INSTALL_FULL_DATADIR}/payui/qml")
9
10add_subdirectory(app)
11add_subdirectory(backend)
12
13configure_file(pay-ui.in pay-ui)
14install(PROGRAMS ${CMAKE_CURRENT_BINARY_DIR}/pay-ui
15 DESTINATION lib/payui
16)
17
18add_custom_target("autopilot"
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
20 DEPENDS payuibackend payuibackend-qmldir
21)
022
=== added directory 'pay-ui/app'
=== added file 'pay-ui/app/CMakeLists.txt'
--- pay-ui/app/CMakeLists.txt 1970-01-01 00:00:00 +0000
+++ pay-ui/app/CMakeLists.txt 2016-03-11 14:49:24 +0000
@@ -0,0 +1,6 @@
1file(GLOB QML_JS_FILES *.qml *.js)
2
3install(FILES ${QML_JS_FILES} DESTINATION ${PAYUI_DIR})
4
5add_subdirectory(components)
6add_subdirectory(ui)
07
=== added directory 'pay-ui/app/components'
=== added file 'pay-ui/app/components/AlertDialog.qml'
--- pay-ui/app/components/AlertDialog.qml 1970-01-01 00:00:00 +0000
+++ pay-ui/app/components/AlertDialog.qml 2016-03-11 14:49:24 +0000
@@ -0,0 +1,31 @@
1/*
2 * Copyright 2013-2014 Canonical Ltd.
3 *
4 * This file is part of webbrowser-app.
5 *
6 * webbrowser-app is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; version 3.
9 *
10 * webbrowser-app is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19import QtQuick 2.0
20import Ubuntu.Components 1.1
21
22ModalDialog {
23 objectName: "alertDialog"
24 title: i18n.dtr("webbrowser-app", "JavaScript Alert")
25
26 Button {
27 objectName: "dialogOkButton"
28 text: i18n.dtr("webbrowser-app", "OK")
29 onClicked: model.accept()
30 }
31}
032
=== added file 'pay-ui/app/components/BeforeUnloadDialog.qml'
--- pay-ui/app/components/BeforeUnloadDialog.qml 1970-01-01 00:00:00 +0000
+++ pay-ui/app/components/BeforeUnloadDialog.qml 2016-03-11 14:49:24 +0000
@@ -0,0 +1,37 @@
1/*
2 * Copyright 2014 Canonical Ltd.
3 *
4 * This file is part of webbrowser-app.
5 *
6 * webbrowser-app is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; version 3.
9 *
10 * webbrowser-app is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19import QtQuick 2.0
20import Ubuntu.Components 1.1
21
22ModalDialog {
23 title: i18n.dtr("webbrowser-app", "Confirm Navigation")
24 objectName: "beforeUnloadDialog"
25
26 Button {
27 objectName: "leaveButton"
28 text: i18n.dtr("webbrowser-app", "Leave")
29 onClicked: model.accept()
30 }
31
32 Button {
33 objectName: "stayButton"
34 text: i18n.dtr("webbrowser-app", "Stay")
35 onClicked: model.reject()
36 }
37}
038
=== added file 'pay-ui/app/components/CMakeLists.txt'
--- pay-ui/app/components/CMakeLists.txt 1970-01-01 00:00:00 +0000
+++ pay-ui/app/components/CMakeLists.txt 2016-03-11 14:49:24 +0000
@@ -0,0 +1,6 @@
1file(GLOB COMPONENTS_QML_JS_FILES *.qml *.js)
2
3# make the files visible in the qtcreator tree
4add_custom_target(payui_components_QMlFiles ALL SOURCES ${COMPONENTS_QML_JS_FILES})
5
6install(FILES ${COMPONENTS_QML_JS_FILES} DESTINATION ${PAYUI_DIR}/components)
07
=== added file 'pay-ui/app/components/ConfirmDialog.qml'
--- pay-ui/app/components/ConfirmDialog.qml 1970-01-01 00:00:00 +0000
+++ pay-ui/app/components/ConfirmDialog.qml 2016-03-11 14:49:24 +0000
@@ -0,0 +1,37 @@
1/*
2 * Copyright 2013-2014 Canonical Ltd.
3 *
4 * This file is part of webbrowser-app.
5 *
6 * webbrowser-app is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; version 3.
9 *
10 * webbrowser-app is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19import QtQuick 2.0
20import Ubuntu.Components 1.1
21
22ModalDialog {
23 objectName: "confirmDialog"
24 title: i18n.dtr("webbrowser-app", "JavaScript Confirmation")
25
26 Button {
27 objectName: "dialogOkButton"
28 text: i18n.dtr("webbrowser-app", "OK")
29 onClicked: model.accept()
30 }
31
32 Button {
33 objectName: "dialogCancelButton"
34 text: i18n.dtr("webbrowser-app", "Cancel")
35 onClicked: model.reject()
36 }
37}
038
=== added file 'pay-ui/app/components/ModalDialog.qml'
--- pay-ui/app/components/ModalDialog.qml 1970-01-01 00:00:00 +0000
+++ pay-ui/app/components/ModalDialog.qml 2016-03-11 14:49:24 +0000
@@ -0,0 +1,32 @@
1/*
2 * Copyright 2014 Canonical Ltd.
3 *
4 * This file is part of webbrowser-app.
5 *
6 * webbrowser-app is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; version 3.
9 *
10 * webbrowser-app is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19import QtQuick 2.0
20import Ubuntu.Components 1.1
21import Ubuntu.Components.Popups 1.0 as Popups
22
23Popups.Dialog {
24 text: model.message
25
26 // Set the parent at construction time, instead of letting show()
27 // set it later on, which for some reason results in the size of
28 // the dialog not being updated.
29 parent: QuickUtils.rootItem(this)
30
31 Component.onCompleted: show()
32}
033
=== added file 'pay-ui/app/components/PromptDialog.qml'
--- pay-ui/app/components/PromptDialog.qml 1970-01-01 00:00:00 +0000
+++ pay-ui/app/components/PromptDialog.qml 2016-03-11 14:49:24 +0000
@@ -0,0 +1,51 @@
1/*
2 * Copyright 2013-2014 Canonical Ltd.
3 *
4 * This file is part of webbrowser-app.
5 *
6 * webbrowser-app is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; version 3.
9 *
10 * webbrowser-app is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19import QtQuick 2.0
20import Ubuntu.Components 1.1
21
22ModalDialog {
23 title: i18n.dtr("webbrowser-app", "JavaScript Prompt")
24
25 TextField {
26 objectName: "dialogInput"
27 id: input
28 text: model.defaultValue
29 onAccepted: model.accept(input.text)
30 }
31
32 Button {
33 objectName: "dialogOkButton"
34 text: i18n.dtr("webbrowser-app", "OK")
35 color: "green"
36 onClicked: model.accept(input.text)
37 }
38
39 Button {
40 objectName: "dialogCancelButton"
41 text: i18n.dtr("webbrowser-app", "Cancel")
42 color: UbuntuColors.coolGrey
43 onClicked: model.reject()
44 }
45
46 Binding {
47 target: model
48 property: "currentValue"
49 value: input.text
50 }
51}
052
=== added file 'pay-ui/app/components/SecurityCertificatePopover.qml'
--- pay-ui/app/components/SecurityCertificatePopover.qml 1970-01-01 00:00:00 +0000
+++ pay-ui/app/components/SecurityCertificatePopover.qml 2016-03-11 14:49:24 +0000
@@ -0,0 +1,145 @@
1/*
2 * Copyright 2014 Canonical Ltd.
3 *
4 * This file is part of webbrowser-app.
5 *
6 * webbrowser-app is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; version 3.
9 *
10 * webbrowser-app is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19import QtQuick 2.0
20import Ubuntu.Components 1.1
21import Ubuntu.Components.ListItems 1.0
22import Ubuntu.Components.Popups 1.0
23import payui 0.1 as Oxide
24
25Popover {
26 id: certificatePopover
27
28 property var securityStatus
29
30 Column {
31 width: parent.width - units.gu(4)
32 anchors.horizontalCenter: parent.horizontalCenter
33 spacing: units.gu(0.5)
34
35 Item {
36 height: units.gu(1.5)
37 width: parent.width
38 }
39
40 Column {
41 width: parent.width
42 visible: securityStatus.securityLevel == Oxide.SecurityStatus.SecurityLevelWarning
43 spacing: units.gu(0.5)
44
45 Row {
46 width: parent.width
47 spacing: units.gu(0.5)
48
49 Icon {
50 name: "security-alert"
51 height: units.gu(2)
52 width: height
53 }
54
55 Label {
56 width: parent.width
57 wrapMode: Text.WordWrap
58 text: i18n.dtr("webbrowser-app", "This site has insecure content")
59 fontSize: "x-small"
60 }
61 }
62
63 ThinDivider {
64 width: parent.width
65 anchors.leftMargin: 0
66 anchors.rightMargin: 0
67 }
68 }
69
70 Label {
71 width: parent.width
72 wrapMode: Text.WordWrap
73 text: i18n.dtr("webbrowser-app", "You are connected to")
74 fontSize: "x-small"
75 }
76
77 Label {
78 width: parent.width
79 wrapMode: Text.WordWrap
80 text: securityStatus.certificate.subjectDisplayName
81 fontSize: "x-small"
82 }
83
84 ThinDivider {
85 width: parent.width
86 anchors.leftMargin: 0
87 anchors.rightMargin: 0
88 visible: orgName.visible || localityName.visible || stateName.visible || countryName.visible
89 }
90
91 Label {
92 width: parent.width
93 wrapMode: Text.WordWrap
94 visible: orgName.visible
95 text: i18n.dtr("webbrowser-app", "Which is run by")
96 fontSize: "x-small"
97 }
98
99 Label {
100 id: orgName
101 width: parent.width
102 wrapMode: Text.WordWrap
103 visible: text.length > 0
104 text: securityStatus.certificate.getSubjectInfo(Oxide.SslCertificate.PrincipalAttrOrganizationName).join(", ")
105 fontSize: "x-small"
106 }
107
108 Label {
109 id: localityName
110 width: parent.width
111 wrapMode: Text.WordWrap
112 visible: text.length > 0
113 text: securityStatus.certificate.getSubjectInfo(Oxide.SslCertificate.PrincipalAttrLocalityName).join(", ")
114 fontSize: "x-small"
115 }
116
117 Label {
118 id: stateName
119 width: parent.width
120 wrapMode: Text.WordWrap
121 visible: text.length > 0
122 text: securityStatus.certificate.getSubjectInfo(Oxide.SslCertificate.PrincipalAttrStateOrProvinceName).join(", ")
123 fontSize: "x-small"
124 }
125
126 Label {
127 id: countryName
128 width: parent.width
129 wrapMode: Text.WordWrap
130 visible: text.length > 0
131 text: securityStatus.certificate.getSubjectInfo(Oxide.SslCertificate.PrincipalAttrCountryName).join(", ")
132 fontSize: "x-small"
133 }
134
135 Item {
136 height: units.gu(1.5)
137 width: parent.width
138 }
139 }
140
141 MouseArea {
142 anchors.fill: parent
143 onClicked: PopupUtils.close(certificatePopover)
144 }
145}
0146
=== added file 'pay-ui/app/payui.qml'
--- pay-ui/app/payui.qml 1970-01-01 00:00:00 +0000
+++ pay-ui/app/payui.qml 2016-03-11 14:49:24 +0000
@@ -0,0 +1,459 @@
1/*
2 * Copyright 2014-2016 Canonical Ltd.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; version 3.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16
17import QtQuick 2.0
18import QtQuick.LocalStorage 2.0
19import Ubuntu.Components 1.1
20import Ubuntu.Components.Popups 0.1
21import Ubuntu.OnlineAccounts 0.1
22import Ubuntu.OnlineAccounts.Client 0.1
23import payui 0.1
24import "ui"
25
26/*!
27 states:
28 - add-payment
29 - buy-interaction
30 - checkout
31 - online-accounts
32 - error
33*/
34
35MainView {
36 id: mainView
37 // objectName for functional testing purposes (autopilot-qt5)
38 objectName: "payui"
39
40 // NOTE: Must match the gettext domain for translations.
41 applicationName: "pay-service"
42
43 /*
44 This property enables the application to change orientation
45 when the device is rotated. The default is false.
46 */
47 // automaticOrientation: true
48
49 width: units.gu(100)
50 height: units.gu(75)
51
52 useDeprecatedToolbar: false
53
54 property bool loading: true
55 property bool purchasing: false
56 property bool recentLogin: false
57 property bool cancellable: true
58 property string suggestedCurrency: "USD"
59
60 backgroundColor: "transparent"
61
62 onStateChanged: {
63 mainView.backgroundColor = "white";
64 }
65
66 AccountServiceModel {
67 id: accounts
68 provider: "ubuntuone"
69 }
70
71 Setup {
72 id: setup
73 applicationId: "pay-service"
74 providerId: "ubuntuone"
75
76 onFinished: {
77 mainView.recentLogin = true;
78 mainView.showLoading();
79 purchase.checkCredentials();
80 }
81 }
82
83 Purchase {
84 id: purchase
85
86 onItemDetailsObtained: {
87 suggestedCurrency = currency;
88 checkout.itemIcon = icon;
89 checkout.itemTitle = title;
90 checkout.itemSubtitle = publisher;
91 checkout.price = formatted_price;
92 }
93
94 onPaymentTypesObtained: {
95 mainView.recentLogin = mainView.recentCredentials();
96 checkout.beforeTimeout = mainView.recentLogin;
97
98 // Check for selected payment, and keep it selected if so.
99 if (checkout.hasSelectedPayment) {
100 for (var i=0; i < payments.length; i++) {
101 if (payments[i].paymentId == checkout.paymentId &&
102 payments[i].backendId == checkout.backendId) {
103 payments[i].preferred = true;
104 } else {
105 payments[i].preferred = false;
106 }
107 }
108 }
109 checkout.model = payments;
110 checkout.hasPayments = payments.length != 0;
111 checkout.setSelectedItem();
112
113 mainView.state = "checkout";
114 pageStack.push(checkout);
115
116 hideLoading();
117 }
118
119 onNoPreferredPaymentMethod: {
120 checkout.hasPreferredPayment = false;
121 var values = mainView.getLastPayment();
122 var backendid = values[0];
123 var paymentid = values[1];
124 if (backendid != "" && paymentid != "") {
125 checkout.hasStoredPayment = true;
126 }
127 }
128
129 onPasswordValid: {
130 hideLoading();
131 // Reset password and otp to not keep them in memory.
132 checkout.password = "";
133 checkout.otp = "";
134 }
135
136 onBuyItemFailed: {
137 hideLoading();
138 purchaseErrorDialog.open = true;
139 }
140
141 onBuyItemSucceeded: {
142 purchase.quitSuccess();
143 }
144
145 onBuyInterationRequired: {
146 mainView.purchasing = false;
147 webkit.title = i18n.tr("Finish Purchase");
148 webkit.url = url;
149 mainView.state = "buy-interaction";
150 pageStack.push(webkit);
151 }
152
153 onError: {
154 hideLoading();
155 serverErrorDialog.open = true;
156 }
157
158 onAuthenticationError: {
159 mainView.recentLogin = false;
160 if (pageStack.currentPage == checkout) {
161 mainView.hideLoading();
162 checkout.showErrorMessage(i18n.tr("Incorrect Password, please try again."));
163 } else {
164 mainView.state = "online-accounts";
165 setup.exec();
166 }
167 }
168
169 onCredentialsFound: {
170 mainView.recentLogin = mainView.recentCredentials();
171 checkout.beforeTimeout = mainView.recentLogin;
172
173 if (mainView.state == "online-accounts") {
174 purchase.checkItemPurchased();
175 } else if (mainView.state != "checkout" && !mainView.purchasing && mainView.state != "buy-interaction") {
176 purchase.getPaymentTypes(suggestedCurrency);
177 }
178 purchase.getItemDetails();
179 }
180
181 onItemNotPurchased: {
182 purchase.getPaymentTypes(suggestedCurrency);
183 }
184
185 onCredentialsNotFound: {
186 mainView.recentLogin = false;
187 if (mainView.state == "online-accounts") {
188 purchase.quitCancel();
189 } else {
190 mainView.state = "online-accounts";
191 setup.exec();
192 }
193 }
194
195 onLoginError: {
196 mainView.recentLogin = false;
197 mainView.hideLoading();
198 checkout.showErrorMessage(message);
199 }
200
201 onTwoFactorAuthRequired: {
202 mainView.hideLoading();
203 checkout.showTwoFactor();
204 }
205
206 onCertificateFound: {
207 checkout.certificate = cert
208 }
209 }
210
211 function showLoading() {
212 mainView.loading = true;
213 PopupUtils.open(loadingDialogContainer);
214 }
215
216 function hideLoading() {
217 mainView.purchasing = false;
218 mainView.loading = false;
219 mainView.cancellable = true;
220 }
221
222 function createDB() {
223 var db = LocalStorage.openDatabaseSync("PayUI", "1.0", "PayUI Credentials Date", 100);
224 db.transaction(
225 function(tx) {
226 // Create the database if it doesn't already exist
227 tx.executeSql('CREATE TABLE IF NOT EXISTS PayUIPayment(backendid TEXT, paymentid TEXT)');
228 }
229 )
230 }
231
232 function recentCredentials() {
233 var valid = false;
234 var date = purchase.getTokenUpdated();
235 var currentDate = new Date();
236 var msec = currentDate - date;
237 var mm = Math.floor(msec / 1000 / 60);
238 if (mm < 15) {
239 valid = true;
240 }
241 return valid;
242 }
243
244 function getLastPayment() {
245 var backendid = "";
246 var paymentid = "";
247 var db = LocalStorage.openDatabaseSync("PayUI", "1.0", "PayUI Credentials Date", 100);
248 db.transaction(
249 function(tx) {
250 var rs = tx.executeSql('SELECT * FROM PayUIPayment');
251 if (rs.rows.length > 0) {
252 backendid = rs.rows.item(0).backendid;
253 paymentid = rs.rows.item(0).paymentid;
254 }
255 }
256 )
257 return [backendid, paymentid];
258 }
259
260 function updatePayment(backendid, paymentid) {
261 var db = LocalStorage.openDatabaseSync("PayUI", "1.0", "PayUI Credentials Date", 100);
262 db.transaction(
263 function(tx) {
264 var rs = tx.executeSql('SELECT * FROM PayUIPayment');
265 if (rs.rows.length > 0) {
266 tx.executeSql('UPDATE PayUIPayment SET backendid = "' + backendid + '", paymentid = "' + paymentid + '"');
267 } else {
268 tx.executeSql('INSERT INTO PayUIPayment VALUES(?, ?)', [backendid, paymentid]);
269 }
270 }
271 )
272 }
273
274 ErrorDialog {
275 id: purchaseErrorDialog
276 title: i18n.tr("Purchase failed")
277 message: i18n.tr("The purchase couldn't be completed.")
278
279 onRetry: {
280 mainView.state = "error";
281 mainView.showLoading();
282 purchase.getItemDetails();
283 }
284
285 onClose: {
286 purchase.quitCancel();
287 }
288 }
289
290 ErrorDialog {
291 id: serverErrorDialog
292 title: i18n.tr("Error contacting the server")
293 message: i18n.tr("Do you want to try again?")
294
295 onRetry: {
296 mainView.state = "error";
297 mainView.showLoading();
298 purchase.getItemDetails();
299 }
300
301 onClose: {
302 purchase.quitCancel();
303 }
304 }
305
306 ErrorDialog {
307 id: creditCardErrorDialog
308 title: i18n.tr("Adding Credit Card failed")
309 message: i18n.tr("Do you want to try again?")
310
311 onRetry: {
312 mainView.state = "error";
313 mainView.showLoading();
314 purchase.getItemDetails();
315 }
316
317 onClose: {
318 purchase.quitCancel();
319 }
320 }
321
322 Component {
323 id: loadingDialogContainer
324
325 Dialog {
326 id: loadingDialog
327 title: mainView.purchasing ? i18n.tr("Processing Purchase") : i18n.tr("Loading")
328 text: i18n.tr("Please wait…")
329 ActivityIndicator {
330 running: mainView.loading ? true : false
331 width: parent.width
332
333 onRunningChanged: {
334 if(!running) {
335 PopupUtils.close(loadingDialog);
336 }
337 }
338 }
339
340 Button {
341 objectName: "buttonCancelLoading"
342 text: i18n.tr("Cancel")
343 color: UbuntuColors.orange
344 visible: mainView.cancellable
345 onClicked: {
346 PopupUtils.close(loadingDialog);
347 purchase.quitCancel();
348 }
349 }
350 }
351 }
352
353 PageStack {
354 id: pageStack
355 objectName: "pageStack"
356
357 Component.onCompleted: {
358 showLoading();
359 mainView.createDB();
360 purchase.checkCredentials();
361 }
362
363 onCurrentPageChanged: {
364 if (pageStack.currentPage == checkout) {
365 mainView.state = "checkout";
366 }
367 }
368
369 CheckoutPage {
370 id: checkout
371 objectName: "pageCheckout"
372 visible: false
373 account: accounts
374
375 onCancel: {
376 purchase.quitCancel();
377 }
378
379 onBuy: {
380 mainView.recentLogin = mainView.recentCredentials();
381 checkout.beforeTimeout = mainView.recentLogin;
382
383 // Pass it on.
384 checkout.hasSelectedPayment = true;
385 checkout.backendId = backendId;
386 checkout.paymentId = paymentId;
387
388 mainView.purchasing = true;
389 mainView.cancellable = false;
390 showLoading();
391 if (!checkout.hasPreferredPayment) {
392 mainView.updatePayment(backendId, paymentId);
393 }
394
395 if (mainView.recentLogin) {
396 purchase.buyItem(email, "", "", suggestedCurrency, paymentId, backendId, mainView.recentLogin);
397 } else {
398 purchase.buyItem(email, password, otp, suggestedCurrency, paymentId, backendId, mainView.recentLogin);
399 }
400 }
401
402 onAddCreditCard: {
403 webkit.title = i18n.tr("Add Payment");
404 webkit.url = purchase.getAddPaymentUrl(suggestedCurrency);
405 mainView.state = "add-payment";
406 pageStack.push(webkit);
407 }
408 }
409
410 UbuntuPurchaseWebkit {
411 id: webkit
412 visible: false
413
414 onPurchaseFailed: {
415 pageStack.pop();
416 hideLoading();
417 purchaseErrorDialog.open = true;
418 }
419
420 onPurchaseCancelled: {
421 hideLoading();
422 if (mainView.state == "add-payment") {
423 mainView.state = "checkout";
424 pageStack.pop();
425 } else {
426 purchase.quitCancel();
427 }
428 }
429
430 onPurchaseSucceeded: {
431 if (mainView.state == "add-payment") {
432 showLoading();
433 purchase.getPaymentTypes(suggestedCurrency);
434 } else {
435 purchase.quitSuccess();
436 }
437 }
438
439 onLoading: {
440 if (value) {
441 showLoading();
442 } else {
443 hideLoading();
444 }
445 }
446 }
447 }
448
449 Rectangle {
450 id: lockIconPlace
451 width: units.gu(7)
452 height: units.gu(7)
453 anchors {
454 right: parent.right
455 top: parent.top
456 }
457 visible: false
458 }
459}
0460
=== added directory 'pay-ui/app/tests'
=== added directory 'pay-ui/app/tests/unit'
=== added directory 'pay-ui/app/tests/unit/js'
=== added file 'pay-ui/app/tests/unit/js/unit_test.js'
--- pay-ui/app/tests/unit/js/unit_test.js 1970-01-01 00:00:00 +0000
+++ pay-ui/app/tests/unit/js/unit_test.js 2016-03-11 14:49:24 +0000
@@ -0,0 +1,17 @@
1.pragma library
2
3// Find an object with the given name in the children tree of "obj"
4function findChild(obj,objectName) {
5 var childs = new Array(0);
6 childs.push(obj)
7 while (childs.length > 0) {
8 if (childs[0].objectName == objectName) {
9 return childs[0]
10 }
11 for (var i in childs[0].children) {
12 childs.push(childs[0].children[i])
13 }
14 childs.splice(0, 1);
15 }
16 return undefined;
17}
018
=== added file 'pay-ui/app/tests/unit/tst_checkoutpage.qml'
--- pay-ui/app/tests/unit/tst_checkoutpage.qml 1970-01-01 00:00:00 +0000
+++ pay-ui/app/tests/unit/tst_checkoutpage.qml 2016-03-11 14:49:24 +0000
@@ -0,0 +1,73 @@
1import QtQuick 2.0
2import QtTest 1.0
3import Ubuntu.Components 0.1
4import "../../ui"
5import "js/unit_test.js" as UT
6
7// See more details @ http://qt-project.org/doc/qt-5.0/qtquick/qml-testcase.html
8
9// Execute tests with:
10// qmltestrunner
11
12Item {
13 id: root
14
15 property string title: "My App"
16 property string subtitle: "My App Subtitle"
17 property string price: "$ 1.99"
18 property string ubuntuid: "mail@mail.com"
19 property bool called: false
20
21 // The objects
22 CheckoutPage {
23 id: checkoutPage
24 itemTitle: root.title
25 itemSubtitle: root.subtitle
26 price: root.price
27 ubuntuID: root.ubuntuid
28
29 onCancel: {
30 root.called = true;
31 }
32
33 onBuy: {
34 root.called = true;
35 }
36 }
37
38 TestCase {
39 name: "CheckoutPage"
40
41 function init() {
42 console.debug("Cleaning vars");
43 root.called = false;
44 }
45
46 function test_uiTexts() {
47 var titleLabel = UT.findChild(checkoutPage, "titleLabel");
48 var subtitleLabel = UT.findChild(checkoutPage, "subtitleLabel");
49 var priceLabel = UT.findChild(checkoutPage, "priceLabel");
50 var ubuntuIdLabel = UT.findChild(checkoutPage, "ubuntuIdLabel");
51 var ubuntuidExpected = "Ubuntu ID: " + root.ubuntuid
52
53 compare(titleLabel.text, root.title);
54 compare(subtitleLabel.text, root.subtitle);
55 compare(priceLabel.text, root.price);
56 compare(ubuntuIdLabel.text, ubuntuidExpected);
57 }
58
59 function test_cancelPressed() {
60 compare(root.called, false);
61 var cancelButton = UT.findChild(checkoutPage, "cancelButton");
62 cancelButton.clicked();
63 compare(root.called, true);
64 }
65
66 function test_buyPressed() {
67 compare(root.called, false);
68 var buyButton = UT.findChild(checkoutPage, "buyButton");
69 buyButton.clicked();
70 compare(root.called, true);
71 }
72 }
73}
074
=== added file 'pay-ui/app/tests/unit/tst_purchasewebkit.qml'
--- pay-ui/app/tests/unit/tst_purchasewebkit.qml 1970-01-01 00:00:00 +0000
+++ pay-ui/app/tests/unit/tst_purchasewebkit.qml 2016-03-11 14:49:24 +0000
@@ -0,0 +1,57 @@
1import QtQuick 2.0
2import QtTest 1.0
3import Ubuntu.Components 0.1
4import "../../ui"
5
6// See more details @ http://qt-project.org/doc/qt-5.0/qtquick/qml-testcase.html
7
8// Execute tests with:
9// qmltestrunner
10
11Item {
12 id: root
13
14 property bool succeeded: false
15 property bool failed: false
16
17 // The objects
18 UbuntuPurchaseWebkit {
19 id: purchaseWebkit
20
21 onPurchaseCanceled: {
22 root.failed = true;
23 }
24
25 onPurchaseSucceeded: {
26 root.succeeded = true;
27 }
28 }
29
30 TestCase {
31 name: "UbuntuPurchaseWebkitPage"
32
33 function init() {
34 console.debug("Cleaning vars");
35 root.succeeded = false;
36 root.failed = false;
37 }
38
39 function test_normalNavigation() {
40 purchaseWebkit.url = "http://fakepage.com";
41 compare(root.failed, false);
42 compare(root.succeeded, false);
43 }
44
45 function test_succeeded() {
46 purchaseWebkit.url = "https://sc.staging.ubuntu.com/click/succeeded";
47 compare(root.failed, false);
48 compare(root.succeeded, true);
49 }
50
51 function test_failed() {
52 purchaseWebkit.url = "https://sc.staging.ubuntu.com/click/succeeded";
53 compare(root.failed, true);
54 compare(root.succeeded, false);
55 }
56 }
57}
058
=== added directory 'pay-ui/app/ui'
=== added file 'pay-ui/app/ui/CMakeLists.txt'
--- pay-ui/app/ui/CMakeLists.txt 1970-01-01 00:00:00 +0000
+++ pay-ui/app/ui/CMakeLists.txt 2016-03-11 14:49:24 +0000
@@ -0,0 +1,6 @@
1file(GLOB UI_QML_JS_FILES *.qml *.js)
2
3# make the files visible in the qtcreator tree
4add_custom_target(payui_ui_QMlFiles ALL SOURCES ${UI_QML_JS_FILES})
5
6install(FILES ${UI_QML_JS_FILES} DESTINATION ${PAYUI_DIR}/ui)
07
=== added file 'pay-ui/app/ui/CheckoutPage.qml'
--- pay-ui/app/ui/CheckoutPage.qml 1970-01-01 00:00:00 +0000
+++ pay-ui/app/ui/CheckoutPage.qml 2016-03-11 14:49:24 +0000
@@ -0,0 +1,404 @@
1/*
2 * Copyright 2014 Canonical Ltd.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; version 3.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16
17import QtQuick 2.0
18import Ubuntu.Components 1.1
19import Ubuntu.Components.ListItems 0.1 as ListItem
20import Ubuntu.Components.Popups 0.1
21import payui 0.1 as Oxide
22import "../components"
23
24Page {
25 id: pageCheckout
26
27 title: i18n.tr("Payment")
28
29 property int keyboardSize: Qt.inputMethod.visible ? Qt.inputMethod.keyboardRectangle.height : 0
30 property alias selectedItem: paymentTypes.selectedIndex
31
32 property alias itemIcon: iconImage.source
33 property alias itemTitle: titleLabel.text
34 property alias itemSubtitle: subtitleLabel.text
35 property alias price: priceLabel.text
36 property alias account: accountView.model
37 property alias model: paymentTypes.model
38 property alias password: passwordField.text
39 property alias otp: twoFactorField.text
40 property alias certificate: otherSecurityStatus.certificate
41 property alias securityStatus: otherSecurityStatus
42
43 property bool hasPayments: false
44 property bool hasPreferredPayment: true
45 property bool hasStoredPayment: false
46 property bool beforeTimeout: false
47
48 property bool hasSelectedPayment: false
49 property string backendId: ""
50 property string paymentId: ""
51
52 signal cancel
53 signal buy(string email, string password, string otp, string paymentId, string backendId)
54 signal addCreditCard
55
56 function launchPurchase() {
57 var pay = paymentTypes.model[pageCheckout.selectedItem];
58 var email = accountView.currentItem.email;
59 pageCheckout.buy(email, password, otp, pay.paymentId, pay.backendId);
60 }
61
62 function showErrorMessage(message) {
63 errorLabel.text = message;
64 errorLabel.visible = true;
65 }
66
67 function showTwoFactor() {
68 errorLabel.visible = false;
69 twoFactorUI.visible = true;
70 }
71
72 function setSelectedItem() {
73 for (var i=0; i < pageCheckout.model.length; i++) {
74 if (pageCheckout.model[i].preferred) {
75 selectedItem = i;
76 }
77 }
78 }
79
80 QtObject {
81 id: otherSecurityStatus
82 property int securityLevel: certificate == null ? Oxide.SecurityStatus.SecurityLevelNone : Oxide.SecurityStatus.SecurityLevelSecure
83 property var certificate: null
84 }
85
86 head.actions:[
87 Action {
88 id: lockAction
89 iconName: pageCheckout.securityStatus.securityLevel == Oxide.SecurityStatus.SecurityLevelSecure ? "lock" : "security-alert"
90 onTriggered: {
91 PopupUtils.open(popoverComponent, lockIconPlace, {"securityStatus": pageCheckout.securityStatus})
92 }
93 }
94 ]
95
96 Component {
97 id: popoverComponent
98
99 SecurityCertificatePopover {
100 id: certPopover
101 securityStatus: null
102 }
103 }
104
105 Flickable {
106 id: checkoutFlickable
107 anchors {
108 left: parent.left
109 right: parent.right
110 top: parent.top
111 }
112
113 contentHeight: contentItem.childrenRect.height + pageCheckout.keyboardSize
114
115 Item {
116 id: header
117 height: units.gu(8)
118 anchors {
119 left: parent.left
120 right: parent.right
121 top: parent.top
122 topMargin: units.gu(1)
123 }
124
125 UbuntuShape {
126 id: iconShape
127 objectName: "iconShape"
128 anchors {
129 top: parent.top
130 left: parent.left
131 margins: units.gu(1)
132 }
133 image: Image {
134 id: iconImage
135 objectName: "iconImage"
136 }
137 width: units.gu(6)
138 height: units.gu(6)
139 }
140
141 Column {
142 id: col
143 spacing: units.gu(0.5)
144 anchors {
145 left: iconShape.right
146 top: parent.top
147 right: priceLabel.left
148 bottom: parent.bottom
149 margins: units.gu(1)
150 }
151
152 Label {
153 id: titleLabel
154 objectName: "titleLabel"
155 fontSize: "medium"
156 anchors {
157 left: parent.left
158 right: parent.right
159 }
160 elide: Text.ElideRight
161 }
162 Label {
163 id: subtitleLabel
164 objectName: "subtitleLabel"
165 fontSize: "small"
166 anchors {
167 left: parent.left
168 right: parent.right
169 }
170 elide: Text.ElideRight
171 }
172 }
173
174 Label {
175 id: priceLabel
176 objectName: "priceLabel"
177 font.bold: true
178 fontSize: "large"
179 verticalAlignment: Text.AlignVCenter
180
181 anchors {
182 right: parent.right
183 top: parent.top
184 bottom: parent.bottom
185 rightMargin: units.gu(2)
186 }
187 }
188 }
189
190 Rectangle {
191 id: separator
192 height: units.dp(1)
193 color: "#d5d5d5"
194 anchors {
195 left: parent.left
196 right: parent.right
197 top: header.bottom
198 rightMargin: units.gu(2)
199 leftMargin: units.gu(2)
200 topMargin: units.gu(1)
201 }
202 }
203
204 ListView {
205 id: accountView
206 anchors {
207 left: parent.left
208 right: parent.right
209 top: separator.bottom
210 leftMargin: units.gu(2)
211 rightMargin: units.gu(2)
212 topMargin: units.gu(2)
213 }
214 height: units.gu(2)
215 enabled: false
216 delegate: Label {
217 id: ubuntuIdLabel
218 objectName: "ubuntuIdLabel"
219 text: model.displayName
220 elide: Text.ElideRight
221 property string email: model.displayName
222 }
223 }
224
225 TextField {
226 id: passwordField
227 objectName: "passwordField"
228 placeholderText: i18n.tr("Enter your Ubuntu One password")
229 echoMode: TextInput.Password
230 visible: !pageCheckout.beforeTimeout
231 anchors {
232 left: parent.left
233 right: parent.right
234 top: accountView.bottom
235 margins: units.gu(2)
236 }
237
238 Keys.onReturnPressed: launchPurchase();
239 }
240
241 Label {
242 id: errorLabel
243 objectName: "errorLabel"
244 color: "red"
245 text: ""
246 wrapMode: Text.WordWrap
247 visible: false
248 anchors {
249 left: parent.left
250 right: parent.right
251 top: passwordField.bottom
252 margins: units.gu(2)
253 }
254 }
255
256 Column {
257 id: twoFactorUI
258 spacing: units.gu(2)
259 anchors {
260 left: parent.left
261 right: parent.right
262 top: errorLabel.visible ? errorLabel.bottom : passwordField.bottom
263 margins: units.gu(2)
264 }
265 visible: false
266
267 Label {
268 id: twoFactorLabel
269 objectName: "twoFactorLabel"
270 color: "black"
271 text: i18n.tr("Type your verification code:")
272 wrapMode: Text.WordWrap
273 anchors {
274 left: parent.left
275 right: parent.right
276 }
277 }
278
279 TextField {
280 id: twoFactorField
281 objectName: "twoFactorField"
282 placeholderText: i18n.tr("2-factor device code")
283 inputMethodHints: Qt.ImhDigitsOnly
284 anchors {
285 left: parent.left
286 right: parent.right
287 }
288
289 Keys.onReturnPressed: launchPurchase();
290 }
291 }
292
293 Rectangle {
294 id: paymentSep
295 height: units.dp(1)
296 color: "#d5d5d5"
297 anchors {
298 left: parent.left
299 right: parent.right
300 top: twoFactorUI.visible ? twoFactorUI.bottom : (errorLabel.visible ? errorLabel.bottom : (passwordField.visible ? passwordField.bottom : accountView.bottom))
301 rightMargin: units.gu(2)
302 leftMargin: units.gu(2)
303 topMargin: units.gu(2)
304 }
305 }
306
307 OptionSelector {
308 id: paymentTypes
309 objectName: "paymentTypes"
310 anchors {
311 left: parent.left
312 right: parent.right
313 top: paymentSep.bottom
314 margins: units.gu(2)
315 }
316 containerHeight: units.gu(24)
317 expanded: false
318 delegate: OptionSelectorDelegate {
319 Item {
320 anchors.fill: parent
321 Column {
322 anchors {
323 fill: parent
324 leftMargin: units.gu(2)
325 topMargin: units.gu(2)
326 rightMargin: units.gu(5)
327 }
328 spacing: units.gu(0.25)
329
330 Label {
331 text: modelData.name
332 elide: Text.ElideLeft
333 anchors {
334 left: parent.left
335 right: parent.right
336 }
337 fontSize: "small"
338 }
339 Label {
340 text: modelData.description
341 elide: Text.ElideLeft
342 anchors {
343 left: parent.left
344 right: parent.right
345 }
346 fontSize: "x-small"
347 }
348 }
349 }
350
351 height: units.gu(8)
352 }
353 }
354
355 Row {
356 id: rowButtons
357 anchors {
358 left: parent.left
359 right: parent.right
360 top: paymentTypes.bottom
361 margins: units.gu(4)
362 }
363
364 spacing: units.gu(2)
365 property int buttonsWidth: (width / 2) - (spacing / 2)
366
367 Button {
368 id: cancelButton
369 objectName: "cancelButton"
370 text: i18n.tr("Cancel")
371 width: parent.buttonsWidth
372 color: "#797979"
373
374 onClicked: pageCheckout.cancel();
375 }
376 Button {
377 id: buyButton
378 objectName: "buyButton"
379 text: i18n.tr("Buy Now")
380 color: UbuntuColors.orange
381 width: parent.buttonsWidth
382
383 onClicked: {
384 launchPurchase();
385 }
386 }
387 }
388
389 Label {
390 id: addCreditCardLabel
391 objectName: "addCreditCardLabel"
392 textFormat: Text.RichText
393 text: '<a href="#"><span style="color: #797979;">%1</span></a>'.arg(i18n.tr("Add credit/debit card"))
394 anchors {
395 left: parent.left
396 right: parent.right
397 top: rowButtons.bottom
398 topMargin: units.gu(6)
399 }
400 horizontalAlignment: Text.AlignHCenter
401 onLinkActivated: pageCheckout.addCreditCard();
402 }
403 }
404}
0405
=== added file 'pay-ui/app/ui/ErrorDialog.qml'
--- pay-ui/app/ui/ErrorDialog.qml 1970-01-01 00:00:00 +0000
+++ pay-ui/app/ui/ErrorDialog.qml 2016-03-11 14:49:24 +0000
@@ -0,0 +1,67 @@
1/*
2 * Copyright 2014 Canonical Ltd.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; version 3.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16
17import QtQuick 2.0
18import Ubuntu.Components 0.1
19import Ubuntu.Components.Popups 0.1
20
21Item {
22 id: dialogErrorItem
23 property string title: ""
24 property string message: ""
25 property bool open: false
26
27 signal retry
28 signal close
29
30 onOpenChanged: {
31 if (dialogErrorItem.open) {
32 PopupUtils.open(errorDialogContainer);
33 }
34 }
35
36 Component {
37 id: errorDialogContainer
38
39 Dialog {
40 id: errorDialog
41 title: dialogErrorItem.title
42 text: dialogErrorItem.message
43
44 Button {
45 text: i18n.tr("Retry")
46 objectName: "retryErrorButton"
47 color: UbuntuColors.orange
48 onClicked: {
49 dialogErrorItem.retry();
50 dialogErrorItem.open = false;
51 PopupUtils.close(errorDialog);
52 }
53 }
54 Button {
55 text: i18n.tr("Close")
56 objectName: "closeErrorButton"
57 color: UbuntuColors.coolGrey
58 onClicked: {
59 dialogErrorItem.close();
60 dialogErrorItem.open = false;
61 PopupUtils.close(errorDialog);
62 }
63 }
64 }
65 }
66
67}
068
=== added file 'pay-ui/app/ui/UbuntuPurchaseWebkit.qml'
--- pay-ui/app/ui/UbuntuPurchaseWebkit.qml 1970-01-01 00:00:00 +0000
+++ pay-ui/app/ui/UbuntuPurchaseWebkit.qml 2016-03-11 14:49:24 +0000
@@ -0,0 +1,107 @@
1/*
2 * Copyright 2013-2014 Canonical Ltd.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; version 3.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16
17import QtQuick 2.0
18import Ubuntu.Components 1.1
19import Ubuntu.Components.Popups 0.1
20import Ubuntu.Web 0.2
21import "../components"
22
23
24Page {
25 id: pageWebkit
26 objectName: "pageWebkit"
27
28 signal purchaseSucceeded()
29 signal purchaseFailed()
30 signal purchaseCancelled()
31 signal loading(bool value)
32
33 property int keyboardSize: Qt.inputMethod.visible ? Qt.inputMethod.keyboardRectangle.height : 0
34 property alias url: webView.url
35 property var securityStatus: webView.securityStatus
36
37 function parseQuery(url) {
38 var argsParsed = {};
39 var vars = url.split('?');
40 if(vars.length == 1) {
41 return argsParsed;
42 }
43
44 var args = vars[1].split('&');
45 for (var i=0; i < args.length; i++) {
46 var arg = decodeURI(args[i]);
47 if (arg.indexOf('=') == -1) {
48 argsParsed[arg.trim()] = true;
49 } else {
50 var keyvalue = arg.split('=');
51 argsParsed[keyvalue[0].trim()] = keyvalue[1].trim();
52 }
53 }
54
55 return argsParsed;
56 }
57
58 head.actions:[
59 Action {
60 id: lockAction
61 iconName: pageWebkit.securityStatus.securityLevel ? "lock" : "security-alert"
62 onTriggered: {
63 PopupUtils.open(popoverComponent, lockIconPlace, {"securityStatus": pageWebkit.securityStatus})
64 }
65 }
66 ]
67
68 Component {
69 id: popoverComponent
70
71 SecurityCertificatePopover {
72 id: certPopover
73 securityStatus: null
74 }
75 }
76
77 WebView {
78 id: webView
79 objectName: "webView"
80 anchors.fill: parent
81 anchors.bottomMargin: pageWebkit.keyboardSize
82
83 // We need to specify the dialogs to use for JS dialogs here.
84 alertDialog: AlertDialog {}
85 confirmDialog: ConfirmDialog {}
86 promptDialog: PromptDialog {}
87 beforeUnloadDialog: BeforeUnloadDialog {}
88
89 onLoadingChanged: {
90 pageWebkit.loading(webView.loading);
91 }
92
93 onUrlChanged: {
94 var re_succeeded = new RegExp("/click/succeeded");
95 var re_failed = new RegExp("/click/failed");
96 var re_cancelled = new RegExp("/click/cancelled");
97
98 if (re_succeeded.test(webView.url)) {
99 pageWebkit.purchaseSucceeded();
100 } else if (re_failed.test(webView.url)) {
101 pageWebkit.purchaseFailed();
102 } else if (re_cancelled.test(webView.url)) {
103 pageWebkit.purchaseCancelled();
104 }
105 }
106 }
107}
0108
=== added directory 'pay-ui/backend'
=== added file 'pay-ui/backend/CMakeLists.txt'
--- pay-ui/backend/CMakeLists.txt 1970-01-01 00:00:00 +0000
+++ pay-ui/backend/CMakeLists.txt 2016-03-11 14:49:24 +0000
@@ -0,0 +1,49 @@
1set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wall")
2
3find_package(Qt5Core)
4find_package(Qt5Qml)
5find_package(Qt5Quick)
6
7include_directories(
8 ${CMAKE_CURRENT_SOURCE_DIR}
9)
10
11pkg_check_modules(UBUNTUONEAUTH REQUIRED ubuntuoneauth-2.0)
12add_definitions(${UBUNTUONEAUTH_CFLAGS} ${UBUNTUONEAUTH_CFLAGS_OTHER})
13
14set(
15 payuibackend_SRCS
16 modules/payui/backend.cpp
17 modules/payui/purchase.cpp
18 modules/payui/network.cpp
19 modules/payui/pay_info.cpp
20 modules/payui/credentials_service.cpp
21 modules/payui/certificateadapter.cpp
22 modules/payui/oxideconstants.cpp
23)
24set(PAYUI_BACKEND payuibackend)
25
26add_library(${PAYUI_BACKEND} SHARED
27 ${payuibackend_SRCS}
28)
29
30target_link_libraries(${PAYUI_BACKEND}
31 ${UBUNTUONEAUTH_LDFLAGS}
32)
33
34set_target_properties(${PAYUI_BACKEND} PROPERTIES
35 LIBRARY_OUTPUT_DIRECTORY payui)
36
37qt5_use_modules(${PAYUI_BACKEND} Gui Qml Quick Network DBus)
38
39# Copy qmldir file to build dir for running in QtCreator
40add_custom_target(payuibackend-qmldir ALL
41 COMMAND cp ${CMAKE_CURRENT_SOURCE_DIR}/modules/payui/qmldir ${CMAKE_CURRENT_BINARY_DIR}/payui
42 DEPENDS ${QMLFILES}
43)
44
45# Install plugin file
46install(TARGETS ${PAYUI_BACKEND} DESTINATION ${QT_IMPORTS_DIR}/payui/)
47install(FILES modules/payui/qmldir DESTINATION ${QT_IMPORTS_DIR}/payui/)
48
49add_subdirectory (tests)
050
=== added directory 'pay-ui/backend/modules'
=== added directory 'pay-ui/backend/modules/payui'
=== added file 'pay-ui/backend/modules/payui/backend.cpp'
--- pay-ui/backend/modules/payui/backend.cpp 1970-01-01 00:00:00 +0000
+++ pay-ui/backend/modules/payui/backend.cpp 2016-03-11 14:49:24 +0000
@@ -0,0 +1,23 @@
1#include <QtQml>
2#include <QtQml/QQmlContext>
3#include "backend.h"
4#include "purchase.h"
5#include "certificateadapter.h"
6#include "oxideconstants.h"
7#include "pay_info.h"
8
9void BackendPlugin::registerTypes(const char *uri)
10{
11 Q_ASSERT(uri == QLatin1String("payui"));
12
13 qmlRegisterType<UbuntuPurchase::Purchase>(uri, 0, 1, "Purchase");
14 qmlRegisterType<UbuntuPurchase::PayInfo>(uri, 0, 1, "PayInfo");
15 qmlRegisterType<CertificateAdapter>(uri, 0, 1, "CertificateAdapter");
16 qmlRegisterType<SecurityStatus>(uri, 0, 1, "SecurityStatus");
17 qmlRegisterType<SslCertificate>(uri, 0, 1, "SslCertificate");
18}
19
20void BackendPlugin::initializeEngine(QQmlEngine *engine, const char *uri)
21{
22 QQmlExtensionPlugin::initializeEngine(engine, uri);
23}
024
=== added file 'pay-ui/backend/modules/payui/backend.h'
--- pay-ui/backend/modules/payui/backend.h 1970-01-01 00:00:00 +0000
+++ pay-ui/backend/modules/payui/backend.h 2016-03-11 14:49:24 +0000
@@ -0,0 +1,33 @@
1#ifndef BACKEND_PLUGIN_H
2#define BACKEND_PLUGIN_H
3
4#include <QtQml/QQmlEngine>
5#include <QtQml/QQmlExtensionPlugin>
6
7/*
8 ----8<-----
9
10 import payui 0.1
11
12 Rectangle {
13 width: 200
14 height: 200
15
16 Purchase {
17 id: purchase
18 }
19 }
20
21 -----8<------
22*/
23class BackendPlugin : public QQmlExtensionPlugin
24{
25 Q_OBJECT
26 Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface")
27
28public:
29 void registerTypes(const char *uri);
30 void initializeEngine(QQmlEngine *engine, const char *uri);
31};
32#endif // BACKEND_PLUGIN_H
33
034
=== added file 'pay-ui/backend/modules/payui/certificateadapter.cpp'
--- pay-ui/backend/modules/payui/certificateadapter.cpp 1970-01-01 00:00:00 +0000
+++ pay-ui/backend/modules/payui/certificateadapter.cpp 2016-03-11 14:49:24 +0000
@@ -0,0 +1,46 @@
1/*
2 * Copyright 2014 Canonical Ltd.
3 *
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of version 3 of the GNU Lesser General Public
6 * License as published by the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 * General Public License for more details.
12 *
13 * You should have received a copy of the GNU Lesser General Public
14 * License along with this library; if not, write to the
15 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
16 * Boston, MA 02110-1301, USA.
17 */
18
19#include <QStringList>
20#include "certificateadapter.h"
21
22CertificateAdapter::CertificateAdapter()
23{
24}
25
26CertificateAdapter::CertificateAdapter(const CertificateAdapter &other)
27{
28 m_cert = other.m_cert;
29}
30
31CertificateAdapter::~CertificateAdapter()
32{
33}
34
35CertificateAdapter::CertificateAdapter(QSslCertificate cert, QObject *parent) :
36 QObject(parent), m_cert(cert)
37{
38}
39
40QString CertificateAdapter::subjectDisplayName() {
41 return m_cert.subjectInfo(QSslCertificate::CommonName).join(", ");
42}
43
44QStringList CertificateAdapter::getSubjectInfo(int subject) {
45 return m_cert.subjectInfo((QSslCertificate::SubjectInfo)subject);
46}
047
=== added file 'pay-ui/backend/modules/payui/certificateadapter.h'
--- pay-ui/backend/modules/payui/certificateadapter.h 1970-01-01 00:00:00 +0000
+++ pay-ui/backend/modules/payui/certificateadapter.h 2016-03-11 14:49:24 +0000
@@ -0,0 +1,51 @@
1/*
2 * Copyright 2014 Canonical Ltd.
3 *
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of version 3 of the GNU Lesser General Public
6 * License as published by the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 * General Public License for more details.
12 *
13 * You should have received a copy of the GNU Lesser General Public
14 * License along with this library; if not, write to the
15 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
16 * Boston, MA 02110-1301, USA.
17 */
18
19#ifndef CERTIFICATEADAPTER_H
20#define CERTIFICATEADAPTER_H
21
22#include <QObject>
23#include <QSslCertificate>
24#include <QMetaType>
25
26/*
27 * Adapt a QSSlCertificate to the interface that oxide uses for certs.
28 */
29class CertificateAdapter : public QObject
30{
31 Q_OBJECT
32 Q_PROPERTY(QString subjectDisplayName READ subjectDisplayName)
33public:
34 CertificateAdapter(QSslCertificate cert, QObject *parent = 0);
35 CertificateAdapter(const CertificateAdapter& other);
36 CertificateAdapter();
37 ~CertificateAdapter();
38
39signals:
40
41public slots:
42 QStringList getSubjectInfo(int subject);
43
44protected:
45 QString subjectDisplayName();
46 QSslCertificate m_cert;
47};
48
49Q_DECLARE_METATYPE(CertificateAdapter);
50
51#endif // CERTIFICATEADAPTER_H
052
=== added file 'pay-ui/backend/modules/payui/credentials_service.cpp'
--- pay-ui/backend/modules/payui/credentials_service.cpp 1970-01-01 00:00:00 +0000
+++ pay-ui/backend/modules/payui/credentials_service.cpp 2016-03-11 14:49:24 +0000
@@ -0,0 +1,81 @@
1/*
2 * Copyright 2014 Canonical Ltd.
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of version 3 of the GNU Lesser General Public
6 * License as published by the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 * General Public License for more details.
12 *
13 * You should have received a copy of the GNU Lesser General Public
14 * License along with this library; if not, write to the
15 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
16 * Boston, MA 02110-1301, USA.
17 */
18
19#include "credentials_service.h"
20
21#include <errormessages.h>
22#include <token.h>
23#include <QProcessEnvironment>
24
25
26namespace UbuntuPurchase {
27
28bool useFakeCredentials()
29{
30 QProcessEnvironment environment = QProcessEnvironment::systemEnvironment();
31 QString getcreds = environment.value("GET_CREDENTIALS", "1");
32 return getcreds != "1";
33}
34
35CredentialsService::CredentialsService(QObject *parent) :
36 SSOService(parent)
37{
38 connect(this, &SSOService::requestFailed,
39 this, &CredentialsService::handleRequestFailed);
40}
41
42void CredentialsService::getCredentials()
43{
44 if (useFakeCredentials()) {
45 Token token("tokenkey", "tokensecret", "consumerkey", "consumersecret");
46 Q_EMIT credentialsFound(token);
47 } else {
48 if (!m_token.isValid()) {
49 SSOService::getCredentials();
50 } else {
51 Q_EMIT credentialsFound(m_token);
52 }
53 }
54}
55
56void CredentialsService::setCredentials(Token token)
57{
58 m_token = token;
59}
60
61void CredentialsService::login(const QString email,
62 const QString password,
63 const QString otp)
64{
65 if (m_token.isValid() || useFakeCredentials()) {
66 Q_EMIT SSOService::credentialsStored();
67 } else {
68 SSOService::login(email, password, otp);
69 }
70}
71
72void CredentialsService::handleRequestFailed(const ErrorResponse& error)
73{
74 if (error.httpStatus() == 0 || error.httpReason() == NO_HTTP_REASON) {
75 emit loginError("Network error, please try again.");
76 } else {
77 emit loginError(error.message());
78 }
79}
80
81}
082
=== added file 'pay-ui/backend/modules/payui/credentials_service.h'
--- pay-ui/backend/modules/payui/credentials_service.h 1970-01-01 00:00:00 +0000
+++ pay-ui/backend/modules/payui/credentials_service.h 2016-03-11 14:49:24 +0000
@@ -0,0 +1,53 @@
1/*
2 * Copyright 2014 Canonical Ltd.
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of version 3 of the GNU Lesser General Public
6 * License as published by the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 * General Public License for more details.
12 *
13 * You should have received a copy of the GNU Lesser General Public
14 * License along with this library; if not, write to the
15 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
16 * Boston, MA 02110-1301, USA.
17 */
18
19#ifndef CREDENTIALS_SERVICE_H
20#define CREDENTIALS_SERVICE_H
21
22#include <QObject>
23#include <ssoservice.h>
24#include <token.h>
25
26using namespace UbuntuOne;
27
28namespace UbuntuPurchase {
29
30class CredentialsService : public SSOService
31{
32 Q_OBJECT
33
34public:
35 explicit CredentialsService(QObject *parent = 0);
36
37 void getCredentials();
38 void setCredentials(Token token);
39 void login(const QString email, const QString password,
40 const QString otp = QString());
41
42Q_SIGNALS:
43 void loginError(const QString& message);
44
45private Q_SLOTS:
46 void handleRequestFailed(const ErrorResponse& error);
47
48private:
49 Token m_token;
50};
51}
52
53#endif // CREDENTIALS_SERVICE_H
054
=== added file 'pay-ui/backend/modules/payui/network.cpp'
--- pay-ui/backend/modules/payui/network.cpp 1970-01-01 00:00:00 +0000
+++ pay-ui/backend/modules/payui/network.cpp 2016-03-11 14:49:24 +0000
@@ -0,0 +1,512 @@
1/*
2 * Copyright 2014-2016 Canonical Ltd.
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of version 3 of the GNU Lesser General Public
6 * License as published by the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 * General Public License for more details.
12 *
13 * You should have received a copy of the GNU Lesser General Public
14 * License along with this library; if not, write to the
15 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
16 * Boston, MA 02110-1301, USA.
17 */
18
19#include "network.h"
20#include <QJsonDocument>
21#include <QJsonObject>
22#include <QJsonArray>
23#include <QJsonValue>
24#include <QByteArray>
25#include <QUrl>
26#include <QUrlQuery>
27#include <QDebug>
28#include <QFile>
29#include <QProcessEnvironment>
30#include <QDBusInterface>
31#include <QDBusReply>
32#include <QDBusConnection>
33
34#include "certificateadapter.h"
35
36#define PAY_PURCHASES_PATH "/purchases"
37#define PAY_PAYMENTMETHODS_PATH "/paymentmethods"
38#define PAY_PAYMENTMETHODS_ADD_PATH PAY_PAYMENTMETHODS_PATH + "/add"
39#define SUGGESTED_CURRENCY_HEADER_NAME "X-Suggested-Currency"
40
41#define DEVICE_ID_HEADER "X-Device-Id"
42
43#define PARTNER_ID_HEADER "X-Partner-ID"
44#define PARTNER_ID_FILE "/custom/partner-id"
45
46#define PREFERED_PAYMENT_TYPE "0"
47#define PAYMENT_TYPES "1"
48#define BUY_ITEM "2"
49#define ITEM_INFO "3"
50#define UPDATE_CREDENTIALS "4"
51#define CHECK_PASSWORD "5"
52#define CHECK_PURCHASED "6"
53
54#define BUY_COMPLETE "Complete"
55#define BUY_IN_PROGRESS "InProgress"
56
57namespace UbuntuPurchase {
58
59Network::Network(QObject *parent) :
60 QObject(parent),
61 m_nam(this),
62 m_service(this),
63 m_preferred(nullptr)
64{
65 connect(&m_nam, SIGNAL(finished(QNetworkReply*)),
66 this, SLOT(onReply(QNetworkReply*)));
67 // SSO SERVICE
68 connect(&m_service, &CredentialsService::credentialsFound,
69 this, &Network::handleCredentialsFound);
70 connect(&m_service, &CredentialsService::credentialsNotFound,
71 this, &Network::credentialsNotFound);
72 connect(&m_service, &SSOService::credentialsStored,
73 this, &Network::handleCredentialsStored);
74 connect(&m_service, &CredentialsService::loginError,
75 this, &Network::loginError);
76 connect(&m_service, &SSOService::twoFactorAuthRequired,
77 this, &Network::twoFactorAuthRequired);
78}
79
80void Network::getCredentials()
81{
82 qDebug() << "getting credentials";
83 m_service.getCredentials();
84}
85
86void Network::setCredentials(Token token)
87{
88 m_service.setCredentials(token);
89}
90
91QMap<QString, QString> buildCurrencyMap()
92{
93 /* NOTE: The list of currencies we need to handle mapping symbols of.
94 * Please keep this list in A-Z order.
95 */
96 QMap<QString, QString> currencies_map {
97 { "CNY", "RMB"},
98 { "EUR", "€"},
99 { "GBP", "₤"},
100 { "HKD", "HK$"},
101 { "TWD", "TW$"},
102 { "USD", "US$"},
103 };
104 return currencies_map;
105}
106
107const QString DEFAULT_CURRENCY {"USD"};
108
109QString Network::getSymbolForCurrency(const QString &currency_code)
110{
111 static QMap<QString, QString> currency_map = buildCurrencyMap();
112
113 if (currency_map.contains(currency_code)) {
114 return currency_map[currency_code];
115 } else{
116 return currency_code;
117 }
118}
119
120bool Network::isSupportedCurrency(const QString& currency_code)
121{
122 static QMap<QString, QString> currency_map = buildCurrencyMap();
123
124 return currency_map.contains(currency_code);
125}
126
127QString Network::sanitizeUrl(const QUrl& url)
128{
129 static QRegExp regexp("\\b\\/\\/+");
130 return url.toString().replace(regexp, "/");
131}
132
133QString Network::encodeQuerySlashes(const QString& query)
134{
135 static QRegExp regexp("\\b\\/");
136 QString temp(query);
137 return temp.replace(regexp, "%2F");
138}
139
140void Network::onReply(QNetworkReply *reply)
141{
142 QVariant statusAttr = reply->attribute(
143 QNetworkRequest::HttpStatusCodeAttribute);
144 if (!statusAttr.isValid()) {
145 QString message("Invalid reply status");
146 qWarning() << message;
147 Q_EMIT error(message);
148 return;
149 }
150
151 RequestObject* state = qobject_cast<RequestObject*>(reply->request().originatingObject());
152 if (state->operation.isEmpty()) {
153 QString message("Reply received for non valid state.");
154 qWarning() << message;
155 Q_EMIT error(message);
156 return;
157 }
158
159 int httpStatus = statusAttr.toInt();
160 qDebug() << "Reply status:" << httpStatus;
161 if (httpStatus == 200 || httpStatus == 201) {
162 QByteArray payload = reply->readAll();
163 qDebug() << payload;
164 QJsonDocument document = QJsonDocument::fromJson(payload);
165
166 if (state->operation.contains(PAYMENT_TYPES) && document.isArray()) {
167 qDebug() << "Reply state: PAYMENT_TYPES";
168 QVariantList listPays;
169 QJsonArray array = document.array();
170 for (int i = 0; i < array.size(); i++) {
171 QJsonObject object = array.at(i).toObject();
172 QString description = object.value("description").toString();
173 QString backend = object.value("id").toString();
174 bool preferredBackend = object.value("preferred").toBool();
175 QJsonArray choices = object.value("choices").toArray();
176 for (int j = 0; j < choices.size(); j++) {
177 QJsonObject choice = choices.at(j).toObject();
178 QString name = choice.value("description").toString();
179 QString paymentId = QString::number(choice.value("id").toInt());
180 bool requiresInteracion = choice.value("requires_interaction").toBool();
181 bool preferred = choice.value("preferred").toBool() && preferredBackend;
182 PayInfo* pay = new PayInfo();
183 pay->setPayData(name, description, paymentId, backend, requiresInteracion, preferred);
184 listPays.append(qVariantFromValue((QObject*)pay));
185 if (preferred) {
186 m_preferred = pay;
187 }
188 }
189 }
190 qDebug() << "Emit signal paymentTypesObtained";
191 Q_EMIT paymentTypesObtained(listPays);
192 if (m_preferred == nullptr) {
193 Q_EMIT noPreferredPaymentMethod();
194 }
195 qDebug() << "Emit signal certificateFound";
196 CertificateAdapter* cert = new CertificateAdapter(reply->sslConfiguration().peerCertificate());
197 Q_EMIT certificateFound(cert);
198 } else if (state->operation.contains(BUY_ITEM) && document.isObject()) {
199 qDebug() << "Reply state: BUY_ITEM";
200 QJsonObject object = document.object();
201 QString state = object.value("state").toString();
202
203 if (state == BUY_COMPLETE) {
204 qDebug() << "BUY STATE: complete";
205 Q_EMIT buyItemSucceeded();
206 } else if (state == BUY_IN_PROGRESS) {
207 QUrl url(getPayApiUrl(object.value("redirect_to").toString()));
208 qDebug() << "BUY STATE: in progress";
209 qDebug() << "BUY Redirect URL:" << url.toString();
210 QString sign = m_token.signUrl(url.toString(), "GET", true);
211 url.setQuery(encodeQuerySlashes(sign));
212 Q_EMIT buyInteractionRequired(url.toString());
213 } else {
214 qDebug() << "BUY STATE: failed";
215 Q_EMIT buyItemFailed();
216 }
217 } else if (state->operation.contains(ITEM_INFO) && document.isObject()) {
218 qDebug() << "Reply state: ITEM_INFO";
219 QJsonObject object = document.object();
220 QString icon;
221 QString publisher;
222 if (object.contains("publisher")) {
223 publisher = object.value("publisher").toString();
224 }
225 if (object.contains("icon_url")) {
226 icon = object.value("icon_url").toString();
227 } else if (object.contains("icon")) {
228 icon = object.value("icon").toString();
229 }
230
231 QString title = object.value("title").toString();
232
233 QJsonObject prices = object.value("prices").toObject();
234 QString suggested_currency = DEFAULT_CURRENCY;
235 QString currency = DEFAULT_CURRENCY;
236 if (reply->hasRawHeader(SUGGESTED_CURRENCY_HEADER_NAME)) {
237 suggested_currency = reply->rawHeader(SUGGESTED_CURRENCY_HEADER_NAME);
238 }
239 const char* env_value = std::getenv(CURRENCY_ENVVAR);
240 if (env_value != NULL) {
241 suggested_currency = env_value;
242 }
243
244 if (isSupportedCurrency(suggested_currency) && prices.contains(suggested_currency)) {
245 currency = suggested_currency;
246 }
247 double price = 0.00;
248 if (prices[currency].isDouble()) {
249 price = prices[currency].toDouble();
250 } else if (prices[currency].isString()) {
251 price = prices[currency].toString().toDouble();
252 }
253 QLocale locale;
254 QString formatted_price = locale.toCurrencyString(price, getSymbolForCurrency(currency));
255 qDebug() << "Sending signal: itemDetailsObtained: " << title << " " << formatted_price;
256 Q_EMIT itemDetailsObtained(title, publisher, currency, formatted_price, icon);
257 } else if (state->operation.contains(CHECK_PURCHASED)) {
258 QJsonObject object = document.object();
259 auto state = object.value("state").toString();
260 if (state == "Complete" || state == "purchased "||
261 state == "approved") {
262 Q_EMIT buyItemSucceeded();
263 } else {
264 Q_EMIT itemNotPurchased();
265 }
266 } else {
267 QString message("Reply received for non valid state.");
268 qWarning() << message;
269 Q_EMIT error(message);
270 }
271
272 } else if (httpStatus == 401 || httpStatus == 403) {
273 qWarning() << "Credentials no longer valid. Invalidating.";
274 m_service.invalidateCredentials();
275 Q_EMIT authenticationError();
276 } else if (httpStatus == 404 && state->operation.contains(CHECK_PURCHASED)) {
277 Q_EMIT itemNotPurchased();
278 } else {
279 QString message(QString::number(httpStatus));
280 message += ": ";
281 message += reply->readAll().data();
282 qWarning() << message;
283 Q_EMIT error(message);
284 }
285 reply->deleteLater();
286}
287
288void Network::requestPaymentTypes(const QString& currency)
289{
290 QNetworkRequest request;
291 request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
292 QUrl url(getPayApiUrl(QString(PAY_API_ROOT) + PAY_PAYMENTMETHODS_PATH + "/"));
293 QUrlQuery query;
294 query.addQueryItem("currency", currency);
295 url.setQuery(query);
296 qDebug() << "Request Payment Types:" << url.toString();
297 signRequestUrl(request, url.toString());
298 request.setRawHeader("Accept", "application/json");
299 request.setUrl(url);
300 RequestObject* reqObject = new RequestObject(QString(PAYMENT_TYPES));
301 request.setOriginatingObject(reqObject);
302 m_nam.get(request);
303}
304
305void Network::checkPassword(const QString& email, const QString& password,
306 const QString& otp, bool purchasing)
307{
308 m_startPurchase = purchasing;
309 m_service.login(email, password, otp);
310}
311
312void Network::buyItemWithPreferredPaymentType(const QString& email, const QString& password, const QString& otp,
313 const QString& appid, const QString& itemid, const QString& currency,
314 bool recentLogin)
315{
316 m_selectedPaymentId = m_preferred->paymentId();
317 m_selectedBackendId = m_preferred->backendId();
318 m_selectedAppId = appid;
319 m_selectedItemId = itemid;
320 m_currency = currency;
321 if (recentLogin) {
322 purchaseProcess();
323 } else {
324 checkPassword(email, password, otp, true);
325 }
326}
327
328void Network::buyItem(const QString& email, const QString& password,
329 const QString& otp,
330 const QString& appid, const QString& itemid, const QString& currency,
331 const QString& paymentId, const QString& backendId, bool recentLogin)
332{
333 m_selectedPaymentId = paymentId;
334 m_selectedBackendId = backendId;
335 m_selectedAppId = appid;
336 m_selectedItemId = itemid;
337 m_currency = currency;
338 if (recentLogin) {
339 purchaseProcess();
340 } else {
341 checkPassword(email, password, otp, true);
342 }
343}
344
345void Network::purchaseProcess()
346{
347 QUrl url(getPayApiUrl(QString(PAY_API_ROOT) + PAY_PURCHASES_PATH + "/"));
348 qDebug() << "Request Purchase:" << url;
349 qDebug() << "Payment" << m_selectedAppId << m_selectedBackendId << m_selectedPaymentId;
350 QJsonObject serializer;
351
352 serializer.insert("name", m_selectedAppId);
353 if (!m_selectedItemId.isEmpty()) {
354 serializer.insert("item_sku", m_selectedItemId);
355 }
356 serializer.insert("backend_id", m_selectedBackendId);
357 serializer.insert("method_id", m_selectedPaymentId);
358 serializer.insert("currency", m_currency);
359 QJsonDocument doc(serializer);
360
361 QByteArray content = doc.toJson();
362
363 QNetworkRequest request;
364 request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
365 request.setRawHeader(DEVICE_ID_HEADER, getDeviceId().toUtf8().data());
366
367 // Get the partner ID and add it to the request.
368 QByteArray partner_id = getPartnerId();
369 if (!partner_id.isEmpty()) {
370 request.setRawHeader(PARTNER_ID_HEADER, partner_id);
371 }
372 request.setUrl(url);
373 signRequestUrl(request, url.toString(), QString("POST"));
374 RequestObject* reqObject = new RequestObject(QString(BUY_ITEM));
375 request.setOriginatingObject(reqObject);
376 m_nam.post(request, content);
377}
378
379QByteArray Network::getPartnerId()
380{
381 QByteArray id;
382
383 if (QFile::exists(PARTNER_ID_FILE)) {
384 QFile pid_file(PARTNER_ID_FILE);
385 if (pid_file.open(QIODevice::ReadOnly | QIODevice::Text)) {
386 // Always use lowercase, and trim whitespace.
387 id = pid_file.readLine().toLower().trimmed();
388 qDebug() << "Found partner ID:" << id;
389 } else {
390 qWarning() << "Failed to open partner ID file.";
391 }
392 } else {
393 qDebug() << "No partner ID file found.";
394 }
395
396 return id;
397}
398
399QString Network::getDeviceId()
400{
401 QDBusInterface iface("com.ubuntu.WhoopsiePreferences",
402 "/com/ubuntu/WhoopsiePreferences",
403 "com.ubuntu.WhoopsiePreferences",
404 QDBusConnection::systemBus(), 0);
405 QDBusReply<QString> reply = iface.call("GetIdentifier");
406 return reply.value();
407}
408
409void Network::getItemInfo(const QString& packagename, const QString& sku)
410{
411 QUrl url;
412
413 if (sku.isEmpty()) {
414 url = getSearchApiUrl(QString(SEARCH_API_ROOT) + "/package/" + packagename);
415 qDebug() << "Request Item Info:" << url;
416 QUrlQuery query;
417 query.addQueryItem("fields", "title,description,price,icon_url");
418 url.setQuery(query);
419 } else {
420 url = getPayApiUrl(QString(IAP_API_ROOT) + "/packages/" + packagename + "/items/by-sku/" + sku);
421 }
422 qDebug() << "Request Item Info:" << url;
423
424 QNetworkRequest request;
425 request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
426 request.setUrl(url);
427 signRequestUrl(request, url.toString(), QStringLiteral("GET"));
428 RequestObject* reqObject = new RequestObject(QString(ITEM_INFO));
429 request.setOriginatingObject(reqObject);
430 m_nam.get(request);
431}
432
433QString Network::getEnvironmentValue(const QString& key,
434 const QString& defaultValue)
435{
436 QProcessEnvironment environment = QProcessEnvironment::systemEnvironment();
437 QString result = environment.value(key, defaultValue);
438 return result;
439}
440
441QString Network::getPayApiUrl(const QString& path)
442{
443 QUrl url(getEnvironmentValue(PAY_BASE_URL_ENVVAR, PAY_BASE_URL).append(path));
444 return sanitizeUrl(url);
445}
446
447QString Network::getSearchApiUrl(const QString& path)
448{
449 QUrl url(getEnvironmentValue(SEARCH_BASE_URL_ENVVAR, SEARCH_BASE_URL).append(path));
450 return sanitizeUrl(url);
451}
452
453void Network::signRequestUrl(QNetworkRequest& request, QString url, QString method)
454{
455 QString sign = m_token.signUrl(url, method);
456 request.setRawHeader("Authorization", sign.toUtf8());
457}
458
459QString Network::getAddPaymentUrl(const QString& currency)
460{
461 QUrl payUrl(getPayApiUrl(QString(PAY_API_ROOT) + PAY_PAYMENTMETHODS_ADD_PATH + "/"));
462 QUrlQuery query;
463 query.addQueryItem("currency", currency);
464 payUrl.setQuery(query);
465 qDebug() << "Get Add Payment URL:" << payUrl;
466 QString sign = m_token.signUrl(payUrl.toString(),
467 QStringLiteral("GET"), true);
468 payUrl.setQuery(encodeQuerySlashes(sign));
469 return payUrl.toString();
470}
471
472QDateTime Network::getTokenUpdated()
473{
474 return m_token.updated();
475}
476
477void Network::checkItemPurchased(const QString& appid, const QString& sku)
478{
479 QUrl url;
480 if (sku.isEmpty()) {
481 url = getPayApiUrl(QString(PAY_API_ROOT) + PAY_PURCHASES_PATH + "/" + appid + "/");
482 } else {
483 url = getPayApiUrl(QString(IAP_API_ROOT) + "/packages/" + appid + "/items/by-sku/" + sku);
484 }
485
486 qDebug() << "Checking for previous purchase:" << url;
487 QNetworkRequest request;
488 request.setUrl(url);
489 signRequestUrl(request, url.toString(), QStringLiteral("GET"));
490 RequestObject* reqObject = new RequestObject(QString(CHECK_PURCHASED));
491 request.setOriginatingObject(reqObject);
492 m_nam.get(request);
493}
494
495void Network::handleCredentialsFound(Token token)
496{
497 m_token = token;
498 Q_EMIT credentialsFound();
499}
500
501void Network::handleCredentialsStored()
502{
503 // Get credentials again to update token object.
504 getCredentials();
505 if (m_startPurchase) {
506 purchaseProcess();
507 } else {
508 Q_EMIT passwordValid();
509 }
510}
511
512}
0513
=== added file 'pay-ui/backend/modules/payui/network.h'
--- pay-ui/backend/modules/payui/network.h 1970-01-01 00:00:00 +0000
+++ pay-ui/backend/modules/payui/network.h 2016-03-11 14:49:24 +0000
@@ -0,0 +1,136 @@
1/*
2 * Copyright 2014-2015 Canonical Ltd.
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of version 3 of the GNU Lesser General Public
6 * License as published by the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 * General Public License for more details.
12 *
13 * You should have received a copy of the GNU Lesser General Public
14 * License along with this library; if not, write to the
15 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
16 * Boston, MA 02110-1301, USA.
17 */
18
19#ifndef NETWORK_H
20#define NETWORK_H
21
22#include <QDateTime>
23#include <QObject>
24#include <QtNetwork/QNetworkReply>
25#include <QStringList>
26#include <QVariantList>
27#include <QUrl>
28#include <QtNetwork/QNetworkAccessManager>
29#include <token.h>
30#include "credentials_service.h"
31
32#include "certificateadapter.h"
33#include "pay_info.h"
34
35using namespace UbuntuOne;
36
37namespace UbuntuPurchase {
38
39constexpr static const char* PAY_BASE_URL_ENVVAR{"PAY_BASE_URL"};
40constexpr static const char* PAY_BASE_URL{"https://myapps.developer.ubuntu.com"};
41constexpr static const char* PAY_API_ROOT{"/api/2.0/click"};
42constexpr static const char* IAP_API_ROOT{"/inventory/api/v1"};
43constexpr static const char* CURRENCY_ENVVAR {"U1_SEARCH_CURRENCY"};
44constexpr static const char* SEARCH_BASE_URL_ENVVAR{"U1_SEARCH_BASE_URL"};
45constexpr static const char* SEARCH_BASE_URL{"https://search.apps.ubuntu.com"};
46constexpr static const char* SEARCH_API_ROOT{"/api/v1"};
47
48class RequestObject : public QObject
49{
50 Q_OBJECT
51public:
52 explicit RequestObject(QString oper, QObject *parent = 0) :
53 QObject(parent)
54 {
55 operation = oper;
56 }
57
58 QString operation;
59};
60
61class Network : public QObject
62{
63 Q_OBJECT
64public:
65 explicit Network(QObject *parent = 0);
66
67 void requestPaymentTypes(const QString& currency);
68 void buyItem(const QString& email, const QString& password,
69 const QString& otp, const QString& appid,
70 const QString& itemid, const QString& currency, const QString& paymentId,
71 const QString& backendId, bool recentLogin);
72 void buyItemWithPreferredPaymentType(const QString& email, const QString& password,
73 const QString& otp,
74 const QString& appid, const QString& itemid, const QString& currency,
75 bool recentLogin);
76 void getItemInfo(const QString& packagename, const QString& sku);
77 void checkPassword(const QString& email, const QString& password,
78 const QString& otp, bool purchasing=false);
79 void getCredentials();
80 void setCredentials(Token token);
81 QString getAddPaymentUrl(const QString& currency);
82 QDateTime getTokenUpdated();
83 void checkItemPurchased(const QString& appid, const QString& sku);
84 static QString getSymbolForCurrency(const QString& currency_code);
85 static bool isSupportedCurrency(const QString& currency_code);
86 static QString sanitizeUrl(const QUrl& url);
87 static QString encodeQuerySlashes(const QString& query);
88 virtual QString getEnvironmentValue(const QString& key,
89 const QString& defaultValue);
90 virtual QString getPayApiUrl(const QString& path);
91 virtual QString getSearchApiUrl(const QString& path);
92
93Q_SIGNALS:
94 void itemDetailsObtained(QString title, QString publisher, QString currency, QString formatted_price, QString icon);
95 void paymentTypesObtained(QVariantList payments);
96 void buyItemSucceeded();
97 void buyItemFailed();
98 void buyInteractionRequired(QString url);
99 void error(QString message);
100 void authenticationError();
101 void credentialsNotFound();
102 void credentialsFound();
103 void passwordValid();
104 void noPreferredPaymentMethod();
105 void loginError(const QString& message);
106 void twoFactorAuthRequired();
107 void itemNotPurchased();
108 void certificateFound(QObject* cert);
109
110private Q_SLOTS:
111 void onReply(QNetworkReply*);
112 void handleCredentialsFound(Token token);
113 void handleCredentialsStored();
114 void purchaseProcess();
115
116private:
117 QNetworkAccessManager m_nam;
118 QNetworkRequest m_request;
119 CredentialsService m_service;
120 Token m_token;
121 PayInfo* m_preferred;
122 QString m_selectedPaymentId;
123 QString m_selectedBackendId;
124 QString m_selectedAppId;
125 QString m_selectedItemId;
126 QString m_currency;
127 bool m_startPurchase = false;
128
129 void signRequestUrl(QNetworkRequest& request, QString url, QString method="GET");
130 QString getDeviceId();
131 QByteArray getPartnerId();
132};
133
134}
135
136#endif // NETWORK_H
0137
=== added file 'pay-ui/backend/modules/payui/oxideconstants.cpp'
--- pay-ui/backend/modules/payui/oxideconstants.cpp 1970-01-01 00:00:00 +0000
+++ pay-ui/backend/modules/payui/oxideconstants.cpp 2016-03-11 14:49:24 +0000
@@ -0,0 +1,19 @@
1/*
2 * Copyright 2014 Canonical Ltd.
3 *
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of version 3 of the GNU Lesser General Public
6 * License as published by the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 * General Public License for more details.
12 *
13 * You should have received a copy of the GNU Lesser General Public
14 * License along with this library; if not, write to the
15 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
16 * Boston, MA 02110-1301, USA.
17 */
18
19#include "oxideconstants.h"
020
=== added file 'pay-ui/backend/modules/payui/oxideconstants.h'
--- pay-ui/backend/modules/payui/oxideconstants.h 1970-01-01 00:00:00 +0000
+++ pay-ui/backend/modules/payui/oxideconstants.h 2016-03-11 14:49:24 +0000
@@ -0,0 +1,53 @@
1/*
2 * Copyright 2014 Canonical Ltd.
3 *
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of version 3 of the GNU Lesser General Public
6 * License as published by the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 * General Public License for more details.
12 *
13 * You should have received a copy of the GNU Lesser General Public
14 * License along with this library; if not, write to the
15 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
16 * Boston, MA 02110-1301, USA.
17 */
18
19#ifndef OXIDECONSTANTS_H
20#define OXIDECONSTANTS_H
21
22#include <QObject>
23
24class SecurityStatus : public QObject
25{
26 Q_OBJECT
27 Q_ENUMS(SecurityLevel)
28public:
29 enum SecurityLevel {
30 SecurityLevelNone,
31 SecurityLevelSecure,
32 SecurityLevelSecureEV,
33 SecurityLevelWarning,
34 SecurityLevelError
35 };
36};
37
38class SslCertificate : public QObject
39{
40 Q_OBJECT
41 Q_ENUMS(PrincipalAttr)
42public:
43 enum PrincipalAttr {
44 PrincipalAttrOrganizationName,
45 PrincipalAttrCommonName,
46 PrincipalAttrLocalityName,
47 PrincipalAttrOrganizationUnitName,
48 PrincipalAttrCountryName,
49 PrincipalAttrStateOrProvinceName
50 };
51};
52
53#endif // OXIDECONSTANTS_H
054
=== added file 'pay-ui/backend/modules/payui/pay_info.cpp'
--- pay-ui/backend/modules/payui/pay_info.cpp 1970-01-01 00:00:00 +0000
+++ pay-ui/backend/modules/payui/pay_info.cpp 2016-03-11 14:49:24 +0000
@@ -0,0 +1,37 @@
1/*
2 * Copyright 2014 Canonical Ltd.
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of version 3 of the GNU Lesser General Public
6 * License as published by the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 * General Public License for more details.
12 *
13 * You should have received a copy of the GNU Lesser General Public
14 * License along with this library; if not, write to the
15 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
16 * Boston, MA 02110-1301, USA.
17 */
18
19#include "pay_info.h"
20namespace UbuntuPurchase {
21
22PayInfo::PayInfo(QObject *parent) :
23 QObject(parent)
24{
25}
26
27void PayInfo::setPayData(QString name, QString description, QString payment, QString backend, bool requiresInteracion, bool preferred)
28{
29 m_name = name;
30 m_paymentId = payment;
31 m_description = description;
32 m_backendId = backend;
33 m_requiresInteracion = requiresInteracion;
34 m_preferred = preferred;
35}
36
37}
038
=== added file 'pay-ui/backend/modules/payui/pay_info.h'
--- pay-ui/backend/modules/payui/pay_info.h 1970-01-01 00:00:00 +0000
+++ pay-ui/backend/modules/payui/pay_info.h 2016-03-11 14:49:24 +0000
@@ -0,0 +1,73 @@
1/*
2 * Copyright 2014 Canonical Ltd.
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of version 3 of the GNU Lesser General Public
6 * License as published by the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 * General Public License for more details.
12 *
13 * You should have received a copy of the GNU Lesser General Public
14 * License along with this library; if not, write to the
15 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
16 * Boston, MA 02110-1301, USA.
17 */
18
19#ifndef PAY_INFO_H
20#define PAY_INFO_H
21
22#include <QObject>
23#include <QString>
24
25namespace UbuntuPurchase {
26
27class PayInfo : public QObject
28{
29 Q_OBJECT
30 Q_PROPERTY(QString name READ name NOTIFY nameChanged)
31 Q_PROPERTY(QString paymentId READ paymentId NOTIFY paymentIdChanged)
32 Q_PROPERTY(QString backendId READ backendId NOTIFY backendIdChanged)
33 Q_PROPERTY(QString description READ description NOTIFY descriptionChanged)
34 Q_PROPERTY(bool requiresInteracion READ requiresInteracion NOTIFY requiresInteracionChanged)
35 Q_PROPERTY(bool preferred READ preferred WRITE setPreferred NOTIFY preferredChanged)
36
37public:
38 explicit PayInfo(QObject *parent = 0);
39
40 QString name() { return m_name; }
41 QString paymentId() { return m_paymentId; }
42 QString backendId() { return m_backendId; }
43 QString description() { return m_description; }
44 bool requiresInteracion() { return m_requiresInteracion; }
45 bool preferred() { return m_preferred; }
46
47 void setPayData(QString name, QString description, QString payment, QString backend, bool steps, bool preferred);
48 void setName(const QString& name) { m_name = name; Q_EMIT nameChanged(); }
49 void setDescription(const QString& description) { m_description = description; Q_EMIT descriptionChanged(); }
50 void setPaymentId(const QString& paymentId) { m_paymentId = paymentId; Q_EMIT paymentIdChanged(); }
51 void setbackendId(const QString& backendId) { m_backendId = backendId; Q_EMIT backendIdChanged(); }
52 void setRequiresInteracion(bool requiresInteracion) { m_requiresInteracion = requiresInteracion; Q_EMIT requiresInteracionChanged(); }
53 void setPreferred(bool preferred) { m_preferred = preferred; Q_EMIT preferredChanged(); }
54
55Q_SIGNALS:
56 void nameChanged();
57 void paymentIdChanged();
58 void backendIdChanged();
59 void descriptionChanged();
60 void requiresInteracionChanged();
61 void preferredChanged();
62
63private:
64 QString m_name;
65 QString m_paymentId;
66 QString m_backendId;
67 QString m_description;
68 bool m_requiresInteracion;
69 bool m_preferred;
70};
71}
72
73#endif // PAY_INFO_H
074
=== added file 'pay-ui/backend/modules/payui/purchase.cpp'
--- pay-ui/backend/modules/payui/purchase.cpp 1970-01-01 00:00:00 +0000
+++ pay-ui/backend/modules/payui/purchase.cpp 2016-03-11 14:49:24 +0000
@@ -0,0 +1,154 @@
1/*
2 * Copyright 2014-2016 Canonical Ltd.
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of version 3 of the GNU Lesser General Public
6 * License as published by the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 * General Public License for more details.
12 *
13 * You should have received a copy of the GNU Lesser General Public
14 * License along with this library; if not, write to the
15 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
16 * Boston, MA 02110-1301, USA.
17 */
18
19#include "purchase.h"
20#include <QUrl>
21#include <QUrlQuery>
22#include <QCoreApplication>
23#include <QDateTime>
24#include <QtDebug>
25
26#include <logging.h>
27
28namespace UbuntuPurchase {
29
30Purchase::Purchase(QObject *parent) :
31 QObject(parent),
32 m_network(this)
33{
34 connect(&m_network, &Network::itemDetailsObtained,
35 this, &Purchase::itemDetailsObtained);
36 connect(&m_network, &Network::paymentTypesObtained,
37 this, &Purchase::paymentTypesObtained);
38 connect(&m_network, &Network::buyItemSucceeded,
39 this, &Purchase::buyItemSucceeded);
40 connect(&m_network, &Network::buyItemFailed,
41 this, &Purchase::buyItemFailed);
42 connect(&m_network, &Network::buyInteractionRequired,
43 this, &Purchase::buyInterationRequired);
44 connect(&m_network, &Network::error,
45 this, &Purchase::error);
46 connect(&m_network, &Network::authenticationError,
47 this, &Purchase::authenticationError);
48 connect(&m_network, &Network::credentialsNotFound,
49 this, &Purchase::credentialsNotFound);
50 connect(&m_network, &Network::credentialsFound,
51 this, &Purchase::credentialsFound);
52 connect(&m_network, &Network::passwordValid,
53 this, &Purchase::passwordValid);
54 connect(&m_network, &Network::noPreferredPaymentMethod,
55 this, &Purchase::noPreferredPaymentMethod);
56 connect(&m_network, &Network::loginError,
57 this, &Purchase::loginError);
58 connect(&m_network, &Network::twoFactorAuthRequired,
59 this, &Purchase::twoFactorAuthRequired);
60 connect(&m_network, &Network::itemNotPurchased,
61 this, &Purchase::itemNotPurchased);
62 connect(&m_network, &Network::certificateFound,
63 this, &Purchase::certificateFound);
64
65 QCoreApplication* instance = QCoreApplication::instance();
66
67 // Set up logging
68 UbuntuOne::AuthLogger::setupLogging();
69 const char* u1_debug = getenv("U1_DEBUG");
70 if (u1_debug != NULL && strcmp(u1_debug, "") != 0) {
71 UbuntuOne::AuthLogger::setLogLevel(QtDebugMsg);
72 }
73
74 // Handle the purchase: URI
75 for (int i = 0; i < instance->arguments().size(); i++) {
76 QString argument = instance->arguments().at(i);
77 if (argument.startsWith("purchase://")) {
78 QUrl data(argument);
79 m_appid = data.host();
80 m_itemid = data.fileName();
81 qDebug() << "Purchase requested for" << m_itemid << "by app" << m_appid;
82 break;
83 }
84 }
85}
86
87Purchase::~Purchase()
88{
89 UbuntuOne::AuthLogger::stopLogging();
90}
91
92void Purchase::quitCancel()
93{
94 qDebug() << "Purchase Canceled: exit code 1";
95 QCoreApplication::exit(1);
96}
97
98void Purchase::quitSuccess()
99{
100 qDebug() << "Purchase Succeeded: exit code 0";
101 QCoreApplication::exit(0);
102}
103
104void Purchase::checkCredentials()
105{
106 m_network.getCredentials();
107}
108
109QString Purchase::getAddPaymentUrl(QString currency)
110{
111 return m_network.getAddPaymentUrl(currency);
112}
113
114void Purchase::getItemDetails()
115{
116 if (m_appid.isEmpty() && m_itemid.isEmpty()) {
117 qCritical() << "AppId or ItemId not provided";
118 quitCancel();
119 }
120 qDebug() << "Getting Item Details";
121 m_network.getItemInfo(m_appid, m_itemid);
122}
123
124void Purchase::getPaymentTypes(const QString& currency)
125{
126 m_network.requestPaymentTypes(currency);
127}
128
129void Purchase::checkWallet(QString email, QString password, QString otp)
130{
131 m_network.checkPassword(email, password, otp);
132}
133
134void Purchase::buyItemWithPreferredPayment(QString email, QString password, QString otp, QString currency, bool recentLogin)
135{
136 m_network.buyItemWithPreferredPaymentType(email, password, otp, m_appid, m_itemid, currency, recentLogin);
137}
138
139void Purchase::buyItem(QString email, QString password, QString otp, QString currency, QString paymentId, QString backendId, bool recentLogin)
140{
141 m_network.buyItem(email, password, otp, m_appid, m_itemid, currency, paymentId, backendId, recentLogin);
142}
143
144QDateTime Purchase::getTokenUpdated()
145{
146 return m_network.getTokenUpdated();
147}
148
149void Purchase::checkItemPurchased()
150{
151 m_network.checkItemPurchased(m_appid, m_itemid);
152}
153
154}
0155
=== added file 'pay-ui/backend/modules/payui/purchase.h'
--- pay-ui/backend/modules/payui/purchase.h 1970-01-01 00:00:00 +0000
+++ pay-ui/backend/modules/payui/purchase.h 2016-03-11 14:49:24 +0000
@@ -0,0 +1,59 @@
1#ifndef PURCHASE_H
2#define PURCHASE_H
3
4#include <QDateTime>
5#include <QObject>
6#include <QString>
7
8#include "network.h"
9#include "certificateadapter.h"
10
11namespace UbuntuPurchase {
12
13class Purchase : public QObject
14{
15 Q_OBJECT
16
17public:
18 explicit Purchase(QObject *parent = 0);
19 ~Purchase();
20
21 Q_INVOKABLE void getItemDetails();
22 Q_INVOKABLE void getPaymentTypes(const QString &currency);
23 Q_INVOKABLE void buyItemWithPreferredPayment(QString email, QString password, QString otp, QString currency, bool recentLogin);
24 Q_INVOKABLE void buyItem(QString email, QString password, QString otp, QString currency, QString paymentId, QString backendId, bool recentLogin);
25 Q_INVOKABLE void checkCredentials();
26 Q_INVOKABLE QString getAddPaymentUrl(QString currency);
27 Q_INVOKABLE void checkWallet(QString email, QString password, QString otp);
28
29 Q_INVOKABLE void quitSuccess();
30 Q_INVOKABLE void quitCancel();
31 Q_INVOKABLE QDateTime getTokenUpdated();
32 Q_INVOKABLE void checkItemPurchased();
33
34Q_SIGNALS:
35 void itemDetailsObtained(QString title, QString publisher, QString currency, QString formatted_price, QString icon);
36 void paymentTypesObtained(QVariantList payments);
37 void buyItemSucceeded();
38 void buyItemFailed();
39 void buyInterationRequired(QString url);
40 void error(QString message);
41 void authenticationError();
42 void credentialsNotFound();
43 void credentialsFound();
44 void passwordValid();
45 void noPreferredPaymentMethod();
46 void loginError(const QString& message);
47 void twoFactorAuthRequired();
48 void itemNotPurchased();
49 void certificateFound(QObject* cert);
50
51private:
52 Network m_network;
53 QString m_appid;
54 QString m_itemid;
55};
56
57}
58
59#endif // PURCHASE_H
060
=== added file 'pay-ui/backend/modules/payui/qmldir'
--- pay-ui/backend/modules/payui/qmldir 1970-01-01 00:00:00 +0000
+++ pay-ui/backend/modules/payui/qmldir 2016-03-11 14:49:24 +0000
@@ -0,0 +1,2 @@
1module payui
2plugin payuibackend
03
=== added directory 'pay-ui/backend/tests'
=== added file 'pay-ui/backend/tests/CMakeLists.txt'
--- pay-ui/backend/tests/CMakeLists.txt 1970-01-01 00:00:00 +0000
+++ pay-ui/backend/tests/CMakeLists.txt 2016-03-11 14:49:24 +0000
@@ -0,0 +1,30 @@
1
2include_directories (${CMAKE_CURRENT_SOURCE_DIR}/../payui)
3set(CMAKE_INCLUDE_CURRENT_DIR ON)
4set(CMAKE_AUTOMOC ON)
5find_package(Qt5Core REQUIRED)
6
7set(PURCHASE_TESTS_TARGET test-purchase-ui)
8
9add_executable(${PURCHASE_TESTS_TARGET}
10 test_network.cpp
11)
12target_link_libraries(${PURCHASE_TESTS_TARGET}
13 -Wl,-rpath,${CMAKE_CURRENT_BINARY_DIR}/../payui
14 -L${CMAKE_CURRENT_BINARY_DIR}/../payui
15 ${PAYUI_BACKEND}
16)
17qt5_use_modules(${PURCHASE_TESTS_TARGET} Qml Quick Core DBus Xml Network Test)
18
19add_custom_target(payui-cppunit-tests
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}
21 DEPENDS ${PURCHASE_TESTS_TARGET} mock_click_server.py
22)
23
24add_test(NAME payui-cppunit-tests
25 COMMAND make payui-cppunit-tests
26)
27
28add_custom_target(mock_click_server.py
29 COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/mock_click_server.py ${CMAKE_CURRENT_BINARY_DIR}/mock_click_server.py
30)
031
=== added file 'pay-ui/backend/tests/mock_click_server.py'
--- pay-ui/backend/tests/mock_click_server.py 1970-01-01 00:00:00 +0000
+++ pay-ui/backend/tests/mock_click_server.py 2016-03-11 14:49:24 +0000
@@ -0,0 +1,162 @@
1import json
2import threading
3from http.server import BaseHTTPRequestHandler, HTTPServer
4
5
6KEEP_ALIVE = True
7
8
9class MyHandler(BaseHTTPRequestHandler):
10
11 def do_HEAD(self):
12 self.send_response(200)
13 self.send_header("X-Click-Token", "X-Click-Token")
14 self.end_headers()
15
16 def response_payment_types(self, fail=False):
17 types = [
18 {
19 "description": "PayPal",
20 "id": "paypal",
21 "preferred": False,
22 "choices": [
23 {
24 "currencies": [
25 "USD",
26 "GBP",
27 "EUR"
28 ],
29 "id": 532,
30 "requires_interaction": False,
31 "preferred": False,
32 "description": "PayPal Preapproved Payment (exp. 2014-04-12)"
33 }
34 ]
35 },
36 {
37 "description": "Credit or Debit Card",
38 "id": "credit_card",
39 "preferred": True,
40 "choices": [
41 {
42 "currencies": [
43 "USD"
44 ],
45 "id": 1767,
46 "requires_interaction": False,
47 "preferred": False,
48 "description": "**** **** **** 1111 (Visa, exp. 02/2015)"
49 },
50 {
51 "currencies": [
52 "USD"
53 ],
54 "id": 1726,
55 "requires_interaction": False,
56 "preferred": True,
57 "description": "**** **** **** 1111 (Visa, exp. 03/2015)"
58 }
59 ]
60 }
61 ]
62 if fail:
63 self.send_response(404)
64 else:
65 self.send_response(200)
66 self.send_header("Content-type", "application/json")
67 self.end_headers()
68 self.wfile.write(bytes(json.dumps(types), 'UTF-8'))
69
70 def response_buy_item(self, fail=False, interaction=False):
71 state = "Complete" if not interaction else "InProgress"
72 response = {
73 "state": state,
74 }
75 if interaction:
76 # Real server returns path starting with / here.
77 response["redirect_to"] = "/redirect.url?currency=USD"
78 if fail:
79 response = {}
80
81 if self.path.find("/notpurchased/") != -1:
82 self.send_response(404)
83 else:
84 self.send_response(200)
85
86 self.send_header("Content-type", "application/json")
87 self.end_headers()
88 self.wfile.write(bytes(json.dumps(response), 'UTF-8'))
89
90 def response_item_info(self, fail, eurozone, dotar):
91 response = {
92 "title": "title",
93 "publisher": "publisher",
94 "price": 9.99,
95 "prices": {
96 "USD": 1.99,
97 "EUR": 1.69,
98 "GBP": 1.29,
99 "ARS": 18.05,
100 },
101 "icon_url": "icon_url",
102 }
103 if fail:
104 self.send_response(404)
105 else:
106 self.send_response(200)
107 self.send_header("Content-type", "application/json")
108 if eurozone:
109 self.send_header("X-Suggested-Currency", "EUR")
110 if dotar:
111 self.send_header("X-Suggested-Currency", "ARS")
112 self.end_headers()
113 self.wfile.write(bytes(json.dumps(response), 'UTF-8'))
114
115 def response_auth_error(self):
116 self.send_response(401)
117 self.send_header("Content-type", "application/json")
118 self.end_headers()
119 self.wfile.write(bytes(json.dumps(dict()), 'UTF-8'))
120
121 def do_POST(self):
122 """Respond to a POST request."""
123 #content = self.rfile.read(int(self.headers.get('content-length')))
124 #structure = json.loads(content.decode('utf-8'))
125 self.do_GET()
126
127 def do_GET(self):
128 """Respond to a GET request."""
129 if self.path.find("/authError/") != -1:
130 self.response_auth_error()
131 elif self.path.find("shutdown") != -1:
132 global KEEP_ALIVE
133 KEEP_ALIVE = False
134 elif self.path.find("paymentmethods/") != -1:
135 fail = self.path.find("/fail/") != -1
136 self.response_payment_types(fail)
137 elif self.path.find("purchases/") != -1:
138 fail = self.path.find("/fail/") != -1
139 interaction = self.path.find("/interaction/") != -1
140 self.response_buy_item(fail=fail, interaction=interaction)
141 elif self.path.find("iteminfo/") != -1:
142 fail = self.path.find("/fail/") != -1
143 eurozone = self.path.find("/eurozone/") != -1
144 dotar = self.path.find("/dotar/") != -1
145 self.response_item_info(fail, eurozone, dotar)
146
147
148def run_click_server():
149 server_address = ('', 8000)
150 httpd = HTTPServer(server_address, MyHandler)
151 global KEEP_ALIVE
152 print('start')
153 while KEEP_ALIVE:
154 httpd.handle_request()
155
156
157def run_mocked_settings():
158 t = threading.Thread(target=run_click_server)
159 t.start()
160
161
162run_mocked_settings()
0163
=== added file 'pay-ui/backend/tests/test_network.cpp'
--- pay-ui/backend/tests/test_network.cpp 1970-01-01 00:00:00 +0000
+++ pay-ui/backend/tests/test_network.cpp 2016-03-11 14:49:24 +0000
@@ -0,0 +1,317 @@
1/*
2 * Copyright 2014-2016 Canonical Ltd.
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of version 3 of the GNU Lesser General Public
6 * License as published by the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 * General Public License for more details.
12 *
13 * You should have received a copy of the GNU Lesser General Public
14 * License along with this library; if not, write to the
15 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
16 * Boston, MA 02110-1301, USA.
17 */
18
19#include <stdlib.h>
20
21#include <QObject>
22#include <QProcess>
23#include <QProcessEnvironment>
24#include <QDebug>
25#include <QVariant>
26#include <QUrlQuery>
27#include <QString>
28#include <QDir>
29#include <QTest>
30#include <QTimer>
31#include <QSignalSpy>
32#include <QVariant>
33#include <QVariantList>
34
35#include <modules/payui/network.h>
36#include <modules/payui/pay_info.h>
37
38using namespace UbuntuPurchase;
39
40class TestNetwork: public QObject
41{
42 Q_OBJECT
43
44private slots:
45 void initTestCase();
46 void testNetworkAuthenticationError();
47 void testNetworkGetPaymentTypes();
48 void testNetworkGetPaymentTypesFail();
49 void testNetworkBuyItem();
50 void testNetworkBuyItemInProgress();
51 void testNetworkBuyItemFail();
52 void testNetworkButItemWithPaymentType();
53 void testNetworkButItemWithPaymentTypeFail();
54 void testNetworkButItemWithPaymentTypeInProgress();
55 void testNetworkGetItemInfo();
56 void testNetworkGetItemInfoEurozone();
57 void testNetworkGetItemInfoOtherCoins();
58 void testNetworkGetItemInfoOverride();
59 void testNetworkGetItemInfoOverrideOther();
60 void testNetworkGetItemInfoFail();
61 void testUseExistingCredentials();
62 void testCheckAlreadyPurchased();
63 void testCheckAlreadyPurchasedFail();
64 void testSanitizeUrl();
65 void testEncodeQuerySlashes();
66 void cleanupTestCase();
67
68private:
69 UbuntuPurchase::Network network;
70 QProcess* process;
71};
72
73void TestNetwork::initTestCase()
74{
75 setenv("SSO_AUTH_BASE_URL", "http://localhost:8000/", 1);
76 qDebug() << "Starting Server";
77 network.setCredentials(Token("token_key", "token_secret", "consumer_key", "consumer_secret"));
78 process = new QProcess(this);
79 QSignalSpy spy(process, SIGNAL(started()));
80 process->setWorkingDirectory(QDir::currentPath() + "/backend/tests/");
81 qDebug() << process->workingDirectory();
82 QString program = "python3";
83 QString script = "mock_click_server.py";
84 process->start(program, QStringList() << script);
85 QTRY_COMPARE(spy.count(), 1);
86 // Wait for server to start
87 QTimer timer;
88 QSignalSpy spy2(&timer, SIGNAL(timeout()));
89 timer.setInterval(2000);
90 timer.setSingleShot(true);
91 timer.start();
92 QTRY_COMPARE(spy2.count(), 1);
93}
94
95void TestNetwork::testNetworkAuthenticationError()
96{
97 setenv(PAY_BASE_URL_ENVVAR, "http://localhost:8000/authError/", 1);
98 QSignalSpy spy(&network, SIGNAL(authenticationError()));
99 network.buyItem("email", "password", "otp", "USD", "appid", "itemid", "paymentid", "backendid", false);
100 // WTF DOES THIS HAPPEN TWICE
101 QTRY_COMPARE(spy.count(), 2);
102}
103
104void TestNetwork::testNetworkGetPaymentTypes()
105{
106 setenv(PAY_BASE_URL_ENVVAR, "http://localhost:8000/", 1);
107 QSignalSpy spy(&network, SIGNAL(paymentTypesObtained(QVariantList)));
108 network.requestPaymentTypes("USD");
109 QTRY_COMPARE(spy.count(), 1);
110 QList<QVariant> arguments = spy.takeFirst();
111 QVERIFY(arguments.at(0).toList().count() == 3);
112 QVariantList listPays = arguments.at(0).toList();
113 for (int i = 0; i < listPays.count(); i++) {
114 QVariant var = listPays.at(i);
115 PayInfo* info = var.value<PayInfo*>();
116 if (i == 2) {
117 QVERIFY(info->preferred() == true);
118 } else {
119 QVERIFY(info->preferred() == false);
120 }
121 }
122}
123
124void TestNetwork::testNetworkGetPaymentTypesFail()
125{
126 setenv(PAY_BASE_URL_ENVVAR, "http://localhost:8000/fail/", 1);
127 QSignalSpy spy(&network, SIGNAL(error(QString)));
128 network.requestPaymentTypes("USD");
129 // WTF DOES THIS HAPPEN TWICE
130 QTRY_COMPARE(spy.count(), 2);
131 QList<QVariant> arguments = spy.takeFirst();
132 QVERIFY(arguments.at(0).toString().startsWith("404:"));
133}
134
135void TestNetwork::testNetworkBuyItem()
136{
137 setenv(PAY_BASE_URL_ENVVAR, "http://localhost:8000/", 1);
138 QSignalSpy spy(&network, SIGNAL(buyItemSucceeded()));
139 network.buyItem("email", "password", "otp", "USD", "appid", "itemid", "paymentid", "backendid", false);
140 QTRY_COMPARE(spy.count(), 1);
141}
142
143void TestNetwork::testNetworkBuyItemInProgress()
144{
145 setenv(PAY_BASE_URL_ENVVAR, "http://localhost:8000/interaction/", 1);
146 QSignalSpy spy(&network, SIGNAL(buyInteractionRequired(QString)));
147 network.buyItem("email", "password", "otp", "USD", "appid", "itemid", "paymentid", "backendid", false);
148 QTRY_COMPARE(spy.count(), 1);
149 QList<QVariant> arguments = spy.takeFirst();
150 QString url(arguments.at(0).toString());
151 QString expected("http://localhost:8000/interaction/redirect.url?currency=USD");
152 QVERIFY(url.startsWith(expected));
153}
154
155void TestNetwork::testNetworkBuyItemFail()
156{
157 setenv(PAY_BASE_URL_ENVVAR, "http://localhost:8000/fail/", 1);
158 QSignalSpy spy(&network, SIGNAL(buyItemFailed()));
159 network.buyItem("email", "password", "otp", "USD", "appid", "itemid", "paymentid", "backendid", false);
160 QTRY_COMPARE(spy.count(), 1);
161}
162
163void TestNetwork::testNetworkButItemWithPaymentType()
164{
165 setenv(PAY_BASE_URL_ENVVAR, "http://localhost:8000/", 1);
166 QSignalSpy spy(&network, SIGNAL(paymentTypesObtained(QVariantList)));
167 network.requestPaymentTypes("USD");
168 QTRY_COMPARE(spy.count(), 1);
169 QSignalSpy spy2(&network, SIGNAL(buyItemSucceeded()));
170 network.buyItemWithPreferredPaymentType("email", "password", "otp", "USD", "appid", "itemid", false);
171 QTRY_COMPARE(spy2.count(), 1);
172}
173
174void TestNetwork::testNetworkButItemWithPaymentTypeFail()
175{
176 setenv(PAY_BASE_URL_ENVVAR, "http://localhost:8000/", 1);
177 QSignalSpy spy(&network, SIGNAL(paymentTypesObtained(QVariantList)));
178 network.requestPaymentTypes("USD");
179 QTRY_COMPARE(spy.count(), 1);
180 setenv(PAY_BASE_URL_ENVVAR, "http://localhost:8000/fail/", 1);
181 QSignalSpy spy2(&network, SIGNAL(buyItemFailed()));
182 network.buyItemWithPreferredPaymentType("email", "password", "otp", "USD", "appid", "itemid", false);
183 QTRY_COMPARE(spy2.count(), 1);
184}
185
186void TestNetwork::testNetworkButItemWithPaymentTypeInProgress()
187{
188 setenv(PAY_BASE_URL_ENVVAR, "http://localhost:8000/", 1);
189 QSignalSpy spy(&network, SIGNAL(paymentTypesObtained(QVariantList)));
190 network.requestPaymentTypes("USD""USD");
191 QTRY_COMPARE(spy.count(), 1);
192 setenv(PAY_BASE_URL_ENVVAR, "http://localhost:8000/interaction/", 1);
193 QSignalSpy spy2(&network, SIGNAL(buyInteractionRequired(QString)));
194 network.buyItemWithPreferredPaymentType("email", "password", "otp", "USD", "appid", "itemid", false);
195 QTRY_COMPARE(spy2.count(), 1);
196}
197
198void TestNetwork::testNetworkGetItemInfo()
199{
200 setenv(SEARCH_BASE_URL_ENVVAR, "http://localhost:8000/iteminfo/", 1);
201 unsetenv(CURRENCY_ENVVAR);
202 QSignalSpy spy(&network, SIGNAL(itemDetailsObtained(QString,QString,QString,QString,QString)));
203 network.getItemInfo("packagename", "");
204 QTRY_COMPARE(spy.count(), 1);
205 QList<QVariant> arguments = spy.takeFirst();
206 QCOMPARE(arguments.at(2).toString(), QStringLiteral("USD"));
207 QCOMPARE(arguments.at(3).toString(), QStringLiteral("US$1.99"));
208}
209
210void TestNetwork::testNetworkGetItemInfoEurozone()
211{
212 setenv(SEARCH_BASE_URL_ENVVAR, "http://localhost:8000/iteminfo/eurozone/", 1);
213 unsetenv(CURRENCY_ENVVAR);
214 QSignalSpy spy(&network, SIGNAL(itemDetailsObtained(QString,QString,QString,QString,QString)));
215 network.getItemInfo("packagename", "");
216 QTRY_COMPARE(spy.count(), 1);
217 QList<QVariant> arguments = spy.takeFirst();
218 QCOMPARE(arguments.at(2).toString(), QStringLiteral("EUR"));
219 QCOMPARE(arguments.at(3).toString(), QStringLiteral("€1.69"));
220}
221
222void TestNetwork::testNetworkGetItemInfoOtherCoins()
223{
224 setenv(SEARCH_BASE_URL_ENVVAR, "http://localhost:8000/iteminfo/dotar/", 1);
225 unsetenv(CURRENCY_ENVVAR);
226 QSignalSpy spy(&network, SIGNAL(itemDetailsObtained(QString,QString,QString,QString,QString)));
227 network.getItemInfo("packagename", "");
228 QTRY_COMPARE(spy.count(), 1);
229 QList<QVariant> arguments = spy.takeFirst();
230 QCOMPARE(arguments.at(2).toString(), QStringLiteral("USD"));
231 QCOMPARE(arguments.at(3).toString(), QStringLiteral("US$1.99"));
232}
233
234void TestNetwork::testNetworkGetItemInfoOverride()
235{
236 setenv(SEARCH_BASE_URL_ENVVAR, "http://localhost:8000/iteminfo/", 1);
237 setenv(CURRENCY_ENVVAR, "EUR", true);
238 QSignalSpy spy(&network, SIGNAL(itemDetailsObtained(QString,QString,QString,QString,QString)));
239 network.getItemInfo("packagename", "");
240 QTRY_COMPARE(spy.count(), 1);
241 QList<QVariant> arguments = spy.takeFirst();
242 QCOMPARE(arguments.at(2).toString(), QStringLiteral("EUR"));
243 QCOMPARE(arguments.at(3).toString(), QStringLiteral("€1.69"));
244 unsetenv(CURRENCY_ENVVAR);
245}
246
247void TestNetwork::testNetworkGetItemInfoOverrideOther()
248{
249 setenv(SEARCH_BASE_URL_ENVVAR, "http://localhost:8000/iteminfo/dotar/", 1);
250 setenv(CURRENCY_ENVVAR, "EUR", true);
251 QSignalSpy spy(&network, SIGNAL(itemDetailsObtained(QString,QString,QString,QString,QString)));
252 network.getItemInfo("packagename", "");
253 QTRY_COMPARE(spy.count(), 1);
254 QList<QVariant> arguments = spy.takeFirst();
255 QCOMPARE(arguments.at(2).toString(), QStringLiteral("EUR"));
256 QCOMPARE(arguments.at(3).toString(), QStringLiteral("€1.69"));
257 unsetenv(CURRENCY_ENVVAR);
258}
259
260void TestNetwork::testNetworkGetItemInfoFail()
261{
262 setenv(SEARCH_BASE_URL_ENVVAR, "http://localhost:8000/fail/iteminfo/", 1);
263 unsetenv(CURRENCY_ENVVAR);
264 QSignalSpy spy(&network, SIGNAL(error(QString)));
265 network.getItemInfo("packagename", "");
266 // WTF DOES THIS HAPPEN TWICE
267 QTRY_COMPARE(spy.count(), 2);
268}
269
270void TestNetwork::testUseExistingCredentials()
271{
272 setenv(PAY_BASE_URL_ENVVAR, "http://localhost:8000/", 1);
273 QSignalSpy spy(&network, SIGNAL(buyItemSucceeded()));
274 network.buyItem("email", "password", "otp", "USD", "appid", "itemid", "paymentid", "backendid", true);
275 QTRY_COMPARE(spy.count(), 1);
276}
277
278void TestNetwork::testCheckAlreadyPurchased()
279{
280 setenv(PAY_BASE_URL_ENVVAR, "http://localhost:8000/", 1);
281 QSignalSpy spy(&network, SIGNAL(buyItemSucceeded()));
282 network.checkItemPurchased("com.example.fakeapp", "");
283 QTRY_COMPARE(spy.count(), 1);
284}
285
286void TestNetwork::testCheckAlreadyPurchasedFail()
287{
288 setenv(PAY_BASE_URL_ENVVAR, "http://localhost:8000/notpurchased/", 1);
289 QSignalSpy spy(&network, SIGNAL(itemNotPurchased()));
290 network.checkItemPurchased("com.example.fakeapp", "");
291 // WTF DOES THIS HAPPEN TWICE
292 QTRY_COMPARE(spy.count(), 2);
293}
294
295void TestNetwork::testSanitizeUrl()
296{
297 QUrl url("https://example.com//test/this/heavily///really%2f/");
298 QUrl expected("https://example.com/test/this/heavily/really%2f/");
299 QString result = network.sanitizeUrl(url);
300 QTRY_COMPARE(result, expected.toString());
301}
302
303void TestNetwork::testEncodeQuerySlashes()
304{
305 QString query("abcdef/01235%3D");
306 QTRY_COMPARE(network.encodeQuerySlashes(query),
307 QStringLiteral("abcdef%2F01235%3D"));
308}
309
310void TestNetwork::cleanupTestCase()
311{
312 process->close();
313 process->deleteLater();
314}
315
316QTEST_MAIN(TestNetwork)
317#include "test_network.moc"
0318
=== added file 'pay-ui/pay-ui.in'
--- pay-ui/pay-ui.in 1970-01-01 00:00:00 +0000
+++ pay-ui/pay-ui.in 2016-03-11 14:49:24 +0000
@@ -0,0 +1,19 @@
1#!/bin/sh
2#
3# Copyright © 2016 Canonical Ltd.
4#
5# This program is free software: you can redistribute it and/or modify it
6# under the terms of the GNU General Public License version 3, as published
7# by the Free Software Foundation.
8#
9# This program is distributed in the hope that it will be useful, but
10# WITHOUT ANY WARRANTY; without even the implied warranties of
11# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
12# PURPOSE. See the GNU General Public License for more details.
13#
14# You should have received a copy of the GNU General Public License along
15# with this program. If not, see <http://www.gnu.org/licenses/>.
16
17set -e
18
19env QML2_IMPORT_PATH=${QML2_IMPORT_PATH}:@QT_IMPORTS_DIR@ qmlscene @PAYUI_DIR@/payui.qml $@
020
=== added directory 'pay-ui/tests'
=== added directory 'pay-ui/tests/autopilot'
=== added directory 'pay-ui/tests/autopilot/pay_ui'
=== added file 'pay-ui/tests/autopilot/pay_ui/__init__.py'
--- pay-ui/tests/autopilot/pay_ui/__init__.py 1970-01-01 00:00:00 +0000
+++ pay-ui/tests/autopilot/pay_ui/__init__.py 2016-03-11 14:49:24 +0000
@@ -0,0 +1,106 @@
1# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
2#
3# Copyright (C) 2015 Canonical Ltd.
4#
5# This program is free software; you can redistribute it and/or modify
6# it under the terms of the GNU General Public License version 3, as published
7# by the Free Software Foundation.
8#
9# This program is distributed in the hope that it will be useful,
10# but WITHOUT ANY WARRANTY; without even the implied warranty of
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12# GNU General Public License for more details.
13#
14# You should have received a copy of the GNU General Public License
15# along with this program. If not, see <http://www.gnu.org/licenses/>.
16
17import logging
18import ubuntuuitoolkit as uitk
19
20from autopilot import logging as autopilot_logging
21
22logger = logging.getLogger(__name__)
23
24
25class MainView(uitk.MainView):
26
27 def _tap(self, objectName):
28 """Find a widget and tap on it."""
29 item = self.wait_select_single(objectName=objectName)
30 if item.enabled:
31 self.pointing_device.click_object(item)
32
33 def _type_text(self, objectName, text):
34 """Find a text widget and enter some text in it."""
35 item = self.wait_select_single(
36 uitk.TextField, objectName=objectName)
37 item.write(text)
38
39 @autopilot_logging.log_action(logger.info)
40 def cancel(self):
41 """Tap on the 'Cancel' button."""
42 self._tap('cancelButton')
43
44 @autopilot_logging.log_action(logger.info)
45 def buy(self):
46 """Tap on the 'Buy Now' button."""
47 self._tap('buyButton')
48
49 @autopilot_logging.log_action(logger.info)
50 def enter_password(self, password):
51 """Type the password into the entry field."""
52 self._type_text('passwordField', password)
53
54 @autopilot_logging.log_action(logger.info)
55 def tap_on_webview(self):
56 """Tap in the center of the web view."""
57 self._tap('webView')
58
59 @autopilot_logging.log_action(logger.info)
60 def open_add_card_page(self):
61 """Open the 'Add Credit/Debit Card' page.
62
63 :return the Add Credit/Debit Card page.
64 """
65 self._tap('addCreditCardLabel')
66 return self.wait_select_single(objectName='pageWebkit')
67
68 @autopilot_logging.log_action(logger.info)
69 def get_payment_types(self):
70 """Get the payment types option selector widget.
71
72 :return the Payment Types option selector widget.
73 """
74 return self.wait_select_single(objectName='paymentTypes')
75
76 @autopilot_logging.log_action(logger.info)
77 def get_checkout_page(self):
78 """Get the widget for the Checkout page.
79
80 :return the Checkout page widget."""
81 return self.wait_select_single(objectName='pageCheckout')
82
83 @autopilot_logging.log_action(logger.info)
84 def tap_dialog_ok_button(self):
85 """Tap the 'OK' button in the dialog."""
86 self._tap('dialogOkButton')
87
88 @autopilot_logging.log_action(logger.info)
89 def tap_dialog_cancel_button(self):
90 """Tap the 'Cancel' button in the dialog."""
91 self._tap('dialogCancelButton')
92
93 @autopilot_logging.log_action(logger.info)
94 def tap_dialog_leave_button(self):
95 """Tap the 'Leave' button in the dialog."""
96 self._tap('leaveButton')
97
98 @autopilot_logging.log_action(logger.info)
99 def tap_dialog_stay_button(self):
100 """Tap the 'Stay' button in the dialog."""
101 self._tap('stayButton')
102
103 @autopilot_logging.log_action(logger.info)
104 def input_dialog_text(self, text):
105 """Type the text into the dialog text entry field."""
106 self._type_text('dialogInput', text)
0107
=== added directory 'pay-ui/tests/autopilot/pay_ui/tests'
=== added file 'pay-ui/tests/autopilot/pay_ui/tests/__init__.py'
--- pay-ui/tests/autopilot/pay_ui/tests/__init__.py 1970-01-01 00:00:00 +0000
+++ pay-ui/tests/autopilot/pay_ui/tests/__init__.py 2016-03-11 14:49:24 +0000
@@ -0,0 +1,77 @@
1# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
2#
3# Copyright (C) 2014-2016 Canonical Ltd.
4#
5# This program is free software; you can redistribute it and/or modify
6# it under the terms of the GNU General Public License version 3, as published
7# by the Free Software Foundation.
8#
9# This program is distributed in the hope that it will be useful,
10# but WITHOUT ANY WARRANTY; without even the implied warranty of
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12# GNU General Public License for more details.
13#
14# You should have received a copy of the GNU General Public License
15# along with this program. If not, see <http://www.gnu.org/licenses/>.
16
17import fixtures
18import json
19import os
20import shutil
21import subprocess
22import tempfile
23import ubuntuuitoolkit as uitk
24
25import pay_ui
26
27
28class BasePayUITestCase(uitk.base.UbuntuUIToolkitAppTestCase):
29 """Base Autopilot test case for the Pay UI project."""
30
31 dirpath = None
32
33 def setUp(self):
34 super().setUp()
35 build_dir = os.environ.get('BUILD_DIR', None)
36 source_dir = os.environ.get('SOURCE_DIR', None)
37 self.app = self.launch_application(build_dir, source_dir)
38
39 def create_config_dir(self):
40 self.dirpath = tempfile.mkdtemp()
41 self.useFixture(fixtures.EnvironmentVariable(
42 'XDG_DATA_HOME', self.dirpath))
43
44 def clean_config_dir(self):
45 if self.dirpath:
46 shutil.rmtree(self.dirpath)
47
48 def launch_application(self, build_dir=None, source_dir=None):
49 if build_dir is None:
50 return self.launch_installed_app()
51 else:
52 return self.launch_built_application(build_dir, source_dir)
53
54 def launch_installed_app(self):
55 return self.launch_test_application(
56 '/usr/lib/payui/pay-ui',
57 app_type='qt',
58 emulator_base=uitk.UbuntuUIToolkitCustomProxyObjectBase)
59
60 def launch_built_application(self, build_dir, source_dir):
61 built_import_path = os.path.join(build_dir, 'backend')
62 self.useFixture(
63 fixtures.EnvironmentVariable(
64 'QML2_IMPORT_PATH', newvalue=built_import_path))
65 main_qml_path = os.path.join(source_dir, 'app', 'payui.qml')
66 return self.launch_test_application(
67 uitk.base.get_qmlscene_launch_command(),
68 main_qml_path,
69 '--transparent',
70 'purchase://com.example.testapp',
71 app_type='qt',
72 emulator_base=uitk.UbuntuUIToolkitCustomProxyObjectBase)
73
74 @property
75 def main_view(self):
76 """Return main view"""
77 return self.app.select_single(pay_ui.MainView)
078
=== added file 'pay-ui/tests/autopilot/pay_ui/tests/mock_server.py'
--- pay-ui/tests/autopilot/pay_ui/tests/mock_server.py 1970-01-01 00:00:00 +0000
+++ pay-ui/tests/autopilot/pay_ui/tests/mock_server.py 2016-03-11 14:49:24 +0000
@@ -0,0 +1,337 @@
1# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
2#
3# Copyright (C) 2014 Canonical Ltd.
4#
5# This program is free software; you can redistribute it and/or modify
6# it under the terms of the GNU General Public License version 3, as published
7# by the Free Software Foundation.
8#
9# This program is distributed in the hope that it will be useful,
10# but WITHOUT ANY WARRANTY; without even the implied warranty of
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12# GNU General Public License for more details.
13#
14# You should have received a copy of the GNU General Public License
15# along with this program. If not, see <http://www.gnu.org/licenses/>.
16
17import json
18import socket
19import threading
20from http.server import BaseHTTPRequestHandler, HTTPServer
21
22
23html_success = """
24<html>
25 <body bgcolor="green" onClick="window.location.assign('/paymentmethods/completeadd')">
26 <h1>Placeholder for web interaction</h1>
27 <p>Click anywhere to proceed</p>
28 </body>
29</html>"
30"""
31
32html_cancel = """
33<html>
34 <body bgcolor="red" onClick="window.location.assign('/api/2.0/click/cancelled')">
35 <h1>Placeholder for web interaction</h1>
36 <p>Click anywhere to cancel</p>
37 </body>
38</html>"
39"""
40
41html_completed = """
42<html>
43 <body onLoad="window.location.assign('/click/succeeded')">
44 </body>
45</html>
46"""
47
48html_beforeunload = """
49<html>
50 <script language="javascript">
51 window.onbeforeunload = function() {
52 return 'Really want to add your card?'
53 }
54 window.onload = function() {
55 window.location.assign('/click/cancelled')
56 }
57 window.onclick = function() {
58 window.onbeforeunload = null;
59 window.location.assign('/paymentmethods/completeadd')
60 }
61 </script>
62 <body bgcolor="yellow">
63 <h1>Placeholder for web interaction</h1>
64 <p>Click 'Stay' and then anywhere to proceed.</p>
65 </body>
66</html>
67"""
68
69html_alert = """
70<html>
71 <body onClick="window.location.assign('/paymentmethods/completeadd')"
72 onLoad="alert('Click OK to add your card.')">
73 <h1>Placeholder for web interaction</h1>
74 <p>Click anywhere to proceed</p>
75 </body>
76</html>
77"""
78
79html_confirm = """
80<html>
81 <script language="javascript">
82 window.onload = function() {
83 if (window.confirm('Do you want to add your card?')) {
84 window.location.assign('/paymentmethods/completeadd')
85 } else {
86 window.location.assign('/click/cancelled')
87 }
88 }
89 </script>
90 <body bgcolor="pink">
91 <h1>Placeholder for web interaction</h1>
92 <p>Click ok to add card, or cancel to not.</p>
93 </body>
94</html>
95"""
96
97html_prompt = """
98<html>
99 <script language="javascript">
100 window.onload = function() {
101 if (window.prompt('Speak friend and enter') == 'friend') {
102 window.location.assign('/paymentmethods/completeadd')
103 } else {
104 window.location.assign('/click/cancelled')
105 }
106 }
107 </script>
108 <body bgcolor="pink">
109 <h1>Placeholder for web interaction</h1>
110 <p>Type friend and ok to add card.</p>
111 </body>
112</html>
113"""
114
115
116class MyHandler(BaseHTTPRequestHandler):
117
118 def do_HEAD(self):
119 self.send_response(200)
120 self.send_header("X-Click-Token", "X-Click-Token")
121 self.end_headers()
122
123 def response_payment_types(self, fail=False):
124 if fail:
125 self.send_response(404)
126 else:
127 self.send_response(200)
128 self.send_header("Content-type", "application/json")
129 self.end_headers()
130 self.wfile.write(bytes(json.dumps(self.server.payment_types), 'UTF-8'))
131
132 def interaction_html(self):
133 return html_cancel if self.server.interaction_cancel else html_success
134
135 def response_cc_interaction(self, fail=False):
136 if fail:
137 self.send_response(404)
138 else:
139 self.send_response(200)
140 self.send_header("Content-type", "text/html")
141 self.end_headers()
142 self.wfile.write(bytes(self.interaction_html(), 'UTF-8'))
143
144 def response_payment_add(self, fail=False):
145 if fail:
146 self.send_response(404)
147 else:
148 self.send_response(200)
149 self.send_header("Content-type", "text/html")
150 self.end_headers()
151 test = self.path.split('/')[1]
152 if test.find('js_alert') != -1:
153 self.wrile.write(bytes(html_alert, 'UTF-8'))
154 elif test.find('js_beforeunload') != -1:
155 self.wfile.write(bytes(html_beforeunload, 'UTF-8'))
156 elif test.find('js_confirm') != -1:
157 self.wfile.write(bytes(html_confirm, 'UTF-8'))
158 elif test.find('js_prompt') != -1:
159 self.wfile.write(bytes(html_prompt, 'UTF-8'))
160 else:
161 self.wfile.write(bytes(self.interaction_html(), 'UTF-8'))
162
163 def response_payment_add_complete(self):
164 """Add the new payment info to the list."""
165 self.server.payment_types[1]["choices"].append({
166 "currencies": ["USD"],
167 "id": 1999,
168 "requires_interaction": False,
169 "preferred": False,
170 "description": "Yet another payment method"
171 })
172 self.send_response(200)
173 self.send_header("Content-type", "text/html")
174 self.end_headers()
175 self.wfile.write(bytes(html_completed, 'UTF-8'))
176
177 def response_buy_item(self):
178 response = {
179 "state": "Complete",
180 }
181 if self.server.buy_cc_interaction:
182 response["state"] = "InProgress"
183 response["redirect_to"] = self.server.buy_cc_interaction
184 if self.server.fail:
185 response = {}
186 self.send_response(200)
187 self.send_header("Content-type", "application/json")
188 self.end_headers()
189 try:
190 self.wfile.write(bytes(json.dumps(response), 'UTF-8'))
191 except socket.error:
192 pass
193
194 def response_update_credentials(self, fail=False):
195 response = {
196 "token_key": "token_key",
197 "token_secret": "token_secret",
198 "consumer_key": "consumer_key",
199 "consumer_secret": "consumer_secret",
200 }
201 if fail:
202 self.send_response(404)
203 else:
204 self.send_response(200)
205 self.send_header("Content-type", "application/json")
206 self.end_headers()
207 self.wfile.write(bytes(json.dumps(response), 'UTF-8'))
208
209 def response_item_info(self, fail=False):
210 response = {
211 "title": "title",
212 "publisher": "publisher",
213 "icon_url": "icon_url",
214 "prices": {
215 "USD": 2.99,
216 "EUR": 2.49,
217 "GBP": 1.99,
218 },
219 }
220 if fail:
221 self.send_response(404)
222 else:
223 self.send_response(200)
224 self.send_header("Content-type", "application/json")
225 self.end_headers()
226 self.wfile.write(bytes(json.dumps(response), 'UTF-8'))
227
228 def response_auth_error(self):
229 self.send_response(401)
230 self.send_header("Content-type", "application/json")
231 self.end_headers()
232 self.wfile.write(bytes(json.dumps(dict()), 'UTF-8'))
233
234 def do_POST(self):
235 """Respond to a POST request."""
236 #content = self.rfile.read(int(self.headers.get('content-length')))
237 #structure = json.loads(content.decode('utf-8'))
238 if self.path.find("purchases/") != -1:
239 self.response_buy_item()
240
241 def do_GET(self):
242 """Respond to a GET request."""
243 fail = self.path.find("/fail/") != -1
244 if self.path.find("paymentmethods/add/") != -1:
245 self.response_payment_add()
246 elif self.path.find("paymentmethods/completeadd") != -1:
247 self.response_payment_add_complete()
248 elif self.path.find("paymentmethods/") != -1:
249 self.response_payment_types(fail)
250 elif self.path.find("creditcard_interaction/") != -1:
251 self.response_cc_interaction()
252 elif self.path.find("purchases/") != -1:
253 self.response_buy_item()
254 elif self.path.find("creds/") != -1 or self.path.find("wallet/") != -1:
255 self.response_update_credentials(fail)
256 elif self.path.find("iteminfo/") != -1:
257 self.response_item_info(fail)
258 elif self.path.find("/authError/") != -1:
259 self.response_auth_error()
260
261
262def initial_payment_types():
263 return [
264 {
265 "description": "PayPal",
266 "id": "paypal",
267 "preferred": False,
268 "choices": [
269 {
270 "currencies": [
271 "USD",
272 "GBP",
273 "EUR"
274 ],
275 "id": 532,
276 "requires_interaction": False,
277 "preferred": False,
278 "description": ("PayPal Preapproved Payment "
279 "(exp. 2014-04-12)")
280 }
281 ]
282 },
283 {
284 "description": "Credit or Debit Card",
285 "id": "credit_card",
286 "preferred": True,
287 "choices": [
288 {
289 "currencies": [
290 "USD"
291 ],
292 "id": 1767,
293 "requires_interaction": False,
294 "preferred": False,
295 "description": ("**** **** **** 1111 "
296 "(Visa, exp. 02/2015)")
297 },
298 {
299 "currencies": [
300 "USD"
301 ],
302 "id": 1726,
303 "requires_interaction": False,
304 "preferred": True,
305 "description": ("**** **** **** 1111 "
306 "(Visa, exp. 03/2015)")
307 }
308 ]
309 }
310 ]
311
312
313class MockServer:
314 def __init__(self):
315 server_address = ('', 0)
316 self.server = HTTPServer(server_address, MyHandler)
317 tcp_port = self.server.socket.getsockname()[1]
318 self.base_url = "http://127.0.0.1:%d/" % tcp_port
319 server_thread = threading.Thread(target=self.server.serve_forever)
320 server_thread.start()
321 self.server.payment_types = initial_payment_types()
322 self.server.fail = False
323 self.server.buy_cc_interaction = None
324 self.server.interaction_cancel = False
325
326 def set_purchase_needs_cc_interaction(self):
327 # Real server returns path starting with / here, not full URL.
328 self.server.buy_cc_interaction = "/creditcard_interaction"
329
330 def set_interaction_result_cancelled(self):
331 self.server.interaction_cancel = True
332
333 def url(self, tail=""):
334 return self.base_url + tail
335
336 def shutdown(self):
337 self.server.shutdown()
0338
=== added file 'pay-ui/tests/autopilot/pay_ui/tests/test_pay_ui.py'
--- pay-ui/tests/autopilot/pay_ui/tests/test_pay_ui.py 1970-01-01 00:00:00 +0000
+++ pay-ui/tests/autopilot/pay_ui/tests/test_pay_ui.py 2016-03-11 14:49:24 +0000
@@ -0,0 +1,186 @@
1# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
2#
3# Copyright (C) 2014-2016 Canonical Ltd.
4#
5# This program is free software; you can redistribute it and/or modify
6# it under the terms of the GNU General Public License version 3, as published
7# by the Free Software Foundation.
8#
9# This program is distributed in the hope that it will be useful,
10# but WITHOUT ANY WARRANTY; without even the implied warranty of
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12# GNU General Public License for more details.
13#
14# You should have received a copy of the GNU General Public License
15# along with this program. If not, see <http://www.gnu.org/licenses/>.
16
17import fixtures
18import testtools
19
20from testtools.matchers import Equals, NotEquals
21from autopilot.matchers import Eventually
22
23from pay_ui import tests
24from pay_ui.tests import mock_server
25
26
27class PayUITestCase(tests.BasePayUITestCase):
28
29 def setUp(self):
30 self.clean_config_dir()
31 self.mock_server = mock_server.MockServer()
32 self.addCleanup(self.mock_server.shutdown)
33 self.useFixture(fixtures.EnvironmentVariable(
34 'PAY_BASE_URL',
35 self.mock_server.url() + '/' + self.id().split('.')[-1]))
36 self.useFixture(fixtures.EnvironmentVariable(
37 'U1_SEARCH_BASE_URL', self.mock_server.url('iteminfo/')))
38 self.useFixture(fixtures.EnvironmentVariable(
39 'SSO_AUTH_BASE_URL', self.mock_server.url('login/')))
40 self.useFixture(fixtures.EnvironmentVariable('GET_CREDENTIALS', '0'))
41 self.create_config_dir()
42 self.addCleanup(self.clean_config_dir)
43 super().setUp()
44
45 def app_returncode(self):
46 return self.app.process.wait(timeout=30)
47
48 def test_ui_initialized(self):
49 main = self.main_view
50 self.assertThat(main, NotEquals(None))
51
52 def test_cancel_purchase(self):
53 self.main_view.cancel()
54 self.assertThat(self.app_returncode(), Equals(1))
55
56 def test_basic_purchase(self):
57 self.skipTest('Mouse clicks on buyButton not registering.')
58 self.main_view.enter_password('password123')
59 self.main_view.buy()
60 self.assertThat(self.app_returncode(), Equals(0))
61
62 def test_add_credit_card_completed(self):
63 payment_types = self.main_view.get_payment_types()
64 self.assertThat(payment_types.get_option_count(), Equals(3))
65 self.main_view.open_add_card_page()
66 self.take_screenshot('add_card_page')
67 self.main_view.tap_on_webview()
68 self.assertThat(payment_types.get_option_count, Eventually(Equals(4)))
69
70 def test_add_credit_card_returns_on_cancel(self):
71 self.mock_server.set_interaction_result_cancelled()
72 payment_types = self.main_view.get_payment_types()
73 self.assertThat(payment_types.get_option_count(), Equals(3))
74 self.main_view.open_add_card_page()
75 self.take_screenshot('add_card_page')
76 self.main_view.tap_on_webview()
77 checkout_page = self.main_view.get_checkout_page()
78 self.assertThat(checkout_page.get_properties()["active"],
79 Eventually(Equals(True)))
80
81 def test_add_credit_card_cancelled(self):
82 self.mock_server.set_interaction_result_cancelled()
83 payment_types = self.main_view.get_payment_types()
84 self.assertThat(payment_types.get_option_count(), Equals(3))
85 self.main_view.open_add_card_page()
86 self.take_screenshot('add_card_page')
87 self.main_view.tap_on_webview()
88 self.assertThat(payment_types.get_option_count, Eventually(Equals(3)))
89
90 @testtools.skip('JS Alert dialog seems to not work.')
91 def test_add_credit_card_js_alert(self):
92 payment_types = self.main_view.get_payment_types()
93 self.assertThat(payment_types.get_option_count(), Equals(3))
94 self.main_view.open_add_card_page()
95 self.take_screenshot('add_card_page')
96 self.main_view.tap_dialog_ok_button()
97 self.assertThat(payment_types.get_option_count, Eventually(Equals(4)))
98
99 def test_add_credit_card_js_beforeunload(self):
100 payment_types = self.main_view.get_payment_types()
101 self.assertThat(payment_types.get_option_count(), Equals(3))
102 self.main_view.open_add_card_page()
103 self.take_screenshot('add_card_page')
104 self.main_view.tap_dialog_stay_button()
105 self.main_view.tap_on_webview()
106 self.assertThat(payment_types.get_option_count, Eventually(Equals(4)))
107
108 @testtools.skip('Clicking add card link second seems to not work.')
109 def test_add_credit_card_js_beforeunload_twice(self):
110 payment_types = self.main_view.get_payment_types()
111 self.assertThat(payment_types.get_option_count(), Equals(3))
112 self.main_view.open_add_card_page()
113 self.take_screenshot('add_card_page')
114 self.main_view.tap_dialog_leave_button()
115 self.take_screenshot('beforeunload_left')
116 self.main_view.open_add_card_page()
117 self.take_screenshot('beforeunload_back')
118 self.main_view.tap_dialog_stay_button()
119 self.main_view.tap_on_web_view()
120 self.assertThat(payment_types.get_option_count, Eventually(Equals(4)))
121
122 def test_add_credit_card_js_beforeunload_cancelled(self):
123 payment_types = self.main_view.get_payment_types()
124 self.assertThat(payment_types.get_option_count(), Equals(3))
125 self.main_view.open_add_card_page()
126 self.take_screenshot('add_card_page')
127 self.main_view.tap_dialog_leave_button()
128 self.take_screenshot('beforeunload_cancelled')
129 self.assertThat(payment_types.get_option_count, Eventually(Equals(3)))
130
131 def test_add_credit_card_js_confirm_ok(self):
132 payment_types = self.main_view.get_payment_types()
133 self.assertThat(payment_types.get_option_count(), Equals(3))
134 self.main_view.open_add_card_page()
135 self.take_screenshot('add_card_page')
136 self.main_view.tap_dialog_ok_button()
137 self.assertThat(payment_types.get_option_count, Eventually(Equals(4)))
138
139 def test_add_credit_card_js_confirm_cancel(self):
140 payment_types = self.main_view.get_payment_types()
141 self.assertThat(payment_types.get_option_count(), Equals(3))
142 self.main_view.open_add_card_page()
143 self.take_screenshot('add_card_page')
144 self.main_view.tap_dialog_cancel_button()
145 self.assertThat(payment_types.get_option_count, Eventually(Equals(3)))
146
147 def test_add_credit_card_js_prompt_ok(self):
148 payment_types = self.main_view.get_payment_types()
149 self.assertThat(payment_types.get_option_count(), Equals(3))
150 self.main_view.open_add_card_page()
151 self.take_screenshot('add_card_page')
152 self.main_view.input_dialog_text('friend')
153 self.main_view.tap_dialog_ok_button()
154 self.assertThat(payment_types.get_option_count, Eventually(Equals(4)))
155
156 def test_add_credit_card_js_prompt_ok_wrong_text(self):
157 payment_types = self.main_view.get_payment_types()
158 self.assertThat(payment_types.get_option_count(), Equals(3))
159 self.main_view.open_add_card_page()
160 self.take_screenshot('add_card_page')
161 self.main_view.input_dialog_text('amigo')
162 self.main_view.tap_dialog_ok_button()
163 self.assertThat(payment_types.get_option_count, Eventually(Equals(3)))
164
165 def test_add_credit_card_js_prompt_cancel(self):
166 payment_types = self.main_view.get_payment_types()
167 self.assertThat(payment_types.get_option_count(), Equals(3))
168 self.main_view.open_add_card_page()
169 self.take_screenshot('add_card_page')
170 self.main_view.tap_dialog_cancel_button()
171 self.assertThat(payment_types.get_option_count, Eventually(Equals(3)))
172
173 def test_purchase_with_web_interaction_completed(self):
174 self.skipTest('Mouse clicks on buyButton not registering.')
175 self.mock_server.set_purchase_needs_cc_interaction()
176 self.main_view.buy()
177 self.main_view.tap_on_webview()
178 self.assertThat(self.app_returncode(), Equals(0))
179
180 def test_purchase_with_web_interaction_cancelled(self):
181 self.skipTest('Mouse clicks on buyButton not registering.')
182 self.mock_server.set_purchase_needs_cc_interaction()
183 self.mock_server.set_interaction_result_cancelled()
184 self.main_view.buy()
185 self.main_view.tap_on_webview()
186 self.assertThat(self.app_returncode(), Equals(1))
0187
=== added file 'pay-ui/tests/autopilot/run_autopilot'
--- pay-ui/tests/autopilot/run_autopilot 1970-01-01 00:00:00 +0000
+++ pay-ui/tests/autopilot/run_autopilot 2016-03-11 14:49:24 +0000
@@ -0,0 +1,1 @@
1GET_CREDENTIALS=0 autopilot3 run -v pay_ui
02
=== modified file 'po/CMakeLists.txt'
--- po/CMakeLists.txt 2015-12-11 19:49:20 +0000
+++ po/CMakeLists.txt 2016-03-11 14:49:24 +0000
@@ -15,7 +15,7 @@
15# Creates the .pot file containing the translations template15# Creates the .pot file containing the translations template
16set(INTLTOOL_ENV16set(INTLTOOL_ENV
17 XGETTEXT="${GETTEXT_XGETTEXT_EXECUTABLE}"17 XGETTEXT="${GETTEXT_XGETTEXT_EXECUTABLE}"
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_"
19 srcdir="${CMAKE_CURRENT_SOURCE_DIR}"19 srcdir="${CMAKE_CURRENT_SOURCE_DIR}"
20)20)
21add_custom_target(${POT_FILE}21add_custom_target(${POT_FILE}
2222
=== modified file 'po/POTFILES.in'
--- po/POTFILES.in 2015-12-11 19:49:20 +0000
+++ po/POTFILES.in 2016-03-11 14:49:24 +0000
@@ -1,1 +1,9 @@
1pay-ui/app/components/ConfirmDialog.qml
2pay-ui/app/components/PromptDialog.qml
3pay-ui/app/components/BeforeUnloadDialog.qml
4pay-ui/app/components/SecurityCertificatePopover.qml
5pay-ui/app/components/AlertDialog.qml
6pay-ui/app/ui/CheckoutPage.qml
7pay-ui/app/ui/ErrorDialog.qml
8pay-ui/app/payui.qml
1service-ng/src/pay-service-2/service/pay_service.go9service-ng/src/pay-service-2/service/pay_service.go
210
=== added file 'po/aa.po'
--- po/aa.po 1970-01-01 00:00:00 +0000
+++ po/aa.po 2016-03-11 14:49:24 +0000
@@ -0,0 +1,106 @@
1# Afar translation for pay-ui
2# Copyright (c) 2015 Rosetta Contributors and Canonical Ltd 2015
3# This file is distributed under the same license as the pay-ui package.
4# FIRST AUTHOR <EMAIL@ADDRESS>, 2015.
5#
6msgid ""
7msgstr ""
8"Project-Id-Version: pay-service\n"
9"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
10"POT-Creation-Date: 2015-02-19 15:01-0500\n"
11"PO-Revision-Date: 2015-04-26 13:56+0000\n"
12"Last-Translator: Charif AYFARAH <ayfarah@ymail.com>\n"
13"Language-Team: Afar <aa@li.org>\n"
14"MIME-Version: 1.0\n"
15"Content-Type: text/plain; charset=UTF-8\n"
16"Content-Transfer-Encoding: 8bit\n"
17"X-Launchpad-Export-Date: 2015-12-25 05:38+0000\n"
18"X-Generator: Launchpad (build 17865)\n"
19
20#: ../app/payui.qml:148
21msgid "Finish Purchase"
22msgstr "Limok gaba kal"
23
24#: ../app/payui.qml:163
25msgid "Incorrect Password, please try again."
26msgstr "Cuumiti makot yan, maganak kaadu gabbat."
27
28#: ../app/payui.qml:276
29msgid "Purchase failed"
30msgstr "Limo makkinna"
31
32#: ../app/payui.qml:277
33msgid "The purchase couldn't be completed."
34msgstr "limo ma duuduminna."
35
36#: ../app/payui.qml:292
37msgid "Error contacting the server"
38msgstr "Yayfaayi angaaraw soka le"
39
40#: ../app/payui.qml:293 ../app/payui.qml:309
41msgid "Do you want to try again?"
42msgstr "Kaadu gabbattam maay faxxa?"
43
44#: ../app/payui.qml:308
45msgid "Adding Credit Card failed"
46msgstr "Abuúd Karti edde osisaanam makkinna"
47
48#: ../app/payui.qml:327
49msgid "Processing Purchase"
50msgstr "Limô gexso"
51
52#: ../app/payui.qml:327
53msgid "Loading"
54msgstr "Qulluumah"
55
56#: ../app/payui.qml:328
57msgid "Please wait…"
58msgstr "Maganak qambal..."
59
60#: ../app/payui.qml:342 ../app/ui/CheckoutPage.qml:370
61msgid "Cancel"
62msgstr "Bayis"
63
64#: ../app/payui.qml:401
65msgid "Add Payment"
66msgstr "Mekla edde osis"
67
68#: ../app/ui/CheckoutPage.qml:27
69msgid "Payment"
70msgstr "Mekla"
71
72#: ../app/ui/CheckoutPage.qml:228
73msgid "Enter your Ubuntu One password"
74msgstr "Ubuntu One cuumita culus"
75
76#: ../app/ui/CheckoutPage.qml:271
77msgid "Type your verification code:"
78msgstr "Mudenti diggoyso culus:"
79
80#: ../app/ui/CheckoutPage.qml:282
81msgid "2-factor device code"
82msgstr "2-afeetah aalatih mudenta"
83
84#: ../app/ui/CheckoutPage.qml:379
85msgid "Buy Now"
86msgstr "Away xaamit"
87
88#: ../app/ui/CheckoutPage.qml:393
89msgid "Add credit/debit card"
90msgstr "Abuú/Raci karti edde osis"
91
92#: ../app/ui/ErrorDialog.qml:45
93msgid "Retry"
94msgstr "Qagis"
95
96#: ../app/ui/ErrorDialog.qml:55
97msgid "Close"
98msgstr "Alif"
99
100#: payui_payui.desktop.in.in.h:1
101msgid "Pay UI"
102msgstr "UI mekel"
103
104#: payui_payui.desktop.in.in.h:2
105msgid "The application for completing a purchase."
106msgstr "Limô duddoh abnisso"
0107
=== added file 'po/am.po'
--- po/am.po 1970-01-01 00:00:00 +0000
+++ po/am.po 2016-03-11 14:49:24 +0000
@@ -0,0 +1,106 @@
1# Amharic translation for pay-ui
2# Copyright (c) 2014 Rosetta Contributors and Canonical Ltd 2014
3# This file is distributed under the same license as the pay-ui package.
4# FIRST AUTHOR <EMAIL@ADDRESS>, 2014.
5#
6msgid ""
7msgstr ""
8"Project-Id-Version: pay-service\n"
9"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
10"POT-Creation-Date: 2015-02-19 15:01-0500\n"
11"PO-Revision-Date: 2015-03-03 14:18+0000\n"
12"Last-Translator: samson <Unknown>\n"
13"Language-Team: Amharic <am@li.org>\n"
14"MIME-Version: 1.0\n"
15"Content-Type: text/plain; charset=UTF-8\n"
16"Content-Transfer-Encoding: 8bit\n"
17"X-Launchpad-Export-Date: 2015-12-25 05:38+0000\n"
18"X-Generator: Launchpad (build 17865)\n"
19
20#: ../app/payui.qml:148
21msgid "Finish Purchase"
22msgstr "ግዢውን መጨረሻ"
23
24#: ../app/payui.qml:163
25msgid "Incorrect Password, please try again."
26msgstr "የተሳሳተ የመግቢያ ቃል እባክዎን እንደገና ይሞክሩ"
27
28#: ../app/payui.qml:276
29msgid "Purchase failed"
30msgstr "ግዢው አልተሳካም"
31
32#: ../app/payui.qml:277
33msgid "The purchase couldn't be completed."
34msgstr "ግዢውን መፈጸም አልተቻለም"
35
36#: ../app/payui.qml:292
37msgid "Error contacting the server"
38msgstr "ስህተት ተፈጥሯል ሰርቨሩን በመገናኘት ላይ እንዳለ"
39
40#: ../app/payui.qml:293 ../app/payui.qml:309
41msgid "Do you want to try again?"
42msgstr "እንደገና መሞከር ይፈልጋሉ?"
43
44#: ../app/payui.qml:308
45msgid "Adding Credit Card failed"
46msgstr "የ ክሬዲር ካርድ መጨመር አልተቻለም"
47
48#: ../app/payui.qml:327
49msgid "Processing Purchase"
50msgstr "ግዢውን በማካሄድ ላይ"
51
52#: ../app/payui.qml:327
53msgid "Loading"
54msgstr "በመጫን ላይ"
55
56#: ../app/payui.qml:328
57msgid "Please wait…"
58msgstr "እባክዎን ይጠብቁ…"
59
60#: ../app/payui.qml:342 ../app/ui/CheckoutPage.qml:370
61msgid "Cancel"
62msgstr "መሰረዣ"
63
64#: ../app/payui.qml:401
65msgid "Add Payment"
66msgstr "ክፍያውን መጨመሪያ"
67
68#: ../app/ui/CheckoutPage.qml:27
69msgid "Payment"
70msgstr "ክፍያ"
71
72#: ../app/ui/CheckoutPage.qml:228
73msgid "Enter your Ubuntu One password"
74msgstr "የ እርስዎን የ ኡቡንቱ ዋን የ መግቢያ ቃል ያስገቡ"
75
76#: ../app/ui/CheckoutPage.qml:271
77msgid "Type your verification code:"
78msgstr "የ እርስዎን ማረጋገጫ ኮድ ይጻፉ:"
79
80#: ../app/ui/CheckoutPage.qml:282
81msgid "2-factor device code"
82msgstr "2-ፋክተር የ አካል ኮድ"
83
84#: ../app/ui/CheckoutPage.qml:379
85msgid "Buy Now"
86msgstr "አሁን መግዣ"
87
88#: ../app/ui/CheckoutPage.qml:393
89msgid "Add credit/debit card"
90msgstr "ክሬዲት/ዴቢት ካርድ መጨመሪያ"
91
92#: ../app/ui/ErrorDialog.qml:45
93msgid "Retry"
94msgstr "እንደገና ይሞክሩ"
95
96#: ../app/ui/ErrorDialog.qml:55
97msgid "Close"
98msgstr "መዝጊያ"
99
100#: payui_payui.desktop.in.in.h:1
101msgid "Pay UI"
102msgstr "መክፈያ UI"
103
104#: payui_payui.desktop.in.in.h:2
105msgid "The application for completing a purchase."
106msgstr "መተግበሪያ ግዢውን ለመፈጸም"
0107
=== added file 'po/ast.po'
--- po/ast.po 1970-01-01 00:00:00 +0000
+++ po/ast.po 2016-03-11 14:49:24 +0000
@@ -0,0 +1,106 @@
1# Asturian translation for pay-ui
2# Copyright (c) 2014 Rosetta Contributors and Canonical Ltd 2014
3# This file is distributed under the same license as the pay-ui package.
4# FIRST AUTHOR <EMAIL@ADDRESS>, 2014.
5#
6msgid ""
7msgstr ""
8"Project-Id-Version: pay-service\n"
9"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
10"POT-Creation-Date: 2015-02-19 15:01-0500\n"
11"PO-Revision-Date: 2015-06-09 15:40+0000\n"
12"Last-Translator: enolp <enolp@softastur.org>\n"
13"Language-Team: Asturian <ast@li.org>\n"
14"MIME-Version: 1.0\n"
15"Content-Type: text/plain; charset=UTF-8\n"
16"Content-Transfer-Encoding: 8bit\n"
17"X-Launchpad-Export-Date: 2015-12-25 05:38+0000\n"
18"X-Generator: Launchpad (build 17865)\n"
19
20#: ../app/payui.qml:148
21msgid "Finish Purchase"
22msgstr "Acabar la compra"
23
24#: ../app/payui.qml:163
25msgid "Incorrect Password, please try again."
26msgstr "La contraseña ye incorreuta. Inténtalo de nueves."
27
28#: ../app/payui.qml:276
29msgid "Purchase failed"
30msgstr "Falló la compra"
31
32#: ../app/payui.qml:277
33msgid "The purchase couldn't be completed."
34msgstr "Nun pudo completase la compra"
35
36#: ../app/payui.qml:292
37msgid "Error contacting the server"
38msgstr "Fallu al contautar col sirvidor"
39
40#: ../app/payui.qml:293 ../app/payui.qml:309
41msgid "Do you want to try again?"
42msgstr "¿Quies intentalo otra vegada?"
43
44#: ../app/payui.qml:308
45msgid "Adding Credit Card failed"
46msgstr "Fallu al amestar tarxeta de creitu"
47
48#: ../app/payui.qml:327
49msgid "Processing Purchase"
50msgstr "Procesando la compra"
51
52#: ../app/payui.qml:327
53msgid "Loading"
54msgstr "Cargando"
55
56#: ../app/payui.qml:328
57msgid "Please wait…"
58msgstr "Espera, por favor..."
59
60#: ../app/payui.qml:342 ../app/ui/CheckoutPage.qml:370
61msgid "Cancel"
62msgstr "Encaboxar"
63
64#: ../app/payui.qml:401
65msgid "Add Payment"
66msgstr "Amestar pagu"
67
68#: ../app/ui/CheckoutPage.qml:27
69msgid "Payment"
70msgstr "Pagu"
71
72#: ../app/ui/CheckoutPage.qml:228
73msgid "Enter your Ubuntu One password"
74msgstr "Introduz la to contraseña d'Ubuntu One"
75
76#: ../app/ui/CheckoutPage.qml:271
77msgid "Type your verification code:"
78msgstr "Escribi'l códigu de comprobación:"
79
80#: ../app/ui/CheckoutPage.qml:282
81msgid "2-factor device code"
82msgstr "códigu de preséu 2-factor"
83
84#: ../app/ui/CheckoutPage.qml:379
85msgid "Buy Now"
86msgstr "Mercar Agora"
The diff has been truncated for viewing.

Subscribers

People subscribed via source and target branches

to all changes: