Merge lp:~dobey/pay-ui/iap-support into lp:pay-ui
- iap-support
- Merge into first-branch
Status: | Merged |
---|---|
Approved by: | dobey |
Approved revision: | 134 |
Merged at revision: | 136 |
Proposed branch: | lp:~dobey/pay-ui/iap-support |
Merge into: | lp:pay-ui |
Prerequisite: | lp:~dobey/pay-ui/fix-page-returns |
Diff against target: |
330 lines (+88/-30) 5 files modified
app/payui.qml (+2/-2) backend/modules/payui/network.cpp (+52/-13) backend/modules/payui/network.h (+5/-4) backend/modules/payui/purchase.cpp (+20/-2) backend/tests/test_network.cpp (+9/-9) |
To merge this branch: | bzr merge lp:~dobey/pay-ui/iap-support |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
PS Jenkins bot (community) | continuous-integration | Approve | |
Charles Kerr (community) | Approve | ||
Review via email: mp+276310@code.launchpad.net |
This proposal supersedes a proposal from 2015-09-21.
Commit message
Support for performing in-app purchases.
Description of the change
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal | # |
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal | # |
PASSED: Continuous integration, rev:133
http://
Executed test runs:
UNSTABLE: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
UNSTABLE: http://
SUCCESS: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
Charles Kerr (charlesk) wrote : Posted in a previous version of this proposal | # |
No blockers, a couple of suggestions inline
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:134
http://
Executed test runs:
UNSTABLE: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
UNSTABLE: http://
SUCCESS: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
Charles Kerr (charlesk) : | # |
PS Jenkins bot (ps-jenkins) : | # |
Preview Diff
1 | === modified file 'app/payui.qml' |
2 | --- app/payui.qml 2015-10-30 19:32:58 +0000 |
3 | +++ app/payui.qml 2015-10-30 19:32:58 +0000 |
4 | @@ -89,7 +89,6 @@ |
5 | checkout.itemTitle = title; |
6 | checkout.itemSubtitle = publisher; |
7 | checkout.price = formatted_price; |
8 | - checkCredentials(); |
9 | } |
10 | |
11 | onPaymentTypesObtained: { |
12 | @@ -176,6 +175,7 @@ |
13 | } else if (mainView.state != "checkout" && !mainView.purchasing && mainView.state != "buy-interaction") { |
14 | purchase.getPaymentTypes(suggestedCurrency); |
15 | } |
16 | + purchase.getItemDetails(); |
17 | } |
18 | |
19 | onItemNotPurchased: { |
20 | @@ -357,7 +357,7 @@ |
21 | Component.onCompleted: { |
22 | showLoading(); |
23 | mainView.createDB(); |
24 | - purchase.getItemDetails(); |
25 | + purchase.checkCredentials(); |
26 | } |
27 | |
28 | onCurrentPageChanged: { |
29 | |
30 | === modified file 'backend/modules/payui/network.cpp' |
31 | --- backend/modules/payui/network.cpp 2015-02-06 21:57:58 +0000 |
32 | +++ backend/modules/payui/network.cpp 2015-10-30 19:32:58 +0000 |
33 | @@ -1,5 +1,5 @@ |
34 | /* |
35 | - * Copyright 2014 Canonical Ltd. |
36 | + * Copyright 2014-2015 Canonical Ltd. |
37 | * |
38 | * This library is free software; you can redistribute it and/or |
39 | * modify it under the terms of version 3 of the GNU Lesser General Public |
40 | @@ -38,6 +38,8 @@ |
41 | #define PAY_PAYMENTMETHODS_ADD_PATH PAY_PAYMENTMETHODS_PATH + "/add" |
42 | #define SUGGESTED_CURRENCY_HEADER_NAME "X-Suggested-Currency" |
43 | |
44 | +#define DEVICE_ID_HEADER "X-Device-Id" |
45 | + |
46 | #define PARTNER_ID_HEADER "X-Partner-ID" |
47 | #define PARTNER_ID_FILE "/custom/partner-id" |
48 | |
49 | @@ -208,9 +210,18 @@ |
50 | } else if (state->operation.contains(ITEM_INFO) && document.isObject()) { |
51 | qDebug() << "Reply state: ITEM_INFO"; |
52 | QJsonObject object = document.object(); |
53 | + QString icon; |
54 | + QString publisher; |
55 | + if (object.contains("publisher")) { |
56 | + publisher = object.value("publisher").toString(); |
57 | + } |
58 | + if (object.contains("icon_url")) { |
59 | + icon = object.value("icon_url").toString(); |
60 | + } else if (object.contains("icon")) { |
61 | + icon = object.value("icon").toString(); |
62 | + } |
63 | + |
64 | QString title = object.value("title").toString(); |
65 | - QString publisher = object.value("publisher").toString(); |
66 | - QString icon = object.value("icon_url").toString(); |
67 | |
68 | QJsonObject prices = object.value("prices").toObject(); |
69 | QString suggested_currency = DEFAULT_CURRENCY; |
70 | @@ -226,13 +237,25 @@ |
71 | if (isSupportedCurrency(suggested_currency) && prices.contains(suggested_currency)) { |
72 | currency = suggested_currency; |
73 | } |
74 | - double price = prices[currency].toDouble(); |
75 | + double price = 0.00; |
76 | + if (prices[currency].isDouble()) { |
77 | + price = prices[currency].toDouble(); |
78 | + } else if (prices[currency].isString()) { |
79 | + price = prices[currency].toString().toDouble(); |
80 | + } |
81 | QLocale locale; |
82 | QString formatted_price = locale.toCurrencyString(price, getSymbolForCurrency(currency)); |
83 | qDebug() << "Sending signal: itemDetailsObtained: " << title << " " << formatted_price; |
84 | Q_EMIT itemDetailsObtained(title, publisher, currency, formatted_price, icon); |
85 | } else if (state->operation.contains(CHECK_PURCHASED)) { |
86 | - Q_EMIT buyItemSucceeded(); |
87 | + QJsonObject object = document.object(); |
88 | + auto state = object.value("state").toString(); |
89 | + if (state == "Complete" || state == "purchased "|| |
90 | + state == "approved") { |
91 | + Q_EMIT buyItemSucceeded(); |
92 | + } else { |
93 | + Q_EMIT itemNotPurchased(); |
94 | + } |
95 | } else { |
96 | qDebug() << "Reply received for non valid state."; |
97 | QString message("Reply received for non valid state."); |
98 | @@ -314,8 +337,10 @@ |
99 | qDebug() << "Payment" << m_selectedAppId << m_selectedBackendId << m_selectedPaymentId; |
100 | QJsonObject serializer; |
101 | |
102 | - serializer.insert("device_id", getDeviceId()); |
103 | serializer.insert("name", m_selectedAppId); |
104 | + if (!m_selectedItemId.isEmpty()) { |
105 | + serializer.insert("item_sku", m_selectedItemId); |
106 | + } |
107 | serializer.insert("backend_id", m_selectedBackendId); |
108 | serializer.insert("method_id", m_selectedPaymentId); |
109 | serializer.insert("currency", m_currency); |
110 | @@ -325,6 +350,7 @@ |
111 | |
112 | QNetworkRequest request; |
113 | request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); |
114 | + request.setRawHeader(DEVICE_ID_HEADER, getDeviceId().toUtf8().data()); |
115 | |
116 | // Get the partner ID and add it to the request. |
117 | QByteArray partner_id = getPartnerId(); |
118 | @@ -368,17 +394,25 @@ |
119 | return reply.value(); |
120 | } |
121 | |
122 | -void Network::getItemInfo(const QString &packagename) |
123 | +void Network::getItemInfo(const QString& packagename, const QString& sku) |
124 | { |
125 | - QUrl url(getSearchApiUrl(QString(SEARCH_API_ROOT) + "/package/" + packagename)); |
126 | + QUrl url; |
127 | + |
128 | + if (sku.isEmpty()) { |
129 | + url = getSearchApiUrl(QString(SEARCH_API_ROOT) + "/package/" + packagename); |
130 | + qDebug() << "Request Item Info:" << url; |
131 | + QUrlQuery query; |
132 | + query.addQueryItem("fields", "title,description,price,icon_url"); |
133 | + url.setQuery(query); |
134 | + } else { |
135 | + url = getPayApiUrl(QString(IAP_API_ROOT) + "/packages/" + packagename + "/items/by-sku/" + sku); |
136 | + } |
137 | qDebug() << "Request Item Info:" << url; |
138 | - QUrlQuery query; |
139 | - query.addQueryItem("fields", "title,description,price,icon_url"); |
140 | - url.setQuery(query); |
141 | |
142 | QNetworkRequest request; |
143 | request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); |
144 | request.setUrl(url); |
145 | + signRequestUrl(request, url.toString(), QStringLiteral("GET")); |
146 | RequestObject* reqObject = new RequestObject(QString(ITEM_INFO)); |
147 | request.setOriginatingObject(reqObject); |
148 | m_nam.get(request); |
149 | @@ -427,9 +461,14 @@ |
150 | return m_token.updated(); |
151 | } |
152 | |
153 | -void Network::checkItemPurchased(const QString& appid) |
154 | +void Network::checkItemPurchased(const QString& appid, const QString& sku) |
155 | { |
156 | - QUrl url(getPayApiUrl(QString(PAY_API_ROOT) + PAY_PURCHASES_PATH + "/" + appid + "/")); |
157 | + QUrl url; |
158 | + if (sku.isEmpty()) { |
159 | + url = getPayApiUrl(QString(PAY_API_ROOT) + PAY_PURCHASES_PATH + "/" + appid + "/"); |
160 | + } else { |
161 | + url = getPayApiUrl(QString(IAP_API_ROOT) + "/packages/" + appid + "/items/by-sku/" + sku); |
162 | + } |
163 | |
164 | qDebug() << "Checking for previous purchase:" << url; |
165 | QNetworkRequest request; |
166 | |
167 | === modified file 'backend/modules/payui/network.h' |
168 | --- backend/modules/payui/network.h 2015-02-06 19:16:42 +0000 |
169 | +++ backend/modules/payui/network.h 2015-10-30 19:32:58 +0000 |
170 | @@ -1,5 +1,5 @@ |
171 | /* |
172 | - * Copyright 2014 Canonical Ltd. |
173 | + * Copyright 2014-2015 Canonical Ltd. |
174 | * |
175 | * This library is free software; you can redistribute it and/or |
176 | * modify it under the terms of version 3 of the GNU Lesser General Public |
177 | @@ -37,8 +37,9 @@ |
178 | namespace UbuntuPurchase { |
179 | |
180 | constexpr static const char* PAY_BASE_URL_ENVVAR{"PAY_BASE_URL"}; |
181 | -constexpr static const char* PAY_BASE_URL{"https://software-center.ubuntu.com"}; |
182 | +constexpr static const char* PAY_BASE_URL{"https://myapps.developer.ubuntu.com"}; |
183 | constexpr static const char* PAY_API_ROOT{"/api/2.0/click"}; |
184 | +constexpr static const char* IAP_API_ROOT{"/inventory/api/v1"}; |
185 | constexpr static const char* CURRENCY_ENVVAR {"U1_SEARCH_CURRENCY"}; |
186 | constexpr static const char* SEARCH_BASE_URL_ENVVAR{"U1_SEARCH_BASE_URL"}; |
187 | constexpr static const char* SEARCH_BASE_URL{"https://search.apps.ubuntu.com"}; |
188 | @@ -72,14 +73,14 @@ |
189 | const QString& otp, |
190 | const QString& appid, const QString& itemid, const QString& currency, |
191 | bool recentLogin); |
192 | - void getItemInfo(const QString &packagename); |
193 | + void getItemInfo(const QString& packagename, const QString& sku); |
194 | void checkPassword(const QString& email, const QString& password, |
195 | const QString& otp, bool purchasing=false); |
196 | void getCredentials(); |
197 | void setCredentials(Token token); |
198 | QString getAddPaymentUrl(const QString& currency); |
199 | QDateTime getTokenUpdated(); |
200 | - void checkItemPurchased(const QString& appid); |
201 | + void checkItemPurchased(const QString& appid, const QString& sku); |
202 | static QString getSymbolForCurrency(const QString& currency_code); |
203 | static bool isSupportedCurrency(const QString& currency_code); |
204 | static QString sanitizeUrl(const QUrl& url); |
205 | |
206 | === modified file 'backend/modules/payui/purchase.cpp' |
207 | --- backend/modules/payui/purchase.cpp 2014-12-02 04:34:04 +0000 |
208 | +++ backend/modules/payui/purchase.cpp 2015-10-30 19:32:58 +0000 |
209 | @@ -1,3 +1,21 @@ |
210 | +/* |
211 | + * Copyright 2014-2015 Canonical Ltd. |
212 | + * |
213 | + * This library is free software; you can redistribute it and/or |
214 | + * modify it under the terms of version 3 of the GNU Lesser General Public |
215 | + * License as published by the Free Software Foundation. |
216 | + * |
217 | + * This program is distributed in the hope that it will be useful, |
218 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
219 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
220 | + * General Public License for more details. |
221 | + * |
222 | + * You should have received a copy of the GNU Lesser General Public |
223 | + * License along with this library; if not, write to the |
224 | + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
225 | + * Boston, MA 02110-1301, USA. |
226 | + */ |
227 | + |
228 | #include "purchase.h" |
229 | #include <QUrl> |
230 | #include <QUrlQuery> |
231 | @@ -93,7 +111,7 @@ |
232 | quitCancel(); |
233 | } |
234 | qDebug() << "Getting Item Details"; |
235 | - m_network.getItemInfo(m_appid); |
236 | + m_network.getItemInfo(m_appid, m_itemid); |
237 | } |
238 | |
239 | void Purchase::getPaymentTypes(const QString& currency) |
240 | @@ -123,7 +141,7 @@ |
241 | |
242 | void Purchase::checkItemPurchased() |
243 | { |
244 | - m_network.checkItemPurchased(m_appid); |
245 | + m_network.checkItemPurchased(m_appid, m_itemid); |
246 | } |
247 | |
248 | } |
249 | |
250 | === modified file 'backend/tests/test_network.cpp' |
251 | --- backend/tests/test_network.cpp 2015-02-06 21:57:58 +0000 |
252 | +++ backend/tests/test_network.cpp 2015-10-30 19:32:58 +0000 |
253 | @@ -1,5 +1,5 @@ |
254 | /* |
255 | - * Copyright 2014 Canonical Ltd. |
256 | + * Copyright 2014-2015 Canonical Ltd. |
257 | * |
258 | * This library is free software; you can redistribute it and/or |
259 | * modify it under the terms of version 3 of the GNU Lesser General Public |
260 | @@ -196,7 +196,7 @@ |
261 | setenv(SEARCH_BASE_URL_ENVVAR, "http://localhost:8000/iteminfo/", 1); |
262 | unsetenv(CURRENCY_ENVVAR); |
263 | QSignalSpy spy(&network, SIGNAL(itemDetailsObtained(QString,QString,QString,QString,QString))); |
264 | - network.getItemInfo("packagename"); |
265 | + network.getItemInfo("packagename", ""); |
266 | QTRY_COMPARE(spy.count(), 1); |
267 | QList<QVariant> arguments = spy.takeFirst(); |
268 | QCOMPARE(arguments.at(2).toString(), QStringLiteral("USD")); |
269 | @@ -208,7 +208,7 @@ |
270 | setenv(SEARCH_BASE_URL_ENVVAR, "http://localhost:8000/iteminfo/eurozone/", 1); |
271 | unsetenv(CURRENCY_ENVVAR); |
272 | QSignalSpy spy(&network, SIGNAL(itemDetailsObtained(QString,QString,QString,QString,QString))); |
273 | - network.getItemInfo("packagename"); |
274 | + network.getItemInfo("packagename", ""); |
275 | QTRY_COMPARE(spy.count(), 1); |
276 | QList<QVariant> arguments = spy.takeFirst(); |
277 | QCOMPARE(arguments.at(2).toString(), QStringLiteral("EUR")); |
278 | @@ -220,7 +220,7 @@ |
279 | setenv(SEARCH_BASE_URL_ENVVAR, "http://localhost:8000/iteminfo/dotar/", 1); |
280 | unsetenv(CURRENCY_ENVVAR); |
281 | QSignalSpy spy(&network, SIGNAL(itemDetailsObtained(QString,QString,QString,QString,QString))); |
282 | - network.getItemInfo("packagename"); |
283 | + network.getItemInfo("packagename", ""); |
284 | QTRY_COMPARE(spy.count(), 1); |
285 | QList<QVariant> arguments = spy.takeFirst(); |
286 | QCOMPARE(arguments.at(2).toString(), QStringLiteral("USD")); |
287 | @@ -232,7 +232,7 @@ |
288 | setenv(SEARCH_BASE_URL_ENVVAR, "http://localhost:8000/iteminfo/", 1); |
289 | setenv(CURRENCY_ENVVAR, "EUR", true); |
290 | QSignalSpy spy(&network, SIGNAL(itemDetailsObtained(QString,QString,QString,QString,QString))); |
291 | - network.getItemInfo("packagename"); |
292 | + network.getItemInfo("packagename", ""); |
293 | QTRY_COMPARE(spy.count(), 1); |
294 | QList<QVariant> arguments = spy.takeFirst(); |
295 | QCOMPARE(arguments.at(2).toString(), QStringLiteral("EUR")); |
296 | @@ -245,7 +245,7 @@ |
297 | setenv(SEARCH_BASE_URL_ENVVAR, "http://localhost:8000/iteminfo/dotar/", 1); |
298 | setenv(CURRENCY_ENVVAR, "EUR", true); |
299 | QSignalSpy spy(&network, SIGNAL(itemDetailsObtained(QString,QString,QString,QString,QString))); |
300 | - network.getItemInfo("packagename"); |
301 | + network.getItemInfo("packagename", ""); |
302 | QTRY_COMPARE(spy.count(), 1); |
303 | QList<QVariant> arguments = spy.takeFirst(); |
304 | QCOMPARE(arguments.at(2).toString(), QStringLiteral("EUR")); |
305 | @@ -258,7 +258,7 @@ |
306 | setenv(SEARCH_BASE_URL_ENVVAR, "http://localhost:8000/fail/iteminfo/", 1); |
307 | unsetenv(CURRENCY_ENVVAR); |
308 | QSignalSpy spy(&network, SIGNAL(error(QString))); |
309 | - network.getItemInfo("packagename"); |
310 | + network.getItemInfo("packagename", ""); |
311 | QTRY_COMPARE(spy.count(), 1); |
312 | } |
313 | |
314 | @@ -274,7 +274,7 @@ |
315 | { |
316 | setenv(PAY_BASE_URL_ENVVAR, "http://localhost:8000/", 1); |
317 | QSignalSpy spy(&network, SIGNAL(buyItemSucceeded())); |
318 | - network.checkItemPurchased("com.example.fakeapp"); |
319 | + network.checkItemPurchased("com.example.fakeapp", ""); |
320 | QTRY_COMPARE(spy.count(), 1); |
321 | } |
322 | |
323 | @@ -282,7 +282,7 @@ |
324 | { |
325 | setenv(PAY_BASE_URL_ENVVAR, "http://localhost:8000/notpurchased/", 1); |
326 | QSignalSpy spy(&network, SIGNAL(itemNotPurchased())); |
327 | - network.checkItemPurchased("com.example.fakeapp"); |
328 | + network.checkItemPurchased("com.example.fakeapp", ""); |
329 | QTRY_COMPARE(spy.count(), 1); |
330 | } |
331 |
PASSED: Continuous integration, rev:132 jenkins. qa.ubuntu. com/job/ pay-ui- ci/142/ jenkins. qa.ubuntu. com/job/ generic- click-autopilot -14.09- touch/17 jenkins. qa.ubuntu. com/job/ generic- click-builder- 14.09-armhf/ 35 jenkins. qa.ubuntu. com/job/ pay-ui- 14.09-armhf- ci/1 jenkins. qa.ubuntu. com/job/ pay-ui- 14.09-armhf- ci/1/artifact/ work/output/ *zip*/output. zip jenkins. qa.ubuntu. com/job/ generic- click-autopilot -runner- mako/925 jenkins. qa.ubuntu. com/job/ generic- click-builder- 14.09-armhf/ 36 s-jenkins. ubuntu- ci:8080/ job/touch- flash-device/ 23534
http://
Executed test runs:
UNSTABLE: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
UNSTABLE: http://
SUCCESS: http://
SUCCESS: http://
Click here to trigger a rebuild: s-jenkins. ubuntu- ci:8080/ job/pay- ui-ci/142/ rebuild
http://