Merge lp:~alecu/unity-lens-music/musicstore-purchase into lp:~unity-team/unity-lens-music/libunity7-compatible

Proposed by Alejandro J. Cura
Status: Merged
Approved by: Didier Roche-Tolomelli
Approved revision: 146
Merged at revision: 141
Proposed branch: lp:~alecu/unity-lens-music/musicstore-purchase
Merge into: lp:~unity-team/unity-lens-music/libunity7-compatible
Diff against target: 1017 lines (+402/-188)
9 files modified
configure.ac (+1/-0)
debian/changelog (+7/-2)
debian/control (+1/-0)
src/Makefile.am (+2/-1)
src/album.vala (+1/-0)
src/musicstore-collection.vala (+12/-0)
src/musicstore-scope.vala (+218/-4)
src/ubuntuone-webservices.vala (+94/-65)
tests/unit/test-ubuntuone-purchases.vala (+66/-116)
To merge this branch: bzr merge lp:~alecu/unity-lens-music/musicstore-purchase
Reviewer Review Type Date Requested Status
Didier Roche-Tolomelli Approve
PS Jenkins bot (community) continuous-integration Approve
Michal Hruby (community) Approve
Review via email: mp+154483@code.launchpad.net

Commit message

- Use payment preview for music purchases (LP: #1154176).

Description of the change

Use the payment preview for music purchases in the musicstore scope.

To post a comment you must log in.
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
143. By Alejandro J. Cura

Remove FIXME comment

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
144. By Alejandro J. Cura

Apply missing fixes

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Michal Hruby (mhr3) wrote :

329 + var cover_pixbuf = new Pixbuf.from_stream (cover_file.read ());

Doing a blocking read on an http connection before actually returning a preview is a very bad idea. Please move this elsewhere.

145. By Alejandro J. Cura

Don't set notification icon before it's needed

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Michal Hruby (mhr3) wrote :

Better, thanks.

review: Approve
146. By Alejandro J. Cura

fixed pay url

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Didier Roche-Tolomelli (didrocks) wrote :

Online services QA gave its +1, let's get it merged

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'configure.ac'
2--- configure.ac 2013-03-14 18:45:38 +0000
3+++ configure.ac 2013-03-22 04:32:21 +0000
4@@ -75,6 +75,7 @@
5 sqlite3 >= 3.7.7
6 gee-1.0
7 json-glib-1.0
8+ libnotify
9 libsoup-2.4
10 oauth
11 unity >= 6.90.0
12
13=== modified file 'debian/changelog'
14--- debian/changelog 2013-03-15 13:22:25 +0000
15+++ debian/changelog 2013-03-22 04:32:21 +0000
16@@ -1,11 +1,16 @@
17-unity-lens-music (6.8.1daily13.03.04-0ubuntu2) UNRELEASED; urgency=low
18+unity-lens-music (6.8.1daily13.03.04-0ubuntu3) UNRELEASED; urgency=low
19
20+ [ Didier Roche ]
21 * debian/control:
22 - bump build-deps to new libunity
23 * debian/*install:
24 - installing/renaming files
25
26- -- Didier Roche <didrocks@ubuntu.com> Fri, 15 Mar 2013 14:21:36 +0100
27+ [ Alejandro J. Cura ]
28+ * debian/control:
29+ - added libnotify-dev build dependency
30+
31+ -- Alejandro J. Cura <alecu@canonical.com> Tue, 19 Mar 2013 18:45:38 -0300
32
33 unity-lens-music (6.8.1daily13.03.04-0ubuntu1) raring; urgency=low
34
35
36=== modified file 'debian/control'
37--- debian/control 2013-03-15 13:11:43 +0000
38+++ debian/control 2013-03-22 04:32:21 +0000
39@@ -11,6 +11,7 @@
40 libglib2.0-dev (>= 2.32),
41 libgee-dev,
42 libjson-glib-dev,
43+ libnotify-dev,
44 libsoup2.4-dev,
45 liboauth-dev,
46 libdee-dev (>= 1.0.7),
47
48=== modified file 'src/Makefile.am'
49--- src/Makefile.am 2013-03-13 12:14:27 +0000
50+++ src/Makefile.am 2013-03-22 04:32:21 +0000
51@@ -72,8 +72,9 @@
52
53 unity_musicstore_daemon_VALAFLAGS = \
54 --pkg json-glib-1.0 \
55+ --pkg libnotify \
56+ --pkg libsoup-2.4 \
57 --pkg oauth \
58- --pkg libsoup-2.4 \
59 $(unity_music_daemon_VALAFLAGS)
60
61 unity_musicstore_daemon_LDADD = \
62
63=== modified file 'src/album.vala'
64--- src/album.vala 2012-09-11 12:50:34 +0000
65+++ src/album.vala 2013-03-22 04:32:21 +0000
66@@ -28,5 +28,6 @@
67 public string uri { get; set; }
68 public string artwork_path { get; set; }
69 public string formatted_price { get; set; }
70+ public string purchase_sku { get; set; }
71 }
72 }
73\ No newline at end of file
74
75=== modified file 'src/musicstore-collection.vala'
76--- src/musicstore-collection.vala 2013-03-13 12:14:27 +0000
77+++ src/musicstore-collection.vala 2013-03-22 04:32:21 +0000
78@@ -104,6 +104,18 @@
79 track.duration = (int)root_obj.get_member ("duration").get_int ();
80 tracks.append (track);
81 }
82+
83+ if (root_obj.has_member ("id"))
84+ {
85+ album.purchase_sku = root_obj.get_string_member ("id");
86+ }
87+ else
88+ {
89+ // U1MS does not allow purchases of an album with no purchase_sku
90+ album.purchase_sku = "";
91+ warning ("Json has no purchase_sku in '%s'", details_uri);
92+ }
93+
94 }
95 else
96 {
97
98=== modified file 'src/musicstore-scope.vala'
99--- src/musicstore-scope.vala 2013-03-13 12:14:27 +0000
100+++ src/musicstore-scope.vala 2013-03-22 04:32:21 +0000
101@@ -18,14 +18,27 @@
102 */
103
104 using GLib;
105+using Notify;
106+using Unity;
107+using Gdk;
108+using Ubuntuone.Webservice;
109
110 namespace Unity.MusicLens {
111+ private const string FORGOTTEN_PASSWORD_URL = "https://login.ubuntu.com/+forgot_password";
112+ private const string CHANGE_PAYMENT_METHOD_URL = "https://pay.ubuntu.com/account/";
113+ private const string ERROR_MESSAGE_NOT_LOGGED_IN = _("It seems you don't have an Ubuntu One account, or you are not logged in. To continue, please login and visit the Ubuntu One online store.");
114+ private const string ERROR_MESSAGE_NO_PAYMENT_METHOD = _("It seems you haven't set yet your preferred Ubuntu One payment method. To add a payment method, please visit the Ubuntu One online store.");
115+ private const string ERROR_MESSAGE_TECHNICAL_PROBLEM = _("Sorry, we have encountered a technical problem. No money has been taken from your account. To try your purchase again, please visit the Ubuntu One online store.");
116+ private const string U1_SSO_UI = "u1_login_register";
117
118 public class MusicStoreScopeProxy : SimpleScope
119 {
120 private MusicStoreCollection collection;
121 private Unity.MusicPreview? music_preview;
122 private PreferencesManager preferences = PreferencesManager.get_default ();
123+ private HashTable<string, Album> album_map;
124+ private PurchaseService purchase_service;
125+ private Notification notification;
126
127 public MusicStoreScopeProxy ()
128 {
129@@ -41,7 +54,11 @@
130
131 base.initialize ();
132
133+ Notify.init ("Music Store Scope");
134+ notification = new Notification("Album name", _("Purchase started"), "");
135 collection = new MusicStoreCollection ();
136+ album_map = new HashTable<string, Album>(str_hash, str_equal);
137+ purchase_service = new PurchaseService ();
138
139 preferences.notify["remote-content-search"].connect((obj, pspec) => { scope.queue_search_changed(SearchType.DEFAULT); });
140
141@@ -66,11 +83,11 @@
142 else
143 {
144 warning ("Failed to generate preview for %s", uri);
145- return download_album (uri);
146+ return open_uri (uri);
147 }
148 }
149
150- public Unity.ActivationResponse download_album (string uri)
151+ public Unity.ActivationResponse open_uri (string uri)
152 {
153 /* launch the music store streaming client or the webstore or whatevz */
154 try {
155@@ -90,6 +107,7 @@
156
157 if (album != null)
158 {
159+ album_map.insert (uri, album);
160 File cover_file = File.new_for_uri (album.artwork_path); //artwork path is a remote uri
161 var cover = new FileIcon (cover_file);
162
163@@ -110,10 +128,10 @@
164 }
165
166 GLib.Icon? icon = new GLib.FileIcon (File.new_for_path (Config.DATADIR + "/icons/unity-icon-theme/places/svg/service-u1.svg"));
167- var download_action = new Unity.PreviewAction ("download_album", _("Download"), icon);
168+ var download_action = new Unity.PreviewAction ("show_purchase_preview", _("Download"), icon);
169 if (album.formatted_price != null)
170 download_action.extra_text = album.formatted_price;
171- download_action.activated.connect (download_album);
172+ download_action.activated.connect (show_purchase_preview);
173 music_preview.add_action (download_action);
174 }
175 return music_preview;
176@@ -146,6 +164,202 @@
177 } catch (IOError e) {
178 warning ("Failed to search for '%s': %s", search.search_string, e.message);
179 }
180+
181+ try {
182+ yield purchase_service.fetch_account_info ();
183+ debug ("retrieved account info: %s %s", purchase_service.nickname, purchase_service.email);
184+ } catch (PurchaseError e) {
185+ debug ("can't get account info: %s", e.message);
186+ }
187+ }
188+
189+ delegate Unity.ActivationResponse LinkHandler (string uri);
190+
191+ private Unity.ActivationResponse build_error_preview (string uri, string error_header, string link_text, LinkHandler link_handler)
192+ {
193+ Album album = null;
194+ SList<Track> tracks = null;
195+ collection.get_album_details (uri, out album, out tracks);
196+ debug ("album art uri: %s", album.artwork_path);
197+ File cover_file = File.new_for_uri (album.artwork_path); //artwork path is a remote uri
198+ var cover = new FileIcon (cover_file);
199+ var error_preview = new Unity.PaymentPreview.for_error(album.title, album.artist, cover);
200+ error_preview.header = error_header;
201+ var error_action = new Unity.PreviewAction ("open_u1_link", link_text, null);
202+ error_action.activated.connect (() => link_handler (uri));
203+ error_preview.add_action (error_action);
204+ return new Unity.ActivationResponse.with_preview (error_preview);
205+ }
206+
207+ public Unity.ActivationResponse open_forgot_password_url (string uri)
208+ {
209+ return open_uri (FORGOTTEN_PASSWORD_URL);
210+ }
211+
212+ public Unity.ActivationResponse change_payment_method (string uri)
213+ {
214+ return open_uri (CHANGE_PAYMENT_METHOD_URL);
215+ }
216+
217+ public Unity.ActivationResponse cancel_purchase (string uri)
218+ {
219+ return new Unity.ActivationResponse.with_preview (preview (uri));
220+ }
221+
222+ public Unity.ActivationResponse open_sso_login (string uri)
223+ {
224+ Album album = null;
225+ SList<Track> tracks = null;
226+ collection.get_album_details (uri, out album, out tracks);
227+
228+ var ui_path = Environment.find_program_in_path (U1_SSO_UI);
229+ GLib.Pid child_pid;
230+ string argv[11] = {
231+ ui_path,
232+ "--album", album.title,
233+ "--artist", album.artist,
234+ "--price", album.formatted_price,
235+ "--picture", album.artwork_path,
236+ "--url", uri
237+ };
238+ if (ui_path != null) {
239+ try {
240+ bool was_started = Process.spawn_async (null, argv, null, 0, null, out child_pid);
241+ // hide dash
242+ if (was_started) {
243+ return new Unity.ActivationResponse (Unity.HandledType.HIDE_DASH);
244+ }
245+ } catch (GLib.SpawnError e) {
246+ debug ("Failed to start u1_sso_ui for uri %s", uri);
247+ }
248+ }
249+ return open_uri (uri);
250+ }
251+
252+ public Unity.ActivationResponse show_purchase_preview (string uri)
253+ {
254+ if (purchase_service.got_credentials () == false)
255+ {
256+ debug ("no credentials available, opening sso login. %s", uri);
257+ return build_error_preview (uri, ERROR_MESSAGE_NOT_LOGGED_IN, _("Continue"), open_sso_login);
258+ }
259+
260+ Album album = null;
261+ SList<Track> tracks = null;
262+ collection.get_album_details (uri, out album, out tracks);
263+ if (album != null)
264+ {
265+ try {
266+ purchase_service.fetch_payment_info (album.purchase_sku);
267+ debug ("retrieved payment method: %s", purchase_service.selected_payment_method);
268+ return new Unity.ActivationResponse.with_preview (purchase_preview (uri, null));
269+ } catch (PurchaseError e) {
270+ debug ("can't get default payment method: %s", e.message);
271+ return build_error_preview (uri, ERROR_MESSAGE_NO_PAYMENT_METHOD, _("Choose Payment Method"), change_payment_method);
272+ }
273+ }
274+ return open_uri (uri);
275+ }
276+
277+ public Unity.ActivationResponse purchase_album (Unity.PreviewAction action, string uri)
278+ {
279+ var password = action.hints["password"].get_string();
280+
281+ if (password == null) {
282+ var preview = purchase_preview (uri, _("Please enter your password"));
283+ debug ("empty password.");
284+ return new Unity.ActivationResponse.with_preview (preview);
285+ }
286+
287+ var album = album_map.get (uri);
288+
289+ File cover_file = File.new_for_uri (album.artwork_path); //artwork path is a remote uri
290+ try {
291+ var cover_pixbuf = new Pixbuf.from_stream (cover_file.read ());
292+ notification.set_icon_from_pixbuf (cover_pixbuf);
293+ } catch (GLib.Error e) {
294+ debug ("Cannot set notification icon from uri %s", uri);
295+ }
296+
297+ notification.summary = album.title;
298+ notification.body = _("Authorizing purchase");
299+ try {
300+ notification.show ();
301+ } catch (GLib.Error e) {
302+ debug ("Error while showing notification: %s", e.message);
303+ }
304+
305+ try {
306+ purchase_service.purchase (album.purchase_sku, password);
307+ debug ("purchase completed.");
308+ notification.update (album.title, _("Purchase completed"), "");
309+ try {
310+ notification.show ();
311+ } catch (GLib.Error e) {
312+ debug ("Error while showing notification: %s", e.message);
313+ }
314+ return new Unity.ActivationResponse (Unity.HandledType.HIDE_DASH);
315+ } catch (PurchaseError e) {
316+ if (e is PurchaseError.WRONG_PASSWORD_ERROR) {
317+ debug ("wrong password error: %s", e.message);
318+ return new Unity.ActivationResponse.with_preview (purchase_preview (uri, _("Wrong password")));
319+ } else {
320+ debug ("got purchase error: %s", e.message);
321+ return build_error_preview (uri, ERROR_MESSAGE_TECHNICAL_PROBLEM, _("Continue"), open_uri);
322+ }
323+ }
324+ }
325+
326+ public Unity.Preview purchase_preview (string uri, string? error_message)
327+ {
328+ Unity.PaymentPreview album_purchase_preview = null;
329+ Album album = null;
330+ SList<Track> tracks = null;
331+ collection.get_album_details (uri, out album, out tracks);
332+
333+ if (album != null)
334+ {
335+ File cover_file = File.new_for_uri (album.artwork_path); //artwork path is a remote uri
336+
337+ var cover = new FileIcon (cover_file);
338+ album_purchase_preview = new Unity.PaymentPreview.for_music(album.title, album.artist, cover);
339+
340+ album_purchase_preview.header = _("Hi %s, you purchased in the past from Ubuntu One,"
341+ + " would you like to use the same payment details? Please review your order.").printf (purchase_service.nickname);
342+ album_purchase_preview.email = purchase_service.email;
343+ album_purchase_preview.payment_method = purchase_service.selected_payment_method;
344+ album_purchase_preview.purchase_prize = album.formatted_price;
345+ album_purchase_preview.purchase_type = _("Digital CD");
346+
347+ // data
348+
349+ var data = new HashTable<string, Variant>(str_hash, str_equal);
350+ if (error_message != null) {
351+ data["error_message"] = error_message;
352+ }
353+
354+ InfoHint info_hint = new InfoHint.with_variant("album_purchase_preview", "", null, data);
355+ album_purchase_preview.add_info(info_hint);
356+
357+ // actions
358+
359+ var purchase_action = new Unity.PreviewAction ("purchase_album", _("Purchase"), null);
360+ purchase_action.activated.connect (purchase_album);
361+ album_purchase_preview.add_action (purchase_action);
362+
363+ var forgot_password_action = new Unity.PreviewAction ("forgot_password", _("forgotten your Ubuntu One password?"), null);
364+ forgot_password_action.activated.connect (open_forgot_password_url);
365+ album_purchase_preview.add_action (forgot_password_action);
366+
367+ var cancel_action = new Unity.PreviewAction ("cancel_purchase", _("Cancel"), null);
368+ cancel_action.activated.connect (cancel_purchase);
369+ album_purchase_preview.add_action (cancel_action);
370+
371+ var change_payment_method_action = new Unity.PreviewAction ("change_payment_method", _("change payment method"), null);
372+ change_payment_method_action.activated.connect (change_payment_method);
373+ album_purchase_preview.add_action (change_payment_method_action);
374+ }
375+ return album_purchase_preview;
376 }
377
378 }
379
380=== modified file 'src/ubuntuone-webservices.vala'
381--- src/ubuntuone-webservices.vala 2012-12-06 15:39:38 +0000
382+++ src/ubuntuone-webservices.vala 2013-03-22 04:32:21 +0000
383@@ -28,19 +28,20 @@
384 public abstract void find_credentials () throws IOError;
385 }
386
387-const string WEBAPI_URI = "https://edge.one.ubuntu.com/";
388-const string ACCOUNT_URI = WEBAPI_URI + "api/account";
389-const string PAYMENT_METHOD_URI = WEBAPI_URI + "music-store/api/1/user/retrieve-payment-method?purchase_sku=%s";
390-const string PURCHASE_WITH_DEFAULT_PAYMENT_URI = WEBAPI_URI + "music-store/api/1/user/purchase-with-default-payment?purchase_sku=%s&authentication=%s";
391-const string AUTHENTICATION_URI = "https://login.staging.ubuntu.com/api/1.1/authentications";
392+const string WEBAPI_SERVER = "https://one.ubuntu.com/";
393+const string ACCOUNT_PATH = "api/account";
394+const string PAYMENT_METHOD_PATH = "music-store-up/api/1/user/retrieve-payment-method?purchase_sku=%s";
395+const string PURCHASE_WITH_DEFAULT_PAYMENT_PATH = "music-store-up/api/1/user/purchase-with-default-payment?purchase_sku=%s&authentication=%s";
396+const string AUTHENTICATION_SERVER = "https://login.ubuntu.com/";
397+const string AUTHENTICATION_PATH = "api/1.1/authentications";
398 const string AUTHENTICATE_PARAMS = "ws.op=authenticate&token_name=Purchase_Token";
399-const int PASSWORD_CACHE_SECONDS = 300;
400-const int PAYMENT_METHOD_CACHE_SECONDS = 120;
401+
402
403 namespace Ubuntuone.Webservice
404 {
405 public errordomain PurchaseError
406 {
407+ MISSING_CREDENTIALS_ERROR,
408 PURCHASE_ERROR,
409 WRONG_PASSWORD_ERROR,
410 UNSPECIFIED_ERROR
411@@ -56,17 +57,13 @@
412 public string selected_payment_method { get; internal set; default = null; }
413 public string consumer_key { get; private set; default = null; }
414 public string token { get; private set; default = null; }
415- internal DateTime _selected_payment_method_expiry;
416- DateTime _password_expiry;
417+ public string open_url { get; private set; default = null; }
418 internal HashTable <string, string> _ubuntuone_credentials = null;
419
420 construct {
421 http_session = build_http_session ();
422 http_session_sso = build_http_session ();
423
424- reset_payment_method_cache (0);
425- reset_password_cache (0);
426-
427 credentials_management = build_credentials_management ();
428 }
429
430@@ -77,24 +74,40 @@
431 return session;
432 }
433
434- internal DateTime now ()
435- {
436- return new DateTime.now_utc ();
437- }
438-
439- internal bool expired_payment_method_cache ()
440- {
441- return _selected_payment_method_expiry.compare (now ()) <= 0;
442- }
443-
444- internal void reset_payment_method_cache (int seconds_from_now)
445- {
446- _selected_payment_method_expiry = now ().add_seconds (seconds_from_now);
447- }
448-
449- internal void reset_password_cache (int seconds_from_now)
450- {
451- _password_expiry = now ().add_seconds (seconds_from_now);
452+ string webapi_server ()
453+ {
454+ string staging_webapi = Environment.get_variable ("U1_STAGING_WEBAPI");
455+ return staging_webapi != null ? staging_webapi : WEBAPI_SERVER;
456+ }
457+
458+ string account_uri ()
459+ {
460+ return webapi_server() + ACCOUNT_PATH;
461+ }
462+
463+ string payment_method_uri ()
464+ {
465+ return webapi_server() + PAYMENT_METHOD_PATH;
466+ }
467+
468+ string purchase_with_default_payment_uri ()
469+ {
470+ return webapi_server() + PURCHASE_WITH_DEFAULT_PAYMENT_PATH;
471+ }
472+
473+ string authentication_server ()
474+ {
475+ string staging_authentication = Environment.get_variable ("U1_STAGING_AUTHENTICATION");
476+ return staging_authentication != null ? staging_authentication : AUTHENTICATION_SERVER;
477+ }
478+
479+ string authentication_uri ()
480+ {
481+ return authentication_server() + AUTHENTICATION_PATH;
482+ }
483+
484+ public bool got_credentials () {
485+ return _ubuntuone_credentials != null;
486 }
487
488 internal virtual CredentialsManagement build_credentials_management ()
489@@ -125,25 +138,33 @@
490 email = root_object.get_string_member("email");
491 }
492
493- internal void parse_payment_method_json (string json_string) throws GLib.Error
494+ internal void parse_payment_method_json (string json_string) throws GLib.Error, PurchaseError
495 {
496 var root_object = parse_json (json_string);
497- Json.Object payload = root_object.get_object_member("payload");
498- selected_payment_method = payload.get_string_member("selected_payment_method");
499+ if (root_object.has_member ("selected_payment_method")) {
500+ selected_payment_method = root_object.get_string_member("selected_payment_method");
501+ } else {
502+ open_url = root_object.get_string_member ("open_url");
503+ var error_message = root_object.get_string_member ("error_message");
504+ throw new PurchaseError.PURCHASE_ERROR (error_message);
505+ }
506 }
507
508 internal void parse_authentication_json (string json_string) throws GLib.Error
509 {
510 var root_object = parse_json (json_string);
511- consumer_key = root_object.get_string_member("consumer_key");
512- token = root_object.get_string_member("token");
513+ consumer_key = root_object.get_string_member ("consumer_key");
514+ token = root_object.get_string_member ("token");
515 }
516
517 internal string parse_purchase_json (string json_string) throws GLib.Error
518 {
519 var root_object = parse_json (json_string);
520- Json.Object payload = root_object.get_object_member("payload");
521- return payload.get_string_member("open_url");
522+ if (root_object.has_member ("open_url")) {
523+ return root_object.get_string_member("open_url");
524+ } else {
525+ return "";
526+ }
527 }
528
529 internal virtual async void fetch_credentials () throws PurchaseError
530@@ -156,7 +177,7 @@
531 fetch_credentials.callback ();
532 });
533 ulong error_handler = credentials_management.credentials_error.connect (() => {
534- error = new PurchaseError.PURCHASE_ERROR ("Can't get Ubuntu One tokens.");
535+ error = new PurchaseError.MISSING_CREDENTIALS_ERROR ("Can't get Ubuntu One tokens.");
536 fetch_credentials.callback ();
537 });
538
539@@ -164,7 +185,7 @@
540 credentials_management.find_credentials ();
541 yield;
542 } catch (IOError e) {
543- error = new PurchaseError.PURCHASE_ERROR ("Can't get Ubuntu One tokens: %s", e.message);
544+ error = new PurchaseError.MISSING_CREDENTIALS_ERROR ("Can't get Ubuntu One tokens: %s", e.message);
545 }
546
547 credentials_management.disconnect (found_handler);
548@@ -208,7 +229,7 @@
549 internal virtual async void fetch_account () throws PurchaseError
550 {
551 string response;
552- PurchaseError error = yield call_api ("GET", ACCOUNT_URI, out response);
553+ PurchaseError error = yield call_api ("GET", account_uri(), out response);
554
555 if (error != null) {
556 debug ("Error while fetching U1 account: %s.", error.message);
557@@ -224,43 +245,39 @@
558 }
559 }
560
561- internal virtual async void fetch_payment_method (string purchase_sku) throws PurchaseError
562+ internal virtual void fetch_payment_method (string purchase_sku) throws PurchaseError
563 {
564- string response;
565- var uri = PAYMENT_METHOD_URI.printf (purchase_sku);
566- PurchaseError error = yield call_api ("GET", uri, out response);
567+ var uri = payment_method_uri().printf (purchase_sku);
568
569- if (error != null) {
570- debug ("Error while fetching payment method: %s.", error.message);
571- throw error;
572+ var message = send_signed_webservice_call ("GET", uri);
573+ if (message.status_code != Soup.KnownStatusCode.OK) {
574+ debug ("Purchase request failed: HTTP %u", message.status_code);
575+ debug ("Reason: %s", message.reason_phrase);
576+ try {
577+ message.response_body.flatten ();
578+ debug ("body: ------\n%s\n------\n", (string) message.response_body.data);
579+ } catch (Error e) {
580+ }
581+ throw new PurchaseError.PURCHASE_ERROR ("Retrieve payment method failed: %s".printf (message.reason_phrase));
582 }
583-
584 try {
585- parse_payment_method_json (response);
586- debug ("got payment method");
587+ message.response_body.flatten ();
588+ var result = (string) message.response_body.data;
589+ parse_payment_method_json (result);
590 } catch (GLib.Error e) {
591- debug ("Error while getting payment method: %s.", e.message);
592 throw new PurchaseError.PURCHASE_ERROR (e.message);
593 }
594 }
595
596- internal virtual async void refetch_payment_info (string purchase_sku) throws PurchaseError
597+ public virtual async void fetch_account_info () throws PurchaseError
598 {
599 yield fetch_credentials ();
600 yield fetch_account ();
601- yield fetch_payment_method (purchase_sku);
602- reset_payment_method_cache (PAYMENT_METHOD_CACHE_SECONDS);
603 }
604
605- public async void fetch_payment_info (string purchase_sku) throws PurchaseError
606+ public virtual void fetch_payment_info (string purchase_sku) throws PurchaseError
607 {
608- if (expired_payment_method_cache ())
609- {
610- debug ("refetching");
611- yield refetch_payment_info (purchase_sku);
612- } else {
613- debug ("in cache");
614- }
615+ fetch_payment_method (purchase_sku);
616 }
617
618 internal virtual void _do_sso_webcall (Soup.Message message, string password)
619@@ -286,6 +303,11 @@
620 if (message.status_code == Soup.KnownStatusCode.UNAUTHORIZED) {
621 throw new PurchaseError.WRONG_PASSWORD_ERROR ("Wrong password");
622 }
623+ try {
624+ message.response_body.flatten ();
625+ debug ("body: ------\n%s\n------\n", (string) message.response_body.data);
626+ } catch (Error e) {
627+ }
628 throw new PurchaseError.PURCHASE_ERROR (message.reason_phrase);
629 }
630 message.response_body.flatten ();
631@@ -294,7 +316,7 @@
632
633 internal virtual string get_purchase_token (string password) throws PurchaseError
634 {
635- var result = authenticated_sso_webcall ("POST", AUTHENTICATION_URI, AUTHENTICATE_PARAMS, password);
636+ var result = authenticated_sso_webcall ("POST", authentication_uri(), AUTHENTICATE_PARAMS, password);
637 try {
638 parse_authentication_json (result);
639 } catch (GLib.Error e) {
640@@ -311,14 +333,19 @@
641 return message;
642 }
643
644- internal virtual void purchase_with_default_payment (string sku, string purchase_token) throws PurchaseError
645+ internal virtual void purchase_with_default_payment (string album_id, string purchase_token) throws PurchaseError
646 {
647- var uri = PURCHASE_WITH_DEFAULT_PAYMENT_URI.printf (sku, purchase_token);
648+ var uri = purchase_with_default_payment_uri().printf (album_id, purchase_token);
649 var message = send_signed_webservice_call ("GET", uri);
650
651 if (message.status_code != Soup.KnownStatusCode.OK) {
652 debug ("Purchase request failed: HTTP %u", message.status_code);
653 debug ("Reason: %s", message.reason_phrase);
654+ try {
655+ message.response_body.flatten ();
656+ debug ("body: ------\n%s\n------\n", (string) message.response_body.data);
657+ } catch (Error e) {
658+ }
659 throw new PurchaseError.PURCHASE_ERROR ("Purchase failed: %s".printf (message.reason_phrase));
660 }
661 try {
662@@ -336,7 +363,9 @@
663 public void purchase (string album_id, string password) throws PurchaseError
664 {
665 var purchase_token = get_purchase_token (password);
666+ debug ("purchasing...");
667 purchase_with_default_payment (album_id, purchase_token);
668+ debug ("purchase completed.");
669 }
670 }
671 }
672
673=== modified file 'tests/unit/test-ubuntuone-purchases.vala'
674--- tests/unit/test-ubuntuone-purchases.vala 2012-12-06 15:39:38 +0000
675+++ tests/unit/test-ubuntuone-purchases.vala 2013-03-22 04:32:21 +0000
676@@ -25,7 +25,7 @@
677
678 const string FAKE_URL = "http://fake/url";
679 const string FAKE_PASSWORD = "PezEspada";
680-const string FAKE_SKU = "fake_store:fake_album:id";
681+const string FAKE_SKU = "7digital:fake_album:id";
682 const string FAKE_TOKEN = "a fake token";
683
684 const string BROKEN_JSON = """
685@@ -45,14 +45,17 @@
686
687 const string FAKE_JSON_PAYMENT_METHOD = """
688 {
689- "meta": {
690- "status": "Ok"
691- },
692- "payload": {
693- "open_url": "",
694- "user_email": "mr@be.an",
695- "selected_payment_method": "Visa 1234"
696- }
697+ "open_url": "",
698+ "user_email": "mr@be.an",
699+ "selected_payment_method": "Visa 1234"
700+ }
701+""";
702+
703+
704+const string FAKE_JSON_PAYMENT_METHOD_ERROR = """
705+ {
706+ "open_url": "http://somewhere/else",
707+ "error_message": "No default payment method selected."
708 }
709 """;
710
711@@ -69,30 +72,18 @@
712
713 const string FAKE_JSON_PURCHASE = """
714 {
715- "meta": {
716- "status": "Ok"
717- },
718- "payload": {
719- "order_id": "1111",
720- "order_status": "OK",
721- "order_detail": "TBD",
722- "open_url": ""
723- }
724+ "order_id": "1111",
725+ "order_status": "OK"
726 }
727 """;
728
729
730 const string FAKE_JSON_PURCHASE_FAILURE = """
731 {
732- "meta": {
733- "status": "Ok"
734- },
735- "payload": {
736- "order_id": "1111",
737- "order_status": "PAYMENT_FAILURE",
738- "order_detail": "TBD",
739- "open_url": "http://slashdot.org/"
740- }
741+ "order_id": "1111",
742+ "order_status": "PAYMENT_FAILURE",
743+ "order_detail": "TBD",
744+ "open_url": "http://slashdot.org/"
745 }
746 """;
747
748@@ -113,12 +104,11 @@
749 Test.add_data_func ("/Unit/PurchaseChecker/ParseAccount", test_parse_account);
750 Test.add_data_func ("/Unit/PurchaseChecker/ParseBrokenAccount", test_parse_broken_account);
751 Test.add_data_func ("/Unit/PurchaseChecker/ParsePaymentMethod", test_parse_payment_method);
752+ Test.add_data_func ("/Unit/PurchaseChecker/ParsePaymentMethodError", test_parse_payment_method_error);
753 Test.add_data_func ("/Unit/PurchaseChecker/ParseAuthentication", test_parse_authentication);
754 Test.add_data_func ("/Unit/PurchaseChecker/ReadyToPurchase", test_ready_to_purchase);
755- Test.add_data_func ("/Unit/PurchaseChecker/FetchPaymentInvalidated", test_fetch_payment_invalidated);
756- Test.add_data_func ("/Unit/PurchaseChecker/FetchPaymentCached", test_fetch_payment_cached);
757- Test.add_data_func ("/Unit/PurchaseChecker/ReFetchPayment", test_refetch_payment);
758- Test.add_data_func ("/Unit/PurchaseChecker/ReFetchPaymentSetsCache", test_refetch_payment_sets_cache);
759+ Test.add_data_func ("/Unit/PurchaseChecker/FetchAccountInfo", test_fetch_account_info);
760+ Test.add_data_func ("/Unit/PurchaseChecker/FetchPaymentInfo", test_fetch_payment_info);
761 Test.add_data_func ("/Unit/PurchaseChecker/FetchCredentials", test_fetch_credentials);
762 Test.add_data_func ("/Unit/PurchaseChecker/FetchCredentialsFails", test_fetch_credentials_fails);
763 Test.add_data_func ("/Unit/PurchaseChecker/FetchCredentialsFailsExtra", test_fetch_credentials_fails_extra);
764@@ -163,11 +153,12 @@
765 async void irl_test_async ()
766 {
767 var purchase_service = new PurchaseService();
768- var purchase_sku = "1";
769+ var purchase_sku = "7digital:album:1347423:WORLD";
770 try {
771- yield purchase_service.fetch_payment_info (purchase_sku);
772+ yield purchase_service.fetch_account_info ();
773+ purchase_service.fetch_payment_info (purchase_sku);
774 debug ("data was available: %s %s %s", purchase_service.nickname, purchase_service.email, purchase_service.selected_payment_method);
775- var real_user_password = "****fake_password_in_bzr***";
776+ var real_user_password = FAKE_PASSWORD;
777 purchase_service.purchase (purchase_sku, real_user_password);
778 debug ("purchase completed.");
779 } catch (PurchaseError e) {
780@@ -186,7 +177,7 @@
781 });
782 return false;
783 });
784- assert (run_with_timeout (loop, 10000));
785+ assert (run_with_timeout (loop, 60000));
786 }
787
788 class FakeCredentialsManagement : GLib.Object, CredentialsManagement
789@@ -290,6 +281,19 @@
790 assert_cmpstr (purchase_service.selected_payment_method, OperatorType.EQUAL, "Visa 1234");
791 }
792
793+ private static void test_parse_payment_method_error ()
794+ {
795+ var purchase_service = new BaseTestPurchaseService ();
796+ try
797+ {
798+ purchase_service.parse_payment_method_json (FAKE_JSON_PAYMENT_METHOD_ERROR);
799+ assert_not_reached ();
800+ } catch (PurchaseError e)
801+ {
802+ assert_cmpstr (purchase_service.open_url, OperatorType.EQUAL, "http://somewhere/else");
803+ }
804+ }
805+
806 private static void test_parse_authentication ()
807 {
808 var purchase_service = new BaseTestPurchaseService ();
809@@ -313,30 +317,6 @@
810 assert (purchase_service.ready_to_purchase == true);
811 }
812
813- class TestFetchPurchaseService : BaseTestPurchaseService {
814- internal bool fetch_called = false;
815- internal override async void refetch_payment_info (string purchase_sku)
816- {
817- fetch_called = true;
818- }
819- }
820-
821- private static void test_fetch_payment_invalidated ()
822- {
823- var purchase_service = new TestFetchPurchaseService ();
824- purchase_service._selected_payment_method_expiry = new DateTime.now_utc ();
825- purchase_service.fetch_payment_info ("fake_sku");
826- assert (purchase_service.fetch_called);
827- }
828-
829- private static void test_fetch_payment_cached ()
830- {
831- var purchase_service = new TestFetchPurchaseService ();
832- purchase_service._selected_payment_method_expiry = new DateTime.now_utc ().add_seconds (60);
833- purchase_service.fetch_payment_info ("fake_sku");
834- assert (purchase_service.fetch_called == false);
835- }
836-
837 class TestRefetchPurchaseService : BaseTestPurchaseService {
838 internal bool credentials_fetched = false;
839 internal bool account_fetched = false;
840@@ -360,26 +340,21 @@
841 });
842 yield;
843 }
844- internal override async void fetch_payment_method (string purchase_sku)
845+ internal override void fetch_payment_method (string purchase_sku)
846 {
847- Idle.add (() => {
848- payment_method_fetched = true;
849- fetch_payment_method.callback ();
850- return false;
851- });
852- yield;
853+ payment_method_fetched = true;
854 }
855 }
856
857- private static void test_refetch_payment ()
858+ private static void test_fetch_account_info ()
859 {
860 var purchase_service = new TestRefetchPurchaseService ();
861
862 MainLoop mainloop = new MainLoop ();
863- purchase_service.refetch_payment_info.begin("fake_sku", (obj, res) => {
864+ purchase_service.fetch_account_info.begin((obj, res) => {
865 mainloop.quit ();
866 try {
867- purchase_service.refetch_payment_info.end (res);
868+ purchase_service.fetch_account_info.end (res);
869 } catch (PurchaseError e) {
870 error ("Can't fetch payment info: %s", e.message);
871 }
872@@ -388,29 +363,19 @@
873
874 assert (purchase_service.credentials_fetched);
875 assert (purchase_service.account_fetched);
876+ }
877+
878+ private static void test_fetch_payment_info ()
879+ {
880+ var purchase_service = new TestRefetchPurchaseService ();
881+ try {
882+ purchase_service.fetch_payment_info("fake_sku");
883+ } catch (PurchaseError e) {
884+ error ("Can't fetch payment info: %s", e.message);
885+ }
886 assert (purchase_service.payment_method_fetched);
887 }
888
889- private static void test_refetch_payment_sets_cache ()
890- {
891- var purchase_service = new TestRefetchPurchaseService ();
892-
893- assert (true == purchase_service.expired_payment_method_cache ());
894-
895- MainLoop mainloop = new MainLoop ();
896- purchase_service.refetch_payment_info.begin("fake_sku", (obj, res) => {
897- mainloop.quit ();
898- try {
899- purchase_service.refetch_payment_info.end (res);
900- } catch (PurchaseError e) {
901- error ("Can't fetch payment info: %s", e.message);
902- }
903- });
904- assert (run_with_timeout (mainloop, 1000));
905-
906- assert (false == purchase_service.expired_payment_method_cache ());
907- }
908-
909 private static void test_fetch_credentials ()
910 {
911 var purchase_service = new BaseTestPurchaseService ();
912@@ -431,7 +396,7 @@
913
914 private static void test_fetch_credentials_fails ()
915 {
916- bool failed = false;
917+ PurchaseError failure = null;
918 var purchase_service = new FailingCredentialsPurchaseService ();
919
920 MainLoop mainloop = new MainLoop ();
921@@ -440,17 +405,16 @@
922 try {
923 purchase_service.fetch_credentials.end (res);
924 } catch (PurchaseError e) {
925- failed = true;
926+ failure = e;
927 }
928 });
929 assert (run_with_timeout (mainloop, 1000));
930-
931- assert (failed);
932+ assert (failure is PurchaseError.MISSING_CREDENTIALS_ERROR);
933 }
934
935 private static void test_fetch_credentials_fails_extra ()
936 {
937- bool failed = false;
938+ PurchaseError failure = null;
939 var purchase_service = new ExtraFailingCredentialsPurchaseService ();
940
941 MainLoop mainloop = new MainLoop ();
942@@ -459,12 +423,11 @@
943 try {
944 purchase_service.fetch_credentials.end (res);
945 } catch (PurchaseError e) {
946- failed = true;
947+ failure = e;
948 }
949 });
950 assert (run_with_timeout (mainloop, 1000));
951-
952- assert (failed);
953+ assert (failure is PurchaseError.MISSING_CREDENTIALS_ERROR);
954 }
955
956 class FakeWebcallPurchaseService : BaseTestPurchaseService
957@@ -565,21 +528,7 @@
958 {
959 var purchase_service = new FakeWebcallPurchaseService (_status_code, _found_json);
960 purchase_service._ubuntuone_credentials = new HashTable <string, string> (str_hash, str_equal);
961-
962- PurchaseError error = null;
963- MainLoop mainloop = new MainLoop ();
964- purchase_service.fetch_payment_method.begin("fake_sku", (obj, res) => {
965- mainloop.quit ();
966- try {
967- purchase_service.fetch_payment_method.end (res);
968- } catch (PurchaseError e) {
969- error = e;
970- }
971- });
972- assert (run_with_timeout (mainloop, 1000));
973- if (error != null) {
974- throw error;
975- }
976+ purchase_service.fetch_payment_method("fake_sku");
977 return purchase_service;
978 }
979
980@@ -616,7 +565,7 @@
981 class TestPurchaseService : BaseTestPurchaseService
982 {
983 internal string password;
984- internal string sku;
985+ internal string album_id;
986 internal string purchase_token;
987
988 internal override string get_purchase_token (string password)
989@@ -624,9 +573,9 @@
990 this.password = password;
991 return FAKE_TOKEN;
992 }
993- internal override void purchase_with_default_payment (string sku, string purchase_token)
994+ internal override void purchase_with_default_payment (string album_id, string purchase_token)
995 {
996- this.sku = sku;
997+ this.album_id = album_id;
998 this.purchase_token = purchase_token;
999 }
1000 }
1001@@ -637,7 +586,7 @@
1002 var purchase_service = new TestPurchaseService ();
1003 purchase_service.purchase (FAKE_SKU, FAKE_PASSWORD);
1004 assert_cmpstr (purchase_service.password, OperatorType.EQUAL, FAKE_PASSWORD);
1005- assert_cmpstr (purchase_service.sku, OperatorType.EQUAL, FAKE_SKU);
1006+ assert_cmpstr (purchase_service.album_id, OperatorType.EQUAL, FAKE_SKU);
1007 assert_cmpstr (purchase_service.purchase_token, OperatorType.EQUAL, FAKE_TOKEN);
1008 } catch (Error e) {
1009 assert_not_reached ();
1010@@ -722,6 +671,7 @@
1011 try {
1012 purchase_service.purchase_with_default_payment (FAKE_SKU, FAKE_TOKEN);
1013 } catch (PurchaseError e) {
1014+ warning (e.message);
1015 assert_not_reached ();
1016 }
1017 }

Subscribers

People subscribed via source and target branches

to all changes: